Signature validity is resolved from live state, so a code-bearing maker holds a revocable option on every order they sign
Signatures are verified through live state, so a maker who acquires code between signing and settlement can switch to ERC-1271 and revoke an order a counterparty has already committed against.
Description
_validateOrder resolves a maker's signature at settlement time through
SignatureChecker.isValidSignatureNow:
if (!SignatureChecker.isValidSignatureNow(order.maker, digest, signature)) {revert InvalidSignature(digest, order.maker);}
OpenZeppelin selects the verification path from live state — signer.code.length == 0 chooses
ECDSA, otherwise ERC-1271 — and the library's own header states the consequence:
Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus change through time. It could return true at block N and false at block N+1 (or the opposite).
The Order struct carries no field pinning which scheme the maker committed to, so for a
code-bearing maker a signature is not a commitment but a standing offer they may withdraw at
the moment of settlement. A contract maker whose isValidSignature reads a flag it owns can
advertise at the top of the book, let a counterparty be matched, and flip the flag — one
SSTORE — so the pair fails. The counterparty cannot cancel a matched order and is offline by
design.
An EOA maker reaches the same position by submitting an EIP-7702 delegation, which makes
code.length non-zero and invalidates all of their outstanding ECDSA signatures at once.
Rated Low because the running system's makers are reported as MPC EOAs with plain 65-byte ECDSA signatures, so the ERC-1271 path is contract-supported but unexercised today. It stops being latent if smart accounts are adopted, which is a documented pre-mainnet decision.
Impact
A code-bearing maker holds a revocable option on every order they have signed: they can advertise at the top of the book, let a counterparty be matched, and invalidate the signature at the moment of settlement. The counterparty cannot cancel a matched order and is offline by design, so they carry the failure. An EOA maker reaches the same position by submitting an EIP-7702 delegation, which invalidates all of their outstanding signatures at once.
Recommendation
Bind the validation scheme into the signature so it cannot change after signing:
struct Order {address maker;Side side;+ bool makerIsContract;uint256 tokenId;...}
with ORDER_TYPEHASH, hashOrder and the JSON artifact updated to match, and in
_validateOrder:
-if (!SignatureChecker.isValidSignatureNow(order.maker, digest, signature)) {+bool ok = order.makerIsContract+ ? SignatureChecker.isValidERC1271SignatureNow(order.maker, digest, signature)+ : ECDSA.recover(digest, signature) == order.maker;+if (!ok) {revert InvalidSignature(digest, order.maker);}
The false branch removes revocability for the entire EOA maker population, which is the
documented production case, and keeps working for a 7702-delegated EOA because ecrecover
still resolves. The true branch leaves genuine contract makers on the revocable path, which
is inherent to ERC-1271 and cannot be closed at this layer — bounding it there is an off-chain
reputation question rather than a contract one.
Resolution
The verification scheme is now a signed field of the order, so it is fixed at signing and cannot
be swapped underneath an order a counterparty has already committed against. Verified at
audit-v2.
Affected files
src/PlakxioSettlement.sol#L605-L618at commit5c38893