All incidents

AiWGPT Public Sell LP Drain

Share
Jul 12, 2023 07:08 UTCAttackLoss: 105,212.23 USDT, 81,017.86 WGPT +1 morePending manual check1 exploit txWindow: Atomic
Estimated Impact
105,212.23 USDT, 81,017.86 WGPT +1 more
Label
Attack
Exploit Tx
1
Addresses
2
Attack Window
Atomic
Jul 12, 2023 07:08 UTC → Jul 12, 2023 07:08 UTC

Exploit Transactions

TX 1BSC
0x258e53526e5a48feb1e4beadbf7ee53e07e816681ea297332533371032446bfd
Jul 12, 2023 07:08 UTCExplorer

Victim Addresses

0x1f415255f7e2a8546559a553e962de7bc60d7942BSC
0x5a596eae0010e16ed3b021fc09bbf0b7f1b2d3cdBSC

Loss Breakdown

105,212.23USDT
81,017.86WGPT
93,666.25WGPT-USDT-LP

Similar Incidents

Root Cause Analysis

AiWGPT Public Sell LP Drain

1. Incident Overview TL;DR

On BSC block 29891710, transaction 0x258e53526e5a48feb1e4beadbf7ee53e07e816681ea297332533371032446bfd exploited AiWGPTToken at 0x1f415255f7e2a8546559a553e962de7bc60d7942 and its WGPT/USDT Pancake pair at 0x5a596eae0010e16ed3b021fc09bbf0b7f1b2d3cd. The adversary used public DODO flash liquidity, bought WGPT to push down the pair's live WGPT balance, then sold WGPT back through PancakeRouter so each sell entered AiWGPTToken's public sell hook and removed protocol-owned LP from the pair.

The root cause is an attacker-manipulable LP-removal formula on the public sell path. AiWGPTToken computes sell-side LP removal from the pair's instantaneous WGPT balance rather than from a manipulation-resistant price source, so any unprivileged seller can force protocol LP withdrawal and deplete both pair reserves whenever isSwap=true.

2. Key Background

AiWGPTToken is not a plain fee-on-transfer token. Its transferFrom() sell branch treats transfers into a listed swap pair as a privileged event and, when burnToken is enabled, calls internal LP-removal logic before finishing the transfer.

The relevant public market is the WGPT/USDT Pancake pair 0x5a596eae0010e16ed3b021fc09bbf0b7f1b2d3cd. Before the exploit transaction, the token contract still held 93671278471332372231927 LP tokens for that pair, and the pair reserves were 152344224829762814626477 WGPT and 246891982286046691958845 USDT.

Public DODO pools on BSC provide permissionless flash liquidity. That matters because the exploit only needs temporary capital to distort the pair balance and then sell back through the public router; it does not require privileged keys, attacker-owned on-chain artifacts from the original incident, or any protocol admin capability.

3. Vulnerability Analysis & Root Cause Summary

The vulnerability is an attack-class logic flaw in AiWGPTToken's sell hook. The contract exposes protocol-owned LP withdrawal on an unprivileged sell path and sizes that withdrawal using a price proxy derived from IERC20(address(this)).balanceOf(swapLPAddr), which is the pair's instantaneous WGPT balance. That value is directly movable by any trader who buys from the pair in the same transaction. Once the attacker lowers the pair's visible WGPT balance, callPrice() falls and (_value * 1e18 / callPrice()) + lpNumber grows, causing removeLp() to burn more protocol LP than intended. The withdrawn USDT and WGPT are then consumed by usdtToToken() and burnToekn(), leaving the pair with lower reserves and the token contract with far less LP. The broken invariant is that an unprivileged seller must never be able to trigger protocol LP removal in an amount derived from a transient, attacker-manipulable spot balance.

4. Detailed Root Cause Analysis

The vulnerable code path in the verified AiWGPTToken source is:

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
    ...
    if(_swapPairList[_to]){
        if(isSwap || _WhiteList[_from]){
            if(burnToken){
                sellburnToken(callBurn(_value));
            }
            ...
        }
    }
}

function callPrice() private returns(uint256){
    uint256 tokenBalance = IERC20(address(this)).balanceOf(swapLPAddr);
    tokenBalance = tokenBalance * 10 ** 18;
    uint256 lpBalance = IERC20(swapLPAddr).totalSupply();
    lpPrice = tokenBalance / lpBalance;
    return lpPrice * 2;
}

function sellburnToken(uint256 _value) private returns(bool){
    (uint amountA, uint amountB) = removeLp((_value * 10**18 / callPrice()) + lpNumber);
    usdtToToken(_value - amountB);
    burnToekn(amountB);
    return true;
}

