BRAToken self-transfer tax bug inflates pool and drains USDT
Exploit Transactions
Victim Addresses
0x8f4ba1832611f0c364de7114bbff92ba676adf0eBSCLoss Breakdown
Similar Incidents
BBX auto-burn sync flaw drains USDT from BBX pool
39%Public mint flaw drains USDT from c3b1 token pool
39%Marketplace proxy 0x9b3e9b92 bug drains USDT and mints rewards
39%AI IPC destroy-sync mechanism drains IPC-USDT pair USDT reserves
38%Public Mint Drains USDT Pair
37%BNB-chain constructor exploit drains full USDT pool balance
36%Root Cause Analysis
BRAToken self-transfer tax bug inflates pool and drains USDT
1. Incident Overview TL;DR
On BNB Smart Chain (BSC), an adversary-controlled EOA 0x67a909f2953fb1138bea4b60894b51291d2d0795 executed two flashloan-powered transactions that abused a bug in BRAToken’s tax logic to mint BRAToken directly into the BRAToken/USDT PancakeSwap pool. By repeatedly triggering BRAToken self-transfers where the AMM pair is both sender and recipient, the attacker inflated the pool’s BRAToken balance without increasing total supply, then swapped the inflated tokens for USDT and WBNB and repaid the flashloans.
Across the two exploit transactions (0x6759db55… and 0x4e5b2efa…), the BRAToken/USDT pool at 0x8f4ba1832611f0c364de7114bbff92ba676adf0e lost 41,078.140669802606214866 USDT, while the adversary cluster realized a net profit of approximately 820.161066222532481522 BNB-equivalent (WBNB inflows minus gas).
2. Key Background
- BRAToken (BRA) is a BEP20 token on BSC whose buy and sell taxes are configured by a separate
ConfigBRAcontract at0x0ed9cf2aF45916acefCd0390D32fEb5e88E74Ef6. Taxes are applied when transfers involve theuniswapV2Pairaddress and can be directed to configurable recipients. - The BRAToken/USDT AMM pool is a standard PancakeSwap pair contract at
0x8f4ba1832611f0c364de7114bbff92ba676adf0e, pairing BRAToken with BEP20USDT (0x55d398326f99059fF775485246999027B3197955). LPs own a proportional share of the pool’s BRAToken and USDT reserves. - DPPAdvanced is a DODO single-token liquidity pool on BSC; the WBNB pool at
0x0fe261aeE0d1C4DFdDee4102E82Dd425999065F4exposes a publicflashLoanfunction that allows any caller to borrow WBNB as long as the amount plus fee is repaid in the same transaction. - The observed
ConfigBRAconfiguration for the BRAToken/USDT pair is:BuyPer = SellPer = 300(i.e., 3% each, on a 1e4 basis),BuyTaxTo = SellTaxTo =the BRAToken/USDT pair address,isAllow(pair) == falseandisAllowSell(pair) == false.
- The adversary deploys two borrower contracts (
0x1FAe46B3…and0x6066435e…) that orchestrate:- flashLoans from DPPAdvanced,
- WBNB ↔ BNB unwrap/wrap,
- PancakeRouter swaps through WBNB/USDT and BRAToken/USDT pairs,
- BRAToken self-transfer loops via pair-level
swapandskimoperations, - final swaps back to WBNB and profit transfers to the EOA.
3. Vulnerability Analysis & Root Cause Summary
The vulnerability is a logic bug in BRAToken’s internal _transfer function, combined with the ConfigBRA deployment parameters. When both the sender and recipient are the BRAToken/USDT pair and the pair is not allowlisted, the function:
- subtracts
amountfrom the pair’s balance, - calculates a single
taxAmountbased onBuyPerorSellPer, - credits
taxAmounttwice (once toBuyTaxTo, once toSellTaxTo) andfinalAmount = amount - taxAmountback to the recipient, - leaves
_totalSupplyunchanged except in the burn branch (recipient == 0).
Under the incident configuration where BuyTaxTo = SellTaxTo = pair and sender == recipient == pair, the net effect is to increase the pair’s BRAToken balance by taxAmount while preserving _totalSupply, violating basic token accounting invariants. An unprivileged adversary can drive this self-transfer path repeatedly via AMM swap and skim operations to mint BRAToken into the pool and then arbitrage it out for USDT and WBNB.
4. Detailed Root Cause Analysis
4.1 Invariant and Breakpoint
Two core invariants are expected:
- Token supply invariant: The sum of all BRAToken balances must equal
_totalSupplyat all times. - AMM reserve consistency: For the BRAToken/USDT pair, the BRAToken balance held by the pair should only change via:
- explicit debits from senders (incoming transfers),
- LP mint/burn mechanics,
- standard swap fee behavior, and remain consistent with the AMM’s constant-product pricing.
BRAToken’s _transfer implementation (from artifacts/root_cause/seed/56/0x449fea37d339a11efe1b181e5d5462464bba3752/src/Contract.sol) is:
function _transfer(address sender, address recipient, uint amount) internal {
require(sender != address(0), "BEP20: transfer from the zero address");
bool recipientAllow = ConfigBRA(BRA).isAllow(recipient);
bool senderAllowSell = ConfigBRA(BRA).isAllowSell(sender);
uint BuyPer = ConfigBRA(BRA).BuyPer();
uint SellPer = ConfigBRA(BRA).SellPer();
address BuyTaxTo_ = address(0);
address SellTaxTo_ = address(0);
_balances[sender] = _balances[sender].sub(amount, "BEP20: transfer amount exceeds balance");
uint256 finalAmount = amount;
uint256 taxAmount = 0;
if(sender==uniswapV2Pair&&!recipientAllow){
taxAmount = amount.div(10000).mul(BuyPer);
BuyTaxTo_ = ConfigBRA(BRA).BuyTaxTo();
}
if(recipient==uniswapV2Pair&&!senderAllowSell){
taxAmount = amount.div(10000).mul(SellPer);
SellTaxTo_ = ConfigBRA(BRA).SellTaxTo();
}
finalAmount = finalAmount - taxAmount;
if(BuyTaxTo_ != address(0)){
_balances[BuyTaxTo_] = _balances[BuyTaxTo_].add(taxAmount);
emit Transfer(sender, BuyTaxTo_, taxAmount);
}
if(SellTaxTo_ != address(0)){
_balances[SellTaxTo_] = _balances[SellTaxTo_].add(taxAmount);
emit Transfer(sender, SellTaxTo_, taxAmount);
}
_balances[recipient] = _balances[recipient].add(finalAmount);
if (recipient == address(0) ) {
totalBurn = totalBurn.add(amount);
_totalSupply = _totalSupply.sub(amount);
emit Burn(sender, address(0), amount);
}
emit Transfer(sender, recipient, finalAmount);
}
Under the exploit conditions:
sender == recipient == uniswapV2Pair,recipientAllow == falseandsenderAllowSell == false,BuyPer = SellPer = 300,BuyTaxTo = SellTaxTo = uniswapV2Pair.
Execution proceeds as:
_balances[pair] -= amount.- First
ifbranch setstaxAmount = amount * BuyPer / 10000andBuyTaxTo_ = pair. - Second
ifbranch overwritestaxAmount = amount * SellPer / 10000(identical value here) and setsSellTaxTo_ = pair. finalAmount = amount - taxAmount.- Credits:
_balances[BuyTaxTo_] += taxAmount(pair),_balances[SellTaxTo_] += taxAmount(pair again),_balances[recipient] += finalAmount(pair).
_totalSupplyis unchanged becauserecipient != address(0).
The net change to the pair’s balance is:
-amount + finalAmount + taxAmount + taxAmount = -amount + (amount - taxAmount) + 2 * taxAmount = +taxAmount.
Thus every such self-transfer mints taxAmount BRAToken into the pair without a matching burn or debit elsewhere, breaking both the global balance invariant and the AMM reserve consistency.
4.2 ConfigBRA Settings Enabling the Bug
The decompiled ConfigBRA contract (artifacts/root_cause/data_collector/iter_1/contract/56/0x0ed9cf2aF45916acefCd0390D32fEb5e88E74Ef6/decompile/...) exposes:
uint256 public Min;
address public owner;
uint256 public SellPer;
uint256 public BuyPer;
address public SellTaxTo;
address public BuyTaxTo;
function isAllow(address arg0) public view returns (bool);
function isAllowSell(address arg0) public view returns (bool);
From the traces for both exploit transactions, the following values are observed for the BRAToken/USDT pair:
ConfigBRA::isAllow(pair) → 0
ConfigBRA::isAllowSell(pair) → 0
ConfigBRA::BuyPer() → 0x12c (300)
ConfigBRA::SellPer() → 0x12c (300)
ConfigBRA::BuyTaxTo() → pair address
ConfigBRA::SellTaxTo() → pair address
This configuration makes the pair both taxable for buys and sells and routes both tax flows back to the pair itself, exactly satisfying the conditions that trigger minting on self-transfer.
4.3 Trace Evidence of Self-Transfer Minting
The trace.cast.log for the first exploit transaction (0x6759db55…) shows repeated BRAToken self-transfers within PancakePair::skim calls where the pair is both sender and recipient and ConfigBRA returns the above parameters. For example:
BRAToken::transfer(PancakePair: [0x8F4BA1832611f0c364dE7114bbff92ba676AdF0E], 10539350743918941916677)
ConfigBRA::isAllow(pair) → 0
ConfigBRA::isAllowSell(pair) → 0
ConfigBRA::BuyPer() → 0x12c
ConfigBRA::SellPer() → 0x12c
ConfigBRA::BuyTaxTo() → pair
ConfigBRA::SellTaxTo() → pair
emit Transfer(from: pair, to: pair, value: 3.16e20)
emit Transfer(from: pair, to: pair, value: 3.16e20)
emit Transfer(from: pair, to: pair, value: 1.02e22)
The corresponding balance_diff.json for the first transaction shows a large positive BRAToken delta for the pair:
{
"token": "0x449fea37d339a11efe1b181e5d5462464bba3752",
"holder": "0x8f4ba1832611f0c364de7114bbff92ba676adf0e",
"before": "19130492548708862134718",
"after": "217219602167267832539918",
"delta": "198089109618558970405200",
"contract_name": "BRAToken"
}
The second exploit transaction (0x4e5b2efa…) shows the same pattern and an even larger BRAToken increase:
{
"token": "0x449fea37d339a11efe1b181e5d5462464bba3752",
"holder": "0x8f4ba1832611f0c364de7114bbff92ba676adf0e",
"before": "99921194193661924936423",
"after": "1666270349299319784421123",
"delta": "1566349155105657859484700",
"contract_name": "BRAToken"
}
These BRAToken inflows to the pair occur without any corresponding increase in _totalSupply and are driven purely by the self-transfer path described above.
4.4 Conditions for an ACT Opportunity
The exploit is an Anyone-Can-Take (ACT) opportunity under the following publicly verifiable conditions:
- BRAToken is deployed with the
_transferimplementation above and usesConfigBRAforBuyPer,SellPer,BuyTaxTo,SellTaxTo,isAllow, andisAllowSell. ConfigBRAis configured so that:BuyPer > 0andSellPer > 0(300 in the incident),BuyTaxTo = SellTaxTo =the BRAToken/USDT pair address,- the pair address is not allowlisted for buy or sell.
- The BRAToken/USDT pair holds sufficient USDT liquidity such that swapping inflated BRAToken back to USDT yields a positive amount of USDT that can be routed to WBNB and withdrawn as profit.
- The DPPAdvanced WBNB pool (or any equivalent public liquidity source) is available to provide the initial WBNB needed for BRAToken purchases.
- An unprivileged account can deploy and call a borrower contract that sequences:
DPPAdvanced.flashLoan,- swaps through WBNB/USDT and BRAToken/USDT,
- the BRAToken self-transfer loop via
swapandskim, - final swaps back to WBNB,
- flashloan repayment and profit transfer.
All of these conditions are satisfied in the observed incident, and no privileged roles or non-public data are required, so any searcher with comparable infrastructure could have realized the same strategy.
5. Adversary Flow Analysis
5.1 Adversary-Related Cluster
-
EOA (attacker):
0x67a909f2953fb1138bea4b60894b51291d2d0795- Sends both exploit transactions.
- Pays gas (native balance deltas show
0.05702991938BNB and0.05691699671BNB in the two transactions). - Receives the final WBNB profit transfers from borrower contracts after flashloan repayment.
-
Borrower contract 1:
0x1FAe46B350C4A5F5C397Dbf25Ad042D3b9a5cb07- Called by the first exploit transaction
0x6759db55…. - Requests and manages the
1.4e21WBNB flashloan, routes funds through PancakeRouter into BRAToken/USDT, drives the BRAToken self-transfer minting loop, swaps inflated BRAToken back to USDT and WBNB, repays DPPAdvanced, and forwards WBNB profit to the EOA.
- Called by the first exploit transaction
-
Borrower contract 2:
0x6066435edcE9C2772F3F1184b33FC5F7826d03e7- Called by the second exploit transaction
0x4e5b2efa…. - Repeats the pattern with a
1.0e21WBNB flashloan, further inflating the BRAToken/USDT pair and draining additional USDT, then converts back to WBNB, repays the flashloan, and transfers WBNB profit to the EOA.
- Called by the second exploit transaction
These accounts together form the adversary-related cluster that realizes the exploit and captures the profit.
5.2 Stage 1: First FlashLoan and Initial Minting (Tx 0x6759db55…)
- Chain: BSC (chainid 56)
- Block: 24,655,772
- Tx:
0x6759db55a4edec4f6bedb5691fc42cf024be3a1a534ddcc7edd471ef205d4047
Flow:
- The attacker EOA calls borrower contract
0x1FAe46B3…, invoking its flashloan entrypoint. - DPPAdvanced pool
0x0fe261aeE0d1C4DFdDee4102E82Dd425999065F4issues a1.4e21WBNB flashloan to the borrower. - The borrower unwraps WBNB to BNB and routes BNB through PancakeRouter
0x10ED…into:- the WBNB/USDT pair
0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE, then - the BRAToken/USDT pair
0x8f4ba1832611f0c364de7114bbff92ba676adf0e, buying BRAToken.
- the WBNB/USDT pair
- With BRAToken in the pool, the borrower triggers repeated BRAToken transfers where the pair is both sender and recipient, via
PancakePair::swapfollowed by multiplePancakePair::skim(pair)calls. The trace shows the pattern:
PancakePair::skim(pair)
BRAToken::balanceOf(pair)
BRAToken::transfer(pair, 10539350743918941916677)
ConfigBRA::isAllow(pair) → 0
ConfigBRA::isAllowSell(pair) → 0
ConfigBRA::BuyPer() → 0x12c
ConfigBRA::SellPer() → 0x12c
ConfigBRA::BuyTaxTo() → pair
ConfigBRA::SellTaxTo() → pair
emit Transfer(pair → pair, …) (three events per call)
- Each such loop mints additional BRAToken into the pair reserve as described in Section 4, increasing its BRAToken balance by
taxAmountper iteration. - The borrower then swaps a portion of the inflated BRAToken back to USDT and WBNB via PancakeRouter, repays the
1.4e21WBNB flashloan plus fee to DPPAdvanced, and finally transfers675724517850647356854WBNB to the attacker EOA. - Native balance deltas show the attacker EOA spends
57029919380000000wei (0.05702991938BNB) in gas for this transaction.
5.3 Stage 2: Second FlashLoan, Further Minting, and USDT Drain (Tx 0x4e5b2efa…)
- Chain: BSC (chainid 56)
- Block: 24,656,152
- Tx:
0x4e5b2efa90c62f2b62925ebd7c10c953dc73c710ef06695eac3f36fe0f6b9348
Flow:
- The attacker EOA calls borrower contract
0x6066435e…. - DPPAdvanced again lends
1.0e21WBNB viaflashLoan. - The borrower unwraps and routes WBNB through WBNB/USDT and into the BRAToken/USDT pair, acquiring BRAToken and setting up the same self-transfer minting loop.
- The trace shows the same ConfigBRA parameters and BRAToken self-transfers where the pair is both sender and recipient, confirming the same minting behavior.
- The second
balance_diff.jsonquantifies the BRAToken and USDT changes for the BRAToken/USDT pair:
{
"token": "0x449fea37d339a11efe1b181e5d5462464bba3752",
"holder": "0x8f4ba1832611f0c364de7114bbff92ba676adf0e",
"before": "99921194193661924936423",
"after": "1666270349299319784421123",
"delta": "1566349155105657859484700",
"contract_name": "BRAToken"
}
{
"token": "0x55d398326f99059ff775485246999027b3197955",
"holder": "0x8f4ba1832611f0c364de7114bbff92ba676adf0e",
"before": "44233396958720717570246",
"after": "3155256288918111355380",
"delta": "-41078140669802606214866",
"contract_name": "BEP20USDT"
}
{
"token": "0x55d398326f99059ff775485246999027b3197955",
"holder": "0x16b9a82891338f9ba80e2d6970fdda79d1eb0dae",
"before": "63373319853144533241823651",
"after": "63414397993814335848038517",
"delta": "41078140669802606214866",
"contract_name": "BEP20USDT"
}
- Thus, the BRAToken/USDT pair loses
41078140669802606214866units of USDT (≈ 41,078.140669802606214866 USDT), which are transferred to the WBNB/USDT pair. - The borrower swaps USDT from the WBNB/USDT pair back to WBNB, repays the
1.0e21WBNB flashloan plus fee, and sends144550495287975124668WBNB to the attacker EOA. - Native balance deltas show the attacker EOA spends
56916996710000000wei (0.05691699671BNB) in gas for this second transaction.
5.4 Profit Computation
Summing across both transactions:
- WBNB inflows to the attacker EOA from borrower contracts:
675724517850647356854WBNB (tx0x6759db55…),144550495287975124668WBNB (tx0x4e5b2efa…),- Total:
820275013138622481522WBNB.
- Gas cost paid by the attacker EOA (native BNB):
57029919380000000wei (0.05702991938BNB),56916996710000000wei (0.05691699671BNB),- Total:
0.11394691609BNB.
Treating WBNB as BNB-equivalent, the incremental portfolio delta attributable to the exploit sequence is:
820.275013138622481522BNB-equivalent of WBNB inflow,- minus
0.11394691609BNB gas, - yielding a net gain of
820.161066222532481522BNB-equivalent.
This profit is funded entirely by the BRAToken/USDT LP’s USDT reserves that were extracted by swapping inflated BRAToken into USDT.
6. Impact & Losses
- The BRAToken/USDT PancakeSwap pool at
0x8f4ba1832611f0c364de7114bbff92ba676adf0eloses41078140669802606214866units of USDT, equivalent to41,078.140669802606214866USDT given USDT’s 18 decimals on BSC. - The same pool’s BRAToken balance increases sharply (by approximately
1.98e23units in the first transaction and1.57e24units in the second), but this increase is purely the result of the minting bug and is not backed by real value; LPs are left holding a larger quantity of devalued BRAToken and much less USDT. - The adversary-related cluster gains
820275013138622481522WBNB from borrower-to-EOA transfers and spends0.11394691609BNB in gas, for a net BNB-equivalent gain of820.161066222532481522. - BRAToken holders as a whole are indirectly harmed because the exploit reduces the economic backing of BRAToken in the main liquidity pool and undermines trust in the token’s accounting.
7. References
-
Exploit Transactions
- Tx
0x6759db55a4edec4f6bedb5691fc42cf024be3a1a534ddcc7edd471ef205d4047(BSC, block 24,655,772): first DPPAdvanced WBNB flashloan and BRAToken self-transfer minting loop. Artifacts underartifacts/root_cause/seed/56/0x6759db55.../. - Tx
0x4e5b2efa90c62f2b62925ebd7c10c953dc73c710ef06695eac3f36fe0f6b9348(BSC, block 24,656,152): second DPPAdvanced WBNB flashloan, further minting, and USDT drain. Artifacts underartifacts/root_cause/seed/56/0x4e5b2efa.../.
- Tx
-
Victim and Stakeholder Contracts
- BRAToken (BRA) implementation at
0x449fea37d339a11efe1b181e5d5462464bba3752: source inartifacts/root_cause/seed/56/0x449fea37.../src/Contract.sol. - ConfigBRA tax configuration contract at
0x0ed9cf2aF45916acefCd0390D32fEb5e88E74Ef6: decompiled code inartifacts/root_cause/data_collector/iter_1/contract/56/0x0ed9cf2a.../decompile/. - BRAToken/USDT PancakeSwap pair at
0x8f4ba1832611f0c364de7114bbff92ba676adf0e: source inartifacts/root_cause/data_collector/iter_1/contract/56/0x8f4ba183.../source/src/Contract.sol. - DPPAdvanced WBNB pool at
0x0fe261aeE0d1C4DFdDee4102E82Dd425999065F4: source/decompile inartifacts/root_cause/data_collector/iter_1/contract/56/0x0fe261ae.../. - Borrower contract 1 at
0x1FAe46B350C4A5F5C397Dbf25Ad042D3b9a5cb07: source inartifacts/root_cause/data_collector/iter_1/contract/56/0x1FAe46B3.../source/. - Borrower contract 2 at
0x6066435edcE9C2772F3F1184b33FC5F7826d03e7: source/decompile inartifacts/root_cause/data_collector/iter_1/contract/56/0x6066435e.../.
- BRAToken (BRA) implementation at
-
Analysis Artifacts
- Root cause JSON:
root_cause.jsonat the session root. - Analyzer reasoning:
artifacts/root_cause/root_cause_analyzer/iter_2/current_analysis_result.json. - Data collection summary:
artifacts/root_cause/data_collector/data_collection_summary.json.
- Root cause JSON: