Skip to content

VDB-54: draft: VIP-664 list vhUSDT, vhUSDC, vhU in the Venus Core Pool - #754

Open
trumpgpt-bot wants to merge 12 commits into
mainfrom
bot/VDB-54-draft-vip-to-list-vhusdt-vhusdc-vhu-in-venus-core-pool
Open

VDB-54: draft: VIP-664 list vhUSDT, vhUSDC, vhU in the Venus Core Pool#754
trumpgpt-bot wants to merge 12 commits into
mainfrom
bot/VDB-54-draft-vip-to-list-vhusdt-vhusdc-vhu-in-venus-core-pool

Conversation

@trumpgpt-bot

@trumpgpt-bottrumpgpt-bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Draft VIP-664 listing three non-borrowable collateral markets in the BNB Core pool, backed by Venus Hub receipt tokens (24-decimal ERC4626): vvhUSDT / vvhUSDC / vvhU. Mainnet and testnet VIPs plus both fork simulations. tsc 0 errors, eslint and prettier clean. The mainnet proposal builds to 66 commands, 22 per market. The testnet proposal lists one market and builds to 25.

Companion deploy branch: VenusProtocol/oracle@feat/vh-capped-oracles.

Confirmed on chain

vhUSDTvhUSDCvhU
Address0x18AfDACF30F8671021dec4b78297E39d2FE872260x9D2D9592cF8DFbf59107fAab703d08494BE146170x0e5AA174d4F31b757a237eb1999DE151596788B0
Name / symbol / decimalsVenus Hub USDT / vhUSDT / 24Venus Hub USDC / vhUSDC / 24Venus Hub U / vhU / 24
ERC4626 asset()USDT 0x55d3…7955USDC 0x8AC7…580dU 0xcE24…6666
Exchange rate1.0006501.0008161.000544
Measured growth1.82%/yr2.29%/yr1.52%/yr
Total supply2.70M0.30M10.00
Supply cap vs supply3.7×33×~1,000,000×
  • vhUSDT's address is confirmed — it was struck through in the source checklist. The contract is the Venus Hub USDT vault with the right name, decimals and underlying.
  • Supply cap unit is the underlying share amount, not USD. U trades at ~$1, so all three caps are ~$10M.
  • 24-decimal underlyings are safe through the oracle.CorrelatedTokenOracle._calculatePrice divides by 10 ** correlatedToken.decimals(), so a 24-decimal share returns a price scaled to 10**12 — exactly Venus's 10**(36-decimals) convention.
  • E-brake 5% / 2% / 1h is valid: DeviationBoundedOracle enforces MIN_THRESHOLD = 5e16, MAX_THRESHOLD = 50e16, reset below trigger, cooldown non-zero. 5% is the contract minimum. VIP-633's 16.67% is an equity setting and is not reused.

Two defects found and fixed

1. The cap was never going to be armed. The capped ERC4626 oracles deploy with every cap parameter zeroed — the same way the asBNB oracle was deployed — and getPrice skips capping entirely while snapshotMaxExchangeRate is 0. The earlier draft assumed the deploy would arm it, so the markets would have listed with an uncapped price source and nothing would have flagged it. VIP-530 is the precedent for arming, and this VIP now does it per market: setSnapshot, then setGrowthRate, then setSnapshotGap. Permissions already sit with the Normal, Fast-Track and Critical timelocks from VIP-517, so no new ACM grants are needed.

The order is load-bearing. updateSnapshot() on an oracle whose snapshot is still 0 computes min(rate, 0) + gap and collapses the cap to the gap alone — about 0.4% of the true rate. setSnapshot must come first.

2. snapshotInterval = MaxUint256 was wrong. The first draft called it a "frozen snapshot", but with that value updateSnapshot() can never fire and the cap grows unbounded from the seed forever. Both live BNB Chain capped oracles use a 30-day interval.

Growth rateIntervalGapSeeded rate
vhUSDT5%/yr30 days41 bps1.004752665021436793
vhUSDC5%/yr30 days41 bps1.004918973574029229
vhU5%/yr30 days41 bps1.004646448325306769

The 41 bps gap is one snapshot interval of capped growth (5% × 30/365 = 0.41%) — the 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. Seeds were read at block 116836175 (2026-08-19T10:57:40Z), which is also the snapshot timestamp.

