TokenBuybackMigrationHelperV2#164
Closed
Debugger022 wants to merge 26 commits into
Closed
Conversation
One-shot helper executed by NormalTimelock to atomically replace the 5-contract Token Converter system with 10 TokenBuyback contracts. Claims ownership of 16 contracts, drains legacy converters into buybacks, allowlists routers, grants operator permissions, pauses converters, rewires PSR distributions, then hands ownership back and renounces ACM admin. Includes fork test covering the full migration flow.
- Fix invalid EIP-55 checksum on UNIV4_SWAP_ROUTER and UNI_UNIVERSAL_ROUTER (compile error blocked Deploy/Docgen/test CI jobs). - Move _coreTokens() pure helper after non-pure internals to satisfy solhint 'ordering' rule (Lint CI job).
Three inline interfaces (IAccessControlManagerV8, IProtocolShareReserve, IAbstractTokenConverter) duplicated names already declared elsewhere in the repo / @venusprotocol/governance-contracts, breaking smock's contract-name lookup and failing 9 pre-existing tests in their 'before each' hook with 'unable to generate smock spec from contract name'. Renamed to migration-scoped names: IACMForMigration, IPsrForMigration, ITokenConverterForMigration. Function selectors unchanged.
Suite hits BSC mainnet token addresses in its before-all hook (balanceOf on USDT/USDC etc.); without an actual fork, the call reverts with empty data. Wrap the describe in the standard Venus 'FORK === "true" && FORKED_NETWORK === bscmainnet' guard so the suite is skipped during normal 'hardhat coverage' runs and only executes when the archive node is configured.
Refactor the gate to follow the existing Venus fork-test pattern
(see tests/fork/RiskFundConverter.ts, SingleTokenConverter.ts):
wrap the suite in forking(FORK_BLOCK, () => { ... }) and gate the
inner describe with FORK_MAINNET. Replace the local impersonate()
helper with initMainnetUser from tests/utils so impersonation logic
stays in one place.
One-shot helper for the Token Converter -> TokenBuyback migration has hardcoded BSC mainnet addresses, so the script restricts deployment to bscmainnet (and hardhat for fork tests). No proxy or ownership transfer needed; the wrapping VIP grants privileges and invokes execute().
Bundling 18 new buyback rows + 12 zeroed stale rows in a single addOrUpdateDistributionConfigs call grew distributionTargets to 30, breaching PSR's maxLoopsLimit (mainnet: 20). Split _rewireProtocolShareReserve into seven phases (A-G) that interleave addOrUpdateDistributionConfigs and removeDistributionConfig so the array length stays <= 20 at every checkpoint while preserving the per-schema sum invariant (1e4 or 0) at each add boundary. Final length: 18. Update the fork test scaffold: pin FORK_BLOCK to the helper deploy block (97105830), replace placeholder zero addresses with the helper's hardcoded mainnet buyback + OPERATOR addresses, and switch the operator-permission assertion to isAllowedToCall via an impersonated buyback signer (matches the on-chain msg.sender encoding ACM uses).
BSC Osaka hardfork enforces per-tx gas cap of 16,777,216 (2^24). The single-tx execute() needed ~17.5M and is unexecutable as queued. Split into two one-shot entrypoints driven by separate VIPs: - execute1: accept all 16 ownerships, allowlist routers, grant operator perms, pause converters, rewire PSR, revoke transient ACM perms, hand back 10 buybacks, renounce DEFAULT_ADMIN_ROLE (~4.26M gas). - execute2: drain 6 converters into replacement buybacks, hand back 6 converters (~8.71M gas). sweepToken is onlyOwner and not pause-gated, so the helper retains converter ownership across the gap while holding zero ACM rights. Fork test drives the split flow end-to-end, asserts the new flags and Execute1NotRun guard, and logs per-tx gasUsed vs the Osaka cap.
Resolved conflicts in deployments/bscmainnet.json and deployments/bscmainnet_addresses.json by taking develop's new buyback entries (UPrimeBuyback + related) and re-adding TokenBuybackMigrationHelper pointing at the new redeployed address 0xa30fcE7A72aD101f6afd4D8b89D1AD8687f51cb0.
…ep 3 Update 10 TokenBuyback proxy addresses to PR #162 redeploy. Move Shortfall.pauseAuctions out of _runPrimeAllocation into _pauseRevenueFlows so revenue-flow pause is atomic with converter pause (single step 3 rather than split across step 3 and step 5). Rewrite trust-model docstring to reflect deploy scripts setting pendingOwner = helper directly, dropping the timelock-hop preparatory acceptOwnership step.
Hardhat 2.16 lacks reliable Cancun hardfork support; 2.22 ships it as default. Pin hardfork: 'cancun' on both Hardhat network configs and flip solc 'evmVersion' from 'paris' to 'cancun' so emitted bytecode matches BSC mainnet's post-Lorentz EVM and benefits from MCOPY for memory copies. Required by the new TokenBuybackMigrationHelper which relies on Cancun-era opcodes on a Cancun-target fork.
VIP now drives PLP/Prime setters directly (addMarket, initializeTokens,
setMax/TokensDistributionSpeed) — helper kept only the PCS V3 USDC ->
{USDT, U} swap legs behind a new 'executeSwap()' one-shot, since the
soft-fail try/catch + leftover-refund still warrants on-chain glue.
Drops unused IPrime/IPLP interfaces + ACM grants, tightens deadline to
1h, consolidates V3 fee constants, DRYs buyback/converter tables.
Add the PLP USDC handoff + executeSwap call between execute1 and execute2, update buyback addresses to the PR #162 redeploy, pin FORK_BLOCK past it, and add asserts for: executedSwap flag, USDT/U landing in PLP above MIN_OUT, USDC leftover refunded to timelock, and Shortfall.auctionsPaused (now set in execute1's _pauseRevenueFlows, not the swap block).
This reverts commit 37841c8.
fred-venus
reviewed
May 13, 2026
| /// depth is too thin for 7,493 USDC; USDT/U pool is deep enough). | ||
| uint256 private constant USDC_PER_LEG = 7493e18; | ||
| uint256 private constant USDT_MIN_OUT = 7418e18; | ||
| uint256 private constant U_MIN_OUT = 7418e18; |
Contributor
There was a problem hiding this comment.
i checked, so among roughly 14k usdc on PrimeLiquidityProvider
- 4k actually belongs to the user (just they don't claim yet)
- so we have 10k usdc left
lets convert 10k to U, for USDT, it's already more than enough
PLP already holds ~25k USDT, more than enough for the May 2026 distribution, and ~4k of PLP's 14.9k USDC is reserved for unclaimed user rewards. Sweep only 10k USDC into the helper and run a single USDC -> USDT -> U multihop. Drops _trySwap and the ExactInputSingleParams struct, retunes USDC_TO_SWAP / U_MIN_OUT against a fresh QuoterV2 read (9,996.60 U quote, ~1% buffer at 9,900e18). Fork test mirrors the new budget and asserts PLP USDT balance is unchanged post-swap.
Merged
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
VIP-618 is unexecutable: single-tx
execute()needs ~17.5M gas, BSC Osaka hardfork enforces a per-tx cap of 16,777,216 (2^24). Split into three one-shot entrypoints driven by the wrapping VIP(s).execute1()— accept ownership of 16 contracts (10 buybacks + 6 timelock-owned converters), self-grant transient ACM perms, pause 6 converters + Shortfall auctions, rewire PSR (7-phase, respectsmaxLoopsLimitand preserves per-schema sum invariant at every checkpoint), revoke transient ACM perms, grant cron operatorexecuteBuyback+forwardBaseAssetperms on all 10 buybacks, renounceDEFAULT_ADMIN_ROLE.executeSwap()— May 2026 Prime allocation swap leg only. VIP feeds USDC to helper viaPLP.sweepToken(USDC, helper, …)first; helper approves PCS V3 router, swaps USDC -> USDT (single-hop) and USDC -> USDT -> U (multihop, direct USDC/U pool too thin), forwards leftover USDC back to NormalTimelock. Both legs soft-fail withStepFailedso thin-pool slippage cannot brick the broader VIP.execute2()— allowlist 9 routers × 10 buybacks, drain 6 converters into replacement buybacks (sweepTokenisonlyOwner, not pause-gated), hand back ownership of all 16 contracts to NormalTimelock.Other Prime allocation actions (
Prime.addMarket(vU),PLP.initializeTokens([U]),PLP.setMaxTokensDistributionSpeed,PLP.setTokensDistributionSpeed) move out of the helper and run as direct NormalTimelock commands in the wrapping VIP — they don't justify on-chain glue.Helper retains converter ownership across the gap. Safe:
sweepTokenisonlyOwner+ not pause-gated; helper holds zero ACM rights between VIPs; converters paused and PSR no longer routes to them.Flags
executed1/executedSwap/executed2+Execute1NotRunguard prevent out-of-order or repeat calls.Buyback proxies use the PR #162 redeploy.
Test plan
npx hardhat compile— cleannpx solhint contracts/helpers/TokenBuybackMigrationHelper.sol— 0 errorsslither— 0 findingsFORK=true FORKED_NETWORK=bscmainnet npx hardhat test tests/fork/TokenBuybackMigrationHelper.tsexecuted1/executedSwap/executed2flags true, ownership returned on all 16, converters paused, ShortfallauctionsPaused, zero residual on every converter for every drained token, recipient buybacks credited with drained balances, PLP credited with>= USDT_MIN_OUTUSDT and>= U_MIN_OUTU, helper holds zero USDC afterexecuteSwap, USDC leftover refunded to NormalTimelock, operator hasexecuteBuyback+forwardBaseAssetperms on every buyback, every router allowlisted on every buyback, PSR rows correct + per-schema sum == 1e4, helper holds noDEFAULT_ADMIN_ROLE.NotTimelockon all three entrypoints,AlreadyExecutedon repeat,Execute1NotRunon out-of-order.Wrapping-VIP recipe
transferOwnership(helper)on the 6 timelock-owned converters (buybackpendingOwneris already set to helper by the deploy script) →grantRole(DEFAULT_ADMIN_ROLE, helper)→helper.execute1().Prime.addMarket(vU, …),PLP.initializeTokens([U]),PLP.setMaxTokensDistributionSpeed([U], …),PLP.sweepToken(USDC, helper, USDC_TO_SWEEP),helper.executeSwap(),PLP.setTokensDistributionSpeed([USDT, U], …), thenhelper.execute2()→acceptOwnershipon all 16 contracts.