0x4353a6d37e95a0844f511f0ea9300ef3081130b24f0cf7a4bd1cae26ec3931010x35f5cef517317694df8c50c894080caa8c92af7dBSC0xa0ba9d82014b33137b195b5753f3bc8bf15700a3BSCThe incident is a single-transaction BNB Smart Chain exploit against WMRP and the related MRP reserve system at block 40122170. The attacker flash-borrowed WBNB, built a temporary WMRP LP position, and then removed that liquidity through a contract fallback that reentered WMRP during the outbound ETH transfer. The root cause is that WMRP pays ETH to the LP before finishing token-side settlement, so a contract recipient can buy against reserves that still include the MRP owed to that same liquidity burn.
WMRP is a wrapper around MRP that uses receive() as an entrypoint for buys and add-liquidity flows. Its transfer() function also contains sentinel-recipient logic that triggers sells, liquidity open/close actions, and withdrawals instead of behaving like a plain ERC-20 transfer. MRP itself contains contract-recipient handling that allows a contract recipient to participate in the sell path, which is relevant because the attacker helper contract liquidates extracted MRP back into native value during the same transaction.
The vulnerability is a reentrancy bug in WMRP liquidity removal. In WMRP.sol, _removeLiquidity computes the LP share, burns LP, and then immediately calls _safeEthTransfer(account, amountETH). Only after that external call returns does the function execute _transfer(address(this), account, amountMRP) and _withdraw(account, amountMRP). Because on WMRP routes incoming ETH into , a malicious contract recipient can use the ETH callback to reenter WMRP before the original token payout is deducted from reserves. The result is that the attacker buys against temporarily overstated token reserves and then still receives the full original liquidity-removal payout. This violates the settlement invariant that the same LP burn must not be able to trade against assets still owed to that burn.
receive()_buy()The vulnerable sequence is visible in the verified WMRP code:
function _removeLiquidity(address account) internal {
uint256 liquidity = LPBalanceOf(account);
(uint256 ethAmount, uint256 tokenAmount) = getReserves();
uint256 amountETH = liquidity * ethAmount / LPTotalSupply;
uint256 amountMRP = liquidity * tokenAmount / LPTotalSupply;
_burnLP(account);
_safeEthTransfer(account, amountETH);
_transfer(address(this), account, amountMRP);
_withdraw(account, amountMRP);
}
This ordering is unsafe because _safeEthTransfer performs an external call before the token-side reserve accounting is finished. The paired buy logic uses current contract reserves when calculating output:
function _buy() internal {
uint256 buyETHAmount = msg.value - buyFeeAmount;
uint256 ethContractAmount = getContractEthAmount();
uint256 balanceOfThis = balanceOf(address(this));
uint256 buyAmount = buyETHAmount * balanceOfThis / ethContractAmount;
_transfer(address(this), _msgSender(), buyAmount);
_withdraw(_msgSender(), buyAmount);
}
During the exploit transaction 0x4353a6d37e95a0844f511f0ea9300ef3081130b24f0cf7a4bd1cae26ec393101, the attacker helper 0x2bd8980a925e6f5a910be8cc0ad1cff663e62d9d receives the ETH leg of _removeLiquidity, immediately reenters WMRP, and performs another buy while the MRP side of the LP burn is still sitting in WMRP reserves. The trace records the original ETH callback into the helper, the reentrant call back into WMRP with 58398821189089608206 wei, and the later transfer path that still pays out the original MRP withdrawal. The balance diff confirms the protocol-side depletion: WMRP and MRP contract-side MRP balances each drop by 6098355549779395839952, while the sender EOA ends the transaction up 17896210904434220483 wei net of gas.
The attacker flow is fully permissionless:
0x132d9bbdbe718365af6cc9e43bac109a9a53b138 sends the exploit transaction and controls helper contract 0x2bd8980a925e6f5a910be8cc0ad1cff663e62d9d.400 WBNB from Pancake pair 0x0ed7e52944161450477ee417de9cd3a859b14fd0, unwraps it, and buys WMRP exposure._buy() before the original amountMRP has been transferred out._removeLiquidity continues and still transfers the full LP token-side payout to the helper.401.2 WBNB to the pair, and forwards the remaining BNB profit to the EOA.The end state is visible in the trace and balance diff: the Pancake pair is repaid, the attacker EOA receives value from the helper near the end of the trace, and the net sender gain after gas is 17896210904434220483 wei.
The exploit drains native-value reserves from the WMRP/MRP system in a single transaction. The measured protocol-side loss is 20066567300062425643 wei of BNB-equivalent reserve value, while the attacker sender EOA realizes 17896210904434220483 wei net profit. The remainder is accounted for by flash-loan fee, protocol fee distribution, and gas. Both WMRP reserve accounting and the linked MRP reserve pool are affected because the exploit extracts excess MRP from WMRP and then monetizes that MRP through the MRP sell path.
0x4353a6d37e95a0844f511f0ea9300ef3081130b24f0cf7a4bd1cae26ec393101WMRP.soltrace.cast.logbalance_diff.jsonmetadata.json