dForce Oracle Reentrancy Liquidation
Exploit Transactions
0xe02227a0c3cba66887613d36fb0d0270aeda3b72fcb560263d7a58ef6fe1fa720xb2b66de51dc527193e426426bbcd9925291b80ad75f4d9a868e4e9d8775611e60x5db5c2400ab56db697b3cc9aa02a05deab658e1438ce2f8692ca009cc45171ddVictim Addresses
0x61afb763bc265bd372e8af8dac00196c9a5ecea0Arbitrum0x6eb2dc694eb516b16dc9fbc678c60052bbdd7d80ArbitrumLoss Breakdown
Similar Incidents
Paribus Redeem Reentrancy
40%Sentiment Balancer Oracle Overborrow
36%Midas LP Oracle Read-Only Reentrancy via Curve stMATIC/WPOL
32%DEI burnFrom Allowance Inversion
27%Curve crvUSD sDOLA Market In-Tx Oracle Refresh Liquidation Attack
26%0VIX ovGHST Oracle Inflation
25%Root Cause Analysis
dForce Oracle Reentrancy Liquidation
1. Incident Overview TL;DR
On Arbitrum, attacker EOA 0xe0d551017c0111ac11108641771897aa33b2817c deployed exploit contract 0xee29b6aee6e4783db176946e4e8f1e5fdcd446a7 in tx 0xe02227a0c3cba66887613d36fb0d0270aeda3b72fcb560263d7a58ef6fe1fa72, deployed helper contract 0x53c59365183cc86bd842150ba8d88cc2da5d7b28 in tx 0xb2b66de51dc527193e426426bbcd9925291b80ad75f4d9a868e4e9d8775611e6, and executed the exploit in tx 0x5db5c2400ab56db697b3cc9aa02a05deab658e1438ce2f8692ca009cc45171dd at block 59527634. The exploit used nested flash liquidity to mint Curve wstETHCRV, posted vwstETHCRV-gauge collateral on dForce, borrowed 2080000 USX, and then reentered dForce liquidations from inside Curve remove_liquidity.
The root cause was dForce pricing vwstETHCRV-gauge collateral from Curve get_virtual_price() during a transient reentrant state inside 0x6eb2dc694eb516b16dc9fbc678c60052bbdd7d80::remove_liquidity. During that callback window, ETH had already left the pool but LP supply had not been burned yet, so dForce consumed a temporarily collapsed LP price as if it were finalized state. That let the attacker self-liquidate the helper borrower and liquidate an existing borrower at a deeply underpriced seize rate, then unwind the seized collateral after the Curve state normalized.
2. Key Background
dForce valued the vwstETHCRV-gauge collateral market 0x2cE498b79C499c6BB64934042eBA487bD31F75ea through LP price model 0x9a0B57024Ff206A658e46ffE9F60C7c14cF30b80::getAssetPrice(address), which ultimately depended on Curve pool 0x6eb2dc694eb516b16dc9fbc678c60052bbdd7d80::get_virtual_price(). In dForce controller 0x61afb763bc265bd372e8af8dac00196c9a5ecea0, collateral value and liquidation seize amounts were driven by the oracle price, exchange rate, collateral factor, close factor, and liquidation incentive.
At the exploit block, the relevant dForce parameters were:
collateral factor = 700000000000000000
borrow factor = 1000000000000000000
close factor = 500000000000000000
liquidation incentive = 1070000000000000000
exchangeRateStored(gauge) = 1000000000000000000
Curve pool 0x6eb2... is a native-ETH pool. During remove_liquidity, it transfers ETH to the caller before the LP burn and final RemoveLiquidity event. That ordering matters because any external callback that consults the pool during the transfer sees depleted pool balances against still-unreduced LP supply.
Both primary victim-side contracts identified in the analysis, dForce controller 0x61af... and Curve pool 0x6eb2..., were unverified on explorer at validation time according to the auditor's deterministic follow-up notes.
3. Vulnerability Analysis & Root Cause Summary
This was an oracle-manipulation liquidation attack caused by consuming intermediate external state, not finalized external state. dForce treated Curve get_virtual_price() as a synchronous pricing input for vwstETHCRV-gauge collateral even while the underlying Curve pool was midway through a native-asset remove_liquidity execution. In the exploit transaction, the attacker forced dForce to read the gauge price inside the payable callback window after Curve transferred out 62125.420362800136144482 ETH but before LP supply was burned. That caused the gauge price to fall from 1562257355379698820370 to 314875214525464209395, which turned a solvent helper account into one with immediate shortfall. dForce then used that depressed price in beforeLiquidateBorrow and liquidateCalculateSeizeTokens, allowing a partial USX repayment to seize all helper collateral. The same price collapse also enabled liquidation of borrower 0x916792f7734089470de27297903BED8a4630b26D. The broken invariant was that liquidation-critical collateral pricing must be taken from finalized Curve state, not from a reentrant intermediate state.
4. Detailed Root Cause Analysis
The attacker first assembled liquidity across Balancer, Aave, dForce flash pools, Uniswap V3, Uniswap V2 pairs, Zyber, and 0xa067668661C84476aFcDc6fA5D758C4c01C34352. With that funding, the exploit added 68429.225457866057113822 ETH-only liquidity to the Curve wstETH/ETH pool and minted 65343.353080959445359616 LP tokens. It transferred 1904.761904761904761904 LP into helper contract 0x53c593..., deposited through gauge deposit 0x098EF55011B6B8c99845128114A9D9159777d697, and borrowed 2080000 USX from vMUSX 0xC462fF1063172BAC6f6823A17ED181a0586f0FC8.
Before the reentrant call, the helper was solvent. Using the normal gauge price 1562257355379698820370, dForce valued the helper's collateral at 2083009807172931760492499, slightly above the 2080000000000000000000000 USX borrow. The exploit then called:
Curve remove_liquidity(63438591176197540597712, [0, 0])
The seed trace shows that Curve transferred ETH and entered the attacker fallback before finalizing the LP burn:
0x6eB2dc694eB516B16Dc9FBc678C60052BbdD7d80::remove_liquidity(63438591176197540597712, [0, 0])
::fallback{value: 62125420362800136144482}()
Inside that callback, dForce recomputed liquidation math while the Curve pool was in the inconsistent state. The trace shows the gauge pricing path descending back into get_virtual_price() during the callback:
::getUnderlyingPrice(0x2cE498b79C499c6BB64934042eBA487bD31F75ea)
::getAssetPrice(0x2cE498b79C499c6BB64934042eBA487bD31F75ea)
0x6eB2dc694eB516B16Dc9FBc678C60052BbdD7d80::get_virtual_price()
At the normal pre-state, get_virtual_price() was 1011371298035750307. During the callback, the same path returned 203843338190912215, and dForce's effective gauge price dropped to 314875214525464209395. With the same collateral factor, the helper collateral value collapsed to 419833619367285612526498, creating a shortfall of 1660166380632714387473502.
That shortfall fed directly into liquidation logic. A repayment of 560525526525080924601515 USX, which would only seize about 394 gauge tokens under the normal price, instead seized the entire helper collateral position:
::liquidateBorrow(
0x53c59365183CC86bD842150ba8d88Cc2Da5d7B28,
560525526525080924601515,
0x2cE498b79C499c6BB64934042eBA487bD31F75ea
)
The same manipulated pricing path was then reused against an existing borrower:
::liquidateBorrow(
0x916792f7734089470de27297903BED8a4630b26D,
300037034111437845493368,
0x2cE498b79C499c6BB64934042eBA487bD31F75ea
)
Only after those liquidations did Curve finish the first removal and emit the final event:
emit RemoveLiquidity(
0xEe29b6AEE6E4783Db176946e4e8F1E5fDCD446A7,
[62125420362800136144482, 3638557373633376072510],
[0, 0],
10631797715176973266648
)
Once the pool state normalized, the attacker withdrew seized gauge collateral, removed the seized LP, swapped recovered wstETH for ETH, repaid every flash source, and retained profit. The seed balance diff records the measurable native-asset loss and attacker gain:
Curve pool native ETH delta = -1018127713136734707493 wei
attacker EOA native ETH delta = 1236653957171163812701 wei
attacker EOA ETH before = 397095775219379452 wei
attacker EOA ETH after = 1237051052946383192153 wei
The auditor's deterministic follow-up also resolved the exact transaction fee as 412752300000000 wei and recorded an additional direct USX transfer of 719437439363481229905117 to the attacker EOA. The exploit therefore satisfies the ACT profit predicate using only public state, public transactions, and attacker-deployed contracts.
5. Adversary Flow Analysis
The adversary lifecycle had four stages. First, tx 0xe02227...fa72 deployed the main exploit contract, and tx 0xb2b66d...11e6 deployed the helper borrower and set approvals. Those were ordinary adversary-crafted transactions that any unprivileged EOA could submit on Arbitrum.
Second, tx 0x5db5c2400ab56db697b3cc9aa02a05deab658e1438ce2f8692ca009cc45171dd assembled flash liquidity across Balancer, Aave, dForce, Uniswap V3, Uniswap V2, Zyber, and A067, then added ETH-only liquidity to Curve. The helper deposited 1904.761904761904761904 gauge collateral and borrowed 2080000 USX, intentionally placing itself close enough to the liquidation threshold that a temporary oracle collapse would matter.
Third, the attacker used Curve's callback timing to force the oracle break and liquidations within the same transaction. The exploit did not rely on private keys, privileged order flow, or attacker-side calldata copied from the original contract; it only needed a payable contract capable of reentering while the pool was mid-execution.
Fourth, after liquidation, the attacker unwound the seized position. The trace shows a second remove_liquidity on 2924339222027299635899 LP, transfer of recovered wstETH, conversion to ETH, conversion of 500000 USX through Curve and GMX into WETH, repayment of all flash sources, transfer of 719437.439363481229905117 USX to the attacker EOA, and native ETH realization of 1236.653957171163812701 net ETH after fees.
6. Impact & Losses
The direct protocol-side native ETH loss recorded in the seed balance diff was 1018127713136734707493 wei (1018.127713136734707493 ETH) from Curve pool 0x6eb2.... The root cause analysis also records direct USX profit of 719437439363481229905117 wei units (719437.439363481229905117 USX) to the attacker EOA after all flash obligations were repaid.
The broader effect was that dForce accepted liquidations that should not have been possible from finalized state. The attacker seized 2924.339222027299635899 gauge tokens across the helper and third-party borrower, then monetized that collateral after the manipulated price window closed. This was therefore both a liquidation-integrity failure for dForce and an extractive loss event for liquidity tied to the Curve pool.
7. References
- Exploit deployment tx:
0xe02227a0c3cba66887613d36fb0d0270aeda3b72fcb560263d7a58ef6fe1fa72 - Helper deployment/setup tx:
0xb2b66de51dc527193e426426bbcd9925291b80ad75f4d9a868e4e9d8775611e6 - Exploit tx:
0x5db5c2400ab56db697b3cc9aa02a05deab658e1438ce2f8692ca009cc45171dd - dForce controller:
0x61afb763bc265bd372e8af8dac00196c9a5ecea0 - dForce LP price model:
0x9a0B57024Ff206A658e46ffE9F60C7c14cF30b80 - Curve wstETH/ETH pool:
0x6eb2dc694eb516b16dc9fbc678c60052bbdd7d80 vwstETHCRV-gaugemarket:0x2cE498b79C499c6BB64934042eBA487bD31F75eavMUSXmarket:0xC462fF1063172BAC6f6823A17ED181a0586f0FC8- Seed trace labeled as "Seed Trace"
- Seed balance diff labeled as "Seed Balance Diff"
- Auditor deterministic follow-up labeled as "Deterministic Field Resolution Notes"