Calculated from recorded token losses using historical USD prices at the incident time.
0x309523343cc1bb9d28b960ebf83175fac941b4a590830caccff44263d9a80ff00x8a43eb772416f934de3df8f9af627359632cb53fBSC0x24721ec014e19ea0e3c965aee2b138cf4b72e941BSCThe incident is a single-transaction BSC exploit against FreeDom at 0x8a43eb772416f934de3df8f9af627359632cb53f. The adversary used a public DODO flash loan for 500 WBNB, bought FreeDom, invoked a public helper contract buy that spent the helper's own BNB, then sold FreeDom back into the FreeDom/WBNB Pancake pair at 0x24721ec014e19ea0e3c965aee2b138cf4b72e941. The decisive flaw is that FreeDom's sell-fee path charges the pair instead of the seller. That mis-accounting let the transaction at 0x309523343cc1bb9d28b960ebf83175fac941b4a590830caccff44263d9a80ff0 extract 60.332909139618131633 WBNB before gas and 60.331339221618131633 BNB net after gas.
FreeDom is a fee-on-transfer token that treats its AMM pair as a special address. For pair-related transfers, _beforeTokenTransfer computes whether the transfer is a buy or sell and calls trigger(t) before the primary token movement. After the token movement, _afterTokenTransfer always calls takeFee(from, to, amount). That architecture matters because the sell path runs custom logic after the seller has already transferred tokens into the pair, so any fee transfer that pulls value from the pair directly alters the AMM reserve state in a way the seller did not pay for.
The incident relies only on public components. The flash-loan liquidity in DODO is public, the Pancake router and pair are public, and the helper contract at 0xae3ada8787245977832c6dab2d4474d3943527ab is publicly callable. The verified pair identity is now established by public Etherscan V2 source metadata plus on-chain , , and bindings.
factory()token0()token1()The vulnerability class is an accounting bug in sell-fee handling. FreeDom correctly determines when a transfer is a sell by checking ispair[to], but its fee collection step then debits the recipient pair rather than the seller. That violates the core invariant for AMM sells: the seller should bear the fee, and the pair's token balance should only reflect what the seller actually contributes net of fees. Because the pair is charged an extra token transfer after receiving the seller's tokens, the pair's reserves drift in a direction favorable to the seller. In the exploit transaction, that drift combines with a public helper buy that supports the price before the final sell. The result is that PancakePair pays out too much WBNB for the effective token sacrifice made by the attacker. The bug is deterministic and sits at a concrete code breakpoint in FreeDom::takeFee.
The relevant FreeDom code is below. The breakpoint is the final super._transfer(to, address(this), feeAmount), which executes when ispair[to] is true:
function _beforeTokenTransfer(address from,address to,uint amount) internal override trading{
if(getStatus(from,to)){
revert InStatusError(from);
}
if(!ispair[from] && !ispair[to] || amount==0) return;
uint t=ispair[from]?1:ispair[to]?2:0;
trigger(t);
}
function _afterTokenTransfer(address from,address to,uint amount) internal override trading{
if(address(0)==from || address(0)==to) return;
takeFee(from,to,amount);
if(_num>0) multiSend(_num);
}
function takeFee(address from,address to,uint amount)internal {
uint fee=ispair[from]?fees.buy:ispair[to]?fees.sell:fees.transfer;
if(block.timestamp<openingTime+killTime){
fee=fee.mul(10);
}
uint feeAmount= amount.mul(fee).div(fees.total);
if(exFees[from]==4 || exFees[to]==4 ) feeAmount=0;
if(ispair[to] && IERC20(to).totalSupply()==0) feeAmount=0;
if(feeAmount>0){
super._transfer(to,address(this),feeAmount);
}
}
This means a sell into the pair is processed in two steps. First, the seller transfers FreeDom into the pair. Second, takeFee transfers additional FreeDom from the pair into the token contract. The pair therefore loses tokens that did not come from the seller's net contribution. That is the broken invariant: for a sell of amount, the pair's token-side accounting must not be reduced by a second transfer from the pair itself.
The exploit trace shows the mechanism directly. During the helper step, the adversary invokes 0xAE3ADa8787245977832c6DaB2d4474D3943527Ab::buyToken(120000000000000000000, 5000000000000000000), and the nested router call spends the helper's own balance through swapExactETHForTokens{value: 120000000000000000000}. Later, on the final sell, the trace records the extra pair-to-token fee transfer and the inflated WBNB payout:
0xAE3ADa8787245977832c6DaB2d4474D3943527Ab::buyToken(120000000000000000000, 5000000000000000000)
0x10ED43C718714eb63d5aA57B78B54704E256024E::swapExactETHForTokens{value: 120000000000000000000}(...)
emit Transfer(from: PancakePair: [0x24721eC014e19eA0e3c965AeE2b138cf4b72e941], to: FreeDom: [0x8A43Eb772416f934DE3DF8F9Af627359632CB53F], value: 516652263134390780740)
PancakePair::swap(0, 560332909139618131633, 0x4512ABB79f1F80830F4641cAefC5Ab33654A2d49, 0x)
WBNB::transfer(0x835B45D38cbDccf99E609436FF38E31Ac05bc502, 60332909139618131633)
The balance diff corroborates the economic outcome. The sender EOA at 0x835b45d38cbdccf99e609436ff38e31ac05bc502 paid 1569918000000000 wei in gas, while the exploit contract delivered 60332909139618131633 WBNB to the sender. The pair's FreeDom balance fell by 1746006725159603596364, and the FreeDom token contract's own balance increased by 860496750644275813098, consistent with fee extraction from the pair during the exploit path.
The attack is a single adversary-crafted transaction on BSC block 35123711. The sender EOA is 0x835b45d38cbdccf99e609436ff38e31ac05bc502, and the direct target contract is 0x4512abb79f1f80830f4641caefc5ab33654a2d49.
First, the exploit contract borrows 500 WBNB from the public DODO pool at 0x6098a5638d8d7e9ed2f952d35b2b67c34ec6b476 via flashLoan(500000000000000000000, 0, ...). Second, it swaps that inventory for FreeDom using the public Pancake router at 0x10ED43C718714eb63d5aA57B78B54704E256024E. Third, it invokes the public helper contract 0xae3ada8787245977832c6dab2d4474d3943527ab::buyToken(120 ether, 5 ether). The outer helper call carries no value, but the nested router call spends 120 BNB from the helper's own balance to buy FreeDom and support the price ahead of the final sell.
Fourth, the exploit contract sells 10,333.045262687815614808 FreeDom back into the Pancake pair. At that point FreeDom's fee logic selects the sell fee and then pulls 516.652263134390780740 FreeDom from the pair into the token contract. Fifth, PancakePair pays out 560.332909139618131633 WBNB to the exploit contract. Sixth, the exploit contract repays the 500 WBNB flash-loan principal and forwards the remainder, 60.332909139618131633 WBNB, to the sender EOA. No privileged keys, governance authority, or private order flow are required at any stage.
The measurable loss in the validated incident is 60.332909139618131633 WBNB, represented in smallest units as "60332909139618131633" with 18 decimals. The immediate victimized market component is the FreeDom/WBNB Pancake pair at 0x24721ec014e19ea0e3c965aee2b138cf4b72e941, which is drained because its token-side accounting is reduced by the token contract's own fee transfer during sells. The broader protocol impact is that any sufficiently funded adversary can combine public liquidity and the broken sell path to extract WBNB from the pair in a permissionless manner.
0x309523343cc1bb9d28b960ebf83175fac941b4a590830caccff44263d9a80ff0 on BSC block 35123711.0x8a43eb772416f934de3df8f9af627359632cb53f, especially _beforeTokenTransfer, _afterTokenTransfer, and takeFee.516652263134390780740, WBNB payout of 560332909139618131633, and final sender transfer of 60332909139618131633.1569918000000000 wei and the resulting asset deltas.0x24721ec014e19ea0e3c965aee2b138cf4b72e941, plus on-chain pair identity calls confirming Pancake factory and token0=FreeDom, token1=WBNB.