import { default as hre, ethers, upgrades } from "hardhat";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { expect } from "chai";
import { GoodMarketMaker, CERC20 } from "../../types";
import { createDAO, increaseTime } from "../helpers";

const BN = ethers.BigNumber;
export const NULL_ADDRESS = ethers.constants.AddressZero;

let cdai: CERC20;

describe("GoodMarketMaker - calculate gd value at reserve", () => {
  let goodDollar,
    controller,
    avatar,
    formula,
    marketMaker: GoodMarketMaker,
    dai,
    cdai,
    founder,
    staker,
    signers,
    initializeToken,
    fakeReserve;

  const deployDAIMock = async () => {
    let dai = await (await ethers.getContractFactory("DAIMock")).deploy();

    return dai.address;
  };

  const deploycDAIMock = async () => {
    let cdai = await (await ethers.getContractFactory("cDAIMock")).deploy(dai);

    return cdai.address;
  };

  before(async () => {
    [founder, staker, fakeReserve, ...signers] = await ethers.getSigners();

    let {
      controller: ctrl,
      avatar: av,
      gd: goodDollar,
      identity,
      daoCreator,
      nameService,
      marketMaker: mm,
      setSchemes,
      setReserveToken,
      setDAOAddress
    } = await loadFixture(createDAO);
    avatar = av;
    marketMaker = mm.connect(fakeReserve);
    controller = ctrl;
    initializeToken = setReserveToken;

    console.log("deployed dao", { goodDollar, identity, controller, avatar });

    dai = await deployDAIMock();
    cdai = await deploycDAIMock();

    //give founder generic call permission
    await setSchemes([founder.address]);
    await setDAOAddress("RESERVE", fakeReserve.address);
    console.log("starting tests...");
  });

  it("should initialize a token with 0 ratio and the ratio should calculate as 100% by default", async () => {
    let dai = await deployDAIMock();
    await initializeToken(
      dai,
      "100",
      "10000",
      "0" //0% rr
    );
    let newreserveratio = await marketMaker.calculateNewReserveRatio(dai);
    expect(newreserveratio.toString()).to.be.equal("1000000"); // no need to expand at day 0
    await increaseTime(24 * 60 * 60);
    newreserveratio = await marketMaker.calculateNewReserveRatio(dai);
    expect(newreserveratio.toString()).to.be.equal("999388", "first expansion"); // result of initial expansion rate * 100% ratio
    await increaseTime(24 * 60 * 60 * 0.5); //expansion should happen at full day intervals
    newreserveratio = await marketMaker.calculateNewReserveRatio(dai);
    expect(newreserveratio.toString()).to.be.equal("999388", "day and half");
    await increaseTime(24 * 60 * 60 * 0.5); //expansion should happen at full day intervals
    newreserveratio = await marketMaker.calculateNewReserveRatio(dai);
    expect(newreserveratio.toString()).to.be.equal(
      "998778",
      "second expansion"
    );
  });

  it("should update token reserve ratio after first 24 hours", async () => {
    let dai1 = await deployDAIMock();
    await initializeToken(
      dai1,
      "100",
      "10000",
      "0" //0% rr
    );
    await increaseTime(24 * 60 * 60);
    await marketMaker.expandReserveRatio(dai1);
    const rr = await marketMaker.reserveTokens(dai1);
    expect(rr["1"].toString()).to.be.equal("999388"); // result of initial expansion rate * 100% ratio
  });

  it("should initialize token with price", async () => {
    const expansion = await initializeToken(
      cdai,
      "100", //1gd
      "10000", //0.0001 cDai
      "1000000" //100% rr
    );
    const price = await marketMaker.currentPrice(cdai);
    expect(price.toString()).to.be.equal("10000"); //1gd is equal 0.0001 cDAI = 100000 wei;
    const onecDAIReturn = await marketMaker.buyReturn(
      cdai,
      "100000000" //1cDai
    );
    expect(onecDAIReturn.toNumber() / 100).to.be.equal(10000); //0.0001 cdai is 1 gd, so for 1eth you get 10000 gd (divide by 100 to account for 2 decimals precision)
  });

  it("should calculate mint UBI correctly for 8 decimals precision when price is 0.0001 and 100% ratio", async () => {
    const expansion = await initializeToken(
      cdai,
      "100", //1gd
      "10000", //0.0001 cDai
      "1000000" //100% rr
    );
    const gdPrice = await marketMaker.currentPrice(cdai);
    const toMint = await marketMaker.calculateMintInterest(cdai, "100000000");
    const expectedTotalMinted = 10 ** 8 / gdPrice.toNumber(); //1cdai divided by gd price;
    expect(expectedTotalMinted).to.be.equal(10000); //1k GD since price is 0.0001 cdai for 1 gd
    expect(toMint.toString()).to.be.equal(
      (expectedTotalMinted * 100).toString()
    ); //add 2 decimals precision
  });

  it("should update reserve ratio by days passed", async () => {
    const expansion = await marketMaker.reserveRatioDailyExpansion();
    // 20% yearly. set up in the constructor
    expect(expansion.toString()).to.be.equal("999388834642296000000000000");
    await increaseTime(24 * 60 * 60);
    await marketMaker.expandReserveRatio(cdai);
    const daytwoRR = await marketMaker.reserveTokens(cdai);
    // after interval expansion
    expect(daytwoRR["1"].toString()).to.be.equal("999388");
    await increaseTime(24 * 60 * 60);
    await marketMaker.expandReserveRatio(cdai);
    const daythreeRR = await marketMaker.reserveTokens(cdai);
    // after interval expansion
    expect(daythreeRR["1"].toString()).to.be.equal("998777");
    await increaseTime(24 * 60 * 60 * 7);
    //after extra 7 days.
    await marketMaker.expandReserveRatio(cdai);
    expect(
      await marketMaker
        .reserveTokens(cdai)
        .then(_ => _["reserveRatio"].toString())
    ).to.be.equal("994511"); // 998777 * 0.999388834642296000000000000^7
  });

  it("should not return a sell contribution if the given gd is less than the given contribution amount", async () => {
    let dai = await deployDAIMock();

    const MM = await ethers.getContractFactory("GoodMarketMaker", fakeReserve);
    const marketMaker1 = await upgrades.deployProxy(MM, [
      await marketMaker.nameService(),
      999388834642296,
      1e15
    ]);
    await marketMaker1.initializeToken(
      dai,
      "100", //1gd
      ethers.utils.parseEther("0.0001"),
      "800000", //80% rr,
      0
    );
    const res = marketMaker1.sellWithContribution(
      dai,
      ethers.utils.parseEther("1"),
      ethers.utils.parseEther("2")
    );
    await expect(res).to.be.revertedWith(
      /GD amount is lower than the contribution amount/
    );
  });

  it("should be able to calculate and update bonding curve gd balance based on oncoming cDAI and the price stays the same", async () => {
    let priceBefore = await marketMaker.currentPrice(cdai);
    await marketMaker.mintInterest(cdai, BN.from(1e8));
    let priceAfter = await marketMaker.currentPrice(cdai);

    expect(priceAfter).gt(0);
    expect(priceBefore).gt(0);
    expect(Math.floor(priceAfter.toNumber() / 100).toString()).to.be.equal(
      Math.floor(priceBefore.toNumber() / 100).toString()
    );

    // very large amount of cdai
    priceBefore = await marketMaker.currentPrice(cdai);
    await marketMaker.mintInterest(cdai, ethers.utils.parseEther("1"));
    priceAfter = await marketMaker.currentPrice(cdai);

    console.log({ priceAfter, priceBefore });
    expect(priceAfter).gt(0);
    expect(priceBefore).gt(0);
    expect(priceAfter).to.eq(priceBefore);
  });

  it("should not be able to mint interest by a non owner", async () => {
    let res = marketMaker.connect(staker).mintInterest(cdai, BN.from(1e8));

    await expect(res).to.be.revertedWith(
      /GoodMarketMaker: not Reserve or Avatar/
    );
  });

  it("should not be able to mint expansion by a non owner", async () => {
    let res = marketMaker.connect(staker).mintExpansion(cdai);
    await expect(res).to.be.revertedWith(
      /GoodMarketMaker: not Reserve or Avatar/
    );
  });

  it("should mint 0 gd tokens if the add token supply is 0", async () => {
    let reserveTokenBefore = await marketMaker.reserveTokens(cdai);
    let gdSupplyBefore = reserveTokenBefore.gdSupply;
    await marketMaker.mintInterest(cdai, "0");
    let reserveTokenAfter = await marketMaker.reserveTokens(cdai);
    let gdSupplyAfter = reserveTokenAfter.gdSupply;
    expect(gdSupplyAfter.toString()).to.be.equal(gdSupplyBefore.toString());
  });

  it("should be able to update the reserve ratio only by the owner", async () => {
    let res = marketMaker.connect(staker).expandReserveRatio(cdai);
    await expect(res).to.be.revertedWith(
      /GoodMarketMaker: not Reserve or Avatar/
    );
  });

  it("should be able to mint interest only by the owner", async () => {
    let res = marketMaker.connect(staker).mintInterest(cdai, BN.from(1e8));
    await expect(res).to.be.revertedWith(
      /GoodMarketMaker: not Reserve or Avatar/
    );
  });

  it("should be able to mint expansion only by the owner", async () => {
    let res = marketMaker.connect(staker).mintExpansion(cdai);
    await expect(res).to.be.revertedWith(
      /GoodMarketMaker: not Reserve or Avatar/
    );
  });

  it("should be able to calculate minted gd based on expansion of reserve ratio, the price stays the same", async () => {
    let reserveTokenBefore = await marketMaker.reserveTokens(cdai);
    let reserveRatioBefore = reserveTokenBefore.reserveRatio;
    await increaseTime(24 * 60 * 60);
    const priceBefore = await marketMaker.currentPrice(cdai);
    const toMint = await marketMaker.calculateMintExpansion(cdai);
    expect(toMint.toString()).not.to.be.equal("0");
    const newRR = await marketMaker.calculateNewReserveRatio(cdai);
    expect(reserveRatioBefore.toString()).not.to.be.equal(newRR.toString());
    const priceAfter = await marketMaker.currentPrice(cdai);
    expect(priceAfter.toString()).to.be.equal(priceBefore.toString());
  });

  it("should be able to calculate and update gd supply based on expansion of reserve ratio, the price stays the same", async () => {
    let reserveTokenBefore = await marketMaker.reserveTokens(cdai);
    let gdSupplyBefore = reserveTokenBefore.gdSupply;
    let reserveRatioBefore = reserveTokenBefore.reserveRatio;
    const priceBefore = await marketMaker.currentPrice(cdai);
    await marketMaker.mintExpansion(cdai);
    let reserveTokenAfter = await marketMaker.reserveTokens(cdai);
    let gdSupplyAfter = reserveTokenAfter.gdSupply;
    let reserveRatioAfter = reserveTokenAfter.reserveRatio;
    const priceAfter = await marketMaker.currentPrice(cdai);
    expect(priceAfter.toString()).to.be.equal(priceBefore.toString());
    expect(gdSupplyBefore.toString()).not.to.be.equal(gdSupplyAfter.toString());
    expect(reserveRatioBefore.toString()).not.to.be.equal(
      reserveRatioAfter.toString()
    );
  });

  it("should have new return amount when RR is not 100%", async () => {
    const expansion = await initializeToken(
      dai,
      "100", //1gd
      ethers.utils.parseEther("0.0001"), //0.0001 dai
      "800000" //80% rr
    );
    const price = await marketMaker.currentPrice(dai);
    expect(price.toString()).to.be.equal(ethers.utils.parseEther("0.000125")); //1gd is equal 0.0001/1*0.8 = 0.000125
    const oneDAIReturn = await marketMaker.buyReturn(
      dai,
      ethers.utils.parseEther("1") //1Dai
    );

    //bancor formula to calcualte return
    //gd return = gdsupply * ((1+tokenamount/tokensupply)^rr -1)
    const expectedReturn = 1 * ((1 + 1 / 0.0001) ** 0.8 - 1);
    expect(oneDAIReturn.toNumber() / 100).to.be.equal(
      Math.floor(expectedReturn * 100) / 100
    );
  });

  it("should calculate mint UBI correctly for 18 decimals precision", async () => {
    const gdPrice = await marketMaker.currentPrice(dai);
    const toMint = await marketMaker.calculateMintInterest(
      dai,
      ethers.utils.parseEther("1")
    );
    let reserveToken = await marketMaker.reserveTokens(dai);

    //we expect price to stay the same p = reserve/supply*RR
    const newPrice = reserveToken.reserveSupply
      .add(ethers.utils.parseEther("1"))
      .mul(1e8)
      .div(reserveToken.gdSupply.add(toMint).mul(reserveToken.reserveRatio));
    expect(newPrice).equal(gdPrice);

    // the formula is amountToMint = reserveInterest * tokenSupply / reserveBalance
    const expectedTotalMinted =
      (10 ** 18 * reserveToken.gdSupply.toNumber()) /
      reserveToken.reserveSupply.toNumber();
    expect(expectedTotalMinted).to.be.equal(1000000);
    expect(toMint.toString()).to.be.equal(expectedTotalMinted.toString());
  });

  it("should calculate sell return with cDAI", async () => {
    await initializeToken(
      cdai,
      "1000000000", //1gd
      "100000000000", //0.0001 cDai
      "900000" //80% rr
    );
    const gDReturn = await marketMaker.sellReturn(
      cdai,
      10 //0.1 gd
    );
    let reserveToken = await marketMaker.reserveTokens(cdai);
    let reserveBalance = reserveToken.reserveSupply.toNumber();
    let sellAmount = 10;
    let supply = reserveToken.gdSupply.toNumber();
    let rr = reserveToken.reserveRatio;
    // sell formula (as in calculateSaleReturn):
    // return = reserveBalance * (1 - (1 - sellAmount / supply) ^ (1000000 / reserveRatio))
    const expectedReturn =
      reserveBalance * (1 - (1 - sellAmount / supply) ** (1000000 / rr));
    expect(gDReturn.toNumber()).to.be.equal(Math.floor(expectedReturn));
  });

  it("should calculate sell return with DAI", async () => {
    const gDReturn = await marketMaker.sellReturn(
      dai,
      10 //0.1 gd
    );
    let reserveToken = await marketMaker.reserveTokens(dai);
    let reserveBalance = reserveToken.reserveSupply.toNumber();
    let sellAmount = 10;
    let supply = reserveToken.gdSupply.toNumber();
    let rr = reserveToken.reserveRatio;
    // sell formula (as in calculateSaleReturn):
    // return = reserveBalance * (1 - (1 - sellAmount / supply) ^ (1000000 / reserveRatio))
    const expectedReturn =
      reserveBalance * (1 - (1 - sellAmount / supply) ** (1000000 / rr));
    expect(gDReturn.toNumber()).to.be.equal(Math.floor(expectedReturn));
  });

  it("should be able to update balances based on buy return calculation", async () => {
    let reserveToken = await marketMaker.reserveTokens(dai);
    let reserveBalanceBefore = reserveToken.reserveSupply;
    let supplyBefore = reserveToken.gdSupply;
    let rrBefore = reserveToken.reserveRatio;
    let amount = ethers.utils.parseEther("1");
    let transaction = await (
      await marketMaker.buy(
        dai,
        ethers.utils.parseEther("1") //1Dai
      )
    ).wait();
    reserveToken = await marketMaker.reserveTokens(dai);
    let reserveBalanceAfter = reserveToken.reserveSupply;
    let supplyAfter = reserveToken.gdSupply;
    let rrAfter = reserveToken.reserveRatio;
    expect(transaction.events[0].event).to.be.equal("BalancesUpdated");
    expect(reserveBalanceAfter.sub(reserveBalanceBefore)).to.be.equal(
      BN.from(amount)
    );
    expect(supplyAfter.sub(supplyBefore)).to.be.equal(
      transaction.events[0].args.returnAmount
    );
    expect(rrAfter.toString()).to.be.equal(rrBefore.toString());
  });

  it("should be able to update balances based on sell return calculation", async () => {
    let reserveToken = await marketMaker.reserveTokens(dai);
    let reserveBalanceBefore = reserveToken.reserveSupply;
    let supplyBefore = reserveToken.gdSupply;
    let rrBefore = reserveToken.reserveRatio;
    let amount = 100;
    let transaction = await (
      await marketMaker.sellWithContribution(dai, 100, 0)
    ).wait();
    reserveToken = await marketMaker.reserveTokens(dai);
    let reserveBalanceAfter = reserveToken.reserveSupply;
    let supplyAfter = reserveToken.gdSupply;
    let rrAfter = reserveToken.reserveRatio;
    expect(transaction.events[0].event).to.be.equal("BalancesUpdated");
    expect(
      reserveBalanceAfter
        .add(transaction.events[0].args.returnAmount)
        .toString()
    ).to.be.equal(reserveBalanceBefore.toString());
    expect(supplyBefore.sub(supplyAfter)).to.be.equal(BN.from(amount));
    expect(rrAfter.toString()).to.be.equal(rrBefore.toString());
  });

  it("should be able to update balances based on sell with contribution return calculation", async () => {
    let reserveToken = await marketMaker.reserveTokens(dai);
    let reserveBalanceBefore = reserveToken.reserveSupply;
    let supplyBefore = reserveToken.gdSupply;
    let rrBefore = reserveToken.reserveRatio;
    let amount = 100;
    let transaction = await (
      await marketMaker.sellWithContribution(dai, 100, 80)
    ).wait();
    reserveToken = await marketMaker.reserveTokens(dai);
    let reserveBalanceAfter = reserveToken.reserveSupply;
    let supplyAfter = reserveToken.gdSupply;
    let rrAfter = reserveToken.reserveRatio;
    expect(transaction.events[0].event).to.be.equal("BalancesUpdated");
    expect(
      reserveBalanceAfter
        .add(transaction.events[0].args.returnAmount)
        .toString()
    ).to.be.equal(reserveBalanceBefore.toString());
    expect(supplyBefore.sub(supplyAfter)).to.be.equal(BN.from(amount));
    expect(rrAfter).gte(rrBefore); //with sell contribution G$ are burned and increase RR
  });

  it("should not be able to calculate the buy return in gd and update the bonding curve params by a non-owner account", async () => {
    let res = marketMaker
      .connect(staker)
      .buy(dai, ethers.utils.parseEther("1"));
    await expect(res).to.be.revertedWith(
      /GoodMarketMaker: not Reserve or Avatar/
    );
  });

  it("should not be able to calculate the sell return in reserve token and update the bonding curve params by a non-owner account", async () => {
    let res = marketMaker.connect(staker).sellWithContribution(dai, 100, 0);
    await expect(res).to.be.revertedWith(
      /GoodMarketMaker: not Reserve or Avatar/
    );
  });

  it("should not be able to calculate the sellWithContribution return in reserve token and update the bonding curve params by a non-owner account", async () => {
    let res = marketMaker.connect(staker).sellWithContribution(dai, 100, 80);
    await expect(res).to.be.revertedWith(
      /GoodMarketMaker: not Reserve or Avatar/
    );
  });

  it("should be able to buy only with active token", async () => {
    let reserveToken = await marketMaker.reserveTokens(cdai);
    let gdSupplyBefore = reserveToken.gdSupply;
    let reserveSupplyBefore = reserveToken.reserveSupply;
    let reserveRatioBefore = reserveToken.reserveRatio;
    await initializeToken(
      cdai,
      "0",
      reserveSupplyBefore.toString(),
      reserveRatioBefore.toString()
    );
    let res = marketMaker.buy(cdai, ethers.utils.parseEther("1"));
    await expect(res).to.be.revertedWith(/Reserve token not initialized/);
    await initializeToken(
      cdai,
      gdSupplyBefore,
      reserveSupplyBefore.toString(),
      reserveRatioBefore.toString()
    );
  });

  it("should be able to sell only with active token", async () => {
    let res = marketMaker.sellWithContribution(
      NULL_ADDRESS,
      ethers.utils.parseEther("1"),
      0
    );

    await expect(res).to.be.revertedWith(/Reserve token not initialized/);
  });

  it("should be able to sellWithContribution only with active token", async () => {
    let res = marketMaker.sellWithContribution(
      NULL_ADDRESS,
      ethers.utils.parseEther("1"),
      ethers.utils.parseEther("1")
    );
    await expect(res).to.be.revertedWith(/Reserve token not initialized/);
  });

  it("should be able to sell gd only when the amount is lower than the total supply", async () => {
    let reserveToken = await marketMaker.reserveTokens(cdai);
    let gdSupply = reserveToken.gdSupply;
    let res = marketMaker.sellWithContribution(
      cdai,
      gdSupply.add(BN.from(1)),
      0
    );
    await expect(res).to.be.revertedWith(
      /GD amount is higher than the total supply/
    );
  });

  it("should set reserve ratio daily expansion by owner", async () => {
    let denom = BN.from(1e15);
    const MM = await ethers.getContractFactory("GoodMarketMaker");
    const ctrl = await ethers.getContractAt("Controller", controller, founder);

    let currentReserveRatioDailyExpansion =
      await marketMaker.reserveRatioDailyExpansion();
    await marketMaker.setReserveRatioDailyExpansion(1, 1e15);

    let newReserveRatioDailyExpansion =
      await marketMaker.reserveRatioDailyExpansion();
    expect(newReserveRatioDailyExpansion).to.be.equal(BN.from("1000000000000"));

    await marketMaker.setReserveRatioDailyExpansion(999388834642296, 1e15);

    let reserveRatioDailyExpansion =
      await marketMaker.reserveRatioDailyExpansion();
    expect(reserveRatioDailyExpansion).to.be.equal(
      BN.from("999388834642296000000000000")
    );
  });

  it("should be able to set the reserve ratio daily expansion only by the owner", async () => {
    let res = marketMaker
      .connect(staker)
      .setReserveRatioDailyExpansion(1, 1e15);
    await expect(res).to.be.revertedWith(
      /GoodMarketMaker: not Reserve or Avatar/
    );
  });

  it("should calculate amount of gd to mint based on incoming cDAI without effecting bonding curve price", async () => {
    await initializeToken(dai, 6e11, ethers.utils.parseEther("600000"), 5e5);

    const priceBefore = await marketMaker.currentPrice(dai);

    await marketMaker.mintInterest(dai, ethers.utils.parseEther("100"));
    const priceAfter = await marketMaker.currentPrice(dai);
    expect(priceBefore.toString()).to.be.equal(priceAfter.toString());
  });

  it("should calculate amount of gd to mint based on incoming cDAI with small precision issue effecting bonding curve price in very low amounts", async () => {
    await initializeToken(dai, 600, 600000000, 5e5);

    const priceBefore = await marketMaker.currentPrice(dai);

    await marketMaker.mintInterest(dai, 60000);
    const priceAfter = await marketMaker.currentPrice(dai);
    expect(priceBefore).equal(200000000);
    expect(priceAfter).equal(200020000);
  });

  it("should not change the reserve ratio when calculate how much decrease it for the reservetoken", async () => {
    let reserveTokenBefore = await marketMaker.reserveTokens(cdai);
    let reserveRatioBefore = reserveTokenBefore.reserveRatio;
    await marketMaker.calculateNewReserveRatio(cdai);
    let reserveTokenAfter = await marketMaker.reserveTokens(cdai);
    let reserveRatioAfter = reserveTokenAfter.reserveRatio;
    expect(reserveRatioBefore.toString()).to.be.equal(
      reserveRatioAfter.toString()
    );
  });

  it("should not change the gd supply when calculate how much gd to mint based on added token supply from interest", async () => {
    let reserveTokenBefore = await marketMaker.reserveTokens(cdai);
    let gdSupplyBefore = reserveTokenBefore.gdSupply;
    await marketMaker.calculateMintInterest(cdai, "100000000");
    let reserveTokenAfter = await marketMaker.reserveTokens(cdai);
    let gdSupplyAfter = reserveTokenAfter.gdSupply;
    expect(gdSupplyAfter.toString()).to.be.equal(gdSupplyBefore.toString());
  });

  it("should not change the gd supply when calculate how much gd to mint based on expansion change", async () => {
    let reserveTokenBefore = await marketMaker.reserveTokens(cdai);
    let gdSupplyBefore = reserveTokenBefore.gdSupply;
    await marketMaker.calculateMintExpansion(cdai);
    let reserveTokenAfter = await marketMaker.reserveTokens(cdai);
    let gdSupplyAfter = reserveTokenAfter.gdSupply;
    expect(gdSupplyAfter.toString()).to.be.equal(gdSupplyBefore.toString());
  });

  it("should price decrease after sell gd when RR is not 100%", async () => {
    let reserveTokenBefore = await marketMaker.reserveTokens(cdai);
    let gdPriceBefore = await marketMaker.currentPrice(cdai);

    let gdSupplyBefore = reserveTokenBefore.gdSupply;
    let reserveRatioBefore = reserveTokenBefore.reserveRatio;
    let reserveSupplyBefore = reserveTokenBefore.reserveSupply;
    await marketMaker.sellWithContribution(cdai, BN.from("500000"), 0);
    let reserveTokenAfter = await marketMaker.reserveTokens(cdai);
    let gdSupplyAfter = reserveTokenAfter.gdSupply;

    let reserveRatioAfter = reserveTokenAfter.reserveRatio;
    let gdPriceAfter = await marketMaker.currentPrice(cdai);
    let reserveSupplyAfter = reserveTokenAfter.reserveSupply;

    expect(gdPriceAfter.lt(gdPriceBefore));
    expect(reserveRatioAfter).to.be.equal(reserveRatioBefore);
  });

  it("should price increase after buy gd when RR is not %100", async () => {
    let reserveTokenBefore = await marketMaker.reserveTokens(cdai);
    let gdPriceBefore = await marketMaker.currentPrice(cdai);

    let gdSupplyBefore = reserveTokenBefore.gdSupply;
    let reserveRatioBefore = reserveTokenBefore.reserveRatio;
    let reserveSupplyBefore = reserveTokenBefore.reserveSupply;
    await marketMaker.buy(cdai, BN.from("500000"));
    let reserveTokenAfter = await marketMaker.reserveTokens(cdai);
    let gdSupplyAfter = reserveTokenAfter.gdSupply;

    let reserveRatioAfter = reserveTokenAfter.reserveRatio;
    let gdPriceAfter = await marketMaker.currentPrice(cdai);
    let reserveSupplyAfter = reserveTokenAfter.reserveSupply;

    expect(gdPriceAfter.gt(gdPriceBefore));
    expect(reserveRatioAfter).to.be.equal(reserveRatioBefore);
  });

  it("should not set reserve ratio daily expansion with illigal values", async () => {
    const invalidZeroDenominator = 0;
    await expect(
      marketMaker.setReserveRatioDailyExpansion(1, invalidZeroDenominator)
    ).to.be.revertedWith(/denominator must be above 0/);

    const denominator = 1;
    const nominatorHigherThanDenom = 2;
    await expect(
      marketMaker.setReserveRatioDailyExpansion(
        nominatorHigherThanDenom,
        denominator
      )
    ).to.be.revertedWith(/Invalid nom or denom value/);
  });

  it("should return same amount when splitting sell into two when exit fee is 0", async () => {
    let reserveTokenBefore = await marketMaker.reserveTokens(cdai);
    console.log({ reserveTokenBefore });
    const res = await (
      await marketMaker.sellWithContribution(cdai, "100000000", "0")
    ).wait();
    let reserveTokenAfterSale = await marketMaker.reserveTokens(cdai);
    console.log({ reserveTokenAfterSale });
    await marketMaker.initializeToken(
      cdai,
      reserveTokenBefore.gdSupply,
      reserveTokenBefore.reserveSupply,
      reserveTokenBefore.reserveRatio,
      0
    );
    const split1 = await (
      await marketMaker.sellWithContribution(cdai, "50000000", "0")
    ).wait();
    const split2 = await (
      await marketMaker.sellWithContribution(cdai, "50000000", "0")
    ).wait();
    let reserveTokenAfterSplitSale = await marketMaker.reserveTokens(cdai);

    expect(reserveTokenAfterSale.gdSupply).eq(
      reserveTokenAfterSplitSale.gdSupply
    );
    expect(reserveTokenAfterSale.reserveSupply).eq(
      reserveTokenAfterSplitSale.reserveSupply
    );
  });

  it("should have minimal effect on return value when splitting sell into two txs when exit fee is non 0", async () => {
    let reserveTokenBefore = await marketMaker.reserveTokens(cdai);
    await marketMaker.initializeToken(
      cdai,
      reserveTokenBefore.gdSupply,
      reserveTokenBefore.reserveSupply,
      200000,
      0
    );
    console.log({ reserveTokenBefore });
    const res = await (
      await marketMaker.sellWithContribution(cdai, "500000000", "50000000")
    ).wait();
    let reserveTokenAfterSale = await marketMaker.reserveTokens(cdai);
    console.log({ reserveTokenAfterSale });
    await marketMaker.initializeToken(
      cdai,
      reserveTokenBefore.gdSupply,
      reserveTokenBefore.reserveSupply,
      200000,
      0
    );
    const split1 = await (
      await marketMaker.sellWithContribution(cdai, "250000000", "25000000")
    ).wait();
    const split2 = await (
      await marketMaker.sellWithContribution(cdai, "250000000", "25000000")
    ).wait();
    let reserveTokenAfterSplitSale = await marketMaker.reserveTokens(cdai);

    console.log({ reserveTokenAfterSplitSale });
    expect(reserveTokenAfterSale.gdSupply).eq(
      reserveTokenAfterSplitSale.gdSupply
    );
    expect(
      Number(reserveTokenAfterSplitSale.reserveSupply) /
        Number(reserveTokenAfterSale.reserveSupply)
    ).gte(0.981);
  });
});
