feat: add VIP for oracle adjustments, VAI vault rewards stop and reserve cleanup - #765
Conversation
Atlas moves up to the pivot role on Core Pool markets where it is the fallback today. VAI gets priced from Atlas only, and XVS rewards to the VAI Vault stop. - Configure the Atlas VAI/USD feed with an 86700s max stale period - Swap pivot and fallback to [Chainlink, Atlas, RedStone] on 13 markets - Set VAI to [Atlas, none, none] in ResilientOracle - Set the VAI Vault XVS rate to 0 - Add a script that writes every Core market's oracle config and the list of markets the VIP updates to JSON, which the VIP reads - Add a fork simulation covering configs, events, prices and the rate
RiskFund cannot receive BNB, so unstaked BNB is wrapped to WBNB.
Greptile SummaryAdds VIP-668 for BNB Chain, combining Core Pool oracle reconfiguration, termination of VAI Vault emissions, Treasury VAI conversion, and cleanup of Risk Fund assets.
Confidence Score: 4/5The proposal is not safe to merge until failed PSM conversions are prevented from silently leaving the Treasury’s VAI in the distributor. The conversion helper intentionally catches PSM failures, but this proposal omits the fallback distribution used by its precedent, allowing a successful governance execution to strand the withdrawn Treasury VAI. Files Needing Attention: vips/vip-668/bscmainnet.ts Important Files Changed
Reviews (1): Last reviewed commit: "feat(vip-668): send risk fund slisbnb to..." | Re-trigger Greptile |
vancegpt-bot
left a comment
There was a problem hiding this comment.
Reviewed the script, the simulation and the on-chain state it depends on. I ran the simulation myself and re-checked every balance, config and address against bscmainnet at head (block 122155716), not just at the snapshot block.
Verdict: the proposal does what it says and is safe to execute as written. The items below are about the on-chain description — the text voters read — being more certain than the code in three places, plus a couple of things worth knowing before execution. None changes a command.
Verified
| Claim | Check at head |
|---|---|
| Simulation | npx hardhat test simulations/vip-668/bscmainnet.ts --fork bscmainnet → 112 passing in my run (the CI Test job is 46s and does not run fork simulations, so the green check is not evidence for this) |
13 markets are [Chainlink, RedStone, Atlas], all enabled, caching off |
all 13 match getTokenConfig at head; VAI is [Chainlink, Binance, 0] [true,true,false] |
| Atlas has no VAI config yet | tokenConfigs(VAI) is zero |
| Atlas will validate Chainlink once it is pivot | BoundValidator.validatePriceWithAnchorPrice is true for all 13 at head; widest deviation is XRP at −2.16%, XVS +1.23%, the rest under 0.5% |
venusVAIVaultRate = 341157958083832; Comptroller admin = Normal Timelock |
both true |
Every sweepToken amount is the exact RiskFund balance |
all 11 exact at head (7 long-tail + ankrBNB, BNBx, slisBNB; stkBNB = amount + the 201756396971 wei dust). RiskFund and VTreasury owner = Normal Timelock |
| VTreasury VAI ≈ 2,343.39 | 2343390747608546723775 wei |
WBNB_AMOUNT still exact |
sharesToBonds, convertBnbXToBnb and pStake exchangeRate return the same values at head as at 122009038 — none of the three rates has moved since the snapshot. Ankr flash fee is 25 bps and the pool holds 54.6 BNB of flash liquidity |
| Recipients | distributor is the VIP-646 contract (convertVaiViaPsm → psm.swapVAIForStable(TREASURY, …)); buyback is the VIP-620/649 TokenBuyback proxy with DESTINATION = RiskFund and BASE_ASSET = USDT; dev recipient is the VIP-649 address |
Worth fixing in the description
1. "XVS accrued up to the execution block is still released to the VAI Vault by this call" is conditional, and the condition is the interesting case.
_setVenusVAIVaultRate calls releaseToVault(), which returns early when the pending amount is under minReleaseAmount (4 XVS on chain). At the rate being retired, 4 XVS is about 1.5 hours of accrual, and releaseToVault also runs on every claimVenus — so at execution the pending amount is quite likely under 4 XVS (at head it is ≈1.1 XVS: releaseStartBlock 122151760). In that case nothing is released, and with the rate at 0 the remainder is never released by this mechanism again. Value is under 4 XVS, so it does not matter economically; it matters only because the sentence promises it. Suggest "XVS accrued since the last release is transferred to the Vault if it exceeds the Comptroller's 4 XVS minimum".
2. "convertVaiViaPsm() swaps it for USDT … The USDT goes back to the Venus Treasury" is a try/catch.
convertVaiViaPsm wraps _convertVaiViaPsm in try/catch and swallows the failure by design (paused PSM, exhausted headroom, stale USDT oracle). The proposal would still execute and pass, with the VAI sitting in the distributor — where the contract's own docs say it is then "distributed as a plain ERC20 by distribute", i.e. into the BTCB/ETH/XVS buybacks rather than becoming Treasury USDT. The simulation had to extend the USDT stale period to make it succeed, which shows the silent path is reachable. Two suggestions: say in the description that the conversion is best-effort and what happens if it does not fire, and check for exactly one VaiConvertedViaPsm in the execution receipt (the simulation asserts it; a human should after execution too).
3. "WIN is not moved either" — the reason is worth one clause.
The RiskFund holds 1,412,410.53 WIN, which PancakeSwap quotes at about 52.6 USDT — more than TRXOLD ($5) and RACA ($4), which are moved. A reader will ask why. The answer is in the buyback: _enforceCapAndDetectSlippage prices tokenIn through RESILIENT_ORACLE.getPrice, and the ResilientOracle has no WIN config (it reverts), so executeBuyback would revert for WIN. Every one of the seven swept tokens does have a price. Saying "WIN has no oracle price, so the buyback cannot value it" turns a puzzling omission into a documented one. (BSW and TWT check out: 1,871 BSW quotes at ~$0.72, TWT at ~$0.01.)
Worth knowing before execution
4. VAI becomes single-source with a 24-hour heartbeat. The description states the change; it does not state the consequence, which a voter should weigh: after this VIP there is no pivot and no fallback for VAI, so no cross-validation, and a missed Atlas push past 86,700 s makes getPrice(VAI) revert, taking every path that prices VAI with it. The feed itself is fine — 0x560f…749D is the same bytecode as the USDC Atlas feed, 18 decimals, reports 0.99902 and was last updated ~21.4 h before I checked, on the same cadence as the USDC feed (also ~21 h) — but that also means these feeds routinely sit within ~2.5 h of the stale limit. Note the simulation cannot detect a stale feed: the Prices block extends the stale periods before asserting. Someone should eyeball the VAI feed's updatedAt on the day.
5. The long-tail tokens change custodian. After the sweep they sit in the TokenBuyback proxy, whose owner is Guardian 2 (0x1C2C…AA6B, a 3-of-N Safe) with its own sweepToken, and whose sale is an ACM-gated executeBuyback an operator has to call — not part of this proposal. Same arrangement as VIP-620/649, so not new, but "sends them to the RiskFund buyback, which sells them for USDT" reads as if the sale happens here. It happens later, under the $30k daily cap (currently ~$3.4k consumed), through allowlisted routers, if there is liquidity — TRXOLD and old USDD are the two I would not assume.
6. WBNB_AMOUNT is hardcoded, and that is fine here. The three exit rates have not changed between the snapshot and head, so the amount is exact today. If they move before execution the difference lands in or comes from the Normal Timelock's own BNB (11.83 BNB at head), as the comment says. Just noting that the "exits pay exactly the wrapped amount" test is pinned to the snapshot block and will keep passing regardless.
Small: the stkBNB ERC777 send to the pStake pool and the WBNB.deposit{value} ordering are correct — the BNB from the three exits lands in the Timelock before the deposit in the same transaction. The SweepToken count (LONG_TAIL_TOKENS.length + 4) and TokenConfigAdded counts (14 resilient, 1 Atlas) match the command list.
Actions list grouped the three sweeps, so voters read it as sweep-all then exit-all. Real order alternates per token. slisBNB paragraph named no return path for the proceeds.
| - TRXOLD: 15.62 (about $5) | ||
| - RACA: 316,724.38 (about $4) | ||
|
|
||
| BSW and TWT are worth less than $1 each and are not moved. WIN (1,412,410.53, about $53) is not moved either, even though it is worth more than TRXOLD and RACA. The RiskFund buyback prices every token it sells through the Resilient Oracle, and the only oracle configured for WIN is Chainlink, whose WIN feed no longer responds. getPrice therefore reverts for WIN and the buyback cannot value it. |
There was a problem hiding this comment.
Maybe we can do this after talking to Buyback, sweep it to the dev account, or can do a round trip of setDirectPrice.
There was a problem hiding this comment.
Went with the sweep to dev option in 8b120a8.
| params: [address, RISK_FUND_BUYBACK, amount], | ||
| })), | ||
|
|
||
| // ankrBNB to BNB through the Ankr flash unstake |
|
|
||
| // RiskFund.sweepToken reverts when the amount is above the balance, so every amount below is the exact | ||
| // RiskFund balance at block 122009038. BSW and TWT (under $1 each) and WIN stay in the RiskFund. | ||
| export const LONG_TAIL_TOKENS = [ |
There was a problem hiding this comment.
Yeah, I initially skipped these tokens because:
- WIN: This is a Venus market (
vWIN_Tronin the Tron pool), but its oracle only uses Chainlink and the Chainlink feed is no longer active. - 小大力神杯, BRAD, CAT: These are not Venus market tokens and have no oracle configuration.
I’ve now added WIN, CAT, 小大力神杯, and BRAD in 8b120a8 and also swept them to the dev recipient, since their buyback prices are fetched through the Resilient Oracle and getPrice reverts for all four.
BSW and TWT remain unchanged; both are currently below $1.
Instant staking exits charge a fee, pay a BNB amount only known at execution and leave stkBNB dust, and the buyback cannot price WIN, CAT, 小大力神杯 and BRAD (ResilientOracle.getPrice reverts), so all eight tokens go whole to the dev recipient for off-chain conversion.


VIP-662: Oracle Adjustments, VAI Vault Rewards Stop, and Treasury and Risk Fund Cleanup
Summary
This proposal makes three operational changes on the BNB Chain Core Pool:
It also cleans up funds held by the Venus Treasury and the Risk Fund:
Atlas as pivot oracle
Atlas is currently the fallback oracle for 13 Core Pool markets: vUSDC, vUSDT, vXVS, vBNB, vBTC, vETH, vXRP, vDAI, vADA, vCAKE, vTRX, vUSD1, vWBNB. For each of them this VIP moves Atlas to the pivot slot and moves the current pivot oracle (RedStone) to the fallback slot. The main oracle (Chainlink), the enabled flags and the caching flag stay unchanged. Markets where Atlas is already the main or the pivot oracle are not modified. No feed, stale period or bound is changed.
VAI price source
The Atlas oracle has no VAI configuration today, so it is first configured with the Atlas VAI/USD feed (0x560fe27da57934EAF2f4107989704EeBd3Da749D) and a maximum stale period of 86700 seconds (24 hour heartbeat plus a 5 minute buffer). The ResilientOracle configuration for VAI is then set to Atlas as the main oracle, with no pivot and no fallback oracle. Chainlink and Binance are removed from the VAI price path.
After this change VAI has a single price source. There is no pivot oracle to cross validate the Atlas price and no fallback oracle to take over. If Atlas stops pushing the VAI price for more than 86700 seconds, getPrice reverts for VAI, and every operation that prices VAI, including minting, repaying and liquidating VAI, reverts with it until a new price is pushed.
VAI Vault rewards
The XVS distribution rate to the VAI Vault is set to 0. The current rate is 341,157,958,083,832 wei of XVS per block (about 65.47 XVS per day). Before the rate is updated, the Comptroller releases the XVS accrued since the last release to the VAI Vault, provided that amount reaches the Comptroller's minimum release amount of 4 XVS; a smaller amount stays in the Comptroller. VAI Vault stakers keep access to rewards already accrued.
Treasury VAI
The Venus Treasury holds about 2,343.39 VAI. The whole balance is withdrawn to the TreasuryTokenBuybackDistributor (0xc594053D4b2FaA311b55dDbFAb2338f7c90D6632, used in VIP-646), and convertVaiViaPsm() swaps it for USDT at the VAI PSM (0xC138aa4E424D1A8539e8F38Af5a754a2B7c3Cc36). The USDT goes back to the Venus Treasury. The PSM charges a 0.1% fee, paid in VAI, which also goes to the Venus Treasury. At the time of writing the swap returns about 2,341.05 USDT.
convertVaiViaPsm() is best effort. It wraps the PSM swap in a try/catch, so if the PSM cannot serve the swap at execution time, for example while it is paused or while its USDT price is stale, the call does not revert and the rest of the proposal still executes. In that case the VAI stays in the TreasuryTokenBuybackDistributor instead of returning to the Venus Treasury as USDT. A successful conversion emits one VaiConvertedViaPsm event, which should be checked in the execution receipt.
Risk Fund long-tail tokens
The Risk Fund (0xdF31a28D68A2AB381D42b380649Ead7ae2A76E42) holds these tokens outside its base assets. Each one is sent to the RiskFund buyback (0x0c71EFabD00329E839745ef23aB946d3ed24A805), which sells it for USDT and sends the USDT to the Risk Fund:
BSW and TWT are worth less than $1 each and are not moved.
Risk Fund tokens the buyback cannot price
The RiskFund buyback prices every token it sells through the Resilient Oracle. getPrice reverts for the four tokens below, so the buyback cannot value them: the only oracle configured for WIN is Chainlink, whose WIN feed no longer responds, and the other three have no oracle configuration. Each one is sent whole to the Venus dev recipient (0x080f8a0fb70f8f0f1b83c6178225a96cbe2be0de, also used in VIP-628, VIP-641 and VIP-649), which converts it off-chain and returns the proceeds to the Risk Fund as USDT in a later step:
Risk Fund staking positions
The Risk Fund holds receipt tokens from four liquid staking protocols. Each one is sent whole to the same Venus dev recipient, which unstakes it off-chain and returns the proceeds to the Risk Fund as USDT in a later step:
The positions are not exited on-chain in this proposal. The instant exits charge a fee (0.25% on Ankr), pay an amount of BNB that is only known at execution and that the Risk Fund cannot receive directly, the pStake pool only accepts multiples of 1e12 wei and would leave dust behind, and Lista's instant withdrawal is limited to whitelisted accounts. Moving the receipt tokens whole avoids all of that and keeps the proposal independent of the state of those protocols at execution.
Actions