Calculated from recorded token losses using historical USD prices at the incident time.
0xd17266bcdf30cbcbd7d0b5a006f43141981aeee2e1f860f68c9a1805ecacbc680x528e046ACfb52bD3f9c400e7A5c79A8a2c2863d0Ethereum0x706206EabD6A70ca4992eEc1646B6D1599259CAeEthereumGhost deployed an ERC404-style token at 0x528e046ACfb52bD3f9c400e7A5c79A8a2c2863d0 and paired it with WETH in the Uniswap V2 pair 0x706206EabD6A70ca4992eEc1646B6D1599259CAe. In Ethereum block 19380955, public liquidity creation and buy transactions left the pair with a large WETH reserve and a large Ghost balance. The adversary then executed transaction 0xd17266bcdf30cbcbd7d0b5a006f43141981aeee2e1f860f68c9a1805ecacbc68, using helper contract 0xCC5159B5538268f45AfdA7b5756FA8769CE3e21f to drain nearly the entire WETH side of the pair.
The root cause was Ghost's ERC404 transferFrom behavior. The implementation allowed an unprivileged caller to move ERC20-denominated Ghost balances out of third-party accounts without a matching allowance or ownership authorization on the exercised path. The helper moved almost all Ghost units out of the pair, called sync() so Uniswap recorded a one-unit Ghost reserve, moved the Ghost units back into the pair, and then called swap() so Uniswap interpreted the restored Ghost balance as token input. The pair transferred 15438613861386138610 WETH wei to the helper, and the sender ended with a net native ETH increase of 14544561918536932745 wei after gas and builder payment.
Uniswap V2 pairs maintain reserves that are updated by functions such as sync() and swap(). During a swap, the pair transfers the requested output token optimistically, then checks the post-swap balances to infer whether enough input token was provided. This accounting assumes that token balances reflect economically valid token movement and that unrelated callers cannot arbitrarily move the pair's token balances around.
Ghost is an ERC1967 proxy token. The proxy address is 0x528e046ACfb52bD3f9c400e7A5c79A8a2c2863d0, and the implementation observed in the artifacts is 0x17dfb09194ab77DCf318839cFdD01f587a73a77E. The token exposes ERC404-style ERC20 and NFT functions, including balanceOf(address), balanceOfNFT(address), tokenOfOwnerByIndex(address,uint256), transfer(address,uint256), and transferFrom(address,address,uint256).
The relevant public setup transactions in block 19380955 are:
0x1393d839106ce8ba0de7d07a847d38f36ae22b25c00d52b170d17760b4a876e4
0xabb72a8d35943cd46c95b45c8446590f1a73dfb095aed35adefe971e33ef03b5
0xf99271d1c8ad63d64218063e00a7c8dc312addca5bc65b3e3110144763006358
0xdcdb017195d1234207e3b4fbc992539aa5610df31ef69ec8fc0930c843095cd7
0xd8caed1d6671ed2dce147716e1f077225ee3c969d796bf17fe160fbc7becdd7f
0x0a227162abef874bd5815403f0c3bb6327a0aba0c6a1c10c6da4022a381bcf79
These transactions created the pair, added liquidity, and performed public buys that moved WETH into the pair and Ghost out of the pair. The adversary could observe this public state and submit an unprivileged transaction against the same public contracts.
The vulnerability class is an access-control failure in Ghost's ERC20-denominated transfer path. For ERC20 semantics, transferFrom(from, to, amount) must only reduce from when msg.sender == from or msg.sender has sufficient allowance. The exploit trace shows the helper contract reducing the pair's Ghost balance and later reducing the Ghost token contract's own Ghost balance through transferFrom calls, without being the pair, the token contract, or an approved spender in the semantic flow.
That broken token invariant became exploitable because the token was paired with WETH on Uniswap V2. Once the helper could move the pair's Ghost balance without authorization, it could make Uniswap record an artificially low Ghost reserve by calling sync(). Moving the same Ghost amount back into the pair then looked like a large input amount to swap(). Uniswap V2's accounting behaved as designed; the invalid state came from Ghost allowing the pair's token balance to be manipulated by an unrelated caller.
The root-cause breakpoint is Ghost implementation selector 0x23b872dd, decoded in the trace as transferFrom(address,address,uint256), as exercised by helper contract 0xCC5159B5538268f45AfdA7b5756FA8769CE3e21f. The safety invariant was violated before Uniswap accounting ran: a third party moved balances owned by the pair and by the Ghost token contract.
Immediately before the adversary extraction, the Ghost/WETH pair had matching Ghost balance and Ghost reserve:
Ghost.balanceOf(pair) = 387833652370923809808350
pair Ghost reserve = 387833652370923809808350
pair WETH reserve = 15438613861386138611
The seed trace shows the helper first querying the pair's Ghost balance, then calling Ghost transferFrom to move all but one Ghost unit from the pair to the Ghost token contract:
0x528e046ACfb52bD3f9c400e7A5c79A8a2c2863d0::balanceOf(
0x706206EabD6A70ca4992eEc1646B6D1599259CAe
) -> 387833652370923809808350
0x528e046ACfb52bD3f9c400e7A5c79A8a2c2863d0::transferFrom(
0x706206EabD6A70ca4992eEc1646B6D1599259CAe,
0x528e046ACfb52bD3f9c400e7A5c79A8a2c2863d0,
387833652370923809808349
)
After that movement, the helper called sync(). This made the pair's recorded reserves follow the manipulated balances: Ghost reserve became 1, while the WETH reserve remained 15438613861386138611.
The helper then called Ghost transferFrom again, moving the same Ghost amount from the Ghost token contract back to the pair. Because the recorded Ghost reserve was still 1, this restored Ghost balance became artificial token input from Uniswap's perspective. The final swap call drained almost the entire WETH reserve:
0x706206EabD6A70ca4992eEc1646B6D1599259CAe::getReserves()
-> reserveGhost = 1
-> reserveWeth = 15438613861386138611
0x706206EabD6A70ca4992eEc1646B6D1599259CAe::swap(
0,
15438613861386138610,
0xCC5159B5538268f45AfdA7b5756FA8769CE3e21f,
0x
)
WETH9::transfer(
0xCC5159B5538268f45AfdA7b5756FA8769CE3e21f,
15438613861386138610
)
The pair emitted Sync with Ghost reserve 387833652370923809808350 and WETH reserve 1 after the swap, confirming the WETH side was reduced to one wei. The helper then called WETH9.withdraw(15438613861386138610), converting the extracted WETH into native ETH.
The adversary transaction was sent by EOA 0x096f0f03e4be68d7e6dd39b22a3846b8ce9849a3. The transaction called helper contract 0xCC5159B5538268f45AfdA7b5756FA8769CE3e21f, which performed the exploit sequence end to end.
The sequence was:
transferFrom(pair, ghostToken, ghostBalance - 1).sync() so pair reserves record only one Ghost unit.transferFrom(ghostToken, pair, ghostBalance - 1).swap(0, reserveWeth - 1, helper, "").withdraw for the WETH received.756492079207920791 wei to the block builder address 0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5.14682121782178217868 wei from the helper to the sender.This is an ACT opportunity. A fresh unprivileged adversary can reproduce the critical sequence using public state, public contract addresses, a locally deployed helper if desired, and normal Ethereum transaction submission. No private key, privileged role, non-public data, or attacker-side artifact from the incident is required.
The direct asset loss was from the Ghost/WETH Uniswap V2 pair:
{
"token_symbol": "WETH",
"amount": "15438613861386138610",
"decimal": 18
}
The balance-diff artifact records the WETH contract native balance decreasing by 15438613861386138610 wei through withdrawal, matching the WETH amount extracted from the pair. It also records the adversary sender's native ETH balance increasing from 18539628821928862590 wei to 33084190740465795335 wei, for a net increase of 14544561918536932745 wei after gas and builder payment.
0xd17266bcdf30cbcbd7d0b5a006f43141981aeee2e1f860f68c9a1805ecacbc680x528e046ACfb52bD3f9c400e7A5c79A8a2c2863d00x17dfb09194ab77DCf318839cFdD01f587a73a77E0x706206EabD6A70ca4992eEc1646B6D1599259CAe0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20x096f0f03e4be68d7e6dd39b22a3846b8ce9849a30xCC5159B5538268f45AfdA7b5756FA8769CE3e21f0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5/workspace/session/artifacts/collector/seed/1/0xd17266bcdf30cbcbd7d0b5a006f43141981aeee2e1f860f68c9a1805ecacbc68/trace.cast.log/workspace/session/artifacts/collector/seed/1/0xd17266bcdf30cbcbd7d0b5a006f43141981aeee2e1f860f68c9a1805ecacbc68/balance_diff.json/workspace/session/artifacts/auditor/block_19380955_interesting_receipts.json/workspace/session/artifacts/auditor/decompiled/token_impl-decompiled.sol