Public mint flaw drains USDT from c3b1 token pool
Exploit Transactions
0xc29c98da0c14f4ca436d38f8238f8da1c84c4b1ee6480c4b4facc4b81a0134380x2775abd1518de94a27b53260767ea5834c21fcaaff62ed93c618c96712ff72040xb3b5f67e80529b95aff2983932ee72b543ce29b2838d7166760c09ce760044bcVictim Addresses
0xc3b1b45e5784a8efececfc0be2e28247d3f49963BSC0x794ed5e8251c4a8d321ca263d9c0bc8ecf5fa1ffBSCLoss Breakdown
Similar Incidents
Public Mint Drains USDT Pair
46%BBX auto-burn sync flaw drains USDT from BBX pool
43%YziLabs pool accounting flaw drains WBNB liquidity
41%BNB-chain constructor exploit drains full USDT pool balance
41%DexToken BEP20USDT pool drain from token-logic exploit
40%Marketplace proxy 0x9b3e9b92 bug drains USDT and mints rewards
40%Root Cause Analysis
Public mint flaw drains USDT from c3b1 token pool
Incident Overview & TL;DR
On BSC (chainid 56), an adversary-controlled helper contract 0xbfd7280b11466bc717eb0053a78675aed2c2e388 abused a publicly accessible mint function on ERC20 token 0xc3b1b45e5784a8efececfc0be2e28247d3f49963 to create a very large amount of that token. The helper then routed the freshly minted tokens through an AMM pair 0x794ed5e8251c4a8d321ca263d9c0bc8ecf5fa1ff, swapping them for a substantial quantity of a stablecoin-like ERC20 token 0x55d398326f99059ff775485246999027b3197955.
The root cause is a protocol-level bug in token 0xc3b1…: it exposes a publicly callable mint function (selector 0xa7c861da) that directly increases totalSupply and a chosen recipient’s balance without any access control. Any unprivileged caller can therefore mint arbitrary amounts of the token and use AMM liquidity pools to drain external collateral (here, stablecoin 0x55d3…) backing the pool.
The core exploit transaction is 0xc29c98da0c14f4ca436d38f8238f8da1c84c4b1ee6480c4b4facc4b81a013438 in block 4,428,0829, sent by EOA 0xce21c6e4fa557a9041fa98dff59a4401ef0a18ac to the helper contract. A follow-up withdrawal transaction 0xb3b5f67e80529b95aff2983932ee72b543ce29b2838d7166760c09ce760044bc moves the drained stablecoin from the helper back to the EOA.
Key Background
- The AMM pair
0x794ed5e8251c4a8d321ca263d9c0bc8ecf5fa1ffexposes a Uniswap V2–style interface (eventsSync,Transfer; functionsmint,skim,sync,token0,token1,permit). Collected ABI data and the exploit transaction’s receipt and trace confirm that it acts as a liquidity pool between token0xc3b1…and stablecoin0x55d3…. - Router contract
0x10ed43c718714eb63d5aa57b78b54704e256024ebehaves as a Uniswap V2 router: the call trace shows it querying reserves on the pair via selector0x0902f1acand invoking the pair with selector0x022c0d9fas part of the swap path that trades minted0xc3b1…for0x55d3…. - Token
0xc3b1b45e5784a8efececfc0be2e28247d3f49963is ERC20-like, emitting standardTransferandApprovalevents and including LayerZero- and owner-configuration functions. Critically, its function with selector0xa7c861dadirectly increasestotalSupplyand a specified address’s balance without checkingmsg.senderagainst any owner or role, making minting effectively permissionless. - Helper contract
0xbfd7280b11466bc717eb0053a78675aed2c2e388, deployed by EOA0xce21…, exposes functions with selectors0x09b8790aand0xbfbaa190. Decompiled code shows0x09b8790aorchestrating calls to the factory, token0xc3b1…, router0x10ed…, and AMM pair0x794e…to perform the mint-and-swap, while0xbfbaa190reads the helper’s token balance withbalanceOf(0x70a08231) and then calls the token’stransfer(0xa9059cbb) to send the entire balance tomsg.sender. - EOA
0xce21c6e4fa557a9041fa98dff59a4401ef0a18acdeploys helper contract0xbfd7…in tx0x2775abd1518de94a27b53260767ea5834c21fcaaff62ed93c618c96712ff7204, and then sends the exploit tx0xc29c…and withdrawal tx0xb3b5…to it within the same block range. Collected address txlists show this tight coupling between the EOA and its helper. - Seed-analysis of tx
0xc29c98da0c14f4ca436d38f8238f8da1c84c4b1ee6480c4b4facc4b81a013438(block 4,428,0829) shows ERC20 balance changes consistent with minting a large supply of0xc3b1…, sending it to the AMM pair, and receiving0x55d3…into the helper contract. Balance-diff artifacts, the receipt’sTransferlogs, and the call trace all agree on the direction and approximate magnitude of these flows.
Vulnerability & Root Cause Analysis
ACT Opportunity and Pre-State
The ACT opportunity is defined at block height B = 4,428,0828 on BSC (chainid 56), just before the exploit block. In this pre-state:
- ERC20 token
0xc3b1b45e5784a8efececfc0be2e28247d3f49963has been deployed with a public mint function (0xa7c861da) that increasestotalSupplyand an arbitrary recipient’s balance without any access control. - AMM pair contract
0x794ed5e8251c4a8d321ca263d9c0bc8ecf5fa1ffholds a liquidity pool between token0xc3b1…and stablecoin-like token0x55d398326f99059ff775485246999027b3197955. - Router
0x10ed43c718714eb63d5aa57b78b54704e256024eand factory0xca143ce32fe78f1f7019d7d551a6402fc5350c73are deployed and wired to the pair so that swaps between0xc3b1…and0x55d3…are possible. - Helper contract
0xbfd7280b11466bc717eb0053a78675aed2c2e388has already been deployed by EOA0xce21c6e4fa557a9041fa98dff59a4401ef0a18acand configured to target this token/pair/router combination.
In this pre-state, the protocol has already embedded the critical invariant violation: the mint function on 0xc3b1… permits arbitrary third-party minting. No on-chain defense (such as an allowlist, owner-only modifier, or off-chain guardian) prevents an attacker from minting large supplies and dumping them into existing liquidity pools.
Unrestricted Mint Function on Token 0xc3b1…
Decompiled code for token 0xc3b1… shows the problematic mint function corresponding to selector 0xa7c861da:
Decompiled mint function for token 0xc3b1…, showing arbitrary recipient minting with no caller access control (collected contract source, decompiled from on-chain bytecode for 0xc3b1…):
/// @custom:selector 0xa7c861da
/// @custom:signature Unresolved_a7c861da(uint16 arg0, address arg1, uint256 arg2, uint256 arg3) public
function Unresolved_a7c861da(uint16 arg0, address arg1, uint256 arg2, uint256 arg3) public {
require(arg0 == (uint16(arg0)));
require(arg1 == (address(arg1)));
require(!arg3 > 0xffffffffffffffff);
require(!(arg3 > 0xffffffffffffffff), "ERC20: mint to the zero address");
...
require(address(arg1), "ERC20: mint to the zero address");
require(!(totalSupply > ~(arg2)), "ERC20: mint to the zero address");
totalSupply = totalSupply + arg2;
address var_a = address(arg1);
storage_map_b[var_a] = arg2 + storage_map_b[var_a];
emit Transfer(0, address(arg1), arg2);
}
Key observations:
- The function is marked
publicand never checksmsg.senderagainstowner, a minter role, or any other privileged set; it is therefore callable by any address. - It takes an arbitrary
address arg1anduint256 arg2, verifies only thatarg1is not the zero address and that the mint does not overflow, then addsarg2tototalSupplyand toarg1’s balance, emitting aTransfer(0, arg1, arg2)event. - There is no cap on
arg2, no per-address limit, and no linkage to any on-chain collateral or locking mechanism.
This is a textbook unrestricted mint vulnerability: any unprivileged address can mint unbounded token supply to itself or a helper contract.
How the Vulnerability Enables Stablecoin Drain via AMM
Given a liquid AMM pool between the vulnerable token 0xc3b1… and stablecoin 0x55d3…, an attacker can:
- Mint an arbitrarily large amount of
0xc3b1…into an address they control (here, the helper contract). - Approve/spend those tokens into the AMM pair via a router.
- Swap the minted tokens for
0x55d3…, pushing the pool price to near-zero for0xc3b1…while extracting valuable0x55d3…reserves.
The collected call trace for the exploit tx 0xc29c… shows exactly this pattern. A simplified excerpt of the debug_traceTransaction callTracer output illustrates the main calls:
Call trace excerpt for exploit tx 0xc29c…, showing factory query, unrestricted mint on 0xc3b1…, and router-mediated swap into 0x55d3… (callTracer result for tx 0xc29c… on BSC):
{
"to": "0xca143ce32fe78f1f7019d7d551a6402fc5350c73",
"input": "0xe6a43905000000000000000000000000c3b1b45e5784a8efececfc0be2e28247d3f4996300000000000000000000000055d398326f99059ff775485246999027b3197955"
}
{
"to": "0xc3b1b45e5784a8efececfc0be2e28247d3f49963",
"input": "0xa7c861da0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bfd7280b11466bc717eb0053a78675aed2c2e3880000000000000000000000000000000000000000000531e553441b2d50bac038..."
}
{
"to": "0x10ed43c718714eb63d5aa57b78b54704e256024e",
"input": "0x5c11d7950000000000000000000000000000000000000000000531e553441b2d50bac038..."
}
{
"to": "0x55d398326f99059ff775485246999027b3197955",
"input": "0x70a08231000000000000000000000000bfd7280b11466bc717eb0053a78675aed2c2e388"
}
From this trace and the transaction’s receipt/balance diff:
- The helper contract calls the factory to obtain the pair address for
(0xc3b1…, 0x55d3…). - It then calls token
0xc3b1…with selector0xa7c861da, minting a large amount of0xc3b1…to itself (0xbfd7…). - It interacts with the router, which pulls
0xc3b1…from the helper and executes a swap on the pair, after which the helper’s0x55d3…balance is non-zero and the pair’s0x55d3…reserves are decreased.
Evidence from Balance Diffs and Logs
The seed balance-diff analysis for tx 0xc29c… summarizes ERC20 transfer effects:
Seed balance diff for tx 0xc29c…, showing minted 0xc3b1… and drained 0x55d3… (derived from pre/post state diffs and ERC20 Transfer events):
{
"erc20_transfers": [
{
"token": "0xc3b1b45e5784a8efececfc0be2e28247d3f49963",
"from": "0x0000000000000000000000000000000000000000",
"to": "0xbfd7280b11466bc717eb0053a78675aed2c2e388",
"value": "6280255360077122982559800"
},
{
"token": "0xc3b1b45e5784a8efececfc0be2e28247d3f49963",
"from": "0xbfd7280b11466bc717eb0053a78675aed2c2e388",
"to": "0x794ed5e8251c4a8d321ca263d9c0bc8ecf5fa1ff",
"value": "6280255360077122982559800"
},
{
"token": "0x55d398326f99059ff775485246999027b3197955",
"from": "0x794ed5e8251c4a8d321ca263d9c0bc8ecf5fa1ff",
"to": "0xbfd7280b11466bc717eb0053a78675aed2c2e388",
"value": "31538624555626362308555"
}
]
}
These diffs match the receipt’s Transfer logs for:
0xc3b1…mint from0x0to0xbfd7…and subsequent transfer from0xbfd7…to0x794e….0x55d3…transfer from0x794e…(the pair) to0xbfd7…(the helper).
Together with the decompiled mint function, this establishes that the attacker exploited a public, unrestricted mint to inject synthetic 0xc3b1… into the AMM and extract real 0x55d3… collateral.
Root Cause Classification
- Category: Protocol bug in token contract
0xc3b1…. - Scope: The bug resides entirely within the ERC20 token’s mint logic; the AMM, router, and helper logic behave as designed given their inputs.
- Fix direction: Restrict
0xa7c861dato a privileged minter role or remove it; additionally, downstream protocols should gate-list or risk-screen tokens with unrestricted mint capabilities before listing them in collateral-bearing pools.
Adversary Flow Analysis
Stage 1 – Helper Contract Deployment and Configuration
- Transaction:
0x2775abd1518de94a27b53260767ea5834c21fcaaff62ed93c618c96712ff7204(block 4,428,0781). - Actor: EOA
0xce21c6e4fa557a9041fa98dff59a4401ef0a18ac. - Effect: Deploys helper contract
0xbfd7280b11466bc717eb0053a78675aed2c2e388. The constructor bytecode embeds references to the key protocol addresses, including0x55d3…(USDT-like token), factory0xca143c…, router0x10ed…, and AMM-related contracts such as0x4f31…. - Evidence: Collected txlist for the EOA shows this tx as a contract creation that results in
contractAddress = 0xbfd7…, with the input data containing multiple hard-coded addresses matching the factory, router, and token addresses used later in the exploit.
This stage prepares a reusable helper that centralizes exploit logic: minting via the vulnerable token, interacting with the AMM, and later withdrawing stolen tokens.
Stage 2 – Unlimited Mint and AMM Swap Draining Stablecoin into Helper
- Transaction:
0xc29c98da0c14f4ca436d38f8238f8da1c84c4b1ee6480c4b4facc4b81a013438(block 4,428,0829). - Actor: EOA
0xce21…calling helper0xbfd7…. - Mechanism:
mint_and_swap. - High-level effect: The helper mints
6,280,255,360,077,122,982,559,800units of0xc3b1…to itself using the unrestricted mint function0xa7c861da, sends those tokens to the AMM pair0x794e…, and receives31,538,624,555,626,362,308,555units of0x55d3…from the pair, depleting its0x55d3…reserves.
The debug_traceTransaction callTracer and receipt logs together show:
- A staticcall from
0xbfd7…to factory0xca143c…(0xe6a43905) that returns the pair address0x794e…for(0xc3b1…, 0x55d3…). - A call from
0xbfd7…to token0xc3b1…with selector0xa7c861daandarg1 = 0xbfd7…,arg2 = 0x0531e553441b2d50bac038(the minted amount), immediately followed by ERC20Transfer(0x0 → 0xbfd7…, value = 6280255360…9800)andTransfer(0xbfd7… → 0x794e…, value = 6280255360…9800)logs for0xc3b1…. - A call from
0xbfd7…to router0x10ed…, which:- Calls
0xc3b1…with selector0x23b872ddto transfer the minted tokens from0xbfd7…to the pair. - Queries the pair’s reserves via
0x0902f1ac. - Calls the pair with selector
0x022c0d9f, producing aTransfer(0x794e… → 0xbfd7…)log on token0x55d3…and aSwap-like event on the pair.
- Calls
The balance diff quoted earlier shows the net inventory change: the helper’s 0x55d3… balance increases by 31538624555626362308555 units, exactly matching the amount transferred out of the pair.
Stage 3 – Post-Exploit Withdrawal from Helper to Adversary EOA
- Transaction:
0xb3b5f67e80529b95aff2983932ee72b543ce29b2838d7166760c09ce760044bc(block 4,428,0859). - Actor: EOA
0xce21…calling helper0xbfd7…. - Mechanism:
withdraw_call. - High-level effect: The EOA instructs the helper to forward its entire balance of token
0x55d3…to the EOA. This consolidates the stolen stablecoin into the adversary’s externally owned account.
The helper’s decompiled withdraw function (selector 0xbfbaa190) implements a straightforward “withdraw all balance to caller” pattern:
Decompiled withdraw function on helper 0xbfd7…, showing balanceOf(this) followed by transfer(msg.sender, balance) (collected contract source, decompiled from on-chain bytecode for 0xbfd7…):
/// @custom:selector 0xbfbaa190
/// @custom:signature Unresolved_bfbaa190(uint256 arg0, address arg1) public payable
function Unresolved_bfbaa190(uint256 arg0, address arg1) public payable {
require(!arg0 > 0xffffffffffffffff);
require(arg1 == (address(arg1)));
address var_h = address(this);
(bool success, bytes memory ret0) = address(arg1).Unresolved_70a08231(var_h); // staticcall balanceOf(this)
...
address var_j = address(msg.sender);
(bool success, bytes memory ret1) = address(arg1).Unresolved_a9059cbb(var_j); // call transfer(msg.sender, balance)
}
The address-level txlist for 0xbfd7… shows tx 0xb3b5… with:
from = 0xce21…to = 0xbfd7…methodId = 0xbfbaa190- the first argument set to token
0x55d3….
This strongly supports the interpretation that 0xb3b5… is a withdrawal of the helper’s 0x55d3… balance to the adversary’s EOA. However, note the following limitation:
- Limitation: Logs and balance-diff artifacts for tx
0xb3b5…are not present in the collected dataset, so the profit attribution for the adversary relies primarily on the helper’s decompiled code and the fact that the helper already held the stolen0x55d3…balance after tx0xc29c….
Impact & Losses
Quantitative Impact
- The AMM pair
0x794ed5e8251c4a8d321ca263d9c0bc8ecf5fa1ffloses 31,538,624,555,626,362,308,555 units of token0x55d398326f99059ff775485246999027b3197955to the helper contract0xbfd7…during the exploit tx0xc29c…. - The same amount of
0x55d3…becomes controlled by the adversary via helper0xbfd7…and, subsequently, the EOA0xce21…after the withdrawal tx0xb3b5…. - Concurrently, the supply of token
0xc3b1…is inflated by 6,280,255,360,077,122,982,559,800 units, with that supply first held by the helper and then largely residing in the AMM pair as worthless collateral from the perspective of stablecoin liquidity providers.
Decimals for these tokens are not explicitly provided in the artifacts; if 0x55d3… behaves like common 18-decimal ERC20 tokens, the nominal loss would be on the order of 31.5386e6 “whole units”, but this report confines itself to the raw on-chain units recorded in the evidence.
Affected Parties
- Primary victim: Liquidity providers (LPs) or protocol vaults backing the
0xc3b1…/0x55d3…pool on0x794e…, who suffer direct depletion of their0x55d3…collateral. - Secondary risk: Any protocol or strategy that treats
0xc3b1…as having a fixed or capped supply, or that uses it as collateral elsewhere, is exposed to dilution and price collapse due to the unbounded minting capability.
References
- [1] Seed transaction metadata and balance diff for exploit tx
0xc29c98da0c14f4ca436d38f8238f8da1c84c4b1ee6480c4b4facc4b81a013438(includesbalance_diff.jsonand associated metadata summarizing ERC20 transfers and native balance changes). - [2] Execution trace and receipt for seed tx
0xc29c…(debug_traceTransactioncallTracer output andreceipt.json, containing detailed call stack and ERC20Transfer/Swaplogs). - [3] Decompiled and ABI artifacts for token
0xc3b1b45e5784a8efececfc0be2e28247d3f49963(supporting identification of the unrestricted mint function and ERC20 behavior). - [4] Decompiled and ABI artifacts for helper contract
0xbfd7280b11466bc717eb0053a78675aed2c2e388(supporting reconstruction of the mint-and-swap orchestration and withdraw-to-caller pattern). - [5] Address-level txlists around block 4,428,0829 for EOA
0xce21…and helper contract0xbfd7…(supporting lifecycle reconstruction: deployment, exploit invocation, and withdrawal).