Arcadia Finance vaultManagementAction() liquidation reentrancy drain (2023-07-10)
- Name: Arcadia Finance
vaultManagementAction()liquidation reentrancy drain - Date: 2023-07-10
- Protocol: Arcadia Finance
- Chain: Ethereum and Optimism
- Category: cross-contract reentrancy / optimistic vault-management accounting failure / liquidation-state desynchronization
- Loss: about $459,030.44 across Ethereum and Optimism
- Summary: Arcadia’s
vaultManagementAction()deliberately let a vault owner withdraw assets to a multicall helper, perform arbitrary allowed actions, and only then run the final health check. That optimistic flow assumed nothing else could mutate the vault’s debt state before the check. The attacker proved otherwise. Using flash liquidity and thendoActionWithLeverage()to lever a fresh vault, they calledvaultManagementAction()to pull all collateral out of the vault and, during the multicall window, re-entered throughLendingPool.liquidateVault(). Liquidation routed throughLiquidatorback intoVault.liquidateVault(), started an auction, and removed the vault’s debt while the collateral was already outside the vault. When control returned tovaultManagementAction(), Arcadia saw a vault with0assets and0debt, so the final solvency check passed and the attacker kept the withdrawn collateral. Arcadia’s own loss accounting later broke the damage out as about103,200.653468 USDCplus11.047195997555799542 ETHon Ethereum and59,427.425162 USDCplus148.224144212923406305 ETHon Optimism. - Trigger / entry point:
vaultManagementAction()let a vault owner optimistically withdraw assets from the vault to a multicall contract before proving the vault would still be healthy at function exit.- During that external-call window, the attacker could call back into Arcadia through
LendingPool.liquidateVault(). - Liquidation crossed modules as
LendingPool -> Liquidator -> Vault.liquidateVault(), so the vault’s debt state changed while the original vault-management action was still in progress.
- Root cause:
- Arcadia’s vault-management flow relied on deferred health checking but did not block liquidation-driven re-entry into the same vault while assets were temporarily outside the vault.
- The broken invariant was simple: the system assumed
assets removed for optimistic external actionsandvault debt / liquidation statuscould not be changed independently during one in-flight management call. - Once liquidation was allowed during that window, Arcadia erased the vault’s debt and started an auction against a temporarily empty vault, converting what should have been an unhealthy intermediate state into a final state that trivially passed validation.
- The protocol’s own postmortem explicitly rejected the idea that this was mainly an untrusted-input bug; the decisive issue was missing reentrancy protection on the liquidation path while
vaultManagementAction()was mid-flight.
- Exploit path:
- The attacker took flash liquidity and opened fresh Arcadia vaults on Ethereum and Optimism.
- On the representative USDC path described by Arcadia, the attacker created a new vault and deposited
20,672 USDC. - The attacker called
doActionWithLeverage()to borrow103,210 USDCfrom the liquidity pool and immediately redeposit it, leaving the vault with about123,882 USDof assets against103,210 USDCof debt. - The attacker then called
vaultManagementAction(), the optimistic vault-management function. - Inside that flow, the attacker withdrew all vault assets to the multicall helper, leaving the vault temporarily with
0assets and the original debt still recorded. - Before
vaultManagementAction()could run its final health check, the attacker re-entered Arcadia by callingLendingPool.liquidateVault(). - The liquidation path routed through
LiquidatorintoVault.liquidateVault(), successfully initiated liquidation against the now-empty vault, and removed the debt from the vault state while the attacker-controlled call still held the withdrawn collateral outside. - Control returned to
vaultManagementAction(). Because the vault now showed0assets and0debt, the end-of-function health check passed. - The attacker repeated the pattern across USDC and WETH pools on Ethereum and Optimism, repaid flash liquidity, and retained the withdrawn collateral.
- What the attacker needed to be true:
- The attacker had to control the vault so they could call
vaultManagementAction()and shape the optimistic asset-withdrawal flow. - The function had to transfer assets out before the health check finalized.
LendingPool.liquidateVault()had to remain reachable during that window, and the liquidation path had to be able to mutate vault debt state before the original call returned.- Arcadia’s final validation had to look only at the post-liquidation state (
0debt) rather than at whether the withdrawn collateral had ever come back. - The targeted pools needed enough liquidity that leveraged vaults on both chains could be opened and unwound profitably.
- The attacker had to control the vault so they could call
- Technical anatomy worth remembering:
- This was a clean cross-contract reentrancy, not same-function recursion. The vulnerable window opened in
Vault.vaultManagementAction(), but the profitable mutation happened throughLendingPoolandLiquidatorback into the vault. - The exploit shows the danger of optimistic accounting patterns that intentionally allow intermediate insolvency and rely on one final end-of-function check. If any sibling entrypoint can rewrite debt, auction, or collateral status during that window, the final check becomes meaningless.
- Arcadia’s own example is crisp: after the attacker withdrew all assets, the vault looked liquidatable; after liquidation removed the debt, the same vault looked healthy again, even though the assets never returned.
- The incident is also a useful complement to Fuse: both exploits monetized stale solvency, but Arcadia’s stale window lived in an optimistic vault-management function rather than in an ETH-transfer callback.
- This was a clean cross-contract reentrancy, not same-function recursion. The vulnerable window opened in
- Representative transactions and addresses:
- Optimism exploit tx:
0xc598bf554a738ebeec234930e4bffe1c7a4266a1bbbdfae63b6951e448a17fc5 - Ethereum exploit tx:
0x01e2683bfdb945fcb6af7ed6a656dbabb04d7c8135125184fd29f5665fd0546c - Arcadia’s postmortem example of a USDC-side exploit transaction:
0xc598bf554a738ebeec234930e4bffe1c7a4266a1bbbdfae63b6951e448a17fc5 - Key contracts / modules called out by Arcadia:
Vault,LendingPool,Liquidator,vaultManagementAction(),doActionWithLeverage(), andliquidateVault()
- Optimism exploit tx:
- Response / mitigation:
- Arcadia paused contracts related to the affected liquidity pools, disabled debt increase and repayment flows, and set the interest rate to
0to prevent additional interest accrual during incident handling. - The protocol said the concrete fix was to add a reentrancy guard on the vault, especially around
liquidateVault()whilevaultManagementAction()is executing. - Arcadia published a chain-by-chain loss split: on Ethereum about
103,200.653468 USDCand11.047195997555799542 ETH, and on Optimism about59,427.425162 USDCand148.224144212923406305 ETH, for the reported total of$459,030.44. - Arcadia also began recovery and law-enforcement work immediately after the exploit.
- Arcadia paused contracts related to the affected liquidity pools, disabled debt increase and repayment flows, and set the interest rate to
Pattern tags
- reentrancy
- collateral-membership-desynchronization
- solvency-check-bypass
- temporary-capability-closure-before-external-delivery
- The preserved failure was not just
multicall reentrancy; it was that Arcadia let one in-flight management action temporarily externalize all collateral while a sibling liquidation path could still treat that same unfinished state as final enough to erase debt. - Arcadia is therefore a clean
optimistic collateral release plus re-entrant debt-state rewritecase, not just a genericliquidation bugormissing nonReentrantanecdote.
Why the exploit worked
vaultManagementAction()deliberately allowed a vault to become temporarily unhealthy while assets sat outside the vault, but Arcadia did not make that intermediate state private to the management flow. A sibling entrypoint could still observe it and act on it.- The decisive mismatch was temporal, not merely input-validation-related: the management path treated
assets temporarily gone, debt still presentas an allowed work-in-progress state, whileLendingPool.liquidateVault()treated the exact same moment as authoritative liquidation truth. - Liquidation then changed the wrong half of the vault first. Once
Liquidator -> Vault.liquidateVault()removed the debt and started an auction while the collateral was already outside the vault, Arcadia had converted a deliberately temporary insolvent state into a final0 assets / 0 debtstate that trivially passed the end-of-function health check. - The final solvency check therefore authenticated the wrong object. It did not prove
the same collateral that left the vault came back before debt was cleared; it only observed that the vault happened to end with no debt after a re-entrant sibling path rewrote state mid-flight. - Flash liquidity and
doActionWithLeverage()scaled the attack but did not create the bug. The real monetizable primitive was that one vault-management frame could externalize collateral while another entrypoint could independently zero the debt ledger before the original frame closed.
Detection and audit pivots
- For any
withdraw now, validate laterhelper, ask which sibling entrypoints can still mutate debt, liquidation flags, collateral eligibility, auction state, or vault ownership before the original frame closes. - Treat temporary collateral release as a capability boundary. If assets leave a vault before solvency re-check, liquidation, debt settlement, and other state-finalizing paths should usually be blocked or scoped to that in-flight action.
- Fuzz cross-entrypoint sequences rather than only same-function recursion:
vaultManagementAction() -> external multicall -> LendingPool.liquidateVault(), plus nearby variants that toggle collateral state, migrate debt, or trigger auctions mid-frame. - Review final health checks for object identity, not just final numbers. The question is not only
does the vault end healthy?butdid the same debt and the same collateral relationship survive the whole action without a sibling path rewriting one side independently? - Trace all places where optimistic helper flows assume deferred validation. If a leverage, rebalance, or multicall helper gets to violate core solvency assumptions transiently, every other entrypoint touching the same vault must explicitly know that the state is provisional.
- Keep monitoring that can distinguish
vault ended with zero debt because the user repaidfromvault ended with zero debt because liquidation fired during an unfinished management frame. Those are economically different endings even if a naive final-state check renders them both as healthy.
Failure-prevention checklist
- Treat
vaultManagementAction()as one explicit in-flight frame: vault id, acting owner or manager, withdrawn-asset inventory, allowed external leg, and the exact debt / liquidation state that must still match when the frame closes. - Block liquidation, auction start, debt write-down, collateral-membership changes, and any other state-finalizing sibling entrypoint while assets are temporarily externalized for management work. If a vault is mid-optimistic action, the rest of the system should know that its solvency state is provisional.
- Keep temporary collateral release and debt-state mutation on one invariant-maintenance path. If a vault may become transiently undercollateralized during a helper flow, no other path should be able to treat that same transient state as money-good liquidation truth.
- Re-check the identity of the returned collateral, not only the final ratio. The closing validation should prove that the same vault debt and the same reserve-supporting assets made it through the whole frame without a sibling path zeroing one side independently.
- Use stage-specific locks or frame-local status rather than broad end-of-function optimism.
We will check health lateris only safe if every sibling mutator is forced to wait until later too. - Prefer escrowed or staged post-action settlement when arbitrary external interactions are required. If users must leave the vault, interact elsewhere, and come back, isolate that route so liquidation cannot reinterpret the half-finished state mid-flight.
- Fuzz
withdraw all collateral -> nested liquidation -> returnand nearby sequences across every debt-mutating or auction-mutating entrypoint, not just same-function recursion. The bug class is cross-entrypoint disagreement about when vault truth is final. - Add monitoring that distinguishes
debt cleared because the borrower repaid inside the intended flowfromdebt cleared because liquidation fired while the intended flow was still open. Those states should never look equivalent operationally.
Audit prompts
- While
vaultManagementAction()is in progress, which exact entrypoints can still change debt, liquidation status, auction state, collateral eligibility, or vault ownership before the original frame closes? - What concrete storage flag or frame object tells sibling modules that the vault’s solvency state is provisional rather than final right now?
- Does the closing health check prove that withdrawn collateral actually returned to the same debt-bearing vault, or does it only observe a final
assets/debtsnapshot that another path could have rewritten? - Can liquidation ever consume a state that was intentionally made temporarily unhealthy for a management, rebalance, leverage, or migration helper? If so, why is that state being published as authoritative at all?
- Which external legs inside
vaultManagementAction()can re-enter Arcadia indirectly through other modules rather than through the same function? - If the protocol pauses on one side of the state machine, does the other side still have enough authority to finalize debt or start auctions against unfinished vault state?
- What tests prove that debt removal, collateral withdrawal, and auction start cannot be reordered into a profitable final
0 assets / 0 debtstate while collateral remains outside the vault? - If a future helper needs the same optimistic
validate at exitpattern, what machine-checkable rule prevents it from reintroducing the same cross-entrypoint disagreement under a different name?
Lessons that should already be standard
Validate at the endis not a security property by itself. It only works when every sibling entrypoint agrees that the intermediate state is provisional and therefore unusable for liquidation, payout, or pricing.- Liquidation is not just a cleanup path; it is a privileged state transition. If it can observe half-finished helper state, it becomes a reentrancy primitive even without exotic token hooks.
- A vault’s debt ledger and its reserve-supporting collateral are one security object. Clearing one while the other is temporarily elsewhere is not
helpful progress; it is balance-sheet corruption. - Allowlisted external actions do not save an optimistic flow if the real bug is internal disagreement about when vault truth becomes final.
- Cross-contract reentrancy often means one module is respecting a temporary fiction while another module is already spending against it as reality.
- Final-state checks can be fooled when they authenticate the wrong object. The healthy question is not only
did the vault end solvent?butdid the same debt-collateral relationship survive the entire frame? - When a design intentionally permits transient insolvency, every nearby mutator must be reviewed as if it were handling raw uncommitted state, because it is.
Why this incident matters
- It is a compact example of how
do external actions now, prove health latercan fail inside a lending protocol even when the external action surface is nominally allowlisted. - The exploit did not require a weird token standard, compiler bug, or governance capture. It only required one optimistic state machine and one sibling liquidation path that disagreed about when vault state was final.
- Arcadia is a useful study case for vault-based lending designs because the key lesson is architectural: if liquidation can observe deliberately incomplete intermediate state, liquidation becomes a reentrancy primitive.
- It is also a clean positive-design reminder that temporary collateral release is already a privileged capability boundary. If a system lets assets leave before solvency is re-proven, then debt-erasing or auction-starting paths must either inherit that provisional-state knowledge or stay shut.
Internal linkages
- Core recurring pattern: reentrancy.
- Sharper vault-state lens: collateral-membership-desynchronization — Arcadia split
which assets still need to support this debtacross an optimistic management flow and a sibling liquidation path that could rewrite debt state mid-flight. - Accounting / solvency lens: solvency-check-bypass.
- Sharper positive-design companion: callback-frame-truth-security-object — Arcadia needed one authenticated vault-management frame binding vault id, withdrawn-collateral inventory, allowed external leg, debt state, and the prohibition on liquidation-style sibling finalization until that frame closed.
- Positive-design companion: helper-path-invariant-reuse-and-fail-closed-discipline — the exploit is also a clean reminder that a leverage / management helper cannot inherit looser temporal assumptions than the core vault and liquidation state machine it manipulates.
- Positive-design companion: temporary-capability-closure-before-external-delivery — useful when the sharper design question is why Arcadia let vault assets leave custody before the system had closed the debt / liquidation capabilities that could still reinterpret that temporary state.
Control surface
-
The leverage sat in Arcadia’s optimistic vault-management state machine: when assets were allowed to leave the vault, when health was checked, and whether liquidation authority was blocked from observing that temporary state.
-
The exploit was not a generic
multicall is dangerousstory. It was that Arcadia let one module treatassets are temporarily goneas acceptable while another module treated the same moment as authoritative grounds to rewrite debt and auction state. -
The deeper review question is therefore temporal: which entrypoint owns vault truth while a management frame is in progress, and which other entrypoints must be unable to treat that half-finished state as money-good or liquidation-good reality?
-
Sources:
-
Last reviewed: 2026-07-20 UTC