Calculated from recorded token losses using historical USD prices at the incident time.
0xcb1a2f5eeb1a767ea5ccbc3665351fadc1af135d12a38c504f8f6eb997e9e6030x403049e886b13e42c149f15450ceb795216cddc6Arbitrum0xdaf57db465298eb268a5dba1484cde20da65c4fdArbitrum0x10bda01ac4e644fd84a04dab01e15a5edcee46ddArbitrum0x7746872c6892bCfB4254390283719f2Bd2D4Da76ArbitrumOn Arbitrum block 195240643, attacker EOA 0x851aa754c39bf23cdaac2025367514dfd7530418 executed transaction 0xcb1a2f5eeb1a767ea5ccbc3665351fadc1af135d12a38c504f8f6eb997e9e603 against the lending market at 0x403049e886b13e42c149f15450ceb795216cddc6. The attacker used helper contracts to thin the share supply of wrapper token 0x10bda01ac4e644fd84a04dab01e15a5edcee46dd, then posted the now-overpriced wrapper as collateral and borrowed real market assets.
The root cause is a manipulable wrapper-collateral oracle. WrapperOracle.latestAnswer() values the wrapper as total wrapper value divided by totalSupply, while the wrapper itself allows permissionless burns via withdrawal. Because the oracle only rejects totalSupply < 1e9, an attacker can drive supply down near that floor while preserving residual value, making each remaining share appear much more valuable. The lending market trusted that derived price through AaveOracle.getAssetPrice() and LendingPool._executeBorrow(), so the attacker could open real debt against inflated collateral.
The affected collateral is the verified Uniswap V3 wrapper USDC-USDC LP at 0x10bda01ac4e644fd84a04dab01e15a5edcee46dd. The collected wrapper source shows that deposits mint wrapper shares proportionally to liquidity, and withdrawals burn shares before returning underlying liquidity:
function _deposit(uint256 amount0, uint256 amount1, uint256 minAdded0, uint256 minAdded1)
internal
returns (uint128 liquidityAdded, uint256 sharesMinted)
{
(liquidityAdded,,) = mintLiquidityPreview(amount0, amount1);
sharesMinted = totalLiquidity == 0
? liquidityAdded
: uint256(liquidityAdded).mulDivDown(totalSupply(), uint256(totalLiquidity));
(amount0, amount1) = _addLiquidity(liquidityAdded, msg.sender);
_mint(msg.sender, sharesMinted);
}
function withdraw(uint256 shares) external returns (uint128 liquidityRemoved, uint256 amount0, uint256 amount1) {
_compound();
liquidityRemoved = uint128(shares.mulDivDown(totalLiquidity, totalSupply()));
_burn(msg.sender, shares);
(amount0, amount1) = _removeLiquidity(liquidityRemoved, msg.sender);
}
The lending market prices this wrapper through an oracle chain:
AaveOracle at 0x9a88F515e6269C9b3c8cB5df8b7625712FBa42970x316432b7E6D1838F3871bA4fF7016feEe7eC5Fe9WrapperOracle at 0x7746872c6892bCfB4254390283719f2Bd2D4Da76The verified oracle source shows the core pricing formula:
function latestAnswer() public view override returns (int256) {
uint256 p0 = _getWADPrice(true);
uint256 p1 = _getWADPrice(false);
uint160 sqrtPriceX96 = ...;
(uint256 r0, uint256 r1) = IWrapper(pool).getAssetsBasedOnPrice(sqrtPriceX96);
require(r0 > 0 || r1 > 0, "invalid-balances");
uint256 totalSupply = IWrapper(pool).totalSupply();
require(totalSupply >= 1e9, "total-supply-too-small");
uint256 preq = _add(_mul(p0, _mul(r0, TO_WAD_0)), _mul(p1, _mul(r1, TO_WAD_1))) / totalSupply;
return int256(preq);
}
The lending path then trusts that price directly. AaveOracle.getAssetPrice() returns the configured source price when positive, and LendingPool._executeBorrow() converts that oracle value into amountInETH before calling ValidationLogic.validateBorrow().
This is an ATTACK-class vulnerability caused by using a thin-supply wrapper share price as lending collateral truth. The explicit invariant is that collateral shares must be priced by recoverable economic value per share in a way that cannot be inflated by permissionless supply-thinning operations. That invariant is broken because WrapperOracle.latestAnswer() divides total value by a manipulable totalSupply and only enforces a dust-level minimum.
The oracle breakpoint is deterministic. When an attacker burns enough wrapper shares, totalSupply falls sharply while residual wrapper value remains non-zero. The quotient (p0*r0 + p1*r1) / totalSupply therefore rises, even though no corresponding external value entered the system. AaveOracle returns that higher value, and LendingPool._executeBorrow() feeds it into ValidationLogic.validateBorrow() as if it were legitimate collateral.
The relevant lending logic is straightforward:
uint256 amountInETH = IPriceOracleGetter(oracle).getAssetPrice(vars.asset).mul(vars.amount).div(
10 ** reserve.configuration.getDecimals()
);
ValidationLogic.validateBorrow(
vars.asset,
reserve,
vars.onBehalfOf,
vars.amount,
amountInETH,
vars.interestRateMode,
_maxStableRateBorrowSizePercent,
_reserves,
userConfig,
_reservesList,
_reservesCount,
oracle
);
And the validator only checks whether collateral value, as priced by the oracle, still covers debt:
(vars.userCollateralBalanceETH, vars.userBorrowBalanceETH, , , vars.healthFactor) =
GenericLogic.calculateUserAccountData(..., oracle);
vars.amountOfCollateralNeededETH =
vars.userBorrowBalanceETH.add(amountInETH).percentDiv(vars.currentLtv);
require(
vars.amountOfCollateralNeededETH <= vars.userCollateralBalanceETH,
Errors.VL_COLLATERAL_CANNOT_COVER_NEW_BORROW
);
No wrapper-specific sanity bound exists between the manipulated share price and the borrowing decision. That is the concrete code-level failure.
Historical state cited in root_cause.json matches the exploit direction and is consistent with the oracle formula:
totalSupply() fell from 999764088549219 at block 195240642 to 1097126341305 at block 195240643.WrapperOracle.latestAnswer() rose from 100995687221350327018324741 to 175680552990073074307882023.28310525317659138287544 to 49245753755259448858796.Those values are exactly what the pricing formula predicts: reducing the denominator while leaving non-zero reserves behind increases the per-share quote.
The exploit trace confirms the end-to-end realization. The transaction starts with the attacker EOA calling attacker contract 0x3e52c217a902002ca296fe6769c22fedaee9fda1, which fans out into helper contract 0x42fae47296b26385c4a5b62c46e4305a27c88988 and related helpers 0xff388fb92190136032c80a5b791cfebaf7fb9b5c and 0x3fcc17a935ea06be79850c880c18d52f3ddfe2ee.
The trace shows the manipulated wrapper price being consumed during lending operations:
0x7746872c6892bCfB4254390283719f2Bd2D4Da76::latestAnswer() [staticcall]
...
LendingPool::borrow(..., onBehalfOf: 0xff388fb92190136032c80a5b791cfebaf7fb9b5c)
...
LendingPool::borrow(..., onBehalfOf: 0x3fcC17a935EA06bE79850C880C18d52F3dDfe2Ee)
The same trace and the balance diff show the protocol-side consequences:
0xff388fb92190136032c80a5b791cfebaf7fb9b5c: 357339053818101738520x42fae47296b26385c4a5b62c46e4305a27c88988: 22970960661980997824750xea0011f2ee37e4ccc4eaa2fe3d272350f34dbc07: 987166847916320 and 632966054075300520x3fcc17a935ea06be79850c880c18d52f3ddfe2ee: 48461005225 and 52714782453These positions do not represent organic collateralization. They are the mechanical result of the lending pool treating the manipulated wrapper share price as genuine collateral value.
The attacker flow is a single-transaction ACT sequence:
0x851aa754... calls orchestrator 0x3e52c217....0x42fae472... interacts with the wrapper to reduce wrapper supply while preserving residual wrapper value.The trace explicitly records downstream borrow execution for multiple real assets. One example is the ARB borrow for helper 0x3fcC17a935EA06bE79850C880C18d52F3dDfe2Ee:
LendingPool::borrow(
OssifiableProxy: [0x5979D7b546E38E414F7E9822514be443A4800529],
8525476552027716376,
2,
0,
0x3fcC17a935EA06bE79850C880C18d52F3dDfe2Ee
)
...
emit Transfer(from: 0xCB1332663a39f238BCD1cc7621E3E24A50251b94,
to: 0x3fcC17a935EA06bE79850C880C18d52F3dDfe2Ee,
value: 8525476552027716376)
At the end of the transaction, the borrowed assets are flushed to the attacker EOA. The balance diff records net gains for 0x851aa754... in USDC, USDC.e, USDT, ARB, and native WETH-equivalent balance.
The lending market was left with undercollateralized debt backed by wrapper collateral that had been overvalued by the oracle chain. The measurable attacker-side gains recorded in the balance diff are:
ETH: "20332763573538003060" with decimal: 18USDC: "77477425666" with decimal: 6USDC.e: "96215533581" with decimal: 6USDT: "60349016947" with decimal: 6ARB: "8525476552027716376" with decimal: 18These amounts match the loss section in root_cause.json and are directly corroborated by balance_diff.json.
0xcb1a2f5eeb1a767ea5ccbc3665351fadc1af135d12a38c504f8f6eb997e9e6030x851aa754c39bf23cdaac2025367514dfd75304180x403049e886b13e42c149f15450ceb795216cddc60xdaf57db465298eb268a5dba1484cde20da65c4fd0x10bda01ac4e644fd84a04dab01e15a5edcee46dd0x7746872c6892bCfB4254390283719f2Bd2D4Da760x9a88F515e6269C9b3c8cB5df8b7625712FBa4297artifacts/collector/seed/42161/0xcb1a2f5eeb1a767ea5ccbc3665351fadc1af135d12a38c504f8f6eb997e9e603/metadata.jsonartifacts/collector/seed/42161/0xcb1a2f5eeb1a767ea5ccbc3665351fadc1af135d12a38c504f8f6eb997e9e603/trace.cast.logartifacts/collector/seed/42161/0xcb1a2f5eeb1a767ea5ccbc3665351fadc1af135d12a38c504f8f6eb997e9e603/balance_diff.jsonWrapperOracle, AaveOracle, LendingPool, and ValidationLogic retrieved through the Etherscan V2 API during validation