Skip to content

Add flash CCR rebalancer#8915

Draft
Mo-Hussain wants to merge 2 commits into
mainfrom
mo/flash-ccr-rebalancer
Draft

Add flash CCR rebalancer#8915
Mo-Hussain wants to merge 2 commits into
mainfrom
mo/flash-ccr-rebalancer

Conversation

@Mo-Hussain

@Mo-Hussain Mo-Hussain commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds FlashCcrRebalancer, a helper contract for atomically rebalancing same-chain CrossCollateralRouter collateral pools with a flashloan and caller-supplied swap calldata.

The intended use case is CROSS/moonpay-style inventory balancing where one asset, e.g. USDC, is deficient on a chain and the paired same-chain asset, e.g. USDT, is surplus. The helper borrows the deficient token, sends it through same-chain CCR transferRemoteTo, receives the surplus token, swaps back into the borrowed token, tops up bounded shortfalls if needed, and repays the flashloan in the same transaction.

Design Diagram

flowchart LR
    Operator[Allowlisted rebalancer]
    Helper[FlashCcrRebalancer]
    Flash{Flashloan provider}
    Aave[Aave V3 flashLoanSimple]
    Uni[Uniswap V3 pool flash]
    Deficit[Deficit CCR router]
    Surplus[Surplus CCR router]
    Swap[Allowlisted swap target]
    Spender[Allowlisted allowance target]
    TopUp[Top-up payer]
    Refund[Refund recipient]

    Operator -->|rebalance params| Helper
    Helper -->|borrow deficit token| Flash
    Flash --> Aave
    Flash --> Uni
    Aave -->|callback + borrowed token| Helper
    Uni -->|callback + borrowed token| Helper

    Helper -->|approve exact CCR debit| Deficit
    Deficit -->|transferRemoteTo localDomain, recipient=helper, target=surplus router| Surplus
    Surplus -->|surplus token| Helper

    TopUp -.->|optional surplus top-up before swap| Helper
    Helper -->|bounded approval amountInMax| Spender
    Helper -->|call supplied calldata/value| Swap
    Spender -->|pull surplus token if route requires| Helper
    Swap -->|deficit token output| Helper

    TopUp -.->|optional deficit top-up for CCR fee or repayment shortfall| Helper
    Helper -->|repay amount + fee/premium| Flash
    Helper -->|unused top-up / route residue| Refund
Loading

Motivation

The design reduces the amount of route inventory that needs to be held by, approved to, or moved out of adapter contracts during a rebalance. Instead of staging assets in an executor or adapter and relying on a multi-step operational flow, the whole rebalance is one atomic transaction:

  • if the swap is bad, the CCR leg reverts
  • if repayment cannot be made, the flashloan reverts
  • if any allowlist, debit, top-up, or output bound is violated, the transaction reverts

This gives us the useful flashloan property discussed in the design thread: the repayment requirement narrows the asset-loss surface because the borrowed asset, CCR transfer, swap, top-up, and repayment must all succeed together.

High-Level Flow

  1. An allowlisted rebalancer calls rebalance with flashloan, CCR, swap, top-up, refund, and deadline params.
  2. The helper starts either:
    • Aave V3 flashLoanSimple, or
    • Uniswap V3 pool flash.
  3. The flashloan callback validates:
    • active callback state exists
    • callback provider type matches the requested provider
    • msg.sender is the allowlisted active provider
    • callback params hash matches the active rebalance params
    • borrowed asset and amount match the request
    • the callback has not already been consumed or reentered
  4. The helper validates the CCR leg:
    • deficit router token equals the flashloan token
    • surplus router token equals swap input token
    • swap output token equals deficit token
    • both routers are on the expected local domain
    • target router is exactly the surplus router
  5. The helper quotes transferRemoteTo and sums only deficit-token quote amounts.
  6. If the CCR debit exceeds the borrowed balance, the helper pulls bounded deficit-token top-up before CCR execution.
  7. The helper approves the deficit router for the exact quoted debit and calls same-domain transferRemoteTo(localDomain, helper, amount, surplusRouter).
  8. The helper verifies the surplus-token balance delta is at least minSurplusReceived.
  9. If the surplus balance is below swap.amountInMax, the helper pulls bounded surplus-token top-up from the configured payer.
  10. The helper approves the configured allowanceTarget for at most amountInMax, calls the configured swap target with calldata/value, then clears the approval.
  11. The helper verifies the deficit-token balance delta is at least minAmountOut.
  12. If the deficit-token balance is still below flashloan debt, the helper pulls bounded deficit-token top-up.
  13. The helper repays the flashloan:
  • Aave path: approves the pool for amount + premium, letting Aave pull repayment
  • Uniswap path: transfers amount + fee back to the pool during callback
  1. After callback completion, the helper clears transient allowances/state and refunds:
  • unused surplus-token top-up back to topUp.payer
  • remaining route residue/native value to refundTo

Provider Configuration

The contract is intentionally configurable but bounded by owner-managed allowlists:

  • allowedRebalancers[caller]
  • allowedFlashLoanProviders[provider][pool]
  • allowedSwapTargets[target]
  • allowedAllowanceTargets[spender]

