Documentation Index
Fetch the complete documentation index at: https://cowswap-mintlify-docs-quality-audit-1774257282.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
- Can be used to generate multiple discrete order (self-expressing)
- Assess a proposed order against a set of conditions (self-validating)
ComposableCoW handles:
- Authorization (multiple owners, with multiple orders per owner)
- Order relaying (watch-towers)
Architecture
The following principles have been employed in the architectural design:O(1)gas-efficiency fornprogrammatic order creation / replacement / deletion- Programmatic orders SHOULD behave the same as a discrete order for EOAs (self-custody of assets, i.e. “wrapper” contracts not required)
- Programmatic orders SHOULD be optimized towards statelessness - pass required data via
calldata - MAY enhance the Safe user experience when paired with
ExtensibleFallbackHandler🐮🔒
O(1) is achieved for n programmatic orders. This is achieved by storing the Merkle Tree root on-chain, and passing the Merkle Tree proof to the ComposableCoW contract. This allows for O(1) gas efficiency for adding / removing programmatic orders.
For simplicity, single orders are also supported, however, this is NOT recommended for large n as the gas efficiency is O(n).
Execution context
As there are many nested contracts, it’s important for a callee to know some context from the caller. To achieve this, ComposableCoW passes abytes32 variable ctx to the callee, such that:
Programmatic order verification flow
The following flowchart illustrates the programmatic order verification flow (assumingsafe):
Settlement execution path
CoW Protocol order settlement execution path (assumingsafe):
Signature verification
ComposableCoW implementsISafeSignatureVerifier, which allows for delegated ERC-1271 signature validation with an enhanced context:
| Parameter | Description |
|---|---|
safe | Contract that is delegating signing |
sender | msg.sender that called isValidSignature on safe |
_hash | Order digest |
domainSeparator | See EIP-712 |
typeHash | Not used |
encodeData | ABI-encoded GPv2Order.Data (per EIP-712) to be settled |
encodeData | ABI-encoded GPv2Order.Data to be settled |
ComposableCoW, the delegating contract may either:
- Be a Safe and use
ExtensibleFallbackHandlerthat allows forEIP-712domain delegation to a custom contract (i.e.ComposableCoW); or - Implement
ERC-1271and within theisValidSignaturemethod, callComposableCoW.isValidSafeSignature().
ComposableCoW can also be used with contracts other than Safe. The
ERC1271Forwarder abstract contract has been provided to allow for new contracts to easily integrate with ComposableCoW.If using
ExtensibleFallbackHandler, and the CoW Protocol settlement domain is delegated to ComposableCoW, ALL ERC-1271 signatures will be processed by ComposableCoW.Discrete order verifiers
A programmatic order that verifies a proposed discrete order against a set of conditions shall implement theIConditionalOrder interface.
| Parameter | Description |
|---|---|
owner | The owner of the programmatic order |
sender | msg.sender context calling isValidSignature |
_hash | EIP-712 order digest |
domainSeparator | EIP-712 domain separator |
ctx | Execution context |
staticInput | Programmatic order type-specific data known at time of creation for all discrete orders |
offchainInput | Programmatic order type-specific data NOT known at time of creation for a specific discrete order (or zero-length bytes if not applicable) |
order | The proposed discrete order’s GPv2Order.Data struct |
ComposableCoW is responsible for checking that all values EXCLUDING
offchainInput belong to an order that was previously registered on-chain.Discrete order generators
A programmatic order that generates discrete orders shall implement theIConditionalOrderGenerator interface.
BaseConditionalOrder contract has been provided that implements the IConditionalOrderGenerator interface, and necessary boilerplate.
Swap guards
A swap guard is a contract that implements theISwapGuard interface, and if set by an owner, will be called by ComposableCoW prior to calling verify on the programmatic order.
This allows for owner-wide restrictions on the programmatic order, such as:
receiverlock (i.e.receiverMUST beowner)- Token whitelist
ISwapGuard interface is as follows:
| Parameter | Description |
|---|---|
order | Proposed discrete order |
ctx | Execution context |
params | ConditionalOrderParams |
offchainInput | Programmatic order type-specific data NOT known at time of creation for a specific discrete order (or zero-length bytes if not applicable) |
Guarantees and Invariants
- CoW Protocol’s settlement contract enforces single-use orders, i.e. NO
GPv2Ordercan be filled more than once - For merkle trees,
H(ConditionalOrderParams)MUST be a member of the merkle treeroots[owner] - For single orders,
singleOrders[owner][H(ConditionalOrderParams)] == true
Data Types and Storage
ConditionalOrderParams
A programmatic order is defined by the following data:
| Field | Description |
|---|---|
handler | The contract implementing the programmatic order logic |
salt | Allows for multiple programmatic orders of the same type and data |
staticData | Data available to ALL discrete orders created by the programmatic order |
All of the above fields are verified by
ComposableCoW to be valid, prior to calling the verify method on the handler (IConditionalOrder).When used with Merkle Trees and a cryptographically-secure random
salt, the programmatic order is effectively private (until a discrete order cut from this programmatic order is broadcast to the CoW Protocol API).PayloadStruct
This is the data passed to ComposableCoW via the payload parameter of isValidSafeSignature:
| Field | Description |
|---|---|
proof | Merkle Tree proof (if applicable, zero length otherwise) |
params | ConditionalOrderParams |
offchainInput | Off-chain input (if applicable, zero length otherwise) |
proof to zero-length, this indicates to ComposableCoW that the order is a single order, and not part of a Merkle Tree.
Proof
Some services pick up new programmatic orders automatically from on-chain events.
The proof data can be emitted on-chain, but it can also be retrieved from other supported on-chain services.
The location field signals where this data can be retrieved.
The
Proof.location is intentionally not made an enum to allow for future extensibility as other proof locations may be integrated.| Field | Description |
|---|---|
location | An integer representing the location where to find the proofs |
data | location implementation specific data for retrieving the proofs |
Locations
| Name | location | data |
|---|---|---|
PRIVATE | 0 | bytes("") |
LOG | 1 | abi.encode(bytes[] order) where order = abi.encode(bytes32[] proof, ConditionalOrderParams params) |
SWARM | 2 | abi.encode(bytes32 swarmCac) |
WAKU | 3 | abi.encode(string protobufUri, string[] enrTreeOrMultiaddr, string contentTopic, bytes payload) |
IPFS | 5 | abi.encode(bytes32 ipfsCid) |
JSON schema for proofs
JSON schema for proofs
It is expected that the proofs retrieved, excluding
PRIVATE and LOG conform to a JSON schema:roots
Using an owner as a key, the roots mapping stores the Merkle Tree root for the programmatic orders of that owner.
singleOrders
Using owner, ctx as a key, the singleOrders mapping stores the single orders for the programmatic orders of that owner.
cabinet
Using owner, ctx as a key, the cabinet mapping stores the programmatic order-specific data for the programmatic orders of that owner.
swapGuards
Using owner as a key, the swapGuards mapping stores the swap guards for the programmatic orders of that owner.
Functions
For users
setRoot / setRootWithContext
A safe or owner calls the respective setter method to set the Merkle Tree root for their programmatic orders:
| Parameter | Description |
|---|---|
root | Merkle Tree root of programmatic orders |
proof | Proof |
factory | An IValueFactory that will be used to populate the ctx storage slot (if applicable) |
data | Data to be passed to the factory to populate the ctx storage slot (if applicable) |
MerkleRootSet(address indexed owner, bytes32 root, Proof proof).
ComposableCoW will NOT verify the proof data passed in via the proof parameter for setRoot. It is the responsibility of the client and watch-tower to verify / validate this.create / createWithContext
The owner calls the respective setter method to create a programmatic order:
| Parameter | Description |
|---|---|
params | ConditionalOrderParams |
factory | An IValueFactory that will be used to populate the ctx storage slot (if applicable) |
data | Data to be passed to the factory to populate the ctx storage slot (if applicable) |
dispatch | If true, broadcast the ConditionalOrderCreated event |
remove
The owner calls the remove(bytes32 singleOrderHash) method to remove a programmatic order:
| Parameter | Description |
|---|---|
singleOrderHash | H(ConditionalOrderParams) |
setSwapGuard
The owner calls the setSwapGuard(ISwapGuard guard) method to set a swap guard for a programmatic order:
| Parameter | Description |
|---|---|
swapGuard | The swap guard contract |
For watch-towers
getTradeableOrderWithSignature
A watch-tower calls the getTradeableOrderWithSignature method to get a discrete order that is tradeable on CoW Protocol:
- Determine if
owneris asafe, and provide theSignatureVerifierMuxerappropriate formatting for theERC-1271signature submission to CoW Protocol. - If not a
safe, format theERC-1271signature according toabi.encode(domainSeparator, staticData, offchainData).
ComposableCoW will:
- Check that the order is authorized.
- Check that the order type supports discrete order generation (i.e.
IConditionalOrderGenerator) by usingIERC165(andrevertif not, allowing the watch-tower to prune invalid monitored programmatic orders). - Call
getTradeableOrderon the handler to get the discrete order (GPv2Order.Data). - Generate the signing data as above.
Indexing
ConditionalOrderCreated(address indexed owner, ConditionalOrderParams params)MerkleRootSet(address index owner, bytes32 root, Proof proof)
Custom error codes
ProofNotAuthed()- the proof is not authorized (merkle root incorrect)SingleOrderNotAuthed()- the single order is not authorizedSwapGuardRestricted()- the swap guard did not pass verificationInvalidHandler()- the handler is not a valid programmatic orderInvalidFallbackHandler()- the fallback handler is not a valid programmatic orderInterfaceNotSupported()- the handler does not support theIConditionalOrderinterface
A programmatic order developer SHOULD use these error codes to ensure that the programmatic order is well-formed and not garbage collected / rate limited by a watch-tower.
OrderNotValid(string)- thestaticInputparameters are not valid for the programmatic orderPollTryNextBlock(string)- signal to a watch-tower that polling should be attempted againPollTryAtBlock(uint256 blockNumber, string)- signal to a watch-tower that polling should be attempted again at a specific block numberPollTryAtEpoch(uint256 timestamp, string)- signal to a watch-tower that polling should be attempted again at a specific epoch (unix timestamp)PollNever(string)- signal to a watch-tower that the programmatic order should not be polled again (delete)
Off-chain
Watch-tower
As these orders are not automatically indexed by the CoW Protocol, there needs to be some method of relaying them to the Order Book API for inclusion in a batch. This is the responsibility of a watch-tower. CoW Protocol runs a watch-tower that will monitor theConditionalOrderCreated event, and relay the discrete orders to the Order Book API.
There is also a DAppNode package for running a watch-tower.