The exploit sequence follows directly from that code:

  1. Start from BSC block 29891709, where isSwap=true, burnToken=true, swapLPAddr already points to the public WGPT/USDT pair, and the token contract still owns a large LP position.
  2. Borrow public USDT liquidity through DODO and buy WGPT from the pair. This reduces IERC20(address(this)).balanceOf(swapLPAddr), which is the exact input used by callPrice().
  3. Sell WGPT back into the pair through PancakeRouter. Each sell reaches transferFrom() with _to equal to the pair, so the token invokes sellburnToken().
  4. Because callPrice() is now artificially depressed, removeLp() withdraws excessive protocol LP from the pair.
  5. The withdrawn assets are partially recycled through usdtToToken() and burnToekn(), while the attacker exits the sell path with positive USDT.

The incident trace shows the repeated LP-removal breakpoint clearly. During the exploit transaction, PancakeRouter repeatedly executes:

PancakeRouter::removeLiquidity(
  BEP20USDT,
  AiWGPTToken,
  4448262008380174093090,
  10,
  10,
  AiWGPTToken,
  1689146288
)
...
PancakeRouter::removeLiquidity(
  BEP20USDT,
  AiWGPTToken,
  2137068849583674609242,
  10,
  10,
  AiWGPTToken,
  1689146288
)

The balance-diff evidence confirms the final state change caused by that path:

{
  "token": "0x5a596eae0010e16ed3b021fc09bbf0b7f1b2d3cd",
  "holder": "0x1f415255f7e2a8546559a553e962de7bc60d7942",
  "before": "93671278471332372231927",
  "after": "5023736168813373194",
  "delta": "-93666254735163558858733"
}

The same balance diff shows both pair reserves draining materially:

{
  "token": "0x55d398326f99059ff775485246999027b3197955",
  "holder": "0x5a596eae0010e16ed3b021fc09bbf0b7f1b2d3cd",
  "delta": "-105212230996130493027690"
}
{
  "token": "0x1f415255f7e2a8546559a553e962de7bc60d7942",
  "holder": "0x5a596eae0010e16ed3b021fc09bbf0b7f1b2d3cd",
  "delta": "-81017855558625925684802"
}

5. Adversary Flow Analysis

The adversary cluster consists of EOA 0xdc459596aed13b9a52fb31e20176a7d430be8b94 and helper contract 0x5336a15f27b74f62cc182388c005df419ffb58b8. The EOA deployed the helper in transaction 0xfcf00c61684f0883a8311dd6f1c1a5c7160ef0ad47686a25c21ed709d94bb171, approved an auxiliary token in transaction 0x868c39fedb968ce2bc30224c2cb6b57c01010c6de193c42ce09476738e9eee75, and then sent the exploit transaction.

In the exploit transaction:

  1. The helper borrows public liquidity through DODO flash loans.
  2. The borrowed USDT is routed into a WGPT buy, lowering the pair's live WGPT balance.
  3. The helper performs repeated sells through PancakeRouter instead of one full-balance sell; this keeps the vulnerable sell hook executable while avoiding the router-side revert noted in the reproduction.
  4. Each sell triggers AiWGPTToken's sellburnToken() path, which repeatedly removes protocol LP from the pair.
  5. After repayment, the attacker retains positive USDT while the protocol loses LP and both sides of the pair are depleted.

The validator reproduction on a mainnet fork reaches the same semantic outcome: LP falls from 93671278471332372231927 to 81571368139716878044820, reserves fall to 135984835161388795441113 WGPT and 243788242260001548332149 USDT, and the attacker retains 3623222550076100488813 USDT after repaying the flash loan principal.

6. Impact & Losses

The incident drained protocol-owned LP and reduced both reserves in the public WGPT/USDT pool. The measured losses reported in the root-cause package are:

  • USDT: 105212230996130493027690
  • WGPT: 81017855558625925684802
  • WGPT-USDT-LP: 93666254735163558858733

Operationally, the protocol lost nearly all LP held by the token contract for the victim pair, burned a large amount of WGPT, and ended with materially depleted public market liquidity.

7. References

  • Incident transaction: 0x258e53526e5a48feb1e4beadbf7ee53e07e816681ea297332533371032446bfd
  • Victim token: 0x1f415255f7e2a8546559a553e962de7bc60d7942
  • Victim pair: 0x5a596eae0010e16ed3b021fc09bbf0b7f1b2d3cd
  • Verified AiWGPTToken source used for breakpoint analysis
  • Incident opcode trace used for flash-loan and repeated removeLiquidity evidence
  • Incident balance diff used for reserve and LP depletion confirmation
  • Address history for 0xdc459596aed13b9a52fb31e20176a7d430be8b94 used for adversary clustering