Arcadia Finance RebalancerSpot swapData router-context spoof / cross-account drain (2025-07-15)
- Name: Arcadia Finance RebalancerSpot
swapDatarouter-context spoof / cross-account drain - Date: 2025-07-15
- Protocol: Arcadia Finance
- Chain: Base
- Category: callback-frame authority reuse / unchecked external call / trusted-router context spoof / cross-account drain
- Loss: about 3.6M
- Summary: Arcadia’s
RebalancerSpotlet an initiator supply arbitraryswapDatathat eventually decoded to(router, amountIn, data)insideRebalancer._swapViaRouter(). During a liveIAccount.flashAction()rebalance callback, the contract executedrouter.call(data)directly from the rebalancer’s own context. That meant the callee sawmsg.sender == RebalancerSpot, and other Arcadia Accounts already trusted that rebalancer as an asset manager. The attacker first repaired target accounts’ debt state with flash-loaned funds so end-of-call health checks would pass, then used a malicious router / helper path to make privileged cross-account calls against victim Arcadia Accounts, withdraw their CL positions, unwind the LP NFTs, repay the flash loans, and keep the remainder. Arcadia’s own pre-incident docs had framed the trust model asa malicious initiator can never steal funds or Liquidity from the Account Owner; the exploit was the concrete counterexample because initiator-shaped routing bytes were allowed to inherit the rebalancer’s already-whitelisted authority. - Trigger / entry point:
RebalancerSpot.rebalance()accepted initiator-controlledswapDataand forwarded execution throughIAccount.flashAction(address(this), actionData)intoRebalancer.executeAction().- Inside
_swapViaRouter(), Arcadia decoded the initiator’s router address and calldata and executed a raw low-levelrouter.call(data)from the rebalancer contract itself. - Victim Arcadia Accounts had already whitelisted the same rebalancer as an asset manager, so calls arriving from the rebalancer context carried real privileges against those separate accounts.
- Arcadia’s flash-action health checks only cared that the relevant account finished solvent, which let the attacker temporarily repay liabilities first and then cash out the privileged call path.
- Root cause:
- Arcadia authenticated only that
some swap should happen inside a valid rebalance frame; it did not bind the router call to one safe counterparty, one allowed selector set, or even one target account. - The crucial trust-boundary mistake was letting arbitrary router execution inherit the rebalancer’s own asset-manager identity.
- The broken invariant was:
initiator-supplied swap routing must never be able to spend rebalancer authority outside the single account and single position being rebalanced. - Once
router.call(data)ran withmsg.sender == RebalancerSpot, the attacker could pivot from their own rebalance frame into victim accounts that already trusted that rebalancer.
- Arcadia authenticated only that
- Exploit path:
- The attacker funded EOA
0x0fa54e967a9cc5df2af38babc376c91a29878615from Tornado Cash in transaction0xf5300504d25e930bd0e11e968d2d77a3c8effe1c56e583150226be084375ee3c. - They prepared attacker-controlled Arcadia account / helper contracts, including first-wave contract
0x6250DFD35ca9eee5Ea21b5837F6F21425BEe4553, and set up a rebalanceable position under their control. - Using large Morpho flash loans, the attacker repaid liabilities on target Arcadia Accounts so the downstream account-health checks would no longer block privileged asset-manager actions. Public breakdowns describe one representative path where about
14.4 cbBTCof victim debt was repaid before the drain leg. - The attacker called
RebalancerSpot.rebalance()on their own controlled account with maliciousswapData. - Arcadia routed the call as
RebalancerSpot.rebalance() -> IAccount.flashAction() -> Rebalancer.executeAction() -> _swap() -> _swapViaRouter(), where_swapViaRouter()decoded the attacker-chosen router and executedrouter.call(data). - Because that router call originated from the rebalancer, the attacker could use the still-live rebalancer authority against other Arcadia Accounts that had already approved the same rebalancer as asset manager.
- The malicious path withdrew victim CL LP positions / NFTs from accounts such as
0x9529e5988ced568898566782e88012cf11c3ec99, then unwound those positions and transferred the value out to the attacker side. - The attacker repeated the pattern across multiple transactions, including first-wave exploit txs
0x06ce76eae6c12073df4aaf0b4231f951e4153a67f3abc1c1a547eb57d1218150and0x0b9bed09d241cef8078e6708909f98574c33ee06abcc2f80bc41731cd462d60b, plus a later wave including0x723989a674074eab665dc93cef3b123f0f5cea764dbfbeb6c8a8c80278d0bc09. - After converting proceeds into ETH on Base, the attacker bridged funds to Ethereum through Across in transaction
0x3a6ec9625dbfd0482781f9c8eaa93def78ffc2a73df3de5ffc650723b9b57211and split the loot across follow-on addresses.
- The attacker funded EOA
- What the attacker needed to be true:
swapDatahad to remain initiator-controlled all the way down to a raw router call.- The router target had to execute with the rebalancer’s caller identity rather than through an unprivileged trampoline.
- Victim accounts had to already trust that rebalancer as an asset manager.
- The attacker needed a way to make victim accounts pass end-of-call health checks, which the flash-loan-funded debt-repayment step provided.
- The targeted accounts needed CL positions valuable enough that withdrawing and burning them would cover flash-loan costs and leave profit.
- Technical anatomy worth remembering:
- This was not just
unchecked external callin the abstract. The decisive bug was that a supposedly boundedswap routersurface ran inside a still-live privileged asset-manager frame. - The attacker did not need to forge Arcadia’s authorization model from scratch. They reused a real authorization object —
RebalancerSpotas trusted asset manager — against the wrong account. - That makes Arcadia a strong callback-frame authority reuse exemplar: the in-flight rebalance frame was real, but the protocol failed to prove that the router leg belonged only to the account currently being rebalanced.
- Arcadia’s own docs are worth preserving as part of the failure: the documented trust assumption was that initiators could choose when and how to rebalance but
can never steal funds or Liquidity from the Account Owner. In reality, the router leg had become a capability-bearing execution surface rather than a passive swap detail. - Arcadia’s later fix is unusually clarifying at the code level. Commit
31f7709changed_swapViaRouter()fromsafeApproveWithRetry(router, amountIn)plus rawrouter.call(data)inside the rebalancer to a flow that transferstokenIninto a separateRouterTrampoline, callsROUTER_TRAMPOLINE.execute(router, data, tokenIn, tokenOut, amountIn), and then only consumes the returned token balances. That preserved flexible routing, but the router now saw the trampoline as caller instead of inheritingRebalancerSpotidentity. - The follow-on hardening commit
30a5a9ealso added aGuardianpause layer sorebalance(),compound(), andclaim()style CL-manager entrypoints could be halted directly withwhenNotPaused, narrowing the operational blast radius of future manager-surface incidents. - The incident also exposed an operational side lesson: Arcadia’s July 14 pause / unpause cycle reportedly left the team inside a cooldown window that complicated emergency re-pausing during the live attack. That did not cause the bug, but it did worsen blast-radius control.
- This was not just
- Representative transactions and addresses:
- Tornado funding tx:
0xf5300504d25e930bd0e11e968d2d77a3c8effe1c56e583150226be084375ee3c - First exploit tx:
0x06ce76eae6c12073df4aaf0b4231f951e4153a67f3abc1c1a547eb57d1218150 - Second exploit tx:
0x0b9bed09d241cef8078e6708909f98574c33ee06abcc2f80bc41731cd462d60b - Later exploit tx:
0x723989a674074eab665dc93cef3b123f0f5cea764dbfbeb6c8a8c80278d0bc09 - Across bridge tx:
0x3a6ec9625dbfd0482781f9c8eaa93def78ffc2a73df3de5ffc650723b9b57211 - Attacker EOA:
0x0fa54e967a9cc5df2af38babc376c91a29878615 - RebalancerSpot:
0xC729213B9b72694F202FeB9cf40FE8ba5F5A4509 - One cited victim Arcadia Account:
0x9529e5988ced568898566782e88012cf11c3ec99 - First-wave attacker helper / account surface:
0x6250DFD35ca9eee5Ea21b5837F6F21425BEe4553 - Second-wave helper:
0x1DBC011983288B334397B4F64c29F941bE4DF265 - Pause tx before the attack window:
0x23c3796c42dbca0148975729a5f2dddf539c4c7a8284289e12190fbd5a6c091b - Unpause tx that reopened the surface:
0x38b744e967e6d6ed8870619ac2f35b6d5612a396eaf3ba981ed754c7395c310d
- Tornado funding tx:
- Response / mitigation:
- Arcadia told users to remove asset-manager permissions, said the loss was isolated to Asset Manager contracts, and published a postmortem the same day.
- The code-level fix path was concrete: commit
31f7709introducedRouterTrampolineso routers executed against a narrow helper holding only swap inventory rather than against the privileged rebalancer itself, and commit30a5a9eadded a dedicatedGuardianpause layer to CL-manager entrypoints. - Public follow-up emphasized that lending and token contracts were not directly compromised; the broken surface sat in periphery asset-manager contracts around rebalancing.
- Arcadia also attempted onchain negotiation with the attacker and publicly offered a
10%white-hat bounty for return of funds, but no recovery was publicly confirmed.
Why this incident matters
- It is a very clean example of how
arbitrary router flexibilitycan silently becomecross-account admin authoritywhen adapter execution inherits the wrong caller identity. - The exploit usefully complements f(x) Protocol and GMX V1. Those incidents show stale provider or refund authority; Arcadia shows the same family in an asset-manager / router setting where the leaked authority was
who this external leg is allowed to act for. - The fix is also reusable beyond Arcadia: when protocols need flexible routing, they should move execution into a deliberately unprivileged trampoline or otherwise pre-commit exact target / selector semantics, instead of letting routers execute from the privileged contract itself.
- It is also a strong ordering lesson. The rebalance frame should have been narrowed before the external router leg ran at all, or resumed only through a much weaker post-router capability that could not inherit cross-account asset-manager authority.
Pattern tags
- Callback-frame authority reuse
- Privilege-stripped external execution and inventory-scoped trampolines
- Cross-account manager identity leakage
- Initiator-shaped routing bytes promoted into privileged execution authority
- Solvency-check laundering via temporary debt repair
Why the exploit worked
- Arcadia had one real rebalance frame, but it never fused
this exact account is being rebalanced,this exact router leg is allowed, andthis exact caller identity may survive the external hopinto one consumed security object. The docs’ trust claim that a malicious initiator could not steal funds depended on that fusion, but the bytecode only proved that some rebalance callback was live. _swapViaRouter()executed attacker-chosen calldata withmsg.sender == RebalancerSpot, so the attacker did not need to counterfeit manager authority. They only needed to smuggle attacker-shaped routing through a frame that already carried real asset-manager permissions on other Arcadia Accounts.- End-of-call health checks enforced solvency, not account-local authority. Flash-loaned debt repayment let the attacker temporarily restore victim accounts to a healthy enough state that the privileged cross-account manager actions would pass the downstream checks.
- The payout path was immediate and legible: once the malicious router leg reached victim accounts, the attacker could withdraw CL positions, unwind the LP NFTs, repay the flash loans, and keep the remaining inventory. There was no narrower trampoline, per-leg inventory bucket, or route-local settlement envelope forcing the router output to stay local to the attacker’s own rebalance.
- Arcadia’s post-incident fixes are useful because they name the broken boundary precisely.
RouterTrampolinestripped the router leg ofRebalancerSpotidentity, while the laterGuardianlayer acknowledged that callback-heavy manager surfaces need direct, low-latency kill switches rather than only broader protocol-level pause assumptions.
Detection and audit pivots
- Search for any
router.call(data), adapter hop, or helper self-call that runs while the caller still holds cross-account manager, operator, or callback-approved authority elsewhere in the system. The dangerous question is not onlycan arbitrary code run, butwhose identity does that code inherit when it runs? - Treat
healthy at function endandauthorized to touch this account right nowas separate invariants. If flash loans, temporary repayments, or intra-tx inventory moves can make a victim finish solvent, a final health check is not evidence that the live external leg stayed scoped to the right account. - Inventory every other contract or account that trusts the same manager identity. Once many user accounts whitelist one helper, any stale router frame on that helper is automatically a cross-account blast-radius problem.
- Check whether the external route executes from a privileged planner or from a deliberately weak trampoline. The architectural review question is
does the router see only this leg's inventory, not merelyis the router allowlisted. - Compare docs and bytecode line by line when product claims say an initiator can choose routes or timing but
can never steal funds. Arcadia is the reminder that these assurances are really claims about caller identity, selector reach, and frame scoping, not just about user intent. - Ask what exact pause surface exists for manager-heavy periphery. If
rebalance(),compound(), or similar helper entrypoints cannot be frozen directly and immediately, then even a small frame-authenticity bug inherits a much larger operational loss window.
Failure-prevention checklist
- Bind each rebalance frame to one exact account, one router or trampoline endpoint, one allowed selector family, one input-asset bucket, and one output-asset bucket before external execution begins.
Some rebalance is liveis not enough once multiple accounts trust the same rebalancer identity. - Never execute initiator-shaped router calldata from a contract that already holds cross-account asset-manager permissions. If routing flexibility is required, move the leg into an explicitly unprivileged trampoline that only holds the current swap inventory.
- Scope inventory and approvals to the current leg. The external router should receive only the exact
tokenInamount and temporary spend rights needed for that swap, not the rebalancer’s standing authority against unrelated Arcadia Accounts. - Treat account-health checks as downstream solvency checks, not as authorization checks. A flow should still fail if the router leg tries to act for the wrong account even when flash-loaned debt repayment can make the victim finish healthy.
- Commit the target account before the callback / router leg and never let external calldata redefine which account, CL position, or LP NFT the privileged frame may touch.
- Separate
initiator may choose a routefrominitiator may choose arbitrary code. Allowed routers, selectors, token pairs, and return-shape expectations should be pre-committed locally rather than inferred from opaqueswapDataat the moment of execution. - Keep settlement leg-local. Measure outputs from trampoline-local deltas or explicit returned balances instead of trusting ambient balance changes on the privileged rebalancer contract.
- Revoke or sharply limit reusable asset-manager identity where possible. If one manager contract is trusted by many user accounts, assume any leaked callback or routing frame can become a cross-account drain unless the frame itself rebinds account identity every time.
- Maintain direct pause authority over rebalancing and CL-manager entrypoints. Arcadia’s later
Guardianlayer is part of the preserved lesson: operator response should be able to halt the exact manager surface under attack without waiting for broader protocol-level intervention or cooldown expiry.
Audit prompts
- What exact identity does the router leg see as
msg.sender, and which other accounts or modules already trust that identity as asset manager, operator, or callback-approved helper? - Does
swapDatacommit one account-local route, or can it still choose a fresh target account, helper contract, selector set, or victim-owned CL position after the rebalance frame has opened? - If an attacker repays a victim account’s debt temporarily, what stops the privileged rebalancer frame from acting on that victim account before end-of-call health checks run?
- Which fields are pre-committed before
flashAction()starts: account, router, tokens, max input amount, expected output tokens, selector family, and settlement bucket? Which of those are still attacker-shaped at execution time? - Does the external router execute from a weak trampoline that only holds one leg’s inventory, or from the same contract identity that other Arcadia Accounts already approved as manager?
- Can router or helper calldata reach sibling account-management functions, NFT withdrawal paths, or CL unwinding surfaces that were never meant to be part of one rebalance route?
- After the router leg completes, does settlement consume leg-local outputs only, or can ambient balances and cross-module state changes on the manager contract be mistaken for valid route results?
- What exact pause or guardian controls can freeze
rebalance(),compound(), or related manager surfaces immediately if a live exploit begins, and are those controls blocked by cooldown or governance latency? - If the docs claim
a malicious initiator cannot steal funds, what onchain checks actually prove that statement when routing bytes, helper addresses, and router selectors are attacker-supplied?
Lessons that should already be standard
- Flexible routing is not a free UX feature when the router executes from a pre-trusted manager identity. Caller identity is part of the asset flow.
- Final solvency checks do not constrain authority scope. If an attacker can temporarily repair a victim account, end-of-call health checks are a terrible substitute for binding the whole frame to the right account from the start.
Whitelisted by each useris not the same thing assafe across users. Once many accounts trust one manager contract, any stale or overly broad execution frame on that contract is a cross-account security object.- Opaque
swapDatais already privileged if it decides which code runs while a live manager frame is open. The bug is not onlyunchecked external call; it isunchecked external call from the wrong identity and against the wrong authority boundary. - Trampolines are not cosmetic wrappers. Arcadia’s own
RouterTrampolinefix preserves the right architectural lesson: flexibility should survive, but it should survive only inside an identity that cannot be mistaken for the privileged planner elsewhere in the system. - Operational kill switches belong inside the design story for callback-heavy manager surfaces. Once a route-authenticity bug goes live, blast-radius control depends on whether the exact helper family under attack can be paused immediately.
- Threat models stated in docs need bytecode-level proof. Arcadia’s pre-incident claim that malicious initiators could not steal user assets is useful precisely because the exploit showed how easy it is for routing freedom to outrun the trust model unless the authority boundary is encoded onchain.
Internal linkages
- Core recurring pattern: callback-frame-authority-reuse.
- Parent family: reentrancy — the exploit ran through a live
flashAction()callback window, even though the sharper lesson is stale authority reuse rather than generic recursive control flow. - Sharper positive-design companion: callback-frame-truth-security-object — Arcadia needed one authenticated rebalance frame binding the specific account under management, the one allowed router / selector surface, the permitted asset flow, and the exact authority that could survive the
flashAction()callback. - Ordering companion: temporary-capability-closure-before-external-delivery — the broad rebalancer authority should have been extinguished or reduced before the initiator-shaped external router leg ran, rather than surviving long enough for
router.call(data)to inherit cross-account asset-manager power. - Architecture companion: privilege-stripped-external-execution-and-inventory-scoped-trampolines — Arcadia’s actual fix moved flexible router execution into
RouterTrampoline, which kept router legs inventory-scoped and stopped routers from inheritingRebalancerSpotidentity in the first place. - Adjacent pattern: account-confusion-root-of-trust-substitution — attacker-shaped account surfaces and router endpoints helped open the pivot, but the decisive theft came from reusing a real in-flight rebalancer frame against the wrong account.
Control surface
-
The leverage sat in initiator-supplied
swapData, raw router execution insideRebalancer._swapViaRouter(), the liveflashAction()callback frame, and Arcadia Accounts that had already whitelisted the same rebalancer as asset manager. -
Arcadia did not just trust the wrong router. It let router execution inherit the exact authority boundary that was supposed to remain local to one account and one rebalance.
-
Sources:
- https://rekt.news/arcadiafi-rekt
- https://x.com/ArcadiaFi/status/1944984622183370867
- https://x.com/ArcadiaFi/status/1945189864183619823
- https://blog.solidityscan.com/arcadia-finance-hack-analysis-a03a722e554d/
- https://www.quillaudits.com/blog/hack-analysis/arcadia-finance-hack-analysis
- https://docs.arcadia.finance/protocol/asset-managers/rebalancers
- https://github.com/arcadia-finance/asset-managers/commit/31f77095956b80d76ebbbbd9956dc770dbe5ada1
- https://github.com/arcadia-finance/asset-managers/commit/30a5a9ea63740b57928b4b0817e7795368062a3e
- https://basescan.org/tx/0x06ce76eae6c12073df4aaf0b4231f951e4153a67f3abc1c1a547eb57d1218150
- https://basescan.org/tx/0x0b9bed09d241cef8078e6708909f98574c33ee06abcc2f80bc41731cd462d60b
- https://basescan.org/tx/0x723989a674074eab665dc93cef3b123f0f5cea764dbfbeb6c8a8c80278d0bc09
- https://etherscan.io/tx/0xf5300504d25e930bd0e11e968d2d77a3c8effe1c56e583150226be084375ee3c
- https://etherscan.io/tx/0x3a6ec9625dbfd0482781f9c8eaa93def78ffc2a73df3de5ffc650723b9b57211
-
Last reviewed: 2026-07-29 UTC