All incidents

MetaverseToken fee misconfiguration drains USDT from Pancake pair

Share
Jan 12, 2026 08:05 UTCAttackLoss: 36,995.24 USDTManually checked1 exploit txWindow: Atomic
Estimated Impact
36,995.24 USDT
Label
Attack
Exploit Tx
1
Addresses
3
Attack Window
Atomic
Jan 12, 2026 08:05 UTC → Jan 12, 2026 08:05 UTC

Exploit Transactions

TX 1BSC
0xc758ab15fd51e713ff8b4184620610a1ac809be06ec374305c32d3b244256a64
Jan 12, 2026 08:05 UTCExplorer

Victim Addresses

0x34ce28c4a7d4a796bed899f5b3e5d88256a17a77BSC
0xbf4707b7f9f53e3aae29bf2558cb373419ef4d45BSC
0x2f3f25046ea518d1e524b8fb6147c656d6722cedBSC

Loss Breakdown

36,995.24USDT

Similar Incidents

Root Cause Analysis

MetaverseToken misconfigured fee-on-transfer drains USDT from MT/USDT Pancake pair

1 Incident Overview TL;DR

  • On BSC block 74937080, a single adversary-crafted contract-creation transaction (0xc758ab15fd51e713ff8b4184620610a1ac809be06ec374305c32d3b244256a64) drained 36995244786737651151991 USDT from the MetaverseToken/USDT Pancake pair at 0xbf4707b7f9f53e3aae29bf2558cb373419ef4d45.
  • The attacker’s EOA 0xe918a1784ceca08e51a1b740f4036fd149339811 ends the transaction with USDT holdings increased from 3010341524395885913846 to 40005586311133537065837, fully funded by the MT/USDT pair while the flash-loaned principal is repaid.
  • The root cause is a misconfigured fee-on-transfer mechanism in MetaverseToken (0x2f3f25046ea518d1e524b8fb6147c656d6722ced): contract-initiated transfers for non-whitelisted addresses debit about 3.15× the nominal transfer amount, while AMM pricing and swap logic assume only the nominal amount moves.
  • Because the misconfiguration is encoded entirely in public on-chain code and storage (fee parameters, contractor arrays, and whitelist mapping) and the attack uses only standard router and pair interfaces, this constitutes an ACT opportunity that any unprivileged adversary could realize starting from the same pre-state.

2 Key Background

  • MetaverseToken extends ERC20 with additional fee and limit logic. Transfers go through an internal transactionFee function that can apply a configurable percentage fee (_transactFeeValue) and redistribute it to contractor wallets based on ContractorsFee arrays, with logic gated on whether the sender or recipient are considered “white-listed” by an external helper contract at 0x34ce28c4a7d4a796bed899f5b3e5d88256a17a77.
  • For non-whitelisted transfers, transactionFee computes a fee amount and then calls super._transfer multiple times to contractor addresses and once to the nominal recipient. The total amount debited from from depends on _transactFeeValue and the entries in ContractorsFee[setType], where setType selects between contract-to-contract, contract-to-EOA, and EOA-to-contract flows.
  • The MT/USDT liquidity pool is a standard PancakeSwap pair on BSC with token0 = MetaverseToken and token1 = BEP20USDT (0x55d398326f99059ff775485246999027b3197955). The attack path uses PancakeRouterV2::swapExactTokensForTokensSupportingFeeOnTransferTokens with path [USDT, MetaverseToken] and then [MetaverseToken, USDT], which assumes that any fee-on-transfer behaviour merely reduces the effective input amount and does not secretly debit more than 100% of the nominal amount from the AMM’s perspective.
  • At block 74937080, decoded storage for MetaverseToken shows _transactFeeValue = 5 and ContractorsFee[0] = [1000, 1200, 2200] for contract-initiated transfers, while the whitelist helper has no whitelist entries for the attacker, the helper contracts, or the MT/USDT pair. This configuration is sufficient to make contract-to-contract transfers debit more than three times the nominal amount without the AMM being aware.

