@gooddollar/goodprotocol
Version:
GoodDollar Protocol
625 lines (559 loc) • 23.3 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 { 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
}
);
}
});