UNPKG

@gooddollar/goodprotocol

Version:
365 lines (337 loc) 11.9 kB
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; } });