NeverFallToken LP Drain
Exploit Transactions
0xccf513fa8a8ed762487a0dcfa54aa65c74285de1bc517bd68dbafa2813e4b7cbVictim Addresses
0x5abde8b434133c98c36f4b21476791d95d888bf5BSC0x97a08a9fb303b4f6f26c5b3c3002ebd0e6417d2cBSCLoss Breakdown
Similar Incidents
CS Pair Balance Burn Drain
40%GGGTOKEN Treasury Drain via receive()
36%SafeMoon LP Burn Drain
36%Cellframe Migration Drain
35%Sheep Burn Reserve Drain
35%Thena Strategy Public Unstake Drain
35%Root Cause Analysis
NeverFallToken LP Drain
1. Incident Overview TL;DR
On BNB Chain block 27863178, transaction 0xccf513fa8a8ed762487a0dcfa54aa65c74285de1bc517bd68dbafa2813e4b7cb used a permissionless flash swap plus NeverFallToken's public buy(uint256) and sell(uint256) entrypoints to drain protocol-owned liquidity and treasury inventory. The sender EOA 0x53b757db8b9f3375d71eafe53e411a16acde75ee routed the transaction through helper contract 0x35353ec557b9e23137ae27e9d4cc829d4dace16b, repaid the flash loan in the same transaction, and finished with 74250894541677726514604 more USDT than it started with.
The root cause is a pair of unrestricted public flows in NeverFallToken that misuse protocol assets. buy() spends a fixed token inventory amount from the token contract itself, then transfers the extracted NF to the caller, while sell() burns LP tokens owned by the token contract instead of LP tokens owned by the caller. Because both functions are public and use super._transfer, an unprivileged actor can convert protocol-owned NF inventory and protocol-owned LP into attacker-controlled assets.
2. Key Background
NeverFallToken at 0x5abde8b434133c98c36f4b21476791d95d888bf5 maintains an NF/USDT Pancake pair at 0x97a08a9fb303b4f6f26c5b3c3002ebd0e6417d2c. In the public pre-state immediately before the exploit transaction, the token contract itself held 99537303621642211235625620728 NF and 8320140689525074913430188 LP tokens, while the pair held 390749131999675869539950 USDT and 177366502266430132318230923 NF. That means any public function able to move the token contract's balances is security-critical.
NeverFallToken overrides _transfer with whitelist checks around AMM transfers, but buy() and sell() bypass that custom logic by calling super._transfer directly. The contract also deploys and whitelists a public pairTempAddress, which allows the intermediate NF output from buySwap() to be routed into a public sink instead of depending on any privileged recipient.
The exploit funding source was Pancake flash-swap liquidity from pair 0x7efaef62fddcca950418312c6c91aef321375a00. That primitive is permissionless: any contract can request output tokens, execute arbitrary logic in pancakeCall, and repay before returning.
3. Vulnerability Analysis & Root Cause Summary
The vulnerability class is an attack caused by broken accounting and missing access control around protocol-held treasury balances. NeverFallToken's buy(uint256 amountU) accepts only the caller's USDT amount as input, but internally forces the contract to add liquidity using the fixed constant initSupply = 99900000000e18 on the NF side. That means the user does not pay for the NF inventory that the function contributes to liquidity. After that liquidity is added, buy() transfers the extracted NF from the token contract to msg.sender with super._transfer, bypassing the whitelist gate implemented in the overridden _transfer.
The companion sell(uint256 amount) path is also public and accepts attacker-held NF via super._transfer(msg.sender, address(this), amount). It then derives needLiquidity from amount * pairTotalSupply / balanceNF and calls removeLiquidity on the LP balance owned by the token contract itself, not on LP owned by the user. The result is that an arbitrary user can first obtain NF from the protocol at subsidized terms through buy(), then feed NF back into sell() to burn protocol-owned LP and extract USDT. The on-chain transaction trace and balance diff show that this exact sequence was used in the incident.
4. Detailed Root Cause Analysis
The decisive code path is in the verified NeverFallToken source:
function buy(uint256 amountU) public returns(uint256){
require(startBuy,"not start");
IERC20(usdtAddress).safeTransferFrom(msg.sender,address(this),amountU);
uint256 beforeLiquidityAmount = balanceOf(address(this));
IERC20(usdtAddress).approve(uniswapV2Router,amountU);
addLiquidity(initSupply, amountU * buyAddLiqFee / 100);
uint256 afterLiquidityAmount = balanceOf(address(this));
buySwap(amountU * buySwapFee / 100);
super._transfer(address(this), msg.sender, beforeLiquidityAmount - afterLiquidityAmount - balanceOf(pairTempAddress));
super._transfer(pairTempAddress, address(this), balanceOf(pairTempAddress));
}
function sell(uint256 amount) public returns(uint256){
super._transfer(msg.sender, address(this), amount);
uint256 balanceNF = this.balanceOf(uniswapV2Pair);
uint256 pairTotalSupply = IERC20(uniswapV2Pair).totalSupply();
uint256 needLiquidity = amount * pairTotalSupply / balanceNF;
uint256 beforeU = IERC20(usdtAddress).balanceOf(address(this));
removeLiquidity(needLiquidity,amount * 90 / 100,0);
uint256 afterU = IERC20(usdtAddress).balanceOf(address(this));
uint256 outU = afterU - beforeU;
IERC20(usdtAddress).safeTransfer(msg.sender, outU * sellFee / 100);
}
This code breaks the core invariant for treasury-managed assets: protocol-owned NF inventory and LP tokens must not be withdrawable by arbitrary users, and any user payout must be bounded by assets that user actually contributed. buy() violates that invariant by hardcoding the NF side of liquidity addition with initSupply instead of deriving it from the caller's payment. sell() violates the same invariant by burning LP from the token contract's balance instead of requiring the seller to own LP.
The trace of the incident transaction shows the exploit sequence exactly:
0x35353E...::pancakeCall(..., 1600000000000000000000000, ...)
NeverFallToken::buy(200000000000000000000000)
PancakeRouter::addLiquidity(..., 99900000000000000000000000000, 184000000000000000000000, ...)
PancakeRouter::swapExactTokensForTokensSupportingFeeOnTransferTokens(
1400000000000000000000000, 1, [USDT, NF], 0x051d6a5f..., ...
)
NeverFallToken::sell(75500000000000000000000000)
PancakeRouter::removeLiquidity(..., 12197877248982797510354403, 67950000000000000000000000, 0, ...)
The observed transaction routed the intermediate router swap output to 0x051d6a5f987e4fc53b458ec4f88a104356e6995a, but the protocol logic does not require that address to be privileged. The contract's own pairTempAddress is public and whitelisted, and the validated PoC shows the same exploit class can be reproduced by sending the intermediate NF output there instead. That preserves the ACT classification: the opportunity was available to any unprivileged on-chain actor using only public state and public contracts.
The profit accounting is also deterministic. The reference asset is USDT, and the balance diff shows the sender EOA ended the transaction with 74250894541677726514604 more USDT than before. The helper first repaid 1604800000000000000000000 USDT to the flash-loan pair, so the EOA's observed USDT delta is already net of the flash-loan premium. The measurable chain fee was paid separately in BNB, where the sender's native balance decreased by 8395810000000000 wei.
5. Adversary Flow Analysis
- Flash-loan funding. The helper contract invoked a Pancake flash swap against pair
0x7efaef62fddcca950418312c6c91aef321375a00and borrowed1600000000000000000000000USDT insidepancakeCall. - Inventory extraction and price distortion. The helper called
NeverFallToken.buy(200000000000000000000000). That forced the token contract to contribute83520176359726925202079336NF and184000000000000000000000USDT into the NF/USDT pair, minted LP back to the token contract, and transferred79947546159667026159169620NF to the helper. The helper then pushed1400000000000000000000000USDT through PancakeRouter into the NF/USDT market, reducing the pair's NF balance before selling back into the broken LP-removal path. - Unauthorized LP burn and profit realization. The helper called
NeverFallToken.sell(75500000000000000000000000). That removed12197877248982797510354403LP tokens from the token contract's own LP position and paid1679050894541677726514604USDT to the helper before flash-loan repayment. After repaying1604800000000000000000000USDT, the helper forwarded74250894541677726514604USDT to the sender EOA and retained4447546159667026159169620NF.
The adversary-related accounts are fully identified from the transaction artifacts:
- EOA
0x53b757db8b9f3375d71eafe53e411a16acde75eesent the adversary-crafted transaction and received the final USDT profit. - Contract
0x35353ec557b9e23137ae27e9d4cc829d4dace16bexecuted the flash swap, calledbuy()andsell(), and held the residual NF.
The victim-side assets were the NeverFallToken contract and its Pancake LP position:
- NeverFallToken
0x5abde8b434133c98c36f4b21476791d95d888bf5 - NeverFall/USDT pair
0x97a08a9fb303b4f6f26c5b3c3002ebd0e6417d2c
6. Impact & Losses
The measurable victim loss in the incident transaction was:
- USDT:
383353993578444384134829smallest units (383353.993578444384134829USDT with 18 decimals) - NF:
177083934115951755744520463smallest units
The balance diff also shows the token contract's LP position collapsing from 8320140689525074913430188 LP tokens to 40778100309290125521392. This was not a transient accounting effect; it was the direct consequence of sell() burning protocol-owned LP on behalf of an arbitrary external caller. The pool was left with only 7395138421231485405121 USDT and 282568150478376573710460 NF after the transaction, materially depleting both liquidity and treasury inventory.
From the attacker's perspective, the realized gains were split across two assets:
- The sender EOA received
74250894541677726514604USDT. - The helper contract retained
4447546159667026159169620NF.
7. References
- Incident transaction metadata:
0xccf513fa8a8ed762487a0dcfa54aa65c74285de1bc517bd68dbafa2813e4b7cb, collected at/workspace/session/artifacts/collector/seed/56/0xccf513fa8a8ed762487a0dcfa54aa65c74285de1bc517bd68dbafa2813e4b7cb/metadata.json - Incident execution trace showing
pancakeCall,buy, router swaps,sell, andremoveLiquidity:/workspace/session/artifacts/collector/seed/56/0xccf513fa8a8ed762487a0dcfa54aa65c74285de1bc517bd68dbafa2813e4b7cb/trace.cast.log - Incident balance changes for the attacker, victim token, pair, and LP balances:
/workspace/session/artifacts/collector/seed/56/0xccf513fa8a8ed762487a0dcfa54aa65c74285de1bc517bd68dbafa2813e4b7cb/balance_diff.json - Verified NeverFallToken source code:
/workspace/session/artifacts/collector/seed/56/0x5abde8b434133c98c36f4b21476791d95d888bf5/src/Contract.sol - Semantic oracle used for PoC validation:
/workspace/session/artifacts/auditor/oracle_definition.json