import { ethers, upgrades } from "hardhat";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { BigNumber, Contract } from "ethers";
import { expect } from "chai";
import { GoodMarketMaker } from "../../types";
import {
  createDAO,
  increaseTime,
  advanceBlocks,
  deployUniswap,
  getStakingFactory
} from "../helpers";
import ContributionCalculation from "@gooddollar/goodcontracts/stakingModel/build/contracts/ContributionCalculation.json";
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("StakingRewards - staking with cDAI mocks and get Rewards in GoodDollar", () => {
  let dai: Contract;
  let bat: Contract;
  let comp: Contract;
  let pair: Contract, uniswapRouter: Contract;
  let cDAI, cDAI1, cDAI2, cDAI3, cBat: Contract;
  let gasFeeOracle: Contract,
    daiEthOracle: Contract,
    daiUsdOracle: Contract,
    batUsdOracle: Contract,
    ethUsdOracle: Contract,
    compUsdOracle: Contract;
  let goodReserve: Contract;
  let goodCompoundStaking;
  let goodFundManager: Contract;
  let avatar,
    goodDollar,
    identity,
    marketMaker: GoodMarketMaker,
    contribution,
    controller,
    founder,
    staker,
    schemeMock,
    signers,
    nameService,
    initializeToken,
    setDAOAddress,
    genericCall,
    goodCompoundStakingFactory,
    goodCompoundStakingTestFactory,
    runAsAvatarOnly,
    deployStaking;


  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,
      runAsAvatarOnly: raao
    } = await loadFixture(createDAO);

    runAsAvatarOnly = raao
    const cdaiFactory = await ethers.getContractFactory("cDAIMock");
    const cBatFactory = await ethers.getContractFactory("cBATMock");
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );
    goodCompoundStakingFactory = await getStakingFactory(
      "GoodCompoundStakingV2"
    );
    goodCompoundStakingTestFactory = await getStakingFactory(
      "GoodCompoundStakingTest"
    );

    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, compPairContract, daiPairContract } = uniswap;
    avatar = av;
    controller = ctrl;
    setDAOAddress = sda;
    nameService = ns;
    initializeToken = setReserveToken;
    goodReserve = reserve;
    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
    });
    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
    });

    console.log("setting permissions...");
    const tokenUsdOracleFactory = await ethers.getContractFactory(
      "BatUSDMockOracle"
    );
    await setDAOAddress("UNISWAP_ROUTER", uniswapRouter.address);

    daiUsdOracle = await tokenUsdOracleFactory.deploy();
    const compUsdOracleFactory = await ethers.getContractFactory(
      "CompUSDMockOracle"
    );
    compUsdOracle = await compUsdOracleFactory.deploy();

    deployStaking = (token, itoken, blocksThreashold = "50") =>
      goodCompoundStakingFactory.deploy().then(async contract => {
        await contract.init(
          token || dai.address,
          itoken || cDAI.address,
          nameService.address,
          "Good DAI",
          "gDAI",
          blocksThreashold,
          daiUsdOracle.address,
          compUsdOracle.address,
          []
        );
        return contract;
      });

    goodCompoundStaking = await deployStaking(null, null, "172800");

    console.log("initializing marketmaker...");

    cDAI1 = await cdaiFactory.deploy(dai.address);
    const cdaiLowWorthFactory = await ethers.getContractFactory(
      "cDAILowWorthMock"
    );
    cDAI2 = await cdaiLowWorthFactory.deploy(dai.address);
    const cdaiNonMintableFactory = await ethers.getContractFactory(
      "cDAINonMintableMock"
    );
    cDAI3 = await cdaiNonMintableFactory.deploy(dai.address);
    bat = await daiFactory.deploy(); // Another erc20 token for uniswap router test
    cBat = await cBatFactory.deploy(bat.address);
    await initializeToken(
      cDAI1.address,
      "100", //1gd
      "10000", //0.0001 cDai
      "1000000" //100% rr
    );
    await initializeToken(
      cDAI2.address,
      "100", //1gd
      "10000", //0.0001 cDai
      "1000000" //100% rr
    );
    await initializeToken(
      cDAI3.address,
      "100", //1gd
      "10000", //0.0001 cDai
      "1000000" //100% rr
    );

    await factory.createPair(bat.address, dai.address); // Create tokenA and dai pair
    const pairAddress = factory.getPair(bat.address, dai.address);
    pair = new Contract(
      pairAddress,
      JSON.stringify(IUniswapV2Pair.abi),
      staker
    ).connect(founder);
    await setDAOAddress("CDAI", cDAI.address);
    await setDAOAddress("DAI", dai.address);

    batUsdOracle = await tokenUsdOracleFactory.deploy();

    await dai["mint(address,uint256)"](
      founder.address,
      ethers.utils.parseEther("2000000")
    );
    await bat["mint(address,uint256)"](
      founder.address,
      ethers.utils.parseEther("2000000")
    );

    await addLiquidity(
      ethers.utils.parseEther("2000000"),
      ethers.utils.parseEther("2000000")
    );

    gasFeeOracle = await ethers.getContractAt(
      "GasPriceMockOracle",
      await nameService.getAddress("GAS_PRICE_ORACLE")
    );
    daiEthOracle = await ethers.getContractAt(
      "DaiEthPriceMockOracle",
      await nameService.getAddress("DAI_ETH_ORACLE")
    );
    await setDAOAddress("MARKET_MAKER", marketMaker.address);
  });

  it("should be set rewards per block for particular stacking contract", async () => {
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );
    const currentBlockNumber = await ethers.provider.getBlockNumber();
    const encodedData = goodFundManagerFactory.interface.encodeFunctionData(
      "setStakingReward",
      [
        "1000",
        goodCompoundStaking.address,
        currentBlockNumber - 5,
        currentBlockNumber + 10,
        false
      ] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);
    let rewardPerBlock = await goodFundManager.rewardsForStakingContract(
      goodCompoundStaking.address
    );
    expect(rewardPerBlock[0].toString()).to.be.equal("1000");
    expect(rewardPerBlock[1].toString()).to.be.equal(
      (currentBlockNumber - 5).toString()
    );
    expect(rewardPerBlock[2].toString()).to.be.equal(
      (currentBlockNumber + 10).toString()
    );
    expect(rewardPerBlock[3]).to.be.equal(false);
  });

  it("should be able to earn rewards after some block passed", async () => {
    let stakingAmount = ethers.utils.parseEther("100");
    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);
    await goodCompoundStaking.connect(staker).stake(stakingAmount, 0, false);
    let gdBalanceBeforeWithdraw = await goodDollar.balanceOf(staker.address);
    await advanceBlocks(4);
    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount, false);
    let gdBalancerAfterWithdraw = await goodDollar.balanceOf(staker.address);
    expect(gdBalancerAfterWithdraw.toString()).to.be.equal("2500");
  });

  it("it should withdraw effective stakes and donated stakes proportionally", async () => {
    const stakingAmount = ethers.utils.parseEther("100");

    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount.mul(BN.from("2")));

    await goodCompoundStaking.connect(staker).stake(stakingAmount, 0, false);
    await goodCompoundStaking.connect(staker).stake(stakingAmount, 100, false);
    const userStakeInfoBeforeWithdraw = await goodCompoundStaking.users(
      staker.address
    );
    expect(userStakeInfoBeforeWithdraw.effectiveStakes).to.be.equal(
      stakingAmount
    );
    expect(userStakeInfoBeforeWithdraw.amount).to.be.equal(
      stakingAmount.mul(BN.from("2"))
    );
    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount, false);
    const userStakeInfoAfterWithdraw = await goodCompoundStaking.users(
      staker.address
    );
    expect(userStakeInfoAfterWithdraw.effectiveStakes).to.be.equal(
      stakingAmount.div(BN.from("2"))
    ); // should be half of the stakes that staked initially
    expect(userStakeInfoAfterWithdraw.amount).to.be.equal(stakingAmount);
    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount, false);
  });
  it("it should not increase totalEffectiveStakes when staked amount's rewards are donated", async () => {
    const stakingAmount = ethers.utils.parseEther("100");

    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);
    const totalEffectiveStakesBeforeStake = await goodCompoundStaking
      .getStats()
      .then(_ => _[3]);
    await goodCompoundStaking.connect(staker).stake(stakingAmount, 100, false);
    const totalEffectiveStakesAfterStake = await goodCompoundStaking
      .getStats()
      .then(_ => _[3]);
    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount, false);
    expect(totalEffectiveStakesAfterStake).to.be.equal(
      totalEffectiveStakesBeforeStake
    );
  });
  it("it should increase totalEffectiveStakes when staked without donation", async () => {
    const stakingAmount = ethers.utils.parseEther("100");

    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);
    const totalEffectiveStakesBeforeStake = await goodCompoundStaking
      .getStats()
      .then(_ => _[3]);
    await goodCompoundStaking.connect(staker).stake(stakingAmount, 0, false);
    const totalEffectiveStakesAfterStake = await goodCompoundStaking
      .getStats()
      .then(_ => _[3]);
    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount, false);
    expect(totalEffectiveStakesAfterStake).to.be.gt(
      totalEffectiveStakesBeforeStake
    );
  });
  it("it should withdraw effective and stakes according to ratio", async () => {
    const stakingAmount = ethers.utils.parseEther("100");

    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);

    await goodCompoundStaking
      .connect(staker)
      .stake(stakingAmount.mul(80).div(100), 0, false);
    await goodCompoundStaking
      .connect(staker)
      .stake(stakingAmount.mul(20).div(100), 100, false);
    const userStakeInfoBeforeWithdraw = await goodCompoundStaking.users(
      staker.address
    );
    const withdrawAmount = stakingAmount.mul(20).div(100);
    const withdrawFromEffectiveStake = withdrawAmount
      .mul(userStakeInfoBeforeWithdraw.effectiveStakes)
      .div(userStakeInfoBeforeWithdraw.amount);
    const withdrawFromDonation = withdrawAmount.sub(withdrawFromEffectiveStake);
    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(withdrawAmount, false);
    const userStakeInfoAfterWithdraw = await goodCompoundStaking.users(
      staker.address
    );
    expect(userStakeInfoBeforeWithdraw.effectiveStakes).to.be.gt(0);
    expect(userStakeInfoAfterWithdraw.effectiveStakes).to.be.equal(
      userStakeInfoBeforeWithdraw.effectiveStakes.sub(
        withdrawFromEffectiveStake
      )
    );
    expect(
      userStakeInfoAfterWithdraw.amount.sub(
        userStakeInfoAfterWithdraw.effectiveStakes
      )
    ).to.be.equal(
      userStakeInfoBeforeWithdraw.amount
        .sub(userStakeInfoBeforeWithdraw.effectiveStakes)
        .sub(withdrawFromDonation)
    );
    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount.sub(withdrawAmount), false); // withdraw left amount
  });
  it("shouldn't be able to earn rewards after rewards blockend passed", async () => {
    let stakingAmount = ethers.utils.parseEther("100");
    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);
    await goodCompoundStaking.connect(staker).stake(stakingAmount, 0, false);

    let gdBalanceBeforeWithdraw = await goodDollar.balanceOf(staker.address);
    advanceBlocks(5);
    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount, false);
    let gdBalancerAfterWithdraw = await goodDollar.balanceOf(staker.address);

    expect(gdBalancerAfterWithdraw.toString()).to.be.equal(
      gdBalanceBeforeWithdraw.toString()
    );
  });

  it("shouldn't be able to mint reward when staking contract is blacklisted", async () => {
    const goodCompoundStaking2 = await deployStaking(null, null, "1728000");

    let encodedDataTwo = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      ["1000", goodCompoundStaking2.address, "55", "1000", false] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedDataTwo);

    let stakingAmount = ethers.utils.parseEther("100");
    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking2.address, stakingAmount);
    await goodCompoundStaking2.connect(staker).stake(stakingAmount, 0, false);

    let gdBalanceBeforeWithdraw = await goodDollar.balanceOf(staker.address);
    advanceBlocks(5);

    encodedDataTwo = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      ["1000", goodCompoundStaking2.address, "55", "1000", true] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedDataTwo);

    await goodCompoundStaking2
      .connect(staker)
      .withdrawStake(stakingAmount, false);
    let gdBalancerAfterWithdraw = await goodDollar.balanceOf(staker.address);

    expect(gdBalancerAfterWithdraw).to.be.equal(gdBalanceBeforeWithdraw);
  });

  it("shouldn't be able to collect interest when contract is not active", async () => {
    const goodCompoundStaking2 = await deployStaking(null, null, "1728000");
    await expect(
      goodFundManager
        .connect(staker)
        .collectInterest([goodCompoundStaking2.address], true)
    ).revertedWith(/not a dao contract/);
  });

  // it("should set blacklisted false and mint rewards", async () => {
  //   const goodFundManagerFactory = await ethers.getContractFactory(
  //     "GoodFundManager"
  //   );
  //   const ictrl = await ethers.getContractAt(
  //     "Controller",
  //     controller,
  //     schemeMock
  //   );
  //   const currentBlockNumber = await ethers.provider.getBlockNumber();
  //   const encodedDataTwo = goodFundManagerFactory.interface.encodeFunctionData(
  //     "setStakingReward",
  //     [
  //       "1000",
  //       goodCompoundStaking.address,
  //       currentBlockNumber,
  //       currentBlockNumber + 500,
  //       false
  //     ] // set 10 gd per block
  //   );
  //   await ictrl.genericCall(goodFundManager.address, encodedDataTwo, avatar, 0);
  //   let stakingAmount = ethers.utils.parseEther("100");
  //   await dai["mint(address,uint256)"](staker.address, stakingAmount);
  //   await dai
  //     .connect(staker)
  //     .approve(goodCompoundStaking.address, stakingAmount);
  //   await goodCompoundStaking.connect(staker).stake(stakingAmount, 0);

  //   let gdBalanceBeforeWithdraw = await goodDollar.balanceOf(staker.address);
  //   advanceBlocks(5);
  //   await goodCompoundStaking.connect(staker).withdrawStake(stakingAmount);
  //   let gdBalancerAfterWithdraw = await goodDollar.balanceOf(staker.address);
  //   let gCDAIbalanceAfter = await goodCompoundStaking.balanceOf(staker.address);
  //   let gCDAITotalSupply = await goodCompoundStaking.totalSupply();
  //   expect(gCDAIbalanceAfter).to.be.equal(gCDAITotalSupply); // staker should own whole staking tokens
  //   expect(gdBalancerAfterWithdraw.toString()).to.be.equal("5500"); // should mint previous rewards as well
  // });

  it("it should send staker's productivity to some other user", async () => {
    let stakingAmount = ethers.utils.parseEther("100");
    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);
    await goodCompoundStaking.connect(staker).stake(stakingAmount, 100, false);
    let stakersProductivityBefore = await goodCompoundStaking.getProductivity(
      staker.address
    );
    await goodCompoundStaking
      .connect(staker)
      .transfer(founder.address, stakingAmount);
    let stakersProductivityAfter = await goodCompoundStaking
      .connect(staker)
      .getProductivity(staker.address);
    let foundersProductivity = await goodCompoundStaking.getProductivity(
      founder.address
    );

    expect(stakersProductivityAfter[0].toString()).to.be.equal("0");
    expect(foundersProductivity[0].toString()).to.be.equals(
      stakingAmount.toString()
    );
  });

  it("it shouldn't be able to withdraw stake when staker sent it to another user", async () => {
    const stakingAmount = ethers.utils.parseEther("100");
    await expect(
      goodCompoundStaking.connect(staker).withdrawStake(stakingAmount, false)
    ).to.be.reverted;
  });

  it("it should be able to withdraw their stake when got staking tokens from somebody else", async () => {
    const stakingAmount = ethers.utils.parseEther("100");

    await goodCompoundStaking.withdrawStake(stakingAmount, false);

    const foundersProductivity = await goodCompoundStaking.getProductivity(
      founder.address
    );
    expect(foundersProductivity[0].toString()).to.be.equal("0");
    expect(foundersProductivity[1].toString()).to.be.equal("0"); // Total productivity also should equal 0
  });

  it("stake should generate some interest and shoul be used to generate UBI", async () => {
    const stakingAmount = ethers.utils.parseEther("100");

    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);
    await goodCompoundStaking.connect(staker).stake(stakingAmount, 100, false);

    await dai["mint(address,uint256)"](
      staker.address,
      ethers.utils.parseEther("1000000")
    );
    await dai
      .connect(staker)
      .transfer(cDAI.address, ethers.utils.parseEther("1000000")); // We should put extra DAI to mock cDAI contract in order to provide interest
    await cDAI.increasePriceWithMultiplier("1500"); // increase interest by calling exchangeRateCurrent

    const currentUBIInterestBeforeWithdraw =
      await goodCompoundStaking.currentGains(false, true);
    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount, false);
    const gdBalanceBeforeCollectInterest = await goodDollar.balanceOf(
      staker.address
    );
    const contractAddressesToBeCollected =
      await goodFundManager.calcSortedContracts();
    const addressesToCollect = contractAddressesToBeCollected.map(x => x[0]);
    await goodFundManager
      .connect(staker)
      .collectInterest(addressesToCollect, false);
    const gdBalanceAfterCollectInterest = await goodDollar.balanceOf(
      staker.address
    );
    const currentUBIInterestAfterWithdraw =
      await goodCompoundStaking.currentGains(false, true);
    expect(currentUBIInterestBeforeWithdraw[0].toString()).to.not.be.equal("0");
    expect(currentUBIInterestAfterWithdraw[0].toString()).to.be.equal("0");
    expect(gdBalanceAfterCollectInterest.gt(gdBalanceBeforeCollectInterest));
  });

  it("it should get rewards with updated values", async () => {
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );
    const currentBlockNumber = await ethers.provider.getBlockNumber();
    const encodedDataTwo = goodFundManagerFactory.interface.encodeFunctionData(
      "setStakingReward",
      [
        "1000",
        goodCompoundStaking.address,
        currentBlockNumber,
        currentBlockNumber + 5000,
        false
      ] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedDataTwo, avatar, 0);

    const stakingAmount = ethers.utils.parseEther("100");

    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);
    await goodCompoundStaking.connect(staker).stake(stakingAmount, 0, false);

    await advanceBlocks(4);
    const stakingContractVals = await goodFundManager.rewardsForStakingContract(
      goodCompoundStaking.address
    );
    let rewardsEarned = await goodCompoundStaking.getUserPendingReward(
      staker.address,
      stakingContractVals[0],
      stakingContractVals[1],
      stakingContractVals[2]
    );
    //baseshare rewards is in 18 decimals
    expect(rewardsEarned.toString()).to.be.equal(
      ethers.utils.parseUnits("20", 18)
    ); // Each block reward is 10gd so total reward 40gd but since multiplier is 0.5 for first month should get 20gd

    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount, false);
  });

  it("should get user minted and pending rewards", async () => {
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );
    const currentBlockNumber = await ethers.provider.getBlockNumber();
    const encodedData = goodFundManagerFactory.interface.encodeFunctionData(
      "setStakingReward",
      [
        "1000",
        goodCompoundStaking.address,
        currentBlockNumber,
        currentBlockNumber + 5000,
        false
      ] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);

    const stakingAmount = ethers.utils.parseEther("100");

    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);
    await goodCompoundStaking.connect(staker).stake(stakingAmount, 0, false);

    await advanceBlocks(4);

    const userMintedAndPending =
      await goodCompoundStaking.getUserMintedAndPending(staker.address);
    const userMintedReward = userMintedAndPending[0].toString();
    const userPendingReward = userMintedAndPending[1].toString();
    expect(userMintedReward).to.equal("5000");
    expect(userPendingReward).to.equal("2000");

    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount, false);
  });

  it("it should get rewards with 1x multiplier for after threshold pass", async () => {
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );

    const simpleStaking = await await deployStaking();

    const currentBlockNumber = await ethers.provider.getBlockNumber();
    let encodedDataTwo = goodFundManagerFactory.interface.encodeFunctionData(
      "setStakingReward",
      [
        "1000",
        simpleStaking.address,
        currentBlockNumber,
        currentBlockNumber + 100,
        false
      ] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedDataTwo, avatar, 0);

    const stakingAmount = ethers.utils.parseEther("100");

    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai.connect(staker).approve(simpleStaking.address, stakingAmount);
    let gdBalanceStakerBeforeWithdraw = await goodDollar.balanceOf(
      staker.address
    );
    await simpleStaking.connect(staker).stake(stakingAmount, 0, false);

    await advanceBlocks(54);
    await simpleStaking.connect(staker).withdrawStake(stakingAmount, false);
    let gdBalanceStakerAfterWithdraw = await goodDollar.balanceOf(
      staker.address
    );

    expect(
      gdBalanceStakerAfterWithdraw.sub(gdBalanceStakerBeforeWithdraw).toString()
    ).to.be.equal("30000"); // 50 blocks reward worth 500gd but since it's with the 0.5x multiplier so 250gd then there is 5 blocks which gets full reward so total reward is 300gd
    encodedDataTwo = goodFundManagerFactory.interface.encodeFunctionData(
      "setStakingReward",
      [
        "1000",
        simpleStaking.address,
        currentBlockNumber,
        currentBlockNumber + 100,
        true
      ] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedDataTwo, avatar, 0);
  });

  it("Should transfer somebody's staking token's when they approve", async () => {
    const stakingAmount = ethers.utils.parseEther("100");

    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);

    await goodCompoundStaking.connect(staker).stake(stakingAmount, 100, false);
    await goodCompoundStaking
      .connect(staker)
      .approve(founder.address, stakingAmount);
    const stakingTokenBalanceBeforeTransfer =
      await goodCompoundStaking.balanceOf(founder.address);
    await goodCompoundStaking.transferFrom(
      staker.address,
      founder.address,
      stakingAmount
    );
    const stakingTokenBalanceAfterTransfer =
      await goodCompoundStaking.balanceOf(founder.address);

    expect(
      stakingTokenBalanceAfterTransfer.gt(stakingTokenBalanceBeforeTransfer)
    ).to.be.true;
    await goodCompoundStaking.withdrawStake(stakingAmount, false);
  });

  it("Should be able to withdraw rewards without withdraw stake", async () => {
    const stakingAmount = ethers.utils.parseEther("100");

    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);

    await goodCompoundStaking.connect(staker).stake(stakingAmount, 0, false);
    await advanceBlocks(5);
    const stakingContractVals = await goodFundManager.rewardsForStakingContract(
      goodCompoundStaking.address
    );
    const earnedRewardBeforeWithdrawReward =
      await goodCompoundStaking.getUserPendingReward(
        staker.address,
        stakingContractVals[0],
        stakingContractVals[1],
        stakingContractVals[2]
      );
    await goodCompoundStaking.connect(staker).withdrawRewards();
    const earnedRewardAfterWithdrawReward =
      await goodCompoundStaking.getUserPendingReward(
        staker.address,
        stakingContractVals[0],
        stakingContractVals[1],
        stakingContractVals[2]
      );

    expect(earnedRewardAfterWithdrawReward.lt(earnedRewardBeforeWithdrawReward))
      .to.be.true;
    expect(earnedRewardAfterWithdrawReward.toString()).to.be.equal("0");
    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount, false);
  });
  it("it should not mint reward when staking contract is not registered", async () => {
    const simpleStaking = await deployStaking();

    const tx = await simpleStaking.withdrawRewards().catch(e => e);
    expect(tx.message).to.not.be.empty;
  });
  it("it should be able to distribute rewards when blockEnd passed but last Reward block was before blockend", async () => {
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );
    const currentBlockNumber = await ethers.provider.getBlockNumber();
    let encodedData = goodFundManagerFactory.interface.encodeFunctionData(
      "setStakingReward",
      [
        "1000",
        goodCompoundStaking.address,
        currentBlockNumber - 10,
        currentBlockNumber + 50,
        false
      ] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
    const stakingAmount = ethers.utils.parseEther("100");
    const initialGdBalance = await goodDollar.balanceOf(staker.address);
    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);
    const stakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1;
    await goodCompoundStaking.connect(staker).stake(stakingAmount, 0, false);
    await advanceBlocks(5);
    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount.div(2), false);
    const gdBalanceAfterFirstWithdraw = await goodDollar.balanceOf(
      staker.address
    );
    const firstWithdrawBlockNumber = await ethers.provider.getBlockNumber();
    await advanceBlocks(60);

    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount.div(2), false);
    const gdBalanceAfterSecondWithdraw = await goodDollar.balanceOf(
      staker.address
    );
    expect(gdBalanceAfterFirstWithdraw).to.be.gt(initialGdBalance);
    expect(gdBalanceAfterFirstWithdraw.sub(initialGdBalance)).to.be.equal(
      BN.from("1000")
        .mul(firstWithdrawBlockNumber - stakeBlockNumber)
        .div(2)
    );
    expect(gdBalanceAfterSecondWithdraw).to.be.gt(gdBalanceAfterFirstWithdraw);
    expect(
      gdBalanceAfterSecondWithdraw.sub(gdBalanceAfterFirstWithdraw)
    ).to.be.equal(
      BN.from("1000")
        .mul(currentBlockNumber + 50 - firstWithdrawBlockNumber)
        .div(2)
        .sub(1)
    ); // sub 1 due to precision loss
  });
  it("it should not earn rewards when currentBlock < blockStart", async () => {
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );
    const currentBlockNumber = await ethers.provider.getBlockNumber();
    let encodedData = goodFundManagerFactory.interface.encodeFunctionData(
      "setStakingReward",
      [
        "1000",
        goodCompoundStaking.address,
        currentBlockNumber + 100,
        currentBlockNumber + 500,
        false
      ] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
    const stakingAmount = ethers.utils.parseEther("100");
    const initialGdBalance = await goodDollar.balanceOf(staker.address);
    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);
    await goodCompoundStaking.connect(staker).stake(stakingAmount, 0, false);
    await advanceBlocks(50);
    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount, false);
    const gdBalanceAfterWithdraw = await goodDollar.balanceOf(staker.address);

    expect(initialGdBalance).to.be.gt(0);
    expect(gdBalanceAfterWithdraw).to.be.equal(initialGdBalance);
  });

  it("it should earn rewards when they stake before blockStart but keep their stake until after blockStart", async () => {
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );
    const currentBlockNumber = await ethers.provider.getBlockNumber();
    let encodedData = goodFundManagerFactory.interface.encodeFunctionData(
      "setStakingReward",
      [
        "1000",
        goodCompoundStaking.address,
        currentBlockNumber + 30,
        currentBlockNumber + 500,
        false
      ] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
    const stakingAmount = ethers.utils.parseEther("100");
    const initialGdBalance = await goodDollar.balanceOf(staker.address);
    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);
    await goodCompoundStaking.connect(staker).stake(stakingAmount, 0, false);
    await advanceBlocks(50);
    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount, false);
    const withdrawBlockNumber = await ethers.provider.getBlockNumber();
    const gdBalanceAfterWithdraw = await goodDollar.balanceOf(staker.address);
    expect(initialGdBalance).to.be.gt(0);
    expect(gdBalanceAfterWithdraw).to.be.gt(initialGdBalance);
    expect(gdBalanceAfterWithdraw.sub(initialGdBalance)).to.be.equal(
      BN.from("1000")
        .mul(withdrawBlockNumber - (currentBlockNumber + 30))
        .div(2)
        .sub(1)
    );
  });
  it("should be able earn to 50% of rewards when owns 50% of total productivity", async () => {
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );
    const ictrl = await ethers.getContractAt(
      "Controller",
      controller,
      schemeMock
    );
    const currentBlockNumber = await ethers.provider.getBlockNumber();
    let encodedDataTwo = goodFundManagerFactory.interface.encodeFunctionData(
      "setStakingReward",
      [
        "1000",
        goodCompoundStaking.address,
        currentBlockNumber - 10,
        currentBlockNumber + 100,
        false
      ] // set 10 gd per block
    );

    const stakingAmount = ethers.utils.parseEther("100");

    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai["mint(address,uint256)"](signers[0].address, stakingAmount); // We use some different signer than founder since founder also UBI INTEREST collector
    await dai
      .connect(signers[0])
      .approve(goodCompoundStaking.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);
    let stakerTwoGDAmountBeforeStake = await goodDollar.balanceOf(
      signers[0].address
    );
    let stakerGDAmountBeforeStake = await goodDollar.balanceOf(staker.address);

    await goodCompoundStaking.connect(staker).stake(stakingAmount, 100, false);
    await goodCompoundStaking
      .connect(signers[0])
      .stake(stakingAmount, 100, false);
    await advanceBlocks(5);

    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount, false);
    await goodCompoundStaking
      .connect(signers[0])
      .withdrawStake(stakingAmount, false);
    let stakerTwoGDAmountAfterStake = await goodDollar.balanceOf(
      signers[0].address
    );
    let stakerGDAmountAfterStake = await goodDollar.balanceOf(staker.address);
    expect(
      stakerTwoGDAmountAfterStake.sub(stakerTwoGDAmountBeforeStake).toString()
    ).to.be.equal(
      stakerGDAmountAfterStake.sub(stakerGDAmountBeforeStake).toString()
    );
  });
  it("Accumulated per share has enough precision when reward << totalproductivity", async () => {
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );

    const currentBlockNumber = await ethers.provider.getBlockNumber();
    let encodedDataTwo = goodFundManagerFactory.interface.encodeFunctionData(
      "setStakingReward",
      [
        "1000",
        goodCompoundStaking.address,
        currentBlockNumber - 10,
        currentBlockNumber + 100,
        false
      ] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedDataTwo, avatar, 0);
    const stakingAmount = ethers.utils.parseEther("1000000000");
    await dai["mint(address,uint256)"](founder.address, stakingAmount); // 1 billion dai to stake
    await dai.approve(goodCompoundStaking.address, stakingAmount);
    await goodCompoundStaking.stake(stakingAmount, 0, false);
    await advanceBlocks(4);
    const gdBalanceBeforeWithdraw = await goodDollar.balanceOf(founder.address);
    await goodCompoundStaking.withdrawStake(stakingAmount, false);
    const gdBalanceAfterWithdraw = await goodDollar.balanceOf(founder.address);
    expect(
      gdBalanceAfterWithdraw.sub(gdBalanceBeforeWithdraw).toString()
    ).to.be.equal("2500");
  });

  it("it should not get any reward when donationPer set to 100", async () => {
    const stakingAmount = ethers.utils.parseEther("100");
    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    let stakerGDAmountBeforeStake = await goodDollar.balanceOf(staker.address);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);
    await goodCompoundStaking.connect(staker).stake(stakingAmount, 100, false);
    await advanceBlocks(4);
    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount, false);
    let stakerGDAmountAfterStake = await goodDollar.balanceOf(staker.address);
    expect(stakerGDAmountAfterStake).to.be.equal(stakerGDAmountBeforeStake);
  });
  it("it should be reverted when donation per set to different than 0 or 100", async () => {
    const stakingAmount = ethers.utils.parseEther("100");
    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);
    const tx = await goodCompoundStaking
      .connect(staker)
      .stake(stakingAmount, 55, false)
      .catch(e => e);
    expect(tx.message).to.be.not.empty;
  });

  it("should be able to sort staking contracts and collect interests from lowest to highest[ @skip-on-coverage ]", async () => {
    const stakingAmount = ethers.utils.parseEther("100");

    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);

    await goodCompoundStaking.connect(staker).stake(stakingAmount, 100, false);

    await cDAI.increasePriceWithMultiplier("6000"); // increase interest by calling exchangeRateCurrent

    const simpleStaking = await deployStaking();

    const simpleStaking1 = await deployStaking();

    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );
    let encodedData = goodFundManagerFactory.interface.encodeFunctionData(
      "setStakingReward",
      ["100", simpleStaking.address, 0, 10, false]
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);
    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai.connect(staker).approve(simpleStaking.address, stakingAmount);
    await simpleStaking.connect(staker).stake(stakingAmount, 100, false);
    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai.connect(staker).approve(simpleStaking1.address, stakingAmount);
    await simpleStaking1.connect(staker).stake(stakingAmount, 100, false);

    await cDAI.increasePriceWithMultiplier("200"); // increase interest by calling increasePriceWithMultiplier
    const contractsToBeCollected = await goodFundManager.calcSortedContracts();
    const addressesToCollect = contractsToBeCollected.map(x => x[0]);
    expect(addressesToCollect[0]).to.be.equal(simpleStaking.address);
    expect(addressesToCollect[1]).to.be.equal(goodCompoundStaking.address);

    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount, false);
    encodedData = goodFundManagerFactory.interface.encodeFunctionData(
      "setStakingReward",
      ["100", simpleStaking.address, 0, 10, true]
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);
  });

  it("It should not collect interest when interest is lower than gas cost [ @skip-on-coverage ]", async () => {
    await gasFeeOracle.setPrice(ethers.constants.WeiPerEther); //increase gas price so costs is more than ubi minted
    await expect(
      goodFundManager.collectInterest([], false, {
        gasLimit: 770000
      })
    ).revertedWith(/X*gas costs/);
    await gasFeeOracle.setPrice(25e8); //undo gas price
  });

  it("It should always collect interest without rewards when forced", async () => {
    await goodFundManager
      .collectInterest([goodCompoundStaking.address], false)
      .catch(e => e); // make sure there is no interest left

    const stakingAmount = ethers.utils.parseEther("100");

    await dai["mint(address,uint256)"](staker.address, stakingAmount);
    await dai
      .connect(staker)
      .approve(goodCompoundStaking.address, stakingAmount);

    await goodCompoundStaking.connect(staker).stake(stakingAmount, 100, false);
    const transaction = goodFundManager.collectInterest(
      [goodCompoundStaking.address],
      true,
      {
        gasLimit: 770000
      }
    );

    await expect(transaction).to.emit(goodFundManager, "FundsTransferred");
    const tx = await (await transaction).wait();
    const event = tx.events.find(e => e.event === "FundsTransferred");
    expect(event.args.gdReward).to.equal(0);

    await goodCompoundStaking
      .connect(staker)
      .withdrawStake(stakingAmount, false);
  });

  it("It should sort array from lowest to highest ", async () => {
    const goodFundManagerTestFactory = await ethers.getContractFactory(
      "GoodFundManagerTest"
    );
    const goodFundManagerTest = await goodFundManagerTestFactory.deploy(
      nameService.address
    );
    const addresses = [
      founder.address,
      staker.address,
      cDAI.address,
      cDAI1.address
    ];
    const balances = [
      ethers.utils.parseEther("100"),
      ethers.utils.parseEther("85"),
      ethers.utils.parseEther("90"),
      ethers.utils.parseEther("30")
    ];
    const sortedArrays = await goodFundManagerTest.testSorting(
      balances,
      addresses
    );
    expect(sortedArrays[0][0]).to.be.equal(ethers.utils.parseEther("30"));
    expect(sortedArrays[0][3]).to.be.equal(ethers.utils.parseEther("100"));
    expect(sortedArrays[1][3]).to.be.equal(founder.address);
    expect(sortedArrays[1][0]).to.be.equal(cDAI1.address);
  });

  // needs to come before next test where we blacklist the goodcompoundstaking contract
  it("should revert when colleced interest is not greater than gas cost when 2 months passed [ @skip-on-coverage ]", async () => {

    // make sure expansion is very low 
    await initializeToken(
      cDAI.address,
      "129966743722", //1gd
      "499870173594", //0.0001 cDai
      "1000000" //100% rr
    );
    await runAsAvatarOnly(
      goodReserve,
      "setReserveRatioDailyExpansion(uint256,uint256)",
      ethers.utils.parseEther("999999999"),
      ethers.utils.parseEther("1000000000")
    );

    const currentBlockNumber = await ethers.provider.getBlockNumber();
    const currentBlock = await ethers.provider.getBlock(currentBlockNumber);

    let encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      [
        "1000",
        goodCompoundStaking.address,
        0,
        currentBlockNumber + 1000000,
        false
      ] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
    await ethers.provider.send("evm_setNextBlockTimestamp", [
      currentBlock.timestamp + 5185020
    ]);
    await ethers.provider.send("evm_mine", []);
    console.log(await goodFundManager.activeContracts(0));
    await expect(
      goodFundManager.collectInterest([goodCompoundStaking.address], false)
    ).revertedWith(/< gas costs/);
  });

  it("should be able to mint ubi when 0 interest but ubi value > interestMultiplier*gas cost when 2 months passed [ @skip-on-coverage ]", async () => {

    await initializeToken(
      cDAI.address,
      "129966743722", //1gd
      "499870173594", //0.0001 cDai
      "400000" //100% rr
    );
    await runAsAvatarOnly(
      goodReserve,
      "setReserveRatioDailyExpansion(uint256,uint256)",
      ethers.utils.parseEther("999999999"),
      ethers.utils.parseEther("1000000000")
    );

    const currentBlockNumber = await ethers.provider.getBlockNumber();
    const currentBlock = await ethers.provider.getBlock(currentBlockNumber);

    let encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      [
        "1000",
        goodCompoundStaking.address,
        0,
        currentBlockNumber + 1000000,
        false
      ] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
    await ethers.provider.send("evm_setNextBlockTimestamp", [
      currentBlock.timestamp + 5185020
    ]);
    await ethers.provider.send("evm_mine", []);
    console.log(await goodFundManager.activeContracts(0));
    await expect(
      goodFundManager.collectInterest([goodCompoundStaking.address], false)
    ).revertedWith(/< gas costs/);
  });

  it("It should not be able to calc and sort array when there is no active staking contract", async () => {
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );
    let encodedData = goodFundManagerFactory.interface.encodeFunctionData(
      "setStakingReward",
      ["100", goodCompoundStaking.address, 0, 10, true]
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);
    const contractsToInterestCollected =
      await goodFundManager.calcSortedContracts();
    expect(contractsToInterestCollected.length).to.be.equal(0);
    encodedData = goodFundManagerFactory.interface.encodeFunctionData(
      "setStakingReward",
      ["100", goodCompoundStaking.address, 100, 1000, false]
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);
  });

  it("Avatar should be able to set gd minting gas amount", async () => {
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );
    let encodedData = goodFundManagerFactory.interface.encodeFunctionData(
      "setGasCost",
      ["140000"]
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);
  });
  it("Avatar should be able to set collectInterestTimeThreshold", async () => {
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );
    let encodedData = goodFundManagerFactory.interface.encodeFunctionData(
      "setCollectInterestTimeThreshold",
      ["5184000"]
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);
  });
  it("Avatar should be able set interestMultiplier", async () => {
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );
    let encodedData = goodFundManagerFactory.interface.encodeFunctionData(
      "setInterestMultiplier",
      ["4"]
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);
  });
  it("Avatar should be able set gasCostExceptInterestCollect", async () => {
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );
    let encodedData = goodFundManagerFactory.interface.encodeFunctionData(
      "setGasCostExceptInterestCollect",
      ["650000"]
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);
  });

  it("It should be able to collect Interest from non DAI or cDAI staking contract [ @skip-on-coverage ]", async () => {
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );

    const simpleStaking = await goodCompoundStakingFactory
      .deploy()
      .then(async contract => {
        await contract.init(
          bat.address,
          cBat.address,
          nameService.address,
          "Good BaT",
          "gBAT",
          "50",
          batUsdOracle.address,
          compUsdOracle.address,
          [bat.address, dai.address]
        );
        return contract;
      });

    let encodedData = goodFundManagerFactory.interface.encodeFunctionData(
      "setStakingReward",
      ["100", simpleStaking.address, 10, 10000, false]
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);
    encodedData = goodCompoundStakingFactory.interface.encodeFunctionData(
      "setcollectInterestGasCostParams",
      ["250000", "150000"]
    );
    await genericCall(simpleStaking.address, encodedData, avatar, 0);
    await bat["mint(address,uint256)"](
      founder.address,
      ethers.utils.parseEther("1001000")
    );
    await bat.transfer(cBat.address, ethers.utils.parseEther("1000000")); // We should put extra BAT to mock cBAT contract in order to provide interest
    await dai["mint(address,uint256)"](
      founder.address,
      ethers.utils.parseEther("1000000")
    );
    await dai.approve(
      goodCompoundStaking.address,
      ethers.utils.parseEther("100")
    );
    await goodCompoundStaking.stake(ethers.utils.parseEther("100"), 100, false);
    await bat.approve(simpleStaking.address, ethers.utils.parseEther("100"));
    await simpleStaking.stake(ethers.utils.parseEther("100"), 100, false);

    await cDAI.increasePriceWithMultiplier("2000");
    await cBat.increasePriceWithMultiplier("500");

    const collectableContracts = await goodFundManager.calcSortedContracts();
    const addressesToCollect = collectableContracts.map(x => x[0]);

    await goodFundManager.collectInterest(addressesToCollect, false, {
      gasLimit: 1500000
    });
    await simpleStaking.withdrawStake(ethers.utils.parseEther("100"), false);
    await goodCompoundStaking.withdrawStake(
      ethers.utils.parseEther("100"),
      false
    );
  });
  it("It should redeem underlying token to DAI", async () => {
    const goodFundManagerFactory = await ethers.getContractFactory(
      "GoodFundManager"
    );

    const simpleStaking = await goodCompoundStakingTestFactory.deploy(
      bat.address,
      cBat.address,
      nameService.address,
      "Good BaT",
      "gBAT",
      "50",
      batUsdOracle.address,
      compUsdOracle.address,
      [bat.address, dai.address]
    );
    await setDAOAddress("RESERVE", simpleStaking.address);
    await bat["mint(address,uint256)"](
      cBat.address,
      ethers.utils.parseEther("1000")
    );
    await cBat["mint(address,uint256)"](
      simpleStaking.address,
      ethers.utils.parseUnits("1000", 8)
    );
    const balanceBeforeRedeem = await dai.balanceOf(simpleStaking.address);
    await simpleStaking.redeemUnderlyingToDAITest(
      ethers.utils.parseUnits("10", 8)
    );
    await setDAOAddress("RESERVE", goodReserve.address);
    const balanceAfterRedeem = await dai.balanceOf(simpleStaking.address);
    const accAmountPerShare = await simpleStaking.accAmountPerShare();
    expect(accAmountPerShare.toString()).to.be.equal("0");
    expect(balanceAfterRedeem.gt(balanceBeforeRedeem)).to.be.true;
  });
  it("it should reverted when someone trying to call rewardsMinted function beside fundmanager", async () => {
    const tx = await goodCompoundStaking
      .rewardsMinted(founder.address, "1000", 50, 100)
      .catch(e => e);
    expect(tx.message).to.not.empty;
  });

  it("it should not decrease proudctivity when there is no enough amount of stake", async () => {
    const simpleStaking = await goodCompoundStakingTestFactory.deploy(
      bat.address,
      cBat.address,
      nameService.address,
      "Good BaT",
      "gBAT",
      "50",
      batUsdOracle.address,
      compUsdOracle.address,
      [bat.address, dai.address]
    );
    await expect(
      simpleStaking.decreaseProductivityTest(
        founder.address,
        ethers.utils.parseEther("100")
      )
    ).reverted;
  });

  it("User pending reward should be zero when there is no stake of user", async () => {
    const simpleStaking = await goodCompoundStakingTestFactory.deploy(
      bat.address,
      cBat.address,
      nameService.address,
      "Good BaT",
      "gBAT",
      "50",
      batUsdOracle.address,
      compUsdOracle.address,
      [bat.address, dai.address]
    );
    let encodedData =
      goodCompoundStakingTestFactory.interface.encodeFunctionData(
        "setcollectInterestGasCostParams",
        ["250000", "150000"]
      );
    await genericCall(simpleStaking.address, encodedData, avatar, 0);
    const stakingContractVals = await goodFundManager.rewardsForStakingContract(
      goodCompoundStaking.address
    );
    const pendingReward = await simpleStaking.getUserPendingReward(
      founder.address,
      stakingContractVals[0],
      stakingContractVals[1],
      stakingContractVals[2]
    );
    expect(pendingReward.toString()).to.be.equal("0");
  });
  it("It should not transfer staking token when there is no enough balance", async () => {
    const simpleStaking = await goodCompoundStakingTestFactory.deploy(
      bat.address,
      cBat.address,
      nameService.address,
      "Good BaT",
      "gBAT",
      "50",
      batUsdOracle.address,
      compUsdOracle.address,
      [bat.address, dai.address]
    );
    const tx = await simpleStaking
      .transfer(staker.address, "10000")
      .catch(e => e);
    expect(tx.message).to.have.string("ERC20: transfer amount exceeds balance");
  });

  async function addLiquidity(
    token0Amount: BigNumber,
    token1Amount: BigNumber
  ) {
    await bat.transfer(pair.address, token0Amount);
    await dai.transfer(pair.address, token1Amount);
    await pair.mint(founder.address);
  }

  it("should not undo staking contract blacklisting", async () => {
    let encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      ["1000", signers[0].address, 10, 1000, true] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
    let data = await goodFundManager.rewardsForStakingContract(
      signers[0].address
    );
    expect(data.isBlackListed).to.equal(true);

    encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      ["1000", signers[0].address, 10, 1000, false] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
    data = await goodFundManager.rewardsForStakingContract(signers[0].address);
    expect(data.isBlackListed).to.equal(true);
  });

  it("it should not remove staking contract from active staking contracts when it's reward set to zero", async () => {
    const simpleStaking1 = await goodCompoundStakingTestFactory.deploy(
      bat.address,
      cBat.address,
      nameService.address,
      "Good BaT",
      "gBAT",
      "50",
      batUsdOracle.address,
      compUsdOracle.address,
      [bat.address, dai.address]
    );
    let encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      ["1000", simpleStaking1.address, 10, 1000, false] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);
    const activeContractsCount =
      await goodFundManager.getActiveContractsCount();

    const simpleStaking = await goodCompoundStakingTestFactory.deploy(
      bat.address,
      cBat.address,
      nameService.address,
      "Good BaT",
      "gBAT",
      "50",
      batUsdOracle.address,
      compUsdOracle.address,
      [bat.address, dai.address]
    );
    encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      ["1000", simpleStaking.address, 10, 1000, false] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);
    const activeContractsCountAfterAdded =
      await goodFundManager.getActiveContractsCount();

    expect(activeContractsCountAfterAdded).equal(activeContractsCount.add(1));

    encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      ["0", simpleStaking.address, 10, 1000, false] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);

    const activeContractsCountAfterRemoved =
      await goodFundManager.getActiveContractsCount();
    const lastActiveContractAfterRemove = await goodFundManager.activeContracts(
      activeContractsCountAfterRemoved.sub(1)
    );

    expect(lastActiveContractAfterRemove).to.be.equal(simpleStaking.address);
    expect(activeContractsCountAfterAdded).to.be.eq(
      activeContractsCountAfterRemoved
    );

    expect(activeContractsCount).to.be.lt(activeContractsCountAfterRemoved);
  });

  it("it should distribute rewards correctly when there is multiple stakers", async () => {
    const simpleStaking1 = await goodCompoundStakingTestFactory.deploy(
      bat.address,
      cBat.address,
      nameService.address,
      "Good BaT",
      "gBAT",
      "50",
      batUsdOracle.address,
      compUsdOracle.address,
      [bat.address, dai.address]
    );
    const currentBlock = await ethers.provider.getBlockNumber();
    const rewardsPerBlock = BN.from("1000");
    let encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      [
        rewardsPerBlock,
        simpleStaking1.address,
        currentBlock - 10,
        currentBlock + 1000,
        false
      ] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);
    const stakingAmount = ethers.utils.parseEther("100");
    await bat["mint(address,uint256)"](founder.address, stakingAmount);
    await bat["mint(address,uint256)"](staker.address, stakingAmount);
    await bat["mint(address,uint256)"](signers[0].address, stakingAmount);
    await bat["mint(address,uint256)"](signers[1].address, stakingAmount);
    await bat["approve(address,uint256)"](
      simpleStaking1.address,
      stakingAmount
    );
    await bat
      .connect(staker)
    ["approve(address,uint256)"](simpleStaking1.address, stakingAmount);
    await bat
      .connect(signers[0])
    ["approve(address,uint256)"](simpleStaking1.address, stakingAmount);
    await bat
      .connect(signers[1])
    ["approve(address,uint256)"](simpleStaking1.address, stakingAmount);
    const stakerOneStakeBlockNumber =
      (await ethers.provider.getBlockNumber()) + 1;
    await simpleStaking1["stake(uint256,uint256,bool)"](
      stakingAmount,
      "0",
      false
    );
    const stakerOneGdBalanceAfterStake = await goodDollar.balanceOf(
      founder.address
    );
    const stakerTwoStakeBlockNumber =
      (await ethers.provider.getBlockNumber()) + 1;
    await simpleStaking1
      .connect(staker)
    ["stake(uint256,uint256,bool)"](stakingAmount.div(5), "0", false);
    const stakerTwoGdBalanceAfterStake = await goodDollar.balanceOf(
      staker.address
    );
    const stakerThreeStakeBlockNumber =
      (await ethers.provider.getBlockNumber()) + 1;
    await simpleStaking1
      .connect(signers[0])
    ["stake(uint256,uint256,bool)"](stakingAmount.div(4), "0", false);
    const stakerThreeGdBalanceAfterStake = await goodDollar.balanceOf(
      signers[0].address
    );
    const stakerFourStakeBlockNumber =
      (await ethers.provider.getBlockNumber()) + 1;
    await simpleStaking1
      .connect(signers[1])
    ["stake(uint256,uint256,bool)"](stakingAmount.div(10), "0", false);
    const stakerFourGdBalanceAfterStake = await goodDollar.balanceOf(
      signers[1].address
    );
    await advanceBlocks(10);
    await simpleStaking1["withdrawStake(uint256,bool)"](stakingAmount, false);
    const stakerOneWithdrawBlockNumber = await ethers.provider.getBlockNumber();
    const stakerOneGdBalanceAfterWithdraw = await goodDollar.balanceOf(
      founder.address
    );
    await simpleStaking1
      .connect(staker)
    ["withdrawStake(uint256,bool)"](stakingAmount.div(5), false);
    const stakerTwoWithdrawBlockNumber = await ethers.provider.getBlockNumber();
    const stakerTwoGdBalanceAfterWithdraw = await goodDollar.balanceOf(
      staker.address
    );
    await simpleStaking1
      .connect(signers[0])
    ["withdrawStake(uint256,bool)"](stakingAmount.div(4), false);

    const stakerThreeGdBalanceAfterWithdraw = await goodDollar.balanceOf(
      signers[0].address
    );
    await simpleStaking1
      .connect(signers[1])
    ["withdrawStake(uint256,bool)"](stakingAmount.div(10), false);
    const stakerFourGdBalanceAfterWithdraw = await goodDollar.balanceOf(
      signers[1].address
    );
    const stakerOneCalculatedReward = rewardsPerBlock
      .add(rewardsPerBlock.mul(100).div(120)) // there is new staker so total stake units are 120 if call full stakingamount is 100
      .add(rewardsPerBlock.mul(100).div(145)) // there is new staker so total stake units are 145
      .add(
        rewardsPerBlock
          .mul(100)
          .mul(stakerOneWithdrawBlockNumber - stakerFourStakeBlockNumber)
          .div(155) // there is new staker so total stake units are 155
      )
      .div(BN.from("2")); // divide 2 since it will just get half of rewards that earned
    const stakerTwoCalculatedReward = BN.from("0")
      .add(rewardsPerBlock.mul(20).div(120))
      .add(rewardsPerBlock.mul(20).div(145))
      .add(
        rewardsPerBlock
          .mul(20)
          .mul(stakerOneWithdrawBlockNumber - stakerFourStakeBlockNumber)
          .div(155)
      )
      .add(rewardsPerBlock.mul(20).div(55))
      .div(BN.from("2"))
      .add("1");
    const stakerThreeCalculatedReward = BN.from("0")
      .add(rewardsPerBlock.mul(25).div(145))
      .add(
        rewardsPerBlock
          .mul(25)
          .mul(stakerOneWithdrawBlockNumber - stakerFourStakeBlockNumber)
          .div(155)
      )
      .add(rewardsPerBlock.mul(25).div(55))
      .add(rewardsPerBlock.mul(25).div(35))
      .div(BN.from("2"));
    const stakerFourCalculatedReward = BN.from("0")
      .add(
        rewardsPerBlock
          .mul(10)
          .mul(stakerOneWithdrawBlockNumber - stakerFourStakeBlockNumber)
          .div(155)
      )
      .add(rewardsPerBlock.mul(10).div(55))
      .add(rewardsPerBlock.mul(10).div(35))
      .add(rewardsPerBlock)
      .div(BN.from("2"))
      .add("1");
    expect(stakerOneGdBalanceAfterWithdraw).to.be.equal(
      stakerOneGdBalanceAfterStake.add(stakerOneCalculatedReward)
    );
    expect(stakerTwoGdBalanceAfterWithdraw).to.be.equal(
      stakerTwoGdBalanceAfterStake.add(stakerTwoCalculatedReward)
    );
    expect(stakerThreeGdBalanceAfterWithdraw).to.be.equal(
      stakerThreeGdBalanceAfterStake.add(stakerThreeCalculatedReward)
    );
    expect(stakerFourGdBalanceAfterWithdraw).to.be.equal(
      stakerFourGdBalanceAfterStake.add(stakerFourCalculatedReward)
    );
    encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      [
        rewardsPerBlock,
        simpleStaking1.address,
        currentBlock - 10,
        currentBlock + 1000,
        true
      ] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);
  });
  it("it should get staking reward even reward amount is too low", async () => {
    const simpleStaking1 = await goodCompoundStakingTestFactory.deploy(
      bat.address,
      cBat.address,
      nameService.address,
      "Good BaT",
      "gBAT",
      "300",
      batUsdOracle.address,
      compUsdOracle.address,
      [bat.address, dai.address]
    );

    const currentBlock = await ethers.provider.getBlockNumber();
    const rewardsPerBlock = BN.from("100");
    let encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      [
        rewardsPerBlock,
        simpleStaking1.address,
        currentBlock - 10,
        currentBlock + 1000,
        false
      ] // set 1 gd per block
    );
    await genericCall(goodFundManager.address, encodedData, avatar, 0);
    const stakingAmount = ethers.utils.parseEther("100");
    await bat["mint(address,uint256)"](founder.address, stakingAmount);
    await bat["mint(address,uint256)"](staker.address, stakingAmount);
    await bat["approve(address,uint256)"](
      simpleStaking1.address,
      stakingAmount
    );
    await bat
      .connect(staker)
    ["approve(address,uint256)"](simpleStaking1.address, stakingAmount);
    await simpleStaking1["stake(uint256,uint256,bool)"](
      stakingAmount,
      "0",
      false
    );
    const founderGdBalanceAfterStake = await goodDollar.balanceOf(
      founder.address
    );
    const stakerStakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1;
    await simpleStaking1
      .connect(staker)
    ["stake(uint256,uint256,bool)"](stakingAmount.div(20), "0", false); // should get ~0.009 gd each block
    const stakerGdBalanceAfterStake = await goodDollar.balanceOf(
      staker.address
    );
    await advanceBlocks(100);
    await simpleStaking1
      .connect(staker)
    ["withdrawStake(uint256,bool)"](stakingAmount.div(20), false);
    const stakerWithdrawBlockNumber = await ethers.provider.getBlockNumber();
    const stakerGdBalanceAfterWithdraw = await goodDollar.balanceOf(
      staker.address
    );
    await simpleStaking1["withdrawStake(uint256,bool)"](stakingAmount, false);
    const founderWithdrawBlockNumber = await ethers.provider.getBlockNumber();
    const founderGdBalanceAfterWithdraw = await goodDollar.balanceOf(
      founder.address
    );
    const stakerCalculatedRewards = rewardsPerBlock
      .mul(5)
      .mul(stakerWithdrawBlockNumber - stakerStakeBlockNumber)
      .div(105)
      .div(BN.from("2"));

    const founderCalculatedRewards = rewardsPerBlock
      .mul(BN.from("2"))
      .add(
        rewardsPerBlock
          .mul(100)
          .mul(stakerWithdrawBlockNumber - stakerStakeBlockNumber)
          .div(105)
      )
      .div(BN.from("2"));
    expect(
      stakerGdBalanceAfterWithdraw.sub(stakerGdBalanceAfterStake)
    ).to.be.equal(stakerCalculatedRewards);
    expect(
      founderGdBalanceAfterWithdraw.sub(founderGdBalanceAfterStake)
    ).to.be.equal(founderCalculatedRewards);
  });
  it("it should calculate price of spent gas in DAI properly", async () => {
    const gasAmount = BN.from("1100000"); // 1.1M
    const gasPrice = await gasFeeOracle.latestAnswer(); // returns 25 gwei
    const daiToEthRate = await daiEthOracle.latestAnswer(); // returns  0.000341481428801721
    const calculatedResult = gasPrice
      .mul(BN.from("10").pow(18)) // we multiply with 1e18 so when we divide it to DAI/ETH rate we would get result in 18 decimals
      .div(daiToEthRate) // we divide gas price to DAI/ETH rate so we can get gas price in DAI
      .mul(gasAmount); // Result is in DAI and we accept 1$ = 1DAI
    const onChainResult = await goodFundManager.getGasPriceIncDAIorDAI(
      gasAmount,
      true
    );
    expect(calculatedResult).to.be.gt(0);
    expect(onChainResult).to.be.equal(calculatedResult);
  });
  it("it should calculate price of spent gas in cDAI properly", async () => {
    const gasAmount = BN.from("1100000"); // 1.1M
    const gasPrice = await gasFeeOracle.latestAnswer(); // returns 25 gwei
    const daiToEthRate = await daiEthOracle.latestAnswer(); // returns  0.000341481428801721 and we accept 1$ = 1DAI
    const gasPriceInDAI = gasPrice.mul(BN.from("10").pow(18)).div(daiToEthRate); // gasPrice in ETH so 18 decimals and DAI/ETH rate is in also 18 decimals so in order to 18 decimals we multiply with 1e18
    const gasPriceInCdai = gasPriceInDAI
      .div(BN.from("10").pow(10)) // we divide DAI amount with 1e10 so it reduces to CDAI decimals which is 8
      .mul(BN.from("10").pow(28)) // we multiply it with mantissa which is 18 + tokenDecimals - cTokenDecimals so token is in 18 decimals and cToken is in 8 decimals therefore difference is 10 for more detail https://compound.finance/docs#protocol-math
      .div(await cDAI.exchangeRateStored()); // then we divide it exchange rate in order to get result
    const calculatedResult = gasPriceInCdai.mul(gasAmount);
    const onChainResult = await goodFundManager.getGasPriceIncDAIorDAI(
      gasAmount,
      false
    );
    expect(calculatedResult).to.be.gt(0);
    expect(onChainResult).to.be.equal(calculatedResult);
  });

  it("it should return currentgains properly according to function parameters", async () => {
    const simpleStaking = await goodCompoundStakingTestFactory.deploy(
      bat.address,
      cBat.address,
      nameService.address,
      "Good BaT",
      "gBAT",
      "50",
      batUsdOracle.address,
      compUsdOracle.address,
      [bat.address, dai.address]
    );
    let encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      ["1000", simpleStaking.address, 10, 1000, false] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
    const stakingAmount = ethers.utils.parseEther("100");
    await bat["mint(address,uint256)"](founder.address, stakingAmount);
    await bat.approve(simpleStaking.address, stakingAmount);
    await simpleStaking.stake(stakingAmount, "0", false);
    await cBat.increasePriceWithMultiplier(100);
    const currentGainsWithBothFalse = await simpleStaking.currentGains(
      false,
      false
    );
    const currentGainsWithBothTrue = await simpleStaking.currentGains(
      true,
      true
    );
    await simpleStaking.withdrawStake(stakingAmount, false);
    expect(currentGainsWithBothFalse[3]).to.be.equal(0);
    expect(currentGainsWithBothFalse[4]).to.be.equal(0);
    expect(currentGainsWithBothTrue[3]).to.be.gt(0);
    expect(currentGainsWithBothTrue[4]).to.be.gt(0);
    encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      ["1000", simpleStaking.address, 10, 1000, true] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
  });

  it("it should collectInterest when there is comp rewards but there is no interest from interest token for nonDAI-cDAI staking contract", async () => {
    const stakingAmount = ethers.utils.parseEther("100");
    let { simpleStaking } = await compTestHelper(
      bat,
      cBat,
      batUsdOracle,
      stakingAmount,
      false
    );

    const currentGains = await simpleStaking.currentGains(false, true);
    expect(currentGains[4]).to.be.equal("0");
    await simpleStaking.withdrawStake(stakingAmount, false);

    const encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      ["1000", simpleStaking.address, 10, 1000, true] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
  });

  it("it should collectInterest from interest token along with comp rewards for nonDAI-cDAI staking contract [ @skip-on-coverage ]", async () => {
    const stakingAmount = ethers.utils.parseEther("100");
    let { simpleStaking } = await compTestHelper(
      bat,
      cBat,
      batUsdOracle,
      stakingAmount,
      true
    );

    const currentGains = await simpleStaking.currentGains(false, true);
    expect(currentGains[4]).to.be.equal("4"); // it's not equal due to precision loss
    await simpleStaking.withdrawStake(stakingAmount, false);

    const encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      ["1000", simpleStaking.address, 10, 1000, true] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
  });

  it("it should collectInterest when there is comp rewards but there is no interest from interest token for DAI-cDAI staking contract", async () => {
    const stakingAmount = ethers.utils.parseEther("100");
    let { simpleStaking } = await compTestHelper(
      dai,
      cDAI,
      daiUsdOracle,
      stakingAmount,
      false
    );

    const currentGains = await simpleStaking.currentGains(false, true);
    expect(currentGains[4]).to.be.equal("0");
    await simpleStaking.withdrawStake(stakingAmount, false);

    const encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      ["1000", simpleStaking.address, 10, 1000, true] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
  });
  it("it should collectInterest from interest token along with comp rewards for DAI-cDAI staking contract [ @skip-on-coverage ]", async () => {
    const stakingAmount = ethers.utils.parseEther("100");
    let { simpleStaking } = await compTestHelper(
      dai,
      cDAI,
      daiUsdOracle,
      stakingAmount,
      true
    );

    const currentGains = await simpleStaking.currentGains(false, true);
    expect(currentGains[4]).to.be.lt("200"); //should be equal 0 but due to precision loss give some offset so make sure less than 200
    await simpleStaking.withdrawStake(stakingAmount, false);

    const encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      ["1000", simpleStaking.address, 10, 1000, true] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
  });
  it("it should mint rewards properly when withdrawRewards", async () => {
    const currentBlockNumber = await ethers.provider.getBlockNumber();
    const simpleStaking = await goodCompoundStakingTestFactory.deploy(
      bat.address,
      cBat.address,
      nameService.address,
      "Good BaT",
      "gBAT",
      "500",
      batUsdOracle.address,
      compUsdOracle.address,
      [bat.address, dai.address]
    );
    let encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      [
        "1000",
        simpleStaking.address,
        currentBlockNumber - 5,
        currentBlockNumber + 1000,
        false
      ] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
    const rewardsPerBlock = BN.from("1000");
    const stakingAmount = ethers.utils.parseEther("100");
    await bat["mint(address,uint256)"](founder.address, stakingAmount);
    await bat.approve(simpleStaking.address, stakingAmount);
    const stakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1;
    await simpleStaking.stake(stakingAmount, "0", false);

    const GDBalanceAfterStake = await goodDollar.balanceOf(founder.address);
    await advanceBlocks(100);
    await simpleStaking.withdrawRewards();
    const withdrawRewardsBlockNumber = await ethers.provider.getBlockNumber();
    const GDBalanceAfterWithdraw = await goodDollar.balanceOf(founder.address);

    expect(GDBalanceAfterWithdraw).to.be.equal(
      GDBalanceAfterStake.add(
        rewardsPerBlock
          .mul(withdrawRewardsBlockNumber - stakeBlockNumber)
          .div(BN.from("2"))
      )
    );
    encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      [
        "1000",
        simpleStaking.address,
        currentBlockNumber - 5,
        currentBlockNumber + 1000,
        true
      ] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
  });
  it("it should not overmint rewards when staker withdraw their rewards", async () => {
    const currentBlockNumber = await ethers.provider.getBlockNumber();
    const simpleStaking = await goodCompoundStakingTestFactory.deploy(
      bat.address,
      cBat.address,
      nameService.address,
      "Good BaT",
      "gBAT",
      "500",
      batUsdOracle.address,
      compUsdOracle.address,
      [bat.address, dai.address]
    );
    let encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      [
        "1000",
        simpleStaking.address,
        currentBlockNumber - 5,
        currentBlockNumber + 1000,
        false
      ] // set 10 gd per block
    );
    const overMintTestFactory = await ethers.getContractFactory(
      "OverMintTesterRegularStake"
    );
    const overMintTester = await overMintTestFactory.deploy(
      bat.address,
      simpleStaking.address,
      goodDollar.address
    );
    await genericCall(goodFundManager.address, encodedData);
    const stakingAmount = ethers.utils.parseEther("100");
    await bat["mint(address,uint256)"](overMintTester.address, stakingAmount);
    const rewardsPerBlock = BN.from("1000");
    const stakeBlockNumber = (await ethers.provider.getBlockNumber()) + 1;
    await overMintTester.stake();
    const GDBalanceAfterStake = await goodDollar.balanceOf(
      overMintTester.address
    );
    await advanceBlocks(100);
    await overMintTester.overMintTest();
    const withdrawRewardsBlockNumber = await ethers.provider.getBlockNumber();
    const GDBalanceAfterWithdrawReward = await goodDollar.balanceOf(
      overMintTester.address
    );
    await advanceBlocks(20);
    await overMintTester.overMintTest();
    const secondWithdrawRewardsBlockNumber =
      await ethers.provider.getBlockNumber();
    const GDBalanceAfterSecondWithdrawReward = await goodDollar.balanceOf(
      overMintTester.address
    );

    expect(GDBalanceAfterWithdrawReward).to.be.gt(GDBalanceAfterStake);
    expect(GDBalanceAfterWithdrawReward.sub(GDBalanceAfterStake)).to.be.equal(
      rewardsPerBlock
        .mul(withdrawRewardsBlockNumber - stakeBlockNumber)
        .div(BN.from("2"))
    );
    expect(GDBalanceAfterSecondWithdrawReward).to.be.gt(
      GDBalanceAfterWithdrawReward
    );
    expect(
      GDBalanceAfterSecondWithdrawReward.sub(GDBalanceAfterWithdrawReward)
    ).to.be.equal(
      rewardsPerBlock
        .mul(secondWithdrawRewardsBlockNumber - withdrawRewardsBlockNumber)
        .div(BN.from("2"))
    );
    encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      [
        "1000",
        simpleStaking.address,
        currentBlockNumber - 5,
        currentBlockNumber + 1000,
        true
      ] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
  });

  const compTestHelper = async (
    token: Contract,
    cToken: Contract,
    tokenUsdOracle: Contract,
    stakingAmount,
    increasePriceOfCtoken
  ) => {
    const simpleStaking = await goodCompoundStakingTestFactory.deploy(
      token.address,
      cToken.address,
      nameService.address,
      "Good BaT",
      "gBAT",
      "50",
      tokenUsdOracle.address,
      compUsdOracle.address,
      [token.address, dai.address]
    );
    let encodedData = goodFundManager.interface.encodeFunctionData(
      "setStakingReward",
      ["1000", simpleStaking.address, 10, 1000, false] // set 10 gd per block
    );
    await genericCall(goodFundManager.address, encodedData);
    await token["mint(address,uint256)"](founder.address, stakingAmount);
    await token.approve(simpleStaking.address, stakingAmount);
    await simpleStaking.stake(stakingAmount, "0", false);
    let currentGains = await simpleStaking.currentGains(false, true);
    expect(currentGains[4]).to.be.equal("0");
    await comp["mint(address,uint256)"](
      simpleStaking.address,
      ethers.utils.parseEther("10")
    );
    currentGains = await simpleStaking.currentGains(false, true);
    const compPriceInDollar = await compUsdOracle.latestAnswer();
    expect(currentGains[4]).to.be.equal(
      BigNumber.from("10").mul(compPriceInDollar)
    );
    if (increasePriceOfCtoken) {
      await cToken.increasePriceWithMultiplier("1000");
    }

    const contractAddressesToBeCollected = await goodFundManager
      .connect(staker)
      .calcSortedContracts();
    const addressesToCollect = contractAddressesToBeCollected.map(x => x[0]);
    await goodFundManager.collectInterest(addressesToCollect, false);
    return { simpleStaking };
  };
});
