diff --git a/tests/hardhat/Fork/RewardsForkTest.ts b/tests/hardhat/Fork/RewardsForkTest.ts index 5fba1eaf6..575020580 100644 --- a/tests/hardhat/Fork/RewardsForkTest.ts +++ b/tests/hardhat/Fork/RewardsForkTest.ts @@ -7,10 +7,9 @@ import { ethers } from "hardhat"; import { convertToUnit } from "../../../helpers/utils"; import { - AccessControlManager, - AccessControlManager__factory, BinanceOracle, BinanceOracle__factory, + ChainlinkOracle__factory, Comptroller, Comptroller__factory, IERC20, @@ -29,7 +28,6 @@ const FORK = process.env.FORK === "true"; const FORKED_NETWORK = process.env.FORKED_NETWORK || "bscmainnet"; const { - ACM, ACC1, ACC2, ADMIN, @@ -38,6 +36,7 @@ const { COMPTROLLER, TOKEN2_HOLDER, BINANCE_ORACLE, + CHAINLINK_ORACLE, REWARD_DISTRIBUTOR1, BLOCK_NUMBER, } = getContractAddresses(FORKED_NETWORK as string); @@ -53,10 +52,10 @@ let token2Holder: Signer; let comptrollerSigner: Signer; let impersonatedTimelock: Signer; let binanceOracle: BinanceOracle; +let chainlinkOracle: ChainlinkOracle; let mintAmount: BigNumberish; let bswBorrowAmount: BigNumberish; let rewardDistributor1: RewardsDistributor; -let accessControlManager: AccessControlManager; async function configureTimelock() { impersonatedTimelock = await initMainnetUser(ADMIN, ethers.utils.parseUnits("2")); @@ -66,20 +65,6 @@ 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) { describe("Rewards distributions", async () => { mintAmount = convertToUnit("10000", 18); @@ -99,18 +84,28 @@ if (FORK) { 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.setMarketSupplyCaps([vTOKEN2.address], [convertToUnit(1, 50)]); await comptroller.setMarketBorrowCaps([vTOKEN2.address], [convertToUnit(1, 50)]); - if (FORKED_NETWORK != "sepolia") { + 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); } + + 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: IERC20, vToken: VToken, amount: BigNumberish) { @@ -128,7 +123,10 @@ if (FORK) { 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); @@ -158,7 +156,10 @@ if (FORK) { .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); @@ -217,7 +218,7 @@ if (FORK) { // Reward1 calculations for user 1 let borrowerAccruedExpected = await computeBorrowRewards(rewardDistributor1, VTOKEN2, vTOKEN2, ACC1); let borrowerAccruedCurrent = await rewardDistributor1.rewardTokenAccrued(ACC1); - expect(borrowerAccruedExpected).to.closeTo(borrowerAccruedCurrent, parseUnits("0.000000000000000079", 18)); + expect(borrowerAccruedExpected).to.closeTo(borrowerAccruedCurrent, parseUnits("0.0000000000000079", 18)); // Repay const borrowBalanceStored = await vTOKEN2.borrowBalanceStored(ACC1); @@ -228,7 +229,7 @@ if (FORK) { // Reward1 calculations for user 1 borrowerAccruedExpected = await computeBorrowRewards(rewardDistributor1, VTOKEN2, vTOKEN2, ACC1); borrowerAccruedCurrent = await rewardDistributor1.rewardTokenAccrued(ACC1); - expect(borrowerAccruedExpected).to.closeTo(borrowerAccruedCurrent, parseUnits("0.000000000000000006", 18)); + expect(borrowerAccruedExpected).to.closeTo(borrowerAccruedCurrent, parseUnits("0.0000000000000006", 18)); }); }); } diff --git a/tests/hardhat/Fork/Shortfall.ts b/tests/hardhat/Fork/Shortfall.ts index ddd264cd2..98e79cde9 100644 --- a/tests/hardhat/Fork/Shortfall.ts +++ b/tests/hardhat/Fork/Shortfall.ts @@ -1,13 +1,12 @@ 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 } from "hardhat"; import { SignerWithAddress } from "hardhat-deploy-ethers/signers"; import { - AccessControlManager, - AccessControlManager__factory, ChainlinkOracle, ChainlinkOracle__factory, Comptroller, @@ -31,17 +30,16 @@ const FORK = process.env.FORK === "true"; const FORKED_NETWORK = process.env.FORKED_NETWORK || "bscmainnet"; const { - ACM, PSR, ADMIN, - TOKEN1, TOKEN2, - VTOKEN1, + USDT, + VUSDT, VTOKEN2, RISKFUND, SHORTFALL, COMPTROLLER, - TOKEN1_HOLDER, + USDT_HOLDER, TOKEN2_HOLDER, RESILIENT_ORACLE, CHAINLINK_ORACLE, @@ -64,38 +62,35 @@ let liquidator: SignerWithAddress; let impersonatedTimelock: SignerWithAddress; let resilientOracle: MockPriceOracle; let chainlinkOracle: ChainlinkOracle; -let accessControlManager: AccessControlManager; let protocolShareReserve: ProtocolShareReserve; const configureTimelock = async () => { impersonatedTimelock = await initMainnetUser(ADMIN, parseEther("2")); }; +let usdtDecimals: BigNumber; +let oldPoolAsetReserves: BigNumber; + const grabTokensTo = async (userAddress: string) => { const token2Holder = await initMainnetUser(TOKEN2_HOLDER, parseEther("2")); - const token1Holder = await initMainnetUser(TOKEN1_HOLDER, parseEther("2")); + const token1Holder = await initMainnetUser(USDT_HOLDER, parseEther("2")); - await token2.connect(token2Holder).transfer(userAddress, parseUnits("10000", 18)); - await token1.connect(token1Holder).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 () => { - accessControlManager = AccessControlManager__factory.connect(ACM, impersonatedTimelock); - await accessControlManager.giveCallPermission(RISKFUND, "setConvertibleBaseAsset(address)", ADMIN); - await accessControlManager.giveCallPermission(SHORTFALL, "updateMinimumPoolBadDebt(uint256)", ADMIN); - riskFund = RiskFund__factory.connect(RISKFUND, impersonatedTimelock); - await riskFund.connect(impersonatedTimelock).setConvertibleBaseAsset(TOKEN1); - protocolShareReserve = ProtocolShareReserve__factory.connect(PSR, impersonatedTimelock); shortfall = Shortfall__factory.connect(SHORTFALL, impersonatedTimelock); - await shortfall.updateMinimumPoolBadDebt(parseUnits("50", 18)); + await shortfall.updateMinimumPoolBadDebt(parseUnits("50", 18)); // -------------------------------------------- }; const setupTokens = async () => { token2 = await ethers.getContractAt("MockToken", TOKEN2); - token1 = await ethers.getContractAt("MockToken", TOKEN1); + token1 = await ethers.getContractAt("MockToken", USDT); + usdtDecimals = await token1.decimals(); await grabTokensTo(manager.address); await grabTokensTo(user1.address); @@ -104,6 +99,7 @@ const setupTokens = async () => { 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], @@ -119,18 +115,12 @@ const setupTokens = async () => { await resilientOracle.connect(impersonatedTimelock).setTokenConfig(tokenConfig); vTOKEN2 = await ethers.getContractAt("VToken", VTOKEN2); - vTOKEN1 = await ethers.getContractAt("VToken", VTOKEN1); - - await vTOKEN1.connect(impersonatedTimelock).setShortfallContract(SHORTFALL); - await vTOKEN2.connect(impersonatedTimelock).setShortfallContract(SHORTFALL); - await vTOKEN1.connect(impersonatedTimelock).setProtocolShareReserve(PSR); - await vTOKEN2.connect(impersonatedTimelock).setProtocolShareReserve(PSR); + vTOKEN1 = await ethers.getContractAt("VToken", VUSDT); }; const generateToken1BadDebt = async () => { - await chainlinkOracle.setDirectPrice(token2.address, parseUnits("100", 18)); const token2SupplyAmount = parseUnits("500", 18); - const token1BorrowAmount = parseUnits("100", 18); + const token1BorrowAmount = parseUnits("100", usdtDecimals); await token1.connect(manager).approve(vTOKEN1.address, token1BorrowAmount); await vTOKEN1.connect(manager).mint(token1BorrowAmount); @@ -143,14 +133,14 @@ const generateToken1BadDebt = async () => { await chainlinkOracle.setDirectPrice(token2.address, "1"); - await token1.connect(liquidator).approve(vTOKEN1.address, parseUnits("100", 18)); + await token1.connect(liquidator).approve(vTOKEN1.address, parseUnits("100", usdtDecimals)); await comptroller.connect(liquidator).healAccount(user1.address); // Restoring original price await chainlinkOracle.setDirectPrice(token2.address, parseUnits("100", 18)); const shortfallSigner = await initMainnetUser(shortfall.address, parseEther("1")); - const dust = (await vTOKEN1.badDebt()).sub(parseUnits("100", 18)); + const dust = (await vTOKEN1.badDebt()).sub(parseUnits("100", usdtDecimals)); await vTOKEN1.connect(shortfallSigner).badDebtRecovered(dust); }; @@ -172,11 +162,13 @@ const setup = async () => { await configureTimelock(); await setupRiskManagementContracts(); await setupTokens(); + 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", 18)); + await pretendRiskFundAccumulatedBaseAsset(parseUnits("100", usdtDecimals)); }; enum AuctionType { @@ -184,19 +176,21 @@ enum AuctionType { LARGE_RISK_FUND = 1, } -if (FORK) { +if (FORK && (FORKED_NETWORK === "bscmainnet" || FORKED_NETWORK === "bsctestnet")) { describe("Shortfall fork tests", async () => { - const token1BadDebt = parseUnits("100", 18); - const token2RiskFund = parseUnits("40", 18); - beforeEach(async () => { await loadFixture(setup); }); it("initializes as expected", async () => { - expect(await vTOKEN1.badDebt()).to.equal(token1BadDebt); + const newPoolAssetReserves = await riskFund.getPoolsBaseAssetReserves(COMPTROLLER); + + expect(await vTOKEN1.badDebt()).to.equal(parseUnits("100", usdtDecimals)); expect(await vTOKEN2.badDebt()).to.equal(0); - expect(await riskFund.getPoolsBaseAssetReserves(COMPTROLLER)).to.closeTo(token2RiskFund, parseUnits("2", 18)); + expect(newPoolAssetReserves.sub(oldPoolAsetReserves)).to.closeTo( + parseUnits("40", usdtDecimals), + parseUnits("5", usdtDecimals), + ); }); describe("startAuction", async () => { @@ -208,7 +202,7 @@ if (FORK) { }); it("starts a LARGE_RISK_FUND auction if risk fund reserve ($) > bad debt plus incentive ($)", async () => { - await pretendRiskFundAccumulatedBaseAsset(parseUnits("200", 18)); + await pretendRiskFundAccumulatedBaseAsset(parseUnits("200", usdtDecimals)); await shortfall.startAuction(COMPTROLLER); @@ -225,14 +219,17 @@ if (FORK) { const badDebtPlusIncentive = (await vTOKEN1.badDebt()) .mul(parseUnits("1.1", 18)) .mul(token1Price) - .div(parseUnits("1", 36)); - expect(badDebtPlusIncentive).to.equal(parseUnits("121", 18)); + .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)); - expect(riskFundReserveInUsd).to.closeTo(parseUnits("40", 18), parseUnits("2", 18)); + expect(riskFundReserveInUsd.sub(oldPoolAsetReserves)).to.closeTo( + parseUnits("40", usdtDecimals), + parseUnits("5", usdtDecimals), + ); await shortfall.startAuction(COMPTROLLER); diff --git a/tests/hardhat/Fork/borrowAndRepayTest.ts b/tests/hardhat/Fork/borrowAndRepayTest.ts index d721a2e34..8cfc13c6f 100644 --- a/tests/hardhat/Fork/borrowAndRepayTest.ts +++ b/tests/hardhat/Fork/borrowAndRepayTest.ts @@ -6,8 +6,6 @@ import { ethers } from "hardhat"; import { convertToUnit } from "../../../helpers/utils"; import { - AccessControlManager, - AccessControlManager__factory, BinanceOracle, BinanceOracle__factory, ChainlinkOracle__factory, @@ -19,6 +17,8 @@ import { ResilientOracleInterface__factory, VToken, VToken__factory, + WrappedNative, + WrappedNative__factory, } from "../../../typechain"; import { getContractAddresses, initMainnetUser, setForkBlock } from "./utils"; @@ -28,13 +28,12 @@ chai.use(smock.matchers); const FORK = process.env.FORK === "true"; const FORKED_NETWORK = process.env.FORKED_NETWORK || "bscmainnet"; -console.log("fork tests are running on network: ", FORKED_NETWORK); +if (FORK) console.log(`fork tests are running on ${FORKED_NETWORK}`); const { ACC1, ACC2, ADMIN, - ACM, TOKEN1, TOKEN2, VTOKEN1, @@ -47,7 +46,7 @@ const { BLOCK_NUMBER, } = getContractAddresses(FORKED_NETWORK as string); -let token1: IERC20; +let token1: IERC20 | WrappedNative; let token2: IERC20; let vTOKEN1: VToken; let vTOKEN2: VToken; @@ -61,7 +60,6 @@ let mintAmount: BigNumber; let TOKEN2BorrowAmount: BigNumberish; let binanceOracle: BinanceOracle; let priceOracle: ResilientOracleInterface; -let accessControlManager: AccessControlManager; async function configureTimelock() { impersonatedTimelock = await initMainnetUser(ADMIN, ethers.utils.parseUnits("2")); @@ -71,20 +69,6 @@ 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) { describe("Borrow and Repay", async () => { mintAmount = BigNumber.from(convertToUnit(1, 21)); @@ -97,16 +81,16 @@ if (FORK) { acc1Signer = await initMainnetUser(ACC1, ethers.utils.parseUnits("2")); acc2Signer = await initMainnetUser(ACC2, ethers.utils.parseUnits("2")); // it will be the depositor - token1Holder = await initMainnetUser(TOKEN1_HOLDER, ethers.utils.parseUnits("2")); 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("HAY", BigInt(150000000000000000)); + await binanceOracle.setMaxStalePeriod("lisUSD", BigInt(150000000000000000)); await binanceOracle.setMaxStalePeriod("USDD", BigInt(150000000000000000)); } - if (FORKED_NETWORK == "ethereum") { + 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); @@ -125,17 +109,20 @@ if (FORK) { await ChainlinkOracle.setTokenConfig(token2NewConfig); } - token2 = IERC20__factory.connect(TOKEN2, impersonatedTimelock); 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([vTOKEN1.address]); await comptroller.connect(acc2Signer).enterMarkets([vTOKEN1.address]); diff --git a/tests/hardhat/Fork/constants.ts b/tests/hardhat/Fork/constants.ts index 418eb5d77..6d9306665 100644 --- a/tests/hardhat/Fork/constants.ts +++ b/tests/hardhat/Fork/constants.ts @@ -57,7 +57,7 @@ export const contractAddresses = { VTOKEN2: EthereumContracts.VToken_vCRV_Curve.address, COMPTROLLER: EthereumContracts.Comptroller_Curve.address, PSR: PsrEthereum.contracts.ProtocolShareReserve.address, - REWARD_DISTRIBUTOR1: EthereumContracts.RewardsDistributor_Core_1.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, @@ -84,6 +84,9 @@ export const contractAddresses = { 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", @@ -105,10 +108,13 @@ export const contractAddresses = { 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: "0x09702Ea135d9D707DD51f530864f2B9220aAD87B", + TOKEN2_HOLDER: "0x0966602e47f6a3ca5692529f1d54ecd1d9b09175", ACC1: "0x3Ac99C7853b58f4AA38b309D372562a5A88bB9C1", ACC2: "0xA4a04C2D661bB514bB8B478CaCB61145894563ef", ACC3: "0x394d1d517e8269596a7E4Cd1DdaC1C928B3bD8b3", @@ -121,10 +127,8 @@ export const contractAddresses = { VTOKEN2: OpBnbTestnetContracts.VToken_vETH_Core.address, COMPTROLLER: OpBnbTestnetContracts.Comptroller_Core.address, PSR: PsrOpBnbTestnet.address, - REWARD_DISTRIBUTOR1: "", POOL_REGISTRY: OpBnbTestnetContracts.PoolRegistry.address, RESILIENT_ORACLE: OracleOpBnbTestnet.contracts.ResilientOracle.address, - CHAINLINK_ORACLE: "", BINANCE_ORACLE: OracleOpBnbTestnet.contracts.BinanceOracle.address, TOKEN1: "0x7Af23F9eA698E9b953D2BD70671173AaD0347f19", // BTCB TOKEN2: "0x94680e003861D43C6c0cf18333972312B6956FF1", // ETH @@ -143,10 +147,8 @@ export const contractAddresses = { VTOKEN2: OpBnbMainnetContracts.VToken_vFDUSD_Core.address, COMPTROLLER: OpBnbMainnetContracts.Comptroller_Core.address, PSR: "0xDDc9017F3073aa53a4A8535163b0bf7311F72C52", - REWARD_DISTRIBUTOR1: "", POOL_REGISTRY: OpBnbMainnetContracts.PoolRegistry.address, RESILIENT_ORACLE: OracleOpBnbMainnet.contracts.ResilientOracle.address, - CHAINLINK_ORACLE: "", BINANCE_ORACLE: OracleOpBnbMainnet.contracts.BinanceOracle.address, TOKEN1: "0x9e5AAC1Ba1a2e6aEd6b32689DFcF62A509Ca96f3", // USDT TOKEN2: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", // FDUSD @@ -160,21 +162,21 @@ export const contractAddresses = { arbitrumsepolia: { ADMIN: "0x1426A5Ae009c4443188DA8793751024E358A61C2", ACM: GovernanceArbSep.contracts.AccessControlManager.address, - VTOKEN1: ArbSepContracts.VToken_vARB_Core.address, - VTOKEN2: ArbSepContracts.VToken_vWETH_Core.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: "0x8E73FE3F7E29100Ad9d1C7F35fba2D2c823c8579", POOL_REGISTRY: ArbSepContracts.PoolRegistry.address, RESILIENT_ORACLE: OracleArbSep.contracts.ResilientOracle.address, CHAINLINK_ORACLE: OracleArbSep.contracts.ChainlinkOracle.address, - TOKEN1: ArbSepContracts.MockARB, // ARB - TOKEN2: "0x980B62Da83eFf3D4576C647993b0c1D7faf17c73", // WETH + TOKEN1: "0x980B62Da83eFf3D4576C647993b0c1D7faf17c73", // WETH + TOKEN2: ArbSepContracts.MockARB.address, // ARB TOKEN1_HOLDER: "0x02EB950C215D12d723b44a18CfF098C6E166C531", - TOKEN2_HOLDER: "0x980B62Da83eFf3D4576C647993b0c1D7faf17c73", - ACC1: "0x3Ac99C7853b58f4AA38b309D372562a5A88bB9C1", - ACC2: "0xA4a04C2D661bB514bB8B478CaCB61145894563ef", - ACC3: "0x394d1d517e8269596a7E4Cd1DdaC1C928B3bD8b3", - BLOCK_NUMBER: 38794894, + TOKEN2_HOLDER: "0x02EB950C215D12d723b44a18CfF098C6E166C531", + ACC1: "0xc7f050b6F465b876c764A866d6337EabBab08Cd4", + ACC2: "0xce0180B3B992649CBc3C8e1cF95b4A52Be9bA3AF", + ACC3: "0x13E0a421c17Ff1e7FFccFa05714957cF530b3aa4", + BLOCK_NUMBER: 40468900, }, }; diff --git a/tests/hardhat/Fork/liquidation.ts b/tests/hardhat/Fork/liquidation.ts index b62a0970f..338d19c5c 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 { @@ -18,6 +18,8 @@ import { MockPriceOracle__factory, VToken, VToken__factory, + WrappedNative, + WrappedNative__factory, } from "../../../typechain"; import { getContractAddresses, initMainnetUser, setForkBlock } from "./utils"; @@ -47,7 +49,7 @@ const { const AddressZero = "0x0000000000000000000000000000000000000000"; -let token1: IERC20; +let token1: IERC20 | WrappedNative; let token2: IERC20; let vTOKEN1: VToken; let vTOKEN2: VToken; @@ -57,7 +59,7 @@ let token2Holder: Signer; let acc1Signer: Signer; let acc2Signer: Signer; let impersonatedTimelock: Signer; -let priceOracle: ChainlinkOracle; +let chainlinkOracle: ChainlinkOracle; let resilientOracle: MockPriceOracle; let accessControlManager: AccessControlManager; @@ -72,33 +74,18 @@ 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 - .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setMarketBorrowCaps(address[],uint256[])", ADMIN); - await tx.wait(); - - tx = await accessControlManager + const tx = await accessControlManager .connect(impersonatedTimelock) - .giveCallPermission(CHAINLINK_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) { describe("Liquidation", async () => { async function setupBeforeEach(mintAmount: BigNumberish, token2BorrowAmount: BigNumberish) { await setup(); - await priceOracle.setDirectPrice(token1.address, "159990000000000000000"); - await priceOracle.setDirectPrice(token2.address, "208000000000000000"); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, "159990000000000000000"); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token2.address, "208000000000000000"); await token2.connect(token2Holder).transfer(ACC1, mintAmount); await token2.connect(acc1Signer).approve(vTOKEN2.address, mintAmount); @@ -120,35 +107,44 @@ if (FORK) { acc1Signer = await initMainnetUser(ACC1, ethers.utils.parseUnits("2")); acc2Signer = await initMainnetUser(ACC2, ethers.utils.parseUnits("2")); - token1Holder = await initMainnetUser(TOKEN1_HOLDER, ethers.utils.parseUnits("2")); token2Holder = await initMainnetUser(TOKEN2_HOLDER, ethers.utils.parseUnits("2")); + token1Holder = await initMainnetUser(TOKEN1_HOLDER, ethers.utils.parseUnits("2000000")); - token2 = IERC20__factory.connect(TOKEN2, impersonatedTimelock); - token1 = IERC20__factory.connect(TOKEN1, impersonatedTimelock); vTOKEN2 = await configureVToken(VTOKEN2); vTOKEN1 = await configureVToken(VTOKEN1); comptroller = Comptroller__factory.connect(COMPTROLLER, impersonatedTimelock); - priceOracle = ChainlinkOracle__factory.connect(CHAINLINK_ORACLE, impersonatedTimelock); + 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: [CHAINLINK_ORACLE, AddressZero, AddressZero], + const tupleForToken1 = { + asset: TOKEN1, + oracles: [chainlinkOracle.address, AddressZero, AddressZero], enableFlagsForOracles: [true, false, false], }; - const tupleForToken1 = { - asset: TOKEN1, - oracles: [CHAINLINK_ORACLE, AddressZero, AddressZero], + const tupleForToken2 = { + asset: TOKEN2, + 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 priceOracle.setDirectPrice(token1.address, convertToUnit("1", 18)); - await priceOracle.setDirectPrice(token2.address, convertToUnit("1", 18)); - - await grantPermissions(); + 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( [vTOKEN2.address, vTOKEN1.address], @@ -204,7 +200,7 @@ if (FORK) { it("Should revert when liquidation is called through vToken and trying to seize more tokens", async function () { await comptroller.setMinLiquidatableCollateral(0); - await priceOracle.setDirectPrice(token1.address, convertToUnit("1", 5)); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, convertToUnit("1", 5)); const borrowBalance = await vTOKEN2.borrowBalanceStored(ACC2); const closeFactor = await comptroller.closeFactorMantissa(); @@ -226,7 +222,7 @@ if (FORK) { await expect(vTOKEN1.connect(acc2Signer).mint(underlyingMintAmount)).to.emit(vTOKEN1, "Mint"); // price manipulation to put user underwater - await priceOracle.setDirectPrice(token1.address, convertToUnit("1", 5)); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, convertToUnit("1", 5)); const borrowBalance = await vTOKEN2.borrowBalanceStored(ACC2); const closeFactor = await comptroller.closeFactorMantissa(); @@ -241,7 +237,7 @@ if (FORK) { it("liquidate user", async () => { await comptroller.setMinLiquidatableCollateral(0); - await priceOracle.setDirectPrice(token1.address, convertToUnit("1", 6)); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, convertToUnit("1", 6)); const borrowBalance = await vTOKEN2.borrowBalanceStored(ACC2); const [err, liquidity, shortfall] = await comptroller.getAccountLiquidity(ACC2); @@ -259,8 +255,8 @@ if (FORK) { const closeFactor = await comptroller.closeFactorMantissa(); const maxClose = (borrowBalance * closeFactor) / 1e18; - const priceBorrowed = await priceOracle.getPrice(TOKEN2); - const priceCollateral = await priceOracle.getPrice(TOKEN1); + const priceBorrowed = await chainlinkOracle.getPrice(TOKEN2); + const priceCollateral = await chainlinkOracle.getPrice(TOKEN1); const liquidationIncentive = await comptroller.liquidationIncentiveMantissa(); const exchangeRateCollateralPrev = await vTOKEN1.callStatic.exchangeRateCurrent(); const num = (liquidationIncentive * priceBorrowed) / 1e18; @@ -315,7 +311,7 @@ if (FORK) { vTokenBorrowed: vTOKEN2.address, repayAmount: repayAmount, }; - await priceOracle.setDirectPrice(token1.address, convertToUnit("100", 12)); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, convertToUnit("100", 12)); await expect(comptroller.connect(acc1Signer).liquidateAccount(ACC2, [param])).to.be.revertedWithCustomError( comptroller, "InsufficientCollateral", @@ -328,8 +324,8 @@ if (FORK) { .setCollateralFactor(vTOKEN1.address, convertToUnit(7, 17), convertToUnit(8, 17)); await comptroller.connect(impersonatedTimelock).setLiquidationIncentive(convertToUnit(1, 18)); - await priceOracle.setDirectPrice(token1.address, convertToUnit("1", 12)); - await priceOracle.setDirectPrice(token2.address, convertToUnit("1", 12)); + 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); expect(err).equals(0); @@ -339,8 +335,8 @@ if (FORK) { const totalReservesToken1Prev = await vTOKEN1.totalReserves(); const vTOKEN1BalAcc1Prev = await vTOKEN1.balanceOf(ACC1); const vTOKEN1BalAcc2Prev = await vTOKEN1.balanceOf(ACC2); - const priceBorrowed = await priceOracle.getPrice(TOKEN2); - const priceCollateral = await priceOracle.getPrice(TOKEN1); + const priceBorrowed = await chainlinkOracle.getPrice(TOKEN2); + const priceCollateral = await chainlinkOracle.getPrice(TOKEN1); const liquidationIncentive = await comptroller.liquidationIncentiveMantissa(); const exchangeRateCollateralPrev = await vTOKEN1.callStatic.exchangeRateCurrent(); @@ -353,10 +349,13 @@ if (FORK) { // repayAmount will be calculated after accruing interest and then using borrowBalanceStored to get the repayAmount. const NetworkRespectiveRepayAmounts = { - bsctestnet: 1000000048189327, - sepolia: 1000000138102913, + bsctestnet: 1000000048189326, + sepolia: 1000000138102911, bscmainnet: 1000000020807824, - ethereum: 1000000262400462, + ethereum: 1000000262400450, + opbnbtestnet: 1000000000288189, + opbnbmainnet: 1000000008986559, + arbitrumsepolia: 1000000000046406, }; const repayAmount = NetworkRespectiveRepayAmounts[FORKED_NETWORK]; @@ -399,19 +398,19 @@ if (FORK) { it("Should success on healing and forgive borrow account", async function () { // Increase price of borrowed underlying tokens to surpass available collateral - await priceOracle.setDirectPrice(token2.address, convertToUnit(1, 25)); // 25 - await priceOracle.setDirectPrice(token1.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 TOKEN1Price = await priceOracle.getPrice(TOKEN1); - const TOKEN2Price = await priceOracle.getPrice(TOKEN2); + const token1Price = await chainlinkOracle.getPrice(TOKEN1); + const token2Price = await chainlinkOracle.getPrice(TOKEN2); const collateralBal = await vTOKEN1.balanceOf(ACC2); const exchangeRateCollateral = await vTOKEN1.callStatic.exchangeRateCurrent(); const borrowBalanceCurrent = await vTOKEN2.callStatic.borrowBalanceCurrent(ACC2); - const vTokenCollateralPrice = TOKEN1Price.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 = TOKEN2Price.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)); diff --git a/tests/hardhat/Fork/reduceReservesTest.ts b/tests/hardhat/Fork/reduceReservesTest.ts index bc9c3aeee..ed7a2e089 100644 --- a/tests/hardhat/Fork/reduceReservesTest.ts +++ b/tests/hardhat/Fork/reduceReservesTest.ts @@ -6,8 +6,6 @@ import { ethers } from "hardhat"; import { convertToUnit } from "../../../helpers/utils"; import { - AccessControlManager, - AccessControlManager__factory, Comptroller, Comptroller__factory, IERC20, @@ -23,21 +21,20 @@ chai.use(smock.matchers); const FORK = process.env.FORK === "true"; const FORKED_NETWORK = process.env.FORKED_NETWORK || "bscmainnet"; -const { ACC1, ACC2, ACM, ADMIN, PSR, TOKEN1_HOLDER, TOKEN1, VTOKEN1, COMPTROLLER, BLOCK_NUMBER } = getContractAddresses( +const { ACC1, ACC2, ADMIN, PSR, TOKEN2_HOLDER, TOKEN2, VTOKEN2, COMPTROLLER, BLOCK_NUMBER } = getContractAddresses( FORKED_NETWORK as string, ); -let token1Holder: string; -let accessControlManager: AccessControlManager; -let token1: IERC20; -let vTOKEN1: VToken; +let holder: string; +let token: IERC20; +let vToken: VToken; let comptroller: Comptroller; let acc1Signer: Signer; let acc2Signer: Signer; let impersonatedTimelock: Signer; let mintAmount: BigNumberish; -let TOKEN1BorrowAmount: BigNumberish; +let borrowAmount: BigNumberish; async function configureTimelock() { impersonatedTimelock = await initMainnetUser(ADMIN, ethers.utils.parseUnits("2")); @@ -48,24 +45,10 @@ 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) { describe("Reduce Reserves", async () => { mintAmount = convertToUnit("1000", 18); - TOKEN1BorrowAmount = convertToUnit("100", 18); + borrowAmount = convertToUnit("100", 18); async function setup() { await setForkBlock(BLOCK_NUMBER); @@ -73,111 +56,113 @@ if (FORK) { acc1Signer = await initMainnetUser(ACC1, ethers.utils.parseUnits("2")); acc2Signer = await initMainnetUser(ACC2, ethers.utils.parseUnits("2")); - token1Holder = await initMainnetUser(TOKEN1_HOLDER, ethers.utils.parseUnits("2")); + holder = await initMainnetUser(TOKEN2_HOLDER, ethers.utils.parseUnits("2000000")); - token1 = IERC20__factory.connect(TOKEN1, impersonatedTimelock); - vTOKEN1 = await configureVToken(VTOKEN1); + token = IERC20__factory.connect(TOKEN2, impersonatedTimelock); + vToken = await configureVToken(VTOKEN2); comptroller = Comptroller__factory.connect(COMPTROLLER, impersonatedTimelock); - await grantPermissions(); - - await comptroller.connect(acc1Signer).enterMarkets([vTOKEN1.address]); - await comptroller.connect(acc2Signer).enterMarkets([vTOKEN1.address]); + await comptroller.connect(acc1Signer).enterMarkets([vToken.address]); + await comptroller.connect(acc2Signer).enterMarkets([vToken.address]); - await comptroller.setMarketSupplyCaps([vTOKEN1.address], [convertToUnit(1, 50)]); - await comptroller.setMarketBorrowCaps([vTOKEN1.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: IERC20, vToken: VToken, amount: BigNumberish) { - await token.connect(token1Holder).transfer(await signer.getAddress(), amount); + 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 vTOKEN1 market to add underlying assets for borrow and reserves purpose - await mintVTokens(acc2Signer, token1, vTOKEN1, 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 vTOKEN1.getCash(); - await mintVTokens(acc1Signer, token1, vTOKEN1, mintAmount); - let totalCashNew = await vTOKEN1.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 vTOKEN1.connect(acc2Signer).borrow(TOKEN1BorrowAmount); - totalCashNew = await vTOKEN1.getCash(); + await vToken.connect(acc2Signer).borrow(borrowAmount); + totalCashNew = await vToken.getCash(); - expect(totalCashOld.sub(totalCashNew)).equals(TOKEN1BorrowAmount); + expect(totalCashOld.sub(totalCashNew)).equals(borrowAmount); // MINE 300000 BLOCKS await mine(300000); // Save states just before accruing interests - const accrualBlockNumberPrior = await vTOKEN1.accrualBlockNumber(); - const borrowRatePrior = await vTOKEN1.borrowRatePerBlock(); - const totalBorrowsPrior = await vTOKEN1.totalBorrows(); - const reserveBefore = await vTOKEN1.totalReserves(); - const psrBalancePrior = await token1.balanceOf(PSR); + 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 vTOKEN1.accrueInterest(); + await vToken.accrueInterest(); // Calculation of reserves - const currBlock = await ethers.provider.getBlockNumber(); - const blockDelta = BigNumber.from(currBlock).sub(BigNumber.from(accrualBlockNumberPrior)); + let currBlockOrTimestamp = await ethers.provider.getBlockNumber(); + + if (FORKED_NETWORK == "arbitrumsepolia") { + 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 vTOKEN1.reserveFactorMantissa(); + const reserveFactorMantissa = await vToken.reserveFactorMantissa(); const totalReservesExpected = reserveFactorMantissa.mul(interestAccumulated).div(convertToUnit(1, 18)); - const psrBalanceNew = await token1.balanceOf(PSR); + const psrBalanceNew = await token.balanceOf(PSR); const psrBalanceDiff = psrBalanceNew.sub(psrBalancePrior); - let totalReservesCurrent = BigNumber.from((await vTOKEN1.totalReserves()).add(psrBalanceDiff)).sub(reserveBefore); + let totalReservesCurrent = BigNumber.from((await vToken.totalReserves()).add(psrBalanceDiff)).sub(reserveBefore); expect(totalReservesExpected).equals(totalReservesCurrent); // Calculation of exchange rate - let exchangeRateStored = await vTOKEN1.exchangeRateStored(); - totalCashNew = await vTOKEN1.getCash(); - let totalSupply = await vTOKEN1.totalSupply(); - let badDebt = await vTOKEN1.badDebt(); - let totalBorrowCurrent = await vTOKEN1.totalBorrows(); + 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 vTOKEN1.totalReserves()); + .sub(await vToken.totalReserves()); let exchangeRateExpected = cashPlusBorrowsMinusReserves.mul(convertToUnit(1, 18)).div(totalSupply); expect(exchangeRateExpected).equals(exchangeRateStored); // Reduce reserves - await vTOKEN1.accrueInterest(); - totalCashOld = await vTOKEN1.getCash(); - const totalReservesOld = await vTOKEN1.totalReserves(); + await vToken.accrueInterest(); + totalCashOld = await vToken.getCash(); + const totalReservesOld = await vToken.totalReserves(); const reduceAmount = totalReservesOld.mul(50).div(100); - const protocolShareBalanceOld = await token1.balanceOf(PSR); + const protocolShareBalanceOld = await token.balanceOf(PSR); - const psrBalanceBefore = await token1.balanceOf(PSR); - await vTOKEN1.reduceReserves(reduceAmount); - const psrBalanceAfter = await token1.balanceOf(PSR); + const psrBalanceBefore = await token.balanceOf(PSR); + await vToken.reduceReserves(reduceAmount); + const psrBalanceAfter = await token.balanceOf(PSR); - totalReservesCurrent = await vTOKEN1.totalReserves(); + totalReservesCurrent = await vToken.totalReserves(); expect(psrBalanceAfter.sub(psrBalanceBefore)).to.be.equal(reduceAmount); - const protocolShareBalanceNew = await token1.balanceOf(PSR); + const protocolShareBalanceNew = await token.balanceOf(PSR); expect(protocolShareBalanceNew.sub(protocolShareBalanceOld)).equals(reduceAmount); - totalCashNew = await vTOKEN1.getCash(); + totalCashNew = await vToken.getCash(); expect(totalCashOld.sub(totalCashNew)).equals(reduceAmount); - exchangeRateStored = await vTOKEN1.exchangeRateStored(); - totalCashNew = await vTOKEN1.getCash(); - totalSupply = await vTOKEN1.totalSupply(); - badDebt = await vTOKEN1.badDebt(); - totalBorrowCurrent = await vTOKEN1.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 fdd4380e2..bc3138c5c 100644 --- a/tests/hardhat/Fork/supply.ts +++ b/tests/hardhat/Fork/supply.ts @@ -2,7 +2,7 @@ import { smock } from "@defi-wonderland/smock"; import { mine } from "@nomicfoundation/hardhat-network-helpers"; import chai from "chai"; import { BigNumber, Signer } from "ethers"; -import { ethers } from "hardhat"; +import { ethers, upgrades } from "hardhat"; import { convertToUnit } from "../../../helpers/utils"; import { @@ -18,6 +18,8 @@ import { MockPriceOracle__factory, VToken, VToken__factory, + WrappedNative, + WrappedNative__factory, } from "../../../typechain"; import { getContractAddresses, initMainnetUser, setForkBlock } from "./utils"; @@ -47,7 +49,7 @@ const { const AddressZero = "0x0000000000000000000000000000000000000000"; -let token1: IERC20; +let token1: IERC20 | WrappedNative; let token2: IERC20; let vTOKEN1: VToken; let vTOKEN2: VToken; @@ -59,7 +61,7 @@ let token1Holder: Signer; let token2Holder: Signer; let impersonatedTimelock: Signer; let resilientOracle: MockPriceOracle; -let priceOracle: ChainlinkOracle; +let chainlinkOracle: ChainlinkOracle; let accessControlManager: AccessControlManager; const blocksToMine: number = 30000; @@ -69,35 +71,17 @@ async function configureTimelock() { impersonatedTimelock = await initMainnetUser(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); - await tx.wait(); - - tx = await accessControlManager - .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setMarketBorrowCaps(address[],uint256[])", ADMIN); - await tx.wait(); - - tx = await accessControlManager - .connect(impersonatedTimelock) - .giveCallPermission(CHAINLINK_ORACLE, "setDirectPrice(address,uint256)", ADMIN); + .giveCallPermission(chainlinkOracle.address, "setDirectPrice(address,uint256)", ADMIN); await tx.wait(); tx = await accessControlManager .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setMinLiquidatableCollateral(uint256)", ADMIN); + .giveCallPermission(chainlinkOracle.address, "setTokenConfig(TokenConfig)", ADMIN); await tx.wait(); - - tx = await accessControlManager - .connect(impersonatedTimelock) - .giveCallPermission(comptroller.address, "setCollateralFactor(address,uint256,uint256)", ADMIN); } if (FORK) { @@ -109,24 +93,35 @@ if (FORK) { 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("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); token2 = IERC20__factory.connect(TOKEN2, impersonatedTimelock); token1 = IERC20__factory.connect(TOKEN1, impersonatedTimelock); - vTOKEN2 = await configureVToken(VTOKEN2); - vTOKEN1 = await configureVToken(VTOKEN1); + if (FORKED_NETWORK == "arbitrumsepolia" || FORKED_NETWORK == "arbitrumone") { + token1 = WrappedNative__factory.connect(TOKEN1, impersonatedTimelock); + await token1.connect(token1Holder).deposit({ value: convertToUnit("200000", 18) }); + } - comptroller = Comptroller__factory.connect(COMPTROLLER, impersonatedTimelock); - priceOracle = ChainlinkOracle__factory.connect(CHAINLINK_ORACLE, impersonatedTimelock); + 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: [CHAINLINK_ORACLE, AddressZero, AddressZero], + oracles: [chainlinkOracle.address, AddressZero, AddressZero], enableFlagsForOracles: [true, false, false], }; const tupleForToken1 = { asset: TOKEN1, - oracles: [CHAINLINK_ORACLE, AddressZero, AddressZero], + oracles: [chainlinkOracle.address, AddressZero, AddressZero], enableFlagsForOracles: [true, false, false], }; @@ -134,9 +129,7 @@ if (FORK) { await resilientOracle.setTokenConfig(tupleForToken2); await resilientOracle.setTokenConfig(tupleForToken1); - await priceOracle.setDirectPrice(token1.address, convertToUnit("1", 18)); - - await grantPermissions(); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, convertToUnit("1", 18)); await comptroller.setMarketSupplyCaps( [vTOKEN2.address, vTOKEN1.address], @@ -153,7 +146,7 @@ if (FORK) { beforeEach(async () => { await setup(); - await priceOracle.setDirectPrice(token2.address, convertToUnit("1", 15)); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token2.address, convertToUnit("1", 15)); }); const calculateExchangeRate = async () => { @@ -211,7 +204,7 @@ if (FORK) { await assertExchangeRate(); // Set oracle price for TOKEN2 - await priceOracle.setDirectPrice(token2.address, convertToUnit("1", 15)); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token2.address, convertToUnit("1", 15)); let [err, liquidity, shortfall] = await comptroller.getAccountLiquidity(ACC2); @@ -248,20 +241,21 @@ if (FORK) { // setup to liquidate the second account(ACC2) with first account(ACC1) await comptroller.setMinLiquidatableCollateral(0); const tuple1 = { - asset: TOKEN2, - feed: CHAINLINK_ORACLE, - maxStalePeriod: "900000000000000000000000000000000000000000000000000000000000", + asset: TOKEN1, + feed: chainlinkOracle.address, + maxStalePeriod: "9000000000000000000", }; const tuple2 = { - asset: TOKEN1, - feed: CHAINLINK_ORACLE, - maxStalePeriod: "900000000000000000000000000000000000000000000000000000000000", + asset: TOKEN2, + feed: chainlinkOracle.address, + maxStalePeriod: "9000000000000000000", }; - await priceOracle.setTokenConfig(tuple1); - await priceOracle.setTokenConfig(tuple2); + + 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 priceOracle.setDirectPrice(token1.address, convertToUnit("1.05", 14)); + await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(token1.address, convertToUnit("1.05", 14)); [err, liquidity, shortfall] = await comptroller.getAccountLiquidity(ACC2); @@ -272,7 +266,7 @@ if (FORK) { const borrowBalance = (await vTOKEN2.borrowBalanceStored(ACC2)).toString(); const closeFactor = (await comptroller.closeFactorMantissa()).toString(); const maxClose = BigInt(BigInt(borrowBalance) * BigInt(closeFactor)) / BigInt(2e18); - let result = vTOKEN2.connect(acc1Signer).liquidateBorrow(ACC2, maxClose, vTOKEN1.address); + const result = vTOKEN2.connect(acc1Signer).liquidateBorrow(ACC2, maxClose, vTOKEN1.address); await expect(result).to.emit(vTOKEN2, "LiquidateBorrow"); // Mine 30,000 blocks @@ -286,8 +280,8 @@ if (FORK) { // Setup for healAccount(ACC2) - await priceOracle.setDirectPrice(token1.address, convertToUnit(1, 10)); - await priceOracle.setDirectPrice(token2.address, convertToUnit(1, 18)); + 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); @@ -295,9 +289,6 @@ if (FORK) { expect(shortfall2).greaterThan(0); await comptroller.setMinLiquidatableCollateral(convertToUnit(1, 22)); - result = comptroller.connect(acc1Signer).healAccount(ACC2); - await expect(result).to.emit(vTOKEN2, "RepayBorrow"); - // Accural all the interest till latest block await vTOKEN2.accrueInterest();