Skip to content

[VPD-1199]: VIP-620 + VIP-621 TokenBuyback migration + per-tx gas-cap sims#708

Merged
fred-venus merged 9 commits into
mainfrom
VPD-1199
May 14, 2026
Merged

[VPD-1199]: VIP-620 + VIP-621 TokenBuyback migration + per-tx gas-cap sims#708
fred-venus merged 9 commits into
mainfrom
VPD-1199

Conversation

@Debugger022
Copy link
Copy Markdown
Contributor

@Debugger022 Debugger022 commented May 13, 2026

Summary

Replaces VIP-618 (queued but unexecutable on-chain — its bundled helper.execute() needs ~17.5M gas, exceeding BSC's Maxwell+Osaka per-tx gas cap of 2²⁴ = 16,777,216; EIP-7825 ships the same cap on Ethereum/Fusaka). The migration is split into two REGULAR proposals — VIP-620 and VIP-621 — that each fit comfortably under the cap, and folds in the May 2026 Prime Rewards Allocation (USDT + U) that was originally bundled in VIP-618.

Also adds a per-tx gas-cap guard to vip-framework so future violations fail in CI instead of at execute time on chain.

Why split

Pre-split (VIP-618) Post-split (VIP-620 + VIP-621)
Single helper call ~17.5M gas — over cap
execute1() (VIP-620) non-drain phase, ~3.12M gas (18.59% of cap)
execute2() (VIP-621) drain + router allowlist + handback, ~11.51M gas (68.6%)

The drain alone iterates 6 converters × 47 core-pool tokens; the router allowlist iterates 10 buybacks × 9 routers. Moving both out of execute1 and into a separate execute2 is the smallest split that fits.

VIP-620 — vips/vip-620/bscmainnet-part-1.ts

  • Grants DEFAULT_ADMIN_ROLE on the ACM to MIGRATION_HELPER_V2 = 0x296a3E00c07E306FB26976FdCa201b14933AffAD. Helper renounces it at the end of execute1().
  • Transfers ownership of the 6 timelock-owned legacy converters to the helper. (The 10 buyback proxies were redeployed with pendingOwner = helper, so no on-chain transfer is needed for them.)
  • helper.execute1(): accepts all 16 ownerships (10 buybacks + 6 converters), pauses every legacy converter + Shortfall auctions, repoints PSR (18 new rows added, 12 stale zeroed, respecting maxLoopsLimit=20 and the per-schema 1e4 invariant at every checkpoint), grants the cron operator persistent executeBuyback + forwardBaseAsset perms on every buyback, renounces ACM admin. Ownership of all 16 contracts is retained by the helper until execute2() (VIP-621).
  • May 2026 Prime Rewards Allocation, driven by the VIP (helper only wraps the swap so a thin-pool revert can't unwind the rest):
    • Prime.addMarket(coreComptroller, vU, supplyMultiplier=2e18, borrowMultiplier=0)
    • PLP.initializeTokens([U]) + setMaxTokensDistributionSpeed([U], [1e18])
    • PLP.sweepToken(USDC, helper, 10,000e18) — ~4k of PLP's ~14.9k USDC is reserved for unclaimed user rewards.
    • helper.executeSwap() — single soft-failing USDC → USDT → U PancakeSwap V3 multihop (direct USDC/U pool is too thin; the deep USDT/U pool is required). Min-out = 9,900e18 U (~1% buffer under the 9,996.60 U QuoterV2 read at 2026-05-13). USDT leg dropped because PLP already holds ~25k USDT for the May distribution. Output to PLP; leftover USDC forwarded back to NormalTimelock.
    • PLP.setTokensDistributionSpeed([USDT, U], […]) at the $12,250/month per-market target.
  • Upgrades RiskFundV2 to the new implementation. Safe because RiskFundConverter is paused inside execute1(), so no in-flight convertExactTokens callback can hit the removed updatePoolState selector — even though the converter still holds balance until VIP-621.

VIP-621 — vips/vip-620/bscmainnet-part-2.ts

  • helper.execute2(): allowlists 9 swap routers (PancakeSwap V2 / V3 / Smart / Universal, Uniswap V2 SwapRouter02 / V3 SwapRouter02 / V4 / Universal, 1inch v5) on every buyback, drains every non-zero core-pool ERC20 balance from each timelock-owned converter into the corresponding new buyback (RiskFundConverterRISK_FUND_BUYBACK; 4× *PrimeConverterU_PRIME_BUYBACK; XVSVaultConverterXVS_BUYBACK), transfers ownership of all 16 contracts back to NormalTimelock.
  • acceptOwnership() on all 16 contracts (10 buybacks + 6 converters).
  • Between VIP-620 and VIP-621 the legacy converters are paused and PSR no longer routes to them, so balances are frozen and there is no economic surface from them — the drain in VIP-621 just moves frozen balances.
  • Post-execution invariant: helper holds no privileges, no balances, no ownership; all three entrypoints (execute1 / executeSwap / execute2) revert AlreadyExecuted on any subsequent call.

vip-framework changes (src/)

The original VIP-618 passed simulation but is stuck on chain because Hardhat fork doesn't enforce consensus per-tx caps. This PR closes that gap:

  • src/networkConfig.ts: PER_TX_GAS_CAP_BY_NETWORK map (bscmainnet / bsctestnet / ethereum / sepolia2²⁴; L2s left unset until a concrete protocol per-tx rule lands).
  • src/utils.ts: resolvePerTxGasCap(forkedNetwork) — prefers the EIP-7825 eth_txGasLimitCap RPC (forward-compatible with future hardforks) and falls back to the static map; logs the chosen source once per network.
  • src/vip-framework/index.ts:
    • testVip builds governor.execute(proposalId) via populateTransaction and sets gasLimit = cap so an over-cap proposal OOGs in sim the way it would on chain.
    • testForkedNetworkVipCommands applies the same gasLimit cap on the destination-chain executor.execute(proposalId).
    • pretendExecutingVip reports per-command max gas + total gas and warns when any individual command exceeds the cap.

On-chain pre-conditions (verified at bscmainnet block 98045598)

  • All 10 buyback proxies have pendingOwner = 0x296a3E00c07E306FB26976FdCa201b14933AffAD
  • The 6 legacy converters are owned by NormalTimelock and unpaused ✓

References

  • protocol-reserve #158 — RiskFundV2 new implementation
  • protocol-reserve #162 — TokenBuyback proxy redeploy (10 buybacks)
  • protocol-reserve #164 — V2 migration helper (execute1 / executeSwap / execute2 split)

Test plan

  • npx hardhat test simulations/vip-620/bscmainnet-part-1.ts --fork bscmainnet
  • npx hardhat test simulations/vip-620/bscmainnet-part-2.ts --fork bscmainnet

add resolvePerTxGasCap (eth_txGasLimitCap RPC, static fallback map in
networkConfig) and apply it as tx.gasLimit in testVip,
testForkedNetworkVipCommands, pretendExecutingVip so hardhat OOGs at the
same boundary mainnet would. catches BSC Maxwell+Osaka and Ethereum
Fusaka EIP-7825 caps; future hardforks via EIP-8123 are picked up
automatically.
VIP-618's bundled execute() needed ~17.5M gas, exceeding BSC's Maxwell+
Osaka per-tx cap of 2^24 = 16,777,216. Replace with vip-800 part-1
(non-drain migration: grants, ownership, routers, ACM perms, pause, PSR
rewire, Prime allocation, RiskFundV2 upgrade) and part-2 (converter
drain + handback). Each fits comfortably under the cap.
- Refresh MIGRATION_HELPER_V2 to the 0x296a3E redeploy (commit 746fe99)
  and update the 10 buyback proxy addresses to the latest redeploy.
- Drop the USDC -> USDT swap leg: helper.executeSwap() now runs a
  single USDC -> USDT -> U multihop. USDC sweep tuned to 10k (4k stays
  on PLP for unclaimed user rewards); U_MIN_OUT raised to 9,900e18
  under the 9,996.60 U QuoterV2 read.
- Pull the May 2026 Prime allocation (Prime.addMarket(vU),
  PLP.initializeTokens/setMax/setSpeed, PLP.sweepToken) out of the
  helper and drive it from the VIP itself; helper now exposes three
  one-shot entrypoints — execute1 / executeSwap / execute2.
- Move router allowlisting out of execute1 into execute2 to fit BSC's
  per-tx gas cap. Part 2 hands back ownership of all 16 contracts
  (10 buybacks + 6 converters) instead of just the 6 converters.
- Sims: replace the artifact-bytecode etch with a local ABI under
  simulations/vip-800/abi/, bump FORK_BLOCK to 98041000 (past the
  helper and buyback redeploys), and add executedSwap / router-
  allowlist assertions for the new flow.
@Debugger022 Debugger022 marked this pull request as ready for review May 13, 2026 11:54
Comment thread vips/vip-618/bscmainnet.ts Outdated
export const USDT_TREASURY_BUYBACK = "0xBF858c95D778022b48E6Ad343D3d644017fb0ca7";
export const USDC_TREASURY_BUYBACK = "0xFB5FA544dBf39983198BDD01e2c26E3AB597e22A";
export const XVS_TREASURY_BUYBACK = "0x01D0f07D389692D386EB8D09Da3bbCa5C83be551";
export const RISK_FUND_BUYBACK = "0x0c71EFabD00329E839745ef23aB946d3ed24A805";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pending owner needs to be point to new helper contract

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah ownership transferred to new helper contract only

@Debugger022 Debugger022 self-assigned this May 13, 2026
pretendExecutingVip runs each cmd as its own tx, so cap applies per-cmd
not to sum. Log per-cmd max + warn on over-cap cmds. Fix EIP-8123 →
EIP-7825. Drop dead re-export (no prior callers).
- Restore vip-618's original 10 buyback addresses, USDC_TO_SWEEP=14,986
  and U_MIN_OUT=7,418 — the VIP is queued on-chain and must match what
  governance audited.
- Define the redeployed buyback addresses (PR #162) and retuned
  single-multihop budget (USDC_TO_SWEEP=10,000, U_MIN_OUT=9,900 under
  the 9,996.60 U QuoterV2 read) locally in vip-800/part-1.
- BUYBACKS index order preserved so PSR-row → buyback mapping survives.
- vip-800/part-1 keeps re-exporting the untouched vip-618 constants so
  sims have a single import surface.
Run part-1 through full governance flow (propose/vote/execute) instead
of direct timelock impersonation via pretendExecutingVip, so part-2 sees
the same post-part-1 state mainnet will. Each testVip uses its own
loadFixture function, so part-2's first snapshot captures post-part-1
state. Balances now captured in callbackAfterExecution.
@Debugger022 Debugger022 changed the title Vpd 1199 [VPD-1199]: VIP-800 TokenBuyback migration + per-tx gas-cap sims May 13, 2026
proposalCount is 619; the two-part TokenBuyback migration now lands as
VIP-620 (part 1) and VIP-621 (part 2). Directory stays as vips/vip-620/
to keep both parts together with the shared simulation ABI.
@fred-venus fred-venus changed the title [VPD-1199]: VIP-800 TokenBuyback migration + per-tx gas-cap sims [VPD-1199]: VIP-620 + VIP-621 TokenBuyback migration + per-tx gas-cap sims May 13, 2026
@fred-venus
Copy link
Copy Markdown
Contributor

@fred-venus fred-venus merged commit 81645b5 into main May 14, 2026
2 checks passed
Sign up for free to 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.

2 participants