All incidents

DexToken BEP20USDT pool drain from token-logic exploit

Share
Jul 08, 2024 07:33 UTCAttackLoss: 13,038.6 BEP20USDT, 1,000,015,840,000,000 DexTokenManually checked1 exploit txWindow: Atomic
Estimated Impact
13,038.6 BEP20USDT, 1,000,015,840,000,000 DexToken
Label
Attack
Exploit Tx
1
Addresses
2
Attack Window
Atomic
Jul 08, 2024 07:33 UTC → Jul 08, 2024 07:33 UTC

Exploit Transactions

TX 1BSC
0x96a955304fed48a8fbfb1396ec7658e7dc42b7c140298b80ce4206df34f40e8d
Jul 08, 2024 07:33 UTCExplorer

Victim Addresses

0xabc6e5a63689b8542dbdc4b4f39a7e00d4ac30c8BSC
0x35886c6d74aced4ed0fbe0b851806278384d9a76BSC

Loss Breakdown

13,038.6BEP20USDT
1,000,015,840,000,000DexToken

Similar Incidents

Root Cause Analysis

DexToken BEP20USDT pool drain from token-logic exploit

1. Incident Overview TL;DR

On BSC (chainid 56), a single contract-creation transaction 0x96a955304fed48a8fbfb1396ec7658e7dc42b7c140298b80ce4206df34f40e8d from unprivileged EOA 0x56b2d55457b31fb4b78ebddd6718ea2667804a06 deploys an adversary-controlled helper/orchestrator contract. During its constructor execution, this helper abuses a broken DexToken::transferFrom implementation to move essentially the entire DexToken balance held by the DexToken contract itself 0xabc6e5a63689b8542dbdc4b4f39a7e00d4ac30c8 into an adversary swap executor contract 0x0496824589cd3758119f74560e4fa970e6dff104 and into the DexToken‑USDT Pancake pair 0x35886c6d74aced4ed0fbe0b851806278384d9a76.

The swap executor then repeatedly sells the stolen DexToken into the DexToken‑USDT pair via PancakeRouter, draining exactly 13,038.598589899306674112 BEP20USDT from the pool. By the end of the transaction, the attacker EOA holds 7,251.288075182757035057 USDT and has paid 0.017506866 BNB in gas, while two additional addresses receive the remaining drained USDT.

The root cause is a logic bug in DexToken’s transferFrom function (implemented under Solidity ^0.7.6). The function calls _transfer(sender, recipient, amount) without enforcing allowance[sender][msg.sender] >= amount and then applies an unchecked subtraction to _allowances[sender][msg.sender]. Under Solidity 0.7.6 (this code path does not use SafeMath), subtraction underflows silently instead of reverting. As a result, any caller can transfer tokens from any address, including the DexToken contract itself, as long as the source address has sufficient balance.

2. Key Background

DexToken is an Ownable ERC20-style token deployed on BSC at 0xabc6e5a63689b8542dbdc4b4f39a7e00d4ac30c8. The verified source (LW.sol) shows that it implements custom fee-on-transfer behavior, an internal AMM helper _internalSwap, and an lpBurn(uint256) function that interacts with a Pancake-style liquidity pool.

// DexToken (excerpt)
contract DexToken is LinkingTheWorld {
    // ...

    // 转账
    function transfer(
        address recipient,
        uint256 amount
    ) public override returns (bool) {
        _transfer(msg.sender, recipient, amount);
        return true;
    }

    // 转账
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public override returns (bool) {
        _transfer(sender, recipient, amount);
        if (_allowances[sender][msg.sender] != MAX) {
            _allowances[sender][msg.sender] =
                _allowances[sender][msg.sender] -
                amount;
        }
        return true;
    }

    // 内部转账方法
    function _transfer(address from, address to, uint256 amount) private {
        // 黑名单无法买卖和转账
        if (_blackList[from] || _blackList[to]) {
            require(false);
        }
        // 判断余额
        uint256 balance = _balances[from];
        require(balance >= amount, "balanceNotEnough");
        // ... fee and swap logic elided ...
        _tokenTransfer(from, to, amount, takeFee, isSell, isTransfer);
    }
}

Snippet origin: DexToken verified source LW.sol for 0xabc6e5a63689b8542dbdc4b4f39a7e00d4ac30c8.

