Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions deployments/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Deployments manifest

`networks.json` is the canonical machine-readable record of framework
deployments, consumed by off-chain monitoring services and indexers as the
single source for where and what to index.

## Schema

```jsonc
{
"version": 1, // manifest schema version
"abiVersion": "2.0.0-dev", // contract ABI version the entries conform to
"networks": {
"<chainId>": {
"composableCow": "0x…", // registry address
"deployBlock": 12345678, // first block to index from
"topic0": {
"conditionalOrderCreated": "0x…",
"merkleRootSet": "0x…",
"conditionalOrderRemoved": "0x…",
"swapGuardSet": "0x…"
}
}
}
}
```

Rules:

- Entries are appended by the deployment pipeline; hand edits are reviewed
like code.
- `topic0` values are recomputed from the ABI at deploy time and MUST match
the compiled event signatures (consumers guard on these at startup).
- A chain absent from `networks` is not serviced.
5 changes: 5 additions & 0 deletions deployments/networks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"version": 1,
"abiVersion": "2.0.0-dev",
"networks": {}
}
21 changes: 15 additions & 6 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ ERC-1271 is the standard for smart contract signature verification, allowing con
The architecture separates two distinct execution paths:

1. **Settlement Path** — on-chain verification during trade execution (gas-sensitive).
2. **Polling Path** — off-chain queries by watch-towers (gas-irrelevant).
2. **Polling Path** — off-chain queries by off-chain monitoring service (historically called a watch-tower)s (gas-irrelevant).

This separation ensures settlement remains gas-efficient while providing rich metadata for off-chain infrastructure.

## Design Principles

1. **Single source of truth**: `generateOrder()` contains all order generation logic.
2. **Lean settlement**: No metadata structs; only constant string errors for debugging.
3. **Rich polling**: Structured results with scheduling hints for watch-towers.
3. **Rich polling**: Structured results with scheduling hints for monitoring services.
4. **Verdict and fill state are orthogonal**: handlers produce a *verdict* (`GeneratorResult`); observed fill state is composed by the registry, never by a handler.
5. **No code duplication**: Polling wraps the same core logic used by settlement.
6. **Handler purity**: handlers are pure functions of their explicit arguments (`owner`, `sender`, `ctx`, `staticInput`, `offchainInput`) and must not branch on `msg.sender`. Off-chain simulation soundness depends on this.
Expand Down Expand Up @@ -140,7 +140,8 @@ signature emitted iff verdict == POST and the order is postable:
- The handler's verdict and the observed fill state are orthogonal: a `POST` verdict coexists with `PARTIALLY_FILLED`, which is what lets a partially filled `partiallyFillable` order keep being posted until fully filled.
- Includes scheduling hints (`nextPollTimestamp` and `waitUntil`).
- Carries machine-readable reason selectors for debugging; names resolve from the handler ABI.
- If the swap guard restricts the order, the returned verdict is forced to `INVALID` with `reasonCode = SwapGuardRestricted.selector` and no signature is emitted.
- If the swap guard restricts the order, the registry reports it in the `PollResult.restriction` overlay (`SWAP_GUARD`) and no signature is emitted; the generator verdict is never overwritten - a guarded `POST` stays visible. Restriction is owner-reversible and its lifecycle is observable via `SwapGuardSet` (clear = `address(0)`).
- **`INVALID` is uniformly terminal.** With restriction expressed as an overlay, the only producer of `INVALID` is the handler's own `OrderNotValid`; consumers never need a reason-selector branch for control flow.
- `checkOrder()` returns the same composed `PollResult` through the same `_poll` helper (including the ERC-165 handler gate), without building the signature. The swap guard is not consulted by `checkOrder`; it is enforced at signature build time and during settlement.

