Minterest Mantle liquidation MEV on undercollateralized positions
Exploit Transactions
0xb3c4c313a8d3e2843c9e6e313b199d7339211cdc70c2eca9f4d88b1e155fd6bdVictim Addresses
0x5aa322875a7c089c1db8ae67b6fc5abd11cf653dMantle0xfa1444ac7917d6b96cac8307e97ed9c862e387beMantle0x5edbd8808f48ffc9e6d4c0d6845e0a0b4711fd5cMantleLoss Breakdown
Similar Incidents
Morpho-Pendle flash-loan liquidation MEV captures undercollateralized spread
31%MineSTM LP-burn MEV drains USDT from STM liquidity
22%HANA tax-wallet MEV arbitrage on Uniswap V2
21%DysonVault / Thena Overnight LP Unwind MEV
21%DOGGO/WETH cross-pool arbitrage MEV extracts WETH spread
21%Multi-venue stablecoin/WETH MEV arbitrage on Ethereum mainnet
21%Root Cause Analysis
Minterest Mantle liquidation MEV on undercollateralized positions
1. Incident Overview TL;DR
On Mantle (chainid 5000), EOA 0x618f768af6291705eb13e0b2e96600b3851911d1 sends contract-creation transaction 0xb3c4c313a8d3e2843c9e6e313b199d7339211cdc70c2eca9f4d88b1e155fd6bd in block 66416577. The transaction deploys an orchestrator contract 0x5fdac50aa48e3e86299a04ad18a68750b2074d2d which, inside its constructor, executes a series of lending and liquidation calls against the Minterest Mantle deployment: the MUSDYToken_Mantle market 0x5edbd8808f48ffc9e6d4c0d6845e0a0b4711fd5c, the METHL2 market proxy 0x5aa322875a7c089c1db8ae67b6fc5abd11cf653d, and the BVM_ETH reserve proxy 0xfa1444ac7917d6b96cac8307e97ed9c862e387be.
Using only public entrypoints and prevailing oracle prices from the AggregatedPriceOracle controller 0x067820894867c71672387282533d3d3abad44095, the orchestrator repays undercollateralized debt and seizes collateral in METHL2 and BVM_ETH, routing the underlying assets through a helper contract 0x9b506584a0f2176494d5f9c858437b54df97bc06 and ultimately to the EOA. No lending or reserve invariants are broken and no unauthorized mint occurs; the incident is a permissionless liquidation MEV opportunity where a competitive liquidator extracts value from undercollateralized borrowers.
2. Key Background
Minterest’s Mantle deployment uses a standard Compound-style money-market architecture, implemented via MToken_Mantle contracts behind proxy addresses. For this incident, the key markets are:
- METHL2 market proxy:
0x5aa322875a7c089c1db8ae67b6fc5abd11cf653d(implementation0xf8e2459f6961aba36c218ad35dc7077053ccb958). - BVM_ETH reserve proxy:
0xfa1444ac7917d6b96cac8307e97ed9c862e387be(implementation0xeb70eb1a325b03796ae8280bdba38ee4885d3853). - MUSDYToken_Mantle market:
0x5edbd8808f48ffc9e6d4c0d6845e0a0b4711fd5c.
Each MToken market tracks:
totalTokenSupply,totalBorrows,totalProtocolInterest,borrowIndex, and per-accountBorrowSnapshotandaccountTokens.- A core reserve accounting invariant via:
// From MToken.sol (MToken_Mantle implementation)
function exchangeRateStoredInternal() internal view returns (uint256) {
if (totalTokenSupply == 0) {
return initialExchangeRateMantissa;
} else {
return ((getCashPrior() + totalBorrows - totalProtocolInterest) * EXP_SCALE) / totalTokenSupply;
}
}
Borrow balances are tied to borrowIndex via borrowBalanceStoredInternal, and updates to totalBorrows and totalProtocolInterest occur only through interest accrual (accrueInterest) and explicit borrow/repay/liquidation paths. A Supervisor contract enforces risk checks via hooks such as beforeBorrow, beforeRedeem, beforeAutoLiquidationRepay, and beforeAutoLiquidationSeize.
Price data comes from AggregatedPriceOracle at 0x067820894867c71672387282533d3d3abad44095, which reads Api3 Dapi feeds to provide:
getUnderlyingPrice(METHL2 mToken)for the METHL2 market.getAssetPrice(BVM_ETH)for BVM_ETH.
The markets are intentionally permissionless: when a borrower’s health factor (computed from Supervisor configuration and oracle prices) falls below the liquidation threshold, any actor can call autoLiquidationRepayBorrow and autoLiquidationSeize to repay debt and seize collateral at a discount. This design creates the possibility of liquidation MEV whenever undercollateralized positions exist.
3. Vulnerability Analysis & Root Cause Summary
The incident is not a protocol bug but a design-consistent MEV opportunity. At pre-state σ_B (Mantle block 66416577), at least one borrower in the MUSDYToken_Mantle and/or METHL2 markets is undercollateralized according to Supervisor checks using AggregatedPriceOracle prices. The MToken markets expose public, permissionless auto-liquidation entrypoints that allow any account to repay this debt and seize more collateral value (in METHL2 and BVM_ETH) than the cost of repayments and gas.
The core lending invariants remain intact:
- Reserve accounting satisfies the
exchangeRateStoredInternalformula above. - Per-account borrows track
borrowIndex. totalBorrowsandtotalProtocolInterestchange only viaaccrueInterestand borrow/repay/liquidation functions.
PrestateTracer storage diffs for 0x5aa3...653d, 0x5edb...1fd5c, and 0xfa14...87be show incremental changes in low-index slots (matching these global variables) and hashed mapping slots (account positions), consistent with normal borrow, repay, and liquidation updates. No evidence appears of arbitrary state resets or unauthorized minting.
The root cause is therefore economic: a permissionless liquidation mechanism, parameterized by oracle prices and risk settings, exposes undercollateralized positions to anyone who can perform the liquidation, allowing third parties to extract liquidation MEV at borrowers’ expense.
4. Detailed Root Cause Analysis
4.1 Invariant and Code-Level Behavior
The relevant MToken functions are implemented in MToken.sol (verified via:
artifacts/root_cause/data_collector/iter_3/contract/5000/0xf8e2459f6961aba36c218ad35dc7077053ccb958/source/src/MToken.sol and the analogous file for 0xeb70...3853).
Key operations:
accrueInterest()updatestotalBorrows,totalProtocolInterest, andborrowIndexaccording to an interest rate model andprotocolInterestFactorMantissa.borrow/borrowFreshincreaseaccountBorrows[borrower].principalandtotalBorrowswhile transferring underlying out viadoTransferOut:
function borrowFresh(uint256 borrowAmount, bool isERC20based) internal nonReentrant {
address borrower = msg.sender;
supervisor.beforeBorrow(this, borrower, borrowAmount);
require(getCashPrior() >= borrowAmount, ErrorCodes.INSUFFICIENT_TOKEN_CASH);
uint256 accountBorrowsNew = borrowBalanceStoredInternal(borrower) + borrowAmount;
uint256 totalBorrowsNew = totalBorrows + borrowAmount;
accountBorrows[borrower].principal = accountBorrowsNew;
accountBorrows[borrower].interestIndex = borrowIndex;
totalBorrows = totalBorrowsNew;
if (isERC20based) doTransferOut(borrower, borrowAmount);
}
autoLiquidationRepayBorrowandautoLiquidationSeizeimplement liquidations for undercollateralized borrowers, gated solely by Supervisor hooks:
function autoLiquidationRepayBorrow(address borrower_, uint256 repayAmount_) external nonReentrant {
supervisor.beforeAutoLiquidationRepay(msg.sender, borrower_, this);
require(accrualBlockNumber == getBlockNumber(), ErrorCodes.MARKET_NOT_FRESH);
require(totalProtocolInterest >= repayAmount_, ErrorCodes.INSUFFICIENT_TOTAL_PROTOCOL_INTEREST);
uint256 borrowBalance = borrowBalanceStoredInternal(borrower_);
accountBorrows[borrower_].principal = borrowBalance - repayAmount_;
accountBorrows[borrower_].interestIndex = borrowIndex;
totalBorrows -= repayAmount_;
totalProtocolInterest -= repayAmount_;
}
function autoLiquidationSeize(
address borrower_,
uint256 seizeUnderlyingAmount_,
bool isLoanInsignificant_,
address receiver_
) external nonReentrant {
supervisor.beforeAutoLiquidationSeize(this, msg.sender, borrower_);
uint256 exchangeRateMantissa = exchangeRateStoredInternal();
uint256 borrowerSeizeTokens;
if (seizeUnderlyingAmount_ == type(uint256).max) {
borrowerSeizeTokens = accountTokens[borrower_];
seizeUnderlyingAmount_ = (borrowerSeizeTokens * exchangeRateMantissa) / EXP_SCALE;
} else {
borrowerSeizeTokens = (seizeUnderlyingAmount_ * EXP_SCALE) / exchangeRateMantissa;
}
uint256 borrowerTokensNew = accountTokens[borrower_] - borrowerSeizeTokens;
uint256 totalSupplyNew = totalTokenSupply - borrowerSeizeTokens;
accountTokens[borrower_] = borrowerTokensNew;
totalTokenSupply = totalSupplyNew;
if (isLoanInsignificant_) {
totalProtocolInterest = totalProtocolInterest + seizeUnderlyingAmount_;
} else {
doTransferOut(receiver_, seizeUnderlyingAmount_);
}
}
These functions:
- Enforce freshness (
accrualBlockNumber == getBlockNumber()). - Adjust borrower and global accounting in a way consistent with the reserve invariant.
- Transfer seized underlying only via
doTransferOut(receiver_, seizeUnderlyingAmount_)when Supervisor has authorized liquidation.
Admin-only functions (e.g., sweepToken, setProtocolInterestFactor) cannot be called by the orchestrator/helper and do not explain the observed value transfer.
4.2 Pre-State and Oracle Prices
Pre-state σ_B is reconstructed for the seed tx using:
- Tx metadata and balance diffs:
artifacts/root_cause/seed/5000/0xb3c4...5bd/metadata.jsonartifacts/root_cause/seed/5000/0xb3c4...5bd/balance_diff.json
- PrestateTracer storage diffs:
- METHL2 market proxy
0x5aa3...653d:artifacts/root_cause/data_collector/iter_2/tx/5000/0xb3c4...5bd/state_diff_0x5aa322875a7c089c1db8ae67b6fc5abd11cf653d.json - MUSDYToken_Mantle market
0x5edb...1fd5c:artifacts/root_cause/data_collector/iter_3/tx/5000/0xb3c4...5bd/state_diff_0x5edbd8808f48ffc9e6d4c0d6845e0a0b4711fd5c.json - BVM_ETH reserve proxy
0xfa14...87be:artifacts/root_cause/data_collector/iter_3/tx/5000/0xb3c4...5bd/state_diff_0xfa1444ac7917d6b96cac8307e97ed9c862e387be.json
- METHL2 market proxy
Oracle simulations at the parent block are captured in:
METHL2 oracle:
AggregatedPriceOracle::getUnderlyingPrice(MintProxy: 0x5aa3...653d)
→ 3315169076041807225751 [~3.315e21]
BVM_ETH oracle:
AggregatedPriceOracle::getAssetPrice(0xdEAddEaDdeadDEadDEADDEAddEADDEAddead1111)
→ 3193431400000000000000 [~3.193e21]
(From oracle_getUnderlyingPrice_METHL2.raw and oracle_getAssetPrice_BVM_ETH.raw.)
These values confirm that both the METHL2 underlying and BVM_ETH are high-value assets at σ_B, so any sizeable inflow of these tokens to the adversary represents economically meaningful profit.
4.3 Storage Diffs and Invariant Preservation
The prestateTracer diffs for the core markets show:
- For
0x5aa3...653d(METHL2 market), slots that match:accrualBlockNumber,borrowIndex,totalBorrows,totalProtocolInterest,totalTokenSupply, and a small set of hashed mapping slots move from positive pre-state values to slightly larger post-state values.- No slots are zeroed or set to anomalous magnitudes inconsistent with incremental interest accrual or position updates.
- For
0x5edb...1fd5c(MUSDYToken_Mantle) and0xfa14...87be(BVM_ETH reserve), the same pattern holds: low-index globals (interest/borrow/supply fields) and a handful of mapping slots change incrementally.
This behavior is exactly what the MToken invariant requires when interest accrues, borrows are repaid, and collateral is seized. There is no evidence of:
- Unauthorized minting of METHL2 or BVM_ETH.
- Writes to unexpected storage slots.
- Exchange rate or borrow index corruption.
The code-level invariant therefore holds across the incident.
4.4 Profit Computation and Success Predicate
The ERC20 and native balance diffs for the adversary EOA are:
{
"native_balance_deltas": [
{
"address": "0x618f768af6291705eb13e0b2e96600b3851911d1",
"before_wei": "223338207881203922053",
"after_wei": "222598952345423922053",
"delta_wei": "-739255535780000000"
}
],
"erc20_balance_deltas": [
{
"token": "0xdeaddeaddeaddeaddeaddeaddeaddeaddead1111",
"holder": "0x618f768af6291705eb13e0b2e96600b3851911d1",
"delta": "223640470785331714615",
"contract_name": "BVM_ETH"
},
{
"token": "0xcda86a272531e8640cd7f1a92c01839911b90bb0",
"holder": "0x618f768af6291705eb13e0b2e96600b3851911d1",
"delta": "204486893253187905361",
"contract_name": "METHL2"
}
]
}
(Summarized from balance_diff.json.)
The protocol/system holders lose the same amounts:
- BVM_ETH reserve proxy
0xfa1444ac7917d6b96cac8307e97ed9c862e387be:delta = -223640470785331714615BVM_ETH. - METHL2 market proxy
0x5aa322875a7c089c1db8ae67b6fc5abd11cf653d:delta = -204486893253187905361METHL2.
Taking BVM_ETH as the reference asset and valuing METHL2 at zero for a conservative lower bound, the adversary’s portfolio change over sequence b (the single seed tx) is:
value_before_in_reference_asset = 0BVM_ETH.value_after_in_reference_asset = 223.640470785331714615BVM_ETH.fees_paid_in_reference_asset ≈ 0.73925553578BVM_ETH-equivalent of gas/L1 fees.value_delta_in_reference_asset = 223.640470785331714615 - 0.73925553578 = 222.901215249551714615BVM_ETH.
This net gain is strictly positive and does not depend on any assumptions about the value of METHL2 or residual liabilities at helper contracts. The success predicate of the ACT opportunity is therefore:
- Profit predicate: adversary net portfolio value in BVM_ETH increases by approximately
222.90BVM_ETH-equivalent after transaction fees.
5. Adversary Flow Analysis
5.1 Adversary-Related Cluster
The adversary-related cluster accounts are:
- EOA (attacker):
0x618f768af6291705eb13e0b2e96600b3851911d1- Sends seed tx
0xb3c4...5bd. - Receives METHL2 and BVM_ETH proceeds.
- Initiates follow-up bridging transactions.
- Sends seed tx
- Orchestrator contract:
0x5fdac50aa48e3e86299a04ad18a68750b2074d2d- Created in
0xb3c4...5bd. - Constructor embeds the liquidation strategy and target addresses for markets, reserve, oracle, and helper.
- Created in
- Helper/adapter contract:
0x9b506584a0f2176494d5f9c858437b54df97bc06- Receives iterative balances of USDYW, rUSDYW, and MUSDYToken_Mantle.
- Calls MToken markets and the oracle.
- Acts as conduit for seized METHL2 and BVM_ETH, forwarding them to the EOA.
The seed transaction’s trace (trace.cast.log) shows heavy in-tx use of the helper and orchestrator, with calls into:
- MUSDYToken_Mantle, USDYW, rUSDYW.
- MToken implementations behind
0x5aa3...653dand0xfa14...87be. - AggregatedPriceOracle and its Dapi proxies.
5.2 Sequence b: In-Block Liquidation and Profit Realization
The ACT transaction sequence b consists of a single existentially-quantified adversary-crafted transaction:
- Chain: Mantle (chainid
5000). - Tx:
0xb3c4c313a8d3e2843c9e6e313b199d7339211cdc70c2eca9f4d88b1e155fd6bd(block66416577).
Within this tx:
-
The EOA deploys the orchestrator, which in its constructor:
- Configures references to:
- AggregatedPriceOracle controller
0x067820894867c71672387282533d3d3abad44095. - METHL2 market proxy
0x5aa3...653d. - BVM_ETH reserve proxy
0xfa14...87be. - MUSDYToken_Mantle
0x5edb...1fd5c. - USDYW
0x5be26527e817998a7206475496fde1e68957c5a6. - rUSDYW
0xab575258d37eaa5c8956efabe71f4ee8f6397cf3.
- AggregatedPriceOracle controller
- Sets the helper contract
0x9b50...bc06as the execution vehicle for liquidation.
- Configures references to:
-
The helper adjusts an existing leveraged position in the MUSDYToken_Mantle market by:
- Receiving USDYW and rUSDYW balances.
- Interacting with MUSDYToken_Mantle to manipulate its position and set up for liquidation.
-
The helper calls the METHL2 market and BVM_ETH reserve MToken implementations:
- Invokes
autoLiquidationRepayBorrowandautoLiquidationSeizefor undercollateralized borrower accounts, as indicated by:- Borrow and collateral slots changing in the markets’ storage diffs.
- Borrow/repay/liquidation-related events in the receipt logs.
- As a result:
- Borrower principal and/or protocol interest balances decrease appropriately.
totalBorrowsandtotalProtocolInterestadjust in line with repayments.accountTokensandtotalTokenSupplydecrease for the liquidated borrowers.- Underlying METHL2 and BVM_ETH are transferred from the markets’ underlying balances to the helper and then to the EOA.
- Invokes
-
The EOA receives:
+223.640470785331714615BVM_ETH from0xfa14...87be.+204.486893253187905361METHL2 from0x5aa3...653d.- Pays approximately
0.73925553578BVM_ETH-equivalent in gas/L1 fees.
This entire sequence is realized in a single transaction whose constructor logic any unprivileged actor can deploy and execute, given the same pre-state σ_B.
5.3 Post-Tx Bridging and Outflow
After the seed tx, the EOA submits follow-up transactions:
0x2d5c8328dc8aacdbf4b4dc0367cbfda258197441cb3629f0bf4da0c573eb950e(block66416654).0x5031e851a83e50725602110ba755f0e47cfb0790718125fe8fd29531ce1b1529(block66416670).0xe03b4147e8292a8cff85188726243847526d212c82390c52dffad23768d197af(block66416687).0x53d694620fbe17cac1d06e5a89c1d12278d237692cc50aefc6c0479d8945a07f(block66416701).
The Etherscan-style txlist (artifacts/root_cause/data_collector/iter_1/address/5000/0x618f...11d1/txlist.json) shows that these are approvals and bridge/send operations through Mantle bridge/adapter contracts (e.g., 0x4c1d3f..., 0xf7628d...) to move a portion of the METHL2 and BVM_ETH off Mantle. These steps do not affect whether the ACT opportunity is profitable—the profit is already realized at the end of 0xb3c4...5bd—but they demonstrate how the adversary exports the MEV.
5.4 ACT Opportunity and Reproducibility
Under the defined ACT adversary model, any unprivileged actor could reproduce this strategy if the same type of undercollateralized positions exist:
- Preconditions:
- At least one borrower’s health factor falls below the liquidation threshold determined by Supervisor configuration and AggregatedPriceOracle prices for MUSDY/METHL2/BVM_ETH.
- Sufficient protocol-held METHL2 and BVM_ETH collateral are available in the METHL2 market and BVM_ETH reserve for liquidation.
- Strategy:
- Deploy an orchestrator contract that:
- References the same MToken markets and oracle controller.
- Uses a helper contract to:
- Position itself within the MUSDYToken_Mantle market.
- Call
autoLiquidationRepayBorrowandautoLiquidationSeizeagainst undercollateralized borrowers.
- Transfers seized METHL2 and BVM_ETH to the adversary EOA.
- Optionally bridge the proceeds off Mantle via standard bridge contracts.
- Deploy an orchestrator contract that:
- Success predicate:
- Net portfolio change in BVM_ETH (and, optionally, METHL2) after gas and L1 fees is strictly positive. In the observed instance, the lower-bound profit is ~
222.90BVM_ETH-equivalent.
- Net portfolio change in BVM_ETH (and, optionally, METHL2) after gas and L1 fees is strictly positive. In the observed instance, the lower-bound profit is ~
This meets the definition of an ACT opportunity: the strategy uses only canonical on-chain data, public contract metadata/code, and standard, unprivileged transactions.
6. Impact & Losses
The immediate on-chain impact during tx 0xb3c4...5bd is:
- From the protocol/system perspective:
- METHL2 market proxy
0x5aa3...653dloses204.486893253187905361METHL2. - BVM_ETH reserve proxy
0xfa1444ac7917d6b96cac8307e97ed9c862e387beloses223.640470785331714615BVM_ETH.
- METHL2 market proxy
- From the adversary perspective:
- EOA
0x618f...11d1gains matching amounts of METHL2 and BVM_ETH. - Pays approximately
0.73925553578BVM_ETH-equivalent in gas/L1 fees.
- EOA
Storage diffs for the affected markets show borrower debt and collateral balances decreasing in a way consistent with liquidation, not with arbitrary asset theft or invariant violation. The economic harm is borne by liquidated borrowers (and, where configured, protocol interest accounts), whose positions are forcibly closed and collateral is seized at a discount. The protocol itself remains solvent, and its accounting invariants hold.
From a protocol-design standpoint, this is an expected outcome of open, incentive-driven liquidations: the protocol intentionally allows third parties to capture liquidation spreads in exchange for maintaining system solvency.
7. References
- Seed transaction metadata and balance diffs for
0xb3c4c313a8d3e2843c9e6e313b199d7339211cdc70c2eca9f4d88b1e155fd6bd:artifacts/root_cause/seed/5000/0xb3c4c313a8d3e2843c9e6e313b199d7339211cdc70c2eca9f4d88b1e155fd6bd/metadata.jsonartifacts/root_cause/seed/5000/0xb3c4c313a8d3e2843c9e6e313b199d7339211cdc70c2eca9f4d88b1e155fd6bd/balance_diff.json
- Execution trace and logs for seed tx:
artifacts/root_cause/data_collector/iter_1/tx/5000/0xb3c4c313a8d3e2843c9e6e313b199d7339211cdc70c2eca9f4d88b1e155fd6bd/trace.cast.logartifacts/root_cause/data_collector/iter_1/tx/5000/0xb3c4c313a8d3e2843c9e6e313b199d7339211cdc70c2eca9f4d88b1e155fd6bd/receipt.json
- Verified MToken implementations for METHL2 and BVM_ETH markets:
artifacts/root_cause/data_collector/iter_3/contract/5000/0xf8e2459f6961aba36c218ad35dc7077053ccb958/source/src/MToken.solartifacts/root_cause/data_collector/iter_3/contract/5000/0xeb70eb1a325b03796ae8280bdba38ee4885d3853/source/src/MToken.sol
- PrestateTracer storage diffs for core markets:
artifacts/root_cause/data_collector/iter_2/tx/5000/0xb3c4c313a8d3e2843c9e6e313b199d7339211cdc70c2eca9f4d88b1e155fd6bd/state_diff_0x5aa322875a7c089c1db8ae67b6fc5abd11cf653d.jsonartifacts/root_cause/data_collector/iter_3/tx/5000/0xb3c4c313a8d3e2843c9e6e313b199d7339211cdc70c2eca9f4d88b1e155fd6bd/state_diff_0x5edbd8808f48ffc9e6d4c0d6845e0a0b4711fd5c.jsonartifacts/root_cause/data_collector/iter_3/tx/5000/0xb3c4c313a8d3e2843c9e6e313b199d7339211cdc70c2eca9f4d88b1e155fd6bd/state_diff_0xfa1444ac7917d6b96cac8307e97ed9c862e387be.json
- AggregatedPriceOracle price simulations at the pre-state block:
artifacts/root_cause/data_collector/iter_3/tx/5000/0xb3c4c313a8d3e2843c9e6e313b199d7339211cdc70c2eca9f4d88b1e155fd6bd/oracle_getUnderlyingPrice_METHL2.rawartifacts/root_cause/data_collector/iter_3/tx/5000/0xb3c4c313a8d3e2843c9e6e313b199d7339211cdc70c2eca9f4d88b1e155fd6bd/oracle_getAssetPrice_BVM_ETH.raw
- Adversary tx history and bridging transactions:
artifacts/root_cause/data_collector/iter_1/address/5000/0x618f768af6291705eb13e0b2e96600b3851911d1/txlist.json