Also in this branch

  • No new interest rate model.0x1Ef3b851… already carries base 0 / multiplier 9% / jump 200% / kink 50% and already backs vasBNB and vslisBNB. Testnet has the mirror at 0x8734dBD8…. Three deploys avoided.
  • Explicit liquidation-threshold and liquidation-incentive assertions.checkRiskParameters skips both when isLegacyPool is true, so a core-pool listing that sets them otherwise gets no coverage.
  • bsctestnet VIP + simulation, running the real capped-oracle path rather than a mocked direct price, so the cap is exercised before mainnet. See below; it is deployed and its simulation passes.

Price protection, and E-mode

Two independent layers, both configured here:

  1. Capped rate — the ERC4626Oracle caps the vault exchange rate at 5%/yr over a 30-day snapshot, so a manipulated or ballooning rate cannot lift the collateral price. Armed by this VIP (above).
  2. E-brakeDeviationBoundedOracle bounds the price with a 5% deviation trigger, 2% reset and 1h cooldown. Its setTokenConfig seeds minPrice and maxPrice from RESILIENT_ORACLE.getPrice(asset), so it is the last command in each market block: it reverts if the capped oracle is not registered first. That self-seeding is also why no separate bounds command is needed.

E-mode is out of scope, per the listing checklist. The Core Comptroller's pool-scoped overloads — setCollateralFactor(uint96,…), setLiquidationIncentive(uint96,…), addPoolMarkets, setIsBorrowAllowed — are not called; the built proposal contains zero uint96 commands, so all three markets land in the base pool. Non-borrowable is therefore enforced with setActionsPaused(BORROW), because setIsBorrowAllowed exists only in the pool-scoped form.

addMarket completeness

The command list was diffed against every recent Core-pool listing (VIP-554, 581, 584, 596, 633, 643). Two commands those listings issue were being left to defaults and are now explicit:

  • _setMarketBorrowCaps — a fresh market defaults to 0, but the borrow cap is a stated risk parameter, and the simulation was asserting a default rather than a decision.
  • _setInterestRateModel — the model was trusted from the vToken constructor. Since this reuses an already deployed model instead of deploying one per market, the VIP now sets it.

Deliberately absent, each checked rather than assumed:

  • Protocol seize share — the legacy Core vToken exposes no protocolSeizeShare getter or setter; it is a constant in the implementation. No Core-pool listing sets it.
  • _acceptAdmin — not needed if the vTokens are constructed with admin = NormalTimelock, which is how VIP-643's market was deployed. Tracked in the todo.
  • setValidateConfig (BoundValidator) — only meaningful with a pivot oracle. These markets use a single main source, as asBNB does.
  • setTokenConfig((address,address,uint256)) — a Chainlink/Atlas-style feed config. The capped ERC4626Oracle derives its price from the vault and needs no feed.
  • setConversionConfigs — token-converter routing for reserves. Tracked in the todo as a follow-up.

bsctestnet

Testnet lists one market, not three. Hub_USDT is the only Venus Hub vault deployed there, 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 in the testnet VIP is redenominated accordingly, and the exchange rate is 1e22 rather than 1e34.

Both contracts are deployed and verified:

address
vSHARE_ERC4626Oracle0xB5A17EB6A135A61057bc2F4f102c633899256BFa
vvSHARE vToken0xb846eEbaC8b014296709dc660Bfcb6ea182718e8

The vToken reuses VBep20Delegate0x73fF7509…, the implementation behind the live vasBNB and vslisBNB markets, and its admin is set to the NormalTimelock in the constructor, so no _acceptAdmin is needed. Risk parameters match mainnet's vhUSDT market: CF and LT 80%, supply cap 10M, borrow cap 0, reserve factor 10%, liquidation incentive 10%, cap 5%/yr over 30 days with a 41 bps gap, E-brake 5% / 2% / 1h.

The bootstrap no longer needs the Treasury pre-funded. The VTreasury holds testnet USDT but no vSHARE, so withdrawTreasuryBEP20(vSHARE, …) would revert. The testnet VIP now withdraws USDT, mints exactly 100 vSHARE from Hub_USDT, and supplies those to the new market. It calls the vault's mint(shares, receiver) rather than deposit(assets, receiver), which pins the share count so the amounts hardcoded in the VIP cannot drift with the vault rate. The timelock keeps neither the asset nor the shares, and both approvals are cleared.

Simulation: 75 passing, 0 failing, all 25 commands executing, at fork block 126331000. The ChainlinkOracle stale period is relaxed in before(), since a pinned fork block goes stale against the feed's 24h window and every getPrice call would revert.