The DexToken contract stores balances in an internal _balances mapping and allowances in _allowances. The constant MAX is used to represent an “infinite” approval, but there is no explicit check that the current allowance is at least the requested transfer amount before subtraction. Because the contract is compiled with pragma solidity ^0.7.6 and does not wrap the allowance subtraction in SafeMath, a subtraction that would underflow simply wraps modulo 2^256 rather than reverting.

The DexToken‑USDT pool is a standard Pancake (Uniswap V2–style) pair contract at 0x35886c6d74aced4ed0fbe0b851806278384d9a76. Its verified source enforces the usual x*y = k invariant and a 0.25% fee.

interface IPancakePair {
    event Swap(
        address indexed sender,
        uint amount0In,
        uint amount1In,
        uint amount0Out,
        uint amount1Out,
        address indexed to
    );
    event Sync(uint112 reserve0, uint112 reserve1);

    function getReserves() external view returns (
        uint112 reserve0,
        uint112 reserve1,
        uint32 blockTimestampLast
    );

    function swap(
        uint amount0Out,
        uint amount1Out,
        address to,
        bytes calldata data
    ) external;
}

Snippet origin: PancakePair flattened source for 0x35886c6d74aced4ed0fbe0b851806278384d9a76.

BEP20USDT is deployed at 0x55d398326f99059ff775485246999027b3197955. The local verified source confirms it is a standard BEP20 implementation with 18 decimals and standard allowance semantics.

contract BEP20USDT is Context, IBEP20, Ownable {
  using SafeMath for uint256;
  mapping (address => uint256) private _balances;
  mapping (address => mapping (address => uint256)) private _allowances;
  uint256 private _totalSupply;
  uint8 public _decimals;
  string public _symbol;
  string public _name;

  constructor() public {
    _name = "Tether USD";
    _symbol = "USDT";
    _decimals = 18;
    _totalSupply = 30000000000000000000000000;
    _balances[msg.sender] = _totalSupply;
    emit Transfer(address(0), msg.sender, _totalSupply);
  }

  function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {
    _transfer(sender, recipient, amount);
    _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(
      amount, "BEP20: transfer amount exceeds allowance"));
    return true;
  }
}

Snippet origin: BEP20USDT verified source for 0x55d398326f99059ff775485246999027b3197955.

The adversary-related cluster comprises:

  • EOA 0x56b2d55457b31fb4b78ebddd6718ea2667804a06 — the sender of the seed contract-creation transaction, which pays gas and ends the transaction holding 7,251.288075182757035057 USDT.
  • Helper/orchestrator contract 0xfe7E9C76affDBa7b7442adACa9C7c059ec3092FC — deployed by the seed transaction (metadata shows to=null and non-empty runtime bytecode at this address), which orchestrates calls into DexToken and the swap executor.
  • Swap executor contract 0x0496824589cd3758119f74560e4fa970e6dff104 — receives the stolen DexToken and acts as the caller to PancakeRouter in the exploit trace. An eth_getCode query returns non-empty bytecode embedding the DexToken, BEP20USDT, and PancakeRouter addresses.

Normal transaction histories for DexToken, the DexToken‑USDT pool, the attacker EOA, the swap executor, and the two large USDT recipients were fetched and reviewed via Etherscan API. These txlists confirm there are no prior approval transactions from the DexToken contract to either the helper or the swap executor, consistent with the claim that the exploit relies solely on the broken transferFrom logic.

3. Vulnerability Analysis & Root Cause Summary

The core vulnerability is an incorrect implementation of DexToken::transferFrom under Solidity ^0.7.6. Instead of enforcing the standard ERC20 invariant that transferFrom succeeds only when allowance[sender][caller] >= amount (unless an infinite approval is in place), DexToken executes the internal transfer first and only then applies an unchecked subtraction to the allowance. Because arithmetic in Solidity 0.7.6 does not revert on underflow when used directly on uint256, an attacker can call transferFrom with zero approval and have the allowance underflow without error.

The relevant invariant can be stated as:

For all addresses s (source), c (caller), r (recipient) and all amounts a, if transferFrom(s, r, a) executed by caller c returns success, then (1) a <= allowance[s][c] unless allowance[s][c] equals MAX (infinite approval), and (2) a <= balanceOf[s].

DexToken breaks this invariant at the following breakpoint:

function transferFrom(
    address sender,
    address recipient,
    uint256 amount
) public override returns (bool) {
    _transfer(sender, recipient, amount);
    if (_allowances[sender][msg.sender] != MAX) {
        _allowances[sender][msg.sender] =
            _allowances[sender][msg.sender] -
            amount;
    }
    return true;
}

