diff --git a/contracts/test/Mocks/MockPriceOracle.sol b/contracts/test/Mocks/MockPriceOracle.sol index 31ce815ec..a95664587 100644 --- a/contracts/test/Mocks/MockPriceOracle.sol +++ b/contracts/test/Mocks/MockPriceOracle.sol @@ -8,6 +8,18 @@ import { ProtocolShareReserve } from "@venusprotocol/protocol-reserve/contracts/ import { VToken } from "../../VToken.sol"; contract MockPriceOracle is ResilientOracleInterface { + struct TokenConfig { + /// @notice asset address + address asset; + /// @notice `oracles` stores the oracles based on their role in the following order: + /// [main, pivot, fallback], + /// It can be indexed with the corresponding enum OracleRole value + address[3] oracles; + /// @notice `enableFlagsForOracles` stores the enabled state + /// for each oracle in the same order as `oracles` + bool[3] enableFlagsForOracles; + } + mapping(address => uint256) public assetPrices; //set price in 6 decimal precision @@ -28,6 +40,10 @@ contract MockPriceOracle is ResilientOracleInterface { return assetPrices[asset]; } + function getTokenConfig(address asset) external view returns (TokenConfig memory) {} + + function setTokenConfig(TokenConfig memory tokenConfig) public {} + //https://compound.finance/docs/prices function getUnderlyingPrice(address vToken) public view override returns (uint256) { return assetPrices[VToken(vToken).underlying()]; diff --git a/hardhat.config.ts b/hardhat.config.ts index f7b084ebe..611e92b16 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -41,11 +41,22 @@ extendConfig((config: HardhatConfig) => { "node_modules/@venusprotocol/protocol-reserve/deployments/ethereum", ], bscmainnet: [ - "node_modules/@venusprotocol/protocol-reserve/deployments/bscmainnet", + "node_modules/@venusprotocol/oracle/deployments/bscmainnet", "node_modules/@venusprotocol/venus-protocol/deployments/bscmainnet", + "node_modules/@venusprotocol/protocol-reserve/deployments/bscmainnet", + ], + opbnbmainnet: [ + "node_modules/@venusprotocol/oracle/deployments/opbnbmainnet", + "node_modules/@venusprotocol/protocol-reserve/deployments/opbnbmainnet", + ], + opbnbtestnet: [ + "node_modules/@venusprotocol/oracle/deployments/opbnbtestnet", + "node_modules/@venusprotocol/protocol-reserve/deployments/opbnbtestnet", + ], + arbitrumsepolia: [ + "node_modules/@venusprotocol/oracle/deployments/arbitrumsepolia", + "node_modules/@venusprotocol/protocol-reserve/deployments/arbitrumsepolia", ], - opbnbmainnet: ["node_modules/@venusprotocol/oracle/deployments/opbnbmainnet"], - arbitrumsepolia: ["node_modules/@venusprotocol/protocol-reserve/deployments/arbitrumsepolia"], arbitrumone: ["node_modules/@venusprotocol/protocol-reserve/deployments/arbitrumone"], }, }; diff --git a/package.json b/package.json index 2cb96e47c..17eec15c8 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@typescript-eslint/eslint-plugin": "^5.27.1", "@typescript-eslint/parser": "^5.27.1", "@venusprotocol/governance-contracts": "2.1.0", - "@venusprotocol/oracle": "2.0.0", + "@venusprotocol/oracle": "2.3.0", "@venusprotocol/protocol-reserve": "2.2.0", "@venusprotocol/venus-protocol": "9.0.0", "bignumber.js": "9.0.0", diff --git a/tests/hardhat/Fork/NativeTokenGateway.ts b/tests/hardhat/Fork/NativeTokenGateway.ts index e20e4b7c0..84b11fbf3 100644 --- a/tests/hardhat/Fork/NativeTokenGateway.ts +++ b/tests/hardhat/Fork/NativeTokenGateway.ts @@ -7,17 +7,14 @@ import { Comptroller, ERC20, NativeTokenGateway, VToken } from "../../../typecha import { initMainnetUser, setForkBlock } from "./utils"; const ADMIN = "0x285960C5B22fD66A736C7136967A3eB15e93CC67"; -const COMPTROLLER_BEACON = "0xAE2C3F21896c02510aA187BdA0791cDA77083708"; -const VTOKEN_BEACON = "0xfc08aADC7a1A93857f6296C3fb78aBA1d286533a"; const COMPTROLLER_ADDRESS = "0x687a01ecF6d3907658f7A7c714749fAC32336D1B"; -const POOL_REGISTRY = "0x61CAff113CCaf05FFc6540302c37adcf077C5179"; const VWETH = "0x7c8ff7d2A1372433726f879BD945fFb250B94c65"; const USDT = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; const VUSDT = "0x8C3e3821259B82fFb32B2450A95d2dcbf161C24E"; const USER1 = "0xf89d7b9c864f589bbF53a82105107622B35EaA40"; const USER2 = "0x974CaA59e49682CdA0AD2bbe82983419A2ECC400"; -const BLOCK_NUMBER = 19139865; +const BLOCK_NUMBER = 19781700; async function configureTimeLock() { impersonatedTimeLock = await initMainnetUser(ADMIN, ethers.utils.parseUnits("2")); @@ -44,16 +41,6 @@ async function setup() { usdt = await ethers.getContractAt("@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20", USDT); const comptroller = await ethers.getContractAt("Comptroller", COMPTROLLER_ADDRESS); - const newComptrollerFactory = await ethers.getContractFactory("Comptroller"); - const newComptrollerImplementation = await newComptrollerFactory.deploy(POOL_REGISTRY); - const beaconContract = await ethers.getContractAt("UpgradeableBeacon", COMPTROLLER_BEACON); - - const newVToken = await ethers.getContractFactory("VToken"); - const newVTokenImplementation = await newVToken.deploy(); - const vTokenBeaconContract = await ethers.getContractAt("UpgradeableBeacon", VTOKEN_BEACON); - - await beaconContract.connect(impersonatedTimeLock).upgradeTo(newComptrollerImplementation.address); - await vTokenBeaconContract.connect(impersonatedTimeLock).upgradeTo(newVTokenImplementation.address); vusdt = await ethers.getContractAt("VToken", VUSDT); vweth = await ethers.getContractAt("VToken", VWETH); @@ -94,7 +81,7 @@ if (FORK && FORKED_NETWORK === "ethereum") { const balanceAfterSupplying = await vweth.balanceOf(await user1.getAddress()); await expect(balanceAfterSupplying.sub(balanceBeforeSupplying).toString()).to.closeTo( parseUnits("10", 8), - parseUnits("1", 5), + parseUnits("1", 7), ); await expect(tx).to.changeEtherBalances([user1], [supplyAmount.mul(-1)]); }); @@ -115,7 +102,7 @@ if (FORK && FORKED_NETWORK === "ethereum") { await expect(ethBalanceAfter.sub(ethBalanceBefore)).to.closeTo(redeemAmount, parseUnits("1", 16)); - expect(await vweth.balanceOf(await user1.getAddress())).to.eq(0); + expect(await vweth.balanceOf(await user1.getAddress())).to.closeTo(0, 10); }); }); diff --git a/tests/hardhat/Fork/RewardsForkTest.ts b/tests/hardhat/Fork/RewardsForkTest.ts index c8219674e..575020580 100644 --- a/tests/hardhat/Fork/RewardsForkTest.ts +++ b/tests/hardhat/Fork/RewardsForkTest.ts @@ -7,68 +7,55 @@ import { ethers } from "hardhat"; import { convertToUnit } from "../../../helpers/utils"; import { - AccessControlManager, - AccessControlManager__factory, BinanceOracle, BinanceOracle__factory, + ChainlinkOracle__factory, Comptroller, Comptroller__factory, - MockToken, - MockToken__factory, + IERC20, + IERC20__factory, RewardsDistributor, RewardsDistributor__factory, VToken, VToken__factory, } from "../../../typechain"; -import { initMainnetUser, setForkBlock } from "./utils"; +import { getContractAddresses, initMainnetUser, setForkBlock } from "./utils"; const { expect } = chai; chai.use(smock.matchers); -const FORK_TESTNET = process.env.FORK === "true" && process.env.FORKED_NETWORK === "bsctestnet"; -const FORK_MAINNET = process.env.FORK === "true" && process.env.FORKED_NETWORK === "bscmainnet"; -const MANTISSA_ONE = convertToUnit(1, 18); - -let ADMIN: string; -let ACM: string; -let acc1: string; -let acc2: string; -let HAY: string; -let COMPTROLLER: string; -let VHAY: string; -let REWARD_DISTRIBUTOR1: string; -let BLOCK_NUMBER: number; -let BINANCE_ORACLE: string; - -if (FORK_TESTNET) { - ADMIN = "0xce10739590001705f7ff231611ba4a48b2820327"; - ACM = "0x45f8a08F534f34A97187626E05d4b6648Eeaa9AA"; - acc1 = "0xe70898180a366F204AA529708fB8f5052ea5723c"; - acc2 = "0xA4a04C2D661bB514bB8B478CaCB61145894563ef"; - HAY = "0xe73774DfCD551BF75650772dC2cC56a2B6323453"; - COMPTROLLER = "0x10b57706AD2345e590c2eA4DC02faef0d9f5b08B"; - VHAY = "0x170d3b2da05cc2124334240fB34ad1359e34C562"; - REWARD_DISTRIBUTOR1 = "0xb0269d68CfdCc30Cb7Cd2E0b52b08Fa7Ffd3079b"; - BINANCE_ORACLE = "0xB58BFDCE610042311Dc0e034a80Cc7776c1D68f5"; - BLOCK_NUMBER = 30908163; -} +const FORK = process.env.FORK === "true"; +const FORKED_NETWORK = process.env.FORKED_NETWORK || "bscmainnet"; + +const { + ACC1, + ACC2, + ADMIN, + TOKEN2, + VTOKEN2, + COMPTROLLER, + TOKEN2_HOLDER, + BINANCE_ORACLE, + CHAINLINK_ORACLE, + REWARD_DISTRIBUTOR1, + BLOCK_NUMBER, +} = getContractAddresses(FORKED_NETWORK as string); -if (FORK_MAINNET) { - // Mainnet addresses -} +const MANTISSA_ONE = convertToUnit(1, 18); -let impersonatedTimelock: Signer; -let accessControlManager: AccessControlManager; +let token2: IERC20; +let vTOKEN2: VToken; let comptroller: Comptroller; -let vHAY: VToken; -let hay: MockToken; -let rewardDistributor1: RewardsDistributor; let acc1Signer: Signer; let acc2Signer: Signer; +let token2Holder: Signer; let comptrollerSigner: Signer; +let impersonatedTimelock: Signer; +let binanceOracle: BinanceOracle; +let chainlinkOracle: ChainlinkOracle; let mintAmount: BigNumberish; let bswBorrowAmount: BigNumberish; -let binanceOracle: BinanceOracle; +let rewardDistributor1: RewardsDistributor; async function configureTimelock() { impersonatedTimelock = await initMainnetUser(ADMIN, ethers.utils.parseUnits("2")); @@ -78,52 +65,51 @@ async function configureVToken(vTokenAddress: string) { return VToken__factory.connect(vTokenAddress, impersonatedTimelock); } -async function grantPermissions() { - accessControlManager = AccessControlManager__factory.connect(ACM, impersonatedTimelock); - - let tx = await accessControlManager - .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setMarketSupplyCaps(address[],uint256[])", ADMIN); - await tx.wait(); - - tx = await accessControlManager - .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setMarketBorrowCaps(address[],uint256[])", ADMIN); - await tx.wait(); -} - -if (FORK_TESTNET || FORK_MAINNET) { +if (FORK) { describe("Rewards distributions", async () => { - mintAmount = convertToUnit("100000000", 18); + mintAmount = convertToUnit("10000", 18); bswBorrowAmount = convertToUnit("100", 18); async function setup() { await setForkBlock(BLOCK_NUMBER); await configureTimelock(); - acc1Signer = await initMainnetUser(acc1, ethers.utils.parseUnits("2")); - acc2Signer = await initMainnetUser(acc2, ethers.utils.parseUnits("2")); + acc1Signer = await initMainnetUser(ACC1, ethers.utils.parseUnits("2")); + acc2Signer = await initMainnetUser(ACC2, ethers.utils.parseUnits("2")); + token2Holder = await initMainnetUser(TOKEN2_HOLDER, ethers.utils.parseUnits("2")); comptrollerSigner = await initMainnetUser(COMPTROLLER, ethers.utils.parseUnits("2")); - hay = MockToken__factory.connect(HAY, impersonatedTimelock); - vHAY = await configureVToken(VHAY); + token2 = IERC20__factory.connect(TOKEN2, impersonatedTimelock); + vTOKEN2 = await configureVToken(VTOKEN2); comptroller = Comptroller__factory.connect(COMPTROLLER, impersonatedTimelock); rewardDistributor1 = RewardsDistributor__factory.connect(REWARD_DISTRIBUTOR1, impersonatedTimelock); - await grantPermissions(); + await comptroller.connect(acc1Signer).enterMarkets([vTOKEN2.address]); + await comptroller.connect(acc2Signer).enterMarkets([vTOKEN2.address]); - await comptroller.connect(acc1Signer).enterMarkets([vHAY.address]); - await comptroller.connect(acc2Signer).enterMarkets([vHAY.address]); + await comptroller.setMarketSupplyCaps([vTOKEN2.address], [convertToUnit(1, 50)]); + await comptroller.setMarketBorrowCaps([vTOKEN2.address], [convertToUnit(1, 50)]); - await comptroller.setMarketSupplyCaps([vHAY.address], [convertToUnit(1, 50)]); - await comptroller.setMarketBorrowCaps([vHAY.address], [convertToUnit(1, 50)]); + if (FORKED_NETWORK == "bscmainnet" || FORKED_NETWORK == "bsctestnet") { + binanceOracle = BinanceOracle__factory.connect(BINANCE_ORACLE, impersonatedTimelock); + await binanceOracle.connect(impersonatedTimelock).setMaxStalePeriod("lisUSD", 31536000); + await binanceOracle.connect(impersonatedTimelock).setMaxStalePeriod("HAY", 31536000); + } - binanceOracle = BinanceOracle__factory.connect(BINANCE_ORACLE, impersonatedTimelock); - await binanceOracle.connect(impersonatedTimelock).setMaxStalePeriod("HAY", 31536000); + if (FORKED_NETWORK == "ethereum" || FORKED_NETWORK == "arbitrumsepolia" || FORKED_NETWORK == "arbitrumone") { + chainlinkOracle = ChainlinkOracle__factory.connect(CHAINLINK_ORACLE, impersonatedTimelock); + let tuple = await chainlinkOracle.tokenConfigs(TOKEN2); + tuple = { + asset: tuple[0], + feed: tuple[1], + maxStalePeriod: "90000000000000000", + }; + await chainlinkOracle.setTokenConfig(tuple); + } } - async function mintVTokens(signer: Signer, token: MockToken, vToken: VToken, amount: BigNumberish) { - await token.connect(signer).faucet(amount); + async function mintVTokens(signer: Signer, token: IERC20, vToken: VToken, amount: BigNumberish) { + await token.connect(token2Holder).transfer(await signer.getAddress(), amount); await token.connect(signer).approve(vToken.address, amount); await expect(vToken.connect(signer).mint(amount)).to.emit(vToken, "Mint"); } @@ -137,7 +123,10 @@ if (FORK_TESTNET || FORK_MAINNET) { const supplierAccruedOld = await rewardDistributor.rewardTokenAccrued(user); await rewardDistributor.connect(comptrollerSigner).updateRewardTokenSupplyIndex(vTokenAddress); - const supplyState = await rewardDistributor.rewardTokenSupplyState(vTokenAddress); + let supplyState = await rewardDistributor.rewardTokenSupplyState(vTokenAddress); + if (FORKED_NETWORK == "arbitrumsepolia" || FORKED_NETWORK == "arbitrumone") { + supplyState = await rewardDistributor.rewardTokenSupplyStateTimeBased(vTokenAddress); + } const supplyIndex = supplyState.index; let supplierIndex = await rewardDistributor.rewardTokenSupplierIndex(vTokenAddress, user); @@ -159,7 +148,7 @@ if (FORK_TESTNET || FORK_MAINNET) { vToken: VToken, user: string, ) { - await vHAY.accrueInterest(); + await vTOKEN2.accrueInterest(); const marketBorrowIndex = await vToken.borrowIndex(); const borrowerAccruedOld = await rewardDistributor.rewardTokenAccrued(user); @@ -167,7 +156,10 @@ if (FORK_TESTNET || FORK_MAINNET) { .connect(comptrollerSigner) .updateRewardTokenBorrowIndex(vTokenAddress, { mantissa: marketBorrowIndex }); - const borrowState = await rewardDistributor.rewardTokenBorrowState(vTokenAddress); + let borrowState = await rewardDistributor.rewardTokenBorrowState(vTokenAddress); + if (FORKED_NETWORK == "arbitrumsepolia" || FORKED_NETWORK == "arbitrumone") { + borrowState = await rewardDistributor.rewardTokenBorrowStateTimeBased(vTokenAddress); + } const borrowIndex = borrowState.index; let borrowerIndex = await rewardDistributor.rewardTokenBorrowerIndex(vTokenAddress, user); @@ -192,52 +184,52 @@ if (FORK_TESTNET || FORK_MAINNET) { }); it("Rewards for suppliers", async function () { - await mintVTokens(acc1Signer, hay, vHAY, mintAmount); + await mintVTokens(acc1Signer, token2, vTOKEN2, mintAmount); await mine(3000000); - await vHAY.accrueInterest(); + await vTOKEN2.accrueInterest(); // Reward1 calculations for user 1 - let supplierAccruedExpected = await computeSupplyRewards(rewardDistributor1, VHAY, vHAY, acc1); - let supplierAccruedCurrent = await rewardDistributor1.rewardTokenAccrued(acc1); + let supplierAccruedExpected = await computeSupplyRewards(rewardDistributor1, VTOKEN2, vTOKEN2, ACC1); + let supplierAccruedCurrent = await rewardDistributor1.rewardTokenAccrued(ACC1); expect(supplierAccruedExpected).equals(supplierAccruedCurrent); // Transfer vTokens to user 2 from user 1 - const acc1Balance = await vHAY.balanceOf(acc1); - await vHAY.connect(acc1Signer).transfer(acc2, acc1Balance); - await vHAY.accrueInterest(); + const acc1Balance = await vTOKEN2.balanceOf(ACC1); + await vTOKEN2.connect(acc1Signer).transfer(ACC2, acc1Balance); + await vTOKEN2.accrueInterest(); // Reward1 calculations for user 1 - supplierAccruedExpected = await computeSupplyRewards(rewardDistributor1, VHAY, vHAY, acc1); - supplierAccruedCurrent = await rewardDistributor1.rewardTokenAccrued(acc1); + supplierAccruedExpected = await computeSupplyRewards(rewardDistributor1, VTOKEN2, vTOKEN2, ACC1); + supplierAccruedCurrent = await rewardDistributor1.rewardTokenAccrued(ACC1); expect(supplierAccruedExpected).equals(supplierAccruedCurrent); // Reward1 calculations for user 2 - supplierAccruedExpected = await computeSupplyRewards(rewardDistributor1, VHAY, vHAY, acc2); - supplierAccruedCurrent = await rewardDistributor1.rewardTokenAccrued(acc2); + supplierAccruedExpected = await computeSupplyRewards(rewardDistributor1, VTOKEN2, vTOKEN2, ACC2); + supplierAccruedCurrent = await rewardDistributor1.rewardTokenAccrued(ACC2); expect(supplierAccruedExpected).equals(supplierAccruedCurrent); }); it("Rewards for borrowers", async function () { - await mintVTokens(acc1Signer, hay, vHAY, mintAmount); - await vHAY.connect(acc1Signer).borrow(bswBorrowAmount); + await mintVTokens(acc1Signer, token2, vTOKEN2, mintAmount); + await vTOKEN2.connect(acc1Signer).borrow(bswBorrowAmount); await mine(3000000); - await vHAY.accrueInterest(); + await vTOKEN2.accrueInterest(); // Reward1 calculations for user 1 - let borrowerAccruedExpected = await computeBorrowRewards(rewardDistributor1, VHAY, vHAY, acc1); - let borrowerAccruedCurrent = await rewardDistributor1.rewardTokenAccrued(acc1); - expect(borrowerAccruedExpected).to.closeTo(borrowerAccruedCurrent, parseUnits("0.000000000000000079", 18)); + let borrowerAccruedExpected = await computeBorrowRewards(rewardDistributor1, VTOKEN2, vTOKEN2, ACC1); + let borrowerAccruedCurrent = await rewardDistributor1.rewardTokenAccrued(ACC1); + expect(borrowerAccruedExpected).to.closeTo(borrowerAccruedCurrent, parseUnits("0.0000000000000079", 18)); // Repay - const borrowBalanceStored = await vHAY.borrowBalanceStored(acc1); - await hay.connect(acc1Signer).faucet(borrowBalanceStored); - await hay.connect(acc1Signer).approve(VHAY, borrowBalanceStored); - await expect(vHAY.connect(acc1Signer).repayBorrow(borrowBalanceStored)).to.emit(vHAY, "RepayBorrow"); + const borrowBalanceStored = await vTOKEN2.borrowBalanceStored(ACC1); + await token2.connect(token2Holder).transfer(ACC1, borrowBalanceStored); + await token2.connect(acc1Signer).approve(VTOKEN2, borrowBalanceStored); + await expect(vTOKEN2.connect(acc1Signer).repayBorrow(borrowBalanceStored)).to.emit(vTOKEN2, "RepayBorrow"); // Reward1 calculations for user 1 - borrowerAccruedExpected = await computeBorrowRewards(rewardDistributor1, VHAY, vHAY, acc1); - borrowerAccruedCurrent = await rewardDistributor1.rewardTokenAccrued(acc1); - expect(borrowerAccruedExpected).to.closeTo(borrowerAccruedCurrent, parseUnits("0.000000000000000006", 18)); + borrowerAccruedExpected = await computeBorrowRewards(rewardDistributor1, VTOKEN2, vTOKEN2, ACC1); + borrowerAccruedCurrent = await rewardDistributor1.rewardTokenAccrued(ACC1); + expect(borrowerAccruedExpected).to.closeTo(borrowerAccruedCurrent, parseUnits("0.0000000000000006", 18)); }); }); } diff --git a/tests/hardhat/Fork/Shortfall.ts b/tests/hardhat/Fork/Shortfall.ts index 35f743931..98e79cde9 100644 --- a/tests/hardhat/Fork/Shortfall.ts +++ b/tests/hardhat/Fork/Shortfall.ts @@ -1,8 +1,9 @@ import { smock } from "@defi-wonderland/smock"; import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import chai from "chai"; +import { BigNumber } from "ethers"; import { parseEther, parseUnits } from "ethers/lib/utils"; -import { ethers, upgrades } from "hardhat"; +import { ethers } from "hardhat"; import { SignerWithAddress } from "hardhat-deploy-ethers/signers"; import { @@ -12,11 +13,10 @@ import { Comptroller__factory, IERC20, IERC20__factory, - IPancakeswapV2Router__factory, + MockPriceOracle, + MockPriceOracle__factory, ProtocolShareReserve, ProtocolShareReserve__factory, - ResilientOracleInterface, - ResilientOracleInterface__factory, RiskFund, RiskFund__factory, Shortfall, @@ -24,165 +24,151 @@ import { VToken, VToken__factory, } from "../../../typechain"; -import { initMainnetUser, setForkBlock } from "./utils"; +import { getContractAddresses, initMainnetUser, setForkBlock } from "./utils"; + +const FORK = process.env.FORK === "true"; +const FORKED_NETWORK = process.env.FORKED_NETWORK || "bscmainnet"; + +const { + PSR, + ADMIN, + TOKEN2, + USDT, + VUSDT, + VTOKEN2, + RISKFUND, + SHORTFALL, + COMPTROLLER, + USDT_HOLDER, + TOKEN2_HOLDER, + RESILIENT_ORACLE, + CHAINLINK_ORACLE, + BLOCK_NUMBER, +} = getContractAddresses(FORKED_NETWORK as string); const { expect } = chai; chai.use(smock.matchers); -const FORK_MAINNET = process.env.FORK === "true" && process.env.FORKED_NETWORK === "bscmainnet"; - -const ADMIN = "0x939bD8d64c0A9583A7Dcea9933f7b21697ab6396"; -const TREASURY = "0xF322942f644A996A617BD29c16bd7d231d9F35E9"; -const ACM = "0x4788629ABc6cFCA10F9f969efdEAa1cF70c23555"; -const ORACLE = "0x6592b5DE802159F3E74B2486b091D11a8256ab8A"; -const CHAINLINK_ORACLE = "0x1B2103441A0A108daD8848D8F5d790e4D402921F"; -const POOL_REGISTRY = "0x9F7b01A536aFA00EF10310A162877fd792cD0666"; - -const SWAP_ROUTER_CORE_POOL = "0x8938E6dA30b59c1E27d5f70a94688A89F7c815a4"; -const COMPTROLLER_TRON = "0x23b4404E4E5eC5FF5a6FFb70B7d14E3FabF237B0"; - -const USDT = "0x55d398326f99059fF775485246999027B3197955"; -const TRX = "0xCE7de646e7208a4Ef112cb6ed5038FA6cC6b12e3"; - -const VUSDT_TRON = "0x281E5378f99A4bc55b295ABc0A3E7eD32Deba059"; -const VTRX_TRON = "0x836beb2cB723C498136e1119248436A645845F4E"; - -const MINIMUM_POOL_BAD_DEBT = parseUnits("10", 18); // USD -const LOOPS_LIMIT = 100; - -let impersonatedTimelock: SignerWithAddress; -let oracle: ResilientOracleInterface; -let chainlinkOracle: ChainlinkOracle; - -let manager: SignerWithAddress; -let user1: SignerWithAddress; -let liquidator: SignerWithAddress; - +let token2: IERC20; +let token1: IERC20; +let vTOKEN2: VToken; +let vTOKEN1: VToken; let comptroller: Comptroller; -let vTRX: VToken; -let vUSDT: VToken; - -let trx: IERC20; -let usdt: IERC20; - let shortfall: Shortfall; -let protocolShareReserve: ProtocolShareReserve; let riskFund: RiskFund; +let user1: SignerWithAddress; +let manager: SignerWithAddress; +let liquidator: SignerWithAddress; +let impersonatedTimelock: SignerWithAddress; +let resilientOracle: MockPriceOracle; +let chainlinkOracle: ChainlinkOracle; +let protocolShareReserve: ProtocolShareReserve; const configureTimelock = async () => { impersonatedTimelock = await initMainnetUser(ADMIN, parseEther("2")); }; +let usdtDecimals: BigNumber; +let oldPoolAsetReserves: BigNumber; + const grabTokensTo = async (userAddress: string) => { - const trxHolder = await initMainnetUser("0x3DdfA8eC3052539b6C9549F12cEA2C295cfF5296", parseEther("2")); - const usdtHolder = await initMainnetUser("0xF977814e90dA44bFA03b6295A0616a897441aceC", parseEther("2")); + const token2Holder = await initMainnetUser(TOKEN2_HOLDER, parseEther("2")); + const token1Holder = await initMainnetUser(USDT_HOLDER, parseEther("2")); - await trx.connect(trxHolder).transfer(userAddress, parseUnits("10000", 6)); - await usdt.connect(usdtHolder).transfer(userAddress, parseUnits("10000", 18)); + await token2.connect(token2Holder).transfer(userAddress, parseUnits("500", 18)); + await token1.connect(token1Holder).transfer(userAddress, parseUnits("500", usdtDecimals)); }; const setupRiskManagementContracts = async () => { - const swapRouter = await ethers.getContractAt( - "IPancakeswapV2Router", - SWAP_ROUTER_CORE_POOL, - ); - - const riskFundFactory = await ethers.getContractFactory("RiskFund"); - riskFund = (await upgrades.deployProxy(riskFundFactory, [ - swapRouter.address, - parseUnits("1", 18), - TRX, // convertibleBaseAsset - ACM, - LOOPS_LIMIT, - ])) as RiskFund; - await riskFund.setPoolRegistry(POOL_REGISTRY); - - const protocolShareReserveFactory = await ethers.getContractFactory( - "ProtocolShareReserve", - ); - protocolShareReserve = (await upgrades.deployProxy(protocolShareReserveFactory, [ - TREASURY, - riskFund.address, - ])) as ProtocolShareReserve; - await protocolShareReserve.setPoolRegistry(POOL_REGISTRY); - - const shortfallFactory = await ethers.getContractFactory("Shortfall"); - shortfall = (await upgrades.deployProxy(shortfallFactory, [ - TRX, // convertibleBaseAsset - riskFund.address, - MINIMUM_POOL_BAD_DEBT, - ACM, - ])) as Shortfall; - await shortfall.updatePoolRegistry(POOL_REGISTRY); + riskFund = RiskFund__factory.connect(RISKFUND, impersonatedTimelock); + protocolShareReserve = ProtocolShareReserve__factory.connect(PSR, impersonatedTimelock); + + shortfall = Shortfall__factory.connect(SHORTFALL, impersonatedTimelock); + await shortfall.updateMinimumPoolBadDebt(parseUnits("50", 18)); // -------------------------------------------- }; const setupTokens = async () => { - trx = await ethers.getContractAt("MockToken", TRX); - usdt = await ethers.getContractAt("MockToken", USDT); + token2 = await ethers.getContractAt("MockToken", TOKEN2); + token1 = await ethers.getContractAt("MockToken", USDT); + usdtDecimals = await token1.decimals(); await grabTokensTo(manager.address); await grabTokensTo(user1.address); await grabTokensTo(liquidator.address); - comptroller = Comptroller__factory.connect(COMPTROLLER_TRON, impersonatedTimelock); + comptroller = Comptroller__factory.connect(COMPTROLLER, impersonatedTimelock); chainlinkOracle = ChainlinkOracle__factory.connect(CHAINLINK_ORACLE, impersonatedTimelock); + resilientOracle = MockPriceOracle__factory.connect(RESILIENT_ORACLE, impersonatedTimelock); + + let tokenConfig = { + asset: token2.address, + oracles: [chainlinkOracle.address, ethers.constants.AddressZero, ethers.constants.AddressZero], + enableFlagsForOracles: [true, false, false], + }; + await resilientOracle.connect(impersonatedTimelock).setTokenConfig(tokenConfig); + + tokenConfig = { + asset: token1.address, + oracles: [chainlinkOracle.address, ethers.constants.AddressZero, ethers.constants.AddressZero], + enableFlagsForOracles: [true, false, false], + }; + await resilientOracle.connect(impersonatedTimelock).setTokenConfig(tokenConfig); + + vTOKEN2 = await ethers.getContractAt("VToken", VTOKEN2); + vTOKEN1 = await ethers.getContractAt("VToken", VUSDT); +}; - vTRX = await ethers.getContractAt("VToken", VTRX_TRON); - vUSDT = await ethers.getContractAt("VToken", VUSDT_TRON); +const generateToken1BadDebt = async () => { + const token2SupplyAmount = parseUnits("500", 18); + const token1BorrowAmount = parseUnits("100", usdtDecimals); - for (const vToken of [vTRX, vUSDT]) { - await vToken.connect(impersonatedTimelock).setShortfallContract(shortfall.address); - await vToken.connect(impersonatedTimelock).setProtocolShareReserve(protocolShareReserve.address); - } -}; + await token1.connect(manager).approve(vTOKEN1.address, token1BorrowAmount); + await vTOKEN1.connect(manager).mint(token1BorrowAmount); + + await token2.connect(user1).approve(vTOKEN2.address, token2SupplyAmount); + await vTOKEN2.connect(user1).mint(token2SupplyAmount); + + await comptroller.connect(user1).enterMarkets([vTOKEN2.address]); + await vTOKEN1.connect(user1).borrow(token1BorrowAmount); -const generateUsdtBadDebt = async () => { - await chainlinkOracle.setDirectPrice(trx.address, parseUnits("100", 18)); - const trxSupplyAmount = parseUnits("100", 6); - const usdtBorrowAmount = parseUnits("500", 18); - await usdt.connect(manager).approve(vUSDT.address, usdtBorrowAmount); - await vUSDT.connect(manager).mint(usdtBorrowAmount); - await trx.connect(user1).approve(vTRX.address, trxSupplyAmount); - await vTRX.connect(user1).mint(trxSupplyAmount); - await comptroller.connect(user1).enterMarkets([vTRX.address]); - await vUSDT.connect(user1).borrow(usdtBorrowAmount); - await chainlinkOracle.setDirectPrice(trx.address, "1"); - - // We repay about 91 USDT but the majority of the debt is bad debt - await usdt.connect(liquidator).approve(vUSDT.address, parseUnits("91.1", 18)); + await chainlinkOracle.setDirectPrice(token2.address, "1"); + + await token1.connect(liquidator).approve(vTOKEN1.address, parseUnits("100", usdtDecimals)); await comptroller.connect(liquidator).healAccount(user1.address); - // Restore original TRX price - await chainlinkOracle.setDirectPrice(trx.address, parseUnits("0.08098989", 18)); + // Restoring original price + await chainlinkOracle.setDirectPrice(token2.address, parseUnits("100", 18)); - // Pretend to recover the dust so that the bad debt is exactly 500 USDT const shortfallSigner = await initMainnetUser(shortfall.address, parseEther("1")); - const dust = (await vUSDT.badDebt()).sub(parseUnits("500", 18)); - await vUSDT.connect(shortfallSigner).badDebtRecovered(dust); + const dust = (await vTOKEN1.badDebt()).sub(parseUnits("100", usdtDecimals)); + await vTOKEN1.connect(shortfallSigner).badDebtRecovered(dust); }; -const pretendRiskFundAccumulatedBaseAsset = async () => { - const trxReserve = parseUnits("1000", 6); - await trx.approve(vTRX.address, trxReserve); - await vTRX.addReserves(trxReserve); - await vTRX.reduceReserves(trxReserve); - await protocolShareReserve.releaseFunds(comptroller.address, trx.address, trxReserve); +const pretendRiskFundAccumulatedBaseAsset = async amount => { + const token1Reserve = amount; + await token1.approve(vTOKEN1.address, token1Reserve); + + await vTOKEN1.addReserves(token1Reserve); + await vTOKEN1.reduceReserves(token1Reserve); + + await protocolShareReserve.releaseFunds(comptroller.address, [token1.address]); }; const setup = async () => { [manager, user1, liquidator] = await ethers.getSigners(); - await setForkBlock(30245720); - - comptroller = await ethers.getContractAt("Comptroller", COMPTROLLER_TRON); - chainlinkOracle = await ethers.getContractAt("ChainlinkOracle", CHAINLINK_ORACLE); - oracle = await ethers.getContractAt("ResilientOracleInterface", ORACLE); + await setForkBlock(BLOCK_NUMBER); await configureTimelock(); await setupRiskManagementContracts(); await setupTokens(); - await generateUsdtBadDebt(); - await pretendRiskFundAccumulatedBaseAsset(); + oldPoolAsetReserves = await riskFund.getPoolsBaseAssetReserves(COMPTROLLER); + + await chainlinkOracle.setDirectPrice(token2.address, parseUnits("100", 18)); + await chainlinkOracle.setDirectPrice(token1.address, parseUnits("100", 18)); + + await generateToken1BadDebt(); + await pretendRiskFundAccumulatedBaseAsset(parseUnits("100", usdtDecimals)); }; enum AuctionType { @@ -190,61 +176,64 @@ enum AuctionType { LARGE_RISK_FUND = 1, } -if (FORK_MAINNET) { +if (FORK && (FORKED_NETWORK === "bscmainnet" || FORKED_NETWORK === "bsctestnet")) { describe("Shortfall fork tests", async () => { - const usdtBadDebt = parseUnits("500", 18); - const trxRiskFund = parseUnits("500", 6); - beforeEach(async () => { await loadFixture(setup); }); it("initializes as expected", async () => { - expect(await vUSDT.badDebt()).to.equal(usdtBadDebt); - expect(await vTRX.badDebt()).to.equal(0); - expect(await riskFund.getPoolsBaseAssetReserves(COMPTROLLER_TRON)).to.equal(trxRiskFund); + const newPoolAssetReserves = await riskFund.getPoolsBaseAssetReserves(COMPTROLLER); + + expect(await vTOKEN1.badDebt()).to.equal(parseUnits("100", usdtDecimals)); + expect(await vTOKEN2.badDebt()).to.equal(0); + expect(newPoolAssetReserves.sub(oldPoolAsetReserves)).to.closeTo( + parseUnits("40", usdtDecimals), + parseUnits("5", usdtDecimals), + ); }); describe("startAuction", async () => { it("starts a LARGE_POOL_DEBT auction if risk fund reserve ($) < bad debt plus incentive ($)", async () => { - // Convertible base asset is TRX, the risk fund balance is about 500 TRX or $40 - await shortfall.startAuction(COMPTROLLER_TRON); + await shortfall.startAuction(COMPTROLLER); - const auction = await shortfall.auctions(COMPTROLLER_TRON); + const auction = await shortfall.auctions(COMPTROLLER); expect(auction.auctionType).to.equal(AuctionType.LARGE_POOL_DEBT); }); it("starts a LARGE_RISK_FUND auction if risk fund reserve ($) > bad debt plus incentive ($)", async () => { - // Convertible base asset is TRX, we set its price to $1.11 so that 500 TRX = $555 > bad debt plus incentive - await chainlinkOracle.setDirectPrice(trx.address, parseUnits("1.11", 18)); - await shortfall.startAuction(COMPTROLLER_TRON); + await pretendRiskFundAccumulatedBaseAsset(parseUnits("200", usdtDecimals)); - const auction = await shortfall.auctions(COMPTROLLER_TRON); + await shortfall.startAuction(COMPTROLLER); + + const auction = await shortfall.auctions(COMPTROLLER); expect(auction.auctionType).to.equal(AuctionType.LARGE_RISK_FUND); }); it("starts a LARGE_POOL_DEBT auction if risk fund reserve ($) == bad debt plus incentive ($)", async () => { - // Convertible base asset is TRX, we set its price to $1.1 so that 500 TRX = $550 - await chainlinkOracle.setDirectPrice(trx.address, parseUnits("1.1", 18)); - // We set USDT price to $1 so that bad debt plus incentive is exactly $550 - await chainlinkOracle.setDirectPrice(usdt.address, parseUnits("1.0", 18)); + await chainlinkOracle.setDirectPrice(token1.address, parseUnits("1.1", 18)); + await chainlinkOracle.setDirectPrice(token2.address, parseUnits("1.0", 18)); // Ensure our computations are correct - const usdtPrice = await oracle.getPrice(usdt.address); - const badDebtPlusIncentive = (await vUSDT.badDebt()) + const token1Price = await resilientOracle.getPrice(token1.address); + const badDebtPlusIncentive = (await vTOKEN1.badDebt()) .mul(parseUnits("1.1", 18)) - .mul(usdtPrice) - .div(parseUnits("1", 36)); - expect(badDebtPlusIncentive).to.equal(parseUnits("550", 18)); + .mul(token1Price) + .div(parseUnits("1", 18 + 36 - usdtDecimals)); + expect(badDebtPlusIncentive).to.equal(parseUnits("121", usdtDecimals)); + + const token2Price = await resilientOracle.getPrice(token2.address); + const riskFundReserve = await riskFund.getPoolsBaseAssetReserves(COMPTROLLER); + const riskFundReserveInUsd = riskFundReserve.mul(token2Price).div(parseUnits("1", 18)); - const trxPrice = await oracle.getPrice(trx.address); - const riskFundReserve = await riskFund.getPoolsBaseAssetReserves(COMPTROLLER_TRON); - const riskFundReserveInUsd = riskFundReserve.mul(trxPrice).div(parseUnits("1", 18)); - expect(riskFundReserveInUsd).to.equal(parseUnits("550", 18)); + expect(riskFundReserveInUsd.sub(oldPoolAsetReserves)).to.closeTo( + parseUnits("40", usdtDecimals), + parseUnits("5", usdtDecimals), + ); - await shortfall.startAuction(COMPTROLLER_TRON); + await shortfall.startAuction(COMPTROLLER); - const auction = await shortfall.auctions(COMPTROLLER_TRON); + const auction = await shortfall.auctions(COMPTROLLER); expect(auction.auctionType).to.equal(AuctionType.LARGE_POOL_DEBT); }); }); diff --git a/tests/hardhat/Fork/borrowAndRepayTest.ts b/tests/hardhat/Fork/borrowAndRepayTest.ts index d307eb1ca..69f791b02 100644 --- a/tests/hardhat/Fork/borrowAndRepayTest.ts +++ b/tests/hardhat/Fork/borrowAndRepayTest.ts @@ -1,78 +1,65 @@ import { smock } from "@defi-wonderland/smock"; import { mine } from "@nomicfoundation/hardhat-network-helpers"; import chai from "chai"; -import { BigNumberish, Signer } from "ethers"; +import { BigNumber, BigNumberish, Signer } from "ethers"; import { ethers } from "hardhat"; import { convertToUnit } from "../../../helpers/utils"; import { - AccessControlManager, - AccessControlManager__factory, BinanceOracle, BinanceOracle__factory, + ChainlinkOracle__factory, Comptroller, Comptroller__factory, - MockToken, - MockToken__factory, + IERC20, + IERC20__factory, ResilientOracleInterface, ResilientOracleInterface__factory, VToken, VToken__factory, + WrappedNative, + WrappedNative__factory, } from "../../../typechain"; -import { initMainnetUser, setForkBlock } from "./utils"; +import { getContractAddresses, initMainnetUser, setForkBlock } from "./utils"; const { expect } = chai; chai.use(smock.matchers); -const FORK_TESTNET = process.env.FORK === "true" && process.env.FORKED_NETWORK === "bsctestnet"; -const FORK_MAINNET = process.env.FORK === "true" && process.env.FORKED_NETWORK === "bscmainnet"; - -let ADMIN: string; -let ACM: string; -let acc1: string; -let acc2: string; -let acc3: string; -let USDD: string; -let HAY: string; -let COMPTROLLER: string; -let VUSDD: string; -let VHAY: string; -let BLOCK_NUMBER: number; -let BINANCE_ORACLE: string; - -if (FORK_TESTNET) { - ADMIN = "0xce10739590001705F7FF231611ba4A48B2820327"; - ACM = "0x45f8a08F534f34A97187626E05d4b6648Eeaa9AA"; - acc1 = "0xe70898180a366F204AA529708fB8f5052ea5723c"; - acc2 = "0xA4a04C2D661bB514bB8B478CaCB61145894563ef"; - acc3 = "0x394d1d517e8269596a7E4Cd1DdaC1C928B3bD8b3"; - USDD = "0x2E2466e22FcbE0732Be385ee2FBb9C59a1098382"; - HAY = "0xe73774DfCD551BF75650772dC2cC56a2B6323453"; - COMPTROLLER = "0x10b57706AD2345e590c2eA4DC02faef0d9f5b08B"; - VUSDD = "0x899dDf81DfbbF5889a16D075c352F2b959Dd24A4"; - VHAY = "0x170d3b2da05cc2124334240fB34ad1359e34C562"; - BINANCE_ORACLE = "0xB58BFDCE610042311Dc0e034a80Cc7776c1D68f5"; - BLOCK_NUMBER = 30912551; -} - -if (FORK_MAINNET) { - // Mainnet addresses -} - -let impersonatedTimelock: Signer; -let accessControlManager: AccessControlManager; +const FORK = process.env.FORK === "true"; +const FORKED_NETWORK = process.env.FORKED_NETWORK || "bscmainnet"; + +if (FORK) console.log(`fork tests are running on: ${FORKED_NETWORK}`); + +const { + ACC1, + ACC2, + ADMIN, + TOKEN1, + TOKEN2, + VTOKEN1, + VTOKEN2, + COMPTROLLER, + BINANCE_ORACLE, + CHAINLINK_ORACLE, + TOKEN1_HOLDER, + TOKEN2_HOLDER, + BLOCK_NUMBER, +} = getContractAddresses(FORKED_NETWORK as string); + +let token1: IERC20 | WrappedNative; +let token2: IERC20; +let vTOKEN1: VToken; +let vTOKEN2: VToken; let comptroller: Comptroller; -let vUSDD: VToken; -let vHAY: VToken; -let usdd: MockToken; -let hay: MockToken; -let priceOracle: ResilientOracleInterface; let acc1Signer: Signer; let acc2Signer: Signer; -let acc3Signer: Signer; -let mintAmount: BigNumberish; -let hayBorrowAmount: BigNumberish; +let token2Holder: Signer; +let token1Holder: Signer; +let impersonatedTimelock: Signer; +let mintAmount: BigNumber; +let TOKEN2BorrowAmount: BigNumberish; let binanceOracle: BinanceOracle; +let priceOracle: ResilientOracleInterface; async function configureTimelock() { impersonatedTimelock = await initMainnetUser(ADMIN, ethers.utils.parseUnits("2")); @@ -82,207 +69,247 @@ async function configureVToken(vTokenAddress: string) { return VToken__factory.connect(vTokenAddress, impersonatedTimelock); } -async function grantPermissions() { - accessControlManager = AccessControlManager__factory.connect(ACM, impersonatedTimelock); - - let tx = await accessControlManager - .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setMarketSupplyCaps(address[],uint256[])", ADMIN); - await tx.wait(); - - tx = await accessControlManager - .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setMarketBorrowCaps(address[],uint256[])", ADMIN); - await tx.wait(); -} - -if (FORK_TESTNET || FORK_MAINNET) { +if (FORK) { describe("Borrow and Repay", async () => { - mintAmount = convertToUnit("1", 21); - hayBorrowAmount = convertToUnit("3", 20); + mintAmount = BigNumber.from(convertToUnit(1, 21)); + TOKEN2BorrowAmount = convertToUnit("3", 20); async function setup() { await setForkBlock(BLOCK_NUMBER); - await configureTimelock(); - acc1Signer = await initMainnetUser(acc1, ethers.utils.parseUnits("2")); - acc2Signer = await initMainnetUser(acc2, ethers.utils.parseUnits("2")); - acc3Signer = await initMainnetUser(acc3, ethers.utils.parseUnits("2")); - - hay = MockToken__factory.connect(HAY, impersonatedTimelock); - usdd = MockToken__factory.connect(USDD, impersonatedTimelock); - vHAY = await configureVToken(VHAY); - vUSDD = await configureVToken(VUSDD); + await configureTimelock(); + acc1Signer = await initMainnetUser(ACC1, ethers.utils.parseUnits("2")); + acc2Signer = await initMainnetUser(ACC2, ethers.utils.parseUnits("2")); + // it will be the depositor + token2Holder = await initMainnetUser(TOKEN2_HOLDER, ethers.utils.parseUnits("2")); + token1Holder = await initMainnetUser(TOKEN1_HOLDER, ethers.utils.parseUnits("2000000")); + + if (FORKED_NETWORK == "bscmainnet") { + binanceOracle = BinanceOracle__factory.connect(BINANCE_ORACLE, impersonatedTimelock); + await binanceOracle.setMaxStalePeriod("lisUSD", BigInt(150000000000000000)); + await binanceOracle.setMaxStalePeriod("USDD", BigInt(150000000000000000)); + } + + if (FORKED_NETWORK == "ethereum" || FORKED_NETWORK == "arbitrumsepolia" || FORKED_NETWORK == "arbitrumone") { + const ChainlinkOracle = ChainlinkOracle__factory.connect(CHAINLINK_ORACLE, impersonatedTimelock); + const token1Config = await ChainlinkOracle.tokenConfigs(TOKEN1); + const token2Config = await ChainlinkOracle.tokenConfigs(TOKEN2); + + const token1NewConfig = { + asset: token1Config.asset, + feed: token1Config.feed, + maxStalePeriod: BigNumber.from(1000000000), + }; + const token2NewConfig = { + asset: token2Config.asset, + feed: token2Config.feed, + maxStalePeriod: BigNumber.from(1000000000), + }; + await ChainlinkOracle.setTokenConfig(token1NewConfig); + await ChainlinkOracle.setTokenConfig(token2NewConfig); + } + + token1 = IERC20__factory.connect(TOKEN1, impersonatedTimelock); + token2 = IERC20__factory.connect(TOKEN2, impersonatedTimelock); + if (FORKED_NETWORK == "arbitrumsepolia" || FORKED_NETWORK == "arbitrumone") { + token1 = WrappedNative__factory.connect(TOKEN1, impersonatedTimelock); + await token1.connect(token1Holder).deposit({ value: convertToUnit("200000", 18) }); + } + + vTOKEN2 = await configureVToken(VTOKEN2); + vTOKEN1 = await configureVToken(VTOKEN1); comptroller = Comptroller__factory.connect(COMPTROLLER, impersonatedTimelock); + const oracle = await comptroller.oracle(); priceOracle = ResilientOracleInterface__factory.connect(oracle, impersonatedTimelock); - await grantPermissions(); - - await comptroller.connect(acc1Signer).enterMarkets([vUSDD.address]); - await comptroller.connect(acc2Signer).enterMarkets([vUSDD.address]); - await comptroller.connect(acc3Signer).enterMarkets([vHAY.address]); + await comptroller.connect(acc1Signer).enterMarkets([vTOKEN1.address]); + await comptroller.connect(acc2Signer).enterMarkets([vTOKEN1.address]); await comptroller.setMarketSupplyCaps( - [vHAY.address, vUSDD.address], + [vTOKEN2.address, vTOKEN1.address], [convertToUnit(1, 50), convertToUnit(1, 50)], ); await comptroller.setMarketBorrowCaps( - [vHAY.address, vUSDD.address], + [vTOKEN2.address, vTOKEN1.address], [convertToUnit(1, 50), convertToUnit(1, 50)], ); - - binanceOracle = BinanceOracle__factory.connect(BINANCE_ORACLE, impersonatedTimelock); - await binanceOracle.connect(impersonatedTimelock).setMaxStalePeriod("HAY", 31536000); } beforeEach(async () => { await setup(); - // Allocate reserves to market from acc3 to the hay market - await hay.connect(acc3Signer).faucet(convertToUnit(100000000000, 18)); - await hay.connect(acc3Signer).approve(vHAY.address, convertToUnit(100000000000, 18)); - await expect(vHAY.connect(acc3Signer).mint(convertToUnit(100000000000, 18))).to.emit(vHAY, "Mint"); + // Allocate reserves to market from ACC3 to the TOKEN2 market + await token2.connect(token2Holder).approve(vTOKEN2.address, convertToUnit(10000, 18)); + await expect(vTOKEN2.connect(token2Holder).mint(convertToUnit(10000, 18))).to.emit(vTOKEN2, "Mint"); - // Increase collateral for acc1 - await usdd.connect(acc1Signer).faucet(mintAmount); - await usdd.connect(acc1Signer).approve(vUSDD.address, mintAmount); - await expect(vUSDD.connect(acc1Signer).mint(mintAmount)).to.emit(vUSDD, "Mint"); + // Increase collateral for ACC + await token1.connect(token1Holder).transfer(ACC1, mintAmount); + await token1.connect(acc1Signer).approve(vTOKEN1.address, mintAmount); - // Increase collateral for acc2 - await usdd.connect(acc2Signer).faucet(mintAmount); - await usdd.connect(acc2Signer).approve(vUSDD.address, mintAmount); - await expect(vUSDD.connect(acc2Signer).mint(mintAmount)).to.emit(vUSDD, "Mint"); + // Increase collateral for ACC2 + await token1.connect(token1Holder).transfer(ACC2, mintAmount); + await token1.connect(acc2Signer).approve(vTOKEN1.address, mintAmount); }); it("Total Borrow Balance with Two Borrowers", async function () { // common factors - const vUSDDCollateralFactor = await comptroller.markets(VUSDD); - const exchangeRateCollateral = await vUSDD.exchangeRateStored(); - const USDDPrice = await priceOracle.getUnderlyingPrice(VUSDD); - const HAYPrice = await priceOracle.getUnderlyingPrice(VHAY); - const vTokenPrice = exchangeRateCollateral.mul(USDDPrice).div(convertToUnit(1, 18)); - const weighhtedPriceUsdd = vTokenPrice - .mul(vUSDDCollateralFactor.collateralFactorMantissa) + const vTOKEN1CollateralFactor = await comptroller.markets(VTOKEN1); + + await expect(vTOKEN1.connect(acc1Signer).mint(mintAmount)).to.emit(vTOKEN1, "Mint"); + + let exchangeRateCollateral = await vTOKEN1.exchangeRateStored(); + + let TOKEN1Price = await priceOracle.getUnderlyingPrice(VTOKEN1); + let TOKEN2Price = await priceOracle.getUnderlyingPrice(VTOKEN2); + + let vTokenPrice = exchangeRateCollateral.mul(TOKEN1Price).div(convertToUnit(1, 18)); + + let weightedPriceTOKEN1 = vTokenPrice + .mul(vTOKEN1CollateralFactor.collateralFactorMantissa) .div(convertToUnit(1, 18)); - const expectedMintAmount = (mintAmount * convertToUnit(1, 18)) / exchangeRateCollateral; - // Acc1 pre borrow checks - let expectedLiquidityAcc1 = weighhtedPriceUsdd.mul(expectedMintAmount).div(convertToUnit(1, 18)); - let [err, liquidity, shortfall] = await comptroller.getBorrowingPower(acc1); + let expectedMintAmount = mintAmount.mul(convertToUnit(1, 18)).div(exchangeRateCollateral); - expect(expectedMintAmount).equals(await vUSDD.balanceOf(acc1)); + // ACC1 pre borrow checks + let expectedLiquidityAcc1 = weightedPriceTOKEN1.mul(expectedMintAmount).div(convertToUnit(1, 18)); + let [err, liquidity, shortfall] = await comptroller.getBorrowingPower(ACC1); + expect(expectedMintAmount).equals(await vTOKEN1.balanceOf(ACC1)); expect(err).equals(0); expect(liquidity).equals(expectedLiquidityAcc1); expect(shortfall).equals(0); // Acc2 pre borrow checks - let expectedLiquidityAcc2 = weighhtedPriceUsdd.mul(expectedMintAmount).div(convertToUnit(1, 18)); - [err, liquidity, shortfall] = await comptroller.getBorrowingPower(acc2); + await expect(vTOKEN1.connect(acc2Signer).mint(mintAmount)).to.emit(vTOKEN1, "Mint"); + exchangeRateCollateral = await vTOKEN1.exchangeRateStored(); + + TOKEN1Price = await priceOracle.getUnderlyingPrice(VTOKEN1); + + TOKEN2Price = await priceOracle.getUnderlyingPrice(VTOKEN2); - expect(expectedMintAmount).equals(await vUSDD.balanceOf(acc2)); + vTokenPrice = exchangeRateCollateral.mul(TOKEN1Price).div(convertToUnit(1, 18)); + + weightedPriceTOKEN1 = vTokenPrice.mul(vTOKEN1CollateralFactor.collateralFactorMantissa).div(convertToUnit(1, 18)); + expectedMintAmount = mintAmount.mul(convertToUnit(1, 18)).div(await vTOKEN1.exchangeRateStored()); + + expectedLiquidityAcc1 = weightedPriceTOKEN1.mul(await vTOKEN1.balanceOf(ACC1)).div(convertToUnit(1, 18)); + + [err, liquidity, shortfall] = await comptroller.getBorrowingPower(ACC2); + + let expectedLiquidityAcc2 = weightedPriceTOKEN1.mul(expectedMintAmount).div(convertToUnit(1, 18)); + [err, liquidity, shortfall] = await comptroller.getBorrowingPower(ACC2); + expect(expectedMintAmount).equals(await vTOKEN1.balanceOf(ACC2)); expect(err).equals(0); expect(liquidity).equals(expectedLiquidityAcc2); expect(shortfall).equals(0); // *************************Borrow Acc1**************************************************/ - await expect(vHAY.connect(acc1Signer).borrow(hayBorrowAmount)).to.be.emit(vHAY, "Borrow"); - const borrowIndexAcc1Prev = await vHAY.borrowIndex(); + + [err, liquidity, shortfall] = await comptroller.getBorrowingPower(ACC1); + expect(liquidity).equals(expectedLiquidityAcc1); + + await expect(vTOKEN2.connect(acc1Signer).borrow(TOKEN2BorrowAmount)).to.be.emit(vTOKEN2, "Borrow"); + [err, liquidity, shortfall] = await comptroller.getBorrowingPower(ACC1); + const borrowIndexAcc1Prev = await vTOKEN2.borrowIndex(); // Acc1 post borrow checks - expect(hayBorrowAmount).equals(await vHAY.borrowBalanceStored(acc1)); - expectedLiquidityAcc1 = expectedLiquidityAcc1.sub(HAYPrice.mul(hayBorrowAmount).div(convertToUnit(1, 18))); - [err, liquidity, shortfall] = await comptroller.getBorrowingPower(acc1); + expect(TOKEN2BorrowAmount).equals(await vTOKEN2.borrowBalanceStored(ACC1)); + expectedLiquidityAcc1 = expectedLiquidityAcc1.sub(TOKEN2Price.mul(TOKEN2BorrowAmount).div(convertToUnit(1, 18))); + [err, liquidity, shortfall] = await comptroller.getBorrowingPower(ACC1); expect(err).equals(0); - expect(liquidity).equals(expectedLiquidityAcc1); + expect(liquidity).equals(expectedLiquidityAcc1); // ************************************ expect(shortfall).equals(0); - // ********************************Mine 300000 blocks***********************************/ - await mine(300000); - await vHAY.accrueInterest(); - let borrowIndexCurrent = await vHAY.borrowIndex(); + // ********************************Mine 30000 blocks***********************************/ + await mine(30000); + await vTOKEN2.accrueInterest(); + let borrowIndexCurrent = await vTOKEN2.borrowIndex(); // Change borrow balance of acc1 - let borrowBalanceStored = await vHAY.borrowBalanceStored(acc1); - expect(borrowIndexCurrent.mul(hayBorrowAmount).div(borrowIndexAcc1Prev)).equals(borrowBalanceStored); + let borrowBalanceStored = await vTOKEN2.borrowBalanceStored(ACC1); + expect(borrowIndexCurrent.mul(TOKEN2BorrowAmount).div(borrowIndexAcc1Prev)).equals(borrowBalanceStored); // *************************Borrow Acc2**************************************************/ - await expect(vHAY.connect(acc2Signer).borrow(hayBorrowAmount)).to.be.emit(vHAY, "Borrow"); - const borrowIndexAcc2Prev = await vHAY.borrowIndex(); + await expect(vTOKEN2.connect(acc2Signer).borrow(TOKEN2BorrowAmount)).to.be.emit(vTOKEN2, "Borrow"); + const borrowIndexAcc2Prev = await vTOKEN2.borrowIndex(); // Acc2 post borrow checks - expect(hayBorrowAmount).equals(await vHAY.borrowBalanceStored(acc2)); - expectedLiquidityAcc2 = expectedLiquidityAcc2.sub(HAYPrice.mul(hayBorrowAmount).div(convertToUnit(1, 18))); - [err, liquidity, shortfall] = await comptroller.getBorrowingPower(acc2); + expect(TOKEN2BorrowAmount).equals(await vTOKEN2.borrowBalanceStored(ACC2)); + expectedLiquidityAcc2 = expectedLiquidityAcc2.sub(TOKEN2Price.mul(TOKEN2BorrowAmount).div(convertToUnit(1, 18))); + [err, liquidity, shortfall] = await comptroller.getBorrowingPower(ACC2); expect(err).equals(0); expect(liquidity).equals(expectedLiquidityAcc2); expect(shortfall).equals(0); // ********************************Mine 300000 blocks***********************************/ await mine(300000); - await vHAY.accrueInterest(); - borrowIndexCurrent = await vHAY.borrowIndex(); + await vTOKEN2.accrueInterest(); + borrowIndexCurrent = await vTOKEN2.borrowIndex(); - // Change borrow balance of acc1 - borrowBalanceStored = await vHAY.borrowBalanceStored(acc1); - expect(borrowIndexCurrent.mul(hayBorrowAmount).div(borrowIndexAcc1Prev)).equals(borrowBalanceStored); + // Change borrow balance of ACC1 + borrowBalanceStored = await vTOKEN2.borrowBalanceStored(ACC1); + expect(borrowIndexCurrent.mul(TOKEN2BorrowAmount).div(borrowIndexAcc1Prev)).equals(borrowBalanceStored); - // Change borrow balance of acc2 - borrowBalanceStored = await vHAY.borrowBalanceStored(acc2); - expect(borrowIndexCurrent.mul(hayBorrowAmount).div(borrowIndexAcc2Prev)).equals(borrowBalanceStored); + // Change borrow balance of ACC2 + borrowBalanceStored = await vTOKEN2.borrowBalanceStored(ACC2); + expect(borrowIndexCurrent.mul(TOKEN2BorrowAmount).div(borrowIndexAcc2Prev)).equals(borrowBalanceStored); - // *************************Repay Acc2**************************************************/ + // *************************Repay ACC2**************************************************/ // Allocate some funds to repay debt - await vHAY.accrueInterest(); - borrowBalanceStored = await vHAY.borrowBalanceStored(acc2); - await hay.connect(acc2Signer).faucet(borrowBalanceStored.add(convertToUnit(1, 20))); - await hay.connect(acc2Signer).approve(vHAY.address, borrowBalanceStored.add(convertToUnit(1, 20))); - await vHAY.connect(acc2Signer).repayBorrow(borrowBalanceStored.add(convertToUnit(1, 20))); + await vTOKEN2.accrueInterest(); + borrowBalanceStored = await vTOKEN2.borrowBalanceStored(ACC2); + + await token2.connect(token2Holder).transfer(ACC2, borrowBalanceStored.add(convertToUnit(1, 20))); + await token2.connect(acc2Signer).approve(vTOKEN2.address, borrowBalanceStored.add(convertToUnit(1, 20))); + await vTOKEN2.connect(acc2Signer).repayBorrow(borrowBalanceStored.add(convertToUnit(1, 20))); // Full debt repaid acc2 - borrowBalanceStored = await vHAY.borrowBalanceStored(acc2); + borrowBalanceStored = await vTOKEN2.borrowBalanceStored(ACC2); expect(borrowBalanceStored).equals(0); // acc1 balance checks - await vHAY.accrueInterest(); - borrowIndexCurrent = await vHAY.borrowIndex(); - borrowBalanceStored = await vHAY.borrowBalanceStored(acc1); - expect(borrowIndexCurrent.mul(hayBorrowAmount).div(borrowIndexAcc1Prev)).equals(borrowBalanceStored); + await vTOKEN2.accrueInterest(); + borrowIndexCurrent = await vTOKEN2.borrowIndex(); + borrowBalanceStored = await vTOKEN2.borrowBalanceStored(ACC1); + expect(borrowIndexCurrent.mul(TOKEN2BorrowAmount).div(borrowIndexAcc1Prev)).equals(borrowBalanceStored); }); it("Attempt to borrow over set cap", async function () { - const vUSDDCollateralFactor = await comptroller.markets(VUSDD); - const exchangeRateCollateral = await vUSDD.exchangeRateStored(); - const USDDPrice = await priceOracle.getUnderlyingPrice(VUSDD); - const HAYPrice = await priceOracle.getUnderlyingPrice(VHAY); - const vTokenPrice = exchangeRateCollateral.mul(USDDPrice).div(convertToUnit(1, 18)); - const weighhtedPriceUsdd = vTokenPrice - .mul(vUSDDCollateralFactor.collateralFactorMantissa) + const vTOKEN1CollateralFactor = await comptroller.markets(VTOKEN1); + await expect(vTOKEN1.connect(acc1Signer).mint(mintAmount)).to.emit(vTOKEN1, "Mint"); + + const exchangeRateCollateral = await vTOKEN1.exchangeRateStored(); + const TOKEN1Price = await priceOracle.getUnderlyingPrice(VTOKEN1); + const TOKEN2Price = await priceOracle.getUnderlyingPrice(VTOKEN2); + const vTokenPrice = exchangeRateCollateral.mul(TOKEN1Price).div(convertToUnit(1, 18)); + const weightedPriceTOKEN1 = vTokenPrice + .mul(vTOKEN1CollateralFactor.collateralFactorMantissa) .div(convertToUnit(1, 18)); - const expectedMintAmount = (mintAmount * convertToUnit(1, 18)) / exchangeRateCollateral; + + const expectedMintAmount = mintAmount.mul(convertToUnit(1, 18)).div(await vTOKEN1.exchangeRateStored()); // checks - let expectedLiquidityAcc1 = weighhtedPriceUsdd.mul(expectedMintAmount).div(convertToUnit(1, 18)); - let [err, liquidity, shortfall] = await comptroller.getBorrowingPower(acc1); + let expectedLiquidityAcc1 = weightedPriceTOKEN1.mul(await vTOKEN1.balanceOf(ACC1)).div(convertToUnit(1, 18)); + let [err, liquidity, shortfall] = await comptroller.getBorrowingPower(ACC1); - expect(expectedMintAmount).equals(await vUSDD.balanceOf(acc1)); + expect(expectedMintAmount).equals(await vTOKEN1.balanceOf(ACC1)); expect(err).equals(0); expect(liquidity).equals(expectedLiquidityAcc1); expect(shortfall).equals(0); // *************************Borrow**************************************************/ - await expect(vHAY.connect(acc1Signer).borrow(hayBorrowAmount)).to.be.emit(vHAY, "Borrow"); - expect(hayBorrowAmount).equals(await vHAY.borrowBalanceStored(acc1)); + await expect(vTOKEN2.connect(acc1Signer).borrow(TOKEN2BorrowAmount)).to.be.emit(vTOKEN2, "Borrow"); + expect(TOKEN2BorrowAmount).equals(await vTOKEN2.borrowBalanceStored(ACC1)); - expectedLiquidityAcc1 = expectedLiquidityAcc1.sub(HAYPrice.mul(hayBorrowAmount).div(convertToUnit(1, 18))); - [err, liquidity, shortfall] = await comptroller.getBorrowingPower(acc1); + expectedLiquidityAcc1 = expectedLiquidityAcc1.sub(TOKEN2Price.mul(TOKEN2BorrowAmount).div(convertToUnit(1, 18))); + [err, liquidity, shortfall] = await comptroller.getBorrowingPower(ACC1); expect(err).equals(0); - expect(liquidity).equals(expectedLiquidityAcc1); + expect(liquidity).to.be.closeTo(BigNumber.from(expectedLiquidityAcc1), 325002723328); expect(shortfall).equals(0); // **************************Set borrow caap zero***********************************/ - await comptroller.setMarketBorrowCaps([VHAY], [0]); - await expect(vHAY.connect(acc1Signer).borrow(hayBorrowAmount)).to.be.revertedWithCustomError( + await comptroller.setMarketBorrowCaps([VTOKEN2], [0]); + await expect(vTOKEN2.connect(acc1Signer).borrow(TOKEN2BorrowAmount)).to.be.revertedWithCustomError( comptroller, "BorrowCapExceeded", ); diff --git a/tests/hardhat/Fork/constants.ts b/tests/hardhat/Fork/constants.ts new file mode 100644 index 000000000..1c88e6133 --- /dev/null +++ b/tests/hardhat/Fork/constants.ts @@ -0,0 +1,206 @@ +import GovernanceArbOne from "@venusprotocol/governance-contracts/deployments/arbitrumone.json"; +import GovernanceArbSep from "@venusprotocol/governance-contracts/deployments/arbitrumsepolia.json"; +import GovernanceBscMainnet from "@venusprotocol/governance-contracts/deployments/bscmainnet.json"; +import GovernanceBscTestnet from "@venusprotocol/governance-contracts/deployments/bsctestnet.json"; +import GovernanceEthMainnet from "@venusprotocol/governance-contracts/deployments/ethereum.json"; +import GovernanceOpBnbMainnet from "@venusprotocol/governance-contracts/deployments/opbnbmainnet.json"; +import GovernanceOpBnbTestnet from "@venusprotocol/governance-contracts/deployments/opbnbtestnet.json"; +import GovernanceSepTestnet from "@venusprotocol/governance-contracts/deployments/sepolia.json"; +import OracleArbOne from "@venusprotocol/oracle/deployments/arbitrumone.json"; +import OracleArbSep from "@venusprotocol/oracle/deployments/arbitrumsepolia.json"; +import OracleBscMainnet from "@venusprotocol/oracle/deployments/bscmainnet.json"; +import OracleBscTestnet from "@venusprotocol/oracle/deployments/bsctestnet.json"; +import OracleEthMainnet from "@venusprotocol/oracle/deployments/ethereum.json"; +import OracleOpBnbMainnet from "@venusprotocol/oracle/deployments/opbnbmainnet.json"; +import OracleOpBnbTestnet from "@venusprotocol/oracle/deployments/opbnbtestnet.json"; +import OracleSepTestnet from "@venusprotocol/oracle/deployments/sepolia.json"; +import PsrArbOne from "@venusprotocol/protocol-reserve/deployments/arbitrumone.json"; +import PsrArbSep from "@venusprotocol/protocol-reserve/deployments/arbitrumsepolia.json"; +import PsrBscMainnet from "@venusprotocol/protocol-reserve/deployments/bscmainnet.json"; +import PsrBscTestnet from "@venusprotocol/protocol-reserve/deployments/bsctestnet.json"; +import PsrEthereum from "@venusprotocol/protocol-reserve/deployments/ethereum.json"; +import PsrOpBnbTestnet from "@venusprotocol/protocol-reserve/deployments/opbnbtestnet/ProtocolShareReserve.json"; +import PsrSepTestnet from "@venusprotocol/protocol-reserve/deployments/sepolia.json"; + +import { contracts as ArbOneContracts } from "../../../deployments/arbitrumone.json"; +import { contracts as ArbSepContracts } from "../../../deployments/arbitrumsepolia.json"; +import { contracts as MainnetContracts } from "../../../deployments/bscmainnet.json"; +import { contracts as TestnetContracts } from "../../../deployments/bsctestnet.json"; +import { contracts as EthereumContracts } from "../../../deployments/ethereum.json"; +import { contracts as OpBnbMainnetContracts } from "../../../deployments/opbnbmainnet.json"; +import { contracts as OpBnbTestnetContracts } from "../../../deployments/opbnbtestnet.json"; +import { contracts as SepoliaContracts } from "../../../deployments/sepolia.json"; + +export const contractAddresses = { + sepolia: { + ADMIN: "0x94fa6078b6b8a26F0B6EDFFBE6501B22A10470fB", + ACM: GovernanceSepTestnet.contracts.AccessControlManager.address, + TOKEN1: SepoliaContracts.MockcrvUSD.address, // crvUSD + TOKEN2: SepoliaContracts.MockCRV.address, // CRV + VTOKEN1: SepoliaContracts.VToken_vcrvUSD_Core.address, + VTOKEN2: SepoliaContracts.VToken_vCRV_Core.address, + COMPTROLLER: SepoliaContracts.Comptroller_Core.address, + PSR: PsrSepTestnet.contracts.ProtocolShareReserve.address, + REWARD_DISTRIBUTOR1: SepoliaContracts.RewardsDistributor_Core_1.address, + POOL_REGISTRY: SepoliaContracts.PoolRegistry.address, + CHAINLINK_ORACLE: OracleSepTestnet.contracts.ChainlinkOracle.address, + RESILIENT_ORACLE: OracleSepTestnet.contracts.ResilientOracle.address, + TOKEN1_HOLDER: "0x02EB950C215D12d723b44a18CfF098C6E166C531", + TOKEN2_HOLDER: "0x02EB950C215D12d723b44a18CfF098C6E166C531", + ACC1: "0x3Ac99C7853b58f4AA38b309D372562a5A88bB9C1", + ACC2: "0xA4a04C2D661bB514bB8B478CaCB61145894563ef", + ACC3: "0x394d1d517e8269596a7E4Cd1DdaC1C928B3bD8b3", + BLOCK_NUMBER: 5820200, + }, + ethereum: { + ADMIN: "0x285960C5B22fD66A736C7136967A3eB15e93CC67", + ACM: GovernanceEthMainnet.contracts.AccessControlManager.address, + TOKEN1: "0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E", // crvUSD + TOKEN2: "0xD533a949740bb3306d119CC777fa900bA034cd52", // CRV + VTOKEN1: EthereumContracts.VToken_vcrvUSD_Curve.address, + VTOKEN2: EthereumContracts.VToken_vCRV_Curve.address, + COMPTROLLER: EthereumContracts.Comptroller_Curve.address, + PSR: PsrEthereum.contracts.ProtocolShareReserve.address, + REWARD_DISTRIBUTOR1: EthereumContracts.RewardsDistributor_Curve_0.address, + POOL_REGISTRY: EthereumContracts.PoolRegistry.address, + CHAINLINK_ORACLE: OracleEthMainnet.contracts.ChainlinkOracle.address, + RESILIENT_ORACLE: OracleEthMainnet.contracts.ResilientOracle.address, + TOKEN1_HOLDER: "0xa920de414ea4ab66b97da1bfe9e6eca7d4219635", + TOKEN2_HOLDER: "0x5f3b5dfeb7b28cdbd7faba78963ee202a494e2a2", + ACC1: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", + ACC2: "0x29182006a4967e9a50C0A66076dA514993D3B4D4", + ACC3: "0xa27CEF8aF2B6575903b676e5644657FAe96F491F", + BLOCK_NUMBER: 19781700, + }, + bsctestnet: { + ADMIN: GovernanceBscTestnet.contracts.NormalTimelock.address, + ACM: GovernanceBscTestnet.contracts.AccessControlManager.address, + TOKEN1: TestnetContracts.MockUSDD.address, + TOKEN2: TestnetContracts.MockHAY.address, + VTOKEN1: TestnetContracts.VToken_vUSDD_StableCoins.address, + VTOKEN2: TestnetContracts.VToken_vHAY_StableCoins.address, + COMPTROLLER: TestnetContracts.Comptroller_StableCoins.address, + PSR: PsrBscTestnet.contracts.ProtocolShareReserve.address, + SHORTFALL: TestnetContracts.Shortfall.address, + RISKFUND: PsrBscTestnet.contracts.RiskFundV2.address, + REWARD_DISTRIBUTOR1: TestnetContracts.RewardsDistributor_StableCoins_0.address, + POOL_REGISTRY: TestnetContracts.PoolRegistry.address, + RESILIENT_ORACLE: OracleBscTestnet.contracts.ResilientOracle.address, + CHAINLINK_ORACLE: OracleBscTestnet.contracts.ChainlinkOracle.address, + BINANCE_ORACLE: OracleBscTestnet.contracts.BinanceOracle.address, + USDT: "0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c", + VUSDT: "0x3338988d0beb4419Acb8fE624218754053362D06", + USDT_HOLDER: "0x02EB950C215D12d723b44a18CfF098C6E166C531", + TOKEN1_HOLDER: "0x02EB950C215D12d723b44a18CfF098C6E166C531", + TOKEN2_HOLDER: "0x02EB950C215D12d723b44a18CfF098C6E166C531", + ACC1: "0xe70898180a366F204AA529708fB8f5052ea5723c", + ACC2: "0xA4a04C2D661bB514bB8B478CaCB61145894563ef", + ACC3: "0x394d1d517e8269596a7E4Cd1DdaC1C928B3bD8b3", + BLOCK_NUMBER: 39974300, + }, + bscmainnet: { + ADMIN: GovernanceBscMainnet.contracts.NormalTimelock.address, + ACM: GovernanceBscMainnet.contracts.AccessControlManager.address, + VTOKEN1: MainnetContracts.VToken_vUSDD_Stablecoins.address, + VTOKEN2: MainnetContracts.VToken_vHAY_Stablecoins.address, + COMPTROLLER: MainnetContracts.Comptroller_Stablecoins.address, + PSR: PsrBscMainnet.contracts.ProtocolShareReserve.address, + SHORTFALL: MainnetContracts.Shortfall.address, + RISKFUND: PsrBscMainnet.contracts.RiskFundV2.address, + REWARD_DISTRIBUTOR1: MainnetContracts.RewardsDistributor_Stablecoins_0.address, + POOL_REGISTRY: MainnetContracts.PoolRegistry.address, + RESILIENT_ORACLE: OracleBscMainnet.contracts.ResilientOracle.address, + CHAINLINK_ORACLE: OracleBscMainnet.contracts.ChainlinkOracle.address, + BINANCE_ORACLE: OracleBscMainnet.contracts.BinanceOracle.address, + USDT: "0x55d398326f99059fF775485246999027B3197955", + VUSDT: "0x5e3072305F9caE1c7A82F6Fe9E38811c74922c3B", + USDT_HOLDER: "0xa180Fe01B906A1bE37BE6c534a3300785b20d947", + TOKEN1: "0xd17479997F34dd9156Deef8F95A52D81D265be9c", // USDD + TOKEN2: "0x0782b6d8c4551B9760e74c0545a9bCD90bdc41E5", // HAY + TOKEN1_HOLDER: "0x53f78A071d04224B8e254E243fFfc6D9f2f3Fa23", + TOKEN2_HOLDER: "0x0966602e47f6a3ca5692529f1d54ecd1d9b09175", + ACC1: "0x3Ac99C7853b58f4AA38b309D372562a5A88bB9C1", + ACC2: "0xA4a04C2D661bB514bB8B478CaCB61145894563ef", + ACC3: "0x394d1d517e8269596a7E4Cd1DdaC1C928B3bD8b3", + BLOCK_NUMBER: 38364500, + }, + opbnbtestnet: { + ADMIN: "0xb15f6EfEbC276A3b9805df81b5FB3D50C2A62BDf", + ACM: GovernanceOpBnbTestnet.contracts.AccessControlManager.address, + VTOKEN1: OpBnbTestnetContracts.VToken_vBTCB_Core.address, + VTOKEN2: OpBnbTestnetContracts.VToken_vETH_Core.address, + COMPTROLLER: OpBnbTestnetContracts.Comptroller_Core.address, + PSR: PsrOpBnbTestnet.address, + POOL_REGISTRY: OpBnbTestnetContracts.PoolRegistry.address, + RESILIENT_ORACLE: OracleOpBnbTestnet.contracts.ResilientOracle.address, + BINANCE_ORACLE: OracleOpBnbTestnet.contracts.BinanceOracle.address, + TOKEN1: "0x7Af23F9eA698E9b953D2BD70671173AaD0347f19", // BTCB + TOKEN2: "0x94680e003861D43C6c0cf18333972312B6956FF1", // ETH + TOKEN1_HOLDER: "0x2ce1d0ffd7e869d9df33e28552b12ddded326706", + TOKEN2_HOLDER: "0x638eb8dfff094fd1d52c5a198b44984806c521e5", + USDT_HOLDER: "0x8894E0a0c962CB723c1976a4421c95949bE2D4E3", + ACC1: "0x3Ac99C7853b58f4AA38b309D372562a5A88bB9C1", + ACC2: "0xA4a04C2D661bB514bB8B478CaCB61145894563ef", + ACC3: "0x394d1d517e8269596a7E4Cd1DdaC1C928B3bD8b3", + BLOCK_NUMBER: 22749193, + }, + opbnbmainnet: { + ADMIN: "0xC46796a21a3A9FAB6546aF3434F2eBfFd0604207", + ACM: GovernanceOpBnbMainnet.contracts.AccessControlManager.address, + VTOKEN1: OpBnbMainnetContracts.VToken_vUSDT_Core.address, + VTOKEN2: OpBnbMainnetContracts.VToken_vFDUSD_Core.address, + COMPTROLLER: OpBnbMainnetContracts.Comptroller_Core.address, + PSR: "0xDDc9017F3073aa53a4A8535163b0bf7311F72C52", + POOL_REGISTRY: OpBnbMainnetContracts.PoolRegistry.address, + RESILIENT_ORACLE: OracleOpBnbMainnet.contracts.ResilientOracle.address, + BINANCE_ORACLE: OracleOpBnbMainnet.contracts.BinanceOracle.address, + TOKEN1: "0x9e5AAC1Ba1a2e6aEd6b32689DFcF62A509Ca96f3", // USDT + TOKEN2: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", // FDUSD + TOKEN1_HOLDER: "0x001ceb373c83ae75b9f5cf78fc2aba3e185d09e2", + TOKEN2_HOLDER: "0x001ceb373c83ae75b9f5cf78fc2aba3e185d09e2", + ACC1: "0x3Ac99C7853b58f4AA38b309D372562a5A88bB9C1", + ACC2: "0xA4a04C2D661bB514bB8B478CaCB61145894563ef", + ACC3: "0x394d1d517e8269596a7E4Cd1DdaC1C928B3bD8b3", + BLOCK_NUMBER: 17881611, + }, + arbitrumsepolia: { + ADMIN: "0x1426A5Ae009c4443188DA8793751024E358A61C2", + ACM: GovernanceArbSep.contracts.AccessControlManager.address, + VTOKEN1: ArbSepContracts.VToken_vWETH_Core.address, + VTOKEN2: ArbSepContracts.VToken_vARB_Core.address, + COMPTROLLER: ArbSepContracts.Comptroller_Core.address, + PSR: PsrArbSep.contracts.ProtocolShareReserve.address, + REWARD_DISTRIBUTOR1: ArbSepContracts.RewardsDistributor_Core_0.address, + POOL_REGISTRY: ArbSepContracts.PoolRegistry.address, + RESILIENT_ORACLE: OracleArbSep.contracts.ResilientOracle.address, + CHAINLINK_ORACLE: OracleArbSep.contracts.ChainlinkOracle.address, + TOKEN1: "0x980B62Da83eFf3D4576C647993b0c1D7faf17c73", // WETH + TOKEN2: ArbSepContracts.MockARB.address, // ARB + TOKEN1_HOLDER: "0x02EB950C215D12d723b44a18CfF098C6E166C531", + TOKEN2_HOLDER: "0x02EB950C215D12d723b44a18CfF098C6E166C531", + ACC1: "0xc7f050b6F465b876c764A866d6337EabBab08Cd4", + ACC2: "0xce0180B3B992649CBc3C8e1cF95b4A52Be9bA3AF", + ACC3: "0x13E0a421c17Ff1e7FFccFa05714957cF530b3aa4", + BLOCK_NUMBER: 58181663, + }, + arbitrumone: { + ADMIN: "0x14e0E151b33f9802b3e75b621c1457afc44DcAA0", + ACM: GovernanceArbOne.contracts.AccessControlManager.address, + VTOKEN1: ArbOneContracts.VToken_vWETH_Core.address, + VTOKEN2: ArbOneContracts.VToken_vARB_Core.address, + COMPTROLLER: ArbOneContracts.Comptroller_Core.address, + PSR: PsrArbOne.contracts.ProtocolShareReserve.address, + REWARD_DISTRIBUTOR1: ArbOneContracts.RewardsDistributor_Core_0.address, + POOL_REGISTRY: ArbOneContracts.PoolRegistry.address, + RESILIENT_ORACLE: OracleArbOne.contracts.ResilientOracle.address, + CHAINLINK_ORACLE: OracleArbOne.contracts.SequencerChainlinkOracle.address, + TOKEN1: "0x82af49447d8a07e3bd95bd0d56f35241523fbab1", // WETH + TOKEN2: "0x912ce59144191c1204e64559fe8253a0e49e6548", // ARB + TOKEN1_HOLDER: "0xf3fc178157fb3c87548baa86f9d24ba38e649b58", + TOKEN2_HOLDER: "0xf3fc178157fb3c87548baa86f9d24ba38e649b58", + ACC1: "0x32B701d3957fee432664cFA57FB44b0fE8496659", + ACC2: "0xB09F16F625B363875e39ADa56C03682088471523", + ACC3: "0x4A2339eE9c4fD4c99DE1d3AeB513B53ab42Db5ca", + BLOCK_NUMBER: 224198807, + }, +}; diff --git a/tests/hardhat/Fork/liquidation.ts b/tests/hardhat/Fork/liquidation.ts index c3a574aca..3841ac91c 100644 --- a/tests/hardhat/Fork/liquidation.ts +++ b/tests/hardhat/Fork/liquidation.ts @@ -2,7 +2,7 @@ import { smock } from "@defi-wonderland/smock"; import chai from "chai"; import { BigNumberish, Signer } from "ethers"; import { parseUnits } from "ethers/lib/utils"; -import { ethers } from "hardhat"; +import { ethers, upgrades } from "hardhat"; import { convertToUnit } from "../../../helpers/utils"; import { @@ -12,48 +12,59 @@ import { ChainlinkOracle__factory, Comptroller, Comptroller__factory, - FaucetToken, - FaucetToken__factory, - IProtocolShareReserve__factory, - MockToken, - MockToken__factory, + IERC20, + IERC20__factory, + MockPriceOracle, + MockPriceOracle__factory, VToken, VToken__factory, + WrappedNative, + WrappedNative__factory, } from "../../../typechain"; -import { initMainnetUser, setForkBlock } from "./utils"; +import { getContractAddresses, initMainnetUser, setForkBlock } from "./utils"; const { expect } = chai; chai.use(smock.matchers); -const FORK_TESTNET = process.env.FORK === "true" && process.env.FORKED_NETWORK === "bsctestnet"; - -const ADMIN = "0xce10739590001705F7FF231611ba4A48B2820327"; -const ORACLE_ADMIN = "0xce10739590001705F7FF231611ba4A48B2820327"; -const ACM = "0x45f8a08F534f34A97187626E05d4b6648Eeaa9AA"; -const ORACLE = "0xCeA29f1266e880A1482c06eD656cD08C148BaA32"; -const acc1 = "0xe70898180a366F204AA529708fB8f5052ea5723c"; -const acc2 = "0xA4a04C2D661bB514bB8B478CaCB61145894563ef"; -const USDD = "0x2E2466e22FcbE0732Be385ee2FBb9C59a1098382"; -const USDT = "0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c"; -const COMPTROLLER = "0x10b57706AD2345e590c2eA4DC02faef0d9f5b08B"; -const VUSDD = "0x899dDf81DfbbF5889a16D075c352F2b959Dd24A4"; -const VUSDT = "0x3338988d0beb4419Acb8fE624218754053362D06"; - -let impersonatedTimelock: Signer; -let impersonatedOracleOwner: Signer; -let accessControlManager: AccessControlManager; -let priceOracle: ChainlinkOracle; +const FORK = process.env.FORK === "true"; +const FORKED_NETWORK = process.env.FORKED_NETWORK || "bscmainnet"; + +const { + ACC1, + ACC2, + ADMIN, + ACM, + PSR, + TOKEN1, + TOKEN2, + VTOKEN1, + VTOKEN2, + COMPTROLLER, + TOKEN1_HOLDER, + TOKEN2_HOLDER, + RESILIENT_ORACLE, + CHAINLINK_ORACLE, + BLOCK_NUMBER, +} = getContractAddresses(FORKED_NETWORK as string); + +const AddressZero = "0x0000000000000000000000000000000000000000"; + +let token1: IERC20 | WrappedNative; +let token2: IERC20; +let vTOKEN1: VToken; +let vTOKEN2: VToken; let comptroller: Comptroller; -let vUSDD: VToken; -let vUSDT: VToken; -let usdd: MockToken; -let usdt: FaucetToken; +let token1Holder: Signer; +let token2Holder: Signer; let acc1Signer: Signer; let acc2Signer: Signer; +let impersonatedTimelock: Signer; +let chainlinkOracle: ChainlinkOracle; +let resilientOracle: MockPriceOracle; +let accessControlManager: AccessControlManager; async function configureTimelock() { impersonatedTimelock = await initMainnetUser(ADMIN, ethers.utils.parseUnits("2")); - impersonatedOracleOwner = await initMainnetUser(ORACLE_ADMIN, ethers.utils.parseUnits("2")); } async function configureVToken(vTokenAddress: string) { @@ -63,126 +74,140 @@ async function configureVToken(vTokenAddress: string) { async function grantPermissions() { accessControlManager = AccessControlManager__factory.connect(ACM, impersonatedTimelock); - - let tx = await accessControlManager - .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setMarketSupplyCaps(address[],uint256[])", ADMIN); - await tx.wait(); - - tx = await accessControlManager + const tx = await accessControlManager .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setMarketBorrowCaps(address[],uint256[])", ADMIN); - await tx.wait(); - - tx = await accessControlManager - .connect(impersonatedTimelock) - .giveCallPermission(ORACLE, "setDirectPrice(address,uint256)", ADMIN); - await tx.wait(); - - tx = await accessControlManager - .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setMinLiquidatableCollateral(uint256)", ADMIN); + .giveCallPermission(chainlinkOracle.address, "setDirectPrice(address,uint256)", ADMIN); await tx.wait(); } -if (FORK_TESTNET) { + +if (FORK) { describe("Liquidation", async () => { - async function setupBeforeEach(mintAmount: BigNumberish, usdtBorrowAmount: BigNumberish) { + async function setupBeforeEach(mintAmount: BigNumberish, token2BorrowAmount: BigNumberish) { await setup(); - await priceOracle.setDirectPrice(usdd.address, "159990000000000000000"); - await priceOracle.setDirectPrice(usdt.address, "208000"); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, "159990000000000000000"); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token2.address, "208000000000000000"); - await usdt.connect(acc1Signer).allocateTo(acc1, mintAmount); - await usdt.connect(acc1Signer).approve(vUSDT.address, mintAmount); - await expect(vUSDT.connect(acc1Signer).mint(mintAmount)).to.emit(vUSDT, "Mint"); + await token2.connect(token2Holder).transfer(ACC1, mintAmount); + await token2.connect(acc1Signer).approve(vTOKEN2.address, mintAmount); + await expect(vTOKEN2.connect(acc1Signer).mint(mintAmount)).to.emit(vTOKEN2, "Mint"); - await usdd.connect(acc2Signer).faucet(mintAmount); - await usdd.connect(acc2Signer).approve(vUSDD.address, mintAmount); - await expect(vUSDD.connect(acc2Signer).mint(mintAmount)).to.emit(vUSDD, "Mint"); - await expect(vUSDT.connect(acc2Signer).borrow(usdtBorrowAmount)).to.emit(vUSDT, "Borrow"); + await token1.connect(token1Holder).transfer(ACC2, mintAmount); + await token1.connect(acc2Signer).approve(vTOKEN1.address, mintAmount); + await expect(vTOKEN1.connect(acc2Signer).mint(mintAmount)).to.emit(vTOKEN1, "Mint"); + await expect(vTOKEN2.connect(acc2Signer).borrow(token2BorrowAmount)).to.emit(vTOKEN2, "Borrow"); // Approve more assets for liquidation - await usdt.connect(acc1Signer).allocateTo(acc1, convertToUnit(3, 18)); - await usdt.connect(acc1Signer).approve(vUSDT.address, convertToUnit(3, 18)); + await token2.connect(token2Holder).transfer(ACC1, convertToUnit(3, 18)); + await token2.connect(acc1Signer).approve(vTOKEN2.address, convertToUnit(3, 18)); } async function setup() { - await setForkBlock(30914000); + await setForkBlock(BLOCK_NUMBER); await configureTimelock(); - acc1Signer = await initMainnetUser(acc1, ethers.utils.parseUnits("2")); - acc2Signer = await initMainnetUser(acc2, ethers.utils.parseUnits("2")); + acc1Signer = await initMainnetUser(ACC1, ethers.utils.parseUnits("2")); + acc2Signer = await initMainnetUser(ACC2, ethers.utils.parseUnits("2")); + token2Holder = await initMainnetUser(TOKEN2_HOLDER, ethers.utils.parseUnits("2")); + token1Holder = await initMainnetUser(TOKEN1_HOLDER, ethers.utils.parseUnits("2000000")); - usdt = FaucetToken__factory.connect(USDT, impersonatedTimelock); - usdd = MockToken__factory.connect(USDD, impersonatedTimelock); - vUSDT = await configureVToken(VUSDT); - vUSDD = await configureVToken(VUSDD); + vTOKEN2 = await configureVToken(VTOKEN2); + vTOKEN1 = await configureVToken(VTOKEN1); comptroller = Comptroller__factory.connect(COMPTROLLER, impersonatedTimelock); - priceOracle = ChainlinkOracle__factory.connect(ORACLE, impersonatedOracleOwner); - + token2 = IERC20__factory.connect(TOKEN2, impersonatedTimelock); + token1 = IERC20__factory.connect(TOKEN1, impersonatedTimelock); + if (FORKED_NETWORK == "arbitrumsepolia" || FORKED_NETWORK == "arbitrumone") { + token1 = WrappedNative__factory.connect(TOKEN1, impersonatedTimelock); + await token1.connect(token1Holder).deposit({ value: convertToUnit("200000", 18) }); + } + + if (FORKED_NETWORK == "opbnbmainnet" || FORKED_NETWORK == "opbnbtestnet") { + const chainlinkOracleFactory = await ethers.getContractFactory("ChainlinkOracle"); + chainlinkOracle = await upgrades.deployProxy(chainlinkOracleFactory, [ACM], { impersonatedTimelock }); + } else { + chainlinkOracle = ChainlinkOracle__factory.connect(CHAINLINK_ORACLE, impersonatedTimelock); + } await grantPermissions(); + const tupleForToken1 = { + asset: TOKEN1, + oracles: [chainlinkOracle.address, AddressZero, AddressZero], + enableFlagsForOracles: [true, false, false], + }; + + const tupleForToken2 = { + asset: TOKEN2, + oracles: [chainlinkOracle.address, AddressZero, AddressZero], + enableFlagsForOracles: [true, false, false], + }; + + resilientOracle = MockPriceOracle__factory.connect(RESILIENT_ORACLE, impersonatedTimelock); + await resilientOracle.setTokenConfig(tupleForToken1); + await resilientOracle.setTokenConfig(tupleForToken2); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, convertToUnit("1", 18)); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token2.address, convertToUnit("1", 18)); + await comptroller.setMarketSupplyCaps( - [vUSDT.address, vUSDD.address], + [vTOKEN2.address, vTOKEN1.address], [convertToUnit(1, 50), convertToUnit(1, 50)], ); await comptroller.setMarketBorrowCaps( - [vUSDT.address, vUSDD.address], + [vTOKEN2.address, vTOKEN1.address], [convertToUnit(1, 50), convertToUnit(1, 50)], ); - await comptroller.connect(acc1Signer).enterMarkets([vUSDT.address]); - await comptroller.connect(acc2Signer).enterMarkets([vUSDD.address]); + await comptroller.connect(acc1Signer).enterMarkets([vTOKEN2.address]); + await comptroller.connect(acc2Signer).enterMarkets([vTOKEN1.address]); } describe("Liquidate from VToken", async () => { - const mintAmount = convertToUnit("1", 17); - const usdtBorrowAmount = convertToUnit("1", 4); + const mintAmount = convertToUnit("1000", 18); + const token2BorrowAmount = convertToUnit("1", 9); beforeEach(async () => { await setup(); - await usdt.connect(acc1Signer).allocateTo(acc1, mintAmount); - await usdt.connect(acc1Signer).approve(vUSDT.address, mintAmount); - await expect(vUSDT.connect(acc1Signer).mint(mintAmount)).to.emit(vUSDT, "Mint"); + await token2.connect(token2Holder).transfer(ACC1, mintAmount); + await token2.connect(acc1Signer).approve(vTOKEN2.address, mintAmount); + await expect(vTOKEN2.connect(acc1Signer).mint(mintAmount)).to.emit(vTOKEN2, "Mint"); - await usdd.connect(acc2Signer).faucet(mintAmount); - await usdd.connect(acc2Signer).approve(vUSDD.address, mintAmount); - await expect(vUSDD.connect(acc2Signer).mint(mintAmount)).to.emit(vUSDD, "Mint"); + await token1.connect(token1Holder).transfer(ACC2, mintAmount); + await token1.connect(acc2Signer).approve(vTOKEN1.address, mintAmount); + await expect(vTOKEN1.connect(acc2Signer).mint(mintAmount)).to.emit(vTOKEN1, "Mint"); - await expect(vUSDT.connect(acc2Signer).borrow(usdtBorrowAmount)).to.emit(vUSDT, "Borrow"); + await expect(vTOKEN2.connect(acc2Signer).borrow(token2BorrowAmount)).to.emit(vTOKEN2, "Borrow"); - await usdt.connect(acc1Signer).allocateTo(acc1, convertToUnit("1", 18)); - await usdt.connect(acc1Signer).approve(vUSDT.address, convertToUnit("1", 18)); + await token2.connect(token2Holder).transfer(ACC1, convertToUnit("1", 18)); + await token2.connect(acc1Signer).approve(vTOKEN2.address, convertToUnit("1", 18)); }); it("Should revert when liquidation is called through vToken and does not met minCollateral Criteria", async function () { + await comptroller.setMinLiquidatableCollateral(convertToUnit("1", 25)); await expect( - vUSDT.connect(acc1Signer).liquidateBorrow(acc2, usdtBorrowAmount, vUSDD.address), + vTOKEN2.connect(acc1Signer).liquidateBorrow(ACC2, token2BorrowAmount, vTOKEN1.address), ).to.be.revertedWithCustomError(comptroller, "MinimalCollateralViolated"); }); it("Should revert when liquidation is called through vToken and no shortfall", async function () { // Mint and Increase collateral of the user - const underlyingMintAmount = convertToUnit("1", 30); - await usdd.connect(acc2Signer).faucet(underlyingMintAmount); - await usdd.connect(acc2Signer).approve(vUSDD.address, underlyingMintAmount); - - await vUSDD.connect(acc2Signer).mint(underlyingMintAmount); + const underlyingMintAmount = convertToUnit("1", 20); + await token1.connect(token1Holder).transfer(ACC2, underlyingMintAmount); + await token1.connect(acc2Signer).approve(vTOKEN1.address, underlyingMintAmount); + await vTOKEN1.connect(acc2Signer).mint(underlyingMintAmount); // Liquidation await expect( - vUSDT.connect(acc1Signer).liquidateBorrow(acc2, usdtBorrowAmount, vUSDD.address), + vTOKEN2.connect(acc1Signer).liquidateBorrow(ACC2, token2BorrowAmount, vTOKEN1.address), ).to.be.revertedWithCustomError(comptroller, "InsufficientShortfall"); }); it("Should revert when liquidation is called through vToken and trying to seize more tokens", async function () { await comptroller.setMinLiquidatableCollateral(0); - await priceOracle.setDirectPrice(usdd.address, convertToUnit("1", 5)); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, convertToUnit("1", 5)); - const borrowBalance = await vUSDT.borrowBalanceStored(acc2); + const borrowBalance = await vTOKEN2.borrowBalanceStored(ACC2); const closeFactor = await comptroller.closeFactorMantissa(); const maxClose = (borrowBalance * closeFactor) / 1e18; // Liquidation - await expect(vUSDT.connect(acc1Signer).liquidateBorrow(acc2, maxClose, vUSDD.address)).to.be.revertedWith( + await expect(vTOKEN2.connect(acc1Signer).liquidateBorrow(ACC2, maxClose, vTOKEN1.address)).to.be.revertedWith( "LIQUIDATE_SEIZE_TOO_MUCH", ); }); @@ -191,196 +216,215 @@ if (FORK_TESTNET) { // Mint and Incrrease collateral of the user await comptroller.setMinLiquidatableCollateral(0); const underlyingMintAmount = convertToUnit("1", 18); - await usdd.connect(acc2Signer).faucet(underlyingMintAmount); - await usdd.connect(acc2Signer).approve(vUSDD.address, underlyingMintAmount); + await token1.connect(token1Holder).transfer(ACC2, underlyingMintAmount); + await token1.connect(acc2Signer).approve(vTOKEN1.address, underlyingMintAmount); + + await expect(vTOKEN1.connect(acc2Signer).mint(underlyingMintAmount)).to.emit(vTOKEN1, "Mint"); - await expect(vUSDD.connect(acc2Signer).mint(underlyingMintAmount)).to.emit(vUSDD, "Mint"); // price manipulation to put user underwater - await priceOracle.setDirectPrice(usdd.address, convertToUnit("1", 5)); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, convertToUnit("1", 5)); - const borrowBalance = await vUSDT.borrowBalanceStored(acc2); + const borrowBalance = await vTOKEN2.borrowBalanceStored(ACC2); const closeFactor = await comptroller.closeFactorMantissa(); - const maxClose = (borrowBalance * closeFactor) / 1e18; + + // amount to repay should be greater than maxClose for getting error of TooMuchRepay + const maxClose = (borrowBalance * closeFactor) / 1e18 + 1e10; // Liquidation await expect( - vUSDT.connect(acc1Signer).liquidateBorrow(acc2, maxClose + 1, vUSDD.address), + vTOKEN2.connect(acc1Signer).liquidateBorrow(ACC2, maxClose, vTOKEN1.address), ).to.be.revertedWithCustomError(comptroller, "TooMuchRepay"); }); it("liquidate user", async () => { await comptroller.setMinLiquidatableCollateral(0); - await priceOracle.setDirectPrice(usdd.address, convertToUnit("100", 15)); - const borrowBalance = await vUSDT.borrowBalanceStored(acc2); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, convertToUnit("1", 6)); + const borrowBalance = await vTOKEN2.borrowBalanceStored(ACC2); - const [err, liquidity, shortfall] = await comptroller.getAccountLiquidity(acc2); + const [err, liquidity, shortfall] = await comptroller.getAccountLiquidity(ACC2); expect(err).equals(0); expect(liquidity).equals(0); expect(shortfall).greaterThan(0); - const totalReservesUsddPrev = await vUSDD.totalReserves(); - const vUSDDBalAcc1Prev = await vUSDD.balanceOf(acc1); - const vUSDDBalAcc2Prev = await vUSDD.balanceOf(acc2); + const totalReservesToken1Prev = await vTOKEN1.totalReserves(); + const vTOKEN1BalAcc1Prev = await vTOKEN1.balanceOf(ACC1); + const vTOKEN1BalAcc2Prev = await vTOKEN1.balanceOf(ACC2); + const psrBalancePrev = await token1.balanceOf(PSR); - const borrowBalancePrev = await vUSDT.borrowBalanceStored(acc2); + const psrAndreservesSumPrev = psrBalancePrev.add(totalReservesToken1Prev); + const borrowBalancePrev = await vTOKEN2.borrowBalanceStored(ACC2); const closeFactor = await comptroller.closeFactorMantissa(); const maxClose = (borrowBalance * closeFactor) / 1e18; - const priceBorrowed = await priceOracle.getUnderlyingPrice(vUSDT.address); - const priceCollateral = await priceOracle.getUnderlyingPrice(vUSDD.address); + const priceBorrowed = await chainlinkOracle.getPrice(TOKEN2); + const priceCollateral = await chainlinkOracle.getPrice(TOKEN1); const liquidationIncentive = await comptroller.liquidationIncentiveMantissa(); - const exchangeRateCollateralPrev = await vUSDD.callStatic.exchangeRateCurrent(); + const exchangeRateCollateralPrev = await vTOKEN1.callStatic.exchangeRateCurrent(); const num = (liquidationIncentive * priceBorrowed) / 1e18; const den = (priceCollateral * exchangeRateCollateralPrev) / 1e18; const ratio = num / den; const seizeTokens = ratio * maxClose; - const result = vUSDT.connect(acc1Signer).liquidateBorrow(acc2, maxClose.toString(), vUSDD.address); + const result = vTOKEN2.connect(acc1Signer).liquidateBorrow(ACC2, maxClose.toString(), vTOKEN1.address); - await expect(result).to.emit(vUSDT, "LiquidateBorrow"); - const vUSDDBalAcc2New = await vUSDD.balanceOf(acc2); - const vUSDDBalAcc1New = await vUSDD.balanceOf(acc1); - const totalReservesUsddNew = await vUSDD.totalReserves(); - const exchangeRateCollateralNew = await vUSDD.exchangeRateStored(); - const protocolSeizeShareMantissa = await vUSDD.protocolSeizeShareMantissa(); + await expect(result).to.emit(vTOKEN2, "LiquidateBorrow"); + const vTOKEN1BalAcc2New = await vTOKEN1.balanceOf(ACC2); + const vTOKEN1BalAcc1New = await vTOKEN1.balanceOf(ACC1); + const totalReservesToken1New = await vTOKEN1.totalReserves(); + const exchangeRateCollateralNew = await vTOKEN1.exchangeRateStored(); + const protocolSeizeShareMantissa = await vTOKEN1.protocolSeizeShareMantissa(); const protocolSeizeTokens = Math.floor((seizeTokens * protocolSeizeShareMantissa) / liquidationIncentive); const liquidatorSeizeTokens = Math.floor(seizeTokens - protocolSeizeTokens); - const reserveIncrease = (protocolSeizeTokens * exchangeRateCollateralNew) / 1e18; - const borrowBalanceNew = await vUSDT.borrowBalanceStored(acc2); + const borrowBalanceNew = await vTOKEN2.borrowBalanceStored(ACC2); + expect(borrowBalancePrev.sub(maxClose)).closeTo(borrowBalanceNew, 200); - expect(borrowBalancePrev - maxClose).equals(borrowBalanceNew); - expect(vUSDDBalAcc2Prev - vUSDDBalAcc2New).equals(Math.floor(seizeTokens)); - expect(vUSDDBalAcc1New - vUSDDBalAcc1Prev).to.closeTo(liquidatorSeizeTokens, 2); - expect(totalReservesUsddNew - totalReservesUsddPrev).to.closeTo( - Math.round(reserveIncrease), - parseUnits("0.00003", 18), - ); + expect(vTOKEN1BalAcc2Prev.sub(vTOKEN1BalAcc2New)).to.closeTo(Math.floor(seizeTokens), 1000); + + expect(vTOKEN1BalAcc1New.sub(vTOKEN1BalAcc1Prev)).to.closeTo(liquidatorSeizeTokens, 1000); + const psrBalanceNew = await token1.balanceOf(PSR); + + const psrAndreservesSumNew = psrBalanceNew.add(totalReservesToken1New); + const difference = psrAndreservesSumNew.sub(psrAndreservesSumPrev); + + expect(difference.toString()).to.closeTo(reserveIncrease.toString(), parseUnits("0.03", 18)); }); }); describe("Liquidate from Comptroller", async () => { const mintAmount: BigNumberish = convertToUnit(1, 15); - const usdtBorrowAmount: BigNumberish = convertToUnit(1, 15); + const token2BorrowAmount: BigNumberish = convertToUnit(1, 15); + beforeEach(async () => { - await setupBeforeEach(mintAmount, usdtBorrowAmount); + await setupBeforeEach(mintAmount, token2BorrowAmount); }); it("Should revert when not enough collateral to seize", async function () { - await usdd.connect(acc2Signer).faucet(1e10); - await usdd.connect(acc2Signer).approve(vUSDD.address, 1e10); - await vUSDD.connect(acc2Signer).mint(1e10); + await token1.connect(token1Holder).transfer(ACC2, 1e10); + await token1.connect(acc2Signer).approve(vTOKEN1.address, 1e10); + await vTOKEN1.connect(acc2Signer).mint(1e10); // Repay amount does not make borrower principal to zero - const repayAmount = Number(usdtBorrowAmount) / 2; + const repayAmount = Number(token2BorrowAmount) / 2; const param = { - vTokenCollateral: vUSDD.address, - vTokenBorrowed: vUSDT.address, + vTokenCollateral: vTOKEN1.address, + vTokenBorrowed: vTOKEN2.address, repayAmount: repayAmount, }; - await priceOracle.setDirectPrice(usdd.address, convertToUnit("100", 12)); - await expect(comptroller.connect(acc1Signer).liquidateAccount(acc2, [param])).to.be.revertedWithCustomError( + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, convertToUnit("100", 12)); + await expect(comptroller.connect(acc1Signer).liquidateAccount(ACC2, [param])).to.be.revertedWithCustomError( comptroller, "InsufficientCollateral", ); }); it("Should success on liquidation when repay amount is equal to borrowing", async function () { - await usdd.connect(acc2Signer).faucet(convertToUnit(1, 10)); - await usdd.connect(acc2Signer).approve(vUSDD.address, convertToUnit(1, 10)); - await vUSDD.connect(acc2Signer).mint(convertToUnit(1, 10)); - await comptroller .connect(impersonatedTimelock) - .setCollateralFactor(vUSDD.address, convertToUnit(7, 17), convertToUnit(8, 17)); + .setCollateralFactor(vTOKEN1.address, convertToUnit(7, 17), convertToUnit(8, 17)); await comptroller.connect(impersonatedTimelock).setLiquidationIncentive(convertToUnit(1, 18)); - await priceOracle.setDirectPrice(usdd.address, convertToUnit("1", 14)); - await priceOracle.setDirectPrice(usdt.address, convertToUnit("1", 2)); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, convertToUnit("1", 12)); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token2.address, convertToUnit("1", 12)); - const [err, liquidity, shortfall] = await comptroller.getAccountLiquidity(acc2); + const [err, liquidity, shortfall] = await comptroller.getAccountLiquidity(ACC2); expect(err).equals(0); expect(liquidity).equals(0); expect(shortfall).greaterThan(0); - const totalReservesUsddPrev = await vUSDD.totalReserves(); - const vUSDDBalAcc1Prev = await vUSDD.balanceOf(acc1); - const vUSDDBalAcc2Prev = await vUSDD.balanceOf(acc2); - - const priceBorrowed = await priceOracle.getUnderlyingPrice(vUSDT.address); - const priceCollateral = await priceOracle.getUnderlyingPrice(vUSDD.address); + const totalReservesToken1Prev = await vTOKEN1.totalReserves(); + const vTOKEN1BalAcc1Prev = await vTOKEN1.balanceOf(ACC1); + const vTOKEN1BalAcc2Prev = await vTOKEN1.balanceOf(ACC2); + const priceBorrowed = await chainlinkOracle.getPrice(TOKEN2); + const priceCollateral = await chainlinkOracle.getPrice(TOKEN1); const liquidationIncentive = await comptroller.liquidationIncentiveMantissa(); - const exchangeRateCollateralPrev = await vUSDD.callStatic.exchangeRateCurrent(); + const exchangeRateCollateralPrev = await vTOKEN1.callStatic.exchangeRateCurrent(); + const num = (liquidationIncentive * priceBorrowed) / 1e18; const den = (priceCollateral * exchangeRateCollateralPrev) / 1e18; const ratio = num / den; + await token1.connect(token1Holder).transfer(ACC2, convertToUnit(1, 12)); + await token1.connect(acc2Signer).approve(vTOKEN1.address, convertToUnit(1, 12)); + await vTOKEN1.connect(acc2Signer).mint(convertToUnit(1, 12)); + + // repayAmount will be calculated after accruing interest and then using borrowBalanceStored to get the repayAmount. + const NetworkRespectiveRepayAmounts = { + bsctestnet: 1000000048189326, + sepolia: 1000000138102911, + bscmainnet: 1000000020807824, + ethereum: 1000000262400450, + opbnbtestnet: 1000000000288189, + opbnbmainnet: 1000000008986559, + arbitrumsepolia: 1000000000046406, + arbitrumone: 1000000032216389, + }; - const repayAmount = 1000001017860540; // After interest accrual + const repayAmount = NetworkRespectiveRepayAmounts[FORKED_NETWORK]; const seizeTokens = ratio * repayAmount; const param = { - vTokenCollateral: vUSDD.address, - vTokenBorrowed: vUSDT.address, + vTokenCollateral: vTOKEN1.address, + vTokenBorrowed: vTOKEN2.address, repayAmount: repayAmount, }; - const result = comptroller.connect(acc1Signer).liquidateAccount(acc2, [param]); - await expect(result).to.emit(vUSDT, "LiquidateBorrow"); - expect(await vUSDT.borrowBalanceStored(acc2)).equals(0); + const result = comptroller.connect(acc1Signer).liquidateAccount(ACC2, [param]); + await expect(result).to.emit(vTOKEN2, "LiquidateBorrow"); + expect(await vTOKEN2.borrowBalanceStored(ACC2)).equals(0); - const vUSDDBalAcc1New = await vUSDD.balanceOf(acc1); - const vUSDDBalAcc2New = await vUSDD.balanceOf(acc2); - const totalReservesUsddNew = await vUSDD.totalReserves(); - const exchangeRateCollateralNew = await vUSDD.exchangeRateStored(); + const vTOKEN1BalAcc1New = await vTOKEN1.balanceOf(ACC1); + const vTOKEN1BalAcc2New = await vTOKEN1.balanceOf(ACC2); + const totalReservesToken1New = await vTOKEN1.totalReserves(); + const exchangeRateCollateralNew = await vTOKEN1.exchangeRateStored(); const liquidatorSeizeTokens = Math.floor((seizeTokens * 95) / 100); const protocolSeizeTokens = Math.floor((seizeTokens * 5) / 100); const reserveIncrease = (protocolSeizeTokens * exchangeRateCollateralNew) / 1e18; - expect(vUSDDBalAcc2Prev - vUSDDBalAcc2New).equals(Math.floor(seizeTokens)); - expect(vUSDDBalAcc1New - vUSDDBalAcc1Prev).equals(liquidatorSeizeTokens); - expect(totalReservesUsddNew - totalReservesUsddPrev).to.closeTo( + expect(vTOKEN1BalAcc2Prev.sub(vTOKEN1BalAcc2New)).to.closeTo(Math.floor(seizeTokens), 100); + expect(vTOKEN1BalAcc1New.sub(vTOKEN1BalAcc1Prev)).to.closeTo(liquidatorSeizeTokens, 1); + expect(totalReservesToken1New.sub(totalReservesToken1Prev)).to.closeTo( Math.round(reserveIncrease), - parseUnits("0.00003", 18), + parseUnits("1", 17), ); }); }); describe("Heal Borrow and Forgive account", () => { const mintAmount = convertToUnit("1", 12); - const usdtBorrowAmount = convertToUnit(1, 4); + const token2BorrowAmount = convertToUnit(1, 4); let result; beforeEach(async () => { - await setupBeforeEach(mintAmount, usdtBorrowAmount); + await setupBeforeEach(mintAmount, token2BorrowAmount); }); it("Should success on healing and forgive borrow account", async function () { // Increase price of borrowed underlying tokens to surpass available collateral - await priceOracle.setDirectPrice(usdt.address, convertToUnit(1, 13)); // 25 - await priceOracle.setDirectPrice(usdd.address, convertToUnit(1, 15)); // 15 + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token2.address, convertToUnit(1, 25)); // 25 + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, convertToUnit(1, 15)); // 15 - const USDDPrice = await priceOracle.getUnderlyingPrice(VUSDD); - const USDTPrice = await priceOracle.getUnderlyingPrice(VUSDT); + const token1Price = await chainlinkOracle.getPrice(TOKEN1); + const token2Price = await chainlinkOracle.getPrice(TOKEN2); - const collateralBal = await vUSDD.balanceOf(acc2); - const exchangeRateCollateral = await vUSDD.callStatic.exchangeRateCurrent(); - const borrowBalanceCurrent = await vUSDT.callStatic.borrowBalanceCurrent(acc2); + const collateralBal = await vTOKEN1.balanceOf(ACC2); + const exchangeRateCollateral = await vTOKEN1.callStatic.exchangeRateCurrent(); + const borrowBalanceCurrent = await vTOKEN2.callStatic.borrowBalanceCurrent(ACC2); - const vTokenCollateralPrice = USDDPrice.mul(exchangeRateCollateral).div(convertToUnit(1, 18)); + const vTokenCollateralPrice = token1Price.mul(exchangeRateCollateral).div(convertToUnit(1, 18)); const totalCollateral = vTokenCollateralPrice.mul(collateralBal).div(convertToUnit(1, 18)); - const scaledBorrows = USDTPrice.mul(borrowBalanceCurrent).div(convertToUnit(1, 18)); + const scaledBorrows = token2Price.mul(borrowBalanceCurrent).div(convertToUnit(1, 18)); const percentageOfRepay = totalCollateral.mul(convertToUnit(11, 18)).div(scaledBorrows); const repayAmount = percentageOfRepay.mul(borrowBalanceCurrent).div(convertToUnit(1, 18)); const badDebt = borrowBalanceCurrent.sub(repayAmount); - result = await comptroller.connect(acc1Signer).healAccount(acc2); - await expect(result).to.emit(vUSDT, "RepayBorrow"); + result = await comptroller.connect(acc1Signer).healAccount(ACC2); + await expect(result).to.emit(vTOKEN2, "RepayBorrow"); // Forgive Account - result = await vUSDT.connect(acc2Signer).getAccountSnapshot(acc2); + result = await vTOKEN2.connect(acc2Signer).getAccountSnapshot(ACC2); expect(result.vTokenBalance).to.equal(0); expect(result.borrowBalance).to.equal(0); - const badDebtAfter = await vUSDT.badDebt(); + const badDebtAfter = await vTOKEN2.badDebt(); expect(badDebtAfter).to.closeTo(badDebt, 1011); }); }); diff --git a/tests/hardhat/Fork/reduceReservesTest.ts b/tests/hardhat/Fork/reduceReservesTest.ts index e5adc8c6f..6bf318dbc 100644 --- a/tests/hardhat/Fork/reduceReservesTest.ts +++ b/tests/hardhat/Fork/reduceReservesTest.ts @@ -1,66 +1,40 @@ import { smock } from "@defi-wonderland/smock"; import { mine } from "@nomicfoundation/hardhat-network-helpers"; import chai from "chai"; -import { BigNumberish, Signer } from "ethers"; -import { parseUnits } from "ethers/lib/utils"; +import { BigNumber, BigNumberish, Signer } from "ethers"; import { ethers } from "hardhat"; import { convertToUnit } from "../../../helpers/utils"; import { - AccessControlManager, - AccessControlManager__factory, Comptroller, Comptroller__factory, - MockToken, - MockToken__factory, + IERC20, + IERC20__factory, VToken, VToken__factory, } from "../../../typechain"; -import { initMainnetUser, setForkBlock } from "./utils"; +import { getContractAddresses, initMainnetUser, setForkBlock } from "./utils"; const { expect } = chai; chai.use(smock.matchers); -const FORK_TESTNET = process.env.FORK === "true" && process.env.FORKED_NETWORK === "bsctestnet"; -const FORK_MAINNET = process.env.FORK === "true" && process.env.FORKED_NETWORK === "bscmainnet"; - -let ADMIN: string; -let ACM: string; -let acc1: string; -let acc2: string; -let USDD: string; -let COMPTROLLER: string; -let VUSDD: string; -let PROTOCOL_SHARE_RESERVE: string; -let POOL_REGISTRY: string; -let BLOCK_NUMBER: number; - -if (FORK_TESTNET) { - ADMIN = "0xce10739590001705F7FF231611ba4A48B2820327"; - ACM = "0x45f8a08F534f34A97187626E05d4b6648Eeaa9AA"; - acc1 = "0xe70898180a366F204AA529708fB8f5052ea5723c"; - acc2 = "0xA4a04C2D661bB514bB8B478CaCB61145894563ef"; - USDD = "0x2E2466e22FcbE0732Be385ee2FBb9C59a1098382"; - COMPTROLLER = "0x10b57706AD2345e590c2eA4DC02faef0d9f5b08B"; - VUSDD = "0x899dDf81DfbbF5889a16D075c352F2b959Dd24A4"; - PROTOCOL_SHARE_RESERVE = "0x8b293600c50d6fbdc6ed4251cc75ece29880276f"; // VTreasury contract - POOL_REGISTRY = "0xC85491616Fa949E048F3aAc39fbf5b0703800667"; - BLOCK_NUMBER = 30951193; -} +const FORK = process.env.FORK === "true"; +const FORKED_NETWORK = process.env.FORKED_NETWORK || "bscmainnet"; -if (FORK_MAINNET) { - // Mainnet addresses -} +const { ACC1, ACC2, ADMIN, PSR, TOKEN2_HOLDER, TOKEN2, VTOKEN2, COMPTROLLER, BLOCK_NUMBER } = getContractAddresses( + FORKED_NETWORK as string, +); + +let holder: string; +let token: IERC20; +let vToken: VToken; -let impersonatedTimelock: Signer; -let accessControlManager: AccessControlManager; let comptroller: Comptroller; -let vUSDD: VToken; -let usdd: MockToken; let acc1Signer: Signer; let acc2Signer: Signer; +let impersonatedTimelock: Signer; let mintAmount: BigNumberish; -let bswBorrowAmount: BigNumberish; +let borrowAmount: BigNumberish; async function configureTimelock() { impersonatedTimelock = await initMainnetUser(ADMIN, ethers.utils.parseUnits("2")); @@ -71,118 +45,124 @@ async function configureVToken(vTokenAddress: string) { return VToken; } -async function grantPermissions() { - accessControlManager = AccessControlManager__factory.connect(ACM, impersonatedTimelock); - - let tx = await accessControlManager - .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setMarketSupplyCaps(address[],uint256[])", ADMIN); - await tx.wait(); - - tx = await accessControlManager - .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setMarketBorrowCaps(address[],uint256[])", ADMIN); - await tx.wait(); -} - -if (FORK_TESTNET || FORK_MAINNET) { +if (FORK) { describe("Reduce Reserves", async () => { mintAmount = convertToUnit("1000", 18); - bswBorrowAmount = convertToUnit("100", 18); + borrowAmount = convertToUnit("100", 18); async function setup() { await setForkBlock(BLOCK_NUMBER); await configureTimelock(); - acc1Signer = await initMainnetUser(acc1, ethers.utils.parseUnits("2")); - acc2Signer = await initMainnetUser(acc2, ethers.utils.parseUnits("2")); + acc1Signer = await initMainnetUser(ACC1, ethers.utils.parseUnits("2")); + acc2Signer = await initMainnetUser(ACC2, ethers.utils.parseUnits("2")); + holder = await initMainnetUser(TOKEN2_HOLDER, ethers.utils.parseUnits("2000000")); - usdd = MockToken__factory.connect(USDD, impersonatedTimelock); - vUSDD = await configureVToken(VUSDD); + token = IERC20__factory.connect(TOKEN2, impersonatedTimelock); + vToken = await configureVToken(VTOKEN2); comptroller = Comptroller__factory.connect(COMPTROLLER, impersonatedTimelock); - await grantPermissions(); + await comptroller.connect(acc1Signer).enterMarkets([vToken.address]); + await comptroller.connect(acc2Signer).enterMarkets([vToken.address]); - await comptroller.connect(acc1Signer).enterMarkets([vUSDD.address]); - await comptroller.connect(acc2Signer).enterMarkets([vUSDD.address]); - - await comptroller.setMarketSupplyCaps([vUSDD.address], [convertToUnit(1, 50)]); - await comptroller.setMarketBorrowCaps([vUSDD.address], [convertToUnit(1, 50)]); + await comptroller.setMarketSupplyCaps([vToken.address], [convertToUnit(1, 50)]); + await comptroller.setMarketBorrowCaps([vToken.address], [convertToUnit(1, 50)]); } - async function mintVTokens(signer: Signer, token: MockToken, vToken: VToken, amount: BigNumberish) { - await token.connect(signer).faucet(amount); + + async function mintVTokens(signer: Signer, token: IERC20, vToken: VToken, amount: BigNumberish) { + await token.connect(holder).transfer(await signer.getAddress(), amount); await token.connect(signer).approve(vToken.address, amount); await expect(vToken.connect(signer).mint(amount)).to.emit(vToken, "Mint"); } beforeEach(async () => { await setup(); - // Mint some tokens in vUsdd market to add underlying assets for borrow and reserves purpose - await mintVTokens(acc2Signer, usdd, vUSDD, mintAmount); + // Mint some tokens in vToken market to add underlying assets for borrow and reserves purpose + await mintVTokens(acc2Signer, token, vToken, mintAmount); }); it("Reduce partial reserves and verify effects", async function () { - let totalCashOld = await vUSDD.getCash(); - await mintVTokens(acc1Signer, usdd, vUSDD, mintAmount); - let totalCashNew = await vUSDD.getCash(); + let totalCashOld = await vToken.getCash(); + await mintVTokens(acc1Signer, token, vToken, mintAmount); + let totalCashNew = await vToken.getCash(); + expect(totalCashNew.sub(totalCashOld)).equals(mintAmount); totalCashOld = totalCashNew; - await vUSDD.connect(acc2Signer).borrow(bswBorrowAmount); - totalCashNew = await vUSDD.getCash(); - expect(totalCashOld.sub(totalCashNew)).equals(bswBorrowAmount); + await vToken.connect(acc2Signer).borrow(borrowAmount); + totalCashNew = await vToken.getCash(); + + expect(totalCashOld.sub(totalCashNew)).equals(borrowAmount); // MINE 300000 BLOCKS await mine(300000); // Save states just before accruing interests - const accrualBlockNumberPrior = await vUSDD.accrualBlockNumber(); - const borrowRatePrior = await vUSDD.borrowRatePerBlock(); - const totalBorrowsPrior = await vUSDD.totalBorrows(); + const accrualBlockNumberPrior = await vToken.accrualBlockNumber(); + const borrowRatePrior = await vToken.borrowRatePerBlock(); + const totalBorrowsPrior = await vToken.totalBorrows(); + const reserveBefore = await vToken.totalReserves(); + const psrBalancePrior = await token.balanceOf(PSR); - await vUSDD.accrueInterest(); + await vToken.accrueInterest(); // Calculation of reserves - const currBlock = await ethers.provider.getBlockNumber(); - const blockDelta = currBlock - accrualBlockNumberPrior; + let currBlockOrTimestamp = await ethers.provider.getBlockNumber(); + + if (FORKED_NETWORK == "arbitrumsepolia" || FORKED_NETWORK == "arbitrumone") { + currBlockOrTimestamp = (await ethers.provider.getBlock("latest")).timestamp; + } + const blockDelta = BigNumber.from(currBlockOrTimestamp).sub(BigNumber.from(accrualBlockNumberPrior)); const simpleInterestFactor = borrowRatePrior.mul(blockDelta); const interestAccumulated = simpleInterestFactor.mul(totalBorrowsPrior).div(convertToUnit(1, 18)); - const reserveFactorMantissa = await vUSDD.reserveFactorMantissa(); + const reserveFactorMantissa = await vToken.reserveFactorMantissa(); const totalReservesExpected = reserveFactorMantissa.mul(interestAccumulated).div(convertToUnit(1, 18)); - let totalReservesCurrent = await vUSDD.totalReserves(); + const psrBalanceNew = await token.balanceOf(PSR); + + const psrBalanceDiff = psrBalanceNew.sub(psrBalancePrior); + let totalReservesCurrent = BigNumber.from((await vToken.totalReserves()).add(psrBalanceDiff)).sub(reserveBefore); + expect(totalReservesExpected).equals(totalReservesCurrent); // Calculation of exchange rate - let exchangeRateStored = await vUSDD.exchangeRateStored(); - totalCashNew = await vUSDD.getCash(); - let totalSupply = await vUSDD.totalSupply(); - let badDebt = await vUSDD.badDebt(); - let totalBorrowCurrent = await vUSDD.totalBorrows(); - let cashPlusBorrowsMinusReserves = totalCashNew.add(totalBorrowCurrent).add(badDebt).sub(totalReservesCurrent); + let exchangeRateStored = await vToken.exchangeRateStored(); + totalCashNew = await vToken.getCash(); + let totalSupply = await vToken.totalSupply(); + let badDebt = await vToken.badDebt(); + let totalBorrowCurrent = await vToken.totalBorrows(); + let cashPlusBorrowsMinusReserves = totalCashNew + .add(totalBorrowCurrent) + .add(badDebt) + .sub(await vToken.totalReserves()); let exchangeRateExpected = cashPlusBorrowsMinusReserves.mul(convertToUnit(1, 18)).div(totalSupply); + expect(exchangeRateExpected).equals(exchangeRateStored); // Reduce reserves - totalCashOld = await vUSDD.getCash(); - const totalReservesOld = await vUSDD.totalReserves(); + await vToken.accrueInterest(); + totalCashOld = await vToken.getCash(); + const totalReservesOld = await vToken.totalReserves(); const reduceAmount = totalReservesOld.mul(50).div(100); - const protocolShareBalanceOld = await usdd.balanceOf(PROTOCOL_SHARE_RESERVE); + const protocolShareBalanceOld = await token.balanceOf(PSR); + + const psrBalanceBefore = await token.balanceOf(PSR); + await vToken.reduceReserves(reduceAmount); + const psrBalanceAfter = await token.balanceOf(PSR); - await vUSDD.reduceReserves(reduceAmount); - totalReservesCurrent = await vUSDD.totalReserves(); - expect(totalReservesOld.sub(totalReservesCurrent)).to.closeTo(reduceAmount, parseUnits("0.0000002", 18)); + totalReservesCurrent = await vToken.totalReserves(); + expect(psrBalanceAfter.sub(psrBalanceBefore)).to.be.equal(reduceAmount); - const protocolShareBalanceNew = await usdd.balanceOf(PROTOCOL_SHARE_RESERVE); + const protocolShareBalanceNew = await token.balanceOf(PSR); expect(protocolShareBalanceNew.sub(protocolShareBalanceOld)).equals(reduceAmount); - totalCashNew = await vUSDD.getCash(); + totalCashNew = await vToken.getCash(); expect(totalCashOld.sub(totalCashNew)).equals(reduceAmount); - exchangeRateStored = await vUSDD.exchangeRateStored(); - totalCashNew = await vUSDD.getCash(); - totalSupply = await vUSDD.totalSupply(); - badDebt = await vUSDD.badDebt(); - totalBorrowCurrent = await vUSDD.totalBorrows(); + exchangeRateStored = await vToken.exchangeRateStored(); + totalCashNew = await vToken.getCash(); + totalSupply = await vToken.totalSupply(); + badDebt = await vToken.badDebt(); + totalBorrowCurrent = await vToken.totalBorrows(); cashPlusBorrowsMinusReserves = totalCashNew.add(totalBorrowCurrent).add(badDebt).sub(totalReservesCurrent); exchangeRateExpected = cashPlusBorrowsMinusReserves.mul(convertToUnit(1, 18)).div(totalSupply); expect(exchangeRateExpected).equals(exchangeRateStored); diff --git a/tests/hardhat/Fork/supply.ts b/tests/hardhat/Fork/supply.ts index c8f89e3f3..bc3138c5c 100644 --- a/tests/hardhat/Fork/supply.ts +++ b/tests/hardhat/Fork/supply.ts @@ -1,9 +1,8 @@ import { smock } from "@defi-wonderland/smock"; import { mine } from "@nomicfoundation/hardhat-network-helpers"; -import BigNumber from "bignumber.js"; import chai from "chai"; -import { Signer } from "ethers"; -import { ethers } from "hardhat"; +import { BigNumber, Signer } from "ethers"; +import { ethers, upgrades } from "hardhat"; import { convertToUnit } from "../../../helpers/utils"; import { @@ -13,261 +12,297 @@ import { ChainlinkOracle__factory, Comptroller, Comptroller__factory, - FaucetToken, - FaucetToken__factory, - MockToken, - MockToken__factory, + IERC20, + IERC20__factory, + MockPriceOracle, + MockPriceOracle__factory, VToken, VToken__factory, + WrappedNative, + WrappedNative__factory, } from "../../../typechain"; -import { initMainnetUser, setForkBlock } from "./utils"; +import { getContractAddresses, initMainnetUser, setForkBlock } from "./utils"; const { expect } = chai; chai.use(smock.matchers); -const FORK_TESTNET = process.env.FORK === "true" && process.env.FORKED_NETWORK === "bsctestnet"; - -const ADMIN = "0xce10739590001705f7ff231611ba4a48b2820327"; -const ORACLE_ADMIN = "0xce10739590001705F7FF231611ba4A48B2820327"; -const ACM = "0x45f8a08F534f34A97187626E05d4b6648Eeaa9AA"; -const ORACLE = "0xCeA29f1266e880A1482c06eD656cD08C148BaA32"; -const acc1 = "0xe70898180a366F204AA529708fB8f5052ea5723c"; -const acc2 = "0xA4a04C2D661bB514bB8B478CaCB61145894563ef"; -const acc3 = "0x394d1d517e8269596a7E4Cd1DdaC1C928B3bD8b3"; -const USDD = "0x2E2466e22FcbE0732Be385ee2FBb9C59a1098382"; -const USDT = "0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c"; -const COMPTROLLER = "0x10b57706AD2345e590c2eA4DC02faef0d9f5b08B"; -const VUSDD = "0x899dDf81DfbbF5889a16D075c352F2b959Dd24A4"; -const VUSDT = "0x3338988d0beb4419Acb8fE624218754053362D06"; - -let impersonatedTimelock: Signer; -let impersonatedOracleOwner: Signer; -let accessControlManager: AccessControlManager; -let priceOracle: ChainlinkOracle; +const FORK = process.env.FORK === "true"; +const FORKED_NETWORK = process.env.FORKED_NETWORK || "bscmainnet"; + +const { + ACM, + ACC1, + ACC2, + ACC3, + ADMIN, + TOKEN1, + TOKEN2, + VTOKEN1, + VTOKEN2, + COMPTROLLER, + TOKEN1_HOLDER, + TOKEN2_HOLDER, + RESILIENT_ORACLE, + CHAINLINK_ORACLE, + BLOCK_NUMBER, +} = getContractAddresses(FORKED_NETWORK as string); + +const AddressZero = "0x0000000000000000000000000000000000000000"; + +let token1: IERC20 | WrappedNative; +let token2: IERC20; +let vTOKEN1: VToken; +let vTOKEN2: VToken; let comptroller: Comptroller; -let vUSDD: VToken; -let vUSDT: VToken; -let usdd: MockToken; -let usdt: FaucetToken; let acc1Signer: Signer; let acc2Signer: Signer; let acc3Signer: Signer; +let token1Holder: Signer; +let token2Holder: Signer; +let impersonatedTimelock: Signer; +let resilientOracle: MockPriceOracle; +let chainlinkOracle: ChainlinkOracle; +let accessControlManager: AccessControlManager; -const blocksToMint: number = 300000; -const usdtBorrowAmount = convertToUnit("1", 4); +const blocksToMine: number = 30000; +const TOKEN2BorrowAmount = convertToUnit("1", 17); async function configureTimelock() { impersonatedTimelock = await initMainnetUser(ADMIN, ethers.utils.parseUnits("2")); - impersonatedOracleOwner = await initMainnetUser(ORACLE_ADMIN, ethers.utils.parseUnits("2")); -} - -async function configureVToken(vTokenAddress: string) { - return VToken__factory.connect(vTokenAddress, impersonatedTimelock); } async function grantPermissions() { accessControlManager = AccessControlManager__factory.connect(ACM, impersonatedTimelock); - let tx = await accessControlManager .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setMarketSupplyCaps(address[],uint256[])", ADMIN); + .giveCallPermission(chainlinkOracle.address, "setDirectPrice(address,uint256)", ADMIN); await tx.wait(); tx = await accessControlManager .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setMarketBorrowCaps(address[],uint256[])", ADMIN); + .giveCallPermission(chainlinkOracle.address, "setTokenConfig(TokenConfig)", ADMIN); await tx.wait(); - - tx = await accessControlManager - .connect(impersonatedTimelock) - .giveCallPermission(ORACLE, "setDirectPrice(address,uint256)", ADMIN); - await tx.wait(); - - tx = await accessControlManager - .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setMinLiquidatableCollateral(uint256)", ADMIN); - await tx.wait(); - - tx = await accessControlManager - .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setCollateralFactor(address,uint256,uint256)", ADMIN); } -if (FORK_TESTNET) { +if (FORK) { describe("Supply fork tests", async () => { async function setup() { - await setForkBlock(30913473); + await setForkBlock(BLOCK_NUMBER); await configureTimelock(); - - acc1Signer = await initMainnetUser(acc1, ethers.utils.parseUnits("2")); - acc2Signer = await initMainnetUser(acc2, ethers.utils.parseUnits("2")); - acc3Signer = await initMainnetUser(acc3, ethers.utils.parseUnits("2")); - - usdt = FaucetToken__factory.connect(USDT, impersonatedTimelock); - usdd = MockToken__factory.connect(USDD, impersonatedTimelock); - vUSDT = await configureVToken(VUSDT); - vUSDD = await configureVToken(VUSDD); + acc1Signer = await initMainnetUser(ACC1, ethers.utils.parseUnits("2")); + acc2Signer = await initMainnetUser(ACC2, ethers.utils.parseUnits("2")); + acc3Signer = await initMainnetUser(ACC3, ethers.utils.parseUnits("2")); + token1Holder = await initMainnetUser(TOKEN1_HOLDER, ethers.utils.parseUnits("2")); + token2Holder = await initMainnetUser(TOKEN2_HOLDER, ethers.utils.parseUnits("2000000")); + + vTOKEN2 = VToken__factory.connect(VTOKEN2, impersonatedTimelock); + vTOKEN1 = VToken__factory.connect(VTOKEN1, impersonatedTimelock); comptroller = Comptroller__factory.connect(COMPTROLLER, impersonatedTimelock); - priceOracle = ChainlinkOracle__factory.connect(ORACLE, impersonatedOracleOwner); + token2 = IERC20__factory.connect(TOKEN2, impersonatedTimelock); + token1 = IERC20__factory.connect(TOKEN1, impersonatedTimelock); + if (FORKED_NETWORK == "arbitrumsepolia" || FORKED_NETWORK == "arbitrumone") { + token1 = WrappedNative__factory.connect(TOKEN1, impersonatedTimelock); + await token1.connect(token1Holder).deposit({ value: convertToUnit("200000", 18) }); + } + if (FORKED_NETWORK == "opbnbmainnet" || FORKED_NETWORK == "opbnbtestnet") { + const chainlinkOracleFactory = await ethers.getContractFactory("ChainlinkOracle"); + chainlinkOracle = await upgrades.deployProxy(chainlinkOracleFactory, [ACM], { impersonatedTimelock }); + } else { + chainlinkOracle = ChainlinkOracle__factory.connect(CHAINLINK_ORACLE, impersonatedTimelock); + } await grantPermissions(); + const tupleForToken2 = { + asset: TOKEN2, + oracles: [chainlinkOracle.address, AddressZero, AddressZero], + enableFlagsForOracles: [true, false, false], + }; + + const tupleForToken1 = { + asset: TOKEN1, + oracles: [chainlinkOracle.address, AddressZero, AddressZero], + enableFlagsForOracles: [true, false, false], + }; + + resilientOracle = MockPriceOracle__factory.connect(RESILIENT_ORACLE, impersonatedTimelock); + await resilientOracle.setTokenConfig(tupleForToken2); + await resilientOracle.setTokenConfig(tupleForToken1); + + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, convertToUnit("1", 18)); + await comptroller.setMarketSupplyCaps( - [vUSDT.address, vUSDD.address], + [vTOKEN2.address, vTOKEN1.address], [convertToUnit(1, 50), convertToUnit(1, 50)], ); await comptroller.setMarketBorrowCaps( - [vUSDT.address, vUSDD.address], + [vTOKEN2.address, vTOKEN1.address], [convertToUnit(1, 50), convertToUnit(1, 50)], ); - await comptroller.connect(acc1Signer).enterMarkets([vUSDT.address]); - await comptroller.connect(acc2Signer).enterMarkets([vUSDT.address, vUSDD.address]); - await comptroller.connect(acc3Signer).enterMarkets([vUSDD.address]); + await comptroller.connect(acc1Signer).enterMarkets([vTOKEN2.address]); + await comptroller.connect(acc2Signer).enterMarkets([vTOKEN2.address, vTOKEN1.address]); + await comptroller.connect(acc3Signer).enterMarkets([vTOKEN1.address]); } beforeEach(async () => { await setup(); - await priceOracle.setDirectPrice(usdt.address, convertToUnit("1", 15)); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token2.address, convertToUnit("1", 15)); }); const calculateExchangeRate = async () => { - const cash = await vUSDT.getCash(); - const borrows = await vUSDT.totalBorrows(); - const badDebt = await vUSDT.badDebt(); - const reserves = await vUSDT.totalReserves(); - const supply = await vUSDT.totalSupply(); - const exchangeRatecal = new BigNumber(Number(cash) + Number(borrows) + Number(badDebt) - Number(reserves)) - .multipliedBy(Number(convertToUnit(1, 18))) - .toFixed(0); + const cash = await vTOKEN2.getCash(); + const borrows = await vTOKEN2.totalBorrows(); + const badDebt = await vTOKEN2.badDebt(); + const reserves = await vTOKEN2.totalReserves(); + const supply = await vTOKEN2.totalSupply(); if (Number(supply) == 0) { - return await vUSDT.exchangeRateStored(); + return await vTOKEN2.exchangeRateStored(); } - return new BigNumber(exchangeRatecal).dividedBy(Number(supply)).toFixed(0); + const exchangeRatecal = BigNumber.from(cash) + .add(BigNumber.from(borrows)) + .add(BigNumber.from(badDebt)) + .sub(BigNumber.from(reserves)) + .mul(BigNumber.from(convertToUnit(1, 18))); + + return BigNumber.from(exchangeRatecal).div(BigNumber.from(supply)); }; const assertExchangeRate = async () => { - const exchangeRate = await vUSDT.callStatic.exchangeRateCurrent(); + await vTOKEN2.accrueInterest(); + const exchangeRate = await vTOKEN2.exchangeRateStored(); const calculatedRate = await calculateExchangeRate(); - expect(exchangeRate).closeTo(calculatedRate, 1); + expect(exchangeRate).equals(calculatedRate); }; const assertRedeemAmount = async (accountBalance, balanceBefore) => { - const balanceAfter = await usdt.balanceOf(acc1); - - const exchangeRate = await vUSDT.callStatic.exchangeRateCurrent(); - const expectedRedeemAmount = new BigNumber(Number(accountBalance)) - .multipliedBy(Number(exchangeRate)) - .dividedBy(Number(convertToUnit(1, 18))) - .plus(Number(balanceBefore)) - .toFixed(0); - expect(expectedRedeemAmount).closeTo(balanceAfter, 10); + const balanceAfter = await token2.balanceOf(ACC1); + const exchangeRate = await vTOKEN2.callStatic.exchangeRateCurrent(); + const expectedRedeemAmount = BigNumber.from(accountBalance) + .mul(BigNumber.from(exchangeRate)) + .div(BigNumber.from(convertToUnit(1, 18))) + .add(BigNumber.from(balanceBefore)); + expect(expectedRedeemAmount).closeTo(balanceAfter, 120); }; it("Evolution of exchange rate", async () => { - const mintAmount = convertToUnit("1", 17); - // Accural all the interest till latest block - await vUSDT.accrueInterest(); + const mintAmount = convertToUnit("1", 18); + await vTOKEN2.accrueInterest(); // Assert current exchange rate await assertExchangeRate(); + // Mint vTOKEN2 with first account(ACC1) + await token2.connect(token2Holder).transfer(ACC1, convertToUnit(2, 18)); + await token2.connect(acc1Signer).approve(vTOKEN2.address, convertToUnit(2, 18)); + await expect(vTOKEN2.connect(acc1Signer).mint(convertToUnit(1, 18))).to.emit(vTOKEN2, "Mint"); - // Mint vUSDT with first account(acc1) - await usdt.connect(acc1Signer).allocateTo(acc1, convertToUnit(2, 18)); - await usdt.connect(acc1Signer).approve(vUSDT.address, convertToUnit(2, 18)); - await expect(vUSDT.connect(acc1Signer).mint(convertToUnit(1, 18))).to.emit(vUSDT, "Mint"); - - // Mine 300,000 blocks - await mine(blocksToMint); + // Mining 300,000 blocks + await mine(300000); // Assert current exchange rate await assertExchangeRate(); - // Set oracle price for usdt - await priceOracle.setDirectPrice(usdt.address, convertToUnit("1", 15)); + // Set oracle price for TOKEN2 + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token2.address, convertToUnit("1", 15)); + + let [err, liquidity, shortfall] = await comptroller.getAccountLiquidity(ACC2); - // Mint vUSDD with second account(acc2) - await usdd.connect(acc2Signer).faucet(mintAmount); - await usdd.connect(acc2Signer).approve(vUSDD.address, mintAmount); - await expect(vUSDD.connect(acc2Signer).mint(mintAmount)).to.emit(vUSDD, "Mint"); + // Mint vTOKEN1 with second account(ACC2) + await token1.connect(token1Holder).transfer(ACC2, mintAmount); + await token1.connect(acc2Signer).approve(vTOKEN1.address, mintAmount); + await expect(vTOKEN1.connect(acc2Signer).mint(mintAmount)).to.emit(vTOKEN1, "Mint"); - // Borrow usdt with second account(acc2) - await expect(vUSDT.connect(acc2Signer).borrow(usdtBorrowAmount)).to.be.emit(vUSDT, "Borrow"); + // Borrow TOKEN2 with second account(ACC2) + await expect(vTOKEN2.connect(acc2Signer).borrow(TOKEN2BorrowAmount)).to.be.emit(vTOKEN2, "Borrow"); - // Mine 300,000 blocks - await mine(blocksToMint); + // Mine 30,000 blocks + await mine(blocksToMine); // Accural all the interest till latest block - await vUSDT.accrueInterest(); + await vTOKEN2.accrueInterest(); // Assert current exchange rate await assertExchangeRate(); - await usdt.connect(acc2Signer).approve(vUSDT.address, convertToUnit(1, 5)); - await vUSDT.connect(acc2Signer).repayBorrow(usdtBorrowAmount); + await token2.connect(acc2Signer).approve(vTOKEN2.address, convertToUnit(1, 18)); - // Mine 300,000 blocks - await mine(blocksToMint); + await vTOKEN2.connect(acc2Signer).repayBorrow(TOKEN2BorrowAmount); + + // Mine 30,000 blocks + await mine(blocksToMine); // Accural all the interest till latest block - await vUSDT.accrueInterest(); + await vTOKEN2.accrueInterest(); // Assert current exchange rate await assertExchangeRate(); - // setup to liquidate the second account(acc2) with first account(acc1) + // setup to liquidate the second account(ACC2) with first account(ACC1) await comptroller.setMinLiquidatableCollateral(0); - await expect(vUSDT.connect(acc2Signer).borrow(usdtBorrowAmount)).to.be.emit(vUSDT, "Borrow"); - await priceOracle.setDirectPrice(usdd.address, convertToUnit("1.05", 14)); + const tuple1 = { + asset: TOKEN1, + feed: chainlinkOracle.address, + maxStalePeriod: "9000000000000000000", + }; + const tuple2 = { + asset: TOKEN2, + feed: chainlinkOracle.address, + maxStalePeriod: "9000000000000000000", + }; + + await chainlinkOracle.connect(impersonatedTimelock).setTokenConfig(tuple1); + await chainlinkOracle.connect(impersonatedTimelock).setTokenConfig(tuple2); + + await expect(vTOKEN2.connect(acc2Signer).borrow(TOKEN2BorrowAmount)).to.be.emit(vTOKEN2, "Borrow"); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, convertToUnit("1.05", 14)); + + [err, liquidity, shortfall] = await comptroller.getAccountLiquidity(ACC2); - const [err, liquidity, shortfall] = await comptroller.callStatic.getAccountLiquidity(acc2); expect(err).equals(0); expect(liquidity).equals(0); expect(shortfall).greaterThan(0); - const borrowBalance = (await vUSDT.borrowBalanceStored(acc2)).toString(); + const borrowBalance = (await vTOKEN2.borrowBalanceStored(ACC2)).toString(); const closeFactor = (await comptroller.closeFactorMantissa()).toString(); - const maxClose = new BigNumber(borrowBalance).multipliedBy(closeFactor).dividedBy(2e18).toFixed(0); - let result = vUSDT.connect(acc1Signer).liquidateBorrow(acc2, maxClose, vUSDD.address); - await expect(result).to.emit(vUSDT, "LiquidateBorrow"); + const maxClose = BigInt(BigInt(borrowBalance) * BigInt(closeFactor)) / BigInt(2e18); + const result = vTOKEN2.connect(acc1Signer).liquidateBorrow(ACC2, maxClose, vTOKEN1.address); + await expect(result).to.emit(vTOKEN2, "LiquidateBorrow"); - // Mine 300,000 blocks - await mine(blocksToMint); + // Mine 30,000 blocks + await mine(blocksToMine); // Accural all the interest till latest block - await vUSDT.accrueInterest(); + await vTOKEN2.accrueInterest(); // Assert current exchange rate await assertExchangeRate(); - // Setup for healAccount(acc2) - await priceOracle.setDirectPrice(usdd.address, convertToUnit(1, 10)); - await priceOracle.setDirectPrice(usdt.address, convertToUnit(1, 18)); + // Setup for healAccount(ACC2) - const [err2, liquidity2, shortfall2] = await comptroller.callStatic.getAccountLiquidity(acc2); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, convertToUnit(1, 10)); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token2.address, convertToUnit(1, 18)); + + const [err2, liquidity2, shortfall2] = await comptroller.getAccountLiquidity(ACC2); expect(err2).equals(0); expect(liquidity2).equals(0); expect(shortfall2).greaterThan(0); await comptroller.setMinLiquidatableCollateral(convertToUnit(1, 22)); - result = comptroller.connect(acc1Signer).healAccount(acc2); - await expect(result).to.emit(vUSDT, "RepayBorrow"); - // Accural all the interest till latest block - await vUSDT.accrueInterest(); + await vTOKEN2.accrueInterest(); // Assert current exchange rate await assertExchangeRate(); - const totalBal = await vUSDT.balanceOf(acc1); - await expect(vUSDT.connect(acc1Signer).redeem(totalBal)).to.emit(vUSDT, "Redeem"); + const totalBal = await vTOKEN2.balanceOf(ACC1); + await expect(vTOKEN2.connect(acc1Signer).redeem(totalBal)).to.emit(vTOKEN2, "Redeem"); - // Mine 300,000 blocks - await mine(blocksToMint); + // Mine 30,000 blocks + await mine(blocksToMine); // Accural all the interest till latest block - await vUSDT.accrueInterest(); + await vTOKEN2.accrueInterest(); // Assert current exchange rate await assertExchangeRate(); @@ -275,41 +310,41 @@ if (FORK_TESTNET) { it("Three users Mint, one redeems", async () => { const mintAmount = convertToUnit("1", 18); - // Mint vUSDT with first account(acc1) - await usdt.connect(acc1Signer).allocateTo(acc1, convertToUnit(2, 18)); - await usdt.connect(acc1Signer).approve(vUSDT.address, convertToUnit(2, 18)); - await expect(vUSDT.connect(acc1Signer).mint(convertToUnit(1, 18))).to.emit(vUSDT, "Mint"); + // Mint vTOKEN2 with first account(ACC1) + await token2.connect(token2Holder).transfer(ACC1, convertToUnit(2, 18)); + await token2.connect(acc1Signer).approve(vTOKEN2.address, convertToUnit(2, 18)); + await expect(vTOKEN2.connect(acc1Signer).mint(convertToUnit(1, 18))).to.emit(vTOKEN2, "Mint"); - // Mint vUSDT with second account(acc2) - await usdt.connect(acc2Signer).allocateTo(acc2, convertToUnit(2, 18)); - await usdt.connect(acc2Signer).approve(vUSDT.address, convertToUnit(2, 18)); - await expect(vUSDT.connect(acc2Signer).mint(convertToUnit(1, 18))).to.emit(vUSDT, "Mint"); + // Mint vTOKEN2 with second account(ACC2) + await token2.connect(token2Holder).transfer(ACC2, convertToUnit(2, 18)); + await token2.connect(acc2Signer).approve(vTOKEN2.address, convertToUnit(2, 18)); + await expect(vTOKEN2.connect(acc2Signer).mint(convertToUnit(1, 18))).to.emit(vTOKEN2, "Mint"); - // Mint vUSDD with second account(acc2) - await usdd.connect(acc3Signer).faucet(mintAmount); - await usdd.connect(acc3Signer).approve(vUSDD.address, mintAmount); - await expect(vUSDD.connect(acc3Signer).mint(mintAmount)).to.emit(vUSDD, "Mint"); + // Mint vTOKEN1 with second account(ACC2) + await token1.connect(token1Holder).transfer(ACC3, mintAmount); + await token1.connect(acc3Signer).approve(vTOKEN1.address, mintAmount); + await expect(vTOKEN1.connect(acc3Signer).mint(mintAmount)).to.emit(vTOKEN1, "Mint"); - // Borrow usdt with third account(acc3) - await expect(vUSDT.connect(acc3Signer).borrow(usdtBorrowAmount)).to.be.emit(vUSDT, "Borrow"); + // Borrow TOKEN2 with third account(ACC3) + await expect(vTOKEN2.connect(acc3Signer).borrow(TOKEN2BorrowAmount)).to.be.emit(vTOKEN2, "Borrow"); - // Mine 300,000 blocks - await mine(blocksToMint); + // Mine 30,000 blocks + await mine(blocksToMine); - // Partial redeem for first account(acc1) - let balanceBefore = await usdt.balanceOf(acc1); - await vUSDT.connect(acc1Signer).redeem(convertToUnit(5, 17)); + // Partial redeem for first account(ACC1) + let balanceBefore = await token2.balanceOf(ACC1); + await vTOKEN2.connect(acc1Signer).redeem(convertToUnit(5, 7)); // Assert undelying after partial redeem - await assertRedeemAmount(convertToUnit(5, 17), balanceBefore); + await assertRedeemAmount(convertToUnit(5, 7), balanceBefore); - // Mine 300,000 blocks - await mine(blocksToMint); + // Mine 30,000 blocks + await mine(blocksToMine); - // Complete redeem for first account(acc1) - const accountBalance = await vUSDT.balanceOf(acc1); - balanceBefore = await usdt.balanceOf(acc1); - await vUSDT.connect(acc1Signer).redeem(accountBalance); + // Complete redeem for first account(ACC1) + const accountBalance = await vTOKEN2.balanceOf(ACC1); + balanceBefore = await token2.balanceOf(ACC1); + await vTOKEN2.connect(acc1Signer).redeem(accountBalance); // Assert undelying after complete redeem await assertRedeemAmount(accountBalance, balanceBefore); diff --git a/tests/hardhat/Fork/utils.ts b/tests/hardhat/Fork/utils.ts index 7b0760760..1ef8a4a2a 100644 --- a/tests/hardhat/Fork/utils.ts +++ b/tests/hardhat/Fork/utils.ts @@ -2,6 +2,12 @@ import { impersonateAccount, setBalance } from "@nomicfoundation/hardhat-network import { NumberLike } from "@nomicfoundation/hardhat-network-helpers/dist/src/types"; import { ethers, network } from "hardhat"; +import { contractAddresses } from "./constants"; + +export function getContractAddresses(name: string) { + return contractAddresses[name]; +} + export const forking = (blockNumber: number, fn: () => void) => { describe(`At block #${blockNumber}`, () => { before(async () => { diff --git a/yarn.lock b/yarn.lock index 572f8c496..cc1a0217d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3131,17 +3131,6 @@ __metadata: languageName: node linkType: hard -"@venusprotocol/governance-contracts@npm:^2.0.0": - version: 2.0.0 - resolution: "@venusprotocol/governance-contracts@npm:2.0.0" - dependencies: - "@venusprotocol/solidity-utilities": 2.0.0 - hardhat-deploy-ethers: ^0.3.0-beta.13 - module-alias: ^2.2.2 - checksum: 18b56d951c4e68fa1edadc93ed44daa55c8b81294778a4969d940a084de6d949630eacd4702d1b92f04ad5d709963a3a0a790014871ec34b0b2f4806cebc731c - languageName: node - linkType: hard - "@venusprotocol/isolated-pools@npm:^2.3.0": version: 2.8.0 resolution: "@venusprotocol/isolated-pools@npm:2.8.0" @@ -3187,7 +3176,7 @@ __metadata: "@typescript-eslint/eslint-plugin": ^5.27.1 "@typescript-eslint/parser": ^5.27.1 "@venusprotocol/governance-contracts": 2.1.0 - "@venusprotocol/oracle": 2.0.0 + "@venusprotocol/oracle": 2.3.0 "@venusprotocol/protocol-reserve": 2.2.0 "@venusprotocol/solidity-utilities": ^2.0.0 "@venusprotocol/venus-protocol": 9.0.0 @@ -3220,16 +3209,16 @@ __metadata: languageName: unknown linkType: soft -"@venusprotocol/oracle@npm:2.0.0": - version: 2.0.0 - resolution: "@venusprotocol/oracle@npm:2.0.0" +"@venusprotocol/oracle@npm:2.3.0": + version: 2.3.0 + resolution: "@venusprotocol/oracle@npm:2.3.0" dependencies: "@chainlink/contracts": ^0.5.1 "@defi-wonderland/smock": ^2.3.4 "@nomicfoundation/hardhat-network-helpers": ^1.0.8 "@openzeppelin/contracts": ^4.6.0 "@openzeppelin/contracts-upgradeable": ^4.7.3 - "@venusprotocol/governance-contracts": ^2.0.0 + "@venusprotocol/governance-contracts": ^2.1.0 "@venusprotocol/solidity-utilities": ^2.0.0 "@venusprotocol/venus-protocol": ^6.0.0 ethers: ^5.6.8 @@ -3237,7 +3226,7 @@ __metadata: hardhat-deploy: ^0.11.14 module-alias: ^2.2.2 solidity-docgen: ^0.6.0-beta.29 - checksum: 00673550cdc63244eba2872b32253454a3b2062c2759075c68449b2923f2bb645e971d5ca0cbfd288bddd092d346a5f3e49d10bbb97d743fb33f9ece14179723 + checksum: 6ba484f72eef7701a92be1673ec5c3d7d180c79aa17c7b7ef0c3848fbf15b456e5cc7633bf0a1fbb4ff36ba05524b3f70f966e40c4f32f8b8cff551c6605878b languageName: node linkType: hard