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/.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/benchmarks/ClusterRewards.ts b/benchmarks/ClusterRewards.ts index 49fe27cf..5b8dfe6d 100755 --- a/benchmarks/ClusterRewards.ts +++ b/benchmarks/ClusterRewards.ts @@ -3,241 +3,356 @@ 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)" + "function getPricesInArbGas() view returns(uint256 gasPerL2Tx, uint256 gasPerL1CallDataByte, uint256)", ]); 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, - ]); - - 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; - } + ], + 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); + + // TODO - 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 { + 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("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()); + } + }); + }); }); 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/ClusterRewards.sol b/contracts/staking/ClusterRewards.sol index 04ef6c47..9208dbe1 100644 --- a/contracts/staking/ClusterRewards.sol +++ b/contracts/staking/ClusterRewards.sol @@ -8,20 +8,21 @@ 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 "./interfaces/IClusterSelector.sol"; import "./ReceiverStaking.sol"; import "./interfaces/IClusterRewards.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; @@ -36,28 +37,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; @@ -69,18 +78,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(); @@ -96,40 +96,37 @@ 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]); } totalRewardWeight = _weight; _changeRewardPerEpoch(_totalRewardsPerEpoch); - 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 constant SWITCHING_PERIOD = 33 days; + uint256 public constant RECEIVER_TICKETS_PER_EPOCH = 2 ** 16; - 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 __unused_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); @@ -141,14 +138,9 @@ 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 { + 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"); + 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"); @@ -159,7 +151,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]; @@ -168,13 +160,13 @@ 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"); + 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"); @@ -196,7 +188,7 @@ contract ClusterRewards is emit ReceiverStakingUpdated(_receiverStaking); } - function changeRewardPerEpoch(uint256 _updatedRewardPerEpoch) external onlyAdmin { + function changeRewardPerEpoch(uint256 _updatedRewardPerEpoch) external override onlyAdmin { _changeRewardPerEpoch(_updatedRewardPerEpoch); } @@ -205,118 +197,106 @@ 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 --------------------------------// -//-------------------------------- 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; + function _getRewardShare( + uint256 _totalNetworkRewardsPerEpoch, + uint256 _epochTotalStake, + uint256 _epochReceiverStake + ) internal pure returns (uint256 _rewardShare) { + 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; } - require( - rewardDistributed <= currentTotalRewardsPerEpoch, - "CRW:F-Reward Distributed cant be more than totalRewardPerEpoch" - ); - rewardDistributedPerEpoch[_epoch] = rewardDistributed; - emit ClusterRewarded(_networkId); } - function _processReceiverTickets(address _receiver, uint256 _epoch, address[] memory _selectedClusters, uint16[] memory _tickets, uint256 _totalNetworkRewardsPerEpoch, uint256 _epochTotalStake, uint256 _epochReceiverStake) 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 _rewardShare = _totalNetworkRewardsPerEpoch * _epochReceiverStake / _epochTotalStake; + 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 * (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); + + ReceiverPayment memory receiverPayment = receiverRewardPayment[_receiver]; + uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_networkId); + uint256 _rewardToGive; + unchecked { - for(uint256 i=0; i < numberOfEpochs; ++i) { - uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_epochs[i], _networkId); - (uint256 _epochTotalStake, uint256 _currentEpoch) = receiverStaking.getEpochInfo(_epochs[i]); - require(_epochs[i] < _currentEpoch, "CRW:IT-Epoch not completed"); + for (uint256 i = 0; i < _epochs.length; ++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"); + } 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 _epochReceiverStake = receiverStaking.balanceOfAt(_receiver, _epochs[i]); + + 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(_rewardToGive); emit TicketsIssued(_networkId, _epochs[i], msg.sender); } } + receiverRewardPayment[_receiver].rewardRemaining = receiverPayment.rewardRemaining; } 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"); @@ -324,72 +304,83 @@ 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 _totalNetworkRewardsPerEpoch; + + ReceiverPayment memory receiverPayment = receiverRewardPayment[_receiver]; + uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_networkId); + uint256 _rewardToGive; 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]); + for (uint256 i = 0; i < _noOfEpochs; ++i) { + _rewardToGive = MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); + uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _stakes[i], _balances[i]) + _rewardToGive; + + _processReceiverTickets(_receiver, _fromEpoch, _selectedClusters[i], _tickets[i], _rewardShare); emit TicketsIssued(_networkId, _fromEpoch, msg.sender); + // Note: no checks before casting as inputs are uint128 + receiverPayment.rewardRemaining -= uint128(_rewardToGive); ++_fromEpoch; } } + + receiverRewardPayment[_receiver].rewardRemaining = receiverPayment.rewardRemaining; } - function _parseTicketInfo(bytes memory ticketInfo) internal view returns( - bytes32 networkId, - uint256 fromEpoch, - uint256 noOfEpochs, - uint16[][] memory tickets - ) { + 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; } } @@ -402,19 +393,26 @@ 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); + ReceiverPayment memory receiverPayment = receiverRewardPayment[_receiver]; - uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_epoch, _networkId); + address[] memory _selectedClusters = clusterSelectors[_networkId].getClusters(_epoch); + uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_networkId); + uint256 _rewardToGive = MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch); - (uint256 _epochReceiverStake, address _receiver) = receiverStaking.balanceOfSignerAt(msg.sender, _epoch); - _processReceiverTickets(_receiver, _epoch, _selectedClusters, _tickets, _totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake); + uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake) + _rewardToGive; + _processReceiverTickets(_receiver, _epoch, _selectedClusters, _tickets, _rewardShare); emit TicketsIssued(_networkId, _epoch, msg.sender); + + // Note: no checks before casting as inputs are uint128 + receiverPayment.rewardRemaining -= uint128(_rewardToGive); + 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) { + if (pendingRewards > 1) { uint256 rewardsToTransfer = pendingRewards - 1; clusterRewards[_cluster] = 1; return rewardsToTransfer; @@ -422,14 +420,22 @@ 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(bytes32 _networkId) public view override returns (uint256) { return (totalRewardsPerEpoch * rewardWeight[_networkId]) / totalRewardWeight; } - function getRewardForEpoch(uint256 _epoch, bytes32 _networkId) public view returns(uint256) { - return _getRewardForEpoch(_epoch, _networkId, receiverStaking.EPOCH_LENGTH()); + //-------------------------------- User functions end --------------------------------// + + bytes32 public constant RECEIVER_PAYMENTS_MANAGER = keccak256("RECEIVER_PAYMENTS_MANAGER"); + + mapping(address => ReceiverPayment) public receiverRewardPayment; + + function _increaseReceiverBalance(address staker, uint128 amount) external override onlyRole(RECEIVER_PAYMENTS_MANAGER) { + receiverRewardPayment[staker].rewardRemaining += amount; } -//-------------------------------- User functions end --------------------------------// + function _setReceiverRewardPerEpoch(address staker, uint128 rewardPerEpoch) external override onlyRole(RECEIVER_PAYMENTS_MANAGER) { + require(staker != address(0), "CRW: address 0"); + receiverRewardPayment[staker].rewardPerEpoch = rewardPerEpoch; + } } 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/RewardDelegators.sol b/contracts/staking/RewardDelegators.sol index 8d05c9cf..e63daa02 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"; @@ -524,4 +525,23 @@ 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, uint128 amount) public { + 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); + } + + function setReceiverRewardPerEpoch(uint128 rewardPerEpoch) public { + address _sender = _msgSender(); + clusterRewards._setReceiverRewardPerEpoch(_sender, rewardPerEpoch); + emit UpdateReceiverRewardPerEpoch(_sender, rewardPerEpoch); + } } 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/contracts/staking/interfaces/IClusterRewards.sol b/contracts/staking/interfaces/IClusterRewards.sol index 302c7d6f..c8548693 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); @@ -12,7 +16,9 @@ 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; + function _setReceiverRewardPerEpoch(address signer, uint128 rewardPerEpoch) external; } diff --git a/deployments/staking/ClusterRewards.ts b/deployments/staking/ClusterRewards.ts index 68b1e8db..c5c657b1 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: [] } + ); - 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"); diff --git a/package-lock.json b/package-lock.json index 6ce2e7ed..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", @@ -2273,6 +4072,7 @@ "integrity": "sha512-bdM5cEGCOhDSwminryHJbRmXc1x7dPKg6Pqns3qyTwFlxsqUgxE29lsERS3PlIW1HTjoIGMUqsk1zQQwST1Yxw==", "dev": true, "hasInstallScript": true, + "optional": true, "dependencies": { "node-gyp-build": "4.3.0" }, @@ -2941,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", @@ -3139,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", @@ -3244,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", @@ -3461,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", @@ -3570,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", @@ -3627,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", @@ -3636,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", @@ -3946,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", @@ -5081,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", @@ -5410,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" @@ -5773,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" @@ -5787,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", @@ -5828,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", @@ -5924,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", @@ -5976,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", @@ -6770,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", @@ -6902,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", @@ -6914,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", @@ -6932,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", @@ -6959,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", @@ -7287,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", @@ -7409,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", @@ -7955,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", @@ -8298,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", @@ -8342,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", @@ -8352,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" @@ -8366,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", @@ -8622,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", @@ -8639,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", @@ -8867,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", @@ -9166,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", @@ -9301,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", @@ -10336,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", @@ -10739,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", @@ -10757,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/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 0e1e9ebf..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,9 +300,11 @@ describe("ClusterRegistry", function () { expect(clusterData.isValidCluster).to.be.true; await rewardDelegators.mock.updateClusterDelegation.reverts(); - 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 () => {}); @@ -318,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]); @@ -352,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]); @@ -386,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]); @@ -420,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]); @@ -454,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]); @@ -488,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]); @@ -522,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]); @@ -556,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]); @@ -590,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]); @@ -624,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]); @@ -658,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]); @@ -692,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]); @@ -726,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]); @@ -760,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]); @@ -794,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]); @@ -820,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); @@ -828,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]); @@ -908,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(); @@ -952,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(); @@ -967,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(); @@ -998,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(); @@ -1062,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(); @@ -1107,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(); @@ -1126,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(); @@ -1157,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(); @@ -1169,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(); @@ -1256,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(); @@ -1316,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(); @@ -1373,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(); @@ -1418,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(); @@ -1430,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]); @@ -1450,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 8b37e14d..9409904e 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,75 +73,40 @@ 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, - 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, [ETHWEIGHT, DOTWEIGHT], [addrs[11], addrs[12], addrs[13]], MAX_REWARD], + { 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: [], + }) + ).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", 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" } + [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], + { kind: "uups", constructorArgs: [] } ); expect(await clusterRewards.hasRole(await clusterRewards.DEFAULT_ADMIN_ROLE(), addrs[0])).to.be.true; @@ -158,20 +124,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" } + [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], + { kind: "uups", constructorArgs: [] } ); - await upgrades.upgradeProxy(clusterRewards.address, ClusterRewards, { kind: "uups" }); + 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; @@ -190,18 +150,15 @@ 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, - ], - { kind: "uups" } + [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], + { kind: "uups", constructorArgs: [] } ); - 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", + constructorArgs: [], + }) + ).to.be.revertedWith("only admin"); }); }); @@ -211,16 +168,8 @@ 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, - ], - { kind: "uups" } + [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], + { kind: "uups", constructorArgs: [] } ); let clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); return clusterRewards; @@ -241,41 +190,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, - ], - { kind: "uups" } + [addrs[0], addrs[1], addrs[10], NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], + { kind: "uups", constructorArgs: [] } ); 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", constructorArgs: [] } + ); + 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,16 +232,8 @@ 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, - ], - { kind: "uups" } + [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], + { kind: "uups", constructorArgs: [] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); }); @@ -343,20 +272,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 +300,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 +311,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,16 +335,8 @@ 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, - ], - { kind: "uups" } + [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], + { kind: "uups", constructorArgs: [] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); }); @@ -426,16 +357,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 +379,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 +406,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 +417,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 +432,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,16 +456,8 @@ 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, - ], - { kind: "uups" } + [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], + { kind: "uups", constructorArgs: [] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); }); @@ -563,16 +498,8 @@ 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, - ], - { kind: "uups" } + [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [addrs[11], addrs[12], addrs[13]], MAX_REWARD], + { kind: "uups", constructorArgs: [] } ); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); }); @@ -596,158 +523,6 @@ describe("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("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" } - ); - 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 () { @@ -756,6 +531,7 @@ describe("ClusterRewards submit tickets", function () { let receiverStaking: Contract; let ethSelector: Contract; let clusterRewards: ClusterRewards; + const receiverEpochLength: number = 900; before(async function () { signers = await ethers.getSigners(); @@ -764,7 +540,7 @@ describe("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()); @@ -772,86 +548,55 @@ 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, - ], - { kind: "uups" } + [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [ethSelector.address, addrs[12], addrs[13]], MAX_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.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 { - 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 () { + 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 () { - 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(); @@ -859,7 +604,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)); @@ -882,15 +627,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 +643,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,60 +661,146 @@ 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 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; + 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 () { 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]]); - 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.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]]); - 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,26 +829,15 @@ 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, - ], - { kind: "uups" } + [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [ethSelector.address, addrs[12], addrs[13]], MAX_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("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]); @@ -1026,82 +846,163 @@ 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"); } + 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); - 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; 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 for 1 epoch before switch with zero rewards, tx submitted after rewards open", async function () { - await receiverStaking.mock.getCurrentEpoch.returns(5); + it("staker can submit compressed tickets after switch with non zero rewards", async function () { + const epochWithRewards = (33 * 86400) / 900 + 2; + const numberOfEpochs = 10; + let receiverRewards1: BN[] = []; + + await skipToTimestamp(startTime + 34 * 86400); + + const ticketsByEpoch: number[][] = []; + let startEpoch = epochWithRewards; + let rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, "0"); + let totalTickets = 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))) + ""); + 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 (!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; + } + + await receiverStaking.mock.getCurrentEpoch.returns(startEpoch + numberOfEpochs); await receiverStaking.mock.totalSupplyAtRanged.revertsWithReason("unexpected query for total balance"); - await receiverStaking.mock.totalSupplyAtRanged.withArgs(2, 1).returns([500]); + 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], 2, 1).returns([50], addrs[4]); - await ethSelector.mock.getClustersRanged.returns([[addrs[31], addrs[32], addrs[33], addrs[34], addrs[35]]]); + 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]])); - 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 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], 1); + expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo(receiverRewards1[1], 1); + expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo(receiverRewards1[2], 1); + expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3], 1); + expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4], 1); + } + + startEpoch += numberOfEpochs; + + rawTicketInfo = ETHHASH + startEpoch.toString(16).padStart(8, "0"); + totalTickets = 0; + let receiverRewards2: 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 (!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; } - await skipToTimestamp(startTime + 34*86400); + 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); - 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); + 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); + } }); - it("staker can submit compressed tickets after switch with non zero rewards", async function () { - const epochWithRewards = 33*86400/900 + 2; + 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); + 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++) { + + const mockRewardDelegators = signers[49]; // any random address can be used + 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); + 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))+""); + 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 (!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); - 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); + 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; } @@ -1109,37 +1010,65 @@ 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++) { + 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], 1); - expect(await clusterRewards.clusterRewards(addrs[32])).to.be.closeTo(receiverRewards1[1], 1); - expect(await clusterRewards.clusterRewards(addrs[33])).to.be.closeTo(receiverRewards1[2], 1); - expect(await clusterRewards.clusterRewards(addrs[34])).to.be.closeTo(receiverRewards1[3], 1); - expect(await clusterRewards.clusterRewards(addrs[35])).to.be.closeTo(receiverRewards1[4], 1); + 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; - 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++) { + 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))+""); + 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 (!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); - 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); + 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; } @@ -1147,45 +1076,64 @@ 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]).add(extraRewards1[0]).add(extraRewards2[4]), + 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]), + 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]), + 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]), + 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]), + receiverRewards1[4].add(receiverRewards2[0]).add(extraRewards1[4]).add(extraRewards2[0]).mul(percentageDelta).div(e18) + ); } }); 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 +1141,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 +1160,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 +1183,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 +1233,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 +1250,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 +1273,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 +1315,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 +1357,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 +1389,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 +1422,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,27 +1456,16 @@ 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, - ], - { kind: "uups" } + [addrs[0], addrs[1], receiverStaking.address, NETWORK_IDS, WEIGHTS, [ethSelector.address, addrs[12], addrs[13]], MAX_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("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 +1473,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 +1505,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 +1513,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 +1524,136 @@ 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", constructorArgs: [] } + ); + clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); + + 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("_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("_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.receiverRewardPayment(signer.getAddress()))[0]; + await clusterRewards.connect(signer)._increaseReceiverBalance(signer.getAddress(), increaseByAmount); + const balanceAfter = (await clusterRewards.receiverRewardPayment(signer.getAddress()))[0]; + + 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(staker.getAddress(), receiverExtraRewardPerEpoch); + expect((await clusterRewards.receiverRewardPayment(staker.getAddress()))[1]).eq(receiverExtraRewardPerEpoch); + }); + + 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); + + 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(), 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)); + + await receiverStaking.mock.balanceOfSignerAt.reverts(); + 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); + 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); + }); +}); diff --git a/test/staking/Integration.ts b/test/staking/Integration.ts index 034ac933..cb0027b1 100644 --- a/test/staking/Integration.ts +++ b/test/staking/Integration.ts @@ -102,6 +102,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); @@ -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: [] }); clusterRewards = getClusterRewards(clusterRewardsContract.address, signers[0]); const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); @@ -193,12 +195,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]); @@ -225,7 +226,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); @@ -614,7 +617,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"); }); @@ -633,7 +636,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)] @@ -641,7 +644,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)] @@ -1092,7 +1095,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 d9597959..5dd90756 100644 --- a/test/staking/RewardDelegators.ts +++ b/test/staking/RewardDelegators.ts @@ -60,7 +60,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 }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [], + }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); @@ -90,7 +94,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; }); @@ -109,7 +115,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]); @@ -129,7 +137,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; @@ -158,7 +168,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]), { @@ -194,7 +206,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 }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [], + }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); @@ -220,7 +236,9 @@ describe("RewardDelegators", function () { ["" + pondTokenId, "" + mpondTokenId], [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation] + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] ); }); @@ -263,7 +281,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 }); + let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [], + }); await expect(rewardDelegators.connect(signers[1]).updateClusterRewards(clusterRewardsInstance2.address)).to.be.reverted; }); @@ -273,7 +295,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 }); + let clusterRewardsInstance2 = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [], + }); await expect(await rewardDelegators.updateClusterRewards(clusterRewardsInstance2.address)).to.emit( rewardDelegators, "ClusterRewardsAddressUpdated" @@ -325,7 +351,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 }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [], + }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); @@ -350,7 +380,9 @@ describe("RewardDelegators", function () { ["" + pondTokenId, "" + mpondTokenId], [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], - [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation] + [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [] ); }); @@ -435,6 +467,8 @@ describe("RewardDelegators", function () { let delegator: Signer; + let receiverSignerAndStaker: Signer; + let registeredClusterRewardAddress: string; let registeredClusterRewardAddress1: string; let registeredClusterRewardAddress2: string; @@ -478,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]); @@ -490,7 +526,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 }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [], + }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); const ClusterRegistry = await ethers.getContractFactory("ClusterRegistry"); @@ -515,7 +555,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]; @@ -528,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, BigNumber.from(10).pow(20)], - { - kind: "uups", - constructorArgs: [blockData.timestamp, 4 * 3600], - } - ); + 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( @@ -553,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], @@ -598,9 +637,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()); @@ -609,10 +652,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 @@ -688,9 +733,35 @@ describe("RewardDelegators", function () { throw new Error("event expected"); } }); + + it("Add Receiver Balance", async () => { + const amountToTransfer = 10000; + await pondInstance.transfer(receiverSignerAndStaker.getAddress(), amountToTransfer); + + 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( + 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.receiverRewardPayment(await receiverSignerAndStaker.getAddress())).rewardRemaining; + 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); + }); }); -describe("RewardDelegators Deployment", function () { +describe("RewardDelegators - 5", function () { let signers: Signer[]; let addrs: string[]; let clusterRegistryInstance: ClusterRegistry; @@ -799,7 +870,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 }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [], + }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); await expect( @@ -811,7 +886,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; }); @@ -829,6 +906,8 @@ describe("RewardDelegators Deployment", function () { [stakingConfig.PondRewardFactor, stakingConfig.MPondRewardFactor], [stakingConfig.PondWeightForThreshold, stakingConfig.MPondWeightForThreshold], [stakingConfig.PondWeightForDelegation, stakingConfig.MPondWeightForDelegation], + [], + [], ], { kind: "uups" } ); @@ -857,13 +936,15 @@ 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) - ], { + let clusterSelectorContract = await upgrades.deployProxy(ClusterSelector, [addrs[0], rewardDelegatorsInstance.address], { kind: "uups", - constructorArgs: [await receiverStaking.START_TIME(), await receiverStaking.EPOCH_LENGTH()] + constructorArgs: [ + await receiverStaking.START_TIME(), + await receiverStaking.EPOCH_LENGTH(), + addrs[56], + BigNumber.from(10).pow(20), + 1000, + ], }); clusterSelectorInstance = getClusterSelector(clusterSelectorContract.address, signers[0]); @@ -1000,7 +1081,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); @@ -1078,7 +1159,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++) { @@ -1087,7 +1168,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", []); @@ -1098,7 +1179,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 @@ -1146,7 +1227,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", []); @@ -1194,7 +1275,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 }); + let clusterRewardsInstanceContract = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [], + }); clusterRewardsInstance = getClusterRewards(clusterRewardsInstanceContract.address, signers[0]); const RewardDelegators = await ethers.getContractFactory("RewardDelegators"); @@ -1210,6 +1295,8 @@ describe("RewardDelegators Deployment", function () { [100], [1], [1], + [], + [], ], { kind: "uups" } ); @@ -1219,13 +1306,9 @@ 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) - ], { + let clusterSelectorContract = await upgrades.deployProxy(ClusterSelector, [addrs[0], rewardDelegatorsInstance.address], { kind: "uups", - constructorArgs: [blockData.timestamp, 4*60*60] + constructorArgs: [blockData.timestamp, 4 * 60 * 60, addrs[56], BigNumber.from(10).pow(20), 100], }); clusterSelectorInstance = getClusterSelector(clusterSelectorContract.address, signers[0]); @@ -1346,7 +1429,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", []); @@ -1404,7 +1487,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 }); + const tempCLusterRewardInstance = await upgrades.deployProxy(ClusterRewards, { + kind: "uups", + initializer: false, + constructorArgs: [], + }); await expect(rewardDelegatorsInstance.connect(signers[1]).updateClusterRewards(tempCLusterRewardInstance.address)).to.be.reverted; await expect( @@ -1505,7 +1592,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..246526f3 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"); @@ -40,227 +40,170 @@ const e22 = ethers.utils.parseEther("10000"); const e30 = ethers.utils.parseEther("1000000000000"); 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 +214,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 +226,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: [], }); - - 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: [], }); + 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(); + }); +}); 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 { 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