Crypto exploits

LPBonus exploit analysis

LPBonus advanced its per-share reward index using the MSN reserve read from a live Pancake pair at accrual, then paid out against the reserve read at withdrawal. An attacker crushed the reserve to 89.327789 at accrual and restored it to 491.113095 before claiming. DeFiHackLabs records about 92,607 USDT drained on BNB Chain.

David

Security ·

Reported loss
$93K
Occurred
Logged
Chain
BNB Chain

Logged as “2026-09-29: Reserve Inconsistency Between Reward Accrual And Lp Withdrawal Lets A Manipulated Msn Reserve Inflate An Lp Reward Claim on LPBonus”.

Analysis3 min read

LPBonus pays FIST rewards to liquidity providers in the FIST and MSN Pancake pair, and it works out what each provider is owed by dividing by that pair's live MSN reserve. The attacker made that reserve small when the reward was accrued and put it back before withdrawing, so the per-share index was set against a reserve that no longer existed by the time the money was paid. The position claimed 1,442,165.713011 FIST from an accrual that had funded 940,041.612768. DeFiHackLabs records the incident and ships a proof of concept that runs the sequence on a fork of BNB Chain.

The denominator is a pool reserve

MSN's transfer hooks drive every state change in LPBonus. The reward functions are all gated require(msg.sender == MSN), which reads like access control and is not. They are callbacks the token fires from inside its own transfer() and transferFrom(), so anyone who can trade MSN or move MSN liquidity can reach them without a key, a signature or an owner step. The contract at 0x52272524a22f941f5489c1233732797314bb054b is unverified, and DeFiHackLabs reversed it from bytecode.

Three of those callbacks carry the defect:

  • BonusSEND(reserveMSN, isRemove) is the accrual. It swaps LPBonus's accumulated MSN fees into FIST and advances the per-share index by fistIn * 1e18 / reserveMSN. A small reserve produces a large index jump.
  • userAddLP registers a provider's weight and snapshots the index they joined at.
  • UserRemoveLp(user, amount, reserveMSN_now) pays out, multiplying that index by the provider's weight.

The first and third both take the MSN reserve as an argument, and both arguments come from pair.getReserves() on the FIST and MSN pair at the moment of the call. The value used to set the index and the value used to cash it out are read at different times, from a balance any swap can move.

The sequence

  1. Flash-borrow 13,481,395.302739 FIST from the USDT and FIST pair.
  2. Buy 140 MSN and add it as liquidity. userAddLP registers the position while the MSN reserve reads about 501.
  3. Swap 12,200,000 FIST into the pair, driving the MSN reserve down to 89.327789.
  4. Swap MSN back into the pair, driving the reserve back up to 491.113095. The MSN that moves fires BonusSEND, which funds FIST into LPBonus and advances the index using the low reserve captured during the crush, as though the stake were priced against about 89 MSN.
  5. Burn a dust amount of the LP position while the reserve reads 491. The MSN leaving the pair fires UserRemoveLp, and CalcPendingUser pays the inflated claim for the entire registered weight: 1,442,165.713011 FIST, against 940,041.612768 FIST of accrual.
  6. Burn the rest of the position, repay the flash loan, and swap the FIST into USDT.

Step 5 is the part that makes the size of the win possible. The payout is computed from the provider's registered weight and the index, not from the amount of LP being burned. Burning enough to move MSN out of the pair by a rounding error is sufficient to trigger a claim on the whole position.

The result

DeFiHackLabs records a loss of about 92,607 USDT, some $92.6k, on BNB Chain, and describes the same mismatch between the 1,442,165.713011 FIST claimed and the roughly 940,041.612768 FIST actually funded.

The chain agrees. The attack transaction at block 124,759,921 ends with a transfer of 92,607.317234 USDT to the attacker's address.

Where the guard was missing

A reserve read from a pool at the moment of accrual should be the same reserve the payout is measured against, and an attacker should not be able to move it between the two. MSN levies a 2% transfer fee and carries LP reward hooks, so the token's own transfers were already reaching into the reward accounting. A manipulable balance had become an accounting input.

The closest match in this corpus is XLootStaking, whose reward share read the live totalSupply() at claim time, so the attacker minted and burned staking tokens around the claim. LPBonus read a live pool reserve instead, and the attacker moved it between accrual and payout, then put it most of the way back, from 89 to about 491 MSN, before the claim was paid.

Sources read (3)

Other exploits

Query every incident yourself — free, no signup

Updated 11 Nov 2026. Figures are as reported by the sources listed above and are not independently verified.