## Error Types
Expand All @@ -151,12 +152,13 @@ a `bytes4 reasonCode`: the selector of a custom error the handler declares (e.g.
part of the handler ABI, so any ABI-aware consumer resolves a reasonCode to a name
without a bespoke table, and revert data stays fixed-size:

| Error | Meaning | Watch-tower Action |
| Error | Meaning | Monitoring service Action |
|-------|---------|-------------------|
| `OrderNotValid(bytes4)` | Permanent failure | Stop polling |
| `PollTryNextBlock(bytes4)` | Transient, retry soon | Poll next block |
| `PollTryAtTimestamp(uint256, bytes4)` | Wait for time | Schedule at timestamp |
| `PollTryAtBlock(uint256, bytes4)` | Wait for block | Schedule at block |
| `PollNeedsOffchainInput(bytes4)` | Needs constructed `offchainInput` | Acquire input (see `docs/discovery.md` on order modules) or park - do not re-poll empty on a schedule |

### Revert Decoding Policy

Expand All @@ -168,6 +170,7 @@ without a bespoke table, and revert data stays fixed-size:
| `PollTryNextBlock(code)` | `TRY_NEXT_BLOCK` | decoded code |
| `PollTryAtTimestamp(t, code)` | `WAIT_TIMESTAMP` (`waitUntil = t`) | decoded code |
| `PollTryAtBlock(b, code)` | `WAIT_BLOCK` (`waitUntil = b`) | decoded code |
| `PollNeedsOffchainInput(code)` | `NEEDS_INPUT` (acquire input or park - never a timed retry) | decoded code |
| `Panic(subcode)` | `TRY_NEXT_BLOCK` | `0x4e487b71` (the `Panic` selector) |
| `Error(string)` (bare `require`) | `TRY_NEXT_BLOCK` | `0x08c379a0` (the `Error` selector) |
| any other custom error | `TRY_NEXT_BLOCK` | the caught selector |
Expand Down Expand Up @@ -223,7 +226,7 @@ struct PollResult {

### Verdict Semantics

| Verdict | Meaning | Watch-tower Action |
| Verdict | Meaning | Monitoring service Action |
|---------|---------|-------------------|
| `POST` | Order ready to post | Submit to CoW Protocol API (if the fill overlay allows) |
| `WAIT_TIMESTAMP` | Wait for specific time | Schedule poll at `waitUntil` |
Expand Down Expand Up @@ -322,7 +325,7 @@ function generateOrder(address owner, address, bytes32 ctx, bytes calldata stati
}
```

A watch-tower enumerates the currently active entries via the manifest and polls once per leg with the leg index as `offchainInput`. The same pattern covers a simplified AMM (index 0 = buy, 1 = sell) and sequenced strategies (the follow-up entry appears in the manifest once the first leg fills).
A monitoring service enumerates the currently active entries via the manifest and polls once per leg with the leg index as `offchainInput`. The same pattern covers a simplified AMM (index 0 = buy, 1 = sell) and sequenced strategies (the follow-up entry appears in the manifest once the first leg fills).

## Order Manifest Interface

Expand Down Expand Up @@ -526,6 +529,12 @@ This fork introduces significant architectural changes from [cowprotocol/composa
| Function added | - | `getNextPollTimestamp()` |
| Function added | - | `describeOrder()` |
| Function added | - | `tryGenerateOrder()` returning full revert data |
| Error + verdict added | - | `PollNeedsOffchainInput(bytes4)` → `NEEDS_INPUT` |
| Struct changed | `Proof { uint256 location; bytes data }` | `Proof { string[] uris; bytes32[] blobVersionedHashes }` (URI mirrors + in-tx blob verification; `MerkleRootSet` topic0 rotates) |
| Struct changed | `PollResult { generator, fill, filledAmount }` | + `Restriction restriction` overlay (`INVALID` uniformly terminal) |
| Behavior | `setRoot(0, proof)` unvalidated | zero root requires an empty proof (`ProofDataMalformed`) |
| Interfaces added | - | `IOrderDescriptor`, `IOrderModule` sidecars (own ERC-165 ids; advertised only when committed) |
| Constructors changed | order types take no descriptor args | order types take `(string[] descriptorUris, bytes32 descriptorDigest)` |

The ERC-165 interface id of `IConditionalOrderGenerator` therefore changes: handlers deployed against the upstream interface will not satisfy the new id and are rejected by the polling path of a registry compiled against this fork.

Expand Down
2 changes: 1 addition & 1 deletion src/BaseConditionalOrder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ error InvalidHash();
/**
* @title Base logic for conditional orders.
* @dev Provides the dual-path plumbing: a lean `verify` for the settlement path and a
* structured, non-reverting `poll` for watch-towers, both derived from one
* structured, non-reverting `poll` for monitoring services, both derived from one
* `generateOrder` implementation.
* @author mfw78 <mfw78@rndlabs.xyz>
*/
Expand Down
12 changes: 6 additions & 6 deletions src/interfaces/IConditionalOrder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ interface IConditionalOrder {
error OrderNotValid(bytes4 reasonCode);

// --- errors specific for polling
// Signal to a watch tower that polling should be attempted again.
// Signal to a monitoring service that polling should be attempted again.
error PollTryNextBlock(bytes4 reasonCode);
// Signal to a watch tower that polling should be attempted again at a specific block number.
// Signal to a monitoring service that polling should be attempted again at a specific block number.
error PollTryAtBlock(uint256 blockNumber, bytes4 reasonCode);
// Signal to a watch tower that polling should be attempted again at a specific timestamp.
// Signal to a monitoring service that polling should be attempted again at a specific timestamp.
error PollTryAtTimestamp(uint256 timestamp, bytes4 reasonCode);
/// @dev Generation requires non-empty `offchainInput` that the caller did not
/// supply. Unlike the timed poll errors, this is not a retry signal:
Expand Down Expand Up @@ -68,7 +68,7 @@ interface IConditionalOrder {
* **MUST** revert if the order condition is not met.
* @dev The `order` parameter is ignored / not validated by the `IConditionalOrderGenerator` implementation.
* This parameter is included to allow more granular control over the order verification logic, and to
* allow a watch tower / user to propose a discrete order without it being generated by on-chain logic.
* allow a monitoring service / user to propose a discrete order without it being generated by on-chain logic.
* @param owner the contract who is the owner of the order
* @param sender the `msg.sender` of the transaction
* @param _hash the hash of the order
Expand All @@ -93,7 +93,7 @@ interface IConditionalOrder {
/**
* @title Conditional Order Generator Interface
* @author mfw78 <mfw78@rndlabs.xyz>
* @notice Adds structured, non-reverting polling for watch-tower integration.
* @notice Adds structured, non-reverting polling for monitoring service integration.
* @dev The generator surface carries the handler's *verdict* only. Observed fill
* state is orthogonal and is composed by the registry (`ComposableCoW`),
* never by a handler.
Expand Down Expand Up @@ -148,7 +148,7 @@ interface IConditionalOrderGenerator is IConditionalOrder, IERC165 {

/**
* Poll for a discrete order with scheduling metadata.
* @dev Called by watch-towers. **MUST NOT** revert for order conditions: the
* @dev Called by monitoring services. **MUST NOT** revert for order conditions: the
* conditional-order errors raised by `generateOrder` are decoded into the
* returned verdict instead.
* @param owner the contract who is the owner of the order
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/IOrderManifest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ interface IOrderManifest {
* @param owner The owner of the conditional order
* @param ctx Context key (bytes32(0) for merkle, hash(params) for single)
* @param staticInput The static input parameters for the conditional order
* @param offchainInput Dynamic parameters from watch-tower (may be empty)
* @param offchainInput Dynamic parameters from monitoring service (may be empty)
* @param offset Starting index for pagination
* @param limit Maximum number of entries to return
* @return entries Array of manifest entries
Expand Down
2 changes: 1 addition & 1 deletion src/types/twap/libraries/TWAPOrderMathLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ library TWAPOrderMathLib {

unchecked {
/// @dev Order is not yet valid before the start (order commences at `t0`);
/// a watch tower should try again at the start time.
/// a monitoring service should try again at the start time.
require(
startTime <= block.timestamp, IConditionalOrder.PollTryAtTimestamp(startTime, BeforeTwapStart.selector)
);
Expand Down