UNPKG

@gooddollar/goodprotocol

Version:
929 lines (856 loc) 37.7 kB
import { default as hre, ethers, upgrades } from "hardhat"; import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import { BigNumber, Contract, Signer } from "ethers"; import { expect } from "chai"; import { GoodMarketMaker, CERC20, GoodReserveCDai, SimpleStaking, GReputation } from "../../types"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; import { createDAO, increaseTime, advanceBlocks } from "../helpers"; import ContributionCalculation from "@gooddollar/goodcontracts/stakingModel/build/contracts/ContributionCalculation.json"; const BN = ethers.BigNumber; export const NULL_ADDRESS = ethers.constants.AddressZero; export const BLOCK_INTERVAL = 30; describe("GovernanceStaking - staking with GD and get Rewards in GDAO", () => { let dai: Contract; let cDAI: Contract; let goodReserve: GoodReserveCDai; let governanceStaking: Contract; let goodFundManager: Contract; let grep: GReputation; let avatar, goodDollar, identity, marketMaker: GoodMarketMaker, contribution, controller, founder, staker, staker2, staker3, schemeMock, signers, nameService, initializeToken, setDAOAddress; before(async () => { [founder, staker, staker2, staker3, ...signers] = await ethers.getSigners(); schemeMock = signers.pop(); const cdaiFactory = await ethers.getContractFactory("cDAIMock"); const goodFundManagerFactory = await ethers.getContractFactory( "GoodFundManager" ); const governanceStakingFactory = await ethers.getContractFactory( "GovernanceStaking" ); let { controller: ctrl, avatar: av, gd, identity, daoCreator, nameService: ns, setDAOAddress: sda, setSchemes, marketMaker: mm, daiAddress, cdaiAddress, reserve, reputation, setReserveToken } = await loadFixture(createDAO); dai = await ethers.getContractAt("DAIMock", daiAddress); cDAI = await ethers.getContractAt("cDAIMock", cdaiAddress); avatar = av; controller = ctrl; setDAOAddress = sda; nameService = ns; initializeToken = setReserveToken; goodReserve = reserve as GoodReserveCDai; console.log("deployed dao", { founder: founder.address, gd, identity, controller, avatar }); goodFundManager = await upgrades.deployProxy( goodFundManagerFactory, [nameService.address], { kind: "uups" } ); grep = (await ethers.getContractAt( "GReputation", reputation )) as GReputation; console.log("Deployed goodfund manager", { manager: goodFundManager.address }); goodDollar = await ethers.getContractAt("IGoodDollar", gd); contribution = await ethers.getContractAt( ContributionCalculation.abi, await nameService.getAddress("CONTRIBUTION_CALCULATION") ); marketMaker = mm; console.log("deployed contribution, deploying reserve...", { founder: founder.address }); console.log("setting permissions..."); governanceStaking = await governanceStakingFactory.deploy( nameService.address ); await setDAOAddress("CDAI", cDAI.address); await setDAOAddress("DAI", dai.address); //This set addresses should be another function because when we put this initialization of addresses in initializer then nameservice is not ready yet so no proper addresses await goodReserve.setAddresses(); await setDAOAddress("MARKET_MAKER", marketMaker.address); await setDAOAddress("FUND_MANAGER", goodFundManager.address); }); it("Should not mint reward when staking contract is not minter ", async () => { await goodDollar.mint(founder.address, "100"); await goodDollar.approve(governanceStaking.address, "100"); await governanceStaking.stake("100"); await advanceBlocks(5); const error = await governanceStaking.withdrawStake("100").catch(e => e); expect(error.message).to.have.string( "GReputation: need minter role or be GDAO contract" ); }); it("Should be able mint rewards after set GDAO staking contract", async () => { await setDAOAddress("GDAO_STAKING", governanceStaking.address); const GDAOBalanceBeforeWithdraw = await grep.balanceOfLocal( founder.address ); await governanceStaking.withdrawStake("100"); const GDAOBalanceAfterWithdraw = await grep.balanceOfLocal(founder.address); expect(GDAOBalanceAfterWithdraw).to.gt(GDAOBalanceBeforeWithdraw); }); it("Avatar should be able to change rewards per block", async () => { const ictrl = await ethers.getContractAt( "Controller", controller, schemeMock ); const governanceStakingFactory = await ethers.getContractFactory( "GovernanceStaking" ); let encodedCall = governanceStakingFactory.interface.encodeFunctionData( "setMonthlyRewards", [ethers.utils.parseEther("1728000")] ); await ictrl.genericCall(governanceStaking.address, encodedCall, avatar, 0); const rewardsPerBlock = await governanceStaking.getRewardsPerBlock(); expect(rewardsPerBlock).to.equal( ethers.utils.parseEther("1728000").div(BN.from("518400")) // 1728000 is montlhy reward amount and 518400 is monthly blocks for FUSE chain ); await goodDollar.mint(founder.address, "100"); await goodDollar.approve(governanceStaking.address, "100"); const stakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1; await governanceStaking.stake("100"); await advanceBlocks(4); const GDAOBalanceBeforeWithdraw = await grep.balanceOfLocal( founder.address ); await governanceStaking.withdrawStake("100"); const withdrawBlockNumber = await ethers.provider.getBlockNumber(); const GDAOBalanceAfterWithdraw = await grep.balanceOfLocal(founder.address); const multiplier = withdrawBlockNumber - stakeBlockNumber; const calculatedReward = rewardsPerBlock.mul(multiplier); // We calculate user rewards since it's the only staker so gets whole rewards so rewardsPerBlock * multipler(block that passed between stake and withdraw) expect(GDAOBalanceAfterWithdraw).to.be.equal( GDAOBalanceBeforeWithdraw.add(calculatedReward) ); encodedCall = governanceStakingFactory.interface.encodeFunctionData( "setMonthlyRewards", [ethers.utils.parseEther("12000000")] ); await ictrl.genericCall(governanceStaking.address, encodedCall, avatar, 0); }); it("Should be able to withdraw rewards without withdraw stake", async () => { const rewardsPerBlock = await governanceStaking.getRewardsPerBlock(); await goodDollar.mint(founder.address, "100"); await goodDollar.approve(governanceStaking.address, "100"); const stakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1; await governanceStaking.stake("100"); await advanceBlocks(4); const GDAOBalanceBeforeWithdraw = await grep.balanceOfLocal( founder.address ); const transaction = await ( await governanceStaking.withdrawRewards() ).wait(); const withdrawBlockNumber = await ethers.provider.getBlockNumber(); const GDAOBalanceAfterWithdraw = await grep.balanceOfLocal(founder.address); const multiplier = withdrawBlockNumber - stakeBlockNumber; const calculatedReward = rewardsPerBlock.mul(multiplier); // We calculate user rewards since it's the only staker so gets whole rewards so rewardsPerBlock * multipler(block that passed between stake and withdraw) expect(GDAOBalanceAfterWithdraw).to.be.equal( GDAOBalanceBeforeWithdraw.add(calculatedReward) ); expect(transaction.events.find(_ => _.event === "ReputationEarned")).to.be .not.empty; await governanceStaking.withdrawStake("100"); }); it("Should be able to withdraw transferred stakes", async () => { await goodDollar.mint(staker.address, "100"); await goodDollar.connect(staker).approve(governanceStaking.address, "100"); await governanceStaking.connect(staker).stake("100"); await advanceBlocks(4); await governanceStaking.connect(staker).transfer(founder.address, "100"); await governanceStaking.connect(staker).withdrawRewards(); const gdaoBalanceBeforeWithdraw = await grep.balanceOfLocal( founder.address ); await governanceStaking.withdrawStake("100"); const gdaoBalanceAfterWithdraw = await grep.balanceOfLocal(founder.address); expect(gdaoBalanceAfterWithdraw).to.gt(gdaoBalanceBeforeWithdraw); }); it("should not be able to withdraw after they send their stake to somebody else", async () => { let transaction = await governanceStaking .connect(staker) .withdrawStake("100") .catch(e => e); expect(transaction.message).to.have.string("Not enough token staked"); }); it("it should distribute reward with correct precision", async () => { const ictrl = await ethers.getContractAt( "Controller", controller, schemeMock ); const governanceStakingFactory = await ethers.getContractFactory( "GovernanceStaking" ); let encodedCall = governanceStakingFactory.interface.encodeFunctionData( "setMonthlyRewards", ["17280000000000000000"] // Give 0.0001 GDAO per block so 17.28 GDAO per month ); await ictrl.genericCall(governanceStaking.address, encodedCall, avatar, 0); const rewardsPerBlock = await governanceStaking.getRewardsPerBlock(); await goodDollar.mint(founder.address, "100"); await goodDollar.approve(governanceStaking.address, "100"); const stakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1; await governanceStaking.stake("100"); await advanceBlocks(4); const GDAOBalanceBeforeWithdraw = await grep.balanceOfLocal( founder.address ); await governanceStaking.withdrawStake("100"); const withdrawBlockNumber = await ethers.provider.getBlockNumber(); const GDAOBalanceAfterWithdraw = await grep.balanceOfLocal(founder.address); const multiplier = withdrawBlockNumber - stakeBlockNumber; const calculatedReward = rewardsPerBlock.mul(multiplier); // We calculate user rewards since it's the only staker so gets whole rewards so rewardsPerBlock * multipler(block that passed between stake and withdraw) expect(GDAOBalanceAfterWithdraw).to.be.equal( GDAOBalanceBeforeWithdraw.add(calculatedReward) ); }); it("it should not generate rewards when rewards per block set to 0", async () => { const ictrl = await ethers.getContractAt( "Controller", controller, schemeMock ); const governanceStakingFactory = await ethers.getContractFactory( "GovernanceStaking" ); let encodedCall = governanceStakingFactory.interface.encodeFunctionData( "setMonthlyRewards", ["0"] // Give 0 GDAO per block ); await ictrl.genericCall(governanceStaking.address, encodedCall, avatar, 0); await goodDollar.mint(founder.address, "100"); await goodDollar.approve(governanceStaking.address, "100"); await governanceStaking.stake("100"); const userProductivity = await governanceStaking[ "getProductivity(address)" ](founder.address); expect(userProductivity[0]).to.be.equal(BN.from("100")); await advanceBlocks(4); const GDAOBalanceBeforeWithdraw = await grep.balanceOfLocal( founder.address ); await governanceStaking.withdrawStake("100"); const GDAOBalanceAfterWithdraw = await grep.balanceOfLocal(founder.address); expect(GDAOBalanceAfterWithdraw.sub(GDAOBalanceBeforeWithdraw)).to.equal(0); encodedCall = governanceStakingFactory.interface.encodeFunctionData( "setMonthlyRewards", [ethers.utils.parseEther("12000000")] ); await ictrl.genericCall(governanceStaking.address, encodedCall, avatar, 0); }); it("it should return productivity values correctly", async () => { await goodDollar.mint(founder.address, "100"); await goodDollar.approve(governanceStaking.address, "100"); await governanceStaking.stake("100"); const productivityValue = await governanceStaking[ "getProductivity(address)" ](founder.address); expect(productivityValue[0].toString()).to.be.equal("100"); expect(productivityValue[1].toString()).to.be.equal("100"); await governanceStaking.withdrawStake("100"); }); it("it should return earned rewards with pending ones properly", async () => { const rewardsPerBlock = await governanceStaking.getRewardsPerBlock(); await goodDollar.mint(founder.address, "100"); await goodDollar.approve(governanceStaking.address, "100"); const stakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1; await governanceStaking.stake("100"); await advanceBlocks(5); const totalEarned = await governanceStaking[ "getUserPendingReward(address)" ](founder.address); const pendingRewardBlockNumber = await ethers.provider.getBlockNumber(); const multiplier = pendingRewardBlockNumber - stakeBlockNumber; const calculatedPendingReward = rewardsPerBlock.mul(multiplier); // We calculate user rewards since it's the only staker so gets whole rewards so rewardsPerBlock * multipler(block that passed between stake and withdraw) expect(totalEarned).to.be.equal(calculatedPendingReward); await governanceStaking.withdrawStake("100"); }); it("Accumulated per share has enough precision when reward << totalproductivity", async () => { const rewardsPerBlock = await governanceStaking.getRewardsPerBlock(); await goodDollar.mint(founder.address, "100000000000000"); // 1 trillion gd stake await goodDollar.approve(governanceStaking.address, "1000000000000"); const stakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1; await governanceStaking.stake("1000000000000"); await advanceBlocks(4); const GDAOBalanceBeforeWithdraw = await grep.balanceOfLocal( founder.address ); await governanceStaking.withdrawStake("1000000000000"); const withdrawBlockNumber = await ethers.provider.getBlockNumber(); const GDAOBalanceAfterWithdraw = await grep.balanceOfLocal(founder.address); const multiplier = withdrawBlockNumber - stakeBlockNumber; const calculatedReward = rewardsPerBlock.mul(multiplier); // We calculate user rewards since it's the only staker so gets whole rewards so rewardsPerBlock * multipler(block that passed between stake and withdraw) expect(GDAOBalanceAfterWithdraw).to.be.equal( GDAOBalanceBeforeWithdraw.add(calculatedReward) ); }); it("user receive fractional gdao properly when his stake << totalProductivity", async () => { const rewardsPerBlock = await governanceStaking.getRewardsPerBlock(); await goodDollar.mint(founder.address, "800"); // 8gd await goodDollar.mint(staker.address, "200"); // 2gd await goodDollar.approve(governanceStaking.address, "800"); await goodDollar.connect(staker).approve(governanceStaking.address, "200"); await governanceStaking.stake("800"); const secondStakerStakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1; await governanceStaking.connect(staker).stake("200"); await advanceBlocks(4); const GDAOBalanceBeforeWithdraw = await grep.balanceOfLocal(staker.address); const FounderGDAOBalanceBeforeWithdraw = await grep.balanceOfLocal( founder.address ); await governanceStaking.withdrawStake("800"); const founderWithdrawBlockNumber = await ethers.provider.getBlockNumber(); await governanceStaking.connect(staker).withdrawStake("200"); const GDAOBalanceAfterWithdraw = await grep.balanceOfLocal(staker.address); const FounderGDAOBalanceAfterWithdraw = await grep.balanceOfLocal( founder.address ); const founderCalculatedRewards = rewardsPerBlock.add( rewardsPerBlock .mul(80) .mul(founderWithdrawBlockNumber - secondStakerStakeBlockNumber) .div(100) ); // Founder should get full rewards for one block then owns of %80 of rewards const stakerCalculatedRewards = rewardsPerBlock .mul(20) .mul(founderWithdrawBlockNumber - secondStakerStakeBlockNumber) .div(100) .add(rewardsPerBlock); // Staker should get %20 of rewards initially then when Founder withdraw their stake Staker would own %100 of rewards and would get full amount expect(FounderGDAOBalanceAfterWithdraw).to.be.equal( FounderGDAOBalanceBeforeWithdraw.add(founderCalculatedRewards) ); expect(GDAOBalanceAfterWithdraw).to.be.equal( GDAOBalanceBeforeWithdraw.add(stakerCalculatedRewards) ); }); it("it should be able to tranfer tokens when user approve", async () => { await goodDollar.mint(founder.address, "100"); await goodDollar.approve(governanceStaking.address, "100"); await governanceStaking.stake("100"); await governanceStaking.approve(staker.address, "100"); const stakerProductivityBeforeTransfer = await governanceStaking[ "getProductivity(address)" ](staker.address); await governanceStaking .connect(staker) .transferFrom(founder.address, staker.address, "100"); const stakerProductivity = await governanceStaking[ "getProductivity(address)" ](staker.address); expect(await governanceStaking.balanceOf(founder.address)).to.equal(0); expect(await governanceStaking.balanceOf(staker.address)).to.equal(100); expect((await governanceStaking.users(staker.address)).amount).to.equal( 100 ); expect(stakerProductivityBeforeTransfer[0]).to.be.equal(0); expect(stakerProductivity[0]).to.be.equal("100"); }); it("it should return staker data", async () => { await goodDollar.mint(staker2.address, "100"); await goodDollar.connect(staker2).approve(governanceStaking.address, "100"); await governanceStaking.connect(staker2).stake("100"); await advanceBlocks(10); expect((await governanceStaking.users(staker2.address)).rewardDebt).to.gt( 0 ); //debt should start according to accumulated rewards in contract. debt is the user stake starting point. await governanceStaking.connect(staker2).withdrawStake("1"); expect(await governanceStaking.balanceOf(staker2.address)).to.equal(99); expect((await governanceStaking.users(staker2.address)).amount).to.equal( 99 ); expect((await governanceStaking.users(staker2.address)).rewardDebt).to.gt( 0 ); //should have withdrawn rewards after withdraw stake expect( (await governanceStaking.users(staker2.address)).rewardEarn ).to.equal(0); //should have 0 pending rewards after withdraw stake await advanceBlocks(10); await goodDollar.connect(staker2).approve(governanceStaking.address, "200"); await governanceStaking.connect(staker2).stake("1"); //should calculate user pending rewards expect( (await governanceStaking.users(staker2.address)).rewardEarn ).to.be.equal( 0 //should have 0 rewardEarned because every action, like the above stake withdraws gdao rewards ); await advanceBlocks(2); // pass some blocks const userPendingReward = await governanceStaking[ "getUserPendingReward(address)" ](staker2.address); governanceStaking.connect(staker2).withdrawStake("100"); expect(userPendingReward).to.be.gt(0); }); it("it should return pendingRewards equal zero after withdraw", async () => { let userPendingRewards = await governanceStaking[ "getUserPendingReward(address)" ](staker.address); expect(userPendingRewards).to.be.gt(0); await governanceStaking.connect(staker).withdrawRewards(); userPendingRewards = await governanceStaking[ "getUserPendingReward(address)" ](staker.address); expect(userPendingRewards).to.equal(0); await advanceBlocks(1); userPendingRewards = await governanceStaking[ "getUserPendingReward(address)" ](staker.address); expect(userPendingRewards).to.gt(0); //one block passed await governanceStaking.connect(staker).withdrawStake(0); userPendingRewards = await governanceStaking[ "getUserPendingReward(address)" ](staker.address); expect(userPendingRewards).to.be.equal(0); }); it("it should calculate accumulated rewards per share correctly", async () => { const governanceStakingFactory = await ethers.getContractFactory( "GovernanceStaking" ); const simpleGovernanceStaking = await governanceStakingFactory.deploy( nameService.address ); await setDAOAddress("GDAO_STAKING", simpleGovernanceStaking.address); await goodDollar.mint(founder.address, "200"); await goodDollar.mint(staker.address, "200"); await goodDollar.approve(simpleGovernanceStaking.address, "200"); await goodDollar .connect(staker) .approve(simpleGovernanceStaking.address, "200"); await simpleGovernanceStaking.stake("100"); let accumulatedRewardsPerShare = await simpleGovernanceStaking[ "totalRewardsPerShare()" ](); expect(accumulatedRewardsPerShare).to.equal(0); //first has no accumulated rewards yet, since no blocks have passed since staking await simpleGovernanceStaking.stake("100"); accumulatedRewardsPerShare = await simpleGovernanceStaking[ "totalRewardsPerShare()" ](); expect(await simpleGovernanceStaking.getRewardsPerBlock()).to.equal( ethers.utils .parseEther("2000000") //2M reputation .div(await simpleGovernanceStaking.FUSE_MONTHLY_BLOCKS()) ); let totalProductiviy = BN.from("100"); //totalRewardsPerShare is in 1e27 , divid by 1e9 to get 1e18 decimals expect(accumulatedRewardsPerShare.div(BN.from(1e9))).to.equal( ethers.utils .parseEther("2000000") //monthly rewards .mul(BN.from(1e2)) //G$ is 2 decimals, dividing reduces decimals by 2, so we first increase to 1e20 decimals .div(await simpleGovernanceStaking.FUSE_MONTHLY_BLOCKS()) //=rewards per block .mul(BN.from("1")) //=rewards per block * number of blocks = rewards earned in period .div(totalProductiviy) //=rewards per share .mul(BN.from("10000000000000000")), //restore lost precision from dividing by totalProductivity G$ 2 decimals; "1 block" ); //1 block passed with actual staking totalProductiviy = totalProductiviy.add(BN.from("100")); //second stake await simpleGovernanceStaking.connect(staker).stake("100"); let accumulatedRewardsPerShare2 = await simpleGovernanceStaking[ "totalRewardsPerShare()" ](); //shouldnt be naive accumlattion of 2 blocks, since total productivity has changed between blocks expect(accumulatedRewardsPerShare2.div(BN.from(1e9))).to.not.equal( ethers.utils .parseEther("2000000") .mul(BN.from(1e2)) //G$ is 2 decimals, dividing reduces decimals by 2, so we first increase to 1e20 decimals .div(await simpleGovernanceStaking.FUSE_MONTHLY_BLOCKS()) .div(totalProductiviy) .mul(BN.from("2")) .mul(BN.from("10000000000000000")), //increase precision to 1e18 from totalProductivity G$ 2 decimals; "2 blocks" ); //2 blocks passed but now we have 200 total productivity before 3rd stake console.log( accumulatedRewardsPerShare2.toString(), accumulatedRewardsPerShare.toString() ); const calculatedAccRewards = rdiv( ethers.utils .parseEther("2000000") .div(await simpleGovernanceStaking.FUSE_MONTHLY_BLOCKS()), totalProductiviy ); //accumulated so far plus block accumulation expect(accumulatedRewardsPerShare2).to.equal( calculatedAccRewards.add(accumulatedRewardsPerShare), //add rewards from previous block "2 blocks correct" ); await setDAOAddress("GDAO_STAKING", governanceStaking.address); }); it("Staking tokens should be 2 decimals", async () => { const decimals = await governanceStaking.decimals(); expect(decimals.toString()).to.be.equal("2"); }); it("Stake amount should be positive", async () => { const tx = await governanceStaking.stake("0").catch(e => e); expect(tx.message).to.have.string( "You need to stake a positive token amount" ); }); it("It should approve stake amount in order to stake", async () => { const tx = await governanceStaking .stake(ethers.utils.parseEther("10000000")) .catch(e => e); expect(tx.message).not.to.be.empty; }); it("Withdraw 0 should withdraw everything", async () => { await expect(governanceStaking.withdrawStake("0")).to.revertedWith( /positive amount/ ); }); it("Should use overriden _transfer that handles productivity when using transferFrom which is defined in super erc20 contract", async () => { await expect( governanceStaking.transferFrom( founder.address, staker.address, ethers.utils.parseEther("10000000") ) ).to.reverted; }); it("it should get rewards for previous stakes when stake new amount of tokens", async () => { const rewardsPerBlock = await governanceStaking.getRewardsPerBlock(); await goodDollar.mint(founder.address, "200"); await goodDollar.approve(governanceStaking.address, "200"); const stakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1; await governanceStaking.stake("100"); await advanceBlocks(5); const gdaoBalanceBeforeGetRewards = await grep.balanceOfLocal( founder.address ); await governanceStaking.stake("100"); const getRewardsBlockNumber = await ethers.provider.getBlockNumber(); const gdaoBalanceAfterGetRewards = await grep.balanceOfLocal( founder.address ); const multiplier = getRewardsBlockNumber - stakeBlockNumber; const calculatedReward = rewardsPerBlock.mul(multiplier); // We calculate user rewards since it's the only staker so gets whole rewards so rewardsPerBlock * multipler(block that passed between stake and withdraw) await governanceStaking.withdrawStake("200"); expect(gdaoBalanceAfterGetRewards).to.be.equal( gdaoBalanceBeforeGetRewards.add(calculatedReward) ); }); it("it should distribute rewards properly when there is multiple stakers", async () => { const rewardsPerBlock = await governanceStaking.getRewardsPerBlock(); const stakingAmount = BN.from("100"); await goodDollar.mint(founder.address, stakingAmount); await goodDollar.mint(staker.address, stakingAmount); await goodDollar.mint(signers[0].address, stakingAmount); await goodDollar.mint(signers[1].address, stakingAmount); await goodDollar.approve(governanceStaking.address, stakingAmount); await goodDollar .connect(staker) .approve(governanceStaking.address, stakingAmount); await goodDollar .connect(signers[0]) .approve(governanceStaking.address, stakingAmount); await goodDollar .connect(signers[1]) .approve(governanceStaking.address, stakingAmount); await governanceStaking.stake(stakingAmount); const stakerOneGDAOBalanceAfterStake = await grep.balanceOfLocal( founder.address ); await governanceStaking.connect(staker).stake(stakingAmount.div(10)); const stakerTwoGDAOBalanceAfterStake = await grep.balanceOfLocal( staker.address ); await governanceStaking.connect(signers[0]).stake(stakingAmount.div(4)); const stakerThreeGDAOBalanceAfterStake = await grep.balanceOfLocal( signers[0].address ); const stakerFourStakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1; await governanceStaking.connect(signers[1]).stake(stakingAmount.div(5)); const stakerFourGDAOBalanceAfterStake = await grep.balanceOfLocal( signers[1].address ); await advanceBlocks(10); await governanceStaking.withdrawStake(stakingAmount); const stakerOneWithdrawBlockNumber = await ethers.provider.getBlockNumber(); const stakerOneGDAOBalanceAfterWithdraw = await grep.balanceOfLocal( founder.address ); await governanceStaking .connect(staker) .withdrawStake(stakingAmount.div(10)); const stakerTwoGDAOBalanceAfterWithdraw = await grep.balanceOfLocal( staker.address ); await governanceStaking .connect(signers[0]) .withdrawStake(stakingAmount.div(4)); const stakerThreeGDAOBalanceAfterWithdraw = await grep.balanceOfLocal( signers[0].address ); await governanceStaking .connect(signers[1]) .withdrawStake(stakingAmount.div(5)); const stakerFourGDAOBalanceAfterWithdraw = await grep.balanceOfLocal( signers[1].address ); const stakerOneRewardsCalculated = rewardsPerBlock .add(rewardsPerBlock.mul(100).div(110)) .add(rewardsPerBlock.mul(100).div(135)) .add( rewardsPerBlock .mul(100) .mul(stakerOneWithdrawBlockNumber - stakerFourStakeBlockNumber) .div(155) ) .add(BN.from("2")); const stakerTwoRewardsCalculated = rewardsPerBlock .mul(10) .div(110) .add(rewardsPerBlock.mul(10).div(135)) .add( rewardsPerBlock .mul(10) .mul(stakerOneWithdrawBlockNumber - stakerFourStakeBlockNumber) .div(155) ) .add(rewardsPerBlock.mul(10).div(55)) .add(BN.from("1")); const stakerThreeRewardsCalculated = rewardsPerBlock .mul(25) .div(135) .add( rewardsPerBlock .mul(25) .mul(stakerOneWithdrawBlockNumber - stakerFourStakeBlockNumber) .div(155) ) .add(rewardsPerBlock.mul(25).div(55)) .add(rewardsPerBlock.mul(25).div(45)) .add(BN.from("1")); const stakerFourRewardsCalculated = rewardsPerBlock .mul(20) .mul(stakerOneWithdrawBlockNumber - stakerFourStakeBlockNumber) .div(155) .add(rewardsPerBlock.mul(20).div(55)) .add(rewardsPerBlock.mul(20).div(45)) .add(rewardsPerBlock) .add(BN.from("1")); expect(stakerOneGDAOBalanceAfterWithdraw).to.be.equal( stakerOneGDAOBalanceAfterStake.add(stakerOneRewardsCalculated) ); expect(stakerTwoGDAOBalanceAfterWithdraw).to.be.equal( stakerTwoGDAOBalanceAfterStake.add(stakerTwoRewardsCalculated) ); expect(stakerThreeGDAOBalanceAfterWithdraw).to.be.equal( stakerThreeGDAOBalanceAfterStake.add(stakerThreeRewardsCalculated) ); expect(stakerFourGDAOBalanceAfterWithdraw).to.be.equal( stakerFourGDAOBalanceAfterStake.add(stakerFourRewardsCalculated) ); }); it("it should get staking reward even reward amount is too low", async () => { const rewardsPerBlock = await governanceStaking.getRewardsPerBlock(); console.log(rewardsPerBlock.toString()); const stakingAmount = BN.from("10000"); await goodDollar.mint(founder.address, stakingAmount); await goodDollar.mint(staker.address, stakingAmount); await goodDollar.approve(governanceStaking.address, stakingAmount); await goodDollar .connect(staker) .approve(governanceStaking.address, stakingAmount); await governanceStaking.stake(stakingAmount); const stakerOneGDAOBalanceAfterStake = await grep.balanceOfLocal( founder.address ); const stakerTwoStakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1; await governanceStaking.connect(staker).stake(stakingAmount.div(10000)); const stakerTwoGDAOBalanceAfterStake = await grep.balanceOfLocal( staker.address ); await advanceBlocks(10); await governanceStaking .connect(staker) .withdrawStake(stakingAmount.div(10000)); const stakerTwoWithdrawBlockNumber = await ethers.provider.getBlockNumber(); await governanceStaking.withdrawStake(stakingAmount); const stakerOneGDAOBalanceAfterWithdraw = await grep.balanceOfLocal( founder.address ); const stakerTwoGDAOBalanceAfterWithdraw = await grep.balanceOfLocal( staker.address ); const calculatedRewardsStakerOne = rewardsPerBlock .add( rewardsPerBlock .mul(10000) .mul(stakerTwoWithdrawBlockNumber - stakerTwoStakeBlockNumber) .div(10001) ) .add(rewardsPerBlock); const calculatedRewardsStakerTwo = rewardsPerBlock .mul(1) .mul(stakerTwoWithdrawBlockNumber - stakerTwoStakeBlockNumber) .div(10001) .add(BN.from("1")); expect(stakerOneGDAOBalanceAfterWithdraw).to.be.equal( stakerOneGDAOBalanceAfterStake.add(calculatedRewardsStakerOne) ); expect(stakerTwoGDAOBalanceAfterWithdraw).to.be.equal( stakerTwoGDAOBalanceAfterStake.add(calculatedRewardsStakerTwo) ); }); it("it should mint rewards properly when withdrawRewards", async () => { const governanceStakingFactory = await ethers.getContractFactory( "GovernanceStaking" ); const simpleGovernanceStaking = await governanceStakingFactory.deploy( nameService.address ); await setDAOAddress("GDAO_STAKING", simpleGovernanceStaking.address); await goodDollar.mint(founder.address, "100"); const rewardsPerBlock = await simpleGovernanceStaking.getRewardsPerBlock(); await goodDollar.approve(simpleGovernanceStaking.address, "200"); const stakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1; await simpleGovernanceStaking.stake("200"); const GDAOBalanceAfterStake = await grep.balanceOfLocal(founder.address); await advanceBlocks(100); await simpleGovernanceStaking.withdrawRewards(); const withdrawRewardsBlockNumber = await ethers.provider.getBlockNumber(); const GDAOBalanceAfterWithdraw = await grep.balanceOfLocal(founder.address); expect(GDAOBalanceAfterWithdraw).to.be.equal( GDAOBalanceAfterStake.add( rewardsPerBlock.mul(withdrawRewardsBlockNumber - stakeBlockNumber) ) ); await setDAOAddress("GDAO_STAKING", governanceStaking.address); }); it("it should not overmint rewards when staker withdraw their rewards", async () => { const governanceStakingFactory = await ethers.getContractFactory( "GovernanceStaking" ); const simpleGovernanceStaking = await governanceStakingFactory.deploy( nameService.address ); await setDAOAddress("GDAO_STAKING", simpleGovernanceStaking.address); const overmintTesterFactory = await ethers.getContractFactory( "OverMintTester" ); const overMintTester = await overmintTesterFactory.deploy( goodDollar.address, simpleGovernanceStaking.address, grep.address ); await goodDollar.mint(overMintTester.address, "100"); const rewardsPerBlock = await simpleGovernanceStaking.getRewardsPerBlock(); const stakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1; await overMintTester.stake(); const GDAOBalanceAfterStake = await grep.balanceOfLocal( overMintTester.address ); await advanceBlocks(100); await overMintTester.overMintTest(); const withdrawRewardsBlockNumber = await ethers.provider.getBlockNumber(); const GDAOBalanceAfterWithdrawReward = await grep.balanceOfLocal( overMintTester.address ); await advanceBlocks(20); await overMintTester.overMintTest(); const secondWithdrawRewardsBlockNumber = await ethers.provider.getBlockNumber(); const GDAOBalanceAfterSecondWithdrawReward = await grep.balanceOfLocal( overMintTester.address ); expect(GDAOBalanceAfterWithdrawReward).to.be.gt(GDAOBalanceAfterStake); expect( GDAOBalanceAfterWithdrawReward.sub(GDAOBalanceAfterStake) ).to.be.equal( rewardsPerBlock.mul(withdrawRewardsBlockNumber - stakeBlockNumber) ); expect(GDAOBalanceAfterSecondWithdrawReward).to.be.gt( GDAOBalanceAfterWithdrawReward ); expect( GDAOBalanceAfterSecondWithdrawReward.sub(GDAOBalanceAfterWithdrawReward) ).to.be.equal( rewardsPerBlock.mul( secondWithdrawRewardsBlockNumber - withdrawRewardsBlockNumber ) ); await setDAOAddress("GDAO_STAKING", governanceStaking.address); }); it("it should accrue previous rewards based on previous monthly rate on monthly rewards rate change to 0", async () => { const ictrl = await ethers.getContractAt( "Controller", controller, schemeMock ); const governanceStakingFactory = await ethers.getContractFactory( "GovernanceStaking" ); const simpleGovernanceStaking = await governanceStakingFactory.deploy( nameService.address ); const rewardsPerBlock = await simpleGovernanceStaking.getRewardsPerBlock(); await goodDollar.mint(founder.address, "100"); await goodDollar.approve(simpleGovernanceStaking.address, "100"); const stakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1; await simpleGovernanceStaking.stake("100"); await advanceBlocks(4); let encodedCall = governanceStakingFactory.interface.encodeFunctionData( "setMonthlyRewards", ["0"] // Give 0.0001 GDAO per block so 17.28 GDAO per month ); await ictrl.genericCall( simpleGovernanceStaking.address, encodedCall, avatar, 0 ); const withdrawBlockNumber = await ethers.provider.getBlockNumber(); const multiplier = withdrawBlockNumber - stakeBlockNumber; const calculatedReward = rewardsPerBlock.mul(multiplier); const pendingReward = await simpleGovernanceStaking[ "getUserPendingReward(address)" ](founder.address); expect(pendingReward).to.equal(calculatedReward); await advanceBlocks(4); }); function rdiv(x: BigNumber, y: BigNumber) { return x.mul(BN.from("10").pow(27)).add(y.div(2)).div(y); } });