diff --git a/simulations/vip-581/addendum2.ts b/simulations/vip-581/addendum2.ts index c2bb991ab..5da33a296 100644 --- a/simulations/vip-581/addendum2.ts +++ b/simulations/vip-581/addendum2.ts @@ -1,5 +1,5 @@ import { expect } from "chai"; -import { BigNumber, Contract, Signer } from "ethers"; +import { BigNumber, Contract, Signer, Wallet } from "ethers"; import { parseUnits } from "ethers/lib/utils"; import { ethers } from "hardhat"; import { NETWORK_ADDRESSES } from "src/networkAddresses"; @@ -22,6 +22,7 @@ import { } from "../../vips/vip-581/bscmainnet"; import VTOKEN_ABI from "../vip-567/abi/VToken.json"; import LEVERAGE_STRATEGIES_MANAGER_ABI from "../vip-576/abi/LeverageStrategiesManager.json"; +import SWAP_HELPER_ABI from "../vip-600/abi/SwapHelper.json"; import COMPTROLLER_ABI from "./abi/Comptroller.json"; import ERC20_ABI from "./abi/ERC20.json"; import RESILIENT_ORACLE_ABI from "./abi/ResilientOracle.json"; @@ -29,75 +30,294 @@ import CHAINLINK_ORACLE_ABI from "./abi/chainlinkOracle.json"; const { bscmainnet } = NETWORK_ADDRESSES; -// Contract addresses +// ============================================================================= +// Constants +// ============================================================================= + const LEVERAGE_STRATEGIES_MANAGER = "0x03F079E809185a669Ca188676D0ADb09cbAd6dC1"; +const SWAP_HELPER = "0xD79be25aEe798Aa34A9Ba1230003d7499be29A24"; -// Core Pool markets with flash loans enabled (from VIP-567) -const vUSDC = "0xecA88125a5ADbe82614ffC12D0DB554E2e2867C8"; const vUSDT = "0xfD5840Cd36d94D7229439859C0112a4185BC0255"; const USDC = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d"; const USDT = "0x55d398326f99059fF775485246999027B3197955"; +const WBNB = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"; + +const PANCAKE_V2_ROUTER = "0x10ED43C718714eb63d5aA57B78B54704E256024E"; +const PANCAKE_V2_ROUTER_ABI = [ + "function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)", + "function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)", +]; -// User with U tokens to fund test user -const U_HOLDER = "0x95282779ee2f3d4cf383041f7361c741cf8cc00e"; -// User with USDC tokens -const USDC_HOLDER = "0x8894E0a0c962CB723c1976a4421c95949bE2D4E3"; -// User with USDT tokens +const U_HOLDER = "0x8894E0a0c962CB723c1976a4421c95949bE2D4E3"; const USDT_HOLDER = "0xF977814e90dA44bFA03b6295A0616a897441aceC"; -// Fork block number - UPDATE THIS when tests fail due to stale oracle data const FORK_BLOCK = 75075100; +// ============================================================================= +// EIP-712 Swap Signer +// ============================================================================= + +let swapSignerWallet: Wallet; +let swapHelperContract: Contract; +let eip712Domain: { name: string; version: string; chainId: number; verifyingContract: string }; +let saltCounter = 0; + +async function setupSwapSigner() { + swapSignerWallet = new Wallet("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", ethers.provider); + + swapHelperContract = new ethers.Contract(SWAP_HELPER, SWAP_HELPER_ABI, ethers.provider); + + const swapHelperOwner = await swapHelperContract.owner(); + const impersonatedOwner = await initMainnetUser(swapHelperOwner, ethers.utils.parseEther("1")); + await swapHelperContract.connect(impersonatedOwner).setBackendSigner(swapSignerWallet.address); + + const [domain, network] = await Promise.all([swapHelperContract.eip712Domain(), ethers.provider.getNetwork()]); + eip712Domain = { + name: domain.name, + version: domain.version, + chainId: network.chainId, + verifyingContract: domain.verifyingContract, + }; +} + +// ============================================================================= +// Swap Calldata Builders (API-first with PancakeSwap V2 fallback) +// ============================================================================= + /** - * Fetches swap data from Venus API for swapping tokens - * Uses swapHelperMulticall format required by LeverageStrategiesManager + * Tries the Venus swap API first (20s timeout), falls back to PancakeSwap V2 on-chain. */ +async function buildSwapCalldata( + tokenIn: string, + tokenOut: string, + amountIn: BigNumber, + recipient: string, + slippageBps: number = 100, +): Promise<{ swapData: string; minAmountOut: BigNumber }> { + try { + return await buildSwapCalldataFromAPI(tokenIn, tokenOut, amountIn, recipient); + } catch (apiError) { + console.log( + ` Swap API unavailable (${ + apiError instanceof Error ? apiError.message : apiError + }), falling back to PancakeSwap V2`, + ); + } + return buildSwapCalldataFromPancakeV2(tokenIn, tokenOut, amountIn, recipient, slippageBps); +} + +async function buildSwapCalldataFromAPI( + tokenIn: string, + tokenOut: string, + amountIn: BigNumber, + recipient: string, +): Promise<{ swapData: string; minAmountOut: BigNumber }> { + const TEN_YEARS_SECS = 10 * 365 * 24 * 60 * 60; + const deadline = Math.floor(Date.now() / 1000) + TEN_YEARS_SECS; + + const params = new URLSearchParams({ + chainId: "56", + tokenInAddress: tokenIn, + tokenOutAddress: tokenOut, + slippagePercentage: "0.5", + recipientAddress: SWAP_HELPER, + deadlineTimestampSecs: deadline.toString(), + type: "exact-in", + shouldTransferToReceiver: "false", + exactAmountInMantissa: amountIn.toString(), + }); + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 20000); + + try { + const res = await fetch(`https://api.venus.io/find-swap?${params}`, { signal: controller.signal }); + if (!res.ok) throw new Error(`Swap API error: ${res.status}`); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const json = (await res.json()) as any; + if (!json.quotes?.length) throw new Error(`No API route found for ${tokenIn} -> ${tokenOut}`); + + const quote = json.quotes[0]; + const swapHelperIface = new ethers.utils.Interface(SWAP_HELPER_ABI); + const calls: string[] = []; + + for (const tx of quote.txs) { + calls.push(swapHelperIface.encodeFunctionData("approveMax", [tokenIn, tx.target])); + calls.push(swapHelperIface.encodeFunctionData("genericCall", [tx.target, tx.data])); + } + calls.push(swapHelperIface.encodeFunctionData("sweep", [tokenOut, recipient])); + + const salt = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["uint256"], [++saltCounter])); + const types = { + Multicall: [ + { name: "caller", type: "address" }, + { name: "calls", type: "bytes[]" }, + { name: "deadline", type: "uint256" }, + { name: "salt", type: "bytes32" }, + ], + }; + const value = { caller: LEVERAGE_STRATEGIES_MANAGER, calls, deadline, salt }; + const signature = await swapSignerWallet._signTypedData(eip712Domain, types, value); + const multicallData = swapHelperIface.encodeFunctionData("multicall", [calls, deadline, salt, signature]); + + return { swapData: multicallData, minAmountOut: BigNumber.from(1) }; + } finally { + clearTimeout(timeoutId); + } +} + +async function buildSwapCalldataFromPancakeV2( + tokenIn: string, + tokenOut: string, + amountIn: BigNumber, + recipient: string, + slippageBps: number, +): Promise<{ swapData: string; minAmountOut: BigNumber }> { + const pancakeRouter = new ethers.Contract(PANCAKE_V2_ROUTER, PANCAKE_V2_ROUTER_ABI, ethers.provider); + + let path: string[]; + let amounts: BigNumber[]; + + try { + path = [tokenIn, tokenOut]; + amounts = await pancakeRouter.getAmountsOut(amountIn, path); + } catch { + if (tokenIn !== WBNB && tokenOut !== WBNB) { + path = [tokenIn, WBNB, tokenOut]; + amounts = await pancakeRouter.getAmountsOut(amountIn, path); + } else { + throw new Error(`No route found for ${tokenIn} -> ${tokenOut}`); + } + } + + const amountOut = amounts[amounts.length - 1]; + const minAmountOut = amountOut.mul(10000 - slippageBps).div(10000); + + const swapHelperIface = new ethers.utils.Interface(SWAP_HELPER_ABI); + const pancakeIface = new ethers.utils.Interface(PANCAKE_V2_ROUTER_ABI); + const deadline = Math.floor(Date.now() / 1000) + 3600; + + const calls = [ + swapHelperIface.encodeFunctionData("approveMax", [tokenIn, PANCAKE_V2_ROUTER]), + swapHelperIface.encodeFunctionData("genericCall", [ + PANCAKE_V2_ROUTER, + pancakeIface.encodeFunctionData("swapExactTokensForTokens", [ + amountIn, + minAmountOut, + path, + SWAP_HELPER, + deadline, + ]), + ]), + swapHelperIface.encodeFunctionData("sweep", [tokenOut, recipient]), + ]; + + const salt = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["uint256"], [++saltCounter])); + const types = { + Multicall: [ + { name: "caller", type: "address" }, + { name: "calls", type: "bytes[]" }, + { name: "deadline", type: "uint256" }, + { name: "salt", type: "bytes32" }, + ], + }; + const value = { caller: LEVERAGE_STRATEGIES_MANAGER, calls, deadline, salt }; + const signature = await swapSignerWallet._signTypedData(eip712Domain, types, value); + const multicallData = swapHelperIface.encodeFunctionData("multicall", [calls, deadline, salt, signature]); + + return { swapData: multicallData, minAmountOut }; +} + +// ============================================================================= +// Swap Data Wrapper +// ============================================================================= + async function getSwapData( tokenInAddress: string, tokenOutAddress: string, exactAmountInMantissa: string, slippagePercentage: string = "0.01", ): Promise<{ swapData: string; minAmountOut: BigNumber }> { - const deadlineTimestamp = Math.floor(Date.now() / 1000) + 3600; // 1 hour from now + const slippageBps = Math.round(parseFloat(slippagePercentage) * 10000); + return buildSwapCalldata( + tokenInAddress, + tokenOutAddress, + BigNumber.from(exactAmountInMantissa), + LEVERAGE_STRATEGIES_MANAGER, + slippageBps, + ); +} - const url = `https://api.venus.io/find-swap?chainId=56&tokenInAddress=${tokenInAddress}&tokenOutAddress=${tokenOutAddress}&slippagePercentage=${slippagePercentage}&recipientAddress=${LEVERAGE_STRATEGIES_MANAGER}&deadlineTimestampSecs=${deadlineTimestamp}&type=exact-in&shouldTransferToReceiver=true&exactAmountInMantissa=${exactAmountInMantissa}`; +// ============================================================================= +// Results Tracker +// ============================================================================= - try { - const response = await fetch(url); - const data: unknown = await response.json(); - - // Type guard for expected Venus API response - if ( - typeof data === "object" && - data !== null && - "quotes" in data && - Array.isArray((data as any).quotes) && - (data as any).quotes.length > 0 - ) { - const quote = (data as any).quotes[0]; - return { - swapData: quote.swapHelperMulticall.calldata.encodedCall, - minAmountOut: BigNumber.from(quote.amountOut).mul(99).div(100), // 1% slippage buffer - }; +interface LeverageResult { + section: string; + test: string; + status: "PASSED" | "SKIPPED" | "FAILED"; + detail: string; +} +const leverageResults: LeverageResult[] = []; + +function printLeverageResultsSummary() { + const passed = leverageResults.filter(r => r.status === "PASSED"); + const failed = leverageResults.filter(r => r.status === "FAILED"); + const skipped = leverageResults.filter(r => r.status === "SKIPPED"); + + console.log("\n" + "=".repeat(120)); + console.log(" LEVERAGE STRATEGIES RESULTS SUMMARY"); + console.log("=".repeat(120)); + + for (const [label, list] of [ + ["PASSED", passed], + ["FAILED", failed], + ["SKIPPED", skipped], + ] as const) { + if (list.length === 0) continue; + console.log(`\n ${label} (${list.length})`); + console.log(" " + "-".repeat(116)); + console.log(` ${"Section".padEnd(35)} ${"Test".padEnd(50)} ${label === "PASSED" ? "Detail" : "Reason"}`); + console.log(" " + "-".repeat(116)); + for (const r of list) { + console.log(` ${r.section.padEnd(35)} ${r.test.padEnd(50)} ${r.detail}`); } - throw new Error("No quotes returned from Venus API"); - } catch (error) { - console.log("Failed to fetch swap data from Venus API, using fallback"); - // Return empty swap data - tests will skip cross-asset tests - return { - swapData: "0x", - minAmountOut: BigNumber.from(0), - }; } + + console.log("\n" + "=".repeat(120)); + console.log( + ` Total: ${leverageResults.length} | Passed: ${passed.length} | Failed: ${failed.length} | Skipped: ${skipped.length}`, + ); + console.log("=".repeat(120) + "\n"); } +// ============================================================================= +// Helpers +// ============================================================================= + +function parseEvents(receipt: any, abi: any, eventName: string): any[] { + const iface = new ethers.utils.Interface(abi); + return receipt.logs + .map((log: { topics: string[]; data: string }) => { + try { + return iface.parseLog(log); + } catch { + return null; + } + }) + .filter((e: { name: string } | null) => e && e.name === eventName); +} + +// ============================================================================= +// Test Suite +// ============================================================================= + forking(FORK_BLOCK, async () => { let vUContract: Contract; let leverageStrategiesManager: Contract; let u: Contract; - let usdc: Contract; let usdt: Contract; - let vUSDCContract: Contract; let vUSDTContract: Contract; let comptroller: Contract; let resilientOracle: Contract; @@ -108,6 +328,7 @@ forking(FORK_BLOCK, async () => { let leverageTestUser: Signer; before(async () => { + // Instantiate contracts (pure local — no RPC) vUContract = new ethers.Contract(vU, VTOKEN_ABI, ethers.provider); leverageStrategiesManager = new ethers.Contract( LEVERAGE_STRATEGIES_MANAGER, @@ -115,34 +336,42 @@ forking(FORK_BLOCK, async () => { ethers.provider, ); u = new ethers.Contract(U, ERC20_ABI, ethers.provider); - usdc = new ethers.Contract(USDC, ERC20_ABI, ethers.provider); usdt = new ethers.Contract(USDT, ERC20_ABI, ethers.provider); - vUSDCContract = new ethers.Contract(vUSDC, VTOKEN_ABI, ethers.provider); vUSDTContract = new ethers.Contract(vUSDT, VTOKEN_ABI, ethers.provider); comptroller = new ethers.Contract(bscmainnet.UNITROLLER, COMPTROLLER_ABI, ethers.provider); resilientOracle = new ethers.Contract(RESILIENT_ORACLE, RESILIENT_ORACLE_ABI, ethers.provider); chainlinkOracle = new ethers.Contract(CHAINLINK_ORACLE, CHAINLINK_ORACLE_ABI, ethers.provider); usdtChainlinkOracle = new ethers.Contract(USDT_CHAINLINK_ORACLE, CHAINLINK_ORACLE_ABI, ethers.provider); - impersonatedTimelock = await initMainnetUser(bscmainnet.NORMAL_TIMELOCK, ethers.utils.parseEther("2")); - // Use fresh Hardhat signers as test users to avoid oracle issues with other markets const signers = await ethers.getSigners(); testUser = signers[0]; leverageTestUser = signers[1]; - // Fund test users with tokens - const uHolder = await initMainnetUser(U_HOLDER, ethers.utils.parseEther("10")); - const usdcHolder = await initMainnetUser(USDC_HOLDER, ethers.utils.parseEther("10")); - const usdtHolder = await initMainnetUser(USDT_HOLDER, ethers.utils.parseEther("10")); - - await u.connect(uHolder).transfer(await testUser.getAddress(), parseUnits("100", 18)); - await usdc.connect(usdcHolder).transfer(await leverageTestUser.getAddress(), parseUnits("1000", 18)); - await usdt.connect(usdtHolder).transfer(await leverageTestUser.getAddress(), parseUnits("1000", 18)); - - // Set direct prices and extend stale periods for oracle compatibility + // Step 1: Impersonate users and setup swap signer (different signers — safe in parallel) + const [, uHolder, usdtHolder, timelockSigner] = await Promise.all([ + setupSwapSigner(), + initMainnetUser(U_HOLDER, ethers.utils.parseEther("10")), + initMainnetUser(USDT_HOLDER, ethers.utils.parseEther("10")), + initMainnetUser(bscmainnet.NORMAL_TIMELOCK, ethers.utils.parseEther("2")), + ]); + impersonatedTimelock = timelockSigner; + + // Step 2: Fund test users + const [testUserAddress, leverageUserAddress] = await Promise.all([ + testUser.getAddress(), + leverageTestUser.getAddress(), + ]); + // uHolder sends two transfers — must be sequential (same signer, nonce conflict) + await u.connect(uHolder).transfer(testUserAddress, parseUnits("100", 18)); + await u.connect(uHolder).transfer(leverageUserAddress, parseUnits("1000", 18)); + // usdtHolder is a different signer but has no parallel partner now, so just await + await usdt.connect(usdtHolder).transfer(leverageUserAddress, parseUnits("1000", 18)); + + // Step 3: Set direct prices (same signer: impersonatedTimelock — must be sequential) await usdtChainlinkOracle.connect(impersonatedTimelock).setDirectPrice(U, parseUnits("1", 18)); await chainlinkOracle.connect(impersonatedTimelock).setDirectPrice(U, parseUnits("1", 18)); + // Step 4: Configure oracle stale periods (uses NORMAL_TIMELOCK internally — sequential) await setMaxStalePeriodInChainlinkOracle( USDT_CHAINLINK_ORACLE, U, @@ -150,12 +379,8 @@ forking(FORK_BLOCK, async () => { bscmainnet.NORMAL_TIMELOCK, 315360000, ); - await setMaxStalePeriodInChainlinkOracle(CHAINLINK_ORACLE, U, USD1_FEED, bscmainnet.NORMAL_TIMELOCK, 315360000); - await setMaxStalePeriod(resilientOracle, u); - - // Set stale periods for USDC and USDT for cross-asset leverage tests await setMaxStalePeriodInChainlinkOracle( bscmainnet.CHAINLINK_ORACLE, USDC, @@ -174,246 +399,159 @@ forking(FORK_BLOCK, async () => { await setMaxStalePeriodInBinanceOracle(bscmainnet.BINANCE_ORACLE, "USDT", 315360000); }); - /** - * VIP Execution Test - * - * Note: At recent blocks, this VIP may have already been executed on mainnet. - * We check for 0 or 1 FlashLoanStatusChanged events to handle both cases: - * - 0 events: VIP was already executed (no state change) - * - 1 event: VIP just executed (state changed from false to true) - */ + // =========================================================================== + // VIP Execution + // =========================================================================== + testVip("VIP-581 Addendum2", await vip581Addendum2(), { callbackAfterExecution: async txResponse => { const receipt = await txResponse.wait(); - const iface = new ethers.utils.Interface(VTOKEN_ABI); - const events = receipt.logs - .map((log: { topics: string[]; data: string }) => { - try { - return iface.parseLog(log); - } catch { - return null; - } - }) - .filter((e: { name: string } | null) => e && e.name === "FlashLoanStatusChanged"); - // Either 0 (already executed) or 1 (just executed) events is acceptable + const events = parseEvents(receipt, VTOKEN_ABI, "FlashLoanStatusChanged"); expect(events.length).to.be.lte(1); }, }); + // =========================================================================== + // Post-VIP checks + // =========================================================================== + describe("Post-VIP checks", () => { it("vU has flash loans enabled", async () => { - const isFlashLoanEnabled = await vUContract.isFlashLoanEnabled(); - expect(isFlashLoanEnabled).to.be.equal(true); + expect(await vUContract.isFlashLoanEnabled()).to.be.equal(true); + + leverageResults.push({ + section: "Post-VIP", + test: "Flash loans enabled on vU", + status: "PASSED", + detail: "isFlashLoanEnabled = true", + }); }); - it("LeverageStrategiesManager: enterSingleAssetLeverage with value checks", async () => { + it("enterSingleAssetLeverage with value checks", async () => { const userAddress = await testUser.getAddress(); const collateralAmountSeed = parseUnits("10", 18); const collateralAmountToFlashLoan = parseUnits("5", 18); - // Approve U tokens to vU for minting collateral first await u.connect(testUser).approve(vU, collateralAmountSeed); - - // Mint vU as collateral await vUContract.connect(testUser).mint(collateralAmountSeed); - - // Enter market so vU can be used as collateral await comptroller.connect(testUser).enterMarkets([vU]); - - // Approve leverage manager to act on behalf of user (updateDelegate) await comptroller.connect(testUser).updateDelegate(LEVERAGE_STRATEGIES_MANAGER, true); - // Get balances before - const vUBalanceBefore = await vUContract.balanceOf(userAddress); - const borrowBalanceBefore = await vUContract.callStatic.borrowBalanceCurrent(userAddress); + const [vUBalanceBefore, borrowBalanceBefore] = await Promise.all([ + vUContract.balanceOf(userAddress), + vUContract.callStatic.borrowBalanceCurrent(userAddress), + ]); - // Call enterSingleAssetLeverage const tx = await leverageStrategiesManager .connect(testUser) .enterSingleAssetLeverage(vU, 0, collateralAmountToFlashLoan); const receipt = await tx.wait(); - // Parse and verify SingleAssetLeverageEntered event - const iface = new ethers.utils.Interface(LEVERAGE_STRATEGIES_MANAGER_ABI); - const leverageEvents = receipt.logs - .map((log: { topics: string[]; data: string }) => { - try { - return iface.parseLog(log); - } catch { - return null; - } - }) - .filter((e: { name: string } | null) => e && e.name === "SingleAssetLeverageEntered"); - + const leverageEvents = parseEvents(receipt, LEVERAGE_STRATEGIES_MANAGER_ABI, "SingleAssetLeverageEntered"); expect(leverageEvents.length).to.equal(1); - const event = leverageEvents[0]; - - // Verify event parameters - expect(event.args.user.toLowerCase()).to.equal(userAddress.toLowerCase()); - expect(event.args.collateralMarket.toLowerCase()).to.equal(vU.toLowerCase()); - expect(event.args.collateralAmountSeed).to.equal(0); - expect(event.args.collateralAmountToFlashLoan).to.equal(collateralAmountToFlashLoan); + expect(leverageEvents[0].args.user.toLowerCase()).to.equal(userAddress.toLowerCase()); + expect(leverageEvents[0].args.collateralMarket.toLowerCase()).to.equal(vU.toLowerCase()); + expect(leverageEvents[0].args.collateralAmountSeed).to.equal(0); + expect(leverageEvents[0].args.collateralAmountToFlashLoan).to.equal(collateralAmountToFlashLoan); - // Verify balance changes - const vUBalanceAfter = await vUContract.balanceOf(userAddress); - const borrowBalanceAfter = await vUContract.callStatic.borrowBalanceCurrent(userAddress); + const [vUBalanceAfter, borrowBalanceAfter] = await Promise.all([ + vUContract.balanceOf(userAddress), + vUContract.callStatic.borrowBalanceCurrent(userAddress), + ]); - // User should have more vU (from flash loan collateral added) expect(vUBalanceAfter).to.be.gt(vUBalanceBefore); - - // User should now have a borrow balance equal to or greater than flash loan amount expect(borrowBalanceAfter).to.be.gt(borrowBalanceBefore); expect(borrowBalanceAfter).to.be.gte(collateralAmountToFlashLoan); - console.log(`SingleAssetLeverage entered:`); - console.log(` vU balance: ${vUBalanceBefore.toString()} -> ${vUBalanceAfter.toString()}`); - console.log(` Borrow: ${borrowBalanceBefore.toString()} -> ${borrowBalanceAfter.toString()}`); + leverageResults.push({ + section: "Post-VIP", + test: "enterSingleAssetLeverage", + status: "PASSED", + detail: `vU: ${vUBalanceBefore} -> ${vUBalanceAfter}, Borrow: ${borrowBalanceBefore} -> ${borrowBalanceAfter}`, + }); }); }); - /** - * Cross-asset leverage tests (enterLeverage, exitLeverage, enterLeverageFromBorrow) - * - * These tests require live swap quotes from the Venus API (api.venus.io/find-swap). - * The swap data includes a signed quote with a deadline and specific amounts. - * - * IMPORTANT: These tests may fail due to: - * 1. Swap quote expiration (deadline passed by the time tx executes) - * 2. Price movement causing slippage to exceed limits - * 3. DEX liquidity changes affecting swap routes - * 4. API unavailability - * - * The tests gracefully skip if API is unavailable or swap fails. - * For reliable testing of cross-asset leverage, use Tenderly virtual testnet - * with fresh swap quotes: https://api.venus.io/find-swap - */ - /** - * ===================================================================== - * LeverageStrategiesManager: enterLeverage (cross-asset) - * ===================================================================== - * - * This test demonstrates a cross-asset leverage operation using the - * LeverageStrategiesManager contract. The user supplies USDC as collateral, - * borrows USDT via a flash loan, and swaps the borrowed USDT for more USDC - * to increase their leveraged position. The swap data is fetched live from - * the Venus API, which provides a signed quote for the swap. - * - * Key steps: - * 1. User enters both USDC and USDT markets and delegates to the manager. - * 2. User approves the manager to spend their USDC. - * 3. The test fetches swap data for USDT->USDC from the Venus API. - * 4. The manager's enterLeverage is called, which: - * - Supplies USDC as seed collateral - * - Borrows USDT via flash loan - * - Swaps USDT for more USDC (increasing collateral) - * - Leaves the user with a leveraged position - * 5. The test verifies: - * - The LeverageEntered event is emitted with correct parameters - * - The user's vUSDC balance increases - * - The user's USDT borrow balance increases - * - The user's USDC wallet balance decreases by the seed amount - * - * This test will gracefully skip if the Venus API is unavailable or the swap fails. - */ - describe("LeverageStrategiesManager: enterLeverage (cross-asset)", () => { + // =========================================================================== + // Cross-Asset Leverage: enterLeverage + // =========================================================================== + + describe("enterLeverage (cross-asset)", () => { let leverageUserAddress: string; before(async () => { leverageUserAddress = await leverageTestUser.getAddress(); - - // Enter markets for both USDC and USDT so the user can supply collateral and borrow - await comptroller.connect(leverageTestUser).enterMarkets([vUSDC, vUSDT]); - - // Approve leverage manager to act on behalf of user (required for leverage operations) + // Sequential — same signer (leverageTestUser) + await comptroller.connect(leverageTestUser).enterMarkets([vU, vUSDT]); await comptroller.connect(leverageTestUser).updateDelegate(LEVERAGE_STRATEGIES_MANAGER, true); }); - it("should enter cross-asset leverage position (USDC collateral, USDT borrow) with value checks", async () => { - // User will supply 100 USDC as seed collateral + it("should enter cross-asset leverage (U collateral, USDT borrow)", async () => { const collateralAmountSeed = parseUnits("100", 18); - // User will borrow 50 USDT via flash loan const borrowedAmountToFlashLoan = parseUnits("50", 18); - // Fetch swap data from Venus API for swapping borrowed USDT to USDC - // This is required for the leverage manager to perform the swap on-chain - const { swapData, minAmountOut } = await getSwapData(USDT, USDC, borrowedAmountToFlashLoan.toString(), "0.01"); + const { swapData, minAmountOut } = await getSwapData(USDT, U, borrowedAmountToFlashLoan.toString(), "0.01"); - // If swap data is unavailable, skip the test if (swapData === "0x") { - console.log("Skipping cross-asset enterLeverage test - Venus API unavailable"); + leverageResults.push({ + section: "enterLeverage", + test: "U collateral / USDT borrow", + status: "SKIPPED", + detail: "Swap data unavailable", + }); + console.log(" [SKIP] Swap data unavailable"); return; } - // Record balances before leverage - const vUSDCBalanceBefore = await vUSDCContract.balanceOf(leverageUserAddress); - const usdtBorrowBefore = await vUSDTContract.callStatic.borrowBalanceCurrent(leverageUserAddress); - const usdcBalanceBefore = await usdc.balanceOf(leverageUserAddress); + const [vUBalanceBefore, usdtBorrowBefore, uBalanceBefore] = await Promise.all([ + vUContract.balanceOf(leverageUserAddress), + vUSDTContract.callStatic.borrowBalanceCurrent(leverageUserAddress), + u.balanceOf(leverageUserAddress), + ]); - // Approve the leverage manager to spend user's USDC for seed collateral - await usdc.connect(leverageTestUser).approve(LEVERAGE_STRATEGIES_MANAGER, collateralAmountSeed); + await u.connect(leverageTestUser).approve(LEVERAGE_STRATEGIES_MANAGER, collateralAmountSeed); try { - // Call enterLeverage on the manager contract - // This will: - // - Supply USDC as collateral - // - Borrow USDT via flash loan - // - Swap USDT for more USDC (increasing collateral) - // - Leave the user with a leveraged position - const tx = await leverageStrategiesManager.connect(leverageTestUser).enterLeverage( - vUSDC, // collateralMarket - collateralAmountSeed, // collateralAmountSeed - vUSDT, // borrowedMarket - borrowedAmountToFlashLoan, // borrowedAmountToFlashLoan - minAmountOut, // minAmountOutAfterSwap - swapData, // swapData from Venus API - ); + const tx = await leverageStrategiesManager + .connect(leverageTestUser) + .enterLeverage(vU, collateralAmountSeed, vUSDT, borrowedAmountToFlashLoan, minAmountOut, swapData); const receipt = await tx.wait(); - // Parse and verify LeverageEntered event - const iface = new ethers.utils.Interface(LEVERAGE_STRATEGIES_MANAGER_ABI); - const leverageEvents = receipt.logs - .map((log: { topics: string[]; data: string }) => { - try { - return iface.parseLog(log); - } catch { - return null; - } - }) - .filter((e: { name: string } | null) => e && e.name === "LeverageEntered"); - - // There should be exactly one LeverageEntered event + const leverageEvents = parseEvents(receipt, LEVERAGE_STRATEGIES_MANAGER_ABI, "LeverageEntered"); expect(leverageEvents.length).to.equal(1); - const event = leverageEvents[0]; - - // Verify event parameters match input - expect(event.args.user.toLowerCase()).to.equal(leverageUserAddress.toLowerCase()); - expect(event.args.collateralMarket.toLowerCase()).to.equal(vUSDC.toLowerCase()); - expect(event.args.collateralAmountSeed).to.equal(collateralAmountSeed); - expect(event.args.borrowedMarket.toLowerCase()).to.equal(vUSDT.toLowerCase()); - expect(event.args.borrowedAmountToFlashLoan).to.equal(borrowedAmountToFlashLoan); - - // Record balances after leverage - const vUSDCBalanceAfter = await vUSDCContract.balanceOf(leverageUserAddress); - const usdtBorrowAfter = await vUSDTContract.callStatic.borrowBalanceCurrent(leverageUserAddress); - const usdcBalanceAfter = await usdc.balanceOf(leverageUserAddress); - - // The user's vUSDC balance should increase (more collateral) - expect(vUSDCBalanceAfter).to.be.gt(vUSDCBalanceBefore); - // The user's USDT borrow balance should increase (from flash loan) + expect(leverageEvents[0].args.user.toLowerCase()).to.equal(leverageUserAddress.toLowerCase()); + expect(leverageEvents[0].args.collateralMarket.toLowerCase()).to.equal(vU.toLowerCase()); + expect(leverageEvents[0].args.collateralAmountSeed).to.equal(collateralAmountSeed); + expect(leverageEvents[0].args.borrowedMarket.toLowerCase()).to.equal(vUSDT.toLowerCase()); + expect(leverageEvents[0].args.borrowedAmountToFlashLoan).to.equal(borrowedAmountToFlashLoan); + + const [vUBalanceAfter, usdtBorrowAfter, uBalanceAfter] = await Promise.all([ + vUContract.balanceOf(leverageUserAddress), + vUSDTContract.callStatic.borrowBalanceCurrent(leverageUserAddress), + u.balanceOf(leverageUserAddress), + ]); + + expect(vUBalanceAfter).to.be.gt(vUBalanceBefore); expect(usdtBorrowAfter).to.be.gt(usdtBorrowBefore); expect(usdtBorrowAfter).to.be.gte(borrowedAmountToFlashLoan); - // The user's USDC wallet balance should decrease by the seed amount - expect(usdcBalanceAfter).to.equal(usdcBalanceBefore.sub(collateralAmountSeed)); - - // Log the results for manual inspection - console.log(`Cross-asset leverage entered:`); - console.log(` vUSDC balance: ${vUSDCBalanceBefore.toString()} -> ${vUSDCBalanceAfter.toString()}`); - console.log(` USDT borrow: ${usdtBorrowBefore.toString()} -> ${usdtBorrowAfter.toString()}`); + expect(uBalanceAfter).to.equal(uBalanceBefore.sub(collateralAmountSeed)); + + leverageResults.push({ + section: "enterLeverage", + test: "U collateral / USDT borrow", + status: "PASSED", + detail: `vU: ${vUBalanceBefore} -> ${vUBalanceAfter}, USDT borrow: ${usdtBorrowBefore} -> ${usdtBorrowAfter}`, + }); } catch (error: unknown) { - // TokenSwapCallFailed (0x428c0cc7) or similar swap errors - skip gracefully - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes("0x428c0cc7") || errorMessage.includes("TokenSwapCallFailed")) { - console.log("Skipping cross-asset enterLeverage - swap quote expired or route unavailable"); + const msg = error instanceof Error ? error.message : String(error); + if (msg.includes("0x428c0cc7") || msg.includes("TokenSwapCallFailed") || msg.includes("Swap API error")) { + leverageResults.push({ + section: "enterLeverage", + test: "U collateral / USDT borrow", + status: "SKIPPED", + detail: "Swap failed or API unavailable", + }); + console.log(" [SKIP] Swap quote expired or route unavailable"); return; } throw error; @@ -421,85 +559,87 @@ forking(FORK_BLOCK, async () => { }); }); - describe("LeverageStrategiesManager: exitLeverage (cross-asset)", () => { - it("should exit cross-asset leverage position with value checks", async () => { + // =========================================================================== + // Cross-Asset Leverage: exitLeverage + // =========================================================================== + + describe("exitLeverage (cross-asset)", () => { + it("should exit cross-asset leverage position", async () => { const leverageUserAddress = await leverageTestUser.getAddress(); - // Check if user has a leverage position to exit const usdtBorrowBefore = await vUSDTContract.callStatic.borrowBalanceCurrent(leverageUserAddress); if (usdtBorrowBefore.eq(0)) { - console.log("Skipping exitLeverage test - no position to exit"); + leverageResults.push({ + section: "exitLeverage", + test: "Exit U/USDT position", + status: "SKIPPED", + detail: "No position to exit", + }); + console.log(" [SKIP] No position to exit"); return; } - // For exitLeverage: flash loan USDT to repay, redeem USDC, swap USDC to USDT - const collateralAmountToRedeem = parseUnits("50", 18); - - // Get swap data from Venus API (USDC -> USDT) - const { swapData, minAmountOut } = await getSwapData(USDC, USDT, collateralAmountToRedeem.toString(), "0.01"); + const collateralAmountToRedeem = parseUnits("55", 18); + const { swapData, minAmountOut } = await getSwapData(U, USDT, collateralAmountToRedeem.toString(), "0.01"); if (swapData === "0x") { - console.log("Skipping exitLeverage test - Venus API unavailable"); + leverageResults.push({ + section: "exitLeverage", + test: "Exit U/USDT position", + status: "SKIPPED", + detail: "Swap data unavailable", + }); + console.log(" [SKIP] Swap data unavailable"); return; } - // Get balances before exit - const vUSDCBalanceBefore = await vUSDCContract.balanceOf(leverageUserAddress); - const usdcBalanceBefore = await usdc.balanceOf(leverageUserAddress); + const [vUBalanceBefore, uBalanceBefore] = await Promise.all([ + vUContract.balanceOf(leverageUserAddress), + u.balanceOf(leverageUserAddress), + ]); - // Flash loan amount: borrow balance + 1% buffer for interest const flashLoanAmount = usdtBorrowBefore.mul(101).div(100); try { - // Call exitLeverage - const tx = await leverageStrategiesManager.connect(leverageTestUser).exitLeverage( - vUSDC, // collateralMarket - collateralAmountToRedeem, // collateralAmountToRedeemForSwap - vUSDT, // borrowedMarket - flashLoanAmount, // borrowedAmountToFlashLoan - minAmountOut, // minAmountOutAfterSwap - swapData, // swapData from Venus API - ); + const tx = await leverageStrategiesManager + .connect(leverageTestUser) + .exitLeverage(vU, collateralAmountToRedeem, vUSDT, flashLoanAmount, minAmountOut, swapData); const receipt = await tx.wait(); - // Parse and verify LeverageExited event - const iface = new ethers.utils.Interface(LEVERAGE_STRATEGIES_MANAGER_ABI); - const exitEvents = receipt.logs - .map((log: { topics: string[]; data: string }) => { - try { - return iface.parseLog(log); - } catch { - return null; - } - }) - .filter((e: { name: string } | null) => e && e.name === "LeverageExited"); - + const exitEvents = parseEvents(receipt, LEVERAGE_STRATEGIES_MANAGER_ABI, "LeverageExited"); expect(exitEvents.length).to.equal(1); - const event = exitEvents[0]; - - // Verify event parameters - expect(event.args.user.toLowerCase()).to.equal(leverageUserAddress.toLowerCase()); - expect(event.args.collateralMarket.toLowerCase()).to.equal(vUSDC.toLowerCase()); - expect(event.args.collateralAmountToRedeemForSwap).to.equal(collateralAmountToRedeem); - expect(event.args.borrowedMarket.toLowerCase()).to.equal(vUSDT.toLowerCase()); - expect(event.args.borrowedAmountToFlashLoan).to.equal(flashLoanAmount); - - // Get balances after exit - const vUSDCBalanceAfter = await vUSDCContract.balanceOf(leverageUserAddress); - const usdtBorrowAfter = await vUSDTContract.callStatic.borrowBalanceCurrent(leverageUserAddress); - const usdcBalanceAfter = await usdc.balanceOf(leverageUserAddress); - - expect(vUSDCBalanceAfter).to.be.lt(vUSDCBalanceBefore); + expect(exitEvents[0].args.user.toLowerCase()).to.equal(leverageUserAddress.toLowerCase()); + expect(exitEvents[0].args.collateralMarket.toLowerCase()).to.equal(vU.toLowerCase()); + expect(exitEvents[0].args.collateralAmountToRedeemForSwap).to.equal(collateralAmountToRedeem); + expect(exitEvents[0].args.borrowedMarket.toLowerCase()).to.equal(vUSDT.toLowerCase()); + expect(exitEvents[0].args.borrowedAmountToFlashLoan).to.equal(flashLoanAmount); + + const [vUBalanceAfter, usdtBorrowAfter, uBalanceAfter] = await Promise.all([ + vUContract.balanceOf(leverageUserAddress), + vUSDTContract.callStatic.borrowBalanceCurrent(leverageUserAddress), + u.balanceOf(leverageUserAddress), + ]); + + expect(vUBalanceAfter).to.be.lt(vUBalanceBefore); expect(usdtBorrowAfter).to.equal(0); - expect(usdcBalanceAfter).to.be.gte(usdcBalanceBefore); - - console.log(`Cross-asset leverage exited:`); - console.log(` vUSDC balance: ${vUSDCBalanceBefore.toString()} -> ${vUSDCBalanceAfter.toString()}`); - console.log(` USDT borrow: ${usdtBorrowBefore.toString()} -> ${usdtBorrowAfter.toString()}`); + expect(uBalanceAfter).to.be.gte(uBalanceBefore); + + leverageResults.push({ + section: "exitLeverage", + test: "Exit U/USDT position", + status: "PASSED", + detail: `vU: ${vUBalanceBefore} -> ${vUBalanceAfter}, USDT borrow: ${usdtBorrowBefore} -> ${usdtBorrowAfter}`, + }); } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes("0x428c0cc7") || errorMessage.includes("TokenSwapCallFailed")) { - console.log("Skipping cross-asset exitLeverage - swap quote expired or route unavailable"); + const msg = error instanceof Error ? error.message : String(error); + if (msg.includes("0x428c0cc7") || msg.includes("TokenSwapCallFailed") || msg.includes("Swap API error")) { + leverageResults.push({ + section: "exitLeverage", + test: "Exit U/USDT position", + status: "SKIPPED", + detail: "Swap failed or API unavailable", + }); + console.log(" [SKIP] Swap quote expired or route unavailable"); return; } throw error; @@ -507,96 +647,99 @@ forking(FORK_BLOCK, async () => { }); }); - describe("LeverageStrategiesManager: enterLeverageFromBorrow", () => { - it("should enter leverage from existing borrow position with value checks", async () => { + // =========================================================================== + // Cross-Asset Leverage: enterLeverageFromBorrow + // =========================================================================== + + describe("enterLeverageFromBorrow", () => { + it("should enter leverage from existing borrow position", async () => { const userAddress = await leverageTestUser.getAddress(); - // Setup: mint vUSDC and borrow some USDT first const mintAmount = parseUnits("200", 18); const initialBorrow = parseUnits("20", 18); - const usdcBalance = await usdc.balanceOf(userAddress); - if (usdcBalance.lt(mintAmount)) { - console.log("Skipping enterLeverageFromBorrow test - insufficient USDC balance"); + const uBalance = await u.balanceOf(userAddress); + if (uBalance.lt(mintAmount)) { + leverageResults.push({ + section: "enterLeverageFromBorrow", + test: "Leverage from existing borrow", + status: "SKIPPED", + detail: "Insufficient U balance", + }); + console.log(" [SKIP] Insufficient U balance"); return; } - // Approve and mint vUSDC - await usdc.connect(leverageTestUser).approve(vUSDC, mintAmount); - await vUSDCContract.connect(leverageTestUser).mint(mintAmount); - - // Borrow some USDT to create initial position + // Sequential — same signer (leverageTestUser) + await u.connect(leverageTestUser).approve(vU, mintAmount); + await vUContract.connect(leverageTestUser).mint(mintAmount); await vUSDTContract.connect(leverageTestUser).borrow(initialBorrow); - // Now use enterLeverageFromBorrow to increase leverage const additionalBorrowSeed = parseUnits("10", 18); const additionalFlashLoan = parseUnits("20", 18); - - // Get swap data (USDT -> USDC) const totalUSDT = additionalBorrowSeed.add(additionalFlashLoan); - const { swapData, minAmountOut } = await getSwapData(USDT, USDC, totalUSDT.toString(), "0.01"); + + const { swapData, minAmountOut } = await getSwapData(USDT, U, totalUSDT.toString(), "0.01"); if (swapData === "0x") { - console.log("Skipping enterLeverageFromBorrow test - Venus API unavailable"); + leverageResults.push({ + section: "enterLeverageFromBorrow", + test: "Leverage from existing borrow", + status: "SKIPPED", + detail: "Swap data unavailable", + }); + console.log(" [SKIP] Swap data unavailable"); return; } - // Get balances before - const vUSDCBalanceBefore = await vUSDCContract.balanceOf(userAddress); - const usdtBorrowBefore = await vUSDTContract.callStatic.borrowBalanceCurrent(userAddress); + const [vUBalanceBefore, usdtBorrowBefore] = await Promise.all([ + vUContract.balanceOf(userAddress), + vUSDTContract.callStatic.borrowBalanceCurrent(userAddress), + ]); try { - // Call enterLeverageFromBorrow - const tx = await leverageStrategiesManager.connect(leverageTestUser).enterLeverageFromBorrow( - vUSDC, // collateralMarket - vUSDT, // borrowedMarket - additionalBorrowSeed, // borrowedAmountSeed (additional borrow, not flash loaned) - additionalFlashLoan, // borrowedAmountToFlashLoan - minAmountOut, // minAmountOutAfterSwap - swapData, // swapData - ); + const tx = await leverageStrategiesManager + .connect(leverageTestUser) + .enterLeverageFromBorrow(vU, vUSDT, additionalBorrowSeed, additionalFlashLoan, minAmountOut, swapData); const receipt = await tx.wait(); - // Parse and verify LeverageEnteredFromBorrow event - const iface = new ethers.utils.Interface(LEVERAGE_STRATEGIES_MANAGER_ABI); - const events = receipt.logs - .map((log: { topics: string[]; data: string }) => { - try { - return iface.parseLog(log); - } catch { - return null; - } - }) - .filter((e: { name: string } | null) => e && e.name === "LeverageEnteredFromBorrow"); - + const events = parseEvents(receipt, LEVERAGE_STRATEGIES_MANAGER_ABI, "LeverageEnteredFromBorrow"); expect(events.length).to.equal(1); - const event = events[0]; - - // Verify event parameters - expect(event.args.user.toLowerCase()).to.equal(userAddress.toLowerCase()); - expect(event.args.collateralMarket.toLowerCase()).to.equal(vUSDC.toLowerCase()); - expect(event.args.borrowedMarket.toLowerCase()).to.equal(vUSDT.toLowerCase()); - expect(event.args.borrowedAmountSeed).to.equal(additionalBorrowSeed); - expect(event.args.borrowedAmountToFlashLoan).to.equal(additionalFlashLoan); - - // Get balances after - const vUSDCBalanceAfter = await vUSDCContract.balanceOf(userAddress); - const usdtBorrowAfter = await vUSDTContract.callStatic.borrowBalanceCurrent(userAddress); - - expect(vUSDCBalanceAfter).to.be.gt(vUSDCBalanceBefore); + expect(events[0].args.user.toLowerCase()).to.equal(userAddress.toLowerCase()); + expect(events[0].args.collateralMarket.toLowerCase()).to.equal(vU.toLowerCase()); + expect(events[0].args.borrowedMarket.toLowerCase()).to.equal(vUSDT.toLowerCase()); + expect(events[0].args.borrowedAmountSeed).to.equal(additionalBorrowSeed); + expect(events[0].args.borrowedAmountToFlashLoan).to.equal(additionalFlashLoan); + + const [vUBalanceAfter, usdtBorrowAfter] = await Promise.all([ + vUContract.balanceOf(userAddress), + vUSDTContract.callStatic.borrowBalanceCurrent(userAddress), + ]); + + expect(vUBalanceAfter).to.be.gt(vUBalanceBefore); expect(usdtBorrowAfter).to.be.gt(usdtBorrowBefore); - console.log(`Leverage from borrow entered:`); - console.log(` vUSDC balance: ${vUSDCBalanceBefore.toString()} -> ${vUSDCBalanceAfter.toString()}`); - console.log(` USDT borrow: ${usdtBorrowBefore.toString()} -> ${usdtBorrowAfter.toString()}`); + leverageResults.push({ + section: "enterLeverageFromBorrow", + test: "Leverage from existing borrow", + status: "PASSED", + detail: `vU: ${vUBalanceBefore} -> ${vUBalanceAfter}, USDT borrow: ${usdtBorrowBefore} -> ${usdtBorrowAfter}`, + }); } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : String(error); + const msg = error instanceof Error ? error.message : String(error); if ( - errorMessage.includes("0x428c0cc7") || - errorMessage.includes("TokenSwapCallFailed") || - errorMessage.includes("transfer amount exceeds allowance") + msg.includes("0x428c0cc7") || + msg.includes("TokenSwapCallFailed") || + msg.includes("transfer amount exceeds allowance") || + msg.includes("Swap API error") ) { - console.log("Skipping enterLeverageFromBorrow - swap quote expired or route unavailable"); + leverageResults.push({ + section: "enterLeverageFromBorrow", + test: "Leverage from existing borrow", + status: "SKIPPED", + detail: "Swap failed or API unavailable", + }); + console.log(" [SKIP] Swap quote expired or route unavailable"); return; } throw error; @@ -604,61 +747,56 @@ forking(FORK_BLOCK, async () => { }); }); - describe("LeverageStrategiesManager: exitSingleAssetLeverage", () => { - it("should exit single asset leverage position with value checks", async () => { + // =========================================================================== + // exitSingleAssetLeverage + // =========================================================================== + + describe("exitSingleAssetLeverage", () => { + it("should exit single asset leverage position", async () => { const userAddress = await testUser.getAddress(); - // Get current borrow balance const borrowBalance = await vUContract.callStatic.borrowBalanceCurrent(userAddress); - if (borrowBalance.eq(0)) { - console.log("Skipping exitSingleAssetLeverage test - no position to exit"); + leverageResults.push({ + section: "exitSingleAssetLeverage", + test: "Exit single-asset position", + status: "SKIPPED", + detail: "No position to exit", + }); + console.log(" [SKIP] No position to exit"); return; } - // Get balances before const vUBalanceBefore = await vUContract.balanceOf(userAddress); - - // Flash loan amount: borrow balance + 1% buffer const flashLoanAmount = borrowBalance.mul(101).div(100); - // Call exitSingleAssetLeverage const tx = await leverageStrategiesManager.connect(testUser).exitSingleAssetLeverage(vU, flashLoanAmount); const receipt = await tx.wait(); - // Parse and verify SingleAssetLeverageExited event - const iface = new ethers.utils.Interface(LEVERAGE_STRATEGIES_MANAGER_ABI); - const exitEvents = receipt.logs - .map((log: { topics: string[]; data: string }) => { - try { - return iface.parseLog(log); - } catch { - return null; - } - }) - .filter((e: { name: string } | null) => e && e.name === "SingleAssetLeverageExited"); - + const exitEvents = parseEvents(receipt, LEVERAGE_STRATEGIES_MANAGER_ABI, "SingleAssetLeverageExited"); expect(exitEvents.length).to.equal(1); - const event = exitEvents[0]; - - // Verify event parameters - expect(event.args.user.toLowerCase()).to.equal(userAddress.toLowerCase()); - expect(event.args.collateralMarket.toLowerCase()).to.equal(vU.toLowerCase()); - expect(event.args.collateralAmountToFlashLoan).to.equal(flashLoanAmount); + expect(exitEvents[0].args.user.toLowerCase()).to.equal(userAddress.toLowerCase()); + expect(exitEvents[0].args.collateralMarket.toLowerCase()).to.equal(vU.toLowerCase()); + expect(exitEvents[0].args.collateralAmountToFlashLoan).to.equal(flashLoanAmount); - // Get balances after - const vUBalanceAfter = await vUContract.balanceOf(userAddress); - const borrowBalanceAfter = await vUContract.callStatic.borrowBalanceCurrent(userAddress); + const [vUBalanceAfter, borrowBalanceAfter] = await Promise.all([ + vUContract.balanceOf(userAddress), + vUContract.callStatic.borrowBalanceCurrent(userAddress), + ]); - // Borrow balance should be zero expect(borrowBalanceAfter).to.equal(0); - - // User should have less vU (used to repay flash loan) expect(vUBalanceAfter).to.be.lt(vUBalanceBefore); - console.log(`Single asset leverage exited:`); - console.log(` vU balance: ${vUBalanceBefore.toString()} -> ${vUBalanceAfter.toString()}`); - console.log(` U borrow: ${borrowBalance.toString()} -> ${borrowBalanceAfter.toString()}`); + leverageResults.push({ + section: "exitSingleAssetLeverage", + test: "Exit single-asset position", + status: "PASSED", + detail: `vU: ${vUBalanceBefore} -> ${vUBalanceAfter}, Borrow: ${borrowBalance} -> ${borrowBalanceAfter}`, + }); }); }); + + after(() => { + printLeverageResultsSummary(); + }); }); diff --git a/simulations/vip-600/abi/Comptroller.json b/simulations/vip-600/abi/Comptroller.json new file mode 100644 index 000000000..5ca3a7000 --- /dev/null +++ b/simulations/vip-600/abi/Comptroller.json @@ -0,0 +1,4145 @@ +[ + { + "inputs": [], + "name": "AlreadyInSelectedPool", + "type": "error" + }, + { + "inputs": [], + "name": "ArrayLengthMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "BorrowNotAllowedInPool", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyPoolLabel", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + } + ], + "name": "InactivePool", + "type": "error" + }, + { + "inputs": [], + "name": "IncompatibleBorrowedAssets", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidOperationForCorePool", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "enum WeightFunction", + "name": "strategy", + "type": "uint8" + } + ], + "name": "InvalidWeightingStrategy", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorCode", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "shortfall", + "type": "uint256" + } + ], + "name": "LiquidityCheckFailed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "MarketAlreadyListed", + "type": "error" + }, + { + "inputs": [], + "name": "MarketConfigNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "MarketNotListedInCorePool", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + } + ], + "name": "PoolDoesNotExist", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "PoolMarketNotFound", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "enum Action", + "name": "action", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "bool", + "name": "pauseState", + "type": "bool" + } + ], + "name": "ActionPausedMarket", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "state", + "type": "bool" + } + ], + "name": "ActionProtocolPaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": true, + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "oldStatus", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "newStatus", + "type": "bool" + } + ], + "name": "BorrowAllowedUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "approver", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "delegate", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "DelegateUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "facetAddress", + "type": "address" + }, + { + "internalType": "enum IDiamondCut.FacetCutAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "bytes4[]", + "name": "functionSelectors", + "type": "bytes4[]" + } + ], + "indexed": false, + "internalType": "struct IDiamondCut.FacetCut[]", + "name": "_diamondCut", + "type": "tuple[]" + } + ], + "name": "DiamondCut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "venusDelta", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "venusBorrowIndex", + "type": "uint256" + } + ], + "name": "DistributedBorrowerVenus", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "supplier", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "venusDelta", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "venusSupplyIndex", + "type": "uint256" + } + ], + "name": "DistributedSupplierVenus", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "DistributedVAIVaultVenus", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "error", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "info", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "detail", + "type": "uint256" + } + ], + "name": "Failure", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "IsForcedLiquidationEnabledForUserUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "IsForcedLiquidationEnabledUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "MarketEntered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "MarketExited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + } + ], + "name": "MarketListed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "MarketUnlisted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlAddress", + "type": "address" + } + ], + "name": "NewAccessControl", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newBorrowCap", + "type": "uint256" + } + ], + "name": "NewBorrowCap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldCloseFactorMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newCloseFactorMantissa", + "type": "uint256" + } + ], + "name": "NewCloseFactor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldCollateralFactorMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newCollateralFactorMantissa", + "type": "uint256" + } + ], + "name": "NewCollateralFactor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldComptrollerLens", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newComptrollerLens", + "type": "address" + } + ], + "name": "NewComptrollerLens", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": true, + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldLiquidationIncentiveMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newLiquidationIncentiveMantissa", + "type": "uint256" + } + ], + "name": "NewLiquidationIncentive", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldLiquidationThresholdMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newLiquidationThresholdMantissa", + "type": "uint256" + } + ], + "name": "NewLiquidationThreshold", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldLiquidatorContract", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newLiquidatorContract", + "type": "address" + } + ], + "name": "NewLiquidatorContract", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldPauseGuardian", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newPauseGuardian", + "type": "address" + } + ], + "name": "NewPauseGuardian", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract ResilientOracleInterface", + "name": "oldPriceOracle", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract ResilientOracleInterface", + "name": "newPriceOracle", + "type": "address" + } + ], + "name": "NewPriceOracle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract IPrime", + "name": "oldPrimeToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract IPrime", + "name": "newPrimeToken", + "type": "address" + } + ], + "name": "NewPrimeToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newSupplyCap", + "type": "uint256" + } + ], + "name": "NewSupplyCap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldTreasuryAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newTreasuryAddress", + "type": "address" + } + ], + "name": "NewTreasuryAddress", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldTreasuryGuardian", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newTreasuryGuardian", + "type": "address" + } + ], + "name": "NewTreasuryGuardian", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldTreasuryPercent", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newTreasuryPercent", + "type": "uint256" + } + ], + "name": "NewTreasuryPercent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract VAIControllerInterface", + "name": "oldVAIController", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract VAIControllerInterface", + "name": "newVAIController", + "type": "address" + } + ], + "name": "NewVAIController", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldVAIMintRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newVAIMintRate", + "type": "uint256" + } + ], + "name": "NewVAIMintRate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault_", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "releaseStartBlock_", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "releaseInterval_", + "type": "uint256" + } + ], + "name": "NewVAIVaultInfo", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldVenusVAIVaultRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newVenusVAIVaultRate", + "type": "uint256" + } + ], + "name": "NewVenusVAIVaultRate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldXVS", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newXVS", + "type": "address" + } + ], + "name": "NewXVSToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldXVSVToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newXVSVToken", + "type": "address" + } + ], + "name": "NewXVSVToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "bool", + "name": "oldStatus", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "newStatus", + "type": "bool" + } + ], + "name": "PoolActiveStatusUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "string", + "name": "label", + "type": "string" + } + ], + "name": "PoolCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "bool", + "name": "oldStatus", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "newStatus", + "type": "bool" + } + ], + "name": "PoolFallbackStatusUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "string", + "name": "oldLabel", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "newLabel", + "type": "string" + } + ], + "name": "PoolLabelUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": true, + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "PoolMarketInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "indexed": true, + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "PoolMarketRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "previousPoolId", + "type": "uint96" + }, + { + "indexed": true, + "internalType": "uint96", + "name": "newPoolId", + "type": "uint96" + } + ], + "name": "PoolSelected", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newSpeed", + "type": "uint256" + } + ], + "name": "VenusBorrowSpeedUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "VenusGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "holder", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "VenusSeized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newSpeed", + "type": "uint256" + } + ], + "name": "VenusSupplySpeedUpdated", + "type": "event" + }, + { + "stateMutability": "nonpayable", + "type": "fallback" + }, + { + "inputs": [ + { + "internalType": "contract Unitroller", + "name": "unitroller", + "type": "address" + } + ], + "name": "_become", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "_grantXVS", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newAccessControlAddress", + "type": "address" + } + ], + "name": "_setAccessControl", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "markets_", + "type": "address[]" + }, + { + "internalType": "enum Action[]", + "name": "actions_", + "type": "uint8[]" + }, + { + "internalType": "bool", + "name": "paused_", + "type": "bool" + } + ], + "name": "_setActionsPaused", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newCloseFactorMantissa", + "type": "uint256" + } + ], + "name": "_setCloseFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ComptrollerLensInterface", + "name": "comptrollerLens_", + "type": "address" + } + ], + "name": "_setComptrollerLens", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "_setForcedLiquidation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "_setForcedLiquidationForUser", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newLiquidatorContract_", + "type": "address" + } + ], + "name": "_setLiquidatorContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newBorrowCaps", + "type": "uint256[]" + } + ], + "name": "_setMarketBorrowCaps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newSupplyCaps", + "type": "uint256[]" + } + ], + "name": "_setMarketSupplyCaps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newPauseGuardian", + "type": "address" + } + ], + "name": "_setPauseGuardian", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "newOracle", + "type": "address" + } + ], + "name": "_setPriceOracle", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IPrime", + "name": "_prime", + "type": "address" + } + ], + "name": "_setPrimeToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "state", + "type": "bool" + } + ], + "name": "_setProtocolPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newTreasuryGuardian", + "type": "address" + }, + { + "internalType": "address", + "name": "newTreasuryAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newTreasuryPercent", + "type": "uint256" + } + ], + "name": "_setTreasuryData", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VAIControllerInterface", + "name": "vaiController_", + "type": "address" + } + ], + "name": "_setVAIController", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newVAIMintRate", + "type": "uint256" + } + ], + "name": "_setVAIMintRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "releaseStartBlock_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minReleaseAmount_", + "type": "uint256" + } + ], + "name": "_setVAIVaultInfo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "supplySpeeds", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "borrowSpeeds", + "type": "uint256[]" + } + ], + "name": "_setVenusSpeeds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "venusVAIVaultRate_", + "type": "uint256" + } + ], + "name": "_setVenusVAIVaultRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "xvs_", + "type": "address" + } + ], + "name": "_setXVSToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "xvsVToken_", + "type": "address" + } + ], + "name": "_setXVSVToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + } + ], + "name": "_supportMarket", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "accountAssets", + "outputs": [ + { + "internalType": "contract VToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + }, + { + "internalType": "enum Action", + "name": "action", + "type": "uint8" + } + ], + "name": "actionPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96[]", + "name": "poolIds", + "type": "uint96[]" + }, + { + "internalType": "address[]", + "name": "vTokens", + "type": "address[]" + } + ], + "name": "addPoolMarkets", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allMarkets", + "outputs": [ + { + "internalType": "contract VToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "approvedDelegates", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "borrowAmount", + "type": "uint256" + } + ], + "name": "borrowAllowed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "borrowCapGuardian", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "borrowCaps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "borrowAmount", + "type": "uint256" + } + ], + "name": "borrowVerify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + } + ], + "name": "checkMembership", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "holders", + "type": "address[]" + }, + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "bool", + "name": "borrowers", + "type": "bool" + }, + { + "internalType": "bool", + "name": "suppliers", + "type": "bool" + }, + { + "internalType": "bool", + "name": "collateral", + "type": "bool" + } + ], + "name": "claimVenus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "holder", + "type": "address" + }, + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + } + ], + "name": "claimVenus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "holder", + "type": "address" + } + ], + "name": "claimVenus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "holders", + "type": "address[]" + }, + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "bool", + "name": "borrowers", + "type": "bool" + }, + { + "internalType": "bool", + "name": "suppliers", + "type": "bool" + } + ], + "name": "claimVenus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "holder", + "type": "address" + } + ], + "name": "claimVenusAsCollateral", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "closeFactorMantissa", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "comptrollerImplementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "comptrollerLens", + "outputs": [ + { + "internalType": "contract ComptrollerLensInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "corePoolId", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "label", + "type": "string" + } + ], + "name": "createPool", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "facetAddress", + "type": "address" + }, + { + "internalType": "enum IDiamondCut.FacetCutAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "bytes4[]", + "name": "functionSelectors", + "type": "bytes4[]" + } + ], + "internalType": "struct IDiamondCut.FacetCut[]", + "name": "diamondCut_", + "type": "tuple[]" + } + ], + "name": "diamondCut", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "vTokens", + "type": "address[]" + } + ], + "name": "enterMarkets", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + } + ], + "name": "enterPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenAddress", + "type": "address" + } + ], + "name": "exitMarket", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "functionSelector", + "type": "bytes4" + } + ], + "name": "facetAddress", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "facetAddress", + "type": "address" + }, + { + "internalType": "uint96", + "name": "functionSelectorPosition", + "type": "uint96" + } + ], + "internalType": "struct ComptrollerV13Storage.FacetAddressAndPosition", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "facetAddresses", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "facet", + "type": "address" + } + ], + "name": "facetFunctionSelectors", + "outputs": [ + { + "internalType": "bytes4[]", + "name": "", + "type": "bytes4[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "facet", + "type": "address" + } + ], + "name": "facetPosition", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "facets", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "facetAddress", + "type": "address" + }, + { + "internalType": "bytes4[]", + "name": "functionSelectors", + "type": "bytes4[]" + } + ], + "internalType": "struct Diamond.Facet[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getAccountLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAllMarkets", + "outputs": [ + { + "internalType": "contract VToken[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getAssetsIn", + "outputs": [ + { + "internalType": "contract VToken[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getBorrowingPower", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getCollateralFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getEffectiveLiquidationIncentive", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "enum WeightFunction", + "name": "weightingStrategy", + "type": "uint8" + } + ], + "name": "getEffectiveLtvFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenModify", + "type": "address" + }, + { + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "borrowAmount", + "type": "uint256" + } + ], + "name": "getHypotheticalAccountLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getLiquidationIncentive", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getLiquidationThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getPoolMarketIndex", + "outputs": [ + { + "internalType": "PoolMarketId", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + } + ], + "name": "getPoolVTokens", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getXVSAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getXVSVTokenAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint96", + "name": "targetPoolId", + "type": "uint96" + } + ], + "name": "hasValidPoolBorrows", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isComptroller", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isForcedLiquidationEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "isForcedLiquidationEnabledForUser", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + } + ], + "name": "isMarketListed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastPoolId", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + } + ], + "name": "liquidateBorrowAllowed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "actualRepayAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "seizeTokens", + "type": "uint256" + } + ], + "name": "liquidateBorrowVerify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "uint256", + "name": "actualRepayAmount", + "type": "uint256" + } + ], + "name": "liquidateCalculateSeizeTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "uint256", + "name": "actualRepayAmount", + "type": "uint256" + } + ], + "name": "liquidateCalculateSeizeTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "uint256", + "name": "actualRepayAmount", + "type": "uint256" + } + ], + "name": "liquidateVAICalculateSeizeTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "liquidatorContract", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "markets", + "outputs": [ + { + "internalType": "bool", + "name": "isListed", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "collateralFactorMantissa", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isVenus", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "liquidationThresholdMantissa", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidationIncentiveMantissa", + "type": "uint256" + }, + { + "internalType": "uint96", + "name": "marketPoolId", + "type": "uint96" + }, + { + "internalType": "bool", + "name": "isBorrowAllowed", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minReleaseAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "internalType": "uint256", + "name": "mintAmount", + "type": "uint256" + } + ], + "name": "mintAllowed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "mintVAIGuardianPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "internalType": "uint256", + "name": "actualMintAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "mintTokens", + "type": "uint256" + } + ], + "name": "mintVerify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "mintedVAIs", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracle", + "outputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pauseGuardian", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingComptrollerImplementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "poolMarkets", + "outputs": [ + { + "internalType": "bool", + "name": "isListed", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "collateralFactorMantissa", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isVenus", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "liquidationThresholdMantissa", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidationIncentiveMantissa", + "type": "uint256" + }, + { + "internalType": "uint96", + "name": "marketPoolId", + "type": "uint96" + }, + { + "internalType": "bool", + "name": "isBorrowAllowed", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "name": "pools", + "outputs": [ + { + "internalType": "string", + "name": "label", + "type": "string" + }, + { + "internalType": "bool", + "name": "isActive", + "type": "bool" + }, + { + "internalType": "bool", + "name": "allowCorePoolFallback", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "prime", + "outputs": [ + { + "internalType": "contract IPrime", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + } + ], + "name": "redeemAllowed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "internalType": "uint256", + "name": "redeemAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + } + ], + "name": "redeemVerify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "releaseStartBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "removePoolMarket", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "payer", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + } + ], + "name": "repayBorrowAllowed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "payer", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "actualRepayAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "borrowerIndex", + "type": "uint256" + } + ], + "name": "repayBorrowVerify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "repayVAIGuardianPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "seizeTokens", + "type": "uint256" + } + ], + "name": "seizeAllowed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "holders", + "type": "address[]" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "seizeVenus", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "seizeTokens", + "type": "uint256" + } + ], + "name": "seizeVerify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "markets_", + "type": "address[]" + }, + { + "internalType": "enum Action[]", + "name": "actions_", + "type": "uint8[]" + }, + { + "internalType": "bool", + "name": "paused_", + "type": "bool" + } + ], + "name": "setActionsPaused", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "bool", + "name": "allowFallback", + "type": "bool" + } + ], + "name": "setAllowCorePoolFallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newCloseFactorMantissa", + "type": "uint256" + } + ], + "name": "setCloseFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newCollateralFactorMantissa", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newLiquidationThresholdMantissa", + "type": "uint256" + } + ], + "name": "setCollateralFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newCollateralFactorMantissa", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newLiquidationThresholdMantissa", + "type": "uint256" + } + ], + "name": "setCollateralFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vTokenBorrowed", + "type": "address" + }, + { + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "setForcedLiquidation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "bool", + "name": "borrowAllowed", + "type": "bool" + } + ], + "name": "setIsBorrowAllowed", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newLiquidationIncentiveMantissa", + "type": "uint256" + } + ], + "name": "setLiquidationIncentive", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newLiquidationIncentiveMantissa", + "type": "uint256" + } + ], + "name": "setLiquidationIncentive", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newBorrowCaps", + "type": "uint256[]" + } + ], + "name": "setMarketBorrowCaps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken[]", + "name": "vTokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newSupplyCaps", + "type": "uint256[]" + } + ], + "name": "setMarketSupplyCaps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "setMintedVAIOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "bool", + "name": "active", + "type": "bool" + } + ], + "name": "setPoolActive", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "poolId", + "type": "uint96" + }, + { + "internalType": "string", + "name": "newLabel", + "type": "string" + } + ], + "name": "setPoolLabel", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "newOracle", + "type": "address" + } + ], + "name": "setPriceOracle", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IPrime", + "name": "_prime", + "type": "address" + } + ], + "name": "setPrimeToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "supplyCaps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract VToken", + "name": "vToken", + "type": "address" + } + ], + "name": "supportMarket", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "src", + "type": "address" + }, + { + "internalType": "address", + "name": "dst", + "type": "address" + }, + { + "internalType": "uint256", + "name": "transferTokens", + "type": "uint256" + } + ], + "name": "transferAllowed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "src", + "type": "address" + }, + { + "internalType": "address", + "name": "dst", + "type": "address" + }, + { + "internalType": "uint256", + "name": "transferTokens", + "type": "uint256" + } + ], + "name": "transferVerify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "treasuryAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "treasuryGuardian", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "treasuryPercent", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "market", + "type": "address" + } + ], + "name": "unlistMarket", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegate", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "updateDelegate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "userPoolId", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaiController", + "outputs": [ + { + "internalType": "contract VAIControllerInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaiMintRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaiVaultAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusAccrued", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusBorrowSpeeds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusBorrowState", + "outputs": [ + { + "internalType": "uint224", + "name": "index", + "type": "uint224" + }, + { + "internalType": "uint32", + "name": "block", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusBorrowerIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "venusInitialIndex", + "outputs": [ + { + "internalType": "uint224", + "name": "", + "type": "uint224" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusSupplierIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusSupplySpeeds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "venusSupplyState", + "outputs": [ + { + "internalType": "uint224", + "name": "index", + "type": "uint224" + }, + { + "internalType": "uint32", + "name": "block", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "venusVAIVaultRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-600/abi/ERC20.json b/simulations/vip-600/abi/ERC20.json new file mode 100644 index 000000000..3a509c9c4 --- /dev/null +++ b/simulations/vip-600/abi/ERC20.json @@ -0,0 +1,134 @@ +[ + { + "inputs": [ + { "internalType": "string", "name": "name_", "type": "string" }, + { "internalType": "string", "name": "symbol_", "type": "string" }, + { "internalType": "uint8", "name": "decimals_", "type": "uint8" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "spender", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "account", "type": "address" }], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "subtractedValue", "type": "uint256" } + ], + "name": "decreaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "amount", "type": "uint256" }], + "name": "faucet", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "addedValue", "type": "uint256" } + ], + "name": "increaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/simulations/vip-600/abi/SwapHelper.json b/simulations/vip-600/abi/SwapHelper.json new file mode 100644 index 000000000..fb92e0573 --- /dev/null +++ b/simulations/vip-600/abi/SwapHelper.json @@ -0,0 +1,444 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "backendSigner_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "CallerNotAuthorized", + "type": "error" + }, + { + "inputs": [], + "name": "DeadlineReached", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidShortString", + "type": "error" + }, + { + "inputs": [], + "name": "MissingSignature", + "type": "error" + }, + { + "inputs": [], + "name": "NoCallsProvided", + "type": "error" + }, + { + "inputs": [], + "name": "SaltAlreadyUsed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "str", + "type": "string" + } + ], + "name": "StringTooLong", + "type": "error" + }, + { + "inputs": [], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ApprovedMax", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldSigner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newSigner", + "type": "address" + } + ], + "name": "BackendSignerUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "EIP712DomainChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "GenericCallExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "callsCount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + } + ], + "name": "MulticallExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Swept", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "approveMax", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "backendSigner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { + "internalType": "bytes1", + "name": "fields", + "type": "bytes1" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "extensions", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "genericCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "calls", + "type": "bytes[]" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "multicall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newSigner", + "type": "address" + } + ], + "name": "setBackendSigner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "sweep", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "usedSalts", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-600/abi/SwapRouter.json b/simulations/vip-600/abi/SwapRouter.json new file mode 100644 index 000000000..ab208ac75 --- /dev/null +++ b/simulations/vip-600/abi/SwapRouter.json @@ -0,0 +1,618 @@ +[ + { + "inputs": [ + { + "internalType": "contract IComptroller", + "name": "_comptroller", + "type": "address" + }, + { + "internalType": "contract SwapHelper", + "name": "_swapHelper", + "type": "address" + }, + { + "internalType": "contract IWBNB", + "name": "_wrappedNative", + "type": "address" + }, + { + "internalType": "address", + "name": "_nativeVToken", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountOut", + "type": "uint256" + } + ], + "name": "InsufficientAmountOut", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "MarketNotListed", + "type": "error" + }, + { + "inputs": [], + "name": "NativeTransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "NoTokensReceived", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorCode", + "type": "uint256" + } + ], + "name": "RepayFailed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorCode", + "type": "uint256" + } + ], + "name": "SupplyFailed", + "type": "error" + }, + { + "inputs": [], + "name": "SwapFailed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "UnauthorizedNativeSender", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAmount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountRepaid", + "type": "uint256" + } + ], + "name": "SwapAndRepay", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountSupplied", + "type": "uint256" + } + ], + "name": "SwapAndSupply", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SweepNative", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SweepToken", + "type": "event" + }, + { + "inputs": [], + "name": "COMPTROLLER", + "outputs": [ + { + "internalType": "contract IComptroller", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NATIVE_TOKEN_ADDR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NATIVE_VTOKEN", + "outputs": [ + { + "internalType": "contract IVBNB", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SWAP_HELPER", + "outputs": [ + { + "internalType": "contract SwapHelper", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WRAPPED_NATIVE", + "outputs": [ + { + "internalType": "contract IWBNB", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountOut", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "swapCallData", + "type": "bytes" + } + ], + "name": "swapAndRepay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "uint256", + "name": "maxAmountIn", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "swapCallData", + "type": "bytes" + } + ], + "name": "swapAndRepayFull", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountOut", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "swapCallData", + "type": "bytes" + } + ], + "name": "swapAndSupply", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "minAmountOut", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "swapCallData", + "type": "bytes" + } + ], + "name": "swapNativeAndRepay", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "bytes", + "name": "swapCallData", + "type": "bytes" + } + ], + "name": "swapNativeAndRepayFull", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "minAmountOut", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "swapCallData", + "type": "bytes" + } + ], + "name": "swapNativeAndSupply", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "sweepNative", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "token", + "type": "address" + } + ], + "name": "sweepToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/simulations/vip-600/abi/VToken.json b/simulations/vip-600/abi/VToken.json new file mode 100644 index 000000000..61a618463 --- /dev/null +++ b/simulations/vip-600/abi/VToken.json @@ -0,0 +1,1747 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "cashPrior", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "interestAccumulated", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "borrowIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalBorrows", + "type": "uint256" + } + ], + "name": "AccrueInterest", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "borrowAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "accountBorrows", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalBorrows", + "type": "uint256" + } + ], + "name": "Borrow", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "error", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "info", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "detail", + "type": "uint256" + } + ], + "name": "Failure", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "vTokenCollateral", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "seizeTokens", + "type": "uint256" + } + ], + "name": "LiquidateBorrow", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "mintAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "mintTokens", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalSupply", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "payer", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "mintAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "mintTokens", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalSupply", + "type": "uint256" + } + ], + "name": "MintBehalf", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlAddress", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "NewAdmin", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract ComptrollerInterface", + "name": "oldComptroller", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract ComptrollerInterface", + "name": "newComptroller", + "type": "address" + } + ], + "name": "NewComptroller", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract InterestRateModel", + "name": "oldInterestRateModel", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract InterestRateModel", + "name": "newInterestRateModel", + "type": "address" + } + ], + "name": "NewMarketInterestRateModel", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldPendingAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newPendingAdmin", + "type": "address" + } + ], + "name": "NewPendingAdmin", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldProtocolShareReserve", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newProtocolShareReserve", + "type": "address" + } + ], + "name": "NewProtocolShareReserve", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldReduceReservesBlockDelta", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newReduceReservesBlockDelta", + "type": "uint256" + } + ], + "name": "NewReduceReservesBlockDelta", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldReserveFactorMantissa", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newReserveFactorMantissa", + "type": "uint256" + } + ], + "name": "NewReserveFactor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "redeemAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalSupply", + "type": "uint256" + } + ], + "name": "Redeem", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + } + ], + "name": "RedeemFee", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "payer", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "accountBorrows", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalBorrows", + "type": "uint256" + } + ], + "name": "RepayBorrow", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "benefactor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "addAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newTotalReserves", + "type": "uint256" + } + ], + "name": "ReservesAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "protocolShareReserve", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reduceAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newTotalReserves", + "type": "uint256" + } + ], + "name": "ReservesReduced", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "constant": false, + "inputs": [], + "name": "_acceptAdmin", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "addAmount", + "type": "uint256" + } + ], + "name": "_addReserves", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "reduceAmount_", + "type": "uint256" + } + ], + "name": "_reduceReserves", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "contract ComptrollerInterface", + "name": "newComptroller", + "type": "address" + } + ], + "name": "_setComptroller", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "contract InterestRateModel", + "name": "newInterestRateModel_", + "type": "address" + } + ], + "name": "_setInterestRateModel", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address payable", + "name": "newPendingAdmin", + "type": "address" + } + ], + "name": "_setPendingAdmin", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "newReserveFactorMantissa_", + "type": "uint256" + } + ], + "name": "_setReserveFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "accrualBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "accrueInterest", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOfUnderlying", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "borrowAmount", + "type": "uint256" + } + ], + "name": "borrow", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "borrowBalanceCurrent", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "borrowBalanceStored", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "borrowAmount", + "type": "uint256" + } + ], + "name": "borrowBehalf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "borrowIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "borrowRatePerBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "comptroller", + "outputs": [ + { + "internalType": "contract ComptrollerInterface", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "exchangeRateCurrent", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "exchangeRateStored", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getAccountSnapshot", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getCash", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "underlying_", + "type": "address" + }, + { + "internalType": "contract ComptrollerInterface", + "name": "comptroller_", + "type": "address" + }, + { + "internalType": "contract InterestRateModel", + "name": "interestRateModel_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "initialExchangeRateMantissa_", + "type": "uint256" + }, + { + "internalType": "string", + "name": "name_", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol_", + "type": "string" + }, + { + "internalType": "uint8", + "name": "decimals_", + "type": "uint8" + } + ], + "name": "initialize", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "contract ComptrollerInterface", + "name": "comptroller_", + "type": "address" + }, + { + "internalType": "contract InterestRateModel", + "name": "interestRateModel_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "initialExchangeRateMantissa_", + "type": "uint256" + }, + { + "internalType": "string", + "name": "name_", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol_", + "type": "string" + }, + { + "internalType": "uint8", + "name": "decimals_", + "type": "uint8" + } + ], + "name": "initialize", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "interestRateModel", + "outputs": [ + { + "internalType": "contract InterestRateModel", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isVToken", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + }, + { + "internalType": "contract VTokenInterface", + "name": "vTokenCollateral", + "type": "address" + } + ], + "name": "liquidateBorrow", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "mintAmount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "mintAmount", + "type": "uint256" + } + ], + "name": "mintBehalf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "pendingAdmin", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "protocolShareReserve", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + } + ], + "name": "redeem", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + } + ], + "name": "redeemBehalf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "redeemAmount", + "type": "uint256" + } + ], + "name": "redeemUnderlying", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "internalType": "uint256", + "name": "redeemAmount", + "type": "uint256" + } + ], + "name": "redeemUnderlyingBehalf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "reduceReservesBlockDelta", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "reduceReservesBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + } + ], + "name": "repayBorrow", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "repayAmount", + "type": "uint256" + } + ], + "name": "repayBorrowBehalf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "reserveFactorMantissa", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "seizeTokens", + "type": "uint256" + } + ], + "name": "seize", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "newAccessControlManagerAddress", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address payable", + "name": "protcolShareReserve_", + "type": "address" + } + ], + "name": "setProtocolShareReserve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "newReduceReservesBlockDelta_", + "type": "uint256" + } + ], + "name": "setReduceReservesBlockDelta", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "supplyRatePerBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalBorrows", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "totalBorrowsCurrent", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalReserves", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "dst", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "src", + "type": "address" + }, + { + "internalType": "address", + "name": "dst", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "underlying", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-600/bscmainnet.ts b/simulations/vip-600/bscmainnet.ts new file mode 100644 index 000000000..c7ff3721c --- /dev/null +++ b/simulations/vip-600/bscmainnet.ts @@ -0,0 +1,952 @@ +import { expect } from "chai"; +import { BigNumber, Contract, Signer, Wallet } from "ethers"; +import { parseUnits } from "ethers/lib/utils"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { initMainnetUser, setMaxStalePeriodInBinanceOracle, setMaxStalePeriodInChainlinkOracle } from "src/utils"; +import { forking, testVip } from "src/vip-framework"; + +import { SWAP_HELPER, SWAP_ROUTER, UNITROLLER, vip600 } from "../../vips/vip-600/bscmainnet"; +import COMPTROLLER_ABI from "./abi/Comptroller.json"; +import ERC20_ABI from "./abi/ERC20.json"; +import SWAP_HELPER_ABI from "./abi/SwapHelper.json"; +import SWAP_ROUTER_ABI from "./abi/SwapRouter.json"; +import VTOKEN_ABI from "./abi/VToken.json"; + +const { bscmainnet } = NETWORK_ADDRESSES; + +// ============================================================================= +// Constants +// ============================================================================= + +const vUSDT = "0xfD5840Cd36d94D7229439859C0112a4185BC0255"; +const vUSDC = "0xecA88125a5ADbe82614ffC12D0DB554E2e2867C8"; +const vBNB = "0xA07c5b74C9B40447a954e1466938b865b6BBea36"; + +const USDT = "0x55d398326f99059fF775485246999027B3197955"; +const USDC = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d"; +const WBNB = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"; + +const USDT_HOLDER = "0xF977814e90dA44bFA03b6295A0616a897441aceC"; +const USDC_HOLDER = "0x8894E0a0c962CB723c1976a4421c95949bE2D4E3"; + +const NATIVE_TOKEN_ADDR = "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"; +const FORK_BLOCK = 76556273; + +// PancakeSwap V2 Router on BSC +const PANCAKE_V2_ROUTER = "0x10ED43C718714eb63d5aA57B78B54704E256024E"; +const PANCAKE_V2_ROUTER_ABI = [ + "function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)", + "function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)", +]; + +// ============================================================================= +// EIP-712 Swap Signer +// ============================================================================= + +let swapSignerWallet: Wallet; +let swapHelperContract: Contract; +let eip712Domain: { name: string; version: string; chainId: number; verifyingContract: string }; +let saltCounter = 0; + +async function setupSwapSigner() { + swapSignerWallet = new Wallet("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", ethers.provider); + + swapHelperContract = new ethers.Contract(SWAP_HELPER, SWAP_HELPER_ABI, ethers.provider); + + const swapHelperOwner = await swapHelperContract.owner(); + const impersonatedOwner = await initMainnetUser(swapHelperOwner, ethers.utils.parseEther("1")); + await swapHelperContract.connect(impersonatedOwner).setBackendSigner(swapSignerWallet.address); + + const [domain, network] = await Promise.all([swapHelperContract.eip712Domain(), ethers.provider.getNetwork()]); + eip712Domain = { + name: domain.name, + version: domain.version, + chainId: network.chainId, + verifyingContract: domain.verifyingContract, + }; +} + +// ============================================================================= +// Swap Calldata Builders (API-first with PancakeSwap V2 fallback) +// ============================================================================= + +/** + * Tries the Venus swap API first (5s timeout), falls back to PancakeSwap V2 on-chain. + */ +async function buildSwapCalldata( + tokenIn: string, + tokenOut: string, + amountIn: BigNumber, + recipient: string, + slippageBps: number = 100, +): Promise<{ swapData: string; minAmountOut: BigNumber; amountOut: BigNumber }> { + try { + const result = await buildSwapCalldataFromAPI(tokenIn, tokenOut, amountIn, recipient); + return { ...result, amountOut: result.minAmountOut }; + } catch (apiError) { + console.log( + ` Swap API unavailable (${ + apiError instanceof Error ? apiError.message : apiError + }), falling back to PancakeSwap V2`, + ); + } + return buildSwapCalldataFromPancakeV2(tokenIn, tokenOut, amountIn, recipient, slippageBps); +} + +async function buildSwapCalldataFromAPI( + tokenIn: string, + tokenOut: string, + amountIn: BigNumber, + recipient: string, +): Promise<{ swapData: string; minAmountOut: BigNumber }> { + const TEN_YEARS_SECS = 10 * 365 * 24 * 60 * 60; + const deadline = Math.floor(Date.now() / 1000) + TEN_YEARS_SECS; + const actualTokenIn = tokenIn.toLowerCase() === NATIVE_TOKEN_ADDR.toLowerCase() ? WBNB : tokenIn; + + const params = new URLSearchParams({ + chainId: "56", + tokenInAddress: actualTokenIn, + tokenOutAddress: tokenOut, + slippagePercentage: "0.5", + recipientAddress: SWAP_HELPER, + deadlineTimestampSecs: deadline.toString(), + type: "exact-in", + shouldTransferToReceiver: "false", + exactAmountInMantissa: amountIn.toString(), + }); + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 20000); + + try { + const res = await fetch(`https://api.venus.io/find-swap?${params}`, { signal: controller.signal }); + if (!res.ok) throw new Error(`Swap API error: ${res.status}`); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const json = (await res.json()) as any; + if (!json.quotes?.length) throw new Error(`No API route found for ${tokenIn} -> ${tokenOut}`); + + const quote = json.quotes[0]; + const swapHelperIface = new ethers.utils.Interface(SWAP_HELPER_ABI); + const calls: string[] = []; + + for (const tx of quote.txs) { + calls.push(swapHelperIface.encodeFunctionData("approveMax", [actualTokenIn, tx.target])); + calls.push(swapHelperIface.encodeFunctionData("genericCall", [tx.target, tx.data])); + } + calls.push(swapHelperIface.encodeFunctionData("sweep", [tokenOut, recipient])); + + const salt = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["uint256"], [++saltCounter])); + const types = { + Multicall: [ + { name: "caller", type: "address" }, + { name: "calls", type: "bytes[]" }, + { name: "deadline", type: "uint256" }, + { name: "salt", type: "bytes32" }, + ], + }; + const value = { caller: SWAP_ROUTER, calls, deadline, salt }; + const signature = await swapSignerWallet._signTypedData(eip712Domain, types, value); + const multicallData = swapHelperIface.encodeFunctionData("multicall", [calls, deadline, salt, signature]); + + return { swapData: multicallData, minAmountOut: BigNumber.from(1) }; + } finally { + clearTimeout(timeoutId); + } +} + +async function buildSwapCalldataFromPancakeV2( + tokenIn: string, + tokenOut: string, + amountIn: BigNumber, + recipient: string, + slippageBps: number, +): Promise<{ swapData: string; minAmountOut: BigNumber; amountOut: BigNumber }> { + const pancakeRouter = new ethers.Contract(PANCAKE_V2_ROUTER, PANCAKE_V2_ROUTER_ABI, ethers.provider); + const actualTokenIn = tokenIn.toLowerCase() === NATIVE_TOKEN_ADDR.toLowerCase() ? WBNB : tokenIn; + + let path: string[]; + let amounts: BigNumber[]; + + try { + path = [actualTokenIn, tokenOut]; + amounts = await pancakeRouter.getAmountsOut(amountIn, path); + } catch { + if (actualTokenIn !== WBNB && tokenOut !== WBNB) { + path = [actualTokenIn, WBNB, tokenOut]; + amounts = await pancakeRouter.getAmountsOut(amountIn, path); + } else { + throw new Error(`No route found for ${tokenIn} -> ${tokenOut}`); + } + } + + const amountOut = amounts[amounts.length - 1]; + const minAmountOut = amountOut.mul(10000 - slippageBps).div(10000); + + const swapHelperIface = new ethers.utils.Interface(SWAP_HELPER_ABI); + const pancakeIface = new ethers.utils.Interface(PANCAKE_V2_ROUTER_ABI); + const deadline = Math.floor(Date.now() / 1000) + 3600; + + const calls = [ + swapHelperIface.encodeFunctionData("approveMax", [actualTokenIn, PANCAKE_V2_ROUTER]), + swapHelperIface.encodeFunctionData("genericCall", [ + PANCAKE_V2_ROUTER, + pancakeIface.encodeFunctionData("swapExactTokensForTokens", [ + amountIn, + minAmountOut, + path, + SWAP_HELPER, + deadline, + ]), + ]), + swapHelperIface.encodeFunctionData("sweep", [tokenOut, recipient]), + ]; + + const salt = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["uint256"], [++saltCounter])); + const types = { + Multicall: [ + { name: "caller", type: "address" }, + { name: "calls", type: "bytes[]" }, + { name: "deadline", type: "uint256" }, + { name: "salt", type: "bytes32" }, + ], + }; + const value = { caller: SWAP_ROUTER, calls, deadline, salt }; + const signature = await swapSignerWallet._signTypedData(eip712Domain, types, value); + const multicallData = swapHelperIface.encodeFunctionData("multicall", [calls, deadline, salt, signature]); + + return { swapData: multicallData, minAmountOut, amountOut }; +} + +// ============================================================================= +// Swap Data Wrappers +// ============================================================================= + +async function getSwapData( + tokenInAddress: string, + tokenOutAddress: string, + exactAmountInMantissa: string, + recipientAddress: string, + slippagePercentage: string = "0.01", +): Promise<{ swapData: string; minAmountOut: BigNumber; amountOut: BigNumber }> { + const slippageBps = Math.round(parseFloat(slippagePercentage) * 10000); + return buildSwapCalldata( + tokenInAddress, + tokenOutAddress, + BigNumber.from(exactAmountInMantissa), + recipientAddress, + slippageBps, + ); +} + +async function getNativeSwapData( + tokenOutAddress: string, + exactAmountInMantissa: string, + recipientAddress: string, + slippagePercentage: string = "0.01", +): Promise<{ swapData: string; minAmountOut: BigNumber; amountOut: BigNumber }> { + return getSwapData(NATIVE_TOKEN_ADDR, tokenOutAddress, exactAmountInMantissa, recipientAddress, slippagePercentage); +} + +// ============================================================================= +// Results Tracker +// ============================================================================= + +interface SwapRouterResult { + section: string; + test: string; + status: "PASSED" | "SKIPPED" | "FAILED"; + detail: string; +} +const swapRouterResults: SwapRouterResult[] = []; + +function printSwapRouterResultsSummary() { + const passed = swapRouterResults.filter(r => r.status === "PASSED"); + const failed = swapRouterResults.filter(r => r.status === "FAILED"); + const skipped = swapRouterResults.filter(r => r.status === "SKIPPED"); + + console.log("\n" + "=".repeat(120)); + console.log(" SWAP ROUTER RESULTS SUMMARY"); + console.log("=".repeat(120)); + + for (const [label, list] of [ + ["PASSED", passed], + ["FAILED", failed], + ["SKIPPED", skipped], + ] as const) { + if (list.length === 0) continue; + console.log(`\n ${label} (${list.length})`); + console.log(" " + "-".repeat(116)); + console.log(` ${"Section".padEnd(30)} ${"Test".padEnd(55)} ${label === "PASSED" ? "Detail" : "Reason"}`); + console.log(" " + "-".repeat(116)); + for (const r of list) { + console.log(` ${r.section.padEnd(30)} ${r.test.padEnd(55)} ${r.detail}`); + } + } + + console.log("\n" + "=".repeat(120)); + console.log( + ` Total: ${swapRouterResults.length} | Passed: ${passed.length} | Failed: ${failed.length} | Skipped: ${skipped.length}`, + ); + console.log("=".repeat(120) + "\n"); +} + +// ============================================================================= +// Helpers +// ============================================================================= + +function parseEventFromReceipt(receipt: any, eventName: string): any[] { + const iface = new ethers.utils.Interface(SWAP_ROUTER_ABI); + return receipt.logs + .map((log: { topics: string[]; data: string }) => { + try { + return iface.parseLog(log); + } catch { + return null; + } + }) + .filter((e: { name: string } | null) => e && e.name === eventName); +} + +// ============================================================================= +// Test Suite +// ============================================================================= + +forking(FORK_BLOCK, async () => { + let swapRouter: Contract; + let comptroller: Contract; + let usdt: Contract; + let usdc: Contract; + let vUSDTContract: Contract; + let vUSDCContract: Contract; + let impersonatedTimelock: Signer; + let testUser: Signer; + let testUserAddress: string; + + before(async () => { + // Instantiate contracts (pure local — no RPC) + swapRouter = await ethers.getContractAt(SWAP_ROUTER_ABI, SWAP_ROUTER); + comptroller = new ethers.Contract(UNITROLLER, COMPTROLLER_ABI, ethers.provider); + usdt = new ethers.Contract(USDT, ERC20_ABI, ethers.provider); + usdc = new ethers.Contract(USDC, ERC20_ABI, ethers.provider); + vUSDTContract = new ethers.Contract(vUSDT, VTOKEN_ABI, ethers.provider); + vUSDCContract = new ethers.Contract(vUSDC, VTOKEN_ABI, ethers.provider); + + const signers = await ethers.getSigners(); + testUser = signers[0]; + testUserAddress = await testUser.getAddress(); + + // Step 1: Impersonate users and setup swap signer (no shared-signer conflicts) + const [, usdtHolder, usdcHolder, timelockSigner] = await Promise.all([ + setupSwapSigner(), + initMainnetUser(USDT_HOLDER, ethers.utils.parseEther("10")), + initMainnetUser(USDC_HOLDER, ethers.utils.parseEther("10")), + initMainnetUser(bscmainnet.NORMAL_TIMELOCK, ethers.utils.parseEther("10")), + ]); + impersonatedTimelock = timelockSigner; + + // Step 2: Configure oracles + // Chainlink calls use NORMAL_TIMELOCK as signer — must be sequential to avoid nonce conflicts + await setMaxStalePeriodInChainlinkOracle( + bscmainnet.CHAINLINK_ORACLE, + USDC, + ethers.constants.AddressZero, + bscmainnet.NORMAL_TIMELOCK, + 315360000, + ); + await setMaxStalePeriodInChainlinkOracle( + bscmainnet.CHAINLINK_ORACLE, + USDT, + ethers.constants.AddressZero, + bscmainnet.NORMAL_TIMELOCK, + 315360000, + ); + // Binance oracle calls use oracle.owner() as signer — same owner so also sequential + await setMaxStalePeriodInBinanceOracle(bscmainnet.BINANCE_ORACLE, "USDC", 315360000); + await setMaxStalePeriodInBinanceOracle(bscmainnet.BINANCE_ORACLE, "USDT", 315360000); + + // Step 3: Fund test user (different signers — safe in parallel) + await Promise.all([ + usdt.connect(usdtHolder).transfer(testUserAddress, parseUnits("10000", 18)), + usdc.connect(usdcHolder).transfer(testUserAddress, parseUnits("10000", 18)), + ]); + }); + + // =========================================================================== + // Pre-VIP + // =========================================================================== + + describe("Pre-VIP behavior", () => { + it("should have correct ownership state and immutable parameters", async () => { + const [pendingOwner, owner, comptrollerAddr, swapHelperAddr, wrappedNative, nativeVToken, nativeTokenAddr] = + await Promise.all([ + swapRouter.pendingOwner(), + swapRouter.owner(), + swapRouter.COMPTROLLER(), + swapRouter.SWAP_HELPER(), + swapRouter.WRAPPED_NATIVE(), + swapRouter.NATIVE_VTOKEN(), + swapRouter.NATIVE_TOKEN_ADDR(), + ]); + + const isValidState = pendingOwner === bscmainnet.NORMAL_TIMELOCK || owner === bscmainnet.NORMAL_TIMELOCK; + expect(isValidState).to.be.true; + expect(comptrollerAddr).to.equal(UNITROLLER); + expect(swapHelperAddr).to.equal(SWAP_HELPER); + expect(wrappedNative).to.equal(WBNB); + expect(nativeVToken).to.equal(vBNB); + expect(nativeTokenAddr).to.equal(NATIVE_TOKEN_ADDR); + }); + }); + + // =========================================================================== + // VIP Execution + // =========================================================================== + + testVip("VIP-600", await vip600(), { + callbackAfterExecution: async txResponse => { + const receipt = await txResponse.wait(); + const events = parseEventFromReceipt(receipt, "OwnershipTransferred"); + expect(events.length).to.be.lte(1); + }, + }); + + // =========================================================================== + // Post-VIP + // =========================================================================== + + describe("Post-VIP behavior", () => { + it("should have NORMAL_TIMELOCK as owner with zero pending owner", async () => { + const [owner, pendingOwner] = await Promise.all([swapRouter.owner(), swapRouter.pendingOwner()]); + expect(owner).to.equal(bscmainnet.NORMAL_TIMELOCK); + expect(pendingOwner).to.equal(ethers.constants.AddressZero); + }); + }); + + // =========================================================================== + // swapAndSupply + // =========================================================================== + + describe("swapAndSupply", () => { + it("should swap USDT -> USDC and supply to vUSDC", async () => { + const amountIn = parseUnits("100", 18); + const { swapData, minAmountOut } = await getSwapData(USDT, USDC, amountIn.toString(), SWAP_ROUTER, "0.01"); + + const [usdtBefore, vUSDCBefore] = await Promise.all([ + usdt.balanceOf(testUserAddress), + vUSDCContract.balanceOf(testUserAddress), + ]); + + await usdt.connect(testUser).approve(SWAP_ROUTER, amountIn); + + const tx = await swapRouter.connect(testUser).swapAndSupply(vUSDC, USDT, amountIn, minAmountOut, swapData); + const receipt = await tx.wait(); + + const events = parseEventFromReceipt(receipt, "SwapAndSupply"); + expect(events.length).to.equal(1); + expect(events[0].args.user.toLowerCase()).to.equal(testUserAddress.toLowerCase()); + expect(events[0].args.amountIn).to.equal(amountIn); + expect(events[0].args.amountOut).to.be.gte(minAmountOut); + + const [usdtAfter, vUSDCAfter] = await Promise.all([ + usdt.balanceOf(testUserAddress), + vUSDCContract.balanceOf(testUserAddress), + ]); + expect(usdtAfter).to.be.lt(usdtBefore); + expect(vUSDCAfter).to.be.gt(vUSDCBefore); + + swapRouterResults.push({ + section: "swapAndSupply", + test: "USDT -> USDC supply to vUSDC", + status: "PASSED", + detail: `amountIn: ${amountIn}, amountOut: ${events[0].args.amountOut}`, + }); + }); + + it("should revert with MarketNotListed, ZeroAmount, and ZeroAddress", async () => { + await usdt.connect(testUser).approve(SWAP_ROUTER, parseUnits("100", 18)); + + await expect( + swapRouter + .connect(testUser) + .swapAndSupply("0x0000000000000000000000000000000000000001", USDT, parseUnits("100", 18), 0, "0x"), + ).to.be.revertedWithCustomError(swapRouter, "MarketNotListed"); + await expect(swapRouter.connect(testUser).swapAndSupply(vUSDC, USDT, 0, 0, "0x")).to.be.revertedWithCustomError( + swapRouter, + "ZeroAmount", + ); + await expect( + swapRouter.connect(testUser).swapAndSupply(ethers.constants.AddressZero, USDT, parseUnits("100", 18), 0, "0x"), + ).to.be.revertedWithCustomError(swapRouter, "ZeroAddress"); + + for (const name of ["MarketNotListed", "ZeroAmount", "ZeroAddress"]) { + swapRouterResults.push({ + section: "swapAndSupply", + test: `Revert ${name}`, + status: "PASSED", + detail: "Correctly reverted", + }); + } + }); + }); + + // =========================================================================== + // swapAndRepay + // =========================================================================== + + describe("swapAndRepay", () => { + before(async () => { + const supplyAmount = parseUnits("5000", 18); + await usdc.connect(testUser).approve(vUSDC, supplyAmount); + await vUSDCContract.connect(testUser).mint(supplyAmount); + await comptroller.connect(testUser).enterMarkets([vUSDC]); + await vUSDTContract.connect(testUser).borrow(parseUnits("1000", 18)); + }); + + it("should swap USDC -> USDT and repay vUSDT borrow", async () => { + const amountIn = parseUnits("500", 18); + const [borrowBefore, { swapData, minAmountOut }] = await Promise.all([ + vUSDTContract.callStatic.borrowBalanceCurrent(testUserAddress), + getSwapData(USDC, USDT, amountIn.toString(), SWAP_ROUTER, "0.01"), + ]); + expect(borrowBefore).to.be.gt(0); + + await usdc.connect(testUser).approve(SWAP_ROUTER, amountIn); + + const tx = await swapRouter.connect(testUser).swapAndRepay(vUSDT, USDC, amountIn, minAmountOut, swapData); + const receipt = await tx.wait(); + + const events = parseEventFromReceipt(receipt, "SwapAndRepay"); + expect(events.length).to.equal(1); + expect(events[0].args.amountRepaid).to.be.gt(0); + + const borrowAfter = await vUSDTContract.callStatic.borrowBalanceCurrent(testUserAddress); + expect(borrowAfter).to.be.lt(borrowBefore); + + swapRouterResults.push({ + section: "swapAndRepay", + test: "USDC -> USDT repay vUSDT borrow", + status: "PASSED", + detail: `Borrow: ${borrowBefore} -> ${borrowAfter}`, + }); + }); + + it("should revert with ZeroAddress when vToken is zero", async () => { + await expect( + swapRouter.connect(testUser).swapAndRepay(ethers.constants.AddressZero, USDT, parseUnits("100", 18), 0, "0x"), + ).to.be.revertedWithCustomError(swapRouter, "ZeroAddress"); + + swapRouterResults.push({ + section: "swapAndRepay", + test: "Revert ZeroAddress", + status: "PASSED", + detail: "Correctly reverted", + }); + }); + }); + + // =========================================================================== + // swapAndRepayFull + // =========================================================================== + + describe("swapAndRepayFull", () => { + before(async () => { + const borrowBalance = await vUSDTContract.callStatic.borrowBalanceCurrent(testUserAddress); + if (borrowBalance.eq(0)) { + const supplyAmount = parseUnits("2000", 18); + await usdc.connect(testUser).approve(vUSDC, supplyAmount); + await vUSDCContract.connect(testUser).mint(supplyAmount); + await vUSDTContract.connect(testUser).borrow(parseUnits("500", 18)); + } + }); + + it("should swap USDC -> USDT and fully repay vUSDT borrow", async () => { + const borrowBalance = await vUSDTContract.callStatic.borrowBalanceCurrent(testUserAddress); + if (borrowBalance.eq(0)) { + swapRouterResults.push({ + section: "swapAndRepayFull", + test: "USDC -> USDT full repay vUSDT", + status: "SKIPPED", + detail: "No borrow balance", + }); + console.log(" [SKIP] No borrow balance"); + return; + } + + const maxAmountIn = borrowBalance.mul(110).div(100); + const { swapData } = await getSwapData(USDC, USDT, maxAmountIn.toString(), SWAP_ROUTER, "0.01"); + + await usdc.connect(testUser).approve(SWAP_ROUTER, maxAmountIn); + + const tx = await swapRouter.connect(testUser).swapAndRepayFull(vUSDT, USDC, maxAmountIn, swapData); + const receipt = await tx.wait(); + + const events = parseEventFromReceipt(receipt, "SwapAndRepay"); + expect(events.length).to.equal(1); + + const borrowAfter = await vUSDTContract.callStatic.borrowBalanceCurrent(testUserAddress); + expect(borrowAfter).to.equal(0); + + swapRouterResults.push({ + section: "swapAndRepayFull", + test: "USDC -> USDT full repay vUSDT", + status: "PASSED", + detail: `Borrow: ${borrowBalance} -> 0`, + }); + }); + }); + + // =========================================================================== + // swapNativeAndSupply + // =========================================================================== + + describe("swapNativeAndSupply", () => { + it("should swap BNB -> USDT and supply to vUSDT", async () => { + const amountIn = parseUnits("1", 18); + const [{ swapData, minAmountOut }, vUSDTBefore] = await Promise.all([ + getNativeSwapData(USDT, amountIn.toString(), SWAP_ROUTER, "0.01"), + vUSDTContract.balanceOf(testUserAddress), + ]); + + const tx = await swapRouter + .connect(testUser) + .swapNativeAndSupply(vUSDT, minAmountOut, swapData, { value: amountIn }); + const receipt = await tx.wait(); + + const events = parseEventFromReceipt(receipt, "SwapAndSupply"); + expect(events.length).to.equal(1); + expect(events[0].args.tokenIn.toLowerCase()).to.equal(WBNB.toLowerCase()); + + expect(await vUSDTContract.balanceOf(testUserAddress)).to.be.gt(vUSDTBefore); + + swapRouterResults.push({ + section: "swapNativeAndSupply", + test: "BNB -> USDT supply to vUSDT", + status: "PASSED", + detail: `amountIn: ${amountIn}, amountOut: ${events[0].args.amountOut}`, + }); + }); + }); + + // =========================================================================== + // swapNativeAndRepay + // =========================================================================== + + describe("swapNativeAndRepay", () => { + before(async () => { + const borrowBalance = await vUSDTContract.callStatic.borrowBalanceCurrent(testUserAddress); + if (borrowBalance.eq(0)) { + const supplyAmount = parseUnits("2000", 18); + await usdc.connect(testUser).approve(vUSDC, supplyAmount); + await vUSDCContract.connect(testUser).mint(supplyAmount); + await vUSDTContract.connect(testUser).borrow(parseUnits("500", 18)); + } + }); + + it("should swap BNB -> USDT and repay vUSDT borrow", async () => { + const amountIn = parseUnits("0.5", 18); + const borrowBefore = await vUSDTContract.callStatic.borrowBalanceCurrent(testUserAddress); + + if (borrowBefore.eq(0)) { + swapRouterResults.push({ + section: "swapNativeAndRepay", + test: "BNB -> USDT repay vUSDT", + status: "SKIPPED", + detail: "No borrow balance", + }); + console.log(" [SKIP] No borrow balance"); + return; + } + + const { swapData, minAmountOut } = await getNativeSwapData(USDT, amountIn.toString(), SWAP_ROUTER, "0.01"); + + const tx = await swapRouter + .connect(testUser) + .swapNativeAndRepay(vUSDT, minAmountOut, swapData, { value: amountIn }); + const receipt = await tx.wait(); + + const events = parseEventFromReceipt(receipt, "SwapAndRepay"); + expect(events.length).to.equal(1); + + const borrowAfter = await vUSDTContract.callStatic.borrowBalanceCurrent(testUserAddress); + expect(borrowAfter).to.be.lt(borrowBefore); + + swapRouterResults.push({ + section: "swapNativeAndRepay", + test: "BNB -> USDT repay vUSDT", + status: "PASSED", + detail: `Borrow: ${borrowBefore} -> ${borrowAfter}`, + }); + }); + }); + + // =========================================================================== + // swapNativeAndRepayFull + // =========================================================================== + + describe("swapNativeAndRepayFull", () => { + before(async () => { + const borrowBalance = await vUSDTContract.callStatic.borrowBalanceCurrent(testUserAddress); + if (borrowBalance.eq(0)) { + const supplyAmount = parseUnits("1000", 18); + await usdc.connect(testUser).approve(vUSDC, supplyAmount); + await vUSDCContract.connect(testUser).mint(supplyAmount); + await vUSDTContract.connect(testUser).borrow(parseUnits("100", 18)); + } + }); + + it("should swap BNB -> USDT and fully repay vUSDT borrow", async () => { + const borrowBalance = await vUSDTContract.callStatic.borrowBalanceCurrent(testUserAddress); + if (borrowBalance.eq(0)) { + swapRouterResults.push({ + section: "swapNativeAndRepayFull", + test: "BNB -> USDT full repay vUSDT", + status: "SKIPPED", + detail: "No borrow balance", + }); + console.log(" [SKIP] No borrow balance"); + return; + } + + const amountIn = parseUnits("1", 18); + const { swapData } = await getNativeSwapData(USDT, amountIn.toString(), SWAP_ROUTER, "0.01"); + + const tx = await swapRouter.connect(testUser).swapNativeAndRepayFull(vUSDT, swapData, { value: amountIn }); + const receipt = await tx.wait(); + + const events = parseEventFromReceipt(receipt, "SwapAndRepay"); + expect(events.length).to.equal(1); + + const borrowAfter = await vUSDTContract.callStatic.borrowBalanceCurrent(testUserAddress); + expect(borrowAfter).to.equal(0); + + swapRouterResults.push({ + section: "swapNativeAndRepayFull", + test: "BNB -> USDT full repay vUSDT", + status: "PASSED", + detail: `Borrow: ${borrowBalance} -> 0`, + }); + }); + }); + + // =========================================================================== + // sweepToken + // =========================================================================== + + describe("sweepToken", () => { + it("should allow owner to sweep ERC20 tokens stuck in the router", async () => { + const accidentalAmount = parseUnits("10", 18); + const usdtHolder = await initMainnetUser(USDT_HOLDER, ethers.utils.parseEther("1")); + await usdt.connect(usdtHolder).transfer(SWAP_ROUTER, accidentalAmount); + + const [routerBalanceBefore, ownerAddress] = await Promise.all([usdt.balanceOf(SWAP_ROUTER), swapRouter.owner()]); + expect(routerBalanceBefore).to.be.gte(accidentalAmount); + + const ownerBalanceBefore = await usdt.balanceOf(ownerAddress); + + const tx = await swapRouter.connect(impersonatedTimelock).sweepToken(USDT); + const receipt = await tx.wait(); + + const events = parseEventFromReceipt(receipt, "SweepToken"); + expect(events.length).to.equal(1); + expect(events[0].args.token.toLowerCase()).to.equal(USDT.toLowerCase()); + expect(events[0].args.amount).to.equal(routerBalanceBefore); + + const [routerBalanceAfter, ownerBalanceAfter] = await Promise.all([ + usdt.balanceOf(SWAP_ROUTER), + usdt.balanceOf(ownerAddress), + ]); + expect(routerBalanceAfter).to.equal(0); + expect(ownerBalanceAfter.sub(ownerBalanceBefore)).to.equal(routerBalanceBefore); + + swapRouterResults.push({ + section: "sweepToken", + test: "Owner sweeps ERC20 tokens", + status: "PASSED", + detail: `Swept ${routerBalanceBefore} USDT`, + }); + }); + + it("should revert when called by non-owner", async () => { + await expect(swapRouter.connect(testUser).sweepToken(USDT)).to.be.revertedWith( + "Ownable: caller is not the owner", + ); + + swapRouterResults.push({ + section: "sweepToken", + test: "Revert when called by non-owner", + status: "PASSED", + detail: "Correctly reverted", + }); + }); + }); + + // =========================================================================== + // sweepNative + // =========================================================================== + + describe("sweepNative", () => { + it("should allow owner to sweep native BNB stuck in the router", async () => { + const forcedAmount = parseUnits("0.1", 18); + await ethers.provider.send("hardhat_setBalance", [ + SWAP_ROUTER, + ethers.utils.hexStripZeros(forcedAmount.toHexString()), + ]); + + const routerBalance = await ethers.provider.getBalance(SWAP_ROUTER); + expect(routerBalance).to.be.gt(0); + + const tx = await swapRouter.connect(impersonatedTimelock).sweepNative(); + const receipt = await tx.wait(); + + const events = parseEventFromReceipt(receipt, "SweepNative"); + expect(events.length).to.equal(1); + expect(events[0].args.amount).to.equal(routerBalance); + + expect(await ethers.provider.getBalance(SWAP_ROUTER)).to.equal(0); + + swapRouterResults.push({ + section: "sweepNative", + test: "Owner sweeps native BNB", + status: "PASSED", + detail: `Swept ${routerBalance} wei BNB`, + }); + }); + + it("should revert for non-owner and unauthorized native sender", async () => { + await expect(swapRouter.connect(testUser).sweepNative()).to.be.revertedWith("Ownable: caller is not the owner"); + await expect( + testUser.sendTransaction({ to: SWAP_ROUTER, value: parseUnits("0.1", 18) }), + ).to.be.revertedWithCustomError(swapRouter, "UnauthorizedNativeSender"); + + swapRouterResults.push( + { + section: "sweepNative", + test: "Revert when called by non-owner", + status: "PASSED", + detail: "Correctly reverted", + }, + { + section: "sweepNative", + test: "Revert UnauthorizedNativeSender", + status: "PASSED", + detail: "Correctly reverted", + }, + ); + }); + }); + + // =========================================================================== + // Error Cases + // =========================================================================== + + describe("Error cases", () => { + it("should revert with MarketNotListed and SwapFailed", async () => { + await usdt.connect(testUser).approve(SWAP_ROUTER, parseUnits("100", 18)); + + await expect( + swapRouter + .connect(testUser) + .swapAndSupply("0x0000000000000000000000000000000000000001", USDT, parseUnits("100", 18), 0, "0x"), + ).to.be.revertedWithCustomError(swapRouter, "MarketNotListed"); + await expect( + swapRouter.connect(testUser).swapAndSupply(vUSDC, USDT, parseUnits("100", 18), 0, "0xdeadbeef"), + ).to.be.revertedWithCustomError(swapRouter, "SwapFailed"); + + swapRouterResults.push( + { section: "Error cases", test: "Revert MarketNotListed", status: "PASSED", detail: "Correctly reverted" }, + { section: "Error cases", test: "Revert SwapFailed", status: "PASSED", detail: "Correctly reverted" }, + ); + }); + }); + + // =========================================================================== + // Ownership + // =========================================================================== + + describe("Ownership", () => { + it("should have correct owner after VIP execution", async () => { + expect(await swapRouter.owner()).to.equal(bscmainnet.NORMAL_TIMELOCK); + }); + + it("should support two-step ownership transfer", async () => { + const newOwner = testUserAddress; + + await swapRouter.connect(impersonatedTimelock).transferOwnership(newOwner); + expect(await swapRouter.pendingOwner()).to.equal(newOwner); + + await swapRouter.connect(testUser).acceptOwnership(); + expect(await swapRouter.owner()).to.equal(newOwner); + + // Cleanup + await swapRouter.connect(testUser).transferOwnership(bscmainnet.NORMAL_TIMELOCK); + await swapRouter.connect(impersonatedTimelock).acceptOwnership(); + expect(await swapRouter.owner()).to.equal(bscmainnet.NORMAL_TIMELOCK); + }); + }); + + // =========================================================================== + // Integration + // =========================================================================== + + describe("Integration: full supply -> borrow -> repay cycle", () => { + it("should complete entire user journey via swap router", async () => { + const signers = await ethers.getSigners(); + const integrationUser = signers[5]; + const integrationUserAddress = await integrationUser.getAddress(); + + await initMainnetUser(integrationUserAddress, parseUnits("10", 18)); + + // Step 1: Swap 2 BNB -> USDC and supply to vUSDC + const bnbToSupply = parseUnits("2", 18); + const { swapData: supplySwapData, minAmountOut: supplyMinOut } = await getNativeSwapData( + USDC, + bnbToSupply.toString(), + SWAP_ROUTER, + "0.02", + ); + + await swapRouter + .connect(integrationUser) + .swapNativeAndSupply(vUSDC, supplyMinOut, supplySwapData, { value: bnbToSupply }); + + const vUSDCBalance = await vUSDCContract.balanceOf(integrationUserAddress); + expect(vUSDCBalance).to.be.gt(0); + + // Step 2: Enter market and borrow + await comptroller.connect(integrationUser).enterMarkets([vUSDC]); + await vUSDTContract.connect(integrationUser).borrow(parseUnits("100", 18)); + + const borrowBalance = await vUSDTContract.callStatic.borrowBalanceCurrent(integrationUserAddress); + expect(borrowBalance).to.be.gt(0); + + // Step 3: Swap 0.5 BNB -> USDT and partially repay + const bnbToRepay = parseUnits("0.5", 18); + const { swapData: repaySwapData, minAmountOut: repayMinOut } = await getNativeSwapData( + USDT, + bnbToRepay.toString(), + SWAP_ROUTER, + "0.02", + ); + + await swapRouter + .connect(integrationUser) + .swapNativeAndRepay(vUSDT, repayMinOut, repaySwapData, { value: bnbToRepay }); + + const borrowAfter = await vUSDTContract.callStatic.borrowBalanceCurrent(integrationUserAddress); + expect(borrowAfter).to.be.lt(borrowBalance); + + swapRouterResults.push({ + section: "Integration", + test: "Full supply -> borrow -> repay cycle", + status: "PASSED", + detail: `Supply vUSDC: ${vUSDCBalance}, Borrow: ${borrowBalance} -> ${borrowAfter}`, + }); + }); + }); + + after(() => { + printSwapRouterResultsSummary(); + }); +}); diff --git a/simulations/vip-600/bsctestnet.ts b/simulations/vip-600/bsctestnet.ts new file mode 100644 index 000000000..72911634a --- /dev/null +++ b/simulations/vip-600/bsctestnet.ts @@ -0,0 +1,46 @@ +import { expect } from "chai"; +import { Contract } from "ethers"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { forking, testVip } from "src/vip-framework"; + +import { SWAP_HELPER, SWAP_ROUTER, UNITROLLER, vip600 } from "../../vips/vip-600/bsctestnet"; +import SWAP_ROUTER_ABI from "./abi/SwapRouter.json"; + +const { bsctestnet } = NETWORK_ADDRESSES; + +forking(85710269, async () => { + let swapRouter: Contract; + + before(async () => { + swapRouter = await ethers.getContractAt(SWAP_ROUTER_ABI, SWAP_ROUTER); + }); + + describe("Pre-VIP behavior", () => { + it("SwapRouter should have NORMAL_TIMELOCK as pending owner", async () => { + expect(await swapRouter.pendingOwner()).to.equal(bsctestnet.NORMAL_TIMELOCK); + }); + + describe("SwapRouter configuration", () => { + it("should have correct COMPTROLLER", async () => { + expect(await swapRouter.COMPTROLLER()).to.equal(UNITROLLER); + }); + + it("should have correct swapHelper", async () => { + expect(await swapRouter.SWAP_HELPER()).to.equal(SWAP_HELPER); + }); + }); + }); + + testVip("VIP-600", await vip600(), {}); + + describe("Post-VIP behavior", () => { + it("SwapRouter should have NORMAL_TIMELOCK as owner", async () => { + expect(await swapRouter.owner()).to.equal(bsctestnet.NORMAL_TIMELOCK); + }); + + it("SwapRouter should have zero address as pending owner", async () => { + expect(await swapRouter.pendingOwner()).to.equal(ethers.constants.AddressZero); + }); + }); +}); diff --git a/vips/vip-600/bscmainnet.ts b/vips/vip-600/bscmainnet.ts new file mode 100644 index 000000000..82b2dcf5e --- /dev/null +++ b/vips/vip-600/bscmainnet.ts @@ -0,0 +1,31 @@ +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +export const UNITROLLER = "0xfD36E2c2a6789Db23113685031d7F16329158384"; +export const SWAP_ROUTER = "0xde7E4f67Af577F29e5F3B995f9e67FD425F73621"; +export const SWAP_HELPER = "0xD79be25aEe798Aa34A9Ba1230003d7499be29A24"; + +export const vip600 = () => { + const meta = { + version: "v2", + title: "VIP-600 [BNB Chain] Implementation of SwapRouter", + description: `Implements SwapRouter contract and transfers its ownership to Venus Protocol on BNB Chain.`, + forDescription: "Execute", + againstDescription: "Do not execute", + abstainDescription: "Abstain", + }; + + return makeProposal( + [ + { + target: SWAP_ROUTER, + signature: "acceptOwnership()", + params: [], + }, + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip600; diff --git a/vips/vip-600/bsctestnet.ts b/vips/vip-600/bsctestnet.ts new file mode 100644 index 000000000..52f927d30 --- /dev/null +++ b/vips/vip-600/bsctestnet.ts @@ -0,0 +1,31 @@ +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +export const UNITROLLER = "0x94d1820b2D1c7c7452A163983Dc888CEC546b77D"; +export const SWAP_ROUTER = "0xd3F226acA3210990DBA3f410b74E36b08F31FCf2"; +export const SWAP_HELPER = "0x55Fa097cA59BAc70C1ba488BEb11A5F6bf7019Eb"; + +export const vip600 = () => { + const meta = { + version: "v2", + title: "VIP-600 [BNB Chain] Implementation of SwapRouter", + description: `Implements SwapRouter contract and transfers its ownership to Venus Protocol on BNB Chain.`, + forDescription: "Execute", + againstDescription: "Do not execute", + abstainDescription: "Abstain", + }; + + return makeProposal( + [ + { + target: SWAP_ROUTER, + signature: "acceptOwnership()", + params: [], + }, + ], + meta, + ProposalType.REGULAR, + ); +}; + +export default vip600;