Calculated from recorded token losses using historical USD prices at the incident time.
0xc346adf14e5082e6df5aeae650f3d7f606d7e08247c2b856510766b4dfcdc57f0x128cd0ae1a0ae7e67419111714155e1b1c6b2d8dBSC0x6fb2020c236bbd5a7ddeb07e14c9298642253333BSCOn BNB Smart Chain block 18225003, transaction 0xc346adf14e5082e6df5aeae650f3d7f606d7e08247c2b856510766b4dfcdc57f used a helper contract to flash-borrow WBNB, buy NOVO, drain nearly the entire NOVO side of the NOVO/WBNB Pancake pair with an unauthorized transferFrom, call sync() to relock reserves at the drained balance, and then sell NOVO back into the distorted pool. The sequence repaid the flash liquidity and left 248526619366198266632 wei of WBNB profit at profit recipient 0x0376564615ae0f59f425bca748076bc25b7b524b.
The root cause is a direct authorization bug in NOVO. Its transferFrom(sender, recipient, amount) function never validates or decrements allowance before executing _transfer(sender, recipient, amount). Because the NOVO/WBNB Pancake pair had no locked-balance protection and no allowance granted to the attacker, any unprivileged caller could still move NOVO out of the pair, distort reserves, and extract WBNB through public Pancake liquidity.
NOVO is a fee-on-transfer token behind a transparent proxy, and its implementation routes user transfers through fee logic that can accumulate NOVO on the token contract itself. The relevant market is the NOVO/WBNB Pancake pair at 0x128cd0ae1a0ae7e67419111714155e1b1c6b2d8d, which updates reserves from live token balances whenever sync() is called.
That reserve-update model is safe only if token balances cannot be changed arbitrarily. PancakePair does not maintain an independent notion of token inventory; instead, it trusts and when updating reserves. If an attacker can remove NOVO from the pair without paying WBNB in, will accept the drained NOVO balance as the new reserve baseline and subsequent swaps will price against that manipulated state.
IERC20(token0).balanceOf(address(this))IERC20(token1).balanceOf(address(this))sync()The exploit also depends on NOVO's internal fee-handling logic. The implementation sets numTokensSellToAddToLiquidity = 5 * 10**5 * 10**9, so forcing a large amount of NOVO into the token contract satisfies the token-side liquidity threshold before the attacker performs the final sell. The ACT opportunity therefore combines a broken delegated-transfer authorization check with a public AMM reserve synchronization primitive and enough market liquidity to monetize the distortion in one transaction.
This incident is an ATTACK-class protocol exploit, not a pure MEV opportunity. The broken invariant is straightforward: a caller must not be able to transfer NOVO out of an address unless that caller is the owner or has sufficient allowance from that owner. The verified NOVO implementation violates that invariant inside transferFrom, where it checks only the sender's locked amount and then calls _transfer directly. The allowance decrement block that should enforce delegated-transfer authorization is present only as commented-out code.
That bug becomes exploitable because the victim is a Pancake liquidity pair. Once NOVO can be pulled out of the pair balance permissionlessly, PancakePair's public sync() function records the drained balance as the new reserve while leaving the WBNB reserve high. The attacker can then sell NOVO into a market that appears extraordinarily WBNB-rich relative to NOVO, causing the pair to release far more WBNB than an honest market state would permit. The exploit is deterministic because every step is permissionless and can be executed atomically by an unprivileged contract.
The code-level breakpoint is the NOVO implementation's transferFrom path:
function transferFrom(
address sender,
address recipient,
uint256 amount
) public override returns (bool) {
uint256 lockedAmount = getLockedAmount(sender);
if (lockedAmount > 0) {
require((balanceOf(sender) - amount) >= lockedAmount, "Your balance was locked");
}
_transfer(sender, recipient, amount);
// _approve(
// sender,
// _msgSender(),
// _allowances[sender][_msgSender()].sub(
// amount,
// "BEP20: transfer amount exceeds allowance"
// )
// );
}
The missing _approve(...sub(amount...)) call means delegated transfers never consume or even require allowance. That is the concrete safety breakpoint identified in root_cause.json: the implementation performs the asset movement first and omits the authorization check entirely.
The second half of the exploit comes from PancakePair's reserve update logic:
function sync() external lock {
_update(
IERC20(token0).balanceOf(address(this)),
IERC20(token1).balanceOf(address(this)),
reserve0,
reserve1
);
}
Because sync() simply snapshots live balances, any unauthorized reduction in the pair's NOVO balance becomes an official reserve value after the call. The attacker used this exact property after draining NOVO from the pair.
The exploit transaction trace confirms the end-to-end mechanism. First, the adversary EOA 0x31a7cc04987520cefacd46f734943a105b29186e called helper contract 0x3463a663de4ccc59c8b21190f81027096f18cf2a, which borrowed 17.2 WBNB from flash pair 0xeebc161437fa948aab99383142564160c92d2974. After buying NOVO through PancakeRouter, the helper called NOVO without any pair allowance:
NOVO::allowance(0x128cd0ae1a0ae7e67419111714155e1b1c6b2d8d, attacker) -> 0
NOVO::transferFrom(
0x128cd0ae1a0ae7e67419111714155e1b1c6b2d8d,
0x6fb2020c236bbd5a7ddeb07e14c9298642253333,
113951614762384370
) -> true
That call moved NOVO from the pair into the NOVO token contract even though the pair had not approved the attacker. The trace then shows NOVO/WBNB Pair::sync() updating reserves so that the NOVO reserve equals the drained balance 1139516147623844 while the WBNB side remains 412201031454274158328. At that point the pair is economically mispriced.
The attacker next sold the NOVO acquired from the initial buy into this manipulated reserve state. During the sell path, PancakePair emitted a swap paying out 33178549229639860748 wei of WBNB on one leg alone, and the overall transaction ended with direct WBNB profit delivered to 0x0376564615ae0f59f425bca748076bc25b7b524b. The balance-diff artifact records the original gas payer spending 9700470000000000 wei of native BNB, which is negligible relative to the extracted WBNB. This is why the ACT predicate is satisfied: an unprivileged actor can deterministically realize positive profit from public state and public liquidity.
The adversary flow is a single-transaction, multi-stage drain.
Flash Liquidity Setup: the helper contract receives the exploit entry call and uses Pancake flash liquidity to borrow 17.2 WBNB from pair 0xeebc161437fa948aab99383142564160c92d2974.Pre-positioning Buy: the helper approves PancakeRouter 0x10ed43c718714eb63d5aa57b78b54704e256024e and swaps the borrowed WBNB for NOVO, acquiring inventory that will later be sold back into the manipulated pool.Unauthorized NOVO Drain: the helper queries the NOVO/WBNB pair allowance, observes it is zero, and still calls NOVO.transferFrom(pair, NOVO, stolenAmount). The pair's NOVO balance falls from 115091130910008214 to 1139516147623844.Reserve Relock: the helper immediately calls sync() on the pair so PancakePair records the drained NOVO balance as the official reserve while the WBNB side stays elevated.Manipulated Sell and Profit: the helper sells the NOVO it bought earlier into the distorted reserve state, receives excess WBNB, repays the flash pair using the standard Pancake fee formula, and transfers the residual WBNB profit out.The accounts identified in the adversary cluster align with these roles. 0x31a7cc04987520cefacd46f734943a105b29186e is the EOA sender, 0x3463a663de4ccc59c8b21190f81027096f18cf2a is the exploit helper that executes the flash callback and reserve manipulation, and 0x0376564615ae0f59f425bca748076bc25b7b524b is the recipient of the final WBNB profit transfer.
The primary victimized asset is the NOVO/WBNB Pancake pair's WBNB reserve position. root_cause.json records total measurable loss as:
[
{
"token_symbol": "WBNB",
"amount": "309472396909544457822",
"decimal": 18
}
]
The direct attacker profit tracked in the ACT success predicate is 248526619366198266632 wei of WBNB after flash-loan repayment, with only 9700470000000000 wei of gas paid by the originating EOA. Additional value movement into NOVO-controlled treasury and liquidity-handling paths occurred during the token's internal fee logic, but the decisive economic damage is the pair-side WBNB depletion enabled by the unauthorized NOVO drain and reserve relock.
The validation and report rely on the following reproducible sources:
0xc346adf14e5082e6df5aeae650f3d7f606d7e08247c2b856510766b4dfcdc57f.transferFrom, sync(), manipulated sale, and repayment.0x87fa07c3572fc4238bf5cb6717ebfebb780e4764.0x128cd0ae1a0ae7e67419111714155e1b1c6b2d8d.For direct verification, the key contract addresses are:
0x6fb2020c236bbd5a7ddeb07e14c92986422533330x87fa07c3572fc4238bf5cb6717ebfebb780e47640x128cd0ae1a0ae7e67419111714155e1b1c6b2d8d0xeebc161437fa948aab99383142564160c92d29740x10ed43c718714eb63d5aa57b78b54704e256024e0xc346adf14e5082e6df5aeae650f3d7f606d7e08247c2b856510766b4dfcdc57f