There is no require enforcing allowance[sender][msg.sender] >= amount before subtraction. Underflow wraps silently, and the actual transfer succeeds as long as _balances[sender] >= amount at the time of _transfer.

In the pre-state immediately before the exploit transaction (block 40,287,545), the on-chain artifacts show:

  • DexToken contract balance (self-holding) at 0xabc6e5a63689b8542dbdc4b4f39a7e00d4ac30c8 is 1,000,015,840,000,000,000,000,000,000,000,000 DexToken units.
  • DexToken‑USDT pair at 0x3588...9a76 holds 793,993,204,459,944,103,014,021,705 DexToken and 13,256,433,814,164,156,746,766 BEP20USDT.
  • Attacker EOA 0x56b2...4a06 holds 0 BEP20USDT and 0.997500000000000001 BNB.

These balances are taken directly from the seed transaction metadata and balance diff artifacts.

{
  "erc20_balance_deltas": [
    {
      "token": "0xabc6e5a63689b8542dbdc4b4f39a7e00d4ac30c8",
      "holder": "0xabc6e5a63689b8542dbdc4b4f39a7e00d4ac30c8",
      "before": "115792089237316195423570985008687907853269984665640563583345235133956513969238",
      "after":  "115792089237316195423570985008687907853269983665624723583345235133956513969238",
      "delta":  "-1000015840000000000000000000000000"
    },
    {
      "token": "0xabc6e5a63689b8542dbdc4b4f39a7e00d4ac30c8",
      "holder": "0x0496824589cd3758119f74560e4fa970e6dff104",
      "before": "0",
      "after":  "999952000000000000000000000000000",
      "delta":  "999952000000000000000000000000000"
    },
    {
      "token": "0xabc6e5a63689b8542dbdc4b4f39a7e00d4ac30c8",
      "holder": "0x35886c6d74aced4ed0fbe0b851806278384d9a76",
      "before": "793993204459944103014021705",
      "after":  "48793993204459944103014021705",
      "delta":  "48000000000000000000000000000"
    }
  ]
}

Snippet origin: seed transaction balance_diff.json for 0x96a9...40e8d on BSC.

The first line shows the DexToken contract’s own balance decreasing by 1,000,015,840,000,000,000,000,000,000,000,000 units. The next two lines show that this exact amount is split into 999,952,000,000,000,000,000,000,000,000,000 DexToken transferred to the swap executor 0x0496... and 48,000,000,000,000,000,000,000,000,000 DexToken transferred to the DexToken‑USDT pair.

The seed transaction trace corroborates that this movement is driven by a call to DexToken::transferFrom with sender set to the DexToken contract itself and recipient set to 0x0496...:

│   ├─ [65526] DexToken::transferFrom(DexToken: [0xABC6e5a63689b8542dbDC4b4f39a7e00d4AC30c8], 0x0496824589CD3758119F74560E4Fa970e6dff104, 1000000000000000000000000000000000 [1e33])
│   │   ├─ [151952] DexToken::transferFrom(0x0496824589CD3758119F74560E4Fa970e6dff104, PancakePair: [0x35886C6D74ACed4Ed0fbe0b851806278384D9A76], 800000000000000000000000000 [8e26])
│   │   ├─ [58568] DexToken::transferFrom(0x0496824589CD3758119F74560E4Fa970e6dff104, PancakePair: [0x35886C6D74ACed4Ed0fbe0b851806278384D9A76], 800000000000000000000000000 [8e26])
... (multiple repeated DexToken::transferFrom calls from 0x0496... to the pair)

Snippet origin: seed transaction trace.cast.log for 0x96a9...40e8d on BSC.

Because there is no approval from the DexToken contract to the helper or swap executor in DexToken’s tx history, and because the DexToken contract is not an EOA capable of signing approvals, the only viable explanation for this movement is the broken transferFrom allowance semantics. The adversary calls transferFrom targeting the token contract’s own balance, and the function performs the transfer without checking allowance, then underflows _allowances without reverting.

4. Detailed Root Cause Analysis

This section describes step-by-step how the vulnerability is turned into an exploit in the concrete transaction 0x96a955304fed48a8fbfb1396ec7658e7dc42b7c140298b80ce4206df34f40e8d on BSC.

4.1 Pre-State (σ_B) and Preconditions