3 Vulnerability Analysis & Root Cause Summary

  • The critical vulnerability lies in how MetaverseToken computes and applies transfer fees for non-whitelisted contract senders. The relevant code from Contract.sol is:
function transactionFee(address from,address to,uint256 amount) internal returns (uint256) {
    if (_msgSender() == address(this)
        || from == address(this)
        || to == address(this)
        || IUniswapV2Pair(_dis).isWhite(from)
        || IUniswapV2Pair(_dis).isWhite(to)
    ) return amount;
    uint setType = 2;
    if (isContract(from)) {
        setType = 0;
    } else if (isContract(to)) {
        setType = 1;
    }
    uint256 realAmount = amount;
    uint256 transactFeeValue = amount.mul(_transactFeeValue).div(100);
    require(balanceOf(from) > amount, "balanceOf is Insufficient");
    require(setType == 0 || balanceOf(from).sub(amount) >= BASE_RATIO.div(10000000), "balanceOf is too small");
    if (transactFeeValue >= 100) {
        realAmount = realAmount.sub(transactFeeValue);
        for (uint256 i = 0; i < ContractorsFee[setType].length; i++) {
            if (ContractorsFee[setType][i] > 0) {
                uint256 value = transactFeeValue.mul(ContractorsFee[setType][i]).div(100);
                super._transfer(from, ContractorsAddress[setType][i], value);
            }
        }
    }
    return realAmount;
}

function _transfer(address from, address to, uint256 amount) internal {
    amount = transactionFee(from, to, amount);
    super._transfer(from, to, amount);
}
  • Decoded configuration at the incident block confirms the parameters that make this function dangerous for contract senders:
{
  "config": {
    "contract_address": "0x2f3f25046ea518d1e524b8fb6147c656d6722ced",
    "block_number": 74937080,
    "transactFee_value": 5,
    "dis_helper": "0x34CE28c4a7D4a796beD899F5B3E5D88256a17A77",
    "contractors": {
      "0": {
        "raw": "[1000, 1200, 2200]\n[0xa0f76967e9F36367c9045AdcfEe0F62D17B4F016, 0x869A387Fa1B10A7A3F6361B89e9D0946a40A4F1A, 0xf8E339c3bCF47417E5b1F8B76bf8d8a4034Ef493]"
      }
    }
  }
}
  • For a contract-initiated transfer (setType = 0) of amount A when neither from nor to is whitelisted:
    • transactFeeValue = A * _transactFeeValue / 100 = 0.05A.
    • Contractor transfers total: 0.05A * (1000 + 1200 + 2200) / 100 = 0.05A * 44 = 2.2A.
    • Recipient receives realAmount = A - 0.05A = 0.95A.
    • The sender loses 2.2A + 0.95A = 3.15A tokens, while external callers (including the AMM) treat the operation as a transfer of only A (subject to standard fee-on-transfer assumptions).
  • The whitelist helper at 0x34ce28c4a7d4a796bed899f5b3e5d88256a17a77 does not exempt the relevant addresses. At block 74937080, its decoded mapping shows:
{
  "contract_address": "0x34ce28c4a7d4a796bed899f5b3e5d88256a17a77",
  "block_number": 74937080,
  "entries": {
    "0xbf4707b7f9f53e3aae29bf2558cb373419ef4d45": { "isWhite_bool": false },
    "0x303bf3805970bb2ebce7a96f342fdced9486729f": { "isWhite_bool": false },
    "0xb64f5d49656fae38655ef2e3c2e3768ddb5f3d5c": { "isWhite_bool": false },
    "0xe918a1784ceca08e51a1b740f4036fd149339811": { "isWhite_bool": false }
  }
}
  • Consequently, both MetaverseToken transfers between the MT/USDT pair and the adversary’s aggregator during the attack are treated as non-whitelisted contract flows and experience the 3.15× over-debit. The AMM, however, updates reserves based on the nominal input amounts implied by the swaps, not the much larger actual token movements, which breaks the constant-product invariant and allows extraction of USDT from the pool.
  • The root cause can therefore be summarized as: a misconfigured fee-on-transfer token with contractor fee coefficients far exceeding 100, combined with non-whitelisting of AMM and aggregator contracts, leading to hidden, multiplicative token debits that invalidate AMM pricing assumptions.

