BurnAddress / MONA - Deferred LP Burn Lets Attacker Zero the MONA Reserve and Drain USDT
On 2026-04-14 at 05:18:27 UTC (BNB Chain block 92,429,268), the attacker used Moolah flash liquidity plus a same-tx Venus USDT borrow to fund a reserve-accounting exploit against the MONA token’s BurnAddress mechanism. The attacker first farmed 10,000 MONA through 25 freshly created accounts, then sold 9,900 MONA to create a deferred burn credit of 7,641.130933404251852809 MONA, bought out the pair’s original MONA inventory, triggered the delayed burn with a zero-value MONA transferFrom, and finally sold only 100 MONA back into a pool that now held almost all of its USDT but almost none of its MONA. The final realized profit was 60,950.308123921915843825 USDT sent to 0x7eeec499e501293f6e589d550046375a2ad0b4c3.
The core bug is not in the flash-loan path, Venus, or the referral setup. The loss comes from the MONA / BurnAddress design itself: a sell pays out USDT immediately, but the MONA side of that sell can be removed from the LP later, on an attacker-chosen transfer, by directly mutating the pair balance and calling sync(). That breaks the AMM invariant and lets the attacker separate the USDT proceeds of a sell from the MONA inventory that should have remained in the pool.
Root Cause
Vulnerable Contracts
- Primary target:
BurnAddressat0xd7ab8cc95eab59bab429242ec176feebaea88da3 - Coupled vulnerable token logic:
monaTokenat0x311838c073a865e8249f5c35e4cb2a5f815a36e8 - Drained pair: MONA/USDT Pancake pair
0x4dfb65e12f331c58380c55d7f288fe8fb22d3ea7 - Not root cause, but setup path:
NodeSubscriptionLisaat0xb9d8f078043dbf3297416735a84ab87324190fec,ReferralRegistryLisaat0x651c11eb567df9dcc5a3385f9f204ccbeee9e002, and unverified farming contract0xaea6e5ca6c1feeabbd3a114bcbca30a21424f76b
Vulnerable Functions
monaToken._update(address,address,uint256)-contracts/mona/monaToken.sol, lines 137-153monaToken._handleSell(address,address,uint256)-contracts/mona/monaToken.sol, lines 181-207BurnAddress.burn()-contracts/mona/burnAddress.sol, lines 44-65monaToken.burnsellMona(uint256)-contracts/mona/monaToken.sol, lines 284-295
Vulnerable Code
The bug is easier to see as a three-step pipeline. First, the sell path pays USDT out immediately, but also records a delayed LP-burn claim that is not settled inline with the swap.
// contracts/mona/monaToken.sol
// Verified source, lines 145-152 and 181-207
function _update(address from, address to, uint256 amount) internal override {
require(!isBlacklisted[from] && !isBlacklisted[to], "Blacklisted");
if (isExcludedFromTransfer[from] || isExcludedFromTransfer[to]) {
super._update(from, to, amount);
return;
}
if (from == lpPairAddress) {
_handleBuy(from, to, amount);
} else if (to == lpPairAddress) {
_handleSell(from, to, amount);
} else {
super._update(from, to, amount);
IburnAddressInterface(burnAddress).burn();
// VULNERABILITY: any non-pair MONA transfer can settle old sell credits later,
// at attacker-chosen timing, even if this transfer moves 0 tokens.
}
}
function _handleSell(address from, address to, uint256 amount) private {
require(isOpenTrade, "Not open");
require(block.timestamp - lastTradeTime[from] >= COOLDOWN_TIME, "Cooldown");
// ... fees omitted ...
uint256 remainingMona = amount - nodeFee - ecologyFee - remainingBurn;
(uint256 monaReserve, uint256 usdtReserve, ) = _getPairReserves();
uint256 amountUSDT = getAmountOut(remainingMona, monaReserve, usdtReserve);
uint256 monaDeduction = _handleProfitDeduction(from, amountUSDT, monaReserve, usdtReserve);
uint256 monaTransfer = remainingMona - monaDeduction;
super._update(from, to, monaTransfer);
_updateLastTradeTime(from);
IburnAddressInterface(burnAddress).sell(monaTransfer);
// VULNERABILITY: the pair pays USDT for this sell now, but `monaTransfer`
// is also queued for a future burn from the LP itself.
}
This first snippet contains two linked flaws:
_handleSell()lets the AMM pay out USDT formonaTransferimmediately, then separately books that samemonaTransferintoBurnAddress.sellMona._update()allows any later non-pair MONA transfer to triggerburnAddress.burn(), so the attacker can decide when those stale sell credits are settled.
Second, BurnAddress converts the accumulated historical sell credits into a fresh burn amount without verifying that the corresponding MONA is still supposed to remain in the pair at that moment.
// contracts/mona/burnAddress.sol
// Verified source, lines 44-58
function burn() external {
if (msg.sender == monaTokenAddress) {
uint256 amount = sellMona - burnedMona;
if (amount == 0) {
return;
}
uint256 currentBurned = monaToken.balanceOf(address(0xdead));
uint256 burnLimit = monaToken.burnLimit();
if (currentBurned < burnLimit) {
uint256 needBurn = burnLimit - currentBurned;
if (needBurn < amount) {
amount = needBurn;
}
burnedMona += amount;
monaToken.burnsellMona(amount);
// VULNERABILITY: burns from the LP based on stale cumulative sell credits,
// not as part of the sell that created those credits.
}
} else {
tapcount++;
}
}
Here the vulnerability is that burn() uses sellMona - burnedMona as a deferred claim on LP inventory. That amount is derived from prior sells, but it is executed later, on a different transfer context, after the attacker has already rearranged the pool reserves.
Third, once burn() decides how much to burn, monaToken.burnsellMona() directly removes those tokens from the pair and commits the manipulated reserve state with sync().
// contracts/mona/monaToken.sol
// Verified source, lines 284-295
function burnsellMona(uint256 amount) external {
require(amount > 0, "Must be greater than 0");
require(msg.sender == burnAddress || msg.sender == joinAddress, "Only burnAddress or joinAddress");
super._update(lpPairAddress, address(0xdead), amount);
// VULNERABILITY: directly mutates the pair's MONA balance after an unrelated trade.
IPancakePair(lpPairAddress).sync();
// VULNERABILITY: commits the manipulated balance to AMM reserves.
emit extracTransfer(lpPairAddress, address(0xdead), amount);
}
This is the final reserve-breaking step: instead of charging the seller inline, the contract reaches back into lpPairAddress, transfers MONA out of the pool to 0xdead, and then calls sync(). That lets the attacker first get paid in USDT, then later erase the MONA side of the earlier sell from the AMM reserves.
Why It Is Vulnerable
Expected behavior: If a sell is taxed or burned, that burn must be settled inside the same swap path, before or during the AMM state transition, and it must come from the seller’s tokens or from a protocol fee bucket. After the seller receives USDT, the pair should still hold the MONA inventory that the swap actually delivered to it.
Actual behavior:
_handleSell()transfersmonaTransferinto the pair and immediately lets the seller receive USDT.- The same
monaTransferis also recorded inBurnAddress.sellMonaas pending future burn credit. - A later, unrelated MONA transfer can trigger
BurnAddress.burn(). burn()callsburnsellMona(amount), which directly subtracts MONA fromlpPairAddressand then callssync().
So the seller gets paid once in USDT, and the pair can lose the MONA side of that exact sell later. That is equivalent to canceling the token side of the earlier swap after the USDT side has already settled. An attacker can therefore choose the moment when the LP inventory disappears.
The exploit becomes practical because the attacker can control when burn() runs. They do not call BurnAddress.burn() directly; that would only increment tapcount. Instead, they trigger it through the token hook in _update() by sending a normal non-pair MONA transfer. In this transaction, they used a zero-value transferFrom(0x000000000000000000000000000000000000deed, 0x000000000000000000000000000000000000deed, 0) to enter the non-pair branch and execute the burn without spending any MONA.
Normal Flow vs Attack Flow
| Stage | Normal flow | Attack flow |
|---|---|---|
| Sell settlement | Seller transfers MONA into the pair and receives quoted USDT; the sold MONA remains in the pair. | Seller helper sells 9,900 MONA, receives 51.249833102620958684 USDT, and queues 7,641.130933404251852809 MONA as delayed burn credit. |
| Burn accounting | Any burn/tax should be applied inline to the same transfer, from seller inventory or a fee bucket. | The MONA side of the earlier sell is deferred into sellMona, decoupled from the swap that already paid USDT out. |
| Burn trigger | Unrelated token transfers should not rewrite LP reserves from previous sells. | After buying almost all MONA out of the pool, the helper triggers burn() with a zero-value transferFrom, causing burnsellMona() to remove the last 7,641.130933404251852809 MONA from the pair. |
| Reserve state | Reserves remain consistent with swap history. | sync() locks in a near-zero MONA reserve while the pair still holds almost all of its USDT, so a tiny final sell can drain the USDT side. |
Attack Execution
High-Level Flow
The attacker EOA
0x7eeec499e501293f6e589d550046375a2ad0b4c3deploys factory0x865217406668c6750547fdf9cbd81dbff33bfefc, which deploys main helper0x6ab3b009614cd4bbd9c58b1f1cd0ee6ae49eaa87in the same transaction.0x6ab3...aa87takes five nested Moolah flash loans from0x8f73b65b4caaf64fba2af91cc5d4a2a1318e5d8cusingflashLoan(address,uint256,bytes)/ callbackonMoolahFlashLoan(uint256,bytes), borrowing:408,880.587066566339821548WBNB349.903711492409140667BTCB270.828520979121253536SolvBTC7,279,312.592405260060030054USDT11,784,826.933714838602152410lisUSD
With the borrowed collateral in place, the helper borrows
94,212,209.464329208278107126USDT from Venus vUSDT0xfd5840cd36d94d7229439859c0112a4185bc0255viaborrow(uint256).The helper creates 25 fresh accounts. Each account:
- binds
0x7eee...b4c3as referrer throughNodeSubscriptionLisa.bindReferrer(address)->ReferralRegistryLisa.bind(address,address) - receives
220USDT from the helper - pays that
220USDT into unverified contract0xaea6e5ca6c1feeabbd3a114bcbca30a21424f76b - receives
400MONA back and forwards it to0x6ab3...aa87
This setup costs
5,500 USDTtotal and yields exactly10,000 MONAto the helper.- binds
The helper sends
9,900 MONAto a freshly deployed sell-helper0x9b9442e49acd08aa084bff9735fa2e76a1e75349. This isolates the initial sell from the main helper’s cooldown state.0x9b94...5349sells9,900 MONAthrough Pancake Router usingswapExactTokensForTokensSupportingFeeOnTransferTokens. Because of MONA’s sell-side deductions, only7,641.130933404251852809 MONAactually reaches the pair and gets recorded inBurnAddress.sellMona; the main helper receives only51.249833102620958684 USDTfrom this sell.The main helper then spends
86,026,351.586237271086020734 USDTthroughswapTokensForExactTokensto buy exactly9,875,067.446160841960227167 MONA, sending the output to separate recipient0xdd0215b556b08dcd7bad43a8116f89814b1545e0. This keeps the main helper off the token’s buy cooldown path.At that point the pair still holds exactly the previously queued
7,641.130933404251852809 MONA. The helper callsMONA.transferFrom(0x000000000000000000000000000000000000deed, 0x000000000000000000000000000000000000deed, 0), which triggers the non-pair_update()branch and thereforeBurnAddress.burn().BurnAddress.burn()callsmonaToken.burnsellMona(7,641.130933404251852809), which transfers the entire remaining MONA balance of the pair to0xdeadand callssync(). The pair is now left with effectively zero MONA but still almost its full USDT reserve.The main helper, which deliberately retained
100 MONA, sells that final100 MONAviaswapExactTokensForTokensSupportingFeeOnTransferTokens. After fees,93.999999998477809336 MONAreaches the pair, and the pair pays out86,092,750.644528090380905875 USDT.The helper repays Venus (
94,212,209.464329208278107126USDT) and the USDT leg of the Moolah flash loans (7,279,312.592405260060030054USDT), then forwards the remaining60,950.308123921915843825 USDTto the attacker EOA.
Why the Attacker Used Multiple Addresses
The address split is deliberate and important:
0x9b94...5349performs the first 9,900 MONA sell so the main helper is not marked bylastTradeTime[from]on a sell.0xdd02...45e0receives the huge MONA buy so the main helper is not marked bylastTradeTime[to]on a buy.0x6ab3...aa87keeps only the final 100 MONA and performs the drain sell after the burn, with no cooldown conflict.
This is not the root bug, but it shows the attacker understood the token’s state machine and threaded around it to reach the real LP-burn vulnerability.
Detailed Call Trace
[39, 43, 47, 53, 57] 0x6ab3...aa87 -> 0x8f73...5d8c
flashLoan(...) / onMoolahFlashLoan(...)
Borrow WBNB, BTCB, SolvBTC, USDT, lisUSD
[186] 0x6ab3...aa87 -> vUSDT 0xfd58...0255
borrow(94,212,209.464329208278107126 USDT)
[404..1227] 25 fresh accounts
bindReferrer(0x7eee...b4c3)
pay 220 USDT each into 0xaea6...f76b
receive 400 MONA each and forward it to 0x6ab3...aa87
[1228] CREATE sell-helper 0x9b94...5349
[1229] MONA.transfer(0x9b94...5349, 9,900 MONA)
[1233] 0x9b94...5349 -> PancakeRouter
swapExactTokensForTokensSupportingFeeOnTransferTokens(
9,900 MONA,
0,
[MONA, USDT],
0x6ab3...aa87,
deadline
)
[1239] MONA -> BurnAddress.sell(7,641.130933404251852809)
[1243] Pair -> 0x6ab3...aa87 transfers 51.249833102620958684 USDT
[1252] 0x6ab3...aa87 -> PancakeRouter
swapTokensForExactTokens(
9,875,067.446160841960227167 MONA,
101,486,073.306567570959095864 USDT max,
[USDT, MONA],
0xdd02...45e0,
deadline
)
[1255] Pair -> 0xdd02...45e0 transfers 9,875,067.446160841960227167 MONA
[1254/604 log] Helper actually pays 86,026,351.586237271086020734 USDT into the pair
[1259] 0x6ab3...aa87 -> MONA
transferFrom(0x000...deed, 0x000...deed, 0)
This is the explicit delayed-burn trigger.
[1263] BurnAddress -> MONA
burnsellMona(7,641.130933404251852809)
[1264] MONA -> Pair
sync()
[1268] 0x6ab3...aa87 -> PancakeRouter
swapExactTokensForTokensSupportingFeeOnTransferTokens(
100 MONA,
0,
[MONA, USDT],
0x6ab3...aa87,
deadline
)
[1274] MONA -> BurnAddress.sell(93.999999998477809336)
[1278] Pair -> 0x6ab3...aa87 transfers 86,092,750.644528090380905875 USDT
[1284] 0x6ab3...aa87 -> vUSDT.repayBorrow(94,212,209.464329208278107126)
[logs] 0x6ab3...aa87 -> Moolah repayment 7,279,312.592405260060030054 USDT
[logs] 0x6ab3...aa87 -> 0x7eee...b4c3 profit transfer 60,950.308123921915843825 USDT
Financial Impact
Pair-Level Loss vs Attacker Profit
| Item | Amount | Notes |
|---|---|---|
| Pair USDT before attack path was complete | 66,450.317305679570184169 USDT | Observed around the exploit path and consistent with prior state |
| Pair USDT after exploit | 0.009181757654340344 USDT | End-of-tx reserve on the MONA/USDT pair |
| Pair USDT loss | 66,450.308123921915843825 USDT | Economic damage to the MONA/USDT pool |
| MONA farming setup cost | 5,500 USDT | 25 * 220 USDT paid into 0xaea6...f76b |
| Attacker profit transferred out | 60,950.308123921915843825 USDT | Sent to 0x7eee...b4c3 |
Reserve Transition Around the Core Drain
| Stage | MONA reserve | USDT reserve |
|---|---|---|
| After initial 9,900 MONA sell settles | 9,882,708.577094246212079976 | 66,399.067472576949225485 |
| After exact-token buy drains original pool MONA | 7,641.130933404251852809 | 86,092,750.653709848035246219 |
| After delayed burn executes | 0 | 86,092,750.653709848035246219 |
| After final 100 MONA sell | 94.000000008477809336 | 0.009181757654340344 |
Key Realized Amounts
| Step | Amount |
|---|---|
| MONA farmed from 25 accounts | 10,000 MONA |
| Initial MONA sold | 9,900 MONA |
MONA actually credited to sellMona / pair | 7,641.130933404251852809 MONA |
| USDT from initial sell | 51.249833102620958684 USDT |
| USDT spent on exact-token buy | 86,026,351.586237271086020734 USDT |
| MONA bought out of pair | 9,875,067.446160841960227167 MONA |
| Final MONA sold | 100 MONA (93.999999998477809336 reaches pair) |
| USDT from final sell | 86,092,750.644528090380905875 USDT |
| Venus repay | 94,212,209.464329208278107126 USDT |
| Moolah USDT repay | 7,279,312.592405260060030054 USDT |
| Net profit | 60,950.308123921915843825 USDT |
Evidence
| Item | Value |
|---|---|
| Attack tx | 0x3a60e1b3a4b0736be4f31839bfd7abc8bfc53b93ddbd3702e77fbc64561a7ea4 |
| Receipt status | 0x1 |
| Block / timestamp | 92,429,268 / 2026-04-14 05:18:27 UTC |
| Tx index in block | 0x13 |
BurnAddress.burn() selector | 0x44df8e70 |
BurnAddress.sell(uint256) selector | 0xe4849b32 |
monaToken.burnsellMona(uint256) selector | 0xf1fef213 |
| Initial sell router call | swapExactTokensForTokensSupportingFeeOnTransferTokens at trace index 1233 |
| Buy-back router call | swapTokensForExactTokens at trace index 1252 |
| Final drain router call | swapExactTokensForTokensSupportingFeeOnTransferTokens at trace index 1268 |
| Explicit delayed-burn trigger | transferFrom(0x000...deed, 0x000...deed, 0) at trace index 1259 |
Burn amount moved from pair to 0xdead | 7,641.130933404251852809 MONA |
| Final USDT payout from pair | 86,092,750.644528090380905875 USDT |
| Profit transfer to attacker EOA | 60,950.308123921915843825 USDT |
| Pair end reserve | 94.000000008477809336 MONA / 0.009181757654340344 USDT |
Secondary Observation: Referral Path Was Sybil-Friendly, but Not the Drain Root Cause
NodeSubscriptionLisa.bindReferrer() and ReferralRegistryLisa.bind() let freshly created accounts bind the attacker’s EOA as referrer through the whitelisted subscription contract, and nothing in that path prevented same-tx account farming. That setup mattered because it produced the initial 10,000 MONA used in the drain. However, even without that farming path, the actual loss of USDT still depends on the BurnAddress design flaw described above.
Residual Unknowns
The contract at 0xaea6e5ca6c1feeabbd3a114bcbca30a21424f76b is unverified. Its exact business logic is not required to establish the LP-drain root cause, because the decisive evidence is already on-chain in the verified MONA / BurnAddress code, the pair swap sequence, the burn trigger, and the reserve outcomes.
Related URLs
- Transaction: https://bscscan.com/tx/0x3a60e1b3a4b0736be4f31839bfd7abc8bfc53b93ddbd3702e77fbc64561a7ea4
- Attacker EOA: https://bscscan.com/address/0x7eeec499e501293f6e589d550046375a2ad0b4c3
- Main helper: https://bscscan.com/address/0x6ab3b009614cd4bbd9c58b1f1cd0ee6ae49eaa87
- BurnAddress: https://bscscan.com/address/0xd7ab8cc95eab59bab429242ec176feebaea88da3#code
- MONA token: https://bscscan.com/address/0x311838c073a865e8249f5c35e4cb2a5f815a36e8#code
- MONA/USDT pair: https://bscscan.com/address/0x4dfb65e12f331c58380c55d7f288fe8fb22d3ea7
- NodeSubscriptionLisa: https://bscscan.com/address/0xb9d8f078043dbf3297416735a84ab87324190fec#code
- ReferralRegistryLisa: https://bscscan.com/address/0x651c11eb567df9dcc5a3385f9f204ccbeee9e002#code
- Venus vUSDT: https://bscscan.com/address/0xfd5840cd36d94d7229439859c0112a4185bc0255#code