From 69a18ead374b12db54020da37246bdefdf87fca7 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Fri, 17 Jul 2026 11:16:50 +0000 Subject: [PATCH] cow: cleave the cow venue from the composable-cow keeper The venue crate is the orderbook alone. ComposableBody and the structured poll seam (Verdict, LegacyRevertAdapter, IConditionalOrder) move to the new composable-cow keeper crate, the Composable variant drops from the venue body, the goldens shed the composable vectors, and a blocking CI gate keeps composable symbols out of crates/cow-venue. --- .github/workflows/ci.yml | 12 +++++++ Cargo.lock | 16 ++++++++- Cargo.toml | 1 + crates/composable-cow/Cargo.toml | 24 ++++++++++++++ .../src/body.rs} | 25 +++++++------- crates/composable-cow/src/lib.rs | 15 +++++++++ .../src/poll.rs} | 14 ++++++++ crates/cow-venue/Cargo.toml | 2 +- crates/cow-venue/src/body.rs | 33 +++++-------------- crates/cow-venue/src/lib.rs | 14 +++----- crates/shepherd-sdk-test/Cargo.toml | 1 + crates/shepherd-sdk-test/tests/mock_venue.rs | 3 +- crates/shepherd-sdk/Cargo.toml | 5 +-- crates/shepherd-sdk/src/cow/mod.rs | 18 +++++----- crates/shepherd-sdk/src/cow/run.rs | 3 +- crates/shepherd-sdk/src/lib.rs | 14 ++++---- crates/shepherd-sdk/src/proptests.rs | 17 ++-------- crates/shepherd-sdk/tests/run.rs | 3 +- justfile | 5 +++ modules/twap-monitor/Cargo.toml | 1 + modules/twap-monitor/src/strategy.rs | 5 +-- scripts/check-cow-orderbook-only.sh | 29 ++++++++++++++++ 22 files changed, 171 insertions(+), 89 deletions(-) create mode 100644 crates/composable-cow/Cargo.toml rename crates/{cow-venue/src/composable.rs => composable-cow/src/body.rs} (72%) create mode 100644 crates/composable-cow/src/lib.rs rename crates/{shepherd-sdk/src/cow/composable.rs => composable-cow/src/poll.rs} (96%) create mode 100755 scripts/check-cow-orderbook-only.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2ca1d19..74809b37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -158,3 +158,15 @@ jobs: with: tool: wasm-tools,ripgrep - run: ./scripts/check-venue-agnostic.sh + + # Blocking orderbook-only gate: the CoW venue crate carries no + # composable symbol (scripts/check-cow-orderbook-only.sh). + cow-orderbook-only: + name: cow-orderbook-only + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: taiki-e/install-action@43aecc8d72668fbcfe75c31400bc4f890f1c5853 # v2.83.2 + with: + tool: ripgrep + - run: ./scripts/check-cow-orderbook-only.sh diff --git a/Cargo.lock b/Cargo.lock index b5d973d0..d63586bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1483,6 +1483,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "composable-cow" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "borsh", + "cowprotocol", + "nexum-sdk", + "proptest", +] + [[package]] name = "const-hex" version = "1.19.1" @@ -5213,7 +5225,7 @@ name = "shepherd-sdk" version = "0.1.0" dependencies = [ "alloy-primitives", - "alloy-sol-types", + "composable-cow", "cow-venue", "cowprotocol", "nexum-sdk", @@ -5231,6 +5243,7 @@ name = "shepherd-sdk-test" version = "0.1.0" dependencies = [ "alloy-primitives", + "composable-cow", "cowprotocol", "nexum-sdk", "nexum-sdk-test", @@ -5902,6 +5915,7 @@ version = "0.1.0" dependencies = [ "alloy-primitives", "alloy-sol-types", + "composable-cow", "cowprotocol", "nexum-sdk", "nexum-sdk-test", diff --git a/Cargo.toml b/Cargo.toml index 54cf5075..327bc2dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "crates/composable-cow", "crates/cow-venue", "crates/nexum-cli", "crates/nexum-launch", diff --git a/crates/composable-cow/Cargo.toml b/crates/composable-cow/Cargo.toml new file mode 100644 index 00000000..ceb66cc6 --- /dev/null +++ b/crates/composable-cow/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "composable-cow" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "ComposableCoW keeper machinery: the conditional-order body and the structured poll Verdict, with the deployed 1.x revert decoding quarantined behind LegacyRevertAdapter." + +[lib] +# Plain library, keeper-side only. The CoW venue crate is orderbook-only +# and never links this. + +[lints] +workspace = true + +[dependencies] +alloy-primitives.workspace = true +alloy-sol-types.workspace = true +borsh.workspace = true +cowprotocol = { version = "0.2.0", default-features = false } +nexum-sdk = { path = "../nexum-sdk" } + +[dev-dependencies] +proptest.workspace = true diff --git a/crates/cow-venue/src/composable.rs b/crates/composable-cow/src/body.rs similarity index 72% rename from crates/cow-venue/src/composable.rs rename to crates/composable-cow/src/body.rs index 5e7a94a4..b5f6bf02 100644 --- a/crates/cow-venue/src/composable.rs +++ b/crates/composable-cow/src/body.rs @@ -1,28 +1,24 @@ -//! The venue-neutral composable (conditional) order body. +//! The composable (conditional) order body. //! //! ComposableCoW expresses a conditional order as the //! `ConditionalOrderParams` tuple: the handler contract that mints the //! tradeable order, a salt that distinguishes otherwise-identical -//! conditional orders, and the opaque handler-specific static input. This -//! body type is that tuple in wire form. The one non-obvious invariant: -//! `static_input` is opaque to the venue; only the named handler parses +//! conditional orders, and the opaque handler-specific static input. +//! This body type is that tuple in wire form. The one non-obvious +//! invariant: `static_input` is opaque; only the named handler parses //! it, so this crate never inspects its bytes. -use alloc::vec::Vec; - use borsh::{BorshDeserialize, BorshSerialize}; -use crate::order::Address; - -/// The venue-neutral conditional order body: `ConditionalOrderParams` in -/// wire form. +/// The conditional order body: `ConditionalOrderParams` in wire form. #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq, Eq)] pub struct ComposableBody { /// The `IConditionalOrder` handler that mints the tradeable order. - pub handler: Address, + pub handler: [u8; 20], /// Salt distinguishing otherwise-identical conditional orders. pub salt: [u8; 32], - /// Handler-specific static input; opaque to the venue. + /// Handler-specific static input; opaque to everything but the + /// named handler. pub static_input: Vec, } @@ -53,6 +49,9 @@ mod tests { let mut body = sample(); body.static_input = Vec::new(); let bytes = borsh::to_vec(&body).expect("encode"); - assert_eq!(ComposableBody::try_from_slice(&bytes).unwrap(), body); + assert_eq!( + ComposableBody::try_from_slice(&bytes).expect("decode"), + body + ); } } diff --git a/crates/composable-cow/src/lib.rs b/crates/composable-cow/src/lib.rs new file mode 100644 index 00000000..505fa115 --- /dev/null +++ b/crates/composable-cow/src/lib.rs @@ -0,0 +1,15 @@ +//! # composable-cow +//! +//! ComposableCoW keeper machinery, kept out of the CoW venue: the +//! conditional-order body ([`ComposableBody`]) and the structured poll +//! seam ([`Verdict`]), with the deployed 1.x reverting wire quarantined +//! behind [`LegacyRevertAdapter`]. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![warn(missing_docs)] + +pub mod body; +pub mod poll; + +pub use body::ComposableBody; +pub use poll::{IConditionalOrder, LegacyRevertAdapter, Verdict}; diff --git a/crates/shepherd-sdk/src/cow/composable.rs b/crates/composable-cow/src/poll.rs similarity index 96% rename from crates/shepherd-sdk/src/cow/composable.rs rename to crates/composable-cow/src/poll.rs index fdb28adf..590549cc 100644 --- a/crates/shepherd-sdk/src/cow/composable.rs +++ b/crates/composable-cow/src/poll.rs @@ -355,4 +355,18 @@ mod tests { Verdict::TryNextBlock { .. } )); } + + use proptest::prelude::*; + + proptest! { + /// `decode` on arbitrary revert bytes never panics and returns + /// `None` for inputs shorter than the 4-byte EVM selector. + #[test] + fn decode_never_panics(bytes in proptest::collection::vec(any::(), 0..64)) { + let outcome = LegacyRevertAdapter::decode(&bytes); + if bytes.len() < 4 { + prop_assert!(outcome.is_none()); + } + } + } } diff --git a/crates/cow-venue/Cargo.toml b/crates/cow-venue/Cargo.toml index eb061887..8e303765 100644 --- a/crates/cow-venue/Cargo.toml +++ b/crates/cow-venue/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition.workspace = true license.workspace = true repository.workspace = true -description = "CoW venue slices. The default `body` slice carries the venue-neutral order/composable intent body types and their borsh IntentBody codec, linkable by adapters and modules." +description = "CoW venue slices, orderbook-only. The default `body` slice carries the venue-neutral order intent body types and their borsh IntentBody codec, linkable by adapters and modules." [lib] # Plain library. The default `body` slice is dependency-light so a venue diff --git a/crates/cow-venue/src/body.rs b/crates/cow-venue/src/body.rs index e7e40fe4..feb7d3c8 100644 --- a/crates/cow-venue/src/body.rs +++ b/crates/cow-venue/src/body.rs @@ -1,10 +1,10 @@ //! The CoW intent body and its versioned `IntentBody` codec. //! -//! A CoW intent is either a direct order or a composable (conditional) -//! order; [`CowIntent`] is that sum. [`CowIntentBody`] is the outer -//! per-venue version enum the venue publishes, and `#[derive(IntentBody)]` -//! gives it the borsh codec: a one-byte version tag plus the borsh -//! payload, with an unknown tag failing as a typed +//! A CoW intent is a direct order for the orderbook; [`CowIntent`] is +//! that sum, open for future intent kinds. [`CowIntentBody`] is the +//! outer per-venue version enum the venue publishes, and +//! `#[derive(IntentBody)]` gives it the borsh codec: a one-byte version +//! tag plus the borsh payload, with an unknown tag failing as a typed //! [`BodyError`](videre_sdk::BodyError) rather than a stringly borsh //! error. The one non-obvious invariant: the tag order is the schema, so //! new versions append at the end and no variant is ever reordered or @@ -13,16 +13,13 @@ use borsh::{BorshDeserialize, BorshSerialize}; use videre_sdk::IntentBody; -use crate::composable::ComposableBody; use crate::order::OrderBody; -/// What the CoW venue accepts: a direct order or a conditional order. +/// What the CoW venue accepts: a direct order for the orderbook. #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq, Eq)] pub enum CowIntent { /// A direct `GPv2Order` to place on the orderbook. Order(OrderBody), - /// A ComposableCoW conditional order that mints tradeable orders. - Composable(ComposableBody), } /// The outer per-venue version enum: the schema the CoW venue publishes. @@ -49,15 +46,7 @@ mod tests { .build() } - fn composable_body() -> ComposableBody { - ComposableBody { - handler: [0xab; 20], - salt: [0xcd; 32], - static_input: vec![9, 8, 7], - } - } - - /// The codec conformance set: both v1 intents as round-trip vectors + /// The codec conformance set: the v1 intent as a round-trip vector /// plus the typed failure contract, in the kit's published form. fn vectors() -> CodecVectors { let mut vectors = CodecVectors::new("cow-venue/cow-intent-body"); @@ -67,12 +56,6 @@ mod tests { &CowIntentBody::V1(CowIntent::Order(order_body())), ) .expect("order body encodes"); - vectors - .push_round_trip( - "v1-composable", - &CowIntentBody::V1(CowIntent::Composable(composable_body())), - ) - .expect("composable body encodes"); let bytes = |intent: CowIntent| CowIntentBody::V1(intent).to_bytes().expect("body encodes"); let mut unknown = bytes(CowIntent::Order(order_body())); @@ -90,7 +73,7 @@ mod tests { truncated, Expectation::Malformed { version: 0 }, ); - let mut trailing = bytes(CowIntent::Composable(composable_body())); + let mut trailing = bytes(CowIntent::Order(order_body())); trailing.push(0); vectors.push_failure( "trailing-bytes", diff --git a/crates/cow-venue/src/lib.rs b/crates/cow-venue/src/lib.rs index 440fa77b..fc4392b5 100644 --- a/crates/cow-venue/src/lib.rs +++ b/crates/cow-venue/src/lib.rs @@ -1,9 +1,10 @@ //! # cow-venue //! -//! The CoW venue, staged as a crate of feature slices. Today only the -//! default [`body`] slice exists: the venue-neutral order and composable -//! intent body types and the borsh `IntentBody` codec over them. The -//! typed client and the adapter component are later slices. +//! The CoW venue, staged as a crate of feature slices: the orderbook +//! and nothing else. The default [`body`] slice carries the +//! venue-neutral order intent body types and the borsh `IntentBody` +//! codec over them; conditional-order keeper machinery lives in its +//! own crate and never here. //! //! The body slice is dependency-light on purpose. It links only the //! venue SDK (for the [`IntentBody`](videre_sdk::IntentBody) derive) @@ -35,9 +36,6 @@ extern crate alloc; #[cfg(feature = "body")] pub mod body; -#[cfg(feature = "body")] -pub mod composable; - #[cfg(feature = "body")] pub mod order; @@ -57,8 +55,6 @@ pub mod client; #[cfg(feature = "body")] pub use body::{CowIntent, CowIntentBody}; #[cfg(feature = "body")] -pub use composable::ComposableBody; -#[cfg(feature = "body")] pub use order::{ BuyToken, BuyTokenDestination, OrderBody, OrderBuilder, OrderKind, SellToken, SellTokenSource, }; diff --git a/crates/shepherd-sdk-test/Cargo.toml b/crates/shepherd-sdk-test/Cargo.toml index 83a3525c..ea2a4a47 100644 --- a/crates/shepherd-sdk-test/Cargo.toml +++ b/crates/shepherd-sdk-test/Cargo.toml @@ -20,4 +20,5 @@ serde_json = { workspace = true, features = ["std"] } # Order construction for the MockVenue acceptance tests that drive # the keeper run end to end. alloy-primitives.workspace = true +composable-cow = { path = "../composable-cow" } cowprotocol = { version = "0.2.0", default-features = false } diff --git a/crates/shepherd-sdk-test/tests/mock_venue.rs b/crates/shepherd-sdk-test/tests/mock_venue.rs index e9b3bef0..6867ecdd 100644 --- a/crates/shepherd-sdk-test/tests/mock_venue.rs +++ b/crates/shepherd-sdk-test/tests/mock_venue.rs @@ -4,10 +4,11 @@ //! strategy code polling the venue directly. use alloy_primitives::{Address, B256, U256, address, hex, keccak256}; +use composable_cow::Verdict; use cowprotocol::{BuyTokenDestination, GPv2OrderData, OrderKind, SellTokenSource}; use nexum_sdk::host::{Fault, LocalStoreHost as _, RateLimit}; use nexum_sdk::keeper::{ConditionalSource, Journal, Tick, WatchRef, WatchSet, watch_key}; -use shepherd_sdk::cow::{CowApiError, CowHost, OrderRejection, Verdict, order_uid_hex, run}; +use shepherd_sdk::cow::{CowApiError, CowHost, OrderRejection, order_uid_hex, run}; use shepherd_sdk_test::{MockHost, MockVenue}; const SEPOLIA: u64 = 11_155_111; diff --git a/crates/shepherd-sdk/Cargo.toml b/crates/shepherd-sdk/Cargo.toml index c4ef8455..5fe969d0 100644 --- a/crates/shepherd-sdk/Cargo.toml +++ b/crates/shepherd-sdk/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition.workspace = true license.workspace = true repository.workspace = true -description = "CoW-domain guest SDK for Shepherd modules: cow-api host trait, order bridging, revert decoding, and prelude on top of cowprotocol types." +description = "CoW-domain guest SDK for Shepherd modules: cow-api host trait, order bridging, and prelude on top of cowprotocol types." [lib] # Plain library - modules link this and emit their own cdylib for the @@ -18,10 +18,11 @@ description = "CoW-domain guest SDK for Shepherd modules: cow-api host trait, or # the table-driven retry classification the cow error surface delegates # to. cow-venue = { path = "../cow-venue", features = ["client"] } +# The structured poll seam the keeper run dispatches on. +composable-cow = { path = "../composable-cow" } nexum-sdk = { path = "../nexum-sdk" } cowprotocol = { version = "0.2.0", default-features = false } alloy-primitives.workspace = true -alloy-sol-types.workspace = true serde_json.workspace = true strum.workspace = true thiserror.workspace = true diff --git a/crates/shepherd-sdk/src/cow/mod.rs b/crates/shepherd-sdk/src/cow/mod.rs index 19e00a6d..620e379d 100644 --- a/crates/shepherd-sdk/src/cow/mod.rs +++ b/crates/shepherd-sdk/src/cow/mod.rs @@ -1,14 +1,14 @@ //! CoW Protocol bridging. //! //! Type conversions and ABI decoding helpers that translate between -//! the on-chain shape (`GPv2OrderData`, `IConditionalOrder` reverts, -//! orderbook JSON) and the typed Rust surface (`OrderData`, -//! `Verdict`, `RetryAction`), plus [`run()`] - the +//! the on-chain shape (`GPv2OrderData`, orderbook JSON) and the typed +//! Rust surface (`OrderData`, `RetryAction`), plus [`run()`] - the //! poll/submit composition over the keeper stores. //! -//! The poll seam is the structured [`Verdict`]; the deployed -//! ComposableCoW 1.x reverting wire is decoded behind the quarantined -//! [`LegacyRevertAdapter`]. +//! The poll seam is the structured +//! [`Verdict`](composable_cow::Verdict), carried by the +//! `composable-cow` keeper crate together with the quarantined revert +//! decoding; only orderbook concerns live here. //! //! The codec submodules stay purely host-neutral: helpers take //! primitive arguments (`&[u8]`, `Option<&str>`, slices) so they can @@ -16,12 +16,10 @@ //! unchanged by TWAP, EthFlow, and future strategy modules. The //! keeper run is generic over the host traits alone. -pub mod composable; pub mod error; pub mod order; pub mod run; -pub use composable::{IConditionalOrder, LegacyRevertAdapter, Verdict}; pub use error::{ CowApiError, HttpFailure, OrderRejection, RetryAction, classify_api_error, classify_submit_error, is_already_submitted, @@ -33,8 +31,8 @@ pub use run::run; /// codec, re-exported from the `cow-venue` default slice. The shim keeps /// this path stable while the module ports move off the legacy surface. pub use cow_venue::{ - BuyToken, BuyTokenDestination, ComposableBody, CowIntent, CowIntentBody, OrderBody, - OrderBuilder, OrderKind, SellToken, SellTokenSource, + BuyToken, BuyTokenDestination, CowIntent, CowIntentBody, OrderBody, OrderBuilder, OrderKind, + SellToken, SellTokenSource, }; use nexum_sdk::host::Host; diff --git a/crates/shepherd-sdk/src/cow/run.rs b/crates/shepherd-sdk/src/cow/run.rs index dbabd179..910090e1 100644 --- a/crates/shepherd-sdk/src/cow/run.rs +++ b/crates/shepherd-sdk/src/cow/run.rs @@ -17,6 +17,7 @@ //! the composed behaviour with one capture. use alloy_primitives::{Address, Bytes}; +use composable_cow::Verdict; use cowprotocol::{GPv2OrderData, OrderCreation, OrderData, Signature}; use nexum_sdk::host::Fault; use nexum_sdk::keeper::{ @@ -24,7 +25,7 @@ use nexum_sdk::keeper::{ }; use super::{ - CowApiError, CowHost, Verdict, classify_submit_error, gpv2_to_order_data, is_already_submitted, + CowApiError, CowHost, classify_submit_error, gpv2_to_order_data, is_already_submitted, order_uid_hex, }; diff --git a/crates/shepherd-sdk/src/lib.rs b/crates/shepherd-sdk/src/lib.rs index 491e7752..40798f85 100644 --- a/crates/shepherd-sdk/src/lib.rs +++ b/crates/shepherd-sdk/src/lib.rs @@ -16,12 +16,11 @@ //! - [`cow`] - the [`CowApiHost`] trait for `shepherd:cow/cow-api` //! (and the [`CowHost`] bound over the core [`Host`]), //! `GPv2OrderData` -> `OrderData` bridging ([`gpv2_to_order_data`]), -//! the structured poll seam ([`Verdict`]) with the deployed 1.x -//! revert decoding quarantined behind [`LegacyRevertAdapter`], the -//! classifiers mapping submit failures into -//! the keeper [`RetryAction`], and [`run`] - the poll -> -//! outcome -> gate/journal/submit composition over the keeper -//! stores. +//! the classifiers mapping submit failures into the keeper +//! [`RetryAction`], and [`run`] - the poll -> outcome -> +//! gate/journal/submit composition over the keeper stores, +//! dispatching the structured [`Verdict`] from the `composable-cow` +//! keeper crate. //! //! - [`bind_cow_host_via_wit_bindgen!`](bind_cow_host_via_wit_bindgen) - //! the CoW layering of `nexum_sdk::bind_host_via_wit_bindgen!`: @@ -50,8 +49,7 @@ //! [`CowHost`]: cow::CowHost //! [`Host`]: nexum_sdk::host::Host //! [`gpv2_to_order_data`]: cow::gpv2_to_order_data -//! [`Verdict`]: cow::Verdict -//! [`LegacyRevertAdapter`]: cow::LegacyRevertAdapter +//! [`Verdict`]: composable_cow::Verdict //! [`RetryAction`]: cow::RetryAction //! [`run`]: cow::run() diff --git a/crates/shepherd-sdk/src/proptests.rs b/crates/shepherd-sdk/src/proptests.rs index 6d5c1453..a9b4d5a2 100644 --- a/crates/shepherd-sdk/src/proptests.rs +++ b/crates/shepherd-sdk/src/proptests.rs @@ -4,29 +4,16 @@ //! //! Covered here: //! -//! - `LegacyRevertAdapter::decode` selector dispatch (no-panic guard). //! - `gpv2_to_order_data` marker mapping (no-panic guard). //! //! The generic properties (`eth_call` round-trip, `scale_decimal`) -//! live in `nexum-sdk`. +//! live in `nexum-sdk`; the revert-decode guard lives in +//! `composable-cow`. #![cfg(test)] use proptest::prelude::*; -proptest! { - /// `LegacyRevertAdapter::decode` on arbitrary revert bytes must - /// never panic and must return `None` for inputs shorter than the - /// 4-byte EVM selector. - #[test] - fn legacy_revert_decode_never_panics(bytes in proptest::collection::vec(any::(), 0..64)) { - let outcome = crate::cow::LegacyRevertAdapter::decode(&bytes); - if bytes.len() < 4 { - prop_assert!(outcome.is_none()); - } - } -} - proptest! { /// `gpv2_to_order_data` is exhaustive over the marker enum; /// fuzzing the inputs as raw u8 (not the typed enum) is the only diff --git a/crates/shepherd-sdk/tests/run.rs b/crates/shepherd-sdk/tests/run.rs index cda235ba..76677e0a 100644 --- a/crates/shepherd-sdk/tests/run.rs +++ b/crates/shepherd-sdk/tests/run.rs @@ -7,11 +7,12 @@ use std::cell::Cell; use alloy_primitives::{Address, B256, U256, address, hex, keccak256}; +use composable_cow::Verdict; use cowprotocol::{BuyTokenDestination, GPv2OrderData, OrderKind, SellTokenSource}; use nexum_sdk::host::{Fault, LocalStoreHost as _, RateLimit}; use nexum_sdk::keeper::{ConditionalSource, Gates, Journal, Tick, WatchRef, WatchSet}; use nexum_sdk_test::capture_tracing; -use shepherd_sdk::cow::{CowApiError, OrderRejection, Verdict, order_uid_hex, run}; +use shepherd_sdk::cow::{CowApiError, OrderRejection, order_uid_hex, run}; use shepherd_sdk_test::MockHost; const SEPOLIA: u64 = 11_155_111; diff --git a/justfile b/justfile index 696d781f..9c89a2f9 100644 --- a/justfile +++ b/justfile @@ -78,6 +78,11 @@ run-e2e: build-e2e build-engine check-venue-agnostic: ./scripts/check-venue-agnostic.sh +# Orderbook-only gate: the CoW venue crate carries no composable +# symbol. Blocking in CI. +check-cow-orderbook-only: + ./scripts/check-cow-orderbook-only.sh + # Check the entire workspace check: cargo check --target wasm32-wasip2 -p example diff --git a/modules/twap-monitor/Cargo.toml b/modules/twap-monitor/Cargo.toml index 91a37a90..5533d59a 100644 --- a/modules/twap-monitor/Cargo.toml +++ b/modules/twap-monitor/Cargo.toml @@ -9,6 +9,7 @@ repository.workspace = true crate-type = ["cdylib"] [dependencies] +composable-cow = { path = "../../crates/composable-cow" } nexum-sdk = { path = "../../crates/nexum-sdk" } shepherd-sdk = { path = "../../crates/shepherd-sdk" } cowprotocol = { version = "0.2.0", default-features = false } diff --git a/modules/twap-monitor/src/strategy.rs b/modules/twap-monitor/src/strategy.rs index dd6da48c..0223f5b8 100644 --- a/modules/twap-monitor/src/strategy.rs +++ b/modules/twap-monitor/src/strategy.rs @@ -16,6 +16,7 @@ use alloy_primitives::{Address, Bytes, keccak256}; use alloy_sol_types::{SolCall, SolEvent, SolValue}; +use composable_cow::{LegacyRevertAdapter, Verdict}; use cowprotocol::{ COMPOSABLE_COW, ComposableCoW::ConditionalOrderCreated, ConditionalOrderParams, GPv2OrderData, }; @@ -23,7 +24,7 @@ use nexum_sdk::chain::{eth_call_params, parse_eth_call_result}; use nexum_sdk::events::Log; use nexum_sdk::host::{ChainError, Fault}; use nexum_sdk::keeper::{ConditionalSource, Tick, WatchRef, WatchSet}; -use shepherd_sdk::cow::{CowHost, LegacyRevertAdapter, Verdict, run}; +use shepherd_sdk::cow::{CowHost, run}; /// Block fields the poll path reads on every dispatch. pub struct BlockInfo { @@ -735,8 +736,8 @@ mod tests { // wire shape the chain backend forwards: a `ChainError::Rpc` // carrying the already-decoded `OrderNotValid` revert bytes. use alloy_sol_types::SolError; + use composable_cow::IConditionalOrder; use nexum_sdk::host::RpcError; - use shepherd_sdk::cow::IConditionalOrder; let host = MockHost::new(); let owner = address!("0011223344556677889900AABBCCDDEEFF001122"); diff --git a/scripts/check-cow-orderbook-only.sh b/scripts/check-cow-orderbook-only.sh new file mode 100755 index 00000000..52229ed2 --- /dev/null +++ b/scripts/check-cow-orderbook-only.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Orderbook-only check for the CoW venue crate: crates/cow-venue carries +# no composable symbol (Composable*, getTradeableOrder*, the +# IConditionalOrder revert selectors, LegacyRevertAdapter) and no +# dependency edge to the composable-cow keeper crate - the Cargo.toml +# scan covers the edge, since the dep line names the crate. Blocking in +# CI; run locally via `just check-cow-orderbook-only`. + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR/.." || exit 2 + +pass() { printf '\033[1;32m[cow PASS]\033[0m %s\n' "$*" >&2; } +fail() { printf '\033[1;31m[cow FAIL]\033[0m %s\n' "$*" >&2; status=1; } + +command -v rg >/dev/null || { echo "ripgrep (rg) is required" >&2; exit 2; } + +status=0 + +symbols='composable|getTradeableOrder|IConditionalOrder|LegacyRevertAdapter|\bVerdict\b|OrderNotValid|PollTryNextBlock|PollTryAtBlock|PollTryAtEpoch|PollNever' +rg -in --no-heading -e "$symbols" crates/cow-venue +case $? in + 0) fail "composable symbols leak into crates/cow-venue" ;; + 1) pass "cow-venue symbol scan empty" ;; + *) fail "symbol scan errored (crates/cow-venue missing?)" ;; +esac + +exit "$status"