evm-audit-notes

Pattern: Remaining-Amount Invalidator (Value-Packed State Machine)

Pattern notes from reading 1inch limit-order-protocol’s RemainingInvalidatorLib.sol + OrderMixin.sol fill path. Part of the working notes.

The Pattern

Partially-fillable orders need two pieces of state per (maker, orderHash): is this order alive and how much is left. The obvious design — a struct or two mappings — costs two SSTOREs on first touch. 1inch’s design packs both into one uint256 with three semantics:

stored value == 0            → order is NEW (never filled)     [mapping default: zero storage cost]
stored value == ~uint256(0)  → order fully filled / invalidated
stored value == ~remaining   → partially filled, remaining left

Reads decode with bitwise NOT: remaining = ~value (in unchecked — NOT never overflows). Writes store the complement of the new remaining. Because ~0 == maxUint256, a partially filled order reaching zero remaining and an explicit cancellation (fullyFilled()) converge to the same stored value — one sentinel encodes both “done” paths.

The zero-value trick is the elegant part: Solidity mappings default to zero, so the most common state in a world of millions of orders (fresh, never touched) costs no storage at all until first fill.

What to Check When You See This Pattern

Generalization

“Value-packed state machines” — one storage word encoding both a lifecycle state and a continuous quantity — appear as:

Variant Zero means Complement/sentinel means Seen in
Remaining invalidator (this pattern) fresh order terminal (filled/cancelled) 1inch LOP
Inverse nonce accumulators all epochs valid bitmap of invalidated epochs Permit2, CowSwap
balanceOf as allowance proxy no position — ERC-4626 donation variants

The audit instinct: whenever a single word is doing double duty, enumerate every value it can hold, then check that each reader’s behavior at each value matches what the writer intended — especially the values that arise from arithmetic convergence (like ~0) rather than explicit writes.