All incidents

GoldCoin Pair Reserve Overwrite

Share
Jul 20, 2023 00:07 UTCAttackLoss: 53,198.43 USDTPending manual check1 exploit txWindow: Atomic
Estimated Impact
53,198.43 USDT
Label
Attack
Exploit Tx
1
Addresses
2
Attack Window
Atomic
Jul 20, 2023 00:07 UTC → Jul 20, 2023 00:07 UTC

Exploit Transactions

TX 1BSC
0x199c4b88cab6b4b495b9d91af98e746811dd8f82f43117c48205e6332db9f0e0
Jul 20, 2023 00:07 UTCExplorer

Victim Addresses

0x80121da952a74c06adc1d7f85a237089b57af347BSC
0x7a3adf2f6b239e64dab1738c695cf48155b6e152BSC

Loss Breakdown

53,198.43USDT

Similar Incidents

Root Cause Analysis

GoldCoin Pair Reserve Overwrite

1. Incident Overview TL;DR

On BSC block 30113118, transaction 0x199c4b88cab6b4b495b9d91af98e746811dd8f82f43117c48205e6332db9f0e0 drained the FFIST/USDT PancakeSwap pool by corrupting the token-side reserve before a sell. The attacker first bought FFIST, then sent a zero-value FFIST transfer that overwrote the pair's FFIST balance to 1, called public sync(), and finally sold 24 raw FFIST units into the now-desynchronized pool. The exploit was possible because GoldCoin's _airdrop logic lets any non-whitelisted transfer write _balances[airdropAddress] = 1 for attacker-controlled addresses, including the LP pair. The seed transaction produced a deterministic adversary portfolio gain of 219.154800816693701658 BNB-equivalent and left the pair short 53198433607272063480945 raw USDT units.

2. Key Background

This incident depends on the interaction between a broken token and a standard constant-product pair. PancakeSwap V2 pairs trust token balanceOf(pair) when sync() or swap() updates reserves, so any token that can arbitrarily rewrite the pair balance can corrupt AMM pricing. GoldCoin exposes exactly that condition because _airdrop runs on any transfer where both endpoints are not fee-whitelisted, including zero-value transfers.

Snippet origin: verified GoldCoin source.

if (!_feeWhiteList[from] && !_feeWhiteList[to]) {
    uint256 maxSellAmount = balance * 999999 / 1000000;
    if (amount > maxSellAmount) {
        amount = maxSellAmount;
    }
    _airdrop(from, to, amount);
}

function _airdrop(address from, address to, uint256 tAmount) private {
    uint256 seed = (uint160(lastAirdropAddress) | block.number) ^ (uint160(from) ^ uint160(to));
    address airdropAddress = address(uint160(seed | tAmount));
    _balances[airdropAddress] = 1;
}

The public pre-state before the seed transaction was also favorable to the attacker. Immediately before the exploit, the FFIST/USDT pair 0x7a3adf2f6b239e64dab1738c695cf48155b6e152 held 55420690999928645538098 raw USDT units and 5696780254392656566076 raw FFIST units, and GoldCoin.lastAirdropAddress() was 0x1E14a05467B0624D7542d61dCFBF27fff56efeDd. That state made the overwrite recipient fully derivable from public data.

3. Vulnerability Analysis & Root Cause Summary

This is an ATTACK-class incident caused by attacker-controlled arbitrary balance overwrite in the GoldCoin token, not by a flaw in PancakeSwap math. The violated invariant is simple: the LP pair's token reserve must always equal tokens legitimately transferred into or out of the pair. GoldCoin breaks that invariant because _airdrop writes 1 directly into a user-chosen _balances slot before normal transfer accounting finishes. Since lastAirdropAddress, block.number, from, to, and amount are either public or attacker-controlled, an unprivileged adversary can solve for a recipient that makes the first airdrop target equal the LP pair. Once the pair's token balance is forced to 1, public PancakePair::sync() commits that forged balance as the official reserve. The next sell then prices FFIST against almost the entire USDT reserve, which deterministically drains the pool.

4. Detailed Root Cause Analysis

  1. The attacker starts from the public pre-state at block 30113118. The victim token contract is GoldCoin 0x80121da952a74c06adc1d7f85a237089b57af347, and the victim pool is the FFIST/USDT pair 0x7a3adf2f6b239e64dab1738c695cf48155b6e152.
  2. The attacker buys FFIST first. This serves two purposes: it acquires a small FFIST balance and updates lastAirdropAddress to a new public value. After this leg, the helper contract holds 237053613464325871 raw FFIST units.
  3. The attacker then chooses a zero-transfer recipient such that:
to = (lastAirdropAddress | block.number) ^ from ^ pair

With amount = 0, the first _airdrop candidate equals the pair address itself. In the seed transaction, that recipient is 0xd3c0e4d4ae359e0ac217f235bd46175fc60cd757.

Snippet origin: seed exploit trace.

GoldCoin::transfer(0xd3c0e4d4ae359e0ac217f235bd46175fc60cd757, 0)
...
@ pair-balance-slot: 0x...0134cf47dc3cc4d537ae -> 1
PancakePair::sync()
emit Sync(reserve0: 55423099372855156505378, reserve1: 1)
  1. After sync(), the pair reserves become (55423099372855156505378 USDT, 1 FFIST). At that point the AMM believes the entire FFIST side is only 1 wei, even though the pair still holds enough USDT liquidity to pay out a large trade.
  2. The attacker sells 24 raw FFIST units into the corrupted pool. The trace shows the pair paying 53200841980198574448225 raw USDT to the intermediate USDT/WBNB route, and the balance diff shows the pair's net USDT loss as 53198433607272063480945 raw units.
  3. The attacker then converts the drained USDT into WBNB and forwards it to the controlling EOA. The final WBNB transfer in the trace is 219169869136693701658 raw WBNB units. Combined with the native BNB delta from the balance diff, the adversary portfolio moved from 161.148686511492538427 BNB-equivalent before the transaction to 380.303487328186240085 BNB-equivalent after it. The root cause artifact records fees paid in the reference asset as 0.01506832 BNB, which is already reflected in the net portfolio increase of 219.154800816693701658.

5. Adversary Flow Analysis

The adversary cluster contains two addresses:

  • EOA 0xcc8617331849962c27f91859578dc91922f6f050, which signed the exploit transaction, funded the helper deployment with 0.01 BNB, and received the final WBNB.
  • Helper contract 0xb31c7b7bdf69554345e47a4393f53c332255c9fb, which executed the entire on-chain strategy inside the same transaction.

The on-chain flow is fully public and permissionless:

  1. The EOA deploys the helper and funds it.
  2. The helper wraps 0.01 BNB into WBNB, approves the Pancake router 0x10ed43c718714eb63d5aa57b78b54704e256024e, and buys FFIST along the path WBNB -> USDT -> FFIST.
  3. The helper computes the overwrite recipient, calls GoldCoin.transfer(recipient, 0), and forces the pair balance to 1.
  4. The helper calls public PancakePair::sync() on the FFIST/USDT pair.
  5. The helper sells 24 raw FFIST units along the path FFIST -> USDT -> WBNB.
  6. The helper transfers the resulting WBNB back to the EOA, realizing profit in the same transaction.

The exploit is ACT because every step uses canonical public state, verified or publicly callable contracts, and public transaction inclusion. No private keys, allowlists, attacker bytecode reuse, or privileged sequencing are required.

6. Impact & Losses

The measurable loss fell on the FFIST/USDT pool and, economically, on GoldCoin liquidity.

  • Victim pool loss: 53198433607272063480945 raw USDT units (53,198.433607272063480945 USDT if interpreted with 18 decimals as collected in the artifacts).
  • Pair state corruption: the FFIST reserve was forced down to 1 during the attack and ended the transaction at only 25 raw FFIST units.
  • Adversary gain: 219169869136693701658 raw WBNB transferred out of the helper, equal to a net portfolio increase of 219.154800816693701658 BNB-equivalent after native gas and funding effects.

The affected public components are GoldCoin 0x80121da952a74c06adc1d7f85a237089b57af347 and the FFIST/USDT PancakePair 0x7a3adf2f6b239e64dab1738c695cf48155b6e152.

7. References

  • Seed exploit transaction: 0x199c4b88cab6b4b495b9d91af98e746811dd8f82f43117c48205e6332db9f0e0 on BSC block 30113118
  • GoldCoin contract: 0x80121da952a74c06adc1d7f85a237089b57af347
  • FFIST/USDT pair: 0x7a3adf2f6b239e64dab1738c695cf48155b6e152
  • Supporting artifacts: seed metadata, seed trace, seed balance diff, and the refreshed resolution evidence under artifacts/auditor/iter_1/resolution_evidence.json
  • Verified source confirmation: GoldCoin verified source in the collected artifacts; PancakePair verification confirmed via Etherscan v2 / BscScan code page