Supported flashloan providers in this PR:

  • Aave V3 flashLoanSimple
  • Uniswap V3 pool flash

The provider enum keeps callback semantics explicit because Aave and Uniswap repay differently.

swaps.xyz / Swap Calldata Design

The helper does not integrate directly with swaps.xyz onchain. Instead, swaps.xyz remains an offchain quote/calldata provider.

The expected integration shape is:

  1. Offchain executor asks swaps.xyz for an exact-output route sized to repay the flash debt.
  2. The request should use the helper as sender, and generally the helper as recipient.
  3. The executor passes the returned transaction fields into SwapCall:
    • target: returned tx.to
    • allowanceTarget: returned approval/spender address, which may differ from tx.to
    • tokenIn: surplus token received from CCR
    • tokenOut: borrowed/deficit token
    • amountInMax: maximum surplus token the helper may approve/spend
    • minAmountOut: minimum deficit-token balance increase required
    • value: returned native value, if any
    • data: returned calldata

The contract does not decode or trust the route internals. It enforces the local invariants around that calldata:

  • target must be allowlisted
  • allowance target must be allowlisted
  • token input approval is capped at amountInMax
  • approval is cleared after the call
  • output is measured by token balance delta
  • swap must produce at least minAmountOut
  • the whole flow reverts if flashloan repayment cannot be made

This supports both simple Uniswap-style call targets and aggregator-style flows where tx.to and ERC20 spender are separate addresses.

Top-Up Model

The helper supports both top-up directions discussed in the design thread:

  • Surplus-token top-up before swap: if CCR returns less surplus token than the swap may need, the helper can pull up to maxSurplusTokenTopUp from topUp.payer.
  • Deficit-token top-up before CCR / before repayment: if CCR fees or flashloan repayment require more deficit token than the borrowed/swap output balance, the helper can pull up to maxDeficitTokenTopUp.

Unused surplus-token top-up is refunded to topUp.payer before any generic residue sweep. Residue that came from the route itself is swept to refundTo.

Safety Properties

Important invariants enforced by the implementation:

  • only allowlisted rebalancers can start a rebalance
  • deadline must not be expired
  • flashloan callback params are bound to the active rebalance by hash
  • callback sender must be the active allowlisted provider
  • callback asset and amount must match the request
  • only one flash callback can be consumed per rebalance
  • nested callback execution is rejected
  • CCR target router must be the configured same-chain surplus router
  • quote tokens must be the deficit token
  • CCR debit must be below maxDeficitTokenDebit
  • swap target and allowance target must be allowlisted
  • swap input approval is bounded and cleared
  • swap output is checked by balance delta
  • top-ups are capped by user-supplied maximums
  • all helper-held relevant token balances are swept/refunded after a successful rebalance

Tests

Adds focused Solidity coverage for:

  • Aave V3 happy path with USDC deficit / USDT surplus
  • Uniswap V3 happy path with USDC deficit / USDT surplus
  • reverse USDT deficit / USDC surplus direction
  • separate swap target and allowance target, matching aggregator/swaps.xyz-style calldata
  • surplus-token top-up with unused amount refunded to payer
  • route residue refunded to refundTo
  • deficit-token top-up for repayment shortfall
  • deficit-token top-up for CCR fee plus repayment shortfall
  • swap output shortfall rolling back the CCR leg
  • repayment shortfall rolling back the CCR leg
  • unauthorized caller rejection
  • unallowlisted flash provider rejection
  • unallowlisted swap target rejection
  • unallowlisted allowance target rejection
  • deadline expiry
  • CCR debit max bound
  • unsupported quote token rejection
  • surplus and deficit top-up limits
  • malicious swap attempting to steal beyond allowance
  • direct callback spoofing
  • nested Aave callback from swap target
  • nested Uniswap callback from swap target
  • entrypoint reentrancy from swap target
  • fuzzed Aave amounts and premiums

Verification

  • forge test --match-path test/rebalancing/FlashCcrRebalancer.t.sol
    • 25 passed
  • forge build --skip test --contracts contracts/rebalancing/FlashCcrRebalancer.sol
    • passed

Follow-Up / Rollout Notes

This PR adds the contract and focused unit coverage. Before production use, the next steps should be:

  • deploy with conservative owner-controlled allowlists
  • fork-test against real Aave/Uniswap pools on target chains
  • fork-test real swaps.xyz calldata for exact-output USDC/USDT routes
  • wire executor config to produce RebalanceParams from route imbalance analysis
  • set operational policies for max debit, max top-up, slippage, deadline, and refund recipient

@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.97436% with 41 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.32%. Comparing base (87f0933) to head (bc12f3c).
⚠️ Report is 26 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8915      +/-   ##
==========================================
- Coverage   79.33%   79.32%   -0.02%     
==========================================
  Files         143      144       +1     
  Lines        4278     4473     +195     
  Branches      436      479      +43     
==========================================
+ Hits         3394     3548     +154     
- Misses        855      895      +40     
- Partials       29       30       +1     
Flag Coverage Δ
solidity 80.50% <78.97%> (-0.09%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
core 87.80% <ø> (ø)
hooks 78.11% <ø> (ø)
isms 81.46% <ø> (ø)
token 88.00% <ø> (ø)
middlewares 87.76% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

1 participant