Immediately before the seed transaction is included in block 40,287,545, balance_diff.json and the associated metadata establish the following pre-state σ_B:

  • DexToken contract (0xabc6...30c8) holds 1,000,015,840,000,000,000,000,000,000,000,000 DexToken.
  • DexToken‑USDT pair (0x3588...9a76) holds 793,993,204,459,944,103,014,021,705 DexToken and 13,256,433,814,164,156,746,766 BEP20USDT.
  • Attacker EOA (0x56b2...4a06) has 0 BEP20USDT and 0.997500000000000001 BNB.
  • No approvals have been granted from the DexToken contract address to the helper or swap executor addresses, as confirmed by the Etherscan txlist for DexToken.

These conditions are fully determined from on-chain data (RPC metadata and balance diffs). Any unprivileged adversary observing the chain can verify that such a pre-state exists.

4.2 Helper/Orchestrator Deployment

The seed transaction is a legacy (type-0) contract-creation transaction with to=null, value=0, and gas parameters consistent with ordinary BSC usage. Metadata for the transaction shows:

{
  "result": {
    "from":  "0x56b2d55457b31fb4b78ebddd6718ea2667804a06",
    "to":    null,
    "gas":   "0x989680",
    "gasPrice": "0xb2d05e00",
    "value": "0x0",
    "type":  "0x0",
    "chainId": "0x38"
  }
}

Snippet origin: seed transaction metadata.json for 0x96a9...40e8d on BSC.

An eth_getCode call for the resulting contract address 0xfe7E9C76affDBa7b7442adACa9C7c059ec3092FC shows non-empty runtime bytecode. Combined with the fact that this address is not present as a destination in any prior transaction, we can deterministically identify it as the helper/orchestrator deployed by the seed transaction. eth_getCode for the sender 0x56b2...4a06 returns 0x, confirming it is an EOA.

Within the reconstructed trace, the depth-1 frame corresponds to the constructor of 0xfe7E9C76.... That constructor immediately executes a sequence of calls into DexToken and the swap executor, which together realize the exploit.

4.3 Theft of DexToken Contract Balance via Broken transferFrom

The first critical operation is the theft of the DexToken contract’s own balance. The trace clearly shows a call to

DexToken::transferFrom(
  DexToken: [0xABC6e5a63689b8542dbDC4b4f39a7e00d4AC30c8],
  0x0496824589CD3758119F74560E4Fa970e6dff104,
  1000000000000000000000000000000000 [1e33]
)

This call uses sender = 0xabc6...30c8 (the DexToken contract itself), recipient = 0x0496... (the swap executor), and amount = 1e33 = 1,000,000,000,000,000,000,000,000,000,000,000 DexToken. Given the exact pre-state balance of the DexToken contract, this amount corresponds to “essentially all” of the DexToken balance held at the contract address (the slight difference between 1e33 and 1,000,015,840,000,000,000,000,000,000,000,000 is accounted for by additional movements into the DexToken‑USDT pair visible in balance_diff.json).

Because transferFrom in DexToken:

  • Does not check the allowance at all before calling _transfer, and
  • Performs an unchecked subtraction of _allowances[sender][msg.sender] under Solidity 0.7.6,

any caller can execute this function as long as _balances[sender] contains enough tokens. The DexToken contract address holds a large balance in the pre-state, so the helper/orchestrator’s call to transferFrom succeeds with no prior approval.

balance_diff.json confirms the net effect:

  • DexToken balance of the DexToken contract decreases by 1,000,015,840,000,000,000,000,000,000,000,000.
  • The swap executor 0x0496... receives 999,952,000,000,000,000,000,000,000,000,000 DexToken.
  • The DexToken‑USDT pair receives 48,000,000,000,000,000,000,000,000,000 DexToken.

There is no approval from the DexToken contract to either the helper or swap executor in the DexToken txlist. Under standard ERC20 semantics, any such approval would need to be a transaction from the DexToken contract’s address, which cannot exist because the DexToken contract is not an EOA. Therefore, the only mechanism that can explain these movements is the broken transferFrom implementation.

4.4 Repeated AMM Swaps to Extract BEP20USDT

Once the swap executor 0x0496... holds nearly all stolen DexToken, it repeatedly sells DexToken into the DexToken‑USDT pair via PancakeRouter::swapExactTokensForTokensSupportingFeeOnTransferTokens. The trace shows multiple nested calls where 0x0496... is the caller to PancakeRouter, leading to DexToken::transferFrom calls from 0x0496... to the pair and then to PancakePair::swap calls that produce USDT out of the pool.

balance_diff.json for BEP20USDT shows the resulting deltas:

{
  "erc20_balance_deltas": [
    {
      "token": "0x55d398326f99059ff775485246999027b3197955",
      "holder": "0x35886c6d74aced4ed0fbe0b851806278384d9a76",
      "before": "13256433814164156746766",
      "after":  "217835224264850072654",
      "delta":  "-13038598589899306674112"
    },
    {
      "token": "0x55d398326f99059ff775485246999027b3197955",
      "holder": "0x5adcefed6f5cfb2aafccf08ca3bfb388e08dd3ee",
      "delta":  "3446357976293290354724"
    },
    {
      "token": "0x55d398326f99059ff775485246999027b3197955",
      "holder": "0x42d9c8e28db07f94d3aa36b41ab6f37ded8e2caa",
      "delta":  "2340952538423259284331"
    },
    {
      "token": "0x55d398326f99059ff775485246999027b3197955",
      "holder": "0x56b2d55457b31fb4b78ebddd6718ea2667804a06",
      "before": "0",
      "after":  "7251288075182757035057",
      "delta":  "7251288075182757035057"
    }
  ]
}

Snippet origin: BEP20USDT section of seed transaction balance_diff.json for 0x96a9...40e8d on BSC.

The pool loses exactly 13,038.598589899306674112 USDT (-13038598589899306674112 in wei). The three recipient deltas sum exactly to this loss:

  • 3,446.357976293290354724 USDT to 0x5adce...3ee.
  • 2,340.952538423259284331 USDT to 0x42d9...2caa.
  • 7,251.288075182757035057 USDT to attacker EOA 0x56b2...4a06.

This confirms that all USDT drained from the pool is distributed among these three addresses.

4.5 Profit Predicate in BEP20USDT

The adversary-related cluster’s profit is computed using BEP20USDT as the reference asset. Focusing on the minimal adversary cluster {EOA 0x56b2...4a06, helper 0xfe7E..., swap executor 0x0496...}, balance_diff.json shows:

  • Attacker EOA BEP20USDT balance delta: +7,251.288075182757035057 USDT.
  • No BEP20USDT is spent by any adversary-related address in this transaction.
  • Native gas expenditure by the attacker EOA is exactly 0.017506866 BNB (from the native_balance_deltas section).

Because the adversary spends 0 USDT in the transaction and ends with a positive USDT balance, the net change in the adversary’s portfolio value in units of BEP20USDT is strictly positive for any non-negative valuation of BNB in USDT. The ACT profit predicate holds deterministically:

  • value_before_in_reference_asset = 0 USDT.
  • value_after_in_reference_asset = 7,251.288075182757035057 USDT.
  • value_delta_in_reference_asset = 7,251.288075182757035057 USDT.
  • fees_paid_in_reference_asset = 0 USDT (gas is paid in BNB only).

The entire opportunity is realized within this single transaction.

5. Adversary Flow Analysis

This section describes the adversary’s end-to-end execution flow in transaction 0x96a9...40e8d on BSC.

  1. EOA submits contract-creation tx

    • EOA 0x56b2...4a06 submits a type-0 transaction with to=null and input containing the creation bytecode for the helper 0xfe7E....
    • The transaction uses standard gas limits and gas price suitable for inclusion by any BSC validator.
  2. Helper/orchestrator constructor executes

    • At depth 1 of the trace, the constructor of 0xfe7E... begins executing.
    • It issues a call into DexToken 0xabc6...30c8 invoking DexToken::transferFrom with sender = 0xabc6...30c8, recipient = 0x0496..., and amount = 1e33 DexToken.
    • This call succeeds because DexToken’s transferFrom does not enforce an allowance check before transferring.
  3. DexToken contract balance drained to adversary

    • _transfer inside DexToken subtracts the amount from _balances[0xabc6...30c8] and credits it to _balances[0x0496...], while also sending a portion to the DexToken‑USDT pair.
    • _allowances[0xabc6...30c8][helper] is then decreased with an unchecked subtraction, potentially underflowing, but the transaction does not revert.
  4. Swap executor sells DexToken into USDT

    • The swap executor 0x0496... invokes PancakeRouter::swapExactTokensForTokensSupportingFeeOnTransferTokens multiple times, each time sending DexToken to the DexToken‑USDT pair and receiving USDT out.
    • The pair’s swap and Sync events in the trace show reserves updating as DexToken flows in and USDT flows out, consistent with AMM pricing.
  5. USDT distribution to attacker and two additional addresses

    • The router and pair calls result in USDT being transferred from the DexToken‑USDT pool to three addresses:
      • 7,251.288075182757035057 USDT to the attacker EOA 0x56b2...4a06.
      • 3,446.357976293290354724 USDT to 0x5adce...3ee.
      • 2,340.952538423259284331 USDT to 0x42d9...2caa.
    • These transfers are visible in the BEP20USDT portion of balance_diff.json and confirmed by the ERC20 Transfer events in the trace.
  6. Transaction final state

    • The attacker EOA ends the transaction with 7,251.288075182757035057 USDT and 0.979993134 BNB (after paying 0.017506866 BNB in gas).
    • The DexToken‑USDT pool has lost 13,038.598589899306674112 USDT.
    • Nearly all DexToken previously held at the DexToken contract has been moved into the DexToken‑USDT pool and then sold for USDT.

