0xb5cfa4ae4d6e459ba285fec7f31caf8885e2285a0b4ff62f66b43e280c9472160x3ff516b89ea72585af520b64285eca5e4a0a8986Arbitrum0x11a8598c4430c7663fda224c877f231895c8ca69Arbitrum0x56ea05c0c3b665f67538909343fb3becb4fe5714ArbitrumOn Arbitrum block 259645908, transaction 0xb5cfa4ae4d6e459ba285fec7f31caf8885e2285a0b4ff62f66b43e280c947216 exploited Volatility by minting shares of wrapper token 0x6700b021a8bcfae25a2493d16d7078c928c13151, depositing those shares as collateral into LendingPool 0x3ff516b89ea72585af520b64285eca5e4a0a8986, borrowing the same wrapper asset through a helper account, and redeeming the borrowed shares through the wrapper. That redemption collapsed wrapper supply and caused the protocol’s wrapper-price oracle to revalue the attacker’s remaining deposited shares upward within the same transaction. The attacker then borrowed additional reserve assets against the inflated collateral and exited with positive net balances.
The root cause is that Volatility used an endogenous wrapper-share oracle for an asset that was both collateral and borrowable. The pricing path AaveOracle -> ChangeDenominator -> WrapperOracle depended on wrapper.getAssetsBasedOnPrice(...) and wrapper.totalSupply(), both of which the borrower could move permissionlessly by borrowing wrapper shares and calling withdraw(uint256).
Volatility listed wrapper reserve 0x6700b021a8bcfae25a2493d16d7078c928c13151 with ltv_bps=9450, liquidation_threshold_bps=9700, borrowing_enabled=true, and decimals=12. The reserve therefore could be posted as collateral and also borrowed from the same market. The relevant market-state artifact shows the wrapper oracle price moved from before the transaction to after the exploit path completed.
58297696004271878131590584988874The wrapper itself represents a Uniswap V3 WETH-USDC position. Decompiled artifacts show the wrapper exposes public deposit(uint256,uint256,uint256,uint256), withdraw(uint256), getAssetsBasedOnPrice(uint160), and totalSupply() entry points. The analysis artifact also records that the wrapper’s underlying pool is 0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443, token0 is WETH 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1, and token1 is bridged USDC.e 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8.
The trace summary shows the lending pool resolved the wrapper price through the exact path below during collateral and borrow checks:
AaveOracle::getAssetPrice(0x6700...3151)
-> ChangeDenominator::latestAnswer()
-> WrapperOracle::latestAnswer()
-> 0x6700...3151::getAssetsBasedOnPrice(3917649707054134793863168)
-> wrapper.totalSupply()
The pricing formula documented in the root-cause artifact is Token Price = (r0 * p0 + r1 * p1) / totalSupply, so shrinking totalSupply while preserving value in the wrapper raises the per-share price used by the lending market.
This incident is an ATTACK, not a passive market event. The exploitable design condition was that the same wrapper share token could be both deposited as collateral and borrowed as debt while its oracle was computed from wrapper-internal state that the borrower could mutate. That made the collateral valuation endogenous to the borrower’s own actions.
The violated invariant is straightforward: a reserve accepted as collateral must not derive its value from permissionless state transitions on the reserve asset itself, and borrowing that reserve must not let the borrower make an unchanged collateral-share balance represent more value without adding external assets. Volatility broke that invariant by using WrapperOracle.latestAnswer() over getAssetsBasedOnPrice(...) and totalSupply() while also allowing wrapper-share borrowing.
The code-level breakpoint is the wrapper oracle read. Before redemption, the wrapper reserve price was 5829769600427187; after the borrowed shares were redeemed, the same oracle source returned 8131590584988874. Because the attacker’s deposited wrapper-share balance remained in the lending pool, the market treated the same number of shares as more valuable collateral and permitted further borrows.
The exploit begins with creation of wrapper shares and immediate collateralization. The focused trace summary records:
LendingPool::deposit(0x6700b021a8bCfAE25A2493D16d7078c928C13151, 430623991193131340, 0x69FA61eB4dC4E07263D401b01ed1CfCeb599dAb8, 0)
During that process the pool called the wrapper oracle path and read borrower-mutable wrapper state. The trace also records the helper account borrowing the same wrapper reserve:
LendingPool::borrow(0x6700b021a8bCfAE25A2493D16d7078c928C13151, 430623991193131340, 2, 0, 0x1209ea2953310FcEB37F7e2c32Fe9153Bd2445c6)
emit Borrow(reserve: 0x6700b021a8bCfAE25A2493D16d7078c928C13151, user: 0x1209ea2953310FcEB37F7e2c32Fe9153Bd2445c6, onBehalfOf: 0x1209ea2953310FcEB37F7e2c32Fe9153Bd2445c6, amount: 430623991193131340, borrowRateMode: 2, ...)
Once the helper borrowed wrapper shares, the attacker transferred those shares back into the attack path and redeemed them through the wrapper. Decompiled wrapper code shows withdraw(uint256) is a public method and burns caller shares proportionally against totalSupply:
/// @custom:signature withdraw(uint256 arg0) public
function withdraw(uint256 arg0) public {
...
require(!(storage_map_r[var_l] < arg0), CustomError_e450d38c());
storage_map_r[var_l] = storage_map_r[var_l] - arg0;
...
emit Transfer(address(msg.sender), 0, arg0);
...
var_d = address((totalLiquidity * arg0) / totalSupply);
...
}
The live exploit trace confirms the borrowed wrapper shares were redeemed:
0x6700b021a8bCfAE25A2493D16d7078c928C13151::withdraw(430623991193131340)
That redemption changed the oracle inputs observed by the market. The root-cause evidence and market-state snapshot show wrapper totalSupply dropped from 430657067248025417 during the initial oracle read to 2141839437882 after the unwind, and the reserve oracle price increased from 5829769600427187 to 8131590584988874. Because Volatility continued to value the attacker’s deposited aToken position using the repriced wrapper share, the account’s collateral capacity increased without any corresponding exogenous value injection.
The attacker then used that inflated borrowing power to draw reserve assets, including native USDC, WETH, and bridged USDC.e. The balance-diff artifact shows the EOA 0x8a0dfb61cad29168e1067f6b23553035d83fcfb2 ended the transaction with 125795603292 native USDC, 2250000000000000000 WETH, and 1000000 bridged USDC.e, while only paying 119875260000000 wei in gas. That final extraction is what makes the state transition economically harmful rather than a transient accounting anomaly.
The adversary cluster consisted of EOA 0x8a0dfb61cad29168e1067f6b23553035d83fcfb2, first-hop contract 0x69fa61eb4dc4e07263d401b01ed1cfceb599dab8, and helper contract 0x1209ea2953310fceb37f7e2c32fe9153bd2445c6. The entire exploit completed inside one attacker-crafted transaction, so there was no privileged setup dependency.
The execution flow was:
430623991193131340 wrapper shares and deposited them into the Volatility lending pool as collateral.6492300768118 native USDC units and borrowed 430623991193131340 wrapper shares from the same reserve.wrapper.withdraw(...), collapsing wrapper supply and increasing the oracle-based per-share price used by the lending market.The focused trace summary captures the key pivot points in-order:
LendingPool::deposit(... wrapper ..., 430623991193131340, 0x69FA..., 0)
LendingPool::deposit(USDC, 6492300768118, 0x1209..., 0)
LendingPool::borrow(... wrapper ..., 430623991193131340, 2, 0, 0x1209...)
0x6700...3151::withdraw(430623991193131340)
LendingPool::borrow(USDC, 6630792705184, 2, 0, 0x69FA...)
LendingPool::borrow(WETH, 153407712731719084, 2, 0, 0x69FA...)
LendingPool::borrow(USDC.e, 298077739, 2, 0, 0x69FA...)
This is an ACT opportunity because every component in the path was permissionless: the attacker used public liquidity, public protocol contracts, public oracle dependencies, and fresh attacker-controlled contracts deployed in the same transaction.
The direct measured reserve loss in the collected artifacts is to the Volatility USDC aToken reserve 0x56ea05c0c3b665f67538909343fb3becb4fe5714, which dropped from 138491937066 units to 0 in the seed transaction. In smallest units, the recorded USDC loss is:
{
"token_symbol": "USDC",
"amount": "138491937066",
"decimal": 6
}
The root-cause artifact also records that the attacker extracted WETH, bridged USDC.e, and a bridged BTC proxy position, but the quantified loss field is the USDC reserve depletion above. The market became undercollateralized because it accepted the manipulated wrapper valuation as valid collateral.
0xb5cfa4ae4d6e459ba285fec7f31caf8885e2285a0b4ff62f66b43e280c947216 on Arbitrum, block 259645908.0x3ff516b89ea72585af520b64285eca5e4a0a8986, including reserve configs, oracle prices, and user account data.0x1e515301f24abbf92dfb3d071e062739a8827b4d behind proxy 0x6700b021a8bcfae25a2493d16d7078c928c13151.