Calculated from recorded token losses using historical USD prices at the incident time.
0xc758ab15fd51e713ff8b4184620610a1ac809be06ec374305c32d3b244256a640x34ce28c4a7d4a796bed899f5b3e5d88256a17a77BSC0xbf4707b7f9f53e3aae29bf2558cb373419ef4d45BSC0x2f3f25046ea518d1e524b8fb6147c656d6722cedBSCMetaverseToken misconfigured fee-on-transfer drains USDT from MT/USDT Pancake pair
1 Incident Overview TL;DR
0xc758ab15fd51e713ff8b4184620610a1ac809be06ec374305c32d3b244256a64) drained 36995244786737651151991 USDT from the MetaverseToken/USDT Pancake pair at 0xbf4707b7f9f53e3aae29bf2558cb373419ef4d45.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.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.2 Key Background
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 .0x34ce28c4a7d4a796bed899f5b3e5d88256a17a77transactionFee 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.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._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
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);
}
{
"config": {
"contract_address": "0x2f3f25046ea518d1e524b8fb6147c656d6722ced",
"block_number": 74937080,
"transactFee_value": 5,
"dis_helper": "0x34CE28c4a7D4a796beD899F5B3E5D88256a17A77",
"contractors": {
"0": {
"raw": "[1000, 1200, 2200]\n[0xa0f76967e9F36367c9045AdcfEe0F62D17B4F016, 0x869A387Fa1B10A7A3F6361B89e9D0946a40A4F1A, 0xf8E339c3bCF47417E5b1F8B76bf8d8a4034Ef493]"
}
}
}
}
setType = 0) of amount A when neither from nor to is whitelisted:
transactFeeValue = A * _transactFeeValue / 100 = 0.05A.0.05A * (1000 + 1200 + 2200) / 100 = 0.05A * 44 = 2.2A.realAmount = A - 0.05A = 0.95A.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).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 }
}
}
4 Detailed Root Cause Analysis
0x2f3f25046ea518d1e524b8fb6147c656d6722ced) has _transactFeeValue = 5 and ContractorsFee[0] = [1000, 1200, 2200] for contract-initiated transfers, with contractor addresses configured as decoded above.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.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
0xc758ab15fd51e713ff8b4184620610a1ac809be06ec374305c32d3b244256a64 is a contract-creation transaction from EOA 0xe918...9811 with to = null. The constructor of the deployed helper contract orchestrates the entire attack.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:
swapExactTokensForTokensSupportingFeeOnTransferTokens.Application of misconfigured fee logic
isContract(from) is true, so setType = 0.from nor to is whitelisted according to the helper’s decoded storage.transactionFee does not apply, so the transfer incurs the full fee logic.A MetaverseToken:
from) loses 3.15A tokens in total.to) receives only 0.95A tokens.2.2A tokens are diverted to contractor wallets unaffiliated with the pair or the aggregator.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
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.{
"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
5 Adversary Flow Analysis
High-level strategy summary
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.0x2f3f25046ea518d1e524b8fb6147c656d6722ced.0xbf4707b7f9f53e3aae29bf2558cb373419ef4d45.Call and value flow within the incident transaction
6 Impact & Losses
Quantified USDT loss
0xbf4707b7f9f53e3aae29bf2558cb373419ef4d45 loses 36995244786737651151991 units of BEP20USDT during the incident transaction, as shown by decoded ERC-20 balance diffs against token 0x55d398326f99059ff775485246999027b3197955.0xe918...9811 gains the exact same amount of USDT, moving from 3010341524395885913846 to 40005586311133537065837 USDT.Effect on liquidity providers and token holders
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.7 References
0xc758ab15fd51e713ff8b4184620610a1ac809be06ec374305c32d3b244256a64, including RPC and explorer data, used to identify the chain, block, sender EOA, and transaction type (contract creation).Contract.sol) for 0x2f3f25046ea518d1e524b8fb6147c656d6722ced, used to analyze transactionFee, _transfer, and related configuration logic.metaversetoken_config_decoded.json), showing _transactFeeValue = 5, dis_helper, contractor fee vectors, and _limit mapping entries for key addresses.whitelist_status_decoded.json), confirming that the MT/USDT pair, aggregator, helper contracts, and attacker EOA are all non-whitelisted._reserves_k_decoded.json), establishing the pre- and post-transaction reserve levels and the collapse of k.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.