Calculated from recorded token losses using historical USD prices at the incident time.
0xee6de822159765daf0fd72d71529d7ab026ec2f2BSC0x03b051df794b36e1767cd083fffdebbf573ecda6BSCATOKEN on BSC exposed a permissionless sell path in which sending ATOKEN to the token contract triggered holder-reward distribution and maxHodler liquidation before the seller's tokens were debited. The attacker first used public routing to append its EOA into HolderUser, then used a flash-loan-funded helper to build a dominant ATOKEN balance, repeatedly self-sell to move the holder cursor onto its slot, capture stale-balance USDT payouts, liquidate the remaining ATOKEN, repay the flash loan plus fee, and keep net profit.
The root cause is ordering inside sellToken(address user, uint256 tokenAmount): ATOKEN executes _splitOtherToken2Holder() and _swapAndLiquify(tokenAmount) before super._transfer(user, address(this), tokenAmount). That lets the sell path create fresh USDT reward inventory and distribute it using the seller's pre-debit balance.
ATOKEN overloads a normal ERC20 transfer into a sell when to == address(this). This means a direct token transfer into the ATOKEN contract is not passive custody; it is active sell execution.
Holder rewards are tracked through holderUsdtAmount and holderUsdtOverAmount. Distribution walks the rotating HolderUser list and only processes ten entries per invocation, so repeated sells can advance the cursor toward a target holder slot.
maxHodler is protocol-held ATOKEN inventory. During sell handling, _checkHodlerSell() can move tokens from , swap them into USDT, and credit 65% of the proceeds into , creating new reward inventory inside the same sell path.
maxHodlerholderUsdtAmountThe bug is an accounting-ordering failure in ATOKEN's custom sell branch. In _transfer, a transfer to address(this) dispatches into sellToken and returns early instead of following the normal ERC20 balance update path. Inside sellToken, ATOKEN first calls _splitOtherToken2Ldx(), _splitOtherToken2Holder(), and _swapAndLiquify(tokenAmount). Only after those side effects does it debit the seller with super._transfer(user, address(this), tokenAmount).
That order breaks the intended invariant that holder payouts during a sell must use post-debit balances. _splitOtherToken2HolderUser() computes each payout from super.balanceOf(user) * thisAmount / tokenTotal, so the seller still appears fully funded while rewards are being distributed. In the same path, _checkHodlerSell() can liquidate maxHodler inventory and increase holderUsdtAmount, meaning the contract manufactures fresh USDT rewards and then allocates them against stale attacker balances. The exploit is therefore permissionless whenever public state already contains pending holder rewards, funded maxHodler inventory, and a reachable HolderUser append path.
The relevant victim code is the ATOKEN verified source at 0xee6de822159765daf0fd72d71529d7ab026ec2f2. The self-sell dispatch occurs in _transfer:
if (to == address(this)) {
sellToken(from, amount);
return;
}
Inside sellToken, the critical order is:
function sellToken(address user, uint256 tokenAmount) private {
destroyPoolToken();
_splitOtherToken2Ldx();
_splitOtherToken2Holder();
_swapAndLiquify(tokenAmount);
super._transfer(user, address(this), tokenAmount);
...
}
_swapAndLiquify(tokenAmount) calls _checkHodlerSell(amount.div(10).mul(6)), which can drain protocol-held inventory from maxHodler, swap it to USDT, and add 65% of the incremental USDT into holderUsdtAmount:
super._transfer(address(maxHodler), address(this), amount);
swapTokensForThis(amount);
holderUsdtAmount = holderUsdtAmount.add(newAmount.div(100).mul(65));
The payout routine then uses stale balances:
haveTokenAmount = super.balanceOf(user);
rewardAmount = haveTokenAmount.mul(thisAmount).div(tokenTotal);
USDT.transfer(user, rewardAmount);
Collector evidence shows the public pre-state at block 39648008 already had nonzero pending holder rewards and funded maxHodler inventory. focused_prestate_compare_summary.json records holderUsdtAmount = 40095658986443164093730, holderUsdtOverAmount = 35949451002286799306284, and maxHodlerBalance = 943206460022269449434890139802. The exploit does not need privileged state mutation; it only needs to append the attacker to HolderUser, accumulate a dominant ATOKEN balance, and repeatedly trigger the broken sell path until the payout cursor reaches the attacker's slot.
The adversary sequence is two transactions.
Transaction 0x65d662281aefabbdc8db890428c304a3e54092ef25be1d7b6a2861948c37c35b at block 39648009 is the public append buy. Collector replay details show the attacker EOA 0x65bba34c11add305cb2a1f8a68cecbd6e75089cd routed through the public aggregator 0x1a0a18ac4becddbd6389559687d1a73d8927e416, wrapped native BNB, swapped through WBNB and USDT, then bought from the ATOKEN/USDT pair 0x03b051df794b36e1767cd083fffdebbf573ecda6. The attacker received 38282788356813998029435 ATOKEN net, and HolderUser length increased from 2198 to 2199, placing the attacker at index 2198.
Transaction 0xde59f5bd65e8f48e5b6137a3b4251afbb9b6240d1036fa6f030e21ab6d950aac at block 39651176 is the exploit. The helper contract 0x73ceea4c6571dbcf9bcc9ea77b1d8107b1d46280 borrowed 50000 USDT from the public flash source 0x46cf1cf8c69595804ba91dfdd8d6b960c9b0a7c4, executed repeated router buy and self-sell stages, topped up ATOKEN with 2000 USDT, and funded a late attacker buy that left the attacker with 737720689073292704626701952940 ATOKEN. Repeated helper self-sells advanced the reward cursor onto the attacker's slot while the stale-balance payout path transferred USDT to the attacker; collector traces record transfers of 24787308372575721886167 and 9806467091441486076300 raw USDT units to the attacker EOA during the exploit.
Late-stage corrected action analysis shows the helper then pulled back 368860363678040819750230128540 ATOKEN from the attacker, liquidated 368874814275592746772631067170 ATOKEN, pulled 34593775464017207962467 USDT from the attacker, repaid 50025000000000000000000 raw USDT units to the flash source, and transferred 8758686015190342089379 raw USDT units of profit back to the attacker EOA.
The historical exploit produced 8758686015190342089379 raw USDT units of attacker profit, with USDT using 18 decimals. The ATOKEN/USDT pair lost 45534803152658825989318 raw USDT units during the exploit transaction before downstream redistribution, and the protocol effect was full exhaustion of the tracked maxHodler inventory.
The direct security consequence is that protocol-owned ATOKEN inventory could be converted into attacker-capturable holder payouts inside a single permissionless sell path. Because the exploit uses public routing, public liquidity, public flash liquidity, and public chain state only, it is an ACT-class attack.
0xee6de822159765daf0fd72d71529d7ab026ec2f2.0x65d662281aefabbdc8db890428c304a3e54092ef25be1d7b6a2861948c37c35b.0xde59f5bd65e8f48e5b6137a3b4251afbb9b6240d1036fa6f030e21ab6d950aac.0xde59f5bd65e8f48e5b6137a3b4251afbb9b6240d1036fa6f030e21ab6d950aac.HolderUser tail entries, and funded maxHodler inventory before the append and exploit windows.