UNPKG

@gooddollar/goodprotocol

Version:
625 lines (559 loc) 23.3 kB
import { ethers, upgrades } from "hardhat"; import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import { BigNumber, Contract } from "ethers"; import { expect } from "chai"; import { GoodMarketMaker, GoodReserveCDai } from "../../types"; import { createDAO, deployUniswap } from "../helpers"; import ContributionCalculation from "@gooddollar/goodcontracts/stakingModel/build/contracts/ContributionCalculation.json"; import IUniswapV2Pair from "@uniswap/v2-core/build/IUniswapV2Pair.json"; import UniswapV2Factory from "@uniswap/v2-core/build/UniswapV2Factory.json"; import WETH9 from "@uniswap/v2-periphery/build/WETH9.json"; import UniswapV2Router02 from "@uniswap/v2-periphery/build/UniswapV2Router02.json"; const MaxUint256 = ethers.constants.MaxUint256; const BN = ethers.BigNumber; export const NULL_ADDRESS = ethers.constants.AddressZero; export const BLOCK_INTERVAL = 1; describe("GoodReserve - buy/sell with any token through uniswap", () => { let dai: Contract, tokenA: Contract, tokenB: Contract, pair: Contract, ABpair: Contract, wethPair: Contract, uniswapRouter: Contract, comp; let cDAI; let goodReserve: Contract; let goodDollar, avatar, identity, marketMaker: GoodMarketMaker, exchangeHelper: Contract, contribution, controller, founder, staker, schemeMock, signers, setDAOAddress, nameService, initializeToken; before(async () => { [founder, staker, ...signers] = await ethers.getSigners(); schemeMock = signers.pop(); let { controller: ctrl, avatar: av, gd, identity, daoCreator, nameService: ns, setDAOAddress: sda, setSchemes, marketMaker: mm, daiAddress, cdaiAddress, reserve, setReserveToken, COMP } = await loadFixture(createDAO); const cdaiFactory = await ethers.getContractFactory("cDAIMock"); const daiFactory = await ethers.getContractFactory("DAIMock"); const routerFactory = new ethers.ContractFactory( UniswapV2Router02.abi, UniswapV2Router02.bytecode, founder ); const uniswapFactory = new ethers.ContractFactory( UniswapV2Factory.abi, UniswapV2Factory.bytecode, founder ); const wethFactory = new ethers.ContractFactory( WETH9.abi, WETH9.bytecode, founder ); dai = await daiFactory.deploy(); cDAI = await cdaiFactory.deploy(dai.address); tokenA = await daiFactory.deploy(); // Another erc20 token for uniswap router test tokenB = await daiFactory.deploy(); // another erc20 for uniswap router test dai = await ethers.getContractAt("DAIMock", daiAddress); cDAI = await ethers.getContractAt("cDAIMock", cdaiAddress); comp = COMP; const uniswap = await deployUniswap(comp, dai); uniswapRouter = uniswap.router; avatar = av; controller = ctrl; setDAOAddress = sda; nameService = ns; initializeToken = setReserveToken; console.log("deployed dao", { founder: founder.address, gd, identity, controller, avatar }); 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 }); goodReserve = reserve; await setDAOAddress("UNISWAP_ROUTER", uniswapRouter.address); const exchangeHelperFactory = await ethers.getContractFactory( "ExchangeHelper" ); exchangeHelper = await upgrades.deployProxy( exchangeHelperFactory, [nameService.address], { kind: "uups" } ); await exchangeHelper.setAddresses(); await setDAOAddress("EXCHANGE_HELPER", exchangeHelper.address); await uniswap.factory.createPair(tokenA.address, dai.address); // Create tokenA and dai pair await uniswap.factory.createPair(tokenA.address, tokenB.address); const pairAddress = uniswap.factory.getPair(tokenA.address, dai.address); const ABpairaddress = uniswap.factory.getPair( tokenA.address, tokenB.address ); pair = new Contract( pairAddress, JSON.stringify(IUniswapV2Pair.abi), staker ).connect(founder); ABpair = new Contract( ABpairaddress, JSON.stringify(IUniswapV2Pair.abi), staker ).connect(founder); wethPair = uniswap.daiPairContract; await setDAOAddress("MARKET_MAKER", marketMaker.address); await setDAOAddress("FUND_MANAGER", founder.address); }); it("should returned fixed 0.0001 market price", async () => { const gdPrice = await goodReserve["currentPrice()"](); const cdaiWorthInGD = gdPrice.mul(BN.from("100000000")); const gdFloatPrice = gdPrice.toNumber() / 10 ** 8; //cdai 8 decimals expect(gdFloatPrice).to.be.equal(0.0001); expect(cdaiWorthInGD.toString()).to.be.equal("1000000000000"); //in 8 decimals precision expect(cdaiWorthInGD.toNumber() / 10 ** 8).to.be.equal(10000); }); // it("should returned price of gd in tokenA", async () => { // let mintAmount = ethers.utils.parseEther("100"); // let depositAmount = ethers.utils.parseEther("50"); // await dai["mint(uint256)"](mintAmount); // await tokenA["mint(uint256)"](mintAmount); // await addLiquidity(depositAmount, depositAmount); // const gdPrice = await goodReserve["currentPrice(address)"](tokenA.address); // const gdFloatPrice = gdPrice.toNumber() / 10 ** 18; //dai 18 decimals // expect(gdFloatPrice).to.be.equal(0.000100706867869197); // await pair.transfer(pair.address, pair.balanceOf(founder.address)); // await pair.burn(founder.address); // }); it("should be able to buy gd with tokenA through UNISWAP", async () => { let daiAmount = ethers.utils.parseEther("10"); const cdaiRateStored = await cDAI.exchangeRateStored(); let amount = daiAmount .div(BigNumber.from(10).pow(10)) .mul(BigNumber.from(10).pow(28)) .div(cdaiRateStored); let mintAmount = ethers.utils.parseEther("100"); let depositAmount = ethers.utils.parseEther("50"); let buyAmount = ethers.utils.parseEther("12.5376128385155467"); // Amount to get 10 DAI await dai["mint(uint256)"](mintAmount); expect(await nameService.getAddress("DAI")).to.be.equal(dai.address); await tokenA["mint(uint256)"](mintAmount); await addLiquidity(depositAmount, depositAmount); let reserveToken = await marketMaker.reserveTokens(cDAI.address); let reserveBalanceBefore = reserveToken.reserveSupply; let supplyBefore = reserveToken.gdSupply; let rrBefore = reserveToken.reserveRatio; const gdBalanceBefore = await goodDollar.balanceOf(founder.address); const tokenABalanceBefore = await tokenA.balanceOf(founder.address); const cDAIBalanceReserveBefore = await cDAI.balanceOf(goodReserve.address); const priceBefore = await goodReserve["currentPrice()"](); await tokenA.approve(exchangeHelper.address, buyAmount); // let gdPriceInTokenABefore = await goodReserve["currentPrice(address)"]( // tokenA.address // ); let transaction = await ( await exchangeHelper.buy( [tokenA.address, dai.address], buyAmount, 0, 0, NULL_ADDRESS ) ).wait(); // let gdPriceInTokenAAfter = await goodReserve["currentPrice(address)"]( // tokenA.address // ); // expect(gdPriceInTokenAAfter.gt(gdPriceInTokenABefore)); reserveToken = await marketMaker.reserveTokens(cDAI.address); let reserveBalanceAfter = reserveToken.reserveSupply; let supplyAfter = reserveToken.gdSupply; let rrAfter = reserveToken.reserveRatio; const gdBalanceAfter = await goodDollar.balanceOf(founder.address); const tokenABalanceAfter = await tokenA.balanceOf(founder.address); const cDAIBalanceReserveAfter = await cDAI.balanceOf(goodReserve.address); const priceAfter = await goodReserve["currentPrice()"](); expect( (cDAIBalanceReserveAfter - cDAIBalanceReserveBefore).toString() ).to.be.equal(amount.toString()); expect( reserveBalanceAfter.sub(reserveBalanceBefore).toString() ).to.be.equal(amount.toString()); expect(supplyAfter.sub(supplyBefore).toString()).to.be.equal( gdBalanceAfter.sub(gdBalanceBefore).toString() ); expect(rrAfter.toString()).to.be.equal(rrBefore.toString()); expect(gdBalanceAfter.gt(gdBalanceBefore)).to.be.true; expect(tokenABalanceBefore.gt(tokenABalanceAfter)).to.be.true; expect(priceAfter.toString()).to.be.equal(priceBefore.toString()); expect(transaction.events.find(_ => _.event === "TokenPurchased")).to.be.not .empty; }); it("should be able to sell gd to tokenA through UNISWAP", async () => { let amount = -10000; let mintAmount = ethers.utils.parseEther("100"); let sellAmount = BN.from("100"); await dai["mint(uint256)"](mintAmount); expect(await nameService.getAddress("DAI")).to.be.equal(dai.address); await tokenA["mint(uint256)"](mintAmount); let reserveToken = await marketMaker.reserveTokens(cDAI.address); let reserveBalanceBefore = reserveToken.reserveSupply; let supplyBefore = reserveToken.gdSupply; let rrBefore = reserveToken.reserveRatio; const gdBalanceBefore = await goodDollar.balanceOf(founder.address); const tokenABalanceBefore = await tokenA.balanceOf(founder.address); const cDAIBalanceReserveBefore = await cDAI.balanceOf(goodReserve.address); const priceBefore = await goodReserve["currentPrice()"](); await goodDollar.approve(exchangeHelper.address, sellAmount); // let gdPriceInTokenABefore = await goodReserve["currentPrice(address)"]( // tokenA.address // ); let transaction = await ( await exchangeHelper.sell( [dai.address, tokenA.address], sellAmount, 0, 0, NULL_ADDRESS ) ).wait(); // let gdPriceInTokenAAfter = await goodReserve["currentPrice(address)"]( // tokenA.address // ); reserveToken = await marketMaker.reserveTokens(cDAI.address); let reserveBalanceAfter = reserveToken.reserveSupply; let supplyAfter = reserveToken.gdSupply; let rrAfter = reserveToken.reserveRatio; const gdBalanceAfter = await goodDollar.balanceOf(founder.address); const tokenABalanceAfter = await tokenA.balanceOf(founder.address); const cDAIBalanceReserveAfter = await cDAI.balanceOf(goodReserve.address); const priceAfter = await goodReserve["currentPrice()"](); expect(cDAIBalanceReserveBefore.gt(cDAIBalanceReserveAfter)).to.be.true; // expect(gdPriceInTokenABefore.gt(gdPriceInTokenAAfter)); expect( reserveBalanceAfter.sub(reserveBalanceBefore).toString() ).to.be.equal(amount.toString()); expect(supplyAfter.sub(supplyBefore).toString()).to.be.equal( gdBalanceAfter.sub(gdBalanceBefore).toString() ); expect(tokenABalanceAfter.gt(tokenABalanceBefore)).to.be.true; expect(rrAfter.toString()).to.be.equal(rrBefore.toString()); expect(gdBalanceBefore.gt(gdBalanceAfter)).to.be.true; expect(priceAfter.toString()).to.be.equal(priceBefore.toString()); expect(transaction.events.find(_ => _.event === "TokenSold")).to.be.not .empty; }); it("should be able to buy gd with tokenA through UNISWAP for some other address", async () => { let daiAmount = ethers.utils.parseEther("10"); const cdaiRateStored = await cDAI.exchangeRateStored(); let amount = daiAmount .div(BigNumber.from(10).pow(10)) .mul(BigNumber.from(10).pow(28)) .div(cdaiRateStored); let mintAmount = ethers.utils.parseEther("100"); let depositAmount = ethers.utils.parseEther("50"); let buyAmount = ethers.utils.parseEther("14.109529451802348229"); // Amount to get 10 DAI await dai["mint(uint256)"](mintAmount); expect(await nameService.getAddress("DAI")).to.be.equal(dai.address); await tokenA["mint(uint256)"](mintAmount); await addLiquidity(depositAmount, depositAmount); let reserveToken = await marketMaker.reserveTokens(cDAI.address); let reserveBalanceBefore = reserveToken.reserveSupply; let supplyBefore = reserveToken.gdSupply; let rrBefore = reserveToken.reserveRatio; const gdBalanceBefore = await goodDollar.balanceOf(staker.address); const tokenABalanceBefore = await tokenA.balanceOf(founder.address); const cDAIBalanceReserveBefore = await cDAI.balanceOf(goodReserve.address); const priceBefore = await goodReserve["currentPrice()"](); await tokenA.approve(exchangeHelper.address, buyAmount); let transaction = await ( await exchangeHelper.buy( [tokenA.address, dai.address], buyAmount, 0, 0, staker.address ) ).wait(); reserveToken = await marketMaker.reserveTokens(cDAI.address); let reserveBalanceAfter = reserveToken.reserveSupply; let supplyAfter = reserveToken.gdSupply; let rrAfter = reserveToken.reserveRatio; const gdBalanceAfter = await goodDollar.balanceOf(staker.address); const tokenABalanceAfter = await tokenA.balanceOf(founder.address); const cDAIBalanceReserveAfter = await cDAI.balanceOf(goodReserve.address); const priceAfter = await goodReserve["currentPrice()"](); expect( (cDAIBalanceReserveAfter - cDAIBalanceReserveBefore).toString() ).to.be.equal(amount.toString()); expect( reserveBalanceAfter.sub(reserveBalanceBefore).toString() ).to.be.equal(amount.toString()); expect(supplyAfter.sub(supplyBefore).toString()).to.be.equal( gdBalanceAfter.sub(gdBalanceBefore).toString() ); expect(rrAfter.toString()).to.be.equal(rrBefore.toString()); expect(gdBalanceAfter.gt(gdBalanceBefore)).to.be.true; expect(tokenABalanceBefore.gt(tokenABalanceAfter)).to.be.true; expect(priceAfter.toString()).to.be.equal(priceBefore.toString()); expect(transaction.events.find(_ => _.event === "TokenPurchased")).to.be.not .empty; }); it("should be able to sell gd to tokenA through UNISWAP for some other address", async () => { let amount = -1000; let mintAmount = ethers.utils.parseEther("100"); let sellAmount = BN.from("10"); await dai["mint(uint256)"](mintAmount); expect(await nameService.getAddress("DAI")).to.be.equal(dai.address); await tokenA["mint(uint256)"](mintAmount); let reserveToken = await marketMaker.reserveTokens(cDAI.address); let reserveBalanceBefore = reserveToken.reserveSupply; let supplyBefore = reserveToken.gdSupply; let rrBefore = reserveToken.reserveRatio; const gdBalanceBefore = await goodDollar.balanceOf(founder.address); const tokenABalanceBefore = await tokenA.balanceOf(staker.address); const cDAIBalanceReserveBefore = await cDAI.balanceOf(goodReserve.address); const priceBefore = await goodReserve["currentPrice()"](); await goodDollar.approve(exchangeHelper.address, sellAmount); let transaction = await ( await exchangeHelper.sell( [dai.address, tokenA.address], sellAmount, 0, 0, staker.address ) ).wait(); reserveToken = await marketMaker.reserveTokens(cDAI.address); let reserveBalanceAfter = reserveToken.reserveSupply; let supplyAfter = reserveToken.gdSupply; let rrAfter = reserveToken.reserveRatio; const gdBalanceAfter = await goodDollar.balanceOf(founder.address); const tokenABalanceAfter = await tokenA.balanceOf(staker.address); const cDAIBalanceReserveAfter = await cDAI.balanceOf(goodReserve.address); const priceAfter = await goodReserve["currentPrice()"](); expect(cDAIBalanceReserveBefore.gt(cDAIBalanceReserveAfter)).to.be.true; expect( reserveBalanceAfter.sub(reserveBalanceBefore).toString() ).to.be.equal(amount.toString()); expect(supplyAfter.sub(supplyBefore).toString()).to.be.equal( gdBalanceAfter.sub(gdBalanceBefore).toString() ); expect(tokenABalanceAfter.gt(tokenABalanceBefore)).to.be.true; expect(rrAfter.toString()).to.be.equal(rrBefore.toString()); expect(gdBalanceBefore.gt(gdBalanceAfter)).to.be.true; expect(priceAfter.toString()).to.be.equal(priceBefore.toString()); expect(transaction.events.find(_ => _.event === "TokenSold")).to.be.not .empty; }); it("shouldn't be able to buy gd with tokenA through UNISWAP without approve", async () => { let depositAmount = ethers.utils.parseEther("5"); tokenA.approve(exchangeHelper.address, "0"); await expect( exchangeHelper.buy( [tokenA.address, dai.address], depositAmount, 0, 0, NULL_ADDRESS ) ).to.be.revertedWith(/ERC20: insufficient allowance/); }); it("shouldn't be able to sell gd to tokenA through UNISWAP without approve", async () => { let sellAmount = BN.from("5"); goodDollar.approve(exchangeHelper.address, "0"); await expect( exchangeHelper.sell( [dai.address, tokenA.address], sellAmount, 0, 0, NULL_ADDRESS ) ).to.be.reverted; }); it("should increase price after buy when RR is not 100%", async () => { await initializeToken( cDAI.address, "100", //1gd "10000", //0.0001 cDai "500000" //50% rr ); let reserveToken = await marketMaker.reserveTokens(cDAI.address); let beforeGdBalance = await goodDollar.balanceOf(founder.address); let buyAmount = BN.from("500000000000000000000000"); // 500k dai await dai["mint(uint256)"](buyAmount); let gdPriceBefore = await goodReserve["currentPrice()"](); dai.approve(exchangeHelper.address, buyAmount); await exchangeHelper["buy(address[],uint256,uint256,uint256,address)"]( [dai.address], buyAmount, 0, 0, NULL_ADDRESS ); let gdPriceAfter = await goodReserve["currentPrice()"](); let laterGdBalance = await goodDollar.balanceOf(founder.address); expect(beforeGdBalance.lt(laterGdBalance)); // GD balance of founder should increase expect(gdPriceAfter.gt(gdPriceBefore)); // GD price should increase }); it("should increase price after sell when RR is not 100%", async () => { let reserveTokenBefore = await marketMaker.reserveTokens(cDAI.address); let reserveRatioBefore = reserveTokenBefore.reserveRatio; let sellAmount = BN.from("5000000"); // Sell 50k GD let gdPriceBefore = await goodReserve["currentPrice()"](); let daiBalanceBefore = await dai.balanceOf(founder.address); goodDollar.approve(exchangeHelper.address, sellAmount); await exchangeHelper["sell(address[],uint256,uint256,uint256,address)"]( [dai.address], sellAmount, 0, 0, NULL_ADDRESS ); let reserveTokenAfter = await marketMaker.reserveTokens(cDAI.address); let reserveRatioAfter = reserveTokenAfter.reserveRatio; let gdPriceAfter = await goodReserve["currentPrice()"](); let daiBalanceAfter = await dai.balanceOf(founder.address); expect(gdPriceAfter.lt(gdPriceBefore)); // GD price should decrease expect(daiBalanceAfter.gt(daiBalanceBefore)); // DAI balance of founder should increase expect(reserveRatioBefore).to.be.equal(reserveRatioAfter); // RR should stay same }); it("should be able to buy GD with ETH", async () => { let mintAmount = ethers.utils.parseEther("100"); const ETHAmount = ethers.utils.parseEther("5"); await dai["mint(uint256)"](mintAmount); let buyAmount = ethers.utils.parseEther("10"); const gdBalanceBeforeSwap = await goodDollar.balanceOf(founder.address); let transaction = await ( await exchangeHelper.buy( [ethers.constants.AddressZero, dai.address], buyAmount, 0, 0, founder.address, { value: buyAmount } ) ).wait(); const gdBalanceAfterSwap = await goodDollar.balanceOf(founder.address); expect(gdBalanceAfterSwap.gt(gdBalanceBeforeSwap)).to.be.true; // Gd balance after swap should greater than before swap }); it("should be able to sell GD for ETH", async () => { const sellAmount = BN.from("1000"); // 10gd const ethBalanceBeforeSwap = await ethers.provider.getBalance( founder.address ); await goodDollar.approve(exchangeHelper.address, sellAmount); let transaction = await ( await exchangeHelper.sell( [dai.address, NULL_ADDRESS], sellAmount, 0, 0, founder.address ) ).wait(); const ethBalanceAfterSwap = await ethers.provider.getBalance( founder.address ); expect(ethBalanceAfterSwap.gt(ethBalanceBeforeSwap)); }); it("should not be able to buy Gd with token when path is invalid", async () => { const buyAmount = ethers.utils.parseEther("10"); const depositAmount = ethers.utils.parseEther("10000"); await tokenB["mint(uint256)"](buyAmount.add(depositAmount)); await tokenA["mint(uint256)"](depositAmount); await tokenA.transfer(ABpair.address, depositAmount); await tokenB.transfer(ABpair.address, depositAmount); await ABpair.mint(founder.address); await tokenB.approve(exchangeHelper.address, buyAmount); const tx = await exchangeHelper .buy([tokenB.address, dai.address], buyAmount, 0, 0, NULL_ADDRESS) .catch(e => e); expect(tx.message).to.be.not.empty; }); it("it should able to buy gd with multiple swaps through UNISWAP", async () => { const buyAmount = ethers.utils.parseEther("10"); await tokenB["mint(uint256)"](buyAmount); const gdBalanceBeforeBuy = await goodDollar.balanceOf(founder.address); await exchangeHelper.buy( [tokenB.address, tokenA.address, dai.address], buyAmount, 0, 0, NULL_ADDRESS ); const gdBalanceAfterBuy = await goodDollar.balanceOf(founder.address); expect(gdBalanceAfterBuy).to.be.gt(gdBalanceBeforeBuy); }); it("it should be able to sell GD with multiple swaps through UNISWAP", async () => { const sellAmount = "100"; await goodDollar.approve(exchangeHelper.address, sellAmount); const tokenBBalanceBeforeSell = await tokenB.balanceOf(founder.address); await exchangeHelper.sell( [dai.address, tokenA.address, tokenB.address], sellAmount, 0, 0, NULL_ADDRESS ); const tokenBBalanceAfterSell = await tokenB.balanceOf(founder.address); expect(tokenBBalanceAfterSell).to.be.gt(tokenBBalanceBeforeSell); }); async function addLiquidity( token0Amount: BigNumber, token1Amount: BigNumber ) { await tokenA.transfer(pair.address, token0Amount); await dai.transfer(pair.address, token1Amount); await pair.mint(founder.address); } async function addETHLiquidity( token0Amount: BigNumber, WETHAmount: BigNumber ) { await dai.approve(uniswapRouter.address, MaxUint256); await uniswapRouter.addLiquidityETH( dai.address, token0Amount, token0Amount, WETHAmount, founder.address, MaxUint256, { value: WETHAmount } ); } });