Skip to content

feat: add Liquidity Hub (USDT, USDC, U) — VIP-650 / VIP-651 mainnet + BSC testnet wiring VPD-1566, 1588, 1653 - #733

Open
Debugger022 wants to merge 13 commits into
mainfrom
feat/VPD-1566
Open

feat: add Liquidity Hub (USDT, USDC, U) — VIP-650 / VIP-651 mainnet + BSC testnet wiring VPD-1566, 1588, 1653#733
Debugger022 wants to merge 13 commits into
mainfrom
feat/VPD-1566

Conversation

@Debugger022

@Debugger022 Debugger022 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Onboards the Liquidity Hub on BNB Chain, testnet rehearsal through to mainnet launch.

  • Mainnet — two proposals, VIP-650 (USDT + USDC) and VIP-651 (U), split to stay under the per-tx propose() gas cap. Prepared, not yet proposed.
  • Testnet — six proposals covering the same surface for USDT, plus a HubRegistry implementation upgrade. All executed.
  • Mainnet ACM grant batches are already stored on-chain (indices 2-4), so the simulations verify the real stored bytes rather than seeding their own.
  • Ownership acceptance, ACM permission setup, Hub registration, protocol wiring and bootstrap deposits, with simulations covering the complete flow.

Note: #744 was stacked on this branch and has been merged down, so this PR carries both halves. The provisional vip-680 numbering is gone — everything now lives under vips/vip-650/ and simulations/vip-650/. The bsctestnet*.ts files deliberately keep their original VIP-680 titles and symbols: they were executed under those labels, so only their paths moved.

Files

vips/vip-650/
  addresses/bscmainnet.ts   mainnet addresses + caps + batch bases (verified on-chain)
  addresses/bsctestnet.ts   testnet address book
  permissions.ts            ACM role strings, verbatim from deployed contracts
  commands.ts               every command both mainnet proposals emit + shared description
  bscmainnet-part-1.ts      VIP-650 — USDT + USDC + registry acceptOwnership
  bscmainnet-part-2.ts      VIP-651 — U
  provisionAcmBatches.ts    one-off: stores the grant batches on the aggregator
  bsctestnet*.ts            testnet rehearsals (executed)
simulations/vip-650/
  shared.ts                 parameterised assertion harness used by both mainnet parts
  bscmainnet-part-{1,2}.ts  part 2 runs part 1 first, then asserts the full end state
  bsctestnet*.ts            one suite per testnet proposal

Mainnet — VIP-650 and VIP-651

Execution order (per proposal)

  1. grantRole(DEFAULT_ADMIN_ROLE, aggregator)executeBatch(i) per asset → revokeRole(...)
  2. HubRegistry.acceptOwnership() (part 1 only), then addHub(hub) per asset
  3. Per-asset wiring
  4. Bootstrap deposit — 10 tokens per asset from the Treasury, shares to 0x…dEaD

Order is load-bearing: the grant sandwich first (all wiring is ACM-gated), addResource before inner queues and addYieldGroup before outer queues (both reject unregistered entries), bootstrap last (deposit routes through the outer queue).

Wiring (per asset)

Source Absolute cap % cap Effect
Core 2,000,000,000 disabled (10,000 bps) binds on absolute; takes deposits immediately
Flux 7,000,000 2,000 bps % binds at launch; fills via Operator reallocate
FRV 5,000,000 3,000 bps config only — no live vault

Outer deposit [Core, Flux], outer withdraw [Flux, Core, FRV]. Fees launch at 0/0/0.

FRV is listed last in the withdraw queue, not omitted. Omitting it is a griefing vector: setOuterWithdrawQueue rejects a queue dropping a registered group with totalAssets() > 0, and totalAssets() counts idle balance, so 1 wei donated to an omitted FRV would permanently block queue reordering. Listing it costs nothing.

Bootstrap — the deployed decimalsOffset of 6 makes first-deposit inflation non-griefing; seeding in the same tx additionally guarantees no external first depositor. Shares are burned, so "never zero-supply" is an on-chain property rather than policy. 30 tokens total, unrecoverable by design.

