Calculated from recorded token losses using historical USD prices at the incident time.
0xfb57c980286ea8755a7b69de5a74483c44b1f74af4ab34b7c52e733fc62dfca60xb32979f3a5b426a4a6ae920f2b391d885abf4c18BSC0x037e6eb26275dbfe3a5244239bbe973f1a56b449BSC0x139bd2ecfde76f5311d7beeb2e05cba6fede26d6BSCOn BNB Chain block 85743227, an unprivileged attacker used a public WBNB flashloan, Pancake router/pair flows, and the public distributeDailyRewards() entrypoint to manipulate the MT/WBNB Pancake pool and drain WBNB. The attacker first accumulated MT even though Movie Token was still in its buy-restricted deflation phase, then sold MT in a way that queued a large pendingBurnAmount, triggered the queued burn directly against the pair reserve, and finally dumped MT into the now one-sidedly distorted pool. The transaction repaid the flashloan principal, paid no WBNB flashloan fee, incurred only 0.000053513831203784 BNB in gas, and finished by transferring 381.746819805990283197 WBNB to attacker EOA 0xdb0901a3254f47c0ce57fffce2c730bc33a1c0e1.
The root cause is a token-level AMM invariant violation in Movie Token. During sells, MT both transfers the post-tax MT into the pair and separately records that same net amount into pendingBurnAmount. Later, the public LP-mining path calls extractFromPoolForLpMining(), which executes _executePendingBurn() and burns pair-side MT directly from the pool before calling sync(). That removes MT reserve without removing WBNB, inflates the quoted MT price, and gives any unprivileged actor who can source MT inventory a deterministic dump opportunity.
Movie Token (0xb32979f3a5b426a4a6ae920f2b391d885abf4c18) is an 18-decimal BEP-20 with a deflation model centered on its Pancake pair . While , direct buys are supposed to be blocked, sells incur a 10% tax, and the pair-side token balance is allowed to fall only until the hardcoded floor . The LP-mining contract at is the only address authorized to call . Auditor verification also confirmed that this LP-mining contract is not source-verified on BscScan, so the validator relies on trace-observed behavior rather than source for that component.
0x037e6eb26275dbfe3a5244239bbe973f1a56b449deflationStopped == falseMIN_LP_SUPPLY = 21,000e180x139bd2ecfde76f5311d7beeb2e05cba6fede26d6extractFromPoolForLpMining()The relevant AMM is a standard Pancake pair. Its swap() function enforces the constant-product check using the live token balances, and sync() force-updates reserves to match current balances. That matters because any token contract that can mutate the pair's token balances out-of-band can also change the pair's quoted price once sync() runs.
Pancake pair reserve accounting:
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
...
uint balance0Adjusted = (balance0.mul(10000).sub(amount0In.mul(25)));
uint balance1Adjusted = (balance1.mul(10000).sub(amount1In.mul(25)));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(10000**2), "Pancake: K");
_update(balance0, balance1, _reserve0, _reserve1);
}
function sync() external lock {
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}
Movie Token also contains a special router-mediated removal branch:
if (sender == uniswapV2Pair && msg.sender == address(uniswapV2Router) && !isRm && !isAdd) {
...
if (transferAmount > 0) {
super._transfer(sender, recipient, transferAmount);
}
return;
}
That branch is important because the incident trace shows router-held MT being released to the attacker during router-mediated liquidity-removal flows even while the contract is still in the "buying not allowed" phase.
This is an ATTACK-class incident caused by token logic that mutates AMM reserves one-sidedly. The violated invariant is straightforward: Movie Token must not reduce the pair's MT balance unless the corresponding pool accounting removes proportional WBNB or LP claims, because otherwise the AMM price becomes artificially inflated. Movie Token breaks that invariant in two steps. First, on a sell, it records the seller's post-tax net amount in pendingBurnAmount while still transferring that same net amount into the pair, so the pair balance and the future burn obligation both increase from the same flow. Second, when LP mining later calls extractFromPoolForLpMining(), _executePendingBurn() subtracts MT directly from the pair balance, credits 0xdead, and immediately calls sync(). The pair now reports drastically less MT but the same WBNB, so the attacker can sell MT back into a pool whose quoted price has been pushed far above any legitimate level. The exploit remains ACT because all required capabilities are public: the flashloan vault, Pancake router/pair methods, and lpMining.distributeDailyRewards() are all permissionless.
Movie Token sell path:
if (isSell && !deflationStopped && !shouldCancelTax) {
if (sender != uniswapV2Pair && recipient == uniswapV2Pair) {
uint256 poolFee = (amount * POOL_BACK_RATE) / FEE_DENOMINATOR;
uint256 ecoFee = (amount * ECO_RATE) / FEE_DENOMINATOR;
uint256 feeAmount = poolFee + ecoFee;
uint256 netAmount = amount - feeAmount;
if (feeAmount > 0) {
super._transfer(sender, address(this), feeAmount);
}
pendingBurnAmount += netAmount;
emit PendingBurnRecorded(sender, netAmount);
super._transfer(sender, recipient, netAmount);
...
}
}
Movie Token burn-realization path:
function extractFromPoolForLpMining(uint256 amount) external returns (uint256) {
require(msg.sender == lpMiningAddr, "Only LP mining");
require(!deflationStopped, "Deflation stopped");
...
_executePendingBurn(extractionPool);
...
try IUniswapV2Pair(extractionPool).sync() {} catch {}
}
function _executePendingBurn(address extractionPool) internal {
...
balanceOf[extractionPool] -= burnAmount;
balanceOf[DEAD_ADDRESS] += burnAmount;
emit Transfer(extractionPool, DEAD_ADDRESS, burnAmount);
emit PendingBurnExecuted(burnAmount);
pendingBurnAmount -= burnAmount;
try IUniswapV2Pair(extractionPool).sync() {} catch {}
}
The incident begins with a public flashloan of 358,681.541916590026223000 WBNB from 0x8f73b65b4caaf64fba2af91cc5d4a2a1318e5d8c. The attacker seeds a small LP position and then uses swapExactTokensForTokensSupportingFeeOnTransferTokens(..., to=PancakeRouter) to push MT from the pair to the router. The trace shows the first buy transferring 10000000000000000000006056 MT from the pair to the Pancake router, not directly to the attacker:
PancakeRouter::swapExactTokensForTokensSupportingFeeOnTransferTokens(..., PancakeRouter, ...)
PancakePair::swap(10000000000000000000006056, 0, PancakeRouter, 0x)
MT::transfer(PancakeRouter, 10000000000000000000006056)
emit Transfer(from: PancakePair, to: PancakeRouter, value: 10000000000000000000006056)
The attacker then uses removeLiquidityETHSupportingFeeOnTransferTokens to trigger the special MT router branch. The trace shows the router subsequently transferring 10000000043672794251859072 MT to the attacker contract. Later in the same transaction, the pattern repeats with a second router-held MT balance of 10000000037038966884234987.
PancakeRouter::removeLiquidityETHSupportingFeeOnTransferTokens(MT, 476750635623691, 0, 0, attacker, ...)
...
MT::transfer(attacker, 10000000043672794251859072)
emit Transfer(from: PancakeRouter, to: attacker, value: 10000000043672794251859072)
This is why the attack can source MT inventory before deflationStopped is set. The buy restriction only blocks the direct pair-to-recipient path; it does not prevent the pair-to-router-to-attacker path that MT itself treats as a router-mediated removal flow.
Once the attacker holds enough MT, it executes a Pancake flash swap and pays back the callback with MT. At that point Movie Token charges the 10% sell tax, transfers the fee portion to itself, records the remaining 9,000,000.039305514826673166 MT in pendingBurnAmount, and still transfers that same net amount into the pair.
MT::transfer(PancakePair, 10000000043672794251859072)
emit Transfer(from: attacker, to: MT, value: 1000000004367279425185906)
emit PendingBurnRecorded(user: attacker, amount: 9000000039305514826673166)
emit Transfer(from: attacker, to: PancakePair, value: 9000000039305514826673166)
That is the exact code-level breakpoint described in the validated root-cause artifact. The pair receives the net MT immediately, but the token contract simultaneously books a future right to burn that same MT from the pair later. The AMM therefore still reports ordinary post-sell reserves right after the sell, while internally carrying a latent reserve deletion that is not reflected in WBNB balances.
After buying back MT inventory, the attacker calls distributeDailyRewards() on the LP-mining contract. The trace shows the LP-mining contract entering MT.extractFromPoolForLpMining() and then executing _executePendingBurn():
0x139bd2...::distributeDailyRewards()
MT::extractFromPoolForLpMining(67565169030649841554529)
emit Transfer(from: PancakePair, to: 0x000000000000000000000000000000000000dEaD, value: 6735516903064984155452968)
emit PendingBurnExecuted(amount: 6735516903064984155452968)
PancakePair::sync()
emit Sync(reserve0: 21000000000000000000000, reserve1: 1201151685215656142616)
This is the decisive invariant break. The pair is forced down to the hard floor of 21,000 MT, but it still retains 1,201.151685215656142616 WBNB. No proportional WBNB leaves the pool during this burn. The price of MT implied by the post-sync() reserves is therefore massively overstated relative to the real economic state that existed before the burn.
With the pool skewed, the attacker performs the final sell after deflationStopped is toggled. The trace shows the attacker selling 10000000037038966884234987 MT into the pair and receiving 1198628257314866098653 WBNB:
emit DeflationStopped()
emit Transfer(from: attacker, to: PancakePair, value: 10000000037038966884234987)
PancakePair::swap(0, 1198628257314866098653, attacker, 0x)
emit Transfer(from: PancakePair, to: attacker, value: 1198628257314866098653)
emit Sync(reserve0: 10021000037038966884234987, reserve1: 2523427900790043963)
The helper contract then repays the flashloan principal and ends with 381746819805990283197 WBNB, which it transfers to the attacker EOA. The balance-diff artifact independently matches this amount, and the native balance delta records only the separate gas payment in BNB.
Public funding and initial setup. The attacker EOA 0xdb0901... calls attacker helper 0xdf7ed22..., which draws a full-vault WBNB flashloan from 0x8f73b65..., seeds a small LP position, and prepares allowances to the Pancake router.
Inventory accumulation while buys are supposedly blocked. The attacker routes WBNB-to-MT swaps to the Pancake router address instead of directly to itself, then uses router-mediated liquidity-removal flows to move the router-held MT balances into the attacker contract. The first extracted MT balance is 10000000043672794251859072; the second is 10000000037038966884234987.
Pending-burn creation. Inside a flash-swap callback, the attacker sends 10,000,000.043672794251859072 MT back to the pair. Movie Token takes the fee, records 9,000,000.039305514826673166 MT as pendingBurnAmount, and still credits that same net amount to the pair.
MT reacquisition. The attacker spends 717.549912450704981677 WBNB to buy roughly ten million MT again, then uses the remaining LP withdrawal flow to pull the second router-held MT balance into the attacker contract so it can participate in the final drain.
Public burn trigger. The attacker calls distributeDailyRewards() on 0x139bd2ecfde76f5311d7beeb2e05cba6fede26d6. This call is public and deterministically reaches MT.extractFromPoolForLpMining(), which burns 6,735,516.903064984155452968 MT from the pair and synchronizes reserves to 21,000 MT and 1,201.151685215656142616 WBNB.
Final dump and exit. After deflationStopped is flipped, the attacker sells 10,000,000.037038966884234987 MT into the reserve-skewed pool, extracts 1,198.628257314866098653 WBNB, repays the flashloan principal, wraps stray native BNB back into WBNB, and transfers 381.746819805990283197 WBNB to the originating EOA.
The measurable loss is 381.746819805990283197 WBNB, encoded on-chain as 381746819805990283197 wei-style token units. The direct value loss lands in the MT/WBNB Pancake pair, whose WBNB reserve collapses from more than 300 WBNB pre-attack to 2.523427900790043963 WBNB after the final dump. The pair's MT reserve is also forcibly driven down to the 21,000 MT floor during the exploit path, and the token's deflationStopped flag is permanently toggled as a side effect of the manipulated reserve state.
Affected protocol components are:
0xb32979f3a5b426a4a6ae920f2b391d885abf4c180x037e6eb26275dbfe3a5244239bbe973f1a56b4490x139bd2ecfde76f5311d7beeb2e05cba6fede26d6https://bscscan.com/tx/0xfb57c980286ea8755a7b69de5a74483c44b1f74af4ab34b7c52e733fc62dfca60xdb0901a3254f47c0ce57fffce2c730bc33a1c0e10xdf7ed22d1fa65eac11a0806b7bb5f35d4a1e957dhttps://bscscan.com/address/0xb32979f3a5b426a4a6ae920f2b391d885abf4c18#codehttps://bscscan.com/address/0x037e6eb26275dbfe3a5244239bbe973f1a56b449#codehttps://bscscan.com/address/0x139bd2ecfde76f5311d7beeb2e05cba6fede26d60xfb57c980..., used for all value-flow and reserve-change claims