DEI burnFrom Allowance Inversion
Exploit Transactions
0xb1141785b7b94eb37c39c37f0272744c6e79ca1517529fec3f4af59d4c3c37efVictim Addresses
0xde1e704dae0b4051e80dabb26ab6ad6c12262da0Arbitrum0x7dc406b9b904a52d10e19e848521bba2de74888bArbitrumLoss Breakdown
Similar Incidents
Paribus Redeem Reentrancy
29%TecraCoin TcrToken burnFrom Allowance Bug Exploits
29%dForce Oracle Reentrancy Liquidation
27%Sentiment Balancer Oracle Overborrow
27%Dexible selfSwap allowance drain
25%SwapX Stale-Allowance Drain
23%Root Cause Analysis
DEI burnFrom Allowance Inversion
1. Incident Overview TL;DR
At Arbitrum block 87626026, transaction 0xb1141785b7b94eb37c39c37f0272744c6e79ca1517529fec3f4af59d4c3c37ef let an unprivileged attacker drain the DEI/USDC stable pair at 0x7DC406b9B904a52D10E19E848521BbA2dE74888b. The attacker used the broken DEI token implementation at 0xDE1E704dae0B4051e80DAbB26ab6ad6c12262DA0 to forge a pair -> attacker allowance, removed almost all DEI from the pair, synced the pair to a 1 DEI / 5,047,470.472573 USDC reserve state, and swapped the restored DEI back into the pair for 5,047,470.472572 USDC.
The root cause is DEI's reversed allowance lookup in burnFrom. Instead of checking allowance(account, caller), the implementation reads allowance(caller, account) and writes that value back as allowance(account, caller). That lets any caller convert an approval they control into approval over an arbitrary victim's DEI balance.
2. Key Background
DEI on Arbitrum is a proxied token, but the verified implementation code matches the behavior observed in the incident trace. The affected pair is a public DEI/USDC stable pair whose sync() function updates reserves to the pair's current balances without any access control, and whose getAmountOut() and swap() functions price trades from stored reserves.
Those pair mechanics matter because reserve manipulation is the monetization step, not the initial bug. Once the attacker can steal almost all DEI from the pair, calling sync() commits the pair to a near-zero DEI reserve while the USDC reserve remains intact. When the attacker then sends the stolen DEI back and calls swap(), the pair prices the incoming DEI against the collapsed reserve and releases essentially all USDC.
3. Vulnerability Analysis & Root Cause Summary
This incident is an ATTACK, not a pure MEV opportunity. The safety invariant for a burn-on-behalf function is: only allowance(account, caller) may authorize burnFrom(account, amount), and the function must decrement that same allowance edge. DEI violates that invariant in its implementation of burnFrom.
In the verified DEI implementation, the function reads _allowances[_msgSender()][account] and then writes that value back with _approve(account, _msgSender(), currentAllowance - amount). For amount = 0, the caller loses nothing, but the victim gains a forged approval toward the caller. That zero-amount state change should never be able to mutate third-party authorization.
The public pair contract then provides deterministic extraction. After the forged approval exists, the attacker can transferFrom almost all DEI out of the pair, leave 1 unit so the pair stays active, call sync() to lock in the manipulated reserve ratio, and finally swap the DEI back for USDC. The trace and balance diff show that exact path and confirm the realized loss.
4. Detailed Root Cause Analysis
Immediately before the exploit transaction, the DEI/USDC pair held 4602837090538811392635120 DEI and 5047470472573 USDC. The relevant victim-side code from the verified DEI implementation is:
function burnFrom(address account, uint256 amount) public virtual {
uint256 currentAllowance = _allowances[_msgSender()][account];
_approve(account, _msgSender(), currentAllowance - amount);
_burn(account, amount);
}
Origin: verified DEI implementation used by the proxy at incident time.
The pair-side monetization primitives are equally direct:
function sync() external lock {
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}
function getAmountOut(uint amountIn, address tokenIn) external view returns (uint) {
(uint _reserve0, uint _reserve1) = (reserve0, reserve1);
amountIn -= amountIn * PairFactory(factory).getFee(stable) / 10000;
return _getAmountOut(amountIn, tokenIn, _reserve0, _reserve1);
}
Origin: verified DEI/USDC pair source used in the exploit path.
The on-chain trace shows the invariant break and the monetization sequence in one transaction:
DEI::approve(pair, max)
DEI::burnFrom(pair, 0)
emit Approval(owner: pair, spender: helper, value: max)
DEI::transferFrom(pair, helper, 4602837090538811392635119)
pair::sync()
emit Sync(: 1, : 5047470472573)
pair::swap(0, 5047470472572, attacker_eoa, 0x)
Origin: incident transaction trace around the exploit core.
This proves the exploit does not depend on privileged keys, private order flow, or a special attacker contract. The sender EOA 0x189cf534de3097c08b6beaf6eb2b9179dab122d1 called its own nonce-0 helper at 0xe2ee6252509382a2b6504d5a5f7a1c5018a38168, but that helper only batches public calls. The ACT conditions are therefore straightforward: the victim must hold DEI, the attacker must first set approve(victim, x) on their own DEI balance, and burnFrom(victim, 0) must remain callable so the forged allowance(victim, attacker) can be created without burning any victim tokens.
5. Adversary Flow Analysis
- The attacker EOA
0x189cf534de3097c08b6beaf6eb2b9179dab122d1sent transaction0xb1141785b7b94eb37c39c37f0272744c6e79ca1517529fec3f4af59d4c3c37efto helper contract0xe2ee6252509382a2b6504d5a5f7a1c5018a38168. - The helper called
DEI.approve(pair, type(uint256).max), creating an attacker-controlledallowance(helper, pair). - The helper called
DEI.burnFrom(pair, 0). BecauseburnFromreads the allowance in the wrong direction, it emittedApproval(owner: pair, spender: helper, value: max)and forged apair -> helperapproval without burning DEI. - Using that forged approval, the helper called
DEI.transferFrom(pair, helper, 4602837090538811392635119), leaving exactly1DEI in the pair. - The helper called
pair.sync(), causing the pair to store reserves of1 DEIand5047470472573 USDC. - The helper computed
getAmountOuton the stolen DEI, transferred the DEI back into the pair, and calledpair.swap(0, 5047470472572, attacker_eoa, ""). - The pair transferred
5047470472572raw USDC units to the attacker EOA, leaving the pair with1raw USDC unit. The balance diff confirms the attacker EOA ended the transaction with all drained USDC and only paid407531400000000wei in native gas.
6. Impact & Losses
The realized loss was the pair's USDC inventory. The balance diff shows the DEI/USDC pair lost 5047470472572 raw USDC units and the attacker EOA gained the same amount.
{
"token_symbol": "USDC",
"amount": "5047470472572",
"decimal": 6
}
That is 5,047,470.472572 USDC. The pair's DEI balance also moved during the exploit path, and DEI fees were distributed to fee recipients during the final swap, but the primary externally realized loss was the drained USDC.
7. References
- Incident transaction:
0xb1141785b7b94eb37c39c37f0272744c6e79ca1517529fec3f4af59d4c3c37ef - Attacker EOA:
0x189cf534de3097c08b6beaf6eb2b9179dab122d1 - Attacker helper:
0xe2ee6252509382a2b6504d5a5f7a1c5018a38168 - DEI token proxy:
0xDE1E704dae0B4051e80DAbB26ab6ad6c12262DA0 - DEI/USDC pair:
0x7DC406b9B904a52D10E19E848521BbA2dE74888b - Verified DEI implementation artifact:
LERC20Upgradable.sol - Verified pair artifact:
Pair.sol - Supporting evidence: incident metadata, opcode-level trace, and balance-diff artifacts collected for the seed transaction