ACM batches — stored on BNB Chain

The 234 grants are pre-seeded on the AuxiliaryCommandsAggregator 0x528A428748dfE73DFcc844176B401475D1831057 and replayed by index. This has now been done, so the batch contents are frozen and readable before voting via getBatch(2..4):

Index Calls Contents Gas Transaction
2 79 USDT stack + the registry's addHub / removeHub 13,664,510 (81.4% of cap) 0x9290a095…b33f2
3 77 USDC stack 13,342,295 (79.5%) 0x3489ad61…46d334
4 78 U stack + the redundant addHub re-grant 13,503,366 (80.5%) 0xccc34312…09f554

batchCount() is now 5. provisionAcmBatches.ts asserted batchCount() == the part's base before each run, used the indexed addBatch overload (reverts InvalidBatchIndex on drift), and read every batch back call-for-call after storing it. The simulations independently deep-compare the stored bytes against the command builder on every run.

One batch per asset because a single batch is the tightest transaction in the whole operation — 79 calls already costs 81.4% of the 16,777,216 per-tx cap, and all 156 of part 1's grants in one addBatch would need roughly 27M gas.

Why the transient DEFAULT_ADMIN_ROLE, and why part 2 needs it too

234 grants inline exceed both proposalMaxOperations and the gas cap; batching brings part 1 to 37 commands. The aggregator (live since VIP-628) replays pre-stored (target, calldata) pairs, so the proposal lends it DEFAULT_ADMIN_ROLE and revokes it in the same transaction.

Part 2 needs it too, for a different reason: inline it is 94 commands, which does fit proposalMaxOperations — but propose() measured 22,954,977 gas (136.8% of cap) and failed before execute() was reached. The 6-command margin is not reclaimable.

Only grants are batched. Wiring targets the Hubs and registry directly; batching it would give a shared upgradeable contract standing Hub governance, and acceptOwnership() cannot be batched at all (Ownable2Step checks msg.sender).

  • Append-only. Each part pins its own base (..._PART_1 = 2, ..._PART_2 = 4). Both parts were seeded back-to-back so nothing could be appended in between and shift part 2. Any code change from here — a role string, an address, a cap — invalidates the stored batches and requires re-seeding at new slots.
  • Both parts must be REGULAR — only the Normal Timelock holds DEFAULT_ADMIN_ROLE on the ACM.
  • Every stored call targets the ACM itself, so the aggregator never holds a permission on a Hub or the registry.
  • Part 2 re-grants addHub so it is authorised by its own batch rather than by part 1 having landed. No-op once part 1 is in (OZ guards _grantRole with if (!hasRole(...))) — ~8k gas, no event. removeHub is not re-granted.
Permission matrix — 77 grants per asset (49 Gov, 21 Operator, 7 Guardian)

Identical per asset. Critical and Fast-Track Timelocks get nothing.

Hub

Function Gov Operator Guardian
addYieldGroup / removeYieldGroup
raiseYieldGroupCap / lowerYieldGroupCap
setOuterDepositQueue / setOuterWithdrawQueue
reallocate
emergencyReallocate
pauseHub / pauseYieldGroup
unpauseHub / unpauseYieldGroup
raiseMaxWithdrawalSize
lowerMaxWithdrawalSize
setManagementFeeBps / setPerformanceFeeBps / setRedeemFeeBps / setFeeRecipient
sweep

Yield sources

Function On Gov Operator Guardian
addResource / removeResource / updateResourceAdapter all
setInnerDepositQueue / setInnerWithdrawQueue all
pauseResource all
unpauseResource / sweep all
raiseResourceCap / lowerResourceCap Core, Flux
setBlocksPerYear Core, Flux
forceRemoveResource FRV

HubRegistryaddHub to Gov in both parts, removeHub to Gov in part 1 only.

