TokenBuyback Contract
See the TokenBuyback overview for migration context and the deployed-contracts page for proxy addresses. Live on BNB Chain via VIP-620 and VIP-621;
Overview
TokenBuyback is the contract that holds protocol income arriving from ProtocolShareReserve (PSR) and converts it into a configured BASE_ASSET via on-chain DEX swaps. Each instance is deployed as a Transparent Proxy bound to a (DESTINATION, BASE_ASSET) pair at construction time; both addresses are immutable, so retargeting either requires a new deployment.
Swaps are initiated by an ACM-authorized finance-team cron, not by external community members. The cron builds the router calldata off-chain using DEX aggregators and submits it to executeBuyback. The contract validates a minimum output, pulls the actual tokenIn delta consumed by the router, forwards the resulting BASE_ASSET directly to DESTINATION, and applies two on-chain safety rails (USD daily cap + abnormal-slippage event) using a ResilientOracle reference price.
TokenBuyback implements IIncomeDestination, the same interface as the legacy converters, so PSR rewires to the new instances via a governance VIP without any PSR contract changes.
Architecture
Per-instance state:
DESTINATION (immutable)
Address that receives BASE_ASSET after each swap or forward
BASE_ASSET (immutable)
Output token of every buyback — every swap converts into this
PROTOCOL_SHARE_RESERVE (immutable)
Only address permitted to call updateAssetsState — prevents spoofed AssetsReceived events
RESILIENT_ORACLE (immutable)
USD-pricing source for the daily cap and the abnormal-slippage signal — not used for swap pricing
allowedRouters
On-chain allowlist of DEX routers (governance-managed)
assetsReserves[token]
Balance watermark used to derive the inflow delta on each PSR call
dailyCapUsd
Rolling 24h USD cap on tokenIn consumption (1e18-scaled)
slippageEventUsd
Absolute USD threshold above which AbnormalSlippage fires (1e18-scaled)
usdConsumedInWindow, lastUpdate
Leaky-bucket accumulator state for the daily cap
Income Flow
PSR transfers underlying tokens to
TokenBuybackand callsupdateAssetsState(comptroller, asset).The contract reads
balanceOf(this), compares against theassetsReserves[asset]watermark, emitsAssetsReceived(comptroller, asset, delta)when the delta is non-zero, and resyncs the watermark to the current balance.The cron monitors
AssetsReceivedevents to know which token landed and from which pool.When the cron decides to convert, it calls
executeBuybackwith off-chain-built router calldata.executeBuybackswapstokenIn → BASE_ASSETthrough the allowlisted router, validatesminAmountOut, forwards the output toDESTINATION, pushes fresh oracle prices, enforces the daily cap, and emitsAbnormalSlippageif the USD delta crosses the threshold.BASE_ASSETthat lands without needing a swap (e.g. PSR deliversBASE_ASSETdirectly) is sent on viaforwardBaseAsset, which partitions the balance by caller-suppliedamountso each portion can be attributed to a different comptroller via separate events.
Daily USD Cap (leaky bucket)
executeBuyback enforces a rolling 24h USD cap on tokenIn consumption. The accumulator decays linearly between calls:
Where WINDOW = 24 hours and usdIn = actualAmountIn * priceIn / 1e18. The accumulator usdConsumedInWindow is bounded by dailyCapUsd at every moment, which caps sustainable throughput at dailyCapUsd / 24h plus a one-time burst of up to dailyCapUsd (so worst-case cumulative consumption over any rolling 24h interval is up to ~2 × dailyCapUsd). Reverts with DailyCapExceeded(attempted, cap) past the cap.
Default: $30,000 cap at deploy. Tunable via setDailyCapUsd (ACM-restricted, rejects zero).
Abnormal Slippage Signal
After settling the swap, executeBuyback USD-prices both legs and emits AbnormalSlippage(tokenIn, actualAmountIn, amountOut, usdIn, usdOut) when usdIn − usdOut > slippageEventUsd. The event is informational only — it does not revert. Used by off-chain monitoring to flag swaps routed through hostile pools or stale-aggregator outputs.
Default: $500 absolute. Tunable via setSlippageEventUsd (ACM-restricted, rejects zero).
The slippage check uses the same oracle snapshot as the cap, refreshed via oracle.updateAssetPrice(tokenIn) and oracle.updateAssetPrice(BASE_ASSET) immediately before the read.
Functions
updateAssetsState(address comptroller, address asset)
updateAssetsState(address comptroller, address asset)PSR-only. Records the balance delta against the assetsReserves[asset] watermark and emits AssetsReceived(comptroller, asset, delta) when non-zero. Restricted by the PROTOCOL_SHARE_RESERVE immutable. The reported amount is a balance delta, not an authenticated source-of-funds record — tokens transferred directly to the contract outside the PSR flow are merged into the next event under whichever comptroller PSR happens to be processing.
executeBuyback(tokenIn, amountIn, minAmountOut, deadline, router, routerCalldata, comptroller)
executeBuyback(tokenIn, amountIn, minAmountOut, deadline, router, routerCalldata, comptroller)ACM-restricted. Performs the swap via the supplied router (must be on the allowlist) and forwards the output to DESTINATION.
Sequence:
Validate
deadline,tokenIn != BASE_ASSET, router allowlisted,amountIn > 0and withinbalanceOf(this).Snapshot
tokenInandBASE_ASSETbalances on this contract.forceApprove(router, amountIn),router.functionCall(routerCalldata),forceApprove(router, 0).Compute
actualAmountIn = tokenInBefore - tokenInAfterandamountOut = baseAssetAfter - baseAssetBefore.Revert with
SlippageExceededifamountOut < minAmountOut.Transfer
amountOutofBASE_ASSETtoDESTINATIONand resync both watermarks.Push fresh oracle prices, enforce the daily USD cap, emit
AbnormalSlippageif applicable.Emit
BuybackExecuted(tokenIn, actualAmountIn, amountOut, router, comptroller).
The reported amountIn in BuybackExecuted is the actual router consumption, not the caller-supplied parameter — the event reflects what the router actually pulled.
forwardBaseAsset(address comptroller, uint256 amount)
forwardBaseAsset(address comptroller, uint256 amount)ACM-restricted. Forwards a caller-specified amount of accumulated BASE_ASSET to DESTINATION without a swap. The amount parameter is exposed so the operator can partition multi-pool BASE_ASSET inflows and attribute each portion via a separate BaseAssetForwarded(comptroller, amount) event. No-op when amount == 0. Resyncs the BASE_ASSET watermark after the transfer.
setAllowedRouter(address router, bool allowed)
setAllowedRouter(address router, bool allowed)Governance-only. Adds or removes a DEX router from the on-chain allowlist. Emits RouterAllowlisted(router, allowed).
setDailyCapUsd(uint256 newCap)
setDailyCapUsd(uint256 newCap)ACM-restricted. Updates the rolling 24h USD cap (1e18-scaled). Reverts with ZeroValueNotAllowed if newCap is zero — the cap is not a kill switch; revoke ACM permissions to fully disable buybacks. Emits DailyCapUpdated(oldCap, newCap).
setSlippageEventUsd(uint256 newThreshold)
setSlippageEventUsd(uint256 newThreshold)ACM-restricted. Updates the absolute USD slippage-event threshold (1e18-scaled). Reverts with ZeroValueNotAllowed if newThreshold is zero. Emits SlippageEventUsdUpdated(oldThreshold, newThreshold).
sweepToken(address token, address to, uint256 amount)
sweepToken(address token, address to, uint256 amount)Governance-only. Emergency token recovery. Also the canonical recovery path for tokens transferred directly to the contract outside the PSR flow, since on-chain accounting cannot distinguish donations from authenticated inflows. Resyncs assetsReserves[token] to the post-transfer balance. Emits SweepToken(token, to, amount).
Events
AssetsReceived(comptroller, asset, amount)
PSR delivers tokens (non-zero delta)
BuybackExecuted(tokenIn, amountIn, amountOut, router, comptroller)
Successful DEX swap; amountIn is the on-chain router consumption
BaseAssetForwarded(comptroller, amount)
BASE_ASSET forwarded to DESTINATION without swap
RouterAllowlisted(router, allowed)
Router added or removed from allowlist
SweepToken(token, to, amount)
Emergency token sweep
AbnormalSlippage(tokenIn, actualAmountIn, amountOut, usdIn, usdOut)
Swap returned less USD value than input by more than slippageEventUsd
DailyCapUpdated(oldCap, newCap)
setDailyCapUsd succeeded
SlippageEventUsdUpdated(oldThreshold, newThreshold)
setSlippageEventUsd succeeded
Errors
UnauthorizedCaller(caller)
updateAssetsState called by anyone other than PROTOCOL_SHARE_RESERVE
RouterNotAllowed(router)
executeBuyback with a router not on the allowlist
InvalidTokenIn(tokenIn)
executeBuyback with tokenIn == BASE_ASSET
InsufficientBalance(token, requested, available)
executeBuyback or forwardBaseAsset requested more than the contract holds
SlippageExceeded(expected, actual)
Swap output fell below minAmountOut
DeadlineExpired(deadline, blockTimestamp)
executeBuyback after the supplied deadline
DailyCapExceeded(attempted, cap)
executeBuyback would push cumulative USD usage past dailyCapUsd
ZeroAddressNotAllowed
Constructor / setAllowedRouter / sweepToken with zero address
ZeroValueNotAllowed
Setters or sweepToken invoked with zero
Destinations
The (DESTINATION, BASE_ASSET) pair is fixed per instance. Common destinations across deployments:
VTreasury— accumulates protocol-revenue tokens (U, BTCB, ETH, USDT, USDC, XVS) bought back from non-Prime, non-RiskFund income.PrimeLiquidityProvider— accumulates Prime rewards in USDT and U; later distributed to Prime users according to per-token speeds configured via VIP. See Prime tokens.RiskFundV2— accumulates USDT used by Shortfall auctions. Per-pool accounting was removed alongside the migration;RiskFundV2now draws against its raw balance.XVSVaultTreasury— accumulates XVS that fundsXVSVaultrewards via VIP.
See deployed contracts for per-chain proxy addresses and their (base asset, destination) mapping.
Operator Notes
Conversions run on a defined schedule rather than on every inflow. The cron decides when to call
executeBuyback; the contract has no internal trigger.The oracle is consulted only for the cap and slippage signal — not for swap pricing. Swap economics are determined by the off-chain-built router calldata.
The daily cap bounds blast radius if the operator key is compromised. It is not intended to throttle normal operation.
Routers must be added to the allowlist by governance before the cron can route through them. Removing a router via
setAllowedRouter(router, false)immediately blocks future swaps through it.Donations to the contract (tokens sent directly, outside the PSR flow) are not distinguished from authenticated PSR inflows on-chain. Use
sweepTokento recover them if needed.
Last updated

