Calculated from recorded token losses using historical USD prices at the incident time.
0xea181f730886ece947e255ab508f5af1d0f569fee3368b651d5dbb28549087b50xd46f4a4b57d8ec355fe83f9ae75d4cc04de371edBSC0x11cee747faaf0c0801075253ac28ab503c888888BSCOn BNB Smart Chain block 37854044, transaction 0xea181f730886ece947e255ab508f5af1d0f569fee3368b651d5dbb28549087b5 used a permissionless DODO flash loan, public PancakeSwap liquidity, and the Hackathon token’s broken fee logic to drain the Hackathon/USDT pool at 0xd46f4a4b57d8ec355fe83f9ae75d4cc04de371ed. The adversary EOA 0xf4c11ac8040936da701015e196bc62e645592e6d called attacker helper contract 0x3907f01c1cac4e758b97e89aaa761051021c5b77, borrowed 200000 USDT from 0x6098a5638d8d7e9ed2f952d35b2b67c34ec6b476, manipulated the Hackathon/USDT pair, repaid the principal, and retained 20984150051048998111169 raw USDT units.
The root cause is a token-accounting bug in Hackathon token 0x11cee747faaf0c0801075253ac28ab503c888888. In Hackathon._transfer, the buy-fee branch and sell-fee branch are both executed when pair[sender] and pair[recipient] are simultaneously true. A pair-to-pair transfer therefore credits the pair twice and credits the dead address twice while debiting the sender only once. That violates balance conservation and lets an attacker inflate the pair-side Hackathon balance, then extract USDT from the AMM.
Hackathon is a fee-on-transfer token that treats transfers involving configured pair addresses specially. The token stores pair flags in pair[address], and its function applies fee logic whenever either the sender or recipient is marked as a pair.
_transferThe relevant liquidity venue is the PancakeSwap pair 0xd46f4a4b57d8ec355fe83f9ae75d4cc04de371ed, which holds Hackathon token 0x11cee747faaf0c0801075253ac28ab503c888888 and BSC USDT 0x55d398326f99059ff775485246999027b3197955. PancakeSwap pair accounting depends on post-transfer token balances. If an external token can cause the pair’s token balance to increase without equivalent economic input, the AMM will mis-price subsequent swaps.
The exploit path is fully permissionless. The attacker needed no privileged keys, private orderflow, or protocol-specific admin rights. The only prerequisites were: the pair remained marked true in Hackathon.pair, the pool had enough USDT liquidity to monetize the inflation, and a public flash-loan pool could front the principal.
The vulnerability class is an accounting bug in a fee-on-transfer token integrated with an AMM. Hackathon’s _transfer debits sender once, computes buy, sell, and transfer fees, then branches on whether either side is a configured pair. The logic is not mutually exclusive: when pair[sender] is true, the function applies the buy branch; when pair[recipient] is true, it independently applies the sell branch. If both are true in the same transfer, both branches execute.
That creates a direct invariant break. For a correct transfer, total credits to the recipient plus fee sinks must equal the amount debited from the sender. Here, the sender is debited once, but the pair can be credited twice and the dead address can receive both fee credits. This is the code-level breakpoint that turns a normal pair self-transfer into inflation.
The exploit does not require bypassing AMM logic. Instead, it abuses the pair’s ordinary reliance on observed token balances. Repeated pair-to-pair transfers make the pair appear to hold far more Hackathon than it should, allowing the attacker to sell into the distorted pool and receive USDT that was not economically paid for.
The critical victim code is the Hackathon _transfer implementation:
function _transfer(address sender, address recipient, uint256 amount) internal {
_balances[sender] = _balances[sender].sub(amount, "BEP20: transfer amount exceeds balance");
uint256 buyFeeAmount = amount * _buyFee / 10000;
uint256 sellFeeAmount = amount * _sellFee / 10000;
...
if(pair[sender] || pair[recipient]){
if(pair[sender]){
amount = amount.sub(buyFeeAmount);
_balances[dead] = _balances[dead].add(buyFeeAmount);
emit Transfer(sender, dead, buyFeeAmount);
_balances[recipient] = _balances[recipient].add(amount);
emit Transfer(sender, recipient, amount);
}
if(pair[recipient]){
_balances[dead] = _balances[dead].add(sellFeeAmount);
emit Transfer(sender, dead, sellFeeAmount);
_balances[recipient] = _balances[recipient].add(amount);
emit Transfer(sender, recipient, amount);
}
}
}
When sender == recipient == pair, the first branch reduces amount by the buy fee, credits dead, and credits the pair. The second branch then runs again on the already-mutated amount, credits dead again, and credits the pair again. The sender was only debited once at the top of the function. This is the exact safety break: credited value exceeds debited value.
The runtime evidence matches the code. The collected execution trace for the seed transaction shows repeated pair self-transfer effects and burn-address credits during PancakePair::skim(...) calls:
emit Transfer(from: PancakePair, to: 0x000000000000000000000000000000000000dEaD, value: 0)
emit Transfer(from: PancakePair, to: PancakePair, value: 0)
emit Transfer(from: PancakePair, to: 0x000000000000000000000000000000000000dEaD, value: 0)
emit Transfer(from: PancakePair, to: PancakePair, value: 0)
The end-state balance diffs quantify the inflation and extraction:
{
"attacker_usdt_delta": "20984150051048998111169",
"pair_usdt_delta": "-20984150051048998111169",
"pair_hackathon_delta": "12189858727664847330656225136",
"dead_hackathon_delta": "6284449902637846003155209133"
}
Those deltas are exactly what the broken accounting predicts. The pair-side Hackathon balance balloons, the dead address accumulates additional Hackathon, and equivalent USDT leaves the pool and lands in the attacker helper contract.
The adversary execution is a single-transaction ACT sequence.
0xf4c11ac8040936da701015e196bc62e645592e6d sends transaction 0xea181f730886ece947e255ab508f5af1d0f569fee3368b651d5dbb28549087b5 to helper contract 0x3907f01c1cac4e758b97e89aaa761051021c5b77.0x6098a5638d8d7e9ed2f952d35b2b67c34ec6b476 and borrows 200000000000000000000000 raw USDT units. The trace shows the flashLoan(uint256,uint256,address,bytes) selector and a final repayment transfer back to the lender.0x10ed43c718714eb63d5aa57b78b54704e256024e, transfers Hackathon into the victim pair, and repeatedly calls skim(pair), skim(pair), and skim(attacker). Those skim operations force pair-originated Hackathon transfers where both sender and recipient are recognized as the pair, repeatedly triggering the broken double-credit logic.transferFrom into the pair and a matching Swap that pays USDT out to the attacker helper.200000 USDT flash loan and retains the remainder as profit.Representative trace evidence from the final sell and repayment stage:
emit Transfer(from: 0x3907F01C1CAC4e758b97E89aAA761051021C5b77, to: PancakePair, value: 12208503740333663378688382404)
emit Transfer(from: PancakePair, to: 0x3907F01C1CAC4e758b97E89aAA761051021C5b77, value: 200027047810388856459000)
emit Swap(sender: 0x10ED43C718714eb63d5aA57B78B54704E256024E, amount0In: 12208503740333663378688382404, amount1Out: 200027047810388856459000, to: 0x3907F01C1CAC4e758b97E89aAA761051021C5b77)
emit Transfer(from: 0x3907F01C1CAC4e758b97E89aAA761051021C5b77, to: 0x6098A5638d8D7e9Ed2f952d35B2b67c34EC6B476, value: 200000000000000000000000)
The transaction remained feasible for any unprivileged actor because every component in the path was public and already deployed.
The measurable loss is the USDT removed from the Hackathon/USDT PancakeSwap pool and retained by the attacker helper contract:
USDT209841500510489981111691820984.150051048998111169 USDTThe pool was left with only 38313720130002957557 raw USDT units, or 38.313720130002957557 USDT, and a massively inflated Hackathon reserve of 12210836343937185752421752187. Economically, the pool was drained of nearly all counter-asset liquidity and left in a broken state.
0xea181f730886ece947e255ab508f5af1d0f569fee3368b651d5dbb28549087b5skim, pair self-transfers, final sell, and repayment0x11cee747faaf0c0801075253ac28ab503c888888, especially Hackathon._transfer0xd46f4a4b57d8ec355fe83f9ae75d4cc04de371ed