Two deliberate departures from the shipment plan's tables, resolved against the deployed contracts:

  • Guardian holds pauseHub() — per Hub.sol's (Operator, Guardian, or VIP) and the README; only the plan's table leaves that cell blank.
  • Gov is not granted reallocate — the plan's table says yes, its own prose says no. Gov holds emergencyReallocate, which also works while paused.

Operator and Guardian are multisigs, not timelocks — no governance delay. Neither holds any unpause, so neither can undo a governance-ordered pause; the Guardian set is a strict subset of Gov.

Role strings are copied verbatim from _checkAccessAllowed(...) (role = keccak256(contract, roleString)). All 32 distinct strings were confirmed present byte-for-byte in the deployed runtime bytecode of the implementations behind the beacons and the registry proxy, with negative controls: FRV's implementation genuinely contains no raiseResourceCap / setBlocksPerYear, and Core/Flux genuinely contain no forceRemoveResource.

Ownership & registry
  • HubRegistry.acceptOwnership() — part 1 only; cannot be repeated. Not a prerequisite for addHub, which is ACM-gated, so part 2 does not depend on part 1 having landed.
  • Hub.acceptOwnership() per asset, retiring the deployer's owner key.
  • Yield sources have no owner and immutable _hub / _accessControlManager bindings — the sim's on-chain check of all 36 is load-bearing.
  • Part 1 registers the USDT and USDC Hubs, part 2 the U Hub. Registration is per-asset; the sim asserts part 2 does not displace either part-1 mapping.
  • Beacon / proxy-admin upgrade authority is the Normal Timelock throughout. Unchanged by this PR.

Proposing

proposalCount() was 649 when these were prepared, so part 1 is VIP-650 and part 2 is VIP-651. Both are REGULAR: only the Normal Timelock holds DEFAULT_ADMIN_ROLE on the ACM, which the grant/revoke sandwich needs.

The two cannot be proposed in the same sitting. GovernorBravoDelegate.propose() enforces one live proposal per proposer — it reverts while the caller's latest proposal is Pending or Active (lines 205-215). The REGULAR voting period is 192,384 blocks (~1 day at 0.45 s/block), so the proposer Safe can only submit part 2 once part 1 has reached Succeeded or later. Re-confirm part 2's proposalId before casting its vote: if any other proposer lands a proposal in that window, VIP-651 shifts and a pre-built castVote(651, 1) would target the wrong proposal.

The voter-facing descriptions carry no inline code spans. The venus.io governance UI drops backticked text, which would otherwise blank out every role name, function name and address in the rendered proposal.

Simulation

npx hardhat test simulations/vip-650/bscmainnet-part-1.ts --fork bscmainnet
npx hardhat test simulations/vip-650/bscmainnet-part-2.ts --fork bscmainnet
# testnet (already executed): bsctestnet{,-wiring,-fast-track,-critical,-guardian,-hubregistry-upgrade}.ts --fork bsctestnet

Both are pinned at block 113736000, past the seeding transactions: part 1 — 89 passing, part 2 — 113 passing, nothing skipped.

There is no fork-seeding fallback. If the batches are not on-chain at the fork block, before() fails with the reason rather than writing its own copy — a suite that seeds its own batches and then deep-compares them proves only that the encoder is deterministic.

