From 2d832bc8e6c9157be317f1c8455bbf94080643a6 Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Fri, 12 May 2023 18:16:29 +0400 Subject: [PATCH 01/30] Add Extra Rewards Feature by Receiver --- contracts/staking/ClusterRewards.sol | 46 +- package-lock.json | 1 + test/staking/ClusterRegistry.ts | 4 +- test/staking/ClusterRewards.ts | 906 +++++++++++++++------------ test/token/MPond.ts | 4 +- test/token/Pond.ts | 4 +- 6 files changed, 558 insertions(+), 407 deletions(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 04ef6c47..4d3e6d0e 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -29,7 +29,8 @@ contract ClusterRewards is /// @custom:oz-upgrades-unsafe-allow constructor // initializes the logic contract without any admins // safeguard against takeover of the logic contract - constructor() initializer {} + constructor() initializer { + } modifier onlyAdmin() { require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "only admin"); @@ -255,6 +256,13 @@ contract ClusterRewards is unchecked { require(_selectedClusters.length <= _tickets.length + 1, "CRW:IPRT-Tickets length not matching selected clusters"); uint256 _rewardShare = _totalNetworkRewardsPerEpoch * _epochReceiverStake / _epochTotalStake; + uint256 _receiverRewardPerEpoch = receiverRewardPerEpoch[_receiver]; + + if(receiverBalance[_receiver] > _receiverRewardPerEpoch){ + _rewardShare += _receiverRewardPerEpoch; + receiverBalance[_receiver] -= _receiverRewardPerEpoch; + } + uint256 _totalTickets; uint256 i; for(; i < _selectedClusters.length - 1; ++i) { @@ -432,4 +440,40 @@ contract ClusterRewards is } //-------------------------------- User functions end --------------------------------// + mapping(address => uint256) public receiverBalance; + mapping(address => uint256) public receiverRewardPerEpoch; + + event AddReceiverBalance(address indexed receiver, uint256 amount); + event RemoveReceiverBalance(address indexed receiver, uint256 amount); + event UpdateReceiverRewardPerEpoch(address indexed receiver, uint256 amount); + + // @notice Anyone can add balance to receiver/staker address + function addReceiverBalance(address receiver, uint256 amount) public { + require(receiver != address(0), "CRW: address 0"); + IERC20Upgradeable PONDToken = receiverStaking.STAKING_TOKEN(); // Todo: shift this to constructor + receiverBalance[receiver]+=amount; + PONDToken.transferFrom(msg.sender, address(this), amount); // Todo: transfer this directly to reward delegators, find a way when removeReceiveBalance is called + emit AddReceiverBalance(receiver, amount); + } + + // @notice Receiver/Staker can remove the unused balance using his signer address + // @dev This may be a attach vector, and hence could be removed + function removeReceiverBalance(address to, uint256 amount) public { + address staker = receiverStaking.signerToStaker(msg.sender); + require(staker != address(0), "CRW: address 0"); + IERC20Upgradeable PONDToken = receiverStaking.STAKING_TOKEN(); // Todo: shift this to constructor + receiverBalance[staker] -= amount; + PONDToken.transfer(to, amount); + + emit RemoveReceiverBalance(staker, amount); + } + + // @notice Set Receiver Epoch, set to 0, to disable extra rewards + function setReceiverRewardPerEpoch(uint256 rewardPerEpoch) public { + address staker = receiverStaking.signerToStaker(msg.sender); + require(staker != address(0), "CRW: address 0"); + + receiverRewardPerEpoch[staker] = rewardPerEpoch; + emit UpdateReceiverRewardPerEpoch(staker, rewardPerEpoch); + } } diff --git a/package-lock.json b/package-lock.json index 6ce2e7ed..50b71919 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2273,6 +2273,7 @@ "integrity": "sha512-bdM5cEGCOhDSwminryHJbRmXc1x7dPKg6Pqns3qyTwFlxsqUgxE29lsERS3PlIW1HTjoIGMUqsk1zQQwST1Yxw==", "dev": true, "hasInstallScript": true, + "optional": true, "dependencies": { "node-gyp-build": "4.3.0" }, diff --git a/test/staking/ClusterRegistry.ts b/test/staking/ClusterRegistry.ts index 0e1e9ebf..98a43679 100644 --- a/test/staking/ClusterRegistry.ts +++ b/test/staking/ClusterRegistry.ts @@ -302,9 +302,11 @@ describe("ClusterRegistry", function () { expect(clusterData.isValidCluster).to.be.true; await rewardDelegators.mock.updateClusterDelegation.reverts(); - clusterData = await clusterRegistry.getRewardInfo(addrs[0]); + { + let clusterData = await clusterRegistry.getRewardInfo(addrs[0]); expect(clusterData[0]).to.equal(7); expect(clusterData[1]).to.equal(addrs[11]); + } }); takeSnapshotBeforeAndAfterEveryTest(async () => {}); diff --git a/test/staking/ClusterRewards.ts b/test/staking/ClusterRewards.ts index 8b37e14d..2f20ca30 100644 --- a/test/staking/ClusterRewards.ts +++ b/test/staking/ClusterRewards.ts @@ -13,7 +13,6 @@ import { getClusterRewards, getClusterSelector, getPond, getReceiverStaking } fr import { getRandomElementsFromArray } from "../helpers/common"; import { getRandomNumber, randomlyDivideInXPieces } from "../../benchmarks/helpers/util"; - async function skipBlocks(n: number) { await Promise.all([...Array(n)].map(async (x) => await ethers.provider.send("evm_mine", []))); } @@ -49,11 +48,13 @@ const MAX_REWARD_1_pc = MAX_REWARD.div(100); const ETH_REWARD = MAX_REWARD.mul(ETHWEIGHT).div(TOTALWEIGHT); const tickets = [ - MAX_TICKETS.mul(10).div(100), - MAX_TICKETS.mul(20).div(100), - MAX_TICKETS.mul(30).div(100), - MAX_TICKETS.mul(15).div(100), - MAX_TICKETS.sub(MAX_TICKETS.mul(10).div(100).add(MAX_TICKETS.mul(20).div(100)).add(MAX_TICKETS.mul(30).div(100)).add(MAX_TICKETS.mul(15).div(100))) + MAX_TICKETS.mul(10).div(100), + MAX_TICKETS.mul(20).div(100), + MAX_TICKETS.mul(30).div(100), + MAX_TICKETS.mul(15).div(100), + MAX_TICKETS.sub( + MAX_TICKETS.mul(10).div(100).add(MAX_TICKETS.mul(20).div(100)).add(MAX_TICKETS.mul(30).div(100)).add(MAX_TICKETS.mul(15).div(100)) + ), ]; describe("ClusterRewards deploy and init", function () { @@ -72,74 +73,38 @@ describe("ClusterRewards deploy and init", function () { let clusterRewards = await ClusterRewards.deploy(); await expect( - clusterRewards.initialize( - addrs[0], - addrs[1], - addrs[10], - NETWORK_IDS, - WEIGHTS, - [addrs[11], addrs[12], addrs[13]], - MAX_REWARD, - ) + clusterRewards.initialize(addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD) ).to.be.revertedWith("Initializable: contract is already initialized"); }); it("deploys as proxy and initializes", async function () { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - await expect(upgrades.deployProxy( - ClusterRewards, - [ - addrs[0], - addrs[1], - addrs[10], - NETWORK_IDS, - [ETHWEIGHT, DOTWEIGHT], - [addrs[11], addrs[12], addrs[13]], - MAX_REWARD, - ], - { kind: "uups" } - )).to.be.revertedWith("CRW:I-Each NetworkId need a corresponding RewardPerEpoch and vice versa"); + await expect( + upgrades.deployProxy( + ClusterRewards, + [addrs[0], addrs[1], addrs[10], NETWORK_IDS, [ETHWEIGHT, DOTWEIGHT], [addrs[11], addrs[12], addrs[13]], MAX_REWARD], + { kind: "uups" } + ) + ).to.be.revertedWith("CRW:I-Each NetworkId need a corresponding RewardPerEpoch and vice versa"); - await expect(upgrades.deployProxy( - ClusterRewards, - [ - addrs[0], - addrs[1], - addrs[10], - NETWORK_IDS, - WEIGHTS, - [addrs[11], addrs[12]], - MAX_REWARD, - ], - { kind: "uups" } - )).to.be.revertedWith("CRW:I-Each NetworkId need a corresponding clusterSelector and vice versa"); + await expect( + upgrades.deployProxy(ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12]], MAX_REWARD], { + kind: "uups", + }) + ).to.be.revertedWith("CRW:I-Each NetworkId need a corresponding clusterSelector and vice versa"); - await expect(upgrades.deployProxy( - ClusterRewards, - [ - addrs[0], - addrs[1], - addrs[10], - NETWORK_IDS, - WEIGHTS, - [addrs[11], addrs[12], ethers.constants.AddressZero], - MAX_REWARD, - ], - { kind: "uups" } - )).to.be.revertedWith("CRW:CN-ClusterSelector must exist"); + await expect( + upgrades.deployProxy( + ClusterRewards, + [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], ethers.constants.AddressZero], MAX_REWARD], + { kind: "uups" } + ) + ).to.be.revertedWith("CRW:CN-ClusterSelector must exist"); const clusterRewards = await upgrades.deployProxy( ClusterRewards, - [ - addrs[0], - addrs[1], - addrs[10], - NETWORK_IDS, - WEIGHTS, - [addrs[11], addrs[12], addrs[13]], - MAX_REWARD, - ], + [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], { kind: "uups" } ); @@ -160,15 +125,7 @@ describe("ClusterRewards deploy and init", function () { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); const clusterRewards = await upgrades.deployProxy( ClusterRewards, - [ - addrs[0], - addrs[1], - addrs[10], - NETWORK_IDS, - WEIGHTS, - [addrs[11], addrs[12], addrs[13]], - MAX_REWARD, - ], + [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], { kind: "uups" } ); await upgrades.upgradeProxy(clusterRewards.address, ClusterRewards, { kind: "uups" }); @@ -190,18 +147,12 @@ describe("ClusterRewards deploy and init", function () { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); const clusterRewards = await upgrades.deployProxy( ClusterRewards, - [ - addrs[0], - addrs[1], - addrs[10], - NETWORK_IDS, - WEIGHTS, - [addrs[11], addrs[12], addrs[13]], - MAX_REWARD, - ], + [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], { kind: "uups" } ); - await expect(upgrades.upgradeProxy(clusterRewards.address, ClusterRewards.connect(signers[1]), { kind: "uups" })).to.be.revertedWith("only admin"); + await expect(upgrades.upgradeProxy(clusterRewards.address, ClusterRewards.connect(signers[1]), { kind: "uups" })).to.be.revertedWith( + "only admin" + ); }); }); @@ -211,15 +162,7 @@ testERC165( const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, - [ - addrs[0], - addrs[1], - addrs[10], - NETWORK_IDS, - WEIGHTS, - [addrs[11], addrs[12], addrs[13]], - MAX_REWARD, - ], + [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], { kind: "uups" } ); let clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -241,41 +184,29 @@ testAdminRole("ClusterRewards admin role", async function (signers: Signer[], ad const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, - [ - addrs[0], - addrs[1], - addrs[10], - NETWORK_IDS, - WEIGHTS, - [addrs[11], addrs[12], addrs[13]], - MAX_REWARD, - ], + [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], { kind: "uups" } ); let clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); return clusterRewards; }); -testRole("ClusterRegistry claimer role", async function (signers: Signer[], addrs: string[]) { - const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsContract = await upgrades.deployProxy( - ClusterRewards, - [ - addrs[0], - addrs[1], - addrs[10], - NETWORK_IDS, - WEIGHTS, - [addrs[11], addrs[12], addrs[13]], - MAX_REWARD, - ], - { kind: "uups" } - ); - let clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); - return clusterRewards; -}, "CLAIMER_ROLE"); +testRole( + "ClusterRegistry claimer role", + async function (signers: Signer[], addrs: string[]) { + const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); + let clusterRewardsContract = await upgrades.deployProxy( + ClusterRewards, + [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], + { kind: "uups" } + ); + let clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); + return clusterRewards; + }, + "CLAIMER_ROLE" +); -let startTime = Math.floor(Date.now()/1000) + 100000; +let startTime = Math.floor(Date.now() / 1000) + 100000; describe("ClusterRewards add network", function () { let signers: Signer[]; @@ -295,15 +226,7 @@ describe("ClusterRewards add network", function () { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, - [ - addrs[0], - addrs[1], - receiverStaking.address, - NETWORK_IDS, - WEIGHTS, - [addrs[11], addrs[12], addrs[13]], - MAX_REWARD, - ], + [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], { kind: "uups" } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -343,20 +266,26 @@ describe("ClusterRewards add network", function () { await clusterSelector.mock.START_TIME.returns(startTime); await clusterSelector.mock.EPOCH_LENGTH.returns(900); - await expect(clusterRewards.addNetwork(ethers.utils.id("ETH"), 400, clusterSelector.address)).to.be.revertedWith("CRW:AN-Network already exists"); + await expect(clusterRewards.addNetwork(ethers.utils.id("ETH"), 400, clusterSelector.address)).to.be.revertedWith( + "CRW:AN-Network already exists" + ); }); it("admin cannot add network with invalid cluster selector", async function () { - await expect(clusterRewards.addNetwork(ethers.utils.id("POLYGON"), 400, ethers.constants.AddressZero)).to.be.revertedWith("CRW:AN-ClusterSelector must exist"); + await expect(clusterRewards.addNetwork(ethers.utils.id("POLYGON"), 400, ethers.constants.AddressZero)).to.be.revertedWith( + "CRW:AN-ClusterSelector must exist" + ); }); it("admin cannot add network if start time does not match", async function () { const ClusterSelector = await ethers.getContractFactory("ClusterSelector"); let clusterSelector = await deployMockContract(signers[0], ClusterSelector.interface.format()); - await clusterSelector.mock.START_TIME.returns(startTime+1); + await clusterSelector.mock.START_TIME.returns(startTime + 1); await clusterSelector.mock.EPOCH_LENGTH.returns(900); - await expect(clusterRewards.addNetwork(ethers.utils.id("POLYGON"), 400, clusterSelector.address)).to.be.revertedWith("CRW:AN-start time inconsistent"); + await expect(clusterRewards.addNetwork(ethers.utils.id("POLYGON"), 400, clusterSelector.address)).to.be.revertedWith( + "CRW:AN-start time inconsistent" + ); }); it("admin cannot add network if epoch length does not match", async function () { @@ -365,7 +294,9 @@ describe("ClusterRewards add network", function () { await clusterSelector.mock.START_TIME.returns(startTime); await clusterSelector.mock.EPOCH_LENGTH.returns(901); - await expect(clusterRewards.addNetwork(ethers.utils.id("POLYGON"), 400, clusterSelector.address)).to.be.revertedWith("CRW:AN-epoch length inconsistent"); + await expect(clusterRewards.addNetwork(ethers.utils.id("POLYGON"), 400, clusterSelector.address)).to.be.revertedWith( + "CRW:AN-epoch length inconsistent" + ); }); it("non admin cannot add network", async function () { @@ -374,7 +305,9 @@ describe("ClusterRewards add network", function () { await clusterSelector.mock.START_TIME.returns(startTime); await clusterSelector.mock.EPOCH_LENGTH.returns(900); - await expect(clusterRewards.connect(signers[1]).addNetwork(ethers.utils.id("POLYGON"), 400, clusterSelector.address)).to.be.revertedWith("only admin"); + await expect( + clusterRewards.connect(signers[1]).addNetwork(ethers.utils.id("POLYGON"), 400, clusterSelector.address) + ).to.be.revertedWith("only admin"); }); }); @@ -396,15 +329,7 @@ describe("ClusterRewards cluster selector", function () { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, - [ - addrs[0], - addrs[1], - receiverStaking.address, - NETWORK_IDS, - WEIGHTS, - [addrs[11], addrs[12], addrs[13]], - MAX_REWARD, - ], + [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], { kind: "uups" } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -426,16 +351,20 @@ describe("ClusterRewards cluster selector", function () { }); it("admin cannot update cluster selector to zero", async function () { - await expect(clusterRewards.updateNetwork(ethers.utils.id("ETH"), ETHWEIGHT, ethers.constants.AddressZero)).to.be.revertedWith("CRW:UN-ClusterSelector must exist"); + await expect(clusterRewards.updateNetwork(ethers.utils.id("ETH"), ETHWEIGHT, ethers.constants.AddressZero)).to.be.revertedWith( + "CRW:UN-ClusterSelector must exist" + ); }); it("admin cannot update cluster selector if start time does not match", async function () { const ClusterSelector = await ethers.getContractFactory("ClusterSelector"); let clusterSelector = await deployMockContract(signers[0], ClusterSelector.interface.format()); - await clusterSelector.mock.START_TIME.returns(startTime+1); + await clusterSelector.mock.START_TIME.returns(startTime + 1); await clusterSelector.mock.EPOCH_LENGTH.returns(900); - await expect(clusterRewards.updateNetwork(ethers.utils.id("ETH"), ETHWEIGHT, clusterSelector.address)).to.be.revertedWith("CRW:UN-start time inconsistent"); + await expect(clusterRewards.updateNetwork(ethers.utils.id("ETH"), ETHWEIGHT, clusterSelector.address)).to.be.revertedWith( + "CRW:UN-start time inconsistent" + ); }); it("admin cannot update cluster selector if epoch length does not match", async function () { @@ -444,7 +373,9 @@ describe("ClusterRewards cluster selector", function () { await clusterSelector.mock.START_TIME.returns(startTime); await clusterSelector.mock.EPOCH_LENGTH.returns(901); - await expect(clusterRewards.updateNetwork(ethers.utils.id("ETH"), ETHWEIGHT, clusterSelector.address)).to.be.revertedWith("CRW:UN-epoch length inconsistent"); + await expect(clusterRewards.updateNetwork(ethers.utils.id("ETH"), ETHWEIGHT, clusterSelector.address)).to.be.revertedWith( + "CRW:UN-epoch length inconsistent" + ); }); it("admin can update reward weight", async function () { @@ -469,7 +400,9 @@ describe("ClusterRewards cluster selector", function () { await clusterSelector.mock.START_TIME.returns(startTime); await clusterSelector.mock.EPOCH_LENGTH.returns(900); - await expect(clusterRewards.updateNetwork(ethers.utils.id("POLYGON"), ETHWEIGHT, clusterSelector.address)).to.be.revertedWith("CRW:UN-Network doesnt exist"); + await expect(clusterRewards.updateNetwork(ethers.utils.id("POLYGON"), ETHWEIGHT, clusterSelector.address)).to.be.revertedWith( + "CRW:UN-Network doesnt exist" + ); }); it("non admin cannot update cluster selector", async function () { @@ -478,7 +411,9 @@ describe("ClusterRewards cluster selector", function () { await clusterSelector.mock.START_TIME.returns(startTime); await clusterSelector.mock.EPOCH_LENGTH.returns(900); - await expect(clusterRewards.connect(signers[1]).updateNetwork(ethers.utils.id("ETH"), ETHWEIGHT, clusterSelector.address)).to.be.revertedWith("only admin"); + await expect( + clusterRewards.connect(signers[1]).updateNetwork(ethers.utils.id("ETH"), ETHWEIGHT, clusterSelector.address) + ).to.be.revertedWith("only admin"); }); it("non admin cannot update weight", async function () { @@ -491,7 +426,9 @@ describe("ClusterRewards cluster selector", function () { await clusterSelector.mock.START_TIME.returns(startTime); await clusterSelector.mock.EPOCH_LENGTH.returns(900); - await expect(clusterRewards.connect(signers[1]).updateNetwork(ethers.utils.id("ETH"), 400, clusterSelector.address)).to.be.revertedWith("only admin"); + await expect(clusterRewards.connect(signers[1]).updateNetwork(ethers.utils.id("ETH"), 400, clusterSelector.address)).to.be.revertedWith( + "only admin" + ); }); }); @@ -513,15 +450,7 @@ describe("ClusterRewards remove network", function () { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, - [ - addrs[0], - addrs[1], - receiverStaking.address, - NETWORK_IDS, - WEIGHTS, - [addrs[11], addrs[12], addrs[13]], - MAX_REWARD, - ], + [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], { kind: "uups" } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -563,15 +492,7 @@ describe("ClusterRewards update global vars", function () { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, - [ - addrs[0], - addrs[1], - receiverStaking.address, - NETWORK_IDS, - WEIGHTS, - [addrs[11], addrs[12], addrs[13]], - MAX_REWARD, - ], + [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], { kind: "uups" } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -613,7 +534,11 @@ describe("ClusterRewards feed rewards", function () { let receiverStaking: Contract; let clusterRewards: ClusterRewards; const FEED_REWARD = BN.from("200000").mul(e18); - const FEEDER_REWARD_1_pc = FEED_REWARD.mul(ETHWEIGHT).div(TOTALWEIGHT).div(100).mul(24*60*60).div(900); + const FEEDER_REWARD_1_pc = FEED_REWARD.mul(ETHWEIGHT) + .div(TOTALWEIGHT) + .div(100) + .mul(24 * 60 * 60) + .div(900); before(async function () { signers = await ethers.getSigners(); @@ -627,15 +552,7 @@ describe("ClusterRewards feed rewards", function () { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, - [ - addrs[0], - addrs[1], - receiverStaking.address, - NETWORK_IDS, - WEIGHTS, - [addrs[11], addrs[12], addrs[13]], - FEED_REWARD, - ], + [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], FEED_REWARD], { kind: "uups" } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -665,7 +582,7 @@ describe("ClusterRewards feed rewards", function () { }); it("feeder can feed rewards till 1 day after switching time", async function () { - await skipToTimestamp(startTime + 33*86400 + 85000); + await skipToTimestamp(startTime + 33 * 86400 + 85000); await clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(50)], 1); @@ -675,13 +592,19 @@ describe("ClusterRewards feed rewards", function () { }); it("feeder cannot feed rewards after 1 day after switching time", async function () { - await skipToTimestamp(startTime + 33*86400 + 90000); + await skipToTimestamp(startTime + 33 * 86400 + 90000); - await expect(clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(50)], 1)).to.be.revertedWith("CRW:F-Invalid method"); + await expect( + clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(50)], 1) + ).to.be.revertedWith("CRW:F-Invalid method"); }); it("feeder cannot feed rewards exceeding total", async function () { - await expect(clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22], addrs[23]], [e16.mul(10), e16.mul(50), (e16.mul(40)).add(e16.div(100))], 1)).to.be.revertedWith("CRW:F-Reward Distributed cant be more than totalRewardPerEpoch"); + await expect( + clusterRewards + .connect(signers[2]) + .feed(ETHHASH, [addrs[21], addrs[22], addrs[23]], [e16.mul(10), e16.mul(50), e16.mul(40).add(e16.div(100))], 1) + ).to.be.revertedWith("CRW:F-Reward Distributed cant be more than totalRewardPerEpoch"); }); it("feeder can feed rewards in multiple parts", async function () { @@ -711,7 +634,9 @@ describe("ClusterRewards feed rewards", function () { expect(await clusterRewards.clusterRewards(addrs[22])).to.equal(FEEDER_REWARD_1_pc.mul(50)); expect(await clusterRewards.rewardDistributedPerEpoch(1)).to.equal(FEEDER_REWARD_1_pc.mul(60)); - await expect(clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[23]], [e16.mul(41)], 1)).to.be.revertedWith("CRW:F-Reward Distributed cant be more than totalRewardPerEpoch"); + await expect(clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[23]], [e16.mul(41)], 1)).to.be.revertedWith( + "CRW:F-Reward Distributed cant be more than totalRewardPerEpoch" + ); }); it("feeder cannot feed rewards again before wait time", async function () { @@ -721,9 +646,13 @@ describe("ClusterRewards feed rewards", function () { expect(await clusterRewards.clusterRewards(addrs[22])).to.equal(FEEDER_REWARD_1_pc.mul(50)); expect(await clusterRewards.rewardDistributedPerEpoch(1)).to.equal(FEEDER_REWARD_1_pc.mul(60)); - await expect(clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(10)], 2)).to.be.revertedWith("CRW:F-Cant distribute reward for new epoch within such short interval"); + await expect( + clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(10)], 2) + ).to.be.revertedWith("CRW:F-Cant distribute reward for new epoch within such short interval"); await skipTime(40000); - await expect(clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(10)], 2)).to.be.revertedWith("CRW:F-Cant distribute reward for new epoch within such short interval"); + await expect( + clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(10)], 2) + ).to.be.revertedWith("CRW:F-Cant distribute reward for new epoch within such short interval"); }); it("feeder can feed rewards again after wait time", async function () { @@ -733,9 +662,13 @@ describe("ClusterRewards feed rewards", function () { expect(await clusterRewards.clusterRewards(addrs[22])).to.equal(FEEDER_REWARD_1_pc.mul(50)); expect(await clusterRewards.rewardDistributedPerEpoch(1)).to.equal(FEEDER_REWARD_1_pc.mul(60)); - await expect(clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(10)], 2)).to.be.revertedWith("CRW:F-Cant distribute reward for new epoch within such short interval"); + await expect( + clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(10)], 2) + ).to.be.revertedWith("CRW:F-Cant distribute reward for new epoch within such short interval"); await skipTime(40000); - await expect(clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(10)], 2)).to.be.revertedWith("CRW:F-Cant distribute reward for new epoch within such short interval"); + await expect( + clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(10)], 2) + ).to.be.revertedWith("CRW:F-Cant distribute reward for new epoch within such short interval"); await skipTime(5000); await clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(10)], 2); @@ -772,15 +705,7 @@ describe("ClusterRewards submit tickets", function () { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, - [ - addrs[0], - addrs[1], - receiverStaking.address, - NETWORK_IDS, - WEIGHTS, - [ethSelector.address, addrs[12], addrs[13]], - MAX_REWARD, - ], + [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [ethSelector.address, addrs[12], addrs[13]], MAX_REWARD], { kind: "uups" } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -797,15 +722,15 @@ describe("ClusterRewards submit tickets", function () { let epochNumber = getRandomNumber(BN.from(20000)).toNumber(); let noOfEpochs = 96; let tickets: number[][] = []; - let rawTicketInfo = networkId + epochNumber.toString(16).padStart(8, '0'); - for(let i=0; i ETH_REWARD.mul(50).mul(e).div(500).div(MAX_TICKETS)); - await skipToTimestamp(startTime + 34*86400); + await skipToTimestamp(startTime + 34 * 86400); await clusterRewards.connect(signers[5])["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, epochWithRewards, tickets.slice(0, -1)); @@ -882,15 +807,15 @@ describe("ClusterRewards submit tickets", function () { expect(await clusterRewards.isTicketsIssued(addrs[4], epochWithRewards + 2)).to.be.true; expect(await clusterRewards.isTicketsIssued(addrs[5], epochWithRewards + 2)).to.be.false; - expect((await clusterRewards.clusterRewards(addrs[31]))).to.be.closeTo((receiverRewards1[0]).add(receiverRewards2[4]), 2); - expect((await clusterRewards.clusterRewards(addrs[32]))).to.be.closeTo((receiverRewards1[1]).add(receiverRewards2[3]), 2); - expect((await clusterRewards.clusterRewards(addrs[33]))).to.be.closeTo((receiverRewards1[2]).add(receiverRewards2[2]), 2); - expect((await clusterRewards.clusterRewards(addrs[34]))).to.be.closeTo((receiverRewards1[3]).add(receiverRewards2[1]), 2); - expect((await clusterRewards.clusterRewards(addrs[35]))).to.be.closeTo((receiverRewards1[4]).add(receiverRewards2[0]), 2); + expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo(receiverRewards1[0].add(receiverRewards2[4]), 2); + expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo(receiverRewards1[1].add(receiverRewards2[3]), 2); + expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo(receiverRewards1[2].add(receiverRewards2[2]), 2); + expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(receiverRewards2[1]), 2); + expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(receiverRewards2[0]), 2); }); it("staker cannot submit tickets multiple times for the same epoch", async function () { - const epochWithRewards = 33*86400/900 + 2; + const epochWithRewards = (33 * 86400) / 900 + 2; await receiverStaking.mock.balanceOfSignerAt.reverts(); await receiverStaking.mock.balanceOfSignerAt.withArgs(addrs[5], epochWithRewards).returns(50, addrs[4]); await receiverStaking.mock.getEpochInfo.reverts(); @@ -898,7 +823,7 @@ describe("ClusterRewards submit tickets", function () { await ethSelector.mock.getClusters.returns([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]); let receiverRewards1 = tickets.map((e) => ETH_REWARD.mul(50).mul(e).div(500).div(MAX_TICKETS)); - await skipToTimestamp(startTime + 34*86400); + await skipToTimestamp(startTime + 34 * 86400); await clusterRewards.connect(signers[5])["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, epochWithRewards, tickets.slice(0, -1)); @@ -916,20 +841,24 @@ describe("ClusterRewards submit tickets", function () { await receiverStaking.mock.getEpochInfo.withArgs(epochWithRewards + 2).returns(125, epochWithRewards + 3); await ethSelector.mock.getClusters.returns([addrs[35], addrs[34], addrs[33], addrs[32], addrs[31]]); - await expect(clusterRewards.connect(signers[5])["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, epochWithRewards, tickets.slice(0, -1))).to.be.revertedWith("CRW:IPRT-Tickets already issued"); + await expect( + clusterRewards.connect(signers[5])["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, epochWithRewards, tickets.slice(0, -1)) + ).to.be.revertedWith("CRW:IPRT-Tickets already issued"); }); it("staker cannot submit tickets for future epochs", async function () { - const epochWithRewards = 33*86400/900 + 5; + const epochWithRewards = (33 * 86400) / 900 + 5; await receiverStaking.mock.balanceOfSignerAt.reverts(); await receiverStaking.mock.balanceOfSignerAt.withArgs(addrs[5], epochWithRewards).returns(50, addrs[4]); await receiverStaking.mock.getEpochInfo.reverts(); await receiverStaking.mock.getEpochInfo.withArgs(epochWithRewards).returns(500, epochWithRewards); await ethSelector.mock.getClusters.returns([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]); - await skipToTimestamp(startTime + 34*86400); + await skipToTimestamp(startTime + 34 * 86400); - await expect(clusterRewards.connect(signers[5])["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, epochWithRewards, tickets.slice(0, -1))).to.be.revertedWith("CRW:IT-Epoch not completed"); + await expect( + clusterRewards.connect(signers[5])["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, epochWithRewards, tickets.slice(0, -1)) + ).to.be.revertedWith("CRW:IT-Epoch not completed"); }); it.skip("staker cannot submit partial tickets", async function () { @@ -939,37 +868,80 @@ describe("ClusterRewards submit tickets", function () { await receiverStaking.mock.getEpochInfo.withArgs(2).returns(500, 5); await ethSelector.mock.getClusters.returns([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]); - await skipToTimestamp(startTime + 34*86400); + await skipToTimestamp(startTime + 34 * 86400); - await expect(clusterRewards.connect(signers[5])["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, 2, [MAX_TICKETS_1_pc.mul(10), MAX_TICKETS_1_pc.mul(20), MAX_TICKETS_1_pc.mul(30), MAX_TICKETS_1_pc.mul(15), MAX_TICKETS_1_pc.mul(24)])).to.be.revertedWith("CRW:IPRT-Total ticket count invalid"); + await expect( + clusterRewards + .connect(signers[5]) + ["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, 2, [ + MAX_TICKETS_1_pc.mul(10), + MAX_TICKETS_1_pc.mul(20), + MAX_TICKETS_1_pc.mul(30), + MAX_TICKETS_1_pc.mul(15), + MAX_TICKETS_1_pc.mul(24), + ]) + ).to.be.revertedWith("CRW:IPRT-Total ticket count invalid"); }); it("staker cannot submit excess tickets", async function () { - const epochWithRewards = 33*86400/900 + 2; + const epochWithRewards = (33 * 86400) / 900 + 2; await receiverStaking.mock.balanceOfSignerAt.reverts(); await receiverStaking.mock.balanceOfSignerAt.withArgs(addrs[5], epochWithRewards).returns(50, addrs[4]); await receiverStaking.mock.getEpochInfo.reverts(); await receiverStaking.mock.getEpochInfo.withArgs(epochWithRewards).returns(500, epochWithRewards + 3); await ethSelector.mock.getClusters.returns([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]); - await skipToTimestamp(startTime + 34*86400); + await skipToTimestamp(startTime + 34 * 86400); - await expect(clusterRewards.connect(signers[5])["issueTickets(bytes32,uint24[],uint16[][])"](ETHHASH, [epochWithRewards], [[MAX_TICKETS_1_pc.mul(10), MAX_TICKETS_1_pc.mul(20), MAX_TICKETS_1_pc.mul(30), MAX_TICKETS_1_pc.mul(41)]])).to.be.revertedWith("CRW:IPRT-Total ticket count invalid"); - await expect(clusterRewards.connect(signers[5])["issueTickets(bytes32,uint24[],uint16[][])"](ETHHASH, [], [[MAX_TICKETS_1_pc.mul(10), MAX_TICKETS_1_pc.mul(20), MAX_TICKETS_1_pc.mul(30), MAX_TICKETS_1_pc.mul(15), MAX_TICKETS_1_pc.mul(26)]])).to.be.revertedWith("CRW:MIT-invalid inputs"); + await expect( + clusterRewards + .connect(signers[5]) + ["issueTickets(bytes32,uint24[],uint16[][])"]( + ETHHASH, + [epochWithRewards], + [[MAX_TICKETS_1_pc.mul(10), MAX_TICKETS_1_pc.mul(20), MAX_TICKETS_1_pc.mul(30), MAX_TICKETS_1_pc.mul(41)]] + ) + ).to.be.revertedWith("CRW:IPRT-Total ticket count invalid"); + await expect( + clusterRewards + .connect(signers[5]) + ["issueTickets(bytes32,uint24[],uint16[][])"]( + ETHHASH, + [], + [ + [ + MAX_TICKETS_1_pc.mul(10), + MAX_TICKETS_1_pc.mul(20), + MAX_TICKETS_1_pc.mul(30), + MAX_TICKETS_1_pc.mul(15), + MAX_TICKETS_1_pc.mul(26), + ], + ] + ) + ).to.be.revertedWith("CRW:MIT-invalid inputs"); }); it("staker cannot submit tickets over maximum", async function () { - const epochWithRewards = 33*86400/900 + 2; + const epochWithRewards = (33 * 86400) / 900 + 2; await receiverStaking.mock.balanceOfSignerAt.reverts(); await receiverStaking.mock.balanceOfSignerAt.withArgs(addrs[5], epochWithRewards).returns(50, addrs[4]); await receiverStaking.mock.getEpochInfo.reverts(); await receiverStaking.mock.getEpochInfo.withArgs(epochWithRewards).returns(500, epochWithRewards + 3); await ethSelector.mock.getClusters.returns([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]); - await skipToTimestamp(startTime + 34*86400); + await skipToTimestamp(startTime + 34 * 86400); // reverted because of overflow in args - await expect(clusterRewards.connect(signers[5])["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, epochWithRewards, [MAX_TICKETS_1_pc.mul(10), MAX_TICKETS_1_pc.mul(20), MAX_TICKETS_1_pc.mul(30), MAX_TICKETS.add(1)])).to.be.revertedWith(""); + await expect( + clusterRewards + .connect(signers[5]) + ["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, epochWithRewards, [ + MAX_TICKETS_1_pc.mul(10), + MAX_TICKETS_1_pc.mul(20), + MAX_TICKETS_1_pc.mul(30), + MAX_TICKETS.add(1), + ]) + ).to.be.revertedWith(""); }); }); @@ -998,15 +970,7 @@ describe("ClusterRewards submit compressed tickets", function () { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, - [ - addrs[0], - addrs[1], - receiverStaking.address, - NETWORK_IDS, - WEIGHTS, - [ethSelector.address, addrs[12], addrs[13]], - MAX_REWARD, - ], + [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [ethSelector.address, addrs[12], addrs[13]], MAX_REWARD], { kind: "uups" } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -1026,13 +990,13 @@ describe("ClusterRewards submit compressed tickets", function () { await ethSelector.mock.getClustersRanged.returns([[addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]]); const tickets: number[][] = []; - let rawTicketInfo = ETHHASH + (2).toString(16).padStart(8, '0'); - for(let i=0; i<1*ticketsLength; i++) { - let j: number = parseInt((i/ticketsLength)+""); - let k: number = i%ticketsLength; - if(!tickets[j]) tickets[j] = []; - tickets[j][k] = parseInt((Math.random()*13000)+""); - rawTicketInfo = rawTicketInfo+tickets[j][k].toString(16).padStart(4, '0'); + let rawTicketInfo = ETHHASH + (2).toString(16).padStart(8, "0"); + for (let i = 0; i < 1 * ticketsLength; i++) { + let j: number = parseInt(i / ticketsLength + ""); + let k: number = i % ticketsLength; + if (!tickets[j]) tickets[j] = []; + tickets[j][k] = parseInt(Math.random() * 13000 + ""); + rawTicketInfo = rawTicketInfo + tickets[j][k].toString(16).padStart(4, "0"); } await clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo); @@ -1056,16 +1020,16 @@ describe("ClusterRewards submit compressed tickets", function () { await ethSelector.mock.getClustersRanged.returns([[addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]]); const tickets: number[][] = []; - let rawTicketInfo = ETHHASH + (2).toString(16).padStart(8, '0'); - for(let i=0; i<1*ticketsLength; i++) { - let j: number = parseInt((i/ticketsLength)+""); - let k: number = i%ticketsLength; - if(!tickets[j]) tickets[j] = []; - tickets[j][k] = parseInt((Math.random()*13000)+""); - rawTicketInfo = rawTicketInfo+tickets[j][k].toString(16).padStart(4, '0'); + let rawTicketInfo = ETHHASH + (2).toString(16).padStart(8, "0"); + for (let i = 0; i < 1 * ticketsLength; i++) { + let j: number = parseInt(i / ticketsLength + ""); + let k: number = i % ticketsLength; + if (!tickets[j]) tickets[j] = []; + tickets[j][k] = parseInt(Math.random() * 13000 + ""); + rawTicketInfo = rawTicketInfo + tickets[j][k].toString(16).padStart(4, "0"); } - await skipToTimestamp(startTime + 34*86400); + await skipToTimestamp(startTime + 34 * 86400); await clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo); @@ -1080,28 +1044,30 @@ describe("ClusterRewards submit compressed tickets", function () { }); it("staker can submit compressed tickets after switch with non zero rewards", async function () { - const epochWithRewards = 33*86400/900 + 2; + const epochWithRewards = (33 * 86400) / 900 + 2; const numberOfEpochs = 10; let receiverRewards1: BN[] = []; - await skipToTimestamp(startTime + 34*86400); + await skipToTimestamp(startTime + 34 * 86400); const ticketsByEpoch: number[][] = []; let startEpoch = epochWithRewards; - let rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, '0'); + let rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, "0"); let totalTickets = 0; - - for(let i=0; i < numberOfEpochs; i++) { + + for (let i = 0; i < numberOfEpochs; i++) { ticketsByEpoch[i] = []; - for(let j=0; j < ticketsLength; j++) { - ticketsByEpoch[i][j] = parseInt((Math.random()*2^16/(ticketsLength+1))+""); + for (let j = 0; j < ticketsLength; j++) { + ticketsByEpoch[i][j] = parseInt(((Math.random() * 2) ^ (16 / (ticketsLength + 1))) + ""); totalTickets += ticketsByEpoch[i][j]; - rawTicketInfo = rawTicketInfo+ticketsByEpoch[i][j].toString(16).padStart(4, '0'); - if(!receiverRewards1[j]) receiverRewards1[j] = BN.from(0); + rawTicketInfo = rawTicketInfo + ticketsByEpoch[i][j].toString(16).padStart(4, "0"); + if (!receiverRewards1[j]) receiverRewards1[j] = BN.from(0); receiverRewards1[j] = receiverRewards1[j].add(ETH_REWARD.mul(50).mul(ticketsByEpoch[i][j]).div(500).div(MAX_TICKETS)); } - if(!receiverRewards1[ticketsLength]) receiverRewards1[ticketsLength] = BN.from(0); - receiverRewards1[ticketsLength] = receiverRewards1[ticketsLength].add(ETH_REWARD.mul(50).div(500).mul(MAX_TICKETS.sub(totalTickets)).div(MAX_TICKETS)); + if (!receiverRewards1[ticketsLength]) receiverRewards1[ticketsLength] = BN.from(0); + receiverRewards1[ticketsLength] = receiverRewards1[ticketsLength].add( + ETH_REWARD.mul(50).div(500).mul(MAX_TICKETS.sub(totalTickets)).div(MAX_TICKETS) + ); totalTickets = 0; } @@ -1109,12 +1075,14 @@ describe("ClusterRewards submit compressed tickets", function () { await receiverStaking.mock.totalSupplyAtRanged.revertsWithReason("unexpected query for total balance"); await receiverStaking.mock.totalSupplyAtRanged.withArgs(startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(500)); await receiverStaking.mock.balanceOfSignerAtRanged.revertsWithReason("unexpected query for signer balance"); - await receiverStaking.mock.balanceOfSignerAtRanged.withArgs(addrs[5], startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(50), addrs[4]); + await receiverStaking.mock.balanceOfSignerAtRanged + .withArgs(addrs[5], startEpoch, numberOfEpochs) + .returns(Array(numberOfEpochs).fill(50), addrs[4]); await ethSelector.mock.getClustersRanged.returns(Array(numberOfEpochs).fill([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]])); - + await clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo); - for(let i=0; i < numberOfEpochs; i++) { + for (let i = 0; i < numberOfEpochs; i++) { expect(await clusterRewards.isTicketsIssued(addrs[4], startEpoch + i)).to.be.true; expect(await clusterRewards.isTicketsIssued(addrs[5], startEpoch + i)).to.be.false; expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo(receiverRewards1[0], 1); @@ -1126,20 +1094,22 @@ describe("ClusterRewards submit compressed tickets", function () { startEpoch += numberOfEpochs; - rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, '0'); + rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, "0"); totalTickets = 0; let receiverRewards2: BN[] = []; - for(let i=0; i < numberOfEpochs; i++) { + for (let i = 0; i < numberOfEpochs; i++) { ticketsByEpoch[i] = []; - for(let j=0; j < ticketsLength; j++) { - ticketsByEpoch[i][j] = parseInt((Math.random()*2^16/(ticketsLength+1))+""); + for (let j = 0; j < ticketsLength; j++) { + ticketsByEpoch[i][j] = parseInt(((Math.random() * 2) ^ (16 / (ticketsLength + 1))) + ""); totalTickets += ticketsByEpoch[i][j]; - rawTicketInfo = rawTicketInfo+ticketsByEpoch[i][j].toString(16).padStart(4, '0'); - if(!receiverRewards2[j]) receiverRewards2[j] = BN.from(0); - receiverRewards2[j]= receiverRewards2[j].add(ETH_REWARD.mul(25).mul(ticketsByEpoch[i][j]).div(125).div(MAX_TICKETS)); + rawTicketInfo = rawTicketInfo + ticketsByEpoch[i][j].toString(16).padStart(4, "0"); + if (!receiverRewards2[j]) receiverRewards2[j] = BN.from(0); + receiverRewards2[j] = receiverRewards2[j].add(ETH_REWARD.mul(25).mul(ticketsByEpoch[i][j]).div(125).div(MAX_TICKETS)); } - if(!receiverRewards2[ticketsLength]) receiverRewards2[ticketsLength] = BN.from(0); - receiverRewards2[ticketsLength] = receiverRewards2[ticketsLength].add(ETH_REWARD.mul(25).div(125).mul(MAX_TICKETS.sub(totalTickets)).div(MAX_TICKETS)); + if (!receiverRewards2[ticketsLength]) receiverRewards2[ticketsLength] = BN.from(0); + receiverRewards2[ticketsLength] = receiverRewards2[ticketsLength].add( + ETH_REWARD.mul(25).div(125).mul(MAX_TICKETS.sub(totalTickets)).div(MAX_TICKETS) + ); totalTickets = 0; } @@ -1147,45 +1117,49 @@ describe("ClusterRewards submit compressed tickets", function () { await receiverStaking.mock.totalSupplyAtRanged.revertsWithReason("unexpected query for total balance"); await receiverStaking.mock.totalSupplyAtRanged.withArgs(startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(125)); await receiverStaking.mock.balanceOfSignerAtRanged.revertsWithReason("unexpected query for signer balance"); - await receiverStaking.mock.balanceOfSignerAtRanged.withArgs(addrs[5], startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(25), addrs[4]); + await receiverStaking.mock.balanceOfSignerAtRanged + .withArgs(addrs[5], startEpoch, numberOfEpochs) + .returns(Array(numberOfEpochs).fill(25), addrs[4]); await ethSelector.mock.getClustersRanged.returns(Array(numberOfEpochs).fill([addrs[35], addrs[34], addrs[33], addrs[32], addrs[31]])); - + await clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo); - for(let i=0; i < numberOfEpochs; i++) { + for (let i = 0; i < numberOfEpochs; i++) { expect(await clusterRewards.isTicketsIssued(addrs[4], startEpoch + i)).to.be.true; expect(await clusterRewards.isTicketsIssued(addrs[5], startEpoch + i)).to.be.false; - expect((await clusterRewards.clusterRewards(addrs[31]))).to.be.closeTo((receiverRewards1[0]).add(receiverRewards2[4]), 2); - expect((await clusterRewards.clusterRewards(addrs[32]))).to.be.closeTo((receiverRewards1[1]).add(receiverRewards2[3]), 2); - expect((await clusterRewards.clusterRewards(addrs[33]))).to.be.closeTo((receiverRewards1[2]).add(receiverRewards2[2]), 2); - expect((await clusterRewards.clusterRewards(addrs[34]))).to.be.closeTo((receiverRewards1[3]).add(receiverRewards2[1]), 2); - expect((await clusterRewards.clusterRewards(addrs[35]))).to.be.closeTo((receiverRewards1[4]).add(receiverRewards2[0]), 2); + expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo(receiverRewards1[0].add(receiverRewards2[4]), 2); + expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo(receiverRewards1[1].add(receiverRewards2[3]), 2); + expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo(receiverRewards1[2].add(receiverRewards2[2]), 2); + expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(receiverRewards2[1]), 2); + expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(receiverRewards2[0]), 2); } }); it("staker can submit compressed tickets if number of clusters are less than clusters to select", async function () { - const epochWithRewards = 33*86400/900 + 2; + const epochWithRewards = (33 * 86400) / 900 + 2; const numberOfEpochs = 10; let receiverRewards1: BN[] = []; - await skipToTimestamp(startTime + 34*86400); + await skipToTimestamp(startTime + 34 * 86400); const ticketsByEpoch: number[][] = []; let startEpoch = epochWithRewards; - let rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, '0'); + let rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, "0"); let totalTickets = 0; - - for(let i=0; i < numberOfEpochs; i++) { + + for (let i = 0; i < numberOfEpochs; i++) { ticketsByEpoch[i] = []; - for(let j=0; j < ticketsLength; j++) { - ticketsByEpoch[i][j] = parseInt((Math.random()*2^16/(ticketsLength+1))+""); + for (let j = 0; j < ticketsLength; j++) { + ticketsByEpoch[i][j] = parseInt(((Math.random() * 2) ^ (16 / (ticketsLength + 1))) + ""); totalTickets += ticketsByEpoch[i][j]; - rawTicketInfo = rawTicketInfo+ticketsByEpoch[i][j].toString(16).padStart(4, '0'); - if(!receiverRewards1[j]) receiverRewards1[j] = BN.from(0); + rawTicketInfo = rawTicketInfo + ticketsByEpoch[i][j].toString(16).padStart(4, "0"); + if (!receiverRewards1[j]) receiverRewards1[j] = BN.from(0); receiverRewards1[j] = receiverRewards1[j].add(ETH_REWARD.mul(50).mul(ticketsByEpoch[i][j]).div(500).div(MAX_TICKETS)); } - if(!receiverRewards1[ticketsLength]) receiverRewards1[ticketsLength] = BN.from(0); - receiverRewards1[ticketsLength] = receiverRewards1[ticketsLength].add(ETH_REWARD.mul(50).div(500).mul(MAX_TICKETS.sub(totalTickets)).div(MAX_TICKETS)); + if (!receiverRewards1[ticketsLength]) receiverRewards1[ticketsLength] = BN.from(0); + receiverRewards1[ticketsLength] = receiverRewards1[ticketsLength].add( + ETH_REWARD.mul(50).div(500).mul(MAX_TICKETS.sub(totalTickets)).div(MAX_TICKETS) + ); totalTickets = 0; } @@ -1193,12 +1167,14 @@ describe("ClusterRewards submit compressed tickets", function () { await receiverStaking.mock.totalSupplyAtRanged.revertsWithReason("unexpected query for total balance"); await receiverStaking.mock.totalSupplyAtRanged.withArgs(startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(500)); await receiverStaking.mock.balanceOfSignerAtRanged.revertsWithReason("unexpected query for signer balance"); - await receiverStaking.mock.balanceOfSignerAtRanged.withArgs(addrs[5], startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(50), addrs[4]); + await receiverStaking.mock.balanceOfSignerAtRanged + .withArgs(addrs[5], startEpoch, numberOfEpochs) + .returns(Array(numberOfEpochs).fill(50), addrs[4]); await ethSelector.mock.getClustersRanged.returns(Array(numberOfEpochs).fill([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]])); - + await clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo); - for(let i=0; i < numberOfEpochs; i++) { + for (let i = 0; i < numberOfEpochs; i++) { expect(await clusterRewards.isTicketsIssued(addrs[4], startEpoch + i)).to.be.true; expect(await clusterRewards.isTicketsIssued(addrs[5], startEpoch + i)).to.be.false; expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo(receiverRewards1[0], 1); @@ -1210,20 +1186,22 @@ describe("ClusterRewards submit compressed tickets", function () { startEpoch += numberOfEpochs; - rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, '0'); + rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, "0"); totalTickets = 0; let receiverRewards2: BN[] = []; - for(let i=0; i < numberOfEpochs; i++) { + for (let i = 0; i < numberOfEpochs; i++) { ticketsByEpoch[i] = []; - for(let j=0; j < ticketsLength; j++) { - ticketsByEpoch[i][j] = parseInt((Math.random()*2^16/(ticketsLength+1))+""); + for (let j = 0; j < ticketsLength; j++) { + ticketsByEpoch[i][j] = parseInt(((Math.random() * 2) ^ (16 / (ticketsLength + 1))) + ""); totalTickets += ticketsByEpoch[i][j]; - rawTicketInfo = rawTicketInfo+ticketsByEpoch[i][j].toString(16).padStart(4, '0'); - if(!receiverRewards2[j]) receiverRewards2[j] = BN.from(0); - receiverRewards2[j]= receiverRewards2[j].add(ETH_REWARD.mul(25).mul(ticketsByEpoch[i][j]).div(125).div(MAX_TICKETS)); + rawTicketInfo = rawTicketInfo + ticketsByEpoch[i][j].toString(16).padStart(4, "0"); + if (!receiverRewards2[j]) receiverRewards2[j] = BN.from(0); + receiverRewards2[j] = receiverRewards2[j].add(ETH_REWARD.mul(25).mul(ticketsByEpoch[i][j]).div(125).div(MAX_TICKETS)); } - if(!receiverRewards2[ticketsLength]) receiverRewards2[ticketsLength] = BN.from(0); - receiverRewards2[ticketsLength] = receiverRewards2[ticketsLength].add(ETH_REWARD.mul(25).div(125).mul(MAX_TICKETS.sub(totalTickets)).div(MAX_TICKETS)); + if (!receiverRewards2[ticketsLength]) receiverRewards2[ticketsLength] = BN.from(0); + receiverRewards2[ticketsLength] = receiverRewards2[ticketsLength].add( + ETH_REWARD.mul(25).div(125).mul(MAX_TICKETS.sub(totalTickets)).div(MAX_TICKETS) + ); totalTickets = 0; } @@ -1231,45 +1209,49 @@ describe("ClusterRewards submit compressed tickets", function () { await receiverStaking.mock.totalSupplyAtRanged.revertsWithReason("unexpected query for total balance"); await receiverStaking.mock.totalSupplyAtRanged.withArgs(startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(125)); await receiverStaking.mock.balanceOfSignerAtRanged.revertsWithReason("unexpected query for signer balance"); - await receiverStaking.mock.balanceOfSignerAtRanged.withArgs(addrs[5], startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(25), addrs[4]); + await receiverStaking.mock.balanceOfSignerAtRanged + .withArgs(addrs[5], startEpoch, numberOfEpochs) + .returns(Array(numberOfEpochs).fill(25), addrs[4]); await ethSelector.mock.getClustersRanged.returns(Array(numberOfEpochs).fill([addrs[35], addrs[34], addrs[33], addrs[32], addrs[31]])); - + await clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo); - for(let i=0; i < numberOfEpochs; i++) { + for (let i = 0; i < numberOfEpochs; i++) { expect(await clusterRewards.isTicketsIssued(addrs[4], startEpoch + i)).to.be.true; expect(await clusterRewards.isTicketsIssued(addrs[5], startEpoch + i)).to.be.false; - expect((await clusterRewards.clusterRewards(addrs[31]))).to.be.closeTo(receiverRewards1[0].add(receiverRewards2[4]), 2); - expect((await clusterRewards.clusterRewards(addrs[32]))).to.be.closeTo(receiverRewards1[1].add(receiverRewards2[3]), 2); - expect((await clusterRewards.clusterRewards(addrs[33]))).to.be.closeTo(receiverRewards1[2].add(receiverRewards2[2]), 2); - expect((await clusterRewards.clusterRewards(addrs[34]))).to.be.closeTo(receiverRewards1[3].add(receiverRewards2[1]), 2); - expect((await clusterRewards.clusterRewards(addrs[35]))).to.be.closeTo(receiverRewards1[4].add(receiverRewards2[0]), 2); + expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo(receiverRewards1[0].add(receiverRewards2[4]), 2); + expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo(receiverRewards1[1].add(receiverRewards2[3]), 2); + expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo(receiverRewards1[2].add(receiverRewards2[2]), 2); + expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(receiverRewards2[1]), 2); + expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(receiverRewards2[0]), 2); } }); it("staker cannot submit compressed tickets multiple times for the same epoch", async function () { - const epochWithRewards = 33*86400/900 + 2; + const epochWithRewards = (33 * 86400) / 900 + 2; const numberOfEpochs = 10; let receiverRewards1: BN[] = []; - await skipToTimestamp(startTime + 34*86400); + await skipToTimestamp(startTime + 34 * 86400); const ticketsByEpoch: number[][] = []; let startEpoch = epochWithRewards; - let rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, '0'); + let rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, "0"); let totalTickets = 0; - - for(let i=0; i < numberOfEpochs; i++) { + + for (let i = 0; i < numberOfEpochs; i++) { ticketsByEpoch[i] = []; - for(let j=0; j < ticketsLength; j++) { - ticketsByEpoch[i][j] = parseInt((Math.random()*2^16/(ticketsLength+1))+""); + for (let j = 0; j < ticketsLength; j++) { + ticketsByEpoch[i][j] = parseInt(((Math.random() * 2) ^ (16 / (ticketsLength + 1))) + ""); totalTickets += ticketsByEpoch[i][j]; - rawTicketInfo = rawTicketInfo+ticketsByEpoch[i][j].toString(16).padStart(4, '0'); - if(!receiverRewards1[j]) receiverRewards1[j] = BN.from(0); + rawTicketInfo = rawTicketInfo + ticketsByEpoch[i][j].toString(16).padStart(4, "0"); + if (!receiverRewards1[j]) receiverRewards1[j] = BN.from(0); receiverRewards1[j] = receiverRewards1[j].add(ETH_REWARD.mul(50).div(500).mul(ticketsByEpoch[i][j]).div(MAX_TICKETS)); } - if(!receiverRewards1[ticketsLength]) receiverRewards1[ticketsLength] = BN.from(0); - receiverRewards1[ticketsLength] = receiverRewards1[ticketsLength].add(ETH_REWARD.mul(50).div(500).mul(MAX_TICKETS.sub(totalTickets)).div(MAX_TICKETS)); + if (!receiverRewards1[ticketsLength]) receiverRewards1[ticketsLength] = BN.from(0); + receiverRewards1[ticketsLength] = receiverRewards1[ticketsLength].add( + ETH_REWARD.mul(50).div(500).mul(MAX_TICKETS.sub(totalTickets)).div(MAX_TICKETS) + ); totalTickets = 0; } @@ -1277,12 +1259,14 @@ describe("ClusterRewards submit compressed tickets", function () { await receiverStaking.mock.totalSupplyAtRanged.revertsWithReason("unexpected query for total balance"); await receiverStaking.mock.totalSupplyAtRanged.withArgs(startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(500)); await receiverStaking.mock.balanceOfSignerAtRanged.revertsWithReason("unexpected query for signer balance"); - await receiverStaking.mock.balanceOfSignerAtRanged.withArgs(addrs[5], startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(50), addrs[4]); + await receiverStaking.mock.balanceOfSignerAtRanged + .withArgs(addrs[5], startEpoch, numberOfEpochs) + .returns(Array(numberOfEpochs).fill(50), addrs[4]); await ethSelector.mock.getClustersRanged.returns(Array(numberOfEpochs).fill([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]])); - + await clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo); - for(let i=0; i < numberOfEpochs; i++) { + for (let i = 0; i < numberOfEpochs; i++) { expect(await clusterRewards.isTicketsIssued(addrs[4], startEpoch + i)).to.be.true; expect(await clusterRewards.isTicketsIssued(addrs[5], startEpoch + i)).to.be.false; expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo(receiverRewards1[0], 1); @@ -1292,20 +1276,22 @@ describe("ClusterRewards submit compressed tickets", function () { expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4], 1); } - rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, '0'); + rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, "0"); totalTickets = 0; let receiverRewards2: BN[] = []; - for(let i=0; i < numberOfEpochs; i++) { + for (let i = 0; i < numberOfEpochs; i++) { ticketsByEpoch[i] = []; - for(let j=0; j < ticketsLength; j++) { - ticketsByEpoch[i][j] = parseInt((Math.random()*2^16/(ticketsLength+1))+""); + for (let j = 0; j < ticketsLength; j++) { + ticketsByEpoch[i][j] = parseInt(((Math.random() * 2) ^ (16 / (ticketsLength + 1))) + ""); totalTickets += ticketsByEpoch[i][j]; - rawTicketInfo = rawTicketInfo+ticketsByEpoch[i][j].toString(16).padStart(4, '0'); - if(!receiverRewards2[j]) receiverRewards2[j] = BN.from(0); - receiverRewards2[j]= receiverRewards2[j].add(ETH_REWARD.mul(25).mul(ticketsByEpoch[i][j]).div(125).div(MAX_TICKETS)); + rawTicketInfo = rawTicketInfo + ticketsByEpoch[i][j].toString(16).padStart(4, "0"); + if (!receiverRewards2[j]) receiverRewards2[j] = BN.from(0); + receiverRewards2[j] = receiverRewards2[j].add(ETH_REWARD.mul(25).mul(ticketsByEpoch[i][j]).div(125).div(MAX_TICKETS)); } - if(!receiverRewards2[ticketsLength]) receiverRewards2[ticketsLength] = BN.from(0); - receiverRewards2[ticketsLength] = receiverRewards2[ticketsLength].add(ETH_REWARD.mul(25).mul(MAX_TICKETS.sub(totalTickets)).div(125).div(MAX_TICKETS)); + if (!receiverRewards2[ticketsLength]) receiverRewards2[ticketsLength] = BN.from(0); + receiverRewards2[ticketsLength] = receiverRewards2[ticketsLength].add( + ETH_REWARD.mul(25).mul(MAX_TICKETS.sub(totalTickets)).div(125).div(MAX_TICKETS) + ); totalTickets = 0; } @@ -1313,35 +1299,41 @@ describe("ClusterRewards submit compressed tickets", function () { await receiverStaking.mock.totalSupplyAtRanged.revertsWithReason("unexpected query for total balance"); await receiverStaking.mock.totalSupplyAtRanged.withArgs(startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(125)); await receiverStaking.mock.balanceOfSignerAtRanged.revertsWithReason("unexpected query for signer balance"); - await receiverStaking.mock.balanceOfSignerAtRanged.withArgs(addrs[5], startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(25), addrs[4]); + await receiverStaking.mock.balanceOfSignerAtRanged + .withArgs(addrs[5], startEpoch, numberOfEpochs) + .returns(Array(numberOfEpochs).fill(25), addrs[4]); await ethSelector.mock.getClustersRanged.returns(Array(numberOfEpochs).fill([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]])); - - await expect(clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo)).to.be.revertedWith("CRW:IPRT-Tickets already issued"); + + await expect(clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo)).to.be.revertedWith( + "CRW:IPRT-Tickets already issued" + ); }); it("staker cannot submit signed tickets for future epochs", async function () { - const epochWithRewards = 33*86400/900 + 2; + const epochWithRewards = (33 * 86400) / 900 + 2; const numberOfEpochs = 10; let receiverRewards1: BN[] = []; - await skipToTimestamp(startTime + 34*86400); + await skipToTimestamp(startTime + 34 * 86400); const ticketsByEpoch: number[][] = []; let startEpoch = epochWithRewards; - let rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, '0'); + let rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, "0"); let totalTickets = 0; - - for(let i=0; i < numberOfEpochs; i++) { + + for (let i = 0; i < numberOfEpochs; i++) { ticketsByEpoch[i] = []; - for(let j=0; j < ticketsLength; j++) { - ticketsByEpoch[i][j] = parseInt((Math.random()*2^16/(ticketsLength+1))+""); + for (let j = 0; j < ticketsLength; j++) { + ticketsByEpoch[i][j] = parseInt(((Math.random() * 2) ^ (16 / (ticketsLength + 1))) + ""); totalTickets += ticketsByEpoch[i][j]; - rawTicketInfo = rawTicketInfo+ticketsByEpoch[i][j].toString(16).padStart(4, '0'); - if(!receiverRewards1[j]) receiverRewards1[j] = BN.from(0); + rawTicketInfo = rawTicketInfo + ticketsByEpoch[i][j].toString(16).padStart(4, "0"); + if (!receiverRewards1[j]) receiverRewards1[j] = BN.from(0); receiverRewards1[j] = receiverRewards1[j].add(ETH_REWARD.mul(50).mul(ticketsByEpoch[i][j]).div(500).div(MAX_TICKETS)); } - if(!receiverRewards1[ticketsLength]) receiverRewards1[ticketsLength] = BN.from(0); - receiverRewards1[ticketsLength] = receiverRewards1[ticketsLength].add(ETH_REWARD.mul(50).mul(MAX_TICKETS.sub(totalTickets)).div(500).div(MAX_TICKETS)); + if (!receiverRewards1[ticketsLength]) receiverRewards1[ticketsLength] = BN.from(0); + receiverRewards1[ticketsLength] = receiverRewards1[ticketsLength].add( + ETH_REWARD.mul(50).mul(MAX_TICKETS.sub(totalTickets)).div(500).div(MAX_TICKETS) + ); totalTickets = 0; } @@ -1349,32 +1341,41 @@ describe("ClusterRewards submit compressed tickets", function () { await receiverStaking.mock.totalSupplyAtRanged.revertsWithReason("unexpected query for total balance"); await receiverStaking.mock.totalSupplyAtRanged.withArgs(startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(500)); await receiverStaking.mock.balanceOfSignerAtRanged.revertsWithReason("unexpected query for signer balance"); - await receiverStaking.mock.balanceOfSignerAtRanged.withArgs(addrs[5], startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(50), addrs[4]); + await receiverStaking.mock.balanceOfSignerAtRanged + .withArgs(addrs[5], startEpoch, numberOfEpochs) + .returns(Array(numberOfEpochs).fill(50), addrs[4]); await ethSelector.mock.getClustersRanged.returns(Array(numberOfEpochs).fill([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]])); - - await expect(clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo)).to.be.revertedWith("CRW:ITC-Epochs not completed"); + + await expect(clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo)).to.be.revertedWith( + "CRW:ITC-Epochs not completed" + ); }); it("staker cannot submit partial tickets", async function () { - const epochWithRewards = 33*86400/900 + 2; + const epochWithRewards = (33 * 86400) / 900 + 2; const numberOfEpochs = 10; - await skipToTimestamp(startTime + 34*86400); + await skipToTimestamp(startTime + 34 * 86400); const ticketsByEpoch: number[][] = []; let startEpoch = epochWithRewards; - let rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, '0'); - - for(let i=0; i < numberOfEpochs; i++) { + let rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, "0"); + + for (let i = 0; i < numberOfEpochs; i++) { ticketsByEpoch[i] = []; - for(let j=0; j < ticketsLength; j++) { - ticketsByEpoch[i][j] = parseInt((Math.random()*2^16/(ticketsLength+1))+""); - if(FuzzedNumber.randomInRange(0, 100).toNumber() > 20) { // drop in 20% of cases - rawTicketInfo = rawTicketInfo+ticketsByEpoch[i][j].toString(16).padStart(4, '0'); + for (let j = 0; j < ticketsLength; j++) { + ticketsByEpoch[i][j] = parseInt(((Math.random() * 2) ^ (16 / (ticketsLength + 1))) + ""); + if (FuzzedNumber.randomInRange(0, 100).toNumber() > 20) { + // drop in 20% of cases + rawTicketInfo = rawTicketInfo + ticketsByEpoch[i][j].toString(16).padStart(4, "0"); } } - if((rawTicketInfo.length - 2 - 8 - 64)%16 == 0) { - rawTicketInfo = rawTicketInfo+parseInt((Math.random()*2^16/(ticketsLength+1))+"").toString(16).padStart(4, '0'); + if ((rawTicketInfo.length - 2 - 8 - 64) % 16 == 0) { + rawTicketInfo = + rawTicketInfo + + parseInt(((Math.random() * 2) ^ (16 / (ticketsLength + 1))) + "") + .toString(16) + .padStart(4, "0"); } } @@ -1382,27 +1383,31 @@ describe("ClusterRewards submit compressed tickets", function () { await receiverStaking.mock.totalSupplyAtRanged.revertsWithReason("unexpected query for total balance"); await receiverStaking.mock.totalSupplyAtRanged.withArgs(startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(500)); await receiverStaking.mock.balanceOfSignerAtRanged.revertsWithReason("unexpected query for signer balance"); - await receiverStaking.mock.balanceOfSignerAtRanged.withArgs(addrs[5], startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(50), addrs[4]); + await receiverStaking.mock.balanceOfSignerAtRanged + .withArgs(addrs[5], startEpoch, numberOfEpochs) + .returns(Array(numberOfEpochs).fill(50), addrs[4]); await ethSelector.mock.getClustersRanged.returns(Array(numberOfEpochs).fill([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]])); - - await expect(clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo)).to.be.revertedWith("CR:IPTI-invalid ticket info encoding"); + + await expect(clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo)).to.be.revertedWith( + "CR:IPTI-invalid ticket info encoding" + ); }); it("staker cannot submit excess tickets", async function () { - const epochWithRewards = 33*86400/900 + 2; + const epochWithRewards = (33 * 86400) / 900 + 2; const numberOfEpochs = 10; - await skipToTimestamp(startTime + 34*86400); + await skipToTimestamp(startTime + 34 * 86400); const ticketsByEpoch: number[][] = []; let startEpoch = epochWithRewards; - let rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, '0'); - - for(let i=0; i < numberOfEpochs; i++) { + let rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, "0"); + + for (let i = 0; i < numberOfEpochs; i++) { ticketsByEpoch[i] = []; ticketsByEpoch[i] = randomlyDivideInXPieces(MAX_TICKETS.add(1), ticketsLength).map((e) => e.toNumber()); - for(let j=0; j < ticketsLength; j++) { - rawTicketInfo = rawTicketInfo+ticketsByEpoch[i][j].toString(16).padStart(4, '0'); + for (let j = 0; j < ticketsLength; j++) { + rawTicketInfo = rawTicketInfo + ticketsByEpoch[i][j].toString(16).padStart(4, "0"); } } @@ -1410,28 +1415,32 @@ describe("ClusterRewards submit compressed tickets", function () { await receiverStaking.mock.totalSupplyAtRanged.revertsWithReason("unexpected query for total balance"); await receiverStaking.mock.totalSupplyAtRanged.withArgs(startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(500)); await receiverStaking.mock.balanceOfSignerAtRanged.revertsWithReason("unexpected query for signer balance"); - await receiverStaking.mock.balanceOfSignerAtRanged.withArgs(addrs[5], startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(50), addrs[4]); + await receiverStaking.mock.balanceOfSignerAtRanged + .withArgs(addrs[5], startEpoch, numberOfEpochs) + .returns(Array(numberOfEpochs).fill(50), addrs[4]); await ethSelector.mock.getClustersRanged.returns(Array(numberOfEpochs).fill([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]])); - - await expect(clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo)).to.be.revertedWith("CRW:IPRT-Total ticket count invalid"); + + await expect(clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo)).to.be.revertedWith( + "CRW:IPRT-Total ticket count invalid" + ); }); it("staker cannot submit signed tickets over maximum", async function () { - const epochWithRewards = 33*86400/900 + 2; + const epochWithRewards = (33 * 86400) / 900 + 2; const numberOfEpochs = 10; - await skipToTimestamp(startTime + 34*86400); + await skipToTimestamp(startTime + 34 * 86400); const ticketsByEpoch: number[][] = []; let startEpoch = epochWithRewards; - let rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, '0'); - - for(let i=0; i < numberOfEpochs; i++) { + let rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, "0"); + + for (let i = 0; i < numberOfEpochs; i++) { ticketsByEpoch[i] = []; ticketsByEpoch[i] = randomlyDivideInXPieces(MAX_TICKETS, ticketsLength + 1).map((e) => e.toNumber()); ticketsByEpoch[i][ticketsLength - 1] = MAX_TICKETS.add(1).toNumber(); - for(let j=0; j < ticketsLength; j++) { - rawTicketInfo = rawTicketInfo+ticketsByEpoch[i][j].toString(16).padStart(4, '0'); + for (let j = 0; j < ticketsLength; j++) { + rawTicketInfo = rawTicketInfo + ticketsByEpoch[i][j].toString(16).padStart(4, "0"); } } @@ -1439,11 +1448,15 @@ describe("ClusterRewards submit compressed tickets", function () { await receiverStaking.mock.totalSupplyAtRanged.revertsWithReason("unexpected query for total balance"); await receiverStaking.mock.totalSupplyAtRanged.withArgs(startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(500)); await receiverStaking.mock.balanceOfSignerAtRanged.revertsWithReason("unexpected query for signer balance"); - await receiverStaking.mock.balanceOfSignerAtRanged.withArgs(addrs[5], startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(50), addrs[4]); + await receiverStaking.mock.balanceOfSignerAtRanged + .withArgs(addrs[5], startEpoch, numberOfEpochs) + .returns(Array(numberOfEpochs).fill(50), addrs[4]); await ethSelector.mock.getClustersRanged.returns(Array(numberOfEpochs).fill([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]])); - + // revert expected because of overflow in args and thus decoder cant recognize format - await expect(clusterRewards.connect(signers[15])["issueTickets(bytes)"](rawTicketInfo)).to.be.revertedWith("CR:IPTI-invalid ticket info encoding"); + await expect(clusterRewards.connect(signers[15])["issueTickets(bytes)"](rawTicketInfo)).to.be.revertedWith( + "CR:IPTI-invalid ticket info encoding" + ); }); }); @@ -1469,15 +1482,7 @@ describe("ClusterRewards claim rewards", function () { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, - [ - addrs[0], - addrs[1], - receiverStaking.address, - NETWORK_IDS, - WEIGHTS, - [ethSelector.address, addrs[12], addrs[13]], - MAX_REWARD, - ], + [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [ethSelector.address, addrs[12], addrs[13]], MAX_REWARD], { kind: "uups" } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -1489,7 +1494,7 @@ describe("ClusterRewards claim rewards", function () { takeSnapshotBeforeAndAfterEveryTest(async () => {}); it("claimer can claim rewards", async function () { - const epochWithRewards = 33*86400/900 + 2; + const epochWithRewards = (33 * 86400) / 900 + 2; await receiverStaking.mock.balanceOfSignerAt.reverts(); await receiverStaking.mock.balanceOfSignerAt.withArgs(addrs[5], epochWithRewards).returns(50, addrs[4]); await receiverStaking.mock.getEpochInfo.reverts(); @@ -1497,7 +1502,7 @@ describe("ClusterRewards claim rewards", function () { await ethSelector.mock.getClusters.returns([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]); let receiverRewards1 = tickets.map((e) => ETH_REWARD.mul(50).mul(e).div(500).div(MAX_TICKETS)); - await skipToTimestamp(startTime + 34*86400); + await skipToTimestamp(startTime + 34 * 86400); await clusterRewards.connect(signers[5])["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, epochWithRewards, tickets.slice(0, -1)); expect(await clusterRewards.isTicketsIssued(addrs[4], epochWithRewards)).to.be.true; @@ -1529,7 +1534,7 @@ describe("ClusterRewards claim rewards", function () { }); it("non claimer cannot claim rewards", async function () { - const epochWithRewards = 33*86400/900 + 2; + const epochWithRewards = (33 * 86400) / 900 + 2; await receiverStaking.mock.balanceOfSignerAt.reverts(); await receiverStaking.mock.balanceOfSignerAt.withArgs(addrs[5], epochWithRewards).returns(50, addrs[4]); await receiverStaking.mock.getEpochInfo.reverts(); @@ -1537,7 +1542,7 @@ describe("ClusterRewards claim rewards", function () { await ethSelector.mock.getClusters.returns([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]); let receiverRewards1 = tickets.map((e) => ETH_REWARD.mul(50).mul(e).div(500).div(MAX_TICKETS)); - await skipToTimestamp(startTime + 34*86400); + await skipToTimestamp(startTime + 34 * 86400); await clusterRewards.connect(signers[5])["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, epochWithRewards, tickets.slice(0, -1)); expect(await clusterRewards.isTicketsIssued(addrs[4], epochWithRewards)).to.be.true; @@ -1548,7 +1553,106 @@ describe("ClusterRewards claim rewards", function () { expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3], 1); expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4], 1); - await expect(clusterRewards.claimReward(addrs[33])).to.be.revertedWith(`AccessControl: account ${addrs[0].toLowerCase()} is missing role ${await clusterRewards.CLAIMER_ROLE()}`); + await expect(clusterRewards.claimReward(addrs[33])).to.be.revertedWith( + `AccessControl: account ${addrs[0].toLowerCase()} is missing role ${await clusterRewards.CLAIMER_ROLE()}` + ); }); }); +describe("ClusterRewards: Add Receiver extra payment", function () { + let signers: Signer[]; + let addrs: string[]; + let receiverStaking: Contract; + let ethSelector: Contract; + let clusterRewards: ClusterRewards; + let mockToken: Contract; + + let staker: Signer; + let signer: Signer; + + before(async function () { + signers = await ethers.getSigners(); + addrs = await Promise.all(signers.map((a) => a.getAddress())); + + staker = signers[10]; + signer = signers[11]; + + const Pond = await ethers.getContractFactory("Pond"); + mockToken = await upgrades.deployProxy(Pond, ["Marlin", "POND"], { kind: "uups" }); + + const ReceiverStaking = await ethers.getContractFactory("ReceiverStaking"); + receiverStaking = await deployMockContract(signers[0], ReceiverStaking.interface.format()); + await receiverStaking.mock.START_TIME.returns(startTime); + await receiverStaking.mock.EPOCH_LENGTH.returns(900); + + const ClusterSelector = await ethers.getContractFactory("ClusterSelector"); + ethSelector = await deployMockContract(signers[0], ClusterSelector.interface.format()); + + const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); + let clusterRewardsContract = await upgrades.deployProxy( + ClusterRewards, + [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [ethSelector.address, addrs[12], addrs[13]], MAX_REWARD], + { kind: "uups" } + ); + clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); + + await clusterRewards.grantRole(clusterRewards.FEEDER_ROLE(), addrs[2]); + await clusterRewards.updateRewardWaitTime(43200); + + await receiverStaking.mock.STAKING_TOKEN.returns(mockToken.address); + await receiverStaking.mock.signerToStaker.withArgs(await signer.getAddress()).returns(await staker.getAddress()); + await receiverStaking.mock.stakerToSigner.withArgs(await staker.getAddress()).returns(await signer.getAddress()); + + await mockToken.transfer(await signer.getAddress(), 100000); + }); + + takeSnapshotBeforeAndAfterEveryTest(async () => {}); + + it("Add Extra Tokens", async () => { + await mockToken.connect(signer).approve(clusterRewards.address, 100000); + await expect(clusterRewards.connect(signer).addReceiverBalance(await staker.getAddress(), 100000)) + .emit(clusterRewards, "AddReceiverBalance") + .withArgs(await staker.getAddress(), 100000); + }); + + it("Remove remaining tokens", async () => { + await mockToken.connect(signer).approve(clusterRewards.address, 100000); + await clusterRewards.connect(signer).addReceiverBalance(await staker.getAddress(), 100000); + await expect(clusterRewards.connect(signer).removeReceiverBalance(await signer.getAddress(), 100000)) + .emit(clusterRewards, "RemoveReceiverBalance") + .withArgs(await staker.getAddress(), 100000); + }); + + it("Set Receiver Reward per Epoch", async () => { + await expect(clusterRewards.connect(signer).setReceiverRewardPerEpoch(50)) + .to.emit(clusterRewards, "UpdateReceiverRewardPerEpoch") + .withArgs(await staker.getAddress(), 50); + }); + + it("Reward Checking after receiver adds extra tokens", async () => { + await mockToken.connect(signer).approve(clusterRewards.address, 100000); + await clusterRewards.connect(signer).addReceiverBalance(await staker.getAddress(), 100000); + const receiverRewardPerEpoch = BN.from(500); + await clusterRewards.connect(signer).setReceiverRewardPerEpoch(receiverRewardPerEpoch); + + const epochWithRewards = (33 * 86400) / 900 + 2; + await ethSelector.mock.getClusters.returns([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]); + let receiverRewards1 = tickets.map((e) => ETH_REWARD.mul(50).mul(e).div(500).div(MAX_TICKETS)); + let extraRewards1 = tickets.map((e) => receiverRewardPerEpoch.mul(e).div(MAX_TICKETS)); + + await receiverStaking.mock.balanceOfSignerAt.reverts(); + await receiverStaking.mock.balanceOfSignerAt + .withArgs(await signer.getAddress(), epochWithRewards) + .returns(50, await staker.getAddress()); + await receiverStaking.mock.getEpochInfo.withArgs(epochWithRewards).returns(500, epochWithRewards + 3); + + await skipToTimestamp(startTime + 34 * 86400); + await clusterRewards.connect(signer)["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, epochWithRewards, tickets.slice(0, -1)); + + expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo(receiverRewards1[0].add(extraRewards1[0]), 1); + expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo(receiverRewards1[1].add(extraRewards1[1]), 1); + expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo(receiverRewards1[2].add(extraRewards1[2]), 1); + expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(extraRewards1[3]), 1); + expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 1); + }); +}); \ No newline at end of file diff --git a/test/token/MPond.ts b/test/token/MPond.ts index d739645b..69769236 100755 --- a/test/token/MPond.ts +++ b/test/token/MPond.ts @@ -2,8 +2,8 @@ import { ethers, upgrades, network } from "hardhat"; import { expect } from "chai"; import { BigNumber as BN, Signer, Contract } from "ethers"; -import { testERC165 } from "../helpers/erc165.ts"; -import { testAdminRole, testRole } from "../helpers/rbac.ts"; +import { testERC165 } from "../helpers/erc165"; +import { testAdminRole, testRole } from "../helpers/rbac"; declare module "ethers" { interface BigNumber { diff --git a/test/token/Pond.ts b/test/token/Pond.ts index ba5fa49b..73184e09 100755 --- a/test/token/Pond.ts +++ b/test/token/Pond.ts @@ -2,8 +2,8 @@ import { ethers, upgrades } from "hardhat"; import { expect } from "chai"; import { BigNumber as BN, Signer, Contract } from "ethers"; -import { testERC165 } from "../helpers/erc165.ts"; -import { testAdminRole, testRole } from "../helpers/rbac.ts"; +import { testERC165 } from "../helpers/erc165"; +import { testAdminRole, testRole } from "../helpers/rbac"; declare module "ethers" { interface BigNumber { From 6319f811db69ab23e20df9d5ced97e471afe9e43 Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Mon, 15 May 2023 11:19:42 +0400 Subject: [PATCH 02/30] remove feature to withdraw unused tokens --- contracts/staking/ClusterRewards.sol | 35 +++++++++++------ test/staking/ClusterRewards.ts | 56 ++++++++++++++++------------ test/staking/Integration.ts | 4 +- test/staking/RewardDelegators.ts | 20 +++++----- 4 files changed, 69 insertions(+), 46 deletions(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 4d3e6d0e..aa7f8e66 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -13,6 +13,8 @@ import "./interfaces/IClusterSelector.sol"; import "./ReceiverStaking.sol"; import "./interfaces/IClusterRewards.sol"; +import "./interfaces/IRewardDelegators.sol"; + contract ClusterRewards is Initializable, // initializer ContextUpgradeable, // _msgSender, _msgData @@ -29,7 +31,9 @@ contract ClusterRewards is /// @custom:oz-upgrades-unsafe-allow constructor // initializes the logic contract without any admins // safeguard against takeover of the logic contract - constructor() initializer { + constructor(IERC20Upgradeable _pondToken, IRewardDelegators _rewardDelegators) initializer { + PONDToken = _pondToken; + rewardDelegators = _rewardDelegators; } modifier onlyAdmin() { @@ -440,6 +444,14 @@ contract ClusterRewards is } //-------------------------------- User functions end --------------------------------// + + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + IERC20Upgradeable public immutable PONDToken; + + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + IRewardDelegators public immutable rewardDelegators; + + mapping(address => uint256) public receiverBalance; mapping(address => uint256) public receiverRewardPerEpoch; @@ -450,23 +462,22 @@ contract ClusterRewards is // @notice Anyone can add balance to receiver/staker address function addReceiverBalance(address receiver, uint256 amount) public { require(receiver != address(0), "CRW: address 0"); - IERC20Upgradeable PONDToken = receiverStaking.STAKING_TOKEN(); // Todo: shift this to constructor receiverBalance[receiver]+=amount; - PONDToken.transferFrom(msg.sender, address(this), amount); // Todo: transfer this directly to reward delegators, find a way when removeReceiveBalance is called + PONDToken.transferFrom(msg.sender, address(rewardDelegators), amount); // Todo: check reward delegators internal book keeping emit AddReceiverBalance(receiver, amount); } // @notice Receiver/Staker can remove the unused balance using his signer address // @dev This may be a attach vector, and hence could be removed - function removeReceiverBalance(address to, uint256 amount) public { - address staker = receiverStaking.signerToStaker(msg.sender); - require(staker != address(0), "CRW: address 0"); - IERC20Upgradeable PONDToken = receiverStaking.STAKING_TOKEN(); // Todo: shift this to constructor - receiverBalance[staker] -= amount; - PONDToken.transfer(to, amount); - - emit RemoveReceiverBalance(staker, amount); - } + // TODO Check if this removing the balance should be added as feature + // function removeReceiverBalance(address to, uint256 amount) public { + // address staker = receiverStaking.signerToStaker(msg.sender); + // require(staker != address(0), "CRW: address 0"); + // receiverBalance[staker] -= amount; + // PONDToken.transfer(to, amount); + + // emit RemoveReceiverBalance(staker, amount); + // } // @notice Set Receiver Epoch, set to 0, to disable extra rewards function setReceiverRewardPerEpoch(uint256 rewardPerEpoch) public { diff --git a/test/staking/ClusterRewards.ts b/test/staking/ClusterRewards.ts index 2f20ca30..1f957653 100644 --- a/test/staking/ClusterRewards.ts +++ b/test/staking/ClusterRewards.ts @@ -57,6 +57,8 @@ const tickets = [ ), ]; +const deadAddress = "0xdeaddeadabcdabcd000000001111111122222222"; + describe("ClusterRewards deploy and init", function () { let signers: Signer[]; let addrs: string[]; @@ -70,7 +72,7 @@ describe("ClusterRewards deploy and init", function () { it("deploys with initialization disabled", async function () { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewards = await ClusterRewards.deploy(); + let clusterRewards = await ClusterRewards.deploy(deadAddress, deadAddress); await expect( clusterRewards.initialize(addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD) @@ -84,13 +86,14 @@ describe("ClusterRewards deploy and init", function () { upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, [ETHWEIGHT, DOTWEIGHT], [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups" } + { kind: "uups", constructorArgs: [deadAddress, deadAddress] } ) ).to.be.revertedWith("CRW:I-Each NetworkId need a corresponding RewardPerEpoch and vice versa"); await expect( upgrades.deployProxy(ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12]], MAX_REWARD], { kind: "uups", + constructorArgs: [deadAddress, deadAddress], }) ).to.be.revertedWith("CRW:I-Each NetworkId need a corresponding clusterSelector and vice versa"); @@ -98,14 +101,14 @@ describe("ClusterRewards deploy and init", function () { upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], ethers.constants.AddressZero], MAX_REWARD], - { kind: "uups" } + { kind: "uups", constructorArgs: [deadAddress, deadAddress] } ) ).to.be.revertedWith("CRW:CN-ClusterSelector must exist"); const clusterRewards = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups" } + { kind: "uups", constructorArgs: [deadAddress, deadAddress] } ); expect(await clusterRewards.hasRole(await clusterRewards.DEFAULT_ADMIN_ROLE(), addrs[0])).to.be.true; @@ -123,12 +126,14 @@ describe("ClusterRewards deploy and init", function () { it("upgrades", async function () { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); + + // TODO: constructorArgs shouldn't be used here, as we need to refer to previous version const clusterRewards = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups" } + { kind: "uups", constructorArgs: [deadAddress, deadAddress] } ); - await upgrades.upgradeProxy(clusterRewards.address, ClusterRewards, { kind: "uups" }); + await upgrades.upgradeProxy(clusterRewards.address, ClusterRewards, { kind: "uups", constructorArgs: [deadAddress, deadAddress] }); expect(await clusterRewards.hasRole(await clusterRewards.DEFAULT_ADMIN_ROLE(), addrs[0])).to.be.true; expect(await clusterRewards.hasRole(await clusterRewards.CLAIMER_ROLE(), addrs[1])).to.be.true; @@ -148,11 +153,14 @@ describe("ClusterRewards deploy and init", function () { const clusterRewards = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups" } - ); - await expect(upgrades.upgradeProxy(clusterRewards.address, ClusterRewards.connect(signers[1]), { kind: "uups" })).to.be.revertedWith( - "only admin" + { kind: "uups", constructorArgs: [deadAddress, deadAddress] } ); + await expect( + upgrades.upgradeProxy(clusterRewards.address, ClusterRewards.connect(signers[1]), { + kind: "uups", + constructorArgs: [deadAddress, deadAddress], + }) + ).to.be.revertedWith("only admin"); }); }); @@ -163,7 +171,7 @@ testERC165( let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups" } + { kind: "uups", constructorArgs: [deadAddress, deadAddress] } ); let clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); return clusterRewards; @@ -185,7 +193,7 @@ testAdminRole("ClusterRewards admin role", async function (signers: Signer[], ad let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups" } + { kind: "uups", constructorArgs: [deadAddress, deadAddress] } ); let clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); return clusterRewards; @@ -198,7 +206,7 @@ testRole( let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups" } + { kind: "uups", constructorArgs: [deadAddress, deadAddress] } ); let clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); return clusterRewards; @@ -227,7 +235,7 @@ describe("ClusterRewards add network", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups" } + { kind: "uups", constructorArgs: [deadAddress, deadAddress] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); }); @@ -330,7 +338,7 @@ describe("ClusterRewards cluster selector", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups" } + { kind: "uups", constructorArgs: [deadAddress, deadAddress] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); }); @@ -451,7 +459,7 @@ describe("ClusterRewards remove network", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups" } + { kind: "uups", constructorArgs: [deadAddress, deadAddress] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); }); @@ -493,7 +501,7 @@ describe("ClusterRewards update global vars", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups" } + { kind: "uups", constructorArgs: [deadAddress, deadAddress] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); }); @@ -553,7 +561,7 @@ describe("ClusterRewards feed rewards", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], FEED_REWARD], - { kind: "uups" } + { kind: "uups", constructorArgs: [deadAddress, deadAddress] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -706,7 +714,7 @@ describe("ClusterRewards submit tickets", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [ethSelector.address, addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups" } + { kind: "uups", constructorArgs: [deadAddress, deadAddress] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -971,7 +979,7 @@ describe("ClusterRewards submit compressed tickets", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [ethSelector.address, addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups" } + { kind: "uups", constructorArgs: [deadAddress, deadAddress] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -1483,7 +1491,7 @@ describe("ClusterRewards claim rewards", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [ethSelector.address, addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups" } + { kind: "uups", constructorArgs: [deadAddress, deadAddress] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -1592,7 +1600,7 @@ describe("ClusterRewards: Add Receiver extra payment", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [ethSelector.address, addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups" } + { kind: "uups", constructorArgs: [mockToken.address, deadAddress] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -1615,7 +1623,7 @@ describe("ClusterRewards: Add Receiver extra payment", function () { .withArgs(await staker.getAddress(), 100000); }); - it("Remove remaining tokens", async () => { + it.skip("Remove remaining tokens (unskip it if remove balance is added as a feature)", async () => { await mockToken.connect(signer).approve(clusterRewards.address, 100000); await clusterRewards.connect(signer).addReceiverBalance(await staker.getAddress(), 100000); await expect(clusterRewards.connect(signer).removeReceiverBalance(await signer.getAddress(), 100000)) @@ -1655,4 +1663,4 @@ describe("ClusterRewards: Add Receiver extra payment", function () { expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(extraRewards1[3]), 1); expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 1); }); -}); \ No newline at end of file +}); diff --git a/test/staking/Integration.ts b/test/staking/Integration.ts index 034ac933..46523a9f 100644 --- a/test/staking/Integration.ts +++ b/test/staking/Integration.ts @@ -31,6 +31,8 @@ const UNDELEGATION_WAIT_TIME = 604800; const REDELEGATION_WAIT_TIME = 21600; const REWARD_PER_EPOCH = BN.from(10).pow(21).mul(35); +const deadAddress = "0xdeaddeadabcdabcd000000001111111122222222"; + describe("Integration", function () { let signers: Signer[]; @@ -170,7 +172,7 @@ describe("Integration", function () { rewardDelegators = getRewardDelegators(rewardDelegatorsContract.address, signers[0]); const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - const clusterRewardsContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false }); + const clusterRewardsContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress ,deadAddress] }); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); diff --git a/test/staking/RewardDelegators.ts b/test/staking/RewardDelegators.ts index d9597959..837759d1 100644 --- a/test/staking/RewardDelegators.ts +++ b/test/staking/RewardDelegators.ts @@ -36,6 +36,8 @@ BN.prototype.e18 = function () { return this.mul(BN.from(10).pow(18)); }; +const deadAddress = "0xdeaddeadabcdabcd000000001111111122222222"; + describe("RewardDelegators", function () { let signers: Signer[]; let addrs: string[]; @@ -60,7 +62,7 @@ describe("RewardDelegators", function () { mpondInstance = getMpond(mpondInstanceContract.address, signers[0]); const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); @@ -194,7 +196,7 @@ describe("RewardDelegators", function () { mpondTokenId = ethers.utils.keccak256(mpondInstance.address); const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); @@ -263,7 +265,7 @@ describe("RewardDelegators", function () { it("non owner cannot update ClusterRewards", async () => { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false }); + let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); await expect(rewardDelegators.connect(signers[1]).updateClusterRewards(clusterRewardsInstance2.address)).to.be.reverted; }); @@ -273,7 +275,7 @@ describe("RewardDelegators", function () { it("owner can update ClusterRewards", async () => { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false }); + let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); await expect(await rewardDelegators.updateClusterRewards(clusterRewardsInstance2.address)).to.emit( rewardDelegators, "ClusterRewardsAddressUpdated" @@ -325,7 +327,7 @@ describe("RewardDelegators", function () { mpondTokenId = ethers.utils.keccak256(mpondInstance.address); const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); @@ -490,7 +492,7 @@ describe("RewardDelegators", function () { mpondTokenId = ethers.utils.keccak256(mpondInstance.address); const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); @@ -799,7 +801,7 @@ describe("RewardDelegators Deployment", function () { rewardDelegatorsInstance = getRewardDelegators(rewardDelegatorsInstanceContract.address, signers[0]); const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); await expect( @@ -1194,7 +1196,7 @@ describe("RewardDelegators Deployment", function () { clusterRegistryInstance = getClusterRegistry(clusterRegistryInstanceContract.address, signers[0]); const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); @@ -1404,7 +1406,7 @@ describe("RewardDelegators Deployment", function () { it("update clusterReward address", async () => { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - const tempCLusterRewardInstance = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false }); + const tempCLusterRewardInstance = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); await expect(rewardDelegatorsInstance.connect(signers[1]).updateClusterRewards(tempCLusterRewardInstance.address)).to.be.reverted; await expect( From ac3cdfdc16e7046a9ada25dac2ea7b9f399e38ad Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Mon, 15 May 2023 13:36:31 +0400 Subject: [PATCH 03/30] fix tests (few pending) --- test/bridge/Bridge.ts | 6 +- test/staking/ClusterRegistry.ts | 98 +- test/staking/ClusterRewards.ts | 67 +- test/staking/Integration.ts | 19 +- test/staking/RewardDelegators.ts | 156 +- test/staking/RewardDelegatorsNew.ts | 2319 ++++++++++++++------------- 6 files changed, 1379 insertions(+), 1286 deletions(-) diff --git a/test/bridge/Bridge.ts b/test/bridge/Bridge.ts index 08c0b322..0ea54a2d 100644 --- a/test/bridge/Bridge.ts +++ b/test/bridge/Bridge.ts @@ -14,7 +14,7 @@ BN.prototype.e18 = function () { return this.mul(BN.from(10).pow(18)); }; -describe.skip("Bridge", function () { +describe("Bridge", function () { let signers: Signer[]; let addrs: string[]; let mpond: MPond; @@ -48,7 +48,7 @@ describe.skip("Bridge", function () { expect(await bridge.mpond()).to.equal(mpond.address); expect(await bridge.pond()).to.equal(pond.address); expect(await bridge.hasRole(await bridge.DEFAULT_ADMIN_ROLE(), addrs[0])).to.be.true; - expect(await bridge.hasRole(await bridge.GOVERNANCE_ROLE(), addrs[1])).to.be.true; + expect(await bridge.hasRole(await bridge.GOVERNANCE_ROLE(), addrs[1])).to.be.false; let currentBlockTimestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp; expect(await bridge.startTime()).to.equal(currentBlockTimestamp); expect(await bridge.liquidityStartTime()).to.equal(currentBlockTimestamp); @@ -66,7 +66,7 @@ describe.skip("Bridge", function () { expect(await bridge.mpond()).to.equal(mpond.address); expect(await bridge.pond()).to.equal(pond.address); expect(await bridge.hasRole(await bridge.DEFAULT_ADMIN_ROLE(), addrs[0])).to.be.true; - expect(await bridge.hasRole(await bridge.GOVERNANCE_ROLE(), addrs[1])).to.be.true; + expect(await bridge.hasRole(await bridge.GOVERNANCE_ROLE(), addrs[1])).to.be.false; let currentBlockTimestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp; expect(await bridge.startTime()).to.equal(currentBlockTimestamp - 1); expect(await bridge.liquidityStartTime()).to.equal(currentBlockTimestamp - 1); diff --git a/test/staking/ClusterRegistry.ts b/test/staking/ClusterRegistry.ts index 98a43679..10d5c9bb 100644 --- a/test/staking/ClusterRegistry.ts +++ b/test/staking/ClusterRegistry.ts @@ -3,13 +3,9 @@ import { expect } from "chai"; import { BigNumber as BN, Contract, Signer } from "ethers"; import { ethers, upgrades } from "hardhat"; -import { - ClusterRegistry, -} from "../../typechain-types"; +import { ClusterRegistry } from "../../typechain-types"; import { takeSnapshotBeforeAndAfterEveryTest } from "../../utils/testSuite"; -import { - getClusterRegistry, -} from "../../utils/typechainConvertor"; +import { getClusterRegistry } from "../../utils/typechainConvertor"; import { skipTime } from "../helpers/common"; import { testERC165 } from "../helpers/erc165"; import { testAdminRole, testRole } from "../helpers/rbac"; @@ -78,7 +74,9 @@ describe("ClusterRegistry", function () { const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); const clusterRegistry = await upgrades.deployProxy(ClusterRegistry, [WAIT_TIMES, addrs[11]], { kind: "uups" }); - await expect(upgrades.upgradeProxy(clusterRegistry.address, ClusterRegistry.connect(signers[1]), { kind: "uups" })).to.be.revertedWith("only admin"); + await expect(upgrades.upgradeProxy(clusterRegistry.address, ClusterRegistry.connect(signers[1]), { kind: "uups" })).to.be.revertedWith( + "only admin" + ); }); }); @@ -257,7 +255,7 @@ describe("ClusterRegistry", function () { await clusterRegistry.requestUnregister(); - await skipTime(ethers, WAIT_TIMES[2]); + await skipTime(ethers, WAIT_TIMES[2] + 1); // plus to ensure that it doesn't revert await clusterRegistry.unregister(); await rewardDelegators.mock.updateClusterDelegation.reverts(); @@ -302,11 +300,11 @@ describe("ClusterRegistry", function () { expect(clusterData.isValidCluster).to.be.true; await rewardDelegators.mock.updateClusterDelegation.reverts(); - { - let clusterData = await clusterRegistry.getRewardInfo(addrs[0]); - expect(clusterData[0]).to.equal(7); - expect(clusterData[1]).to.equal(addrs[11]); - } + { + let clusterData = await clusterRegistry.getRewardInfo(addrs[0]); + expect(clusterData[0]).to.equal(7); + expect(clusterData[1]).to.equal(addrs[11]); + } }); takeSnapshotBeforeAndAfterEveryTest(async () => {}); @@ -320,7 +318,7 @@ describe("ClusterRegistry", function () { expect(clusterData.clientKey).to.equal(addrs[22]); expect(clusterData.isValidCluster).to.be.true; - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); // +1 to ensure that is doesn't revert await clusterRegistry.updateCommission(); clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -354,7 +352,7 @@ describe("ClusterRegistry", function () { expect(clusterData.clientKey).to.equal(addrs[12]); expect(clusterData.isValidCluster).to.be.true; - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); await clusterRegistry.updateCommission(); clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -388,7 +386,7 @@ describe("ClusterRegistry", function () { expect(clusterData.clientKey).to.equal(addrs[22]); expect(clusterData.isValidCluster).to.be.true; - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); // +1 to ensure that is doesn;t revert await clusterRegistry.updateCommission(); clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -422,7 +420,7 @@ describe("ClusterRegistry", function () { expect(clusterData.clientKey).to.equal(addrs[22]); expect(clusterData.isValidCluster).to.be.true; - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); // +1 to ensure that it doesn;t revert await clusterRegistry.updateCommission(); clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -456,7 +454,7 @@ describe("ClusterRegistry", function () { expect(clusterData.clientKey).to.equal(addrs[22]); expect(clusterData.isValidCluster).to.be.true; - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); // +1 to ensure that it doesn't revert await expect(clusterRegistry.updateCommission()).to.be.revertedWith("CR:UCM-No commission update request"); clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -490,7 +488,7 @@ describe("ClusterRegistry", function () { expect(clusterData.clientKey).to.equal(addrs[12]); expect(clusterData.isValidCluster).to.be.true; - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); await clusterRegistry.updateCommission(); clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -524,7 +522,7 @@ describe("ClusterRegistry", function () { expect(clusterData.clientKey).to.equal(addrs[12]); expect(clusterData.isValidCluster).to.be.true; - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); await clusterRegistry.updateCommission(); clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -558,7 +556,7 @@ describe("ClusterRegistry", function () { expect(clusterData.clientKey).to.equal(addrs[22]); expect(clusterData.isValidCluster).to.be.true; - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); await clusterRegistry.updateCommission(); clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -592,7 +590,7 @@ describe("ClusterRegistry", function () { expect(clusterData.clientKey).to.equal(addrs[12]); expect(clusterData.isValidCluster).to.be.true; - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); await expect(clusterRegistry.updateCommission()).to.be.revertedWith("CR:UCM-No commission update request"); clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -626,7 +624,7 @@ describe("ClusterRegistry", function () { expect(clusterData.clientKey).to.equal(addrs[22]); expect(clusterData.isValidCluster).to.be.true; - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); await expect(clusterRegistry.updateCommission()).to.be.revertedWith("CR:UCM-No commission update request"); clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -660,7 +658,7 @@ describe("ClusterRegistry", function () { expect(clusterData.clientKey).to.equal(addrs[22]); expect(clusterData.isValidCluster).to.be.true; - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); await expect(clusterRegistry.updateCommission()).to.be.revertedWith("CR:UCM-No commission update request"); clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -694,7 +692,7 @@ describe("ClusterRegistry", function () { expect(clusterData.clientKey).to.equal(addrs[12]); expect(clusterData.isValidCluster).to.be.true; - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); await clusterRegistry.updateCommission(); clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -728,7 +726,7 @@ describe("ClusterRegistry", function () { expect(clusterData.clientKey).to.equal(addrs[12]); expect(clusterData.isValidCluster).to.be.true; - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); await expect(clusterRegistry.updateCommission()).to.be.revertedWith("CR:UCM-No commission update request"); clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -762,7 +760,7 @@ describe("ClusterRegistry", function () { expect(clusterData.clientKey).to.equal(addrs[12]); expect(clusterData.isValidCluster).to.be.true; - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); await expect(clusterRegistry.updateCommission()).to.be.revertedWith("CR:UCM-No commission update request"); clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -796,7 +794,7 @@ describe("ClusterRegistry", function () { expect(clusterData.clientKey).to.equal(addrs[22]); expect(clusterData.isValidCluster).to.be.true; - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); await expect(clusterRegistry.updateCommission()).to.be.revertedWith("CR:UCM-No commission update request"); clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -822,7 +820,12 @@ describe("ClusterRegistry", function () { }); it("can update nothing", async () => { - await clusterRegistry.updateCluster(ethers.constants.MaxUint256, ethers.constants.HashZero, ethers.constants.AddressZero, ethers.constants.AddressZero); + await clusterRegistry.updateCluster( + ethers.constants.MaxUint256, + ethers.constants.HashZero, + ethers.constants.AddressZero, + ethers.constants.AddressZero + ); let clusterData = await clusterRegistry.getCluster(addrs[0]); expect(clusterData.networkId).to.equal(DOTHASH); expect(clusterData.commission).to.equal(7); @@ -830,7 +833,7 @@ describe("ClusterRegistry", function () { expect(clusterData.clientKey).to.equal(addrs[12]); expect(clusterData.isValidCluster).to.be.true; - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); await expect(clusterRegistry.updateCommission()).to.be.revertedWith("CR:UCM-No commission update request"); clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -910,7 +913,7 @@ describe("ClusterRegistry", function () { await clusterRegistry.requestUnregister(); await rewardDelegators.mock.removeClusterDelegation.returns(); - await skipTime(ethers, WAIT_TIMES[2]); + await skipTime(ethers, WAIT_TIMES[2] + 1); await clusterRegistry.unregister(); await rewardDelegators.mock.removeClusterDelegation.reverts(); @@ -954,7 +957,7 @@ describe("ClusterRegistry", function () { it("can update commission after wait time", async () => { await clusterRegistry.requestCommissionUpdate(70); - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); await clusterRegistry.updateCommission(); @@ -969,7 +972,7 @@ describe("ClusterRegistry", function () { it("cannot update commission again after update", async () => { await clusterRegistry.requestCommissionUpdate(70); - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); await clusterRegistry.updateCommission(); @@ -1000,12 +1003,12 @@ describe("ClusterRegistry", function () { it("cannot update commission if unregistered after request", async () => { await clusterRegistry.requestCommissionUpdate(70); - await skipTime(ethers, WAIT_TIMES[0]); + await skipTime(ethers, WAIT_TIMES[0] + 1); await clusterRegistry.requestUnregister(); await rewardDelegators.mock.removeClusterDelegation.returns(); - await skipTime(ethers, WAIT_TIMES[2]); + await skipTime(ethers, WAIT_TIMES[2] + 1); await clusterRegistry.unregister(); await rewardDelegators.mock.removeClusterDelegation.reverts(); @@ -1064,7 +1067,7 @@ describe("ClusterRegistry", function () { await clusterRegistry.requestUnregister(); await rewardDelegators.mock.removeClusterDelegation.returns(); - await skipTime(ethers, WAIT_TIMES[2]); + await skipTime(ethers, WAIT_TIMES[2] + 1); await clusterRegistry.unregister(); await rewardDelegators.mock.removeClusterDelegation.reverts(); @@ -1109,7 +1112,7 @@ describe("ClusterRegistry", function () { it("can switch network after wait time", async () => { await clusterRegistry.requestNetworkSwitch(NEARHASH); - await skipTime(ethers, WAIT_TIMES[1]); + await skipTime(ethers, WAIT_TIMES[1] + 1); await rewardDelegators.mock.removeClusterDelegation.reverts(); await rewardDelegators.mock.removeClusterDelegation.withArgs(addrs[0], DOTHASH).returns(); @@ -1128,7 +1131,7 @@ describe("ClusterRegistry", function () { it("cannot switch network again after update", async () => { await clusterRegistry.requestNetworkSwitch(NEARHASH); - await skipTime(ethers, WAIT_TIMES[1]); + await skipTime(ethers, WAIT_TIMES[1] + 1); await rewardDelegators.mock.removeClusterDelegation.reverts(); await rewardDelegators.mock.removeClusterDelegation.withArgs(addrs[0], DOTHASH).returns(); @@ -1159,7 +1162,7 @@ describe("ClusterRegistry", function () { }); it("cannot switch network without request", async () => { - await skipTime(ethers, WAIT_TIMES[1]); + await skipTime(ethers, WAIT_TIMES[1] + 1); await rewardDelegators.mock.removeClusterDelegation.reverts(); await rewardDelegators.mock.removeClusterDelegation.withArgs(addrs[0], DOTHASH).returns(); @@ -1171,12 +1174,12 @@ describe("ClusterRegistry", function () { it("cannot switch network if unregistered after request", async () => { await clusterRegistry.requestNetworkSwitch(NEARHASH); - await skipTime(ethers, WAIT_TIMES[1]); + await skipTime(ethers, WAIT_TIMES[1] + 1); await clusterRegistry.requestUnregister(); await rewardDelegators.mock.removeClusterDelegation.returns(); - await skipTime(ethers, WAIT_TIMES[2]); + await skipTime(ethers, WAIT_TIMES[2] + 1); await clusterRegistry.unregister(); await rewardDelegators.mock.removeClusterDelegation.reverts(); @@ -1258,7 +1261,7 @@ describe("ClusterRegistry", function () { await clusterRegistry.requestUnregister(); await rewardDelegators.mock.removeClusterDelegation.returns(); - await skipTime(ethers, WAIT_TIMES[2]); + await skipTime(ethers, WAIT_TIMES[2] + 1); await clusterRegistry.unregister(); await rewardDelegators.mock.removeClusterDelegation.reverts(); @@ -1318,7 +1321,7 @@ describe("ClusterRegistry", function () { await clusterRegistry.requestUnregister(); await rewardDelegators.mock.removeClusterDelegation.returns(); - await skipTime(ethers, WAIT_TIMES[2]); + await skipTime(ethers, WAIT_TIMES[2] + 1); await clusterRegistry.unregister(); await rewardDelegators.mock.removeClusterDelegation.reverts(); @@ -1375,7 +1378,7 @@ describe("ClusterRegistry", function () { it("cannot request unregister if unregistered", async () => { await clusterRegistry.requestUnregister(); await rewardDelegators.mock.removeClusterDelegation.returns(); - await skipTime(ethers, WAIT_TIMES[2]); + await skipTime(ethers, WAIT_TIMES[2] + 1); await clusterRegistry.unregister(); await rewardDelegators.mock.removeClusterDelegation.reverts(); @@ -1420,7 +1423,7 @@ describe("ClusterRegistry", function () { await clusterRegistry.requestUnregister(); await rewardDelegators.mock.removeClusterDelegation.withArgs(addrs[0], DOTHASH).returns(); - await skipTime(ethers, WAIT_TIMES[2]); + await skipTime(ethers, WAIT_TIMES[2] + 1); await clusterRegistry.unregister(); await rewardDelegators.mock.removeClusterDelegation.withArgs(addrs[0], DOTHASH).reverts(); @@ -1432,7 +1435,7 @@ describe("ClusterRegistry", function () { await clusterRegistry.requestUnregister(); await rewardDelegators.mock.removeClusterDelegation.returns(); - await skipTime(ethers, WAIT_TIMES[2]); + await skipTime(ethers, WAIT_TIMES[2] + 1); await clusterRegistry.unregister(); let clusterData = await clusterRegistry.getCluster(addrs[0]); @@ -1452,9 +1455,8 @@ describe("ClusterRegistry", function () { it("cannot unregister without request", async () => { await rewardDelegators.mock.removeClusterDelegation.returns(); - await skipTime(ethers, WAIT_TIMES[2]); + await skipTime(ethers, WAIT_TIMES[2] + 1); await expect(clusterRegistry.unregister()).to.be.revertedWith("CR:UR-No unregistration request"); }); }); - diff --git a/test/staking/ClusterRewards.ts b/test/staking/ClusterRewards.ts index 1f957653..1153fb60 100644 --- a/test/staking/ClusterRewards.ts +++ b/test/staking/ClusterRewards.ts @@ -724,32 +724,32 @@ describe("ClusterRewards submit tickets", function () { takeSnapshotBeforeAndAfterEveryTest(async () => {}); - it.skip("delete me", async () => { - await ethSelector.mock.NUMBER_OF_CLUSTERS_TO_SELECT.returns(5); - let networkId = ethers.utils.id("ETH"); - let epochNumber = getRandomNumber(BN.from(20000)).toNumber(); - let noOfEpochs = 96; - let tickets: number[][] = []; - let rawTicketInfo = networkId + epochNumber.toString(16).padStart(8, "0"); - for (let i = 0; i < noOfEpochs * 4; i++) { - let j: number = parseInt(i / 4 + ""); - let k: number = i % 4; - if (!tickets[j]) tickets[j] = []; - tickets[j][k] = parseInt(Math.random() * 13000 + ""); - rawTicketInfo = rawTicketInfo + tickets[j][k].toString(16).padStart(4, "0"); - } - console.log(rawTicketInfo); - const data = await clusterRewards._parseTicketInfo(rawTicketInfo); - console.log(tickets); - expect(data.networkId).to.equal(networkId); - expect(data.fromEpoch).to.equal(epochNumber); - expect(data.noOfEpochs).to.equal(noOfEpochs); - tickets.map((e, i) => { - e.map((ele, j) => { - expect(tickets[i][j]).to.equal(data.tickets[i][j], `epoch ${i} ticket ${j} not equal`); - }); - }); - }); + // it.skip("delete me", async () => { + // await ethSelector.mock.NUMBER_OF_CLUSTERS_TO_SELECT.returns(5); + // let networkId = ethers.utils.id("ETH"); + // let epochNumber = getRandomNumber(BN.from(20000)).toNumber(); + // let noOfEpochs = 96; + // let tickets: number[][] = []; + // let rawTicketInfo = networkId + epochNumber.toString(16).padStart(8, "0"); + // for (let i = 0; i < noOfEpochs * 4; i++) { + // let j: number = parseInt(i / 4 + ""); + // let k: number = i % 4; + // if (!tickets[j]) tickets[j] = []; + // tickets[j][k] = parseInt(Math.random() * 13000 + ""); + // rawTicketInfo = rawTicketInfo + tickets[j][k].toString(16).padStart(4, "0"); + // } + // console.log(rawTicketInfo); + // const data = await clusterRewards._parseTicketInfo(rawTicketInfo); + // console.log(tickets); + // expect(data.networkId).to.equal(networkId); + // expect(data.fromEpoch).to.equal(epochNumber); + // expect(data.noOfEpochs).to.equal(noOfEpochs); + // tickets.map((e, i) => { + // e.map((ele, j) => { + // expect(tickets[i][j]).to.equal(data.tickets[i][j], `epoch ${i} ticket ${j} not equal`); + // }); + // }); + // }); it("staker can submit tickets before switch with zero rewards", async function () { await receiverStaking.mock.balanceOfSignerAt.reverts(); @@ -1623,13 +1623,14 @@ describe("ClusterRewards: Add Receiver extra payment", function () { .withArgs(await staker.getAddress(), 100000); }); - it.skip("Remove remaining tokens (unskip it if remove balance is added as a feature)", async () => { - await mockToken.connect(signer).approve(clusterRewards.address, 100000); - await clusterRewards.connect(signer).addReceiverBalance(await staker.getAddress(), 100000); - await expect(clusterRewards.connect(signer).removeReceiverBalance(await signer.getAddress(), 100000)) - .emit(clusterRewards, "RemoveReceiverBalance") - .withArgs(await staker.getAddress(), 100000); - }); + // TODO: add remove balance test if this feature is enabled + // it.skip("Remove remaining tokens (unskip it if remove balance is added as a feature)", async () => { + // await mockToken.connect(signer).approve(clusterRewards.address, 100000); + // await clusterRewards.connect(signer).addReceiverBalance(await staker.getAddress(), 100000); + // await expect(clusterRewards.connect(signer).removeReceiverBalance(await signer.getAddress(), 100000)) + // .emit(clusterRewards, "RemoveReceiverBalance") + // .withArgs(await staker.getAddress(), 100000); + // }); it("Set Receiver Reward per Epoch", async () => { await expect(clusterRewards.connect(signer).setReceiverRewardPerEpoch(50)) diff --git a/test/staking/Integration.ts b/test/staking/Integration.ts index 46523a9f..1d5e56de 100644 --- a/test/staking/Integration.ts +++ b/test/staking/Integration.ts @@ -104,6 +104,8 @@ describe("Integration", function () { await skipTime(ethers, epochDuration); await skipTime(ethers, 1); // extra 1 second for safety }; + + const startTime = Math.floor(Date.now()/1000) + 100000; before(async function() { this.timeout(400000); @@ -195,12 +197,11 @@ describe("Integration", function () { ClusterSelector, [ await clusterSelectorAdmin.getAddress(), - rewardDelegators.address, - BN.from(10).pow(20).toString(), + rewardDelegators.address ], { kind: "uups", - constructorArgs: [await receiverStaking.START_TIME(), await receiverStaking.EPOCH_LENGTH()], + constructorArgs: [startTime, 900, await signers[56].getAddress(), BN.from(10).pow(20), BN.from(10).pow(18)], } ); let clusterSelector = getClusterSelector(clusterSelectorContract.address, signers[0]); @@ -227,7 +228,9 @@ describe("Integration", function () { [pondTokenId, mpondTokenId], [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation] + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] ); await clusterRegistry.initialize([lockWaitTimes[0], lockWaitTimes[1], lockWaitTimes[2]], rewardDelegators.address); @@ -616,7 +619,7 @@ describe("Integration", function () { let currentEpoch = (await clusterSelectors[0].getCurrentEpoch()).toNumber(); await expect( - clusterRewards.connect(receiver)["issueTickets(bytes32,uint256,uint256[])"](supportedNetworkIds[0], currentEpoch, [fraction]) + clusterRewards.connect(receiver)["issueTickets(bytes32,uint24,uint16[])"](supportedNetworkIds[0], currentEpoch, [fraction]) ).to.be.revertedWith("CRW:IPRT-Not eligible to issue tickets"); }); @@ -635,7 +638,7 @@ describe("Integration", function () { const firstClusterTickets = totalTickets.mul(2).div(3); await expect( - clusterRewards.connect(ethReceivers[0])["issueTickets(bytes32,uint256,uint256[])"]( + clusterRewards.connect(ethReceivers[0])["issueTickets(bytes32,uint24,uint16[])"]( supportedNetworkIds[0], currentPlusOne, [firstClusterTickets, totalTickets.sub(firstClusterTickets).add(1)] @@ -643,7 +646,7 @@ describe("Integration", function () { ).to.be.revertedWith("CRW:IPRT-Total ticket count invalid"); await expect( - clusterRewards.connect(ethReceivers[0])["issueTickets(bytes32,uint256,uint256[])"]( + clusterRewards.connect(ethReceivers[0])["issueTickets(bytes32,uint24,uint16[])"]( supportedNetworkIds[0], currentPlusOne, [firstClusterTickets, totalTickets.sub(firstClusterTickets).sub(1)] @@ -1094,7 +1097,7 @@ const issueTicketsForClusters = async ( for (let index = 0; index < networkIds.length; index++) { const networkId = networkIds[index]; - await clusterRewardsInstance.connect(receiver)["issueTickets(bytes32,uint256,uint256[])"](networkId, currentPlusOne, new_weights); + await clusterRewardsInstance.connect(receiver)["issueTickets(bytes32,uint24,uint16[])"](networkId, currentPlusOne, new_weights); } const pondRewardsPerShare: BN[] = []; diff --git a/test/staking/RewardDelegators.ts b/test/staking/RewardDelegators.ts index 837759d1..5bc08767 100644 --- a/test/staking/RewardDelegators.ts +++ b/test/staking/RewardDelegators.ts @@ -62,7 +62,11 @@ describe("RewardDelegators", function () { mpondInstance = getMpond(mpondInstanceContract.address, signers[0]); const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [deadAddress, deadAddress], + }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); @@ -92,7 +96,9 @@ describe("RewardDelegators", function () { [pondTokenId, mpondTokenId], [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation] + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] ) ).to.be.reverted; }); @@ -111,7 +117,9 @@ describe("RewardDelegators", function () { [pondTokenId, mpondTokenId], [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation] + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] ); expect(await rewardDelegators.hasRole(await rewardDelegators.DEFAULT_ADMIN_ROLE(), addrs[0])).to.be.true; expect([await rewardDelegators.tokenList(0), await rewardDelegators.tokenList(1)]).to.eql([pondTokenId, mpondTokenId]); @@ -131,7 +139,9 @@ describe("RewardDelegators", function () { [pondTokenId, mpondTokenId], [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation] + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] ); await upgrades.upgradeProxy(rewardDelegators.address, RewardDelegators, { kind: "uups" }); expect(await rewardDelegators.hasRole(await rewardDelegators.DEFAULT_ADMIN_ROLE(), addrs[0])).to.be.true; @@ -160,7 +170,9 @@ describe("RewardDelegators", function () { [pondTokenId, mpondTokenId], [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation] + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] ); await expect( upgrades.upgradeProxy(rewardDelegators.address, RewardDelegators.connect(signers[1]), { @@ -196,7 +208,11 @@ describe("RewardDelegators", function () { mpondTokenId = ethers.utils.keccak256(mpondInstance.address); const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [deadAddress, deadAddress], + }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); @@ -222,7 +238,9 @@ describe("RewardDelegators", function () { ["" + pondTokenId, "" + mpondTokenId], [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation] + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] ); }); @@ -265,7 +283,11 @@ describe("RewardDelegators", function () { it("non owner cannot update ClusterRewards", async () => { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); + let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [deadAddress, deadAddress], + }); await expect(rewardDelegators.connect(signers[1]).updateClusterRewards(clusterRewardsInstance2.address)).to.be.reverted; }); @@ -275,7 +297,11 @@ describe("RewardDelegators", function () { it("owner can update ClusterRewards", async () => { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); + let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [deadAddress, deadAddress], + }); await expect(await rewardDelegators.updateClusterRewards(clusterRewardsInstance2.address)).to.emit( rewardDelegators, "ClusterRewardsAddressUpdated" @@ -327,7 +353,11 @@ describe("RewardDelegators", function () { mpondTokenId = ethers.utils.keccak256(mpondInstance.address); const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [deadAddress, deadAddress], + }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); @@ -352,7 +382,9 @@ describe("RewardDelegators", function () { ["" + pondTokenId, "" + mpondTokenId], [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation] + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] ); }); @@ -492,7 +524,11 @@ describe("RewardDelegators", function () { mpondTokenId = ethers.utils.keccak256(mpondInstance.address); const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [deadAddress, deadAddress], + }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); @@ -517,7 +553,9 @@ describe("RewardDelegators", function () { ["" + pondTokenId, "" + mpondTokenId], [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation] + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] ); const lockWaitTimes = [20, 21, 22]; @@ -532,10 +570,10 @@ describe("RewardDelegators", function () { let ClusterSelector = await ethers.getContractFactory("ClusterSelector"); let clusterSelectorContract = await upgrades.deployProxy( ClusterSelector, - [addrs[0], rewardDelegators.address, BigNumber.from(10).pow(20)], + [addrs[0], rewardDelegators.address], { kind: "uups", - constructorArgs: [blockData.timestamp, 4 * 3600], + constructorArgs: [blockData.timestamp, 4 * 3600, await signers[56].getAddress(), 1000, 100], } ); clusterSelector = getClusterSelector(clusterSelectorContract.address, signers[0]); @@ -600,9 +638,13 @@ describe("RewardDelegators", function () { await clusterRegistryInstance.connect(registeredCluster).register(networkId, commission, registeredClusterRewardAddress, clientKey1); await clusterRegistryInstance.connect(registeredCluster1).register(networkId, commission, registeredClusterRewardAddress1, clientKey2); await clusterRegistryInstance.connect(registeredCluster2).register(networkId, commission, registeredClusterRewardAddress2, clientKey3); - await clusterRegistryInstance.connect(registeredCluster3).register(ethers.utils.id("RANDOM"), commission, registeredClusterRewardAddress3, clientKey4); - await clusterRegistryInstance.connect(registeredCluster4).register(ethers.utils.id("RANDOM"), commission, registeredClusterRewardAddress4, clientKey5); - + await clusterRegistryInstance + .connect(registeredCluster3) + .register(ethers.utils.id("RANDOM"), commission, registeredClusterRewardAddress3, clientKey4); + await clusterRegistryInstance + .connect(registeredCluster4) + .register(ethers.utils.id("RANDOM"), commission, registeredClusterRewardAddress4, clientKey5); + const fakeClusterRegistry = signers[10]; await rewardDelegators.updateClusterRegistry(await fakeClusterRegistry.getAddress()); @@ -611,10 +653,12 @@ describe("RewardDelegators", function () { await rewardDelegators.connect(fakeClusterRegistry).updateClusterDelegation(await registeredCluster3.getAddress(), networkId); - await rewardDelegators.connect(fakeClusterRegistry).removeClusterDelegation(await registeredCluster4.getAddress(), ethers.utils.id("RANDOM")); + await rewardDelegators + .connect(fakeClusterRegistry) + .removeClusterDelegation(await registeredCluster4.getAddress(), ethers.utils.id("RANDOM")); await rewardDelegators.connect(fakeClusterRegistry).removeClusterDelegation(await registeredCluster1.getAddress(), networkId); - }) + }); it("refresh cluster delegation", async () => { // Check For Correct Update Case @@ -801,7 +845,11 @@ describe("RewardDelegators Deployment", function () { rewardDelegatorsInstance = getRewardDelegators(rewardDelegatorsInstanceContract.address, signers[0]); const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [deadAddress, deadAddress], + }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); await expect( @@ -813,7 +861,9 @@ describe("RewardDelegators Deployment", function () { [pondTokenId, mpondTokenId], [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation] + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] ) ).to.be.reverted; }); @@ -830,7 +880,7 @@ describe("RewardDelegators Deployment", function () { [pondTokenId, mpondTokenId], [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation],[] ,[] ], { kind: "uups" } ); @@ -859,14 +909,14 @@ describe("RewardDelegators Deployment", function () { await receiverStaking.initialize(addrs[0], "Receiver POND", "rPOND"); let ClusterSelector = await ethers.getContractFactory("ClusterSelector"); - let clusterSelectorContract = await upgrades.deployProxy(ClusterSelector, [ - addrs[0], - rewardDelegatorsInstance.address, - BigNumber.from(10).pow(20) - ], { - kind: "uups", - constructorArgs: [await receiverStaking.START_TIME(), await receiverStaking.EPOCH_LENGTH()] - }); + let clusterSelectorContract = await upgrades.deployProxy( + ClusterSelector, + [addrs[0], rewardDelegatorsInstance.address], + { + kind: "uups", + constructorArgs: [await receiverStaking.START_TIME(), await receiverStaking.EPOCH_LENGTH(), addrs[56], BigNumber.from(10).pow(20), 1000], + } + ); clusterSelectorInstance = getClusterSelector(clusterSelectorContract.address, signers[0]); await mpondInstance.grantRole(await mpondInstance.WHITELIST_ROLE(), stakeManagerInstance.address); @@ -1002,7 +1052,7 @@ describe("RewardDelegators Deployment", function () { await ethers.provider.send("evm_increaseTime", [4 * 60 * 61]); await ethers.provider.send("evm_mine", []); - await clusterRewardsInstance.connect(receiverSigner)["issueTickets(bytes32,uint256,uint256[])"](ethers.utils.id("DOT"), epoch, tickets); + await clusterRewardsInstance.connect(receiverSigner)["issueTickets(bytes32,uint24,uint16[])"](ethers.utils.id("DOT"), epoch, tickets); const clusterUpdatedReward = await clusterRewardsInstance.clusterRewards(await registeredCluster.getAddress()); expect(Number(clusterUpdatedReward)).equal(3333); @@ -1080,7 +1130,7 @@ describe("RewardDelegators Deployment", function () { await pondInstance.connect(receiverStaker).approve(receiverStaking.address, pondToUse); await receiverStaking.connect(receiverStaker).deposit(pondToUse); // 1 million pond - let [epoch, clusters] = (await mineTillGivenClusterIsSelected(clusterSelectorInstance, registeredCluster1, false)); + let [epoch, clusters] = await mineTillGivenClusterIsSelected(clusterSelectorInstance, registeredCluster1, false); const tickets: BigNumber[] = []; for (let i = 0; i < clusters.length; i++) { @@ -1089,7 +1139,7 @@ describe("RewardDelegators Deployment", function () { tickets.push(value); } - await clusterRewardsInstance.connect(receiverSigner)["issueTickets(bytes32,uint256,uint256[])"](ethers.utils.id("DOT"), epoch, tickets); + await clusterRewardsInstance.connect(receiverSigner)["issueTickets(bytes32,uint24,uint16[])"](ethers.utils.id("DOT"), epoch, tickets); await ethers.provider.send("evm_increaseTime", [24 * 60 * 60]); await ethers.provider.send("evm_mine", []); @@ -1100,7 +1150,7 @@ describe("RewardDelegators Deployment", function () { // expect(cluster2Reward).to.equal(Math.round((((4 + 2) / (10 + 2 + 4 + 2)) * stakingConfig.rewardPerEpoch) / 3)); // issue tickets have no link with total delegations to cluster, hence skipping it. - expect(cluster1Reward).to.eq((stakingConfig.rewardPerEpoch/3).toFixed(0)); + expect(cluster1Reward).to.eq((stakingConfig.rewardPerEpoch / 3).toFixed(0)); // do some delegations for both users to the cluster // rewards for one user is withdraw - this reward should be as per the time of oracle feed @@ -1148,7 +1198,7 @@ describe("RewardDelegators Deployment", function () { console.log({ tickets }); - await clusterRewardsInstance.connect(receiverSigner)["issueTickets(bytes32,uint256,uint256[])"](ethers.utils.id("DOT"), epoch, tickets); + await clusterRewardsInstance.connect(receiverSigner)["issueTickets(bytes32,uint24,uint16[])"](ethers.utils.id("DOT"), epoch, tickets); await ethers.provider.send("evm_increaseTime", [24 * 60 * 60]); await ethers.provider.send("evm_mine", []); @@ -1196,7 +1246,11 @@ describe("RewardDelegators Deployment", function () { clusterRegistryInstance = getClusterRegistry(clusterRegistryInstanceContract.address, signers[0]); const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [deadAddress, deadAddress], + }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); @@ -1211,7 +1265,7 @@ describe("RewardDelegators Deployment", function () { [testTokenId], [100], [1], - [1], + [1], [] ,[] ], { kind: "uups" } ); @@ -1221,14 +1275,14 @@ describe("RewardDelegators Deployment", function () { const blockData = await ethers.provider.getBlock("latest"); let ClusterSelector = await ethers.getContractFactory("ClusterSelector"); - let clusterSelectorContract = await upgrades.deployProxy(ClusterSelector, [ - addrs[0], - rewardDelegatorsInstance.address, - BigNumber.from(10).pow(20) - ], { - kind: "uups", - constructorArgs: [blockData.timestamp, 4*60*60] - }); + let clusterSelectorContract = await upgrades.deployProxy( + ClusterSelector, + [addrs[0], rewardDelegatorsInstance.address], + { + kind: "uups", + constructorArgs: [blockData.timestamp, 4 * 60 * 60, addrs[56], BigNumber.from(10).pow(20), 100], + } + ); clusterSelectorInstance = getClusterSelector(clusterSelectorContract.address, signers[0]); await rewardDelegatorsInstance.connect(signers[0]).updateClusterRewards(clusterRewardsInstance.address); @@ -1348,7 +1402,7 @@ describe("RewardDelegators Deployment", function () { tickets.push(value); } - await clusterRewardsInstance.connect(receiverSigner)["issueTickets(bytes32,uint256,uint256[])"](ethers.utils.id("DOT"), epoch, tickets); + await clusterRewardsInstance.connect(receiverSigner)["issueTickets(bytes32,uint24,uint16[])"](ethers.utils.id("DOT"), epoch, tickets); // await ethers.provider.send("evm_increaseTime", [24 * 60 * 60]); // await ethers.provider.send("evm_mine", []); @@ -1406,7 +1460,11 @@ describe("RewardDelegators Deployment", function () { it("update clusterReward address", async () => { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - const tempCLusterRewardInstance = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress, deadAddress] }); + const tempCLusterRewardInstance = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [deadAddress, deadAddress], + }); await expect(rewardDelegatorsInstance.connect(signers[1]).updateClusterRewards(tempCLusterRewardInstance.address)).to.be.reverted; await expect( @@ -1507,7 +1565,7 @@ describe("RewardDelegators Deployment", function () { registeredCluster: Signer, print: boolean, iter = 0 - ): Promise<[ currentEpoch: number, clusters: string[] ]> { + ): Promise<[currentEpoch: number, clusters: string[]]> { let currentEpoch = (await clusterSelector.getCurrentEpoch()).toNumber(); let registeredClusterAddress = (await registeredCluster.getAddress()).toLowerCase(); diff --git a/test/staking/RewardDelegatorsNew.ts b/test/staking/RewardDelegatorsNew.ts index d2f2108f..a97359b7 100644 --- a/test/staking/RewardDelegatorsNew.ts +++ b/test/staking/RewardDelegatorsNew.ts @@ -30,7 +30,7 @@ import { testAdminRole, testRole } from "../helpers/rbac"; const stakingConfig = require("../config/staking.json"); const START_DELAY = 100000; -const startTime = Math.floor(Date.now()/1000) + START_DELAY; +const startTime = Math.floor(Date.now() / 1000) + START_DELAY; const e14 = ethers.utils.parseEther("0.0001"); const e16 = ethers.utils.parseEther("0.01"); @@ -39,228 +39,173 @@ const e20 = ethers.utils.parseEther("100"); const e22 = ethers.utils.parseEther("10000"); const e30 = ethers.utils.parseEther("1000000000000"); +const deadAddress = "0xdeaddeadabcdabcd000000001111111122222222"; + describe("RewardDelegators init and upgrades", function () { - let signers: Signer[]; - let addrs: string[]; - - before(async function () { - signers = await ethers.getSigners(); - addrs = await Promise.all(signers.map((a) => a.getAddress())); - }); - - takeSnapshotBeforeAndAfterEveryTest(async () => {}); - - it("deploys with initialization disabled", async () => { - const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); - let rewardDelegators = await RewardDelegators.deploy(); - - await expect( - rewardDelegators.initialize( - addrs[10], - addrs[9], - addrs[8], - addrs[7], - [ethers.utils.id(addrs[7]), ethers.utils.id(addrs[6])], - [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], - [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], - [], - [] - ) - ).to.be.reverted; - }); - - it("deploys as proxy and initializes", async function () { - const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); - const rewardDelegatorsUntyped: Contract = await upgrades.deployProxy(RewardDelegators, { - kind: "uups", - initializer: false, - }); - const rewardDelegators: RewardDelegators = getRewardDelegators(rewardDelegatorsUntyped.address, signers[0]); - - const fakeStakeManagerAddr = addrs[10]; - const fakeClusterRewardsAddr = addrs[9]; - const fakeClusterRegistryAddr = addrs[8]; - const fakePond = addrs[7]; - const fakeMPond = addrs[6]; - const pondTokenId = ethers.utils.id(fakePond); - const mpondTokenId = ethers.utils.id(fakeMPond); - const initializationTx = expect(rewardDelegators.initialize( - fakeStakeManagerAddr, - fakeClusterRewardsAddr, - fakeClusterRegistryAddr, - fakePond, - [pondTokenId, mpondTokenId], - [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], - [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], - [], - [] - )) - await initializationTx.to.emit(rewardDelegators, "AddReward").withArgs(pondTokenId, stakingConfig.PondRewardFactor); - await initializationTx.to.emit(rewardDelegators, "AddReward").withArgs(mpondTokenId, stakingConfig.MPondRewardFactor); - await initializationTx.to.emit(rewardDelegators, "TokenWeightsUpdated").withArgs(pondTokenId, stakingConfig.PondWeightForThreshold, stakingConfig.PondWeightForDelegation); - await initializationTx.to.emit(rewardDelegators, "TokenWeightsUpdated").withArgs(mpondTokenId, stakingConfig.MPondWeightForThreshold, stakingConfig.MPondWeightForDelegation); - - expect(await rewardDelegators.stakeAddress()).to.equal(fakeStakeManagerAddr); - expect(await rewardDelegators.clusterRegistry()).to.equal(fakeClusterRegistryAddr); - expect(await rewardDelegators.clusterRewards()).to.equal(fakeClusterRewardsAddr); - expect(await rewardDelegators.PONDToken()).to.equal(fakePond); - expect(await rewardDelegators.rewardFactor(pondTokenId)).to.equal(stakingConfig.PondRewardFactor); - expect(await rewardDelegators.rewardFactor(mpondTokenId)).to.equal(stakingConfig.MPondRewardFactor); - const [pondWeightForDelegation, pondWeightForThreshold] = await rewardDelegators.tokenWeights(pondTokenId); - expect([pondWeightForDelegation.toNumber(), pondWeightForThreshold.toNumber()]).to.eql([parseInt(stakingConfig.PondWeightForThreshold), parseInt(stakingConfig.PondWeightForDelegation)]); - const [mpondWeightForDelegation, mpondWeightForThreshold] = await rewardDelegators.tokenWeights(mpondTokenId); - expect([mpondWeightForDelegation.toNumber(), mpondWeightForThreshold.toNumber()]).to.eql([parseInt(stakingConfig.MPondWeightForThreshold), parseInt(stakingConfig.MPondWeightForDelegation)]); - expect([ - (await rewardDelegators.tokenIndex(pondTokenId)).toNumber(), - (await rewardDelegators.tokenIndex(mpondTokenId)).toNumber()] - ).to.eql([0, 1]); - expect([await rewardDelegators.tokenList(0), await rewardDelegators.tokenList(1)]).to.eql([pondTokenId, mpondTokenId]); - expect(await rewardDelegators.hasRole(await rewardDelegators.DEFAULT_ADMIN_ROLE(), addrs[0])).to.be.true; - }); - - it("upgrades", async function () { - const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); - const rewardDelegatorsUntyped: Contract = await upgrades.deployProxy(RewardDelegators, { - kind: "uups", - initializer: false, - }); - const rewardDelegators: RewardDelegators = getRewardDelegators(rewardDelegatorsUntyped.address, signers[0]); - - const fakeStakeManagerAddr = addrs[10]; - const fakeClusterRewardsAddr = addrs[9]; - const fakeClusterRegistryAddr = addrs[8]; - const fakePond = addrs[7]; - const fakeMPond = addrs[6]; - const pondTokenId = ethers.utils.id(fakePond); - const mpondTokenId = ethers.utils.id(fakeMPond); - const initializationTx = expect(rewardDelegators.initialize( - fakeStakeManagerAddr, - fakeClusterRewardsAddr, - fakeClusterRegistryAddr, - fakePond, - [pondTokenId, mpondTokenId], - [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], - [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], - [], - [] - )) - await initializationTx.to.emit(rewardDelegators, "AddReward").withArgs(pondTokenId, stakingConfig.PondRewardFactor); - await initializationTx.to.emit(rewardDelegators, "AddReward").withArgs(mpondTokenId, stakingConfig.MPondRewardFactor); - await initializationTx.to.emit(rewardDelegators, "TokenWeightsUpdated").withArgs(pondTokenId, stakingConfig.PondWeightForThreshold, stakingConfig.PondWeightForDelegation); - await initializationTx.to.emit(rewardDelegators, "TokenWeightsUpdated").withArgs(mpondTokenId, stakingConfig.MPondWeightForThreshold, stakingConfig.MPondWeightForDelegation); - - const originalImplemetationAddress = await upgrades.erc1967.getImplementationAddress(rewardDelegators.address); - await upgrades.upgradeProxy(rewardDelegators, RewardDelegators, { kind: "uups" }); - // TODO: Find a diff way of upgrading with different contract - // expect(originalImplemetationAddress).to.not.equal(await upgrades.erc1967.getImplementationAddress(rewardDelegators.address)); - - expect(await rewardDelegators.stakeAddress()).to.equal(fakeStakeManagerAddr); - expect(await rewardDelegators.clusterRegistry()).to.equal(fakeClusterRegistryAddr); - expect(await rewardDelegators.clusterRewards()).to.equal(fakeClusterRewardsAddr); - expect(await rewardDelegators.PONDToken()).to.equal(fakePond); - expect(await rewardDelegators.rewardFactor(pondTokenId)).to.equal(stakingConfig.PondRewardFactor); - expect(await rewardDelegators.rewardFactor(mpondTokenId)).to.equal(stakingConfig.MPondRewardFactor); - const [pondWeightForDelegation, pondWeightForThreshold] = await rewardDelegators.tokenWeights(pondTokenId); - expect([pondWeightForDelegation.toNumber(), pondWeightForThreshold.toNumber()]).to.eql([parseInt(stakingConfig.PondWeightForThreshold), parseInt(stakingConfig.PondWeightForDelegation)]); - const [mpondWeightForDelegation, mpondWeightForThreshold] = await rewardDelegators.tokenWeights(mpondTokenId); - expect([mpondWeightForDelegation.toNumber(), mpondWeightForThreshold.toNumber()]).to.eql([parseInt(stakingConfig.MPondWeightForThreshold), parseInt(stakingConfig.MPondWeightForDelegation)]); - expect([ - (await rewardDelegators.tokenIndex(pondTokenId)).toNumber(), - (await rewardDelegators.tokenIndex(mpondTokenId)).toNumber()] - ).to.eql([0, 1]); - expect([await rewardDelegators.tokenList(0), await rewardDelegators.tokenList(1)]).to.eql([pondTokenId, mpondTokenId]); - expect(await rewardDelegators.hasRole(await rewardDelegators.DEFAULT_ADMIN_ROLE(), addrs[0])).to.be.true; + let signers: Signer[]; + let addrs: string[]; + + before(async function () { + signers = await ethers.getSigners(); + addrs = await Promise.all(signers.map((a) => a.getAddress())); + }); + + takeSnapshotBeforeAndAfterEveryTest(async () => {}); + + it("deploys with initialization disabled", async () => { + const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); + let rewardDelegators = await RewardDelegators.deploy(); + + await expect( + rewardDelegators.initialize( + addrs[10], + addrs[9], + addrs[8], + addrs[7], + [ethers.utils.id(addrs[7]), ethers.utils.id(addrs[6])], + [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], + [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] + ) + ).to.be.reverted; + }); + + it("deploys as proxy and initializes", async function () { + const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); + const rewardDelegatorsUntyped: Contract = await upgrades.deployProxy(RewardDelegators, { + kind: "uups", + initializer: false, }); - - it("does not upgrade without admin", async () => { - const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); - const rewardDelegatorsUntyped: Contract = await upgrades.deployProxy(RewardDelegators, { - kind: "uups", - initializer: false, - }); - const rewardDelegators: RewardDelegators = getRewardDelegators(rewardDelegatorsUntyped.address, signers[0]); - - const fakeStakeManagerAddr = addrs[10]; - const fakeClusterRewardsAddr = addrs[9]; - const fakeClusterRegistryAddr = addrs[8]; - const fakePond = addrs[7]; - const fakeMPond = addrs[6]; - const pondTokenId = ethers.utils.id(fakePond); - const mpondTokenId = ethers.utils.id(fakeMPond); - const initializationTx = expect(rewardDelegators.initialize( - fakeStakeManagerAddr, - fakeClusterRewardsAddr, - fakeClusterRegistryAddr, - fakePond, - [pondTokenId, mpondTokenId], - [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], - [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], - [], - [] - )) - await initializationTx.to.emit(rewardDelegators, "AddReward").withArgs(pondTokenId, stakingConfig.PondRewardFactor); - await initializationTx.to.emit(rewardDelegators, "AddReward").withArgs(mpondTokenId, stakingConfig.MPondRewardFactor); - await initializationTx.to.emit(rewardDelegators, "TokenWeightsUpdated").withArgs(pondTokenId, stakingConfig.PondWeightForThreshold, stakingConfig.PondWeightForDelegation); - await initializationTx.to.emit(rewardDelegators, "TokenWeightsUpdated").withArgs(mpondTokenId, stakingConfig.MPondWeightForThreshold, stakingConfig.MPondWeightForDelegation); - - await upgrades.upgradeProxy(rewardDelegators.address, RewardDelegators, { kind: "uups" }); - - await expect(upgrades.upgradeProxy(rewardDelegators.address, RewardDelegators.connect(signers[1]) ,{ kind: "uups" })).to.be.reverted; + const rewardDelegators: RewardDelegators = getRewardDelegators(rewardDelegatorsUntyped.address, signers[0]); + + const fakeStakeManagerAddr = addrs[10]; + const fakeClusterRewardsAddr = addrs[9]; + const fakeClusterRegistryAddr = addrs[8]; + const fakePond = addrs[7]; + const fakeMPond = addrs[6]; + const pondTokenId = ethers.utils.id(fakePond); + const mpondTokenId = ethers.utils.id(fakeMPond); + const initializationTx = expect( + rewardDelegators.initialize( + fakeStakeManagerAddr, + fakeClusterRewardsAddr, + fakeClusterRegistryAddr, + fakePond, + [pondTokenId, mpondTokenId], + [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], + [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] + ) + ); + await initializationTx.to.emit(rewardDelegators, "AddReward").withArgs(pondTokenId, stakingConfig.PondRewardFactor); + await initializationTx.to.emit(rewardDelegators, "AddReward").withArgs(mpondTokenId, stakingConfig.MPondRewardFactor); + await initializationTx.to + .emit(rewardDelegators, "TokenWeightsUpdated") + .withArgs(pondTokenId, stakingConfig.PondWeightForThreshold, stakingConfig.PondWeightForDelegation); + await initializationTx.to + .emit(rewardDelegators, "TokenWeightsUpdated") + .withArgs(mpondTokenId, stakingConfig.MPondWeightForThreshold, stakingConfig.MPondWeightForDelegation); + + expect(await rewardDelegators.stakeAddress()).to.equal(fakeStakeManagerAddr); + expect(await rewardDelegators.clusterRegistry()).to.equal(fakeClusterRegistryAddr); + expect(await rewardDelegators.clusterRewards()).to.equal(fakeClusterRewardsAddr); + expect(await rewardDelegators.PONDToken()).to.equal(fakePond); + expect(await rewardDelegators.rewardFactor(pondTokenId)).to.equal(stakingConfig.PondRewardFactor); + expect(await rewardDelegators.rewardFactor(mpondTokenId)).to.equal(stakingConfig.MPondRewardFactor); + const [pondWeightForDelegation, pondWeightForThreshold] = await rewardDelegators.tokenWeights(pondTokenId); + expect([pondWeightForDelegation.toNumber(), pondWeightForThreshold.toNumber()]).to.eql([ + parseInt(stakingConfig.PondWeightForThreshold), + parseInt(stakingConfig.PondWeightForDelegation), + ]); + const [mpondWeightForDelegation, mpondWeightForThreshold] = await rewardDelegators.tokenWeights(mpondTokenId); + expect([mpondWeightForDelegation.toNumber(), mpondWeightForThreshold.toNumber()]).to.eql([ + parseInt(stakingConfig.MPondWeightForThreshold), + parseInt(stakingConfig.MPondWeightForDelegation), + ]); + expect([ + (await rewardDelegators.tokenIndex(pondTokenId)).toNumber(), + (await rewardDelegators.tokenIndex(mpondTokenId)).toNumber(), + ]).to.eql([0, 1]); + expect([await rewardDelegators.tokenList(0), await rewardDelegators.tokenList(1)]).to.eql([pondTokenId, mpondTokenId]); + expect(await rewardDelegators.hasRole(await rewardDelegators.DEFAULT_ADMIN_ROLE(), addrs[0])).to.be.true; + }); + + it("upgrades", async function () { + const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); + const rewardDelegatorsUntyped: Contract = await upgrades.deployProxy(RewardDelegators, { + kind: "uups", + initializer: false, }); -}); + const rewardDelegators: RewardDelegators = getRewardDelegators(rewardDelegatorsUntyped.address, signers[0]); -testERC165("ReceiverStaking ERC165", async function (signers: Signer[], addrs: string[]) { - const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); - const rewardDelegatorsUntyped: Contract = await upgrades.deployProxy(RewardDelegators, { - kind: "uups", - initializer: false, - }); - const rewardDelegators: RewardDelegators = getRewardDelegators(rewardDelegatorsUntyped.address, signers[0]); - - const fakeStakeManagerAddr = addrs[10]; - const fakeClusterRewardsAddr = addrs[9]; - const fakeClusterRegistryAddr = addrs[8]; - const fakePond = addrs[7]; - const fakeMPond = addrs[6]; - const pondTokenId = ethers.utils.id(fakePond); - const mpondTokenId = ethers.utils.id(fakeMPond); - await rewardDelegators.initialize( - fakeStakeManagerAddr, - fakeClusterRewardsAddr, - fakeClusterRegistryAddr, - fakePond, - [pondTokenId, mpondTokenId], - [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], - [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], - [], - [] - ); - return rewardDelegators; - }, { - "IAccessControl": [ - "hasRole(bytes32,address)", - "getRoleAdmin(bytes32)", - "grantRole(bytes32,address)", - "revokeRole(bytes32,address)", - "renounceRole(bytes32,address)", - ], - "IAccessControlEnumerable": [ - "getRoleMember(bytes32,uint256)", - "getRoleMemberCount(bytes32)" - ], - } -); - -testAdminRole("ReceiverStaking Admin role", async function (signers: Signer[], addrs: string[]) { + const fakeStakeManagerAddr = addrs[10]; + const fakeClusterRewardsAddr = addrs[9]; + const fakeClusterRegistryAddr = addrs[8]; + const fakePond = addrs[7]; + const fakeMPond = addrs[6]; + const pondTokenId = ethers.utils.id(fakePond); + const mpondTokenId = ethers.utils.id(fakeMPond); + const initializationTx = expect( + rewardDelegators.initialize( + fakeStakeManagerAddr, + fakeClusterRewardsAddr, + fakeClusterRegistryAddr, + fakePond, + [pondTokenId, mpondTokenId], + [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], + [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] + ) + ); + await initializationTx.to.emit(rewardDelegators, "AddReward").withArgs(pondTokenId, stakingConfig.PondRewardFactor); + await initializationTx.to.emit(rewardDelegators, "AddReward").withArgs(mpondTokenId, stakingConfig.MPondRewardFactor); + await initializationTx.to + .emit(rewardDelegators, "TokenWeightsUpdated") + .withArgs(pondTokenId, stakingConfig.PondWeightForThreshold, stakingConfig.PondWeightForDelegation); + await initializationTx.to + .emit(rewardDelegators, "TokenWeightsUpdated") + .withArgs(mpondTokenId, stakingConfig.MPondWeightForThreshold, stakingConfig.MPondWeightForDelegation); + + const originalImplemetationAddress = await upgrades.erc1967.getImplementationAddress(rewardDelegators.address); + await upgrades.upgradeProxy(rewardDelegators, RewardDelegators, { kind: "uups" }); + // TODO: Find a diff way of upgrading with different contract + // expect(originalImplemetationAddress).to.not.equal(await upgrades.erc1967.getImplementationAddress(rewardDelegators.address)); + + expect(await rewardDelegators.stakeAddress()).to.equal(fakeStakeManagerAddr); + expect(await rewardDelegators.clusterRegistry()).to.equal(fakeClusterRegistryAddr); + expect(await rewardDelegators.clusterRewards()).to.equal(fakeClusterRewardsAddr); + expect(await rewardDelegators.PONDToken()).to.equal(fakePond); + expect(await rewardDelegators.rewardFactor(pondTokenId)).to.equal(stakingConfig.PondRewardFactor); + expect(await rewardDelegators.rewardFactor(mpondTokenId)).to.equal(stakingConfig.MPondRewardFactor); + const [pondWeightForDelegation, pondWeightForThreshold] = await rewardDelegators.tokenWeights(pondTokenId); + expect([pondWeightForDelegation.toNumber(), pondWeightForThreshold.toNumber()]).to.eql([ + parseInt(stakingConfig.PondWeightForThreshold), + parseInt(stakingConfig.PondWeightForDelegation), + ]); + const [mpondWeightForDelegation, mpondWeightForThreshold] = await rewardDelegators.tokenWeights(mpondTokenId); + expect([mpondWeightForDelegation.toNumber(), mpondWeightForThreshold.toNumber()]).to.eql([ + parseInt(stakingConfig.MPondWeightForThreshold), + parseInt(stakingConfig.MPondWeightForDelegation), + ]); + expect([ + (await rewardDelegators.tokenIndex(pondTokenId)).toNumber(), + (await rewardDelegators.tokenIndex(mpondTokenId)).toNumber(), + ]).to.eql([0, 1]); + expect([await rewardDelegators.tokenList(0), await rewardDelegators.tokenList(1)]).to.eql([pondTokenId, mpondTokenId]); + expect(await rewardDelegators.hasRole(await rewardDelegators.DEFAULT_ADMIN_ROLE(), addrs[0])).to.be.true; + }); + + it("does not upgrade without admin", async () => { const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); const rewardDelegatorsUntyped: Contract = await upgrades.deployProxy(RewardDelegators, { - kind: "uups", - initializer: false, + kind: "uups", + initializer: false, }); const rewardDelegators: RewardDelegators = getRewardDelegators(rewardDelegatorsUntyped.address, signers[0]); @@ -271,7 +216,8 @@ testAdminRole("ReceiverStaking Admin role", async function (signers: Signer[], a const fakeMPond = addrs[6]; const pondTokenId = ethers.utils.id(fakePond); const mpondTokenId = ethers.utils.id(fakeMPond); - await rewardDelegators.initialize( + const initializationTx = expect( + rewardDelegators.initialize( fakeStakeManagerAddr, fakeClusterRewardsAddr, fakeClusterRegistryAddr, @@ -282,969 +228,1052 @@ testAdminRole("ReceiverStaking Admin role", async function (signers: Signer[], a [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], [], [] + ) ); - return rewardDelegators; + await initializationTx.to.emit(rewardDelegators, "AddReward").withArgs(pondTokenId, stakingConfig.PondRewardFactor); + await initializationTx.to.emit(rewardDelegators, "AddReward").withArgs(mpondTokenId, stakingConfig.MPondRewardFactor); + await initializationTx.to + .emit(rewardDelegators, "TokenWeightsUpdated") + .withArgs(pondTokenId, stakingConfig.PondWeightForThreshold, stakingConfig.PondWeightForDelegation); + await initializationTx.to + .emit(rewardDelegators, "TokenWeightsUpdated") + .withArgs(mpondTokenId, stakingConfig.MPondWeightForThreshold, stakingConfig.MPondWeightForDelegation); + + await upgrades.upgradeProxy(rewardDelegators.address, RewardDelegators, { kind: "uups" }); + + await expect(upgrades.upgradeProxy(rewardDelegators.address, RewardDelegators.connect(signers[1]), { kind: "uups" })).to.be.reverted; + }); }); -describe("RewardDelegators global var updates", function () { - let signers: Signer[]; - let addrs: string[]; - - let rewardDelegators: RewardDelegators; - let pondTokenId: string; - let mpondTokenId: string; - - before(async function () { - signers = await ethers.getSigners(); - addrs = await Promise.all(signers.map((a) => a.getAddress())); - const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); - const rewardDelegatorsUntyped: Contract = await upgrades.deployProxy(RewardDelegators, { - kind: "uups", - initializer: false, - }); - rewardDelegators = getRewardDelegators(rewardDelegatorsUntyped.address, signers[0]); - - const fakeStakeManagerAddr = addrs[10]; - const fakeClusterRewardsAddr = addrs[9]; - const fakeClusterRegistryAddr = addrs[8]; - const fakePond = addrs[7]; - const fakeMPond = addrs[6]; - pondTokenId = ethers.utils.id(fakePond); - mpondTokenId = ethers.utils.id(fakeMPond); - await rewardDelegators.initialize( - fakeStakeManagerAddr, - fakeClusterRewardsAddr, - fakeClusterRegistryAddr, - fakePond, - [pondTokenId, mpondTokenId], - [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], - [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], - [], - [] - ); - return rewardDelegators; +testERC165( + "ReceiverStaking ERC165", + async function (signers: Signer[], addrs: string[]) { + const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); + const rewardDelegatorsUntyped: Contract = await upgrades.deployProxy(RewardDelegators, { + kind: "uups", + initializer: false, }); + const rewardDelegators: RewardDelegators = getRewardDelegators(rewardDelegatorsUntyped.address, signers[0]); - takeSnapshotBeforeAndAfterEveryTest(async () => {}); + const fakeStakeManagerAddr = addrs[10]; + const fakeClusterRewardsAddr = addrs[9]; + const fakeClusterRegistryAddr = addrs[8]; + const fakePond = addrs[7]; + const fakeMPond = addrs[6]; + const pondTokenId = ethers.utils.id(fakePond); + const mpondTokenId = ethers.utils.id(fakeMPond); + await rewardDelegators.initialize( + fakeStakeManagerAddr, + fakeClusterRewardsAddr, + fakeClusterRegistryAddr, + fakePond, + [pondTokenId, mpondTokenId], + [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], + [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] + ); + return rewardDelegators; + }, + { + IAccessControl: [ + "hasRole(bytes32,address)", + "getRoleAdmin(bytes32)", + "grantRole(bytes32,address)", + "revokeRole(bytes32,address)", + "renounceRole(bytes32,address)", + ], + IAccessControlEnumerable: ["getRoleMember(bytes32,uint256)", "getRoleMemberCount(bytes32)"], + } +); - it("non owner cannot update PondAddress", async () => { - const Pond = await ethers.getContractFactory("Pond"); - let pondInstance2 = await upgrades.deployProxy(Pond, ["Marlin", "POND"], { kind: "uups" }); - await expect(rewardDelegators.connect(signers[1]).updatePONDAddress(pondInstance2.address)).to.be.reverted; - }); +testAdminRole("ReceiverStaking Admin role", async function (signers: Signer[], addrs: string[]) { + const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); + const rewardDelegatorsUntyped: Contract = await upgrades.deployProxy(RewardDelegators, { + kind: "uups", + initializer: false, + }); + const rewardDelegators: RewardDelegators = getRewardDelegators(rewardDelegatorsUntyped.address, signers[0]); + + const fakeStakeManagerAddr = addrs[10]; + const fakeClusterRewardsAddr = addrs[9]; + const fakeClusterRegistryAddr = addrs[8]; + const fakePond = addrs[7]; + const fakeMPond = addrs[6]; + const pondTokenId = ethers.utils.id(fakePond); + const mpondTokenId = ethers.utils.id(fakeMPond); + await rewardDelegators.initialize( + fakeStakeManagerAddr, + fakeClusterRewardsAddr, + fakeClusterRegistryAddr, + fakePond, + [pondTokenId, mpondTokenId], + [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], + [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] + ); + return rewardDelegators; +}); - it("cannot update PondAddress to 0", async () => { - await expect(rewardDelegators.updatePONDAddress(ethers.constants.AddressZero)).to.be.reverted; - }); +describe("RewardDelegators global var updates", function () { + let signers: Signer[]; + let addrs: string[]; - it("owner can update PondAddress", async () => { - const Pond = await ethers.getContractFactory("Pond"); - let pondInstance2 = await upgrades.deployProxy(Pond, ["Marlin", "POND"], { kind: "uups" }); - await expect(rewardDelegators.updatePONDAddress(pondInstance2.address)).to.emit(rewardDelegators, "PONDAddressUpdated"); - }); + let rewardDelegators: RewardDelegators; + let pondTokenId: string; + let mpondTokenId: string; - it("non owner cannot update ClusterRegistry", async () => { - const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); - let clusterRegistryInstance2 = await upgrades.deployProxy(ClusterRegistry, { kind: "uups", initializer: false }); - await expect(rewardDelegators.connect(signers[1]).updateClusterRegistry(clusterRegistryInstance2.address)).to.be.reverted; + before(async function () { + signers = await ethers.getSigners(); + addrs = await Promise.all(signers.map((a) => a.getAddress())); + const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); + const rewardDelegatorsUntyped: Contract = await upgrades.deployProxy(RewardDelegators, { + kind: "uups", + initializer: false, }); + rewardDelegators = getRewardDelegators(rewardDelegatorsUntyped.address, signers[0]); - it("cannot update ClusterRegistry to 0", async () => { - await expect(rewardDelegators.updateClusterRegistry(ethers.constants.AddressZero)).to.be.reverted; + const fakeStakeManagerAddr = addrs[10]; + const fakeClusterRewardsAddr = addrs[9]; + const fakeClusterRegistryAddr = addrs[8]; + const fakePond = addrs[7]; + const fakeMPond = addrs[6]; + pondTokenId = ethers.utils.id(fakePond); + mpondTokenId = ethers.utils.id(fakeMPond); + await rewardDelegators.initialize( + fakeStakeManagerAddr, + fakeClusterRewardsAddr, + fakeClusterRegistryAddr, + fakePond, + [pondTokenId, mpondTokenId], + [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], + [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] + ); + return rewardDelegators; + }); + + takeSnapshotBeforeAndAfterEveryTest(async () => {}); + + it("non owner cannot update PondAddress", async () => { + const Pond = await ethers.getContractFactory("Pond"); + let pondInstance2 = await upgrades.deployProxy(Pond, ["Marlin", "POND"], { kind: "uups" }); + await expect(rewardDelegators.connect(signers[1]).updatePONDAddress(pondInstance2.address)).to.be.reverted; + }); + + it("cannot update PondAddress to 0", async () => { + await expect(rewardDelegators.updatePONDAddress(ethers.constants.AddressZero)).to.be.reverted; + }); + + it("owner can update PondAddress", async () => { + const Pond = await ethers.getContractFactory("Pond"); + let pondInstance2 = await upgrades.deployProxy(Pond, ["Marlin", "POND"], { kind: "uups" }); + await expect(rewardDelegators.updatePONDAddress(pondInstance2.address)).to.emit(rewardDelegators, "PONDAddressUpdated"); + }); + + it("non owner cannot update ClusterRegistry", async () => { + const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); + let clusterRegistryInstance2 = await upgrades.deployProxy(ClusterRegistry, { kind: "uups", initializer: false }); + await expect(rewardDelegators.connect(signers[1]).updateClusterRegistry(clusterRegistryInstance2.address)).to.be.reverted; + }); + + it("cannot update ClusterRegistry to 0", async () => { + await expect(rewardDelegators.updateClusterRegistry(ethers.constants.AddressZero)).to.be.reverted; + }); + + it("owner can update ClusterRegistry", async () => { + const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); + let clusterRegistryInstance2 = await upgrades.deployProxy(ClusterRegistry, { kind: "uups", initializer: false }); + await expect(await rewardDelegators.updateClusterRegistry(clusterRegistryInstance2.address)).to.emit( + rewardDelegators, + "ClusterRegistryUpdated" + ); + }); + + it("non owner cannot update ClusterRewards", async () => { + const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); + let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [deadAddress, deadAddress], }); - - it("owner can update ClusterRegistry", async () => { - const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); - let clusterRegistryInstance2 = await upgrades.deployProxy(ClusterRegistry, { kind: "uups", initializer: false }); - await expect(await rewardDelegators.updateClusterRegistry(clusterRegistryInstance2.address)).to.emit( - rewardDelegators, - "ClusterRegistryUpdated" - ); + await expect(rewardDelegators.connect(signers[1]).updateClusterRewards(clusterRewardsInstance2.address)).to.be.reverted; + }); + + it("cannot update ClusterRewards to 0", async () => { + await expect(rewardDelegators.updateClusterRewards(ethers.constants.AddressZero)).to.be.reverted; + }); + + it("owner can update ClusterRewards", async () => { + const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); + let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [deadAddress, deadAddress], }); + await expect(await rewardDelegators.updateClusterRewards(clusterRewardsInstance2.address)).to.emit( + rewardDelegators, + "ClusterRewardsAddressUpdated" + ); + }); + + it("non owner cannot update StakeManager", async () => { + const StakeManager = await ethers.getContractFactory("StakeManager"); + let stakeManagerInstance2 = await upgrades.deployProxy(StakeManager, { kind: "uups", initializer: false }); + await expect(rewardDelegators.connect(signers[1]).updateStakeAddress(stakeManagerInstance2.address)).to.be.reverted; + }); + + it("cannot update StakeManager to 0", async () => { + await expect(rewardDelegators.updateStakeAddress(ethers.constants.AddressZero)).to.be.reverted; + }); + + it("owner can update StakeManager", async () => { + const StakeManager = await ethers.getContractFactory("StakeManager"); + let stakeManagerInstance2 = await upgrades.deployProxy(StakeManager, { kind: "uups", initializer: false }); + await expect(await rewardDelegators.updateStakeAddress(stakeManagerInstance2.address)).to.emit(rewardDelegators, "StakeAddressUpdated"); + }); + + it("non owner cannot add rewardFactor", async () => { + let testTokenId = await ethers.utils.keccak256(addrs[0]); + await expect(rewardDelegators.connect(signers[1]).addRewardFactor(testTokenId, 1)).to.be.reverted; + }); + + it(" cannot add for already existing tokenId", async () => { + await expect(rewardDelegators.addRewardFactor("" + pondTokenId, stakingConfig.PondRewardFactor)).to.be.reverted; + }); + + it("cannot add 0 reward Factor", async () => { + let testTokenId = await ethers.utils.keccak256(addrs[0]); + await expect(rewardDelegators.addRewardFactor(testTokenId, 0)).to.be.reverted; + }); + + it("owner can add rewardFactor", async () => { + let testTokenId = await ethers.utils.keccak256(addrs[0]); + await expect(await rewardDelegators.addRewardFactor(testTokenId, 1)).to.emit(rewardDelegators, "AddReward"); + }); + + it("non owner cannot remove rewardFactor", async () => { + await expect(rewardDelegators.connect(signers[1]).removeRewardFactor("" + pondTokenId)).to.be.reverted; + }); + + it("cannot remove non existing tokenId", async () => { + let testTokenId = await ethers.utils.keccak256(addrs[0]); + await expect(rewardDelegators.removeRewardFactor(testTokenId)).to.be.reverted; + }); + + it("owner can remove rewardFactor", async () => { + await expect(await rewardDelegators.removeRewardFactor("" + pondTokenId)).to.emit(rewardDelegators, "RemoveReward"); + }); + + it("non owner cannot update reward Factor", async () => { + await expect(rewardDelegators.connect(signers[1]).updateRewardFactor("" + pondTokenId, stakingConfig.PondRewardFactor + 1)).to.be + .reverted; + }); + + it("cannot update non existing tokenId", async () => { + let testTokenId = await ethers.utils.keccak256(addrs[0]); + await expect(rewardDelegators.updateRewardFactor(testTokenId, 1)).to.be.reverted; + }); + + it("cannot update rewardFactor to 0", async () => { + await expect(rewardDelegators.updateRewardFactor("" + pondTokenId, 0)).to.be.reverted; + }); + + it("owner can update rewardFactor", async () => { + await expect(await rewardDelegators.updateRewardFactor("" + pondTokenId, stakingConfig.PondRewardFactor + 1)).to.emit( + rewardDelegators, + "RewardsUpdated" + ); + }); +}); - it("non owner cannot update ClusterRewards", async () => { - const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false }); - await expect(rewardDelegators.connect(signers[1]).updateClusterRewards(clusterRewardsInstance2.address)).to.be.reverted; - }); +describe("RewardDelegators ", function () { + let signers: Signer[]; + let addrs: string[]; - it("cannot update ClusterRewards to 0", async () => { - await expect(rewardDelegators.updateClusterRewards(ethers.constants.AddressZero)).to.be.reverted; - }); + let registeredClusters: Signer[]; + let registeredClusterRewardAddresses: string[]; + let clientKeys: string[]; + let delegators: Signer[]; - it("owner can update ClusterRewards", async () => { - const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false }); - await expect(await rewardDelegators.updateClusterRewards(clusterRewardsInstance2.address)).to.emit( - rewardDelegators, - "ClusterRewardsAddressUpdated" - ); - }); + let rewardDelegators: RewardDelegators; + let fakeStakeManager: MockContract; + let imperonatedStakeManager: Signer; + let fakeClusterRewards: MockContract; + let fakeClusterRegistry: MockContract; + let impersonatedClusterRegistry: Signer; - it("non owner cannot update StakeManager", async () => { - const StakeManager = await ethers.getContractFactory("StakeManager"); - let stakeManagerInstance2 = await upgrades.deployProxy(StakeManager, { kind: "uups", initializer: false }); - await expect(rewardDelegators.connect(signers[1]).updateStakeAddress(stakeManagerInstance2.address)).to.be.reverted; - }); + let pond: Pond; + let pondTokenId: string; + let mpondTokenId: string; - it("cannot update StakeManager to 0", async () => { - await expect(rewardDelegators.updateStakeAddress(ethers.constants.AddressZero)).to.be.reverted; - }); + let fakeClusterSelectors: Record = {}; - it("owner can update StakeManager", async () => { - const StakeManager = await ethers.getContractFactory("StakeManager"); - let stakeManagerInstance2 = await upgrades.deployProxy(StakeManager, { kind: "uups", initializer: false }); - await expect(await rewardDelegators.updateStakeAddress(stakeManagerInstance2.address)).to.emit( - rewardDelegators, - "StakeAddressUpdated" - ); - }); + before(async function () { + signers = await ethers.getSigners(); + addrs = await Promise.all(signers.map((a) => a.getAddress())); - it("non owner cannot add rewardFactor", async () => { - let testTokenId = await ethers.utils.keccak256(addrs[0]); - await expect(rewardDelegators.connect(signers[1]).addRewardFactor(testTokenId, 1)).to.be.reverted; - }); + registeredClusters = signers.slice(20, 30); + registeredClusterRewardAddresses = addrs.slice(30, 40); + clientKeys = addrs.slice(40, 50); + delegators = signers.slice(50, 60); - it(" cannot add for already existing tokenId", async () => { - await expect(rewardDelegators.addRewardFactor("" + pondTokenId, stakingConfig.PondRewardFactor)).to.be.reverted; + const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); + const rewardDelegatorsUntyped: Contract = await upgrades.deployProxy(RewardDelegators, { + kind: "uups", + initializer: false, }); + rewardDelegators = getRewardDelegators(rewardDelegatorsUntyped.address, signers[0]); + + const StakeManager = await ethers.getContractFactory("StakeManager"); + // setting address while deploying mock is not yet implemented in ethereum-waffle yet + fakeStakeManager = await deployMockContract(signers[9], StakeManager.interface.format()); + imperonatedStakeManager = await impersonate(ethers, fakeStakeManager.address); + // mocking receive function is not implemented in ethereum-waffle yet + await setBalance(ethers, fakeStakeManager.address, e18); + const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); + fakeClusterRewards = await deployMockContract(signers[8], ClusterRewards.interface.format()); + const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); + fakeClusterRegistry = await deployMockContract(signers[7], ClusterRegistry.interface.format()); + impersonatedClusterRegistry = await impersonate(ethers, fakeClusterRegistry.address); + await setBalance(ethers, fakeClusterRegistry.address, e18); + const Pond = await ethers.getContractFactory("Pond"); + const pondUntyped = await upgrades.deployProxy(Pond, ["Marlin", "POND"], { kind: "uups" }); + pond = getPond(pondUntyped.address, signers[0]); + const fakeMPond = addrs[6]; + pondTokenId = ethers.utils.id(pond.address); + mpondTokenId = ethers.utils.id(fakeMPond); + await rewardDelegators.initialize( + fakeStakeManager.address, + fakeClusterRewards.address, + fakeClusterRegistry.address, + pond.address, + [pondTokenId, mpondTokenId], + [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], + [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] + ); - it("cannot add 0 reward Factor", async () => { - let testTokenId = await ethers.utils.keccak256(addrs[0]); - await expect(rewardDelegators.addRewardFactor(testTokenId, 0)).to.be.reverted; - }); + await pond.transfer(rewardDelegators.address, ethers.utils.parseEther("100000")); + + const clusterSelector = await ethers.getContractFactory("ClusterSelector"); + await fakeClusterRewards.mock.clusterSelectors.returns(ethers.constants.AddressZero); + fakeClusterSelectors["ETH"] = await deployMockContract(signers[5], clusterSelector.interface.format()); + await fakeClusterRewards.mock.clusterSelectors.withArgs(ethers.utils.id("ETH")).returns(fakeClusterSelectors["ETH"].address); + fakeClusterSelectors["DOT"] = await deployMockContract(signers[4], clusterSelector.interface.format()); + await fakeClusterRewards.mock.clusterSelectors.withArgs(ethers.utils.id("DOT")).returns(fakeClusterSelectors["DOT"].address); + }); + + takeSnapshotBeforeAndAfterEveryTest(async () => {}); + + // NOTE: Reward factor doesn't do anything rn + + it("delegate to cluster single token", async () => { + const delegator = await delegators[0].getAddress(); + const delegator1 = await delegators[1].getAddress(); + const cluster = await registeredClusters[FuzzedNumber.randomInRange(0, registeredClusters.length).toNumber()].getAddress(); + const rewardAddress = + registeredClusterRewardAddresses[FuzzedNumber.randomInRange(0, registeredClusterRewardAddresses.length).toNumber()]; + const networkId = ethers.utils.id("ETH"); + + let rewardAmount = FuzzedNumber.randomInRange(100000, 500000); + let commission = FuzzedNumber.randomInRange(0, 100); + await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(rewardAmount); + await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster).returns(commission, rewardAddress); + await fakeClusterRegistry.mock.getNetwork.withArgs(cluster).returns(networkId); + // TODO: only upsert with `cluster` as arg are accepted + await fakeClusterSelectors["ETH"].mock.upsert.returns(); + + // ----------------- Give reward when previous total delegation and user has no prev rewards --------------- + const amount1 = FuzzedNumber.randomInRange(e16, e18); + await expect(rewardDelegators.delegate(delegator, cluster, [pondTokenId], [amount1])).to.be.revertedWith( + "RD:OS-only stake contract can invoke" + ); - it("owner can add rewardFactor", async () => { - let testTokenId = await ethers.utils.keccak256(addrs[0]); - await expect(await rewardDelegators.addRewardFactor(testTokenId, 1)).to.emit(rewardDelegators, "AddReward"); - }); + let clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + let delegationInit = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); + let rewardPerShare = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + await expect( + rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster, [pondTokenId], [amount1]) + ).to.changeTokenBalances( + pond, + [rewardDelegators, rewardAddress, delegator], + [-commission.mul(rewardAmount).div(100), commission.mul(rewardAmount).div(100), 0] + ); + let clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + let delegation = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); + let rewardPerShare1 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + + expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount1)); + expect(delegation).to.equal(delegationInit.add(amount1)); + // as there is only pond token all reward is given to pond token + expect(rewardPerShare1.sub(rewardPerShare)).to.equal(0); + + // ----------------- Give reward when previous total delegation is non 0 and user has no prev rewards --------------- + clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + delegationInit = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); + let clusterCommission = commission.mul(rewardAmount).div(100); + await expect( + rewardDelegators.connect(imperonatedStakeManager).delegate(delegator1, cluster, [pondTokenId], [amount1]) + ).to.changeTokenBalances(pond, [rewardDelegators, rewardAddress, delegator1], [-clusterCommission, clusterCommission, 0]); + clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + delegation = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); + const rewardPerShare2 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + + expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount1)); + expect(delegation).to.equal(delegationInit.add(amount1)); + // as there is only pond token all reward is given to pond token + expect(rewardPerShare2.sub(rewardPerShare1)).to.equal(rewardAmount.sub(clusterCommission).mul(e30).div(clusterDelegationInit)); + + // ----------------- Gives reward when previous delegation is non 0 and user has prev rewards, no new rewards for cluster --------------- + await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(0); + + clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + delegationInit = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); + let delegatorCurrentReward = rewardPerShare2.sub(rewardPerShare).mul(delegationInit).div(e30); + await expect( + rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster, [pondTokenId], [amount1]) + ).to.changeTokenBalances(pond, [rewardDelegators, rewardAddress, delegator], [-delegatorCurrentReward, 0, delegatorCurrentReward]); + clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + delegation = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); + const rewardPerShare3 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + + expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount1)); + expect(delegation).to.equal(delegationInit.add(amount1)); + expect(rewardPerShare3).to.equal(rewardPerShare2); + + // ----------------- Give new rewards to cluster --------------- + const rewardAmount1 = rewardAmount.mul(2).div(3); + await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(rewardAmount1); + clusterCommission = rewardAmount1.mul(commission).div(100); + await expect(rewardDelegators._updateRewards(cluster)).to.changeTokenBalances( + pond, + [rewardDelegators, rewardAddress, delegator, delegator1], + [-clusterCommission, clusterCommission, 0, 0] + ); + const rewardPerShare4 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + + expect(rewardPerShare4.sub(rewardPerShare3)).to.equal(rewardAmount1.sub(clusterCommission).mul(e30).div(clusterDelegation)); + + // ----------------- Gives reward when previous delegation is non 0 and user has prev rewards, new rewards for cluster --------------- + const amount2 = FuzzedNumber.randomInRange(100000, 500000); + + clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + delegationInit = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); + // TODO: fix the add(1) at the end + let delegator1CurrentReward = rewardPerShare4 + .add(rewardAmount1.sub(clusterCommission).mul(e30).div(clusterDelegationInit)) + .sub(rewardPerShare2) + .mul(delegationInit) + .div(e30) + .add(1); + clusterCommission = rewardAmount1.mul(commission).div(100); + await expect( + rewardDelegators.connect(imperonatedStakeManager).delegate(delegator1, cluster, [pondTokenId], [amount2]) + ).to.changeTokenBalances( + pond, + [rewardDelegators, rewardAddress, delegator1], + [-delegator1CurrentReward.add(clusterCommission), clusterCommission, delegator1CurrentReward] + ); + clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + delegation = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); + const rewardPerShare5 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + + expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount2)); + expect(delegation).to.equal(delegationInit.add(amount2)); + expect(rewardPerShare5.sub(rewardPerShare4)).to.equal(rewardAmount1.sub(clusterCommission).mul(e30).div(clusterDelegationInit)); + + // ----------------- Gives reward when previous delegation is non 0 and user has prev rewards, new rewards for cluster,0 commission --------------- + await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster).returns(0, rewardAddress); + + clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + delegationInit = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); + delegatorCurrentReward = rewardPerShare5 + .add(rewardAmount1.mul(e30).div(clusterDelegationInit)) + .sub(rewardPerShare3) + .mul(delegationInit) + .div(e30) + .add(1); + await expect( + rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster, [pondTokenId], [amount2]) + ).to.changeTokenBalances(pond, [rewardDelegators, rewardAddress, delegator], [-delegatorCurrentReward, 0, delegatorCurrentReward]); + clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + delegation = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); + const rewardPerShare6 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + + expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount2)); + expect(delegation).to.equal(delegationInit.add(amount2)); + expect(rewardPerShare6.sub(rewardPerShare5)).to.equal(rewardAmount1.mul(e30).div(clusterDelegationInit)); + }); + + it("delegate to cluster multiple tokens", async () => { + const delegator = await delegators[0].getAddress(); + const delegator1 = await delegators[1].getAddress(); + const cluster = await registeredClusters[FuzzedNumber.randomInRange(0, registeredClusters.length).toNumber()].getAddress(); + const rewardAddress = + registeredClusterRewardAddresses[FuzzedNumber.randomInRange(0, registeredClusterRewardAddresses.length).toNumber()]; + const networkId = ethers.utils.id("ETH"); + + let rewardAmount = FuzzedNumber.randomInRange(100000, 500000); + let commission = FuzzedNumber.randomInRange(0, 100); + await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(rewardAmount); + await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster).returns(commission, rewardAddress); + await fakeClusterRegistry.mock.getNetwork.withArgs(cluster).returns(networkId); + // TODO: only upsert with `cluster` as arg are accepted + await fakeClusterSelectors["ETH"].mock.upsert.returns(); + + // ----------------- Give reward when previous total delegation and user has no prev rewards --------------- + const amount1 = FuzzedNumber.randomInRange(e20, e22); + const mpondAmount1 = FuzzedNumber.randomInRange(e16, e18); + await expect(rewardDelegators.delegate(delegator, cluster, [pondTokenId, mpondTokenId], [amount1, mpondAmount1])).to.be.revertedWith( + "RD:OS-only stake contract can invoke" + ); - it("non owner cannot remove rewardFactor", async () => { - await expect(rewardDelegators.connect(signers[1]).removeRewardFactor("" + pondTokenId)).to.be.reverted; - }); + let clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + let mpondClusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); + let delegationInit = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); + let mpondDelegationInit = await rewardDelegators.getDelegation(cluster, delegator, mpondTokenId); + let rewardPerShare = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + let mpondRewardPerShare = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); + await expect( + rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster, [pondTokenId, mpondTokenId], [amount1, mpondAmount1]) + ).to.changeTokenBalances( + pond, + [rewardDelegators, rewardAddress, delegator], + [-commission.mul(rewardAmount).div(100), commission.mul(rewardAmount).div(100), 0] + ); + let clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + let mpondClusterDelegation = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); + let delegation = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); + let mpondDelegation = await rewardDelegators.getDelegation(cluster, delegator, mpondTokenId); + let rewardPerShare1 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + let mpondRewardPerShare1 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); + + expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount1)); + expect(mpondClusterDelegation).to.equal(mpondClusterDelegationInit.add(mpondAmount1)); + expect(delegation).to.equal(delegationInit.add(amount1)); + expect(mpondDelegation).to.equal(mpondDelegationInit.add(mpondAmount1)); + expect(rewardPerShare1.sub(rewardPerShare)).to.equal(0); + expect(mpondRewardPerShare1.sub(mpondRewardPerShare)).to.equal(0); + + // ----------------- Give reward when previous total delegation is non 0 and user has no prev rewards --------------- + clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + mpondClusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); + delegationInit = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); + mpondDelegationInit = await rewardDelegators.getDelegation(cluster, delegator1, mpondTokenId); + rewardPerShare = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + mpondRewardPerShare = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); + let clusterCommission = commission.mul(rewardAmount).div(100); + await expect( + rewardDelegators.connect(imperonatedStakeManager).delegate(delegator1, cluster, [pondTokenId, mpondTokenId], [amount1, mpondAmount1]) + ).to.changeTokenBalances(pond, [rewardDelegators, rewardAddress, delegator1], [-clusterCommission, clusterCommission, 0]); + clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + mpondClusterDelegation = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); + delegation = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); + mpondDelegation = await rewardDelegators.getDelegation(cluster, delegator1, mpondTokenId); + const rewardPerShare2 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + const mpondRewardPerShare2 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); + + expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount1)); + expect(mpondClusterDelegation).to.equal(mpondClusterDelegationInit.add(mpondAmount1)); + expect(delegation).to.equal(delegationInit.add(amount1)); + expect(mpondDelegation).to.equal(mpondDelegationInit.add(mpondAmount1)); + expect(rewardPerShare2.sub(rewardPerShare1)).to.equal(rewardAmount.sub(clusterCommission).mul(e30).div(2).div(clusterDelegationInit)); + expect(mpondRewardPerShare2.sub(mpondRewardPerShare1)).to.equal( + rewardAmount.sub(clusterCommission).mul(e30).div(2).div(mpondClusterDelegationInit) + ); - it("cannot remove non existing tokenId", async () => { - let testTokenId = await ethers.utils.keccak256(addrs[0]); - await expect(rewardDelegators.removeRewardFactor(testTokenId)).to.be.reverted; - }); + // ----------------- Gives reward when previous delegation is non 0 and user has prev rewards, no new rewards for cluster --------------- + await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(0); + + clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + mpondClusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); + delegationInit = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); + mpondDelegationInit = await rewardDelegators.getDelegation(cluster, delegator, mpondTokenId); + let delegatorCurrentReward = rewardPerShare2.sub(rewardPerShare).mul(delegationInit).div(e30); + let delegatorCurrentRewardMpond = mpondRewardPerShare2.sub(mpondRewardPerShare).mul(mpondDelegationInit).div(e30); + await expect( + rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster, [pondTokenId, mpondTokenId], [amount1, mpondAmount1]) + ).to.changeTokenBalances( + pond, + [rewardDelegators, rewardAddress, delegator], + [-delegatorCurrentReward.add(delegatorCurrentRewardMpond), 0, delegatorCurrentReward.add(delegatorCurrentRewardMpond)] + ); + clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + mpondClusterDelegation = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); + delegation = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); + mpondDelegation = await rewardDelegators.getDelegation(cluster, delegator, mpondTokenId); + const rewardPerShare3 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + const mpondRewardPerShare3 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); + + expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount1)); + expect(mpondClusterDelegation).to.equal(mpondClusterDelegationInit.add(mpondAmount1)); + expect(delegation).to.equal(delegationInit.add(amount1)); + expect(mpondDelegation).to.equal(mpondDelegationInit.add(mpondAmount1)); + expect(rewardPerShare3.sub(rewardPerShare2)).to.equal(0); + expect(mpondRewardPerShare3.sub(mpondRewardPerShare2)).to.equal(0); + + // ----------------- Give new rewards to cluster --------------- + const rewardAmount1 = rewardAmount.mul(2).div(3); + await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(rewardAmount1); + clusterCommission = rewardAmount1.mul(commission).div(100); + await expect(rewardDelegators._updateRewards(cluster)).to.changeTokenBalances( + pond, + [rewardDelegators, rewardAddress, delegator, delegator1], + [-clusterCommission, clusterCommission, 0, 0] + ); + const rewardPerShare4 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + const mpondRewardPerShare4 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); - it("owner can remove rewardFactor", async () => { - await expect(await rewardDelegators.removeRewardFactor("" + pondTokenId)).to.emit(rewardDelegators, "RemoveReward"); - }); + expect(rewardPerShare4.sub(rewardPerShare3)).to.equal(rewardAmount1.sub(clusterCommission).mul(e30).div(2).div(clusterDelegation)); + expect(mpondRewardPerShare4.sub(mpondRewardPerShare3)).to.equal( + rewardAmount1.sub(clusterCommission).mul(e30).div(2).div(mpondClusterDelegation) + ); - it("non owner cannot update reward Factor", async () => { - await expect(rewardDelegators.connect(signers[1]).updateRewardFactor("" + pondTokenId, stakingConfig.PondRewardFactor + 1)) - .to.be.reverted; - }); + // ----------------- Gives reward when previous delegation is non 0 and user has prev rewards, new rewards for cluster --------------- + const amount2 = FuzzedNumber.randomInRange(100000, 500000); + const mpondAmount2 = FuzzedNumber.randomInRange(100, 500); + + clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + mpondClusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); + delegationInit = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); + mpondDelegationInit = await rewardDelegators.getDelegation(cluster, delegator1, mpondTokenId); + // TODO: Fix add(1) at the end + let delegator1CurrentReward = rewardPerShare4 + .add(rewardAmount1.sub(clusterCommission).mul(e30).div(2).div(clusterDelegationInit)) + .sub(rewardPerShare2) + .mul(delegationInit) + .div(e30) + .add(1); + let delegator1CurrentRewardMpond = mpondRewardPerShare4 + .add(rewardAmount1.sub(clusterCommission).mul(e30).div(2).div(mpondClusterDelegation)) + .sub(mpondRewardPerShare2) + .mul(mpondDelegationInit) + .div(e30) + .add(1); + clusterCommission = rewardAmount1.mul(commission).div(100); + await expect( + rewardDelegators.connect(imperonatedStakeManager).delegate(delegator1, cluster, [pondTokenId, mpondTokenId], [amount2, mpondAmount2]) + ).to.changeTokenBalances( + pond, + [rewardDelegators, rewardAddress, delegator1], + [ + -delegator1CurrentReward.add(delegator1CurrentRewardMpond).add(clusterCommission), + clusterCommission, + delegator1CurrentReward.add(delegator1CurrentRewardMpond), + ], + 2 + ); + clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + mpondClusterDelegation = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); + delegation = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); + mpondDelegation = await rewardDelegators.getDelegation(cluster, delegator1, mpondTokenId); + const rewardPerShare5 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + const mpondRewardPerShare5 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); + + expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount2)); + expect(mpondClusterDelegation).to.equal(mpondClusterDelegationInit.add(mpondAmount2)); + expect(delegation).to.equal(delegationInit.add(amount2)); + expect(mpondDelegation).to.equal(mpondDelegationInit.add(mpondAmount2)); + expect(rewardPerShare5.sub(rewardPerShare4).div(e14)).to.equal( + rewardAmount1.sub(clusterCommission).div(2).mul(e30).div(clusterDelegationInit).div(e14) + ); + expect(mpondRewardPerShare5.sub(mpondRewardPerShare4).div(e14)).to.equal( + rewardAmount1.sub(clusterCommission).div(2).mul(e30).div(mpondClusterDelegation).div(e14) + ); - it("cannot update non existing tokenId", async () => { - let testTokenId = await ethers.utils.keccak256(addrs[0]); - await expect(rewardDelegators.updateRewardFactor(testTokenId, 1)).to.be.reverted; - }); + // ----------------- Gives reward when previous delegation is non 0 and user has prev rewards, new rewards for cluster,0 commission --------------- + await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster).returns(0, rewardAddress); + + clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + mpondClusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); + delegationInit = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); + mpondDelegationInit = await rewardDelegators.getDelegation(cluster, delegator, mpondTokenId); + // TODO: Fix add(1) at the end + let changeInRewardPerShare = rewardAmount1.mul(e30).div(2).div(clusterDelegationInit); + let changeInRewardPerShareMpond = rewardAmount1.mul(e30).div(2).div(mpondClusterDelegation); + delegatorCurrentReward = rewardPerShare5.add(changeInRewardPerShare).sub(rewardPerShare3).mul(delegationInit).div(e30).add(1); + delegatorCurrentRewardMpond = mpondRewardPerShare5 + .add(changeInRewardPerShareMpond) + .sub(mpondRewardPerShare3) + .mul(mpondDelegationInit) + .div(e30) + .add(1); + await expect( + rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster, [pondTokenId, mpondTokenId], [amount2, mpondAmount2]) + ).to.changeTokenBalances( + pond, + [rewardDelegators, rewardAddress, delegator], + [-delegatorCurrentReward.add(delegatorCurrentRewardMpond), 0, delegatorCurrentReward.add(delegatorCurrentRewardMpond)] + ); + clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + mpondClusterDelegation = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); + delegation = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); + mpondDelegation = await rewardDelegators.getDelegation(cluster, delegator, mpondTokenId); + const rewardPerShare6 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + const mpondRewardPerShare6 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); + + expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount2)); + expect(mpondClusterDelegation).to.equal(mpondClusterDelegationInit.add(mpondAmount2)); + expect(delegation).to.equal(delegationInit.add(amount2)); + expect(mpondDelegation).to.equal(mpondDelegationInit.add(mpondAmount2)); + expect(rewardPerShare6.sub(rewardPerShare5)).to.equal(changeInRewardPerShare); + expect(mpondRewardPerShare6.sub(mpondRewardPerShare5)).to.equal(changeInRewardPerShareMpond); + }); + + it("undelegate from cluster", async () => { + const delegator = await delegators[0].getAddress(); + const delegator1 = await delegators[1].getAddress(); + const cluster = await registeredClusters[FuzzedNumber.randomInRange(0, registeredClusters.length).toNumber()].getAddress(); + const rewardAddress = + registeredClusterRewardAddresses[FuzzedNumber.randomInRange(0, registeredClusterRewardAddresses.length).toNumber()]; + const networkId = ethers.utils.id("ETH"); + + let rewardAmount = FuzzedNumber.randomInRange(100000, 500000); + let commission = FuzzedNumber.randomInRange(0, 100); + await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(rewardAmount); + await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster).returns(commission, rewardAddress); + await fakeClusterRegistry.mock.getNetwork.withArgs(cluster).returns(networkId); + // TODO: only upsert with `cluster` as arg are accepted + await fakeClusterSelectors["ETH"].mock.upsert.returns(); + // ----------------- Setup clusters and their delegations --------------- + const amount1 = FuzzedNumber.randomInRange(e18, e20); + const mpondAmount1 = FuzzedNumber.randomInRange(e18, e20); + const amount2 = FuzzedNumber.randomInRange(e18, e20); + const mpondAmount2 = FuzzedNumber.randomInRange(e18, e20); + await rewardDelegators + .connect(imperonatedStakeManager) + .delegate(delegator, cluster, [pondTokenId, mpondTokenId], [amount1, mpondAmount1]); + const rewardPerShare1 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + const mpondRewardPerShare1 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); + await rewardDelegators + .connect(imperonatedStakeManager) + .delegate(delegator1, cluster, [pondTokenId, mpondTokenId], [amount2, mpondAmount2]); + const rewardPerShare2 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + const mpondRewardPerShare2 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); + + // ----------------- Undelegate when there are rewards for delegator and no new rewards for cluster --------------- + await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(0); + + await expect( + rewardDelegators.undelegate(delegator, cluster, [pondTokenId, mpondTokenId], [amount1.mul(1).div(3), mpondAmount1.mul(1).div(3)]) + ).to.be.revertedWith("RD:OS-only stake contract can invoke"); + + let clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + let mpondClusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); + let delegationInit = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); + let mpondDelegationInit = await rewardDelegators.getDelegation(cluster, delegator, mpondTokenId); + let delegatorCurrentReward = rewardPerShare2.sub(rewardPerShare1).mul(delegationInit).div(e30); + let delegatorCurrentRewardMpond = mpondRewardPerShare2.sub(mpondRewardPerShare1).mul(mpondDelegationInit).div(e30); + await expect( + rewardDelegators + .connect(imperonatedStakeManager) + .undelegate(delegator, cluster, [pondTokenId, mpondTokenId], [amount1.div(3), mpondAmount1.div(3)]) + ).to.changeTokenBalances( + pond, + [rewardDelegators, rewardAddress, delegator], + [-delegatorCurrentReward.add(delegatorCurrentRewardMpond), 0, delegatorCurrentReward.add(delegatorCurrentRewardMpond)] + ); + let clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + let mpondClusterDelegation = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); + let delegation = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); + let mpondDelegation = await rewardDelegators.getDelegation(cluster, delegator, mpondTokenId); + let rewardPerShare3 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + let mpondRewardPerShare3 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); + + expect(clusterDelegation).to.equal(clusterDelegationInit.sub(amount1.div(3))); + expect(mpondClusterDelegation).to.equal(mpondClusterDelegationInit.sub(mpondAmount1.div(3))); + expect(delegation).to.equal(delegationInit.sub(amount1.div(3))); + expect(mpondDelegation).to.equal(mpondDelegationInit.sub(mpondAmount1.div(3))); + expect(rewardPerShare3.sub(rewardPerShare2)).to.equal(0); + expect(mpondRewardPerShare3.sub(mpondRewardPerShare2)).to.equal(0); + + // ----------------- Give new rewards to cluster --------------- + await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(rewardAmount); + + const rewardAmount1 = rewardAmount.mul(2).div(3); + await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(rewardAmount1); + let clusterCommission = rewardAmount1.mul(commission).div(100); + await expect(rewardDelegators._updateRewards(cluster)).to.changeTokenBalances( + pond, + [rewardDelegators, rewardAddress, delegator, delegator1], + [-clusterCommission, clusterCommission, 0, 0] + ); + const rewardPerShare4 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + const mpondRewardPerShare4 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); - it("cannot update rewardFactor to 0", async () => { - await expect(rewardDelegators.updateRewardFactor("" + pondTokenId, 0)).to.be.reverted; - }); + expect(rewardPerShare4.sub(rewardPerShare3)).to.equal(rewardAmount1.sub(clusterCommission).mul(e30).div(2).div(clusterDelegation)); + expect(mpondRewardPerShare4.sub(mpondRewardPerShare3)).to.equal( + rewardAmount1.sub(clusterCommission).mul(e30).div(2).div(mpondClusterDelegation) + ); - it("owner can update rewardFactor", async () => { - await expect(await rewardDelegators.updateRewardFactor("" + pondTokenId, stakingConfig.PondRewardFactor + 1)) - .to.emit( - rewardDelegators, - "RewardsUpdated" - ); - }); -}); + // ----------------- Undelegate when there are rewards for delegator and new rewards for cluster --------------- + clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + mpondClusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); + delegationInit = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); + mpondDelegationInit = await rewardDelegators.getDelegation(cluster, delegator1, mpondTokenId); + // TODO: Fix add(1) at the end + let changeInRewardPerShare = rewardAmount1.sub(clusterCommission).mul(e30).div(2).div(clusterDelegation); + let changeInRewardPerShareMpond = rewardAmount1.sub(clusterCommission).mul(e30).div(2).div(mpondClusterDelegation); + delegatorCurrentReward = rewardPerShare4.add(changeInRewardPerShare).sub(rewardPerShare2).mul(delegationInit).div(e30).add(1); + delegatorCurrentRewardMpond = mpondRewardPerShare4 + .add(changeInRewardPerShareMpond) + .sub(mpondRewardPerShare2) + .mul(mpondDelegationInit) + .div(e30) + .add(1); + clusterCommission = commission.mul(rewardAmount1).div(100); + + await expect( + rewardDelegators + .connect(imperonatedStakeManager) + .undelegate(delegator1, cluster, [pondTokenId, mpondTokenId], [amount2.div(2), mpondAmount2.div(2)]) + ).to.changeTokenBalances( + pond, + [rewardDelegators, rewardAddress, delegator1], + [ + -delegatorCurrentReward.add(delegatorCurrentRewardMpond).add(clusterCommission), + clusterCommission, + delegatorCurrentReward.add(delegatorCurrentRewardMpond), + ], + 2 + ); + clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); + mpondClusterDelegation = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); + delegation = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); + mpondDelegation = await rewardDelegators.getDelegation(cluster, delegator1, mpondTokenId); + let rewardPerShare5 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); + let mpondRewardPerShare5 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); + + expect(clusterDelegation).to.equal(clusterDelegationInit.sub(amount2.div(2))); + expect(mpondClusterDelegation).to.equal(mpondClusterDelegationInit.sub(mpondAmount2.div(2))); + expect(delegation).to.equal(delegationInit.sub(amount2.div(2))); + expect(mpondDelegation).to.equal(mpondDelegationInit.sub(mpondAmount2.div(2))); + expect(rewardPerShare5.sub(rewardPerShare4)).to.equal(changeInRewardPerShare); + expect(mpondRewardPerShare5.sub(mpondRewardPerShare4)).to.equal(changeInRewardPerShareMpond); + + // ----------------- Undelegate only pond when there are rewards for delegator and new rewards for cluster --------------- + + // ----------------- Undelegate only mpond when there are rewards for delegator and new rewards for cluster --------------- + }); + + it("update cluster delegation", async () => { + const delegator = await delegators[0].getAddress(); + const delegator1 = await delegators[1].getAddress(); + const cluster = await registeredClusters[0].getAddress(); + const cluster1 = await registeredClusters[1].getAddress(); + const cluster2 = await registeredClusters[2].getAddress(); + const rewardAddress = + registeredClusterRewardAddresses[FuzzedNumber.randomInRange(0, registeredClusterRewardAddresses.length).toNumber()]; + const networkId = ethers.utils.id("ETH"); + + let rewardAmount = FuzzedNumber.randomInRange(100000, 500000); + let commission = FuzzedNumber.randomInRange(0, 100); + await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(rewardAmount); + await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster).returns(commission, rewardAddress); + await fakeClusterRegistry.mock.getNetwork.withArgs(cluster).returns(networkId); + await fakeClusterRewards.mock.claimReward.withArgs(cluster1).returns(rewardAmount); + await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster1).returns(commission, rewardAddress); + await fakeClusterRegistry.mock.getNetwork.withArgs(cluster1).returns(networkId); + await fakeClusterRewards.mock.claimReward.withArgs(cluster2).returns(rewardAmount); + await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster2).returns(commission, rewardAddress); + await fakeClusterRegistry.mock.getNetwork.withArgs(cluster2).returns(networkId); + // TODO: only upsert with `cluster` as arg are accepted + await fakeClusterSelectors["ETH"].mock.upsert.returns(); + + await rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster, [pondTokenId], [10000000000]); + await rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster1, [mpondTokenId], [6]); + await rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster2, [mpondTokenId], [10]); + + await fakeClusterSelectors["ETH"].mock.upsert.revertsWithReason("unexpected upsert"); + await fakeClusterSelectors["ETH"].mock.deleteIfPresent.revertsWithReason("unexpected delete"); + + await rewardDelegators.updateThresholdForSelection(networkId, 10000000); + await expect(rewardDelegators.connect(impersonatedClusterRegistry).updateClusterDelegation(cluster, networkId)).to.be.revertedWith( + "unexpected delete" + ); + await expect(rewardDelegators.connect(impersonatedClusterRegistry).updateClusterDelegation(cluster1, networkId)).to.be.revertedWith( + "unexpected delete" + ); + await expect(rewardDelegators.connect(impersonatedClusterRegistry).updateClusterDelegation(cluster2, networkId)).to.be.revertedWith( + "unexpected upsert" + ); -describe("RewardDelegators ", function () { - let signers: Signer[]; - let addrs: string[]; - - let registeredClusters: Signer[]; - let registeredClusterRewardAddresses: string[]; - let clientKeys: string[]; - let delegators: Signer[]; - - let rewardDelegators: RewardDelegators; - let fakeStakeManager: MockContract; - let imperonatedStakeManager: Signer; - let fakeClusterRewards: MockContract; - let fakeClusterRegistry: MockContract; - let impersonatedClusterRegistry: Signer; - - let pond: Pond; - let pondTokenId: string; - let mpondTokenId: string; - - let fakeClusterSelectors: Record = {}; - - before(async function () { - signers = await ethers.getSigners(); - addrs = await Promise.all(signers.map((a) => a.getAddress())); - - registeredClusters = signers.slice(20, 30); - registeredClusterRewardAddresses = addrs.slice(30, 40); - clientKeys = addrs.slice(40, 50); - delegators = signers.slice(50, 60); - - const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); - const rewardDelegatorsUntyped: Contract = await upgrades.deployProxy(RewardDelegators, { - kind: "uups", - initializer: false, - }); - rewardDelegators = getRewardDelegators(rewardDelegatorsUntyped.address, signers[0]); - - const StakeManager = await ethers.getContractFactory("StakeManager"); - // setting address while deploying mock is not yet implemented in ethereum-waffle yet - fakeStakeManager = await deployMockContract(signers[9], StakeManager.interface.format()); - imperonatedStakeManager = await impersonate(ethers, fakeStakeManager.address); - // mocking receive function is not implemented in ethereum-waffle yet - await setBalance(ethers, fakeStakeManager.address, e18); - const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - fakeClusterRewards = await deployMockContract(signers[8], ClusterRewards.interface.format()); - const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); - fakeClusterRegistry = await deployMockContract(signers[7], ClusterRegistry.interface.format()); - impersonatedClusterRegistry = await impersonate(ethers, fakeClusterRegistry.address); - await setBalance(ethers, fakeClusterRegistry.address, e18); - const Pond = await ethers.getContractFactory("Pond"); - const pondUntyped = await upgrades.deployProxy(Pond, ["Marlin", "POND"], { kind: "uups" }); - pond = getPond(pondUntyped.address, signers[0]); - const fakeMPond = addrs[6]; - pondTokenId = ethers.utils.id(pond.address); - mpondTokenId = ethers.utils.id(fakeMPond); - await rewardDelegators.initialize( - fakeStakeManager.address, - fakeClusterRewards.address, - fakeClusterRegistry.address, - pond.address, - [pondTokenId, mpondTokenId], - [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], - [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], - [], - [] - ); - - await pond.transfer(rewardDelegators.address, ethers.utils.parseEther("100000")); - - const clusterSelector = await ethers.getContractFactory("ClusterSelector"); - await fakeClusterRewards.mock.clusterSelectors.returns(ethers.constants.AddressZero); - fakeClusterSelectors["ETH"] = await deployMockContract(signers[5], clusterSelector.interface.format()); - await fakeClusterRewards.mock.clusterSelectors.withArgs(ethers.utils.id("ETH")).returns(fakeClusterSelectors["ETH"].address); - fakeClusterSelectors["DOT"] = await deployMockContract(signers[4], clusterSelector.interface.format()); - await fakeClusterRewards.mock.clusterSelectors.withArgs(ethers.utils.id("DOT")).returns(fakeClusterSelectors["DOT"].address); - }); + await rewardDelegators.updateThresholdForSelection(networkId, 0); + await fakeClusterSelectors["ETH"].mock.upsert.returns(); + await fakeClusterSelectors["ETH"].mock.deleteIfPresent.returns(); + await rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster, [mpondTokenId], [12]); + await rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster1, [pondTokenId], [4000000]); + await rewardDelegators.connect(imperonatedStakeManager).undelegate(delegator, cluster2, [mpondTokenId], [1]); - takeSnapshotBeforeAndAfterEveryTest(async () => {}); - - // NOTE: Reward factor doesn't do anything rn - - it("delegate to cluster single token", async () => { - const delegator = await delegators[0].getAddress(); - const delegator1 = await delegators[1].getAddress(); - const cluster = await registeredClusters[FuzzedNumber.randomInRange(0, registeredClusters.length).toNumber()].getAddress(); - const rewardAddress = registeredClusterRewardAddresses[FuzzedNumber.randomInRange(0, registeredClusterRewardAddresses.length).toNumber()]; - const networkId = ethers.utils.id("ETH"); - - let rewardAmount = FuzzedNumber.randomInRange(100000, 500000); - let commission = FuzzedNumber.randomInRange(0, 100); - await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(rewardAmount); - await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster).returns(commission, rewardAddress); - await fakeClusterRegistry.mock.getNetwork.withArgs(cluster).returns(networkId); - // TODO: only upsert with `cluster` as arg are accepted - await fakeClusterSelectors["ETH"].mock.upsert.returns(); - - // ----------------- Give reward when previous total delegation and user has no prev rewards --------------- - const amount1 = FuzzedNumber.randomInRange(e16, e18); - await expect(rewardDelegators.delegate(delegator, cluster, [pondTokenId], [amount1])) - .to.be.revertedWith("RD:OS-only stake contract can invoke"); - - let clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - let delegationInit = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); - let rewardPerShare = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - await expect(rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster, [pondTokenId], [amount1])) - .to.changeTokenBalances( - pond, - [rewardDelegators, rewardAddress, delegator], - [-(commission.mul(rewardAmount).div(100)), commission.mul(rewardAmount).div(100), 0] - ); - let clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - let delegation = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); - let rewardPerShare1 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - - expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount1)); - expect(delegation).to.equal(delegationInit.add(amount1)); - // as there is only pond token all reward is given to pond token - expect(rewardPerShare1.sub(rewardPerShare)).to.equal(0); - - // ----------------- Give reward when previous total delegation is non 0 and user has no prev rewards --------------- - clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - delegationInit = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); - let clusterCommission = commission.mul(rewardAmount).div(100); - await expect(rewardDelegators.connect(imperonatedStakeManager).delegate(delegator1, cluster, [pondTokenId], [amount1])) - .to.changeTokenBalances( - pond, - [rewardDelegators, rewardAddress, delegator1], - [-clusterCommission, clusterCommission, 0] - ); - clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - delegation = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); - const rewardPerShare2 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - - expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount1)); - expect(delegation).to.equal(delegationInit.add(amount1)); - // as there is only pond token all reward is given to pond token - expect(rewardPerShare2.sub(rewardPerShare1)).to.equal(rewardAmount.sub(clusterCommission).mul(e30).div(clusterDelegationInit)); - - // ----------------- Gives reward when previous delegation is non 0 and user has prev rewards, no new rewards for cluster --------------- - await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(0); - - clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - delegationInit = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); - let delegatorCurrentReward = rewardPerShare2.sub(rewardPerShare).mul(delegationInit).div(e30); - await expect(rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster, [pondTokenId], [amount1])) - .to.changeTokenBalances( - pond, - [rewardDelegators, rewardAddress, delegator], - [-delegatorCurrentReward, 0, delegatorCurrentReward] - ); - clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - delegation = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); - const rewardPerShare3 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - - expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount1)); - expect(delegation).to.equal(delegationInit.add(amount1)); - expect(rewardPerShare3).to.equal(rewardPerShare2); - - // ----------------- Give new rewards to cluster --------------- - const rewardAmount1 = rewardAmount.mul(2).div(3); - await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(rewardAmount1); - clusterCommission = rewardAmount1.mul(commission).div(100); - await expect(rewardDelegators._updateRewards(cluster)).to.changeTokenBalances( - pond, - [rewardDelegators, rewardAddress, delegator, delegator1], - [-clusterCommission, clusterCommission, 0, 0] - ); - const rewardPerShare4 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - - expect(rewardPerShare4.sub(rewardPerShare3)).to.equal(rewardAmount1.sub(clusterCommission).mul(e30).div(clusterDelegation)); - - // ----------------- Gives reward when previous delegation is non 0 and user has prev rewards, new rewards for cluster --------------- - const amount2 = FuzzedNumber.randomInRange(100000, 500000); - - clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - delegationInit = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); - // TODO: fix the add(1) at the end - let delegator1CurrentReward = rewardPerShare4.add(rewardAmount1.sub(clusterCommission).mul(e30).div(clusterDelegationInit)).sub(rewardPerShare2).mul(delegationInit).div(e30).add(1); - clusterCommission = rewardAmount1.mul(commission).div(100); - await expect(rewardDelegators.connect(imperonatedStakeManager).delegate(delegator1, cluster, [pondTokenId], [amount2])) - .to.changeTokenBalances( - pond, - [rewardDelegators, rewardAddress, delegator1], - [-(delegator1CurrentReward.add(clusterCommission)), clusterCommission, delegator1CurrentReward] - ); - clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - delegation = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); - const rewardPerShare5 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - - expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount2)); - expect(delegation).to.equal(delegationInit.add(amount2)); - expect(rewardPerShare5.sub(rewardPerShare4)).to.equal(rewardAmount1.sub(clusterCommission).mul(e30).div(clusterDelegationInit)); - - // ----------------- Gives reward when previous delegation is non 0 and user has prev rewards, new rewards for cluster,0 commission --------------- - await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster).returns(0, rewardAddress); - - clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - delegationInit = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); - delegatorCurrentReward = rewardPerShare5.add(rewardAmount1.mul(e30).div(clusterDelegationInit)).sub(rewardPerShare3).mul(delegationInit).div(e30).add(1); - await expect(rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster, [pondTokenId], [amount2])) - .to.changeTokenBalances( - pond, - [rewardDelegators, rewardAddress, delegator], - [-(delegatorCurrentReward), 0, delegatorCurrentReward] - ); - clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - delegation = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); - const rewardPerShare6 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - - expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount2)); - expect(delegation).to.equal(delegationInit.add(amount2)); - expect(rewardPerShare6.sub(rewardPerShare5)).to.equal(rewardAmount1.mul(e30).div(clusterDelegationInit)); - }); + await rewardDelegators.updateThresholdForSelection(networkId, 10000000); + await fakeClusterSelectors["ETH"].mock.upsert.revertsWithReason("upsert called"); + await fakeClusterSelectors["ETH"].mock.deleteIfPresent.revertsWithReason("delete called"); + await rewardDelegators.connect(impersonatedClusterRegistry).updateClusterDelegation(cluster, ethers.utils.id("RANDOM")); - it("delegate to cluster multiple tokens", async () => { - const delegator = await delegators[0].getAddress(); - const delegator1 = await delegators[1].getAddress(); - const cluster = await registeredClusters[FuzzedNumber.randomInRange(0, registeredClusters.length).toNumber()].getAddress(); - const rewardAddress = registeredClusterRewardAddresses[FuzzedNumber.randomInRange(0, registeredClusterRewardAddresses.length).toNumber()]; - const networkId = ethers.utils.id("ETH"); - - let rewardAmount = FuzzedNumber.randomInRange(100000, 500000); - let commission = FuzzedNumber.randomInRange(0, 100); - await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(rewardAmount); - await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster).returns(commission, rewardAddress); - await fakeClusterRegistry.mock.getNetwork.withArgs(cluster).returns(networkId); - // TODO: only upsert with `cluster` as arg are accepted - await fakeClusterSelectors["ETH"].mock.upsert.returns(); - - // ----------------- Give reward when previous total delegation and user has no prev rewards --------------- - const amount1 = FuzzedNumber.randomInRange(e20, e22); - const mpondAmount1 = FuzzedNumber.randomInRange(e16, e18); - await expect(rewardDelegators.delegate(delegator, cluster, [pondTokenId, mpondTokenId], [amount1, mpondAmount1])) - .to.be.revertedWith("RD:OS-only stake contract can invoke"); - - let clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - let mpondClusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); - let delegationInit = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); - let mpondDelegationInit = await rewardDelegators.getDelegation(cluster, delegator, mpondTokenId); - let rewardPerShare = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - let mpondRewardPerShare = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); - await expect(rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator, - cluster, - [pondTokenId, mpondTokenId], - [amount1, mpondAmount1]) - ).to.changeTokenBalances( - pond, - [rewardDelegators, rewardAddress, delegator], - [-(commission.mul(rewardAmount).div(100)), commission.mul(rewardAmount).div(100), 0] - ); - let clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - let mpondClusterDelegation = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); - let delegation = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); - let mpondDelegation = await rewardDelegators.getDelegation(cluster, delegator, mpondTokenId); - let rewardPerShare1 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - let mpondRewardPerShare1 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); - - expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount1)); - expect(mpondClusterDelegation).to.equal(mpondClusterDelegationInit.add(mpondAmount1)); - expect(delegation).to.equal(delegationInit.add(amount1)); - expect(mpondDelegation).to.equal(mpondDelegationInit.add(mpondAmount1)); - expect(rewardPerShare1.sub(rewardPerShare)).to.equal(0); - expect(mpondRewardPerShare1.sub(mpondRewardPerShare)).to.equal(0); - - // ----------------- Give reward when previous total delegation is non 0 and user has no prev rewards --------------- - clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - mpondClusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); - delegationInit = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); - mpondDelegationInit = await rewardDelegators.getDelegation(cluster, delegator1, mpondTokenId); - rewardPerShare = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - mpondRewardPerShare = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); - let clusterCommission = commission.mul(rewardAmount).div(100); - await expect(rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator1, - cluster, - [pondTokenId, mpondTokenId], - [amount1, mpondAmount1]) - ).to.changeTokenBalances( - pond, - [rewardDelegators, rewardAddress, delegator1], - [-clusterCommission, clusterCommission, 0] - ); - clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - mpondClusterDelegation = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); - delegation = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); - mpondDelegation = await rewardDelegators.getDelegation(cluster, delegator1, mpondTokenId); - const rewardPerShare2 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - const mpondRewardPerShare2 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); - - expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount1)); - expect(mpondClusterDelegation).to.equal(mpondClusterDelegationInit.add(mpondAmount1)); - expect(delegation).to.equal(delegationInit.add(amount1)); - expect(mpondDelegation).to.equal(mpondDelegationInit.add(mpondAmount1)); - expect(rewardPerShare2.sub(rewardPerShare1)).to.equal(rewardAmount.sub(clusterCommission).mul(e30).div(2).div(clusterDelegationInit)); - expect(mpondRewardPerShare2.sub(mpondRewardPerShare1)).to.equal(rewardAmount.sub(clusterCommission).mul(e30).div(2).div(mpondClusterDelegationInit)); - - // ----------------- Gives reward when previous delegation is non 0 and user has prev rewards, no new rewards for cluster --------------- - await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(0); - - clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - mpondClusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); - delegationInit = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); - mpondDelegationInit = await rewardDelegators.getDelegation(cluster, delegator, mpondTokenId); - let delegatorCurrentReward = rewardPerShare2.sub(rewardPerShare).mul(delegationInit).div(e30); - let delegatorCurrentRewardMpond = mpondRewardPerShare2.sub(mpondRewardPerShare).mul(mpondDelegationInit).div(e30); - await expect(rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator, - cluster, - [pondTokenId, mpondTokenId], - [amount1, mpondAmount1]) - ).to.changeTokenBalances( - pond, - [rewardDelegators, rewardAddress, delegator], - [-delegatorCurrentReward.add(delegatorCurrentRewardMpond), 0, delegatorCurrentReward.add(delegatorCurrentRewardMpond)] - ); - clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - mpondClusterDelegation = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); - delegation = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); - mpondDelegation = await rewardDelegators.getDelegation(cluster, delegator, mpondTokenId); - const rewardPerShare3 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - const mpondRewardPerShare3 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); - - expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount1)); - expect(mpondClusterDelegation).to.equal(mpondClusterDelegationInit.add(mpondAmount1)); - expect(delegation).to.equal(delegationInit.add(amount1)); - expect(mpondDelegation).to.equal(mpondDelegationInit.add(mpondAmount1)); - expect(rewardPerShare3.sub(rewardPerShare2)).to.equal(0); - expect(mpondRewardPerShare3.sub(mpondRewardPerShare2)).to.equal(0); - - // ----------------- Give new rewards to cluster --------------- - const rewardAmount1 = rewardAmount.mul(2).div(3); - await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(rewardAmount1); - clusterCommission = rewardAmount1.mul(commission).div(100); - await expect(rewardDelegators._updateRewards(cluster)).to.changeTokenBalances( - pond, - [rewardDelegators, rewardAddress, delegator, delegator1], - [-clusterCommission, clusterCommission, 0, 0] - ); - const rewardPerShare4 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - const mpondRewardPerShare4 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); - - expect(rewardPerShare4.sub(rewardPerShare3)).to.equal(rewardAmount1.sub(clusterCommission).mul(e30).div(2).div(clusterDelegation)); - expect(mpondRewardPerShare4.sub(mpondRewardPerShare3)).to.equal(rewardAmount1.sub(clusterCommission).mul(e30).div(2).div(mpondClusterDelegation)); - - // ----------------- Gives reward when previous delegation is non 0 and user has prev rewards, new rewards for cluster --------------- - const amount2 = FuzzedNumber.randomInRange(100000, 500000); - const mpondAmount2 = FuzzedNumber.randomInRange(100, 500); - - clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - mpondClusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); - delegationInit = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); - mpondDelegationInit = await rewardDelegators.getDelegation(cluster, delegator1, mpondTokenId); - // TODO: Fix add(1) at the end - let delegator1CurrentReward = rewardPerShare4.add(rewardAmount1.sub(clusterCommission).mul(e30).div(2).div(clusterDelegationInit)).sub(rewardPerShare2).mul(delegationInit).div(e30).add(1); - let delegator1CurrentRewardMpond = mpondRewardPerShare4.add(rewardAmount1.sub(clusterCommission).mul(e30).div(2).div(mpondClusterDelegation)).sub(mpondRewardPerShare2).mul(mpondDelegationInit).div(e30).add(1); - clusterCommission = rewardAmount1.mul(commission).div(100); - await expect(rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator1, - cluster, - [pondTokenId, mpondTokenId], - [amount2, mpondAmount2]) - ).to.changeTokenBalances( - pond, - [rewardDelegators, rewardAddress, delegator1], - [-delegator1CurrentReward.add(delegator1CurrentRewardMpond).add(clusterCommission), clusterCommission, delegator1CurrentReward.add(delegator1CurrentRewardMpond)], - 2 - ); - clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - mpondClusterDelegation = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); - delegation = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); - mpondDelegation = await rewardDelegators.getDelegation(cluster, delegator1, mpondTokenId); - const rewardPerShare5 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - const mpondRewardPerShare5 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); - - expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount2)); - expect(mpondClusterDelegation).to.equal(mpondClusterDelegationInit.add(mpondAmount2)); - expect(delegation).to.equal(delegationInit.add(amount2)); - expect(mpondDelegation).to.equal(mpondDelegationInit.add(mpondAmount2)); - expect(rewardPerShare5.sub(rewardPerShare4).div(e14)).to.equal(rewardAmount1.sub(clusterCommission).div(2).mul(e30).div(clusterDelegationInit).div(e14)); - expect(mpondRewardPerShare5.sub(mpondRewardPerShare4).div(e14)).to.equal(rewardAmount1.sub(clusterCommission).div(2).mul(e30).div(mpondClusterDelegation).div(e14)); - - // ----------------- Gives reward when previous delegation is non 0 and user has prev rewards, new rewards for cluster,0 commission --------------- - await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster).returns(0, rewardAddress); - - clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - mpondClusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); - delegationInit = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); - mpondDelegationInit = await rewardDelegators.getDelegation(cluster, delegator, mpondTokenId); - // TODO: Fix add(1) at the end - let changeInRewardPerShare = rewardAmount1.mul(e30).div(2).div(clusterDelegationInit); - let changeInRewardPerShareMpond = rewardAmount1.mul(e30).div(2).div(mpondClusterDelegation); - delegatorCurrentReward = rewardPerShare5.add(changeInRewardPerShare).sub(rewardPerShare3).mul(delegationInit).div(e30).add(1); - delegatorCurrentRewardMpond = mpondRewardPerShare5.add(changeInRewardPerShareMpond).sub(mpondRewardPerShare3).mul(mpondDelegationInit).div(e30).add(1); - await expect(rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator, - cluster, - [pondTokenId, mpondTokenId], - [amount2, mpondAmount2]) - ).to.changeTokenBalances( - pond, - [rewardDelegators, rewardAddress, delegator], - [-delegatorCurrentReward.add(delegatorCurrentRewardMpond), 0, delegatorCurrentReward.add(delegatorCurrentRewardMpond)] - ); - clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - mpondClusterDelegation = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); - delegation = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); - mpondDelegation = await rewardDelegators.getDelegation(cluster, delegator, mpondTokenId); - const rewardPerShare6 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - const mpondRewardPerShare6 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); - - expect(clusterDelegation).to.equal(clusterDelegationInit.add(amount2)); - expect(mpondClusterDelegation).to.equal(mpondClusterDelegationInit.add(mpondAmount2)); - expect(delegation).to.equal(delegationInit.add(amount2)); - expect(mpondDelegation).to.equal(mpondDelegationInit.add(mpondAmount2)); - expect(rewardPerShare6.sub(rewardPerShare5)).to.equal(changeInRewardPerShare); - expect(mpondRewardPerShare6.sub(mpondRewardPerShare5)).to.equal(changeInRewardPerShareMpond); - }); + await expect(rewardDelegators.connect(impersonatedClusterRegistry).updateClusterDelegation(cluster, networkId)).to.be.revertedWith( + "upsert called" + ); + await expect(rewardDelegators.connect(impersonatedClusterRegistry).updateClusterDelegation(cluster1, networkId)).to.be.revertedWith( + "delete called" + ); + await expect(rewardDelegators.connect(impersonatedClusterRegistry).updateClusterDelegation(cluster2, networkId)).to.be.revertedWith( + "delete called" + ); + }); + + it("remove cluster delegation", async () => { + const cluster = await registeredClusters[0].getAddress(); + await fakeClusterSelectors["ETH"].mock.deleteIfPresent.revertsWithReason("delete called"); + expect(fakeClusterSelectors["ETH"].address).to.not.equal(ethers.constants.AddressZero); + await rewardDelegators.connect(impersonatedClusterRegistry).removeClusterDelegation(cluster, ethers.utils.id("RANDOM")); + await expect( + rewardDelegators.connect(impersonatedClusterRegistry).removeClusterDelegation(cluster, ethers.utils.id("ETH")) + ).to.be.revertedWith("delete called"); + }); + + it("withdraw rewards", async () => { + const delegator = await delegators[0].getAddress(); + const delegator1 = await delegators[1].getAddress(); + const cluster = await registeredClusters[0].getAddress(); + const cluster1 = await registeredClusters[1].getAddress(); + const cluster2 = await registeredClusters[2].getAddress(); + const rewardAddress = + registeredClusterRewardAddresses[FuzzedNumber.randomInRange(0, registeredClusterRewardAddresses.length).toNumber()]; + const networkId = ethers.utils.id("ETH"); + + let commission = FuzzedNumber.randomInRange(0, 100).toNumber(); + await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(0); + await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster).returns(commission, rewardAddress); + await fakeClusterRegistry.mock.getNetwork.withArgs(cluster).returns(networkId); + await fakeClusterRewards.mock.claimReward.withArgs(cluster1).returns(0); + await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster1).returns(commission, rewardAddress); + await fakeClusterRegistry.mock.getNetwork.withArgs(cluster1).returns(networkId); + await fakeClusterRewards.mock.claimReward.withArgs(cluster2).returns(0); + await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster2).returns(commission, rewardAddress); + await fakeClusterRegistry.mock.getNetwork.withArgs(cluster2).returns(networkId); + // TODO: only upsert with `cluster` as arg are accepted + await fakeClusterSelectors["ETH"].mock.upsert.returns(); + + // TODO: fix this + // await expect(rewardDelegators["withdrawRewards(address,address)"](delegator, cluster)) + // .to.changeTokenBalances(pond, [rewardDelegators.address, delegator, cluster], [0, 0, 0]); + + await rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster, [pondTokenId], [20000000]); + await rewardDelegators.connect(imperonatedStakeManager).delegate(delegator1, cluster1, [pondTokenId], [100000]); + await rewardDelegators.connect(imperonatedStakeManager).delegate(delegator1, cluster, [pondTokenId, mpondTokenId], [4000000, 6]); + await rewardDelegators.connect(imperonatedStakeManager).delegate(delegator1, cluster2, [mpondTokenId], [10]); + let reward = 100000000; + await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(reward); + await fakeClusterRewards.mock.claimReward.withArgs(cluster1).returns(reward); + await fakeClusterRewards.mock.claimReward.withArgs(cluster2).returns(reward); + await rewardDelegators._updateRewards(cluster); + await rewardDelegators._updateRewards(cluster1); + await rewardDelegators._updateRewards(cluster2); + await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(0); + await fakeClusterRewards.mock.claimReward.withArgs(cluster1).returns(0); + await fakeClusterRewards.mock.claimReward.withArgs(cluster2).returns(0); + + let rewardAfterCommission = (reward * (100 - commission)) / 100; + let delegatorClusterReward = Math.floor((rewardAfterCommission * 20000000) / (20000000 + 4000000) / 2); + await expect(rewardDelegators["withdrawRewards(address,address)"](delegator, cluster)).to.changeTokenBalances( + pond, + [rewardDelegators.address, delegator, cluster], + [-delegatorClusterReward, delegatorClusterReward, 0] + ); + await expect(rewardDelegators["withdrawRewards(address,address)"](delegator1, cluster1)).to.changeTokenBalances( + pond, + [rewardDelegators.address, delegator1, cluster1], + [-rewardAfterCommission, rewardAfterCommission, 0] + ); - it("undelegate from cluster", async () => { - const delegator = await delegators[0].getAddress(); - const delegator1 = await delegators[1].getAddress(); - const cluster = await registeredClusters[FuzzedNumber.randomInRange(0, registeredClusters.length).toNumber()].getAddress(); - const rewardAddress = registeredClusterRewardAddresses[FuzzedNumber.randomInRange(0, registeredClusterRewardAddresses.length).toNumber()]; - const networkId = ethers.utils.id("ETH"); - - let rewardAmount = FuzzedNumber.randomInRange(100000, 500000); - let commission = FuzzedNumber.randomInRange(0, 100); - await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(rewardAmount); - await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster).returns(commission, rewardAddress); - await fakeClusterRegistry.mock.getNetwork.withArgs(cluster).returns(networkId); - // TODO: only upsert with `cluster` as arg are accepted - await fakeClusterSelectors["ETH"].mock.upsert.returns(); - // ----------------- Setup clusters and their delegations --------------- - const amount1 = FuzzedNumber.randomInRange(e18, e20); - const mpondAmount1 = FuzzedNumber.randomInRange(e18, e20); - const amount2 = FuzzedNumber.randomInRange(e18, e20); - const mpondAmount2 = FuzzedNumber.randomInRange(e18, e20); - await rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster, [pondTokenId, mpondTokenId], [amount1, mpondAmount1]); - const rewardPerShare1 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - const mpondRewardPerShare1 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); - await rewardDelegators.connect(imperonatedStakeManager).delegate(delegator1, cluster, [pondTokenId, mpondTokenId], [amount2, mpondAmount2]); - const rewardPerShare2 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - const mpondRewardPerShare2 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); - - // ----------------- Undelegate when there are rewards for delegator and no new rewards for cluster --------------- - await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(0); - - await expect(rewardDelegators.undelegate( - delegator, - cluster, - [pondTokenId, mpondTokenId], - [amount1.mul(1).div(3), mpondAmount1.mul(1).div(3)]) - ).to.be.revertedWith("RD:OS-only stake contract can invoke"); - - let clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - let mpondClusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); - let delegationInit = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); - let mpondDelegationInit = await rewardDelegators.getDelegation(cluster, delegator, mpondTokenId); - let delegatorCurrentReward = rewardPerShare2.sub(rewardPerShare1).mul(delegationInit).div(e30); - let delegatorCurrentRewardMpond = mpondRewardPerShare2.sub(mpondRewardPerShare1).mul(mpondDelegationInit).div(e30); - await expect(rewardDelegators.connect(imperonatedStakeManager).undelegate( - delegator, - cluster, - [pondTokenId, mpondTokenId], - [amount1.div(3), mpondAmount1.div(3)]) - ).to.changeTokenBalances( - pond, - [rewardDelegators, rewardAddress, delegator], - [-(delegatorCurrentReward.add(delegatorCurrentRewardMpond)), 0, delegatorCurrentReward.add(delegatorCurrentRewardMpond)] - ); - let clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - let mpondClusterDelegation = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); - let delegation = await rewardDelegators.getDelegation(cluster, delegator, pondTokenId); - let mpondDelegation = await rewardDelegators.getDelegation(cluster, delegator, mpondTokenId); - let rewardPerShare3 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - let mpondRewardPerShare3 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); - - expect(clusterDelegation).to.equal(clusterDelegationInit.sub(amount1.div(3))); - expect(mpondClusterDelegation).to.equal(mpondClusterDelegationInit.sub(mpondAmount1.div(3))); - expect(delegation).to.equal(delegationInit.sub(amount1.div(3))); - expect(mpondDelegation).to.equal(mpondDelegationInit.sub(mpondAmount1.div(3))); - expect(rewardPerShare3.sub(rewardPerShare2)).to.equal(0); - expect(mpondRewardPerShare3.sub(mpondRewardPerShare2)).to.equal(0); - - // ----------------- Give new rewards to cluster --------------- - await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(rewardAmount); - - const rewardAmount1 = rewardAmount.mul(2).div(3); - await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(rewardAmount1); - let clusterCommission = rewardAmount1.mul(commission).div(100); - await expect(rewardDelegators._updateRewards(cluster)).to.changeTokenBalances( - pond, - [rewardDelegators, rewardAddress, delegator, delegator1], - [-clusterCommission, clusterCommission, 0, 0] - ); - const rewardPerShare4 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - const mpondRewardPerShare4 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); - - expect(rewardPerShare4.sub(rewardPerShare3)).to.equal(rewardAmount1.sub(clusterCommission).mul(e30).div(2).div(clusterDelegation)); - expect(mpondRewardPerShare4.sub(mpondRewardPerShare3)).to.equal(rewardAmount1.sub(clusterCommission).mul(e30).div(2).div(mpondClusterDelegation)); - - // ----------------- Undelegate when there are rewards for delegator and new rewards for cluster --------------- - clusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - mpondClusterDelegationInit = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); - delegationInit = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); - mpondDelegationInit = await rewardDelegators.getDelegation(cluster, delegator1, mpondTokenId); - // TODO: Fix add(1) at the end - let changeInRewardPerShare = rewardAmount1.sub(clusterCommission).mul(e30).div(2).div(clusterDelegation); - let changeInRewardPerShareMpond = rewardAmount1.sub(clusterCommission).mul(e30).div(2).div(mpondClusterDelegation); - delegatorCurrentReward = rewardPerShare4.add(changeInRewardPerShare).sub(rewardPerShare2).mul(delegationInit).div(e30).add(1); - delegatorCurrentRewardMpond = mpondRewardPerShare4.add(changeInRewardPerShareMpond).sub(mpondRewardPerShare2).mul(mpondDelegationInit).div(e30).add(1); - clusterCommission = commission.mul(rewardAmount1).div(100); - - await expect(rewardDelegators.connect(imperonatedStakeManager).undelegate( - delegator1, - cluster, - [pondTokenId, mpondTokenId], - [amount2.div(2), mpondAmount2.div(2)]) - ).to.changeTokenBalances( - pond, - [rewardDelegators, rewardAddress, delegator1], - [-(delegatorCurrentReward.add(delegatorCurrentRewardMpond).add(clusterCommission)), clusterCommission, delegatorCurrentReward.add(delegatorCurrentRewardMpond)], - 2 - ); - clusterDelegation = await rewardDelegators.getClusterDelegation(cluster, pondTokenId); - mpondClusterDelegation = await rewardDelegators.getClusterDelegation(cluster, mpondTokenId); - delegation = await rewardDelegators.getDelegation(cluster, delegator1, pondTokenId); - mpondDelegation = await rewardDelegators.getDelegation(cluster, delegator1, mpondTokenId); - let rewardPerShare5 = await rewardDelegators.getAccRewardPerShare(cluster, pondTokenId); - let mpondRewardPerShare5 = await rewardDelegators.getAccRewardPerShare(cluster, mpondTokenId); - - expect(clusterDelegation).to.equal(clusterDelegationInit.sub(amount2.div(2))); - expect(mpondClusterDelegation).to.equal(mpondClusterDelegationInit.sub(mpondAmount2.div(2))); - expect(delegation).to.equal(delegationInit.sub(amount2.div(2))); - expect(mpondDelegation).to.equal(mpondDelegationInit.sub(mpondAmount2.div(2))); - expect(rewardPerShare5.sub(rewardPerShare4)).to.equal(changeInRewardPerShare); - expect(mpondRewardPerShare5.sub(mpondRewardPerShare4)).to.equal(changeInRewardPerShareMpond); - - // ----------------- Undelegate only pond when there are rewards for delegator and new rewards for cluster --------------- - - // ----------------- Undelegate only mpond when there are rewards for delegator and new rewards for cluster --------------- - }); + await expect(rewardDelegators["withdrawRewards(address,address)"](delegator1, cluster2)).to.changeTokenBalances( + pond, + [rewardDelegators.address, delegator1, cluster2], + [-rewardAfterCommission, rewardAfterCommission, 0] + ); - it("update cluster delegation", async () => { - const delegator = await delegators[0].getAddress(); - const delegator1 = await delegators[1].getAddress(); - const cluster = await registeredClusters[0].getAddress(); - const cluster1 = await registeredClusters[1].getAddress(); - const cluster2 = await registeredClusters[2].getAddress(); - const rewardAddress = registeredClusterRewardAddresses[FuzzedNumber.randomInRange(0, registeredClusterRewardAddresses.length).toNumber()]; - const networkId = ethers.utils.id("ETH"); - - let rewardAmount = FuzzedNumber.randomInRange(100000, 500000); - let commission = FuzzedNumber.randomInRange(0, 100); - await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(rewardAmount); - await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster).returns(commission, rewardAddress); - await fakeClusterRegistry.mock.getNetwork.withArgs(cluster).returns(networkId); - await fakeClusterRewards.mock.claimReward.withArgs(cluster1).returns(rewardAmount); - await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster1).returns(commission, rewardAddress); - await fakeClusterRegistry.mock.getNetwork.withArgs(cluster1).returns(networkId); - await fakeClusterRewards.mock.claimReward.withArgs(cluster2).returns(rewardAmount); - await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster2).returns(commission, rewardAddress); - await fakeClusterRegistry.mock.getNetwork.withArgs(cluster2).returns(networkId); - // TODO: only upsert with `cluster` as arg are accepted - await fakeClusterSelectors["ETH"].mock.upsert.returns(); - - await rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator, - cluster, - [pondTokenId], - [10000000000] - ); - await rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator, - cluster1, - [mpondTokenId], - [6] - ); - await rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator, - cluster2, - [mpondTokenId], - [10] - ); - - await fakeClusterSelectors["ETH"].mock.upsert.revertsWithReason("unexpected upsert"); - await fakeClusterSelectors["ETH"].mock.deleteIfPresent.revertsWithReason("unexpected delete"); - - await rewardDelegators.updateThresholdForSelection(networkId, 10000000); - await expect(rewardDelegators.connect(impersonatedClusterRegistry).updateClusterDelegation(cluster, networkId)) - .to.be.revertedWith("unexpected delete"); - await expect(rewardDelegators.connect(impersonatedClusterRegistry).updateClusterDelegation(cluster1, networkId)) - .to.be.revertedWith("unexpected delete"); - await expect(rewardDelegators.connect(impersonatedClusterRegistry).updateClusterDelegation(cluster2, networkId)) - .to.be.revertedWith("unexpected upsert"); - - await rewardDelegators.updateThresholdForSelection(networkId, 0); - await fakeClusterSelectors["ETH"].mock.upsert.returns(); - await fakeClusterSelectors["ETH"].mock.deleteIfPresent.returns(); - await rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator, - cluster, - [mpondTokenId], - [12] - ); - await rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator, - cluster1, - [pondTokenId], - [4000000] - ); - await rewardDelegators.connect(imperonatedStakeManager).undelegate(delegator, cluster2, [mpondTokenId], [1]); - - await rewardDelegators.updateThresholdForSelection(networkId, 10000000); - await fakeClusterSelectors["ETH"].mock.upsert.revertsWithReason("upsert called"); - await fakeClusterSelectors["ETH"].mock.deleteIfPresent.revertsWithReason("delete called"); - await rewardDelegators.connect(impersonatedClusterRegistry).updateClusterDelegation(cluster, ethers.utils.id("RANDOM")); - - await expect(rewardDelegators.connect(impersonatedClusterRegistry).updateClusterDelegation(cluster, networkId)) - .to.be.revertedWith("upsert called"); - await expect(rewardDelegators.connect(impersonatedClusterRegistry).updateClusterDelegation(cluster1, networkId)) - .to.be.revertedWith("delete called"); - await expect(rewardDelegators.connect(impersonatedClusterRegistry).updateClusterDelegation(cluster2, networkId)) - .to.be.revertedWith("delete called"); - }); + // TODO: understand the lower total balance + await expect(rewardDelegators["withdrawRewards(address,address)"](delegator1, cluster)).to.changeTokenBalances( + pond, + [rewardDelegators.address, delegator1, cluster], + [-(rewardAfterCommission - delegatorClusterReward), rewardAfterCommission - delegatorClusterReward, 0], + 2 + ); - it("remove cluster delegation", async () => { - const cluster = await registeredClusters[0].getAddress(); - await fakeClusterSelectors["ETH"].mock.deleteIfPresent.revertsWithReason("delete called"); - expect(fakeClusterSelectors["ETH"].address).to.not.equal(ethers.constants.AddressZero); - await rewardDelegators.connect(impersonatedClusterRegistry).removeClusterDelegation(cluster, ethers.utils.id("RANDOM")); - await expect(rewardDelegators.connect(impersonatedClusterRegistry).removeClusterDelegation(cluster, ethers.utils.id("ETH"))) - .to.be.revertedWith("delete called"); - }); + // no reward when already withdrawn + await expect(rewardDelegators["withdrawRewards(address,address)"](delegator1, cluster1)).to.changeTokenBalances( + pond, + [rewardDelegators.address, delegator1, cluster1], + [0, 0, 0] + ); - it("withdraw rewards", async () => { - const delegator = await delegators[0].getAddress(); - const delegator1 = await delegators[1].getAddress(); - const cluster = await registeredClusters[0].getAddress(); - const cluster1 = await registeredClusters[1].getAddress(); - const cluster2 = await registeredClusters[2].getAddress(); - const rewardAddress = registeredClusterRewardAddresses[FuzzedNumber.randomInRange(0, registeredClusterRewardAddresses.length).toNumber()]; - const networkId = ethers.utils.id("ETH"); - - let commission = FuzzedNumber.randomInRange(0, 100).toNumber(); - await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(0); - await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster).returns(commission, rewardAddress); - await fakeClusterRegistry.mock.getNetwork.withArgs(cluster).returns(networkId); - await fakeClusterRewards.mock.claimReward.withArgs(cluster1).returns(0); - await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster1).returns(commission, rewardAddress); - await fakeClusterRegistry.mock.getNetwork.withArgs(cluster1).returns(networkId); - await fakeClusterRewards.mock.claimReward.withArgs(cluster2).returns(0); - await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster2).returns(commission, rewardAddress); - await fakeClusterRegistry.mock.getNetwork.withArgs(cluster2).returns(networkId); - // TODO: only upsert with `cluster` as arg are accepted - await fakeClusterSelectors["ETH"].mock.upsert.returns(); - - // TODO: fix this - // await expect(rewardDelegators["withdrawRewards(address,address)"](delegator, cluster)) - // .to.changeTokenBalances(pond, [rewardDelegators.address, delegator, cluster], [0, 0, 0]); - - await rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator, - cluster, - [pondTokenId], - [20000000] - ); - await rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator1, - cluster1, - [pondTokenId], - [100000] - ); - await rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator1, - cluster, - [pondTokenId, mpondTokenId], - [4000000, 6] - ); - await rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator1, - cluster2, - [mpondTokenId], - [10] - ); - let reward = 100000000; - await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(reward); - await fakeClusterRewards.mock.claimReward.withArgs(cluster1).returns(reward); - await fakeClusterRewards.mock.claimReward.withArgs(cluster2).returns(reward); - await rewardDelegators._updateRewards(cluster); - await rewardDelegators._updateRewards(cluster1); - await rewardDelegators._updateRewards(cluster2); - await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(0); - await fakeClusterRewards.mock.claimReward.withArgs(cluster1).returns(0); - await fakeClusterRewards.mock.claimReward.withArgs(cluster2).returns(0); - - let rewardAfterCommission = reward*(100-commission)/100; - let delegatorClusterReward = Math.floor(rewardAfterCommission*20000000/(20000000 + 4000000)/2); - await expect(rewardDelegators["withdrawRewards(address,address)"](delegator, cluster)) - .to.changeTokenBalances(pond, [rewardDelegators.address, delegator, cluster], [-delegatorClusterReward, delegatorClusterReward, 0]); - await expect(rewardDelegators["withdrawRewards(address,address)"](delegator1, cluster1)) - .to.changeTokenBalances(pond, [rewardDelegators.address, delegator1, cluster1], [-rewardAfterCommission, rewardAfterCommission, 0]); - - await expect(rewardDelegators["withdrawRewards(address,address)"](delegator1, cluster2)) - .to.changeTokenBalances(pond, [rewardDelegators.address, delegator1, cluster2], [-rewardAfterCommission, rewardAfterCommission, 0]); - - // TODO: understand the lower total balance - await expect(rewardDelegators["withdrawRewards(address,address)"](delegator1, cluster)) - .to.changeTokenBalances(pond, [rewardDelegators.address, delegator1, cluster], [-(rewardAfterCommission - delegatorClusterReward), rewardAfterCommission - delegatorClusterReward, 0], 2); - - // no reward when already withdrawn - await expect(rewardDelegators["withdrawRewards(address,address)"](delegator1, cluster1)) - .to.changeTokenBalances(pond, [rewardDelegators.address, delegator1, cluster1], [0, 0, 0]); - - await expect(rewardDelegators["withdrawRewards(address,address)"](delegator1, cluster2)) - .to.changeTokenBalances(pond, [rewardDelegators.address, delegator1, cluster2], [0, 0, 0]); - - await expect(rewardDelegators["withdrawRewards(address,address)"](delegator1, cluster)) - .to.changeTokenBalances(pond, [rewardDelegators.address, delegator1, cluster], [0, 0, 0]); - }); + await expect(rewardDelegators["withdrawRewards(address,address)"](delegator1, cluster2)).to.changeTokenBalances( + pond, + [rewardDelegators.address, delegator1, cluster2], + [0, 0, 0] + ); - it("refresh cluster delegation", async () => { - const delegator = await delegators[0].getAddress(); - const delegator1 = await delegators[1].getAddress(); - const cluster = await registeredClusters[0].getAddress(); - const cluster1 = await registeredClusters[1].getAddress(); - const cluster2 = await registeredClusters[2].getAddress(); - const rewardAddress = registeredClusterRewardAddresses[FuzzedNumber.randomInRange(0, registeredClusterRewardAddresses.length).toNumber()]; - const networkId = ethers.utils.id("ETH"); - - let rewardAmount = FuzzedNumber.randomInRange(100000, 500000); - let commission = FuzzedNumber.randomInRange(0, 100); - await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(0); - await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster).returns(commission, rewardAddress); - await fakeClusterRegistry.mock.getNetwork.withArgs(cluster).returns(networkId); - await fakeClusterRewards.mock.claimReward.withArgs(cluster1).returns(0); - await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster1).returns(commission, rewardAddress); - await fakeClusterRegistry.mock.getNetwork.withArgs(cluster1).returns(networkId); - await fakeClusterRewards.mock.claimReward.withArgs(cluster2).returns(0); - await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster2).returns(commission, rewardAddress); - await fakeClusterRegistry.mock.getNetwork.withArgs(cluster2).returns(networkId); - // TODO: only upsert with `cluster` as arg are accepted - await fakeClusterSelectors["ETH"].mock.upsert.returns(); - - await rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator, - cluster, - [pondTokenId], - [1000000] - ); - await rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator1, - cluster1, - [pondTokenId], - [10000] - ); - await rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator1, - cluster, - [pondTokenId, mpondTokenId], - [2000000, 6] - ); - await rewardDelegators.connect(imperonatedStakeManager).delegate( - delegator1, - cluster2, - [mpondTokenId], - [100] - ); - - await fakeClusterSelectors["ETH"].mock.upsertMultiple.withArgs([cluster, cluster1, cluster2], [3000, 100, 10000]).returns(); - await rewardDelegators.refreshClusterDelegation(networkId, [cluster, cluster1, cluster2]); - await fakeClusterSelectors["ETH"].mock.upsertMultiple.withArgs([cluster, cluster1, cluster2], [3000, 100, 10000]).reverts(); - - await rewardDelegators.updateThresholdForSelection(networkId, 1000000); - await fakeClusterSelectors["ETH"].mock.upsertMultiple.withArgs([cluster, cluster2], [3000, 10000]).returns(); - await rewardDelegators.refreshClusterDelegation(networkId, [cluster, cluster1, cluster2]); - await fakeClusterSelectors["ETH"].mock.upsertMultiple.withArgs([cluster, cluster2], [3000, 10000]).reverts(); - - await rewardDelegators.updateThresholdForSelection(networkId, 6000000); - await fakeClusterSelectors["ETH"].mock.upsertMultiple.withArgs([cluster, cluster2], [3000, 10000]).returns(); - await rewardDelegators.refreshClusterDelegation(networkId, [cluster, cluster1, cluster2]); - await fakeClusterSelectors["ETH"].mock.upsertMultiple.withArgs([cluster, cluster2], [3000, 10000]).reverts(); - - await rewardDelegators.updateThresholdForSelection(networkId, 6000001); - await fakeClusterSelectors["ETH"].mock.upsertMultiple.withArgs([cluster2], [10000]).returns(); - await rewardDelegators.refreshClusterDelegation(networkId, [cluster, cluster1, cluster2]); - await fakeClusterSelectors["ETH"].mock.upsertMultiple.withArgs([cluster2], [10000]).reverts(); - }); -}); \ No newline at end of file + await expect(rewardDelegators["withdrawRewards(address,address)"](delegator1, cluster)).to.changeTokenBalances( + pond, + [rewardDelegators.address, delegator1, cluster], + [0, 0, 0] + ); + }); + + it("refresh cluster delegation", async () => { + const delegator = await delegators[0].getAddress(); + const delegator1 = await delegators[1].getAddress(); + const cluster = await registeredClusters[0].getAddress(); + const cluster1 = await registeredClusters[1].getAddress(); + const cluster2 = await registeredClusters[2].getAddress(); + const rewardAddress = + registeredClusterRewardAddresses[FuzzedNumber.randomInRange(0, registeredClusterRewardAddresses.length).toNumber()]; + const networkId = ethers.utils.id("ETH"); + + let rewardAmount = FuzzedNumber.randomInRange(100000, 500000); + let commission = FuzzedNumber.randomInRange(0, 100); + await fakeClusterRewards.mock.claimReward.withArgs(cluster).returns(0); + await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster).returns(commission, rewardAddress); + await fakeClusterRegistry.mock.getNetwork.withArgs(cluster).returns(networkId); + await fakeClusterRewards.mock.claimReward.withArgs(cluster1).returns(0); + await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster1).returns(commission, rewardAddress); + await fakeClusterRegistry.mock.getNetwork.withArgs(cluster1).returns(networkId); + await fakeClusterRewards.mock.claimReward.withArgs(cluster2).returns(0); + await fakeClusterRegistry.mock.getRewardInfo.withArgs(cluster2).returns(commission, rewardAddress); + await fakeClusterRegistry.mock.getNetwork.withArgs(cluster2).returns(networkId); + // TODO: only upsert with `cluster` as arg are accepted + await fakeClusterSelectors["ETH"].mock.upsert.returns(); + + await rewardDelegators.connect(imperonatedStakeManager).delegate(delegator, cluster, [pondTokenId], [1000000]); + await rewardDelegators.connect(imperonatedStakeManager).delegate(delegator1, cluster1, [pondTokenId], [10000]); + await rewardDelegators.connect(imperonatedStakeManager).delegate(delegator1, cluster, [pondTokenId, mpondTokenId], [2000000, 6]); + await rewardDelegators.connect(imperonatedStakeManager).delegate(delegator1, cluster2, [mpondTokenId], [100]); + + await fakeClusterSelectors["ETH"].mock.upsertMultiple.withArgs([cluster, cluster1, cluster2], [3000, 100, 10000]).returns(); + await rewardDelegators.refreshClusterDelegation(networkId, [cluster, cluster1, cluster2]); + await fakeClusterSelectors["ETH"].mock.upsertMultiple.withArgs([cluster, cluster1, cluster2], [3000, 100, 10000]).reverts(); + + await rewardDelegators.updateThresholdForSelection(networkId, 1000000); + await fakeClusterSelectors["ETH"].mock.upsertMultiple.withArgs([cluster, cluster2], [3000, 10000]).returns(); + await rewardDelegators.refreshClusterDelegation(networkId, [cluster, cluster1, cluster2]); + await fakeClusterSelectors["ETH"].mock.upsertMultiple.withArgs([cluster, cluster2], [3000, 10000]).reverts(); + + await rewardDelegators.updateThresholdForSelection(networkId, 6000000); + await fakeClusterSelectors["ETH"].mock.upsertMultiple.withArgs([cluster, cluster2], [3000, 10000]).returns(); + await rewardDelegators.refreshClusterDelegation(networkId, [cluster, cluster1, cluster2]); + await fakeClusterSelectors["ETH"].mock.upsertMultiple.withArgs([cluster, cluster2], [3000, 10000]).reverts(); + + await rewardDelegators.updateThresholdForSelection(networkId, 6000001); + await fakeClusterSelectors["ETH"].mock.upsertMultiple.withArgs([cluster2], [10000]).returns(); + await rewardDelegators.refreshClusterDelegation(networkId, [cluster, cluster1, cluster2]); + await fakeClusterSelectors["ETH"].mock.upsertMultiple.withArgs([cluster2], [10000]).reverts(); + }); +}); From ed889b541ec84fea93ef0246cd6f58b97c82d259 Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Mon, 15 May 2023 13:44:25 +0400 Subject: [PATCH 04/30] remove removeBalances in cluster rewards and relevant tests --- contracts/staking/ClusterRewards.sol | 12 ------------ test/staking/ClusterRewards.ts | 9 --------- 2 files changed, 21 deletions(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index aa7f8e66..0f2cca3d 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -467,18 +467,6 @@ contract ClusterRewards is emit AddReceiverBalance(receiver, amount); } - // @notice Receiver/Staker can remove the unused balance using his signer address - // @dev This may be a attach vector, and hence could be removed - // TODO Check if this removing the balance should be added as feature - // function removeReceiverBalance(address to, uint256 amount) public { - // address staker = receiverStaking.signerToStaker(msg.sender); - // require(staker != address(0), "CRW: address 0"); - // receiverBalance[staker] -= amount; - // PONDToken.transfer(to, amount); - - // emit RemoveReceiverBalance(staker, amount); - // } - // @notice Set Receiver Epoch, set to 0, to disable extra rewards function setReceiverRewardPerEpoch(uint256 rewardPerEpoch) public { address staker = receiverStaking.signerToStaker(msg.sender); diff --git a/test/staking/ClusterRewards.ts b/test/staking/ClusterRewards.ts index 1153fb60..c721db8f 100644 --- a/test/staking/ClusterRewards.ts +++ b/test/staking/ClusterRewards.ts @@ -1623,15 +1623,6 @@ describe("ClusterRewards: Add Receiver extra payment", function () { .withArgs(await staker.getAddress(), 100000); }); - // TODO: add remove balance test if this feature is enabled - // it.skip("Remove remaining tokens (unskip it if remove balance is added as a feature)", async () => { - // await mockToken.connect(signer).approve(clusterRewards.address, 100000); - // await clusterRewards.connect(signer).addReceiverBalance(await staker.getAddress(), 100000); - // await expect(clusterRewards.connect(signer).removeReceiverBalance(await signer.getAddress(), 100000)) - // .emit(clusterRewards, "RemoveReceiverBalance") - // .withArgs(await staker.getAddress(), 100000); - // }); - it("Set Receiver Reward per Epoch", async () => { await expect(clusterRewards.connect(signer).setReceiverRewardPerEpoch(50)) .to.emit(clusterRewards, "UpdateReceiverRewardPerEpoch") From ae81fa2e717d45fd69803864535a6dc439b90f54 Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Wed, 17 May 2023 13:27:17 +0400 Subject: [PATCH 05/30] fix benchmarks --- benchmarks/ClusterRewards.ts | 473 ++++++++++--------- benchmarks/fixtures/ClusterRewards.ts | 202 ++++---- contracts/mocks/ArbGasInfo.sol | 24 + contracts/staking/ClusterSelector.sol | 5 +- contracts/staking/interfaces/IArbGasInfo.sol | 7 + deployments/staking/ClusterRewards.ts | 78 +-- 6 files changed, 432 insertions(+), 357 deletions(-) create mode 100644 contracts/mocks/ArbGasInfo.sol create mode 100644 contracts/staking/interfaces/IArbGasInfo.sol diff --git a/benchmarks/ClusterRewards.ts b/benchmarks/ClusterRewards.ts index 49fe27cf..bb8e91d9 100755 --- a/benchmarks/ClusterRewards.ts +++ b/benchmarks/ClusterRewards.ts @@ -5,239 +5,268 @@ import { BigNumber, BigNumberish, constants, Contract, PopulatedTransaction, Sig import { randomlyDivideInXPieces, skipTime } from "./helpers/util"; const estimator = new ethers.Contract("0x000000000000000000000000000000000000006c", [ - "function getPricesInArbGas() view returns(uint256 gasPerL2Tx, uint256 gasPerL1CallDataByte, uint256)" + "function getPricesInArbGas() view returns(uint256 gasPerL2Tx, uint256 gasPerL1CallDataByte, uint256)", ]); const mainnetProvider = new ethers.providers.JsonRpcProvider("https://arb1.arbitrum.io/rpc"); +const deadAddress = "0xdeaddeadabcdabcd000000001111111122222222"; + describe("Cluster Rewards", async () => { - benchmarkDeployment('ClusterRewards', [], [ + benchmarkDeployment( + "ClusterRewards", + [deadAddress, deadAddress], + [ "0x000000000000000000000000000000000000dEaD", "0x000000000000000000000000000000000000dEaD", "0x000000000000000000000000000000000000dEaD", [ethers.utils.id("ETH"), ethers.utils.id("DOT"), ethers.utils.id("NEAR")], - [100, 200, 300], + [100, 200, 300], [ - "0x000000000000000000000000000000000000dEaD", - "0x000000000000000000000000000000000000dEaD", - "0x000000000000000000000000000000000000dEaD", + "0x000000000000000000000000000000000000dEaD", + "0x000000000000000000000000000000000000dEaD", + "0x000000000000000000000000000000000000dEaD", ], 60000, - ]); - - describe("issue tickets", async () => { - let pond: Contract; - let receiverStaking: Contract; - let clusterSelector: Contract; - let clusterRewards: Contract; - let admin: Signer; - let rewardDelegatorsMock: Signer; - let nodesInserted: number; - let receivers: Signer[]; - let receiverSigners: Signer[]; - let l1GasDetails: any; - - const MAX_TICKETS = BigNumber.from(2).pow(16); - const DAY = 60*60*24; - const EPOCH_LENGTH = 15*60; - - interface SignedTicket { - tickets: BigNumberish[]; - v: number; - r: string; - s: string; - } - - beforeEach(async function() { - this.timeout(1000000); - ({ - pond, - receiverStaking, - clusterSelector, - clusterRewards, - admin, - rewardDelegatorsMock, - nodesInserted, - receivers, - receiverSigners - } = await waffle.loadFixture(initDataFixture)); - - l1GasDetails = await estimator.connect(mainnetProvider).getPricesInArbGas(); - }); - - it("to single epoch, tickets to 1 - 5 clusters, input clusters ordered", async () => { - const selectedReceiverIndex: number = Math.floor(Math.random()*receivers.length); - const selectedReceiverSigner: Signer = receiverSigners[selectedReceiverIndex]; - - for(let i=1; i <= 5; i++) { - // skip first epoch - await skipTime(EPOCH_LENGTH); - - // select clusters for next epoch - await clusterSelector.selectClusters(); - - // skip to next epoch - await skipTime(EPOCH_LENGTH); - - let epoch = await clusterSelector.getCurrentEpoch(); - const tickets: BigNumber[] = randomlyDivideInXPieces(MAX_TICKETS, i); - - // skip to next epoch so that tickets can be distributed for previous epoch - await skipTime(EPOCH_LENGTH); - - const gasEstimate = await clusterRewards.connect(selectedReceiverSigner)["issueTickets(bytes32,uint24,uint16[])"].estimateGas( - ethers.utils.id("ETH"), epoch, tickets - ); - console.log(`gas used for ${i} cluster : ${gasEstimate.toNumber()}`); - - const tx: PopulatedTransaction = await clusterRewards.connect(selectedReceiverSigner)["issueTickets(bytes32,uint24,uint16[])"].populateTransaction( - ethers.utils.id("ETH"), epoch, tickets - ); - const gasData = await estimator.connect(selectedReceiverSigner).callStatic.gasEstimateComponents(clusterRewards.address, false, tx.data); - console.log(gasData); - } - }); - - it("single epochs, 50 receivers signed tickets to all selected clusters each", async () => { - const noOfReceivers = 50; - // skip first epoch - await skipTime(EPOCH_LENGTH); - - // select clusters for next epoch - await clusterSelector.selectClusters(); - - // skip to next epoch - await skipTime(EPOCH_LENGTH); - - let epoch = await clusterSelector.getCurrentEpoch(); - - // skip to next epoch so that tickets can be distributed for previous epoch - await skipTime(EPOCH_LENGTH); - - // const selectedReceiverIndex: number = Math.floor(Math.random()*(receivers.length - noOfReceivers)); - const selectedReceiverSigners: Signer[] = receiverSigners.slice(0, noOfReceivers); - - const signedTickets: SignedTicket[] = []; - - for(let i=0; i < selectedReceiverSigners.length; i++) { - const tickets = randomlyDivideInXPieces(MAX_TICKETS, 5).map(val => val.toString()); - tickets.pop(); - - const messageHash = utils.keccak256(utils.defaultAbiCoder.encode( - ["bytes32", "uint256", "uint16[]"], - [ethers.utils.id("ETH"), epoch, tickets] - )); - const arrayifyHash = ethers.utils.arrayify(messageHash); - const signedMessage = await selectedReceiverSigners[i].signMessage(arrayifyHash); - const splitSig = utils.splitSignature(signedMessage); - signedTickets[i] = { - tickets, - v: splitSig.v, - r: splitSig.r, - s: splitSig.s - }; - } - - const tx = await clusterRewards["issueTickets(bytes32,uint24,(uint16[],uint8,bytes32,bytes32)[])"]( - ethers.utils.id("ETH"), epoch, signedTickets - ); - - const receipt = await tx.wait(); - console.log(`gas used : ${receipt.gasUsed.sub(21000).toNumber()}`); - console.log(receipt.gasUsed.mul(1600).mul(6).mul(365).div(BigNumber.from(10).pow(10)).toString()); - }); - - it.only("all epochs in a day, tickets to all selected clusters", async function() { - this.timeout(1000000) - const selectedReceiverIndex: number = Math.floor(Math.random()*receivers.length); - const selectedReceiverSigner: Signer = receiverSigners[selectedReceiverIndex]; - - const selectedClusters: string[][] = []; - const issuedTickets: BigNumber[][] = []; - const epochs: BigNumber[] = []; - - for(let i=1; i <= DAY/EPOCH_LENGTH; i++) { - // skip first epoch - await skipTime(EPOCH_LENGTH); - - // select clusters for next epoch - await clusterSelector.selectClusters(); - - // skip to next epoch - await skipTime(EPOCH_LENGTH); - - let epoch = await clusterSelector.getCurrentEpoch(); - let clusters: string[] = await clusterSelector.getClusters(epoch); - selectedClusters.push(clusters); - const tickets: BigNumber[] = randomlyDivideInXPieces(MAX_TICKETS, clusters.length); - tickets.pop(); - - issuedTickets.push(tickets); - epochs.push(epoch); - // skip to next epoch so that tickets can be distributed for previous epoch - await skipTime(EPOCH_LENGTH); - } - - const gasEstimate = await clusterRewards.connect(selectedReceiverSigner).estimateGas["issueTickets(bytes32,uint24[],uint16[][])"]( - ethers.utils.id("ETH"), epochs, issuedTickets - ); - - const tx = await clusterRewards.connect(selectedReceiverSigner).populateTransaction["issueTickets(bytes32,uint24[],uint16[][])"]( - ethers.utils.id("ETH"), epochs, issuedTickets - ); - if(!tx.data) return; - - console.log(`gas used for ${DAY/EPOCH_LENGTH} epochs : ${gasEstimate.toNumber()}`); - console.log("L1 gas cost Per L2 Tx", l1GasDetails.gasPerL2Tx.toNumber(), "L1 Gas cost Per calldata byte", l1GasDetails.gasPerL1CallDataByte.toNumber()); - const l1GasInL2 = l1GasDetails.gasPerL2Tx.add(l1GasDetails.gasPerL1CallDataByte.mul((tx.data.length - 2)/2)); - console.log(`L1 gas used for ${DAY/EPOCH_LENGTH} epochs : ${l1GasInL2.toNumber()}`); - - console.log(gasEstimate.add(l1GasInL2).mul(1600).mul(50).mul(365).div(BigNumber.from(10).pow(10)).toString()); - }); - - it.only("all epochs in a day, tickets to all selected clusters optimized", async function() { - this.timeout(1000000) - const selectedReceiverIndex: number = Math.floor(Math.random()*receivers.length); - const selectedReceiverSigner: Signer = receiverSigners[selectedReceiverIndex]; - - let noOfEpochs = DAY/EPOCH_LENGTH; - - // skip first epoch - await skipTime(EPOCH_LENGTH*3); - - // select clusters for next epoch - await clusterSelector.selectClusters(); - let epoch = (await clusterSelector.getCurrentEpoch()).toNumber() + 1; - - for(let i=1; i <= noOfEpochs; i++) { - // skip to next epoch - await skipTime(EPOCH_LENGTH); - - await clusterSelector.selectClusters(); - } - // skip to next epoch so that tickets can be distributed for previous epoch - await skipTime(EPOCH_LENGTH); - - let networkId = ethers.utils.id("ETH"); - let tickets: number[][] = []; - let rawTicketInfo = networkId + epoch.toString(16).padStart(8, '0'); - for(let i=0; i { + let pond: Contract; + let receiverStaking: Contract; + let clusterSelector: Contract; + let clusterRewards: Contract; + let admin: Signer; + let rewardDelegatorsMock: Signer; + let nodesInserted: number; + let receivers: Signer[]; + let receiverSigners: Signer[]; + let l1GasDetails: any; + + const MAX_TICKETS = BigNumber.from(2).pow(16); + const DAY = 60 * 60 * 24; + const EPOCH_LENGTH = 15 * 60; + + interface SignedTicket { + tickets: BigNumberish[]; + v: number; + r: string; + s: string; + } + + beforeEach(async function () { + this.timeout(1000000); + ({ pond, receiverStaking, clusterSelector, clusterRewards, admin, rewardDelegatorsMock, nodesInserted, receivers, receiverSigners } = + await waffle.loadFixture(initDataFixture)); + + l1GasDetails = await estimator.connect(mainnetProvider).getPricesInArbGas(); + }); + + it("to single epoch, tickets to 1 - 5 clusters, input clusters ordered", async () => { + const selectedReceiverIndex: number = Math.floor(Math.random() * receivers.length); + const selectedReceiverSigner: Signer = receiverSigners[selectedReceiverIndex]; + + for (let i = 1; i <= 5; i++) { + // skip first epoch + await skipTime(EPOCH_LENGTH); + + // select clusters for next epoch + await clusterSelector.selectClusters(); + + // skip to next epoch + await skipTime(EPOCH_LENGTH); + + let epoch = await clusterSelector.getCurrentEpoch(); + const tickets: BigNumber[] = randomlyDivideInXPieces(MAX_TICKETS, i); + + // skip to next epoch so that tickets can be distributed for previous epoch + await skipTime(EPOCH_LENGTH); + + // TODO + + // const gasEstimate = await clusterRewards + // .connect(selectedReceiverSigner) + // ["issueTickets(bytes32,uint24,uint16[])"].estimateGas(ethers.utils.id("ETH"), epoch, tickets); + // console.log(`gas used for ${i} cluster : ${gasEstimate.toNumber()}`); + + // const tx: PopulatedTransaction = await clusterRewards + // .connect(selectedReceiverSigner) + // ["issueTickets(bytes32,uint24,uint16[])"].populateTransaction(ethers.utils.id("ETH"), epoch, tickets); + // const gasData = await estimator + // .connect(selectedReceiverSigner) + // .callStatic.gasEstimateComponents(clusterRewards.address, false, tx.data); + // console.log(gasData); + } }); + it("single epochs, 50 receivers signed tickets to all selected clusters each", async () => { + const noOfReceivers = 50; + // skip first epoch + await skipTime(EPOCH_LENGTH); + + // select clusters for next epoch + await clusterSelector.selectClusters(); + + // skip to next epoch + await skipTime(EPOCH_LENGTH); + + let epoch = await clusterSelector.getCurrentEpoch(); + + // skip to next epoch so that tickets can be distributed for previous epoch + await skipTime(EPOCH_LENGTH); + + // const selectedReceiverIndex: number = Math.floor(Math.random()*(receivers.length - noOfReceivers)); + const selectedReceiverSigners: Signer[] = receiverSigners.slice(0, noOfReceivers); + + const signedTickets: SignedTicket[] = []; + + for (let i = 0; i < selectedReceiverSigners.length; i++) { + const tickets = randomlyDivideInXPieces(MAX_TICKETS, 5).map((val) => val.toString()); + tickets.pop(); + + const messageHash = utils.keccak256( + utils.defaultAbiCoder.encode(["bytes32", "uint256", "uint16[]"], [ethers.utils.id("ETH"), epoch, tickets]) + ); + const arrayifyHash = ethers.utils.arrayify(messageHash); + const signedMessage = await selectedReceiverSigners[i].signMessage(arrayifyHash); + const splitSig = utils.splitSignature(signedMessage); + signedTickets[i] = { + tickets, + v: splitSig.v, + r: splitSig.r, + s: splitSig.s, + }; + } + + // TODO + // const tx = await clusterRewards["issueTickets(bytes32,uint24,(uint16[],uint8,bytes32,bytes32)[])"]( + // ethers.utils.id("ETH"), + // epoch, + // signedTickets + // ); + + // const receipt = await tx.wait(); + // console.log(`gas used : ${receipt.gasUsed.sub(21000).toNumber()}`); + // console.log(receipt.gasUsed.mul(1600).mul(6).mul(365).div(BigNumber.from(10).pow(10)).toString()); + }); + + it("all epochs in a day, tickets to all selected clusters", async function () { + this.timeout(1000000); + const selectedReceiverIndex: number = Math.floor(Math.random() * receivers.length); + const selectedReceiverSigner: Signer = receiverSigners[selectedReceiverIndex]; + + const selectedClusters: string[][] = []; + const issuedTickets: BigNumber[][] = []; + const epochs: BigNumber[] = []; + + for (let i = 1; i <= DAY / EPOCH_LENGTH; i++) { + // skip first epoch + await skipTime(EPOCH_LENGTH); + + // select clusters for next epoch + await clusterSelector.selectClusters(); + + // skip to next epoch + await skipTime(EPOCH_LENGTH); + + let epoch = await clusterSelector.getCurrentEpoch(); + let clusters: string[] = await clusterSelector.getClusters(epoch); + selectedClusters.push(clusters); + const tickets: BigNumber[] = randomlyDivideInXPieces(MAX_TICKETS, clusters.length); + tickets.pop(); + + issuedTickets.push(tickets); + epochs.push(epoch); + // skip to next epoch so that tickets can be distributed for previous epoch + await skipTime(EPOCH_LENGTH); + } + + const gasEstimate = await clusterRewards + .connect(selectedReceiverSigner) + .estimateGas["issueTickets(bytes32,uint24[],uint16[][])"](ethers.utils.id("ETH"), epochs, issuedTickets); + + const tx = await clusterRewards + .connect(selectedReceiverSigner) + .populateTransaction["issueTickets(bytes32,uint24[],uint16[][])"](ethers.utils.id("ETH"), epochs, issuedTickets); + if (!tx.data) return; + + console.log(`gas used for ${DAY / EPOCH_LENGTH} epochs : ${gasEstimate.toNumber()}`); + console.log( + "L1 gas cost Per L2 Tx", + l1GasDetails.gasPerL2Tx.toNumber(), + "L1 Gas cost Per calldata byte", + l1GasDetails.gasPerL1CallDataByte.toNumber() + ); + const l1GasInL2 = l1GasDetails.gasPerL2Tx.add(l1GasDetails.gasPerL1CallDataByte.mul((tx.data.length - 2) / 2)); + console.log(`L1 gas used for ${DAY / EPOCH_LENGTH} epochs : ${l1GasInL2.toNumber()}`); + + console.log(gasEstimate.add(l1GasInL2).mul(1600).mul(50).mul(365).div(BigNumber.from(10).pow(10)).toString()); + }); + + it("all epochs in a day, tickets to all selected clusters optimized", async function () { + this.timeout(1000000); + const selectedReceiverIndex: number = Math.floor(Math.random() * receivers.length); + const selectedReceiverSigner: Signer = receiverSigners[selectedReceiverIndex]; + + let noOfEpochs = DAY / EPOCH_LENGTH; + + // skip first epoch + await skipTime(EPOCH_LENGTH * 3); + + // select clusters for next epoch + await clusterSelector.selectClusters(); + let epoch = (await clusterSelector.getCurrentEpoch()).toNumber() + 1; + + for (let i = 1; i <= noOfEpochs; i++) { + // skip to next epoch + await skipTime(EPOCH_LENGTH); + + await clusterSelector.selectClusters(); + } + // skip to next epoch so that tickets can be distributed for previous epoch + await skipTime(EPOCH_LENGTH); + + let networkId = ethers.utils.id("ETH"); + let tickets: number[][] = []; + let rawTicketInfo = networkId + epoch.toString(16).padStart(8, "0"); + for (let i = 0; i < noOfEpochs * 4; i++) { + let j: number = parseInt(i / 4 + ""); + let k: number = i % 4; + if (!tickets[j]) tickets[j] = []; + tickets[j][k] = parseInt(Math.random() * 13000 + ""); + rawTicketInfo = rawTicketInfo + tickets[j][k].toString(16).padStart(4, "0"); + } + + const gasEstimate = await clusterRewards.connect(selectedReceiverSigner).estimateGas["issueTickets(bytes)"](rawTicketInfo); + + console.log(`gas used for ${DAY / EPOCH_LENGTH} epochs : ${gasEstimate.toNumber()}`); + + console.log( + "L1 gas cost Per L2 Tx", + l1GasDetails.gasPerL2Tx.toNumber(), + "L1 Gas cost Per calldata byte", + l1GasDetails.gasPerL1CallDataByte.toNumber() + ); + const l1GasInL2 = l1GasDetails.gasPerL2Tx.add(l1GasDetails.gasPerL1CallDataByte.mul((rawTicketInfo.length - 2) / 2)); + console.log(`L1 gas used for ${DAY / EPOCH_LENGTH} epochs : ${l1GasInL2.toNumber()}`); + + console.log(gasEstimate.add(l1GasInL2).mul(1600).mul(50).mul(365).div(BigNumber.from(10).pow(10)).toString()); + }); + + it("Receiver Adds balance to be distributed to the relayers", async() => { + const selectedReceiverIndex: number = Math.floor(Math.random() * receivers.length); + const selectedReceiverSigner: Signer = receiverSigners[selectedReceiverIndex]; + + const stakerAddress = await receiverStaking.signerToStaker(await selectedReceiverSigner.getAddress()); + console.log({stakerAddress}) + + const amountToDistribute = 100000 + const rewardPerEpoch = 1000 + + await pond.transfer(await selectedReceiverSigner.getAddress(), amountToDistribute) + await pond.connect(selectedReceiverSigner).approve(clusterRewards.address, amountToDistribute); + + await clusterRewards.connect(selectedReceiverSigner).setReceiverRewardPerEpoch(rewardPerEpoch); + await clusterRewards.connect(selectedReceiverSigner).addReceiverBalance(stakerAddress, amountToDistribute); + }) + }); }); diff --git a/benchmarks/fixtures/ClusterRewards.ts b/benchmarks/fixtures/ClusterRewards.ts index ad8407a8..de545d32 100644 --- a/benchmarks/fixtures/ClusterRewards.ts +++ b/benchmarks/fixtures/ClusterRewards.ts @@ -4,102 +4,118 @@ import { deploy as deployClusterRewards } from "../../deployments/staking/Cluste import { deploy as deployClusterSelector } from "../../deployments/staking/ClusterSelector"; import { deploy as deployReceiverStaking } from "../../deployments/staking/ReceiverStaking"; -const EPOCH_LENGTH = 15*60; +import { ArbGasInfo__factory } from "../../typechain-types" -export async function deployFixture() { - const signers = await ethers.getSigners(); - const addrs = await Promise.all(signers.map((a) => a.getAddress())); - - const blockNum = await ethers.provider.getBlockNumber(); - const blockData = await ethers.provider.getBlock(blockNum); - - const Pond = await ethers.getContractFactory("Pond"); - const pond = await upgrades.deployProxy(Pond, ["Marlin POND", "POND"], { - kind: "uups", - }); - - const receiverStaking = await deployReceiverStaking(addrs[0], blockData.timestamp, EPOCH_LENGTH, pond.address, true); +const EPOCH_LENGTH = 15 * 60; - const clusterSelector = await deployClusterSelector("ETH", addrs[1], "0x000000000000000000000000000000000000006C", addrs[0], blockData.timestamp, EPOCH_LENGTH, ethers.utils.parseEther('1').toString(), true); - - const clusterRewards = await deployClusterRewards(addrs[1], receiverStaking.address, { - "ETH": clusterSelector.address - }, addrs[0], true); - - return { - pond, - receiverStaking, - clusterSelector, - clusterRewards, - admin: signers[0], - rewardDelegatorsMock: signers[1] - }; +export async function deployFixture() { + const signers = await ethers.getSigners(); + const addrs = await Promise.all(signers.map((a) => a.getAddress())); + + const blockNum = await ethers.provider.getBlockNumber(); + const blockData = await ethers.provider.getBlock(blockNum); + + const Pond = await ethers.getContractFactory("Pond"); + const pond = await upgrades.deployProxy(Pond, ["Marlin POND", "POND"], { + kind: "uups", + }); + + const mockArbGas = await new ArbGasInfo__factory().connect(signers[0]).deploy() + mockArbGas.setPrices(10000, 10000 ,10000); + + const receiverStaking = await deployReceiverStaking(addrs[0], blockData.timestamp, EPOCH_LENGTH, pond.address, true); + + const clusterSelector = await deployClusterSelector( + "ETH", + addrs[1], + mockArbGas.address, + addrs[0], + blockData.timestamp, + EPOCH_LENGTH, + "10000000", + ethers.utils.parseEther("1").toString(), + true + ); + + const clusterRewards = await deployClusterRewards( + addrs[1], // reward delegators is mocked, ideally this should be reward delegators + receiverStaking.address, + { + ETH: clusterSelector.address, + }, + pond.address, + addrs[0], + true + ); + + return { + pond, + receiverStaking, + clusterSelector, + clusterRewards, + admin: signers[0], + rewardDelegatorsMock: signers[1], + }; } export async function initDataFixture() { - const nodesToInsert: number = 75; - const receiverCount: number = 100; - - const signers = await ethers.getSigners(); - const preAllocEthSigner = signers[8]; - - const { - pond, - receiverStaking, - clusterSelector, - clusterRewards, - admin, - rewardDelegatorsMock - } = await deployFixture(); - - const tokenSupply: BigNumber = await pond.totalSupply(); - - const clusters: string[] = []; - const balances: BigNumberish[] = []; - const receivers: Signer[] = []; - const receiverSigners: Signer[] = []; - - // generate clusters and balance data - for(let i=0; i < nodesToInsert; i++) { - const address = Wallet.createRandom().address; - clusters.push(address); - balances.push(BigNumber.from(ethers.utils.randomBytes(32)).mod(tokenSupply.div(utils.parseEther(nodesToInsert+"")))); - } - - // insert clusterData into selector - for(let i=0; i < clusters.length; i+=50) { - await clusterSelector.connect(rewardDelegatorsMock).upsertMultiple(clusters.slice(i, i+50), balances.slice(i, i+50)); - } - - for(let i=0; i < receiverCount; i++) { - // create receiver and stake - const receiver = Wallet.createRandom().connect(ethers.provider); - const receiverSigner = Wallet.createRandom().connect(ethers.provider); - receivers.push(receiver); - receiverSigners.push(receiverSigner); - const depositAmount = BigNumber.from(ethers.utils.randomBytes(32)).mod(tokenSupply.div(receiverCount)); - await preAllocEthSigner.sendTransaction({ - to: receiver.address, - value: utils.parseEther("0.5").toString() - }); - await preAllocEthSigner.sendTransaction({ - to: receiverSigner.address, - value: utils.parseEther("0.5").toString() - }); - await pond.transfer(receiver.address, depositAmount); - await pond.connect(receiver).approve(receiverStaking.address, depositAmount); - await receiverStaking.connect(receiver)["depositFor(uint256,address)"](depositAmount, receiverSigner.address); - } - - return { - pond, - receiverStaking, - clusterSelector, - clusterRewards, - admin, - rewardDelegatorsMock, - nodesInserted: nodesToInsert, - receivers, - receiverSigners - }; + const nodesToInsert: number = 75; + const receiverCount: number = 100; + + const signers = await ethers.getSigners(); + const preAllocEthSigner = signers[8]; + + const { pond, receiverStaking, clusterSelector, clusterRewards, admin, rewardDelegatorsMock } = await deployFixture(); + + const tokenSupply: BigNumber = await pond.totalSupply(); + + const clusters: string[] = []; + const balances: BigNumberish[] = []; + const receivers: Signer[] = []; + const receiverSigners: Signer[] = []; + + // generate clusters and balance data + for (let i = 0; i < nodesToInsert; i++) { + const address = Wallet.createRandom().address; + clusters.push(address); + balances.push(BigNumber.from(ethers.utils.randomBytes(32)).mod(tokenSupply.div(utils.parseEther(nodesToInsert + "")))); + } + + // insert clusterData into selector + for (let i = 0; i < clusters.length; i += 50) { + await clusterSelector.connect(rewardDelegatorsMock).upsertMultiple(clusters.slice(i, i + 50), balances.slice(i, i + 50)); + } + + for (let i = 0; i < receiverCount; i++) { + // create receiver and stake + const receiver = Wallet.createRandom().connect(ethers.provider); + const receiverSigner = Wallet.createRandom().connect(ethers.provider); + receivers.push(receiver); + receiverSigners.push(receiverSigner); + const depositAmount = BigNumber.from(ethers.utils.randomBytes(32)).mod(tokenSupply.div(receiverCount)); + await preAllocEthSigner.sendTransaction({ + to: receiver.address, + value: utils.parseEther("0.5").toString(), + }); + await preAllocEthSigner.sendTransaction({ + to: receiverSigner.address, + value: utils.parseEther("0.5").toString(), + }); + await pond.transfer(receiver.address, depositAmount); + await pond.connect(receiver).approve(receiverStaking.address, depositAmount); + await receiverStaking.connect(receiver)["depositFor(uint256,address)"](depositAmount, receiver.address); + await receiverStaking.connect(receiver).setSigner(receiverSigner.address); + } + + return { + pond, + receiverStaking, + clusterSelector, + clusterRewards, + admin, + rewardDelegatorsMock, + nodesInserted: nodesToInsert, + receivers, + receiverSigners, + }; } diff --git a/contracts/mocks/ArbGasInfo.sol b/contracts/mocks/ArbGasInfo.sol new file mode 100644 index 00000000..2b4fe488 --- /dev/null +++ b/contracts/mocks/ArbGasInfo.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../staking/interfaces/IArbGasInfo.sol"; + +contract ArbGasInfo is IArbGasInfo { + + uint256 public perL2Tx; + uint256 public gasForL1Calldata; + uint256 public storageArbGas; + + constructor(){} + + function setPrices(uint256 _perL2Tx,uint256 _gasForL1Calldata,uint256 _storageArbGas) public { + perL2Tx = _perL2Tx; + gasForL1Calldata = _gasForL1Calldata; + storageArbGas = _storageArbGas; + } + + function getPricesInArbGas() external view override returns (uint, uint, uint) { + return (perL2Tx, gasForL1Calldata, storageArbGas); + } +} \ No newline at end of file diff --git a/contracts/staking/ClusterSelector.sol b/contracts/staking/ClusterSelector.sol index 98a6f51c..38b7f54c 100755 --- a/contracts/staking/ClusterSelector.sol +++ b/contracts/staking/ClusterSelector.sol @@ -11,10 +11,7 @@ import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol" import "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "./tree/TreeUpgradeable.sol"; - -interface IArbGasInfo { - function getPricesInArbGas() external view returns (uint, uint, uint); -} +import "./interfaces/IArbGasInfo.sol"; /// @title Contract to select the top 5 clusters in an epoch contract ClusterSelector is diff --git a/contracts/staking/interfaces/IArbGasInfo.sol b/contracts/staking/interfaces/IArbGasInfo.sol new file mode 100644 index 00000000..785d4ba9 --- /dev/null +++ b/contracts/staking/interfaces/IArbGasInfo.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IArbGasInfo { + function getPricesInArbGas() external view returns (uint, uint, uint); +} \ No newline at end of file diff --git a/deployments/staking/ClusterRewards.ts b/deployments/staking/ClusterRewards.ts index 68b1e8db..53180986 100644 --- a/deployments/staking/ClusterRewards.ts +++ b/deployments/staking/ClusterRewards.ts @@ -1,90 +1,92 @@ -import { ethers, run, upgrades } from 'hardhat'; -import { Contract } from 'ethers'; -import * as fs from 'fs'; -import { upgrade as upgradeUtil } from './Upgrade'; -const config = require('./config'); - -export async function deploy(rewardDelegators: string, receiverStaking: string, clusterSelectorMap: any, admin?: string, noLog?: boolean): Promise { +import { ethers, run, upgrades } from "hardhat"; +import { Contract } from "ethers"; +import * as fs from "fs"; +import { upgrade as upgradeUtil } from "./Upgrade"; +const config = require("./config"); + +export async function deploy( + rewardDelegators: string, + receiverStaking: string, + clusterSelectorMap: any, + pondToken: string, + admin?: string, + noLog?: boolean +): Promise { let chainId = (await ethers.provider.getNetwork()).chainId; const chainConfig = config[chainId]; - const ClusterRewards = await ethers.getContractFactory('ClusterRewards'); + const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - var addresses: {[key: string]: {[key: string]: string}} = {}; - if(!noLog) { + var addresses: { [key: string]: { [key: string]: string } } = {}; + if (!noLog) { console.log("Chain Id:", chainId); - if(fs.existsSync('address.json')) { - addresses = JSON.parse(fs.readFileSync('address.json', 'utf8')); + if (fs.existsSync("address.json")) { + addresses = JSON.parse(fs.readFileSync("address.json", "utf8")); } - if(addresses[chainId] === undefined) { + if (addresses[chainId] === undefined) { addresses[chainId] = {}; } - if(addresses[chainId]['ClusterRewards'] !== undefined) { - console.log("Existing deployment:", addresses[chainId]['ClusterRewards']); - return ClusterRewards.attach(addresses[chainId]['ClusterRewards']); + if (addresses[chainId]["ClusterRewards"] !== undefined) { + console.log("Existing deployment:", addresses[chainId]["ClusterRewards"]); + return ClusterRewards.attach(addresses[chainId]["ClusterRewards"]); } } - const networkIds: string[] = []; const rewardWeights: string[] = []; const clusterSelectors: string[] = []; - for(let network in chainConfig.staking.rewardWeights) { + for (let network in chainConfig.staking.rewardWeights) { networkIds.push(ethers.utils.id(network)); rewardWeights.push(chainConfig.staking.rewardWeights[network]); clusterSelectors.push(clusterSelectorMap[network]); } - if(!admin) admin = chainConfig.admin; + if (!admin) admin = chainConfig.admin; - let clusterRewards = await upgrades.deployProxy(ClusterRewards, [ - admin, - rewardDelegators, - receiverStaking, - networkIds, - rewardWeights, - clusterSelectors, - chainConfig.totalRewardsPerEpoch - ], { kind: "uups" }); + let clusterRewards = await upgrades.deployProxy( + ClusterRewards, + [admin, rewardDelegators, receiverStaking, networkIds, rewardWeights, clusterSelectors, chainConfig.totalRewardsPerEpoch], + { kind: "uups", constructorArgs: [pondToken, rewardDelegators] } + ); - if(!noLog) { + if (!noLog) { console.log("Deployed addr:", clusterRewards.address); - addresses[chainId]['ClusterRewards'] = clusterRewards.address; + addresses[chainId]["ClusterRewards"] = clusterRewards.address; - fs.writeFileSync('address.json', JSON.stringify(addresses, null, 2), 'utf8'); + fs.writeFileSync("address.json", JSON.stringify(addresses, null, 2), "utf8"); } return clusterRewards; } export async function upgrade() { - await upgradeUtil('ClusterRewards', 'ClusterRewards', []); + await upgradeUtil("ClusterRewards", "ClusterRewards", []); } export async function verify() { let chainId = (await ethers.provider.getNetwork()).chainId; console.log("Chain Id:", chainId); - var addresses: {[key: string]: {[key: string]: string}} = {}; - if(fs.existsSync('address.json')) { - addresses = JSON.parse(fs.readFileSync('address.json', 'utf8')); + var addresses: { [key: string]: { [key: string]: string } } = {}; + if (fs.existsSync("address.json")) { + addresses = JSON.parse(fs.readFileSync("address.json", "utf8")); } - if(addresses[chainId] === undefined || addresses[chainId]['ClusterRewards'] === undefined) { + if (addresses[chainId] === undefined || addresses[chainId]["ClusterRewards"] === undefined) { throw new Error("Cluster Rewards not deployed"); } - const implAddress = await upgrades.erc1967.getImplementationAddress(addresses[chainId]['ClusterRewards']); + const implAddress = await upgrades.erc1967.getImplementationAddress(addresses[chainId]["ClusterRewards"]); await run("verify:verify", { address: implAddress, - constructorArguments: [] + constructorArguments: [], }); console.log("Cluster Rewards verified"); From d00314b53549f1a78e40878b061a92a78b495da6 Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Sat, 3 Jun 2023 13:48:41 +0400 Subject: [PATCH 06/30] update --- contracts/staking/ClusterRewards.sol | 40 +++---- contracts/staking/RewardDelegators.sol | 17 +++ .../staking/interfaces/IClusterRewards.sol | 2 + test/staking/ClusterRewards.ts | 104 ++++++++++++------ test/staking/RewardDelegators.ts | 89 ++++++++------- test/staking/RewardDelegatorsNew.ts | 6 +- 6 files changed, 155 insertions(+), 103 deletions(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 0f2cca3d..64b5dcb4 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -31,10 +31,7 @@ contract ClusterRewards is /// @custom:oz-upgrades-unsafe-allow constructor // initializes the logic contract without any admins // safeguard against takeover of the logic contract - constructor(IERC20Upgradeable _pondToken, IRewardDelegators _rewardDelegators) initializer { - PONDToken = _pondToken; - rewardDelegators = _rewardDelegators; - } + constructor() initializer {} modifier onlyAdmin() { require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "only admin"); @@ -262,9 +259,16 @@ contract ClusterRewards is uint256 _rewardShare = _totalNetworkRewardsPerEpoch * _epochReceiverStake / _epochTotalStake; uint256 _receiverRewardPerEpoch = receiverRewardPerEpoch[_receiver]; - if(receiverBalance[_receiver] > _receiverRewardPerEpoch){ - _rewardShare += _receiverRewardPerEpoch; - receiverBalance[_receiver] -= _receiverRewardPerEpoch; + // if(receiverBalance[_receiver] > _receiverRewardPerEpoch){ + // _rewardShare += _receiverRewardPerEpoch; + // receiverBalance[_receiver] -= _receiverRewardPerEpoch; + // } + + // using least number of variables to avoid stack overflow + uint256 _temp = receiverBalance[_receiver]; + _temp = _temp > _receiverRewardPerEpoch ? _receiverRewardPerEpoch: _temp; + if(_temp != 0){ + receiverBalance[_receiver] -= _temp; } uint256 _totalTickets; @@ -445,34 +449,18 @@ contract ClusterRewards is //-------------------------------- User functions end --------------------------------// - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - IERC20Upgradeable public immutable PONDToken; - - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - IRewardDelegators public immutable rewardDelegators; - + bytes32 public constant RECEIVER_PAYMENTS_MANAGER = keccak256("RECEIVER_PAYMENTS_MANAGER"); mapping(address => uint256) public receiverBalance; mapping(address => uint256) public receiverRewardPerEpoch; - event AddReceiverBalance(address indexed receiver, uint256 amount); - event RemoveReceiverBalance(address indexed receiver, uint256 amount); - event UpdateReceiverRewardPerEpoch(address indexed receiver, uint256 amount); - - // @notice Anyone can add balance to receiver/staker address - function addReceiverBalance(address receiver, uint256 amount) public { - require(receiver != address(0), "CRW: address 0"); + function _increaseReceiverBalance(address receiver, uint256 amount) onlyRole(RECEIVER_PAYMENTS_MANAGER) external override { receiverBalance[receiver]+=amount; - PONDToken.transferFrom(msg.sender, address(rewardDelegators), amount); // Todo: check reward delegators internal book keeping - emit AddReceiverBalance(receiver, amount); } - // @notice Set Receiver Epoch, set to 0, to disable extra rewards - function setReceiverRewardPerEpoch(uint256 rewardPerEpoch) public { + function _setReceiverRewardPerEpoch(address signer, uint256 rewardPerEpoch) onlyRole(RECEIVER_PAYMENTS_MANAGER) external override { address staker = receiverStaking.signerToStaker(msg.sender); require(staker != address(0), "CRW: address 0"); - receiverRewardPerEpoch[staker] = rewardPerEpoch; - emit UpdateReceiverRewardPerEpoch(staker, rewardPerEpoch); } } diff --git a/contracts/staking/RewardDelegators.sol b/contracts/staking/RewardDelegators.sol index 8d05c9cf..5e5c8b8a 100755 --- a/contracts/staking/RewardDelegators.sol +++ b/contracts/staking/RewardDelegators.sol @@ -524,4 +524,21 @@ contract RewardDelegators is return 0; } } + + // ------- receiver payments ------------------ // + + event AddReceiverBalance(address indexed receiver, uint256 amount); + event UpdateReceiverRewardPerEpoch(address indexed receiver, uint256 amount); + + function addReceiverBalance(address receiver, uint256 amount) public { + require(receiver != address(0), "CRW: address 0"); + clusterRewards._increaseReceiverBalance(receiver, amount); + emit AddReceiverBalance(receiver, amount); + } + + function setReceiverRewardPerEpoch(uint256 rewardPerEpoch) public { + require(rewardPerEpoch != 0, "CRW: reward 0"); + clusterRewards._setReceiverRewardPerEpoch(msg.sender, rewardPerEpoch); + emit UpdateReceiverRewardPerEpoch(msg.sender, rewardPerEpoch); + } } diff --git a/contracts/staking/interfaces/IClusterRewards.sol b/contracts/staking/interfaces/IClusterRewards.sol index 302c7d6f..771dc460 100644 --- a/contracts/staking/interfaces/IClusterRewards.sol +++ b/contracts/staking/interfaces/IClusterRewards.sol @@ -15,4 +15,6 @@ interface IClusterRewards { function getRewardForEpoch(uint256 epoch, bytes32 networkId) external view returns(uint256); function claimReward(address cluster) external returns(uint256); function changeRewardPerEpoch(uint256 updatedRewardPerEpoch) external; + function _increaseReceiverBalance(address receiver, uint256 amount) external; + function _setReceiverRewardPerEpoch(address signer, uint256 rewardPerEpoch) external; } diff --git a/test/staking/ClusterRewards.ts b/test/staking/ClusterRewards.ts index c721db8f..8e89b4f1 100644 --- a/test/staking/ClusterRewards.ts +++ b/test/staking/ClusterRewards.ts @@ -57,8 +57,6 @@ const tickets = [ ), ]; -const deadAddress = "0xdeaddeadabcdabcd000000001111111122222222"; - describe("ClusterRewards deploy and init", function () { let signers: Signer[]; let addrs: string[]; @@ -72,7 +70,7 @@ describe("ClusterRewards deploy and init", function () { it("deploys with initialization disabled", async function () { const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewards = await ClusterRewards.deploy(deadAddress, deadAddress); + let clusterRewards = await ClusterRewards.deploy(); await expect( clusterRewards.initialize(addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD) @@ -86,14 +84,14 @@ describe("ClusterRewards deploy and init", function () { upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, [ETHWEIGHT, DOTWEIGHT], [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups", constructorArgs: [deadAddress, deadAddress] } + { kind: "uups", constructorArgs: [] } ) ).to.be.revertedWith("CRW:I-Each NetworkId need a corresponding RewardPerEpoch and vice versa"); await expect( upgrades.deployProxy(ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12]], MAX_REWARD], { kind: "uups", - constructorArgs: [deadAddress, deadAddress], + constructorArgs: [], }) ).to.be.revertedWith("CRW:I-Each NetworkId need a corresponding clusterSelector and vice versa"); @@ -101,14 +99,14 @@ describe("ClusterRewards deploy and init", function () { upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], ethers.constants.AddressZero], MAX_REWARD], - { kind: "uups", constructorArgs: [deadAddress, deadAddress] } + { kind: "uups", constructorArgs: [] } ) ).to.be.revertedWith("CRW:CN-ClusterSelector must exist"); const clusterRewards = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups", constructorArgs: [deadAddress, deadAddress] } + { kind: "uups", constructorArgs: [] } ); expect(await clusterRewards.hasRole(await clusterRewards.DEFAULT_ADMIN_ROLE(), addrs[0])).to.be.true; @@ -131,9 +129,9 @@ describe("ClusterRewards deploy and init", function () { const clusterRewards = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups", constructorArgs: [deadAddress, deadAddress] } + { kind: "uups", constructorArgs: [] } ); - await upgrades.upgradeProxy(clusterRewards.address, ClusterRewards, { kind: "uups", constructorArgs: [deadAddress, deadAddress] }); + await upgrades.upgradeProxy(clusterRewards.address, ClusterRewards, { kind: "uups", constructorArgs: [] }); expect(await clusterRewards.hasRole(await clusterRewards.DEFAULT_ADMIN_ROLE(), addrs[0])).to.be.true; expect(await clusterRewards.hasRole(await clusterRewards.CLAIMER_ROLE(), addrs[1])).to.be.true; @@ -153,12 +151,12 @@ describe("ClusterRewards deploy and init", function () { const clusterRewards = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups", constructorArgs: [deadAddress, deadAddress] } + { kind: "uups", constructorArgs: [] } ); await expect( upgrades.upgradeProxy(clusterRewards.address, ClusterRewards.connect(signers[1]), { kind: "uups", - constructorArgs: [deadAddress, deadAddress], + constructorArgs: [], }) ).to.be.revertedWith("only admin"); }); @@ -171,7 +169,7 @@ testERC165( let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups", constructorArgs: [deadAddress, deadAddress] } + { kind: "uups", constructorArgs: [] } ); let clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); return clusterRewards; @@ -193,7 +191,7 @@ testAdminRole("ClusterRewards admin role", async function (signers: Signer[], ad let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups", constructorArgs: [deadAddress, deadAddress] } + { kind: "uups", constructorArgs: [] } ); let clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); return clusterRewards; @@ -206,7 +204,7 @@ testRole( let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups", constructorArgs: [deadAddress, deadAddress] } + { kind: "uups", constructorArgs: [] } ); let clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); return clusterRewards; @@ -235,7 +233,7 @@ describe("ClusterRewards add network", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups", constructorArgs: [deadAddress, deadAddress] } + { kind: "uups", constructorArgs: [] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); }); @@ -338,7 +336,7 @@ describe("ClusterRewards cluster selector", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups", constructorArgs: [deadAddress, deadAddress] } + { kind: "uups", constructorArgs: [] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); }); @@ -459,7 +457,7 @@ describe("ClusterRewards remove network", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups", constructorArgs: [deadAddress, deadAddress] } + { kind: "uups", constructorArgs: [] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); }); @@ -501,7 +499,7 @@ describe("ClusterRewards update global vars", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups", constructorArgs: [deadAddress, deadAddress] } + { kind: "uups", constructorArgs: [] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); }); @@ -561,7 +559,7 @@ describe("ClusterRewards feed rewards", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], FEED_REWARD], - { kind: "uups", constructorArgs: [deadAddress, deadAddress] } + { kind: "uups", constructorArgs: [] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -714,7 +712,7 @@ describe("ClusterRewards submit tickets", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [ethSelector.address, addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups", constructorArgs: [deadAddress, deadAddress] } + { kind: "uups", constructorArgs: [] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -979,7 +977,7 @@ describe("ClusterRewards submit compressed tickets", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [ethSelector.address, addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups", constructorArgs: [deadAddress, deadAddress] } + { kind: "uups", constructorArgs: [] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -1491,7 +1489,7 @@ describe("ClusterRewards claim rewards", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [ethSelector.address, addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups", constructorArgs: [deadAddress, deadAddress] } + { kind: "uups", constructorArgs: [] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -1600,7 +1598,7 @@ describe("ClusterRewards: Add Receiver extra payment", function () { let clusterRewardsContract = await upgrades.deployProxy( ClusterRewards, [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [ethSelector.address, addrs[12], addrs[13]], MAX_REWARD], - { kind: "uups", constructorArgs: [mockToken.address, deadAddress] } + { kind: "uups", constructorArgs: [] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); @@ -1616,24 +1614,59 @@ describe("ClusterRewards: Add Receiver extra payment", function () { takeSnapshotBeforeAndAfterEveryTest(async () => {}); - it("Add Extra Tokens", async () => { - await mockToken.connect(signer).approve(clusterRewards.address, 100000); - await expect(clusterRewards.connect(signer).addReceiverBalance(await staker.getAddress(), 100000)) - .emit(clusterRewards, "AddReceiverBalance") - .withArgs(await staker.getAddress(), 100000); + it("_increaseReceiverBalance: can be only called by specific role", async () => { + await expect(clusterRewards.connect(signer)._increaseReceiverBalance(signer.getAddress(), 50)).to.be.revertedWith( + `AccessControl: account ${( + await signer.getAddress() + ).toLowerCase()} is missing role ${await clusterRewards.RECEIVER_PAYMENTS_MANAGER()}` + ); }); - it("Set Receiver Reward per Epoch", async () => { - await expect(clusterRewards.connect(signer).setReceiverRewardPerEpoch(50)) - .to.emit(clusterRewards, "UpdateReceiverRewardPerEpoch") - .withArgs(await staker.getAddress(), 50); + it("_setReceiverRewardPerEpoch: can be only called by specific role", async () => { + await expect(clusterRewards.connect(signer)._setReceiverRewardPerEpoch(signer.getAddress(), 50)).to.be.revertedWith( + `AccessControl: account ${( + await signer.getAddress() + ).toLowerCase()} is missing role ${await clusterRewards.RECEIVER_PAYMENTS_MANAGER()}` + ); + }); + + it("only admin can grant RECEIVER_PAYMENTS_MANAGER", async () => { + await expect( + clusterRewards.connect(signer).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), signer.getAddress()) + ).to.be.revertedWith( + `AccessControl: account ${(await signer.getAddress()).toLowerCase()} is missing role ${await clusterRewards.DEFAULT_ADMIN_ROLE()}` + ); + + await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), signer.getAddress()); + }); + + it("Check increase balance", async () => { + const increaseByAmount = 100; + await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), signer.getAddress()); + + const balanceBefore = await clusterRewards.receiverBalance(signer.getAddress()); + await clusterRewards.connect(signer)._increaseReceiverBalance(signer.getAddress(), increaseByAmount); + const balanceAfter = await clusterRewards.receiverBalance(signer.getAddress()); + + expect(balanceAfter).eq(balanceBefore.add(increaseByAmount)); + }); + + it("Check reward per epoch setting", async () => { + const receiverExtraRewardPerEpoch = 109; + await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), signer.getAddress()); + + await clusterRewards.connect(signer)._setReceiverRewardPerEpoch(signer.getAddress(), receiverExtraRewardPerEpoch); + expect(await clusterRewards.receiverRewardPerEpoch(staker.getAddress())).eq(receiverExtraRewardPerEpoch); }); it("Reward Checking after receiver adds extra tokens", async () => { - await mockToken.connect(signer).approve(clusterRewards.address, 100000); - await clusterRewards.connect(signer).addReceiverBalance(await staker.getAddress(), 100000); const receiverRewardPerEpoch = BN.from(500); - await clusterRewards.connect(signer).setReceiverRewardPerEpoch(receiverRewardPerEpoch); + await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), signer.getAddress()); + // balance can be added by any address, hence we are using staker address directly + await clusterRewards.connect(signer)._increaseReceiverBalance(staker.getAddress(), 100000); + + // reward will set only by signer, hence we are using signer address here + await clusterRewards.connect(signer)._setReceiverRewardPerEpoch(signer.getAddress(), receiverRewardPerEpoch); const epochWithRewards = (33 * 86400) / 900 + 2; await ethSelector.mock.getClusters.returns([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]); @@ -1656,3 +1689,4 @@ describe("ClusterRewards: Add Receiver extra payment", function () { expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 1); }); }); + diff --git a/test/staking/RewardDelegators.ts b/test/staking/RewardDelegators.ts index 5bc08767..7de76896 100644 --- a/test/staking/RewardDelegators.ts +++ b/test/staking/RewardDelegators.ts @@ -36,8 +36,6 @@ BN.prototype.e18 = function () { return this.mul(BN.from(10).pow(18)); }; -const deadAddress = "0xdeaddeadabcdabcd000000001111111122222222"; - describe("RewardDelegators", function () { let signers: Signer[]; let addrs: string[]; @@ -65,7 +63,7 @@ describe("RewardDelegators", function () { let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, - constructorArgs: [deadAddress, deadAddress], + constructorArgs: [], }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); @@ -211,7 +209,7 @@ describe("RewardDelegators", function () { let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, - constructorArgs: [deadAddress, deadAddress], + constructorArgs: [], }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); @@ -286,7 +284,7 @@ describe("RewardDelegators", function () { let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, - constructorArgs: [deadAddress, deadAddress], + constructorArgs: [], }); await expect(rewardDelegators.connect(signers[1]).updateClusterRewards(clusterRewardsInstance2.address)).to.be.reverted; }); @@ -300,7 +298,7 @@ describe("RewardDelegators", function () { let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, - constructorArgs: [deadAddress, deadAddress], + constructorArgs: [], }); await expect(await rewardDelegators.updateClusterRewards(clusterRewardsInstance2.address)).to.emit( rewardDelegators, @@ -356,7 +354,7 @@ describe("RewardDelegators", function () { let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, - constructorArgs: [deadAddress, deadAddress], + constructorArgs: [], }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); @@ -469,6 +467,8 @@ describe("RewardDelegators", function () { let delegator: Signer; + let receiverSignerAndStaker: Signer; + let registeredClusterRewardAddress: string; let registeredClusterRewardAddress1: string; let registeredClusterRewardAddress2: string; @@ -512,6 +512,8 @@ describe("RewardDelegators", function () { registeredClusterRewardAddress3 = addrs[22]; registeredClusterRewardAddress4 = addrs[23]; + receiverSignerAndStaker = signers[50]; + const Pond = await ethers.getContractFactory("Pond"); let pondInstanceContract = await upgrades.deployProxy(Pond, ["Marlin", "POND"], { kind: "uups" }); pondInstance = getPond(pondInstanceContract.address, signers[0]); @@ -527,7 +529,7 @@ describe("RewardDelegators", function () { let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, - constructorArgs: [deadAddress, deadAddress], + constructorArgs: [], }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); @@ -568,14 +570,10 @@ describe("RewardDelegators", function () { const blockData = await ethers.provider.getBlock("latest"); let ClusterSelector = await ethers.getContractFactory("ClusterSelector"); - let clusterSelectorContract = await upgrades.deployProxy( - ClusterSelector, - [addrs[0], rewardDelegators.address], - { - kind: "uups", - constructorArgs: [blockData.timestamp, 4 * 3600, await signers[56].getAddress(), 1000, 100], - } - ); + let clusterSelectorContract = await upgrades.deployProxy(ClusterSelector, [addrs[0], rewardDelegators.address], { + kind: "uups", + constructorArgs: [blockData.timestamp, 4 * 3600, await signers[56].getAddress(), 1000, 100], + }); clusterSelector = getClusterSelector(clusterSelectorContract.address, signers[0]); await clusterRewardsInstance.initialize( @@ -593,6 +591,7 @@ describe("RewardDelegators", function () { ); await clusterRewardsInstance.grantRole(await clusterRewardsInstance.DEFAULT_ADMIN_ROLE(), await signers[0].getAddress()); + await clusterRewardsInstance.grantRole(await clusterRewardsInstance.RECEIVER_PAYMENTS_MANAGER(), rewardDelegators.address); await stakeManagerInstance.initialize( ["" + pondTokenId, "" + mpondTokenId], @@ -734,9 +733,21 @@ describe("RewardDelegators", function () { throw new Error("event expected"); } }); + + it.only("Add Receiver Balance", async () => { + const amountToTransfer = 10000; + await pondInstance.transfer(receiverSignerAndStaker.getAddress(), amountToTransfer); + + await pondInstance.connect(receiverSignerAndStaker).approve(rewardDelegators.address, amountToTransfer); + await expect( + rewardDelegators.connect(receiverSignerAndStaker).addReceiverBalance(receiverSignerAndStaker.getAddress(), amountToTransfer) + ) + .to.emit(rewardDelegators, "AddReceiverBalance") + .withArgs(await receiverSignerAndStaker.getAddress(), amountToTransfer); + }); }); -describe("RewardDelegators Deployment", function () { +describe("RewardDelegators - 5", function () { let signers: Signer[]; let addrs: string[]; let clusterRegistryInstance: ClusterRegistry; @@ -848,7 +859,7 @@ describe("RewardDelegators Deployment", function () { let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, - constructorArgs: [deadAddress, deadAddress], + constructorArgs: [], }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); @@ -880,7 +891,9 @@ describe("RewardDelegators Deployment", function () { [pondTokenId, mpondTokenId], [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation],[] ,[] + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [], ], { kind: "uups" } ); @@ -909,14 +922,16 @@ describe("RewardDelegators Deployment", function () { await receiverStaking.initialize(addrs[0], "Receiver POND", "rPOND"); let ClusterSelector = await ethers.getContractFactory("ClusterSelector"); - let clusterSelectorContract = await upgrades.deployProxy( - ClusterSelector, - [addrs[0], rewardDelegatorsInstance.address], - { - kind: "uups", - constructorArgs: [await receiverStaking.START_TIME(), await receiverStaking.EPOCH_LENGTH(), addrs[56], BigNumber.from(10).pow(20), 1000], - } - ); + let clusterSelectorContract = await upgrades.deployProxy(ClusterSelector, [addrs[0], rewardDelegatorsInstance.address], { + kind: "uups", + constructorArgs: [ + await receiverStaking.START_TIME(), + await receiverStaking.EPOCH_LENGTH(), + addrs[56], + BigNumber.from(10).pow(20), + 1000, + ], + }); clusterSelectorInstance = getClusterSelector(clusterSelectorContract.address, signers[0]); await mpondInstance.grantRole(await mpondInstance.WHITELIST_ROLE(), stakeManagerInstance.address); @@ -1249,7 +1264,7 @@ describe("RewardDelegators Deployment", function () { let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, - constructorArgs: [deadAddress, deadAddress], + constructorArgs: [], }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); @@ -1265,7 +1280,9 @@ describe("RewardDelegators Deployment", function () { [testTokenId], [100], [1], - [1], [] ,[] + [1], + [], + [], ], { kind: "uups" } ); @@ -1275,14 +1292,10 @@ describe("RewardDelegators Deployment", function () { const blockData = await ethers.provider.getBlock("latest"); let ClusterSelector = await ethers.getContractFactory("ClusterSelector"); - let clusterSelectorContract = await upgrades.deployProxy( - ClusterSelector, - [addrs[0], rewardDelegatorsInstance.address], - { - kind: "uups", - constructorArgs: [blockData.timestamp, 4 * 60 * 60, addrs[56], BigNumber.from(10).pow(20), 100], - } - ); + let clusterSelectorContract = await upgrades.deployProxy(ClusterSelector, [addrs[0], rewardDelegatorsInstance.address], { + kind: "uups", + constructorArgs: [blockData.timestamp, 4 * 60 * 60, addrs[56], BigNumber.from(10).pow(20), 100], + }); clusterSelectorInstance = getClusterSelector(clusterSelectorContract.address, signers[0]); await rewardDelegatorsInstance.connect(signers[0]).updateClusterRewards(clusterRewardsInstance.address); @@ -1463,7 +1476,7 @@ describe("RewardDelegators Deployment", function () { const tempCLusterRewardInstance = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, - constructorArgs: [deadAddress, deadAddress], + constructorArgs: [], }); await expect(rewardDelegatorsInstance.connect(signers[1]).updateClusterRewards(tempCLusterRewardInstance.address)).to.be.reverted; diff --git a/test/staking/RewardDelegatorsNew.ts b/test/staking/RewardDelegatorsNew.ts index a97359b7..246526f3 100644 --- a/test/staking/RewardDelegatorsNew.ts +++ b/test/staking/RewardDelegatorsNew.ts @@ -39,8 +39,6 @@ const e20 = ethers.utils.parseEther("100"); const e22 = ethers.utils.parseEther("10000"); const e30 = ethers.utils.parseEther("1000000000000"); -const deadAddress = "0xdeaddeadabcdabcd000000001111111122222222"; - describe("RewardDelegators init and upgrades", function () { let signers: Signer[]; let addrs: string[]; @@ -400,7 +398,7 @@ describe("RewardDelegators global var updates", function () { let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, - constructorArgs: [deadAddress, deadAddress], + constructorArgs: [], }); await expect(rewardDelegators.connect(signers[1]).updateClusterRewards(clusterRewardsInstance2.address)).to.be.reverted; }); @@ -414,7 +412,7 @@ describe("RewardDelegators global var updates", function () { let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, - constructorArgs: [deadAddress, deadAddress], + constructorArgs: [], }); await expect(await rewardDelegators.updateClusterRewards(clusterRewardsInstance2.address)).to.emit( rewardDelegators, From d41c97be038eef0e7fd592c6692f5020caa9fa09 Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Sat, 3 Jun 2023 13:54:28 +0400 Subject: [PATCH 07/30] update --- contracts/staking/ClusterRewards.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 64b5dcb4..21e9ca35 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -253,7 +253,6 @@ contract ClusterRewards is function _processReceiverTickets(address _receiver, uint256 _epoch, address[] memory _selectedClusters, uint16[] memory _tickets, uint256 _totalNetworkRewardsPerEpoch, uint256 _epochTotalStake, uint256 _epochReceiverStake) internal { require(!_isTicketsIssued(_receiver, _epoch), "CRW:IPRT-Tickets already issued"); - unchecked { require(_selectedClusters.length <= _tickets.length + 1, "CRW:IPRT-Tickets length not matching selected clusters"); uint256 _rewardShare = _totalNetworkRewardsPerEpoch * _epochReceiverStake / _epochTotalStake; @@ -269,6 +268,7 @@ contract ClusterRewards is _temp = _temp > _receiverRewardPerEpoch ? _receiverRewardPerEpoch: _temp; if(_temp != 0){ receiverBalance[_receiver] -= _temp; + _rewardShare += _temp; } uint256 _totalTickets; From c7e8ac0ead432319fc6958a7262705f08acabc36 Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Sat, 3 Jun 2023 14:05:10 +0400 Subject: [PATCH 08/30] fix benchmarks --- benchmarks/ClusterRewards.ts | 21 +-------------------- deployments/staking/ClusterRewards.ts | 2 +- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/benchmarks/ClusterRewards.ts b/benchmarks/ClusterRewards.ts index bb8e91d9..b7d8f2ea 100755 --- a/benchmarks/ClusterRewards.ts +++ b/benchmarks/ClusterRewards.ts @@ -9,12 +9,10 @@ const estimator = new ethers.Contract("0x000000000000000000000000000000000000006 ]); const mainnetProvider = new ethers.providers.JsonRpcProvider("https://arb1.arbitrum.io/rpc"); -const deadAddress = "0xdeaddeadabcdabcd000000001111111122222222"; - describe("Cluster Rewards", async () => { benchmarkDeployment( "ClusterRewards", - [deadAddress, deadAddress], + [], [ "0x000000000000000000000000000000000000dEaD", "0x000000000000000000000000000000000000dEaD", @@ -251,22 +249,5 @@ describe("Cluster Rewards", async () => { console.log(gasEstimate.add(l1GasInL2).mul(1600).mul(50).mul(365).div(BigNumber.from(10).pow(10)).toString()); }); - - it("Receiver Adds balance to be distributed to the relayers", async() => { - const selectedReceiverIndex: number = Math.floor(Math.random() * receivers.length); - const selectedReceiverSigner: Signer = receiverSigners[selectedReceiverIndex]; - - const stakerAddress = await receiverStaking.signerToStaker(await selectedReceiverSigner.getAddress()); - console.log({stakerAddress}) - - const amountToDistribute = 100000 - const rewardPerEpoch = 1000 - - await pond.transfer(await selectedReceiverSigner.getAddress(), amountToDistribute) - await pond.connect(selectedReceiverSigner).approve(clusterRewards.address, amountToDistribute); - - await clusterRewards.connect(selectedReceiverSigner).setReceiverRewardPerEpoch(rewardPerEpoch); - await clusterRewards.connect(selectedReceiverSigner).addReceiverBalance(stakerAddress, amountToDistribute); - }) }); }); diff --git a/deployments/staking/ClusterRewards.ts b/deployments/staking/ClusterRewards.ts index 53180986..c5c657b1 100644 --- a/deployments/staking/ClusterRewards.ts +++ b/deployments/staking/ClusterRewards.ts @@ -51,7 +51,7 @@ export async function deploy( let clusterRewards = await upgrades.deployProxy( ClusterRewards, [admin, rewardDelegators, receiverStaking, networkIds, rewardWeights, clusterSelectors, chainConfig.totalRewardsPerEpoch], - { kind: "uups", constructorArgs: [pondToken, rewardDelegators] } + { kind: "uups", constructorArgs: [] } ); if (!noLog) { From 0d9d31bdf73921a83f47401232fce9b9a8ac2a9f Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Mon, 5 Jun 2023 10:37:15 +0400 Subject: [PATCH 09/30] fix the receiver balance and recheck coverage --- contracts/staking/ClusterRewards.sol | 35 +++++++++++++------------- contracts/staking/RewardDelegators.sol | 12 ++++++--- test/staking/ClusterRewards.ts | 12 +++++---- test/staking/Integration.ts | 4 +-- test/staking/RewardDelegators.ts | 16 +++++++++++- 5 files changed, 48 insertions(+), 31 deletions(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 21e9ca35..534e832a 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -251,25 +251,25 @@ contract ClusterRewards is emit ClusterRewarded(_networkId); } + function _extraReceiverRewards(address _receiver) internal returns (uint256) { + uint256 _receiverRewardPerEpoch = receiverRewardPerEpoch[_receiver]; + + // using least number of variables to avoid stack overflow + uint256 _receiverBalance = receiverBalance[_receiver]; + uint256 _extraRewards = _receiverBalance > _receiverRewardPerEpoch ? _receiverRewardPerEpoch: _receiverBalance; + if(_extraRewards != 0){ + receiverBalance[_receiver] -= _extraRewards; + } + + return _extraRewards; + } + function _processReceiverTickets(address _receiver, uint256 _epoch, address[] memory _selectedClusters, uint16[] memory _tickets, uint256 _totalNetworkRewardsPerEpoch, uint256 _epochTotalStake, uint256 _epochReceiverStake) internal { require(!_isTicketsIssued(_receiver, _epoch), "CRW:IPRT-Tickets already issued"); unchecked { require(_selectedClusters.length <= _tickets.length + 1, "CRW:IPRT-Tickets length not matching selected clusters"); uint256 _rewardShare = _totalNetworkRewardsPerEpoch * _epochReceiverStake / _epochTotalStake; - uint256 _receiverRewardPerEpoch = receiverRewardPerEpoch[_receiver]; - - // if(receiverBalance[_receiver] > _receiverRewardPerEpoch){ - // _rewardShare += _receiverRewardPerEpoch; - // receiverBalance[_receiver] -= _receiverRewardPerEpoch; - // } - - // using least number of variables to avoid stack overflow - uint256 _temp = receiverBalance[_receiver]; - _temp = _temp > _receiverRewardPerEpoch ? _receiverRewardPerEpoch: _temp; - if(_temp != 0){ - receiverBalance[_receiver] -= _temp; - _rewardShare += _temp; - } + _rewardShare += _extraReceiverRewards(_receiver); uint256 _totalTickets; uint256 i; @@ -454,12 +454,11 @@ contract ClusterRewards is mapping(address => uint256) public receiverBalance; mapping(address => uint256) public receiverRewardPerEpoch; - function _increaseReceiverBalance(address receiver, uint256 amount) onlyRole(RECEIVER_PAYMENTS_MANAGER) external override { - receiverBalance[receiver]+=amount; + function _increaseReceiverBalance(address staker, uint256 amount) onlyRole(RECEIVER_PAYMENTS_MANAGER) external override { + receiverBalance[staker]+=amount; } - function _setReceiverRewardPerEpoch(address signer, uint256 rewardPerEpoch) onlyRole(RECEIVER_PAYMENTS_MANAGER) external override { - address staker = receiverStaking.signerToStaker(msg.sender); + function _setReceiverRewardPerEpoch(address staker, uint256 rewardPerEpoch) onlyRole(RECEIVER_PAYMENTS_MANAGER) external override { require(staker != address(0), "CRW: address 0"); receiverRewardPerEpoch[staker] = rewardPerEpoch; } diff --git a/contracts/staking/RewardDelegators.sol b/contracts/staking/RewardDelegators.sol index 5e5c8b8a..37ae1524 100755 --- a/contracts/staking/RewardDelegators.sol +++ b/contracts/staking/RewardDelegators.sol @@ -531,14 +531,18 @@ contract RewardDelegators is event UpdateReceiverRewardPerEpoch(address indexed receiver, uint256 amount); function addReceiverBalance(address receiver, uint256 amount) public { - require(receiver != address(0), "CRW: address 0"); + require(receiver != address(0), "RD: address 0"); + require(amount != 0, "RD: amount 0"); + PONDToken.transferFrom(msg.sender, address(this), amount); clusterRewards._increaseReceiverBalance(receiver, amount); emit AddReceiverBalance(receiver, amount); } + // msg.sender is staker here function setReceiverRewardPerEpoch(uint256 rewardPerEpoch) public { - require(rewardPerEpoch != 0, "CRW: reward 0"); - clusterRewards._setReceiverRewardPerEpoch(msg.sender, rewardPerEpoch); - emit UpdateReceiverRewardPerEpoch(msg.sender, rewardPerEpoch); + require(rewardPerEpoch != 0, "RD: reward 0"); + address _sender = _msgSender(); + clusterRewards._setReceiverRewardPerEpoch(_sender, rewardPerEpoch); + emit UpdateReceiverRewardPerEpoch(_sender, rewardPerEpoch); } } diff --git a/test/staking/ClusterRewards.ts b/test/staking/ClusterRewards.ts index 8e89b4f1..4b7946c0 100644 --- a/test/staking/ClusterRewards.ts +++ b/test/staking/ClusterRewards.ts @@ -1005,7 +1005,8 @@ describe("ClusterRewards submit compressed tickets", function () { rawTicketInfo = rawTicketInfo + tickets[j][k].toString(16).padStart(4, "0"); } - await clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo); + // check the events arguments emitted + await expect(clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo)).to.emit(clusterRewards, 'TicketsIssued'); expect(await clusterRewards.isTicketsIssued(addrs[4], 2)).to.be.true; expect(await clusterRewards.isTicketsIssued(addrs[4], 3)).to.be.false; @@ -1655,18 +1656,19 @@ describe("ClusterRewards: Add Receiver extra payment", function () { const receiverExtraRewardPerEpoch = 109; await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), signer.getAddress()); - await clusterRewards.connect(signer)._setReceiverRewardPerEpoch(signer.getAddress(), receiverExtraRewardPerEpoch); + await clusterRewards.connect(signer)._setReceiverRewardPerEpoch(staker.getAddress(), receiverExtraRewardPerEpoch); expect(await clusterRewards.receiverRewardPerEpoch(staker.getAddress())).eq(receiverExtraRewardPerEpoch); }); it("Reward Checking after receiver adds extra tokens", async () => { + const mockRewardDelegators = signers[39]; // any random address can be used const receiverRewardPerEpoch = BN.from(500); - await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), signer.getAddress()); + await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), mockRewardDelegators.getAddress()); // balance can be added by any address, hence we are using staker address directly - await clusterRewards.connect(signer)._increaseReceiverBalance(staker.getAddress(), 100000); + await clusterRewards.connect(mockRewardDelegators)._increaseReceiverBalance(staker.getAddress(), 100000); // reward will set only by signer, hence we are using signer address here - await clusterRewards.connect(signer)._setReceiverRewardPerEpoch(signer.getAddress(), receiverRewardPerEpoch); + await clusterRewards.connect(mockRewardDelegators)._setReceiverRewardPerEpoch(staker.getAddress(), receiverRewardPerEpoch); const epochWithRewards = (33 * 86400) / 900 + 2; await ethSelector.mock.getClusters.returns([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]); diff --git a/test/staking/Integration.ts b/test/staking/Integration.ts index 1d5e56de..cb0027b1 100644 --- a/test/staking/Integration.ts +++ b/test/staking/Integration.ts @@ -31,8 +31,6 @@ const UNDELEGATION_WAIT_TIME = 604800; const REDELEGATION_WAIT_TIME = 21600; const REWARD_PER_EPOCH = BN.from(10).pow(21).mul(35); -const deadAddress = "0xdeaddeadabcdabcd000000001111111122222222"; - describe("Integration", function () { let signers: Signer[]; @@ -174,7 +172,7 @@ describe("Integration", function () { rewardDelegators = getRewardDelegators(rewardDelegatorsContract.address, signers[0]); const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - const clusterRewardsContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [deadAddress ,deadAddress] }); + const clusterRewardsContract = await upgrades.deployProxy(ClusterRewards, { kind: "uups", initializer: false, constructorArgs: [] }); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); diff --git a/test/staking/RewardDelegators.ts b/test/staking/RewardDelegators.ts index 7de76896..e251893a 100644 --- a/test/staking/RewardDelegators.ts +++ b/test/staking/RewardDelegators.ts @@ -734,16 +734,30 @@ describe("RewardDelegators", function () { } }); - it.only("Add Receiver Balance", async () => { + it("Add Receiver Balance", async () => { const amountToTransfer = 10000; await pondInstance.transfer(receiverSignerAndStaker.getAddress(), amountToTransfer); + const receiverBalanceBefore = await clusterRewardsInstance.receiverBalance(await receiverSignerAndStaker.getAddress()); + const tokenBalanceBefore = await pondInstance.balanceOf(rewardDelegators.address); await pondInstance.connect(receiverSignerAndStaker).approve(rewardDelegators.address, amountToTransfer); await expect( rewardDelegators.connect(receiverSignerAndStaker).addReceiverBalance(receiverSignerAndStaker.getAddress(), amountToTransfer) ) .to.emit(rewardDelegators, "AddReceiverBalance") .withArgs(await receiverSignerAndStaker.getAddress(), amountToTransfer); + + const tokenBalanceAfter = await pondInstance.balanceOf(rewardDelegators.address); + const receiverBalanceAfter = await clusterRewardsInstance.receiverBalance(await receiverSignerAndStaker.getAddress()); + expect(tokenBalanceAfter).eq(tokenBalanceBefore.add(amountToTransfer)); + expect(receiverBalanceAfter).eq(receiverBalanceBefore.add(amountToTransfer)); + }); + + it("Set Receiver Reward Per Epoch", async () => { + const rewardPerEpoch = 12345; + await expect(rewardDelegators.connect(receiverSignerAndStaker).setReceiverRewardPerEpoch(rewardPerEpoch)) + .to.emit(rewardDelegators, "UpdateReceiverRewardPerEpoch") + .withArgs(await receiverSignerAndStaker.getAddress(), rewardPerEpoch); }); }); From 4fe9625b64e0ce491e538fbd0318b95f217f6f9e Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Mon, 5 Jun 2023 17:22:11 +0400 Subject: [PATCH 10/30] remove _processReceiverTickets func params --- contracts/staking/ClusterRewards.sol | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 534e832a..6b0b5db8 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -264,13 +264,15 @@ contract ClusterRewards is return _extraRewards; } - function _processReceiverTickets(address _receiver, uint256 _epoch, address[] memory _selectedClusters, uint16[] memory _tickets, uint256 _totalNetworkRewardsPerEpoch, uint256 _epochTotalStake, uint256 _epochReceiverStake) internal { + function _getRewardShare(address _receiver, uint256 _totalNetworkRewardsPerEpoch, uint256 _epochTotalStake, uint256 _epochReceiverStake) internal returns (uint256 _rewardShare) { + _rewardShare = _totalNetworkRewardsPerEpoch * _epochReceiverStake / _epochTotalStake; + _rewardShare += _extraReceiverRewards(_receiver); + } + function _processReceiverTickets(address _receiver, uint256 _epoch, address[] memory _selectedClusters, uint16[] memory _tickets, uint256 _rewardShare) internal { require(!_isTicketsIssued(_receiver, _epoch), "CRW:IPRT-Tickets already issued"); unchecked { require(_selectedClusters.length <= _tickets.length + 1, "CRW:IPRT-Tickets length not matching selected clusters"); - uint256 _rewardShare = _totalNetworkRewardsPerEpoch * _epochReceiverStake / _epochTotalStake; - _rewardShare += _extraReceiverRewards(_receiver); - + uint256 _totalTickets; uint256 i; for(; i < _selectedClusters.length - 1; ++i) { @@ -320,7 +322,9 @@ contract ClusterRewards is require(_epochs[i] < _currentEpoch, "CRW:IT-Epoch not completed"); address[] memory _selectedClusters = clusterSelectors[_networkId].getClusters(_epochs[i]); (uint256 _epochReceiverStake, address _receiver) = receiverStaking.balanceOfSignerAt(msg.sender, _epochs[i]); - _processReceiverTickets(_receiver, _epochs[i], _selectedClusters, _tickets[i], _totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake); + + uint256 _rewardShare = _getRewardShare(_receiver, _totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake); + _processReceiverTickets(_receiver, _epochs[i], _selectedClusters, _tickets[i], _rewardShare); emit TicketsIssued(_networkId, _epochs[i], msg.sender); } } @@ -346,7 +350,8 @@ contract ClusterRewards is unchecked { for(uint256 i=0; i < _noOfEpochs; ++i) { _totalNetworkRewardsPerEpoch = _getRewardForEpoch(_fromEpoch, _networkId, _epochLength); - _processReceiverTickets(_receiver, _fromEpoch, _selectedClusters[i], _tickets[i], _totalNetworkRewardsPerEpoch, _stakes[i], _balances[i]); + uint256 _rewardShare = _getRewardShare(_receiver, _totalNetworkRewardsPerEpoch, _stakes[i], _balances[i]); + _processReceiverTickets(_receiver, _fromEpoch, _selectedClusters[i], _tickets[i], _rewardShare); emit TicketsIssued(_networkId, _fromEpoch, msg.sender); ++_fromEpoch; } @@ -423,7 +428,9 @@ contract ClusterRewards is uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_epoch, _networkId); (uint256 _epochReceiverStake, address _receiver) = receiverStaking.balanceOfSignerAt(msg.sender, _epoch); - _processReceiverTickets(_receiver, _epoch, _selectedClusters, _tickets, _totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake); + + uint256 _rewardShare = _getRewardShare(_receiver, _totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake); + _processReceiverTickets(_receiver, _epoch, _selectedClusters, _tickets, _rewardShare); emit TicketsIssued(_networkId, _epoch, msg.sender); } @@ -462,4 +469,4 @@ contract ClusterRewards is require(staker != address(0), "CRW: address 0"); receiverRewardPerEpoch[staker] = rewardPerEpoch; } -} +} \ No newline at end of file From 70ce2573d374339c374b3b4f62e74912f682e593 Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Tue, 6 Jun 2023 14:33:01 +0400 Subject: [PATCH 11/30] receiver extra rewards --- .gitignore | 5 +- contracts/staking/ClusterRewards.sol | 301 ++-- package-lock.json | 2449 +++++++++++++++++++++++++- package.json | 7 +- test/staking/ClusterRewards.ts | 20 +- 5 files changed, 2636 insertions(+), 146 deletions(-) diff --git a/.gitignore b/.gitignore index 82dd4902..a51d732f 100755 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,7 @@ coverage.json .openzeppelin/*-9876543210.json .gitsigners typechain-types/* -*.log \ No newline at end of file +*.log +custom_flatten +*.dot +*.png \ No newline at end of file diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 6b0b5db8..ebf759e6 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -16,14 +16,14 @@ import "./interfaces/IClusterRewards.sol"; import "./interfaces/IRewardDelegators.sol"; contract ClusterRewards is - Initializable, // initializer - ContextUpgradeable, // _msgSender, _msgData - ERC165Upgradeable, // supportsInterface - AccessControlUpgradeable, // RBAC - AccessControlEnumerableUpgradeable, // RBAC enumeration - ERC1967UpgradeUpgradeable, // delegate slots, proxy admin, private upgrade - UUPSUpgradeable, // public upgrade - IClusterRewards // interface + Initializable, // initializer + ContextUpgradeable, // _msgSender, _msgData + ERC165Upgradeable, // supportsInterface + AccessControlUpgradeable, // RBAC + AccessControlEnumerableUpgradeable, // RBAC enumeration + ERC1967UpgradeUpgradeable, // delegate slots, proxy admin, private upgrade + UUPSUpgradeable, // public upgrade + IClusterRewards // interface { // in case we add more contracts in the inheritance chain uint256[500] private __gap0; @@ -38,28 +38,36 @@ contract ClusterRewards is _; } -//-------------------------------- Overrides start --------------------------------// + //-------------------------------- Overrides start --------------------------------// - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, AccessControlUpgradeable, AccessControlEnumerableUpgradeable) returns (bool) { + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(ERC165Upgradeable, AccessControlUpgradeable, AccessControlEnumerableUpgradeable) returns (bool) { return super.supportsInterface(interfaceId); } - function _grantRole(bytes32 role, address account) internal virtual override(AccessControlUpgradeable, AccessControlEnumerableUpgradeable) { + function _grantRole( + bytes32 role, + address account + ) internal virtual override(AccessControlUpgradeable, AccessControlEnumerableUpgradeable) { super._grantRole(role, account); } - function _revokeRole(bytes32 role, address account) internal virtual override(AccessControlUpgradeable, AccessControlEnumerableUpgradeable) { + function _revokeRole( + bytes32 role, + address account + ) internal virtual override(AccessControlUpgradeable, AccessControlEnumerableUpgradeable) { super._revokeRole(role, account); // protect against accidentally removing all admins require(getRoleMemberCount(DEFAULT_ADMIN_ROLE) != 0, "Cannot be adminless"); } - function _authorizeUpgrade(address /*account*/) onlyAdmin internal view override {} + function _authorizeUpgrade(address /*account*/) internal view override onlyAdmin {} -//-------------------------------- Overrides end --------------------------------// + //-------------------------------- Overrides end --------------------------------// -//-------------------------------- Initializer start --------------------------------// + //-------------------------------- Initializer start --------------------------------// uint256[50] private __gap1; @@ -71,18 +79,9 @@ contract ClusterRewards is uint256[] memory _rewardWeight, address[] memory _clusterSelectors, uint256 _totalRewardsPerEpoch - ) - public - initializer - { - require( - _networkIds.length == _rewardWeight.length, - "CRW:I-Each NetworkId need a corresponding RewardPerEpoch and vice versa" - ); - require( - _networkIds.length == _clusterSelectors.length, - "CRW:I-Each NetworkId need a corresponding clusterSelector and vice versa" - ); + ) public initializer { + require(_networkIds.length == _rewardWeight.length, "CRW:I-Each NetworkId need a corresponding RewardPerEpoch and vice versa"); + require(_networkIds.length == _clusterSelectors.length, "CRW:I-Each NetworkId need a corresponding clusterSelector and vice versa"); __Context_init_unchained(); __ERC165_init_unchained(); @@ -98,9 +97,9 @@ contract ClusterRewards is _updateReceiverStaking(_receiverStaking); uint256 _weight = 0; - for(uint256 i=0; i < _networkIds.length; i++) { + for (uint256 i = 0; i < _networkIds.length; i++) { rewardWeight[_networkIds[i]] = _rewardWeight[i]; - require(_clusterSelectors[i] != address(0), "CRW:CN-ClusterSelector must exist"); + require(_clusterSelectors[i] != address(0), "CRW:CN-ClusterSelector must exist"); clusterSelectors[_networkIds[i]] = IClusterSelector(_clusterSelectors[i]); _weight += _rewardWeight[i]; emit NetworkAdded(_networkIds[i], _rewardWeight[i], _clusterSelectors[i]); @@ -110,13 +109,13 @@ contract ClusterRewards is payoutDenomination = 1e18; } -//-------------------------------- Initializer end --------------------------------// + //-------------------------------- Initializer end --------------------------------// -//-------------------------------- Admin functions start --------------------------------// + //-------------------------------- Admin functions start --------------------------------// bytes32 public constant CLAIMER_ROLE = keccak256("CLAIMER_ROLE"); bytes32 public constant FEEDER_ROLE = keccak256("FEEDER_ROLE"); - uint256 public constant RECEIVER_TICKETS_PER_EPOCH = 2**16; + uint256 public constant RECEIVER_TICKETS_PER_EPOCH = 2 ** 16; uint256 constant SWITCHING_PERIOD = 33 days; mapping(address => uint256) public clusterRewards; @@ -150,7 +149,7 @@ contract ClusterRewards is function addNetwork(bytes32 _networkId, uint256 _rewardWeight, address _clusterSelector) external onlyAdmin { require(rewardWeight[_networkId] == 0, "CRW:AN-Network already exists"); - require(_clusterSelector != address(0), "CRW:AN-ClusterSelector must exist"); + require(_clusterSelector != address(0), "CRW:AN-ClusterSelector must exist"); rewardWeight[_networkId] = _rewardWeight; IClusterSelector networkClusterSelector = IClusterSelector(_clusterSelector); require(networkClusterSelector.START_TIME() == receiverStaking.START_TIME(), "CRW:AN-start time inconsistent"); @@ -172,11 +171,11 @@ contract ClusterRewards is function updateNetwork(bytes32 _networkId, uint256 _updatedRewardWeight, address _updatedClusterSelector) external onlyAdmin { uint256 networkWeight = rewardWeight[_networkId]; - require(_updatedClusterSelector != address(0), "CRW:UN-ClusterSelector must exist"); + require(_updatedClusterSelector != address(0), "CRW:UN-ClusterSelector must exist"); address currentClusterSelector = address(clusterSelectors[_networkId]); require(currentClusterSelector != address(0), "CRW:UN-Network doesnt exist"); - if(_updatedClusterSelector != currentClusterSelector) { + if (_updatedClusterSelector != currentClusterSelector) { IClusterSelector networkClusterSelector = IClusterSelector(_updatedClusterSelector); require(networkClusterSelector.START_TIME() == receiverStaking.START_TIME(), "CRW:UN-start time inconsistent"); require(networkClusterSelector.EPOCH_LENGTH() == receiverStaking.EPOCH_LENGTH(), "CRW:UN-epoch length inconsistent"); @@ -216,19 +215,14 @@ contract ClusterRewards is emit RewardDistributionWaitTimeChanged(_updatedWaitTime); } -//-------------------------------- Admin functions end --------------------------------// + //-------------------------------- Admin functions end --------------------------------// -//-------------------------------- User functions start --------------------------------// + //-------------------------------- User functions start --------------------------------// - function feed( - bytes32 _networkId, - address[] calldata _clusters, - uint256[] calldata _payouts, - uint256 _epoch - ) external onlyFeeder { + function feed(bytes32 _networkId, address[] calldata _clusters, uint256[] calldata _payouts, uint256 _epoch) external onlyFeeder { require(receiverStaking.START_TIME() + SWITCHING_PERIOD + 1 days > block.timestamp, "CRW:F-Invalid method"); uint256 rewardDistributed = rewardDistributedPerEpoch[_epoch]; - if(rewardDistributed == 0) { + if (rewardDistributed == 0) { require( block.timestamp > latestNewEpochRewardAt + rewardDistributionWaitTime, "CRW:F-Cant distribute reward for new epoch within such short interval" @@ -237,106 +231,111 @@ contract ClusterRewards is } uint256 currentPayoutDenomination = payoutDenomination; uint256 networkRewardWeight = rewardWeight[_networkId]; - uint256 currentTotalRewardsPerEpoch = totalRewardsPerEpoch*1 days/receiverStaking.EPOCH_LENGTH()*networkRewardWeight/totalRewardWeight; - for(uint256 i=0; i < _clusters.length; i++) { + uint256 currentTotalRewardsPerEpoch = (((totalRewardsPerEpoch * 1 days) / receiverStaking.EPOCH_LENGTH()) * networkRewardWeight) / + totalRewardWeight; + for (uint256 i = 0; i < _clusters.length; i++) { uint256 clusterReward = (currentTotalRewardsPerEpoch * _payouts[i]) / currentPayoutDenomination; rewardDistributed = rewardDistributed + clusterReward; clusterRewards[_clusters[i]] = clusterRewards[_clusters[i]] + clusterReward; } - require( - rewardDistributed <= currentTotalRewardsPerEpoch, - "CRW:F-Reward Distributed cant be more than totalRewardPerEpoch" - ); + require(rewardDistributed <= currentTotalRewardsPerEpoch, "CRW:F-Reward Distributed cant be more than totalRewardPerEpoch"); rewardDistributedPerEpoch[_epoch] = rewardDistributed; emit ClusterRewarded(_networkId); } - function _extraReceiverRewards(address _receiver) internal returns (uint256) { - uint256 _receiverRewardPerEpoch = receiverRewardPerEpoch[_receiver]; - - // using least number of variables to avoid stack overflow - uint256 _receiverBalance = receiverBalance[_receiver]; - uint256 _extraRewards = _receiverBalance > _receiverRewardPerEpoch ? _receiverRewardPerEpoch: _receiverBalance; - if(_extraRewards != 0){ - receiverBalance[_receiver] -= _extraRewards; - } - - return _extraRewards; + function _getRewardShare( + uint256 _totalNetworkRewardsPerEpoch, + uint256 _epochTotalStake, + uint256 _epochReceiverStake + ) internal pure returns (uint256 _rewardShare) { + _rewardShare = (_totalNetworkRewardsPerEpoch * _epochReceiverStake) / _epochTotalStake; } - function _getRewardShare(address _receiver, uint256 _totalNetworkRewardsPerEpoch, uint256 _epochTotalStake, uint256 _epochReceiverStake) internal returns (uint256 _rewardShare) { - _rewardShare = _totalNetworkRewardsPerEpoch * _epochReceiverStake / _epochTotalStake; - _rewardShare += _extraReceiverRewards(_receiver); - } - function _processReceiverTickets(address _receiver, uint256 _epoch, address[] memory _selectedClusters, uint16[] memory _tickets, uint256 _rewardShare) internal { + function _processReceiverTickets( + address _receiver, + uint256 _epoch, + address[] memory _selectedClusters, + uint16[] memory _tickets, + uint256 _rewardShare + ) internal { require(!_isTicketsIssued(_receiver, _epoch), "CRW:IPRT-Tickets already issued"); unchecked { require(_selectedClusters.length <= _tickets.length + 1, "CRW:IPRT-Tickets length not matching selected clusters"); - + uint256 _totalTickets; uint256 i; - for(; i < _selectedClusters.length - 1; ++i) { + for (; i < _selectedClusters.length - 1; ++i) { // cant overflow as max supply of POND is 1e28, so max value of multiplication is 1e28*2^16 < uint256 // value that can be added per iteration is < 1e28*2^16/2^16, so clusterRewards for cluster cant overflow - clusterRewards[_selectedClusters[i]] += _rewardShare * uint256(_tickets[i]) / RECEIVER_TICKETS_PER_EPOCH; + clusterRewards[_selectedClusters[i]] += (_rewardShare * uint256(_tickets[i])) / RECEIVER_TICKETS_PER_EPOCH; // cant overflow as tickets[i] <= 2^16 _totalTickets += uint256(_tickets[i]); } require(RECEIVER_TICKETS_PER_EPOCH >= _totalTickets, "CRW:IPRT-Total ticket count invalid"); - clusterRewards[_selectedClusters[i]] += _rewardShare * uint256(RECEIVER_TICKETS_PER_EPOCH - _totalTickets)/RECEIVER_TICKETS_PER_EPOCH; + clusterRewards[_selectedClusters[i]] += + (_rewardShare * uint256(RECEIVER_TICKETS_PER_EPOCH - _totalTickets)) / + RECEIVER_TICKETS_PER_EPOCH; } _markAsIssued(_receiver, _epoch); } - function _isTicketsIssued(address _receiver, uint256 _epoch) internal view returns(bool) { + function _isTicketsIssued(address _receiver, uint256 _epoch) internal view returns (bool) { unchecked { - uint256 _index = _epoch/256; - uint256 _pos = _epoch%256; + uint256 _index = _epoch / 256; + uint256 _pos = _epoch % 256; uint256 _issuedFlags = ticketsIssued[_receiver][_index]; - return (_issuedFlags & 2**(255-_pos)) != 0; + return (_issuedFlags & (2 ** (255 - _pos))) != 0; } } - function isTicketsIssued(address _receiver, uint256 _epoch) public view returns(bool) { + function isTicketsIssued(address _receiver, uint256 _epoch) public view returns (bool) { return _isTicketsIssued(_receiver, _epoch); } function _markAsIssued(address _receiver, uint256 _epoch) internal { unchecked { - uint256 _index = _epoch/256; - uint256 _pos = _epoch%256; + uint256 _index = _epoch / 256; + uint256 _pos = _epoch % 256; uint256 _issuedFlags = ticketsIssued[_receiver][_index]; - ticketsIssued[_receiver][_index] = _issuedFlags | 2**(255-_pos); + ticketsIssued[_receiver][_index] = _issuedFlags | (2 ** (255 - _pos)); } } function issueTickets(bytes32 _networkId, uint24[] calldata _epochs, uint16[][] calldata _tickets) external { - uint256 numberOfEpochs = _epochs.length; - require(numberOfEpochs == _tickets.length, "CRW:MIT-invalid inputs"); + require(_epochs.length == _tickets.length, "CRW:MIT-invalid inputs"); + + address _receiver = receiverStaking.signerToStaker(msg.sender); + + uint256 _receiverExtraRewardsRemaining = receiverBalance[_receiver]; + uint256 _receiverExtraRewardsPerEpoch = receiverRewardPerEpoch[_receiver]; + unchecked { - for(uint256 i=0; i < numberOfEpochs; ++i) { + for (uint256 i = 0; i < _epochs.length; ++i) { uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_epochs[i], _networkId); (uint256 _epochTotalStake, uint256 _currentEpoch) = receiverStaking.getEpochInfo(_epochs[i]); + require(_epochs[i] < _currentEpoch, "CRW:IT-Epoch not completed"); address[] memory _selectedClusters = clusterSelectors[_networkId].getClusters(_epochs[i]); - (uint256 _epochReceiverStake, address _receiver) = receiverStaking.balanceOfSignerAt(msg.sender, _epochs[i]); - - uint256 _rewardShare = _getRewardShare(_receiver, _totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake); + uint256 _epochReceiverStake = receiverStaking.balanceOfAt(_receiver, _epochs[i]); + + uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake) + + _extraReceiverRewardThisEpoch(_receiverExtraRewardsRemaining, _receiverExtraRewardsPerEpoch); + _processReceiverTickets(_receiver, _epochs[i], _selectedClusters, _tickets[i], _rewardShare); - emit TicketsIssued(_networkId, _epochs[i], msg.sender); + _receiverExtraRewardsRemaining -= _extraReceiverRewardThisEpoch( + _receiverExtraRewardsRemaining, + _receiverExtraRewardsPerEpoch + ); + _emitTicketsIssued(_networkId, _epochs[i], msg.sender); } } + _setReceiverBalance(_receiver, _receiverExtraRewardsRemaining); } function issueTickets(bytes calldata _ticketInfo) external { - ( - bytes32 _networkId, - uint256 _fromEpoch, - uint256 _noOfEpochs, - uint16[][] memory _tickets - ) = _parseTicketInfo(_ticketInfo); + (bytes32 _networkId, uint256 _fromEpoch, uint256 _noOfEpochs, uint16[][] memory _tickets) = _parseTicketInfo(_ticketInfo); ReceiverStaking _receiverStaking = receiverStaking; require(_fromEpoch + _noOfEpochs <= _receiverStaking.getCurrentEpoch(), "CRW:ITC-Epochs not completed"); @@ -344,73 +343,105 @@ contract ClusterRewards is uint256[] memory _stakes = _receiverStaking.totalSupplyAtRanged(_fromEpoch, _noOfEpochs); (uint256[] memory _balances, address _receiver) = _receiverStaking.balanceOfSignerAtRanged(msg.sender, _fromEpoch, _noOfEpochs); address[][] memory _selectedClusters = clusterSelectors[_networkId].getClustersRanged(_fromEpoch, _noOfEpochs); - uint256 _epochLength = _receiverStaking.EPOCH_LENGTH(); + + uint256 _receiverExtraRewardsRemaining = receiverBalance[_receiver]; + uint256 _receiverExtraRewardsPerEpoch = receiverRewardPerEpoch[_receiver]; uint256 _totalNetworkRewardsPerEpoch; unchecked { - for(uint256 i=0; i < _noOfEpochs; ++i) { - _totalNetworkRewardsPerEpoch = _getRewardForEpoch(_fromEpoch, _networkId, _epochLength); - uint256 _rewardShare = _getRewardShare(_receiver, _totalNetworkRewardsPerEpoch, _stakes[i], _balances[i]); + for (uint256 i = 0; i < _noOfEpochs; ++i) { + // _totalNetworkRewardsPerEpoch = inflation rewards + receiver rewards + _totalNetworkRewardsPerEpoch = getRewardForEpoch(_fromEpoch, _networkId); + uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _stakes[i], _balances[i]) + + _extraReceiverRewardThisEpoch(_receiverExtraRewardsRemaining, _receiverExtraRewardsPerEpoch); + _processReceiverTickets(_receiver, _fromEpoch, _selectedClusters[i], _tickets[i], _rewardShare); - emit TicketsIssued(_networkId, _fromEpoch, msg.sender); + _emitTicketsIssued(_networkId, _fromEpoch, msg.sender); + _receiverExtraRewardsRemaining -= _extraReceiverRewardThisEpoch( + _receiverExtraRewardsRemaining, + _receiverExtraRewardsPerEpoch + ); ++_fromEpoch; } } + + _setReceiverBalance(_receiver, _receiverExtraRewardsRemaining); } - function _parseTicketInfo(bytes memory ticketInfo) internal view returns( - bytes32 networkId, - uint256 fromEpoch, - uint256 noOfEpochs, - uint16[][] memory tickets - ) { + function _setReceiverBalance(address _receiver, uint256 _receiverExtraRewardsRemaining) internal { + if (_receiverExtraRewardsRemaining != 0) { + receiverBalance[_receiver] = _receiverExtraRewardsRemaining; + } + } + + function _extraReceiverRewardThisEpoch( + uint256 _receiverExtraRewardsRemaining, + uint256 _receiverExtraRewardsPerEpoch + ) internal pure returns (uint256) { + return + _receiverExtraRewardsRemaining > _receiverExtraRewardsPerEpoch ? _receiverExtraRewardsPerEpoch : _receiverExtraRewardsRemaining; + } + + function _emitTicketsIssued(bytes32 _networkId, uint256 _epoch, address signer) internal { + emit TicketsIssued(_networkId, _epoch, signer); + } + + function _parseTicketInfo( + bytes memory ticketInfo + ) internal view returns (bytes32 networkId, uint256 fromEpoch, uint256 noOfEpochs, uint16[][] memory tickets) { // Ticket Structure // |--NetworkId(256 bits)--|--FromEpoch(32 bits)--|--N*Ticket(16 bits)--| uint256 length; bytes32 currentWord; assembly { - length := mload(ticketInfo) + length := mload(ticketInfo) networkId := mload(add(ticketInfo, 0x20)) currentWord := mload(add(ticketInfo, 0x40)) } fromEpoch = uint256(currentWord >> 224); unchecked { - require(length >= 36 && (length - 36)%8 == 0, "CR:IPTI-invalid ticket info encoding"); - noOfEpochs = (length - 36)/8; // 32 (networkId) + 4 (fromEpoch) / 2(tickets) / 4(tickets per epoch) + require(length >= 36 && (length - 36) % 8 == 0, "CR:IPTI-invalid ticket info encoding"); + noOfEpochs = (length - 36) / 8; // 32 (networkId) + 4 (fromEpoch) / 2(tickets) / 4(tickets per epoch) // +1 because of slight overflow on last word - uint256 noOfWords = length/32; + uint256 noOfWords = length / 32; tickets = new uint16[][](noOfEpochs); uint256 clustersToSelect = clusterSelectors[networkId].NUMBER_OF_CLUSTERS_TO_SELECT(); // revert due to memory expansion overflow in case of underflow tickets[0] = new uint16[](clustersToSelect - 1); (uint256 _currentEpochIndex, uint256 _currentTicketIndex) = _extractTickets(currentWord, 2, 0, 0, tickets); - for(uint256 i=1; i < noOfWords; ++i) { + for (uint256 i = 1; i < noOfWords; ++i) { assembly { currentWord := mload(add(ticketInfo, add(0x40, mul(0x20, i)))) } - (_currentEpochIndex, _currentTicketIndex) = _extractTickets(currentWord, 0, _currentEpochIndex, _currentTicketIndex, tickets); + (_currentEpochIndex, _currentTicketIndex) = _extractTickets( + currentWord, + 0, + _currentEpochIndex, + _currentTicketIndex, + tickets + ); } } } function _extractTickets( - bytes32 word, - uint256 startIndex, - uint256 currentEpochIndex, + bytes32 word, + uint256 startIndex, + uint256 currentEpochIndex, uint256 currentTicketIndex, uint16[][] memory tickets - ) internal pure returns(uint256, uint256) { + ) internal pure returns (uint256, uint256) { unchecked { - for(uint256 i = startIndex; i < 16; ++i) { - tickets[currentEpochIndex][currentTicketIndex] = uint16(uint256(word >> (256 - (i + 1)*16))); + for (uint256 i = startIndex; i < 16; ++i) { + tickets[currentEpochIndex][currentTicketIndex] = uint16(uint256(word >> (256 - (i + 1) * 16))); currentTicketIndex++; - if(currentTicketIndex > tickets[currentEpochIndex].length - 1) { - if(currentEpochIndex == tickets.length - 1) return (currentEpochIndex, currentTicketIndex); + if (currentTicketIndex > tickets[currentEpochIndex].length - 1) { + if (currentEpochIndex == tickets.length - 1) return (currentEpochIndex, currentTicketIndex); currentEpochIndex++; - tickets[currentEpochIndex] = new uint16[](tickets[currentEpochIndex-1].length); + tickets[currentEpochIndex] = new uint16[](tickets[currentEpochIndex - 1].length); currentTicketIndex = 0; } } @@ -423,21 +454,25 @@ contract ClusterRewards is require(_epoch < _currentEpoch, "CRW:IT-Epoch not completed"); - address[] memory _selectedClusters = clusterSelectors[_networkId].getClusters(_epoch); + (uint256 _epochReceiverStake, address _receiver) = receiverStaking.balanceOfSignerAt(msg.sender, _epoch); + uint256 _receiverExtraRewardsRemaining = receiverBalance[_receiver]; + uint256 _receiverExtraRewardsPerEpoch = receiverRewardPerEpoch[_receiver]; + address[] memory _selectedClusters = clusterSelectors[_networkId].getClusters(_epoch); uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_epoch, _networkId); - (uint256 _epochReceiverStake, address _receiver) = receiverStaking.balanceOfSignerAt(msg.sender, _epoch); - - uint256 _rewardShare = _getRewardShare(_receiver, _totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake); + uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake) + + _extraReceiverRewardThisEpoch(_receiverExtraRewardsRemaining, _receiverExtraRewardsPerEpoch); _processReceiverTickets(_receiver, _epoch, _selectedClusters, _tickets, _rewardShare); + _emitTicketsIssued(_networkId, _epoch, msg.sender); - emit TicketsIssued(_networkId, _epoch, msg.sender); + _receiverExtraRewardsRemaining -= _extraReceiverRewardThisEpoch(_receiverExtraRewardsRemaining, _receiverExtraRewardsPerEpoch); + _setReceiverBalance(_receiver, _receiverExtraRewardsRemaining); } - function claimReward(address _cluster) external onlyRole(CLAIMER_ROLE) returns(uint256) { + function claimReward(address _cluster) external onlyRole(CLAIMER_ROLE) returns (uint256) { uint256 pendingRewards = clusterRewards[_cluster]; - if(pendingRewards > 1) { + if (pendingRewards > 1) { uint256 rewardsToTransfer = pendingRewards - 1; clusterRewards[_cluster] = 1; return rewardsToTransfer; @@ -445,28 +480,28 @@ contract ClusterRewards is return 0; } - function _getRewardForEpoch(uint256 _epoch, bytes32 _networkId, uint256 _epochLength) internal view returns(uint256) { - if(_epoch < SWITCHING_PERIOD/_epochLength) return 0; + function _getRewardForEpoch(uint256 _epoch, bytes32 _networkId, uint256 _epochLength) internal view returns (uint256) { + if (_epoch < SWITCHING_PERIOD / _epochLength) return 0; return (totalRewardsPerEpoch * rewardWeight[_networkId]) / totalRewardWeight; } - function getRewardForEpoch(uint256 _epoch, bytes32 _networkId) public view returns(uint256) { + function getRewardForEpoch(uint256 _epoch, bytes32 _networkId) public view returns (uint256) { return _getRewardForEpoch(_epoch, _networkId, receiverStaking.EPOCH_LENGTH()); } -//-------------------------------- User functions end --------------------------------// + //-------------------------------- User functions end --------------------------------// bytes32 public constant RECEIVER_PAYMENTS_MANAGER = keccak256("RECEIVER_PAYMENTS_MANAGER"); - + mapping(address => uint256) public receiverBalance; mapping(address => uint256) public receiverRewardPerEpoch; - function _increaseReceiverBalance(address staker, uint256 amount) onlyRole(RECEIVER_PAYMENTS_MANAGER) external override { - receiverBalance[staker]+=amount; + function _increaseReceiverBalance(address staker, uint256 amount) external override onlyRole(RECEIVER_PAYMENTS_MANAGER) { + receiverBalance[staker] += amount; } - function _setReceiverRewardPerEpoch(address staker, uint256 rewardPerEpoch) onlyRole(RECEIVER_PAYMENTS_MANAGER) external override { + function _setReceiverRewardPerEpoch(address staker, uint256 rewardPerEpoch) external override onlyRole(RECEIVER_PAYMENTS_MANAGER) { require(staker != address(0), "CRW: address 0"); receiverRewardPerEpoch[staker] = rewardPerEpoch; } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 50b71919..e0e59790 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,12 +30,1781 @@ "ethers": "^5.7.2", "hardhat": "2.12.7", "hardhat-gas-reporter": "^1.0.9", + "prettier": "^2.8.8", + "prettier-plugin-solidity": "^1.1.3", + "solgraph": "^1.0.2", "solidity-coverage": "^0.8.2", "ts-node": "^10.9.1", "typechain": "^8.1.1", "typescript": "^4.9.5" } }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/cli": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.21.5.tgz", + "integrity": "sha512-TOKytQ9uQW9c4np8F+P7ZfPINy5Kv+pizDIUwSVH8X5zHgYHV4AA8HE5LA450xXeu4jEfmUckTYvv1I4S26M/g==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "commander": "^4.0.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.2.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0" + }, + "bin": { + "babel": "bin/babel.js", + "babel-external-helpers": "bin/babel-external-helpers.js" + }, + "engines": { + "node": ">=6.9.0" + }, + "optionalDependencies": { + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.4.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/cli/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@babel/cli/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@babel/cli/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.3.tgz", + "integrity": "sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.1.tgz", + "integrity": "sha512-Hkqu7J4ynysSXxmAahpN1jjRwVJ+NdpraFLIWflgjpVob3KNyK3/tIUc7Q7szed8WMp0JNa7Qtd1E9Oo22F9gA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.22.0", + "@babel/helper-compilation-targets": "^7.22.1", + "@babel/helper-module-transforms": "^7.22.1", + "@babel/helpers": "^7.22.0", + "@babel/parser": "^7.22.0", + "@babel/template": "^7.21.9", + "@babel/traverse": "^7.22.1", + "@babel/types": "^7.22.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.3.tgz", + "integrity": "sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.3", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.3.tgz", + "integrity": "sha512-ahEoxgqNoYXm0k22TvOke48i1PkavGu0qGCmcq9ugi6gnmvKNaMjKBSrZTnWUi1CFEeNAUiVba0Wtzm03aSkJg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.1.tgz", + "integrity": "sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.0", + "@babel/helper-validator-option": "^7.21.0", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.1.tgz", + "integrity": "sha512-SowrZ9BWzYFgzUMwUmowbPSGu6CXL5MSuuCkG3bejahSpSymioPmuLdhPxNOc9MjuNGjy7M/HaXvJ8G82Lywlw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.22.1", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-member-expression-to-functions": "^7.22.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.22.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/helper-split-export-declaration": "^7.18.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.1.tgz", + "integrity": "sha512-WWjdnfR3LPIe+0EY8td7WmjhytxXtjKAEpnAxun/hkNiyOaPlvGK+NZaBFIdi9ndYV3Gav7BpFvtUwnaJlwi1w==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.3.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz", + "integrity": "sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.1.tgz", + "integrity": "sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.3.tgz", + "integrity": "sha512-Gl7sK04b/2WOb6OPVeNy9eFKeD3L6++CzL3ykPOWqTn08xgYYK0wz4TUh2feIImDXxcVW3/9WQ1NMKY66/jfZA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", + "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.1.tgz", + "integrity": "sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.1", + "@babel/helper-module-imports": "^7.21.4", + "@babel/helper-simple-access": "^7.21.5", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.21.9", + "@babel/traverse": "^7.22.1", + "@babel/types": "^7.22.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz", + "integrity": "sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.1.tgz", + "integrity": "sha512-ut4qrkE4AuSfrwHSps51ekR1ZY/ygrP1tp0WFm8oVq6nzc/hvfV/22JylndIbsf2U2M9LOMwiSddr6y+78j+OQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.1", + "@babel/helper-member-expression-to-functions": "^7.22.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/template": "^7.21.9", + "@babel/traverse": "^7.22.1", + "@babel/types": "^7.22.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz", + "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", + "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", + "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.3.tgz", + "integrity": "sha512-jBJ7jWblbgr7r6wYZHMdIqKc73ycaTcCaWRq4/2LpuPHcx7xMlZvpGQkOYc9HeSjn6rcx15CPlgVcBtZ4WZJ2w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.21.9", + "@babel/traverse": "^7.22.1", + "@babel/types": "^7.22.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.4.tgz", + "integrity": "sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.3.tgz", + "integrity": "sha512-6r4yRwEnorYByILoDRnEqxtojYKuiIv9FojW2E8GUKo9eWBwbKcd9IiZOZpdyXc64RmyGGyPu3/uAcrz/dq2kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-transform-optional-chaining": "^7.22.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.3.tgz", + "integrity": "sha512-i35jZJv6aO7hxEbIWQ41adVfOzjm9dcYDNeWlBMd8p0ZQRtNUCBrmGwZt+H5lb+oOC9a3svp956KP0oWGA1YsA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz", + "integrity": "sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.3.tgz", + "integrity": "sha512-36A4Aq48t66btydbZd5Fk0/xJqbpg/v4QWI4AH4cYHBXy9Mu42UOupZpebKFiCFNT9S9rJFcsld0gsv0ayLjtA==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.1", + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", + "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz", + "integrity": "sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.3.tgz", + "integrity": "sha512-mASLsd6rhOrLZ5F3WbCxkzl67mmOnqik0zrg5W6D/X0QMW7HtvnoL1dRARLKIbMP3vXwkwziuLesPqWVGIl6Bw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.1", + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.3.tgz", + "integrity": "sha512-5BirgNWNOx7cwbTJCOmKFJ1pZjwk5MUfMIwiBBvsirCJMZeQgs5pk6i1OlkVg+1Vef5LfBahFOrdCnAWvkVKMw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.1", + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz", + "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz", + "integrity": "sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/template": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz", + "integrity": "sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.1.tgz", + "integrity": "sha512-rlhWtONnVBPdmt+jeewS0qSnMz/3yLFrqAP8hHC6EDcrYRSyuz9f9yQhHvVn2Ad6+yO9fHXac5piudeYrInxwQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.3.tgz", + "integrity": "sha512-5Ti1cHLTDnt3vX61P9KZ5IG09bFXp4cDVFJIAeCZuxu9OXXJJZp5iP0n/rzM2+iAutJY+KWEyyHcRaHlpQ/P5g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz", + "integrity": "sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.3.tgz", + "integrity": "sha512-IuvOMdeOOY2X4hRNAT6kwbePtK21BUyrAEgLKviL8pL6AEEVUVcqtRdN/HJXBLGIbt9T3ETmXRnFedRRmQNTYw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.3.tgz", + "integrity": "sha512-CbayIfOw4av2v/HYZEsH+Klks3NC2/MFIR3QR8gnpGNNPEaq2fdlVCRYG/paKs7/5hvBLQ+H70pGWOHtlNEWNA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", + "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz", + "integrity": "sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.21.5", + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/helper-simple-access": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.3.tgz", + "integrity": "sha512-V21W3bKLxO3ZjcBJZ8biSvo5gQ85uIXW2vJfh7JSWf/4SLUSr1tOoHX3ruN4+Oqa2m+BKfsxTR1I+PsvkIWvNw==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.22.1", + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/helper-validator-identifier": "^7.19.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.3.tgz", + "integrity": "sha512-c6HrD/LpUdNNJsISQZpds3TXvfYIAbo+efE9aWmY/PmSRD0agrJ9cPMt4BmArwUQ7ZymEWTFjTyp+yReLJZh0Q==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.1", + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.3.tgz", + "integrity": "sha512-5RuJdSo89wKdkRTqtM9RVVJzHum9c2s0te9rB7vZC1zKKxcioWIy+xcu4OoIAjyFZhb/bp5KkunuLin1q7Ct+w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.3.tgz", + "integrity": "sha512-CpaoNp16nX7ROtLONNuCyenYdY/l7ZsR6aoVa7rW7nMWisoNoQNIH5Iay/4LDyRjKMuElMqXiBoOQCDLTMGZiw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.3.tgz", + "integrity": "sha512-+AF88fPDJrnseMh5vD9+SH6wq4ZMvpiTMHh58uLs+giMEyASFVhcT3NkoyO+NebFCNnpHJEq5AXO2txV4AGPDQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.3.tgz", + "integrity": "sha512-38bzTsqMMCI46/TQnJwPPpy33EjLCc1Gsm2hRTF6zTMWnKsN61vdrpuzIEGQyKEhDSYDKyZHrrd5FMj4gcUHhw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.3", + "@babel/helper-compilation-targets": "^7.22.1", + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.22.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.3.tgz", + "integrity": "sha512-bnDFWXFzWY0BsOyqaoSXvMQ2F35zutQipugog/rqotL2S4ciFOKlRYUu9djt4iq09oh2/34hqfRR2k1dIvuu4g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.3.tgz", + "integrity": "sha512-63v3/UFFxhPKT8j8u1jTTGVyITxl7/7AfOqK8C5gz1rHURPUGe3y5mvIf68eYKGoBNahtJnTxBKug4BQOnzeJg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.3.tgz", + "integrity": "sha512-x7QHQJHPuD9VmfpzboyGJ5aHEr9r7DsAsdxdhJiTB3J3j8dyl+NFZ+rX5Q2RWFDCs61c06qBfS4ys2QYn8UkMw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.3.tgz", + "integrity": "sha512-fC7jtjBPFqhqpPAE+O4LKwnLq7gGkD3ZmC2E3i4qWH34mH3gOg2Xrq5YMHUq6DM30xhqM1DNftiRaSqVjEG+ug==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.1", + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.3.tgz", + "integrity": "sha512-C7MMl4qWLpgVCbXfj3UW8rR1xeCnisQ0cU7YJHV//8oNBS0aCIVg1vFnZXxOckHhEpQyqNNkWmvSEWnMLlc+Vw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.22.1", + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz", + "integrity": "sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5", + "regenerator-transform": "^0.15.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", + "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz", + "integrity": "sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.3.tgz", + "integrity": "sha512-5ScJ+OmdX+O6HRuMGW4kv7RL9vIKdtdAj9wuWUKy1wbHY3jaM/UlyIiC1G7J6UJiiyMukjjK0QwL3P0vBd0yYg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.1", + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.3.tgz", + "integrity": "sha512-hNufLdkF8vqywRp+P55j4FHXqAX2LRUccoZHH7AFn1pq5ZOO2ISKW9w13bFZVjBoTqeve2HOgoJCcaziJVhGNw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.1", + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.22.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.4.tgz", + "integrity": "sha512-c3lHOjbwBv0TkhYCr+XCR6wKcSZ1QbQTVdSkZUaVpLv8CVWotBMArWUi5UAJrcrQaEnleVkkvaV8F/pmc/STZQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.3", + "@babel/helper-compilation-targets": "^7.22.1", + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/helper-validator-option": "^7.21.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.3", + "@babel/plugin-proposal-private-property-in-object": "^7.21.0", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/plugin-syntax-import-attributes": "^7.22.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.21.5", + "@babel/plugin-transform-async-generator-functions": "^7.22.3", + "@babel/plugin-transform-async-to-generator": "^7.20.7", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.21.0", + "@babel/plugin-transform-class-properties": "^7.22.3", + "@babel/plugin-transform-class-static-block": "^7.22.3", + "@babel/plugin-transform-classes": "^7.21.0", + "@babel/plugin-transform-computed-properties": "^7.21.5", + "@babel/plugin-transform-destructuring": "^7.21.3", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-dynamic-import": "^7.22.1", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-export-namespace-from": "^7.22.3", + "@babel/plugin-transform-for-of": "^7.21.5", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-json-strings": "^7.22.3", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.22.3", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.20.11", + "@babel/plugin-transform-modules-commonjs": "^7.21.5", + "@babel/plugin-transform-modules-systemjs": "^7.22.3", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.3", + "@babel/plugin-transform-new-target": "^7.22.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.3", + "@babel/plugin-transform-numeric-separator": "^7.22.3", + "@babel/plugin-transform-object-rest-spread": "^7.22.3", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-optional-catch-binding": "^7.22.3", + "@babel/plugin-transform-optional-chaining": "^7.22.3", + "@babel/plugin-transform-parameters": "^7.22.3", + "@babel/plugin-transform-private-methods": "^7.22.3", + "@babel/plugin-transform-private-property-in-object": "^7.22.3", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.21.5", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.20.7", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.21.5", + "@babel/plugin-transform-unicode-property-regex": "^7.22.3", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/plugin-transform-unicode-sets-regex": "^7.22.3", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.22.4", + "babel-plugin-polyfill-corejs2": "^0.4.3", + "babel-plugin-polyfill-corejs3": "^0.8.1", + "babel-plugin-polyfill-regenerator": "^0.5.0", + "core-js-compat": "^3.30.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.21.0.tgz", + "integrity": "sha512-9nKsPmYDi5DidAqJaQooxIhsLJiNMkGr8ypQ8Uic7cIox7UCDsM7HuUGxdGT7mSDTYbqzIdsOWzfBton/YJrMw==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.5", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz", + "integrity": "sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.21.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.21.9.tgz", + "integrity": "sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.21.4", + "@babel/parser": "^7.21.9", + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.22.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.4.tgz", + "integrity": "sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.22.3", + "@babel/helper-environment-visitor": "^7.22.1", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.22.4", + "@babel/types": "^7.22.4", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.22.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.4.tgz", + "integrity": "sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.21.5", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1270,6 +3039,20 @@ "@trufflesuite/bigint-buffer": "1.1.9" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", @@ -1279,6 +3062,15 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", @@ -1373,6 +3165,13 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "dev": true, + "optional": true + }, "node_modules/@noble/hashes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", @@ -2942,6 +4741,45 @@ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "dev": true }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz", + "integrity": "sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.4.0", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz", + "integrity": "sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.0", + "core-js-compat": "^3.30.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz", + "integrity": "sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3140,6 +4978,38 @@ "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", "dev": true }, + "node_modules/browserslist": { + "version": "4.21.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.7.tgz", + "integrity": "sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001489", + "electron-to-chromium": "^1.4.411", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/bs58": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", @@ -3245,6 +5115,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001495", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001495.tgz", + "integrity": "sha512-F6x5IEuigtUfU5ZMQK2jsy5JqUUlEFRVZq8bO2a+ysq5K7jD6PPc9YXZj78xDNS3uNchesp1Jw47YXEqr+Viyg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -3462,6 +5352,20 @@ "node": ">=8" } }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -3571,6 +5475,12 @@ "node": ">= 12" } }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, "node_modules/compare-versions": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.3.tgz", @@ -3628,6 +5538,12 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, "node_modules/cookie": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", @@ -3637,6 +5553,19 @@ "node": ">= 0.6" } }, + "node_modules/core-js-compat": { + "version": "3.30.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.2.tgz", + "integrity": "sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-js-pure": { "version": "3.29.1", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.29.1.tgz", @@ -3947,6 +5876,12 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/electron-to-chromium": { + "version": "1.4.421", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.421.tgz", + "integrity": "sha512-wZOyn3s/aQOtLGAwXMZfteQPN68kgls2wDAnYOA8kCjBvKVrW5RwmWVspxJYTqrcN7Y263XJVsC66VCIGzDO3g==", + "dev": true + }, "node_modules/elliptic": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", @@ -5082,6 +7017,20 @@ "node": ">=8" } }, + "node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/find-replace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", @@ -5411,7 +7360,6 @@ "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", "integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==", "dev": true, - "hasInstallScript": true, "optional": true, "dependencies": { "node-gyp-build": "^4.3.0" @@ -5774,7 +7722,6 @@ "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz", "integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==", "dev": true, - "hasInstallScript": true, "optional": true, "dependencies": { "node-gyp-build": "^4.3.0" @@ -5788,6 +7735,15 @@ "inBundle": true, "license": "MIT" }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -5829,6 +7785,28 @@ "node": ">=4" } }, + "node_modules/get-stdin": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-3.0.2.tgz", + "integrity": "sha512-jpAk+A3NUGL/O4N3pwG1Ld3PDqOxTkuAA/HoVHwutXsqW/qYcc4YSgccFpPlg/qbNhZ33ybFhJjbJqRZhryolQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-stdin-promise": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/get-stdin-promise/-/get-stdin-promise-0.1.1.tgz", + "integrity": "sha512-0RgDBFza6edY8WeH/LCXbTJ7g+XuA1S1Ua9Qkd46h6wkzL79W4RVYPTuc4hdzM5EcVl+7U8Xyw3Kr1PI+yJxjg==", + "dev": true, + "dependencies": { + "get-stdin": "^3.0.2", + "rsvp": "^3.0.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -5925,6 +7903,15 @@ "node": ">=6" } }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/globalthis": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", @@ -5977,6 +7964,25 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/graphlib-dot": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/graphlib-dot/-/graphlib-dot-0.6.4.tgz", + "integrity": "sha512-rdhDTu0mBlloTpFMfkQq+e3y4yL22OqP5MhQbkw6QUURqa+4YLgv3XZy2fA64wdEcJNZ+waI76URemVgdFtzng==", + "dev": true, + "dependencies": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, "node_modules/growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -6771,6 +8777,18 @@ "node": ">=8" } }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -6903,6 +8921,15 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -6915,6 +8942,12 @@ "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", "dev": true }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -6933,6 +8966,18 @@ "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -6960,6 +9005,18 @@ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -7288,6 +9345,12 @@ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -7410,6 +9473,28 @@ "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==", "dev": true }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -7956,6 +10041,12 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-releases": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", + "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", + "dev": true + }, "node_modules/nofilter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", @@ -8299,6 +10390,12 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -8343,6 +10440,88 @@ "node": ">=0.10.0" } }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -8353,9 +10532,9 @@ } }, "node_modules/prettier": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", - "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -8367,6 +10546,65 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-plugin-solidity": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz", + "integrity": "sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg==", + "dev": true, + "dependencies": { + "@solidity-parser/parser": "^0.16.0", + "semver": "^7.3.8", + "solidity-comments-extractor": "^0.0.7" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "prettier": ">=2.3.0 || >=3.0.0-alpha.0" + } + }, + "node_modules/prettier-plugin-solidity/node_modules/@solidity-parser/parser": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.0.tgz", + "integrity": "sha512-ESipEcHyRHg4Np4SqBCfcXwyxxna1DgFVz69bgpLV8vzl/NP1DtcKsJ4dJZXWQhY/Z4J2LeKBiOkOVZn9ct33Q==", + "dev": true, + "dependencies": { + "antlr4ts": "^0.5.0-alpha.4" + } + }, + "node_modules/prettier-plugin-solidity/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prettier-plugin-solidity/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prettier-plugin-solidity/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -8623,6 +10861,39 @@ "node": ">=6" } }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -8640,6 +10911,44 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, "node_modules/req-cwd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/req-cwd/-/req-cwd-2.0.0.tgz", @@ -8868,6 +11177,15 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true }, + "node_modules/rsvp": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", + "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==", + "dev": true, + "engines": { + "node": "0.12.* || 4.* || 6.* || >= 7.*" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -9167,6 +11485,18 @@ "node": "*" } }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/shelljs": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", @@ -9302,12 +11632,42 @@ "semver": "bin/semver" } }, + "node_modules/solgraph": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/solgraph/-/solgraph-1.0.2.tgz", + "integrity": "sha512-jSml2xRdGcD4NGVX2okXPVtqFOlduCg1Zs+JCZYEiVXeYWvCfn65PjCvt+BG3dfW7gWaRzqDa5mqf3lhaFflxw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@babel/cli": "^7.19.3", + "@babel/core": "^7.20.5", + "@babel/preset-env": "^7.20.2", + "@babel/register": "^7.18.9", + "@solidity-parser/parser": "^0.14.5", + "commander": "*", + "get-stdin-promise": "*", + "graphlib": "^2.1.8", + "graphlib-dot": "^0.6.4" + }, + "bin": { + "solgraph": "solgraph.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/solidity-ast": { "version": "0.4.46", "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.46.tgz", "integrity": "sha512-MlPZQfPhjWXqh7YxWcBGDXaPZIfMYCOHYoLEhGDWulNwEPIQQZuB7mA9eP17CU0jY/bGR4avCEUVVpvHtT2gbA==", "dev": true }, + "node_modules/solidity-comments-extractor": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz", + "integrity": "sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw==", + "dev": true + }, "node_modules/solidity-coverage": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.2.tgz", @@ -10337,6 +12697,15 @@ "node": ">=0.6.0" } }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -10740,6 +13109,46 @@ "node": ">=12.18" } }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -10758,6 +13167,36 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index e1ad7e86..f05d8bee 100755 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "coverage": "npx hardhat coverage", "clean": "npx hardhat clean && rm -rf coverage && rm -rf artifacts && npx hardhat typechain", "prettify:contracts": "npx prettier --write contracts/**/*.sol", - "prettify:test": "npx prettier --write test" + "prettify:test": "npx prettier --write test", + "flatten": "npx waffle flatten", + "solgraph": "solgraph" }, "repository": { "type": "git", @@ -46,6 +48,9 @@ "ethers": "^5.7.2", "hardhat": "2.12.7", "hardhat-gas-reporter": "^1.0.9", + "prettier": "^2.8.8", + "prettier-plugin-solidity": "^1.1.3", + "solgraph": "^1.0.2", "solidity-coverage": "^0.8.2", "ts-node": "^10.9.1", "typechain": "^8.1.1", diff --git a/test/staking/ClusterRewards.ts b/test/staking/ClusterRewards.ts index 4b7946c0..5b6de288 100644 --- a/test/staking/ClusterRewards.ts +++ b/test/staking/ClusterRewards.ts @@ -870,6 +870,7 @@ describe("ClusterRewards submit tickets", function () { it.skip("staker cannot submit partial tickets", async function () { await receiverStaking.mock.balanceOfSignerAt.reverts(); await receiverStaking.mock.balanceOfSignerAt.withArgs(addrs[5], 2).returns(50, addrs[4]); + await receiverStaking.mock.signerToStaker.withArgs(addrs[5]).returns(addrs[4]); await receiverStaking.mock.getEpochInfo.reverts(); await receiverStaking.mock.getEpochInfo.withArgs(2).returns(500, 5); await ethSelector.mock.getClusters.returns([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]); @@ -892,7 +893,10 @@ describe("ClusterRewards submit tickets", function () { it("staker cannot submit excess tickets", async function () { const epochWithRewards = (33 * 86400) / 900 + 2; await receiverStaking.mock.balanceOfSignerAt.reverts(); - await receiverStaking.mock.balanceOfSignerAt.withArgs(addrs[5], epochWithRewards).returns(50, addrs[4]); + + await receiverStaking.mock.signerToStaker.withArgs(addrs[5]).returns(addrs[4]); + await receiverStaking.mock.balanceOfAt.withArgs(addrs[4], epochWithRewards).returns(50); + await receiverStaking.mock.getEpochInfo.reverts(); await receiverStaking.mock.getEpochInfo.withArgs(epochWithRewards).returns(500, epochWithRewards + 3); await ethSelector.mock.getClusters.returns([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]); @@ -1661,17 +1665,20 @@ describe("ClusterRewards: Add Receiver extra payment", function () { }); it("Reward Checking after receiver adds extra tokens", async () => { - const mockRewardDelegators = signers[39]; // any random address can be used - const receiverRewardPerEpoch = BN.from(500); + const mockRewardDelegators = signers[49]; // any random address can be used + const receiverRewardPerEpoch = BN.from(3000); + const receiverBalance = BN.from(100000) + await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), mockRewardDelegators.getAddress()); // balance can be added by any address, hence we are using staker address directly - await clusterRewards.connect(mockRewardDelegators)._increaseReceiverBalance(staker.getAddress(), 100000); + await clusterRewards.connect(mockRewardDelegators)._increaseReceiverBalance(staker.getAddress(), receiverBalance); // reward will set only by signer, hence we are using signer address here await clusterRewards.connect(mockRewardDelegators)._setReceiverRewardPerEpoch(staker.getAddress(), receiverRewardPerEpoch); const epochWithRewards = (33 * 86400) / 900 + 2; await ethSelector.mock.getClusters.returns([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]); + // is this 500 inflation rewards let receiverRewards1 = tickets.map((e) => ETH_REWARD.mul(50).mul(e).div(500).div(MAX_TICKETS)); let extraRewards1 = tickets.map((e) => receiverRewardPerEpoch.mul(e).div(MAX_TICKETS)); @@ -1679,6 +1686,8 @@ describe("ClusterRewards: Add Receiver extra payment", function () { await receiverStaking.mock.balanceOfSignerAt .withArgs(await signer.getAddress(), epochWithRewards) .returns(50, await staker.getAddress()); + + // is this 500 inflation rewards await receiverStaking.mock.getEpochInfo.withArgs(epochWithRewards).returns(500, epochWithRewards + 3); await skipToTimestamp(startTime + 34 * 86400); @@ -1690,5 +1699,4 @@ describe("ClusterRewards: Add Receiver extra payment", function () { expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(extraRewards1[3]), 1); expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 1); }); -}); - +}); \ No newline at end of file From fa10959b510af40529fb871af007ce1b04309015 Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Tue, 6 Jun 2023 14:33:19 +0400 Subject: [PATCH 12/30] add-waffle-file --- waffle.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 waffle.json diff --git a/waffle.json b/waffle.json new file mode 100644 index 00000000..e96ef257 --- /dev/null +++ b/waffle.json @@ -0,0 +1,3 @@ +{ + "flattenOutputDirectory": "./custom_flatten" +} \ No newline at end of file From bc87a827c848c499d442098a6ba10c81f093f5b2 Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Wed, 7 Jun 2023 12:49:12 +0400 Subject: [PATCH 13/30] update --- test/staking/ClusterRewards.ts | 177 +++++++++++++++++++++++++++++++-- 1 file changed, 171 insertions(+), 6 deletions(-) diff --git a/test/staking/ClusterRewards.ts b/test/staking/ClusterRewards.ts index 5b6de288..917aff0a 100644 --- a/test/staking/ClusterRewards.ts +++ b/test/staking/ClusterRewards.ts @@ -852,6 +852,41 @@ describe("ClusterRewards submit tickets", function () { ).to.be.revertedWith("CRW:IPRT-Tickets already issued"); }); + it("staker can submit tickets with extra rewards enabled", async function () { + const epochWithRewards = (33 * 86400) / 900 + 2; + await receiverStaking.mock.balanceOfSignerAt.reverts(); + + await receiverStaking.mock.balanceOfAt.withArgs(addrs[4], epochWithRewards).returns(50); + await receiverStaking.mock.signerToStaker.withArgs(addrs[5]).returns(addrs[4]); + + const mockRewardDelegators = signers[49]; // any random address can be used + const receiverRewardPerEpoch = BN.from(3456); + const receiverBalance = BN.from(100000); + await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), mockRewardDelegators.getAddress()); + await clusterRewards.connect(mockRewardDelegators)._increaseReceiverBalance(signers[4].getAddress(), receiverBalance); + await clusterRewards.connect(mockRewardDelegators)._setReceiverRewardPerEpoch(signers[4].getAddress(), receiverRewardPerEpoch); + + await receiverStaking.mock.getEpochInfo.reverts(); + await receiverStaking.mock.getEpochInfo.withArgs(epochWithRewards).returns(500, epochWithRewards + 2); + await ethSelector.mock.getClusters.returns([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]); + let receiverRewards1 = tickets.map((e) => ETH_REWARD.mul(50).mul(e).div(500).div(MAX_TICKETS)); + let extraRewards1 = tickets.map((e) => receiverRewardPerEpoch.mul(e).div(MAX_TICKETS)); + + await skipToTimestamp(startTime + 34 * 86400); + + await clusterRewards + .connect(signers[5]) + ["issueTickets(bytes32,uint24[],uint16[][])"](ETHHASH, [epochWithRewards], [tickets.slice(0, -1)]); + + expect(await clusterRewards.isTicketsIssued(addrs[4], epochWithRewards)).to.be.true; + expect(await clusterRewards.isTicketsIssued(addrs[5], epochWithRewards)).to.be.false; + expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo(receiverRewards1[0].add(extraRewards1[0]), 1); + expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo(receiverRewards1[1].add(extraRewards1[1]), 1); + expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo(receiverRewards1[2].add(extraRewards1[2]), 1); + expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(extraRewards1[3]), 1); + expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 1); + }); + it("staker cannot submit tickets for future epochs", async function () { const epochWithRewards = (33 * 86400) / 900 + 5; await receiverStaking.mock.balanceOfSignerAt.reverts(); @@ -893,7 +928,7 @@ describe("ClusterRewards submit tickets", function () { it("staker cannot submit excess tickets", async function () { const epochWithRewards = (33 * 86400) / 900 + 2; await receiverStaking.mock.balanceOfSignerAt.reverts(); - + await receiverStaking.mock.signerToStaker.withArgs(addrs[5]).returns(addrs[4]); await receiverStaking.mock.balanceOfAt.withArgs(addrs[4], epochWithRewards).returns(50); @@ -1009,8 +1044,8 @@ describe("ClusterRewards submit compressed tickets", function () { rawTicketInfo = rawTicketInfo + tickets[j][k].toString(16).padStart(4, "0"); } - // check the events arguments emitted - await expect(clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo)).to.emit(clusterRewards, 'TicketsIssued'); + // check the events arguments emitted + await expect(clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo)).to.emit(clusterRewards, "TicketsIssued"); expect(await clusterRewards.isTicketsIssued(addrs[4], 2)).to.be.true; expect(await clusterRewards.isTicketsIssued(addrs[4], 3)).to.be.false; @@ -1146,6 +1181,136 @@ describe("ClusterRewards submit compressed tickets", function () { } }); + it("staker can submit compressed tickets after switch with non zero rewards and extra rewards enabled", async function () { + const epochWithRewards = (33 * 86400) / 900 + 2; + const numberOfEpochs = 10; + let receiverRewards1: BN[] = []; + let extraRewards1: BN[] = []; + + await skipToTimestamp(startTime + 34 * 86400); + + const ticketsByEpoch: number[][] = []; + let startEpoch = epochWithRewards; + let rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, "0"); + let totalTickets = 0; + + const mockRewardDelegators = signers[49]; // any random address can be used + const receiverRewardPerEpoch = BN.from("29872398329842389"); + const receiverBalance = receiverRewardPerEpoch.mul(numberOfEpochs); // use large receiver balance due as there are large number of epochs present + await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), mockRewardDelegators.getAddress()); + await clusterRewards.connect(mockRewardDelegators)._increaseReceiverBalance(signers[4].getAddress(), receiverBalance); + await clusterRewards.connect(mockRewardDelegators)._setReceiverRewardPerEpoch(signers[4].getAddress(), receiverRewardPerEpoch); + + for (let i = 0; i < numberOfEpochs; i++) { + ticketsByEpoch[i] = []; + for (let j = 0; j < ticketsLength; j++) { + ticketsByEpoch[i][j] = parseInt(((Math.random() * 2) ^ (16 / (ticketsLength + 1))) + ""); + totalTickets += ticketsByEpoch[i][j]; + rawTicketInfo = rawTicketInfo + ticketsByEpoch[i][j].toString(16).padStart(4, "0"); + if (!receiverRewards1[j]) receiverRewards1[j] = BN.from(0); + receiverRewards1[j] = receiverRewards1[j].add(ETH_REWARD.mul(50).mul(ticketsByEpoch[i][j]).div(500).div(MAX_TICKETS)); + + if (!extraRewards1[j]) extraRewards1[j] = BN.from(0); + extraRewards1[j] = extraRewards1[j].add(receiverRewardPerEpoch.mul(ticketsByEpoch[i][j]).div(MAX_TICKETS)); + } + if (!receiverRewards1[ticketsLength]) receiverRewards1[ticketsLength] = BN.from(0); + if (!extraRewards1[ticketsLength]) extraRewards1[ticketsLength] = BN.from(0); + receiverRewards1[ticketsLength] = receiverRewards1[ticketsLength].add( + ETH_REWARD.mul(50).div(500).mul(MAX_TICKETS.sub(totalTickets)).div(MAX_TICKETS) + ); + extraRewards1[ticketsLength] = extraRewards1[ticketsLength].add( + receiverRewardPerEpoch.mul(MAX_TICKETS.sub(totalTickets)).div(MAX_TICKETS) + ); + totalTickets = 0; + } + + await receiverStaking.mock.getCurrentEpoch.returns(startEpoch + numberOfEpochs); + await receiverStaking.mock.totalSupplyAtRanged.revertsWithReason("unexpected query for total balance"); + await receiverStaking.mock.totalSupplyAtRanged.withArgs(startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(500)); + await receiverStaking.mock.balanceOfSignerAtRanged.revertsWithReason("unexpected query for signer balance"); + await receiverStaking.mock.balanceOfSignerAtRanged + .withArgs(addrs[5], startEpoch, numberOfEpochs) + .returns(Array(numberOfEpochs).fill(50), addrs[4]); + await ethSelector.mock.getClustersRanged.returns(Array(numberOfEpochs).fill([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]])); + + await clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo); + + for (let i = 0; i < numberOfEpochs; i++) { + expect(await clusterRewards.isTicketsIssued(addrs[4], startEpoch + i)).to.be.true; + expect(await clusterRewards.isTicketsIssued(addrs[5], startEpoch + i)).to.be.false; + expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo(receiverRewards1[0].add(extraRewards1[0]), 5); + expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo(receiverRewards1[1].add(extraRewards1[1]), 5); + expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo(receiverRewards1[2].add(extraRewards1[2]), 5); + expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(extraRewards1[3]), 5); + expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 5); + } + + startEpoch += numberOfEpochs; + + rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, "0"); + totalTickets = 0; + let receiverRewards2: BN[] = []; + let extraRewards2: BN[] = []; + for (let i = 0; i < numberOfEpochs; i++) { + ticketsByEpoch[i] = []; + for (let j = 0; j < ticketsLength; j++) { + ticketsByEpoch[i][j] = parseInt(((Math.random() * 2) ^ (16 / (ticketsLength + 1))) + ""); + totalTickets += ticketsByEpoch[i][j]; + rawTicketInfo = rawTicketInfo + ticketsByEpoch[i][j].toString(16).padStart(4, "0"); + if (!receiverRewards2[j]) receiverRewards2[j] = BN.from(0); + receiverRewards2[j] = receiverRewards2[j].add(ETH_REWARD.mul(25).mul(ticketsByEpoch[i][j]).div(125).div(MAX_TICKETS)); + + if (!extraRewards2[j]) extraRewards2[j] = BN.from(0); + extraRewards2[j] = extraRewards2[j].add(receiverRewardPerEpoch.mul(ticketsByEpoch[i][j]).div(MAX_TICKETS)); + } + if (!receiverRewards2[ticketsLength]) receiverRewards2[ticketsLength] = BN.from(0); + if (!extraRewards2[ticketsLength]) extraRewards2[ticketsLength] = BN.from(0); + receiverRewards2[ticketsLength] = receiverRewards2[ticketsLength].add( + ETH_REWARD.mul(25).div(125).mul(MAX_TICKETS.sub(totalTickets)).div(MAX_TICKETS) + ); + extraRewards2[ticketsLength] = extraRewards2[ticketsLength].add( + receiverRewardPerEpoch.mul(MAX_TICKETS.sub(totalTickets)).div(MAX_TICKETS) + ); + totalTickets = 0; + } + + await receiverStaking.mock.getCurrentEpoch.returns(startEpoch + numberOfEpochs); + await receiverStaking.mock.totalSupplyAtRanged.revertsWithReason("unexpected query for total balance"); + await receiverStaking.mock.totalSupplyAtRanged.withArgs(startEpoch, numberOfEpochs).returns(Array(numberOfEpochs).fill(125)); + await receiverStaking.mock.balanceOfSignerAtRanged.revertsWithReason("unexpected query for signer balance"); + await receiverStaking.mock.balanceOfSignerAtRanged + .withArgs(addrs[5], startEpoch, numberOfEpochs) + .returns(Array(numberOfEpochs).fill(25), addrs[4]); + await ethSelector.mock.getClustersRanged.returns(Array(numberOfEpochs).fill([addrs[35], addrs[34], addrs[33], addrs[32], addrs[31]])); + + await clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo); + + for (let i = 0; i < numberOfEpochs; i++) { + expect(await clusterRewards.isTicketsIssued(addrs[4], startEpoch + i)).to.be.true; + expect(await clusterRewards.isTicketsIssued(addrs[5], startEpoch + i)).to.be.false; + expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo( + receiverRewards1[0].add(receiverRewards2[4]).add(extraRewards1[0]).add(extraRewards2[4]), + 11 + ); + expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo( + receiverRewards1[1].add(receiverRewards2[3]).add(extraRewards1[1]).add(extraRewards2[3]), + 11 + ); + expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo( + receiverRewards1[2].add(receiverRewards2[2]).add(extraRewards1[2]).add(extraRewards2[2]), + 11 + ); + expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo( + receiverRewards1[3].add(receiverRewards2[1]).add(extraRewards1[3]).add(extraRewards2[1]), + 11 + ); + expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo( + receiverRewards1[4].add(receiverRewards2[0]).add(extraRewards1[4]).add(extraRewards2[0]), + 11 + ); + } + }); + it("staker can submit compressed tickets if number of clusters are less than clusters to select", async function () { const epochWithRewards = (33 * 86400) / 900 + 2; const numberOfEpochs = 10; @@ -1667,7 +1832,7 @@ describe("ClusterRewards: Add Receiver extra payment", function () { it("Reward Checking after receiver adds extra tokens", async () => { const mockRewardDelegators = signers[49]; // any random address can be used const receiverRewardPerEpoch = BN.from(3000); - const receiverBalance = BN.from(100000) + const receiverBalance = BN.from(100000); await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), mockRewardDelegators.getAddress()); // balance can be added by any address, hence we are using staker address directly @@ -1686,7 +1851,7 @@ describe("ClusterRewards: Add Receiver extra payment", function () { await receiverStaking.mock.balanceOfSignerAt .withArgs(await signer.getAddress(), epochWithRewards) .returns(50, await staker.getAddress()); - + // is this 500 inflation rewards await receiverStaking.mock.getEpochInfo.withArgs(epochWithRewards).returns(500, epochWithRewards + 3); @@ -1699,4 +1864,4 @@ describe("ClusterRewards: Add Receiver extra payment", function () { expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(extraRewards1[3]), 1); expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 1); }); -}); \ No newline at end of file +}); From fdb185fdd1d3ef39366ea6b0b347e1c410d4720d Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Mon, 12 Jun 2023 11:40:23 +0400 Subject: [PATCH 14/30] Combine rewards and rewardsPerEpoch into a struct --- contracts/staking/ClusterRewards.sol | 63 +++++++------------ contracts/staking/RewardDelegators.sol | 5 +- .../staking/interfaces/IClusterRewards.sol | 8 ++- test/staking/ClusterRewards.ts | 6 +- 4 files changed, 35 insertions(+), 47 deletions(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index ebf759e6..ffaabf7f 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -8,6 +8,8 @@ import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeabl import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol"; import "./interfaces/IClusterSelector.sol"; import "./ReceiverStaking.sol"; @@ -24,7 +26,8 @@ contract ClusterRewards is ERC1967UpgradeUpgradeable, // delegate slots, proxy admin, private upgrade UUPSUpgradeable, // public upgrade IClusterRewards // interface -{ +{ + using SafeCastUpgradeable for uint256; // in case we add more contracts in the inheritance chain uint256[500] private __gap0; @@ -308,8 +311,7 @@ contract ClusterRewards is address _receiver = receiverStaking.signerToStaker(msg.sender); - uint256 _receiverExtraRewardsRemaining = receiverBalance[_receiver]; - uint256 _receiverExtraRewardsPerEpoch = receiverRewardPerEpoch[_receiver]; + ReceiverPayment memory receiverPayment = receiverRewardPayment[_receiver]; unchecked { for (uint256 i = 0; i < _epochs.length; ++i) { @@ -321,17 +323,14 @@ contract ClusterRewards is uint256 _epochReceiverStake = receiverStaking.balanceOfAt(_receiver, _epochs[i]); uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake) + - _extraReceiverRewardThisEpoch(_receiverExtraRewardsRemaining, _receiverExtraRewardsPerEpoch); + MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); _processReceiverTickets(_receiver, _epochs[i], _selectedClusters, _tickets[i], _rewardShare); - _receiverExtraRewardsRemaining -= _extraReceiverRewardThisEpoch( - _receiverExtraRewardsRemaining, - _receiverExtraRewardsPerEpoch - ); + receiverPayment.rewardRemaining -= MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch).toUint128(); _emitTicketsIssued(_networkId, _epochs[i], msg.sender); } } - _setReceiverBalance(_receiver, _receiverExtraRewardsRemaining); + _setReceiverBalance(_receiver, receiverPayment.rewardRemaining); } function issueTickets(bytes calldata _ticketInfo) external { @@ -344,44 +343,31 @@ contract ClusterRewards is (uint256[] memory _balances, address _receiver) = _receiverStaking.balanceOfSignerAtRanged(msg.sender, _fromEpoch, _noOfEpochs); address[][] memory _selectedClusters = clusterSelectors[_networkId].getClustersRanged(_fromEpoch, _noOfEpochs); - uint256 _receiverExtraRewardsRemaining = receiverBalance[_receiver]; - uint256 _receiverExtraRewardsPerEpoch = receiverRewardPerEpoch[_receiver]; + ReceiverPayment memory receiverPayment = receiverRewardPayment[_receiver]; uint256 _totalNetworkRewardsPerEpoch; unchecked { for (uint256 i = 0; i < _noOfEpochs; ++i) { // _totalNetworkRewardsPerEpoch = inflation rewards + receiver rewards _totalNetworkRewardsPerEpoch = getRewardForEpoch(_fromEpoch, _networkId); - uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _stakes[i], _balances[i]) + - _extraReceiverRewardThisEpoch(_receiverExtraRewardsRemaining, _receiverExtraRewardsPerEpoch); + uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _stakes[i], _balances[i]) + MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); _processReceiverTickets(_receiver, _fromEpoch, _selectedClusters[i], _tickets[i], _rewardShare); _emitTicketsIssued(_networkId, _fromEpoch, msg.sender); - _receiverExtraRewardsRemaining -= _extraReceiverRewardThisEpoch( - _receiverExtraRewardsRemaining, - _receiverExtraRewardsPerEpoch - ); + receiverPayment.rewardRemaining -= MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch).toUint128(); ++_fromEpoch; } } - _setReceiverBalance(_receiver, _receiverExtraRewardsRemaining); + _setReceiverBalance(_receiver, receiverPayment.rewardRemaining); } - function _setReceiverBalance(address _receiver, uint256 _receiverExtraRewardsRemaining) internal { + function _setReceiverBalance(address _receiver, uint128 _receiverExtraRewardsRemaining) internal { if (_receiverExtraRewardsRemaining != 0) { - receiverBalance[_receiver] = _receiverExtraRewardsRemaining; + receiverRewardPayment[_receiver].rewardRemaining = _receiverExtraRewardsRemaining; } } - function _extraReceiverRewardThisEpoch( - uint256 _receiverExtraRewardsRemaining, - uint256 _receiverExtraRewardsPerEpoch - ) internal pure returns (uint256) { - return - _receiverExtraRewardsRemaining > _receiverExtraRewardsPerEpoch ? _receiverExtraRewardsPerEpoch : _receiverExtraRewardsRemaining; - } - function _emitTicketsIssued(bytes32 _networkId, uint256 _epoch, address signer) internal { emit TicketsIssued(_networkId, _epoch, signer); } @@ -455,19 +441,17 @@ contract ClusterRewards is require(_epoch < _currentEpoch, "CRW:IT-Epoch not completed"); (uint256 _epochReceiverStake, address _receiver) = receiverStaking.balanceOfSignerAt(msg.sender, _epoch); - uint256 _receiverExtraRewardsRemaining = receiverBalance[_receiver]; - uint256 _receiverExtraRewardsPerEpoch = receiverRewardPerEpoch[_receiver]; + ReceiverPayment memory receiverPayment = receiverRewardPayment[_receiver]; address[] memory _selectedClusters = clusterSelectors[_networkId].getClusters(_epoch); uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_epoch, _networkId); - uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake) + - _extraReceiverRewardThisEpoch(_receiverExtraRewardsRemaining, _receiverExtraRewardsPerEpoch); + uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake) + MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); _processReceiverTickets(_receiver, _epoch, _selectedClusters, _tickets, _rewardShare); _emitTicketsIssued(_networkId, _epoch, msg.sender); - _receiverExtraRewardsRemaining -= _extraReceiverRewardThisEpoch(_receiverExtraRewardsRemaining, _receiverExtraRewardsPerEpoch); - _setReceiverBalance(_receiver, _receiverExtraRewardsRemaining); + receiverPayment.rewardRemaining -= MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch).toUint128(); + _setReceiverBalance(_receiver, receiverPayment.rewardRemaining); } function claimReward(address _cluster) external onlyRole(CLAIMER_ROLE) returns (uint256) { @@ -493,15 +477,14 @@ contract ClusterRewards is bytes32 public constant RECEIVER_PAYMENTS_MANAGER = keccak256("RECEIVER_PAYMENTS_MANAGER"); - mapping(address => uint256) public receiverBalance; - mapping(address => uint256) public receiverRewardPerEpoch; + mapping(address => ReceiverPayment) public receiverRewardPayment; - function _increaseReceiverBalance(address staker, uint256 amount) external override onlyRole(RECEIVER_PAYMENTS_MANAGER) { - receiverBalance[staker] += amount; + function _increaseReceiverBalance(address staker, uint128 amount) external override onlyRole(RECEIVER_PAYMENTS_MANAGER) { + receiverRewardPayment[staker].rewardRemaining += amount; } - function _setReceiverRewardPerEpoch(address staker, uint256 rewardPerEpoch) external override onlyRole(RECEIVER_PAYMENTS_MANAGER) { + function _setReceiverRewardPerEpoch(address staker, uint128 rewardPerEpoch) external override onlyRole(RECEIVER_PAYMENTS_MANAGER) { require(staker != address(0), "CRW: address 0"); - receiverRewardPerEpoch[staker] = rewardPerEpoch; + receiverRewardPayment[staker].rewardPerEpoch = rewardPerEpoch; } } diff --git a/contracts/staking/RewardDelegators.sol b/contracts/staking/RewardDelegators.sol index 37ae1524..43a453e7 100755 --- a/contracts/staking/RewardDelegators.sol +++ b/contracts/staking/RewardDelegators.sol @@ -10,6 +10,7 @@ import "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgrad import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol"; + import "./interfaces/IClusterRewards.sol"; import "./interfaces/IClusterRegistry.sol"; import "./interfaces/IRewardDelegators.sol"; @@ -530,7 +531,7 @@ contract RewardDelegators is event AddReceiverBalance(address indexed receiver, uint256 amount); event UpdateReceiverRewardPerEpoch(address indexed receiver, uint256 amount); - function addReceiverBalance(address receiver, uint256 amount) public { + function addReceiverBalance(address receiver, uint128 amount) public { require(receiver != address(0), "RD: address 0"); require(amount != 0, "RD: amount 0"); PONDToken.transferFrom(msg.sender, address(this), amount); @@ -539,7 +540,7 @@ contract RewardDelegators is } // msg.sender is staker here - function setReceiverRewardPerEpoch(uint256 rewardPerEpoch) public { + function setReceiverRewardPerEpoch(uint128 rewardPerEpoch) public { require(rewardPerEpoch != 0, "RD: reward 0"); address _sender = _msgSender(); clusterRewards._setReceiverRewardPerEpoch(_sender, rewardPerEpoch); diff --git a/contracts/staking/interfaces/IClusterRewards.sol b/contracts/staking/interfaces/IClusterRewards.sol index 771dc460..eafdfa0a 100644 --- a/contracts/staking/interfaces/IClusterRewards.sol +++ b/contracts/staking/interfaces/IClusterRewards.sol @@ -5,6 +5,10 @@ pragma solidity ^0.8.0; import "./IClusterSelector.sol"; interface IClusterRewards { + struct ReceiverPayment { + uint128 rewardRemaining; + uint128 rewardPerEpoch; + } function clusterSelectors(bytes32 networkId) external returns (IClusterSelector); function clusterRewards(address cluster) external returns(uint256); function rewardWeight(bytes32 networkId) external returns(uint256); @@ -15,6 +19,6 @@ interface IClusterRewards { function getRewardForEpoch(uint256 epoch, bytes32 networkId) external view returns(uint256); function claimReward(address cluster) external returns(uint256); function changeRewardPerEpoch(uint256 updatedRewardPerEpoch) external; - function _increaseReceiverBalance(address receiver, uint256 amount) external; - function _setReceiverRewardPerEpoch(address signer, uint256 rewardPerEpoch) external; + function _increaseReceiverBalance(address receiver, uint128 amount) external; + function _setReceiverRewardPerEpoch(address signer, uint128 rewardPerEpoch) external; } diff --git a/test/staking/ClusterRewards.ts b/test/staking/ClusterRewards.ts index 917aff0a..3b3359e4 100644 --- a/test/staking/ClusterRewards.ts +++ b/test/staking/ClusterRewards.ts @@ -1814,9 +1814,9 @@ describe("ClusterRewards: Add Receiver extra payment", function () { const increaseByAmount = 100; await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), signer.getAddress()); - const balanceBefore = await clusterRewards.receiverBalance(signer.getAddress()); + const balanceBefore = (await clusterRewards.receiverRewardPayment(signer.getAddress()))[0]; await clusterRewards.connect(signer)._increaseReceiverBalance(signer.getAddress(), increaseByAmount); - const balanceAfter = await clusterRewards.receiverBalance(signer.getAddress()); + const balanceAfter = (await clusterRewards.receiverRewardPayment(signer.getAddress()))[0]; expect(balanceAfter).eq(balanceBefore.add(increaseByAmount)); }); @@ -1826,7 +1826,7 @@ describe("ClusterRewards: Add Receiver extra payment", function () { await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), signer.getAddress()); await clusterRewards.connect(signer)._setReceiverRewardPerEpoch(staker.getAddress(), receiverExtraRewardPerEpoch); - expect(await clusterRewards.receiverRewardPerEpoch(staker.getAddress())).eq(receiverExtraRewardPerEpoch); + expect((await clusterRewards.receiverRewardPayment(staker.getAddress()))[1]).eq(receiverExtraRewardPerEpoch); }); it("Reward Checking after receiver adds extra tokens", async () => { From ffaefd8dbf632ea366967a242e0aca43bcfbe49d Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Mon, 12 Jun 2023 21:56:32 +0400 Subject: [PATCH 15/30] update --- contracts/staking/ClusterRewards.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index ffaabf7f..9e6f96d1 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -363,9 +363,7 @@ contract ClusterRewards is } function _setReceiverBalance(address _receiver, uint128 _receiverExtraRewardsRemaining) internal { - if (_receiverExtraRewardsRemaining != 0) { receiverRewardPayment[_receiver].rewardRemaining = _receiverExtraRewardsRemaining; - } } function _emitTicketsIssued(bytes32 _networkId, uint256 _epoch, address signer) internal { From adb9cc59521e9022f157bd78a4f6ac1a019dff0b Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Tue, 13 Jun 2023 12:21:30 +0400 Subject: [PATCH 16/30] lint cluster-rewards --- contracts/staking/ClusterRewards.sol | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 9e6f96d1..92781b29 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -26,7 +26,7 @@ contract ClusterRewards is ERC1967UpgradeUpgradeable, // delegate slots, proxy admin, private upgrade UUPSUpgradeable, // public upgrade IClusterRewards // interface -{ +{ using SafeCastUpgradeable for uint256; // in case we add more contracts in the inheritance chain uint256[500] private __gap0; @@ -326,7 +326,9 @@ contract ClusterRewards is MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); _processReceiverTickets(_receiver, _epochs[i], _selectedClusters, _tickets[i], _rewardShare); - receiverPayment.rewardRemaining -= MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch).toUint128(); + receiverPayment.rewardRemaining -= MathUpgradeable + .min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch) + .toUint128(); _emitTicketsIssued(_networkId, _epochs[i], msg.sender); } } @@ -350,11 +352,14 @@ contract ClusterRewards is for (uint256 i = 0; i < _noOfEpochs; ++i) { // _totalNetworkRewardsPerEpoch = inflation rewards + receiver rewards _totalNetworkRewardsPerEpoch = getRewardForEpoch(_fromEpoch, _networkId); - uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _stakes[i], _balances[i]) + MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); + uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _stakes[i], _balances[i]) + + MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); _processReceiverTickets(_receiver, _fromEpoch, _selectedClusters[i], _tickets[i], _rewardShare); _emitTicketsIssued(_networkId, _fromEpoch, msg.sender); - receiverPayment.rewardRemaining -= MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch).toUint128(); + receiverPayment.rewardRemaining -= MathUpgradeable + .min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch) + .toUint128(); ++_fromEpoch; } } @@ -363,7 +368,7 @@ contract ClusterRewards is } function _setReceiverBalance(address _receiver, uint128 _receiverExtraRewardsRemaining) internal { - receiverRewardPayment[_receiver].rewardRemaining = _receiverExtraRewardsRemaining; + receiverRewardPayment[_receiver].rewardRemaining = _receiverExtraRewardsRemaining; } function _emitTicketsIssued(bytes32 _networkId, uint256 _epoch, address signer) internal { @@ -444,7 +449,8 @@ contract ClusterRewards is address[] memory _selectedClusters = clusterSelectors[_networkId].getClusters(_epoch); uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_epoch, _networkId); - uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake) + MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); + uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake) + + MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); _processReceiverTickets(_receiver, _epoch, _selectedClusters, _tickets, _rewardShare); _emitTicketsIssued(_networkId, _epoch, msg.sender); From a0b2d4e508af2bc3b32e6a9c4fa146e3919d0fe1 Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Wed, 14 Jun 2023 10:56:12 +0400 Subject: [PATCH 17/30] update --- contracts/staking/ClusterRewards.sol | 10 +++------- test/staking/ClusterRewards.ts | 6 +++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 92781b29..171f7566 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -332,7 +332,7 @@ contract ClusterRewards is _emitTicketsIssued(_networkId, _epochs[i], msg.sender); } } - _setReceiverBalance(_receiver, receiverPayment.rewardRemaining); + receiverRewardPayment[_receiver].rewardRemaining = receiverPayment.rewardRemaining; } function issueTickets(bytes calldata _ticketInfo) external { @@ -364,11 +364,7 @@ contract ClusterRewards is } } - _setReceiverBalance(_receiver, receiverPayment.rewardRemaining); - } - - function _setReceiverBalance(address _receiver, uint128 _receiverExtraRewardsRemaining) internal { - receiverRewardPayment[_receiver].rewardRemaining = _receiverExtraRewardsRemaining; + receiverRewardPayment[_receiver].rewardRemaining = receiverPayment.rewardRemaining; } function _emitTicketsIssued(bytes32 _networkId, uint256 _epoch, address signer) internal { @@ -455,7 +451,7 @@ contract ClusterRewards is _emitTicketsIssued(_networkId, _epoch, msg.sender); receiverPayment.rewardRemaining -= MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch).toUint128(); - _setReceiverBalance(_receiver, receiverPayment.rewardRemaining); + receiverRewardPayment[_receiver].rewardRemaining = receiverPayment.rewardRemaining; } function claimReward(address _cluster) external onlyRole(CLAIMER_ROLE) returns (uint256) { diff --git a/test/staking/ClusterRewards.ts b/test/staking/ClusterRewards.ts index 3b3359e4..823dd046 100644 --- a/test/staking/ClusterRewards.ts +++ b/test/staking/ClusterRewards.ts @@ -1195,8 +1195,8 @@ describe("ClusterRewards submit compressed tickets", function () { let totalTickets = 0; const mockRewardDelegators = signers[49]; // any random address can be used - const receiverRewardPerEpoch = BN.from("29872398329842389"); - const receiverBalance = receiverRewardPerEpoch.mul(numberOfEpochs); // use large receiver balance due as there are large number of epochs present + const receiverRewardPerEpoch = BN.from("12100"); + const receiverBalance = receiverRewardPerEpoch.mul(numberOfEpochs).mul(2); // use large receiver balance due as there are large number of epochs present await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), mockRewardDelegators.getAddress()); await clusterRewards.connect(mockRewardDelegators)._increaseReceiverBalance(signers[4].getAddress(), receiverBalance); await clusterRewards.connect(mockRewardDelegators)._setReceiverRewardPerEpoch(signers[4].getAddress(), receiverRewardPerEpoch); @@ -1242,7 +1242,7 @@ describe("ClusterRewards submit compressed tickets", function () { expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo(receiverRewards1[1].add(extraRewards1[1]), 5); expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo(receiverRewards1[2].add(extraRewards1[2]), 5); expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(extraRewards1[3]), 5); - expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 5); + expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 8); } startEpoch += numberOfEpochs; From 5b88725e2821fdf7a9432936dd98aa9396984ae8 Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Wed, 14 Jun 2023 13:24:29 +0400 Subject: [PATCH 18/30] freeze tests --- test/staking/ClusterRewards.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/staking/ClusterRewards.ts b/test/staking/ClusterRewards.ts index 823dd046..e37b7fe1 100644 --- a/test/staking/ClusterRewards.ts +++ b/test/staking/ClusterRewards.ts @@ -1195,7 +1195,7 @@ describe("ClusterRewards submit compressed tickets", function () { let totalTickets = 0; const mockRewardDelegators = signers[49]; // any random address can be used - const receiverRewardPerEpoch = BN.from("12100"); + const receiverRewardPerEpoch = BN.from("121002872328783237837823"); const receiverBalance = receiverRewardPerEpoch.mul(numberOfEpochs).mul(2); // use large receiver balance due as there are large number of epochs present await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), mockRewardDelegators.getAddress()); await clusterRewards.connect(mockRewardDelegators)._increaseReceiverBalance(signers[4].getAddress(), receiverBalance); @@ -1238,11 +1238,11 @@ describe("ClusterRewards submit compressed tickets", function () { for (let i = 0; i < numberOfEpochs; i++) { expect(await clusterRewards.isTicketsIssued(addrs[4], startEpoch + i)).to.be.true; expect(await clusterRewards.isTicketsIssued(addrs[5], startEpoch + i)).to.be.false; - expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo(receiverRewards1[0].add(extraRewards1[0]), 5); - expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo(receiverRewards1[1].add(extraRewards1[1]), 5); - expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo(receiverRewards1[2].add(extraRewards1[2]), 5); - expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(extraRewards1[3]), 5); - expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 8); + expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo(receiverRewards1[0].add(extraRewards1[0]), 10); + expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo(receiverRewards1[1].add(extraRewards1[1]), 10); + expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo(receiverRewards1[2].add(extraRewards1[2]), 10); + expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(extraRewards1[3]), 10); + expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 10); } startEpoch += numberOfEpochs; From dc438a70c142ad71ddef87b6c158d5a7c814eeda Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Wed, 14 Jun 2023 14:30:48 +0400 Subject: [PATCH 19/30] update --- contracts/staking/ClusterRewards.sol | 49 +--- .../staking/interfaces/IClusterRewards.sol | 2 +- test/staking/ClusterRewards.ts | 224 ++---------------- test/staking/RewardDelegators.ts | 4 +- 4 files changed, 30 insertions(+), 249 deletions(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 171f7566..512ec784 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -145,11 +145,6 @@ contract ClusterRewards is event RewardDistributionWaitTimeChanged(uint256 updatedWaitTime); event TicketsIssued(bytes32 indexed networkId, uint256 indexed epoch, address indexed user); - modifier onlyFeeder() { - require(hasRole(FEEDER_ROLE, _msgSender()), "only feeder"); - _; - } - function addNetwork(bytes32 _networkId, uint256 _rewardWeight, address _clusterSelector) external onlyAdmin { require(rewardWeight[_networkId] == 0, "CRW:AN-Network already exists"); require(_clusterSelector != address(0), "CRW:AN-ClusterSelector must exist"); @@ -220,32 +215,6 @@ contract ClusterRewards is //-------------------------------- Admin functions end --------------------------------// - //-------------------------------- User functions start --------------------------------// - - function feed(bytes32 _networkId, address[] calldata _clusters, uint256[] calldata _payouts, uint256 _epoch) external onlyFeeder { - require(receiverStaking.START_TIME() + SWITCHING_PERIOD + 1 days > block.timestamp, "CRW:F-Invalid method"); - uint256 rewardDistributed = rewardDistributedPerEpoch[_epoch]; - if (rewardDistributed == 0) { - require( - block.timestamp > latestNewEpochRewardAt + rewardDistributionWaitTime, - "CRW:F-Cant distribute reward for new epoch within such short interval" - ); - latestNewEpochRewardAt = block.timestamp; - } - uint256 currentPayoutDenomination = payoutDenomination; - uint256 networkRewardWeight = rewardWeight[_networkId]; - uint256 currentTotalRewardsPerEpoch = (((totalRewardsPerEpoch * 1 days) / receiverStaking.EPOCH_LENGTH()) * networkRewardWeight) / - totalRewardWeight; - for (uint256 i = 0; i < _clusters.length; i++) { - uint256 clusterReward = (currentTotalRewardsPerEpoch * _payouts[i]) / currentPayoutDenomination; - rewardDistributed = rewardDistributed + clusterReward; - clusterRewards[_clusters[i]] = clusterRewards[_clusters[i]] + clusterReward; - } - require(rewardDistributed <= currentTotalRewardsPerEpoch, "CRW:F-Reward Distributed cant be more than totalRewardPerEpoch"); - rewardDistributedPerEpoch[_epoch] = rewardDistributed; - emit ClusterRewarded(_networkId); - } - function _getRewardShare( uint256 _totalNetworkRewardsPerEpoch, uint256 _epochTotalStake, @@ -312,10 +281,10 @@ contract ClusterRewards is address _receiver = receiverStaking.signerToStaker(msg.sender); ReceiverPayment memory receiverPayment = receiverRewardPayment[_receiver]; + uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_networkId); unchecked { for (uint256 i = 0; i < _epochs.length; ++i) { - uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_epochs[i], _networkId); (uint256 _epochTotalStake, uint256 _currentEpoch) = receiverStaking.getEpochInfo(_epochs[i]); require(_epochs[i] < _currentEpoch, "CRW:IT-Epoch not completed"); @@ -346,12 +315,11 @@ contract ClusterRewards is address[][] memory _selectedClusters = clusterSelectors[_networkId].getClustersRanged(_fromEpoch, _noOfEpochs); ReceiverPayment memory receiverPayment = receiverRewardPayment[_receiver]; - uint256 _totalNetworkRewardsPerEpoch; + // _totalNetworkRewardsPerEpoch = inflation rewards + receiver rewards + uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_networkId); unchecked { for (uint256 i = 0; i < _noOfEpochs; ++i) { - // _totalNetworkRewardsPerEpoch = inflation rewards + receiver rewards - _totalNetworkRewardsPerEpoch = getRewardForEpoch(_fromEpoch, _networkId); uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _stakes[i], _balances[i]) + MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); @@ -443,7 +411,7 @@ contract ClusterRewards is ReceiverPayment memory receiverPayment = receiverRewardPayment[_receiver]; address[] memory _selectedClusters = clusterSelectors[_networkId].getClusters(_epoch); - uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_epoch, _networkId); + uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_networkId); uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake) + MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); @@ -464,13 +432,8 @@ contract ClusterRewards is return 0; } - function _getRewardForEpoch(uint256 _epoch, bytes32 _networkId, uint256 _epochLength) internal view returns (uint256) { - if (_epoch < SWITCHING_PERIOD / _epochLength) return 0; - return (totalRewardsPerEpoch * rewardWeight[_networkId]) / totalRewardWeight; - } - - function getRewardForEpoch(uint256 _epoch, bytes32 _networkId) public view returns (uint256) { - return _getRewardForEpoch(_epoch, _networkId, receiverStaking.EPOCH_LENGTH()); + function getRewardForEpoch(bytes32 _networkId) public view returns (uint256) { + return (totalRewardsPerEpoch * rewardWeight[_networkId]) / receiverStaking.EPOCH_LENGTH(); } //-------------------------------- User functions end --------------------------------// diff --git a/contracts/staking/interfaces/IClusterRewards.sol b/contracts/staking/interfaces/IClusterRewards.sol index eafdfa0a..c8548693 100644 --- a/contracts/staking/interfaces/IClusterRewards.sol +++ b/contracts/staking/interfaces/IClusterRewards.sol @@ -16,7 +16,7 @@ interface IClusterRewards { function addNetwork(bytes32 networkId, uint256 rewardWeight, address clusterSelector) external; function removeNetwork(bytes32 networkId) external; function updateNetwork(bytes32 networkId, uint256 updatedRewardWeight, address updatedClusterSelector) external; - function getRewardForEpoch(uint256 epoch, bytes32 networkId) external view returns(uint256); + function getRewardForEpoch(bytes32 networkId) external view returns(uint256); function claimReward(address cluster) external returns(uint256); function changeRewardPerEpoch(uint256 updatedRewardPerEpoch) external; function _increaseReceiverBalance(address receiver, uint128 amount) external; diff --git a/test/staking/ClusterRewards.ts b/test/staking/ClusterRewards.ts index e37b7fe1..c13a66d0 100644 --- a/test/staking/ClusterRewards.ts +++ b/test/staking/ClusterRewards.ts @@ -57,7 +57,7 @@ const tickets = [ ), ]; -describe("ClusterRewards deploy and init", function () { +describe.only("ClusterRewards deploy and init", function () { let signers: Signer[]; let addrs: string[]; @@ -214,7 +214,7 @@ testRole( let startTime = Math.floor(Date.now() / 1000) + 100000; -describe("ClusterRewards add network", function () { +describe.only("ClusterRewards add network", function () { let signers: Signer[]; let addrs: string[]; let receiverStaking: Contract; @@ -317,7 +317,7 @@ describe("ClusterRewards add network", function () { }); }); -describe("ClusterRewards cluster selector", function () { +describe.only("ClusterRewards cluster selector", function () { let signers: Signer[]; let addrs: string[]; let receiverStaking: Contract; @@ -438,7 +438,7 @@ describe("ClusterRewards cluster selector", function () { }); }); -describe("ClusterRewards remove network", function () { +describe.only("ClusterRewards remove network", function () { let signers: Signer[]; let addrs: string[]; let receiverStaking: Contract; @@ -480,7 +480,7 @@ describe("ClusterRewards remove network", function () { }); }); -describe("ClusterRewards update global vars", function () { +describe.only("ClusterRewards update global vars", function () { let signers: Signer[]; let addrs: string[]; let receiverStaking: Contract; @@ -534,162 +534,7 @@ describe("ClusterRewards update global vars", function () { }); }); -describe("ClusterRewards feed rewards", function () { - let signers: Signer[]; - let addrs: string[]; - let receiverStaking: Contract; - let clusterRewards: ClusterRewards; - const FEED_REWARD = BN.from("200000").mul(e18); - const FEEDER_REWARD_1_pc = FEED_REWARD.mul(ETHWEIGHT) - .div(TOTALWEIGHT) - .div(100) - .mul(24 * 60 * 60) - .div(900); - - before(async function () { - signers = await ethers.getSigners(); - addrs = await Promise.all(signers.map((a) => a.getAddress())); - - const ReceiverStaking = await ethers.getContractFactory("ReceiverStaking"); - let receiverStaking = await deployMockContract(signers[0], ReceiverStaking.interface.format()); - await receiverStaking.mock.START_TIME.returns(startTime); - await receiverStaking.mock.EPOCH_LENGTH.returns(900); - - const ClusterRewards = await ethers.getContractFactory("ClusterRewards"); - let clusterRewardsContract = await upgrades.deployProxy( - ClusterRewards, - [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], FEED_REWARD], - { kind: "uups", constructorArgs: [] } - ); - clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); - - await clusterRewards.grantRole(clusterRewards.FEEDER_ROLE(), addrs[2]); - await clusterRewards.updateRewardWaitTime(43200); - }); - - takeSnapshotBeforeAndAfterEveryTest(async () => {}); - - it("feeder can feed rewards before start time", async function () { - await clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(50)], 1); - - expect(await clusterRewards.clusterRewards(addrs[21])).to.equal(FEEDER_REWARD_1_pc.mul(10)); - expect(await clusterRewards.clusterRewards(addrs[22])).to.equal(FEEDER_REWARD_1_pc.mul(50)); - expect(await clusterRewards.rewardDistributedPerEpoch(1)).to.equal(FEEDER_REWARD_1_pc.mul(60)); - }); - - it("feeder can feed rewards after start time", async function () { - await skipToTimestamp(startTime + 10); - - await clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(50)], 1); - - expect(await clusterRewards.clusterRewards(addrs[21])).to.equal(FEEDER_REWARD_1_pc.mul(10)); - expect(await clusterRewards.clusterRewards(addrs[22])).to.equal(FEEDER_REWARD_1_pc.mul(50)); - expect(await clusterRewards.rewardDistributedPerEpoch(1)).to.equal(FEEDER_REWARD_1_pc.mul(60)); - }); - - it("feeder can feed rewards till 1 day after switching time", async function () { - await skipToTimestamp(startTime + 33 * 86400 + 85000); - - await clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(50)], 1); - - expect(await clusterRewards.clusterRewards(addrs[21])).to.equal(FEEDER_REWARD_1_pc.mul(10)); - expect(await clusterRewards.clusterRewards(addrs[22])).to.equal(FEEDER_REWARD_1_pc.mul(50)); - expect(await clusterRewards.rewardDistributedPerEpoch(1)).to.equal(FEEDER_REWARD_1_pc.mul(60)); - }); - - it("feeder cannot feed rewards after 1 day after switching time", async function () { - await skipToTimestamp(startTime + 33 * 86400 + 90000); - - await expect( - clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(50)], 1) - ).to.be.revertedWith("CRW:F-Invalid method"); - }); - - it("feeder cannot feed rewards exceeding total", async function () { - await expect( - clusterRewards - .connect(signers[2]) - .feed(ETHHASH, [addrs[21], addrs[22], addrs[23]], [e16.mul(10), e16.mul(50), e16.mul(40).add(e16.div(100))], 1) - ).to.be.revertedWith("CRW:F-Reward Distributed cant be more than totalRewardPerEpoch"); - }); - - it("feeder can feed rewards in multiple parts", async function () { - await clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21]], [e16.mul(10)], 1); - - expect(await clusterRewards.clusterRewards(addrs[21])).to.equal(FEEDER_REWARD_1_pc.mul(10)); - expect(await clusterRewards.clusterRewards(addrs[22])).to.equal(0); - expect(await clusterRewards.rewardDistributedPerEpoch(1)).to.equal(FEEDER_REWARD_1_pc.mul(10)); - - await clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[22]], [e16.mul(50)], 1); - - expect(await clusterRewards.clusterRewards(addrs[21])).to.equal(FEEDER_REWARD_1_pc.mul(10)); - expect(await clusterRewards.clusterRewards(addrs[22])).to.equal(FEEDER_REWARD_1_pc.mul(50)); - expect(await clusterRewards.rewardDistributedPerEpoch(1)).to.equal(FEEDER_REWARD_1_pc.mul(60)); - }); - - it("feeder cannot feed rewards in multiple parts exceeding total", async function () { - await clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21]], [e16.mul(10)], 1); - - expect(await clusterRewards.clusterRewards(addrs[21])).to.equal(FEEDER_REWARD_1_pc.mul(10)); - expect(await clusterRewards.clusterRewards(addrs[22])).to.equal(0); - expect(await clusterRewards.rewardDistributedPerEpoch(1)).to.equal(FEEDER_REWARD_1_pc.mul(10)); - - await clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[22]], [e16.mul(50)], 1); - - expect(await clusterRewards.clusterRewards(addrs[21])).to.equal(FEEDER_REWARD_1_pc.mul(10)); - expect(await clusterRewards.clusterRewards(addrs[22])).to.equal(FEEDER_REWARD_1_pc.mul(50)); - expect(await clusterRewards.rewardDistributedPerEpoch(1)).to.equal(FEEDER_REWARD_1_pc.mul(60)); - - await expect(clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[23]], [e16.mul(41)], 1)).to.be.revertedWith( - "CRW:F-Reward Distributed cant be more than totalRewardPerEpoch" - ); - }); - - it("feeder cannot feed rewards again before wait time", async function () { - await clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(50)], 1); - - expect(await clusterRewards.clusterRewards(addrs[21])).to.equal(FEEDER_REWARD_1_pc.mul(10)); - expect(await clusterRewards.clusterRewards(addrs[22])).to.equal(FEEDER_REWARD_1_pc.mul(50)); - expect(await clusterRewards.rewardDistributedPerEpoch(1)).to.equal(FEEDER_REWARD_1_pc.mul(60)); - - await expect( - clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(10)], 2) - ).to.be.revertedWith("CRW:F-Cant distribute reward for new epoch within such short interval"); - await skipTime(40000); - await expect( - clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(10)], 2) - ).to.be.revertedWith("CRW:F-Cant distribute reward for new epoch within such short interval"); - }); - - it("feeder can feed rewards again after wait time", async function () { - await clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(50)], 1); - - expect(await clusterRewards.clusterRewards(addrs[21])).to.equal(FEEDER_REWARD_1_pc.mul(10)); - expect(await clusterRewards.clusterRewards(addrs[22])).to.equal(FEEDER_REWARD_1_pc.mul(50)); - expect(await clusterRewards.rewardDistributedPerEpoch(1)).to.equal(FEEDER_REWARD_1_pc.mul(60)); - - await expect( - clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(10)], 2) - ).to.be.revertedWith("CRW:F-Cant distribute reward for new epoch within such short interval"); - await skipTime(40000); - await expect( - clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(10)], 2) - ).to.be.revertedWith("CRW:F-Cant distribute reward for new epoch within such short interval"); - await skipTime(5000); - await clusterRewards.connect(signers[2]).feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(10)], 2); - - expect(await clusterRewards.clusterRewards(addrs[21])).to.equal(FEEDER_REWARD_1_pc.mul(20)); - expect(await clusterRewards.clusterRewards(addrs[22])).to.equal(FEEDER_REWARD_1_pc.mul(60)); - expect(await clusterRewards.rewardDistributedPerEpoch(1)).to.equal(FEEDER_REWARD_1_pc.mul(60)); - expect(await clusterRewards.rewardDistributedPerEpoch(2)).to.equal(FEEDER_REWARD_1_pc.mul(20)); - }); - - it("non feeder cannot feed rewards", async function () { - await expect(clusterRewards.feed(ETHHASH, [addrs[21], addrs[22]], [e16.mul(10), e16.mul(50)], 1)).to.be.revertedWith("only feeder"); - }); -}); - -describe("ClusterRewards submit tickets", function () { +describe.only("ClusterRewards submit tickets", function () { let signers: Signer[]; let addrs: string[]; let receiverStaking: Contract; @@ -722,33 +567,6 @@ describe("ClusterRewards submit tickets", function () { takeSnapshotBeforeAndAfterEveryTest(async () => {}); - // it.skip("delete me", async () => { - // await ethSelector.mock.NUMBER_OF_CLUSTERS_TO_SELECT.returns(5); - // let networkId = ethers.utils.id("ETH"); - // let epochNumber = getRandomNumber(BN.from(20000)).toNumber(); - // let noOfEpochs = 96; - // let tickets: number[][] = []; - // let rawTicketInfo = networkId + epochNumber.toString(16).padStart(8, "0"); - // for (let i = 0; i < noOfEpochs * 4; i++) { - // let j: number = parseInt(i / 4 + ""); - // let k: number = i % 4; - // if (!tickets[j]) tickets[j] = []; - // tickets[j][k] = parseInt(Math.random() * 13000 + ""); - // rawTicketInfo = rawTicketInfo + tickets[j][k].toString(16).padStart(4, "0"); - // } - // console.log(rawTicketInfo); - // const data = await clusterRewards._parseTicketInfo(rawTicketInfo); - // console.log(tickets); - // expect(data.networkId).to.equal(networkId); - // expect(data.fromEpoch).to.equal(epochNumber); - // expect(data.noOfEpochs).to.equal(noOfEpochs); - // tickets.map((e, i) => { - // e.map((ele, j) => { - // expect(tickets[i][j]).to.equal(data.tickets[i][j], `epoch ${i} ticket ${j} not equal`); - // }); - // }); - // }); - it("staker can submit tickets before switch with zero rewards", async function () { await receiverStaking.mock.balanceOfSignerAt.reverts(); await receiverStaking.mock.balanceOfSignerAt.withArgs(addrs[5], 2).returns(50, addrs[4]); @@ -990,7 +808,7 @@ describe("ClusterRewards submit tickets", function () { }); }); -describe("ClusterRewards submit compressed tickets", function () { +describe.only("ClusterRewards submit compressed tickets", function () { let signers: Signer[]; let addrs: string[]; let receiverStaking: Contract; @@ -1290,23 +1108,23 @@ describe("ClusterRewards submit compressed tickets", function () { expect(await clusterRewards.isTicketsIssued(addrs[5], startEpoch + i)).to.be.false; expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo( receiverRewards1[0].add(receiverRewards2[4]).add(extraRewards1[0]).add(extraRewards2[4]), - 11 + 15 ); expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo( receiverRewards1[1].add(receiverRewards2[3]).add(extraRewards1[1]).add(extraRewards2[3]), - 11 + 15 ); expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo( receiverRewards1[2].add(receiverRewards2[2]).add(extraRewards1[2]).add(extraRewards2[2]), - 11 + 15 ); expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo( receiverRewards1[3].add(receiverRewards2[1]).add(extraRewards1[3]).add(extraRewards2[1]), - 11 + 15 ); expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo( receiverRewards1[4].add(receiverRewards2[0]).add(extraRewards1[4]).add(extraRewards2[0]), - 11 + 15 ); } }); @@ -1636,7 +1454,7 @@ describe("ClusterRewards submit compressed tickets", function () { }); }); -describe("ClusterRewards claim rewards", function () { +describe.only("ClusterRewards claim rewards", function () { let signers: Signer[]; let addrs: string[]; let receiverStaking: Contract; @@ -1735,7 +1553,7 @@ describe("ClusterRewards claim rewards", function () { }); }); -describe("ClusterRewards: Add Receiver extra payment", function () { +describe.only("ClusterRewards: Add Receiver extra payment", function () { let signers: Signer[]; let addrs: string[]; let receiverStaking: Contract; @@ -1831,8 +1649,8 @@ describe("ClusterRewards: Add Receiver extra payment", function () { it("Reward Checking after receiver adds extra tokens", async () => { const mockRewardDelegators = signers[49]; // any random address can be used - const receiverRewardPerEpoch = BN.from(3000); - const receiverBalance = BN.from(100000); + const receiverRewardPerEpoch = BN.from(3000).mul(e18); + const receiverBalance = BN.from(100000).mul(e18); await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), mockRewardDelegators.getAddress()); // balance can be added by any address, hence we are using staker address directly @@ -1858,10 +1676,10 @@ describe("ClusterRewards: Add Receiver extra payment", function () { await skipToTimestamp(startTime + 34 * 86400); await clusterRewards.connect(signer)["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, epochWithRewards, tickets.slice(0, -1)); - expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo(receiverRewards1[0].add(extraRewards1[0]), 1); - expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo(receiverRewards1[1].add(extraRewards1[1]), 1); - expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo(receiverRewards1[2].add(extraRewards1[2]), 1); - expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(extraRewards1[3]), 1); - expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 1); + expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo(receiverRewards1[0].add(extraRewards1[0]), 10); + expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo(receiverRewards1[1].add(extraRewards1[1]), 10); + expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo(receiverRewards1[2].add(extraRewards1[2]), 10); + expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(extraRewards1[3]), 10); + expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 10); }); }); diff --git a/test/staking/RewardDelegators.ts b/test/staking/RewardDelegators.ts index e251893a..5dd90756 100644 --- a/test/staking/RewardDelegators.ts +++ b/test/staking/RewardDelegators.ts @@ -738,7 +738,7 @@ describe("RewardDelegators", function () { const amountToTransfer = 10000; await pondInstance.transfer(receiverSignerAndStaker.getAddress(), amountToTransfer); - const receiverBalanceBefore = await clusterRewardsInstance.receiverBalance(await receiverSignerAndStaker.getAddress()); + const receiverBalanceBefore = (await clusterRewardsInstance.receiverRewardPayment(await receiverSignerAndStaker.getAddress())).rewardRemaining; const tokenBalanceBefore = await pondInstance.balanceOf(rewardDelegators.address); await pondInstance.connect(receiverSignerAndStaker).approve(rewardDelegators.address, amountToTransfer); await expect( @@ -748,7 +748,7 @@ describe("RewardDelegators", function () { .withArgs(await receiverSignerAndStaker.getAddress(), amountToTransfer); const tokenBalanceAfter = await pondInstance.balanceOf(rewardDelegators.address); - const receiverBalanceAfter = await clusterRewardsInstance.receiverBalance(await receiverSignerAndStaker.getAddress()); + const receiverBalanceAfter = (await clusterRewardsInstance.receiverRewardPayment(await receiverSignerAndStaker.getAddress())).rewardRemaining; expect(tokenBalanceAfter).eq(tokenBalanceBefore.add(amountToTransfer)); expect(receiverBalanceAfter).eq(receiverBalanceBefore.add(amountToTransfer)); }); From 0c54502a9a8b77cbed6df6c0f66cee62a67e5f0c Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Thu, 15 Jun 2023 12:01:36 +0400 Subject: [PATCH 20/30] remove feed related operations, fix tests for the same --- .solhint.json | 3 +- contracts/staking/ClusterRewards.sol | 45 +++----- test/staking/ClusterRewards.ts | 156 ++++++++++----------------- 3 files changed, 76 insertions(+), 128 deletions(-) diff --git a/.solhint.json b/.solhint.json index b60d56c5..44b3691d 100644 --- a/.solhint.json +++ b/.solhint.json @@ -6,6 +6,7 @@ "avoid-suicide": "error", "avoid-sha3": "warn", "not-rely-on-time": "warn", - "not-rely-on-block-hash": "warn" + "not-rely-on-block-hash": "warn", + "max-line-length": 140 } } diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 512ec784..208a6628 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -109,7 +109,6 @@ contract ClusterRewards is } totalRewardWeight = _weight; _changeRewardPerEpoch(_totalRewardsPerEpoch); - payoutDenomination = 1e18; } //-------------------------------- Initializer end --------------------------------// @@ -117,23 +116,21 @@ contract ClusterRewards is //-------------------------------- Admin functions start --------------------------------// bytes32 public constant CLAIMER_ROLE = keccak256("CLAIMER_ROLE"); - bytes32 public constant FEEDER_ROLE = keccak256("FEEDER_ROLE"); uint256 public constant RECEIVER_TICKETS_PER_EPOCH = 2 ** 16; - uint256 constant SWITCHING_PERIOD = 33 days; - mapping(address => uint256) public clusterRewards; + mapping(address => uint256) public override clusterRewards; - mapping(bytes32 => uint256) public rewardWeight; + mapping(bytes32 => uint256) public override rewardWeight; uint256 public totalRewardWeight; - uint256 public totalRewardsPerEpoch; - uint256 public payoutDenomination; + uint256 public override totalRewardsPerEpoch; + uint256 public unsed_payoutDenomination; - mapping(uint256 => uint256) public rewardDistributedPerEpoch; - uint256 public latestNewEpochRewardAt; - uint256 public rewardDistributionWaitTime; + mapping(uint256 => uint256) public unused_rewardDistributedPerEpoch; + uint256 public unused_latestNewEpochRewardAt; + uint256 public unused_rewardDistributionWaitTime; mapping(address => mapping(uint256 => uint256)) public ticketsIssued; - mapping(bytes32 => IClusterSelector) public clusterSelectors; // networkId -> clusterSelector + mapping(bytes32 => IClusterSelector) public override clusterSelectors; // networkId -> clusterSelector ReceiverStaking public receiverStaking; event NetworkAdded(bytes32 networkId, uint256 rewardPerEpoch, address clusterSelector); @@ -145,7 +142,7 @@ contract ClusterRewards is event RewardDistributionWaitTimeChanged(uint256 updatedWaitTime); event TicketsIssued(bytes32 indexed networkId, uint256 indexed epoch, address indexed user); - function addNetwork(bytes32 _networkId, uint256 _rewardWeight, address _clusterSelector) external onlyAdmin { + function addNetwork(bytes32 _networkId, uint256 _rewardWeight, address _clusterSelector) external override onlyAdmin { require(rewardWeight[_networkId] == 0, "CRW:AN-Network already exists"); require(_clusterSelector != address(0), "CRW:AN-ClusterSelector must exist"); rewardWeight[_networkId] = _rewardWeight; @@ -158,7 +155,7 @@ contract ClusterRewards is emit NetworkAdded(_networkId, _rewardWeight, _clusterSelector); } - function removeNetwork(bytes32 _networkId) external onlyAdmin { + function removeNetwork(bytes32 _networkId) external override onlyAdmin { uint256 networkWeight = rewardWeight[_networkId]; require(address(clusterSelectors[_networkId]) != address(0), "CRW:RN-Network doesnt exist"); delete rewardWeight[_networkId]; @@ -167,7 +164,7 @@ contract ClusterRewards is emit NetworkRemoved(_networkId); } - function updateNetwork(bytes32 _networkId, uint256 _updatedRewardWeight, address _updatedClusterSelector) external onlyAdmin { + function updateNetwork(bytes32 _networkId, uint256 _updatedRewardWeight, address _updatedClusterSelector) external override onlyAdmin { uint256 networkWeight = rewardWeight[_networkId]; require(_updatedClusterSelector != address(0), "CRW:UN-ClusterSelector must exist"); address currentClusterSelector = address(clusterSelectors[_networkId]); @@ -195,7 +192,7 @@ contract ClusterRewards is emit ReceiverStakingUpdated(_receiverStaking); } - function changeRewardPerEpoch(uint256 _updatedRewardPerEpoch) external onlyAdmin { + function changeRewardPerEpoch(uint256 _updatedRewardPerEpoch) external override onlyAdmin { _changeRewardPerEpoch(_updatedRewardPerEpoch); } @@ -204,15 +201,6 @@ contract ClusterRewards is emit RewardPerEpochChanged(_updatedRewardPerEpoch); } - function updateRewardWaitTime(uint256 _updatedWaitTime) external onlyAdmin { - _updateRewardWaitTime(_updatedWaitTime); - } - - function _updateRewardWaitTime(uint256 _updatedWaitTime) internal { - rewardDistributionWaitTime = _updatedWaitTime; - emit RewardDistributionWaitTimeChanged(_updatedWaitTime); - } - //-------------------------------- Admin functions end --------------------------------// function _getRewardShare( @@ -283,6 +271,7 @@ contract ClusterRewards is ReceiverPayment memory receiverPayment = receiverRewardPayment[_receiver]; uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_networkId); + unchecked { for (uint256 i = 0; i < _epochs.length; ++i) { (uint256 _epochTotalStake, uint256 _currentEpoch) = receiverStaking.getEpochInfo(_epochs[i]); @@ -315,7 +304,6 @@ contract ClusterRewards is address[][] memory _selectedClusters = clusterSelectors[_networkId].getClustersRanged(_fromEpoch, _noOfEpochs); ReceiverPayment memory receiverPayment = receiverRewardPayment[_receiver]; - // _totalNetworkRewardsPerEpoch = inflation rewards + receiver rewards uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_networkId); unchecked { @@ -415,6 +403,7 @@ contract ClusterRewards is uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake) + MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); + _processReceiverTickets(_receiver, _epoch, _selectedClusters, _tickets, _rewardShare); _emitTicketsIssued(_networkId, _epoch, msg.sender); @@ -422,7 +411,7 @@ contract ClusterRewards is receiverRewardPayment[_receiver].rewardRemaining = receiverPayment.rewardRemaining; } - function claimReward(address _cluster) external onlyRole(CLAIMER_ROLE) returns (uint256) { + function claimReward(address _cluster) external onlyRole(CLAIMER_ROLE) override returns (uint256) { uint256 pendingRewards = clusterRewards[_cluster]; if (pendingRewards > 1) { uint256 rewardsToTransfer = pendingRewards - 1; @@ -432,8 +421,8 @@ contract ClusterRewards is return 0; } - function getRewardForEpoch(bytes32 _networkId) public view returns (uint256) { - return (totalRewardsPerEpoch * rewardWeight[_networkId]) / receiverStaking.EPOCH_LENGTH(); + function getRewardForEpoch(bytes32 _networkId) public view override returns (uint256) { + return (totalRewardsPerEpoch * rewardWeight[_networkId]) / totalRewardWeight; } //-------------------------------- User functions end --------------------------------// diff --git a/test/staking/ClusterRewards.ts b/test/staking/ClusterRewards.ts index c13a66d0..38a01cc9 100644 --- a/test/staking/ClusterRewards.ts +++ b/test/staking/ClusterRewards.ts @@ -57,7 +57,7 @@ const tickets = [ ), ]; -describe.only("ClusterRewards deploy and init", function () { +describe("ClusterRewards deploy and init", function () { let signers: Signer[]; let addrs: string[]; @@ -214,7 +214,7 @@ testRole( let startTime = Math.floor(Date.now() / 1000) + 100000; -describe.only("ClusterRewards add network", function () { +describe("ClusterRewards add network", function () { let signers: Signer[]; let addrs: string[]; let receiverStaking: Contract; @@ -317,7 +317,7 @@ describe.only("ClusterRewards add network", function () { }); }); -describe.only("ClusterRewards cluster selector", function () { +describe("ClusterRewards cluster selector", function () { let signers: Signer[]; let addrs: string[]; let receiverStaking: Contract; @@ -438,7 +438,7 @@ describe.only("ClusterRewards cluster selector", function () { }); }); -describe.only("ClusterRewards remove network", function () { +describe("ClusterRewards remove network", function () { let signers: Signer[]; let addrs: string[]; let receiverStaking: Contract; @@ -480,7 +480,7 @@ describe.only("ClusterRewards remove network", function () { }); }); -describe.only("ClusterRewards update global vars", function () { +describe("ClusterRewards update global vars", function () { let signers: Signer[]; let addrs: string[]; let receiverStaking: Contract; @@ -523,23 +523,15 @@ describe.only("ClusterRewards update global vars", function () { it("non admin cannot update reward per epoch", async function () { await expect(clusterRewards.connect(signers[1]).changeRewardPerEpoch(30000)).to.be.reverted; }); - - it("admin can update reward wait time", async function () { - await clusterRewards.updateRewardWaitTime(30000); - expect(await clusterRewards.rewardDistributionWaitTime()).to.equal(30000); - }); - - it("non admin cannot update reward wait time", async function () { - await expect(clusterRewards.connect(signers[1]).updateRewardWaitTime(30000)).to.be.reverted; - }); }); -describe.only("ClusterRewards submit tickets", function () { +describe("ClusterRewards submit tickets", function () { let signers: Signer[]; let addrs: string[]; let receiverStaking: Contract; let ethSelector: Contract; let clusterRewards: ClusterRewards; + const receiverEpochLength: number = 900 before(async function () { signers = await ethers.getSigners(); @@ -548,7 +540,7 @@ describe.only("ClusterRewards submit tickets", function () { const ReceiverStaking = await ethers.getContractFactory("ReceiverStaking"); receiverStaking = await deployMockContract(signers[0], ReceiverStaking.interface.format()); await receiverStaking.mock.START_TIME.returns(startTime); - await receiverStaking.mock.EPOCH_LENGTH.returns(900); + await receiverStaking.mock.EPOCH_LENGTH.returns(receiverEpochLength); const ClusterSelector = await ethers.getContractFactory("ClusterSelector"); ethSelector = await deployMockContract(signers[0], ClusterSelector.interface.format()); @@ -560,43 +552,47 @@ describe.only("ClusterRewards submit tickets", function () { { kind: "uups", constructorArgs: [] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); - - await clusterRewards.grantRole(clusterRewards.FEEDER_ROLE(), addrs[2]); - await clusterRewards.updateRewardWaitTime(43200); }); takeSnapshotBeforeAndAfterEveryTest(async () => {}); - it("staker can submit tickets before switch with zero rewards", async function () { + it("staker can submit tickets", async function () { + const totalEpochStake = 500 + const epochToUse = 2 + const receiverStakeAmount = 50 await receiverStaking.mock.balanceOfSignerAt.reverts(); - await receiverStaking.mock.balanceOfSignerAt.withArgs(addrs[5], 2).returns(50, addrs[4]); + await receiverStaking.mock.balanceOfSignerAt.withArgs(addrs[5], epochToUse).returns(receiverStakeAmount, addrs[4]); await receiverStaking.mock.getEpochInfo.reverts(); - await receiverStaking.mock.getEpochInfo.withArgs(2).returns(500, 5); + await receiverStaking.mock.getEpochInfo.withArgs(2).returns(totalEpochStake, 5); await ethSelector.mock.getClusters.returns([addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]); await clusterRewards.connect(signers[5])["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, 2, tickets.slice(0, -1)); - expect(await clusterRewards.isTicketsIssued(addrs[4], 2)).to.be.true; - expect(await clusterRewards.isTicketsIssued(addrs[5], 2)).to.be.false; - expect(await clusterRewards.clusterRewards(addrs[31])).to.equal(0); - expect(await clusterRewards.clusterRewards(addrs[32])).to.equal(0); - expect(await clusterRewards.clusterRewards(addrs[33])).to.equal(0); - expect(await clusterRewards.clusterRewards(addrs[34])).to.equal(0); - expect(await clusterRewards.clusterRewards(addrs[35])).to.equal(0); + let receiverRewards1 = tickets.map((e) => ETH_REWARD.mul(receiverStakeAmount).mul(e).div(totalEpochStake).div(MAX_TICKETS)); - await skipToTimestamp(startTime + 34 * 86400); + expect(await clusterRewards.isTicketsIssued(addrs[4], epochToUse)).to.be.true; + expect(await clusterRewards.isTicketsIssued(addrs[5], epochToUse)).to.be.false; + expect(await clusterRewards.clusterRewards(addrs[31])).to.closeTo(receiverRewards1[0], 1); + expect(await clusterRewards.clusterRewards(addrs[32])).to.closeTo(receiverRewards1[1], 1); + expect(await clusterRewards.clusterRewards(addrs[33])).to.closeTo(receiverRewards1[2], 1); + expect(await clusterRewards.clusterRewards(addrs[34])).to.closeTo(receiverRewards1[3], 1); + expect(await clusterRewards.clusterRewards(addrs[35])).to.closeTo(receiverRewards1[4], 1); - await receiverStaking.mock.balanceOfSignerAt.withArgs(addrs[6], 2).returns(50, addrs[7]); + await skipToTimestamp(startTime + 34 * 86400); + const anotherReceiverStake = 78 + await receiverStaking.mock.balanceOfSignerAt.withArgs(addrs[6], 2).returns(anotherReceiverStake, addrs[7]); await clusterRewards.connect(signers[6])["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, 2, tickets.slice(0, -1)); + let receiverRewards2 = tickets.map((e) => ETH_REWARD.mul(anotherReceiverStake).mul(e).div(totalEpochStake).div(MAX_TICKETS)); + expect(await clusterRewards.isTicketsIssued(addrs[7], 2)).to.be.true; expect(await clusterRewards.isTicketsIssued(addrs[6], 2)).to.be.false; - expect(await clusterRewards.clusterRewards(addrs[31])).to.equal(0); - expect(await clusterRewards.clusterRewards(addrs[32])).to.equal(0); - expect(await clusterRewards.clusterRewards(addrs[33])).to.equal(0); - expect(await clusterRewards.clusterRewards(addrs[34])).to.equal(0); - expect(await clusterRewards.clusterRewards(addrs[35])).to.equal(0); + expect(await clusterRewards.clusterRewards(addrs[31])).to.closeTo(receiverRewards1[0].add(receiverRewards2[0]), 1); + expect(await clusterRewards.clusterRewards(addrs[32])).to.closeTo(receiverRewards1[1].add(receiverRewards2[1]), 1); + expect(await clusterRewards.clusterRewards(addrs[33])).to.closeTo(receiverRewards1[2].add(receiverRewards2[2]), 1); + expect(await clusterRewards.clusterRewards(addrs[34])).to.closeTo(receiverRewards1[3].add(receiverRewards2[3]), 1); + expect(await clusterRewards.clusterRewards(addrs[35])).to.closeTo(receiverRewards1[4].add(receiverRewards2[4]), 1); }); it("staker can submit tickets after switch with non zero rewards", async function () { @@ -808,7 +804,7 @@ describe.only("ClusterRewards submit tickets", function () { }); }); -describe.only("ClusterRewards submit compressed tickets", function () { +describe("ClusterRewards submit compressed tickets", function () { let signers: Signer[]; let addrs: string[]; let receiverStaking: Contract; @@ -837,14 +833,11 @@ describe.only("ClusterRewards submit compressed tickets", function () { { kind: "uups", constructorArgs: [] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); - - await clusterRewards.grantRole(clusterRewards.FEEDER_ROLE(), addrs[2]); - await clusterRewards.updateRewardWaitTime(43200); }); takeSnapshotBeforeAndAfterEveryTest(async () => {}); - it("staker can submit compressed tickets for 1 epoch before switch with zero rewards", async function () { + it("staker can submit compressed tickets for 1 epoch", async function () { await receiverStaking.mock.getCurrentEpoch.returns(5); await receiverStaking.mock.totalSupplyAtRanged.revertsWithReason("unexpected query for total balance"); await receiverStaking.mock.totalSupplyAtRanged.withArgs(2, 1).returns([500]); @@ -861,6 +854,9 @@ describe.only("ClusterRewards submit compressed tickets", function () { tickets[j][k] = parseInt(Math.random() * 13000 + ""); rawTicketInfo = rawTicketInfo + tickets[j][k].toString(16).padStart(4, "0"); } + let lastTicket = MAX_TICKETS.sub(tickets[0].reduce((prev, curr) => prev.add(curr), BN.from(0))) + let receiverRewards1 = tickets[0].map((e) => ETH_REWARD.mul(50).mul(e).div(500).div(MAX_TICKETS)); + let lastReward = ETH_REWARD.mul(50).mul(lastTicket).div(500).div(MAX_TICKETS) // check the events arguments emitted await expect(clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo)).to.emit(clusterRewards, "TicketsIssued"); @@ -868,43 +864,11 @@ describe.only("ClusterRewards submit compressed tickets", function () { expect(await clusterRewards.isTicketsIssued(addrs[4], 2)).to.be.true; expect(await clusterRewards.isTicketsIssued(addrs[4], 3)).to.be.false; expect(await clusterRewards.isTicketsIssued(addrs[5], 2)).to.be.false; - expect(await clusterRewards.clusterRewards(addrs[31])).to.equal(0); - expect(await clusterRewards.clusterRewards(addrs[32])).to.equal(0); - expect(await clusterRewards.clusterRewards(addrs[33])).to.equal(0); - expect(await clusterRewards.clusterRewards(addrs[34])).to.equal(0); - expect(await clusterRewards.clusterRewards(addrs[35])).to.equal(0); - }); - - it("staker can submit compressed tickets for 1 epoch before switch with zero rewards, tx submitted after rewards open", async function () { - await receiverStaking.mock.getCurrentEpoch.returns(5); - await receiverStaking.mock.totalSupplyAtRanged.revertsWithReason("unexpected query for total balance"); - await receiverStaking.mock.totalSupplyAtRanged.withArgs(2, 1).returns([500]); - await receiverStaking.mock.balanceOfSignerAtRanged.revertsWithReason("unexpected query for signer balance"); - await receiverStaking.mock.balanceOfSignerAtRanged.withArgs(addrs[5], 2, 1).returns([50], addrs[4]); - await ethSelector.mock.getClustersRanged.returns([[addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]]); - - const tickets: number[][] = []; - let rawTicketInfo = ETHHASH + (2).toString(16).padStart(8, "0"); - for (let i = 0; i < 1 * ticketsLength; i++) { - let j: number = parseInt(i / ticketsLength + ""); - let k: number = i % ticketsLength; - if (!tickets[j]) tickets[j] = []; - tickets[j][k] = parseInt(Math.random() * 13000 + ""); - rawTicketInfo = rawTicketInfo + tickets[j][k].toString(16).padStart(4, "0"); - } - - await skipToTimestamp(startTime + 34 * 86400); - - await clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo); - - expect(await clusterRewards.isTicketsIssued(addrs[4], 2)).to.be.true; - expect(await clusterRewards.isTicketsIssued(addrs[4], 3)).to.be.false; - expect(await clusterRewards.isTicketsIssued(addrs[5], 2)).to.be.false; - expect(await clusterRewards.clusterRewards(addrs[31])).to.equal(0); - expect(await clusterRewards.clusterRewards(addrs[32])).to.equal(0); - expect(await clusterRewards.clusterRewards(addrs[33])).to.equal(0); - expect(await clusterRewards.clusterRewards(addrs[34])).to.equal(0); - expect(await clusterRewards.clusterRewards(addrs[35])).to.equal(0); + expect(await clusterRewards.clusterRewards(addrs[31])).to.equal(receiverRewards1[0]); + expect(await clusterRewards.clusterRewards(addrs[32])).to.equal(receiverRewards1[1]); + expect(await clusterRewards.clusterRewards(addrs[33])).to.equal(receiverRewards1[2]); + expect(await clusterRewards.clusterRewards(addrs[34])).to.equal(receiverRewards1[3]); + expect(await clusterRewards.clusterRewards(addrs[35])).to.closeTo(lastReward, 1); }); it("staker can submit compressed tickets after switch with non zero rewards", async function () { @@ -1108,23 +1072,23 @@ describe.only("ClusterRewards submit compressed tickets", function () { expect(await clusterRewards.isTicketsIssued(addrs[5], startEpoch + i)).to.be.false; expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo( receiverRewards1[0].add(receiverRewards2[4]).add(extraRewards1[0]).add(extraRewards2[4]), - 15 + 20 ); expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo( receiverRewards1[1].add(receiverRewards2[3]).add(extraRewards1[1]).add(extraRewards2[3]), - 15 + 20 ); expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo( receiverRewards1[2].add(receiverRewards2[2]).add(extraRewards1[2]).add(extraRewards2[2]), - 15 + 20 ); expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo( receiverRewards1[3].add(receiverRewards2[1]).add(extraRewards1[3]).add(extraRewards2[1]), - 15 + 20 ); expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo( receiverRewards1[4].add(receiverRewards2[0]).add(extraRewards1[4]).add(extraRewards2[0]), - 15 + 20 ); } }); @@ -1454,7 +1418,7 @@ describe.only("ClusterRewards submit compressed tickets", function () { }); }); -describe.only("ClusterRewards claim rewards", function () { +describe("ClusterRewards claim rewards", function () { let signers: Signer[]; let addrs: string[]; let receiverStaking: Contract; @@ -1480,9 +1444,6 @@ describe.only("ClusterRewards claim rewards", function () { { kind: "uups", constructorArgs: [] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); - - await clusterRewards.grantRole(clusterRewards.FEEDER_ROLE(), addrs[2]); - await clusterRewards.updateRewardWaitTime(43200); }); takeSnapshotBeforeAndAfterEveryTest(async () => {}); @@ -1553,7 +1514,7 @@ describe.only("ClusterRewards claim rewards", function () { }); }); -describe.only("ClusterRewards: Add Receiver extra payment", function () { +describe("ClusterRewards: Add Receiver extra payment", function () { let signers: Signer[]; let addrs: string[]; let receiverStaking: Contract; @@ -1590,9 +1551,6 @@ describe.only("ClusterRewards: Add Receiver extra payment", function () { ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); - await clusterRewards.grantRole(clusterRewards.FEEDER_ROLE(), addrs[2]); - await clusterRewards.updateRewardWaitTime(43200); - await receiverStaking.mock.STAKING_TOKEN.returns(mockToken.address); await receiverStaking.mock.signerToStaker.withArgs(await signer.getAddress()).returns(await staker.getAddress()); await receiverStaking.mock.stakerToSigner.withArgs(await staker.getAddress()).returns(await signer.getAddress()); @@ -1649,8 +1607,8 @@ describe.only("ClusterRewards: Add Receiver extra payment", function () { it("Reward Checking after receiver adds extra tokens", async () => { const mockRewardDelegators = signers[49]; // any random address can be used - const receiverRewardPerEpoch = BN.from(3000).mul(e18); - const receiverBalance = BN.from(100000).mul(e18); + const receiverRewardPerEpoch = BN.from(3000); + const receiverBalance = BN.from(100000); await clusterRewards.connect(signers[0]).grantRole(await clusterRewards.RECEIVER_PAYMENTS_MANAGER(), mockRewardDelegators.getAddress()); // balance can be added by any address, hence we are using staker address directly @@ -1676,10 +1634,10 @@ describe.only("ClusterRewards: Add Receiver extra payment", function () { await skipToTimestamp(startTime + 34 * 86400); await clusterRewards.connect(signer)["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, epochWithRewards, tickets.slice(0, -1)); - expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo(receiverRewards1[0].add(extraRewards1[0]), 10); - expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo(receiverRewards1[1].add(extraRewards1[1]), 10); - expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo(receiverRewards1[2].add(extraRewards1[2]), 10); - expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(extraRewards1[3]), 10); - expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 10); + expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo(receiverRewards1[0].add(extraRewards1[0]), 1); + expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo(receiverRewards1[1].add(extraRewards1[1]), 1); + expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo(receiverRewards1[2].add(extraRewards1[2]), 1); + expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(extraRewards1[3]), 1); + expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 1); }); -}); +}); \ No newline at end of file From 3d9238d57ade62ee422f6407d6fd71385b8a2ccc Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Mon, 19 Jun 2023 12:21:30 +0400 Subject: [PATCH 21/30] change delta to percentage --- contracts/staking/ClusterRewards.sol | 2 +- test/staking/ClusterRewards.ts | 52 ++++++++++++++++++---------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 208a6628..1eb7fcb6 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -123,7 +123,7 @@ contract ClusterRewards is mapping(bytes32 => uint256) public override rewardWeight; uint256 public totalRewardWeight; uint256 public override totalRewardsPerEpoch; - uint256 public unsed_payoutDenomination; + uint256 public unused_payoutDenomination; mapping(uint256 => uint256) public unused_rewardDistributedPerEpoch; uint256 public unused_latestNewEpochRewardAt; diff --git a/test/staking/ClusterRewards.ts b/test/staking/ClusterRewards.ts index 38a01cc9..9409904e 100644 --- a/test/staking/ClusterRewards.ts +++ b/test/staking/ClusterRewards.ts @@ -531,7 +531,7 @@ describe("ClusterRewards submit tickets", function () { let receiverStaking: Contract; let ethSelector: Contract; let clusterRewards: ClusterRewards; - const receiverEpochLength: number = 900 + const receiverEpochLength: number = 900; before(async function () { signers = await ethers.getSigners(); @@ -557,9 +557,9 @@ describe("ClusterRewards submit tickets", function () { takeSnapshotBeforeAndAfterEveryTest(async () => {}); it("staker can submit tickets", async function () { - const totalEpochStake = 500 - const epochToUse = 2 - const receiverStakeAmount = 50 + const totalEpochStake = 500; + const epochToUse = 2; + const receiverStakeAmount = 50; await receiverStaking.mock.balanceOfSignerAt.reverts(); await receiverStaking.mock.balanceOfSignerAt.withArgs(addrs[5], epochToUse).returns(receiverStakeAmount, addrs[4]); await receiverStaking.mock.getEpochInfo.reverts(); @@ -580,7 +580,7 @@ describe("ClusterRewards submit tickets", function () { await skipToTimestamp(startTime + 34 * 86400); - const anotherReceiverStake = 78 + const anotherReceiverStake = 78; await receiverStaking.mock.balanceOfSignerAt.withArgs(addrs[6], 2).returns(anotherReceiverStake, addrs[7]); await clusterRewards.connect(signers[6])["issueTickets(bytes32,uint24,uint16[])"](ETHHASH, 2, tickets.slice(0, -1)); @@ -854,9 +854,9 @@ describe("ClusterRewards submit compressed tickets", function () { tickets[j][k] = parseInt(Math.random() * 13000 + ""); rawTicketInfo = rawTicketInfo + tickets[j][k].toString(16).padStart(4, "0"); } - let lastTicket = MAX_TICKETS.sub(tickets[0].reduce((prev, curr) => prev.add(curr), BN.from(0))) + let lastTicket = MAX_TICKETS.sub(tickets[0].reduce((prev, curr) => prev.add(curr), BN.from(0))); let receiverRewards1 = tickets[0].map((e) => ETH_REWARD.mul(50).mul(e).div(500).div(MAX_TICKETS)); - let lastReward = ETH_REWARD.mul(50).mul(lastTicket).div(500).div(MAX_TICKETS) + let lastReward = ETH_REWARD.mul(50).mul(lastTicket).div(500).div(MAX_TICKETS); // check the events arguments emitted await expect(clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo)).to.emit(clusterRewards, "TicketsIssued"); @@ -1017,14 +1017,30 @@ describe("ClusterRewards submit compressed tickets", function () { await clusterRewards.connect(signers[5])["issueTickets(bytes)"](rawTicketInfo); + const percentageDelta = BN.from(10).pow(2); for (let i = 0; i < numberOfEpochs; i++) { expect(await clusterRewards.isTicketsIssued(addrs[4], startEpoch + i)).to.be.true; expect(await clusterRewards.isTicketsIssued(addrs[5], startEpoch + i)).to.be.false; - expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo(receiverRewards1[0].add(extraRewards1[0]), 10); - expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo(receiverRewards1[1].add(extraRewards1[1]), 10); - expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo(receiverRewards1[2].add(extraRewards1[2]), 10); - expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(extraRewards1[3]), 10); - expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 10); + expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo( + receiverRewards1[0].add(extraRewards1[0]), + receiverRewards1[0].add(extraRewards1[0]).mul(percentageDelta).div(e18) + ); + expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo( + receiverRewards1[1].add(extraRewards1[1]), + receiverRewards1[1].add(extraRewards1[1]).mul(percentageDelta).div(e18) + ); + expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo( + receiverRewards1[2].add(extraRewards1[2]), + receiverRewards1[2].add(extraRewards1[2]).mul(percentageDelta).div(e18) + ); + expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo( + receiverRewards1[3].add(extraRewards1[3]), + receiverRewards1[3].add(extraRewards1[3]).mul(percentageDelta).div(e18) + ); + expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo( + receiverRewards1[4].add(extraRewards1[4]), + receiverRewards1[4].add(extraRewards1[4]).mul(percentageDelta).div(e18) + ); } startEpoch += numberOfEpochs; @@ -1072,23 +1088,23 @@ describe("ClusterRewards submit compressed tickets", function () { expect(await clusterRewards.isTicketsIssued(addrs[5], startEpoch + i)).to.be.false; expect(await clusterRewards.clusterRewards(addrs[31])).to.be.closeTo( receiverRewards1[0].add(receiverRewards2[4]).add(extraRewards1[0]).add(extraRewards2[4]), - 20 + receiverRewards1[0].add(receiverRewards2[4]).add(extraRewards1[0]).add(extraRewards2[4]).mul(percentageDelta).div(e18) ); expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo( receiverRewards1[1].add(receiverRewards2[3]).add(extraRewards1[1]).add(extraRewards2[3]), - 20 + receiverRewards1[1].add(receiverRewards2[3]).add(extraRewards1[1]).add(extraRewards2[3]).mul(percentageDelta).div(e18) ); expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo( receiverRewards1[2].add(receiverRewards2[2]).add(extraRewards1[2]).add(extraRewards2[2]), - 20 + receiverRewards1[2].add(receiverRewards2[2]).add(extraRewards1[2]).add(extraRewards2[2]).mul(percentageDelta).div(e18) ); expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo( receiverRewards1[3].add(receiverRewards2[1]).add(extraRewards1[3]).add(extraRewards2[1]), - 20 + receiverRewards1[3].add(receiverRewards2[1]).add(extraRewards1[3]).add(extraRewards2[1]).mul(percentageDelta).div(e18) ); expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo( receiverRewards1[4].add(receiverRewards2[0]).add(extraRewards1[4]).add(extraRewards2[0]), - 20 + receiverRewards1[4].add(receiverRewards2[0]).add(extraRewards1[4]).add(extraRewards2[0]).mul(percentageDelta).div(e18) ); } }); @@ -1640,4 +1656,4 @@ describe("ClusterRewards: Add Receiver extra payment", function () { expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3].add(extraRewards1[3]), 1); expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4].add(extraRewards1[4]), 1); }); -}); \ No newline at end of file +}); From deccd9b2ade076f4bdd0193452401b26c38ee5a8 Mon Sep 17 00:00:00 2001 From: Prateek Reddy Date: Mon, 19 Jun 2023 14:04:41 +0400 Subject: [PATCH 22/30] cast in issueTickets to uint128 without checks to save gas --- contracts/staking/ClusterRewards.sol | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 1eb7fcb6..0914520b 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -9,7 +9,6 @@ import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol" import "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol"; import "./interfaces/IClusterSelector.sol"; import "./ReceiverStaking.sol"; @@ -27,7 +26,6 @@ contract ClusterRewards is UUPSUpgradeable, // public upgrade IClusterRewards // interface { - using SafeCastUpgradeable for uint256; // in case we add more contracts in the inheritance chain uint256[500] private __gap0; @@ -284,9 +282,9 @@ contract ClusterRewards is MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); _processReceiverTickets(_receiver, _epochs[i], _selectedClusters, _tickets[i], _rewardShare); - receiverPayment.rewardRemaining -= MathUpgradeable - .min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch) - .toUint128(); + // Note: no checks before casting as inputs are uint128 + receiverPayment.rewardRemaining -= uint128(MathUpgradeable + .min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch)); _emitTicketsIssued(_networkId, _epochs[i], msg.sender); } } @@ -313,9 +311,9 @@ contract ClusterRewards is _processReceiverTickets(_receiver, _fromEpoch, _selectedClusters[i], _tickets[i], _rewardShare); _emitTicketsIssued(_networkId, _fromEpoch, msg.sender); - receiverPayment.rewardRemaining -= MathUpgradeable - .min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch) - .toUint128(); + // Note: no checks before casting as inputs are uint128 + receiverPayment.rewardRemaining -= uint128(MathUpgradeable + .min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch)); ++_fromEpoch; } } From c0151a4ced6c6a09e0889bf913aa0edbf0e2c532 Mon Sep 17 00:00:00 2001 From: Prateek Reddy Date: Mon, 19 Jun 2023 14:09:49 +0400 Subject: [PATCH 23/30] Remove unused imports in ClusterRewards --- contracts/staking/ClusterRewards.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 0914520b..f5b37d1e 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -14,8 +14,6 @@ import "./interfaces/IClusterSelector.sol"; import "./ReceiverStaking.sol"; import "./interfaces/IClusterRewards.sol"; -import "./interfaces/IRewardDelegators.sol"; - contract ClusterRewards is Initializable, // initializer ContextUpgradeable, // _msgSender, _msgData From 245f1869c55da8cdc4a7fc8bc0207c8e6569a0a0 Mon Sep 17 00:00:00 2001 From: Prateek Reddy Date: Mon, 19 Jun 2023 14:17:22 +0400 Subject: [PATCH 24/30] update unused var names to match nomenculature in rest of contracts --- contracts/staking/ClusterRewards.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index f5b37d1e..830cbf15 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -119,11 +119,11 @@ contract ClusterRewards is mapping(bytes32 => uint256) public override rewardWeight; uint256 public totalRewardWeight; uint256 public override totalRewardsPerEpoch; - uint256 public unused_payoutDenomination; + uint256 public __unused_payoutDenomination; - mapping(uint256 => uint256) public unused_rewardDistributedPerEpoch; - uint256 public unused_latestNewEpochRewardAt; - uint256 public unused_rewardDistributionWaitTime; + mapping(uint256 => uint256) public __unused_rewardDistributedPerEpoch; + uint256 public __unused_latestNewEpochRewardAt; + uint256 public __unused_rewardDistributionWaitTime; mapping(address => mapping(uint256 => uint256)) public ticketsIssued; mapping(bytes32 => IClusterSelector) public override clusterSelectors; // networkId -> clusterSelector From ba900e8581a51352c8a5a826c12a91c914c8a9f5 Mon Sep 17 00:00:00 2001 From: Prateek Reddy Date: Mon, 19 Jun 2023 14:23:10 +0400 Subject: [PATCH 25/30] save gas by removing safemath checks in _getRewardShare --- contracts/staking/ClusterRewards.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 830cbf15..eefb1cd6 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -204,7 +204,10 @@ contract ClusterRewards is uint256 _epochTotalStake, uint256 _epochReceiverStake ) internal pure returns (uint256 _rewardShare) { - _rewardShare = (_totalNetworkRewardsPerEpoch * _epochReceiverStake) / _epochTotalStake; + unchecked { + // Note: multiplication can't overflow as max token supply is 10^38, hence max value of multiplication is 10^38*10^38 < 2^256 + _rewardShare = (_totalNetworkRewardsPerEpoch * _epochReceiverStake) / _epochTotalStake; + } } function _processReceiverTickets( From c8965b34c1f424ff683e9e7a8e0bc1f44673e1f5 Mon Sep 17 00:00:00 2001 From: Prateek Reddy Date: Mon, 19 Jun 2023 14:24:51 +0400 Subject: [PATCH 26/30] cast in issueTickets to uint128 without checks to save gas --- contracts/staking/ClusterRewards.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index eefb1cd6..40f66d9e 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -406,7 +406,8 @@ contract ClusterRewards is _processReceiverTickets(_receiver, _epoch, _selectedClusters, _tickets, _rewardShare); _emitTicketsIssued(_networkId, _epoch, msg.sender); - receiverPayment.rewardRemaining -= MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch).toUint128(); + // Note: no checks before casting as inputs are uint128 + receiverPayment.rewardRemaining -= uint128(MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch)); receiverRewardPayment[_receiver].rewardRemaining = receiverPayment.rewardRemaining; } From 5b35d40774bc7b74598279a598da3bf297980fad Mon Sep 17 00:00:00 2001 From: Prateek Reddy Date: Mon, 19 Jun 2023 14:30:52 +0400 Subject: [PATCH 27/30] Remove unnecessary casting in _processReceiverTickets --- contracts/staking/ClusterRewards.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 40f66d9e..43d483a1 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -233,7 +233,7 @@ contract ClusterRewards is } require(RECEIVER_TICKETS_PER_EPOCH >= _totalTickets, "CRW:IPRT-Total ticket count invalid"); clusterRewards[_selectedClusters[i]] += - (_rewardShare * uint256(RECEIVER_TICKETS_PER_EPOCH - _totalTickets)) / + (_rewardShare * (RECEIVER_TICKETS_PER_EPOCH - _totalTickets)) / RECEIVER_TICKETS_PER_EPOCH; } From 8763c0bf66528e580da400e256a6b5eacdd34556 Mon Sep 17 00:00:00 2001 From: Prateek Reddy Date: Mon, 19 Jun 2023 15:53:01 +0400 Subject: [PATCH 28/30] remove multiple calculations of rewardToGive in issueTickets --- contracts/staking/ClusterRewards.sol | 41 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/contracts/staking/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 43d483a1..9208dbe1 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -269,24 +269,27 @@ contract ClusterRewards is ReceiverPayment memory receiverPayment = receiverRewardPayment[_receiver]; uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_networkId); - + uint256 _rewardToGive; unchecked { for (uint256 i = 0; i < _epochs.length; ++i) { - (uint256 _epochTotalStake, uint256 _currentEpoch) = receiverStaking.getEpochInfo(_epochs[i]); + _rewardToGive = MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); + uint256 _epochTotalStake; + { + uint256 _currentEpoch; + (_epochTotalStake, _currentEpoch) = receiverStaking.getEpochInfo(_epochs[i]); - require(_epochs[i] < _currentEpoch, "CRW:IT-Epoch not completed"); + require(_epochs[i] < _currentEpoch, "CRW:IT-Epoch not completed"); + } address[] memory _selectedClusters = clusterSelectors[_networkId].getClusters(_epochs[i]); uint256 _epochReceiverStake = receiverStaking.balanceOfAt(_receiver, _epochs[i]); - uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake) + - MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); + uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake) + _rewardToGive; _processReceiverTickets(_receiver, _epochs[i], _selectedClusters, _tickets[i], _rewardShare); // Note: no checks before casting as inputs are uint128 - receiverPayment.rewardRemaining -= uint128(MathUpgradeable - .min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch)); - _emitTicketsIssued(_networkId, _epochs[i], msg.sender); + receiverPayment.rewardRemaining -= uint128(_rewardToGive); + emit TicketsIssued(_networkId, _epochs[i], msg.sender); } } receiverRewardPayment[_receiver].rewardRemaining = receiverPayment.rewardRemaining; @@ -304,17 +307,17 @@ contract ClusterRewards is ReceiverPayment memory receiverPayment = receiverRewardPayment[_receiver]; uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_networkId); + uint256 _rewardToGive; unchecked { for (uint256 i = 0; i < _noOfEpochs; ++i) { - uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _stakes[i], _balances[i]) + - MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); + _rewardToGive = MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); + uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _stakes[i], _balances[i]) + _rewardToGive; _processReceiverTickets(_receiver, _fromEpoch, _selectedClusters[i], _tickets[i], _rewardShare); - _emitTicketsIssued(_networkId, _fromEpoch, msg.sender); + emit TicketsIssued(_networkId, _fromEpoch, msg.sender); // Note: no checks before casting as inputs are uint128 - receiverPayment.rewardRemaining -= uint128(MathUpgradeable - .min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch)); + receiverPayment.rewardRemaining -= uint128(_rewardToGive); ++_fromEpoch; } } @@ -322,10 +325,6 @@ contract ClusterRewards is receiverRewardPayment[_receiver].rewardRemaining = receiverPayment.rewardRemaining; } - function _emitTicketsIssued(bytes32 _networkId, uint256 _epoch, address signer) internal { - emit TicketsIssued(_networkId, _epoch, signer); - } - function _parseTicketInfo( bytes memory ticketInfo ) internal view returns (bytes32 networkId, uint256 fromEpoch, uint256 noOfEpochs, uint16[][] memory tickets) { @@ -399,15 +398,15 @@ contract ClusterRewards is address[] memory _selectedClusters = clusterSelectors[_networkId].getClusters(_epoch); uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_networkId); + uint256 _rewardToGive = MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); - uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake) + - MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); + uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake) + _rewardToGive; _processReceiverTickets(_receiver, _epoch, _selectedClusters, _tickets, _rewardShare); - _emitTicketsIssued(_networkId, _epoch, msg.sender); + emit TicketsIssued(_networkId, _epoch, msg.sender); // Note: no checks before casting as inputs are uint128 - receiverPayment.rewardRemaining -= uint128(MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch)); + receiverPayment.rewardRemaining -= uint128(_rewardToGive); receiverRewardPayment[_receiver].rewardRemaining = receiverPayment.rewardRemaining; } From ecb8c6742023454246239c78ba1797d11cb521cc Mon Sep 17 00:00:00 2001 From: Prateek Reddy Date: Mon, 19 Jun 2023 18:11:14 +0400 Subject: [PATCH 29/30] allow rewardPerEpoch to be set to 0 by receiver --- contracts/staking/RewardDelegators.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/staking/RewardDelegators.sol b/contracts/staking/RewardDelegators.sol index 43a453e7..e63daa02 100755 --- a/contracts/staking/RewardDelegators.sol +++ b/contracts/staking/RewardDelegators.sol @@ -539,9 +539,7 @@ contract RewardDelegators is emit AddReceiverBalance(receiver, amount); } - // msg.sender is staker here function setReceiverRewardPerEpoch(uint128 rewardPerEpoch) public { - require(rewardPerEpoch != 0, "RD: reward 0"); address _sender = _msgSender(); clusterRewards._setReceiverRewardPerEpoch(_sender, rewardPerEpoch); emit UpdateReceiverRewardPerEpoch(_sender, rewardPerEpoch); From f8dd47977286acf526f2f0f049889d4054478dc2 Mon Sep 17 00:00:00 2001 From: Akshay Meher Date: Wed, 21 Jun 2023 10:40:07 +0400 Subject: [PATCH 30/30] Call issueTickets(bytes) twice in benchmarks --- benchmarks/ClusterRewards.ts | 131 +++++++++++++++++++++++++++++++---- 1 file changed, 118 insertions(+), 13 deletions(-) diff --git a/benchmarks/ClusterRewards.ts b/benchmarks/ClusterRewards.ts index b7d8f2ea..5b8dfe6d 100755 --- a/benchmarks/ClusterRewards.ts +++ b/benchmarks/ClusterRewards.ts @@ -3,6 +3,7 @@ import { benchmark as benchmarkDeployment } from "./helpers/deployment"; import { initDataFixture } from "./fixtures/ClusterRewards"; import { BigNumber, BigNumberish, constants, Contract, PopulatedTransaction, Signer, utils } from "ethers"; import { randomlyDivideInXPieces, skipTime } from "./helpers/util"; +import { ClusterRewards__factory } from "../typechain-types"; const estimator = new ethers.Contract("0x000000000000000000000000000000000000006c", [ "function getPricesInArbGas() view returns(uint256 gasPerL2Tx, uint256 gasPerL1CallDataByte, uint256)", @@ -10,23 +11,23 @@ const estimator = new ethers.Contract("0x000000000000000000000000000000000000006 const mainnetProvider = new ethers.providers.JsonRpcProvider("https://arb1.arbitrum.io/rpc"); describe("Cluster Rewards", async () => { - benchmarkDeployment( - "ClusterRewards", - [], + benchmarkDeployment( + "ClusterRewards", + [], + [ + "0x000000000000000000000000000000000000dEaD", + "0x000000000000000000000000000000000000dEaD", + "0x000000000000000000000000000000000000dEaD", + [ethers.utils.id("ETH"), ethers.utils.id("DOT"), ethers.utils.id("NEAR")], + [100, 200, 300], [ "0x000000000000000000000000000000000000dEaD", "0x000000000000000000000000000000000000dEaD", "0x000000000000000000000000000000000000dEaD", - [ethers.utils.id("ETH"), ethers.utils.id("DOT"), ethers.utils.id("NEAR")], - [100, 200, 300], - [ - "0x000000000000000000000000000000000000dEaD", - "0x000000000000000000000000000000000000dEaD", - "0x000000000000000000000000000000000000dEaD", - ], - 60000, - ] - ); + ], + 60000, + ] + ); describe("issue tickets", async () => { let pond: Contract; @@ -249,5 +250,109 @@ describe("Cluster Rewards", async () => { console.log(gasEstimate.add(l1GasInL2).mul(1600).mul(50).mul(365).div(BigNumber.from(10).pow(10)).toString()); }); + + it("all epochs in a day, tickets to all selected clusters optimized, two times (not gas estimate)", async function () { + { + this.timeout(1000000); + const selectedReceiverIndex: number = Math.floor(Math.random() * receivers.length); + const selectedReceiverSigner: Signer = receiverSigners[selectedReceiverIndex]; + + let noOfEpochs = DAY / EPOCH_LENGTH; + + // skip first epoch + await skipTime(EPOCH_LENGTH * 3); + + // select clusters for next epoch + await clusterSelector.selectClusters(); + let epoch = (await clusterSelector.getCurrentEpoch()).toNumber() + 1; + + for (let i = 1; i <= noOfEpochs; i++) { + // skip to next epoch + await skipTime(EPOCH_LENGTH); + + await clusterSelector.selectClusters(); + } + // skip to next epoch so that tickets can be distributed for previous epoch + await skipTime(EPOCH_LENGTH); + + let networkId = ethers.utils.id("ETH"); + let tickets: number[][] = []; + let rawTicketInfo = networkId + epoch.toString(16).padStart(8, "0"); + for (let i = 0; i < noOfEpochs * 4; i++) { + let j: number = parseInt(i / 4 + ""); + let k: number = i % 4; + if (!tickets[j]) tickets[j] = []; + tickets[j][k] = parseInt(Math.random() * 13000 + ""); + rawTicketInfo = rawTicketInfo + tickets[j][k].toString(16).padStart(4, "0"); + } + + const clusterRewardInstance = ClusterRewards__factory.connect(clusterRewards.address, selectedReceiverSigner); + const transaction = await clusterRewardInstance.connect(selectedReceiverSigner)["issueTickets(bytes)"](rawTicketInfo); + const receipt = await transaction.wait(); + console.log(`gas used for ${DAY / EPOCH_LENGTH} epochs : ${receipt.gasUsed.toNumber()}`); + + console.log( + "L1 gas cost Per L2 Tx", + l1GasDetails.gasPerL2Tx.toNumber(), + "L1 Gas cost Per calldata byte", + l1GasDetails.gasPerL1CallDataByte.toNumber() + ); + const l1GasInL2 = l1GasDetails.gasPerL2Tx.add(l1GasDetails.gasPerL1CallDataByte.mul((rawTicketInfo.length - 2) / 2)); + console.log(`L1 gas used for ${DAY / EPOCH_LENGTH} epochs : ${l1GasInL2.toNumber()}`); + + console.log(receipt.gasUsed.add(l1GasInL2).mul(1600).mul(50).mul(365).div(BigNumber.from(10).pow(10)).toString()); + } + + { + this.timeout(1000000); + const selectedReceiverIndex: number = Math.floor(Math.random() * receivers.length); + const selectedReceiverSigner: Signer = receiverSigners[selectedReceiverIndex]; + + let noOfEpochs = DAY / EPOCH_LENGTH; + + // skip first epoch + await skipTime(EPOCH_LENGTH * 3); + + // select clusters for next epoch + await clusterSelector.selectClusters(); + let epoch = (await clusterSelector.getCurrentEpoch()).toNumber() + 1; + + for (let i = 1; i <= noOfEpochs; i++) { + // skip to next epoch + await skipTime(EPOCH_LENGTH); + + await clusterSelector.selectClusters(); + } + // skip to next epoch so that tickets can be distributed for previous epoch + await skipTime(EPOCH_LENGTH); + + let networkId = ethers.utils.id("ETH"); + let tickets: number[][] = []; + let rawTicketInfo = networkId + epoch.toString(16).padStart(8, "0"); + for (let i = 0; i < noOfEpochs * 4; i++) { + let j: number = parseInt(i / 4 + ""); + let k: number = i % 4; + if (!tickets[j]) tickets[j] = []; + tickets[j][k] = parseInt(Math.random() * 13000 + ""); + rawTicketInfo = rawTicketInfo + tickets[j][k].toString(16).padStart(4, "0"); + } + + const clusterRewardInstance = ClusterRewards__factory.connect(clusterRewards.address, selectedReceiverSigner); + const transaction = await clusterRewardInstance.connect(selectedReceiverSigner)["issueTickets(bytes)"](rawTicketInfo); + const receipt = await transaction.wait(); + console.log(`gas used for ${DAY / EPOCH_LENGTH} epochs : ${receipt.gasUsed.toNumber()}`); + + console.log( + "L1 gas cost Per L2 Tx", + l1GasDetails.gasPerL2Tx.toNumber(), + "L1 Gas cost Per calldata byte", + l1GasDetails.gasPerL1CallDataByte.toNumber() + ); + const l1GasInL2 = l1GasDetails.gasPerL2Tx.add(l1GasDetails.gasPerL1CallDataByte.mul((rawTicketInfo.length - 2) / 2)); + console.log(`L1 gas used for ${DAY / EPOCH_LENGTH} epochs : ${l1GasInL2.toNumber()}`); + + console.log(receipt.gasUsed.add(l1GasInL2).mul(1600).mul(50).mul(365).div(BigNumber.from(10).pow(10)).toString()); + } + }); }); });