Calculated from recorded token losses using historical USD prices at the incident time.
0x8f4ba1832611f0c364de7114bbff92ba676adf0eBSCOn 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).
ConfigBRA contract at 0x0ed9cf2aF45916acefCd0390D32fEb5e88E74Ef6. Taxes are applied when transfers involve the uniswapV2Pair address and can be directed to configurable recipients.0x8f4ba1832611f0c364de7114bbff92ba676adf0e, pairing BRAToken with BEP20USDT (0x55d398326f99059fF775485246999027B3197955). LPs own a proportional share of the pool’s BRAToken and USDT reserves.0x0fe261aeE0d1C4DFdDee4102E82Dd425999065F4 exposes a public flashLoan function that allows any caller to borrow WBNB as long as the amount plus fee is repaid in the same transaction.ConfigBRA configuration 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) == false and isAllowSell(pair) == false.0x1FAe46B3… and 0x6066435e…) that orchestrate:
swap and skim operations,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:
amount from the pair’s balance,taxAmount based on BuyPer or SellPer,taxAmount twice (once to BuyTaxTo, once to SellTaxTo) and finalAmount = amount - taxAmount back to the recipient,_totalSupply unchanged 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.
Two core invariants are expected:
_totalSupply at all times.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 == false and senderAllowSell == false,BuyPer = SellPer = 300,BuyTaxTo = SellTaxTo = uniswapV2Pair.Execution proceeds as:
_balances[pair] -= amount.if branch sets taxAmount = amount * BuyPer / 10000 and BuyTaxTo_ = pair.if branch overwrites taxAmount = amount * SellPer / 10000 (identical value here) and sets SellTaxTo_ = pair.finalAmount = amount - taxAmount._balances[BuyTaxTo_] += taxAmount (pair),_balances[SellTaxTo_] += taxAmount (pair again),_balances[recipient] += finalAmount (pair)._totalSupply is unchanged because recipient != 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.
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.
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.
The exploit is an Anyone-Can-Take (ACT) opportunity under the following publicly verifiable conditions:
_transfer implementation above and uses ConfigBRA for BuyPer, SellPer, BuyTaxTo, SellTaxTo, isAllow, and isAllowSell.ConfigBRA is configured so that:
BuyPer > 0 and SellPer > 0 (300 in the incident),BuyTaxTo = SellTaxTo = the BRAToken/USDT pair address,DPPAdvanced.flashLoan,swap and skim,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.
EOA (attacker): 0x67a909f2953fb1138bea4b60894b51291d2d0795
0.05702991938 BNB and 0.05691699671 BNB in the two transactions).Borrower contract 1: 0x1FAe46B350C4A5F5C397Dbf25Ad042D3b9a5cb07
0x6759db55….1.4e21 WBNB 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.Borrower contract 2: 0x6066435edcE9C2772F3F1184b33FC5F7826d03e7
0x4e5b2efa….1.0e21 WBNB 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.These accounts together form the adversary-related cluster that realizes the exploit and captures the profit.
0x6759db55a4edec4f6bedb5691fc42cf024be3a1a534ddcc7edd471ef205d4047Flow:
0x1FAe46B3…, invoking its flashloan entrypoint.0x0fe261aeE0d1C4DFdDee4102E82Dd425999065F4 issues a 1.4e21 WBNB flashloan to the borrower.0x10ED… into:
0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE, then0x8f4ba1832611f0c364de7114bbff92ba676adf0e, buying BRAToken.PancakePair::swap followed by multiple PancakePair::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)
taxAmount per iteration.1.4e21 WBNB flashloan plus fee to DPPAdvanced, and finally transfers 675724517850647356854 WBNB to the attacker EOA.57029919380000000 wei (0.05702991938 BNB) in gas for this transaction.0x4e5b2efa90c62f2b62925ebd7c10c953dc73c710ef06695eac3f36fe0f6b9348Flow:
0x6066435e….1.0e21 WBNB via flashLoan.balance_diff.json quantifies 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"
}
41078140669802606214866 units of USDT (≈ 41,078.140669802606214866 USDT), which are transferred to the WBNB/USDT pair.1.0e21 WBNB flashloan plus fee, and sends 144550495287975124668 WBNB to the attacker EOA.56916996710000000 wei (0.05691699671 BNB) in gas for this second transaction.Summing across both transactions:
675724517850647356854 WBNB (tx 0x6759db55…),144550495287975124668 WBNB (tx 0x4e5b2efa…),820275013138622481522 WBNB.57029919380000000 wei (0.05702991938 BNB),56916996710000000 wei (0.05691699671 BNB),0.11394691609 BNB.Treating WBNB as BNB-equivalent, the incremental portfolio delta attributable to the exploit sequence is:
820.275013138622481522 BNB-equivalent of WBNB inflow,0.11394691609 BNB gas,820.161066222532481522 BNB-equivalent.This profit is funded entirely by the BRAToken/USDT LP’s USDT reserves that were extracted by swapping inflated BRAToken into USDT.
0x8f4ba1832611f0c364de7114bbff92ba676adf0e loses 41078140669802606214866 units of USDT, equivalent to 41,078.140669802606214866 USDT given USDT’s 18 decimals on BSC.1.98e23 units in the first transaction and 1.57e24 units 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.820275013138622481522 WBNB from borrower-to-EOA transfers and spends 0.11394691609 BNB in gas, for a net BNB-equivalent gain of 820.161066222532481522.Exploit Transactions
0x6759db55a4edec4f6bedb5691fc42cf024be3a1a534ddcc7edd471ef205d4047 (BSC, block 24,655,772): first DPPAdvanced WBNB flashloan and BRAToken self-transfer minting loop. Artifacts under artifacts/root_cause/seed/56/0x6759db55.../.0x4e5b2efa90c62f2b62925ebd7c10c953dc73c710ef06695eac3f36fe0f6b9348 (BSC, block 24,656,152): second DPPAdvanced WBNB flashloan, further minting, and USDT drain. Artifacts under artifacts/root_cause/seed/56/0x4e5b2efa.../.Victim and Stakeholder Contracts
0x449fea37d339a11efe1b181e5d5462464bba3752: source in artifacts/root_cause/seed/56/0x449fea37.../src/Contract.sol.0x0ed9cf2aF45916acefCd0390D32fEb5e88E74Ef6: decompiled code in artifacts/root_cause/data_collector/iter_1/contract/56/0x0ed9cf2a.../decompile/.0x8f4ba1832611f0c364de7114bbff92ba676adf0e: source in artifacts/root_cause/data_collector/iter_1/contract/56/0x8f4ba183.../source/src/Contract.sol.0x0fe261aeE0d1C4DFdDee4102E82Dd425999065F4: source/decompile in artifacts/root_cause/data_collector/iter_1/contract/56/0x0fe261ae.../.0x1FAe46B350C4A5F5C397Dbf25Ad042D3b9a5cb07: source in artifacts/root_cause/data_collector/iter_1/contract/56/0x1FAe46B3.../source/.0x6066435edcE9C2772F3F1184b33FC5F7826d03e7: source/decompile in artifacts/root_cause/data_collector/iter_1/contract/56/0x6066435e.../.Analysis Artifacts
root_cause.json at the session root.artifacts/root_cause/root_cause_analyzer/iter_2/current_analysis_result.json.artifacts/root_cause/data_collector/data_collection_summary.json.