Calculated from recorded token losses using historical USD prices at the incident time.
0x8d35dfd9968ce61fb969ffe8dcc29eeeae864e466d2cb0b7d26ce636446919940xb47955b5b7eaf49c815ebc389850eb576c460092BSC0xee2a9d05b943c1f33f3920c750ac88f74d0220c3BSCAt BNB Chain transaction 0x8d35dfd9968ce61fb969ffe8dcc29eeeae864e466d2cb0b7d26ce63644691994, an attacker-controlled helper contract 0x45aa258ad08eeeb841c1c02eca7658f9dd4779c0 used five public DODO flash loans to borrow USDT, bought APE2, repeatedly recycled that APE2 through the official APE2/USDT Pancake pair with skim(), then called the public goDead() function to burn pair-held APE2 and collapse the pool reserve. The exploit was possible because APE2 records a 20% deferred burn liability on sell-classified transfers in amountToDead, but it never escrows the backing tokens for that liability. When goDead() runs, it pays the queued burn directly from the LP pair and immediately syncs reserves, so liquidity providers absorb the debit. The attacker ended the transaction with 7127248018299132277754 USDT, and even after converting the BNB gas cost into USDT at block 30072293, the net profit remained 7104753978095161372836 USDT.
Three protocol behaviors matter for this incident.
First, PancakeSwap V2 pairs keep reserves in storage and expose a permissionless skim(address) method that transfers any token balance sitting above the stored reserves to an arbitrary receiver. That means a token can be sent to the pair, left unsynced, and then pulled back out by anyone allowed to call skim().
Second, APE2 classifies transfers into the official pair as sells unless the live pair balance and reserve delta look like an add-liquidity pattern. Sell-classified transfers incur token-side fees and, crucially, increment the accumulator.
amountToDeadThird, the queued dead-burn is not backed by seller-funded escrow. APE2 only transfers 5% of the gross sell amount into token-controlled balances while separately increasing amountToDead by 20% of the gross amount. That mismatch is what lets the later public burn spend LP reserves instead of seller funds.
This is an ATTACK-class accounting flaw in the victim token, not a benign MEV strategy. The violated invariant is that any deferred burn amount must already be fully collateralized before it is allowed to debit third-party assets. In APE2, the sell path records amountToDead += amountx * 20 /100 after a sell-classified transfer, but the contract never escrows that extra 20% anywhere. The public goDead() function later settles the queued amount by executing _rawTransfer(address(pair), address(0xdEaD), amountToDead); and then pair.sync();, so the liquidity pair becomes the source of truth and the source of payment. Because skim() is permissionless, the attacker can keep reusing almost the same APE2 balance across many sell-classified transfers while amountToDead keeps growing. Once goDead() burns a large amount directly from the pair, the pool’s APE2 reserve collapses and the attacker can dump the remaining APE2 into a badly distorted market for large USDT extraction.
The core bug is visible directly in the verified APE2 source:
if (_transferType == 2){
if (isOpenToDead){
amountToDead += amountx * 20 /100;
}
}
function goDead() public{
if (amountToDead > 0){
_rawTransfer(address(pair), address(0xdEaD), amountToDead);
pair.sync();
amountToDead = 0;
}
}
This code establishes the concrete breakpoint. During a sell-classified transfer, the token books a future burn equal to 20% of the gross amount, but the earlier fee logic only moves 2% to address(this), 2% to the NFT distributor, 1% to address(this) again, and optionally a time-based sell tax. No escrow is created for the extra 20%, so amountToDead is an unbacked liability.
The trace shows how the attacker turns that accounting bug into a reusable primitive. After buying APE2 with 19000000000000000000000 USDT, the attacker transfers APE2 into the pair, which the token classifies as a sell, and then immediately pulls the unsynced excess balance back out with skim():
APE2::transfer(PancakePair, 2126740767132717497770)
emit Transfer(from: attacker, to: PancakePair, value: 2126740767132717497770)
PancakePair::skim(attacker)
APE2::transfer(attacker, 2126740767132717497770)
emit Transfer(from: PancakePair, to: attacker, value: 2020403728776081622883)
The important semantic effect is not the exact fee dust but the repeated state transition: every sell-classified transfer increases amountToDead, while skim() returns most of the same APE2 back to the attacker so it can be used again. The exploit trace shows this recycle pattern sixteen times before the attacker triggers the burn.
Once the queued burn is large enough, the attacker calls goDead() and the pair pays the liability:
APE2::goDead()
emit Transfer(from: PancakePair, to: 0x000000000000000000000000000000000000dEaD, value: 3708941327967849359474)
PancakePair::sync()
emit Sync(reserve0: 27993453783576933576082, reserve1: 31146393415761651955)
PancakePair::swap(26127248018299132278755, 0, attacker, 0x)
That is the exact invariant break in realized form. Before goDead(), LP reserves still implicitly support the APE2/USDT market. After goDead() and sync(), the pair’s APE2 reserve is only 31146393415761651955, so the attacker’s remaining 437148509290176342673 APE2 becomes enough to pull 26127248018299132278755 USDT from the pair in the final swap. The collected balance diff confirms the pair lost 19155190926317811152304 USDT and the attacker contract retained 7127248018299132277754 USDT after repaying all five flash loans.
The exploit is a single adversary-crafted BNB Chain transaction by EOA 0x10703f7114dce7beaf8d23cde4bf72130bb0f56a, routed through helper contract 0x45aa258ad08eeeb841c1c02eca7658f9dd4779c0.
2153560542292103331545884 USDT.0x10ED43C718714eb63d5aA57B78B54704E256024E and swaps 19000000000000000000000 USDT into APE2.APE2.transfer(pair, ...) and PancakePair.skim(attacker) sixteen times, inflating amountToDead without surrendering the full token position.APE2.goDead(), which burns 3708941327967849359474 APE2 from pair 0xee2a9d05b943c1f33f3920c750ac88f74d0220c3 and syncs the pair to a tiny APE2 reserve.1001 USDT dust plus its remaining APE2 into the pair, computes the swap output with PancakeRouter.getAmountsOut, and extracts 26127248018299132278755 USDT.All external components used in this path are permissionless: the DODO flash-loan pools, PancakeRouter, PancakePair skim() and swap(), and the public APE2 goDead() entrypoint. That satisfies the ACT framing.
The measured victim-side loss is the USDT drained from the APE2/USDT Pancake pair: 19155190926317811152304 raw units of USDT with 18 decimals. The pair also lost 4501944705070935349078 APE2 over the transaction, including the direct dead-address burn that forced the reserve collapse.
The attacker contract ended with 7127248018299132277754 USDT. The transaction receipt shows gasUsed=18835861 and effectiveGasPrice=5000000000, which equals 94179305000000000 wei of BNB gas. Replaying PancakeRouter getAmountsOut(94179305000000000, [WBNB, USDT]) at block 30072293 yields 22494040203970904918 USDT, so the fee-adjusted profit is still 7104753978095161372836 USDT. The affected public components are the APEDAO token contract 0xb47955b5b7eaf49c815ebc389850eb576c460092 and its official APE2/USDT Pancake pair 0xee2a9d05b943c1f33f3920c750ac88f74d0220c3.
0x8d35dfd9968ce61fb969ffe8dcc29eeeae864e466d2cb0b7d26ce636446919940x10703f7114dce7beaf8d23cde4bf72130bb0f56a0x45aa258ad08eeeb841c1c02eca7658f9dd4779c00xb47955b5b7eaf49c815ebc389850eb576c4600920xee2a9d05b943c1f33f3920c750ac88f74d0220c3artifacts/collector/seed/56/0xb47955b5b7eaf49c815ebc389850eb576c460092/src/kape/APE2.solartifacts/collector/seed/56/0x8d35dfd9968ce61fb969ffe8dcc29eeeae864e466d2cb0b7d26ce63644691994/trace.cast.logartifacts/collector/seed/56/0x8d35dfd9968ce61fb969ffe8dcc29eeeae864e466d2cb0b7d26ce63644691994/balance_diff.json30072293