Auditor Heuristics for MEV

A compact set of patterns and questions an auditor should apply mechanically when reviewing any contract that touches price-sensitive logic. Use this as a checklist alongside the body of the chapter.

Pattern Recognition

For each function in scope, ask:

Does this function read a price or quantity from on-chain state?

If yes, the read can be manipulated by a same-block transaction. Look for:

  • AMM spot-price reads (getReserves(), slot0().sqrtPriceX96, etc.) used for anything other than display.
  • Single-source oracle reads without staleness or deviation checks.
  • TWAP windows shorter than the realistic manipulation cost.

Does this function pay a reward to the first valid caller?

If yes, expect it to be gas-raced and the reward to be largely captured by validators. Verify:

  • The reward is small relative to the protocol benefit (otherwise users are subsidizing searcher gas wars).
  • VRF or batched-caller distribution is considered for high-value triggers.

Does this function settle a value at execution time that depends on input parameters?

If yes, it is potentially sandwich-able. Verify:

  • minAmountOut (or equivalent) is required and enforced.
  • deadline is required and enforced.
  • Internal callers (routers, aggregators, helpers) cannot defeat these checks.

Does this function execute conditionally based on an observation that another searcher might trigger?

If yes (liquidations, options exercises, conditional fills), it is a competitive auction. Verify:

  • Auction mechanics are designed (Dutch auction, fixed bounty, etc.) rather than left implicit.
  • The protocol's solvency does not depend on the auction being non-competitive.

Does this function rely on the order of transactions within a block?

If yes, the order can almost certainly be manipulated. Verify:

  • Atomic composition (price proof + action in same tx).
  • No "first to observe" privilege that can be bought from a builder.

Red-Flag Code Patterns

// 1. Slippage check missing
function swap(uint256 amountIn) external {
    uint256 out = _swap(amountIn);  // no minAmountOut
    IERC20(tokenOut).transfer(msg.sender, out);
}

// 2. Slippage check that can be bypassed
function swap(uint256 amountIn, uint256 minOut) external {
    require(minOut > 0, "min out");  // user-supplied, defaults to 1
    // ...
}

// 3. Spot price used for value-bearing logic
function liquidate(address borrower) external {
    uint256 collateralPrice = pool.getReserves();  // spot, manipulable
    // compute health factor and seize collateral
}

// 4. Oracle update followed by dependent action in separate tx
function poke() external {
    oracle.update();  // anyone can call; immediate effect on protocol state
}

// 5. Public mint with no rate limit and predictable value
function mint() external payable {
    require(msg.value == 0.1 ether, "price");
    _mint(msg.sender);  // raceable; no allowlist; expected value > 0.1 ETH
}

// 6. Reward callable by anyone, large vs gas cost
function poke() external {
    require(block.timestamp >= nextRebase, "early");
    nextRebase = block.timestamp + 1 days;
    _rebase();
    IERC20(rewardToken).transfer(msg.sender, 1000 ether);  // big bounty → gas war
}

// 7. Off-chain message replayable across chains
function exerciseOption(uint256 strike, uint256 nonce, bytes calldata sig) external {
    bytes32 h = keccak256(abi.encodePacked(strike, nonce, msg.sender));
    require(_recover(h, sig) == oracle, "bad sig");
    // missing: chain id, contract address, deadline
}

Each of these is a recurring finding template.

Questions for the Protocol Team

When MEV exposure is identified, ask the team explicitly:

  1. Have you analyzed your contract's MEV exposure? A team that hasn't even considered it has a structural problem.
  2. Which classes of MEV do you consider acceptable? Back-running by liquidators is often fine. Sandwiches against retail users usually are not.
  3. What submission channel do you recommend for users? If none, why not?
  4. Have you tested your contract against a fork with adversarial searchers? Foundry + a mempool replay can simulate this.
  5. What's your monitoring story for MEV-related anomalies? A liquidation that yields suspiciously large bonuses, an oracle update that arrives suspiciously close to a large position, etc.

Reporting MEV Findings

MEV findings often live in a grey zone: the contract behaves as specified, the loss is to the user, the mitigation requires user behavior. Report them anyway. The protocol team has a duty of care to users that does not end at "the contract is technically correct."

A useful structure for MEV findings:

  • What the user experiences: in plain language, what loss do they incur?
  • What enables it: specific lines of code or design choices.
  • Realistic exploitability: what does a searcher need to extract the value? Is this a generalized attack or specialized?
  • Estimated loss: for a typical user transaction, what percentage of the trade value is captured by the searcher? Order of magnitude is fine; precision is not the point.
  • Recommended fix: prefer contract-level fixes; fall back to user-side mitigations; be explicit about residual exposure.

Severity for MEV findings is contested in the audit industry. As a rough rule:

  • Critical / High: sandwiches with no minAmountOut, missing deadline, reused signatures across chains, oracle sandwiches with no price-proof bundling.
  • Medium: liquidation gas wars, sniped mints with predictable value, back-runnable position changes.
  • Low / Informational: generalized arbitrage that doesn't worsen user outcomes (JIT liquidity affecting LPs, ordinary back-running).

What "Safe Enough" Looks Like

A protocol that has thought carefully about MEV typically:

  • Enforces minAmountOut and deadline on every value-transferring function.
  • Bundles price proofs with actions where possible (pull oracles).
  • Documents recommended user submission channels.
  • Uses VRF or batching for any "first caller wins" reward larger than ~1 ETH.
  • Surfaces MEV statistics in its analytics dashboard.
  • Has run forked-mainnet simulations against a representative adversary.

Most live protocols do not yet meet this bar. That is a description of the industry's current state, not an excuse for the next audit.