0x6a2f989b5493b52ffc078d0a59a3bf9727d134b403aa6e0bf309fd513a728f7f0xf81b4381b70ef520ae635afd4b0e8aeb994131fbArbitrum0x2e2fe9bc7904649b65b6373baf40f9e2e0b883c5ArbitrumThe Arbitrum transaction 0x6a2f989b5493b52ffc078d0a59a3bf9727d134b403aa6e0bf309fd513a728f7f drained value from Dolomite smart loan 0xf81b4381b70ef520ae635afd4b0e8aeb994131fb and the Dolomite WETH pool 0x2e2fe9bc7904649b65b6373baf40f9e2e0b883c5. The attacker used an attacker-owned orchestrator and an attacker-owned fake TraderJoe pair/hook to make Dolomite treat wrapped loan ETH as reward proceeds, while simultaneously opening new WETH debt without repaying the original USDC side. The deterministic root cause is the composition of two logic flaws: claimReward(ILBPair,uint256[]) trusted an arbitrary user-supplied TraderJoe pair/hook, and swapDebtParaSwap(bytes32,bytes32,uint256,uint256,bytes4,bytes) allowed _repayAmount = 0 while borrowing the replacement asset first. The result was a final attacker profit of 66.619545304650988218 WETH.
Dolomite smart loans are user-owned proxy accounts created through Smart Loans Factory 0xFf5e3dDaefF411A1Dc6CCe00014e4BcA39265c20. The owner can route calls through facets on the loan. Two Dolomite facets matter here:
claimReward(ILBPair pair, uint256[] calldata ids)
swapDebtParaSwap(bytes32 fromAsset, bytes32 toAsset, uint256 repayAmount, uint256 borrowAmount, bytes4 selector, bytes data)
The first path resolves a reward hook from TraderJoe hook lens state based on the caller-supplied pair address. The second path borrows the new debt asset, executes ParaSwap router calldata, and only then repays the old debt asset. At block 273278742, the relevant public state also included visible WETH liquidity in Dolomite’s WETH pool and publicly available RedStone payload bytes sufficient for the solvency checks used by the smart loan.
The vulnerability class is protocol attack caused by unsafe trust delegation and broken debt-swap accounting. In claimReward(ILBPair,uint256[]), the smart loan accepted an arbitrary pair address, queried LBHookLens.getHooks(pair), accepted the returned hook contract as the rewarder, and measured reward solely as the loan’s reward-token balance delta. There was no whitelist check tying the supplied pair to a legitimate Dolomite-supported TraderJoe market. In swapDebtParaSwap, Dolomite accepted _repayAmount = 0, borrowed the replacement asset before debt reduction, executed arbitrary ParaSwap calldata, and finally called repay(0), which left the original debt untouched. The attacker composed these flaws by first opening WETH debt while repaying zero USDC, then using a fake TraderJoe pair/hook to callback into the loan and wrap the loan’s ETH during the reward-measurement window. Because the reward path trusted an attacker-controlled hook and measured only token-balance increase, the loan transferred the freshly wrapped WETH to the attacker as if it were a legitimate reward.
The incident trace shows swapDebtParaSwap on the smart loan with _fromAsset = USDC, _toAsset = ETH, _repayAmount = 0, and _borrowAmount = 66619545304650988218. The trace then shows WethPool::borrow(66619545304650988218) followed by ParaSwap simpleSwap, which unwraps the borrowed WETH into native ETH and sends that ETH to the loan, and then UsdcPool::repay(0). This is the first broken invariant: Dolomite increased borrow exposure before any debt reduction and allowed the reduction leg to be zero.
WethPool::borrow(66619545304650988218)
0xDEF171Fe...::simpleSwap(...)
UsdcPool::repay(0)
The second broken invariant appears in the reward path. The trace shows the loan calling claimReward(0x52ee5c0e..., [0]). The TraderJoe hook lens resolves hooks for that attacker-supplied pair, and the pair itself returns its own address from getLBHooksParameters() and WETH from getRewardToken(). The loan then calls 0x52ee5c0e...::claim(...) as if it were a legitimate rewarder.
claimReward(0x52EE5c0e..., [0])
LBHookLens::getHooks(0x52EE5c0e...)
0x52EE5c0e...::getLBHooksParameters() -> 0x52EE5c0e...
0x52EE5c0e...::getRewardToken() -> WETH
0x52EE5c0e...::claim(loan, [0])
The collected attacker-contract decompiles and the trace jointly explain what happens next. The fake pair/hook calls the attacker orchestrator, and the orchestrator calls the smart loan’s wrapNativeToken during the reward-claim window. That converts the loan’s native ETH balance, which now includes the flash-loaned ETH staging plus the ETH obtained from unwrapping the borrowed WETH, into WETH. Because claimReward measures reward by WETH balance delta on the loan rather than by validating reward provenance, the smart loan transfers the resulting WETH to the attacker owner. The seed balance diff confirms the economic outcome: WETH pool 0x2e2fe9bc7904649b65b6373baf40f9e2e0b883c5 lost 66619545304650988218 wei of WETH and attacker recipient 0x52ee5c0ea2e7b38d4b24c09d4d18cba6c293200e gained the same amount.
The adversary cluster consists of creator EOA 0xb87881637b5c8e6885c51ab7d895e53fa7d7c567, orchestrator 0x0b2bcf06f740c322bc7276b6b90de08812ce9bfe, and fake pair/hook plus final recipient 0x52ee5c0ea2e7b38d4b24c09d4d18cba6c293200e. Collector linkage artifacts show all three were created or controlled from the same origin shortly before the exploit block.
The exploit transaction unfolds in one sequence:
2859954771512993088821 WETH and force-sends the equivalent ETH into the freshly created Dolomite smart loan.swapDebtParaSwap with zero repayment and borrows 66619545304650988218 WETH from Dolomite’s WETH pool.claimReward with attacker contract 0x52ee5c0e... as the pair.wrapNativeToken on the loan, turning the loan’s ETH into WETH during the reward accounting window.claimReward transfers the WETH balance increase to the attacker owner as fake reward proceeds.66.619545304650988218 WETH.The measurable protocol loss is 66.619545304650988218 WETH from Dolomite’s WETH pool. The exploited smart loan ended with zero asset balance and 66619545304650988218 wei of WETH debt. The loss was borne by the Dolomite WETH lending side while the attacker kept the extracted WETH after flash-loan repayment.
0x6a2f989b5493b52ffc078d0a59a3bf9727d134b403aa6e0bf309fd513a728f7f0xf81b4381b70ef520ae635afd4b0e8aeb994131fb0x2e2fe9bc7904649b65b6373baf40f9e2e0b883c5claimReward facet source collected for the victim loanswapDebtParaSwap facet source collected for the victim loanrepay(0), fake claimReward, and callback execution0x52ee5c0ea2e7b38d4b24c09d4d18cba6c293200e