CowSettlementAssertion
Inherits: CowSettlementHelpers
Title: CowSettlementAssertion
Author: Phylax Systems
Example assertion bundle protecting a CoW Protocol (Gnosis Protocol v2) settlement contract against the two on-chain harms a malicious (or compromised) solver can cause that the settlement contract does NOT guard against itself.
The GPv2 settlement contract gates settle/swap behind an allowlist of bonded solvers,
enforces user signatures, limit prices, fill amounts and expiry, and forbids interactions
with the vault relayer. What it intentionally leaves open — solvers may run arbitrary
interactions and may dip into the contract’s own accumulated buffers — is exactly what these
two invariants cover. Neither uses a price oracle; both observe real token movements.
- Surplus protection (
assertSolverDoesNotExtractValue, persettle/swapcall): the settlement contract must not pay any of its tokens to the solver that authored the call. Solver rewards are paid out-of-band in COW; a settlement that transfers tokens to its own caller is siphoning batch surplus / buffer value that belongs to users or the DAO. - Inventory protection (
assertBufferConserved, per watched-token balance change): the settlement contract’s watched-token outflow must be explained by authorized DAO sweeps or by actual GPv2Tradevolume emitted by the settlement. This catches buffer drains that are not settlement volume, including the standing-approval class behind the February 2023 incident, while allowing normal settlements to use accumulated inventory. Limitations (documented intentionally, not bugs): without a reference price the bundle cannot judge whether surplus was “fairly” maximized, only that it did not flow to the solver; the surplus check assumes the solver address is not itself an order receiver in the same batch; andTradeevents do not expose receivers, so the buffer check reconciles settlement volume rather than receiver-level authorization.
Functions
constructor
constructor(
address settlement_,
address sweepRecipient_,
address[] memory bufferTokens_,
uint256 bufferToleranceBps_
) CowSettlementHelpers(settlement_, sweepRecipient_, bufferTokens_, bufferToleranceBps_);
triggers
Registers the surplus check against the solver-only entry points, and the buffer conservation check at transaction end and on watched-token balance changes.
The surplus check is call-scoped (it needs the per-call solver and the call’s Transfer logs). The buffer check is a transaction-envelope property, so it runs at tx end and on ERC20 balance changes for every watched token. The ERC20-change trigger lets the check fire for a drain transaction that never calls the settlement contract directly.
function triggers() external view override;
assertSolverDoesNotExtractValue
A settlement must not pay batch surplus to the solver that authored it.
Solvers are compensated off-chain in COW, never by the settlement contract transferring traded tokens back to its own caller. Any token (across the whole call frame, including nested solver interactions) moved from the settlement contract to the solver is treated as siphoned surplus / buffer value and trips the assertion. A failure means the solver directed value that should have reached users (as price improvement) or stayed in the DAO buffer to itself instead. This is the on-chain footprint of surplus theft that the settlement’s signature / limit-price checks cannot catch, because those only guarantee the user’s signed floor, not where any excess goes.
function assertSolverDoesNotExtractValue() external view;
assertBufferConserved
The settlement contract’s watched-token outflow must be explained by GPv2 settlement volume or an authorized DAO sweep.
Reconciles both gross ERC20 outflow and net PreTx/PostTx balance decrease for every
watched token. Normal settlements can legitimately use accumulated inventory, so GPv2
Trade event volume is allowed. Authorized sweeps to the configured recipient are also
allowed. Any remaining outflow — including an external contract exploiting a standing
approval on the settlement’s balance — trips the assertion.
function assertBufferConserved() external view;