This flow uses only publicly observable contract code (DexToken, PancakePair, BEP20USDT), standard AMM behavior, and the broken transferFrom logic. Any unprivileged EOA could deploy a similar helper contract and realize the same opportunity given the pre-state σ_B.

6. Impact & Losses

The measurable on-chain impact is fully determined from balance_diff.json:

  • The DexToken‑USDT Pancake pair at 0x35886c6d74aced4ed0fbe0b851806278384d9a76 loses exactly 13,038.598589899306674112 BEP20USDT.
  • The DexToken contract’s self-held balance decreases by 1,000,015,840,000,000,000,000,000,000,000,000 DexToken, of which 999,952,000,000,000,000,000,000,000,000,000 are routed to the swap executor and 48,000,000,000,000,000,000,000,000,000 are injected into the DexToken‑USDT pair.
  • The attacker-controlled EOA 0x56b2...4a06 realizes a net gain of 7,251.288075182757035057 BEP20USDT in a single transaction while paying only 0.017506866 BNB in gas.
  • Two additional addresses, 0x5adcefed6f5cfb2aafccf08ca3bfb388e08dd3ee and 0x42d9c8e28db07f94d3aa36b41ab6f37ded8e2caa, receive the remaining drained USDT, but they are not included in the minimal adversary cluster because available on-chain data does not deterministically prove they are controlled by the same adversary.

Liquidity providers in the DexToken‑USDT pool absorb the entire USDT loss. The protocol’s token design (holding a large balance in the token contract itself) amplifies the effect of the broken transferFrom by making a large quantity of DexToken directly stealable without prior user consent.

7. References

Key artifacts used to validate this root cause analysis:

  1. Seed transaction metadata for 0x96a955304fed48a8fbfb1396ec7658e7dc42b7c140298b80ce4206df34f40e8d on BSC — contains raw RPC data (from, to, gas, gasPrice, input, blockNumber, etc.) used to identify the transaction as a contract-creation tx from 0x56b2...4a06 and link it to helper contract 0xfe7E....
  2. Seed transaction balance diff (balance_diff.json) — records precise pre- and post-state balances for BNB, DexToken, and BEP20USDT. Used to quantify the DexToken contract balance theft, the USDT drained from the pool, and the final USDT holdings of the attacker and other recipients.
  3. DexToken verified source (LW.sol) — provides the exact implementation of transferFrom, _transfer, _tokenTransfer, and related functions, demonstrating the missing allowance check and unchecked subtraction under Solidity 0.7.6.
  4. DexToken‑USDT PancakePair flattened source — shows that the liquidity pool is a standard Pancake/Uniswap V2 clone obeying x*y=k with a 0.25% fee and implementing swap/Sync semantics consistent with the observed USDT outflows.
  5. BEP20USDT verified source — confirms standard BEP20 behavior with SafeMath-protected allowances and transfers, ruling out bugs in USDT itself and isolating the vulnerability to DexToken.
  6. Seed transaction trace (trace.cast.log) — provides an annotated call tree showing helper constructor execution, DexToken::transferFrom calls from the DexToken contract to 0x0496..., repeated DexToken::transferFrom calls from 0x0496... to the pair, and the downstream AMM swaps that produce USDT.
  7. Etherscan txlists for DexToken, the DexToken‑USDT pair, the attacker EOA, the swap executor, and the large USDT recipients — used to verify the absence of approvals from the DexToken contract to the adversary cluster and to support the conservative adversary clustering decision.

These artifacts are sufficient for an independent investigator to reproduce the exploit flow, verify the root cause in DexToken’s transferFrom, and confirm the profit and loss figures stated above.