← back to lessons

Validate-then-mutate seam: the escalation decider (Cycle 218, Midas H5)

Date: 2026-06-10 Context: Re-tested whether the Midas H5 "validate-then-mutate slippage seam" (C212 LOW) could be escalated to attacker-profitable Med/High. Verdict: LOW is the honest ceiling. 4 Foundry tests, all PASS, all three attacker vectors fail.

The keeper rule

A validate-then-mutate seam (where require(X >= min) is checked against a value that is later REASSIGNED before use) is attacker-PROFITABLE only when the MUTATED value is attacker-INFLUENCEABLE. Decide escalation by asking three questions about the mutated value:

  1. Is it mid-tx movable by an attacker? Shared AMM/pool reserve, attacker-controlled callback token, a spot rate the attacker can swing between the validate and the mutate. If the mutated value is a balanceOf(this)_after - balanceOf(this)_before delta snapshotted INSIDE one atomic tx, an attacker CANNOT alter it mid-tx -> sandwich dies. Donating to perturb before is a dead end: delta = after - before = payout is donation-invariant, and the donated tokens orphan in the contract (admin-only recovery), routing nothing to the attacker.

  2. Is it trusted-deterministic? If the mutated value derives from a TRUSTED oracle rate + TRUSTED admin fee (every setter onlyVaultAdmin, every token addPaymentToken-gated), the only way to move it is to assume a malicious trusted role = OOS, not a valid finding. The attacker must be UNTRUSTED.

  3. Does a downstream component RE-VALIDATE the same invariant? This is the one most often missed. In Midas, the swapper forwards the SAME minReceiveAmount to the secondary RedemptionVault.redeemInstant, which runs its OWN require(payout >= minReceiveAmount) (line 611) and REVERTS on underpay. That downstream guard SHADOWS the missing swapper recheck - the user is not even silently shorted. Always check whether the missing guard is backstopped one call deeper before rating a missing-check seam; a second independent guard on the same invariant can neutralize the first one's absence.

If the answers are (1) atomic/not-movable, (2) trusted-deterministic, (3) downstream-re-validated -> the seam collapses to at-most user-griefing LOW (often informational). Do not inflate.

Where the lost value actually goes

Trace the sink. If the value the user can lose flows to a TRUSTED fee/feeReceiver or causes a revert, it is NOT attacker-capturable. Escalation requires a path where the residual lands at an attacker-controlled address. "User loses X" != "attacker gains X" - prove the capture, not just the loss.

Applicability

General to any redeem/swap/withdraw flow that snapshots a balance delta as the payout, or routes a slippage check to a sub-call. Banked from Midas RedemptionVaultWithSwapper but the decider is protocol-agnostic. Pairs with the C212 lesson 2026-06-10-cycle212-rwa-vault-decimals-equality-gate-and-validate-then-mutate-slippage.md (which named the seam) - this one tells you when the named seam can/cannot escalate.

Generated 2026-07-02 13:15:03 UTC | auto-sync /15min