UNPKG

@gooddollar/goodprotocol

Version:
1,011 lines (929 loc) 36.6 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, deployUniswap } from "../helpers"; import ContributionCalculation from "@gooddollar/goodcontracts/stakingModel/build/contracts/ContributionCalculation.json"; import { getStakingFactory } from "../helpers"; const BN = ethers.BigNumber; export const NULL_ADDRESS = ethers.constants.AddressZero; export const BLOCK_INTERVAL = 30; describe("StakersDistribution - staking with GD and get Rewards in GDAO", () => { let dai: Contract; let cDAI: Contract; let goodReserve: GoodReserveCDai; let stakersDistribution: Contract; let goodFundManager: Contract; let simpleStaking: Contract; let simpleUsdcStaking: Contract; let usdcUsdOracle: Contract; let usdc: Contract; let cUsdc: Contract; let comp: Contract; let grep: GReputation; let avatar, goodDollar, identity, marketMaker: GoodMarketMaker, contribution, controller, founder, staker, sender, receiver, schemeMock, signers, nameService, initializeToken, genericCall, setDAOAddress, daiEthOracle, ethUsdOracle, gasFeeOracle, daiUsdOracle, compUsdOracle: Contract, deployDaiStaking; before(async () => { [founder, staker, sender, receiver, ...signers] = await ethers.getSigners(); schemeMock = signers.pop(); const cdaiFactory = await ethers.getContractFactory("cDAIMock"); const cUsdcFactory = await ethers.getContractFactory("cUSDCMock"); const usdcFactory = await ethers.getContractFactory("USDCMock"); const goodFundManagerFactory = await ethers.getContractFactory( "GoodFundManager" ); const stakersDistributiongFactory = await ethers.getContractFactory( "StakersDistribution" ); let { controller: ctrl, avatar: av, gd, identity, daoCreator, nameService: ns, setDAOAddress: sda, setSchemes, marketMaker: mm, daiAddress, cdaiAddress, reserve, reputation, setReserveToken, genericCall: gc, COMP } = await loadFixture(createDAO); genericCall = gc; 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..."); await setDAOAddress("MARKET_MAKER", marketMaker.address); await setDAOAddress("FUND_MANAGER", goodFundManager.address); const tokenUsdOracleFactory = await ethers.getContractFactory( "BatUSDMockOracle" ); compUsdOracle = await ( await ethers.getContractFactory("CompUSDMockOracle") ).deploy(); daiUsdOracle = await tokenUsdOracleFactory.deploy(); usdc = await usdcFactory.deploy(); cUsdc = await cUsdcFactory.deploy(usdc.address); usdcUsdOracle = await tokenUsdOracleFactory.deploy(); comp = COMP; await setDAOAddress("COMP", comp.address); const uniswap = await deployUniswap(comp, dai); const router = uniswap.router; await setDAOAddress("UNISWAP_ROUTER", router.address); let simpleStakingFactory = await getStakingFactory("GoodCompoundStakingV2"); simpleUsdcStaking = await simpleStakingFactory .deploy() .then(async contract => { await contract.init( usdc.address, cUsdc.address, nameService.address, "Good USDC", "gUSDC", "172800", usdcUsdOracle.address, compUsdOracle.address, [usdc.address, daiAddress] ); return contract; }); deployDaiStaking = async () => { return simpleStakingFactory.deploy().then(async contract => { await contract.init( dai.address, cDAI.address, nameService.address, "Good DAI", "gDAI", "200", daiUsdOracle.address, compUsdOracle.address, [] ); return contract; }); }; simpleStaking = await deployDaiStaking(); const ictrl = await ethers.getContractAt( "Controller", controller, schemeMock ); const currentBlockNumber = await ethers.provider.getBlockNumber(); let encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "1000", simpleStaking.address, currentBlockNumber, currentBlockNumber + 1000, false ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); stakersDistribution = await upgrades.deployProxy( stakersDistributiongFactory, [nameService.address], { kind: "uups" } ); await setDAOAddress("GDAO_STAKERS", stakersDistribution.address); }); it("it should have 2M monthly Reputation distribution", async () => { const monthlyReputationDistribution = await stakersDistribution.monthlyReputationDistribution(); expect(monthlyReputationDistribution).to.be.equal( ethers.utils.parseEther("2000000") ); }); it("it should have 0 monthly rewards since staking amount was zero while initializing stakersDistribution", async () => { const rewardsPerBlock = await stakersDistribution.rewardsPerBlock( simpleStaking.address ); expect(rewardsPerBlock).to.be.equal(0); }); it("It should update monthly rewards according to staking amount of staking contract after one month passed from initialized", async () => { const stakingAmount = ethers.utils.parseEther("1000"); const rewardsPerBlockBeforeStake = await stakersDistribution.rewardsPerBlock(simpleStaking.address); await dai["mint(address,uint256)"](staker.address, stakingAmount.mul(2)); await dai .connect(staker) .approve(simpleStaking.address, stakingAmount.mul(2)); await simpleStaking.connect(staker).stake(stakingAmount, 0, false); await increaseTime(86700 * 30); await simpleStaking.connect(staker).stake(stakingAmount, 0, false); const rewardsPerBlockAfterStake = await stakersDistribution.rewardsPerBlock( simpleStaking.address ); await simpleStaking .connect(staker) .withdrawStake(stakingAmount.mul(2), false); const rewardsPerBlockAfterWithdraw = await stakersDistribution.rewardsPerBlock(simpleStaking.address); const chainBlockPerMonth = await stakersDistribution.getChainBlocksPerMonth(); expect(rewardsPerBlockBeforeStake).to.be.equal(BN.from("0")); expect(rewardsPerBlockAfterStake).to.be.equal( ethers.utils.parseEther("2000000").div(chainBlockPerMonth) ); expect(rewardsPerBlockAfterStake).to.be.equal(rewardsPerBlockAfterWithdraw); }); it("it should not be set monthly reputation when not Avatar", async () => { const transaction = await stakersDistribution .setMonthlyReputationDistribution("1000000") .catch(e => e); expect(transaction.message).to.have.string( "only avatar can call this method" ); }); it("it should set monthly reputation when Avatar", async () => { let encoded = stakersDistribution.interface.encodeFunctionData( "setMonthlyReputationDistribution", [ethers.utils.parseEther("1000000")] ); await genericCall(stakersDistribution.address, encoded); const monthlyReputationDistribution = await stakersDistribution.monthlyReputationDistribution(); expect(monthlyReputationDistribution).to.be.equal( ethers.utils.parseEther("1000000") ); encoded = stakersDistribution.interface.encodeFunctionData( "setMonthlyReputationDistribution", [ethers.utils.parseEther("2000000")] ); await genericCall(stakersDistribution.address, encoded); }); it("it should distribute monthly rewards according to staking amount of contracts so in this particular case simpleStaking contract should get %75 of the monthly rewards ", async () => { const goodFundManagerFactory = await ethers.getContractFactory( "GoodFundManager" ); const simpleStaking1 = await deployDaiStaking(); const ictrl = await ethers.getContractAt( "Controller", controller, schemeMock ); const currentBlockNumber = await ethers.provider.getBlockNumber(); let encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "1000", simpleStaking1.address, currentBlockNumber, currentBlockNumber + 1000, false ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); const stakingAmount = ethers.utils.parseEther("1000"); const rewardsPerBlockBeforeStakeContractOne = await stakersDistribution.rewardsPerBlock(simpleStaking.address); const rewardsPerBlockBeforeStakeContractTwo = await stakersDistribution.rewardsPerBlock(simpleStaking1.address); await dai["mint(address,uint256)"](staker.address, stakingAmount.mul(100)); await dai .connect(staker) .approve(simpleStaking.address, stakingAmount.mul(75)); await simpleStaking.connect(staker).stake(stakingAmount.mul(50), 0, false); await dai .connect(staker) .approve(simpleStaking1.address, stakingAmount.mul(25)); await simpleStaking1.connect(staker).stake(stakingAmount.mul(25), 0, false); await increaseTime(86700 * 30); // Increase one month await simpleStaking.connect(staker).stake(stakingAmount.mul(25), 0, false); const rewardsPerBlockAfterStakeContractOne = await stakersDistribution.rewardsPerBlock(simpleStaking.address); const rewardsPerBlockAftereStakeContractTwo = await stakersDistribution.rewardsPerBlock(simpleStaking1.address); await simpleStaking .connect(staker) .withdrawStake(stakingAmount.mul(75), false); expect(rewardsPerBlockAfterStakeContractOne).to.be.equal( rewardsPerBlockAftereStakeContractTwo.mul(3).add(1) ); // added 1 cause of precision loss encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "1000", simpleStaking1.address, currentBlockNumber, currentBlockNumber + 1000, true ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); }); it("It should not update monthly rewards if staking contract's blockEnd Passed", async () => { const goodFundManagerFactory = await ethers.getContractFactory( "GoodFundManager" ); const ictrl = await ethers.getContractAt( "Controller", controller, schemeMock ); const currentBlockNumber = await ethers.provider.getBlockNumber(); let encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "1000", simpleStaking.address, currentBlockNumber - 5, currentBlockNumber + 20, false ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); const stakingAmount = ethers.utils.parseEther("1000"); await dai["mint(address,uint256)"](staker.address, stakingAmount.mul(2)); await dai .connect(staker) .approve(simpleStaking.address, stakingAmount.mul(2)); const rewardsPerBlockBeforeStake = await stakersDistribution.rewardsPerBlock(simpleStaking.address); await simpleStaking.connect(staker).stake(stakingAmount, 0, false); await advanceBlocks(40); await increaseTime(86700 * 30); // Increase one month const stakerGDAOBalanceBeforeStake = await grep.balanceOf(staker.address); await simpleStaking.connect(staker).stake(stakingAmount, 0, false); const stakerGDAOBalanceAfterStake = await grep.balanceOf(staker.address); await simpleStaking.connect(staker).withdrawRewards(); const rewardsPerBlockAfterStake = await stakersDistribution.rewardsPerBlock( simpleStaking.address ); const GDAOBalanceBeforeWithdraw = await grep.balanceOf(staker.address); await advanceBlocks(10); await simpleStaking.connect(staker).withdrawStake(stakingAmount, false); const GDAOBalanceAfterWithdraw = await grep.balanceOf(staker.address); expect(rewardsPerBlockAfterStake).to.not.be.equal( rewardsPerBlockBeforeStake ); // Should update rewards per block every stake/withdraw expect(GDAOBalanceBeforeWithdraw).to.be.equal(GDAOBalanceAfterWithdraw); // Should not earn any GDAO since simplestaking blockend passed expect(stakerGDAOBalanceAfterStake.gt(stakerGDAOBalanceBeforeStake)).to.be .true; }); it("it should give distribute if blockend passed but some of the rewards during reward period was not distributed", async () => { const goodFundManagerFactory = await ethers.getContractFactory( "GoodFundManager" ); const ictrl = await ethers.getContractAt( "Controller", controller, schemeMock ); const currentBlockNumber = await ethers.provider.getBlockNumber(); let encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "1000", simpleStaking.address, currentBlockNumber - 5, currentBlockNumber + 20, false ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); const stakingAmount = ethers.utils.parseEther("1000"); await simpleStaking.connect(staker).withdrawStake(stakingAmount, false); await dai["mint(address,uint256)"](staker.address, stakingAmount); await dai.connect(staker).approve(simpleStaking.address, stakingAmount); const rewardsPerBlock = await stakersDistribution.rewardsPerBlock( simpleStaking.address ); const blockNumberOfStake = (await ethers.provider.getBlockNumber()) + 1; await simpleStaking.connect(staker).stake(stakingAmount, 0, false); await advanceBlocks(30); const GDAOBalanceBeforeWithdraw = await grep.balanceOf(staker.address); await simpleStaking.connect(staker).withdrawStake(stakingAmount, false); const GDAOBalanceAfterWithdraw = await grep.balanceOf(staker.address); expect(GDAOBalanceAfterWithdraw).to.be.gt(GDAOBalanceBeforeWithdraw); expect(GDAOBalanceAfterWithdraw).to.be.equal( GDAOBalanceBeforeWithdraw.add( rewardsPerBlock.mul(currentBlockNumber + 20 - blockNumberOfStake) ) ); }); it("it should not increaseProductivity of staking contract which is blacklisted", async () => { const goodFundManagerFactory = await ethers.getContractFactory( "GoodFundManager" ); const simpleStaking1 = await deployDaiStaking(); const ictrl = await ethers.getContractAt( "Controller", controller, schemeMock ); const currentBlockNumber = await ethers.provider.getBlockNumber(); let encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "1000", simpleStaking1.address, currentBlockNumber - 5, currentBlockNumber + 20, true ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); const stakingAmount = ethers.utils.parseEther("1000"); await dai["mint(address,uint256)"](staker.address, stakingAmount); await dai.connect(staker).approve(simpleStaking1.address, stakingAmount); await simpleStaking1.connect(staker).stake(stakingAmount, 0, false); const productivityOfStaker = await stakersDistribution.getProductivity( simpleStaking1.address, staker.address ); expect(productivityOfStaker[0]).to.be.equal(0); }); it("it should not decreaseProductivity of staking contract which is blacklisted", async () => { const goodFundManagerFactory = await ethers.getContractFactory( "GoodFundManager" ); const simpleStaking1 = await deployDaiStaking(); const ictrl = await ethers.getContractAt( "Controller", controller, schemeMock ); const currentBlockNumber = await ethers.provider.getBlockNumber(); let encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "100000", simpleStaking1.address, currentBlockNumber - 5, currentBlockNumber + 20, false ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); await increaseTime(86700 * 30); // Increase one month const stakingAmount = ethers.utils.parseEther("1000"); await dai["mint(address,uint256)"](staker.address, stakingAmount); await dai.connect(staker).approve(simpleStaking1.address, stakingAmount); await simpleStaking1.connect(staker).stake(stakingAmount, 0, false); await advanceBlocks(5); //should accumulate some gdao rewards let pendingGDAO = await stakersDistribution.getUserPendingRewards( [simpleStaking1.address], staker.address ); expect(pendingGDAO).gt(0); encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "1000", simpleStaking1.address, currentBlockNumber - 5, currentBlockNumber + 20, true ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); await simpleStaking1.connect(staker).withdrawStake(stakingAmount, false); const productivityOfStaker = await stakersDistribution.getProductivity( simpleStaking1.address, staker.address ); expect(productivityOfStaker[0]).to.be.equal(stakingAmount); const repBefore = await grep["balanceOf(address)"](staker.address); await stakersDistribution.claimReputation(staker.address, [ simpleStaking1.address ]); const repAfter = await grep["balanceOf(address)"](staker.address); expect(repBefore).to.equal(repAfter); //should not have been awarded rep accumulated before blacklisting contract pendingGDAO = await stakersDistribution.getUserPendingRewards( [simpleStaking1.address], staker.address ); expect(pendingGDAO).equal(0); //should have 0 pending after contract was blacklisted }); it("it should not earn rewards when current block < startBlock", async () => { const goodFundManagerFactory = await ethers.getContractFactory( "GoodFundManager" ); const simpleStaking1 = await deployDaiStaking(); const ictrl = await ethers.getContractAt( "Controller", controller, schemeMock ); const currentBlockNumber = await ethers.provider.getBlockNumber(); let encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "1000", simpleStaking1.address, currentBlockNumber + 500, currentBlockNumber + 1000, false ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); const stakingAmount = ethers.utils.parseEther("1000"); const userProductivityBeforeStaking = await stakersDistribution.getProductivity( simpleStaking1.address, staker.address ); await dai["mint(address,uint256)"](staker.address, stakingAmount); await dai.connect(staker).approve(simpleStaking1.address, stakingAmount); await simpleStaking1.connect(staker).stake(stakingAmount, 0, false); const userProductivityAfterStaking = await stakersDistribution.getProductivity( simpleStaking1.address, staker.address ); await advanceBlocks(10); const userPendingRewards = await stakersDistribution.getUserPendingReward( simpleStaking1.address, currentBlockNumber + 500, currentBlockNumber + 1000, staker.address ); expect(userProductivityAfterStaking[0]).to.be.equal(stakingAmount); expect(userProductivityAfterStaking[0]).to.be.gt( userProductivityBeforeStaking[0] ); expect(userPendingRewards).to.be.equal(0); encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "1000", simpleStaking1.address, currentBlockNumber + 500, currentBlockNumber + 1000, true ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); }); it("Accumulated per share has enough precision when reward << totalproductivity", async () => { const goodFundManagerFactory = await ethers.getContractFactory( "GoodFundManager" ); const simpleStaking1 = await deployDaiStaking(); const ictrl = await ethers.getContractAt( "Controller", controller, schemeMock ); const currentBlockNumber = await ethers.provider.getBlockNumber(); let encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "1000", simpleStaking1.address, currentBlockNumber - 10, currentBlockNumber + 100, false ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); const stakingAmount = ethers.utils.parseEther("10000000000"); await dai["mint(address,uint256)"](staker.address, stakingAmount); // 10 billion dai to stake await dai.connect(staker).approve(simpleStaking1.address, stakingAmount); const stakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1; await simpleStaking1.connect(staker).stake(stakingAmount, 0, false); const rewardsPerBlock = await stakersDistribution.rewardsPerBlock( simpleStaking1.address ); await advanceBlocks(4); const gdaoBalanceBeforeWithdraw = await grep.balanceOf(staker.address); await simpleStaking1.connect(staker).withdrawStake(stakingAmount, false); const withdrawBlockNumber = await ethers.provider.getBlockNumber(); await stakersDistribution.claimReputation(staker.address, [ simpleUsdcStaking.address, simpleStaking.address ]); const gdaoBalanceAfterWithdraw = await grep.balanceOf(staker.address); const calculatedNewBalance = gdaoBalanceBeforeWithdraw.add( rewardsPerBlock.mul(withdrawBlockNumber - stakeBlockNumber) ); expect(gdaoBalanceAfterWithdraw).to.be.gt(gdaoBalanceBeforeWithdraw); expect(gdaoBalanceAfterWithdraw).to.be.equal(calculatedNewBalance); encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "1000", simpleStaking1.address, currentBlockNumber + 500, currentBlockNumber + 1000, true ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); }); it("it should distribute rewards properly when staking contract's token is different decimals than 18", async () => { const goodFundManagerFactory = await ethers.getContractFactory( "GoodFundManager" ); const ictrl = await ethers.getContractAt( "Controller", controller, schemeMock ); const currentBlockNumber = await ethers.provider.getBlockNumber(); let encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "1000", simpleStaking.address, currentBlockNumber - 5, currentBlockNumber + 200, false ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "1000", simpleUsdcStaking.address, currentBlockNumber - 5, currentBlockNumber + 200, false ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); const stakingAmountDai = ethers.utils.parseEther("10000"); const stakingAmountUsdc = ethers.utils.parseUnits("10000", 6); await dai["mint(address,uint256)"](staker.address, stakingAmountDai); await dai.connect(staker).approve(simpleStaking.address, stakingAmountDai); await usdc["mint(address,uint256)"](staker.address, stakingAmountUsdc); await usdc .connect(staker) .approve(simpleUsdcStaking.address, stakingAmountUsdc); await simpleStaking.connect(staker).stake(stakingAmountDai, 0, false); await increaseTime(86700 * 30); // Increase one month await simpleUsdcStaking.connect(staker).stake(stakingAmountUsdc, 0, false); const usdcStakingProductivity = await stakersDistribution.getProductivity( simpleUsdcStaking.address, staker.address ); const daiStakingProductivity = await stakersDistribution.getProductivity( simpleStaking.address, staker.address ); await advanceBlocks(10); const UserPendingGdaos = await stakersDistribution.getUserPendingRewards( [simpleStaking.address, simpleUsdcStaking.address], staker.address ); expect(UserPendingGdaos).to.be.gt(0); const usdcStakingPendingGdaos = await stakersDistribution.getUserPendingReward( simpleUsdcStaking.address, currentBlockNumber - 5, currentBlockNumber + 200, staker.address ); expect(usdcStakingPendingGdaos).to.be.gt(0); const daiStakingPendingGdaos = await stakersDistribution.getUserPendingReward( simpleStaking.address, currentBlockNumber - 5, currentBlockNumber + 200, staker.address ); expect(daiStakingPendingGdaos).to.be.gt(0); const usdcStakingRewardsPerBlock = await stakersDistribution.rewardsPerBlock(simpleUsdcStaking.address); const daiStakingRewardsPerBlock = await stakersDistribution.rewardsPerBlock( simpleStaking.address ); expect(usdcStakingRewardsPerBlock).to.equal(daiStakingRewardsPerBlock); await simpleUsdcStaking .connect(staker) .withdrawStake(stakingAmountUsdc, false); await simpleStaking.connect(staker).withdrawStake(stakingAmountDai, false); await stakersDistribution.claimReputation(staker.address, [ simpleUsdcStaking.address, simpleStaking.address ]); expect(UserPendingGdaos).to.be.equal( usdcStakingPendingGdaos.add(daiStakingPendingGdaos) ); expect(usdcStakingProductivity[0]).to.be.equal(stakingAmountUsdc.mul(1e12)); // mul with 1e12 since usdc in 6 decimals but we keep productivity in 18 decimals expect(daiStakingProductivity[0]).to.be.equal(stakingAmountDai); expect(usdcStakingProductivity[0]).to.be.equal(daiStakingProductivity[0]); expect(usdcStakingProductivity[1]).to.be.equal(daiStakingProductivity[1]); expect(usdcStakingRewardsPerBlock).to.be.equal(daiStakingRewardsPerBlock); }); it("should be able to transfer staking token when using stakersdistribution", async () => { const stakingAmountDai = ethers.utils.parseEther("10000"); const stakingAmountUsdc = ethers.utils.parseUnits("10000", 6); await dai["mint(address,uint256)"](staker.address, stakingAmountDai); await dai.connect(staker).approve(simpleStaking.address, stakingAmountDai); await usdc["mint(address,uint256)"](staker.address, stakingAmountUsdc); await usdc .connect(staker) .approve(simpleUsdcStaking.address, stakingAmountUsdc); await simpleStaking.connect(staker).stake(stakingAmountDai, 0, false); await increaseTime(86700 * 30); // Increase one month await simpleUsdcStaking.connect(staker).stake(stakingAmountUsdc, 0, false); await advanceBlocks(10); await simpleUsdcStaking .connect(staker) .transfer(signers[1].address, stakingAmountUsdc); expect(await simpleUsdcStaking.balanceOf(signers[1].address)).to.eq( stakingAmountUsdc ); expect(await simpleUsdcStaking.balanceOf(staker.address)).to.eq(0); // await expect( // simpleStaking // .connect(staker) // .transfer(signers[1].address, ethers.utils.parseEther("10000")) // ).to.not.reverted; }); it("it should distribute rewards properly when transeferring a staking contract's token that's different decimals than 18 decimals", async () => { const goodFundManagerFactory = await ethers.getContractFactory( "GoodFundManager" ); const ictrl = await ethers.getContractAt( "Controller", controller, schemeMock ); const currentBlockNumber = await ethers.provider.getBlockNumber(); let encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "1000", simpleUsdcStaking.address, currentBlockNumber - 5, currentBlockNumber + 200, false ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); const stakingAmountUsdc = ethers.utils.parseUnits("10000", 6); await usdc["mint(address,uint256)"](sender.address, stakingAmountUsdc); await usdc .connect(sender) .approve(simpleUsdcStaking.address, stakingAmountUsdc); await increaseTime(86700 * 30); // Increase one month await simpleUsdcStaking.connect(sender).stake(stakingAmountUsdc, 0, false); await simpleUsdcStaking .connect(sender) .transfer(receiver.address, stakingAmountUsdc); await advanceBlocks(10); const UserPendingGdaos = await stakersDistribution.getUserPendingRewards( [simpleUsdcStaking.address], receiver.address ); expect(UserPendingGdaos).to.be.gt(0); expect(UserPendingGdaos.toString().length).to.be.gt(18); const usdcStakingPendingGdaos = await stakersDistribution.getUserPendingReward( simpleUsdcStaking.address, currentBlockNumber - 5, currentBlockNumber + 200, receiver.address ); expect(usdcStakingPendingGdaos).to.be.gt(0); expect(usdcStakingPendingGdaos.toString().length).to.be.gt(18); }); it("should get user minted and pending rewards", async () => { const goodFundManagerFactory = await ethers.getContractFactory( "GoodFundManager" ); const simpleStaking1 = await deployDaiStaking(); const ictrl = await ethers.getContractAt( "Controller", controller, schemeMock ); const currentBlockNumber = await ethers.provider.getBlockNumber(); let encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "100000", simpleStaking1.address, currentBlockNumber - 5, currentBlockNumber + 20, false ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); let [userMintedRewardBeforeStake, userPendingRewardBeforeStake] = await getUserMintedAndPendingRewards( staker.address, simpleStaking1.address ); expect(userMintedRewardBeforeStake).to.eq("0"); expect(userPendingRewardBeforeStake).to.eq("0"); const stakingAmount = ethers.utils.parseEther("1000"); await dai["mint(address,uint256)"](staker.address, stakingAmount); await dai.connect(staker).approve(simpleStaking1.address, stakingAmount); await simpleStaking1.connect(staker).stake(stakingAmount, 0, false); await advanceBlocks(5); //should accumulate some gdao rewards let [userMintedRewardAfterStake, userPendingRewardAfterStake] = await getUserMintedAndPendingRewards( staker.address, simpleStaking1.address ); expect(userMintedRewardAfterStake).eq(0); expect(userPendingRewardAfterStake).gt(0); await stakersDistribution.claimReputation(staker.address, [ simpleStaking1.address ]); let [userMintedRewardAfterClaim, userPendingRewardAfterClaim] = await getUserMintedAndPendingRewards( staker.address, simpleStaking1.address ); expect(userMintedRewardAfterClaim).gt(0); expect(userPendingRewardAfterClaim).eq(0); }); async function getUserMintedAndPendingRewards( stakerAddress, stakingContractAddress ) { const userMintedAndPending = await stakersDistribution.getUserMintedAndPending( [stakingContractAddress], stakerAddress ); const userMintedReward = userMintedAndPending[0]; const userPendingReward = userMintedAndPending[1]; return [userMintedReward, userPendingReward]; } it("should update user rewards when monthly rate has changed", async () => { const goodFundManagerFactory = await ethers.getContractFactory( "GoodFundManager" ); const simpleStaking1 = await deployDaiStaking(); const ictrl = await ethers.getContractAt( "Controller", controller, schemeMock ); const currentBlockNumber = await ethers.provider.getBlockNumber(); let encodedData = goodFundManagerFactory.interface.encodeFunctionData( "setStakingReward", [ "100000", simpleStaking1.address, currentBlockNumber - 5, currentBlockNumber + 20, false ] // set 10 gd per block ); await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0); let [userMintedRewardBeforeStake, userPendingRewardBeforeStake] = await getUserMintedAndPendingRewards( staker.address, simpleStaking1.address ); expect(userMintedRewardBeforeStake).to.eq("0"); expect(userPendingRewardBeforeStake).to.eq("0"); const stakingAmount = ethers.utils.parseEther("1000"); await dai["mint(address,uint256)"](staker.address, stakingAmount); await dai.connect(staker).approve(simpleStaking1.address, stakingAmount); await simpleStaking1.connect(staker).stake(stakingAmount, 0, false); await advanceBlocks(5); //should accumulate some gdao rewards const rewardsPerBlockBefore = await stakersDistribution.rewardsPerBlock( simpleStaking1.address ); // reduce rewarsd speed by half from 2m to 1m let encoded = stakersDistribution.interface.encodeFunctionData( "setMonthlyReputationDistribution", [ethers.utils.parseEther("1000000")] ); await genericCall(stakersDistribution.address, encoded); const rewardsPerBlockAfter = await stakersDistribution.rewardsPerBlock( simpleStaking1.address ); let [userMintedRewardAfterStake, userPendingRewardAfterStake] = await getUserMintedAndPendingRewards( staker.address, simpleStaking1.address ); expect(userMintedRewardAfterStake).eq(0); expect(userPendingRewardAfterStake).gt(0); await advanceBlocks(4); //should accumulate some gdao rewards let [userMintedRewardAfterUpdate, userPendingRewardAfterUpdate] = await getUserMintedAndPendingRewards( staker.address, simpleStaking1.address ); expect(userMintedRewardAfterUpdate).eq(0); // rewards speed has been reduced by half, expect(rewardsPerBlockAfter).lt(rewardsPerBlockBefore.mul(55).div(100)); // was forced 4 blocks to pass since reward update expect(userPendingRewardAfterUpdate).eq( userPendingRewardAfterStake.add(rewardsPerBlockAfter.mul(4)) ); }); });