@gooddollar/goodprotocol
Version:
GoodDollar Protocol
1,311 lines (1,196 loc) • 55.8 kB
text/typescript
import { ethers, upgrades } from "hardhat";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { Contract } from "ethers";
import { expect } from "chai";
import { GoodMarketMaker, GoodReserveCDai } from "../../types";
import { createDAO, advanceBlocks, getStakingFactory } from "../helpers";
const BN = ethers.BigNumber;
export const NULL_ADDRESS = ethers.constants.AddressZero;
export const BLOCK_INTERVAL = 30;
describe("SimpleDAISTAking - staking with cDAI mocks", () => {
let dai: Contract;
let cDAI, cDAI1, cDAI2, cDAI3: Contract;
let comp: Contract;
let gasFeeOracle,
daiEthOracle: Contract,
ethUsdOracle: Contract,
daiUsdOracle: Contract,
compUsdOracle: Contract;
let goodReserve: GoodReserveCDai;
let goodCompoundStaking;
let goodFundManager: Contract;
let avatar,
identity,
marketMaker: GoodMarketMaker,
controller,
founder,
staker,
schemeMock,
signers,
nameService,
setDAOAddress,
initializeToken,
goodCompoundStakingFactory,
deployStaking,
runAsAvatarOnly;
before(async () => {
[founder, staker, ...signers] = await ethers.getSigners();
schemeMock = signers.pop();
const cdaiFactory = await ethers.getContractFactory("cDAIMock");
const goodFundManagerFactory = await ethers.getContractFactory(
"GoodFundManager"
);
let {
controller: ctrl,
avatar: av,
gd,
identity,
daoCreator,
nameService: ns,
setDAOAddress: sda,
setSchemes,
marketMaker: mm,
daiAddress,
cdaiAddress,
reserve,
setReserveToken,
runAsAvatarOnly: raao
} = await loadFixture(createDAO);
dai = await ethers.getContractAt("DAIMock", daiAddress);
cDAI = await ethers.getContractAt("cDAIMock", cdaiAddress);
avatar = av;
controller = ctrl;
setDAOAddress = sda;
nameService = ns;
runAsAvatarOnly = raao;
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" }
);
console.log("Deployed goodfund manager", {
manager: goodFundManager.address
});
marketMaker = mm;
console.log("setting permissions...");
const tokenUsdOracleFactory = await ethers.getContractFactory(
"BatUSDMockOracle"
);
daiUsdOracle = await tokenUsdOracleFactory.deploy();
const daiFactory = await ethers.getContractFactory("DAIMock");
await setDAOAddress("UNISWAP_ROUTER", signers[0].address); // need this address for initialize simplestaking
const compUsdOracleFactory = await ethers.getContractFactory(
"CompUSDMockOracle"
);
compUsdOracle = await compUsdOracleFactory.deploy();
goodCompoundStakingFactory = await getStakingFactory(
"GoodCompoundStakingV2"
);
deployStaking = async (token, itoken) => {
return goodCompoundStakingFactory.deploy().then(async contract => {
await contract.init(
token || dai.address,
itoken || cDAI.address,
nameService.address,
"Good DAI",
"gDAI",
"172800",
daiUsdOracle.address,
compUsdOracle.address,
[]
);
return contract;
});
};
//give reserve generic call permission
goodCompoundStaking = await deployStaking();
await setDAOAddress("FUND_MANAGER", goodFundManager.address);
console.log("initializing marketmaker...");
cDAI1 = await cdaiFactory.deploy(dai.address);
const cdaiLowWorthFactory = await ethers.getContractFactory(
"cDAILowWorthMock"
);
cDAI2 = await cdaiLowWorthFactory.deploy(dai.address);
const cdaiNonMintableFactory = await ethers.getContractFactory(
"cDAINonMintableMock"
);
cDAI3 = await cdaiNonMintableFactory.deploy(dai.address);
await initializeToken(
cDAI1.address,
"100", //1gd
"10000", //0.0001 cDai
"1000000" //100% rr
);
await initializeToken(
cDAI2.address,
"100", //1gd
"10000", //0.0001 cDai
"1000000" //100% rr
);
await initializeToken(
cDAI3.address,
"100", //1gd
"10000", //0.0001 cDai
"1000000" //100% rr
);
const ictrl = await ethers.getContractAt(
"Controller",
controller,
schemeMock
);
const encodedData = goodFundManagerFactory.interface.encodeFunctionData(
"setStakingReward",
["1000", goodCompoundStaking.address, "1", "44", false] // set 10 gd per block
);
await ictrl.genericCall(goodFundManager.address, encodedData, avatar, 0);
await setDAOAddress("MARKET_MAKER", marketMaker.address);
});
it("should not be initializable twice", async () => {
let tx = await goodCompoundStaking
.init(
dai.address,
cDAI.address,
nameService.address,
"Good DAI",
"gDAI",
"172800",
daiUsdOracle.address
)
.catch(e => e);
expect(tx.message).to.be.not.empty;
});
it("should mock cdai exchange rate 1e28 precision", async () => {
let rate = await cDAI.exchangeRateStored();
expect(rate.toString()).to.be.equal("200000000000000000000000000"); // defined initial exchange rate in the cDAIMock contract
});
it("should mint new dai", async () => {
let balance = await dai.balanceOf(founder.address);
expect(balance.toString()).to.be.equal("0");
await dai["mint(uint256)"](ethers.utils.parseEther("100"));
await dai.transfer(staker.address, ethers.utils.parseEther("100"));
balance = await dai.balanceOf(staker.address);
expect(balance.toString()).to.be.at.equal("100000000000000000000");
});
it("should mint new cdai", async () => {
let balance = await dai.balanceOf(staker.address);
expect(balance.toString()).to.be.at.equal("100000000000000000000");
await dai.connect(staker).approve(cDAI.address, "100000000000000000000");
await cDAI.connect(staker)["mint(uint256)"](ethers.utils.parseEther("100"));
balance = await dai.balanceOf(staker.address);
expect(balance.toString()).to.be.equal("0");
let cdaiBalance = await cDAI.balanceOf(staker.address);
expect(cdaiBalance.toString()).to.be.equal("500000000000"); // must mint 5000cDAI with 100DAI
});
it("should redeem cdai", async () => {
let cdaiBalance = await cDAI.balanceOf(staker.address);
await cDAI.connect(staker)["redeem(uint256)"](cdaiBalance.toString());
let balance = await dai.balanceOf(staker.address);
let er = await cDAI.exchangeRateStored();
expect(balance).to.be.equal(cdaiBalance.mul(er).div(BN.from(10).pow(18)));
await dai.connect(staker).transfer(dai.address, balance.toString());
});
it("should return an error if non avatar account is trying to execute recover", async () => {
const cdaiFactory = await ethers.getContractFactory("cDAIMock");
const cdai1 = await cdaiFactory.deploy(dai.address);
await expect(goodCompoundStaking.recover(cdai1.address)).to.be.reverted;
});
it("should not transfer any funds if trying to execute recover of token without balance", async () => {
const cdaiFactory = await ethers.getContractFactory("cDAIMock");
const cdai1 = await cdaiFactory.deploy(dai.address);
await dai["mint(address,uint256)"](
cdai1.address,
ethers.utils.parseEther("100")
);
let balanceBefore = await cdai1.balanceOf(avatar);
let encodedCall = goodCompoundStakingFactory.interface.encodeFunctionData(
"recover",
[cdai1.address]
);
const ictrl = await ethers.getContractAt(
"Controller",
controller,
schemeMock
);
await ictrl.genericCall(
goodCompoundStaking.address,
encodedCall,
avatar,
0
);
let balanceAfter = await cdai1.balanceOf(avatar);
expect(balanceAfter.toString()).to.be.equal(balanceBefore.toString());
});
it("should transfer funds when execute recover of token which the contract has some balance", async () => {
const cdaiFactory = await ethers.getContractFactory("cDAIMock");
const cdai1 = await cdaiFactory.deploy(dai.address);
await dai["mint(address,uint256)"](
cdai1.address,
ethers.utils.parseEther("100")
);
const cdai1BalanceFounder = await cdai1.balanceOf(founder.address);
await cdai1.transfer(goodCompoundStaking.address, cdai1BalanceFounder);
let balanceBefore = await cdai1.balanceOf(avatar);
let encodedCall = goodCompoundStakingFactory.interface.encodeFunctionData(
"recover",
[cdai1.address]
);
const ictrl = await ethers.getContractAt(
"Controller",
controller,
schemeMock
);
await ictrl.genericCall(
goodCompoundStaking.address,
encodedCall,
avatar,
0
);
let balanceAfter = await cdai1.balanceOf(avatar);
expect(balanceAfter.sub(balanceBefore).toString()).to.be.equal(
cdai1BalanceFounder.toString()
);
});
it("should returns the exact amount of staked dai without any effect of having excessive dai tokens in the contract", async () => {
expect(await nameService.getAddress("RESERVE")).to.be.equal(
goodReserve.address
);
//we should change cDAI address to cDAI1's address in nameservice so it can work properly for this test case
await setDAOAddress("CDAI", cDAI1.address);
//update reserve addresses
await goodReserve.setAddresses();
let simpleStaking1 = await deployStaking();
const ictrl = await ethers.getContractAt(
"Controller",
controller,
schemeMock
);
const goodFundManagerFactory = await ethers.getContractFactory(
"GoodFundManager"
);
let encodedDataTwo = goodFundManagerFactory.interface.encodeFunctionData(
"setStakingReward",
["1000", simpleStaking1.address, "1", "44", false] // set 10 gd per block
);
await ictrl.genericCall(goodFundManager.address, encodedDataTwo, avatar, 0);
const weiAmount = ethers.utils.parseEther("1000");
// staking dai
await dai["mint(address,uint256)"](staker.address, weiAmount);
let stakerBalanceBefore = await dai.balanceOf(staker.address);
await dai.connect(staker).approve(simpleStaking1.address, weiAmount);
await simpleStaking1.connect(staker).stake(weiAmount, 100, false);
// transfer excessive dai to the contract
await dai["mint(address,uint256)"](founder.address, weiAmount);
await dai.transfer(simpleStaking1.address, weiAmount);
let balanceBefore = await dai.balanceOf(avatar);
let encodedCall = goodCompoundStakingFactory.interface.encodeFunctionData(
"recover",
[dai.address]
);
await ictrl.genericCall(simpleStaking1.address, encodedCall, avatar, 0);
await simpleStaking1.connect(staker).withdrawStake(weiAmount, false);
let balanceAfter = await dai.balanceOf(avatar);
let stakerBalanceAfter = await dai.balanceOf(staker.address);
// checks that the excessive dai tokens have recovered and that all of the staked
// tokens have returned to the staker
expect(balanceAfter.sub(balanceBefore).toString()).to.be.equal(
weiAmount.toString()
);
expect(stakerBalanceAfter.toString()).to.be.equal(
stakerBalanceBefore.toString()
);
//should revert cdai address back in nameservice
await setDAOAddress("CDAI", cDAI.address);
//update reserve addresses
await goodReserve.setAddresses();
encodedDataTwo = goodFundManagerFactory.interface.encodeFunctionData(
"setStakingReward",
["1000", simpleStaking1.address, "1", "44", true] // set 10 gd per block
);
await ictrl.genericCall(goodFundManager.address, encodedDataTwo, avatar, 0);
});
it("should not transfer user's funds when execute recover", async () => {
let depositAmount = ethers.utils.parseEther("100");
await dai["mint(address,uint256)"](staker.address, depositAmount);
await dai
.connect(staker)
.approve(goodCompoundStaking.address, depositAmount);
let balanceBefore = await dai.balanceOf(avatar);
let stakerBalanceBefore = await dai.balanceOf(staker.address);
await goodCompoundStaking.connect(staker).stake(depositAmount, 100, false);
let encodedCall = goodCompoundStaking.interface.encodeFunctionData(
"recover",
[dai.address]
);
const ictrl = await ethers.getContractAt(
"Controller",
controller,
schemeMock
);
await ictrl.genericCall(
goodCompoundStaking.address,
encodedCall,
avatar,
0
);
await goodCompoundStaking
.connect(staker)
.withdrawStake(depositAmount, false);
let balanceAfter = await dai.balanceOf(avatar);
let stakerBalanceAfter = await dai.balanceOf(staker.address);
expect(balanceAfter.toString()).to.be.equal(balanceBefore.toString());
expect(stakerBalanceAfter.toString()).to.be.equal(
stakerBalanceBefore.toString()
);
});
it("should not transfer excessive cdai funds when total staked is more than 0 and not paused and execute recover", async () => {
//should set CDAI in nameservice to cDAI1's address
await setDAOAddress("CDAI", cDAI1.address);
//update reserve addresses
await goodReserve.setAddresses();
let simpleStaking1 = await deployStaking();
const weiAmount = ethers.utils.parseEther("1000");
const ictrl = await ethers.getContractAt(
"Controller",
controller,
schemeMock
);
const goodFundManagerFactory = await ethers.getContractFactory(
"GoodFundManager"
);
let encodedDataTwo = goodFundManagerFactory.interface.encodeFunctionData(
"setStakingReward",
["1000", simpleStaking1.address, "1", "44", false] // set 10 gd per block
);
await ictrl.genericCall(goodFundManager.address, encodedDataTwo, avatar, 0);
await dai["mint(address,uint256)"](
founder.address,
ethers.utils.parseEther("100")
);
await dai.approve(cDAI1.address, ethers.utils.parseEther("100"));
await cDAI1["mint(uint256)"](ethers.utils.parseEther("100"));
const cdaiBalanceFM = await cDAI1.balanceOf(goodFundManager.address);
await cDAI1.transfer(simpleStaking1.address, cdaiBalanceFM);
await dai["mint(address,uint256)"](staker.address, weiAmount);
await dai.connect(staker).approve(simpleStaking1.address, weiAmount);
let balanceBefore = await cDAI1.balanceOf(avatar);
let stakerBalanceBefore = await dai.balanceOf(staker.address);
await simpleStaking1.connect(staker).stake(weiAmount, 100, false);
let encodedCall = goodCompoundStakingFactory.interface.encodeFunctionData(
"recover",
[cDAI1.address]
);
await ictrl.genericCall(simpleStaking1.address, encodedCall, avatar, 0);
await simpleStaking1.connect(staker).withdrawStake(weiAmount, false);
let balanceAfter = await cDAI1.balanceOf(avatar);
let stakerBalanceAfter = await dai.balanceOf(staker.address);
expect(balanceAfter.sub(balanceBefore).toString()).to.be.equal("0");
expect(stakerBalanceAfter.toString()).to.be.equal(
stakerBalanceBefore.toString()
);
//should revert cdai address back in nameservice
await setDAOAddress("CDAI", cDAI.address);
//update reserve addresses
await goodReserve.setAddresses();
encodedDataTwo = goodFundManagerFactory.interface.encodeFunctionData(
"setStakingReward",
["1000", simpleStaking1.address, "1", "44", true] // set 10 gd per block
);
await ictrl.genericCall(goodFundManager.address, encodedDataTwo, avatar, 0);
});
it("should transfer excessive dai funds when execute recover", async () => {
await dai["mint(address,uint256)"](
staker.address,
ethers.utils.parseEther("100")
);
await dai
.connect(staker)
.approve(goodCompoundStaking.address, ethers.utils.parseEther("100"));
let totalStaked0 = await goodCompoundStaking.getProductivity(
founder.address
);
totalStaked0 = totalStaked0[1];
await goodCompoundStaking
.connect(staker)
.stake(ethers.utils.parseEther("100"), 100, false);
let totalStaked1 = await goodCompoundStaking.getProductivity(
founder.address
);
totalStaked1 = totalStaked1[1];
await dai["mint(address,uint256)"](
goodCompoundStaking.address,
ethers.utils.parseEther("100")
);
let stakerBalanceBefore = await dai.balanceOf(staker.address);
let avatarBalanceBefore = await dai.balanceOf(avatar);
let encodedCall = goodCompoundStakingFactory.interface.encodeFunctionData(
"recover",
[dai.address]
);
const ictrl = await ethers.getContractAt(
"Controller",
controller,
schemeMock
);
await ictrl.genericCall(
goodCompoundStaking.address,
encodedCall,
avatar,
0
);
let avatarBalanceAfter = await dai.balanceOf(avatar);
// checks that after recover stake balance is still available
await goodCompoundStaking
.connect(staker)
.withdrawStake(ethers.utils.parseEther("100"), false);
let stakerBalanceAfter = await dai.balanceOf(staker.address);
expect(totalStaked1.sub(totalStaked0).toString()).to.be.equal(
ethers.utils.parseEther("100")
);
expect(stakerBalanceAfter.sub(stakerBalanceBefore).toString()).to.be.equal(
totalStaked1.sub(totalStaked0).toString()
);
expect(avatarBalanceAfter.sub(avatarBalanceBefore).toString()).to.be.equal(
ethers.utils.parseEther("100")
);
});
it("should be able to stake dai", async () => {
let totalStakedBefore = await goodCompoundStaking.getProductivity(
founder.address
);
totalStakedBefore = totalStakedBefore[1];
await dai["mint(address,uint256)"](
staker.address,
ethers.utils.parseEther("100")
);
await dai
.connect(staker)
.approve(goodCompoundStaking.address, ethers.utils.parseEther("100"));
await goodCompoundStaking
.connect(staker)
.stake(ethers.utils.parseEther("100"), 100, false)
.catch(e => e);
let totalStakedAfter = await goodCompoundStaking.getProductivity(
founder.address
);
totalStakedAfter = totalStakedAfter[1];
let data = await goodCompoundStaking.users(staker.address);
expect(data.amount.toString()).to.be.equal(
ethers.utils.parseEther("100") //100 dai
);
expect(totalStakedAfter.sub(totalStakedBefore).toString()).to.be.equal(
ethers.utils.parseEther("100")
);
let stakedcDaiBalance = await cDAI.balanceOf(goodCompoundStaking.address);
expect(stakedcDaiBalance.toString()).to.be.equal(
"500000000000" // 100 DAI worth 5000 cDAI with 0.02 exchangeRate
);
});
it("should be able to withdraw stake by staker", async () => {
let stakedcDaiBalanceBefore = await cDAI.balanceOf(
goodCompoundStaking.address
); // simpleStaking cDAI balance
let stakerDaiBalanceBefore = await dai.balanceOf(staker.address); // staker DAI balance
let balanceBefore = await goodCompoundStaking.users(staker.address); // user staked balance in GoodStaking
let totalStakedBefore = await goodCompoundStaking.getProductivity(
founder.address
); // total staked in GoodStaking
totalStakedBefore = totalStakedBefore[1];
const transaction = await (
await goodCompoundStaking
.connect(staker)
.withdrawStake(balanceBefore.amount, false)
).wait();
let stakedcDaiBalanceAfter = await cDAI.balanceOf(
goodCompoundStaking.address
); // simpleStaking cDAI balance
let stakerDaiBalanceAfter = await dai.balanceOf(staker.address); // staker DAI balance
let balanceAfter = await goodCompoundStaking.users(staker.address); // user staked balance in GoodStaking
let totalStakedAfter = await goodCompoundStaking.getProductivity(
founder.address
); // total staked in GoodStaking
totalStakedAfter = totalStakedAfter[1];
expect(stakedcDaiBalanceAfter.lt(stakedcDaiBalanceBefore)).to.be.true;
expect(stakerDaiBalanceAfter.gt(stakerDaiBalanceBefore)).to.be.true;
expect(balanceBefore.amount.toString()).to.be.equal(
(stakerDaiBalanceAfter - stakerDaiBalanceBefore).toString()
);
expect((totalStakedBefore - totalStakedAfter).toString()).to.be.equal(
balanceBefore.amount.toString()
);
expect(balanceAfter.amount.toString()).to.be.equal("0");
expect(stakedcDaiBalanceAfter.toString()).to.be.equal("0");
expect(transaction.events.find(_ => _.event === "StakeWithdraw")).to.be.not
.empty;
expect(
transaction.events.find(_ => _.event === "StakeWithdraw").args.staker
).to.be.equal(staker.address);
expect(
transaction.events
.find(_ => _.event === "StakeWithdraw")
.args.value.toString()
).to.be.equal((stakerDaiBalanceAfter - stakerDaiBalanceBefore).toString());
});
it("should be able to withdraw stake by staker when the worth is lower than the actual staked", async () => {
//should set cdai in nameservice
await setDAOAddress("CDAI", cDAI2.address);
//update reserve addresses
await goodReserve.setAddresses();
dai["mint(address,uint256)"](
cDAI2.address,
ethers.utils.parseEther("100000000")
);
let simpleStaking1 = await deployStaking(dai.address, cDAI2.address);
const ictrl = await ethers.getContractAt(
"Controller",
controller,
schemeMock
);
const goodFundManagerFactory = await ethers.getContractFactory(
"GoodFundManager"
);
let encodedDataTwo = goodFundManagerFactory.interface.encodeFunctionData(
"setStakingReward",
["1000", simpleStaking1.address, "1", "44", false] // set 10 gd per block
);
await ictrl.genericCall(goodFundManager.address, encodedDataTwo, avatar, 0);
const weiAmount = ethers.utils.parseEther("1000");
await dai["mint(address,uint256)"](staker.address, weiAmount);
await dai.connect(staker).approve(simpleStaking1.address, weiAmount);
await simpleStaking1.connect(staker).stake(weiAmount, 100, false);
let balanceBefore = await simpleStaking1.users(staker.address); // user staked balance in GoodStaking
let stakerDaiBalanceBefore = await dai.balanceOf(staker.address); // staker DAI balance
await simpleStaking1.connect(staker).withdrawStake(weiAmount, false);
let balanceAfter = await simpleStaking1.users(staker.address); // user staked balance in GoodStaking
let stakerDaiBalanceAfter = await dai.balanceOf(staker.address); // staker DAI balance
expect(balanceAfter.amount.toString()).to.be.equal("0");
expect(balanceBefore.amount.div(BN.from("2")).toString()).to.be.equal(
stakerDaiBalanceAfter.sub(stakerDaiBalanceBefore).toString()
);
//should revert cdai address back in nameservice
await setDAOAddress("CDAI", cDAI.address);
//update reserve addresses
await goodReserve.setAddresses();
encodedDataTwo = goodFundManagerFactory.interface.encodeFunctionData(
"setStakingReward",
["1000", simpleStaking1.address, "1", "44", true] // set 10 gd per block
);
await ictrl.genericCall(goodFundManager.address, encodedDataTwo, avatar, 0);
});
it("should return 0s for gains when the current cdai worth is lower than the inital worth", async () => {
//should set CDAI in nameService to cDAI1 address
await setDAOAddress("CDAI", cDAI1.address);
//update reserve addresses
await goodReserve.setAddresses();
await dai["mint(address,uint256)"](
cDAI1.address,
ethers.utils.parseEther("100000000")
);
let simpleStaking1 = await deployStaking();
const weiAmount = ethers.utils.parseEther("1000");
await dai["mint(address,uint256)"](staker.address, weiAmount);
await dai.connect(staker).approve(simpleStaking1.address, weiAmount);
await simpleStaking1.connect(staker).stake(weiAmount, 100, false);
let gains = await simpleStaking1.currentGains(false, true);
expect(gains["0"].toString()).to.be.equal("0"); // cdaiGains
expect(gains["1"].toString()).to.be.equal("0"); // daiGains
//should revert cdai address back in nameservice
await setDAOAddress("CDAI", cDAI.address);
//update reserve addresses
await goodReserve.setAddresses();
});
it("should convert user staked DAI to the equal value of cDAI owned by the staking contract", async () => {
await dai["mint(address,uint256)"](
staker.address,
ethers.utils.parseEther("100")
);
await dai
.connect(staker)
.approve(goodCompoundStaking.address, ethers.utils.parseEther("100"));
await goodCompoundStaking
.connect(staker)
.stake(ethers.utils.parseEther("100"), 100, false)
.catch(e => e);
let stakedcDaiBalance = await cDAI.balanceOf(goodCompoundStaking.address);
let stakercDaiBalance = await cDAI.balanceOf(staker.address);
expect(stakedcDaiBalance.toString()).to.be.equal(
"500000000000" //8 decimals precision
);
let stakedDaiBalance = await dai.balanceOf(goodCompoundStaking.address);
expect(stakedDaiBalance.isZero()).to.be.true;
expect(stakercDaiBalance.isZero()).to.be.true;
await goodCompoundStaking
.connect(staker)
.withdrawStake(ethers.utils.parseEther("100"), false);
});
it("should not change the staker DAI balance if the conversion failed", async () => {
const cdaiFactory = await ethers.getContractFactory("cDAIMock");
const daiFactory = await ethers.getContractFactory("DAIMock");
let fakeDai = await daiFactory.deploy();
let fakecDAI = await cdaiFactory.deploy(fakeDai.address);
await fakeDai["mint(address,uint256)"](
fakecDAI.address,
ethers.utils.parseEther("100000000")
);
let fakeSimpleStaking = await deployStaking(dai.address, fakecDAI.address);
await dai["mint(address,uint256)"](
staker.address,
ethers.utils.parseEther("100")
);
await dai
.connect(staker)
.approve(fakeSimpleStaking.address, ethers.utils.parseEther("100"));
let stakerDaiBalanceBefore = await dai.balanceOf(staker.address);
const error = await fakeSimpleStaking
.connect(staker)
.stake(ethers.utils.parseEther("100"), false)
.catch(e => e);
expect(error.message).not.to.be.empty;
let stakerDaiBalanceAfter = await dai.balanceOf(staker.address);
expect(stakerDaiBalanceAfter.toString()).to.be.equal(
stakerDaiBalanceBefore.toString()
);
});
it("should not change the totalStaked if the conversion failed", async () => {
const cdaiFactory = await ethers.getContractFactory("cDAIMock");
const daiFactory = await ethers.getContractFactory("DAIMock");
let fakeDai = await daiFactory.deploy();
let fakecDAI = await cdaiFactory.deploy(fakeDai.address);
await fakeDai["mint(address,uint256)"](
fakecDAI.address,
ethers.utils.parseEther("100000000")
);
let fakeSimpleStaking = await deployStaking(dai.address, fakecDAI.address);
await dai["mint(address,uint256)"](
staker.address,
ethers.utils.parseEther("100")
);
await dai
.connect(staker)
.approve(fakeSimpleStaking.address, ethers.utils.parseEther("100"));
let totalStakedBefore = await fakeSimpleStaking.getProductivity(
founder.address
);
const error = await fakeSimpleStaking
.connect(staker)
.stake(ethers.utils.parseEther("100"), false)
.catch(e => e);
expect(error.message).not.to.be.empty;
let totalStakedAfter = await fakeSimpleStaking.getProductivity(
founder.address
);
expect(totalStakedAfter[1].toString()).to.be.equal(
totalStakedBefore[1].toString()
);
});
it("should not update the staker list if the conversion failed", async () => {
const cdaiFactory = await ethers.getContractFactory("cDAIMock");
const daiFactory = await ethers.getContractFactory("DAIMock");
let fakeDai = await daiFactory.deploy();
let fakecDAI = await cdaiFactory.deploy(fakeDai.address);
await fakeDai["mint(address,uint256)"](
fakecDAI.address,
ethers.utils.parseEther("100000000")
);
let fakeSimpleStaking = await deployStaking(dai.address, fakecDAI.address);
await dai["mint(address,uint256)"](
staker.address,
ethers.utils.parseEther("100")
);
await dai
.connect(staker)
.approve(fakeSimpleStaking.address, ethers.utils.parseEther("100"));
const error = await fakeSimpleStaking
.connect(staker)
.stake(ethers.utils.parseEther("100"), false)
.catch(e => e);
expect(error.message).not.to.be.empty;
let balance = await fakeSimpleStaking.users(staker.address);
expect(balance.amount.toString()).to.be.equal("0");
});
it("should be able to stake dai when the allowed dai amount is higher than the staked amount", async () => {
await dai["mint(address,uint256)"](
staker.address,
ethers.utils.parseEther("100")
);
await dai
.connect(staker)
.approve(goodCompoundStaking.address, ethers.utils.parseEther("200"));
let balanceBefore = await goodCompoundStaking.users(staker.address);
let stakedcDaiBalanceBefore = await cDAI.balanceOf(
goodCompoundStaking.address
);
await goodCompoundStaking
.connect(staker)
.stake(ethers.utils.parseEther("100"), 100, false)
.catch(e => e);
let balanceAfter = await goodCompoundStaking.users(staker.address);
expect((balanceAfter.amount - balanceBefore.amount).toString()).to.be.equal(
"100000000000000000000" //100 dai
);
let stakedcDaiBalanceAfter = await cDAI.balanceOf(
goodCompoundStaking.address
);
expect(
(stakedcDaiBalanceAfter - stakedcDaiBalanceBefore).toString()
).to.be.equal(
"500000000000" // 100 dai worth 5000 cdai in the 0.02 exchangerate
);
await goodCompoundStaking
.connect(staker)
.withdrawStake(ethers.utils.parseEther("100"), false);
});
it("should not be able to stake 0 dai", async () => {
const error = await goodCompoundStaking
.connect(staker)
.stake("0", 100, false)
.catch(e => e);
expect(error.message).to.be.not.empty;
});
it("should not be able to stake when approved dai amount is lower than staking amount", async () => {
let lowWeiAmount = ethers.utils.parseEther("99");
let weiAmount = ethers.utils.parseEther("100");
await dai["mint(address,uint256)"](staker.address, weiAmount);
await dai
.connect(staker)
.approve(goodCompoundStaking.address, lowWeiAmount);
const error = await goodCompoundStaking
.connect(staker)
.stake(weiAmount, 100, false)
.catch(e => e);
expect(error);
expect(error.message).not.to.be.empty;
});
it("should not be able to stake when staker dai balance is too low", async () => {
let currentBalance = await dai.balanceOf(staker.address);
let weiAmount = ethers.utils.parseEther("100");
let approvedAmount = currentBalance.valueOf() + weiAmount;
await dai
.connect(staker)
.approve(goodCompoundStaking.address, approvedAmount);
const error = await goodCompoundStaking
.connect(staker)
.stake(approvedAmount, 100, false)
.catch(e => e);
expect(error.message).not.to.be.empty;
});
it("should emit a DAIStaked event", async () => {
let weiAmount = ethers.utils.parseEther("100");
await dai["mint(address,uint256)"](staker.address, weiAmount);
await dai.approve(goodCompoundStaking.address, weiAmount);
const transaction = await (
await goodCompoundStaking.connect(staker).stake(weiAmount, 100, false)
).wait();
expect(transaction.events.find(_ => _.event === "Staked")).not.to.be.empty;
expect(
transaction.events.find(_ => _.event === "Staked").args.value.toString()
).to.be.equal(weiAmount.toString());
await goodCompoundStaking.connect(staker).withdrawStake(weiAmount, false);
});
it("should not withdraw interest to owner if cDAI value is lower than the staked", async () => {
const weiAmount = ethers.utils.parseEther("1000");
await dai["mint(address,uint256)"](staker.address, weiAmount);
await dai.connect(staker).approve(goodCompoundStaking.address, weiAmount);
await goodCompoundStaking
.connect(staker)
.stake(weiAmount, 100, false)
.catch(e => e);
const gains = await goodCompoundStaking.currentGains(false, true);
const cdaiGains = gains["0"];
const fundBalanceBefore = await cDAI.balanceOf(founder.address);
await advanceBlocks(BLOCK_INTERVAL);
await setDAOAddress("FUND_MANAGER", founder.address);
const res = await goodCompoundStaking.collectUBIInterest(founder.address);
await setDAOAddress("FUND_MANAGER", goodFundManager.address);
const fundBalanceAfter = await cDAI.balanceOf(founder.address);
expect(cdaiGains.toString()).to.be.equal("0");
expect(fundBalanceAfter.toString()).to.be.equal(
fundBalanceBefore.toString()
);
await goodCompoundStaking.connect(staker).withdrawStake(weiAmount, false);
});
it("should not be able to stake if the getting an error while minting new cdai", async () => {
//should set CDAI in nameService to cDAI1 address
await setDAOAddress("CDAI", cDAI3.address);
//update reserve addresses
await goodReserve.setAddresses();
await dai["mint(address,uint256)"](
cDAI3.address,
ethers.utils.parseEther("100000000")
);
let simpleStaking1 = await deployStaking(dai.address, cDAI3.address);
const weiAmount = ethers.utils.parseUnits("1000", "ether");
await dai["mint(address,uint256)"](staker.address, weiAmount);
await dai.connect(staker).approve(simpleStaking1.address, weiAmount);
const error = await simpleStaking1
.connect(staker)
.stake(weiAmount, 100, false)
.catch(e => e);
expect(error.message).to.be.not.empty;
//should revert cdai address back in nameservice
await setDAOAddress("CDAI", cDAI.address);
//update reserve addresses
await goodReserve.setAddresses();
});
it("should mock cdai updated exchange rate", async () => {
await cDAI.exchangeRateCurrent();
let rate = await cDAI.exchangeRateStored();
expect(rate.toString()).to.be.equal("300000000000000000000000000"); // it was 200000000000000000000000000 so after we call exchangeratecurrent it will update value by 1e26 so should be 300000000000000000000000000
});
it("should report interest gains", async () => {
let stakingAmount = ethers.utils.parseEther("400");
await dai["mint(address,uint256)"](staker.address, stakingAmount);
await dai
.connect(staker)
.approve(goodCompoundStaking.address, stakingAmount);
await goodCompoundStaking
.connect(staker)
.stake(stakingAmount, 100, false)
.catch(e => e);
await cDAI.exchangeRateCurrent();
const gains = await goodCompoundStaking.currentGains(false, true);
const cdaiGains = gains["0"];
expect(cdaiGains.toString()).to.be.equal("333333333325"); //cdaiGains after increase exchangerate and calculated manually to make sure contract calculations are correct
await goodCompoundStaking
.connect(staker)
.withdrawStake(stakingAmount, false);
await dai["mint(address,uint256)"](
staker.address,
ethers.utils.parseEther("1000000")
);
await dai
.connect(staker)
.transfer(cDAI.address, ethers.utils.parseEther("1000000")); // We should put extra DAI to mock cDAI contract in order to provide interest
});
it("should withdraw interest to owner", async () => {
const stakingAmount = ethers.utils.parseEther("100");
await dai["mint(address,uint256)"](staker.address, stakingAmount);
await dai
.connect(staker)
.approve(goodCompoundStaking.address, stakingAmount);
await goodCompoundStaking
.connect(staker)
.stake(stakingAmount, 100, false)
.catch(e => e);
await cDAI.increasePriceWithMultiplier("1500"); // increase interest by calling exchangeRateCurrent
const gains = await goodCompoundStaking.currentGains(false, true);
const cdaiGains = gains["0"];
const fundBalance0 = await cDAI.balanceOf(goodReserve.address);
const contractAddressesToBeCollected =
await goodFundManager.calcSortedContracts();
const addressesToCollect = contractAddressesToBeCollected.map(x => x[0]);
const res = await goodFundManager.collectInterest(
addressesToCollect,
false,
{
gasLimit: 1100000
}
);
const fundBalance1 = await cDAI.balanceOf(goodReserve.address);
const fundDaiWorth = await goodCompoundStaking.currentGains(false, true);
expect(cdaiGains.toString()).to.be.equal(
fundBalance1.sub(fundBalance0).toString()
);
expect(fundDaiWorth[2].toString()).to.be.equal(
//it should be equal 100000000000000000000 but since there is some precision loss due to iToken decimal < token decimal it returns 100000000124064646464
"100000000147200000000"
);
await goodCompoundStaking
.connect(staker)
.withdrawStake(stakingAmount, false);
});
it("should withdraw only by fundmanager", async () => {
const error = await goodCompoundStaking
.connect(staker)
.collectUBIInterest(founder.address)
.catch(e => e);
expect(error.message).to.be.not.empty;
});
it("should not be able to double withdraw stake", async () => {
let stakingAmount = ethers.utils.parseEther("100");
await dai["mint(address,uint256)"](staker.address, stakingAmount);
await dai
.connect(staker)
.approve(goodCompoundStaking.address, stakingAmount);
await goodCompoundStaking.connect(staker).stake(stakingAmount, 100, false);
let balance = await goodCompoundStaking.users(staker.address);
await goodCompoundStaking
.connect(staker)
.withdrawStake(balance.amount, false)
.catch(e => e);
//make sure staking contract has the required balance to test double withdraw
await cDAI["mint(address,uint256)"](
goodCompoundStaking.address,
stakingAmount.mul(1000)
);
await expect(
goodCompoundStaking.connect(staker).withdrawStake(stakingAmount, false)
).to.be.reverted;
});
it("should not be able to withdraw if not a staker", async () => {
await expect(
goodCompoundStaking.withdrawStake(ethers.utils.parseEther("100"), false)
).to.be.reverted;
});
it("should not be able to change the reserve cDAI balance in case of an error", async () => {
let stakedcDaiBalanceBefore = await cDAI.balanceOf(goodReserve.address);
await goodCompoundStaking
.withdrawStake(ethers.utils.parseEther("100"), false)
.catch(e => e);
let stakedcDaiBalanceAfter = await cDAI.balanceOf(goodReserve.address);
expect(stakedcDaiBalanceAfter.toString()).to.be.equal(
stakedcDaiBalanceBefore.toString()
);
});
it("should be able to withdraw stake by staker and precision loss should not be equal to 0", async () => {
const weiAmount = ethers.utils.parseEther("100");
await dai["mint(address,uint256)"](staker.address, weiAmount);
await dai.connect(staker).approve(goodCompoundStaking.address, weiAmount);
await goodCompoundStaking
.connect(staker)
.stake(weiAmount, 100, false)
.catch(e => e);
let stakedcDaiBalanceBefore = await cDAI.balanceOf(
goodCompoundStaking.address
); // simpleStaking cDAI balance
const transaction = await goodCompoundStaking
.connect(staker)
.withdrawStake(weiAmount, false);
let stakedcDaiBalanceAfter = await cDAI.balanceOf(
goodCompoundStaking.address
); // simpleStaking cDAI balance
expect(stakedcDaiBalanceAfter.lt(stakedcDaiBalanceBefore)).to.be.true;
expect(stakedcDaiBalanceAfter.toString()).to.not.be.equal("0"); //precision loss, so it wont be exactly 0
});
it("should withdraw interest to recipient specified by the owner", async () => {
const weiAmount = ethers.utils.parseEther("100");
await dai["mint(address,uint256)"](staker.address, weiAmount);
await dai.connect(staker).approve(goodCompoundStaking.address, weiAmount);
await goodCompoundStaking
.connect(staker)
.stake(weiAmount, 100, false)
.catch(e => e);
const gains = await goodCompoundStaking.currentGains(false, true);
const cdaiGains = gains["0"];
await advanceBlocks(BLOCK_INTERVAL);
await setDAOAddress("FUND_MANAGER", founder.address);
const res = await goodCompoundStaking.collectUBIInterest(staker.address);
await setDAOAddress("FUND_MANAGER", goodFundManager.address);
const stakerBalance = await cDAI.balanceOf(staker.address);
const fundDaiWorth = await goodCompoundStaking.currentGains(false, true);
expect(cdaiGains.toString()).to.be.equal(stakerBalance.toString());
// expect(fundDaiWorth.toString()).to.be.equal(
// // 10 gwei = 10 decimals + precisionLoss = 20 decimals = 100 ether of DAI
// web3.utils.toWei("10", "gwei") + precisionLossDai
// );
});
it("it should be reverted when approved iToken amount is less than stake amount", async () => {
const stakingAmount = ethers.utils.parseUnits("100", 8);
await cDAI["mint(address,uint256)"](staker.address, stakingAmount);
await cDAI
.connect(staker)
.approve(goodCompoundStaking.address, stakingAmount.div(2));
const transaction = await goodCompoundStaking
.connect(staker)
.stake(stakingAmount, 100, true)
.catch(e => e);
expect(transaction.message).to.have.string("ERC20: insufficient allowance");
});
it("it should be able stake and withdraw their stake in iToken", async () => {
const stakingAmount = ethers.utils.parseUnits("100", 8);
await cDAI["mint(address,uint256)"](staker.address, stakingAmount);
const stakercDAIBalanceBeforeStake = await cDAI.balanceOf(staker.address);
await cDAI
.connect(staker)
.approve(goodCompoundStaking.address, stakingAmount);
const productivityBeforeStake = await goodCompoundStaking.getProductivity(
staker.address
);
const stakingContractCdaiBalanceBeforeStake = await cDAI.balanceOf(
goodCompoundStaking.address
);
await goodCompoundStaking.connect(staker).stake(stakingAmount, 100, true);
const stakingContractCdaiBalanceAfterStake = await cDAI.balanceOf(
goodCompoundStaking.address
);
const stakercDAIBalanceAfterStake = await cDAI.balanceOf(staker.address);
const productivityAfterStake = await goodCompoundStaking.getProductivity(
staker.address
);
await goodCompoundStaking
.connect(staker)
.withdrawStake(stakingAmount, true);
const stakingContractCdaiBalanceAfterWithdraw = await cDAI.balanceOf(
goodCompoundStaking.address
);
const stakerCdaiBalanceAfterWithdraw = await cDAI.balanceOf(staker.address);
const productivityAfterWithdraw = await goodCompoundStaking.getProductivity(
staker.address
);
expect(productivityAfterStake[0].gt(productivityBeforeStake[0])).to.be.true;
expect(productivityBeforeStake[0]).to.be.equal(
productivityAfterWithdraw[0]
);
expect(stakercDAIBalanceBeforeStake.sub(stakingAmount)).to.be.equal(
stakercDAIBalanceAfterStake
);
expect(stakerCdaiBalanceAfterWithdraw).to.be.equal(
stakercDAIBalanceBeforeStake
);
expect(stakingContractCdaiBalanceAfterStake.sub(stakingAmount)).to.be.equal(
stakingContractCdaiBalanceBeforeStake
);
expect(
stakingContractCdaiBalanceAfterWithdraw.add(stakingAmount)
).to.be.equal(stakingContractCdaiBalanceAfterStake);
});
it("it should be able to stake in iToken and withdraw in Token", async () => {
const stakingAmount = ethers.utils.parseUnits("100", 8);
const daiMintAmount = ethers.utils.parseEther("100000");
await dai["mint(address,uint256)"](cDAI.address, daiMintAmount);
await cDAI["mint(address,uint256)"](staker.address, stakingAmount);
await cDAI
.connect(staker)
.approve(goodCompoundStaking.address, stakingAmount);
const productivityBeforeStake = await goodCompoundStaking.getProductivity(
staker.address
);
const stakerCdaiBalanceBeforeStake = await cDAI.balanceOf(staker.address);
const stakingContractCdaiBalanceBeforeStake = await cDAI.balanceOf(
goodCompoundStaking.address
);
await goodCompoundStaking.connect(staker).stake(stakingAmount, 100, true);
const stakingContractCdaiBalanceAfterStake = await cDAI.balanceOf(
goodCompoundStaking.address
);
const stakerCdaiBalanceAfterStake = await cDAI.balanceOf(staker.address);
const productivityAfterStaker = await goodCompoundStaking.getProductivity(
staker.address
);
const stakerDaiBalanceBeforeWithdraw = await dai.balanceOf(staker.address);
const exchangeRateStored = await cDAI.exchangeRateStored();
const withdrawAmount = stakingAmount
.mul(BN.from("10").pow(10))
.mul(exchangeRateStored)
.div(BN.from("10").pow(28));
await goodCompoundStaking
.connect(staker)
.withdrawStake(withdrawAmount, false); // 1603010101010101010101 is equaliavent of 100cDAI in DAI with currentexchange rate
const stakingContractCdaiBalanceAfterWithdraw = await cDAI.balanceOf(
goodCompoundStaking.address
);
const stakerDaiBalanceAfterWithdraw = await dai.balanceOf(staker.address);
const productivityAfterWithdraw = await goodCompoundStaking.getProductivity(
staker.address
);
expect(productivityBeforeStake[0]).to.be.equal(
productivityAfterWithdraw[0]
);
expect(stakerDaiBalanceAfterWithdraw.gt(stakerDaiBalanceBeforeWithdraw));
expect(productivityAfterStaker[0].gt(productivityBeforeStake[0])).to.be
.true;
expect(stakerCdaiBalanceBeforeStake.sub(stakingAmount)).to.be.equal(
stakerCdaiBalanceAfterStake
);
expect(stakingContractCdaiBalanceAfterStake.sub(stakingAmount)).to.be.equal(
stakingContractCdaiBalanceBeforeStake
);
expect(
stakingContractCdaiBalanceAfterWithdraw.lt(
stakingContractCdaiBalanceAfterStake
)
).to.be.true;
});
it("it should be able to stake in Token and withdraw in iToken", async () => {
const stakingAmount = ethers.utils.parseEther("100");
await dai["mint(address,uint256)"](staker.address, stakingAmount);
await dai
.connect(staker)
.approve(goodCompoundStaking.address, stakingAmount);
const stakerDaiBalanceBeforeStake = await dai.balanceOf(staker.address);
const productivityBeforeStake = await goodCompoundStaking.getProductivity(
staker.address
);
await goodCompoundStaking.connect(staker).stake(stakingAmount, 100, false);
const stakerDaiBalanceAfterStake = await dai.balanceOf(staker.address);
const productivityAfterStake = await goodCompoundStaking.getProductivity(
staker.address
);
const stakerCdaiBalanceBeforeWithdraw = await cDAI.balanceOf(
staker.address
);
const exchangeRateStored = await cDAI.exchangeRateStored();
const withdrawAmount = stakingAmount
.div(BN.from("10").pow(10))
.mul(BN.from("10").pow(28))
.div(exchangeRateStored);
await goodCompoundStaking
.connect(staker)
.withdrawStake(withdrawAmount, true);
await goodCompoundStaking
.connect(staker)
.withdrawStake("00000000003200000000", false); // 00000000003200000000 is precision loss due to itoken decimals < token decimals
const stakerCdaiBalanceAfterWithdraw = await cDAI.balanceOf(staker.address);
const productivityAfterWithdraw = await goodCompoundStaking.getProductivity(
staker.address
);
expect(productivityBeforeStake[0]).to.be.equal(
productivityAfterWithdraw[0]
);
expect(productivityAfterStake[0].gt(productivityBeforeStake[0])).to.be.true;
expect(stakerDaiBalanceAfterStake.add(stakingAmount)).to.be.equal(
stakerDaiBalanceBeforeStake
);
expect(stakerCdaiBalanceBeforeWithdraw.add("664893617")).to.be.equal(
stakerCdaiBalanceAfterWithdraw
);
});
it("should not be able to withdraw stake when the withdrawn amount is higher than the staked amount", async () => {
const stakeAmount = ethers.utils.parseEther("100");
const higherThanStakeAmount = ethers.utils.parseEther("101");
await cDAI["mint(address,uint256)"](staker.address, stakeAmount);
await cDAI
.connect(staker)
.approve(goodCompoundStaking.address, higherThanStakeAmount);
await goodCompoundStaking.connect(staker).stake(stakeAmount, "100", true);
const tx = await goodCompoundStaking
.connect(staker)
.withdrawStake(higherThanStakeAmount, true)
.catch(e => e);
expect(tx.message).to.be.not.empty;
// revent to original state
await goodCompoundStaking.connect(staker).withdrawStake(stakeAmount, true);
});
it("should pause the contract", async () => {
let encodedCall = goodCompoundStakingFactory.interface.encodeFunctionData(
"pause",
[true]
);
const ictrl = await ethers.getContractAt(
"Controller",
controller,
schemeMock
);
await ictrl.genericCall(
goodCompoundStaking.address,
encodedCall,
avatar,
0
);
const isPaused = await goodCompoundStaking.isPaused();
expect(isPaused).to.be.true;
});
it("should not transfer excessive cdai funds when the contract is paused and the total staked is not 0", async () => {
let simpleStaking1 = await deployStaking();
const weiAmount = ethers.utils.parseEther("100");
// staking dai
await dai["mint(address,uint256)"](staker.address, weiAmount);
await dai.connect(staker).appro