@gooddollar/goodprotocol
Version:
GoodDollar Protocol
365 lines (337 loc) • 11.9 kB
text/typescript
import { ethers, upgrades } from "hardhat";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { BigNumber, Contract } from "ethers";
import { expect } from "chai";
import { createDAO, deployUniswap, getStakingFactory } from "../helpers";
import IUniswapV2Pair from "@uniswap/v2-core/build/IUniswapV2Pair.json";
const BN = ethers.BigNumber;
export const NULL_ADDRESS = ethers.constants.AddressZero;
export const BLOCK_INTERVAL = 30;
describe("SwapHelper - Helper library for swap on the Uniswap", () => {
let dai: Contract;
let bat: Contract;
let comp: Contract;
let wethContract: Contract;
let pair: Contract,
uniswapRouter: Contract,
uniswapFactory: Contract,
usdcPair,
compPair,
daiPair,
swapHelperTest;
let cDAI, cUsdc, usdc, cBat: Contract;
let batUsdOracle: Contract, compUsdOracle: Contract;
let goodFundManager: Contract;
let avatar,
controller,
founder,
staker,
schemeMock,
signers,
nameService,
setDAOAddress,
genericCall,
goodCompoundStakingFactory,
goodCompoundStakingTestFactory;
before(async () => {
[founder, staker, ...signers] = await ethers.getSigners();
schemeMock = signers.pop();
let {
controller: ctrl,
avatar: av,
gd,
identity,
nameService: ns,
setDAOAddress: sda,
setSchemes,
marketMaker: mm,
daiAddress,
cdaiAddress,
reserve,
setReserveToken,
genericCall: gc,
COMP
} = await loadFixture(createDAO);
const cdaiFactory = await ethers.getContractFactory("cDAIMock");
const cBatFactory = await ethers.getContractFactory("cDecimalsMock");
const goodFundManagerFactory = await ethers.getContractFactory(
"GoodFundManager"
);
const swapHelperTestFactory = await ethers.getContractFactory(
"SwapHelperTest"
);
goodCompoundStakingFactory = await getStakingFactory(
"GoodCompoundStakingV2"
);
goodCompoundStakingTestFactory = await getStakingFactory(
"GoodCompoundStakingTest"
);
swapHelperTest = await swapHelperTestFactory.deploy();
const daiFactory = await ethers.getContractFactory("DAIMock");
genericCall = gc;
dai = await ethers.getContractAt("DAIMock", daiAddress);
cDAI = await ethers.getContractAt("cDAIMock", cdaiAddress);
comp = COMP;
const uniswap = await deployUniswap(comp, dai);
uniswapRouter = uniswap.router;
const { factory, weth, daiPairContract, compPairContract } = uniswap;
wethContract = weth;
uniswapFactory = factory;
daiPair = daiPairContract;
compPair = compPairContract;
avatar = av;
controller = ctrl;
setDAOAddress = sda;
nameService = ns;
console.log("deployed dao", {
founder: founder.address,
gd,
identity,
controller,
avatar
});
goodFundManager = await upgrades.deployProxy(
goodFundManagerFactory,
[nameService.address],
{
kind: "uups"
}
);
await setDAOAddress("FUND_MANAGER", goodFundManager.address);
console.log("Deployed goodfund manager", {
manager: goodFundManager.address
});
const tokenUsdOracleFactory = await ethers.getContractFactory(
"BatUSDMockOracle"
);
await setDAOAddress("UNISWAP_ROUTER", uniswapRouter.address);
const compUsdOracleFactory = await ethers.getContractFactory(
"CompUSDMockOracle"
);
compUsdOracle = await compUsdOracleFactory.deploy();
bat = await daiFactory.deploy(); // Another erc20 token for uniswap router test
cBat = await cBatFactory.deploy(bat.address);
usdc = await (await ethers.getContractFactory("USDCMock")).deploy();
cUsdc = await (
await ethers.getContractFactory("cUSDCMock")
).deploy(usdc.address);
await factory.createPair(bat.address, dai.address); // Create tokenA and dai pair
const pairAddress = factory.getPair(bat.address, dai.address);
await factory.createPair(bat.address, usdc.address);
usdcPair = new Contract(
await factory.getPair(bat.address, usdc.address),
JSON.stringify(IUniswapV2Pair.abi),
staker
);
pair = new Contract(
pairAddress,
JSON.stringify(IUniswapV2Pair.abi),
staker
).connect(founder);
batUsdOracle = await tokenUsdOracleFactory.deploy();
await bat["mint(address,uint256)"](
founder.address,
ethers.utils.parseEther("5001000")
);
await usdc["mint(address,uint256)"](
founder.address,
ethers.utils.parseUnits("10000000", 6)
);
await bat.transfer(cBat.address, ethers.utils.parseEther("1000000")); // We should put extra BAT to mock cBAT contract in order to provide interest
await bat.transfer(usdcPair.address, ethers.utils.parseEther("2000000"));
await usdc.transfer(
usdcPair.address,
ethers.utils.parseUnits("2000000", 6)
);
await usdcPair.mint(founder.address);
await addLiquidity(
ethers.utils.parseEther("200000"),
ethers.utils.parseEther("200000")
);
});
it("it should swap only safe amount when gains larger than safe amount", async () => {
const goodFundManagerFactory = await ethers.getContractFactory(
"GoodFundManager"
);
const simpleStaking = await deployStaking(
bat.address,
cBat.address,
"50",
batUsdOracle.address,
[bat.address, dai.address]
);
const reserve = await swapHelperTest.getReserves(
uniswapFactory.address,
bat.address,
dai.address
);
await bat.approve(simpleStaking.address, ethers.utils.parseEther("100"));
await simpleStaking.stake(ethers.utils.parseEther("100"), 100, false);
await cBat.increasePriceWithMultiplier("1500");
const collectableContracts = await goodFundManager.calcSortedContracts();
const addressToCollect = collectableContracts.map(x => x[0]);
const safeAmount = reserve[0].mul(BN.from(3)).div(BN.from(1000));
const safeAmountInIToken = await simpleStaking.tokenWorthIniToken(
safeAmount
);
const er = await cBat.exchangeRateStored();
const redeemedAmount = safeAmountInIToken
.mul(er)
.mul(BN.from(10).pow(10))
.div(BN.from(10).pow(28));
const currentGains = await simpleStaking.currentGains(true, true);
await goodFundManager.collectInterest(addressToCollect, false, {
gasLimit: 1500000
});
const currentReserve = await swapHelperTest.getReserves(
uniswapFactory.address,
bat.address,
dai.address
);
await simpleStaking.withdrawStake(ethers.utils.parseEther("100"), false);
const encodedData = goodFundManagerFactory.interface.encodeFunctionData(
"setStakingReward",
["100", simpleStaking.address, 0, 10000, true]
);
await genericCall(goodFundManager.address, encodedData, avatar, 0);
console.log({ redeemedAmount, safeAmount });
expect(reserve[0].sub(currentReserve[0])).to.be.lt(currentGains[1]);
expect(currentReserve[0].sub(reserve[0])).to.be.equal(redeemedAmount);
});
it("it should swap with multiple hops", async () => {
const simpleStaking = await deployStaking(
usdc.address,
cUsdc.address,
"50",
batUsdOracle.address,
[usdc.address, bat.address, dai.address]
);
const reserve = await swapHelperTest.getReserves(
uniswapFactory.address,
bat.address,
dai.address
);
const usdcPairReserve = await swapHelperTest.getReserves(
uniswapFactory.address,
usdc.address,
bat.address
);
await usdc.transfer(cUsdc.address, ethers.utils.parseUnits("1000000", 6)); // We should put extra usdc to mock cUSDC contract in order to provide interest
await usdc.approve(
simpleStaking.address,
ethers.utils.parseUnits("100", 6)
);
await simpleStaking.stake(ethers.utils.parseUnits("100", 6), 100, false);
await cUsdc.increasePriceWithMultiplier("1500");
const collectableContracts = await goodFundManager.calcSortedContracts();
const currentGains = await simpleStaking.currentGains(true, true);
const addressesToCollect = collectableContracts.map(x => x[0]);
await goodFundManager.collectInterest(addressesToCollect, false, {
gasLimit: 1500000
});
const currentReserve = await swapHelperTest.getReserves(
uniswapFactory.address,
bat.address,
dai.address
);
const currentUsdcPairReserve = await swapHelperTest.getReserves(
uniswapFactory.address,
usdc.address,
bat.address
);
await simpleStaking.withdrawStake(ethers.utils.parseUnits("100", 6), false);
const safeAmount = usdcPairReserve[0].mul(BN.from(3)).div(BN.from(1000));
const safeAmountInIToken = await simpleStaking.tokenWorthIniToken(
safeAmount
);
const er = await cUsdc.exchangeRateStored();
const redeemedAmount = safeAmountInIToken
.div(BN.from(10).pow(2))
.mul(er)
.div(BN.from(10).pow(16));
expect(redeemedAmount).to.be.eq(
currentUsdcPairReserve[0].sub(usdcPairReserve[0])
);
expect(usdcPairReserve[1]).to.be.gt(currentUsdcPairReserve[1]); // Since we use multiple hops to swap initial reserves should be greater than after reserve for bat
expect(reserve[1]).to.be.gt(currentReserve[1]); // bat reserve should be greater than initial reserve since we swap bat to dai
});
it("it should swap comp to dai", async () => {
const simpleStaking = await deployStaking(
usdc.address,
cUsdc.address,
"50",
batUsdOracle.address,
[usdc.address, bat.address, dai.address]
);
comp["mint(address,uint256)"](
simpleStaking.address,
ethers.utils.parseEther("10000")
);
const collectableContracts = await goodFundManager.calcSortedContracts();
const addressesToCollect = collectableContracts.map(x => x[0]);
const reserveBeforeSwap = await swapHelperTest.getReserves(
uniswapFactory.address,
comp.address,
wethContract.address
);
await goodFundManager.collectInterest(addressesToCollect, false, {
gasLimit: 1500000
});
const reserveAfterSwap = await swapHelperTest.getReserves(
uniswapFactory.address,
comp.address,
wethContract.address
);
const safeAmount = reserveBeforeSwap[0].mul(BN.from(3)).div(BN.from(1000));
expect(safeAmount).to.be.equal(
reserveAfterSwap[0].sub(reserveBeforeSwap[0])
);
});
async function addLiquidity(
token0Amount: BigNumber,
token1Amount: BigNumber
) {
await bat.transfer(pair.address, token0Amount);
await dai.transfer(pair.address, token1Amount);
await pair.mint(founder.address);
}
async function deployStaking(
token,
itoken,
blocksThreshold = "50",
tokenUsdOracle,
swapPath = null
) {
const goodFundManagerFactory = await ethers.getContractFactory(
"GoodFundManager"
);
const currentBlock = await ethers.provider.getBlockNumber();
const simpleStaking = await goodCompoundStakingFactory
.deploy()
.then(async contract => {
await contract.init(
token,
itoken,
nameService.address,
"Good Decimals",
"gcDecimals",
blocksThreshold,
tokenUsdOracle,
compUsdOracle.address,
swapPath || [token, dai.address]
);
return contract;
});
let encodedData = goodFundManagerFactory.interface.encodeFunctionData(
"setStakingReward",
["100", simpleStaking.address, currentBlock, currentBlock + 10000, false]
);
await genericCall(goodFundManager.address, encodedData, avatar, 0);
encodedData = goodCompoundStakingFactory.interface.encodeFunctionData(
"setcollectInterestGasCostParams",
["250000", "150000"]
);
await genericCall(simpleStaking.address, encodedData, avatar, 0);
return simpleStaking;
}
});