Vulnerability class · VC-05

MEV and front-running: when correct code still loses users money

Sandwich attacks, liquidation races, weak commit-reveal and the protocol design decisions that decide how much value leaks to searchers before a user's transaction lands.

Transaction ordering is an input to your protocol

Most protocol logic is written as though transactions arrive in the order users sent them. They do not. Between broadcast and inclusion, a transaction sits in a public mempool where anyone can read it, and block builders order the block to maximise their own revenue.

That makes ordering an attacker-controlled input. If any outcome in your protocol depends on the sequence in which transactions land, an adversary chooses that sequence — and pays the builder for the privilege out of the profit.

This is why MEV findings are genuinely uncomfortable to write. There is often no line of code that is wrong. The contract behaves exactly as specified; the specification simply assumed something the chain does not guarantee.

Sandwich attacks and the slippage parameters that stop them

The canonical extraction. A user submits a swap. A searcher sees it, buys ahead of them (pushing the price up), lets the user's swap execute at the worse price, and sells immediately after.

// VULNERABLE — amountOutMin of 0 authorises unlimited loss
router.swapExactTokensForTokens(
    amountIn,
    0,                      // "the swap should just work"
    path,
    msg.sender,
    block.timestamp         // deadline that is always satisfiable
);

// FIXED
router.swapExactTokensForTokens(
    amountIn,
    minOut,                 // computed from a quote, with explicit tolerance
    path,
    msg.sender,
    deadline                // supplied by the caller, not the block
);

Two failures are combined in the vulnerable version. amountOutMin: 0 means the user accepts any output, including nearly zero. block.timestamp as a deadline means no deadline at all — the transaction can sit in the mempool for hours and execute whenever conditions favour a searcher.

Contract-level findingThe worst version is a protocol that performs internal swaps with hardcoded zero slippage during rebalancing or liquidation. Users never see that parameter and cannot protect themselves — the protocol is donating value to searchers on every operation.

Liquidation races and priority-gas auctions

Liquidation is a designed MEV opportunity. The question is whether the design captures value for the protocol or leaks it to a gas auction.

  • Fixed-bonus liquidations create a race: every bot sees the same profit and bids gas up to just under it. The value goes to the builder, not to the protocol or the liquidated user.
  • Dutch-auction liquidations start the bonus at zero and increase it over time, so the position clears at approximately the minimum bonus the market requires. This is strictly better for both protocol and borrower.
  • Liquidation griefing is the failure mode to check for: can an attacker make a position unliquidatable? A liquidation path that reverts on a token blacklist, an oracle staleness check, or an unbounded loop turns a solvent protocol insolvent while everyone watches.
  • Partial liquidation rounding can leave dust positions that cost more gas to liquidate than they yield. Enforce a minimum liquidation size or allow full closure of small positions.

Commit-reveal that actually commits

Auctions, games, randomness and governance often reach for commit-reveal. It is easy to implement it in a way that provides no protection.

// VULNERABLE — brute-forceable commitment
commitment = keccak256(abi.encodePacked(bidAmount));
// A bid is a bounded number. An attacker enumerates plausible bids
// offline and matches the hash in seconds.

// FIXED — salt makes the pre-image space unsearchable,
// and binding the sender prevents commitment theft.
commitment = keccak256(abi.encodePacked(bidAmount, salt, msg.sender));

Then check the protocol around it: is there a penalty for committing and never revealing? Without one, an attacker commits to several outcomes and reveals only the favourable one — a free option, which is the exact thing the scheme was meant to prevent. Bond the commitment, and slash it on non-reveal.

Design choices that reduce leakage

  • Never hardcode zero slippage. Anywhere. Internal swaps included.
  • Deadlines come from the caller. block.timestamp is not a deadline.
  • Use oracle-priced settlement where possible. If a trade settles against a validated oracle price rather than instantaneous pool state, sandwiching has nothing to squeeze.
  • Batch into a single clearing price. Frequent batch auctions eliminate ordering advantage within a batch entirely. It is a bigger design commitment but the strongest available defence.
  • Dutch auctions over fixed bonuses. Let the market discover the minimum incentive.
  • Private submission where the UX permits. Routing user transactions through a private relay keeps them out of the public mempool. This is a user-side mitigation, not a protocol-level fix, and it does not protect against a builder.
  • Two-step approvals. The classic ERC-20 approve race is still live; increaseAllowance / decreaseAllowance or permit-based flows avoid it.

How MEV appears in an audit report

We treat MEV exposure as a first-class finding with a quantified cost rather than a note at the end. The structure we use:

  1. Enumerate ordering-sensitive operations. Every function whose outcome changes based on when it lands relative to other transactions.
  2. Identify who captures the value. Protocol, user, liquidator, searcher, or builder. If the answer is “searcher”, quantify it.
  3. Estimate leakage per operation at realistic volumes, and annualise it. “Roughly 0.3% of swap volume leaks to sandwiching” lands very differently in a report than “consider MEV risk”.
  4. Check for griefing, not just extraction. Can ordering be used to make an operation permanently fail rather than merely expensive?
  5. Recommend the design change, not just the parameter. Adding a slippage argument is a patch; settling against an oracle removes the category.

MEV is not a bug to be eliminated — it is a property of a public, permissionless mempool. The question a review answers is how much of your users' value leaks out of your design by default, and whether that number is one you would be comfortable publishing.


All code in this article is illustrative — written to demonstrate a pattern, not taken from any client engagement. If you want this class of issue hunted in your own codebase, that is what a DeFi Protocol Security Review is for.

Found this useful?

We hunt this class for a living.

Send the repository. Free scoping in under two working days, and you'll know who is reading your code.