Root Cause
The exploit combined two independent weaknesses:
1. Missing state update in the legacy locker's withdrawal function
The 2021 DxSale locker contract failed to zero the lock record amount after withdrawal, allowing re-withdrawal against the same lock position until the shared pool was empty.
// Vulnerable pattern (simplified):
function withdrawLP(uint256 lockId) external onlyLockOwner(lockId) {
LockRecord storage rec = locks[lockId];
uint256 amount = rec.amount;
// rec.amount = 0; <-- MISSING: enables repeated withdrawal
IERC20(rec.token).transfer(msg.sender, amount);
emit Withdrawn(lockId, amount);
}
2. Owner-key compromise amplified via EIP-7702 delegation
EIP-7702 (Ethereum Pectra hard-fork, adopted on BNB Chain) allows an EOA to temporarily delegate code execution to a smart contract for a single transaction. The attacker:
- Obtained or generated control of the DxSale deployer key (0x47BAcf93) — likely via insider access or phishing.
- At 01:08 UTC May 26, called
transferOwnership(0xC4574D)on the legacy locker. - At 03:45 UTC May 28, submitted an EIP-7702 authorization designating a batch-executor contract as the EOA's code.
- One atomic transaction iterated over 1,400+ lock records, calling
withdrawLPon each.
Without EIP-7702, draining 1,400 pools would have required 1,400 separate transactions — observable and stoppable. The batch made it atomic and near-instant.
Attack Steps
| Step | Timestamp (UTC) | Action |
|---|---|---|
| 1 | May 26, 01:08 | transferOwnership(attackerEOA) on legacy locker |
| 2 | May 28, 03:45 | Attacker deploys batch-drain contract; sets EIP-7702 delegation |
| 3 | May 28, 03:45-04:10 | Single EIP-7702 transaction drains 1,400+ LP pools |
| 4 | May 28-29 | Funds routed through 80+ wallet hops to Binance addresses |
Impact
- Loss: $7.3M in locked LP tokens across 1,400+ BNB Chain pools
- Affected version: DxSale v1 locker (2021 contract); v2 and later confirmed unaffected
- Chain: BNB Chain (EVM)
- Funds largely unrecovered; exit via multiple centralized exchange addresses
Lessons for Auditors
- Check-Effects-Interactions: zero state before transfer. Any stateful withdrawal must clear the record before executing the transfer. Missing
rec.amount = 0is a classic reentrancy-family bug that enables draining even without a re-entrant call. - EIP-7702 changes the EOA-owner threat model. Contracts with
onlyOwnerchecks tied to an EOA must now account for the fact that owner EOAs can batch arbitrary calls atomically via EIP-7702. Consider time-locks on ownership-sensitive functions. - Legacy contracts accumulating TVL require active monitoring. A 5-year-old unmaintained locker held $7M+. Protocols must sunset, migrate, or actively monitor aging contracts rather than assume continued safety.
- Ownership-transfer events on high-TVL contracts should trigger alerts. A
transferOwnershipevent on a contract holding millions is an actionable signal detectable within minutes — not after the drain.