Covers: batch deep-compare against the builder, upgrade authority (including the registry proxy's EIP-1967 admin slot), the full permission matrix, pre-VIP absence of every grant, post-VIP ownership / registration / wiring / bootstrap, an end-to-end deposit → cascading withdraw, and the FRV donation-griefing scenario.

Measured gas, all within the 16,777,216 per-tx cap:

propose() queue() execute()
Part 1 6,259,238 (37.3%) 1,976,072 (11.8%) 11,955,969 (71.3%)
Part 2 3,530,169 (21.0%) 1,057,724 (6.3%) 6,148,240 (36.6%)

Follow-up before proposing part 2

Nothing is outstanding for part 1. Once part 1 has executed on-chain, simulations/vip-650/bscmainnet-part-2.ts needs three changes — the block number alone is not enough, because part 1's effects will already be part of the pre-state:

  • Re-pin BLOCK_NUMBER past part 1's execution block.
  • Drop the testVip("VIP-650 part 1 (setup…)") line — otherwise it replays part 1 and acceptOwnership() reverts.
  • Flip registryAccepted to true and narrow grantsExpectedAbsent to the U stack's own grants, since the registry will be Timelock-owned and its addHub role already granted.

BNB Chain Testnet — executed

Onboards the Liquidity Hub (USDT) on BNB Chain Testnet: accepts ownership, grants every role, registers the Hub in the HubRegistry, and wires all three yield sources (Core, FRV, Flux). All executed on BNB Chain Testnet.

Note: supersedes the earlier revision of this PR, which targeted a since-redeployed stack, predated the HubRegistry, and did FRV/Flux wiring via Guardian multisig. All contracts now exist, so it goes through governance instead. bsctestnet-addendum.ts is deleted, replaced by the fast-track/critical split.

Proposals

The five permission/wiring proposals are split because the full surface (222 ops) is far over BSC's per-tx propose gas cap of 16,777,216 — the main proposal alone is 12.73M (75.85%). All REGULAR. The sixth, 708, is an unrelated later addition: a single-op implementation upgrade.

File Contents Ops Executed
bsctestnet.ts Normal Timelock governance set + acceptOwnership() on Hub and HubRegistry 53 702
bsctestnet-wiring.ts addHub → per-source addResource + inner queues → addYieldGroup ×3 → outer queues 15 703
bsctestnet-guardian.ts Guardian full set across the stack (testnet only) 52 705
bsctestnet-fast-track.ts Fast-Track Timelock governance set 51 706
bsctestnet-critical.ts Critical Timelock governance set 51 707
bsctestnet-hubregistry-upgrade.ts HubRegistry implementation upgrade — adds the assetForHub(hub) reverse getter 1 708

702 executes before 703 (the wiring needs 702's roles). The rest are independent. 708 is a later addition and targets the registry's ProxyAdmin 0x9f8413eEE33D434F6D4f40C83181f32A831c9ef7 rather than the Hub stack; verified live — the proxy now runs impl 0x4D2C18fB4520c2e4f7C754979e9a4F3BbC1BCe92 and assetForHub(Hub_USDT) returns USDT.

Two shared files keep addresses and role strings out of the call sites: addresses/bsctestnet.ts (governance/ACM from NETWORK_ADDRESSES, Hub stack inlined from deployments/bsctestnet/*.json) and permissions.ts (role strings verbatim from each contract's _checkAccessAllowed).

Configuration

Yield groups — registered in queue order, all uncapped per testnet policy:

# Source Resource Adapter Absolute cap % cap
1 FRVSource_USDT
0xA0Fb0fFeBdcB7F45A3Ec841cCE7F78B7CeBD0f82
FRV vault
0x9F6Edab0123188C852854D2D9601115168f52F7a
AdapterFRV
0xeF0E85ab9A23F50EB4595CF7e2F5461feF7E7fc5
type(uint128).max 10000 (off)
2 FluxSource_USDT
0x044E572144bc08ed2D90E081EeEd7b5b6Cb01016
Fluid fUSDT
0x52217232e12A1c906aB8DEf58532a3618970D025
AdapterFlux
0x15Dca35ae0b16BeceabAEC9Dea49630e8C601730
type(uint128).max 10000 (off)
3 CoreSource_USDT
0x11e39DC7b8b16BBDA8D9C2903dF741Ae9341Ec88
vUSDT
0xb7526572FFE56AB9D7489838Bf2E18e3323b441A
AdapterCoreV1
0xDf669957448eCB23309eEFda4de230c62d22AE33
type(uint128).max 10000 (off)

type(uint128).max is the canonical "no ceiling" — the Hub rejects type(uint256).max as InvalidCap. 10000 bps disables the percentage-of-TVL dimension.

Queues

  • Outer deposit = outer withdraw = [FRVSource, FluxSource, CoreSource]. FRV first so deposits reach the vault under test; Core last because it's uncapped and would otherwise absorb everything.
  • Inner deposit = inner withdraw = the single resource on each source.

Core

HubRegistry 0x5346f648029d1D1d1034e09e8AD7a115f5D7A159
Hub_USDT 0x7cE6ADF754D0eC81A6CF8ACd9C7454F45077dc61
USDT 0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c
Hub owner / registry owner Normal Timelock 0xce10739590001705F7FF231611ba4A48B2820327
ACM 0x45f8a08F534f34A97187626E05d4b6648Eeaa9AA

Set at deploy, not by these VIPs

Key Value
Hub decimals 12 (USDT 6 + decimalsOffset 6)
maxWithdrawalSize 1e24
redeemFeeBps 0
feeRecipient 0x8b293600C50D6fbdc6Ed4251cc75ECe29880276f
blocksPerYear Core 70080000; FRV/Flux 0 — their adapters ignore the annualiser

Notes

  • Wiring order is load-bearing. addHub first, so HubAdded precedes every YieldGroupAdded in the same tx; each addResource precedes its inner-queue setters; each addYieldGroup precedes the outer-queue setters.
  • Guardian holds the full permission set across the Hub and the three sources, so any testnet reconfiguration (reordering queues, changing caps, adding or swapping a resource) can be done by the Guardian directly with no VIP.

- Grant the Hub's asymmetric ACM roles: the full governance set to the
  Normal timelock (which then runs the wiring) plus the Guardian
  operator set; a companion addendum grants governance to the
  Fast-Track and Critical timelocks.
- Wire the Core yield source end-to-end (register vUSDT behind
  AdapterCoreV1, set inner and outer queues) so the USDT Hub routes
  deposits and withdrawals through the Core pool.
- Split the grants into a main proposal plus bsctestnet-addendum.ts:
  all three timelocks' grants plus the wiring exceed BSC's per-tx
  propose gas cap in a single proposal.
- Defer the FRV and Flux sources — neither has a concrete resource on
  testnet yet (no FRV vault instance; Flux adapter not deployed).
- Add fork simulations for both proposals: pre/post state, the 40 and
  58 role grants, and an end-to-end maxDeposit check.
- New proposal grants the Guardian the full Governance role set
  across the Hub stack (Hub, Core, FRV, Flux) so resources can be
  listed and reconfigured via multisig without a proposal per change
- Deliberate testnet-only deviation from the asymmetric model where
  the Operator is tighten-only; kept in a bsctestnet file, not for
  mainnet
- Factor the shared YieldGroupBase signatures into one base array;
  FRV uses YieldGroupFRV (adds forceRemoveResource, no cap setters
  or setBlocksPerYear) so it keeps a separate set
- Update the main and addendum descriptions to reference the three
  proposals as one package
@Debugger022
Debugger022 marked this pull request as ready for review July 9, 2026 11:59
@Debugger022 Debugger022 changed the title [VPD-1566]feat: add VIP Liquidity Hub (USDT) testnet wiring feat: add VIP-680 Liquidity Hub (USDT) testnet wiring VPD-1566 Jul 9, 2026
@Debugger022 Debugger022 changed the title feat: add VIP-680 Liquidity Hub (USDT) testnet wiring VPD-1566 feat: add VIP-680 Liquidity Hub (USDT) testnet wiring VPD-1566 & 1588 Jul 10, 2026
@Debugger022 Debugger022 changed the title feat: add VIP-680 Liquidity Hub (USDT) testnet wiring VPD-1566 & 1588 feat: add VIP-680 Liquidity Hub (USDT) BSC testnet wiring VPD-1566 & 1588 Jul 13, 2026
The Liquidity Hub (USDT) stack was redeployed on BNB Chain testnet
(new HubRegistry, Hub, Core/FRV/Flux sources and adapters), so the
previous VIP-680 targets stale addresses and predates the registry.
Rewrite it to onboard the current deployment on-chain in one package.

- Split into five REGULAR proposals: the full surface (ownership
  accepts, ~205 role grants, source wiring) exceeds the BSC per-tx
  propose gas cap in a single proposal.
- main: accept Hub and registry ownership, grant the Normal Timelock
  the governance set across the stack.
- wiring: register the Hub, then wire Core, FRV and Flux end to end
  (addHub before addYieldGroup, outer queue [FRV, Flux, Core]).
- fast-track and critical: grant each timelock the governance set.
- guardian: grant the Guardian full permissions (testnet only).
- move addresses and ACM role strings into addresses.ts and
  permissions.ts; add the HubRegistry ABI, regenerate the Hub and
  YieldGroup ABIs, and drop the superseded addendum files.
…l + sim

A separate proposal (bsctestnetHubregistryUpgrade), independent of the Liquidity Hub
onboarding proposals, that upgrades the HubRegistry proxy to the implementation exposing
`assetForHub(hub)` — the reverse of `hubForAsset`. Goes through the Normal-Timelock-owned
ProxyAdmin; append-only change so a plain `upgrade` preserves registry storage.

Adds HUB_REGISTRY_IMPL to addresses, a ProxyAdmin ABI, refreshes the HubRegistry ABI to
include assetForHub. Fork sim (bsctestnet, block 119680000) passes 8/8: pre-VIP the getter
reverts on the old impl; post-execution the proxy runs the new impl, emits Upgraded, and
assetForHub resolves an unregistered hub to address(0).
- Move the address book to addresses/bsctestnet.ts so the upcoming
  mainnet VIP gets its own file
- Keep permissions.ts shared and unprefixed: the role strings are
  literal contract function signatures, identical on mainnet, and
  the Operator sets it defines are unused on testnet and exist only
  for mainnet. Prefixing it would invite a copy that drifts
- Pass the ACM into giveCallPermission so permissions.ts keeps no
  network coupling
- Kebab-case bsctestnetHubregistryUpgrade.ts to match its siblings

No change to the encoded proposals: same targets, signatures and
params.
- Ship the whole onboarding as one proposal instead of testnet's
  five. Inline it would be 110 commands (96 ACM grants + 14 wiring),
  over GovernorBravo's proposalMaxOperations of 100, so the grants
  are pre-seeded as one AuxiliaryCommandsAggregator batch and run in
  three commands.
- Batch only the grants. Every batch call targets the ACM, so the
  aggregator holds no Hub permission. Wiring stays inline because its
  targets are the Hub and registry themselves, and acceptOwnership
  cannot be batched at all: Ownable2Step checks msg.sender against
  pendingOwner, which is the timelock.
- Apply the asymmetric permission model, deliberately not the testnet
  one: Normal Timelock gets the full governance set, Fast-Track only
  the risk and ops levers, Critical nothing, Operator tighten-only
  plus reallocate. permissions.ts gains three fast-track sets; the
  existing sets are untouched so the testnet proposals are unchanged.
- Register the FRV yield group but leave it unwired and off both
  outer queues. No fixed-rate vault instance exists for USDT on BNB
  Chain yet, and an empty group reports zero assets, so the withdraw
  queue coverage guard allows omitting it.
- Leave the Hub stack addresses, the operator account, the batch
  index and the launch caps as marked placeholders. The stack is not
  deployed yet, so the simulation fails at validateTargetAddresses
  until it is.
- Ship one Hub per asset (USDT, USDC, U), each with Core, Flux, and
  FRV yield groups; the registry, adapters, and beacons are shared
  once per chain.
- Set the mainnet permission model: the Normal Timelock holds full
  governance, a Guardian multisig holds emergency containment only
  (pause everywhere, emergencyReallocate, FRV forceRemoveResource),
  and the Operator keeper gains the raise-cap levers. Critical and
  Fast-Track receive nothing on the Hub stack.
- Pre-seed the 233 ACM grants as three per-asset batches replayed by
  three executeBatch commands, since they exceed both GovernorBravo's
  100-operation limit and the per-transaction gas cap. The aggregator
  holds DEFAULT_ADMIN_ROLE only transiently.
- Fill every address from the live deployment, verified on-chain, and
  align the ABIs and fork simulation to the deployed contracts.
@Debugger022 Debugger022 self-assigned this Jul 29, 2026
Debugger022 and others added 6 commits July 29, 2026 16:37
- All three assets in one proposal cannot be created at all:
  propose() needs ~23M gas against the 16,777,216 per-tx cap,
  because GovernorBravo copies every target and calldata into
  storage. proposalMaxOperations was never the binding constraint.
- Split by asset into part 1 (USDT, USDC) and part 2 (U). The three
  Hub stacks are disjoint contract sets, so this is packaging only:
  neither part configures or depends on anything the other touches.
- Part 1 carries the one-time HubRegistry acceptOwnership; part 2
  re-grants addHub alone so it is authorised by its own grants
  rather than by part 1 having landed first. Re-granting a held
  role writes nothing and emits nothing.
- Each part pins its own aggregator batch base. Batches are
  append-only and the two parts are provisioned at different times,
  so a shared base would silently point part 2 at the wrong slot.
- FRV now sits last in each outer withdraw queue instead of being
  omitted: an omitted registered group can be made "funded" with a
  1 wei transfer, permanently blocking any later queue reorder.
- Commands and simulation assertions are shared by both parts, so
  what is proposed and what is asserted cannot drift.
- The registry proxy was redeployed behind the chain's shared
  DefaultProxyAdmin, so the previous proxy is dead. Nothing had been
  onboarded on it, so no state carries over.
- Source DefaultProxyAdmin from NETWORK_ADDRESSES instead of a per-VIP
  literal, and assert the proxy's EIP-1967 admin slot points at it. The
  owner check alone only proved some ProxyAdmin is Timelock owned, not
  the one governing this proxy.
- Re-pin both mainnet fork blocks past the new deployment and refresh
  the measured gas figures.
- Ran provisionAcmBatches.ts on BNB Chain, storing all 234 grants on
  the AuxiliaryCommandsAggregator across indices 2..4:
    2  79 calls, USDT + the registry's addHub/removeHub
       0x9290a095b0079b2a33bc01639f3a7b4e34d32b47e8a29665fdcace97b26b33f2
    3  77 calls, USDC
       0x3489ad614e32fc3157cd3e0f0261bdccf13ba96c3ba3fdd2c44b78818a46d334
    4  78 calls, U + the redundant addHub re-grant
       0xccc34312a199b1593669a8732fd85350979264835179febbb574ab434909f554
  Each batch was read back call-for-call after storing.
- Dropped the simulations' fork-seeding path now that there is real
  state to read. A run that seeds its own batches and then deep-compares
  them only proves the encoder is deterministic.
- Turned the skipped "comparing against REAL mainnet batches" test into
  a hard assertion on batchCount(), so a fork block pinned before the
  seeding transactions fails loudly instead of silently self-verifying.
- Re-pinned both fork blocks past the seeding transactions and refreshed
  the measured gas figures.
Renumber the provisional vip-680 directory to the real proposal numbers:
part 1 (USDT + USDC) is VIP-650, part 2 (U) is VIP-651. proposalCount() was
649 at preparation time.

Strip inline backticks from the voter-facing description: the venus.io
governance UI drops backticked spans, which would have blanked out every
role name, function name and deployed address in the rendered text.

The bsctestnet rehearsal files keep their original VIP-680 labels — they
were already executed under those names; only their import paths moved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
feat: add Liquidity Hub (USDT, USDC, U) mainnet vips — VIP-650 and VIP-651
@fred-venus fred-venus changed the title feat: add VIP-680 Liquidity Hub (USDT) BSC testnet wiring VPD-1566 & 1588 feat: add Liquidity Hub (USDT, USDC, U) — VIP-650 / VIP-651 mainnet + BSC testnet wiring VPD-1566, 1588, 1653 Aug 3, 2026
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