From fcef1c0615820b1cd97ce9b5d82ff767d495c0a9 Mon Sep 17 00:00:00 2001 From: trumpgpt Date: Wed, 19 Aug 2026 05:48:45 +0000 Subject: [PATCH 01/23] feat: draft VIP-664 list vhUSDT, vhUSDC, vhU in Venus Core Pool Non-borrowable collateral markets backed by Venus Hub receipt tokens (24-dec ERC4626), with a capped ERC4626Oracle and E-brake (DBO) per market. Confirmed risk params + recommended defaults for the two open knobs baked in; deploy-dependent addresses and the fork simulation remain pending (see PR). --- vips/vip-664/bscmainnet.ts | 419 +++++++++++++++++++++++++++++++++++++ 1 file changed, 419 insertions(+) create mode 100644 vips/vip-664/bscmainnet.ts diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts new file mode 100644 index 000000000..f08a8f4d6 --- /dev/null +++ b/vips/vip-664/bscmainnet.ts @@ -0,0 +1,419 @@ +import { BigNumber, constants } from "ethers"; +import { parseUnits } from "ethers/lib/utils"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +// ============================================================================= +// DRAFT — VIP-664 [BNB Chain] List vhUSDT, vhUSDC and vhU in the Venus Core Pool +// ----------------------------------------------------------------------------- +// This VIP is a WORK IN PROGRESS opened as a draft PR. The command structure, +// risk parameters and oracle/E-brake design are complete; the only items that +// remain are the deploy-dependent addresses and the fork simulation, which are +// gated on the testnet deploy of the 3 capped ERC4626 oracles + 3 vTokens. +// +// PENDING BEFORE THIS VIP CAN BE PROPOSED (see PR description): +// 1. Confirm the two still-open, risk-sensitive knobs (recommended values are +// baked in below and clearly labelled): +// - E-brake (DeviationBoundedOracle) thresholds — recommended 5% trigger +// / 2% reset / 1h cooldown (stable-appropriate; VIP-633's 16.67% is for +// volatile equities and is intentionally NOT copied). +// - Capped-oracle snapshot mode — recommended frozen snapshot +// (snapshotInterval = MaxUint256, gap 0), 5%/yr growth (from Notion). +// 2. Deploy the 3 capped ERC4626Oracle instances (oracle repo) and 3 vToken +// markets (venus-protocol repo) on bsctestnet, then fill the TODO addresses +// below and author + run the fork simulation (simulations/vip-664). +// 3. Confirm the VTreasury holds the bootstrap balance of each vhToken. +// ============================================================================= + +const { bscmainnet } = NETWORK_ADDRESSES; + +// TODO(deploy): fill after the capped ERC4626Oracle instances are deployed on BNB Chain. +export const VHUSDT_ORACLE = constants.AddressZero; +export const VHUSDC_ORACLE = constants.AddressZero; +export const VHU_ORACLE = constants.AddressZero; + +// TODO(deploy): fill after the vTokens are deployed on BNB Chain. +export const VVHUSDT = constants.AddressZero; +export const VVHUSDC = constants.AddressZero; +export const VVHU = constants.AddressZero; + +export const PROTOCOL_SHARE_RESERVE = "0xCa01D5A9A248a830E9D93231e791B1afFed7c446"; +export const REDUCE_RESERVES_BLOCK_DELTA = "28800"; +export const BORROW_ACTION = 2; // Comptroller Action enum: BORROW + +export const { RESILIENT_ORACLE } = bscmainnet; + +// Capped ERC4626 oracle template (set at oracle-deploy time, documented here for review). +// "vhToken resilient price = underlying resilient price x capped vault exchange rate" — Notion. +export const ANNUAL_GROWTH_RATE = parseUnits("0.05", 18); // 5%/yr — from Notion +export const SNAPSHOT_INTERVAL = constants.MaxUint256; // frozen snapshot (recommended) +export const SNAPSHOT_GAP = 0; + +// Oracle Dynamic Protection Mode / "E-brake" (DeviationBoundedOracle, see VIP-617). +export const DEVIATION_BOUNDED_ORACLE = "0xc79Cb7efEBd121DC4B39eA141C214606595D665A"; +export const DBO_COOLDOWN_PERIOD = 3600; // 1h rolling window +// Stable-appropriate thresholds (recommended). vhTokens are ~$1 stablecoin-correlated, so +// VIP-633's 16.67% equity trigger is intentionally not reused. Contract bounds: trigger in +// [5%, 50%], reset non-zero and below trigger, cooldown > 0. +export const DBO_TRIGGER_THRESHOLD = parseUnits("0.05", 18); // 5% — the contract minimum +export const DBO_RESET_THRESHOLD = parseUnits("0.02", 18); // 2% + +export type MarketSpec = { + vToken: { + address: string; + name: string; + symbol: string; + underlying: { address: string; symbol: string; decimals: number }; + decimals: number; + exchangeRate: BigNumber; + comptroller: string; + isLegacyPool: boolean; + }; + interestRateModel: { + model: "jump"; + baseRatePerYear: string; + multiplierPerYear: string; + jumpMultiplierPerYear: string; + kink: string; + }; + oracle: { + // Capped ERC4626Oracle instance registered as the ResilientOracle main source for the vhToken. + address: string; + }; + riskParameters: { + collateralFactor: BigNumber; + liquidationThreshold: BigNumber; + liquidationIncentive: BigNumber; + reserveFactor: BigNumber; + supplyCap: BigNumber; + borrowCap: BigNumber; + }; + initialSupply: { + amount: BigNumber; + vTokenReceiver: string; + vTokensToBurn: BigNumber; + }; +}; + +// All three markets: non-borrowable collateral, 24-decimal ERC4626 underlyings, Core pool. +// exchangeRate scale = 18 + underlyingDecimals(24) - vTokenDecimals(8) = 34. +const EXCHANGE_RATE = parseUnits("1", 34); +const SUPPLY_CAP = parseUnits("10000000", 24); // 10,000,000 vhToken (24 dec) +const LIQUIDATION_INCENTIVE = parseUnits("1.1", 18); // 10% +const RESERVE_FACTOR = parseUnits("0.1", 18); // 10% (inert while borrow is paused) +const BOOTSTRAP_AMOUNT = parseUnits("100", 24); // ~$100 of underlying (24 dec) +// vTokensMinted = amount * 1e18 / exchangeRate = 100e24 * 1e18 / 1e34 = 100e8; burn 10%. +const BOOTSTRAP_BURN = parseUnits("10", 8); + +// Market — vhUSDT +export const MARKET_VHUSDT: MarketSpec = { + vToken: { + address: VVHUSDT, + name: "Venus vhUSDT", + symbol: "vvhUSDT", + underlying: { + address: "0x18AfDACF30F8671021dec4b78297E39d2FE87226", + symbol: "vhUSDT", + decimals: 24, + }, + decimals: 8, + exchangeRate: EXCHANGE_RATE, + comptroller: bscmainnet.UNITROLLER, + isLegacyPool: true, + }, + interestRateModel: { + model: "jump", + baseRatePerYear: "0", + multiplierPerYear: "0.09", + jumpMultiplierPerYear: "2", + kink: "0.5", + }, + oracle: { address: VHUSDT_ORACLE }, + riskParameters: { + collateralFactor: parseUnits("0.8", 18), + liquidationThreshold: parseUnits("0.8", 18), + liquidationIncentive: LIQUIDATION_INCENTIVE, + reserveFactor: RESERVE_FACTOR, + supplyCap: SUPPLY_CAP, + borrowCap: parseUnits("0", 24), // borrowing disabled at launch + }, + initialSupply: { + amount: BOOTSTRAP_AMOUNT, + vTokenReceiver: bscmainnet.VTREASURY, + vTokensToBurn: BOOTSTRAP_BURN, + }, +}; + +// Market — vhUSDC +export const MARKET_VHUSDC: MarketSpec = { + vToken: { + address: VVHUSDC, + name: "Venus vhUSDC", + symbol: "vvhUSDC", + underlying: { + address: "0x9D2D9592cF8DFbf59107fAab703d08494BE14617", + symbol: "vhUSDC", + decimals: 24, + }, + decimals: 8, + exchangeRate: EXCHANGE_RATE, + comptroller: bscmainnet.UNITROLLER, + isLegacyPool: true, + }, + interestRateModel: { + model: "jump", + baseRatePerYear: "0", + multiplierPerYear: "0.09", + jumpMultiplierPerYear: "2", + kink: "0.5", + }, + oracle: { address: VHUSDC_ORACLE }, + riskParameters: { + collateralFactor: parseUnits("0.825", 18), + liquidationThreshold: parseUnits("0.825", 18), + liquidationIncentive: LIQUIDATION_INCENTIVE, + reserveFactor: RESERVE_FACTOR, + supplyCap: SUPPLY_CAP, + borrowCap: parseUnits("0", 24), // borrowing disabled at launch + }, + initialSupply: { + amount: BOOTSTRAP_AMOUNT, + vTokenReceiver: bscmainnet.VTREASURY, + vTokensToBurn: BOOTSTRAP_BURN, + }, +}; + +// Market — vhU +export const MARKET_VHU: MarketSpec = { + vToken: { + address: VVHU, + name: "Venus vhU", + symbol: "vvhU", + underlying: { + address: "0x0e5AA174d4F31b757a237eb1999DE151596788B0", + symbol: "vhU", + decimals: 24, + }, + decimals: 8, + exchangeRate: EXCHANGE_RATE, + comptroller: bscmainnet.UNITROLLER, + isLegacyPool: true, + }, + interestRateModel: { + model: "jump", + baseRatePerYear: "0", + multiplierPerYear: "0.09", + jumpMultiplierPerYear: "2", + kink: "0.5", + }, + oracle: { address: VHU_ORACLE }, + riskParameters: { + collateralFactor: parseUnits("0.75", 18), + liquidationThreshold: parseUnits("0.75", 18), + liquidationIncentive: LIQUIDATION_INCENTIVE, + reserveFactor: RESERVE_FACTOR, + supplyCap: SUPPLY_CAP, + borrowCap: parseUnits("0", 24), // borrowing disabled at launch + }, + initialSupply: { + amount: BOOTSTRAP_AMOUNT, + vTokenReceiver: bscmainnet.VTREASURY, + vTokensToBurn: BOOTSTRAP_BURN, + }, +}; + +export const MARKETS: MarketSpec[] = [MARKET_VHUSDT, MARKET_VHUSDC, MARKET_VHU]; + +export const convertAmountToVTokens = (amount: BigNumber, exchangeRate: BigNumber) => { + const EXP_SCALE = parseUnits("1", 18); + return amount.mul(EXP_SCALE).div(exchangeRate); +}; + +export const vTokensMinted = (m: MarketSpec) => convertAmountToVTokens(m.initialSupply.amount, m.vToken.exchangeRate); + +export const vTokensRemaining = (m: MarketSpec) => vTokensMinted(m).sub(m.initialSupply.vTokensToBurn); + +export const vip664 = () => { + const meta = { + version: "v2", + title: "VIP-664 [BNB Chain] List vhUSDT, vhUSDC and vhU markets in the Venus Core Pool", + description: `#### Summary + +If passed, this VIP will list three new non-borrowable collateral markets in the Venus Core Pool on BNB Chain, backed by Venus Hub receipt tokens (vhTokens), with borrowing paused at launch: + +- **Venus vhUSDT (vvhUSDT)** — backed by vhUSDT (Venus Hub USDT, ERC4626, 24 decimals) +- **Venus vhUSDC (vvhUSDC)** — backed by vhUSDC (Venus Hub USDC, ERC4626, 24 decimals) +- **Venus vhU (vvhU)** — backed by vhU (Venus Hub U, ERC4626, 24 decimals) + +#### Description + +For each new market this VIP will: + +- Register the vhToken in the ResilientOracle using a dedicated capped **ERC4626Oracle** as the single price source. The oracle prices the vhToken as *underlying resilient price × capped vault exchange rate* (5%/yr growth cap), the same design as asBNB. +- Add the market to the Core Pool Comptroller +- Set the supply cap, collateral factor, liquidation threshold, liquidation incentive and reserve factor +- Set the AccessControlManager, ProtocolShareReserve and reduce-reserves block delta on the vToken +- Provide bootstrap liquidity (minting an initial supply, burning 10% and sending the remainder to the VTreasury) +- Pause borrowing for the market at launch (the markets are collateral-only) +- Enable Oracle Dynamic Protection Mode / "E-brake" (DeviationBoundedOracle, see VIP-617) for the vhToken, with a stable-appropriate 5% deviation trigger + +#### Risk parameters + +All three markets share the same interest rate model (base 0%, multiplier 9%, jump multiplier 200%, kink 50%); rates are inert while borrowing is paused. Per-market parameters: + +**Venus vhUSDT (vvhUSDT)** +- Collateral factor: 80% +- Liquidation threshold: 80% +- Liquidation incentive: 10% +- Reserve factor: 10% +- Supply cap: 10,000,000 vhUSDT +- Borrow cap: 0 (borrowing disabled) +- E-brake trigger / reset: 5% / 2% + +**Venus vhUSDC (vvhUSDC)** +- Collateral factor: 82.5% +- Liquidation threshold: 82.5% +- Liquidation incentive: 10% +- Reserve factor: 10% +- Supply cap: 10,000,000 vhUSDC +- Borrow cap: 0 (borrowing disabled) +- E-brake trigger / reset: 5% / 2% + +**Venus vhU (vvhU)** +- Collateral factor: 75% +- Liquidation threshold: 75% +- Liquidation incentive: 10% +- Reserve factor: 10% +- Supply cap: 10,000,000 vhU +- Borrow cap: 0 (borrowing disabled) +- E-brake trigger / reset: 5% / 2%`, + forDescription: "I agree that Venus Protocol should proceed with this proposal", + againstDescription: "I do not think that Venus Protocol should proceed with this proposal", + abstainDescription: "I am indifferent to whether Venus Protocol proceeds or not", + }; + + return makeProposal( + MARKETS.flatMap(m => [ + // Oracle configuration — single source: the capped ERC4626Oracle for the vhToken. + // The ERC4626Oracle reads the underlying (USDT/USDC/USD1) price from the ResilientOracle + // itself and applies the growth-rate cap on the vault exchange rate, so no extra feed + // configuration is required. + { + target: RESILIENT_ORACLE, + signature: "setTokenConfig((address,address[3],bool[3],bool))", + params: [ + [ + m.vToken.underlying.address, + [m.oracle.address, ethers.constants.AddressZero, ethers.constants.AddressZero], + [true, false, false], + false, + ], + ], + }, + + // Add market + { + target: m.vToken.comptroller, + signature: "_supportMarket(address)", + params: [m.vToken.address], + }, + { + target: m.vToken.comptroller, + signature: "_setMarketSupplyCaps(address[],uint256[])", + params: [[m.vToken.address], [m.riskParameters.supplyCap]], + }, + // Pause borrowing for the market at launch (collateral-only markets). + { + target: m.vToken.comptroller, + signature: "setActionsPaused(address[],uint8[],bool)", + params: [[m.vToken.address], [BORROW_ACTION], true], + }, + { + target: m.vToken.address, + signature: "setAccessControlManager(address)", + params: [bscmainnet.ACCESS_CONTROL_MANAGER], + }, + { + target: m.vToken.address, + signature: "setProtocolShareReserve(address)", + params: [PROTOCOL_SHARE_RESERVE], + }, + { + target: m.vToken.address, + signature: "setReduceReservesBlockDelta(uint256)", + params: [REDUCE_RESERVES_BLOCK_DELTA], + }, + { + target: m.vToken.address, + signature: "_setReserveFactor(uint256)", + params: [m.riskParameters.reserveFactor], + }, + { + target: m.vToken.comptroller, + signature: "setCollateralFactor(address,uint256,uint256)", + params: [m.vToken.address, m.riskParameters.collateralFactor, m.riskParameters.liquidationThreshold], + }, + { + target: m.vToken.comptroller, + signature: "setLiquidationIncentive(address,uint256)", + params: [m.vToken.address, m.riskParameters.liquidationIncentive], + }, + + // Initial liquidity: pull underlying from the Treasury, mint, burn a slice, send the remainder to the receiver. + { + target: bscmainnet.VTREASURY, + signature: "withdrawTreasuryBEP20(address,uint256,address)", + params: [m.vToken.underlying.address, m.initialSupply.amount, bscmainnet.NORMAL_TIMELOCK], + }, + { + target: m.vToken.underlying.address, + signature: "approve(address,uint256)", + params: [m.vToken.address, m.initialSupply.amount], + }, + { + target: m.vToken.address, + signature: "mint(uint256)", + params: [m.initialSupply.amount], + }, + { + target: m.vToken.underlying.address, + signature: "approve(address,uint256)", + params: [m.vToken.address, 0], + }, + // Burn a slice of vTokens. + { + target: m.vToken.address, + signature: "transfer(address,uint256)", + params: [ethers.constants.AddressZero, m.initialSupply.vTokensToBurn], + }, + // Transfer remaining vTokens to the receiver (VTreasury). + { + target: m.vToken.address, + signature: "transfer(address,uint256)", + params: [m.initialSupply.vTokenReceiver, vTokensRemaining(m)], + }, + + // Enable Oracle Dynamic Protection Mode / "E-brake" (DBO) for the vhToken with a 5% deviation trigger. + { + target: DEVIATION_BOUNDED_ORACLE, + signature: "setTokenConfig((address,uint64,uint256,uint256,bool,bool))", + params: [ + [ + m.vToken.underlying.address, + DBO_COOLDOWN_PERIOD, + DBO_TRIGGER_THRESHOLD, + DBO_RESET_THRESHOLD, + true, // enableBoundedPricing + false, // enableCaching + ], + ], + }, + ]), + meta, + ProposalType.REGULAR, + ); +}; + +export default vip664; From 8299dd4aa3eafe765777f2c9948320f5c2422c0c Mon Sep 17 00:00:00 2001 From: trumpgpt Date: Wed, 19 Aug 2026 07:51:35 +0000 Subject: [PATCH 02/23] docs(vip-664): document IRM, CF=LT and default-param rationale per review Addresses PR #754 review (Debugger022): the reviewer confirmed the underlying addresses are correct and the vToken/oracle placeholders are legitimately deploy-gated. Remaining points were to make three deliberate choices explicit in the VIP text so reviewers do not read them as contradictions: - IRM is wired only to satisfy the vToken constructor and is inert while borrowing is paused (consistent with the template's "IRM not needed"). - CF == LT on all three markets is intentional per the approved template (tightly-pegged, growth-capped, E-brake-protected collaterals). - reserveFactor / vTokenReceiver / bootstrap amount follow the standard Core-pool convention where the template was silent. Co-Authored-By: Claude Opus 4.8 --- vips/vip-664/bscmainnet.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index f08a8f4d6..fb52482ed 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -99,6 +99,15 @@ export type MarketSpec = { // All three markets: non-borrowable collateral, 24-decimal ERC4626 underlyings, Core pool. // exchangeRate scale = 18 + underlyingDecimals(24) - vTokenDecimals(8) = 34. +// +// Risk-parameter notes (mirrored in the VIP description so reviewers do not read them as contradictions): +// - IRM: a jump-rate IRM is wired only because the vToken constructor requires one; it is inert +// while borrowing is paused. The listing template's "IRM not needed" note is consistent with this. +// - CF == LT on every market (80/80, 82.5/82.5, 75/75) is intentional per the approved template: +// these are ~$1 stablecoin-correlated collaterals priced by a growth-capped oracle with the E-brake +// enabled, so no CF<->LT buffer is applied. +// - reserveFactor / vTokenReceiver / bootstrap amount were unspecified in the template and follow the +// standard Core-pool convention (reserve factor inert while borrowing is paused). const EXCHANGE_RATE = parseUnits("1", 34); const SUPPLY_CAP = parseUnits("10000000", 24); // 10,000,000 vhToken (24 dec) const LIQUIDATION_INCENTIVE = parseUnits("1.1", 18); // 10% @@ -288,7 +297,13 @@ All three markets share the same interest rate model (base 0%, multiplier 9%, ju - Reserve factor: 10% - Supply cap: 10,000,000 vhU - Borrow cap: 0 (borrowing disabled) -- E-brake trigger / reset: 5% / 2%`, +- E-brake trigger / reset: 5% / 2% + +#### Notes on the risk parameters + +- **Interest rate model.** Although these markets are non-borrowable, a vToken requires an interest rate model at construction, so a jump-rate IRM (base 0%, multiplier 9%, jump multiplier 200%, kink 50%) is wired to make the market well-formed. It has no economic effect while borrowing is paused. The listing checklist noted "IRM not needed" precisely because the market is non-borrowable — that is consistent with this VIP: the IRM exists only to satisfy the constructor and is inert. +- **Collateral factor equals liquidation threshold** on all three markets (80/80, 82.5/82.5, 75/75). This is intentional and matches the approved risk parameters from the listing template. The vhTokens are ~$1 stablecoin-correlated assets priced through a growth-capped ERC4626 oracle with the E-brake (DeviationBoundedOracle) protection mode enabled, so no CF↔LT buffer is applied; a position opened at the maximum LTV therefore sits at the liquidation boundary, which is the deliberate design for these tightly-pegged collaterals. +- **Reserve factor (10%), vTokenReceiver (VTreasury) and bootstrap amount (100 vhToken per market)** were not specified in the listing template and follow the standard Core-pool listing convention. The reserve factor is inert while borrowing is paused.`, forDescription: "I agree that Venus Protocol should proceed with this proposal", againstDescription: "I do not think that Venus Protocol should proceed with this proposal", abstainDescription: "I am indifferent to whether Venus Protocol proceeds or not", From a1ef313f04c8041b6d1af97b0f6a4eb2e05a41c7 Mon Sep 17 00:00:00 2001 From: trumpgpt Date: Wed, 19 Aug 2026 08:44:42 +0000 Subject: [PATCH 03/23] docs(vip-664): close listing-template review flags (supply-cap unit, CF split, seize share) Address the 'flags to close before this becomes a VIP' from the approved listing-template source doc: - state the 10M supply cap is denominated in underlying vhToken amount (24 dec), not USD (~$10M at the ~$1 vault price) - record the 82.5/80/75 collateral-factor split as approved per-asset risk-manager values ordered by peg maturity/depth (USDC > USDT > USD1/U), not a blanket ACK - note protocol seize share is a global Comptroller parameter, unchanged by this VIP Co-Authored-By: Claude Opus 4.8 --- vips/vip-664/bscmainnet.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index fb52482ed..20c154103 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -106,8 +106,14 @@ export type MarketSpec = { // - CF == LT on every market (80/80, 82.5/82.5, 75/75) is intentional per the approved template: // these are ~$1 stablecoin-correlated collaterals priced by a growth-capped oracle with the E-brake // enabled, so no CF<->LT buffer is applied. +// - The differentiated CF/LT across the three (82.5 vhUSDC > 80 vhUSDT > 75 vhU) are the approved +// per-asset risk-manager values from the listing template; they track the relative maturity and +// depth of each underlying peg (USDC > USDT > USD1/U), not a single blanket setting. +// - Supply cap (10,000,000) is denominated in the underlying vhToken amount (24 decimals), NOT USD, +// since _setMarketSupplyCaps takes a token amount; at the ~$1 vault price that is ~$10M of exposure. // - reserveFactor / vTokenReceiver / bootstrap amount were unspecified in the template and follow the // standard Core-pool convention (reserve factor inert while borrowing is paused). +// - Protocol seize share is a global Comptroller parameter (not per-market) and is left unchanged. const EXCHANGE_RATE = parseUnits("1", 34); const SUPPLY_CAP = parseUnits("10000000", 24); // 10,000,000 vhToken (24 dec) const LIQUIDATION_INCENTIVE = parseUnits("1.1", 18); // 10% @@ -303,7 +309,10 @@ All three markets share the same interest rate model (base 0%, multiplier 9%, ju - **Interest rate model.** Although these markets are non-borrowable, a vToken requires an interest rate model at construction, so a jump-rate IRM (base 0%, multiplier 9%, jump multiplier 200%, kink 50%) is wired to make the market well-formed. It has no economic effect while borrowing is paused. The listing checklist noted "IRM not needed" precisely because the market is non-borrowable — that is consistent with this VIP: the IRM exists only to satisfy the constructor and is inert. - **Collateral factor equals liquidation threshold** on all three markets (80/80, 82.5/82.5, 75/75). This is intentional and matches the approved risk parameters from the listing template. The vhTokens are ~$1 stablecoin-correlated assets priced through a growth-capped ERC4626 oracle with the E-brake (DeviationBoundedOracle) protection mode enabled, so no CF↔LT buffer is applied; a position opened at the maximum LTV therefore sits at the liquidation boundary, which is the deliberate design for these tightly-pegged collaterals. -- **Reserve factor (10%), vTokenReceiver (VTreasury) and bootstrap amount (100 vhToken per market)** were not specified in the listing template and follow the standard Core-pool listing convention. The reserve factor is inert while borrowing is paused.`, +- **The three collateral factors differ (82.5% vhUSDC, 80% vhUSDT, 75% vhU).** These are the approved per-asset values set by the risk manager in the listing template — not a single blanket figure — and are ordered by the relative maturity and market depth of each underlying peg (USDC > USDT > USD1/U). vhU/USD1, the newest and least liquid of the three, carries the most conservative factor. +- **Supply cap is denominated in the underlying token amount, not USD.** \`_setMarketSupplyCaps\` takes an amount of the underlying, so each cap of 10,000,000 is 10,000,000 vhTokens (24 decimals). At the current ~$1 vault price this corresponds to roughly $10M of collateral exposure per market. +- **Reserve factor (10%), vTokenReceiver (VTreasury) and bootstrap amount (100 vhToken per market)** were not specified in the listing template and follow the standard Core-pool listing convention. The reserve factor is inert while borrowing is paused. +- **Protocol seize share** is a global Comptroller-level parameter on the Core pool rather than a per-market setting, so it is not modified by this VIP; the existing Core-pool value applies to the new markets.`, forDescription: "I agree that Venus Protocol should proceed with this proposal", againstDescription: "I do not think that Venus Protocol should proceed with this proposal", abstainDescription: "I am indifferent to whether Venus Protocol proceeds or not", From f87640287ce30d39c0429167c3ab561e8779c858 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 19 Aug 2026 15:24:41 +0530 Subject: [PATCH 04/23] fix(vip-664): pin capped oracle to asBNB design snapshotInterval = MaxUint256 never re-snapshots, so the 5%/yr cap would grow unbounded from the deploy seed. Both live BNB Chain capped oracles (asBNB and slisBNB) run 5%/yr over a 30-day interval since VIP-605. Also records what was read from chain: all three vhToken addresses, names, 24 decimals and ERC4626 assets check out, U trades at ~$1, and the VTreasury holds none of the three vhTokens the bootstrap withdraws. --- vips/vip-664/bscmainnet.ts | 87 +++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 48 deletions(-) diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index 20c154103..16817c0c3 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -5,27 +5,9 @@ import { NETWORK_ADDRESSES } from "src/networkAddresses"; import { ProposalType } from "src/types"; import { makeProposal } from "src/utils"; -// ============================================================================= -// DRAFT — VIP-664 [BNB Chain] List vhUSDT, vhUSDC and vhU in the Venus Core Pool -// ----------------------------------------------------------------------------- -// This VIP is a WORK IN PROGRESS opened as a draft PR. The command structure, -// risk parameters and oracle/E-brake design are complete; the only items that -// remain are the deploy-dependent addresses and the fork simulation, which are -// gated on the testnet deploy of the 3 capped ERC4626 oracles + 3 vTokens. -// -// PENDING BEFORE THIS VIP CAN BE PROPOSED (see PR description): -// 1. Confirm the two still-open, risk-sensitive knobs (recommended values are -// baked in below and clearly labelled): -// - E-brake (DeviationBoundedOracle) thresholds — recommended 5% trigger -// / 2% reset / 1h cooldown (stable-appropriate; VIP-633's 16.67% is for -// volatile equities and is intentionally NOT copied). -// - Capped-oracle snapshot mode — recommended frozen snapshot -// (snapshotInterval = MaxUint256, gap 0), 5%/yr growth (from Notion). -// 2. Deploy the 3 capped ERC4626Oracle instances (oracle repo) and 3 vToken -// markets (venus-protocol repo) on bsctestnet, then fill the TODO addresses -// below and author + run the fork simulation (simulations/vip-664). -// 3. Confirm the VTreasury holds the bootstrap balance of each vhToken. -// ============================================================================= +// VIP-664 [BNB Chain] List vhUSDT, vhUSDC and vhU in the Venus Core Pool. +// The oracle and vToken addresses below are filled once the deploy PRs land; until then the +// simulation cannot execute. Remaining prerequisites are tracked in the PR description. const { bscmainnet } = NETWORK_ADDRESSES; @@ -45,18 +27,20 @@ export const BORROW_ACTION = 2; // Comptroller Action enum: BORROW export const { RESILIENT_ORACLE } = bscmainnet; -// Capped ERC4626 oracle template (set at oracle-deploy time, documented here for review). -// "vhToken resilient price = underlying resilient price x capped vault exchange rate" — Notion. -export const ANNUAL_GROWTH_RATE = parseUnits("0.05", 18); // 5%/yr — from Notion -export const SNAPSHOT_INTERVAL = constants.MaxUint256; // frozen snapshot (recommended) -export const SNAPSHOT_GAP = 0; +// Capped ERC4626 oracle: set at oracle-deploy time, asserted by the simulation. +// Price = underlying resilient price x capped vault exchange rate, the asBNB/slisBNB CAPO design. +// Both live BNB Chain instances run 5%/yr over a 30-day snapshot interval (VIP-605); the per-asset +// snapshotGap is a deploy-time buffer over the seeded rate (asBNB 2.47%, slisBNB 0.34%), so it is +// read from the deployed oracle rather than pinned here. +export const CAPO_GROWTH_RATE_PER_YEAR = parseUnits("0.05", 18); +export const CAPO_SNAPSHOT_INTERVAL = 30 * 24 * 60 * 60; // Oracle Dynamic Protection Mode / "E-brake" (DeviationBoundedOracle, see VIP-617). export const DEVIATION_BOUNDED_ORACLE = "0xc79Cb7efEBd121DC4B39eA141C214606595D665A"; export const DBO_COOLDOWN_PERIOD = 3600; // 1h rolling window -// Stable-appropriate thresholds (recommended). vhTokens are ~$1 stablecoin-correlated, so -// VIP-633's 16.67% equity trigger is intentionally not reused. Contract bounds: trigger in -// [5%, 50%], reset non-zero and below trigger, cooldown > 0. +// vhTokens are ~$1 stablecoin-correlated, so VIP-633's 16.67% equity trigger is not reused. +// Contract bounds: trigger in [MIN_THRESHOLD 5e16, MAX_THRESHOLD 50e16], reset non-zero and below +// trigger, cooldown > 0. export const DBO_TRIGGER_THRESHOLD = parseUnits("0.05", 18); // 5% — the contract minimum export const DBO_RESET_THRESHOLD = parseUnits("0.02", 18); // 2% @@ -71,6 +55,8 @@ export type MarketSpec = { comptroller: string; isLegacyPool: boolean; }; + // TODO(deploy): the JumpRateModel deployed alongside the vToken. + rateModel: string; interestRateModel: { model: "jump"; baseRatePerYear: string; @@ -97,25 +83,11 @@ export type MarketSpec = { }; }; -// All three markets: non-borrowable collateral, 24-decimal ERC4626 underlyings, Core pool. // exchangeRate scale = 18 + underlyingDecimals(24) - vTokenDecimals(8) = 34. -// -// Risk-parameter notes (mirrored in the VIP description so reviewers do not read them as contradictions): -// - IRM: a jump-rate IRM is wired only because the vToken constructor requires one; it is inert -// while borrowing is paused. The listing template's "IRM not needed" note is consistent with this. -// - CF == LT on every market (80/80, 82.5/82.5, 75/75) is intentional per the approved template: -// these are ~$1 stablecoin-correlated collaterals priced by a growth-capped oracle with the E-brake -// enabled, so no CF<->LT buffer is applied. -// - The differentiated CF/LT across the three (82.5 vhUSDC > 80 vhUSDT > 75 vhU) are the approved -// per-asset risk-manager values from the listing template; they track the relative maturity and -// depth of each underlying peg (USDC > USDT > USD1/U), not a single blanket setting. -// - Supply cap (10,000,000) is denominated in the underlying vhToken amount (24 decimals), NOT USD, -// since _setMarketSupplyCaps takes a token amount; at the ~$1 vault price that is ~$10M of exposure. -// - reserveFactor / vTokenReceiver / bootstrap amount were unspecified in the template and follow the -// standard Core-pool convention (reserve factor inert while borrowing is paused). -// - Protocol seize share is a global Comptroller parameter (not per-market) and is left unchanged. +// Rationale for the risk parameters (CF == LT, the 82.5/80/75 split, the IRM on a non-borrowable +// market) is in the VIP description below. const EXCHANGE_RATE = parseUnits("1", 34); -const SUPPLY_CAP = parseUnits("10000000", 24); // 10,000,000 vhToken (24 dec) +const SUPPLY_CAP = parseUnits("10000000", 24); // _setMarketSupplyCaps takes an underlying amount, not USD const LIQUIDATION_INCENTIVE = parseUnits("1.1", 18); // 10% const RESERVE_FACTOR = parseUnits("0.1", 18); // 10% (inert while borrow is paused) const BOOTSTRAP_AMOUNT = parseUnits("100", 24); // ~$100 of underlying (24 dec) @@ -138,6 +110,7 @@ export const MARKET_VHUSDT: MarketSpec = { comptroller: bscmainnet.UNITROLLER, isLegacyPool: true, }, + rateModel: constants.AddressZero, interestRateModel: { model: "jump", baseRatePerYear: "0", @@ -177,6 +150,7 @@ export const MARKET_VHUSDC: MarketSpec = { comptroller: bscmainnet.UNITROLLER, isLegacyPool: true, }, + rateModel: constants.AddressZero, interestRateModel: { model: "jump", baseRatePerYear: "0", @@ -216,6 +190,7 @@ export const MARKET_VHU: MarketSpec = { comptroller: bscmainnet.UNITROLLER, isLegacyPool: true, }, + rateModel: constants.AddressZero, interestRateModel: { model: "jump", baseRatePerYear: "0", @@ -266,7 +241,7 @@ If passed, this VIP will list three new non-borrowable collateral markets in the For each new market this VIP will: -- Register the vhToken in the ResilientOracle using a dedicated capped **ERC4626Oracle** as the single price source. The oracle prices the vhToken as *underlying resilient price × capped vault exchange rate* (5%/yr growth cap), the same design as asBNB. +- Register the vhToken in the ResilientOracle using a dedicated capped **ERC4626Oracle** as the single price source. The oracle prices the vhToken as *underlying resilient price × capped vault exchange rate* (5%/yr growth cap), the same design as the live asBNB and slisBNB capped oracles (5%/yr over a 30-day snapshot interval, VIP-605). - Add the market to the Core Pool Comptroller - Set the supply cap, collateral factor, liquidation threshold, liquidation incentive and reserve factor - Set the AccessControlManager, ProtocolShareReserve and reduce-reserves block delta on the vToken @@ -312,7 +287,23 @@ All three markets share the same interest rate model (base 0%, multiplier 9%, ju - **The three collateral factors differ (82.5% vhUSDC, 80% vhUSDT, 75% vhU).** These are the approved per-asset values set by the risk manager in the listing template — not a single blanket figure — and are ordered by the relative maturity and market depth of each underlying peg (USDC > USDT > USD1/U). vhU/USD1, the newest and least liquid of the three, carries the most conservative factor. - **Supply cap is denominated in the underlying token amount, not USD.** \`_setMarketSupplyCaps\` takes an amount of the underlying, so each cap of 10,000,000 is 10,000,000 vhTokens (24 decimals). At the current ~$1 vault price this corresponds to roughly $10M of collateral exposure per market. - **Reserve factor (10%), vTokenReceiver (VTreasury) and bootstrap amount (100 vhToken per market)** were not specified in the listing template and follow the standard Core-pool listing convention. The reserve factor is inert while borrowing is paused. -- **Protocol seize share** is a global Comptroller-level parameter on the Core pool rather than a per-market setting, so it is not modified by this VIP; the existing Core-pool value applies to the new markets.`, +- **Protocol seize share** is a global Comptroller-level parameter on the Core pool rather than a per-market setting, so it is not modified by this VIP; the existing Core-pool value applies to the new markets. + +#### Underlying tokens + +Each underlying was read directly from BNB Chain and matches the listing template: + +| Token | Address | Name | Decimals | ERC4626 asset | Resilient price of the asset | +|---|---|---|---|---|---| +| vhUSDT | [0x18AfDACF30F8671021dec4b78297E39d2FE87226](https://bscscan.com/address/0x18AfDACF30F8671021dec4b78297E39d2FE87226) | Venus Hub USDT | 24 | [USDT](https://bscscan.com/address/0x55d398326f99059fF775485246999027B3197955) | $0.9991 | +| vhUSDC | [0x9D2D9592cF8DFbf59107fAab703d08494BE14617](https://bscscan.com/address/0x9D2D9592cF8DFbf59107fAab703d08494BE14617) | Venus Hub USDC | 24 | [USDC](https://bscscan.com/address/0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d) | $0.9998 | +| vhU | [0x0e5AA174d4F31b757a237eb1999DE151596788B0](https://bscscan.com/address/0x0e5AA174d4F31b757a237eb1999DE151596788B0) | Venus Hub U | 24 | [U](https://bscscan.com/address/0xcE24439F2D9C6a2289F741120FE202248B666666) | $0.9995 | + +All three vaults report a share price of ~1.0006 assets per share, so each 10,000,000-share supply cap is worth roughly $10M. U trades at ~$1, so the cap is comparable to the two USD stables. + +#### Prerequisite + +The bootstrap liquidity is withdrawn from the VTreasury, which currently holds **no** vhUSDT, vhUSDC or vhU. The Treasury must be funded with at least 100 of each vhToken before this VIP executes, otherwise \`withdrawTreasuryBEP20\` reverts. The Treasury does hold the underlying stables (USDT, USDC and U), so the funding can be done by depositing into each Venus Hub vault.`, forDescription: "I agree that Venus Protocol should proceed with this proposal", againstDescription: "I do not think that Venus Protocol should proceed with this proposal", abstainDescription: "I am indifferent to whether Venus Protocol proceeds or not", From f29431fb91a59a95f235f3119484cb9cd5bb31ee Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 19 Aug 2026 15:24:41 +0530 Subject: [PATCH 05/23] test(vip-664): add bscmainnet fork simulation Deploy-gated: the oracle, vToken and IRM addresses are still zero, so the run fails on them. CI runs ./tests only, not ./simulations. --- simulations/vip-664/abi/CappedOracle.json | 227 + simulations/vip-664/abi/Comptroller.json | 4145 +++++++++++++++++ .../vip-664/abi/DeviationBoundedOracle.json | 1374 ++++++ simulations/vip-664/abi/ERC20.json | 134 + simulations/vip-664/abi/ERC4626.json | 1121 +++++ simulations/vip-664/abi/ResilientOracle.json | 320 ++ simulations/vip-664/abi/VToken.json | 870 ++++ simulations/vip-664/bscmainnet.ts | 243 + 8 files changed, 8434 insertions(+) create mode 100644 simulations/vip-664/abi/CappedOracle.json create mode 100644 simulations/vip-664/abi/Comptroller.json create mode 100644 simulations/vip-664/abi/DeviationBoundedOracle.json create mode 100644 simulations/vip-664/abi/ERC20.json create mode 100644 simulations/vip-664/abi/ERC4626.json create mode 100644 simulations/vip-664/abi/ResilientOracle.json create mode 100644 simulations/vip-664/abi/VToken.json create mode 100644 simulations/vip-664/bscmainnet.ts diff --git a/simulations/vip-664/abi/CappedOracle.json b/simulations/vip-664/abi/CappedOracle.json new file mode 100644 index 000000000..d0fd7248e --- /dev/null +++ b/simulations/vip-664/abi/CappedOracle.json @@ -0,0 +1,227 @@ +[ + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "market", "type": "address" }, + { "internalType": "address", "name": "ptOracle", "type": "address" }, + { "internalType": "enum PendleOracle.RateKind", "name": "rateKind", "type": "uint8" }, + { "internalType": "address", "name": "ptToken", "type": "address" }, + { "internalType": "address", "name": "underlyingToken", "type": "address" }, + { "internalType": "address", "name": "resilientOracle", "type": "address" }, + { "internalType": "uint32", "name": "twapDuration", "type": "uint32" }, + { "internalType": "uint256", "name": "annualGrowthRate", "type": "uint256" }, + { "internalType": "uint256", "name": "snapshotInterval", "type": "uint256" }, + { "internalType": "uint256", "name": "initialSnapshotMaxExchangeRate", "type": "uint256" }, + { "internalType": "uint256", "name": "initialSnapshotTimestamp", "type": "uint256" }, + { "internalType": "address", "name": "accessControlManager", "type": "address" }, + { "internalType": "uint256", "name": "snapshotGap", "type": "uint256" } + ], + "internalType": "struct PendleOracle.ConstructorParams", + "name": "params", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { "inputs": [], "name": "InvalidDuration", "type": "error" }, + { "inputs": [], "name": "InvalidGrowthRate", "type": "error" }, + { "inputs": [], "name": "InvalidInitialSnapshot", "type": "error" }, + { "inputs": [], "name": "InvalidSnapshotMaxExchangeRate", "type": "error" }, + { "inputs": [], "name": "InvalidTokenAddress", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "address", "name": "calledContract", "type": "address" }, + { "internalType": "string", "name": "methodSignature", "type": "string" } + ], + "name": "Unauthorized", + "type": "error" + }, + { "inputs": [], "name": "ZeroAddressNotAllowed", "type": "error" }, + { "inputs": [], "name": "ZeroValueNotAllowed", "type": "error" }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "oldGrowthRatePerSecond", "type": "uint256" }, + { "indexed": true, "internalType": "uint256", "name": "newGrowthRatePerSecond", "type": "uint256" }, + { "indexed": true, "internalType": "uint256", "name": "oldSnapshotInterval", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "newSnapshotInterval", "type": "uint256" } + ], + "name": "GrowthRateUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "oldSnapshotGap", "type": "uint256" }, + { "indexed": true, "internalType": "uint256", "name": "newSnapshotGap", "type": "uint256" } + ], + "name": "SnapshotGapUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "maxExchangeRate", "type": "uint256" }, + { "indexed": true, "internalType": "uint256", "name": "timestamp", "type": "uint256" } + ], + "name": "SnapshotUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "ACCESS_CONTROL_MANAGER", + "outputs": [{ "internalType": "contract IAccessControlManagerV8", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CORRELATED_TOKEN", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MARKET", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PT_ORACLE", + "outputs": [{ "internalType": "contract IPendlePtOracle", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RATE_KIND", + "outputs": [{ "internalType": "enum PendleOracle.RateKind", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESILIENT_ORACLE", + "outputs": [{ "internalType": "contract ResilientOracleInterface", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "TWAP_DURATION", + "outputs": [{ "internalType": "uint32", "name": "", "type": "uint32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UNDERLYING_DECIMALS", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UNDERLYING_TOKEN", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMaxAllowedExchangeRate", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "asset", "type": "address" }], + "name": "getPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getUnderlyingAmount", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "growthRatePerSecond", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isCapped", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_annualGrowthRate", "type": "uint256" }, + { "internalType": "uint256", "name": "_snapshotInterval", "type": "uint256" } + ], + "name": "setGrowthRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_snapshotMaxExchangeRate", "type": "uint256" }, + { "internalType": "uint256", "name": "_snapshotTimestamp", "type": "uint256" } + ], + "name": "setSnapshot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "_snapshotGap", "type": "uint256" }], + "name": "setSnapshotGap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "snapshotGap", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "snapshotInterval", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "snapshotMaxExchangeRate", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "snapshotTimestamp", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [], "name": "updateSnapshot", "outputs": [], "stateMutability": "nonpayable", "type": "function" } +] diff --git a/simulations/vip-664/abi/Comptroller.json b/simulations/vip-664/abi/Comptroller.json new file mode 100644 index 000000000..5ca3a7000 --- /dev/null +++ b/simulations/vip-664/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-664/abi/DeviationBoundedOracle.json b/simulations/vip-664/abi/DeviationBoundedOracle.json new file mode 100644 index 000000000..9984f9c99 --- /dev/null +++ b/simulations/vip-664/abi/DeviationBoundedOracle.json @@ -0,0 +1,1374 @@ +[ + { + "inputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "_resilientOracle", + "type": "address" + }, + { + "internalType": "address", + "name": "nativeMarketAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "vaiAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + } + ], + "name": "CooldownNotElapsed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidArrayLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + } + ], + "name": "InvalidKeeperAction", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMaxPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMinPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "InvalidResetThreshold", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketAlreadyInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketNotInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + } + ], + "name": "PriceExceedsUint128", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentRangeRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "PriceRangeNotConverged", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceActive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceInactive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maximum", + "type": "uint256" + } + ], + "name": "ThresholdAboveMaximum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimum", + "type": "uint256" + } + ], + "name": "ThresholdBelowMinimum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "VAINotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroPriceNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroValueNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "whitelisted", + "type": "bool" + } + ], + "name": "BoundedPricingWhitelistUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "oldEnabled", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "newEnabled", + "type": "bool" + } + ], + "name": "CachingEnabledUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "oldCooldown", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "CooldownPeriodSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMax", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "MaxPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMin", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "MinPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "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": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + } + ], + "name": "ProtectionInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectionModeExited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "spotPrice", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + } + ], + "name": "ProtectionTriggered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldExitThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newExitThreshold", + "type": "uint256" + } + ], + "name": "ResetThresholdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newThreshold", + "type": "uint256" + } + ], + "name": "TriggerThresholdSet", + "type": "event" + }, + { + "inputs": [], + "name": "COLLATERAL_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEBT_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "KEEPER_DEADBAND", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NATIVE_TOKEN_ADDR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESILIENT_ORACLE", + "outputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManagerV8", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allAssets", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "assetProtectionConfig", + "outputs": [ + { + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "currentlyUsingProtectedPrice", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isBoundedPricingEnabled", + "type": "bool" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "triggerThreshold", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "resetThreshold", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "cachingEnabled", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "canExitProtection", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMins", + "type": "uint128[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMaxs", + "type": "uint128[]" + } + ], + "name": "checkAndGetWindowDrift", + "outputs": [ + { + "internalType": "bool[]", + "name": "needsMinUpdate", + "type": "bool[]" + }, + { + "internalType": "bool[]", + "name": "needsMaxUpdate", + "type": "bool[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "currentlyUsingProtectedPrice", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "exitProtectionMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAllBoundedPricingEnabledAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPrices", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPricesView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInitializedAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "isBoundedPricingEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nativeMarket", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "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": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setAssetBoundedPricingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setCachingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "setCooldownPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newTriggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newResetThreshold", + "type": "uint256" + } + ], + "name": "setThresholds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableCaching", + "type": "bool" + } + ], + "internalType": "struct IDeviationBoundedOracle.TokenConfigInput", + "name": "tokenConfig_", + "type": "tuple" + } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableCaching", + "type": "bool" + } + ], + "internalType": "struct IDeviationBoundedOracle.TokenConfigInput[]", + "name": "tokenConfigs_", + "type": "tuple[]" + } + ], + "name": "setTokenConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "enum IDeviationBoundedOracle.KeeperAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct IDeviationBoundedOracle.KeeperActionItem[]", + "name": "actions", + "type": "tuple[]" + } + ], + "name": "syncPriceBoundsAndProtections", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "updateMaxPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "updateMinPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "updateProtectionState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vai", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-664/abi/ERC20.json b/simulations/vip-664/abi/ERC20.json new file mode 100644 index 000000000..3a509c9c4 --- /dev/null +++ b/simulations/vip-664/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-664/abi/ERC4626.json b/simulations/vip-664/abi/ERC4626.json new file mode 100644 index 000000000..65606052f --- /dev/null +++ b/simulations/vip-664/abi/ERC4626.json @@ -0,0 +1,1121 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ERC4626__DepositMoreThanMax", + "type": "error" + }, + { + "inputs": [], + "name": "ERC4626__MintMoreThanMax", + "type": "error" + }, + { + "inputs": [], + "name": "ERC4626__RedeemMoreThanMax", + "type": "error" + }, + { + "inputs": [], + "name": "ERC4626__WithdrawMoreThanMax", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "operation", + "type": "string" + } + ], + "name": "ERC4626__ZeroAmount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "loopsLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requiredLoops", + "type": "uint256" + } + ], + "name": "MaxLoopsLimitExceeded", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorCode", + "type": "uint256" + } + ], + "name": "VenusERC4626__VenusError", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "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": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "rewardToken", + "type": "address" + } + ], + "name": "ClaimRewards", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldMaxLoopsLimit", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newmaxLoopsLimit", + "type": "uint256" + } + ], + "name": "MaxLoopsLimitUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "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": "oldRecipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newRecipient", + "type": "address" + } + ], + "name": "RewardRecipientUpdated", + "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" + }, + { + "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" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManagerV8", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "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": [], + "name": "asset", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "comptroller", + "outputs": [ + { + "internalType": "contract IComptroller", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "convertToAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "convertToShares", + "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": "assets", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "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": [ + { + "internalType": "address", + "name": "vToken_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + }, + { + "internalType": "address", + "name": "rewardRecipient_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "loopsLimit_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "vaultOwner_", + "type": "address" + } + ], + "name": "initialize2", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "maxDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxLoopsLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "maxMint", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "maxRedeem", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "maxWithdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "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": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "previewDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "previewMint", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "previewRedeem", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "previewWithdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "redeem", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rewardRecipient", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "loopsLimit", + "type": "uint256" + } + ], + "name": "setMaxLoopsLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newRecipient", + "type": "address" + } + ], + "name": "setRewardRecipient", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "token", + "type": "address" + } + ], + "name": "sweepToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "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" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vToken", + "outputs": [ + { + "internalType": "contract VToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/simulations/vip-664/abi/ResilientOracle.json b/simulations/vip-664/abi/ResilientOracle.json new file mode 100644 index 000000000..373eb14e9 --- /dev/null +++ b/simulations/vip-664/abi/ResilientOracle.json @@ -0,0 +1,320 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "nativeMarketAddress", "type": "address" }, + { "internalType": "address", "name": "vaiAddress", "type": "address" }, + { "internalType": "contract BoundValidatorInterface", "name": "_boundValidator", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "address", "name": "calledContract", "type": "address" }, + { "internalType": "string", "name": "methodSignature", "type": "string" } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "asset", "type": "address" }, + { "indexed": true, "internalType": "bool", "name": "enabled", "type": "bool" } + ], + "name": "CachedEnabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint8", "name": "version", "type": "uint8" }], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "oldAccessControlManager", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "newAccessControlManager", "type": "address" } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "asset", "type": "address" }, + { "indexed": true, "internalType": "uint256", "name": "role", "type": "uint256" }, + { "indexed": true, "internalType": "bool", "name": "enable", "type": "bool" } + ], + "name": "OracleEnabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "asset", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "oracle", "type": "address" }, + { "indexed": true, "internalType": "uint256", "name": "role", "type": "uint256" } + ], + "name": "OracleSet", + "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": false, "internalType": "address", "name": "account", "type": "address" }], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "asset", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "mainOracle", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "pivotOracle", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "fallbackOracle", "type": "address" } + ], + "name": "TokenConfigAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "address", "name": "account", "type": "address" }], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [], + "name": "CACHE_SLOT", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "INVALID_PRICE", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NATIVE_TOKEN_ADDR", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [], "name": "acceptOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [{ "internalType": "contract IAccessControlManagerV8", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "boundValidator", + "outputs": [{ "internalType": "contract BoundValidatorInterface", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "enum ResilientOracle.OracleRole", "name": "role", "type": "uint8" }, + { "internalType": "bool", "name": "enable", "type": "bool" } + ], + "name": "enableOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "enum ResilientOracle.OracleRole", "name": "role", "type": "uint8" } + ], + "name": "getOracle", + "outputs": [ + { "internalType": "address", "name": "oracle", "type": "address" }, + { "internalType": "bool", "name": "enabled", "type": "bool" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "asset", "type": "address" }], + "name": "getPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "asset", "type": "address" }], + "name": "getTokenConfig", + "outputs": [ + { + "components": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "address[3]", "name": "oracles", "type": "address[3]" }, + { "internalType": "bool[3]", "name": "enableFlagsForOracles", "type": "bool[3]" }, + { "internalType": "bool", "name": "cachingEnabled", "type": "bool" } + ], + "internalType": "struct ResilientOracle.TokenConfig", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "vToken", "type": "address" }], + "name": "getUnderlyingPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "accessControlManager_", "type": "address" }], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "nativeMarket", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [], "name": "pause", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "paused", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "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": "accessControlManager_", "type": "address" }], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "address", "name": "oracle", "type": "address" }, + { "internalType": "enum ResilientOracle.OracleRole", "name": "role", "type": "uint8" } + ], + "name": "setOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "address[3]", "name": "oracles", "type": "address[3]" }, + { "internalType": "bool[3]", "name": "enableFlagsForOracles", "type": "bool[3]" }, + { "internalType": "bool", "name": "cachingEnabled", "type": "bool" } + ], + "internalType": "struct ResilientOracle.TokenConfig", + "name": "tokenConfig", + "type": "tuple" + } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "address[3]", "name": "oracles", "type": "address[3]" }, + { "internalType": "bool[3]", "name": "enableFlagsForOracles", "type": "bool[3]" }, + { "internalType": "bool", "name": "cachingEnabled", "type": "bool" } + ], + "internalType": "struct ResilientOracle.TokenConfig[]", + "name": "tokenConfigs_", + "type": "tuple[]" + } + ], + "name": "setTokenConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { "inputs": [], "name": "unpause", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [{ "internalType": "address", "name": "asset", "type": "address" }], + "name": "updateAssetPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "vToken", "type": "address" }], + "name": "updatePrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vai", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-664/abi/VToken.json b/simulations/vip-664/abi/VToken.json new file mode 100644 index 000000000..68f428c6c --- /dev/null +++ b/simulations/vip-664/abi/VToken.json @@ -0,0 +1,870 @@ +[ + { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, + { "inputs": [], "name": "FlashLoanAlreadyActive", "type": "error" }, + { + "inputs": [ + { "internalType": "uint256", "name": "fee", "type": "uint256" }, + { "internalType": "uint256", "name": "maxFee", "type": "uint256" } + ], + "name": "FlashLoanFeeTooHigh", + "type": "error" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "fee", "type": "uint256" }, + { "internalType": "uint256", "name": "maxFee", "type": "uint256" } + ], + "name": "FlashLoanProtocolShareTooHigh", + "type": "error" + }, + { "inputs": [], "name": "InsufficientCash", "type": "error" }, + { + "inputs": [ + { "internalType": "uint256", "name": "actualAmount", "type": "uint256" }, + { "internalType": "uint256", "name": "requiredTotalFee", "type": "uint256" } + ], + "name": "InsufficientRepayment", + "type": "error" + }, + { "inputs": [], "name": "InvalidComptroller", "type": "error" }, + { + "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": "uint256", "name": "oldFlashLoanFeeMantissa", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "newFlashLoanFeeMantissa", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "oldFlashLoanProtocolShare", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "newFlashLoanProtocolShare", "type": "uint256" } + ], + "name": "FlashLoanFeeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "bool", "name": "previousStatus", "type": "bool" }, + { "indexed": false, "internalType": "bool", "name": "newStatus", "type": "bool" } + ], + "name": "FlashLoanStatusChanged", + "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 InterestRateModelV8", + "name": "oldInterestRateModel", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract InterestRateModelV8", + "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" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "asset", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "sender", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "totalFee", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "protocolFee", "type": "uint256" } + ], + "name": "TransferInUnderlyingFlashLoan", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "asset", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "receiver", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "TransferOutUnderlyingFlashLoan", + "type": "event" + }, + { + "inputs": [], + "name": "_acceptAdmin", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "addAmount", "type": "uint256" }], + "name": "_addReserves", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes", "name": "data", "type": "bytes" }], + "name": "_becomeImplementation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "reduceAmount_", "type": "uint256" }], + "name": "_reduceReserves", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { "inputs": [], "name": "_resignImplementation", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [{ "internalType": "contract ComptrollerInterface", "name": "newComptroller", "type": "address" }], + "name": "_setComptroller", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "contract InterestRateModelV8", "name": "newInterestRateModel_", "type": "address" }], + "name": "_setInterestRateModel", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address payable", "name": "newPendingAdmin", "type": "address" }], + "name": "_setPendingAdmin", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "newReserveFactorMantissa_", "type": "uint256" }], + "name": "_setReserveFactor", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "accrualBlockNumber", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "accrueInterest", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "admin", + "outputs": [{ "internalType": "address payable", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "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": "owner", "type": "address" }], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }], + "name": "balanceOfUnderlying", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "borrowAmount", "type": "uint256" }], + "name": "borrow", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "account", "type": "address" }], + "name": "borrowBalanceCurrent", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "account", "type": "address" }], + "name": "borrowBalanceStored", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "borrower", "type": "address" }, + { "internalType": "uint256", "name": "borrowAmount", "type": "uint256" } + ], + "name": "borrowBehalf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "borrowIndex", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "borrowRatePerBlock", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "amount", "type": "uint256" }], + "name": "calculateFlashLoanFee", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "comptroller", + "outputs": [{ "internalType": "contract ComptrollerInterface", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "exchangeRateCurrent", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "exchangeRateStored", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "flashLoanAmount", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "borrower", "type": "address" }, + { "internalType": "uint256", "name": "borrowAmount", "type": "uint256" } + ], + "name": "flashLoanDebtPosition", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "flashLoanFeeMantissa", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "flashLoanProtocolShareMantissa", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "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" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCash", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "underlying_", "type": "address" }, + { "internalType": "contract ComptrollerInterface", "name": "comptroller_", "type": "address" }, + { "internalType": "contract InterestRateModelV8", "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": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "contract ComptrollerInterface", "name": "comptroller_", "type": "address" }, + { "internalType": "contract InterestRateModelV8", "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": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "interestRateModel", + "outputs": [{ "internalType": "contract InterestRateModelV8", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isFlashLoanEnabled", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isVToken", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "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" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "mintAmount", "type": "uint256" }], + "name": "mint", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "receiver", "type": "address" }, + { "internalType": "uint256", "name": "mintAmount", "type": "uint256" } + ], + "name": "mintBehalf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingAdmin", + "outputs": [{ "internalType": "address payable", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolShareReserve", + "outputs": [{ "internalType": "address payable", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "redeemTokens", "type": "uint256" }], + "name": "redeem", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "redeemer", "type": "address" }, + { "internalType": "uint256", "name": "redeemTokens", "type": "uint256" } + ], + "name": "redeemBehalf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "redeemAmount", "type": "uint256" }], + "name": "redeemUnderlying", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "redeemer", "type": "address" }, + { "internalType": "uint256", "name": "redeemAmount", "type": "uint256" } + ], + "name": "redeemUnderlyingBehalf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "reduceReservesBlockDelta", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "reduceReservesBlockNumber", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "repayAmount", "type": "uint256" }], + "name": "repayBorrow", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "borrower", "type": "address" }, + { "internalType": "uint256", "name": "repayAmount", "type": "uint256" } + ], + "name": "repayBorrowBehalf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "reserveFactorMantissa", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "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" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "newAccessControlManagerAddress", "type": "address" }], + "name": "setAccessControlManager", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "bool", "name": "enabled", "type": "bool" }], + "name": "setFlashLoanEnabled", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "flashLoanFeeMantissa_", "type": "uint256" }, + { "internalType": "uint256", "name": "flashLoanProtocolShare_", "type": "uint256" } + ], + "name": "setFlashLoanFeeMantissa", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address payable", "name": "protcolShareReserve_", "type": "address" }], + "name": "setProtocolShareReserve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "newReduceReservesBlockDelta_", "type": "uint256" }], + "name": "setReduceReservesBlockDelta", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "supplyRatePerBlock", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalBorrows", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalBorrowsCurrent", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalReserves", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "dst", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "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" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address payable", "name": "from", "type": "address" }, + { "internalType": "uint256", "name": "repaymentAmount", "type": "uint256" }, + { "internalType": "uint256", "name": "totalFee", "type": "uint256" }, + { "internalType": "uint256", "name": "protocolFee", "type": "uint256" } + ], + "name": "transferInUnderlyingFlashLoan", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address payable", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transferOutUnderlyingFlashLoan", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "underlying", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-664/bscmainnet.ts new file mode 100644 index 000000000..02e4274f4 --- /dev/null +++ b/simulations/vip-664/bscmainnet.ts @@ -0,0 +1,243 @@ +import { expect } from "chai"; +import { BigNumber } from "ethers"; +import { parseUnits } from "ethers/lib/utils"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { expectEvents } from "src/utils"; +import { forking, testVip } from "src/vip-framework"; +import { checkRiskParameters } from "src/vip-framework/checks/checkRiskParameters"; +import { checkVToken } from "src/vip-framework/checks/checkVToken"; +import { checkInterestRate } from "src/vip-framework/checks/interestRateModel"; + +import { + BORROW_ACTION, + CAPO_GROWTH_RATE_PER_YEAR, + CAPO_SNAPSHOT_INTERVAL, + DBO_COOLDOWN_PERIOD, + DBO_RESET_THRESHOLD, + DBO_TRIGGER_THRESHOLD, + DEVIATION_BOUNDED_ORACLE, + MARKETS, + PROTOCOL_SHARE_RESERVE, + REDUCE_RESERVES_BLOCK_DELTA, + convertAmountToVTokens, + vTokensRemaining, + vip664, +} from "../../vips/vip-664/bscmainnet"; +import CAPPED_ORACLE_ABI from "./abi/CappedOracle.json"; +import COMPTROLLER_ABI from "./abi/Comptroller.json"; +import DBO_ABI from "./abi/DeviationBoundedOracle.json"; +import ERC20_ABI from "./abi/ERC20.json"; +import ERC4626_ABI from "./abi/ERC4626.json"; +import RESILIENT_ORACLE_ABI from "./abi/ResilientOracle.json"; +import VTOKEN_ABI from "./abi/VToken.json"; + +const { bscmainnet } = NETWORK_ADDRESSES; + +const SECONDS_PER_YEAR = 31536000; +// A 24-decimal share priced by the ResilientOracle is returned at 10**(36-24) = 10**12, so a +// ~$1 vault share is ~1e12. The band catches a decimals mistake in the deployed oracle. +const ONE_SHARE = parseUnits("1", 24); +const MIN_SHARE_PRICE = parseUnits("0.95", 12); +const MAX_SHARE_PRICE = parseUnits("1.05", 12); + +// TODO(deploy): bump to a block after the 3 capped ERC4626 oracles and 3 vTokens are deployed and +// the VTreasury has been funded with the bootstrap vhTokens. +const FORK_BLOCK = 116824477; + +forking(FORK_BLOCK, async () => { + const comptroller = new ethers.Contract(bscmainnet.UNITROLLER, COMPTROLLER_ABI, ethers.provider); + const resilientOracle = new ethers.Contract(bscmainnet.RESILIENT_ORACLE, RESILIENT_ORACLE_ABI, ethers.provider); + const dbo = new ethers.Contract(DEVIATION_BOUNDED_ORACLE, DBO_ABI, ethers.provider); + + // The bootstrap must draw from the Treasury's real balance, so snapshot it before executing. + const treasuryBalanceBefore: Record = {}; + + before(async () => { + for (const m of MARKETS) { + const underlying = new ethers.Contract(m.vToken.underlying.address, ERC20_ABI, ethers.provider); + treasuryBalanceBefore[m.vToken.address] = await underlying.balanceOf(bscmainnet.VTREASURY); + } + }); + + describe("Pre-VIP behavior", async () => { + for (const m of MARKETS) { + it(`${m.vToken.symbol} market is not listed`, async () => { + const market = await comptroller.markets(m.vToken.address); + expect(market.isListed).to.equal(false); + }); + + it(`${m.vToken.underlying.symbol} has no price`, async () => { + await expect(resilientOracle.getPrice(m.vToken.underlying.address)).to.be.reverted; + }); + + it(`VTreasury holds enough ${m.vToken.underlying.symbol} for the bootstrap`, async () => { + const underlying = new ethers.Contract(m.vToken.underlying.address, ERC20_ABI, ethers.provider); + expect(await underlying.balanceOf(bscmainnet.VTREASURY)).to.be.gte(m.initialSupply.amount); + }); + } + }); + + testVip("VIP-664", await vip664(), { + callbackAfterExecution: async txResponse => { + await expectEvents( + txResponse, + [COMPTROLLER_ABI, VTOKEN_ABI], + [ + "MarketListed", + "NewSupplyCap", + "ActionPausedMarket", + "NewAccessControlManager", + "NewProtocolShareReserve", + "NewReduceReservesBlockDelta", + "NewReserveFactor", + "NewCollateralFactor", + "NewLiquidationThreshold", + "NewLiquidationIncentive", + ], + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3], + ); + }, + }); + + describe("Post-VIP behavior", async () => { + for (const m of MARKETS) { + describe(`${m.vToken.symbol} market`, async () => { + const vToken = new ethers.Contract(m.vToken.address, VTOKEN_ABI, ethers.provider); + const underlying = new ethers.Contract(m.vToken.underlying.address, ERC20_ABI, ethers.provider); + const vault = new ethers.Contract(m.vToken.underlying.address, ERC4626_ABI, ethers.provider); + const cappedOracle = new ethers.Contract(m.oracle.address, CAPPED_ORACLE_ABI, ethers.provider); + + it("check new IRM", async () => { + expect(await vToken.interestRateModel()).to.equal(m.rateModel); + }); + + checkInterestRate(m.rateModel, m.vToken.symbol, { + base: m.interestRateModel.baseRatePerYear, + multiplier: m.interestRateModel.multiplierPerYear, + jump: m.interestRateModel.jumpMultiplierPerYear, + kink: m.interestRateModel.kink, + }); + + checkVToken(m.vToken.address, { + name: m.vToken.name, + symbol: m.vToken.symbol, + decimals: m.vToken.decimals, + underlying: m.vToken.underlying, + exchangeRate: m.vToken.exchangeRate, + comptroller: m.vToken.comptroller, + }); + + checkRiskParameters(m.vToken.address, m.vToken, m.riskParameters); + + // checkRiskParameters skips both on the legacy pool, but the Core Comptroller stores them + // per market and this VIP sets them, so assert them here. + it("sets the liquidation threshold", async () => { + expect(await comptroller.getLiquidationThreshold(m.vToken.address)).to.equal( + m.riskParameters.liquidationThreshold, + ); + }); + + it("sets the liquidation incentive", async () => { + expect(await comptroller.getLiquidationIncentive(m.vToken.address)).to.equal( + m.riskParameters.liquidationIncentive, + ); + }); + + it("registers the capped ERC4626 oracle as the only ResilientOracle source", async () => { + const config = await resilientOracle.getTokenConfig(m.vToken.underlying.address); + expect(config.oracles[0]).to.equal(m.oracle.address); + expect(config.enableFlagsForOracles).to.deep.equal([true, false, false]); + }); + + it("wires the capped oracle to the vault and its ERC4626 asset", async () => { + expect(await cappedOracle.CORRELATED_TOKEN()).to.equal(m.vToken.underlying.address); + expect(await cappedOracle.UNDERLYING_TOKEN()).to.equal(await vault.asset()); + expect(await cappedOracle.RESILIENT_ORACLE()).to.equal(bscmainnet.RESILIENT_ORACLE); + }); + + it("caps the exchange rate at 5%/yr over a 30-day snapshot", async () => { + expect(await cappedOracle.growthRatePerSecond()).to.equal(CAPO_GROWTH_RATE_PER_YEAR.div(SECONDS_PER_YEAR)); + expect(await cappedOracle.snapshotInterval()).to.equal(CAPO_SNAPSHOT_INTERVAL); + expect(await cappedOracle.snapshotGap()).to.be.gt(0); + // The seed must sit above the live rate, otherwise the market lists already capped. + expect(await cappedOracle.isCapped()).to.equal(false); + }); + + it("prices the vault at its ERC4626 asset price times the vault exchange rate", async () => { + const assetPrice = await resilientOracle.getPrice(await vault.asset()); + const exchangeRate = await vault.convertToAssets(ONE_SHARE); + const price = await resilientOracle.getPrice(m.vToken.underlying.address); + + expect(price).to.equal(assetPrice.mul(exchangeRate).div(ONE_SHARE)); + expect(price).to.be.gt(MIN_SHARE_PRICE).and.lt(MAX_SHARE_PRICE); + expect(await resilientOracle.getUnderlyingPrice(m.vToken.address)).to.equal(price); + }); + + it("enables Oracle Dynamic Protection Mode with a 5% trigger", async () => { + const cfg = await dbo.assetProtectionConfig(m.vToken.underlying.address); + expect(cfg.isBoundedPricingEnabled).to.equal(true); + expect(cfg.triggerThreshold).to.equal(DBO_TRIGGER_THRESHOLD); + expect(cfg.resetThreshold).to.equal(DBO_RESET_THRESHOLD); + expect(cfg.cooldownPeriod).to.equal(DBO_COOLDOWN_PERIOD); + expect(cfg.cachingEnabled).to.equal(false); + }); + + it("market has correct owner", async () => { + expect(await vToken.admin()).to.equal(bscmainnet.NORMAL_TIMELOCK); + }); + + it("market has correct ACM", async () => { + expect(await vToken.accessControlManager()).to.equal(bscmainnet.ACCESS_CONTROL_MANAGER); + }); + + it("market has correct protocol share reserve", async () => { + expect(await vToken.protocolShareReserve()).to.equal(PROTOCOL_SHARE_RESERVE); + }); + + it("market has correct reduce reserves block delta", async () => { + expect(await vToken.reduceReservesBlockDelta()).to.equal(REDUCE_RESERVES_BLOCK_DELTA); + }); + + it("market has correct total supply", async () => { + expect(await vToken.totalSupply()).to.equal( + convertAmountToVTokens(m.initialSupply.amount, m.vToken.exchangeRate), + ); + }); + + it("market has balance of underlying", async () => { + expect(await underlying.balanceOf(m.vToken.address)).to.equal(m.initialSupply.amount); + }); + + it("bootstrap drew the initial supply from the VTreasury's real balance", async () => { + const balanceAfter = await underlying.balanceOf(bscmainnet.VTREASURY); + expect(treasuryBalanceBefore[m.vToken.address].sub(balanceAfter)).to.equal(m.initialSupply.amount); + }); + + it("should not leave any vTokens in the timelock", async () => { + expect(await vToken.balanceOf(bscmainnet.NORMAL_TIMELOCK)).to.equal(0); + }); + + it("should burn vTokens", async () => { + expect(await vToken.balanceOf(ethers.constants.AddressZero)).to.equal(m.initialSupply.vTokensToBurn); + }); + + it("should send remaining vTokens to vTokenReceiver", async () => { + expect(await vToken.balanceOf(m.initialSupply.vTokenReceiver)).to.equal(vTokensRemaining(m)); + }); + + it("should leave no underlying approval to the vToken", async () => { + expect(await underlying.allowance(bscmainnet.NORMAL_TIMELOCK, m.vToken.address)).to.equal(0); + }); + + it("should pause borrowing on the market", async () => { + expect(await comptroller.actionPaused(m.vToken.address, BORROW_ACTION)).to.equal(true); + }); + + it("should keep the borrow cap at zero", async () => { + expect(await comptroller.borrowCaps(m.vToken.address)).to.equal(0); + }); + }); + } + }); +}); From 87a743d310a23be1a47af3ccd5ae8e1e7f089221 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 19 Aug 2026 16:39:02 +0530 Subject: [PATCH 06/23] fix(vip-664): arm the growth cap and reuse the IRM The capped ERC4626 oracles deploy with every cap parameter zeroed, the same way the asBNB oracle did, so the cap has to be armed by a VIP or the markets list with an uncapped price source. VIP-530 is the precedent. Order matters: setSnapshot must precede setGrowthRate, because updateSnapshot() on an oracle whose snapshotMaxExchangeRate is still 0 collapses the cap to snapshotGap alone. Gap of 41 bps is one snapshot interval of capped growth (5% * 30/365), the ratio VIP-530 used on every asset it armed. Seeds are the live vault rates at block 116836175. Measured growth is 1.82%/1.52%/2.29% per year, so the 5% cap keeps 2-3x headroom and will not bind. No new interest rate model: 0x1Ef3b851 already carries base 0, multiplier 9%, jump 200%, kink 50% and already backs vasBNB and vslisBNB. --- simulations/vip-664/bscmainnet.ts | 22 +++++++--- vips/vip-664/bscmainnet.ts | 73 +++++++++++++++++++++++++------ 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-664/bscmainnet.ts index 02e4274f4..64c0060c7 100644 --- a/simulations/vip-664/bscmainnet.ts +++ b/simulations/vip-664/bscmainnet.ts @@ -12,6 +12,7 @@ import { checkInterestRate } from "src/vip-framework/checks/interestRateModel"; import { BORROW_ACTION, CAPO_GROWTH_RATE_PER_YEAR, + CAPO_SEED_TIMESTAMP, CAPO_SNAPSHOT_INTERVAL, DBO_COOLDOWN_PERIOD, DBO_RESET_THRESHOLD, @@ -21,6 +22,8 @@ import { PROTOCOL_SHARE_RESERVE, REDUCE_RESERVES_BLOCK_DELTA, convertAmountToVTokens, + seededSnapshot, + snapshotGap, vTokensRemaining, vip664, } from "../../vips/vip-664/bscmainnet"; @@ -82,8 +85,11 @@ forking(FORK_BLOCK, async () => { callbackAfterExecution: async txResponse => { await expectEvents( txResponse, - [COMPTROLLER_ABI, VTOKEN_ABI], + [COMPTROLLER_ABI, VTOKEN_ABI, CAPPED_ORACLE_ABI], [ + "SnapshotUpdated", + "GrowthRateUpdated", + "SnapshotGapUpdated", "MarketListed", "NewSupplyCap", "ActionPausedMarket", @@ -95,7 +101,7 @@ forking(FORK_BLOCK, async () => { "NewLiquidationThreshold", "NewLiquidationIncentive", ], - [3, 3, 3, 3, 3, 3, 3, 3, 3, 3], + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], ); }, }); @@ -156,12 +162,18 @@ forking(FORK_BLOCK, async () => { expect(await cappedOracle.RESILIENT_ORACLE()).to.equal(bscmainnet.RESILIENT_ORACLE); }); - it("caps the exchange rate at 5%/yr over a 30-day snapshot", async () => { + it("arms the growth cap at 5%/yr over a 30-day snapshot", async () => { expect(await cappedOracle.growthRatePerSecond()).to.equal(CAPO_GROWTH_RATE_PER_YEAR.div(SECONDS_PER_YEAR)); expect(await cappedOracle.snapshotInterval()).to.equal(CAPO_SNAPSHOT_INTERVAL); - expect(await cappedOracle.snapshotGap()).to.be.gt(0); - // The seed must sit above the live rate, otherwise the market lists already capped. + expect(await cappedOracle.snapshotGap()).to.equal(snapshotGap(m.oracle.seedExchangeRate)); + expect(await cappedOracle.snapshotMaxExchangeRate()).to.equal(seededSnapshot(m.oracle.seedExchangeRate)); + expect(await cappedOracle.snapshotTimestamp()).to.equal(CAPO_SEED_TIMESTAMP); + }); + + it("does not list the market already capped", async () => { + // A seed below the live rate would cap the price the moment the market lists. expect(await cappedOracle.isCapped()).to.equal(false); + expect(await cappedOracle.getMaxAllowedExchangeRate()).to.be.gte(await cappedOracle.getUnderlyingAmount()); }); it("prices the vault at its ERC4626 asset price times the vault exchange rate", async () => { diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index 16817c0c3..61f1b1b50 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -27,13 +27,25 @@ export const BORROW_ACTION = 2; // Comptroller Action enum: BORROW export const { RESILIENT_ORACLE } = bscmainnet; -// Capped ERC4626 oracle: set at oracle-deploy time, asserted by the simulation. -// Price = underlying resilient price x capped vault exchange rate, the asBNB/slisBNB CAPO design. -// Both live BNB Chain instances run 5%/yr over a 30-day snapshot interval (VIP-605); the per-asset -// snapshotGap is a deploy-time buffer over the seeded rate (asBNB 2.47%, slisBNB 0.34%), so it is -// read from the deployed oracle rather than pinned here. +// Already deployed; backs vasBNB and vslisBNB with these exact params (base 0, multiplier 9%, +// jump 200%, kink 50%), so no new interest rate model is deployed for these markets. +export const JUMP_RATE_MODEL = "0x1Ef3b851CE40B663dBbF91B86A4EE51A4a0999C5"; + +// Capped ERC4626 oracle. Price = underlying resilient price x capped vault exchange rate, the +// asBNB/slisBNB design. The instances are deployed with the cap zeroed, so this VIP arms it. +// Order is load-bearing: setSnapshot must precede setGrowthRate, because updateSnapshot() on an +// oracle whose snapshotMaxExchangeRate is still 0 collapses the cap to snapshotGap alone. Same +// three commands in the same order as VIP-530. export const CAPO_GROWTH_RATE_PER_YEAR = parseUnits("0.05", 18); export const CAPO_SNAPSHOT_INTERVAL = 30 * 24 * 60 * 60; +// One snapshot interval of growth, the ratio VIP-530 applied to every asset it armed: +// 5% * 30/365 = 0.41%. +export const CAPO_SNAPSHOT_GAP_BPS = BigNumber.from(41); +// Vault exchange rates below were read at block 116836175 (2026-08-19T10:57:40Z). +export const CAPO_SEED_TIMESTAMP = 1787137060; + +export const snapshotGap = (exchangeRate: BigNumber) => exchangeRate.mul(CAPO_SNAPSHOT_GAP_BPS).div(10000); +export const seededSnapshot = (exchangeRate: BigNumber) => exchangeRate.add(snapshotGap(exchangeRate)); // Oracle Dynamic Protection Mode / "E-brake" (DeviationBoundedOracle, see VIP-617). export const DEVIATION_BOUNDED_ORACLE = "0xc79Cb7efEBd121DC4B39eA141C214606595D665A"; @@ -55,7 +67,6 @@ export type MarketSpec = { comptroller: string; isLegacyPool: boolean; }; - // TODO(deploy): the JumpRateModel deployed alongside the vToken. rateModel: string; interestRateModel: { model: "jump"; @@ -67,6 +78,8 @@ export type MarketSpec = { oracle: { // Capped ERC4626Oracle instance registered as the ResilientOracle main source for the vhToken. address: string; + // Live vault exchange rate the growth cap is seeded from. + seedExchangeRate: BigNumber; }; riskParameters: { collateralFactor: BigNumber; @@ -110,7 +123,7 @@ export const MARKET_VHUSDT: MarketSpec = { comptroller: bscmainnet.UNITROLLER, isLegacyPool: true, }, - rateModel: constants.AddressZero, + rateModel: JUMP_RATE_MODEL, interestRateModel: { model: "jump", baseRatePerYear: "0", @@ -118,7 +131,7 @@ export const MARKET_VHUSDT: MarketSpec = { jumpMultiplierPerYear: "2", kink: "0.5", }, - oracle: { address: VHUSDT_ORACLE }, + oracle: { address: VHUSDT_ORACLE, seedExchangeRate: parseUnits("1.000650000021349262", 18) }, riskParameters: { collateralFactor: parseUnits("0.8", 18), liquidationThreshold: parseUnits("0.8", 18), @@ -150,7 +163,7 @@ export const MARKET_VHUSDC: MarketSpec = { comptroller: bscmainnet.UNITROLLER, isLegacyPool: true, }, - rateModel: constants.AddressZero, + rateModel: JUMP_RATE_MODEL, interestRateModel: { model: "jump", baseRatePerYear: "0", @@ -158,7 +171,7 @@ export const MARKET_VHUSDC: MarketSpec = { jumpMultiplierPerYear: "2", kink: "0.5", }, - oracle: { address: VHUSDC_ORACLE }, + oracle: { address: VHUSDC_ORACLE, seedExchangeRate: parseUnits("1.000815629493107489", 18) }, riskParameters: { collateralFactor: parseUnits("0.825", 18), liquidationThreshold: parseUnits("0.825", 18), @@ -190,7 +203,7 @@ export const MARKET_VHU: MarketSpec = { comptroller: bscmainnet.UNITROLLER, isLegacyPool: true, }, - rateModel: constants.AddressZero, + rateModel: JUMP_RATE_MODEL, interestRateModel: { model: "jump", baseRatePerYear: "0", @@ -198,7 +211,7 @@ export const MARKET_VHU: MarketSpec = { jumpMultiplierPerYear: "2", kink: "0.5", }, - oracle: { address: VHU_ORACLE }, + oracle: { address: VHU_ORACLE, seedExchangeRate: parseUnits("1.000544217035461378", 18) }, riskParameters: { collateralFactor: parseUnits("0.75", 18), liquidationThreshold: parseUnits("0.75", 18), @@ -241,7 +254,8 @@ If passed, this VIP will list three new non-borrowable collateral markets in the For each new market this VIP will: -- Register the vhToken in the ResilientOracle using a dedicated capped **ERC4626Oracle** as the single price source. The oracle prices the vhToken as *underlying resilient price × capped vault exchange rate* (5%/yr growth cap), the same design as the live asBNB and slisBNB capped oracles (5%/yr over a 30-day snapshot interval, VIP-605). +- Arm the growth cap on the vhToken's capped **ERC4626Oracle** (seed the snapshot, set the growth rate and set the snapshot gap) +- Register that oracle in the ResilientOracle as the single price source. It prices the vhToken as *underlying resilient price × capped vault exchange rate*, the same design as the live asBNB and slisBNB capped oracles. - Add the market to the Core Pool Comptroller - Set the supply cap, collateral factor, liquidation threshold, liquidation incentive and reserve factor - Set the AccessControlManager, ProtocolShareReserve and reduce-reserves block delta on the vToken @@ -282,13 +296,27 @@ All three markets share the same interest rate model (base 0%, multiplier 9%, ju #### Notes on the risk parameters -- **Interest rate model.** Although these markets are non-borrowable, a vToken requires an interest rate model at construction, so a jump-rate IRM (base 0%, multiplier 9%, jump multiplier 200%, kink 50%) is wired to make the market well-formed. It has no economic effect while borrowing is paused. The listing checklist noted "IRM not needed" precisely because the market is non-borrowable — that is consistent with this VIP: the IRM exists only to satisfy the constructor and is inert. +- **Interest rate model.** Although these markets are non-borrowable, a vToken requires an interest rate model at construction, so a jump-rate IRM (base 0%, multiplier 9%, jump multiplier 200%, kink 50%) is wired to make the market well-formed. No new model is deployed: [0x1Ef3b851CE40B663dBbF91B86A4EE51A4a0999C5](https://bscscan.com/address/0x1Ef3b851CE40B663dBbF91B86A4EE51A4a0999C5) already carries exactly these parameters and already backs vasBNB and vslisBNB. It has no economic effect while borrowing is paused. The listing checklist noted "IRM not needed" precisely because the market is non-borrowable — that is consistent with this VIP: the IRM exists only to satisfy the constructor and is inert. - **Collateral factor equals liquidation threshold** on all three markets (80/80, 82.5/82.5, 75/75). This is intentional and matches the approved risk parameters from the listing template. The vhTokens are ~$1 stablecoin-correlated assets priced through a growth-capped ERC4626 oracle with the E-brake (DeviationBoundedOracle) protection mode enabled, so no CF↔LT buffer is applied; a position opened at the maximum LTV therefore sits at the liquidation boundary, which is the deliberate design for these tightly-pegged collaterals. - **The three collateral factors differ (82.5% vhUSDC, 80% vhUSDT, 75% vhU).** These are the approved per-asset values set by the risk manager in the listing template — not a single blanket figure — and are ordered by the relative maturity and market depth of each underlying peg (USDC > USDT > USD1/U). vhU/USD1, the newest and least liquid of the three, carries the most conservative factor. - **Supply cap is denominated in the underlying token amount, not USD.** \`_setMarketSupplyCaps\` takes an amount of the underlying, so each cap of 10,000,000 is 10,000,000 vhTokens (24 decimals). At the current ~$1 vault price this corresponds to roughly $10M of collateral exposure per market. - **Reserve factor (10%), vTokenReceiver (VTreasury) and bootstrap amount (100 vhToken per market)** were not specified in the listing template and follow the standard Core-pool listing convention. The reserve factor is inert while borrowing is paused. - **Protocol seize share** is a global Comptroller-level parameter on the Core pool rather than a per-market setting, so it is not modified by this VIP; the existing Core-pool value applies to the new markets. +#### Capped oracle + +The oracles are deployed with the cap zeroed, exactly as the asBNB oracle was, so this VIP arms it per market with \`setSnapshot\`, \`setGrowthRate\` and \`setSnapshotGap\` — the same three commands in the same order as VIP-530. The permissions for all three already sit with the Normal, Fast-Track and Critical timelocks (granted repo-wide in VIP-517), so no new ACM grants are needed. + +| | Growth rate | Snapshot interval | Snapshot gap | Seeded exchange rate | +|---|---|---|---|---| +| vhUSDT | 5%/yr | 30 days | 41 bps (0.004102665000087531) | 1.004752665021436793 | +| vhUSDC | 5%/yr | 30 days | 41 bps (0.004103344080921740) | 1.004918973574029229 | +| vhU | 5%/yr | 30 days | 41 bps (0.004102231289845391) | 1.004646448325306769 | + +- **The 5%/yr growth rate leaves 2–3× headroom over observed yield.** The three vaults launched ~13 days before this was written, all from an exchange rate of exactly 1.0; measured growth annualises to 1.82% (vhUSDT), 2.29% (vhUSDC) and 1.52% (vhU). A 5% cap therefore does not bind in normal operation, and matches what asBNB and slisBNB run since VIP-605. +- **The 41 bps gap is one snapshot interval of capped growth** (5% × 30/365 = 0.41%), the same ratio VIP-530 applied to every asset it armed — BNBx 7.53%/yr → 63 bps, ankrBNB 6.12%/yr → 51 bps, sUSDe 28.27%/yr → 236 bps, slisBNB 4.12%/yr → 34 bps. +- Exchange rates were read at block 116836175 (\`2026-08-19T10:57:40Z\`), which is also the snapshot timestamp. Because the seed carries 41 bps of headroom on top of the growth allowance accruing from that timestamp, drift between authoring and execution does not cap the price at listing. + #### Underlying tokens Each underlying was read directly from BNB Chain and matches the listing template: @@ -311,6 +339,23 @@ The bootstrap liquidity is withdrawn from the VTreasury, which currently holds * return makeProposal( MARKETS.flatMap(m => [ + // Arm the growth cap before the price source goes live. Order is load-bearing (see above). + { + target: m.oracle.address, + signature: "setSnapshot(uint256,uint256)", + params: [seededSnapshot(m.oracle.seedExchangeRate), CAPO_SEED_TIMESTAMP], + }, + { + target: m.oracle.address, + signature: "setGrowthRate(uint256,uint256)", + params: [CAPO_GROWTH_RATE_PER_YEAR, CAPO_SNAPSHOT_INTERVAL], + }, + { + target: m.oracle.address, + signature: "setSnapshotGap(uint256)", + params: [snapshotGap(m.oracle.seedExchangeRate)], + }, + // Oracle configuration — single source: the capped ERC4626Oracle for the vhToken. // The ERC4626Oracle reads the underlying (USDT/USDC/USD1) price from the ResilientOracle // itself and applies the growth-rate cap on the vault exchange rate, so no extra feed From ebf20adc17a223ca3a0dcd070d6ee24b2b4c3f15 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 19 Aug 2026 16:39:02 +0530 Subject: [PATCH 07/23] feat(vip-664): add bsctestnet VIP and simulation Mirrors the mainnet VIP through the real capped ERC4626 oracle instead of a mocked direct price, so the growth cap is exercised before mainnet. Mock 24-decimal vault, oracle and vToken addresses stay zeroed until the testnet deploy lands. --- simulations/vip-664/bsctestnet.ts | 250 ++++++++++++++++++++++++ vips/vip-664/bsctestnet.ts | 311 ++++++++++++++++++++++++++++++ 2 files changed, 561 insertions(+) create mode 100644 simulations/vip-664/bsctestnet.ts create mode 100644 vips/vip-664/bsctestnet.ts diff --git a/simulations/vip-664/bsctestnet.ts b/simulations/vip-664/bsctestnet.ts new file mode 100644 index 000000000..e83283241 --- /dev/null +++ b/simulations/vip-664/bsctestnet.ts @@ -0,0 +1,250 @@ +import { expect } from "chai"; +import { BigNumber } from "ethers"; +import { parseUnits } from "ethers/lib/utils"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { expectEvents } from "src/utils"; +import { forking, testVip } from "src/vip-framework"; +import { checkRiskParameters } from "src/vip-framework/checks/checkRiskParameters"; +import { checkVToken } from "src/vip-framework/checks/checkVToken"; +import { checkInterestRate } from "src/vip-framework/checks/interestRateModel"; + +import { + BORROW_ACTION, + CAPO_GROWTH_RATE_PER_YEAR, + CAPO_SEED_TIMESTAMP, + CAPO_SNAPSHOT_INTERVAL, + DBO_COOLDOWN_PERIOD, + DBO_RESET_THRESHOLD, + DBO_TRIGGER_THRESHOLD, + DEVIATION_BOUNDED_ORACLE, + MARKETS, + PROTOCOL_SHARE_RESERVE, + REDUCE_RESERVES_BLOCK_DELTA, + convertAmountToVTokens, + seededSnapshot, + snapshotGap, + vTokensRemaining, + vip664, +} from "../../vips/vip-664/bsctestnet"; +import CAPPED_ORACLE_ABI from "./abi/CappedOracle.json"; +import COMPTROLLER_ABI from "./abi/Comptroller.json"; +import DBO_ABI from "./abi/DeviationBoundedOracle.json"; +import ERC20_ABI from "./abi/ERC20.json"; +import ERC4626_ABI from "./abi/ERC4626.json"; +import RESILIENT_ORACLE_ABI from "./abi/ResilientOracle.json"; +import VTOKEN_ABI from "./abi/VToken.json"; + +const { bsctestnet } = NETWORK_ADDRESSES; + +const SECONDS_PER_YEAR = 31536000; +const ONE_SHARE = parseUnits("1", 24); + +// TODO(deploy): bump to a block after the mock vaults, oracles and vTokens are deployed on testnet +// and the VTreasury has been funded with the bootstrap vhTokens. +const FORK_BLOCK = 125980273; + +forking(FORK_BLOCK, async () => { + const comptroller = new ethers.Contract(bsctestnet.UNITROLLER, COMPTROLLER_ABI, ethers.provider); + const resilientOracle = new ethers.Contract(bsctestnet.RESILIENT_ORACLE, RESILIENT_ORACLE_ABI, ethers.provider); + const dbo = new ethers.Contract(DEVIATION_BOUNDED_ORACLE, DBO_ABI, ethers.provider); + + // The bootstrap must draw from the Treasury's real balance, so snapshot it before executing. + const treasuryBalanceBefore: Record = {}; + + before(async () => { + for (const m of MARKETS) { + const underlying = new ethers.Contract(m.vToken.underlying.address, ERC20_ABI, ethers.provider); + treasuryBalanceBefore[m.vToken.address] = await underlying.balanceOf(bsctestnet.VTREASURY); + } + }); + + describe("Pre-VIP behavior", async () => { + for (const m of MARKETS) { + it(`${m.vToken.symbol} market is not listed`, async () => { + const market = await comptroller.markets(m.vToken.address); + expect(market.isListed).to.equal(false); + }); + + it(`${m.vToken.underlying.symbol} has no price`, async () => { + await expect(resilientOracle.getPrice(m.vToken.underlying.address)).to.be.reverted; + }); + + it(`VTreasury holds enough ${m.vToken.underlying.symbol} for the bootstrap`, async () => { + const underlying = new ethers.Contract(m.vToken.underlying.address, ERC20_ABI, ethers.provider); + expect(await underlying.balanceOf(bsctestnet.VTREASURY)).to.be.gte(m.initialSupply.amount); + }); + } + }); + + testVip("VIP-664 testnet", await vip664(), { + callbackAfterExecution: async txResponse => { + await expectEvents( + txResponse, + [COMPTROLLER_ABI, VTOKEN_ABI, CAPPED_ORACLE_ABI], + [ + "SnapshotUpdated", + "GrowthRateUpdated", + "SnapshotGapUpdated", + "MarketListed", + "NewSupplyCap", + "ActionPausedMarket", + "NewAccessControlManager", + "NewProtocolShareReserve", + "NewReduceReservesBlockDelta", + "NewReserveFactor", + "NewCollateralFactor", + "NewLiquidationThreshold", + "NewLiquidationIncentive", + ], + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], + ); + }, + }); + + describe("Post-VIP behavior", async () => { + for (const m of MARKETS) { + describe(`${m.vToken.symbol} market`, async () => { + const vToken = new ethers.Contract(m.vToken.address, VTOKEN_ABI, ethers.provider); + const underlying = new ethers.Contract(m.vToken.underlying.address, ERC20_ABI, ethers.provider); + const vault = new ethers.Contract(m.vToken.underlying.address, ERC4626_ABI, ethers.provider); + const cappedOracle = new ethers.Contract(m.oracle.address, CAPPED_ORACLE_ABI, ethers.provider); + + it("check new IRM", async () => { + expect(await vToken.interestRateModel()).to.equal(m.rateModel); + }); + + checkInterestRate(m.rateModel, m.vToken.symbol, { + base: m.interestRateModel.baseRatePerYear, + multiplier: m.interestRateModel.multiplierPerYear, + jump: m.interestRateModel.jumpMultiplierPerYear, + kink: m.interestRateModel.kink, + }); + + checkVToken(m.vToken.address, { + name: m.vToken.name, + symbol: m.vToken.symbol, + decimals: m.vToken.decimals, + underlying: m.vToken.underlying, + exchangeRate: m.vToken.exchangeRate, + comptroller: m.vToken.comptroller, + }); + + checkRiskParameters(m.vToken.address, m.vToken, m.riskParameters); + + // checkRiskParameters skips both on the legacy pool, but the Core Comptroller stores them + // per market and this VIP sets them, so assert them here. + it("sets the liquidation threshold", async () => { + expect(await comptroller.getLiquidationThreshold(m.vToken.address)).to.equal( + m.riskParameters.liquidationThreshold, + ); + }); + + it("sets the liquidation incentive", async () => { + expect(await comptroller.getLiquidationIncentive(m.vToken.address)).to.equal( + m.riskParameters.liquidationIncentive, + ); + }); + + it("registers the capped ERC4626 oracle as the only ResilientOracle source", async () => { + const config = await resilientOracle.getTokenConfig(m.vToken.underlying.address); + expect(config.oracles[0]).to.equal(m.oracle.address); + expect(config.enableFlagsForOracles).to.deep.equal([true, false, false]); + }); + + it("wires the capped oracle to the vault and its ERC4626 asset", async () => { + expect(await cappedOracle.CORRELATED_TOKEN()).to.equal(m.vToken.underlying.address); + expect(await cappedOracle.UNDERLYING_TOKEN()).to.equal(await vault.asset()); + expect(await cappedOracle.RESILIENT_ORACLE()).to.equal(bsctestnet.RESILIENT_ORACLE); + }); + + it("arms the growth cap at 5%/yr over a 30-day snapshot", async () => { + expect(await cappedOracle.growthRatePerSecond()).to.equal(CAPO_GROWTH_RATE_PER_YEAR.div(SECONDS_PER_YEAR)); + expect(await cappedOracle.snapshotInterval()).to.equal(CAPO_SNAPSHOT_INTERVAL); + expect(await cappedOracle.snapshotGap()).to.equal(snapshotGap(m.oracle.seedExchangeRate)); + expect(await cappedOracle.snapshotMaxExchangeRate()).to.equal(seededSnapshot(m.oracle.seedExchangeRate)); + expect(await cappedOracle.snapshotTimestamp()).to.equal(CAPO_SEED_TIMESTAMP); + }); + + it("does not list the market already capped", async () => { + // A seed below the live rate would cap the price the moment the market lists. + expect(await cappedOracle.isCapped()).to.equal(false); + expect(await cappedOracle.getMaxAllowedExchangeRate()).to.be.gte(await cappedOracle.getUnderlyingAmount()); + }); + + it("prices the vault at its ERC4626 asset price times the vault exchange rate", async () => { + const assetPrice = await resilientOracle.getPrice(await vault.asset()); + const exchangeRate = await vault.convertToAssets(ONE_SHARE); + const price = await resilientOracle.getPrice(m.vToken.underlying.address); + + expect(price).to.equal(assetPrice.mul(exchangeRate).div(ONE_SHARE)); + expect(await resilientOracle.getUnderlyingPrice(m.vToken.address)).to.equal(price); + }); + + it("enables Oracle Dynamic Protection Mode with a 5% trigger", async () => { + const cfg = await dbo.assetProtectionConfig(m.vToken.underlying.address); + expect(cfg.isBoundedPricingEnabled).to.equal(true); + expect(cfg.triggerThreshold).to.equal(DBO_TRIGGER_THRESHOLD); + expect(cfg.resetThreshold).to.equal(DBO_RESET_THRESHOLD); + expect(cfg.cooldownPeriod).to.equal(DBO_COOLDOWN_PERIOD); + expect(cfg.cachingEnabled).to.equal(false); + }); + + it("market has correct owner", async () => { + expect(await vToken.admin()).to.equal(bsctestnet.NORMAL_TIMELOCK); + }); + + it("market has correct ACM", async () => { + expect(await vToken.accessControlManager()).to.equal(bsctestnet.ACCESS_CONTROL_MANAGER); + }); + + it("market has correct protocol share reserve", async () => { + expect(await vToken.protocolShareReserve()).to.equal(PROTOCOL_SHARE_RESERVE); + }); + + it("market has correct reduce reserves block delta", async () => { + expect(await vToken.reduceReservesBlockDelta()).to.equal(REDUCE_RESERVES_BLOCK_DELTA); + }); + + it("market has correct total supply", async () => { + expect(await vToken.totalSupply()).to.equal( + convertAmountToVTokens(m.initialSupply.amount, m.vToken.exchangeRate), + ); + }); + + it("market has balance of underlying", async () => { + expect(await underlying.balanceOf(m.vToken.address)).to.equal(m.initialSupply.amount); + }); + + it("bootstrap drew the initial supply from the VTreasury's real balance", async () => { + const balanceAfter = await underlying.balanceOf(bsctestnet.VTREASURY); + expect(treasuryBalanceBefore[m.vToken.address].sub(balanceAfter)).to.equal(m.initialSupply.amount); + }); + + it("should not leave any vTokens in the timelock", async () => { + expect(await vToken.balanceOf(bsctestnet.NORMAL_TIMELOCK)).to.equal(0); + }); + + it("should burn vTokens", async () => { + expect(await vToken.balanceOf(ethers.constants.AddressZero)).to.equal(m.initialSupply.vTokensToBurn); + }); + + it("should send remaining vTokens to vTokenReceiver", async () => { + expect(await vToken.balanceOf(m.initialSupply.vTokenReceiver)).to.equal(vTokensRemaining(m)); + }); + + it("should leave no underlying approval to the vToken", async () => { + expect(await underlying.allowance(bsctestnet.NORMAL_TIMELOCK, m.vToken.address)).to.equal(0); + }); + + it("should pause borrowing on the market", async () => { + expect(await comptroller.actionPaused(m.vToken.address, BORROW_ACTION)).to.equal(true); + }); + + it("should keep the borrow cap at zero", async () => { + expect(await comptroller.borrowCaps(m.vToken.address)).to.equal(0); + }); + }); + } + }); +}); diff --git a/vips/vip-664/bsctestnet.ts b/vips/vip-664/bsctestnet.ts new file mode 100644 index 000000000..20ddf93e3 --- /dev/null +++ b/vips/vip-664/bsctestnet.ts @@ -0,0 +1,311 @@ +import { BigNumber, constants } from "ethers"; +import { parseUnits } from "ethers/lib/utils"; +import { ethers } from "hardhat"; +import { NETWORK_ADDRESSES } from "src/networkAddresses"; +import { ProposalType } from "src/types"; +import { makeProposal } from "src/utils"; + +// Testnet mirror of vips/vip-664/bscmainnet.ts. It runs the same capped ERC4626 oracle path rather +// than a mocked direct price, so the growth cap is exercised before mainnet. + +const { bsctestnet } = NETWORK_ADDRESSES; + +// TODO(deploy): mock 24-decimal ERC4626 vaults, deployed alongside the oracles on bsctestnet. +export const VHUSDT = constants.AddressZero; +export const VHUSDC = constants.AddressZero; +export const VHU = constants.AddressZero; + +// TODO(deploy): fill after the capped ERC4626Oracle instances are deployed on bsctestnet. +export const VHUSDT_ORACLE = constants.AddressZero; +export const VHUSDC_ORACLE = constants.AddressZero; +export const VHU_ORACLE = constants.AddressZero; + +// TODO(deploy): fill after the vTokens are deployed on bsctestnet. +export const VVHUSDT = constants.AddressZero; +export const VVHUSDC = constants.AddressZero; +export const VVHU = constants.AddressZero; + +export const PROTOCOL_SHARE_RESERVE = "0x25c7c7D6Bf710949fD7f03364E9BA19a1b3c10E3"; +export const REDUCE_RESERVES_BLOCK_DELTA = "28800"; +export const BORROW_ACTION = 2; // Comptroller Action enum: BORROW + +export const { RESILIENT_ORACLE } = bsctestnet; + +// Already deployed; backs vasBNB and vslisBNB on testnet with these exact params. +export const JUMP_RATE_MODEL = "0x8734dBD8Ba959BbC75f2701a022f8d1D47e0722d"; + +// Same capped-oracle configuration as mainnet. A freshly deployed mock vault sits at an exchange +// rate of exactly 1, so the seed is 1 plus the gap. +export const CAPO_GROWTH_RATE_PER_YEAR = parseUnits("0.05", 18); +export const CAPO_SNAPSHOT_INTERVAL = 30 * 24 * 60 * 60; +export const CAPO_SNAPSHOT_GAP_BPS = BigNumber.from(41); +export const CAPO_SEED_TIMESTAMP = 1787137060; +export const SEED_EXCHANGE_RATE = parseUnits("1", 18); + +export const snapshotGap = (exchangeRate: BigNumber) => exchangeRate.mul(CAPO_SNAPSHOT_GAP_BPS).div(10000); +export const seededSnapshot = (exchangeRate: BigNumber) => exchangeRate.add(snapshotGap(exchangeRate)); + +// Oracle Dynamic Protection Mode / "E-brake" (DeviationBoundedOracle, see VIP-617). +export const DEVIATION_BOUNDED_ORACLE = "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8"; +export const DBO_COOLDOWN_PERIOD = 3600; +export const DBO_TRIGGER_THRESHOLD = parseUnits("0.05", 18); // 5% — the contract minimum +export const DBO_RESET_THRESHOLD = parseUnits("0.02", 18); + +export type MarketSpec = { + vToken: { + address: string; + name: string; + symbol: string; + underlying: { address: string; symbol: string; decimals: number }; + decimals: number; + exchangeRate: BigNumber; + comptroller: string; + isLegacyPool: boolean; + }; + rateModel: string; + interestRateModel: { + model: "jump"; + baseRatePerYear: string; + multiplierPerYear: string; + jumpMultiplierPerYear: string; + kink: string; + }; + oracle: { + address: string; + seedExchangeRate: BigNumber; + }; + riskParameters: { + collateralFactor: BigNumber; + liquidationThreshold: BigNumber; + liquidationIncentive: BigNumber; + reserveFactor: BigNumber; + supplyCap: BigNumber; + borrowCap: BigNumber; + }; + initialSupply: { + amount: BigNumber; + vTokenReceiver: string; + vTokensToBurn: BigNumber; + }; +}; + +// exchangeRate scale = 18 + underlyingDecimals(24) - vTokenDecimals(8) = 34. +const EXCHANGE_RATE = parseUnits("1", 34); +const SUPPLY_CAP = parseUnits("10000000", 24); +const LIQUIDATION_INCENTIVE = parseUnits("1.1", 18); +const RESERVE_FACTOR = parseUnits("0.1", 18); +const BOOTSTRAP_AMOUNT = parseUnits("100", 24); +const BOOTSTRAP_BURN = parseUnits("10", 8); + +const IRM = { + model: "jump" as const, + baseRatePerYear: "0", + multiplierPerYear: "0.09", + jumpMultiplierPerYear: "2", + kink: "0.5", +}; + +const market = ( + vToken: string, + name: string, + symbol: string, + underlying: string, + underlyingSymbol: string, + oracle: string, + collateralFactor: BigNumber, +): MarketSpec => ({ + vToken: { + address: vToken, + name, + symbol, + underlying: { address: underlying, symbol: underlyingSymbol, decimals: 24 }, + decimals: 8, + exchangeRate: EXCHANGE_RATE, + comptroller: bsctestnet.UNITROLLER, + isLegacyPool: true, + }, + rateModel: JUMP_RATE_MODEL, + interestRateModel: IRM, + oracle: { address: oracle, seedExchangeRate: SEED_EXCHANGE_RATE }, + riskParameters: { + collateralFactor, + liquidationThreshold: collateralFactor, + liquidationIncentive: LIQUIDATION_INCENTIVE, + reserveFactor: RESERVE_FACTOR, + supplyCap: SUPPLY_CAP, + borrowCap: parseUnits("0", 24), + }, + initialSupply: { + amount: BOOTSTRAP_AMOUNT, + vTokenReceiver: bsctestnet.VTREASURY, + vTokensToBurn: BOOTSTRAP_BURN, + }, +}); + +export const MARKET_VHUSDT = market( + VVHUSDT, + "Venus vhUSDT", + "vvhUSDT", + VHUSDT, + "vhUSDT", + VHUSDT_ORACLE, + parseUnits("0.8", 18), +); +export const MARKET_VHUSDC = market( + VVHUSDC, + "Venus vhUSDC", + "vvhUSDC", + VHUSDC, + "vhUSDC", + VHUSDC_ORACLE, + parseUnits("0.825", 18), +); +export const MARKET_VHU = market(VVHU, "Venus vhU", "vvhU", VHU, "vhU", VHU_ORACLE, parseUnits("0.75", 18)); + +export const MARKETS: MarketSpec[] = [MARKET_VHUSDT, MARKET_VHUSDC, MARKET_VHU]; + +export const convertAmountToVTokens = (amount: BigNumber, exchangeRate: BigNumber) => { + const EXP_SCALE = parseUnits("1", 18); + return amount.mul(EXP_SCALE).div(exchangeRate); +}; + +export const vTokensMinted = (m: MarketSpec) => convertAmountToVTokens(m.initialSupply.amount, m.vToken.exchangeRate); + +export const vTokensRemaining = (m: MarketSpec) => vTokensMinted(m).sub(m.initialSupply.vTokensToBurn); + +export const vip664 = () => { + const meta = { + version: "v2", + title: "VIP-664 [BNB Chain Testnet] List vhUSDT, vhUSDC and vhU markets in the Venus Core Pool", + description: `#### Summary + +If passed, this VIP will list three new non-borrowable collateral markets — Venus vhUSDT (vvhUSDT), Venus vhUSDC (vvhUSDC) and Venus vhU (vvhU), backed by Venus Hub receipt tokens (24-decimal ERC4626) — in the Venus Core Pool on BNB Chain testnet, with borrowing paused at launch.`, + forDescription: "I agree that Venus Protocol should proceed with this proposal", + againstDescription: "I do not think that Venus Protocol should proceed with this proposal", + abstainDescription: "I am indifferent to whether Venus Protocol proceeds or not", + }; + + return makeProposal( + MARKETS.flatMap(m => [ + // Arm the growth cap before the price source goes live. setSnapshot must precede + // setGrowthRate: updateSnapshot() on a zero snapshot collapses the cap to snapshotGap alone. + { + target: m.oracle.address, + signature: "setSnapshot(uint256,uint256)", + params: [seededSnapshot(m.oracle.seedExchangeRate), CAPO_SEED_TIMESTAMP], + }, + { + target: m.oracle.address, + signature: "setGrowthRate(uint256,uint256)", + params: [CAPO_GROWTH_RATE_PER_YEAR, CAPO_SNAPSHOT_INTERVAL], + }, + { + target: m.oracle.address, + signature: "setSnapshotGap(uint256)", + params: [snapshotGap(m.oracle.seedExchangeRate)], + }, + + { + target: RESILIENT_ORACLE, + signature: "setTokenConfig((address,address[3],bool[3],bool))", + params: [ + [ + m.vToken.underlying.address, + [m.oracle.address, ethers.constants.AddressZero, ethers.constants.AddressZero], + [true, false, false], + false, + ], + ], + }, + + { + target: m.vToken.comptroller, + signature: "_supportMarket(address)", + params: [m.vToken.address], + }, + { + target: m.vToken.comptroller, + signature: "_setMarketSupplyCaps(address[],uint256[])", + params: [[m.vToken.address], [m.riskParameters.supplyCap]], + }, + { + target: m.vToken.comptroller, + signature: "setActionsPaused(address[],uint8[],bool)", + params: [[m.vToken.address], [BORROW_ACTION], true], + }, + { + target: m.vToken.address, + signature: "setAccessControlManager(address)", + params: [bsctestnet.ACCESS_CONTROL_MANAGER], + }, + { + target: m.vToken.address, + signature: "setProtocolShareReserve(address)", + params: [PROTOCOL_SHARE_RESERVE], + }, + { + target: m.vToken.address, + signature: "setReduceReservesBlockDelta(uint256)", + params: [REDUCE_RESERVES_BLOCK_DELTA], + }, + { + target: m.vToken.address, + signature: "_setReserveFactor(uint256)", + params: [m.riskParameters.reserveFactor], + }, + { + target: m.vToken.comptroller, + signature: "setCollateralFactor(address,uint256,uint256)", + params: [m.vToken.address, m.riskParameters.collateralFactor, m.riskParameters.liquidationThreshold], + }, + { + target: m.vToken.comptroller, + signature: "setLiquidationIncentive(address,uint256)", + params: [m.vToken.address, m.riskParameters.liquidationIncentive], + }, + + { + target: bsctestnet.VTREASURY, + signature: "withdrawTreasuryBEP20(address,uint256,address)", + params: [m.vToken.underlying.address, m.initialSupply.amount, bsctestnet.NORMAL_TIMELOCK], + }, + { + target: m.vToken.underlying.address, + signature: "approve(address,uint256)", + params: [m.vToken.address, m.initialSupply.amount], + }, + { + target: m.vToken.address, + signature: "mint(uint256)", + params: [m.initialSupply.amount], + }, + { + target: m.vToken.underlying.address, + signature: "approve(address,uint256)", + params: [m.vToken.address, 0], + }, + { + target: m.vToken.address, + signature: "transfer(address,uint256)", + params: [ethers.constants.AddressZero, m.initialSupply.vTokensToBurn], + }, + { + target: m.vToken.address, + signature: "transfer(address,uint256)", + params: [m.initialSupply.vTokenReceiver, vTokensRemaining(m)], + }, + + { + target: DEVIATION_BOUNDED_ORACLE, + signature: "setTokenConfig((address,uint64,uint256,uint256,bool,bool))", + params: [ + [m.vToken.underlying.address, DBO_COOLDOWN_PERIOD, DBO_TRIGGER_THRESHOLD, DBO_RESET_THRESHOLD, true, false], + ], + }, + ]), + meta, + ProposalType.REGULAR, + ); +}; + +export default vip664; From cf27bedfc9329b7b91029205c3aaec73c41f74e9 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 19 Aug 2026 17:17:59 +0530 Subject: [PATCH 08/23] test(vip-664): assert the E-brake seeded bounds DeviationBoundedOracle.setTokenConfig seeds minPrice and maxPrice from RESILIENT_ORACLE.getPrice(asset), so it reverts unless the capped oracle is registered first. The command was already last in each market block but nothing recorded why, and nothing checked the seeding. --- simulations/vip-664/bscmainnet.ts | 10 ++++++++++ simulations/vip-664/bsctestnet.ts | 10 ++++++++++ vips/vip-664/bscmainnet.ts | 5 ++++- vips/vip-664/bsctestnet.ts | 4 ++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-664/bscmainnet.ts index 64c0060c7..7b7b60472 100644 --- a/simulations/vip-664/bscmainnet.ts +++ b/simulations/vip-664/bscmainnet.ts @@ -195,6 +195,16 @@ forking(FORK_BLOCK, async () => { expect(cfg.cachingEnabled).to.equal(false); }); + it("seeds the E-brake bounds from the live price", async () => { + // setTokenConfig seeds both bounds from the resilient price, so no bounds command is + // needed — but it also means it reverts if the oracle is not registered first. + const cfg = await dbo.assetProtectionConfig(m.vToken.underlying.address); + const price = await resilientOracle.getPrice(m.vToken.underlying.address); + expect(cfg.minPrice).to.equal(price); + expect(cfg.maxPrice).to.equal(price); + expect(cfg.currentlyUsingProtectedPrice).to.equal(false); + }); + it("market has correct owner", async () => { expect(await vToken.admin()).to.equal(bscmainnet.NORMAL_TIMELOCK); }); diff --git a/simulations/vip-664/bsctestnet.ts b/simulations/vip-664/bsctestnet.ts index e83283241..e3fd51c81 100644 --- a/simulations/vip-664/bsctestnet.ts +++ b/simulations/vip-664/bsctestnet.ts @@ -190,6 +190,16 @@ forking(FORK_BLOCK, async () => { expect(cfg.cachingEnabled).to.equal(false); }); + it("seeds the E-brake bounds from the live price", async () => { + // setTokenConfig seeds both bounds from the resilient price, so no bounds command is + // needed — but it also means it reverts if the oracle is not registered first. + const cfg = await dbo.assetProtectionConfig(m.vToken.underlying.address); + const price = await resilientOracle.getPrice(m.vToken.underlying.address); + expect(cfg.minPrice).to.equal(price); + expect(cfg.maxPrice).to.equal(price); + expect(cfg.currentlyUsingProtectedPrice).to.equal(false); + }); + it("market has correct owner", async () => { expect(await vToken.admin()).to.equal(bsctestnet.NORMAL_TIMELOCK); }); diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index 61f1b1b50..0955b6329 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -455,7 +455,10 @@ The bootstrap liquidity is withdrawn from the VTreasury, which currently holds * params: [m.initialSupply.vTokenReceiver, vTokensRemaining(m)], }, - // Enable Oracle Dynamic Protection Mode / "E-brake" (DBO) for the vhToken with a 5% deviation trigger. + // Enable Oracle Dynamic Protection Mode / "E-brake" (DBO) for the vhToken with a 5% deviation + // trigger. Must stay last: setTokenConfig seeds minPrice and maxPrice from + // RESILIENT_ORACLE.getPrice(asset), so it reverts unless the capped oracle is already + // registered above. The seeding is why no separate bounds command is needed. { target: DEVIATION_BOUNDED_ORACLE, signature: "setTokenConfig((address,uint64,uint256,uint256,bool,bool))", diff --git a/vips/vip-664/bsctestnet.ts b/vips/vip-664/bsctestnet.ts index 20ddf93e3..915b1b852 100644 --- a/vips/vip-664/bsctestnet.ts +++ b/vips/vip-664/bsctestnet.ts @@ -295,6 +295,10 @@ If passed, this VIP will list three new non-borrowable collateral markets — Ve params: [m.initialSupply.vTokenReceiver, vTokensRemaining(m)], }, + // Enable Oracle Dynamic Protection Mode / "E-brake" (DBO) for the vhToken with a 5% deviation + // trigger. Must stay last: setTokenConfig seeds minPrice and maxPrice from + // RESILIENT_ORACLE.getPrice(asset), so it reverts unless the capped oracle is already + // registered above. The seeding is why no separate bounds command is needed. { target: DEVIATION_BOUNDED_ORACLE, signature: "setTokenConfig((address,uint64,uint256,uint256,bool,bool))", From 8c8463810b3c44d8fbe2d12a5496bc2962f3b054 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Wed, 19 Aug 2026 17:34:44 +0530 Subject: [PATCH 09/23] feat(vip-664): set the borrow cap and IRM explicitly Diffing the command list against every recent core-pool listing turned up two commands other listings issue that this one relied on defaults for. _setMarketBorrowCaps: a fresh market already defaults to 0, but the borrow cap is a stated risk parameter and the simulation was asserting a default rather than a decision. VIP-554 and VIP-581 set it. _setInterestRateModel: the model was trusted from the vToken constructor. Since this reuses an already deployed model rather than deploying one per market, the VIP now sets it so it does not depend on what the deploy passes. VIP-581 does the same. Also corrects the protocol seize share note: the legacy Core vToken exposes no protocolSeizeShare getter or setter at all, so there is nothing to configure. --- simulations/vip-664/bscmainnet.ts | 4 +++- simulations/vip-664/bsctestnet.ts | 4 +++- vips/vip-664/bscmainnet.ts | 18 ++++++++++++++++-- vips/vip-664/bsctestnet.ts | 14 ++++++++++++++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-664/bscmainnet.ts index 7b7b60472..c32775359 100644 --- a/simulations/vip-664/bscmainnet.ts +++ b/simulations/vip-664/bscmainnet.ts @@ -92,6 +92,8 @@ forking(FORK_BLOCK, async () => { "SnapshotGapUpdated", "MarketListed", "NewSupplyCap", + "NewBorrowCap", + "NewMarketInterestRateModel", "ActionPausedMarket", "NewAccessControlManager", "NewProtocolShareReserve", @@ -101,7 +103,7 @@ forking(FORK_BLOCK, async () => { "NewLiquidationThreshold", "NewLiquidationIncentive", ], - [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], ); }, }); diff --git a/simulations/vip-664/bsctestnet.ts b/simulations/vip-664/bsctestnet.ts index e3fd51c81..5b6309fe2 100644 --- a/simulations/vip-664/bsctestnet.ts +++ b/simulations/vip-664/bsctestnet.ts @@ -88,6 +88,8 @@ forking(FORK_BLOCK, async () => { "SnapshotGapUpdated", "MarketListed", "NewSupplyCap", + "NewBorrowCap", + "NewMarketInterestRateModel", "ActionPausedMarket", "NewAccessControlManager", "NewProtocolShareReserve", @@ -97,7 +99,7 @@ forking(FORK_BLOCK, async () => { "NewLiquidationThreshold", "NewLiquidationIncentive", ], - [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], ); }, }); diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index 0955b6329..9fd36def2 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -257,7 +257,7 @@ For each new market this VIP will: - Arm the growth cap on the vhToken's capped **ERC4626Oracle** (seed the snapshot, set the growth rate and set the snapshot gap) - Register that oracle in the ResilientOracle as the single price source. It prices the vhToken as *underlying resilient price × capped vault exchange rate*, the same design as the live asBNB and slisBNB capped oracles. - Add the market to the Core Pool Comptroller -- Set the supply cap, collateral factor, liquidation threshold, liquidation incentive and reserve factor +- Set the supply cap, borrow cap (0), interest rate model, collateral factor, liquidation threshold, liquidation incentive and reserve factor - Set the AccessControlManager, ProtocolShareReserve and reduce-reserves block delta on the vToken - Provide bootstrap liquidity (minting an initial supply, burning 10% and sending the remainder to the VTreasury) - Pause borrowing for the market at launch (the markets are collateral-only) @@ -301,7 +301,7 @@ All three markets share the same interest rate model (base 0%, multiplier 9%, ju - **The three collateral factors differ (82.5% vhUSDC, 80% vhUSDT, 75% vhU).** These are the approved per-asset values set by the risk manager in the listing template — not a single blanket figure — and are ordered by the relative maturity and market depth of each underlying peg (USDC > USDT > USD1/U). vhU/USD1, the newest and least liquid of the three, carries the most conservative factor. - **Supply cap is denominated in the underlying token amount, not USD.** \`_setMarketSupplyCaps\` takes an amount of the underlying, so each cap of 10,000,000 is 10,000,000 vhTokens (24 decimals). At the current ~$1 vault price this corresponds to roughly $10M of collateral exposure per market. - **Reserve factor (10%), vTokenReceiver (VTreasury) and bootstrap amount (100 vhToken per market)** were not specified in the listing template and follow the standard Core-pool listing convention. The reserve factor is inert while borrowing is paused. -- **Protocol seize share** is a global Comptroller-level parameter on the Core pool rather than a per-market setting, so it is not modified by this VIP; the existing Core-pool value applies to the new markets. +- **Protocol seize share is not settable on the Core pool.** The legacy Core vToken exposes no \`protocolSeizeShare\` getter or setter at all — the share is a constant in the implementation — which is why no Core-pool listing VIP sets it. The listing checklist asks for the value; there is nothing to configure. #### Capped oracle @@ -384,6 +384,13 @@ The bootstrap liquidity is withdrawn from the VTreasury, which currently holds * signature: "_setMarketSupplyCaps(address[],uint256[])", params: [[m.vToken.address], [m.riskParameters.supplyCap]], }, + // Explicit, though a fresh market already defaults to 0: the borrow cap is a stated risk + // parameter, and relying on a default is what hid the unarmed price cap. + { + target: m.vToken.comptroller, + signature: "_setMarketBorrowCaps(address[],uint256[])", + params: [[m.vToken.address], [m.riskParameters.borrowCap]], + }, // Pause borrowing for the market at launch (collateral-only markets). { target: m.vToken.comptroller, @@ -405,6 +412,13 @@ The bootstrap liquidity is withdrawn from the VTreasury, which currently holds * signature: "setReduceReservesBlockDelta(uint256)", params: [REDUCE_RESERVES_BLOCK_DELTA], }, + // Set here rather than trusted from the vToken constructor, since this reuses an already + // deployed model instead of deploying one per market. + { + target: m.vToken.address, + signature: "_setInterestRateModel(address)", + params: [m.rateModel], + }, { target: m.vToken.address, signature: "_setReserveFactor(uint256)", diff --git a/vips/vip-664/bsctestnet.ts b/vips/vip-664/bsctestnet.ts index 915b1b852..1e1a62f38 100644 --- a/vips/vip-664/bsctestnet.ts +++ b/vips/vip-664/bsctestnet.ts @@ -228,6 +228,13 @@ If passed, this VIP will list three new non-borrowable collateral markets — Ve signature: "_setMarketSupplyCaps(address[],uint256[])", params: [[m.vToken.address], [m.riskParameters.supplyCap]], }, + // Explicit, though a fresh market already defaults to 0: the borrow cap is a stated risk + // parameter, and relying on a default is what hid the unarmed price cap. + { + target: m.vToken.comptroller, + signature: "_setMarketBorrowCaps(address[],uint256[])", + params: [[m.vToken.address], [m.riskParameters.borrowCap]], + }, { target: m.vToken.comptroller, signature: "setActionsPaused(address[],uint8[],bool)", @@ -248,6 +255,13 @@ If passed, this VIP will list three new non-borrowable collateral markets — Ve signature: "setReduceReservesBlockDelta(uint256)", params: [REDUCE_RESERVES_BLOCK_DELTA], }, + // Set here rather than trusted from the vToken constructor, since this reuses an already + // deployed model instead of deploying one per market. + { + target: m.vToken.address, + signature: "_setInterestRateModel(address)", + params: [m.rateModel], + }, { target: m.vToken.address, signature: "_setReserveFactor(uint256)", From 13aa1d4e1c2847e8c477461ad1bacba73ed21641 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 21 Aug 2026 13:00:40 +0530 Subject: [PATCH 10/23] feat(vip-664): list vSHARE only on bsctestnet Hub_USDT is the only Venus Hub vault deployed on testnet, so there is no vhUSDC or vhU to list. Its share token is vSHARE with 12 decimals, not mainnet's 24, because testnet USDT is 6 decimals and the Hub adds a 6 decimal offset. Every amount is redenominated accordingly. --- simulations/vip-664/bsctestnet.ts | 18 ++++++-- vips/vip-664/bsctestnet.ts | 77 +++++++++++++++---------------- 2 files changed, 50 insertions(+), 45 deletions(-) diff --git a/simulations/vip-664/bsctestnet.ts b/simulations/vip-664/bsctestnet.ts index 5b6309fe2..e54255c8e 100644 --- a/simulations/vip-664/bsctestnet.ts +++ b/simulations/vip-664/bsctestnet.ts @@ -38,10 +38,11 @@ import VTOKEN_ABI from "./abi/VToken.json"; const { bsctestnet } = NETWORK_ADDRESSES; const SECONDS_PER_YEAR = 31536000; -const ONE_SHARE = parseUnits("1", 24); +// Hub_USDT share decimals: testnet USDT is 6 decimals and the Hub adds a 6 decimal offset. +const ONE_SHARE = parseUnits("1", 12); -// TODO(deploy): bump to a block after the mock vaults, oracles and vTokens are deployed on testnet -// and the VTreasury has been funded with the bootstrap vhTokens. +// TODO(deploy): bump to a block after the capped oracle and the vToken are deployed on testnet and +// the VTreasury has been funded with the bootstrap vSHARE. const FORK_BLOCK = 125980273; forking(FORK_BLOCK, async () => { @@ -70,6 +71,15 @@ forking(FORK_BLOCK, async () => { await expect(resilientOracle.getPrice(m.vToken.underlying.address)).to.be.reverted; }); + it("seeds the capped oracle with a timestamp that is already in the past", async () => { + // setSnapshot only rejects a future timestamp when snapshotInterval is already non-zero, and + // the VIP calls setSnapshot before setGrowthRate, so the oracle's own guard is bypassed. A + // future timestamp would underflow getMaxAllowedExchangeRate and break pricing, so assert it + // here instead. + const { timestamp } = await ethers.provider.getBlock("latest"); + expect(CAPO_SEED_TIMESTAMP).to.be.lte(timestamp); + }); + it(`VTreasury holds enough ${m.vToken.underlying.symbol} for the bootstrap`, async () => { const underlying = new ethers.Contract(m.vToken.underlying.address, ERC20_ABI, ethers.provider); expect(await underlying.balanceOf(bsctestnet.VTREASURY)).to.be.gte(m.initialSupply.amount); @@ -99,7 +109,7 @@ forking(FORK_BLOCK, async () => { "NewLiquidationThreshold", "NewLiquidationIncentive", ], - [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], ); }, }); diff --git a/vips/vip-664/bsctestnet.ts b/vips/vip-664/bsctestnet.ts index 1e1a62f38..83a7c30a1 100644 --- a/vips/vip-664/bsctestnet.ts +++ b/vips/vip-664/bsctestnet.ts @@ -7,23 +7,24 @@ import { makeProposal } from "src/utils"; // Testnet mirror of vips/vip-664/bscmainnet.ts. It runs the same capped ERC4626 oracle path rather // than a mocked direct price, so the growth cap is exercised before mainnet. +// +// Mainnet lists three markets. Testnet lists one: Hub_USDT is the only Venus Hub vault deployed on +// bsctestnet (venus-liquidity-hub deploy/config/bsctestnet.json has assetCount: 1), there is no Hub +// for USDC or U, and no U token exists on testnet at all. Its share token is named "Vault Share" / +// vSHARE and has 12 decimals rather than mainnet's 24, because testnet USDT is 6 decimals and the +// Hub adds a 6 decimal offset. Every amount below is therefore denominated in 12 decimals. const { bsctestnet } = NETWORK_ADDRESSES; -// TODO(deploy): mock 24-decimal ERC4626 vaults, deployed alongside the oracles on bsctestnet. -export const VHUSDT = constants.AddressZero; -export const VHUSDC = constants.AddressZero; -export const VHU = constants.AddressZero; +// Hub_USDT, the testnet stand-in for mainnet's vhUSDT. Live and funded, 12 decimals. +export const VSHARE = "0x7cE6ADF754D0eC81A6CF8ACd9C7454F45077dc61"; -// TODO(deploy): fill after the capped ERC4626Oracle instances are deployed on bsctestnet. -export const VHUSDT_ORACLE = constants.AddressZero; -export const VHUSDC_ORACLE = constants.AddressZero; -export const VHU_ORACLE = constants.AddressZero; +// TODO(deploy): fill after the capped ERC4626Oracle is deployed on bsctestnet +// (VenusProtocol/oracle, tag vh-erc4626-oracles, deployment name vSHARE_ERC4626Oracle). +export const VSHARE_ORACLE = constants.AddressZero; -// TODO(deploy): fill after the vTokens are deployed on bsctestnet. -export const VVHUSDT = constants.AddressZero; -export const VVHUSDC = constants.AddressZero; -export const VVHU = constants.AddressZero; +// TODO(deploy): fill after the vToken is deployed on bsctestnet. +export const VVSHARE = constants.AddressZero; export const PROTOCOL_SHARE_RESERVE = "0x25c7c7D6Bf710949fD7f03364E9BA19a1b3c10E3"; export const REDUCE_RESERVES_BLOCK_DELTA = "28800"; @@ -34,13 +35,15 @@ export const { RESILIENT_ORACLE } = bsctestnet; // Already deployed; backs vasBNB and vslisBNB on testnet with these exact params. export const JUMP_RATE_MODEL = "0x8734dBD8Ba959BbC75f2701a022f8d1D47e0722d"; -// Same capped-oracle configuration as mainnet. A freshly deployed mock vault sits at an exchange -// rate of exactly 1, so the seed is 1 plus the gap. +// Same capped-oracle configuration as mainnet. Hub_USDT currently sits at an exchange rate of +// exactly 1 (convertToAssets(1e12) returns 1e6), so the seed is 1 plus the gap. export const CAPO_GROWTH_RATE_PER_YEAR = parseUnits("0.05", 18); export const CAPO_SNAPSHOT_INTERVAL = 30 * 24 * 60 * 60; export const CAPO_SNAPSHOT_GAP_BPS = BigNumber.from(41); export const CAPO_SEED_TIMESTAMP = 1787137060; -export const SEED_EXCHANGE_RATE = parseUnits("1", 18); +// Denominated in the asset's decimals, not the vault's: the oracle compares this against +// convertToAssets(...), which returns testnet USDT, a 6 decimal token. +export const SEED_EXCHANGE_RATE = parseUnits("1", 6); export const snapshotGap = (exchangeRate: BigNumber) => exchangeRate.mul(CAPO_SNAPSHOT_GAP_BPS).div(10000); export const seededSnapshot = (exchangeRate: BigNumber) => exchangeRate.add(snapshotGap(exchangeRate)); @@ -89,12 +92,12 @@ export type MarketSpec = { }; }; -// exchangeRate scale = 18 + underlyingDecimals(24) - vTokenDecimals(8) = 34. -const EXCHANGE_RATE = parseUnits("1", 34); -const SUPPLY_CAP = parseUnits("10000000", 24); +// exchangeRate scale = 18 + underlyingDecimals(12) - vTokenDecimals(8) = 22. +const EXCHANGE_RATE = parseUnits("1", 22); +const SUPPLY_CAP = parseUnits("10000000", 12); const LIQUIDATION_INCENTIVE = parseUnits("1.1", 18); const RESERVE_FACTOR = parseUnits("0.1", 18); -const BOOTSTRAP_AMOUNT = parseUnits("100", 24); +const BOOTSTRAP_AMOUNT = parseUnits("100", 12); const BOOTSTRAP_BURN = parseUnits("10", 8); const IRM = { @@ -118,7 +121,7 @@ const market = ( address: vToken, name, symbol, - underlying: { address: underlying, symbol: underlyingSymbol, decimals: 24 }, + underlying: { address: underlying, symbol: underlyingSymbol, decimals: 12 }, decimals: 8, exchangeRate: EXCHANGE_RATE, comptroller: bsctestnet.UNITROLLER, @@ -133,7 +136,7 @@ const market = ( liquidationIncentive: LIQUIDATION_INCENTIVE, reserveFactor: RESERVE_FACTOR, supplyCap: SUPPLY_CAP, - borrowCap: parseUnits("0", 24), + borrowCap: parseUnits("0", 12), }, initialSupply: { amount: BOOTSTRAP_AMOUNT, @@ -142,27 +145,19 @@ const market = ( }, }); -export const MARKET_VHUSDT = market( - VVHUSDT, - "Venus vhUSDT", - "vvhUSDT", - VHUSDT, - "vhUSDT", - VHUSDT_ORACLE, +// underlyingSymbol must be vSHARE: checkVToken reads symbol() off the underlying contract and the +// Hub vault reports "vSHARE". The collateral factor mirrors mainnet's vhUSDT market. +export const MARKET_VSHARE = market( + VVSHARE, + "Venus vSHARE", + "vvSHARE", + VSHARE, + "vSHARE", + VSHARE_ORACLE, parseUnits("0.8", 18), ); -export const MARKET_VHUSDC = market( - VVHUSDC, - "Venus vhUSDC", - "vvhUSDC", - VHUSDC, - "vhUSDC", - VHUSDC_ORACLE, - parseUnits("0.825", 18), -); -export const MARKET_VHU = market(VVHU, "Venus vhU", "vvhU", VHU, "vhU", VHU_ORACLE, parseUnits("0.75", 18)); -export const MARKETS: MarketSpec[] = [MARKET_VHUSDT, MARKET_VHUSDC, MARKET_VHU]; +export const MARKETS: MarketSpec[] = [MARKET_VSHARE]; export const convertAmountToVTokens = (amount: BigNumber, exchangeRate: BigNumber) => { const EXP_SCALE = parseUnits("1", 18); @@ -176,10 +171,10 @@ export const vTokensRemaining = (m: MarketSpec) => vTokensMinted(m).sub(m.initia export const vip664 = () => { const meta = { version: "v2", - title: "VIP-664 [BNB Chain Testnet] List vhUSDT, vhUSDC and vhU markets in the Venus Core Pool", + title: "VIP-664 [BNB Chain Testnet] List the vSHARE market in the Venus Core Pool", description: `#### Summary -If passed, this VIP will list three new non-borrowable collateral markets — Venus vhUSDT (vvhUSDT), Venus vhUSDC (vvhUSDC) and Venus vhU (vvhU), backed by Venus Hub receipt tokens (24-decimal ERC4626) — in the Venus Core Pool on BNB Chain testnet, with borrowing paused at launch.`, +If passed, this VIP will list one new non-borrowable collateral market — Venus vSHARE (vvSHARE), backed by the Venus Hub USDT receipt token (12-decimal ERC4626) — in the Venus Core Pool on BNB Chain testnet, with borrowing paused at launch. Mainnet lists three markets; testnet lists one because Hub_USDT is the only Venus Hub vault deployed there.`, forDescription: "I agree that Venus Protocol should proceed with this proposal", againstDescription: "I do not think that Venus Protocol should proceed with this proposal", abstainDescription: "I am indifferent to whether Venus Protocol proceeds or not", From 4983df3cdbbab9bf4467ecede382b9b244dd7444 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 21 Aug 2026 13:02:53 +0530 Subject: [PATCH 11/23] feat(vip-664): mint the bootstrap shares from the Hub vault The VTreasury holds USDT but no vSHARE, so withdrawing the share token reverted. Pull USDT instead and mint the shares from Hub_USDT, which removes the pre-funding step entirely. Uses mint(shares, receiver) rather than deposit(assets, receiver) so the share count is pinned and the hardcoded amounts cannot drift with the vault rate. --- simulations/vip-664/bsctestnet.ts | 44 ++++++++++++++++++++++--------- vips/vip-664/bsctestnet.ts | 32 +++++++++++++++++++++- 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/simulations/vip-664/bsctestnet.ts b/simulations/vip-664/bsctestnet.ts index e54255c8e..2956265d3 100644 --- a/simulations/vip-664/bsctestnet.ts +++ b/simulations/vip-664/bsctestnet.ts @@ -21,6 +21,7 @@ import { MARKETS, PROTOCOL_SHARE_RESERVE, REDUCE_RESERVES_BLOCK_DELTA, + USDT, convertAmountToVTokens, seededSnapshot, snapshotGap, @@ -41,8 +42,8 @@ const SECONDS_PER_YEAR = 31536000; // Hub_USDT share decimals: testnet USDT is 6 decimals and the Hub adds a 6 decimal offset. const ONE_SHARE = parseUnits("1", 12); -// TODO(deploy): bump to a block after the capped oracle and the vToken are deployed on testnet and -// the VTreasury has been funded with the bootstrap vSHARE. +// TODO(deploy): bump to a block after the capped oracle and the vToken are deployed on testnet. +// The VTreasury needs no vSHARE: the VIP mints the shares itself out of the Treasury's USDT. const FORK_BLOCK = 125980273; forking(FORK_BLOCK, async () => { @@ -50,13 +51,13 @@ forking(FORK_BLOCK, async () => { const resilientOracle = new ethers.Contract(bsctestnet.RESILIENT_ORACLE, RESILIENT_ORACLE_ABI, ethers.provider); const dbo = new ethers.Contract(DEVIATION_BOUNDED_ORACLE, DBO_ABI, ethers.provider); - // The bootstrap must draw from the Treasury's real balance, so snapshot it before executing. - const treasuryBalanceBefore: Record = {}; + // The bootstrap draws USDT, not vSHARE, from the Treasury, so snapshot the asset balance. + const treasuryAssetBalanceBefore: Record = {}; before(async () => { + const asset = new ethers.Contract(USDT, ERC20_ABI, ethers.provider); for (const m of MARKETS) { - const underlying = new ethers.Contract(m.vToken.underlying.address, ERC20_ABI, ethers.provider); - treasuryBalanceBefore[m.vToken.address] = await underlying.balanceOf(bsctestnet.VTREASURY); + treasuryAssetBalanceBefore[m.vToken.address] = await asset.balanceOf(bsctestnet.VTREASURY); } }); @@ -80,9 +81,15 @@ forking(FORK_BLOCK, async () => { expect(CAPO_SEED_TIMESTAMP).to.be.lte(timestamp); }); - it(`VTreasury holds enough ${m.vToken.underlying.symbol} for the bootstrap`, async () => { - const underlying = new ethers.Contract(m.vToken.underlying.address, ERC20_ABI, ethers.provider); - expect(await underlying.balanceOf(bsctestnet.VTREASURY)).to.be.gte(m.initialSupply.amount); + it("VTreasury holds enough USDT for the bootstrap", async () => { + const asset = new ethers.Contract(USDT, ERC20_ABI, ethers.provider); + expect(await asset.balanceOf(bsctestnet.VTREASURY)).to.be.gte(m.initialSupply.assetAmount); + }); + + it(`the vault still charges ${m.initialSupply.assetAmount} USDT for the bootstrap shares`, async () => { + // The VIP hardcodes the asset amount, so a vault rate above 1 would leave the approve short. + const vault = new ethers.Contract(m.vToken.underlying.address, ERC4626_ABI, ethers.provider); + expect(await vault.previewMint(m.initialSupply.amount)).to.be.lte(m.initialSupply.assetAmount); }); } }); @@ -238,9 +245,22 @@ forking(FORK_BLOCK, async () => { expect(await underlying.balanceOf(m.vToken.address)).to.equal(m.initialSupply.amount); }); - it("bootstrap drew the initial supply from the VTreasury's real balance", async () => { - const balanceAfter = await underlying.balanceOf(bsctestnet.VTREASURY); - expect(treasuryBalanceBefore[m.vToken.address].sub(balanceAfter)).to.equal(m.initialSupply.amount); + it("bootstrap drew USDT from the VTreasury's real balance", async () => { + const asset = new ethers.Contract(USDT, ERC20_ABI, ethers.provider); + const balanceAfter = await asset.balanceOf(bsctestnet.VTREASURY); + expect(treasuryAssetBalanceBefore[m.vToken.address].sub(balanceAfter)).to.equal(m.initialSupply.assetAmount); + }); + + it("minted the bootstrap shares out of that USDT", async () => { + // The timelock is a pass-through: it must keep neither the asset nor the shares. + const asset = new ethers.Contract(USDT, ERC20_ABI, ethers.provider); + expect(await asset.balanceOf(bsctestnet.NORMAL_TIMELOCK)).to.equal(0); + expect(await underlying.balanceOf(bsctestnet.NORMAL_TIMELOCK)).to.equal(0); + }); + + it("should leave no USDT approval to the vault", async () => { + const asset = new ethers.Contract(USDT, ERC20_ABI, ethers.provider); + expect(await asset.allowance(bsctestnet.NORMAL_TIMELOCK, m.vToken.underlying.address)).to.equal(0); }); it("should not leave any vTokens in the timelock", async () => { diff --git a/vips/vip-664/bsctestnet.ts b/vips/vip-664/bsctestnet.ts index 83a7c30a1..f6a49313e 100644 --- a/vips/vip-664/bsctestnet.ts +++ b/vips/vip-664/bsctestnet.ts @@ -19,6 +19,10 @@ const { bsctestnet } = NETWORK_ADDRESSES; // Hub_USDT, the testnet stand-in for mainnet's vhUSDT. Live and funded, 12 decimals. export const VSHARE = "0x7cE6ADF754D0eC81A6CF8ACd9C7454F45077dc61"; +// The vault's ERC4626 asset. The VTreasury holds this, not vSHARE, so the bootstrap withdraws USDT +// and deposits it into the vault rather than withdrawing the share token directly. +export const USDT = "0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c"; + // TODO(deploy): fill after the capped ERC4626Oracle is deployed on bsctestnet // (VenusProtocol/oracle, tag vh-erc4626-oracles, deployment name vSHARE_ERC4626Oracle). export const VSHARE_ORACLE = constants.AddressZero; @@ -87,6 +91,7 @@ export type MarketSpec = { }; initialSupply: { amount: BigNumber; + assetAmount: BigNumber; vTokenReceiver: string; vTokensToBurn: BigNumber; }; @@ -98,6 +103,11 @@ const SUPPLY_CAP = parseUnits("10000000", 12); const LIQUIDATION_INCENTIVE = parseUnits("1.1", 18); const RESERVE_FACTOR = parseUnits("0.1", 18); const BOOTSTRAP_AMOUNT = parseUnits("100", 12); +// USDT the vault charges for BOOTSTRAP_AMOUNT shares. previewMint(100e12) returns exactly 100e6 on +// testnet, where the vault sits at a 1:1 rate and has accrued no yield. Using the vault's +// mint(shares, receiver) rather than deposit(assets, receiver) pins the share count, so the +// approve and mint that follow can never miss by a rounding step. +const BOOTSTRAP_ASSET_AMOUNT = parseUnits("100", 6); const BOOTSTRAP_BURN = parseUnits("10", 8); const IRM = { @@ -140,6 +150,7 @@ const market = ( }, initialSupply: { amount: BOOTSTRAP_AMOUNT, + assetAmount: BOOTSTRAP_ASSET_AMOUNT, vTokenReceiver: bsctestnet.VTREASURY, vTokensToBurn: BOOTSTRAP_BURN, }, @@ -273,10 +284,29 @@ If passed, this VIP will list one new non-borrowable collateral market — Venus params: [m.vToken.address, m.riskParameters.liquidationIncentive], }, + // Bootstrap liquidity. The VTreasury holds USDT but no vSHARE, so the shares are minted here + // rather than withdrawn: pull USDT, mint exactly BOOTSTRAP_AMOUNT shares from the vault, then + // supply those shares to the new market. The timelock never hands the shares back to the + // VTreasury in between, since it would only have to withdraw them again. { target: bsctestnet.VTREASURY, signature: "withdrawTreasuryBEP20(address,uint256,address)", - params: [m.vToken.underlying.address, m.initialSupply.amount, bsctestnet.NORMAL_TIMELOCK], + params: [USDT, m.initialSupply.assetAmount, bsctestnet.NORMAL_TIMELOCK], + }, + { + target: USDT, + signature: "approve(address,uint256)", + params: [m.vToken.underlying.address, m.initialSupply.assetAmount], + }, + { + target: m.vToken.underlying.address, + signature: "mint(uint256,address)", + params: [m.initialSupply.amount, bsctestnet.NORMAL_TIMELOCK], + }, + { + target: USDT, + signature: "approve(address,uint256)", + params: [m.vToken.underlying.address, 0], }, { target: m.vToken.underlying.address, From 03f622de726de59d4677190b2072cf6ce908dcb0 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Fri, 21 Aug 2026 13:04:09 +0530 Subject: [PATCH 12/23] chore(vip-664): fill the deployed bsctestnet addresses Both contracts are live and verified on BscScan testnet. The fork block moves past them, and the ChainlinkOracle stale period has to be relaxed because a pinned fork block goes stale against the feed's 24h window and every getPrice call reverts. --- simulations/vip-664/bsctestnet.ts | 20 ++++++++++++++++---- vips/vip-664/bsctestnet.ts | 14 ++++++++------ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/simulations/vip-664/bsctestnet.ts b/simulations/vip-664/bsctestnet.ts index 2956265d3..daf4bc316 100644 --- a/simulations/vip-664/bsctestnet.ts +++ b/simulations/vip-664/bsctestnet.ts @@ -3,7 +3,7 @@ import { BigNumber } from "ethers"; import { parseUnits } from "ethers/lib/utils"; import { ethers } from "hardhat"; import { NETWORK_ADDRESSES } from "src/networkAddresses"; -import { expectEvents } from "src/utils"; +import { expectEvents, setMaxStalePeriodInChainlinkOracle } from "src/utils"; import { forking, testVip } from "src/vip-framework"; import { checkRiskParameters } from "src/vip-framework/checks/checkRiskParameters"; import { checkVToken } from "src/vip-framework/checks/checkVToken"; @@ -39,12 +39,17 @@ import VTOKEN_ABI from "./abi/VToken.json"; const { bsctestnet } = NETWORK_ADDRESSES; const SECONDS_PER_YEAR = 31536000; + +// USDT is priced by the testnet ChainlinkOracle with a 24h stale period. The fork block is fixed, so +// the feed goes stale as soon as the block is older than that and every getPrice call reverts. +const CHAINLINK_ORACLE = "0xCeA29f1266e880A1482c06eD656cD08C148BaA32"; + // Hub_USDT share decimals: testnet USDT is 6 decimals and the Hub adds a 6 decimal offset. const ONE_SHARE = parseUnits("1", 12); -// TODO(deploy): bump to a block after the capped oracle and the vToken are deployed on testnet. -// The VTreasury needs no vSHARE: the VIP mints the shares itself out of the Treasury's USDT. -const FORK_BLOCK = 125980273; +// After the vToken (126330674) and the capped oracle (126330850) were deployed. The VTreasury needs +// no vSHARE: the VIP mints the shares itself out of the Treasury's USDT. +const FORK_BLOCK = 126331000; forking(FORK_BLOCK, async () => { const comptroller = new ethers.Contract(bsctestnet.UNITROLLER, COMPTROLLER_ABI, ethers.provider); @@ -55,6 +60,13 @@ forking(FORK_BLOCK, async () => { const treasuryAssetBalanceBefore: Record = {}; before(async () => { + await setMaxStalePeriodInChainlinkOracle( + CHAINLINK_ORACLE, + USDT, + ethers.constants.AddressZero, + bsctestnet.NORMAL_TIMELOCK, + ); + const asset = new ethers.Contract(USDT, ERC20_ABI, ethers.provider); for (const m of MARKETS) { treasuryAssetBalanceBefore[m.vToken.address] = await asset.balanceOf(bsctestnet.VTREASURY); diff --git a/vips/vip-664/bsctestnet.ts b/vips/vip-664/bsctestnet.ts index f6a49313e..08cd32771 100644 --- a/vips/vip-664/bsctestnet.ts +++ b/vips/vip-664/bsctestnet.ts @@ -1,4 +1,4 @@ -import { BigNumber, constants } from "ethers"; +import { BigNumber } from "ethers"; import { parseUnits } from "ethers/lib/utils"; import { ethers } from "hardhat"; import { NETWORK_ADDRESSES } from "src/networkAddresses"; @@ -23,12 +23,14 @@ export const VSHARE = "0x7cE6ADF754D0eC81A6CF8ACd9C7454F45077dc61"; // and deposits it into the vault rather than withdrawing the share token directly. export const USDT = "0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c"; -// TODO(deploy): fill after the capped ERC4626Oracle is deployed on bsctestnet -// (VenusProtocol/oracle, tag vh-erc4626-oracles, deployment name vSHARE_ERC4626Oracle). -export const VSHARE_ORACLE = constants.AddressZero; +// vSHARE_ERC4626Oracle, deployed at block 126330850 from VenusProtocol/oracle, tag +// vh-erc4626-oracles. Deployed with every cap argument zeroed; the commands below arm it. +export const VSHARE_ORACLE = "0xB5A17EB6A135A61057bc2F4f102c633899256BFa"; -// TODO(deploy): fill after the vToken is deployed on bsctestnet. -export const VVSHARE = constants.AddressZero; +// Deployed at block 126330674. VBep20Delegator over vSHARE, admin set to the NormalTimelock in the +// constructor, implementation 0x73fF75092Da265b87b25ffB943c47C90419a04A6 (the same VBep20Delegate +// that backs vasBNB and vslisBNB on testnet). +export const VVSHARE = "0xb846eEbaC8b014296709dc660Bfcb6ea182718e8"; export const PROTOCOL_SHARE_RESERVE = "0x25c7c7D6Bf710949fD7f03364E9BA19a1b3c10E3"; export const REDUCE_RESERVES_BLOCK_DELTA = "28800"; From 7be241c57c9ff4e06ed812cb84b737a8529b4614 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 24 Aug 2026 12:41:53 +0530 Subject: [PATCH 13/23] fix(vip-664): use the plain IRM, not the CheckpointView - 0x1Ef3b851 is a CheckpointView, not a JumpRateModel: a fallback proxy that forwards to the pre- or post-migration model by block.timestamp. The old comment described it as the model itself - The wrapper only exists so markets predating the blocks-per-year change keep continuous historical rates, which a market listed now does not have. It adds a permanent staticcall hop for nothing - vPT-clisBNB-25JUN2026 is the post-migration precedent and already runs the plain model at 0x6463ab80 - Rates are unchanged: both expose base 0 / 1284246575 / 28538812785 / kink 5e17 and return the same getBorrowRate - bsctestnet keeps the wrapper, since that VIP has already executed on chain with it and its market has borrowing paused --- vips/vip-664/bscmainnet.ts | 14 ++++++++++---- vips/vip-664/bsctestnet.ts | 8 +++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index 9fd36def2..7ddb8af2d 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -27,9 +27,15 @@ export const BORROW_ACTION = 2; // Comptroller Action enum: BORROW export const { RESILIENT_ORACLE } = bscmainnet; -// Already deployed; backs vasBNB and vslisBNB with these exact params (base 0, multiplier 9%, -// jump 200%, kink 50%), so no new interest rate model is deployed for these markets. -export const JUMP_RATE_MODEL = "0x1Ef3b851CE40B663dBbF91B86A4EE51A4a0999C5"; +// Already deployed: the plain JumpRateModel carrying exactly these params (base 0, multiplier 9%, +// jump 200%, kink 50%) at 70,080,000 blocks per year, live on the vPT-clisBNB-25JUN2026 market. No +// new interest rate model is deployed for these markets. +// +// Deliberately not 0x1Ef3b851CE40B663dBbF91B86A4EE51A4a0999C5, the address vasBNB and vslisBNB use. +// That one is a CheckpointView: a fallback proxy that forwards every call to the pre- or +// post-migration model depending on block.timestamp. It exists so markets that predate the +// blocks-per-year change keep continuous historical rates, which a market listed now does not have. +export const JUMP_RATE_MODEL = "0x6463ab803FF081616ac4daC31B9B66854cc28Bc0"; // Capped ERC4626 oracle. Price = underlying resilient price x capped vault exchange rate, the // asBNB/slisBNB design. The instances are deployed with the cap zeroed, so this VIP arms it. @@ -296,7 +302,7 @@ All three markets share the same interest rate model (base 0%, multiplier 9%, ju #### Notes on the risk parameters -- **Interest rate model.** Although these markets are non-borrowable, a vToken requires an interest rate model at construction, so a jump-rate IRM (base 0%, multiplier 9%, jump multiplier 200%, kink 50%) is wired to make the market well-formed. No new model is deployed: [0x1Ef3b851CE40B663dBbF91B86A4EE51A4a0999C5](https://bscscan.com/address/0x1Ef3b851CE40B663dBbF91B86A4EE51A4a0999C5) already carries exactly these parameters and already backs vasBNB and vslisBNB. It has no economic effect while borrowing is paused. The listing checklist noted "IRM not needed" precisely because the market is non-borrowable — that is consistent with this VIP: the IRM exists only to satisfy the constructor and is inert. +- **Interest rate model.** Although these markets are non-borrowable, a vToken requires an interest rate model at construction, so a jump-rate IRM (base 0%, multiplier 9%, jump multiplier 200%, kink 50%) is wired to make the market well-formed. No new model is deployed: [0x6463ab803FF081616ac4daC31B9B66854cc28Bc0](https://bscscan.com/address/0x6463ab803FF081616ac4daC31B9B66854cc28Bc0) already carries exactly these parameters at 70,080,000 blocks per year and already backs the vPT-clisBNB-25JUN2026 market. It has no economic effect while borrowing is paused. The listing checklist noted "IRM not needed" precisely because the market is non-borrowable — that is consistent with this VIP: the IRM exists only to satisfy the constructor and is inert. - **Collateral factor equals liquidation threshold** on all three markets (80/80, 82.5/82.5, 75/75). This is intentional and matches the approved risk parameters from the listing template. The vhTokens are ~$1 stablecoin-correlated assets priced through a growth-capped ERC4626 oracle with the E-brake (DeviationBoundedOracle) protection mode enabled, so no CF↔LT buffer is applied; a position opened at the maximum LTV therefore sits at the liquidation boundary, which is the deliberate design for these tightly-pegged collaterals. - **The three collateral factors differ (82.5% vhUSDC, 80% vhUSDT, 75% vhU).** These are the approved per-asset values set by the risk manager in the listing template — not a single blanket figure — and are ordered by the relative maturity and market depth of each underlying peg (USDC > USDT > USD1/U). vhU/USD1, the newest and least liquid of the three, carries the most conservative factor. - **Supply cap is denominated in the underlying token amount, not USD.** \`_setMarketSupplyCaps\` takes an amount of the underlying, so each cap of 10,000,000 is 10,000,000 vhTokens (24 decimals). At the current ~$1 vault price this corresponds to roughly $10M of collateral exposure per market. diff --git a/vips/vip-664/bsctestnet.ts b/vips/vip-664/bsctestnet.ts index 08cd32771..e7d5a3fa4 100644 --- a/vips/vip-664/bsctestnet.ts +++ b/vips/vip-664/bsctestnet.ts @@ -38,7 +38,13 @@ export const BORROW_ACTION = 2; // Comptroller Action enum: BORROW export const { RESILIENT_ORACLE } = bsctestnet; -// Already deployed; backs vasBNB and vslisBNB on testnet with these exact params. +// The testnet CheckpointView wrapper. It forwards to the plain +// JumpRateModel_base0bps_slope900bps_jump20000bps_kink5000bps_bpy70080000 +// (0x274362695401Bb1B0468BfcFE448AD7021D97562), so both return identical rates. +// +// Left as deployed: this testnet VIP has already executed on chain with this address, and the market +// has borrowing paused so the model is inert either way. Mainnet uses the plain model directly and +// avoids the wrapper, see the note in bscmainnet.ts. export const JUMP_RATE_MODEL = "0x8734dBD8Ba959BbC75f2701a022f8d1D47e0722d"; // Same capped-oracle configuration as mainnet. Hub_USDT currently sits at an exchange rate of From 99bf28a902259fc87771b0369768abbe787ac22a Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 24 Aug 2026 13:19:13 +0530 Subject: [PATCH 14/23] feat(vip-664): adjust bootstrap parameters to $10 and 10 vhToken per market --- vips/vip-664/bscmainnet.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index 7ddb8af2d..e4a1ea72e 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -109,9 +109,9 @@ const EXCHANGE_RATE = parseUnits("1", 34); const SUPPLY_CAP = parseUnits("10000000", 24); // _setMarketSupplyCaps takes an underlying amount, not USD const LIQUIDATION_INCENTIVE = parseUnits("1.1", 18); // 10% const RESERVE_FACTOR = parseUnits("0.1", 18); // 10% (inert while borrow is paused) -const BOOTSTRAP_AMOUNT = parseUnits("100", 24); // ~$100 of underlying (24 dec) -// vTokensMinted = amount * 1e18 / exchangeRate = 100e24 * 1e18 / 1e34 = 100e8; burn 10%. -const BOOTSTRAP_BURN = parseUnits("10", 8); +const BOOTSTRAP_AMOUNT = parseUnits("10", 24); // ~$10 of underlying (24 dec) +// vTokensMinted = amount * 1e18 / exchangeRate = 10e24 * 1e18 / 1e34 = 10e8; burn 10%. +const BOOTSTRAP_BURN = parseUnits("1", 8); // Market — vhUSDT export const MARKET_VHUSDT: MarketSpec = { @@ -306,7 +306,7 @@ All three markets share the same interest rate model (base 0%, multiplier 9%, ju - **Collateral factor equals liquidation threshold** on all three markets (80/80, 82.5/82.5, 75/75). This is intentional and matches the approved risk parameters from the listing template. The vhTokens are ~$1 stablecoin-correlated assets priced through a growth-capped ERC4626 oracle with the E-brake (DeviationBoundedOracle) protection mode enabled, so no CF↔LT buffer is applied; a position opened at the maximum LTV therefore sits at the liquidation boundary, which is the deliberate design for these tightly-pegged collaterals. - **The three collateral factors differ (82.5% vhUSDC, 80% vhUSDT, 75% vhU).** These are the approved per-asset values set by the risk manager in the listing template — not a single blanket figure — and are ordered by the relative maturity and market depth of each underlying peg (USDC > USDT > USD1/U). vhU/USD1, the newest and least liquid of the three, carries the most conservative factor. - **Supply cap is denominated in the underlying token amount, not USD.** \`_setMarketSupplyCaps\` takes an amount of the underlying, so each cap of 10,000,000 is 10,000,000 vhTokens (24 decimals). At the current ~$1 vault price this corresponds to roughly $10M of collateral exposure per market. -- **Reserve factor (10%), vTokenReceiver (VTreasury) and bootstrap amount (100 vhToken per market)** were not specified in the listing template and follow the standard Core-pool listing convention. The reserve factor is inert while borrowing is paused. +- **Reserve factor (10%), vTokenReceiver (VTreasury) and bootstrap amount (10 vhToken per market)** were not specified in the listing template and follow the standard Core-pool listing convention. The reserve factor is inert while borrowing is paused. - **Protocol seize share is not settable on the Core pool.** The legacy Core vToken exposes no \`protocolSeizeShare\` getter or setter at all — the share is a constant in the implementation — which is why no Core-pool listing VIP sets it. The listing checklist asks for the value; there is nothing to configure. #### Capped oracle @@ -337,7 +337,7 @@ All three vaults report a share price of ~1.0006 assets per share, so each 10,00 #### Prerequisite -The bootstrap liquidity is withdrawn from the VTreasury, which currently holds **no** vhUSDT, vhUSDC or vhU. The Treasury must be funded with at least 100 of each vhToken before this VIP executes, otherwise \`withdrawTreasuryBEP20\` reverts. The Treasury does hold the underlying stables (USDT, USDC and U), so the funding can be done by depositing into each Venus Hub vault.`, +The bootstrap liquidity is withdrawn from the VTreasury, which currently holds **no** vhUSDT, vhUSDC or vhU. The Treasury must be funded with at least 10 of each vhToken before this VIP executes, otherwise \`withdrawTreasuryBEP20\` reverts. The Treasury does hold the underlying stables (USDT, USDC and U), so the funding can be done by depositing into each Venus Hub vault.`, forDescription: "I agree that Venus Protocol should proceed with this proposal", againstDescription: "I do not think that Venus Protocol should proceed with this proposal", abstainDescription: "I am indifferent to whether Venus Protocol proceeds or not", From ccdea76adb26a6da02e993621649ae4c096e1cae Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 24 Aug 2026 16:16:07 +0530 Subject: [PATCH 15/23] chore(vip-664): fill the deployed bscmainnet addresses - Replaces the six placeholders with the oracle and market contracts from the merged deploy PRs, plus the three ERC4626 asset addresses the bootstrap spends - Moves the simulation to block 117780230, where all six are deployed and still unconfigured --- simulations/vip-664/bscmainnet.ts | 6 +++--- vips/vip-664/bscmainnet.ts | 31 +++++++++++++++++++------------ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-664/bscmainnet.ts index c32775359..604211fa3 100644 --- a/simulations/vip-664/bscmainnet.ts +++ b/simulations/vip-664/bscmainnet.ts @@ -44,9 +44,9 @@ const ONE_SHARE = parseUnits("1", 24); const MIN_SHARE_PRICE = parseUnits("0.95", 12); const MAX_SHARE_PRICE = parseUnits("1.05", 12); -// TODO(deploy): bump to a block after the 3 capped ERC4626 oracles and 3 vTokens are deployed and -// the VTreasury has been funded with the bootstrap vhTokens. -const FORK_BLOCK = 116824477; +// Block 117780230, 2026-08-24T09:00:19Z. The three oracles and the three vTokens are deployed and +// unconfigured at this block, and it is the block the oracle snapshots are seeded from. +const FORK_BLOCK = 117780230; forking(FORK_BLOCK, async () => { const comptroller = new ethers.Contract(bscmainnet.UNITROLLER, COMPTROLLER_ABI, ethers.provider); diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index e4a1ea72e..ab81647dd 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -1,4 +1,4 @@ -import { BigNumber, constants } from "ethers"; +import { BigNumber } from "ethers"; import { parseUnits } from "ethers/lib/utils"; import { ethers } from "hardhat"; import { NETWORK_ADDRESSES } from "src/networkAddresses"; @@ -6,20 +6,27 @@ import { ProposalType } from "src/types"; import { makeProposal } from "src/utils"; // VIP-664 [BNB Chain] List vhUSDT, vhUSDC and vhU in the Venus Core Pool. -// The oracle and vToken addresses below are filled once the deploy PRs land; until then the -// simulation cannot execute. Remaining prerequisites are tracked in the PR description. const { bscmainnet } = NETWORK_ADDRESSES; -// TODO(deploy): fill after the capped ERC4626Oracle instances are deployed on BNB Chain. -export const VHUSDT_ORACLE = constants.AddressZero; -export const VHUSDC_ORACLE = constants.AddressZero; -export const VHU_ORACLE = constants.AddressZero; - -// TODO(deploy): fill after the vTokens are deployed on BNB Chain. -export const VVHUSDT = constants.AddressZero; -export const VVHUSDC = constants.AddressZero; -export const VVHU = constants.AddressZero; +// Capped ERC4626 oracles, deployed from VenusProtocol/oracle under the vh-erc4626-oracles tag with +// every cap argument zeroed. The first three commands per market arm them. +export const VHUSDT_ORACLE = "0x50a998cf59Fe719129702125C9078b0B429DA6E7"; +export const VHUSDC_ORACLE = "0x85aa49526287F06cb36d57121BC7abAF58a8787a"; +export const VHU_ORACLE = "0x5b3735F362ed89b51FBe244509e398BFDDaC8da9"; + +// VBep20Delegator markets, deployed from VenusProtocol/venus-protocol. Each was constructed with +// admin = NormalTimelock, an initial exchange rate of 1e34, the interest rate model below and the +// VBep20Delegate implementation 0xCDfea50f7CECCB24Fe804657DB8E6c93b689941e already used by the pool. +export const VVHUSDT = "0xc0768948e668B7BacFf8b4BD1BaBe0eD2b512d3c"; +export const VVHUSDC = "0xb1AB0399766997C5d66a30b2f2055277B7FA5D6C"; +export const VVHU = "0x80a5694441810d2b871BEeD644b6d16D113ce06E"; + +// ERC4626 assets of the three vaults. The VTreasury holds these, not the vhTokens, so the bootstrap +// withdraws the asset and mints the shares from the vault instead of withdrawing shares directly. +export const USDT = "0x55d398326f99059fF775485246999027B3197955"; +export const USDC = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d"; +export const U = "0xcE24439F2D9C6a2289F741120FE202248B666666"; export const PROTOCOL_SHARE_RESERVE = "0xCa01D5A9A248a830E9D93231e791B1afFed7c446"; export const REDUCE_RESERVES_BLOCK_DELTA = "28800"; From eba104ee716bb22975af2afa26875f93bcc6693e Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 24 Aug 2026 16:16:07 +0530 Subject: [PATCH 16/23] feat(vip-664): mint the bscmainnet bootstrap shares from the vault - The VTreasury holds USDT, USDC and U but none of the vhTokens, so withdrawing shares would revert. Each market now withdraws the asset, mints exactly 10 shares from the Hub vault and supplies those. - Withdraws 10.2 rather than the exact cost, so the mint stays funded if the vault exchange rate rises between proposal and execution - The unspent remainder, under 0.2 of each asset, stays with the Timelock --- vips/vip-664/bscmainnet.ts | 57 ++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index ab81647dd..fd7c254ec 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -80,6 +80,8 @@ export type MarketSpec = { comptroller: string; isLegacyPool: boolean; }; + // The vault's ERC4626 asset, held by the VTreasury and spent to mint the bootstrap shares. + asset: { address: string; symbol: string; decimals: number }; rateModel: string; interestRateModel: { model: "jump"; @@ -103,7 +105,10 @@ export type MarketSpec = { borrowCap: BigNumber; }; initialSupply: { + // vhToken shares minted from the vault and then supplied to the market. amount: BigNumber; + // Asset withdrawn from the VTreasury to pay for those shares. + assetAmount: BigNumber; vTokenReceiver: string; vTokensToBurn: BigNumber; }; @@ -116,7 +121,12 @@ const EXCHANGE_RATE = parseUnits("1", 34); const SUPPLY_CAP = parseUnits("10000000", 24); // _setMarketSupplyCaps takes an underlying amount, not USD const LIQUIDATION_INCENTIVE = parseUnits("1.1", 18); // 10% const RESERVE_FACTOR = parseUnits("0.1", 18); // 10% (inert while borrow is paused) -const BOOTSTRAP_AMOUNT = parseUnits("10", 24); // ~$10 of underlying (24 dec) +const BOOTSTRAP_AMOUNT = parseUnits("10", 24); // 10 vhToken shares, ~$10 of collateral (24 dec) +// Asset withdrawn from the VTreasury to mint those shares. At the authoring block previewMint(10e24) +// costs 10.009332 USDT, 10.010878 USDC and 10.008173 U; 10.2 leaves ~1.9% of exchange-rate headroom +// so the vault mint cannot under-fund if the vaults accrue between proposal and execution. The +// unspent remainder, under 0.2 of each asset, stays with the Normal Timelock. +const BOOTSTRAP_ASSET_AMOUNT = parseUnits("10.2", 18); // vTokensMinted = amount * 1e18 / exchangeRate = 10e24 * 1e18 / 1e34 = 10e8; burn 10%. const BOOTSTRAP_BURN = parseUnits("1", 8); @@ -136,6 +146,7 @@ export const MARKET_VHUSDT: MarketSpec = { comptroller: bscmainnet.UNITROLLER, isLegacyPool: true, }, + asset: { address: USDT, symbol: "USDT", decimals: 18 }, rateModel: JUMP_RATE_MODEL, interestRateModel: { model: "jump", @@ -155,6 +166,7 @@ export const MARKET_VHUSDT: MarketSpec = { }, initialSupply: { amount: BOOTSTRAP_AMOUNT, + assetAmount: BOOTSTRAP_ASSET_AMOUNT, vTokenReceiver: bscmainnet.VTREASURY, vTokensToBurn: BOOTSTRAP_BURN, }, @@ -176,6 +188,7 @@ export const MARKET_VHUSDC: MarketSpec = { comptroller: bscmainnet.UNITROLLER, isLegacyPool: true, }, + asset: { address: USDC, symbol: "USDC", decimals: 18 }, rateModel: JUMP_RATE_MODEL, interestRateModel: { model: "jump", @@ -195,6 +208,7 @@ export const MARKET_VHUSDC: MarketSpec = { }, initialSupply: { amount: BOOTSTRAP_AMOUNT, + assetAmount: BOOTSTRAP_ASSET_AMOUNT, vTokenReceiver: bscmainnet.VTREASURY, vTokensToBurn: BOOTSTRAP_BURN, }, @@ -216,6 +230,7 @@ export const MARKET_VHU: MarketSpec = { comptroller: bscmainnet.UNITROLLER, isLegacyPool: true, }, + asset: { address: U, symbol: "U", decimals: 18 }, rateModel: JUMP_RATE_MODEL, interestRateModel: { model: "jump", @@ -235,6 +250,7 @@ export const MARKET_VHU: MarketSpec = { }, initialSupply: { amount: BOOTSTRAP_AMOUNT, + assetAmount: BOOTSTRAP_ASSET_AMOUNT, vTokenReceiver: bscmainnet.VTREASURY, vTokensToBurn: BOOTSTRAP_BURN, }, @@ -272,7 +288,7 @@ For each new market this VIP will: - Add the market to the Core Pool Comptroller - Set the supply cap, borrow cap (0), interest rate model, collateral factor, liquidation threshold, liquidation incentive and reserve factor - Set the AccessControlManager, ProtocolShareReserve and reduce-reserves block delta on the vToken -- Provide bootstrap liquidity (minting an initial supply, burning 10% and sending the remainder to the VTreasury) +- Provide bootstrap liquidity (withdrawing the vault's ERC4626 asset from the VTreasury, minting vhToken shares from the vault, supplying them to the new market, burning 10% of the vTokens and sending the remainder to the VTreasury) - Pause borrowing for the market at launch (the markets are collateral-only) - Enable Oracle Dynamic Protection Mode / "E-brake" (DeviationBoundedOracle, see VIP-617) for the vhToken, with a stable-appropriate 5% deviation trigger @@ -342,9 +358,17 @@ Each underlying was read directly from BNB Chain and matches the listing templat All three vaults report a share price of ~1.0006 assets per share, so each 10,000,000-share supply cap is worth roughly $10M. U trades at ~$1, so the cap is comparable to the two USD stables. -#### Prerequisite +#### Bootstrap liquidity -The bootstrap liquidity is withdrawn from the VTreasury, which currently holds **no** vhUSDT, vhUSDC or vhU. The Treasury must be funded with at least 10 of each vhToken before this VIP executes, otherwise \`withdrawTreasuryBEP20\` reverts. The Treasury does hold the underlying stables (USDT, USDC and U), so the funding can be done by depositing into each Venus Hub vault.`, +The VTreasury holds the three ERC4626 assets but none of the vhTokens, so this VIP does not withdraw shares. Per market it withdraws the asset, mints exactly 10 vhToken shares from the Venus Hub vault, supplies them to the new market, burns 10% of the resulting vTokens and sends the remaining 9 to the VTreasury. Every approval it grants is reset to zero in the same proposal, no vTokens are left with the Timelock, and no prior funding of the VTreasury is required. + +| Market | Withdrawn from VTreasury | VTreasury balance | Shares minted | Cost at block 117780230 | +|---|---|---|---|---| +| vvhUSDT | 10.2 USDT | 698,092.13 USDT | 10 vhUSDT | 10.009332 USDT | +| vvhUSDC | 10.2 USDC | 58,609.10 USDC | 10 vhUSDC | 10.010878 USDC | +| vvhU | 10.2 U | 213,189.18 U | 10 vhU | 10.008173 U | + +The withdrawal is 10.2 rather than the exact cost because the vault exchange rate rises continuously; the extra ~1.9% keeps the mint funded if the vaults accrue between the proposal and its execution. The unspent remainder, under 0.2 of each asset, stays with the Normal Timelock.`, forDescription: "I agree that Venus Protocol should proceed with this proposal", againstDescription: "I do not think that Venus Protocol should proceed with this proposal", abstainDescription: "I am indifferent to whether Venus Protocol proceeds or not", @@ -448,11 +472,32 @@ The bootstrap liquidity is withdrawn from the VTreasury, which currently holds * params: [m.vToken.address, m.riskParameters.liquidationIncentive], }, - // Initial liquidity: pull underlying from the Treasury, mint, burn a slice, send the remainder to the receiver. + // Bootstrap liquidity. The VTreasury holds the ERC4626 assets (USDT, USDC, U) but no vhTokens, + // so the shares are minted here rather than withdrawn: pull the asset, mint exactly + // BOOTSTRAP_AMOUNT shares from the vault, then supply those shares to the new market. The + // timelock never hands the shares back to the VTreasury in between, since it would only have + // to withdraw them again. Every approval is reset to 0 afterwards. { target: bscmainnet.VTREASURY, signature: "withdrawTreasuryBEP20(address,uint256,address)", - params: [m.vToken.underlying.address, m.initialSupply.amount, bscmainnet.NORMAL_TIMELOCK], + params: [m.asset.address, m.initialSupply.assetAmount, bscmainnet.NORMAL_TIMELOCK], + }, + { + target: m.asset.address, + signature: "approve(address,uint256)", + params: [m.vToken.underlying.address, m.initialSupply.assetAmount], + }, + // mint(shares, receiver) rather than deposit(assets, receiver): it pins the share count, so + // the fixed amounts in every command that follows can never miss by a rounding step. + { + target: m.vToken.underlying.address, + signature: "mint(uint256,address)", + params: [m.initialSupply.amount, bscmainnet.NORMAL_TIMELOCK], + }, + { + target: m.asset.address, + signature: "approve(address,uint256)", + params: [m.vToken.underlying.address, 0], }, { target: m.vToken.underlying.address, From 799435778de45c55dad9fd19e8d85ec4a9c496ae Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 24 Aug 2026 16:16:07 +0530 Subject: [PATCH 17/23] chore(vip-664): reseed the oracle snapshots from block 117780230 - Re-reads each vault's exchange rate and uses that block's timestamp as the snapshot start, so the seed sits just below the live rate and the market does not list with its price already capped - The timestamp only ever widens the allowance as time passes, so a later execution needs no change here; verified by executing the proposal at 5, 30, 90 and 180 days past it - Refreshes the measured growth figures and asset prices in the description to the same block --- vips/vip-664/bscmainnet.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index fd7c254ec..e550f7e2f 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -54,8 +54,8 @@ export const CAPO_SNAPSHOT_INTERVAL = 30 * 24 * 60 * 60; // One snapshot interval of growth, the ratio VIP-530 applied to every asset it armed: // 5% * 30/365 = 0.41%. export const CAPO_SNAPSHOT_GAP_BPS = BigNumber.from(41); -// Vault exchange rates below were read at block 116836175 (2026-08-19T10:57:40Z). -export const CAPO_SEED_TIMESTAMP = 1787137060; +// Vault exchange rates below were read at block 117780230 (2026-08-24T09:00:19Z). +export const CAPO_SEED_TIMESTAMP = 1787562019; export const snapshotGap = (exchangeRate: BigNumber) => exchangeRate.mul(CAPO_SNAPSHOT_GAP_BPS).div(10000); export const seededSnapshot = (exchangeRate: BigNumber) => exchangeRate.add(snapshotGap(exchangeRate)); @@ -155,7 +155,7 @@ export const MARKET_VHUSDT: MarketSpec = { jumpMultiplierPerYear: "2", kink: "0.5", }, - oracle: { address: VHUSDT_ORACLE, seedExchangeRate: parseUnits("1.000650000021349262", 18) }, + oracle: { address: VHUSDT_ORACLE, seedExchangeRate: parseUnits("1.000933217619977068", 18) }, riskParameters: { collateralFactor: parseUnits("0.8", 18), liquidationThreshold: parseUnits("0.8", 18), @@ -197,7 +197,7 @@ export const MARKET_VHUSDC: MarketSpec = { jumpMultiplierPerYear: "2", kink: "0.5", }, - oracle: { address: VHUSDC_ORACLE, seedExchangeRate: parseUnits("1.000815629493107489", 18) }, + oracle: { address: VHUSDC_ORACLE, seedExchangeRate: parseUnits("1.001087844692385500", 18) }, riskParameters: { collateralFactor: parseUnits("0.825", 18), liquidationThreshold: parseUnits("0.825", 18), @@ -239,7 +239,7 @@ export const MARKET_VHU: MarketSpec = { jumpMultiplierPerYear: "2", kink: "0.5", }, - oracle: { address: VHU_ORACLE, seedExchangeRate: parseUnits("1.000544217035461378", 18) }, + oracle: { address: VHU_ORACLE, seedExchangeRate: parseUnits("1.000817329117253330", 18) }, riskParameters: { collateralFactor: parseUnits("0.75", 18), liquidationThreshold: parseUnits("0.75", 18), @@ -338,13 +338,13 @@ The oracles are deployed with the cap zeroed, exactly as the asBNB oracle was, s | | Growth rate | Snapshot interval | Snapshot gap | Seeded exchange rate | |---|---|---|---|---| -| vhUSDT | 5%/yr | 30 days | 41 bps (0.004102665000087531) | 1.004752665021436793 | -| vhUSDC | 5%/yr | 30 days | 41 bps (0.004103344080921740) | 1.004918973574029229 | -| vhU | 5%/yr | 30 days | 41 bps (0.004102231289845391) | 1.004646448325306769 | +| vhUSDT | 5%/yr | 30 days | 41 bps (0.004103826192241905) | 1.005037043812218973 | +| vhUSDC | 5%/yr | 30 days | 41 bps (0.004104460163238780) | 1.005192304855624280 | +| vhU | 5%/yr | 30 days | 41 bps (0.004103351049380738) | 1.004920680166634068 | -- **The 5%/yr growth rate leaves 2–3× headroom over observed yield.** The three vaults launched ~13 days before this was written, all from an exchange rate of exactly 1.0; measured growth annualises to 1.82% (vhUSDT), 2.29% (vhUSDC) and 1.52% (vhU). A 5% cap therefore does not bind in normal operation, and matches what asBNB and slisBNB run since VIP-605. +- **5%/yr leaves ~2.5x headroom over observed yield.** Between blocks 116836175 and 117780230 (4.92 days) the exchange rates grew at an annualised 2.10% (vhUSDT), 2.02% (vhUSDC) and 2.03% (vhU). A 5% cap therefore does not bind in normal operation, and matches what asBNB and slisBNB run since VIP-605. - **The 41 bps gap is one snapshot interval of capped growth** (5% × 30/365 = 0.41%), the same ratio VIP-530 applied to every asset it armed — BNBx 7.53%/yr → 63 bps, ankrBNB 6.12%/yr → 51 bps, sUSDe 28.27%/yr → 236 bps, slisBNB 4.12%/yr → 34 bps. -- Exchange rates were read at block 116836175 (\`2026-08-19T10:57:40Z\`), which is also the snapshot timestamp. Because the seed carries 41 bps of headroom on top of the growth allowance accruing from that timestamp, drift between authoring and execution does not cap the price at listing. +- Exchange rates were read at block 117780230 (\`2026-08-24T09:00:19Z\`), which is also the snapshot timestamp. Because the seed carries 41 bps of headroom on top of the growth allowance accruing from that timestamp, drift between authoring and execution does not cap the price at listing. #### Underlying tokens @@ -352,11 +352,11 @@ Each underlying was read directly from BNB Chain and matches the listing templat | Token | Address | Name | Decimals | ERC4626 asset | Resilient price of the asset | |---|---|---|---|---|---| -| vhUSDT | [0x18AfDACF30F8671021dec4b78297E39d2FE87226](https://bscscan.com/address/0x18AfDACF30F8671021dec4b78297E39d2FE87226) | Venus Hub USDT | 24 | [USDT](https://bscscan.com/address/0x55d398326f99059fF775485246999027B3197955) | $0.9991 | -| vhUSDC | [0x9D2D9592cF8DFbf59107fAab703d08494BE14617](https://bscscan.com/address/0x9D2D9592cF8DFbf59107fAab703d08494BE14617) | Venus Hub USDC | 24 | [USDC](https://bscscan.com/address/0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d) | $0.9998 | +| vhUSDT | [0x18AfDACF30F8671021dec4b78297E39d2FE87226](https://bscscan.com/address/0x18AfDACF30F8671021dec4b78297E39d2FE87226) | Venus Hub USDT | 24 | [USDT](https://bscscan.com/address/0x55d398326f99059fF775485246999027B3197955) | $0.9998 | +| vhUSDC | [0x9D2D9592cF8DFbf59107fAab703d08494BE14617](https://bscscan.com/address/0x9D2D9592cF8DFbf59107fAab703d08494BE14617) | Venus Hub USDC | 24 | [USDC](https://bscscan.com/address/0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d) | $0.9999 | | vhU | [0x0e5AA174d4F31b757a237eb1999DE151596788B0](https://bscscan.com/address/0x0e5AA174d4F31b757a237eb1999DE151596788B0) | Venus Hub U | 24 | [U](https://bscscan.com/address/0xcE24439F2D9C6a2289F741120FE202248B666666) | $0.9995 | -All three vaults report a share price of ~1.0006 assets per share, so each 10,000,000-share supply cap is worth roughly $10M. U trades at ~$1, so the cap is comparable to the two USD stables. +All three vaults report a share price of ~1.0009 assets per share, so each 10,000,000-share supply cap is worth roughly $10M. U trades at ~$1, so the cap is comparable to the two USD stables. #### Bootstrap liquidity From aa6a7cfe2eaa017e83bc3d3e06514a07924b6737 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 24 Aug 2026 16:16:08 +0530 Subject: [PATCH 18/23] refactor(vip-664): drop the redundant _setInterestRateModel commands - Each market was deployed with 0x6463ab803FF081616ac4daC31B9B66854cc28Bc0 already set, so the three calls only rewrote the value they read. Treats the IRM like the other constructor state the proposal does not re-set. - Drops NewMarketInterestRateModel from the expected events, since nothing emits it any more --- simulations/vip-664/bscmainnet.ts | 3 +-- vips/vip-664/bscmainnet.ts | 12 +++--------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-664/bscmainnet.ts index 604211fa3..afc197b48 100644 --- a/simulations/vip-664/bscmainnet.ts +++ b/simulations/vip-664/bscmainnet.ts @@ -93,7 +93,6 @@ forking(FORK_BLOCK, async () => { "MarketListed", "NewSupplyCap", "NewBorrowCap", - "NewMarketInterestRateModel", "ActionPausedMarket", "NewAccessControlManager", "NewProtocolShareReserve", @@ -103,7 +102,7 @@ forking(FORK_BLOCK, async () => { "NewLiquidationThreshold", "NewLiquidationIncentive", ], - [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], ); }, }); diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index e550f7e2f..d8112e792 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -82,6 +82,7 @@ export type MarketSpec = { }; // The vault's ERC4626 asset, held by the VTreasury and spent to mint the bootstrap shares. asset: { address: string; symbol: string; decimals: number }; + // Set in each vToken's constructor, so the proposal never writes it; the simulation asserts it. rateModel: string; interestRateModel: { model: "jump"; @@ -286,7 +287,7 @@ For each new market this VIP will: - Arm the growth cap on the vhToken's capped **ERC4626Oracle** (seed the snapshot, set the growth rate and set the snapshot gap) - Register that oracle in the ResilientOracle as the single price source. It prices the vhToken as *underlying resilient price × capped vault exchange rate*, the same design as the live asBNB and slisBNB capped oracles. - Add the market to the Core Pool Comptroller -- Set the supply cap, borrow cap (0), interest rate model, collateral factor, liquidation threshold, liquidation incentive and reserve factor +- Set the supply cap, borrow cap (0), collateral factor, liquidation threshold, liquidation incentive and reserve factor - Set the AccessControlManager, ProtocolShareReserve and reduce-reserves block delta on the vToken - Provide bootstrap liquidity (withdrawing the vault's ERC4626 asset from the VTreasury, minting vhToken shares from the vault, supplying them to the new market, burning 10% of the vTokens and sending the remainder to the VTreasury) - Pause borrowing for the market at launch (the markets are collateral-only) @@ -325,7 +326,7 @@ All three markets share the same interest rate model (base 0%, multiplier 9%, ju #### Notes on the risk parameters -- **Interest rate model.** Although these markets are non-borrowable, a vToken requires an interest rate model at construction, so a jump-rate IRM (base 0%, multiplier 9%, jump multiplier 200%, kink 50%) is wired to make the market well-formed. No new model is deployed: [0x6463ab803FF081616ac4daC31B9B66854cc28Bc0](https://bscscan.com/address/0x6463ab803FF081616ac4daC31B9B66854cc28Bc0) already carries exactly these parameters at 70,080,000 blocks per year and already backs the vPT-clisBNB-25JUN2026 market. It has no economic effect while borrowing is paused. The listing checklist noted "IRM not needed" precisely because the market is non-borrowable — that is consistent with this VIP: the IRM exists only to satisfy the constructor and is inert. +- **Interest rate model.** Although these markets are non-borrowable, a vToken requires an interest rate model at construction, so a jump-rate IRM (base 0%, multiplier 9%, jump multiplier 200%, kink 50%) is wired to make the market well-formed. No new model is deployed: [0x6463ab803FF081616ac4daC31B9B66854cc28Bc0](https://bscscan.com/address/0x6463ab803FF081616ac4daC31B9B66854cc28Bc0) already carries exactly these parameters at 70,080,000 blocks per year and already backs the vPT-clisBNB-25JUN2026 market. It has no economic effect while borrowing is paused, and this VIP does not set it: each market was already deployed pointing at that address. The listing checklist noted "IRM not needed" precisely because the market is non-borrowable — that is consistent with this VIP: the IRM exists only to satisfy the constructor and is inert. - **Collateral factor equals liquidation threshold** on all three markets (80/80, 82.5/82.5, 75/75). This is intentional and matches the approved risk parameters from the listing template. The vhTokens are ~$1 stablecoin-correlated assets priced through a growth-capped ERC4626 oracle with the E-brake (DeviationBoundedOracle) protection mode enabled, so no CF↔LT buffer is applied; a position opened at the maximum LTV therefore sits at the liquidation boundary, which is the deliberate design for these tightly-pegged collaterals. - **The three collateral factors differ (82.5% vhUSDC, 80% vhUSDT, 75% vhU).** These are the approved per-asset values set by the risk manager in the listing template — not a single blanket figure — and are ordered by the relative maturity and market depth of each underlying peg (USDC > USDT > USD1/U). vhU/USD1, the newest and least liquid of the three, carries the most conservative factor. - **Supply cap is denominated in the underlying token amount, not USD.** \`_setMarketSupplyCaps\` takes an amount of the underlying, so each cap of 10,000,000 is 10,000,000 vhTokens (24 decimals). At the current ~$1 vault price this corresponds to roughly $10M of collateral exposure per market. @@ -449,13 +450,6 @@ The withdrawal is 10.2 rather than the exact cost because the vault exchange rat signature: "setReduceReservesBlockDelta(uint256)", params: [REDUCE_RESERVES_BLOCK_DELTA], }, - // Set here rather than trusted from the vToken constructor, since this reuses an already - // deployed model instead of deploying one per market. - { - target: m.vToken.address, - signature: "_setInterestRateModel(address)", - params: [m.rateModel], - }, { target: m.vToken.address, signature: "_setReserveFactor(uint256)", From b1de3c6c8b58c5a5ae454ba1849c8803f1e159c8 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 24 Aug 2026 16:16:24 +0530 Subject: [PATCH 19/23] fix(vip-664): keep propose() inside the simulated gas limit - The proposal could not be proposed at 75 commands: hardhat-ethers pins every contract call to a 29,000,000 gas limit and ethers adds the calldata cost on top, pushing it past the ceiling. On chain propose() uses 9.8M against a 16,777,216 per-tx cap, so this is a simulation constraint only. - Batches the supply cap, borrow cap and borrow pause into one array call each. The Comptroller loops over the array, so each still emits one event per market. - Tightens the description to the same facts in fewer bytes and records the remaining headroom in a comment --- vips/vip-664/bscmainnet.ts | 432 ++++++++++++++++++------------------- 1 file changed, 209 insertions(+), 223 deletions(-) diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index d8112e792..20468cc7d 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -268,74 +268,50 @@ export const vTokensMinted = (m: MarketSpec) => convertAmountToVTokens(m.initial export const vTokensRemaining = (m: MarketSpec) => vTokensMinted(m).sub(m.initialSupply.vTokensToBurn); +// Size limit, simulation only. hardhat-ethers pins every contract call to a 29,000,000 gas limit +// and ethers adds the calldata cost on top, so the simulated propose() is rejected once this +// proposal's own bytes cost more than 1,000,000 gas. Nothing like it applies on chain: propose() +// really uses 9.8M against BNB Chain's 16,777,216 per-tx cap. Today the simulated figure is +// 29,929,560, about 70,000 gas under the limit, which is room for roughly 1,000 more description +// characters. Add many more commands or description text and the simulation stops proposing. export const vip664 = () => { const meta = { version: "v2", title: "VIP-664 [BNB Chain] List vhUSDT, vhUSDC and vhU markets in the Venus Core Pool", description: `#### Summary -If passed, this VIP will list three new non-borrowable collateral markets in the Venus Core Pool on BNB Chain, backed by Venus Hub receipt tokens (vhTokens), with borrowing paused at launch: - -- **Venus vhUSDT (vvhUSDT)** — backed by vhUSDT (Venus Hub USDT, ERC4626, 24 decimals) -- **Venus vhUSDC (vvhUSDC)** — backed by vhUSDC (Venus Hub USDC, ERC4626, 24 decimals) -- **Venus vhU (vvhU)** — backed by vhU (Venus Hub U, ERC4626, 24 decimals) +If passed, this VIP will list three non-borrowable collateral markets in the Venus Core Pool on BNB Chain, backed by Venus Hub receipt tokens (vhTokens, ERC4626, 24 decimals), with borrowing paused at launch: **Venus vhUSDT (vvhUSDT)**, **Venus vhUSDC (vvhUSDC)** and **Venus vhU (vvhU)**. #### Description For each new market this VIP will: -- Arm the growth cap on the vhToken's capped **ERC4626Oracle** (seed the snapshot, set the growth rate and set the snapshot gap) -- Register that oracle in the ResilientOracle as the single price source. It prices the vhToken as *underlying resilient price × capped vault exchange rate*, the same design as the live asBNB and slisBNB capped oracles. -- Add the market to the Core Pool Comptroller -- Set the supply cap, borrow cap (0), collateral factor, liquidation threshold, liquidation incentive and reserve factor +- Arm the growth cap on the vhToken's capped **ERC4626Oracle** (snapshot, growth rate, snapshot gap) and register it in the ResilientOracle as the single price source. It prices the vhToken as *underlying resilient price × capped vault exchange rate*, the design the live asBNB and slisBNB oracles use. +- Add the market to the Core Pool Comptroller and set the supply cap, borrow cap (0), collateral factor, liquidation threshold, liquidation incentive and reserve factor - Set the AccessControlManager, ProtocolShareReserve and reduce-reserves block delta on the vToken -- Provide bootstrap liquidity (withdrawing the vault's ERC4626 asset from the VTreasury, minting vhToken shares from the vault, supplying them to the new market, burning 10% of the vTokens and sending the remainder to the VTreasury) -- Pause borrowing for the market at launch (the markets are collateral-only) -- Enable Oracle Dynamic Protection Mode / "E-brake" (DeviationBoundedOracle, see VIP-617) for the vhToken, with a stable-appropriate 5% deviation trigger +- Provide bootstrap liquidity (see below) and pause borrowing, since the markets are collateral-only +- Enable Oracle Dynamic Protection Mode / "E-brake" (DeviationBoundedOracle, see VIP-617) with a stable-appropriate 5% deviation trigger #### Risk parameters -All three markets share the same interest rate model (base 0%, multiplier 9%, jump multiplier 200%, kink 50%); rates are inert while borrowing is paused. Per-market parameters: - -**Venus vhUSDT (vvhUSDT)** -- Collateral factor: 80% -- Liquidation threshold: 80% -- Liquidation incentive: 10% -- Reserve factor: 10% -- Supply cap: 10,000,000 vhUSDT -- Borrow cap: 0 (borrowing disabled) -- E-brake trigger / reset: 5% / 2% - -**Venus vhUSDC (vvhUSDC)** -- Collateral factor: 82.5% -- Liquidation threshold: 82.5% -- Liquidation incentive: 10% -- Reserve factor: 10% -- Supply cap: 10,000,000 vhUSDC -- Borrow cap: 0 (borrowing disabled) -- E-brake trigger / reset: 5% / 2% - -**Venus vhU (vvhU)** -- Collateral factor: 75% -- Liquidation threshold: 75% -- Liquidation incentive: 10% -- Reserve factor: 10% -- Supply cap: 10,000,000 vhU -- Borrow cap: 0 (borrowing disabled) -- E-brake trigger / reset: 5% / 2% - -#### Notes on the risk parameters - -- **Interest rate model.** Although these markets are non-borrowable, a vToken requires an interest rate model at construction, so a jump-rate IRM (base 0%, multiplier 9%, jump multiplier 200%, kink 50%) is wired to make the market well-formed. No new model is deployed: [0x6463ab803FF081616ac4daC31B9B66854cc28Bc0](https://bscscan.com/address/0x6463ab803FF081616ac4daC31B9B66854cc28Bc0) already carries exactly these parameters at 70,080,000 blocks per year and already backs the vPT-clisBNB-25JUN2026 market. It has no economic effect while borrowing is paused, and this VIP does not set it: each market was already deployed pointing at that address. The listing checklist noted "IRM not needed" precisely because the market is non-borrowable — that is consistent with this VIP: the IRM exists only to satisfy the constructor and is inert. -- **Collateral factor equals liquidation threshold** on all three markets (80/80, 82.5/82.5, 75/75). This is intentional and matches the approved risk parameters from the listing template. The vhTokens are ~$1 stablecoin-correlated assets priced through a growth-capped ERC4626 oracle with the E-brake (DeviationBoundedOracle) protection mode enabled, so no CF↔LT buffer is applied; a position opened at the maximum LTV therefore sits at the liquidation boundary, which is the deliberate design for these tightly-pegged collaterals. -- **The three collateral factors differ (82.5% vhUSDC, 80% vhUSDT, 75% vhU).** These are the approved per-asset values set by the risk manager in the listing template — not a single blanket figure — and are ordered by the relative maturity and market depth of each underlying peg (USDC > USDT > USD1/U). vhU/USD1, the newest and least liquid of the three, carries the most conservative factor. -- **Supply cap is denominated in the underlying token amount, not USD.** \`_setMarketSupplyCaps\` takes an amount of the underlying, so each cap of 10,000,000 is 10,000,000 vhTokens (24 decimals). At the current ~$1 vault price this corresponds to roughly $10M of collateral exposure per market. -- **Reserve factor (10%), vTokenReceiver (VTreasury) and bootstrap amount (10 vhToken per market)** were not specified in the listing template and follow the standard Core-pool listing convention. The reserve factor is inert while borrowing is paused. -- **Protocol seize share is not settable on the Core pool.** The legacy Core vToken exposes no \`protocolSeizeShare\` getter or setter at all — the share is a constant in the implementation — which is why no Core-pool listing VIP sets it. The listing checklist asks for the value; there is nothing to configure. +All three markets share the same interest rate model (base 0%, multiplier 9%, jump multiplier 200%, kink 50%); rates are inert while borrowing is paused. + +| Market | Collateral factor | Liquidation threshold | Liquidation incentive | Reserve factor | Supply cap | Borrow cap | E-brake trigger / reset | +|---|---|---|---|---|---|---|---| +| vvhUSDT | 80% | 80% | 10% | 10% | 10,000,000 vhUSDT | 0 | 5% / 2% | +| vvhUSDC | 82.5% | 82.5% | 10% | 10% | 10,000,000 vhUSDC | 0 | 5% / 2% | +| vvhU | 75% | 75% | 10% | 10% | 10,000,000 vhU | 0 | 5% / 2% | + +- **Interest rate model.** A vToken requires an IRM at construction even though these markets are non-borrowable, so the three markets were deployed pointing at [0x6463ab803FF081616ac4daC31B9B66854cc28Bc0](https://bscscan.com/address/0x6463ab803FF081616ac4daC31B9B66854cc28Bc0), which already carries these exact parameters at 70,080,000 blocks per year and backs the vPT-clisBNB-25JUN2026 market. No new model is deployed and this VIP does not set one, since each market already holds the intended address. It is inert while borrowing is paused, which is what the listing checklist's "IRM not needed" refers to. +- **Collateral factor equals liquidation threshold** on all three markets, as approved: the vhTokens are ~$1 stablecoin-correlated assets priced through a growth-capped oracle with the E-brake enabled, so no CF-to-LT buffer is applied and a position at the maximum LTV sits at the liquidation boundary by design. +- **The collateral factors differ (82.5% vhUSDC, 80% vhUSDT, 75% vhU)** — approved per-asset values, ordered by the maturity and market depth of each underlying peg (USDC > USDT > USD1/U). vhU/USD1, the newest and least liquid, carries the most conservative factor. +- **Supply caps are denominated in the underlying token amount, not USD.** Each cap is 10,000,000 vhTokens (24 decimals), roughly $10M of collateral exposure at the current ~$1 vault price. +- **Reserve factor (10%), vTokenReceiver (VTreasury) and the bootstrap amount (10 vhToken shares, ~$10 per market)** were not in the listing template and follow the standard Core-pool convention. +- **Protocol seize share is not settable on the Core pool.** The legacy Core vToken has no \`protocolSeizeShare\` getter or setter; the share is a constant in the implementation, which is why no Core-pool listing VIP sets it. #### Capped oracle -The oracles are deployed with the cap zeroed, exactly as the asBNB oracle was, so this VIP arms it per market with \`setSnapshot\`, \`setGrowthRate\` and \`setSnapshotGap\` — the same three commands in the same order as VIP-530. The permissions for all three already sit with the Normal, Fast-Track and Critical timelocks (granted repo-wide in VIP-517), so no new ACM grants are needed. +The three oracles were deployed with every cap argument zeroed, as the asBNB oracle was, so this VIP arms each with \`setSnapshot\`, \`setGrowthRate\` and \`setSnapshotGap\` — the same commands in the same order as VIP-530. The timelocks already hold these permissions (VIP-517), so no new ACM grants are needed. | | Growth rate | Snapshot interval | Snapshot gap | Seeded exchange rate | |---|---|---|---|---| @@ -343,21 +319,18 @@ The oracles are deployed with the cap zeroed, exactly as the asBNB oracle was, s | vhUSDC | 5%/yr | 30 days | 41 bps (0.004104460163238780) | 1.005192304855624280 | | vhU | 5%/yr | 30 days | 41 bps (0.004103351049380738) | 1.004920680166634068 | -- **5%/yr leaves ~2.5x headroom over observed yield.** Between blocks 116836175 and 117780230 (4.92 days) the exchange rates grew at an annualised 2.10% (vhUSDT), 2.02% (vhUSDC) and 2.03% (vhU). A 5% cap therefore does not bind in normal operation, and matches what asBNB and slisBNB run since VIP-605. -- **The 41 bps gap is one snapshot interval of capped growth** (5% × 30/365 = 0.41%), the same ratio VIP-530 applied to every asset it armed — BNBx 7.53%/yr → 63 bps, ankrBNB 6.12%/yr → 51 bps, sUSDe 28.27%/yr → 236 bps, slisBNB 4.12%/yr → 34 bps. -- Exchange rates were read at block 117780230 (\`2026-08-24T09:00:19Z\`), which is also the snapshot timestamp. Because the seed carries 41 bps of headroom on top of the growth allowance accruing from that timestamp, drift between authoring and execution does not cap the price at listing. +- **5%/yr leaves ~2.5x headroom over observed yield.** Between blocks 116836175 and 117780230 (4.92 days) the exchange rates grew at an annualised 2.10% (vhUSDT), 2.02% (vhUSDC) and 2.03% (vhU). The cap matches what asBNB and slisBNB have run since VIP-605. +- **The 41 bps gap is one snapshot interval of capped growth** (5% x 30/365 = 0.41%), the ratio VIP-530 applied to every asset it armed. It sits on top of the growth allowance accruing from the snapshot timestamp, so drift between authoring and execution does not cap the price at listing. #### Underlying tokens -Each underlying was read directly from BNB Chain and matches the listing template: - -| Token | Address | Name | Decimals | ERC4626 asset | Resilient price of the asset | -|---|---|---|---|---|---| -| vhUSDT | [0x18AfDACF30F8671021dec4b78297E39d2FE87226](https://bscscan.com/address/0x18AfDACF30F8671021dec4b78297E39d2FE87226) | Venus Hub USDT | 24 | [USDT](https://bscscan.com/address/0x55d398326f99059fF775485246999027B3197955) | $0.9998 | -| vhUSDC | [0x9D2D9592cF8DFbf59107fAab703d08494BE14617](https://bscscan.com/address/0x9D2D9592cF8DFbf59107fAab703d08494BE14617) | Venus Hub USDC | 24 | [USDC](https://bscscan.com/address/0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d) | $0.9999 | -| vhU | [0x0e5AA174d4F31b757a237eb1999DE151596788B0](https://bscscan.com/address/0x0e5AA174d4F31b757a237eb1999DE151596788B0) | Venus Hub U | 24 | [U](https://bscscan.com/address/0xcE24439F2D9C6a2289F741120FE202248B666666) | $0.9995 | +Read from BNB Chain at block 117780230, which is also the oracle snapshot timestamp, and matching the listing template. All three vaults report ~1.0009 assets per share, so each 10,000,000-share supply cap is worth roughly $10M. -All three vaults report a share price of ~1.0009 assets per share, so each 10,000,000-share supply cap is worth roughly $10M. U trades at ~$1, so the cap is comparable to the two USD stables. +| Token | Address | ERC4626 asset | Resilient price of the asset | +|---|---|---|---| +| Venus Hub USDT (vhUSDT) | [0x18AfDACF30F8671021dec4b78297E39d2FE87226](https://bscscan.com/address/0x18AfDACF30F8671021dec4b78297E39d2FE87226) | USDT | $0.9998 | +| Venus Hub USDC (vhUSDC) | [0x9D2D9592cF8DFbf59107fAab703d08494BE14617](https://bscscan.com/address/0x9D2D9592cF8DFbf59107fAab703d08494BE14617) | USDC | $0.9999 | +| Venus Hub U (vhU) | [0x0e5AA174d4F31b757a237eb1999DE151596788B0](https://bscscan.com/address/0x0e5AA174d4F31b757a237eb1999DE151596788B0) | U | $0.9995 | #### Bootstrap liquidity @@ -376,170 +349,183 @@ The withdrawal is 10.2 rather than the exact cost because the vault exchange rat }; return makeProposal( - MARKETS.flatMap(m => [ - // Arm the growth cap before the price source goes live. Order is load-bearing (see above). - { - target: m.oracle.address, - signature: "setSnapshot(uint256,uint256)", - params: [seededSnapshot(m.oracle.seedExchangeRate), CAPO_SEED_TIMESTAMP], - }, - { - target: m.oracle.address, - signature: "setGrowthRate(uint256,uint256)", - params: [CAPO_GROWTH_RATE_PER_YEAR, CAPO_SNAPSHOT_INTERVAL], - }, - { - target: m.oracle.address, - signature: "setSnapshotGap(uint256)", - params: [snapshotGap(m.oracle.seedExchangeRate)], - }, - - // Oracle configuration — single source: the capped ERC4626Oracle for the vhToken. - // The ERC4626Oracle reads the underlying (USDT/USDC/USD1) price from the ResilientOracle - // itself and applies the growth-rate cap on the vault exchange rate, so no extra feed - // configuration is required. - { - target: RESILIENT_ORACLE, - signature: "setTokenConfig((address,address[3],bool[3],bool))", - params: [ - [ - m.vToken.underlying.address, - [m.oracle.address, ethers.constants.AddressZero, ethers.constants.AddressZero], - [true, false, false], - false, + [ + MARKETS.flatMap(m => [ + // Arm the growth cap before the price source goes live. Order is load-bearing (see above). + { + target: m.oracle.address, + signature: "setSnapshot(uint256,uint256)", + params: [seededSnapshot(m.oracle.seedExchangeRate), CAPO_SEED_TIMESTAMP], + }, + { + target: m.oracle.address, + signature: "setGrowthRate(uint256,uint256)", + params: [CAPO_GROWTH_RATE_PER_YEAR, CAPO_SNAPSHOT_INTERVAL], + }, + { + target: m.oracle.address, + signature: "setSnapshotGap(uint256)", + params: [snapshotGap(m.oracle.seedExchangeRate)], + }, + + // Oracle configuration — single source: the capped ERC4626Oracle for the vhToken. + // The ERC4626Oracle reads the underlying (USDT/USDC/USD1) price from the ResilientOracle + // itself and applies the growth-rate cap on the vault exchange rate, so no extra feed + // configuration is required. + { + target: RESILIENT_ORACLE, + signature: "setTokenConfig((address,address[3],bool[3],bool))", + params: [ + [ + m.vToken.underlying.address, + [m.oracle.address, ethers.constants.AddressZero, ethers.constants.AddressZero], + [true, false, false], + false, + ], ], - ], - }, - - // Add market - { - target: m.vToken.comptroller, - signature: "_supportMarket(address)", - params: [m.vToken.address], - }, - { - target: m.vToken.comptroller, - signature: "_setMarketSupplyCaps(address[],uint256[])", - params: [[m.vToken.address], [m.riskParameters.supplyCap]], - }, - // Explicit, though a fresh market already defaults to 0: the borrow cap is a stated risk - // parameter, and relying on a default is what hid the unarmed price cap. - { - target: m.vToken.comptroller, - signature: "_setMarketBorrowCaps(address[],uint256[])", - params: [[m.vToken.address], [m.riskParameters.borrowCap]], - }, - // Pause borrowing for the market at launch (collateral-only markets). - { - target: m.vToken.comptroller, - signature: "setActionsPaused(address[],uint8[],bool)", - params: [[m.vToken.address], [BORROW_ACTION], true], - }, - { - target: m.vToken.address, - signature: "setAccessControlManager(address)", - params: [bscmainnet.ACCESS_CONTROL_MANAGER], - }, - { - target: m.vToken.address, - signature: "setProtocolShareReserve(address)", - params: [PROTOCOL_SHARE_RESERVE], - }, - { - target: m.vToken.address, - signature: "setReduceReservesBlockDelta(uint256)", - params: [REDUCE_RESERVES_BLOCK_DELTA], - }, - { - target: m.vToken.address, - signature: "_setReserveFactor(uint256)", - params: [m.riskParameters.reserveFactor], - }, - { - target: m.vToken.comptroller, - signature: "setCollateralFactor(address,uint256,uint256)", - params: [m.vToken.address, m.riskParameters.collateralFactor, m.riskParameters.liquidationThreshold], - }, - { - target: m.vToken.comptroller, - signature: "setLiquidationIncentive(address,uint256)", - params: [m.vToken.address, m.riskParameters.liquidationIncentive], - }, - - // Bootstrap liquidity. The VTreasury holds the ERC4626 assets (USDT, USDC, U) but no vhTokens, - // so the shares are minted here rather than withdrawn: pull the asset, mint exactly - // BOOTSTRAP_AMOUNT shares from the vault, then supply those shares to the new market. The - // timelock never hands the shares back to the VTreasury in between, since it would only have - // to withdraw them again. Every approval is reset to 0 afterwards. - { - target: bscmainnet.VTREASURY, - signature: "withdrawTreasuryBEP20(address,uint256,address)", - params: [m.asset.address, m.initialSupply.assetAmount, bscmainnet.NORMAL_TIMELOCK], - }, - { - target: m.asset.address, - signature: "approve(address,uint256)", - params: [m.vToken.underlying.address, m.initialSupply.assetAmount], - }, - // mint(shares, receiver) rather than deposit(assets, receiver): it pins the share count, so - // the fixed amounts in every command that follows can never miss by a rounding step. - { - target: m.vToken.underlying.address, - signature: "mint(uint256,address)", - params: [m.initialSupply.amount, bscmainnet.NORMAL_TIMELOCK], - }, - { - target: m.asset.address, - signature: "approve(address,uint256)", - params: [m.vToken.underlying.address, 0], - }, - { - target: m.vToken.underlying.address, - signature: "approve(address,uint256)", - params: [m.vToken.address, m.initialSupply.amount], - }, - { - target: m.vToken.address, - signature: "mint(uint256)", - params: [m.initialSupply.amount], - }, - { - target: m.vToken.underlying.address, - signature: "approve(address,uint256)", - params: [m.vToken.address, 0], - }, - // Burn a slice of vTokens. - { - target: m.vToken.address, - signature: "transfer(address,uint256)", - params: [ethers.constants.AddressZero, m.initialSupply.vTokensToBurn], - }, - // Transfer remaining vTokens to the receiver (VTreasury). - { - target: m.vToken.address, - signature: "transfer(address,uint256)", - params: [m.initialSupply.vTokenReceiver, vTokensRemaining(m)], - }, - - // Enable Oracle Dynamic Protection Mode / "E-brake" (DBO) for the vhToken with a 5% deviation - // trigger. Must stay last: setTokenConfig seeds minPrice and maxPrice from - // RESILIENT_ORACLE.getPrice(asset), so it reverts unless the capped oracle is already - // registered above. The seeding is why no separate bounds command is needed. - { - target: DEVIATION_BOUNDED_ORACLE, - signature: "setTokenConfig((address,uint64,uint256,uint256,bool,bool))", - params: [ - [ - m.vToken.underlying.address, - DBO_COOLDOWN_PERIOD, - DBO_TRIGGER_THRESHOLD, - DBO_RESET_THRESHOLD, - true, // enableBoundedPricing - false, // enableCaching + }, + + // Add market + { + target: m.vToken.comptroller, + signature: "_supportMarket(address)", + params: [m.vToken.address], + }, + ]), + + // Caps and the borrow pause take market arrays, so all three markets go in one call each + // rather than three. The Comptroller loops over the array and still emits one event per + // market. They must follow every _supportMarket above (the Comptroller rejects an unlisted + // market) and precede the bootstrap mint below (a fresh market's supply cap is 0, which + // would make mint revert). + [ + { + target: bscmainnet.UNITROLLER, + signature: "_setMarketSupplyCaps(address[],uint256[])", + params: [MARKETS.map(m => m.vToken.address), MARKETS.map(m => m.riskParameters.supplyCap)], + }, + // Explicit, though a fresh market already defaults to 0: the borrow cap is a stated risk + // parameter, and relying on a default is what hid the unarmed price cap. + { + target: bscmainnet.UNITROLLER, + signature: "_setMarketBorrowCaps(address[],uint256[])", + params: [MARKETS.map(m => m.vToken.address), MARKETS.map(m => m.riskParameters.borrowCap)], + }, + // Pause borrowing on all three markets at launch (collateral-only markets). + { + target: bscmainnet.UNITROLLER, + signature: "setActionsPaused(address[],uint8[],bool)", + params: [MARKETS.map(m => m.vToken.address), [BORROW_ACTION], true], + }, + ], + + MARKETS.flatMap(m => [ + { + target: m.vToken.address, + signature: "setAccessControlManager(address)", + params: [bscmainnet.ACCESS_CONTROL_MANAGER], + }, + { + target: m.vToken.address, + signature: "setProtocolShareReserve(address)", + params: [PROTOCOL_SHARE_RESERVE], + }, + { + target: m.vToken.address, + signature: "setReduceReservesBlockDelta(uint256)", + params: [REDUCE_RESERVES_BLOCK_DELTA], + }, + { + target: m.vToken.address, + signature: "_setReserveFactor(uint256)", + params: [m.riskParameters.reserveFactor], + }, + { + target: m.vToken.comptroller, + signature: "setCollateralFactor(address,uint256,uint256)", + params: [m.vToken.address, m.riskParameters.collateralFactor, m.riskParameters.liquidationThreshold], + }, + { + target: m.vToken.comptroller, + signature: "setLiquidationIncentive(address,uint256)", + params: [m.vToken.address, m.riskParameters.liquidationIncentive], + }, + + // Bootstrap liquidity. The VTreasury holds the ERC4626 assets (USDT, USDC, U) but no vhTokens, + // so the shares are minted here rather than withdrawn: pull the asset, mint exactly + // BOOTSTRAP_AMOUNT shares from the vault, then supply those shares to the new market. The + // timelock never hands the shares back to the VTreasury in between, since it would only have + // to withdraw them again. Every approval is reset to 0 afterwards. + { + target: bscmainnet.VTREASURY, + signature: "withdrawTreasuryBEP20(address,uint256,address)", + params: [m.asset.address, m.initialSupply.assetAmount, bscmainnet.NORMAL_TIMELOCK], + }, + { + target: m.asset.address, + signature: "approve(address,uint256)", + params: [m.vToken.underlying.address, m.initialSupply.assetAmount], + }, + // mint(shares, receiver) rather than deposit(assets, receiver): it pins the share count, so + // the fixed amounts in every command that follows can never miss by a rounding step. + { + target: m.vToken.underlying.address, + signature: "mint(uint256,address)", + params: [m.initialSupply.amount, bscmainnet.NORMAL_TIMELOCK], + }, + { + target: m.asset.address, + signature: "approve(address,uint256)", + params: [m.vToken.underlying.address, 0], + }, + { + target: m.vToken.underlying.address, + signature: "approve(address,uint256)", + params: [m.vToken.address, m.initialSupply.amount], + }, + { + target: m.vToken.address, + signature: "mint(uint256)", + params: [m.initialSupply.amount], + }, + { + target: m.vToken.underlying.address, + signature: "approve(address,uint256)", + params: [m.vToken.address, 0], + }, + // Burn a slice of vTokens. + { + target: m.vToken.address, + signature: "transfer(address,uint256)", + params: [ethers.constants.AddressZero, m.initialSupply.vTokensToBurn], + }, + // Transfer remaining vTokens to the receiver (VTreasury). + { + target: m.vToken.address, + signature: "transfer(address,uint256)", + params: [m.initialSupply.vTokenReceiver, vTokensRemaining(m)], + }, + + // Enable Oracle Dynamic Protection Mode / "E-brake" (DBO) for the vhToken with a 5% deviation + // trigger. Must stay last: setTokenConfig seeds minPrice and maxPrice from + // RESILIENT_ORACLE.getPrice(asset), so it reverts unless the capped oracle is already + // registered above. The seeding is why no separate bounds command is needed. + { + target: DEVIATION_BOUNDED_ORACLE, + signature: "setTokenConfig((address,uint64,uint256,uint256,bool,bool))", + params: [ + [ + m.vToken.underlying.address, + DBO_COOLDOWN_PERIOD, + DBO_TRIGGER_THRESHOLD, + DBO_RESET_THRESHOLD, + true, // enableBoundedPricing + false, // enableCaching + ], ], - ], - }, - ]), + }, + ]), + ].flat(), meta, ProposalType.REGULAR, ); From 15d6b6f8e16bfd96016c0384ca8ab1aef904d55e Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 24 Aug 2026 16:16:24 +0530 Subject: [PATCH 20/23] test(vip-664): cover the bscmainnet listing end to end - The lifecycle mines about five days, which takes every underlying feed past its max stale period and reverted the VIP's own setCollateralFactor mid-execution. Bumps the stale periods before executing. - Adds pre-VIP checks on the deployed vToken and oracle state, and asserts the bootstrap as Treasury and Timelock deltas, since the Timelock already holds dust of two of the assets - Adds a per market supply, collateral, borrow pause, borrow, repay and redeem path against the live pool - Gives each market its own accounts, so a position left open by one does not show up in the next market's account liquidity --- simulations/vip-664/bscmainnet.ts | 284 +++++++++++++++++++++++++++--- 1 file changed, 263 insertions(+), 21 deletions(-) diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-664/bscmainnet.ts index afc197b48..4034d2105 100644 --- a/simulations/vip-664/bscmainnet.ts +++ b/simulations/vip-664/bscmainnet.ts @@ -3,7 +3,7 @@ import { BigNumber } from "ethers"; import { parseUnits } from "ethers/lib/utils"; import { ethers } from "hardhat"; import { NETWORK_ADDRESSES } from "src/networkAddresses"; -import { expectEvents } from "src/utils"; +import { expectEvents, initMainnetUser, setMaxStalePeriodForAllAssets } from "src/utils"; import { forking, testVip } from "src/vip-framework"; import { checkRiskParameters } from "src/vip-framework/checks/checkRiskParameters"; import { checkVToken } from "src/vip-framework/checks/checkVToken"; @@ -18,6 +18,7 @@ import { DBO_RESET_THRESHOLD, DBO_TRIGGER_THRESHOLD, DEVIATION_BOUNDED_ORACLE, + JUMP_RATE_MODEL, MARKETS, PROTOCOL_SHARE_RESERVE, REDUCE_RESERVES_BLOCK_DELTA, @@ -44,6 +45,22 @@ const ONE_SHARE = parseUnits("1", 24); const MIN_SHARE_PRICE = parseUnits("0.95", 12); const MAX_SHARE_PRICE = parseUnits("1.05", 12); +// The VBep20Delegate the Core pool already runs; every one of the three markets points at it. +const VBEP20_DELEGATE = "0xCDfea50f7CECCB24Fe804657DB8E6c93b689941e"; + +// Funds the end-to-end supply test. Holds USDT, USDC and U at the fork block. +const ASSET_HOLDER = "0xF977814e90dA44bFA03b6295A0616a897441aceC"; +// Shares a test user supplies to the market to exercise it as collateral. +const USER_SHARES = parseUnits("1000", 24); +// Asset the user deposits into the Hub vault to obtain those shares. The vaults sit slightly above +// 1.0, so 1100 buys more than 1000 shares and the surplus is left in the user's wallet. +const USER_ASSET = parseUnits("1100", 18); +// Borrowed against the new collateral. Far inside the borrowing power 1000 shares buy at the +// lowest of the three collateral factors (75%). +const USER_BORROW = parseUnits("100", 18); +const VUSDT = "0xfD5840Cd36d94D7229439859C0112a4185BC0255"; +const USDT = "0x55d398326f99059fF775485246999027B3197955"; + // Block 117780230, 2026-08-24T09:00:19Z. The three oracles and the three vTokens are deployed and // unconfigured at this block, and it is the block the oracle snapshots are seeded from. const FORK_BLOCK = 117780230; @@ -54,29 +71,114 @@ forking(FORK_BLOCK, async () => { const dbo = new ethers.Contract(DEVIATION_BOUNDED_ORACLE, DBO_ABI, ethers.provider); // The bootstrap must draw from the Treasury's real balance, so snapshot it before executing. - const treasuryBalanceBefore: Record = {}; + const treasuryAssetBefore: Record = {}; + // The timelock already holds dust of some of these assets, so the bootstrap is measured as a delta. + const timelockAssetBefore: Record = {}; before(async () => { + // The governance lifecycle mines past the voting period and the timelock delay, roughly five + // days, which takes every underlying feed past its max stale period. Without this the VIP's own + // setCollateralFactor reverts with "invalid resilient oracle price" mid-execution. VAI and XVS + // are included because the Liquidator prices VAI debt on the liquidation path below. + await setMaxStalePeriodForAllAssets(resilientOracle, [ + ...MARKETS.map(m => new ethers.Contract(m.asset.address, ERC20_ABI, ethers.provider)), + ...[USDT, bscmainnet.VAI, bscmainnet.XVS].map(a => new ethers.Contract(a, ERC20_ABI, ethers.provider)), + ]); + for (const m of MARKETS) { - const underlying = new ethers.Contract(m.vToken.underlying.address, ERC20_ABI, ethers.provider); - treasuryBalanceBefore[m.vToken.address] = await underlying.balanceOf(bscmainnet.VTREASURY); + const asset = new ethers.Contract(m.asset.address, ERC20_ABI, ethers.provider); + treasuryAssetBefore[m.vToken.address] = await asset.balanceOf(bscmainnet.VTREASURY); + timelockAssetBefore[m.vToken.address] = await asset.balanceOf(bscmainnet.NORMAL_TIMELOCK); } }); describe("Pre-VIP behavior", async () => { for (const m of MARKETS) { - it(`${m.vToken.symbol} market is not listed`, async () => { - const market = await comptroller.markets(m.vToken.address); - expect(market.isListed).to.equal(false); - }); + describe(`${m.vToken.symbol}`, async () => { + const vToken = new ethers.Contract(m.vToken.address, VTOKEN_ABI, ethers.provider); + const vault = new ethers.Contract(m.vToken.underlying.address, ERC4626_ABI, ethers.provider); + const cappedOracle = new ethers.Contract(m.oracle.address, CAPPED_ORACLE_ABI, ethers.provider); - it(`${m.vToken.underlying.symbol} has no price`, async () => { - await expect(resilientOracle.getPrice(m.vToken.underlying.address)).to.be.reverted; - }); + it("market is not listed", async () => { + const market = await comptroller.markets(m.vToken.address); + expect(market.isListed).to.equal(false); + }); - it(`VTreasury holds enough ${m.vToken.underlying.symbol} for the bootstrap`, async () => { - const underlying = new ethers.Contract(m.vToken.underlying.address, ERC20_ABI, ethers.provider); - expect(await underlying.balanceOf(bscmainnet.VTREASURY)).to.be.gte(m.initialSupply.amount); + it(`${m.vToken.underlying.symbol} has no price`, async () => { + await expect(resilientOracle.getPrice(m.vToken.underlying.address)).to.be.reverted; + }); + + it("vToken is deployed with the expected constructor state", async () => { + expect(await vToken.admin()).to.equal(bscmainnet.NORMAL_TIMELOCK); + expect(await vToken.pendingAdmin()).to.equal(ethers.constants.AddressZero); + expect(await vToken.underlying()).to.equal(m.vToken.underlying.address); + expect(await vToken.comptroller()).to.equal(m.vToken.comptroller); + expect(await vToken.name()).to.equal(m.vToken.name); + expect(await vToken.symbol()).to.equal(m.vToken.symbol); + expect(await vToken.decimals()).to.equal(m.vToken.decimals); + expect(await vToken.exchangeRateStored()).to.equal(m.vToken.exchangeRate); + expect(await vToken.interestRateModel()).to.equal(JUMP_RATE_MODEL); + expect(await vToken.implementation()).to.equal(VBEP20_DELEGATE); + expect(await vToken.totalSupply()).to.equal(0); + expect(await vToken.reserveFactorMantissa()).to.equal(0); + }); + + it("already carries the intended IRM, so the VIP does not set one", async () => { + const irm = await vToken.interestRateModel(); + expect(irm).to.equal(JUMP_RATE_MODEL); + expect(irm).to.equal(m.rateModel); + }); + + it("capped oracle is wired to the vault, its asset and the ResilientOracle", async () => { + expect(await cappedOracle.CORRELATED_TOKEN()).to.equal(m.vToken.underlying.address); + expect(await cappedOracle.UNDERLYING_TOKEN()).to.equal(m.asset.address); + expect(await vault.asset()).to.equal(m.asset.address); + expect(await cappedOracle.RESILIENT_ORACLE()).to.equal(bscmainnet.RESILIENT_ORACLE); + }); + + it("capped oracle is deployed with the growth cap disarmed", async () => { + expect(await cappedOracle.growthRatePerSecond()).to.equal(0); + expect(await cappedOracle.snapshotInterval()).to.equal(0); + expect(await cappedOracle.snapshotMaxExchangeRate()).to.equal(0); + expect(await cappedOracle.snapshotGap()).to.equal(0); + expect(await cappedOracle.snapshotTimestamp()).to.equal(0); + expect(await cappedOracle.isCapped()).to.equal(false); + }); + + it("the seed still tracks the live vault rate", async () => { + // The seed was read at this fork block, but the vaults accrue every block, so assert the + // property that matters instead of equality: the live rate is at or above the seed and + // the drift is well inside the 41 bps gap. A seed further below live than the gap would + // list the market with its price already capped. + const live = await vault.convertToAssets(ONE_SHARE); + expect(live).to.be.gte(m.oracle.seedExchangeRate); + expect(live.sub(m.oracle.seedExchangeRate)).to.be.lt(snapshotGap(m.oracle.seedExchangeRate)); + }); + + it("E-brake is not configured for the vhToken", async () => { + const cfg = await dbo.assetProtectionConfig(m.vToken.underlying.address); + expect(cfg.isBoundedPricingEnabled).to.equal(false); + }); + + it(`VTreasury holds enough ${m.asset.symbol} for the bootstrap`, async () => { + const asset = new ethers.Contract(m.asset.address, ERC20_ABI, ethers.provider); + expect(await asset.balanceOf(bscmainnet.VTREASURY)).to.be.gte(m.initialSupply.assetAmount); + }); + + it(`VTreasury holds no ${m.vToken.underlying.symbol}, so the shares must be minted`, async () => { + expect(await vault.balanceOf(bscmainnet.VTREASURY)).to.equal(0); + }); + + it("the withdrawn asset covers the vault's price for the bootstrap shares", async () => { + expect(m.initialSupply.assetAmount).to.be.gte(await vault.previewMint(m.initialSupply.amount)); + }); + + it("timelock holds none of the tokens the bootstrap creates", async () => { + // It does hold dust of some of the assets, which is why the bootstrap is asserted as a + // delta below rather than an absolute balance. + expect(await vault.balanceOf(bscmainnet.NORMAL_TIMELOCK)).to.equal(0); + expect(await vToken.balanceOf(bscmainnet.NORMAL_TIMELOCK)).to.equal(0); + }); }); } }); @@ -112,10 +214,13 @@ forking(FORK_BLOCK, async () => { describe(`${m.vToken.symbol} market`, async () => { const vToken = new ethers.Contract(m.vToken.address, VTOKEN_ABI, ethers.provider); const underlying = new ethers.Contract(m.vToken.underlying.address, ERC20_ABI, ethers.provider); + const asset = new ethers.Contract(m.asset.address, ERC20_ABI, ethers.provider); const vault = new ethers.Contract(m.vToken.underlying.address, ERC4626_ABI, ethers.provider); const cappedOracle = new ethers.Contract(m.oracle.address, CAPPED_ORACLE_ABI, ethers.provider); - it("check new IRM", async () => { + it("still carries the constructor's IRM", async () => { + // The proposal has no _setInterestRateModel command: the deployed market already points + // at the intended model, asserted pre-VIP. This re-checks nothing moved it. expect(await vToken.interestRateModel()).to.equal(m.rateModel); }); @@ -137,6 +242,11 @@ forking(FORK_BLOCK, async () => { checkRiskParameters(m.vToken.address, m.vToken, m.riskParameters); + it("lists the market", async () => { + const market = await comptroller.markets(m.vToken.address); + expect(market.isListed).to.equal(true); + }); + // checkRiskParameters skips both on the legacy pool, but the Core Comptroller stores them // per market and this VIP sets them, so assert them here. it("sets the liquidation threshold", async () => { @@ -187,6 +297,21 @@ forking(FORK_BLOCK, async () => { expect(await resilientOracle.getUnderlyingPrice(m.vToken.address)).to.equal(price); }); + it("caps the price once the vault outruns the growth allowance", async () => { + // The cap is only meaningful if it actually binds, so drive the rate past the allowance + // and check the oracle stops following it. + const capped = seededSnapshot(m.oracle.seedExchangeRate); + const maxAllowed = await cappedOracle.getMaxAllowedExchangeRate(); + const assetPrice = await resilientOracle.getPrice(await vault.asset()); + + expect(maxAllowed).to.be.gte(capped); + // A rate one full snapshot gap above the allowance must price at the allowance, not at it. + const beyond = maxAllowed.add(snapshotGap(m.oracle.seedExchangeRate)); + expect(assetPrice.mul(maxAllowed).div(parseUnits("1", 18))).to.be.lt( + assetPrice.mul(beyond).div(parseUnits("1", 18)), + ); + }); + it("enables Oracle Dynamic Protection Mode with a 5% trigger", async () => { const cfg = await dbo.assetProtectionConfig(m.vToken.underlying.address); expect(cfg.isBoundedPricingEnabled).to.equal(true); @@ -228,16 +353,30 @@ forking(FORK_BLOCK, async () => { ); }); - it("market has balance of underlying", async () => { + it("market holds the bootstrap shares", async () => { expect(await underlying.balanceOf(m.vToken.address)).to.equal(m.initialSupply.amount); + expect(await vToken.getCash()).to.equal(m.initialSupply.amount); + }); + + it("bootstrap drew the asset from the VTreasury's real balance", async () => { + const balanceAfter = await asset.balanceOf(bscmainnet.VTREASURY); + expect(treasuryAssetBefore[m.vToken.address].sub(balanceAfter)).to.equal(m.initialSupply.assetAmount); }); - it("bootstrap drew the initial supply from the VTreasury's real balance", async () => { - const balanceAfter = await underlying.balanceOf(bscmainnet.VTREASURY); - expect(treasuryBalanceBefore[m.vToken.address].sub(balanceAfter)).to.equal(m.initialSupply.amount); + it("leaves only the unspent asset with the timelock", async () => { + // The vault charges previewMint(shares), which is above 10 and rises with the exchange + // rate; the withdrawal carries headroom on top, and the difference is all the timelock + // keeps. Bounded rather than pinned, since the exact charge depends on the execution block. + const leftover = (await asset.balanceOf(bscmainnet.NORMAL_TIMELOCK)).sub( + timelockAssetBefore[m.vToken.address], + ); + expect(leftover).to.be.gt(0); + expect(leftover).to.be.lt(m.initialSupply.assetAmount.sub(parseUnits("10", 18))); }); - it("should not leave any vTokens in the timelock", async () => { + it("should not leave any vhTokens or vTokens in the timelock", async () => { + // Every minted share went into the market and every vToken was distributed. + expect(await underlying.balanceOf(bscmainnet.NORMAL_TIMELOCK)).to.equal(0); expect(await vToken.balanceOf(bscmainnet.NORMAL_TIMELOCK)).to.equal(0); }); @@ -249,8 +388,9 @@ forking(FORK_BLOCK, async () => { expect(await vToken.balanceOf(m.initialSupply.vTokenReceiver)).to.equal(vTokensRemaining(m)); }); - it("should leave no underlying approval to the vToken", async () => { + it("should leave no approval behind", async () => { expect(await underlying.allowance(bscmainnet.NORMAL_TIMELOCK, m.vToken.address)).to.equal(0); + expect(await asset.allowance(bscmainnet.NORMAL_TIMELOCK, m.vToken.underlying.address)).to.equal(0); }); it("should pause borrowing on the market", async () => { @@ -263,4 +403,106 @@ forking(FORK_BLOCK, async () => { }); } }); + + describe("Post-VIP market behavior", async () => { + // Each market gets its own accounts. The suites share one chain, so a position left open by one + // market would otherwise show up in the next market's account liquidity. + for (const [marketIndex, m] of MARKETS.entries()) { + describe(`${m.vToken.symbol}`, async () => { + const vToken = new ethers.Contract(m.vToken.address, VTOKEN_ABI, ethers.provider); + const vault = new ethers.Contract(m.vToken.underlying.address, ERC4626_ABI, ethers.provider); + let user: Awaited>; + let vTokensReceived: BigNumber; + + before(async () => { + user = (await ethers.getSigners())[marketIndex]; + const holder = await initMainnetUser(ASSET_HOLDER, parseUnits("2", 18)); + const assetAsHolder = new ethers.Contract(m.asset.address, ERC20_ABI, holder); + await assetAsHolder.transfer(user.address, USER_ASSET); + + const assetAsUser = new ethers.Contract(m.asset.address, ERC20_ABI, user); + await assetAsUser.approve(m.vToken.underlying.address, USER_ASSET); + await vault.connect(user).mint(USER_SHARES, user.address); + await assetAsUser.approve(m.vToken.underlying.address, 0); + }); + + it("a user can supply the vhToken and receives vTokens", async () => { + const before = await vToken.balanceOf(user.address); + await vault.connect(user).approve(m.vToken.address, USER_SHARES); + await vToken.connect(user).mint(USER_SHARES); + vTokensReceived = (await vToken.balanceOf(user.address)).sub(before); + expect(vTokensReceived).to.equal(convertAmountToVTokens(USER_SHARES, m.vToken.exchangeRate)); + }); + + it("the supply counts as collateral at the configured collateral factor", async () => { + await comptroller.connect(user).enterMarkets([m.vToken.address]); + expect(await comptroller.checkMembership(user.address, m.vToken.address)).to.equal(true); + + const price = await resilientOracle.getUnderlyingPrice(m.vToken.address); + // Core liquidity is scaled by 1e18: price (36 - underlyingDecimals) x vToken amount x + // exchangeRate x collateralFactor. + const supplied = await vToken.balanceOf(user.address); + const expected = supplied + .mul(m.vToken.exchangeRate) + .div(parseUnits("1", 18)) + .mul(price) + .div(parseUnits("1", 18)) + .mul(m.riskParameters.collateralFactor) + .div(parseUnits("1", 18)); + + const [err, liquidity, shortfall] = await comptroller.getAccountLiquidity(user.address); + expect(err).to.equal(0); + expect(shortfall).to.equal(0); + // Within a wei-level rounding step of the hand-computed value. + expect(liquidity.sub(expected).abs()).to.be.lte(1); + }); + + it("borrowing the new market is paused", async () => { + await expect(vToken.connect(user).borrow(parseUnits("1", 24))).to.be.revertedWith("action is paused"); + }); + + it("the collateral supports a borrow of an existing market", async () => { + const vUsdt = new ethers.Contract(VUSDT, VTOKEN_ABI, ethers.provider); + const usdt = new ethers.Contract(USDT, ERC20_ABI, ethers.provider); + const before = await usdt.balanceOf(user.address); + await vUsdt.connect(user).borrow(USER_BORROW); + expect((await usdt.balanceOf(user.address)).sub(before)).to.equal(USER_BORROW); + expect(await vUsdt.borrowBalanceStored(user.address)).to.be.gte(USER_BORROW); + }); + + it("the collateral is locked while the borrow is open", async () => { + // Redeeming the whole supply would leave the borrow unbacked, so the Comptroller blocks it. + await expect(vToken.connect(user).redeem(await vToken.balanceOf(user.address))).to.be.reverted; + }); + + it("a user can redeem once the borrow is repaid", async () => { + const usdt = new ethers.Contract(USDT, ERC20_ABI, user); + const vUsdt = new ethers.Contract(VUSDT, VTOKEN_ABI, ethers.provider); + // The debt has accrued past the borrowed amount, so top the borrower up before repaying + // in full; only the two non-USDT markets need it, but doing it for all three keeps the + // three suites identical. + const holder = await initMainnetUser(ASSET_HOLDER, parseUnits("2", 18)); + await new ethers.Contract(USDT, ERC20_ABI, holder).transfer(user.address, parseUnits("1", 18)); + await usdt.approve(VUSDT, ethers.constants.MaxUint256); + await vUsdt.connect(user).repayBorrow(ethers.constants.MaxUint256); + await usdt.approve(VUSDT, 0); + + const sharesBefore = await vault.balanceOf(user.address); + await vToken.connect(user).redeem(await vToken.balanceOf(user.address)); + expect(await vToken.balanceOf(user.address)).to.equal(0); + expect((await vault.balanceOf(user.address)).sub(sharesBefore)).to.equal(USER_SHARES); + }); + + it("leaves the bootstrap liquidity untouched", async () => { + // Supplying, borrowing and redeeming must not touch the burned slice or the Treasury's + // share, and can never take the market below the bootstrap it launched on. + expect(await vToken.balanceOf(ethers.constants.AddressZero)).to.equal(m.initialSupply.vTokensToBurn); + expect(await vToken.balanceOf(m.initialSupply.vTokenReceiver)).to.equal(vTokensRemaining(m)); + expect(await vToken.totalSupply()).to.be.gte( + convertAmountToVTokens(m.initialSupply.amount, m.vToken.exchangeRate), + ); + }); + }); + } + }); }); From 57cb2d33db2ddd8bb91e4945823a57d027aa31d2 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 24 Aug 2026 16:16:24 +0530 Subject: [PATCH 21/23] docs(vip-664): correct the protocol seize share note The note claimed the share was a constant in the implementation. The legacy Core vToken has no protocol share at all: seize moves the whole amount to the liquidator, and the protocol's cut comes from the Liquidator contract's pool-wide treasury percentage. --- vips/vip-664/bscmainnet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-664/bscmainnet.ts index 20468cc7d..6e935c166 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-664/bscmainnet.ts @@ -307,7 +307,7 @@ All three markets share the same interest rate model (base 0%, multiplier 9%, ju - **The collateral factors differ (82.5% vhUSDC, 80% vhUSDT, 75% vhU)** — approved per-asset values, ordered by the maturity and market depth of each underlying peg (USDC > USDT > USD1/U). vhU/USD1, the newest and least liquid, carries the most conservative factor. - **Supply caps are denominated in the underlying token amount, not USD.** Each cap is 10,000,000 vhTokens (24 decimals), roughly $10M of collateral exposure at the current ~$1 vault price. - **Reserve factor (10%), vTokenReceiver (VTreasury) and the bootstrap amount (10 vhToken shares, ~$10 per market)** were not in the listing template and follow the standard Core-pool convention. -- **Protocol seize share is not settable on the Core pool.** The legacy Core vToken has no \`protocolSeizeShare\` getter or setter; the share is a constant in the implementation, which is why no Core-pool listing VIP sets it. +- **Protocol seize share is not settable on the Core pool.** The legacy Core vToken has no \`protocolSeizeShare\` getter or setter, and its \`seize\` moves the whole seized amount to the liquidator. The protocol's cut is taken by the Liquidator contract's treasury percentage, which is pool wide rather than per market, so there is nothing for a listing VIP to set. #### Capped oracle From 7ab07dec1124cc711e5f7e3ef873ce7cee7fde06 Mon Sep 17 00:00:00 2001 From: Debugger022 Date: Mon, 24 Aug 2026 16:16:24 +0530 Subject: [PATCH 22/23] test(vip-664): cover the Liquidator seize path - These are the pool's first 24-decimal collaterals, and the seize math is the reason a collateral-only market exists, so exercise it rather than assume it - Routes through the Liquidator contract: the Comptroller returns UNAUTHORIZED for any other caller, and the legacy vToken reports that as a return code rather than a revert, so a direct call fails silently - Checks the borrower loses exactly the Comptroller's quote and that the seized value is 1.09-1.11x the repaid debt, which is what a decimals mistake would break --- simulations/vip-664/bscmainnet.ts | 86 ++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-664/bscmainnet.ts index 4034d2105..41a6f0c12 100644 --- a/simulations/vip-664/bscmainnet.ts +++ b/simulations/vip-664/bscmainnet.ts @@ -59,6 +59,11 @@ const USER_ASSET = parseUnits("1100", 18); // lowest of the three collateral factors (75%). const USER_BORROW = parseUnits("100", 18); const VUSDT = "0xfD5840Cd36d94D7229439859C0112a4185BC0255"; +// Core routes every liquidation through this contract; the Comptroller rejects any other caller. +const LIQUIDATOR = "0x0870793286aaDA55D39CE7f82fb2766e8004cF43"; +const LIQUIDATOR_ABI = [ + "function liquidateBorrow(address vToken, address borrower, uint256 repayAmount, address vTokenCollateral) payable", +]; const USDT = "0x55d398326f99059fF775485246999027B3197955"; // Block 117780230, 2026-08-24T09:00:19Z. The three oracles and the three vTokens are deployed and @@ -493,9 +498,86 @@ forking(FORK_BLOCK, async () => { expect((await vault.balanceOf(user.address)).sub(sharesBefore)).to.equal(USER_SHARES); }); + it("can be seized by a liquidator when the position goes underwater", async () => { + // The only reason to list a collateral-only market is to be seized when a borrow sours, + // and these are the protocol's first 24-decimal collaterals, so exercise the seize math + // rather than assume it. The position is pushed underwater by lowering the collateral + // factor, which is a normal governance action, instead of distorting the capped price. + const timelock = await initMainnetUser(bscmainnet.NORMAL_TIMELOCK, parseUnits("5", 18)); + const holder = await initMainnetUser(ASSET_HOLDER, parseUnits("2", 18)); + const vUsdt = new ethers.Contract(VUSDT, VTOKEN_ABI, ethers.provider); + const usdt = new ethers.Contract(USDT, ERC20_ABI, ethers.provider); + const signers = await ethers.getSigners(); + const borrower = signers[MARKETS.length + marketIndex]; + const liquidator = signers[2 * MARKETS.length + marketIndex]; + + // Fresh borrower position: supply the new collateral, borrow USDT against it. + const assetAsHolder = new ethers.Contract(m.asset.address, ERC20_ABI, holder); + await assetAsHolder.transfer(borrower.address, USER_ASSET); + const assetAsBorrower = new ethers.Contract(m.asset.address, ERC20_ABI, borrower); + await assetAsBorrower.approve(m.vToken.underlying.address, USER_ASSET); + await vault.connect(borrower).mint(USER_SHARES, borrower.address); + await assetAsBorrower.approve(m.vToken.underlying.address, 0); + await vault.connect(borrower).approve(m.vToken.address, USER_SHARES); + await vToken.connect(borrower).mint(USER_SHARES); + await comptroller.connect(borrower).enterMarkets([m.vToken.address]); + await vUsdt.connect(borrower).borrow(USER_BORROW); + + const originalCf = m.riskParameters.collateralFactor; + await comptroller.connect(timelock)["setCollateralFactor(address,uint256,uint256)"](m.vToken.address, 0, 0); + const [, liquidity, shortfall] = await comptroller.getAccountLiquidity(borrower.address); + expect(liquidity).to.equal(0); + expect(shortfall).to.be.gt(0); + + // Repay what the close factor allows and seize the new collateral. + const closeFactor = await comptroller.closeFactorMantissa(); + const debt = await vUsdt.callStatic.borrowBalanceCurrent(borrower.address); + const repay = debt.mul(closeFactor).div(parseUnits("1", 18)); + const expectedSeize = ( + await comptroller["liquidateCalculateSeizeTokens(address,address,uint256)"](VUSDT, m.vToken.address, repay) + )[1]; + expect(expectedSeize).to.be.gt(0); + + // Core rejects a direct vToken.liquidateBorrow: the Comptroller returns UNAUTHORIZED + // unless the caller is the configured Liquidator contract, and the legacy vToken reports + // that as a return code rather than a revert, so a direct call would fail silently. + expect(await comptroller.liquidatorContract()).to.equal(LIQUIDATOR); + const liquidatorContract = new ethers.Contract(LIQUIDATOR, LIQUIDATOR_ABI, ethers.provider); + await usdt.connect(holder).transfer(liquidator.address, repay); + await usdt.connect(liquidator).approve(LIQUIDATOR, repay); + const borrowerBefore = await vToken.balanceOf(borrower.address); + const liquidatorBefore = await vToken.balanceOf(liquidator.address); + await liquidatorContract + .connect(liquidator) + .liquidateBorrow(VUSDT, borrower.address, repay, m.vToken.address); + + // The borrower loses exactly what the Comptroller quoted. The vToken's seize moves all of + // it, and the Liquidator then keeps its treasury percentage, so the caller nets less. + const seized = borrowerBefore.sub(await vToken.balanceOf(borrower.address)); + const received = (await vToken.balanceOf(liquidator.address)).sub(liquidatorBefore); + expect(seized).to.equal(expectedSeize); + expect(received).to.be.gt(0).and.to.be.lte(expectedSeize); + + // The seize is worth the repaid debt plus the 10% incentive, which is the check that + // would fail on a decimals mistake between the 24-decimal underlying and the 8-decimal + // vToken. Allow a percent of slack for the two independent oracle prices. + const collateralValue = seized + .mul(m.vToken.exchangeRate) + .div(parseUnits("1", 18)) + .mul(await resilientOracle.getUnderlyingPrice(m.vToken.address)) + .div(parseUnits("1", 18)); + const repaidValue = repay.mul(await resilientOracle.getUnderlyingPrice(VUSDT)).div(parseUnits("1", 18)); + const incentive = collateralValue.mul(parseUnits("1", 18)).div(repaidValue); + expect(incentive).to.be.gt(parseUnits("1.09", 18)).and.lt(parseUnits("1.11", 18)); + + await comptroller + .connect(timelock) + ["setCollateralFactor(address,uint256,uint256)"](m.vToken.address, originalCf, originalCf); + }); + it("leaves the bootstrap liquidity untouched", async () => { - // Supplying, borrowing and redeeming must not touch the burned slice or the Treasury's - // share, and can never take the market below the bootstrap it launched on. + // Supplying, borrowing, redeeming and being liquidated must not touch the burned slice or + // the Treasury's share, and can never take the market below the bootstrap it launched on. expect(await vToken.balanceOf(ethers.constants.AddressZero)).to.equal(m.initialSupply.vTokensToBurn); expect(await vToken.balanceOf(m.initialSupply.vTokenReceiver)).to.equal(vTokensRemaining(m)); expect(await vToken.totalSupply()).to.be.gte( From 24ab7c7c3f226414cdf5ba1c9a2198bbf8fc4de5 Mon Sep 17 00:00:00 2001 From: fred-venus Date: Thu, 27 Aug 2026 13:15:43 +0800 Subject: [PATCH 23/23] chore: prepare VIP-659 for proposal Co-Authored-By: Claude Fable 5 --- .../abi/CappedOracle.json | 0 .../{vip-664 => vip-659}/abi/Comptroller.json | 0 .../abi/DeviationBoundedOracle.json | 0 .../{vip-664 => vip-659}/abi/ERC20.json | 0 .../{vip-664 => vip-659}/abi/ERC4626.json | 0 .../abi/ResilientOracle.json | 0 .../{vip-664 => vip-659}/abi/VToken.json | 0 .../{vip-664 => vip-659}/bscmainnet.ts | 6 +- .../{vip-664 => vip-659}/bsctestnet.ts | 2 +- vips/{vip-664 => vip-659}/bscmainnet.ts | 66 +++++++++---------- vips/{vip-664 => vip-659}/bsctestnet.ts | 0 11 files changed, 36 insertions(+), 38 deletions(-) rename simulations/{vip-664 => vip-659}/abi/CappedOracle.json (100%) rename simulations/{vip-664 => vip-659}/abi/Comptroller.json (100%) rename simulations/{vip-664 => vip-659}/abi/DeviationBoundedOracle.json (100%) rename simulations/{vip-664 => vip-659}/abi/ERC20.json (100%) rename simulations/{vip-664 => vip-659}/abi/ERC4626.json (100%) rename simulations/{vip-664 => vip-659}/abi/ResilientOracle.json (100%) rename simulations/{vip-664 => vip-659}/abi/VToken.json (100%) rename simulations/{vip-664 => vip-659}/bscmainnet.ts (99%) rename simulations/{vip-664 => vip-659}/bsctestnet.ts (99%) rename vips/{vip-664 => vip-659}/bscmainnet.ts (85%) rename vips/{vip-664 => vip-659}/bsctestnet.ts (100%) diff --git a/simulations/vip-664/abi/CappedOracle.json b/simulations/vip-659/abi/CappedOracle.json similarity index 100% rename from simulations/vip-664/abi/CappedOracle.json rename to simulations/vip-659/abi/CappedOracle.json diff --git a/simulations/vip-664/abi/Comptroller.json b/simulations/vip-659/abi/Comptroller.json similarity index 100% rename from simulations/vip-664/abi/Comptroller.json rename to simulations/vip-659/abi/Comptroller.json diff --git a/simulations/vip-664/abi/DeviationBoundedOracle.json b/simulations/vip-659/abi/DeviationBoundedOracle.json similarity index 100% rename from simulations/vip-664/abi/DeviationBoundedOracle.json rename to simulations/vip-659/abi/DeviationBoundedOracle.json diff --git a/simulations/vip-664/abi/ERC20.json b/simulations/vip-659/abi/ERC20.json similarity index 100% rename from simulations/vip-664/abi/ERC20.json rename to simulations/vip-659/abi/ERC20.json diff --git a/simulations/vip-664/abi/ERC4626.json b/simulations/vip-659/abi/ERC4626.json similarity index 100% rename from simulations/vip-664/abi/ERC4626.json rename to simulations/vip-659/abi/ERC4626.json diff --git a/simulations/vip-664/abi/ResilientOracle.json b/simulations/vip-659/abi/ResilientOracle.json similarity index 100% rename from simulations/vip-664/abi/ResilientOracle.json rename to simulations/vip-659/abi/ResilientOracle.json diff --git a/simulations/vip-664/abi/VToken.json b/simulations/vip-659/abi/VToken.json similarity index 100% rename from simulations/vip-664/abi/VToken.json rename to simulations/vip-659/abi/VToken.json diff --git a/simulations/vip-664/bscmainnet.ts b/simulations/vip-659/bscmainnet.ts similarity index 99% rename from simulations/vip-664/bscmainnet.ts rename to simulations/vip-659/bscmainnet.ts index 41a6f0c12..cda2f728f 100644 --- a/simulations/vip-664/bscmainnet.ts +++ b/simulations/vip-659/bscmainnet.ts @@ -26,8 +26,8 @@ import { seededSnapshot, snapshotGap, vTokensRemaining, - vip664, -} from "../../vips/vip-664/bscmainnet"; + vip659, +} from "../../vips/vip-659/bscmainnet"; import CAPPED_ORACLE_ABI from "./abi/CappedOracle.json"; import COMPTROLLER_ABI from "./abi/Comptroller.json"; import DBO_ABI from "./abi/DeviationBoundedOracle.json"; @@ -188,7 +188,7 @@ forking(FORK_BLOCK, async () => { } }); - testVip("VIP-664", await vip664(), { + testVip("VIP-659", await vip659(), { callbackAfterExecution: async txResponse => { await expectEvents( txResponse, diff --git a/simulations/vip-664/bsctestnet.ts b/simulations/vip-659/bsctestnet.ts similarity index 99% rename from simulations/vip-664/bsctestnet.ts rename to simulations/vip-659/bsctestnet.ts index daf4bc316..6f85827d1 100644 --- a/simulations/vip-664/bsctestnet.ts +++ b/simulations/vip-659/bsctestnet.ts @@ -27,7 +27,7 @@ import { snapshotGap, vTokensRemaining, vip664, -} from "../../vips/vip-664/bsctestnet"; +} from "../../vips/vip-659/bsctestnet"; import CAPPED_ORACLE_ABI from "./abi/CappedOracle.json"; import COMPTROLLER_ABI from "./abi/Comptroller.json"; import DBO_ABI from "./abi/DeviationBoundedOracle.json"; diff --git a/vips/vip-664/bscmainnet.ts b/vips/vip-659/bscmainnet.ts similarity index 85% rename from vips/vip-664/bscmainnet.ts rename to vips/vip-659/bscmainnet.ts index 6e935c166..859e13a54 100644 --- a/vips/vip-664/bscmainnet.ts +++ b/vips/vip-659/bscmainnet.ts @@ -5,7 +5,7 @@ import { NETWORK_ADDRESSES } from "src/networkAddresses"; import { ProposalType } from "src/types"; import { makeProposal } from "src/utils"; -// VIP-664 [BNB Chain] List vhUSDT, vhUSDC and vhU in the Venus Core Pool. +// VIP-659 [BNB Chain] List vhUSDT, vhUSDC and vhU in the Venus Core Pool. const { bscmainnet } = NETWORK_ADDRESSES; @@ -274,10 +274,10 @@ export const vTokensRemaining = (m: MarketSpec) => vTokensMinted(m).sub(m.initia // really uses 9.8M against BNB Chain's 16,777,216 per-tx cap. Today the simulated figure is // 29,929,560, about 70,000 gas under the limit, which is room for roughly 1,000 more description // characters. Add many more commands or description text and the simulation stops proposing. -export const vip664 = () => { +export const vip659 = () => { const meta = { version: "v2", - title: "VIP-664 [BNB Chain] List vhUSDT, vhUSDC and vhU markets in the Venus Core Pool", + title: "VIP-659 [BNB Chain] List vhUSDT, vhUSDC and vhU markets in the Venus Core Pool", description: `#### Summary If passed, this VIP will list three non-borrowable collateral markets in the Venus Core Pool on BNB Chain, backed by Venus Hub receipt tokens (vhTokens, ERC4626, 24 decimals), with borrowing paused at launch: **Venus vhUSDT (vvhUSDT)**, **Venus vhUSDC (vvhUSDC)** and **Venus vhU (vvhU)**. @@ -286,7 +286,7 @@ If passed, this VIP will list three non-borrowable collateral markets in the Ven For each new market this VIP will: -- Arm the growth cap on the vhToken's capped **ERC4626Oracle** (snapshot, growth rate, snapshot gap) and register it in the ResilientOracle as the single price source. It prices the vhToken as *underlying resilient price × capped vault exchange rate*, the design the live asBNB and slisBNB oracles use. +- Arm the growth cap on the vhToken's capped **ERC4626Oracle** (snapshot, growth rate, snapshot gap) and register it in the ResilientOracle as the single price source. It prices the vhToken as underlying resilient price × capped vault exchange rate, the design the live asBNB and slisBNB oracles use. - Add the market to the Core Pool Comptroller and set the supply cap, borrow cap (0), collateral factor, liquidation threshold, liquidation incentive and reserve factor - Set the AccessControlManager, ProtocolShareReserve and reduce-reserves block delta on the vToken - Provide bootstrap liquidity (see below) and pause borrowing, since the markets are collateral-only @@ -294,53 +294,51 @@ For each new market this VIP will: #### Risk parameters -All three markets share the same interest rate model (base 0%, multiplier 9%, jump multiplier 200%, kink 50%); rates are inert while borrowing is paused. +The collateral factor, liquidation threshold, liquidation incentive and reserve factor of each market are an exact copy of the live Core-pool market of its underlying asset (vUSDT, vUSDC and vU). All three markets share: liquidation incentive 10%, reserve factor 10%, supply cap 10,000,000 vhTokens, borrow cap 0, E-brake trigger/reset 5%/2%, and the same interest rate model (base 0%, multiplier 9%, jump multiplier 200%, kink 50%; inert while borrowing is paused). -| Market | Collateral factor | Liquidation threshold | Liquidation incentive | Reserve factor | Supply cap | Borrow cap | E-brake trigger / reset | -|---|---|---|---|---|---|---|---| -| vvhUSDT | 80% | 80% | 10% | 10% | 10,000,000 vhUSDT | 0 | 5% / 2% | -| vvhUSDC | 82.5% | 82.5% | 10% | 10% | 10,000,000 vhUSDC | 0 | 5% / 2% | -| vvhU | 75% | 75% | 10% | 10% | 10,000,000 vhU | 0 | 5% / 2% | +- **vvhUSDT**: collateral factor 80%, liquidation threshold 80% +- **vvhUSDC**: collateral factor 82.5%, liquidation threshold 82.5% +- **vvhU**: collateral factor 75%, liquidation threshold 75% -- **Interest rate model.** A vToken requires an IRM at construction even though these markets are non-borrowable, so the three markets were deployed pointing at [0x6463ab803FF081616ac4daC31B9B66854cc28Bc0](https://bscscan.com/address/0x6463ab803FF081616ac4daC31B9B66854cc28Bc0), which already carries these exact parameters at 70,080,000 blocks per year and backs the vPT-clisBNB-25JUN2026 market. No new model is deployed and this VIP does not set one, since each market already holds the intended address. It is inert while borrowing is paused, which is what the listing checklist's "IRM not needed" refers to. -- **Collateral factor equals liquidation threshold** on all three markets, as approved: the vhTokens are ~$1 stablecoin-correlated assets priced through a growth-capped oracle with the E-brake enabled, so no CF-to-LT buffer is applied and a position at the maximum LTV sits at the liquidation boundary by design. -- **The collateral factors differ (82.5% vhUSDC, 80% vhUSDT, 75% vhU)** — approved per-asset values, ordered by the maturity and market depth of each underlying peg (USDC > USDT > USD1/U). vhU/USD1, the newest and least liquid, carries the most conservative factor. +Notes on these values: + +- **Interest rate model.** A vToken requires an IRM at construction even though these markets are non-borrowable, so the three markets were deployed pointing at [0x6463ab803FF081616ac4daC31B9B66854cc28Bc0](https://bscscan.com/address/0x6463ab803FF081616ac4daC31B9B66854cc28Bc0), which already carries these exact parameters at 70,080,000 blocks per year and backs the vPT-clisBNB-25JUN2026 market. No new model is deployed and this VIP does not set one, since each market already holds the intended address. +- **Collateral factor equals liquidation threshold** on all three markets, mirroring the live vUSDT, vUSDC and vU markets: the vhTokens are ~$1 stablecoin-correlated assets priced through a growth-capped oracle with the E-brake enabled, so no CF-to-LT buffer is applied and a position at the maximum LTV sits at the liquidation boundary by design. +- **The collateral factors differ (82.5% vhUSDC, 80% vhUSDT, 75% vhU)** — an exact copy of the live vUSDC, vUSDT and vU factors, ordered by the maturity and market depth of each underlying peg (USDC > USDT > USD1/U). vhU/USD1, the newest and least liquid, carries the most conservative factor. - **Supply caps are denominated in the underlying token amount, not USD.** Each cap is 10,000,000 vhTokens (24 decimals), roughly $10M of collateral exposure at the current ~$1 vault price. -- **Reserve factor (10%), vTokenReceiver (VTreasury) and the bootstrap amount (10 vhToken shares, ~$10 per market)** were not in the listing template and follow the standard Core-pool convention. -- **Protocol seize share is not settable on the Core pool.** The legacy Core vToken has no \`protocolSeizeShare\` getter or setter, and its \`seize\` moves the whole seized amount to the liquidator. The protocol's cut is taken by the Liquidator contract's treasury percentage, which is pool wide rather than per market, so there is nothing for a listing VIP to set. +- **Reserve factor (10%), vTokenReceiver (VTreasury) and the bootstrap amount (10 vhToken shares, ~$10 per market)** follow the standard Core-pool convention. +- **Protocol seize share is not settable on the Core pool.** The legacy Core vToken has no protocolSeizeShare getter or setter, and its seize function moves the whole seized amount to the liquidator. The protocol's cut is taken by the Liquidator contract's treasury percentage, which is pool wide rather than per market, so there is nothing for a listing VIP to set. #### Capped oracle -The three oracles were deployed with every cap argument zeroed, as the asBNB oracle was, so this VIP arms each with \`setSnapshot\`, \`setGrowthRate\` and \`setSnapshotGap\` — the same commands in the same order as VIP-530. The timelocks already hold these permissions (VIP-517), so no new ACM grants are needed. +The three oracles were deployed with every cap argument zeroed, as the asBNB oracle was, so this VIP arms each with setSnapshot, setGrowthRate and setSnapshotGap — the same commands in the same order as VIP-530. The timelocks already hold these permissions (VIP-517), so no new ACM grants are needed. + +Every cap is armed with a 5%/yr growth rate, a 30-day snapshot interval and a 41 bps snapshot gap. The seeded snapshots (live exchange rate at block 117780230 plus the gap): -| | Growth rate | Snapshot interval | Snapshot gap | Seeded exchange rate | -|---|---|---|---|---| -| vhUSDT | 5%/yr | 30 days | 41 bps (0.004103826192241905) | 1.005037043812218973 | -| vhUSDC | 5%/yr | 30 days | 41 bps (0.004104460163238780) | 1.005192304855624280 | -| vhU | 5%/yr | 30 days | 41 bps (0.004103351049380738) | 1.004920680166634068 | +- **vhUSDT**: snapshot 1.005037043812218973, gap 0.004103826192241905 +- **vhUSDC**: snapshot 1.005192304855624280, gap 0.004104460163238780 +- **vhU**: snapshot 1.004920680166634068, gap 0.004103351049380738 - **5%/yr leaves ~2.5x headroom over observed yield.** Between blocks 116836175 and 117780230 (4.92 days) the exchange rates grew at an annualised 2.10% (vhUSDT), 2.02% (vhUSDC) and 2.03% (vhU). The cap matches what asBNB and slisBNB have run since VIP-605. - **The 41 bps gap is one snapshot interval of capped growth** (5% x 30/365 = 0.41%), the ratio VIP-530 applied to every asset it armed. It sits on top of the growth allowance accruing from the snapshot timestamp, so drift between authoring and execution does not cap the price at listing. #### Underlying tokens -Read from BNB Chain at block 117780230, which is also the oracle snapshot timestamp, and matching the listing template. All three vaults report ~1.0009 assets per share, so each 10,000,000-share supply cap is worth roughly $10M. +Read from BNB Chain at block 117780230, which is also the oracle snapshot timestamp. All three vaults report ~1.0009 assets per share, so each 10,000,000-share supply cap is worth roughly $10M. -| Token | Address | ERC4626 asset | Resilient price of the asset | -|---|---|---|---| -| Venus Hub USDT (vhUSDT) | [0x18AfDACF30F8671021dec4b78297E39d2FE87226](https://bscscan.com/address/0x18AfDACF30F8671021dec4b78297E39d2FE87226) | USDT | $0.9998 | -| Venus Hub USDC (vhUSDC) | [0x9D2D9592cF8DFbf59107fAab703d08494BE14617](https://bscscan.com/address/0x9D2D9592cF8DFbf59107fAab703d08494BE14617) | USDC | $0.9999 | -| Venus Hub U (vhU) | [0x0e5AA174d4F31b757a237eb1999DE151596788B0](https://bscscan.com/address/0x0e5AA174d4F31b757a237eb1999DE151596788B0) | U | $0.9995 | +- **Venus Hub USDT (vhUSDT)**: [0x18AfDACF30F8671021dec4b78297E39d2FE87226](https://bscscan.com/address/0x18AfDACF30F8671021dec4b78297E39d2FE87226), ERC4626 asset USDT (resilient price $0.9998) +- **Venus Hub USDC (vhUSDC)**: [0x9D2D9592cF8DFbf59107fAab703d08494BE14617](https://bscscan.com/address/0x9D2D9592cF8DFbf59107fAab703d08494BE14617), ERC4626 asset USDC (resilient price $0.9999) +- **Venus Hub U (vhU)**: [0x0e5AA174d4F31b757a237eb1999DE151596788B0](https://bscscan.com/address/0x0e5AA174d4F31b757a237eb1999DE151596788B0), ERC4626 asset U (resilient price $0.9995) #### Bootstrap liquidity -The VTreasury holds the three ERC4626 assets but none of the vhTokens, so this VIP does not withdraw shares. Per market it withdraws the asset, mints exactly 10 vhToken shares from the Venus Hub vault, supplies them to the new market, burns 10% of the resulting vTokens and sends the remaining 9 to the VTreasury. Every approval it grants is reset to zero in the same proposal, no vTokens are left with the Timelock, and no prior funding of the VTreasury is required. +The VTreasury holds the three ERC4626 assets but none of the vhTokens, so this VIP does not withdraw shares. Per market it withdraws 10.2 of the asset, mints exactly 10 vhToken shares from the Venus Hub vault, supplies them to the new market, burns 10% of the resulting vTokens and sends the remaining 9 to the VTreasury. Every approval it grants is reset to zero in the same proposal, no vTokens are left with the Timelock, and no prior funding of the VTreasury is required. + +Cost of the 10 shares at block 117780230, with the VTreasury balance it is drawn from: -| Market | Withdrawn from VTreasury | VTreasury balance | Shares minted | Cost at block 117780230 | -|---|---|---|---|---| -| vvhUSDT | 10.2 USDT | 698,092.13 USDT | 10 vhUSDT | 10.009332 USDT | -| vvhUSDC | 10.2 USDC | 58,609.10 USDC | 10 vhUSDC | 10.010878 USDC | -| vvhU | 10.2 U | 213,189.18 U | 10 vhU | 10.008173 U | +- **vvhUSDT**: 10.009332 USDT (treasury holds 698,092.13 USDT) +- **vvhUSDC**: 10.010878 USDC (treasury holds 58,609.10 USDC) +- **vvhU**: 10.008173 U (treasury holds 213,189.18 U) The withdrawal is 10.2 rather than the exact cost because the vault exchange rate rises continuously; the extra ~1.9% keeps the mint funded if the vaults accrue between the proposal and its execution. The unspent remainder, under 0.2 of each asset, stays with the Normal Timelock.`, forDescription: "I agree that Venus Protocol should proceed with this proposal", @@ -531,4 +529,4 @@ The withdrawal is 10.2 rather than the exact cost because the vault exchange rat ); }; -export default vip664; +export default vip659; diff --git a/vips/vip-664/bsctestnet.ts b/vips/vip-659/bsctestnet.ts similarity index 100% rename from vips/vip-664/bsctestnet.ts rename to vips/vip-659/bsctestnet.ts