Programmatic Order Framework
Programmatic Order Framework is a framework for smoothing the developer experience when building conditional orders on CoW Protocol. Conditional orders are a subset of ERC-1271
smart contract orders. It allows one to create conditional orders that:
Can be used to generate multiple discrete order (self-expressing)
Assess a proposed order against a set of conditions (self-validating)
The framework makes boilerplate code for conditional orders a thing of the past, and allows developers to focus on the business logic of their order. Programmatic Order Framework 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 forn
conditional order creation / replacement / deletionConditional orders SHOULD behave the same as a discrete order for EOAs (self-custody of assets, ie. "wrapper" contracts not required)
Conditional orders SHOULD be optimized towards statelessness - pass required data via
calldata
MAY enhance the
Safe
user experience when paired withExtensibleFallbackHandler
🐮🔒
By using Merkle Trees, the gas efficiency of O(1)
is achieved for n
conditional 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 conditional 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 a bytes32
variable ctx
to the callee, such that:
Having this context also allows for conditional orders / merkle roots to use this as a key in a mapping, to store conditional order-specific data.
Conditional order verification flow
The following flowchart illustrates the conditional order verification flow (assuming safe
):
isValidSafeSignaturevalidinvalidvalidinvalidvalidinvalidvalidinvalidExtensible Fallback Handler: SignatureVerifierMuxerCheck Authorization: MerkleRoot Proof & ConditionalOrderParamsSwapGuard:verifyRevertIConditionalOrder:verifyCheck Authorization: Single Order ConditionalOrderParamsReturn ERC1271 Magic
Settlement execution path
CoW Protocol order settlement execution path (assuming safe
):
call: isValidSignaturedelegatecall: isValidSignaturecall: isValidSignaturecall: isValidSafeSignaturecall: verifyGPv2SettlementSafeProxySafeSingleton : FallbackManagerExtensibleFallbackHandler : SignatureVerifierMuxerComposableCoWIConditionalOrder
Signature verification
Programmatic Order Framework implements ISafeSignatureVerifier
, which allows for delegated ERC-1271
signature validation with an enhanced context:
Parameter | Description |
| Contract that is delegating signing |
|
|
| Order digest |
| See |
| Not used |
| ABI-encoded |
| ABI-encoded |
In order to delegate signature verification to ComposableCoW
, the delegating contract may either:
Be a
Safe
and useExtensibleFallbackHandler
that allows forEIP-712
domain delegation to a custom contract (ie.ComposableCoW
); orImplement
ERC-1271
and within theisValidSignature
method, callComposableCoW.isValidSafeSignature()
.
TIP
Programmatic Order Framework 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 Programmatic Order Framework.
NOTE
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 conditional order that verifies a proposed discrete order against a set of conditions shall implement the IConditionalOrder
interface.
Parameter | Description |
| The owner of the conditional order |
|
|
|
|
|
|
| |
| Conditional order type-specific data known at time of creation for all discrete orders |
| Conditional order type-specific data NOT known at time of creation for a specific discrete order (or zero-length bytes if not applicable) |
| The proposed discrete order's |
DANGER
Order implementations MUST validate / verify offchainInput
!
CAUTION
The verify
method MUST revert
with OrderNotValid(string)
if the parameters in staticInput
do not correspond to a valid order.
NOTE
All values EXCLUDING offchainInput
are verified by Programmatic Order Framework prior to calling an order type's verify
.
Discrete order generators
A conditional order that generates discrete orders shall implement the IConditionalOrderGenerator
interface.
To simplify the developer experience, a BaseConditionalOrder
contract has been provided that implements the IConditionalOrderGenerator
interface, and necessary boilerplate.
Swap guards
A swap guard is a contract that implements the ISwapGuard
interface, and if set by an owner
, will be called by ComposableCoW
prior to calling verify
on the conditional order.
This allows for owner
-wide restrictions on the conditional order, such as:
receiver
lock (ie.receiver
MUST beowner
)Token whitelist
The ISwapGuard
interface is as follows:
Parameter | Description |
| Proposed discrete order |
| |
| |
| Conditional 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, ie. NO
GPv2Order
can be filled more than onceFor 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
ConditionalOrderParams
A conditional order is defined by the following data:
Field | Description |
| The contract implementing the conditional order logic |
| Allows for multiple conditional orders of the same type and data |
| Data available to ALL discrete orders created by the conditional order |
NOTE
All of the above fields are verified by ComposableCoW
to be valid, prior to calling the verify
method on the handler (IConditionalOrder
).
TIP
When used with Merkle Trees and a cryptographically-secure random salt
, the conditional order is effectively private (until a discrete order cut from this conditional order is broadcast to the CoW Protocol API).
CAUTION
H(ConditionalOrderParams)
MUST be uniqueNot setting
salt
to a cryptographically-secure random value MAY result in leaking information or hash collisions
PayloadStruct
PayloadStruct
This is the data passed to ComposableCoW
via the payload
parameter of isValidSafeSignature
:
Field | Description |
| Merkle Tree proof (if applicable, zero length otherwise) |
| |
| Off-chain input (if applicable, zero length otherwise) |
By setting proof
to zero-length, this indicates to ComposableCoW
that the order is a single order, and not part of a Merkle Tree.
Proof
Proof
NOTE
The Proof.location
is intentionally not made an enum
to allow for future extensibility as other proof locations may be integrated.
Field | Description |
| An integer representing the location where to find the proofs |
|
|
Locations
Name |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
roots
roots
Using an owner
as a key, the roots
mapping stores the Merkle Tree root for the conditional orders of that owner
.
singleOrders
singleOrders
Using owner, ctx
as a key, the singleOrders
mapping stores the single orders for the conditional orders of that owner
.
cabinet
cabinet
Using owner, ctx
as a key, the cabinet
mapping stores the conditional order-specific data for the conditional orders of that owner
.
swapGuards
swapGuards
Using owner
as a key, the swapGuards
mapping stores the swap guards for the conditional 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 conditional orders:
Parameter | Description |
| Merkle Tree root of conditional orders |
| |
| An |
| Data to be passed to the |
When a new merkle root is set, emits MerkleRootSet(address indexed owner, bytes32 root, Proof proof)
.
NOTE
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 conditional order:
Parameter | Description |
| |
| An |
| Data to be passed to the |
| If |
remove
The owner
calls the remove(bytes32 singleOrderHash)
method to remove a conditional order:
Parameter | Description |
|
|
setSwapGuard
The owner
calls the setSwapGuard(ISwapGuard guard)
method to set a swap guard for a conditional order:
Parameter | Description |
| 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:
This function will:
Determine if
owner
is asafe
, and provide theSignatureVerifierMuxer
appropriate formatting for theERC-1271
signature submission to CoW Protocol.If not a
safe
, format theERC-1271
signature according toabi.encode(domainSeparator, staticData, offchainData)
.
Subsequently, ComposableCoW
will:
Check that the order is authorized.
Check that the order type supports discrete order generation (ie.
IConditionalOrderGenerator
) by usingIERC165
(andrevert
if not, allowing the watch-tower to prune invalid monitored conditional orders).Call
getTradeableOrder
on 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 conditional orderInvalidFallbackHandler()
- the fallback handler is not a valid conditional orderInterfaceNotSupported()
- the handler does not support theIConditionalOrder
interface
KEEP YOUR ORDERS WATCHED
A conditional order developer SHOULD use these error codes to ensure that the conditional order is well-formed and not garbage collected / rate limited by a watch-tower.
OrderNotValid(string)
- thestaticInput
parameters are not valid for the conditional 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 conditional 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 the ConditionalOrderCreated
event, and relay the discrete orders to the Order Book API.
There is also a DAppNode package for running a watch-tower.
Last updated