4 Detailed Root Cause Analysis

  • Pre-state σ_B (block 74937080 pre-state)
    • MetaverseToken (0x2f3f25046ea518d1e524b8fb6147c656d6722ced) has _transactFeeValue = 5 and ContractorsFee[0] = [1000, 1200, 2200] for contract-initiated transfers, with contractor addresses configured as decoded above.
    • The whitelist helper (0x34ce28c4a7d4a796bed899f5b3e5d88256a17a77) reports isWhite = false for all key participants: the MT/USDT pair, the aggregator contract, the intermediate helper, and the attacker EOA. No special whitelisting is required or present.
    • The MT/USDT Pancake pair (0xbf4707b7f9f53e3aae29bf2558cb373419ef4d45) holds substantial MetaverseToken reserves, as shown by decoded reserves just before and after the incident transaction:
{
  "contract_address": "0xbf4707b7f9f53e3aae29bf2558cb373419ef4d45",
  "txhash": "0xc758ab15fd51e713ff8b4184620610a1ac809be06ec374305c32d3b244256a64",
  "reserves_before": {
    "reserve0": 25914029372088303000000,
    "reserve1": 2,
    "k": 51828058744176605000000
  },
  "reserves_after": {
    "reserve0": 564878464577161130000,
    "reserve1": 5,
    "k": 2824392322885805400000
  },
  "reserves_deltas": {
    "delta_reserve0": -25349150907511140000000,
    "delta_reserve1": 3,
    "delta_k": -49003666421290805000000
  }
}
  • token0 is MetaverseToken and token1 is BEP20USDT, so reserve0 tracks MetaverseToken and reserve1 tracks USDT. The enormous drop in reserve0 and only small increase in reserve1 across a single transaction indicates that the AMM invariant k collapses, consistent with hidden over-debits of MetaverseToken not accounted for by the pricing formula.

  • Adversary transaction and two-swap structure

    • The seed transaction 0xc758ab15fd51e713ff8b4184620610a1ac809be06ec374305c32d3b244256a64 is a contract-creation transaction from EOA 0xe918...9811 with to = null. The constructor of the deployed helper contract orchestrates the entire attack.
    • From the opcode-level trace, the helper obtains a USDT flash loan via a Moolah-style lending contract, then directs USDT into an aggregator contract 0x303bf3805970bb2ebce7a96f342fdced9486729f, which interacts with PancakeRouterV2 and the MT/USDT pair. The trace shows calls of the form:
