owned this note
owned this note
Published
Linked with GitHub
# `ComposableCoW` Architecture
The following principles have been employed in the architectural design:
1. `O(1)` efficiency for `n` order creation / replacement / deletion.
2. Conditional orders **SHOULD** behave the same as a discrete order for EOAs (self-custody of assets, no need to create "container" contracts).
3. Orders are _stateless_, with any required data passed via `calldata`.
4. Make CoW a first class citizen of `Safe` and vice-versa 🐮🔒.
## Assumptions
- CoW Protocol enforces single-use orders, ie. no `GPv2Order` can be filled more than once.
## Definitions
*Conditional Order*: A logical construct representing `0..n` discrete orders.
*Discrete Order*: An order submitted to the CoW Protocol API (ie. `GPv2Order.Data`), ie. a **single** `orderUid` as defined by CoW Protocol.
For the purposes of this documentation, if the _type_ of order is not specified, it shall be assumed to be a _conditional order_.
## Use cases
Currently a _smart contract_ interacting with CoW is required to either:
1. Call `GPv2Settlement.setPreSignature(orderUid)` (API signing type "pre-sign"); or
2. Implement `isValidSignature(bytes32,bytes)` (API signing type "eip1271"), where the `bytes32` parameter passed is the EIP-712 digest of the `GPv2Order.Data`.
Presently orders have been spawning new contracts on chain, necessitating the handling of basic `retrieve` / `cancel` functionality to recover assets. `Safe` already provides a best-in-class for this purpose, so let's not reinvent the wheel! 🛞
Use cases that this revised architecture seeks to enable include:
* Automatically swap token ABC for XYZ above a defined threshold balance of ABC.
* Good after time (`GAT`) orders (discrete orders, conditional on a starting time).
* TWAP by breaking orders into `n x GAT` orders.
* Private conditional orders (trailing stop loss)
* Wait4CoW orders (only matching an order with other CoW traders)
* Doing all the above simultaneously (`n x conditional orders`)
## Current Architecture
> The architectural analysis is from the perspective of using `Safe` with CoW Protocol.
All CoW Protocol discrete orders are "signed". The signing methods are:
* EIP-712 (gasless)
* Eth-Sign (gasless)
* EIP-1271 (gasless)
* Pre-Sign (tx)
```mermaid
flowchart TD
A[GPv2Settlement] -->|EOA Only| B[EIP-712]
A -->|EOA Only| C[Eth-Sign]
A -->|Contract Only| D[EIP-1271]
A -->|Contract & EOA| E[Pre-Sign]
```
Signing via `Safe` is done via `approvedHash` or `threshold` signatures, as follows:
```mermaid
flowchart TD
A[Safe] -->|delegatecall SigningLib| B[approvedHash - tx]
A -->|manual assembly of signatures| C[threshold - gasless]
```
Therefore, when using `Safe`:
1. **EACH** discrete order (ie. `orderUid`) must be approved/signed individually by the `Safe`.
2. **ONLY** `threshold` type EIP-1271 signatures are gasless from a CoW Protocol and `Safe` perspective.
The result is a significantly constrained user experience.
## `ComposableCoW`
This contract implements `ISafeSignatureVerifier`, designed to be used with `ExtensibleFallbackHandler` - [a new `FallbackHandler` for `Safe`](https://hackmd.io/-nLuF3JIRyuS5w864_mbrg). `ExtensibleFallbackHandler` provides a third method of EIP-1271 validation for `safe` - it allows delegating an EIP-712 domain to a **custom contract**.
`ComposableCoW` therefore will process **ALL** EIP-1271 signatures for the `GPv2Settlement.domainSeparator()` EIP-712 domain.
Therefore, `ComposableCoW` is responsible for:
1. Discrete order verification routing of `n` conditional orders per `owner`.
2. Lookup discrete orders from a conditional order (watch-towers).
### Order Data
Each conditional order has the following properties/data available at **settlement** depending on implementation:
1. `handler` - the contract that will verify the conditional order's parameters.
2. `salt` - allows for multiple conditional orders of the same type and data.
3. `staticData` - data available to **ALL** *discrete* orders created by the conditional order.
4. `offchainData` - data *optionally* provided from off-chain to a *discrete* order.
As all of these, excluding `offchainData` are known at creation time, they are grouped together in the struct `ConditionalOrderParams`:
```solidity
struct ConditionalOrderParams {
IConditionalOrder handler;
bytes32 salt;
bytes staticData;
}
```
`ConditionalOrderParams` has the properties:
1. `H(ConditionalOrderParams)` **MUST** be unique.
2. `salt` **SHOULD** be set to a cryptographically-secure random value to ensure (1) **WHEN REQUIRED**.
3. Provides order secrecy (until a discrete order cut from this conditional order is broadcast to the CoW Protocol API).
4. All values are **verified by `ComposableCoW`** prior to calling an order type's `verify`.
`offchainInput` has the properties:
1. Allows input (such as from an off-chain oracle) that is **NOT** known at order creation time.
2. **NOT** verified by `ComposableCoW`. Validation is the responsibility of the handler.
**WARNING: Order implementations MUST validate / verify `offchainInput`!**
### Storage
```solidity
mapping (address => bytes32) roots; // For Merkle roots (n conditional orders)
mapping (address => mapping (bytes32 => bool)) singleOrders; // Per conditional order (if not in Merkle root)
```
### Settlement Execution Path
CoW Protocol order settlement execution path (assuming `safe`):
```mermaid
flowchart TD
A[GPv2Settlement] -->|call: isValidSignature| B[SafeProxy]
B -->|delegatecall: isValidSignature| C[SafeSingleton : FallbackManager]
C -->|call: isValidSignature| D[ExtensibleFallbackHandler : SignatureVerifierMuxer]
D -->|call: isValidSafeSignature| E[ComposableCoW]
E -->|call: verify| F[IConditionalOrder]
```
Implementing the `ISafeSignatureVerifier` means that `ComposableCoW` will implement `isValidSafeSignature`:
```solidity
function isValidSafeSignature(
Safe safe,
address sender,
bytes32 _hash,
bytes32 domainSeparator,
bytes32, // typeHash
bytes calldata encodeData,
bytes calldata payload
) external view override returns (bytes4 magic);
```
1. `encodeData`: The ABI-encoded `GPv2Order.Data` for the order being validated **during a settlement**.
2. `payload`: `abi.encode(bytes32[] proof, ConditionalOrderParams params, bytes offchainInput)`
3. `typeHash` is ignored as CoW Protocol only has one `typeHash` (`GPv2Order.Data`).
### Settlement Validity
The following security constraints are enforced by `ComposableCoW`:
1. `ConditionalOrderParams` **MUST** be authorised for use by the `owner`.
```mermaid
flowchart TD
A[Extensible Fallback Handler: SignatureVerifierMuxer] -->|isValidSafeSignature| B[Check Authorisation: MerkleRoot \n Proof & ConditionalOrderParams]
B -->|valid| V[IConditionalOrder:verify]
B -->|invalid| C(Check Authorisation: Single Order \n ConditionalOrderParams)
C -->|valid| V
C -->|invalid| I[Revert]
V -->|valid| T[Return ERC1271 Magic]
V -->|invalid| I
```
#### Merkle Root
**Leaf**: `H(ConditionalOrderParams)`, ie. `H(handler || salt || data)`
**Properties**:
* Achieves `O(1)` gas efficiency for add / remove `n` conditional orders.
**Methodology**:
The caller passes `abi.encode(bytes32[] proof, ConditionalOrderParams params)` as the `payload` parameter where `proof` contains the Merkle Tree proof.
**Authorisation**:
Order **O** is valid if `proof` asserts that leaf `H(params)` is a member of merkle tree `roots[owner]`.
#### Single Order
**Properties**:
* Simpler method for enabling an order (less tooling overhead).
* Gas expensive (`n` x `SSTORE` per order).
**Methodology**:
The caller passes `abi.encode(bytes32[] proof, ConditionalOrderParams params, bytes offchainInput)` as the `payload` parameter where `proof` is a zero-length `bytes32[]`.
**Authorisation**:
Order **O** is valid if `singleOrders(owner, H(params)) == true`.
### Add / Remove Orders
The `owner` uses the applicable setter method depending on how they wish to specify the order.
#### Merkle Root
The `owner` calls the `setRoot(bytes32 root, Proof calldata proof)` setter method.
```solidity
struct Proof {
uint256 location;
bytes data;
}
```
* `root`: Merkle Tree of conditional orders (leaves = `H(params)`).
* `proof`: Where watch towers may locate the proofs from.
**Private:** `Proof({location: 0, data: bytes("")})` - no proofs available to watch-towers.
**Log:** `Proof.location = 1` and `Proof.data = abi.encode(bytes[] order)` where `order = abi.encode(bytes32[] proof, ConditionalOrderParams params)`
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.
**NOTE**: The `Proof.location` is intentionally not made an `enum` to allow for future extensibility as other proof locations may be integrated, including but not limited to Swarm, Waku, IPFS etc.
#### Single Order
The `owner` calls the respective setter method:
* Create orders: `create(ConditionalOrderParams params, bool dispatch)`
If the `owner` wants to make the order public, `dispatch` is set `true`, and the order creation will result in an emission of `ConditionalOrderCreated(address indexed owner, ConditionalOrderParams params)`.
* Removing order: `remove(bytes32 orderHash)`
To remove an order, set `orderhash = H(params)` from the initial `ConditionalOrderCreated`. No event is emitted, as the order is invalidated (any watch tower trying to cut discrete orders will see the `getTradeableOrder()` will revert).
### Discrete Order Generation
The default is to use a `Factory` base pattern for conditional orders, where based on an order's properties, it is able to generate a tradeable order (`GPv2Order.Data`).
To do this, an order implements the `IConditionalOrderFactory` interface. Following this pattern, a developer **MUST** ensure:
`H(IConditionalOrderFactory.getTradeableOrder(owner,sender,params,offchainInput)) == _hash`
Where `_hash` is the `_hash` passed into the `isValidSignature()` call.
#### Advanced conditional orders
These implement the top-level `IConditionalOrder` interface, which only provides a raw `verify` method, and doens't allow for order generation.
### Watch Towers
As these orders are not automatically indexed by the CoW Protocol API, there needs to be some method of relaying them to CoW for inclusion in a batch. This is done by reference to the events emitted by `ComposableCoW`:
* `ConditionalOrderCreated(address indexed owner, ConditionalOrderParams params)`
* `MerkleRootSet(address index owner, bytes32 root, Proof proof)`
The contract that _emits_ the above methods shall provide a method:
```solidity
function getTradeableOrderWithSignature(
address owner,
bytes32[] proof,
ConditionalOrder params,
bytes offchainInput
) external view (GPv2Order.Data memory, bytes memory signature);
```
In the context of `ComposableCoW` this will:
1. Determine if `owner` is a `safe`, and provide the `SignatureVerifierMuxer` appropriate formatting for the EIP-1271 `signature` submission to CoW Protocol.
2. If not a `safe`, format the EIP-1271 `signature` according to `abi.encode(domainSeparator, staticData, offchainData)`.
`ComposableCoW` will:
1. Check that the order is authorised.
2. Check that the order type supports discrete order generation (ie. `IConditionalOrderFactory`) by using `IERC165` (and `revert` if not, allowing the watch-tower to prune invalid monitored conditional orders).
3. Call `getTradeableOrder` on the handler to get the `GPv2Order.Data`
4. Generate the signing data as above.
**NOTE:** There is no need to emit these events when orders are cancelled / removed as calling the `getTradeableOrderWithSignature` will yield a `revert` with a custom `error` indicating the order will never be valid with the current state.
### Interfaces
#### `IConditionalOrder`
This is the root-level interface for conditional orders, implementing:
```solidity
function verify(
address owner,
address sender,
bytes32 hash,
bytes32 domainSeparator,
bytes calldata staticInput,
bytes calldata offchainInput
GPv2Order.Data calldata order,
) external view;
```
**CAUTION:** The `verify` method **MUST** `revert` if the parameters specified do not correspond to a valid order.
#### `IConditionalOrderFactory`
Allows for generation of discrete orders for submission to CoW Protocol by implementing `ERC165` and:
```solidity
function getTradeableOrder(
address owner,
address sender,
bytes calldata staticInput,
bytes calldata offchainInput
) external view returns (GPv2Order.Data memory);
```