Calculated from recorded token losses using historical USD prices at the incident time.
0x2c0ada695a507d7a03f4f308f545c7db4847b2b2c82de79e702d655d8c95dadb0xf51cbf9f8e089ca48e454eb79731037a405972ceBSC0x61373083f4deef88ba449ad82218059781962d76BSCOn BSC block 38539573, transaction 0x2c0ada695a507d7a03f4f308f545c7db4847b2b2c82de79e702d655d8c95dadb used a public DODO flashloan to buy GPU, repeatedly self-transfer the token to inflate the attacker's balance, dump the inflated GPU into the PancakeSwap V2 GPU/USDT pair, repay the flashloan, and keep 32399.797724236292795089 USDT profit. The root cause is a token-accounting bug in GPU token 0xf51cbf9f8e089ca48e454eb79731037a405972ce: its inherited ERC20 transfer logic caches both balances before writing storage, so a transfer where sender == recipient increases the sender's balance instead of leaving it unchanged.
GPU is a fee-on-transfer token paired against USDT in PancakeSwap V2 pair 0x61373083f4deef88ba449ad82218059781962d76. The token also applies burn and contract-fee branches on buys and sells before the final balance write.
PancakeSwap V2 trusts the token balances it observes after token transfer hooks complete. If a token lets an external account inflate its own spendable balance off-pair, that account can sell the counterfeit balance into the pair and receive real quote assets.
The attacker did not need privileged access. DODO pool 0x6098a5638d8d7e9ed2f952d35b2b67c34ec6b476 exposed a public USDT flashloan, PancakeRouter 0x10ed43c718714eb63d5aa57b78b54704e256024e exposed public swap functions, and GPU exposed the broken self-transfer path to any caller.
The bug sits in GPU's inherited ERC20 implementation, not in DODO or PancakeSwap. In the verified source, _transfer reads both and into local variables before writing either slot. When and are the same address, both locals are snapshots of the same pre-state balance. The first write subtracts , but the second write immediately overwrites that same storage slot with . That violates the ERC20 invariant that a self-transfer must leave the holder's balance unchanged and that transfers must conserve balances.
_balances[sender]_balances[recipient]senderrecipientamountoldBalance + amountThe relevant code path from the verified GPU source is:
function _transfer(address sender, address recipient, uint256 amount) internal virtual {
uint256 senderAmount = _balances[sender];
uint256 recipientAmount = _balances[recipient];
require(senderAmount >= amount, "ERC20: transfer amount exceeds balance");
_balances[sender] = senderAmount.sub(amount);
_balances[recipient] = recipientAmount.add(amount);
emit Transfer(sender, recipient, amount);
}
GPU's top-level token logic routes ordinary wallet-to-wallet transfers through super._transfer(from, to, amount), so a user can trigger this bug simply by calling transfer(msg.sender, balance) after acquiring any positive GPU balance.
The exploit starts from the BSC pre-state immediately before block 38539573, where the DODO USDT pool held lendable USDT and the GPU/USDT Pancake pair held monetizable quote liquidity. The seed trace shows the attacker-controlled contract 0x1334fc75449802e72812f801a6b5eb3a06d953ca borrowing 226007684814310683599320 raw USDT from the DODO pool, then swapping that USDT through PancakeRouter into GPU.
The trace then shows the GPU buy result:
GPU::transfer(attacker, 26992803697364241636753)
emit Transfer(from: pair, to: attacker, amount: 26183019586443314387599)
That leaves the attacker contract with a positive GPU balance after transfer fees. From there, the attacker repeatedly calls GPU with the same sender and recipient:
GPU::transfer(attacker, 26183019586443314387599)
GPU::transfer(attacker, 52363420870927984436161)
GPU::transfer(attacker, 104721605399768876067718)
...
Each call increases the attacker's balance because the second _balances[recipient] assignment reuses the pre-state value. The trace emitted self-transfers while the observed stored balance kept growing, which is only consistent with the cached-balance overwrite bug being live on-chain.
After enough self-transfers, the attacker had an inflated balance large enough to dump 27226790220045602380642892762984 raw GPU into the pair. PancakeSwap then paid out 258407482538546976394409 raw USDT to the attacker contract. The attacker repaid the DODO flashloan principal of 226007684814310683599320 raw USDT and transferred the remainder, 32399797724236292795089 raw USDT, to EOA 0xcc78063840428c5ae53f3dc6d80759984788cbc0.
The trace evidence for the liquidation stage is:
emit Transfer(from: attacker, to: GPU/USDT Pair, amount: 27226790220045602380642892762984)
emit Transfer(from: GPU/USDT Pair, to: attacker, amount: 258407482538546976394409)
emit Transfer(from: attacker, to: DODO Pool, amount: 226007684814310683599320)
emit Transfer(from: attacker, to: 0xcC78063840428c5aE53f3DC6D80759984788Cbc0, amount: 32399797724236292795089)
This establishes an end-to-end causal chain: public flash liquidity provided initial capital, the broken GPU self-transfer path created counterfeit spendable balance, PancakeSwap converted that counterfeit balance into real USDT, and the remainder after repayment became attacker profit.
The adversary cluster in the seed transaction consists of EOA 0xcc78063840428c5ae53f3dc6d80759984788cbc0, deployer/helper contract 0x5234001627a376f5e0accb082548a283b1fa1586, and exploit contract 0x1334fc75449802e72812f801a6b5eb3a06d953ca. The EOA paid gas and received the final profit; the exploit contract executed the flashloan, swaps, inflation loop, repayment, and payout.
The on-chain flow is:
GPU.transfer(address(this), currentBalance).The strategy is therefore a single-transaction, permissionless ACT exploit: no privileged signer, no private admin function, no attacker-specific bytecode reuse, and no dependence on off-chain secrets.
The measurable loss was real USDT removed from the GPU/USDT Pancake pair. The root cause JSON records a net attacker profit of 32399797724236292795089 raw USDT units, with 18 decimals, which is 32399.797724236292795089 USDT. The validator fork trace reproduced the same mechanism and left the pair with only 39782116270530 raw USDT, confirming that the quote-side liquidity collapse is the critical economic impact.
Affected components were:
0xf51cbf9f8e089ca48e454eb79731037a405972ce, which exposed the broken transfer invariant.0x61373083f4deef88ba449ad82218059781962d76, which was drained of USDT when it priced the inflated GPU balance as real inventory.0x2c0ada695a507d7a03f4f308f545c7db4847b2b2c82de79e702d655d8c95dadb0xf51cbf9f8e089ca48e454eb79731037a405972ce0x61373083f4deef88ba449ad82218059781962d760x6098a5638d8d7e9ed2f952d35b2b67c34ec6b476GPUseed/56/0x61373083f4deef88ba449ad82218059781962d76/src/Contract.solartifacts/collector/seed/56/0x2c0ada695a507d7a03f4f308f545c7db4847b2b2c82de79e702d655d8c95dadb/