PancakeRouterV2_...::swapExactTokensForTokensSupportingFeeOnTransferTokens(
  594572298978549731565,
  0,
  [0x2f3f25046Ea518d1E524B8fB6147c656D6722CeD, 0x55d398326f99059fF775485246999027B3197955],
  0x303Bf3805970Bb2ebcE7A96f342FDCEd9486729F,
  1768205155
)
  • The transaction encompasses:

    • A first swap where the MT/USDT pair sends MetaverseToken to the aggregator in exchange for USDT (USDT→MT path).
    • A second swap where the aggregator sends MetaverseToken back to the pair in exchange for USDT (MT→USDT path), again via swapExactTokensForTokensSupportingFeeOnTransferTokens.
  • Application of misconfigured fee logic

    • In both directions, the transfers of MetaverseToken between the pair and aggregator are contract-to-contract transfers involving non-whitelisted addresses. For such transfers:
      • isContract(from) is true, so setType = 0.
      • Neither from nor to is whitelisted according to the helper’s decoded storage.
      • The early-return condition in transactionFee does not apply, so the transfer incurs the full fee logic.
    • For a nominal transfer of A MetaverseToken:
      • The pair or aggregator (as from) loses 3.15A tokens in total.
      • The counterparty (to) receives only 0.95A tokens.
      • 2.2A tokens are diverted to contractor wallets unaffiliated with the pair or the aggregator.
    • The Pancake pair and router, however, continue to apply pricing based on the nominal A values and their view of reserves, unaware that the pair’s MetaverseToken balance is actually being reduced by 3.15A on these interactions.
  • Resulting invariant break and USDT drain

    • The MT/USDT pair’s pre- and post-transaction reserves show that reserve0 (MetaverseToken) drops by approximately 2.53e22 while reserve1 (USDT) increases by only 3 units, and k falls from 5.1828e22 to 2.8244e21. This is incompatible with constant-product behaviour under any standard swap, but exactly what is expected if the pair’s MetaverseToken debits are multiplied by 3.15× while swap math assumes a much smaller input.
    • The extended BEP20USDT balance diff quantifies the value movement:
{
  "erc20_balance_deltas": [
    {
      "token": "0x55d398326f99059ff775485246999027b3197955",
      "holder": "0xbf4707b7f9f53e3aae29bf2558cb373419ef4d45",
      "before": "37009240317278254683142",
      "after": "13995530540603531151",
      "delta": "-36995244786737651151991",
      "contract_name": "BEP20USDT"
    },
    {
      "token": "0x55d398326f99059ff775485246999027b3197955",
      "holder": "0xe918a1784ceca08e51a1b740f4036fd149339811",
      "before": "3010341524395885913846",
      "after": "40005586311133537065837",
      "delta": "36995244786737651151991",
      "contract_name": "BEP20USDT"
    }
  ]
}
  • The pair loses exactly 36995244786737651151991 USDT, and the attacker EOA gains the same amount. These deltas match the success_predicate and value_flow_overview in the analysis and demonstrate that the misconfigured fee-on-transfer logic is fully sufficient to explain the observed drain without any additional hidden mechanisms.

  • ACT opportunity properties

    • All ingredients of the exploit are encoded in public on-chain artifacts: MetaverseToken source code and storage, the whitelist helper source and storage, standard Pancake router and pair contracts, and the observable incident transaction.
    • The attack transaction is a single contract-creation call from an unprivileged EOA that interacts only with public functions on the flash-loan provider, router, pair, token, and helper. There is no reliance on privileged roles, whitelist entries, or off-chain coordination.
    • Any unprivileged adversary, given the same pre-state σ_B, could deploy an equivalent helper/aggregator pair and submit an analogous transaction to realize the same USDT drain, subject only to gas and capital/flash-loan availability.