Proposed, queued and executed on bsctestnet as proposal 713.

steptxgas used
propose0x81b3cb16…3,975,678
queue0x2f0b8297…1,393,251
execute0x28fc694d…3,121,454

Every gas figure matches the local simulation exactly, and state(713) reads 7 (Executed). Reads against the live market after execution:

  • markets(vvSHARE) listed with CF 0.80; supply cap 10,000,000 vSHARE; borrow cap 0 with the BORROW action paused; reserve factor 10%; interest rate model 0x8734dBD8…
  • vvSHARE.totalSupply is 100, of which 10 sit at the zero address and 90 at the vTokenReceiver; exchangeRateStored is 1e22
  • ResilientOracle.getPrice(vSHARE) returns 5e23 ($0.50), with the main oracle set to 0xB5A17EB6… and flags true / false / false
  • The capped oracle is armed: snapshotMaxExchangeRate 1004100, which is the 1,000,000 seed plus the 41 bps gap, at snapshotTimestamp 1787137060

Todo

Blocked on deploys and funding:

  • Fund VTreasury with >=100 each of vhUSDT, vhUSDC, vhU — it holds none; the underlyings are there. Mainnet only: the testnet VIP mints its own shares out of the Treasury's USDT, and mainnet can take the same route.
  • Deploy the 3 mainnet capped oracles (oracle#320) → fill VHUSDT/VHUSDC/VHU_ORACLE. The testnet one is deployed and its artifact is committed on that PR.
  • Deploy the 3 mainnet vTokens, admin = NormalTimelock → fill VVHUSDT/VVHUSDC/VVHU. The testnet vToken is deployed and filled in.
  • Bump the mainnet FORK_BLOCK and run its simulation green. Testnet is done: 75 passing at block 126331000.

Decisions needed:

  • CF == LT (80/80, 82.5/82.5, 75/75) — a max-LTV position opens on the liquidation boundary
  • Cite the source for the 82.5 / 80 / 75 split, or record a per-asset ACK
  • 41 bps gap and 5%/yr growth rate — derived from VIP-530's ratio, not handed down
  • vhU's cap is 100% of its current supply (10M of 10.00M); vhUSDT 3.7x, vhUSDC 33x
    • Note on vhU, read on chain: totalSupply is 10.000000040 shares and 10 of those sit at 0x000000000000000000000000000000000000dEaD, so the vault is effectively empty and unused. totalAssets is 10.0065 U.
    • That makes the 10M cap roughly 1,000,000x the entire vault rather than 1.0x, and it leaves the 100-share bootstrap unfundable until someone deposits U into the Hub.
  • 5%/yr rests on ~13 days of vault history

Outside this repo:

  • Backend: market records in venus-protocol-api (Phase 3 BE, unstarted)
  • Front-end token records + Vercel QA environment
  • Register addresses in venus-protocol-documentation
  • Deviation sentinel + HashDit canary in venus-monitoring — vhUSDC's block is the unticked one
  • Token icons for all three
  • Token-converter conversion configs (setConversionConfigs) for the three vhTokens
  • Delete the struck-through 5% e-mode liquidation-incentive tables from the source checklist

Not blocking: CI runs ./tests only, so the deploy-gated simulations don't affect it. The testnet simulation now passes end to end; the mainnet one forks and runs, with every remaining failure tracing to the zero addresses plus the unfunded Treasury.

Circular Flow: Users can supply USDT to Venus Hub, receive vhUSDT, then use vhUSDT as collateral in the Core Pool to borrow USDT. This circular flow is expected and is allowed as part of the intended integration.

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).
@trumpgpt-bot
trumpgpt-bot marked this pull request as ready for review August 19, 2026 05:53
…view
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 <noreply@anthropic.com>
@vancegpt-bot

This comment was marked as outdated.

trumpgpt-botand others added 3 commits August 19, 2026 08:44
…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 <noreply@anthropic.com>
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.
Deploy-gated: the oracle, vToken and IRM addresses are still zero, so the
run fails on them. CI runs ./tests only, not ./simulations.
@Debugger022
Debugger022force-pushed the bot/VDB-54-draft-vip-to-list-vhusdt-vhusdc-vhu-in-venus-core-pool branch from ca89111 to f29431fCompareAugust 19, 2026 10:45
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.
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.
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.
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.
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.
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.
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.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@trumpgpt-bot@vancegpt-bot@Debugger022