5 Adversary Flow Analysis

  • High-level strategy summary

    • The adversary EOA 0xe918...9811 deploys a helper contract whose constructor takes a USDT flash loan, routes the loaned USDT into an aggregator contract, performs two MetaverseToken swaps against the MT/USDT pair that are both subject to the misconfigured fee-on-transfer logic, repays the flash loan, and sends the net USDT profit back to the EOA.
  • Key adversary-related accounts and roles

    • 0xe918a1784ceca08e51a1b740f4036fd149339811 – EOA originator of the incident transaction and final recipient of USDT profit (+36995244786737651151991 USDT).
    • 0x29766568a9c3358a6d94511effa5430dda017cf1 – Helper contract deployed in the seed transaction; its constructor orchestrates the flash loan and attack sequence.
    • 0xb64f5d49656fae38655ef2e3c2e3768ddb5f3d5c – Intermediate helper used to initiate the flash loan and deploy the aggregator contract.
    • 0x303bf3805970bb2ebce7a96f342fdced9486729f – Aggregator contract that interacts directly with PancakeRouterV2, MetaverseToken, and the MT/USDT pair to execute the two critical swaps.
    • Victim contracts:
      • MetaverseToken: 0x2f3f25046ea518d1e524b8fb6147c656d6722ced.
      • MT/USDT pair: 0xbf4707b7f9f53e3aae29bf2558cb373419ef4d45.
  • Call and value flow within the incident transaction

    • The EOA sends the contract-creation transaction; the helper’s constructor executes:
      • Calls into a lending/flash-loan provider to obtain a large amount of BEP20USDT.
      • Transfers USDT to the aggregator and sets necessary approvals for the router.
      • Aggregator calls PancakeRouterV2 to perform:
        1. USDT→MetaverseToken swap, receiving MetaverseToken from the MT/USDT pair.
        2. MetaverseToken→USDT swap, sending MetaverseToken back to the pair and receiving a larger quantity of USDT.
      • Helper and aggregator contracts repay the flash loan from the USDT returned by the second swap.
      • Residual USDT is transferred to the attacker EOA.
    • Throughout these steps, MetaverseToken transfers between the pair and aggregator are contract-to-contract and non-whitelisted, so each transfer experiences the 3.15× over-debit at the token level, but router and pair math operate on nominal amounts. This mismatch is what allows the second swap to pull more USDT from the pair than a constant-product AMM would normally allow.
    • The final extended balance diff confirms that all net USDT leaving the MT/USDT pair is accounted for by the increase in the attacker EOA’s USDT balance, with flash-loaned principal fully repaid.

6 Impact & Losses

  • Quantified USDT loss

    • The MT/USDT pair at 0xbf4707b7f9f53e3aae29bf2558cb373419ef4d45 loses 36995244786737651151991 units of BEP20USDT during the incident transaction, as shown by decoded ERC-20 balance diffs against token 0x55d398326f99059ff775485246999027b3197955.
    • The attacker EOA 0xe918...9811 gains the exact same amount of USDT, moving from 3010341524395885913846 to 40005586311133537065837 USDT.
  • Effect on liquidity providers and token holders

    • The MT/USDT pair’s reserves become severely imbalanced: MetaverseToken reserves drop by roughly 2.53e22 units while USDT reserves drop sharply relative to the pre-incident state, and the invariant k collapses by almost an order of magnitude.
    • Liquidity providers in the MT/USDT pool suffer a permanent loss of USDT liquidity and are left with a pool dominated by MetaverseToken, whose value is no longer supported by a healthy USDT backing.
    • MetaverseToken holders are indirectly harmed because the token’s liquidity and price stability on PancakeSwap are severely degraded by the drain.

7 References

  • [1] Seed transaction metadata for 0xc758ab15fd51e713ff8b4184620610a1ac809be06ec374305c32d3b244256a64, including RPC and explorer data, used to identify the chain, block, sender EOA, and transaction type (contract creation).
  • [2] Verified MetaverseToken source code (Contract.sol) for 0x2f3f25046ea518d1e524b8fb6147c656d6722ced, used to analyze transactionFee, _transfer, and related configuration logic.
  • [3] Decoded MetaverseToken configuration at block 74937080 (metaversetoken_config_decoded.json), showing _transactFeeValue = 5, dis_helper, contractor fee vectors, and _limit mapping entries for key addresses.
  • [4] Decoded whitelist helper storage at block 74937080 (whitelist_status_decoded.json), confirming that the MT/USDT pair, aggregator, helper contracts, and attacker EOA are all non-whitelisted.
  • [5] Decoded MT/USDT pair reserves and invariant around the incident (_reserves_k_decoded.json), establishing the pre- and post-transaction reserve levels and the collapse of k.
  • [6] Extended BEP20USDT balance diffs (balance_diff.extended.json) for the incident transaction, quantifying the exact USDT loss from the pair and gain to the attacker EOA and supporting the profit-based success predicate.