diff --git a/crates/composable-cow/src/run.rs b/crates/composable-cow/src/run.rs index ac90f64a..97342acd 100644 --- a/crates/composable-cow/src/run.rs +++ b/crates/composable-cow/src/run.rs @@ -153,7 +153,7 @@ where let intent = CowIntentBody::V1(CowIntent::Signed(SignedOrder { order: order_data_to_body(&order_data), - owner: owner.into_array(), + owner, signature: signature.to_vec(), })); // Reserve the exact wire bytes the venue submit and the reconcile diff --git a/crates/composable-cow/tests/run.rs b/crates/composable-cow/tests/run.rs index e3bf7ba1..b4b7e411 100644 --- a/crates/composable-cow/tests/run.rs +++ b/crates/composable-cow/tests/run.rs @@ -149,7 +149,7 @@ fn intent_bytes(order: &GPv2OrderData) -> Vec { let order_data = gpv2_to_order_data(order).expect("known markers"); CowIntentBody::V1(CowIntent::Signed(SignedOrder { order: order_data_to_body(&order_data), - owner: sample_owner().into_array(), + owner: sample_owner(), signature: hex!("c0ffeec0ffeec0ffee").to_vec(), })) .to_bytes() diff --git a/crates/cow-venue/Cargo.toml b/crates/cow-venue/Cargo.toml index d718ad35..650cee31 100644 --- a/crates/cow-venue/Cargo.toml +++ b/crates/cow-venue/Cargo.toml @@ -33,7 +33,10 @@ nexum-sdk = { path = "../nexum-sdk", optional = true } # submission bodies. Express-declared (not workspace-inherited) so the # guest build never inherits the native `http-client` feature. cowprotocol = { version = "0.2.0", default-features = false, optional = true } -alloy-primitives = { workspace = true, optional = true } +# `body` slice: the order body fields are alloy `Address`/`U256`, and +# `borsh` supplies their borsh impls (ruint for `U256`). Optional only +# so the empty `--no-default-features` build stays dependency-free. +alloy-primitives = { workspace = true, features = ["borsh"], optional = true } alloy-sol-types = { workspace = true, optional = true } # `adapter` slice: the orderbook REST speaker over the scoped # wasi:http transport. @@ -51,6 +54,7 @@ toml = { workspace = true } thiserror = { workspace = true } [dev-dependencies] +alloy-primitives = { workspace = true } serde = { workspace = true } toml = { workspace = true } thiserror = { workspace = true } @@ -66,12 +70,12 @@ cowprotocol = { version = "0.2.0", default-features = false } # `--no-default-features` drops everything so downstream can depend on a # single slice without pulling the codec or the keeper transitively. default = ["body"] -body = ["dep:borsh", "dep:videre-sdk"] +body = ["dep:borsh", "dep:videre-sdk", "dep:alloy-primitives"] client = ["body", "dep:nexum-sdk"] # Chain-edge order assembly, shared by the adapter's submit and the # keeper's legacy submit path. Carries no component glue, so a keeper # module can link it without exporting the adapter face. -assembly = ["body", "dep:cowprotocol", "dep:alloy-primitives", "dep:alloy-sol-types"] +assembly = ["body", "dep:cowprotocol", "dep:alloy-sol-types"] # The venue-adapter component slice: the `#[videre_sdk::venue]` export # and the orderbook transport. Only the cdylib wasm build enables it. adapter = [ diff --git a/crates/cow-venue/src/adapter.rs b/crates/cow-venue/src/adapter.rs index 416d6ae3..298d437b 100644 --- a/crates/cow-venue/src/adapter.rs +++ b/crates/cow-venue/src/adapter.rs @@ -21,7 +21,7 @@ use core::time::Duration; use std::sync::{PoisonError, RwLock}; -use alloy_primitives::{Address, U256}; +use alloy_primitives::Address; use cowprotocol::{ ApiError, Chain, OrderCreation, OrderData, OrderKind, OrderStatus, QuoteAppData, QuoteRequest, }; @@ -29,7 +29,7 @@ use nexum_sdk::keeper::RetryAction; use serde::Deserialize; use url::Url; use videre_sdk::transport::http::Fetch; -use videre_sdk::value_flow::{Asset, AssetAmount, Erc20}; +use videre_sdk::value_flow::AssetAmount; use videre_sdk::{ AuthScheme, IntentBody as _, IntentHeader, IntentStatus, Quotation, RateLimit, Settlement, SubmitOutcome, UnsignedTx, VenueError, @@ -149,8 +149,8 @@ pub(crate) fn derive_header_with(chain: u64, body: &[u8]) -> Result (&signed.order, AuthScheme::Eip1271), }; Ok(IntentHeader { - gives: erc20(order.sell_token, minimal_be(&order.sell_amount)), - wants: erc20(order.buy_token, minimal_be(&order.buy_amount)), + gives: AssetAmount::erc20(order.sell_token, order.sell_amount), + wants: AssetAmount::erc20(order.buy_token, order.buy_amount), settlement: Settlement { chain }, authorisation, }) @@ -170,7 +170,7 @@ pub(crate) fn submit_with( match decode(body)? { CowIntent::Signed(signed) => { let order = assembly::body_to_order_data(&signed.order); - let owner = Address::from(signed.owner); + let owner = signed.owner; let creation = assembly::build_order_creation(&order, &signed.signature, owner) .map_err(|e| VenueError::InvalidBody(e.to_string()))?; let uid = match post_order(fetch, config, &creation)? { @@ -243,7 +243,7 @@ pub(crate) fn quote_with( let intent = decode(body)?; let (wire, from) = match &intent { CowIntent::Order(order) => (order, config.owner.ok_or(VenueError::Unsupported)?), - CowIntent::Signed(signed) => (&signed.order, Address::from(signed.owner)), + CowIntent::Signed(signed) => (&signed.order, signed.owner), }; let order = assembly::body_to_order_data(wire); let request = serde_json::to_vec("e_request(&order, from)) @@ -260,18 +260,9 @@ pub(crate) fn quote_with( let quoted: cowprotocol::OrderQuoteResponse = serde_json::from_slice(response.body()) .map_err(|e| VenueError::Unavailable(format!("quote decode failed: {e}")))?; Ok(Quotation { - gives: erc20( - order.sell_token.into_array(), - minimal_be_u256(quoted.quote.sell_amount), - ), - wants: erc20( - order.buy_token.into_array(), - minimal_be_u256(quoted.quote.buy_amount), - ), - fee: erc20( - order.sell_token.into_array(), - minimal_be_u256(quoted.quote.fee_amount), - ), + gives: AssetAmount::erc20(order.sell_token, quoted.quote.sell_amount), + wants: AssetAmount::erc20(order.buy_token, quoted.quote.buy_amount), + fee: AssetAmount::erc20(order.sell_token, quoted.quote.fee_amount), valid_until_ms: u64::from(quoted.quote.valid_to).saturating_mul(1000), }) } @@ -435,28 +426,6 @@ fn classified(api: &ApiError) -> VenueError { } } -// ── value projections ──────────────────────────────────────────────── - -/// Big-endian bytes with leading zeros trimmed: the minimal `uint` -/// spelling, where an empty list is zero. -fn minimal_be(bytes: &[u8; 32]) -> Vec { - let first = bytes.iter().position(|byte| *byte != 0); - first.map_or(Vec::new(), |index| bytes[index..].to_vec()) -} - -fn minimal_be_u256(value: U256) -> Vec { - minimal_be(&value.to_be_bytes::<32>()) -} - -fn erc20(token: [u8; 20], amount: Vec) -> AssetAmount { - AssetAmount { - asset: Asset::Erc20(Erc20 { - token: token.to_vec(), - }), - amount, - } -} - // The component-ABI export glue only exists on the wasm build; the // native build keeps the same trait impl (for conformance suites) // without export symbols no native linker accepts. @@ -530,8 +499,10 @@ mod export { #[cfg(test)] mod tests { + use alloy_primitives::U256; use videre_sdk::transport::BoundedFetch; use videre_sdk::transport::http::FetchError; + use videre_sdk::value_flow::{Asset, Erc20}; use videre_sdk::{IntentBody as _, VenueFault}; use videre_test::MockFetch; use videre_test::reconcile::ReconcileFixture; @@ -566,26 +537,20 @@ mod tests { fn order_body() -> OrderBody { OrderBody::sell( - SellToken([0x11; 20]), - amount(42), - BuyToken([0x22; 20]), - amount(41), + SellToken(Address::repeat_byte(0x11)), + U256::from(42u64), + BuyToken(Address::repeat_byte(0x22)), + U256::from(41u64), 1_700_000_000, ) .app_data([0x44; 32]) .build() } - fn amount(value: u8) -> [u8; 32] { - let mut bytes = [0u8; 32]; - bytes[31] = value; - bytes - } - fn signed_bytes() -> Vec { CowIntentBody::V1(CowIntent::Signed(SignedOrder { order: order_body(), - owner: owner().into_array(), + owner: owner(), signature: vec![0xC0, 0xFF, 0xEE], })) .to_bytes() diff --git a/crates/cow-venue/src/assembly.rs b/crates/cow-venue/src/assembly.rs index 6e04db1c..7bbf213b 100644 --- a/crates/cow-venue/src/assembly.rs +++ b/crates/cow-venue/src/assembly.rs @@ -8,10 +8,6 @@ //! the adapter, not the keeper, owns every projection across that //! edge. -use alloc::format; -use alloc::string::String; -use alloc::vec::Vec; - use alloy_primitives::{Address, Bytes}; use alloy_sol_types::SolCall; use cowprotocol::{ @@ -113,14 +109,14 @@ pub fn order_uid(chain: Chain, order: &OrderData, owner: Address) -> cowprotocol #[must_use] pub fn order_data_to_body(order: &OrderData) -> OrderBody { OrderBody { - sell_token: order.sell_token.into_array(), - buy_token: order.buy_token.into_array(), - receiver: order.receiver.map(Address::into_array), - sell_amount: order.sell_amount.to_be_bytes(), - buy_amount: order.buy_amount.to_be_bytes(), + sell_token: order.sell_token, + buy_token: order.buy_token, + receiver: order.receiver, + sell_amount: order.sell_amount, + buy_amount: order.buy_amount, valid_to: order.valid_to, app_data: order.app_data.0, - fee_amount: order.fee_amount.to_be_bytes(), + fee_amount: order.fee_amount, kind: match order.kind { OrderKind::Sell => crate::order::OrderKind::Sell, OrderKind::Buy => crate::order::OrderKind::Buy, @@ -143,14 +139,14 @@ pub fn order_data_to_body(order: &OrderData) -> OrderBody { #[must_use] pub fn body_to_order_data(body: &OrderBody) -> OrderData { OrderData { - sell_token: Address::from(body.sell_token), - buy_token: Address::from(body.buy_token), - receiver: body.receiver.map(Address::from), - sell_amount: alloy_primitives::U256::from_be_bytes(body.sell_amount), - buy_amount: alloy_primitives::U256::from_be_bytes(body.buy_amount), + sell_token: body.sell_token, + buy_token: body.buy_token, + receiver: body.receiver, + sell_amount: body.sell_amount, + buy_amount: body.buy_amount, valid_to: body.valid_to, app_data: body.app_data.into(), - fee_amount: alloy_primitives::U256::from_be_bytes(body.fee_amount), + fee_amount: body.fee_amount, kind: match body.kind { crate::order::OrderKind::Sell => OrderKind::Sell, crate::order::OrderKind::Buy => OrderKind::Buy, @@ -283,14 +279,14 @@ mod tests { let g = submittable_gpv2(); let order = gpv2_to_order_data(&g).expect("known markers"); let body = order_data_to_body(&order); - assert_eq!(body.sell_token, g.sellToken.into_array()); - assert_eq!(body.buy_token, g.buyToken.into_array()); - assert_eq!(body.receiver, Some(g.receiver.into_array())); - assert_eq!(body.sell_amount, g.sellAmount.to_be_bytes::<32>()); - assert_eq!(body.buy_amount, g.buyAmount.to_be_bytes::<32>()); + assert_eq!(body.sell_token, g.sellToken); + assert_eq!(body.buy_token, g.buyToken); + assert_eq!(body.receiver, Some(g.receiver)); + assert_eq!(body.sell_amount, g.sellAmount); + assert_eq!(body.buy_amount, g.buyAmount); assert_eq!(body.valid_to, g.validTo); assert_eq!(body.app_data, g.appData.0); - assert_eq!(body.fee_amount, g.feeAmount.to_be_bytes::<32>()); + assert_eq!(body.fee_amount, g.feeAmount); assert_eq!(body.kind, crate::order::OrderKind::Sell); assert!(!body.partially_fillable); assert_eq!( diff --git a/crates/cow-venue/src/body.rs b/crates/cow-venue/src/body.rs index 1442089e..4d0f450f 100644 --- a/crates/cow-venue/src/body.rs +++ b/crates/cow-venue/src/body.rs @@ -35,17 +35,18 @@ pub enum CowIntentBody { #[cfg(test)] mod tests { - use super::*; + use alloy_primitives::{Address, U256}; use videre_test::{CodecVectors, Expectation}; + use super::*; use crate::order::{BuyToken, SellToken}; fn order_body() -> OrderBody { OrderBody::sell( - SellToken([0x11; 20]), - [0x01; 32], - BuyToken([0x22; 20]), - [0x02; 32], + SellToken(Address::repeat_byte(0x11)), + U256::from(1u64), + BuyToken(Address::repeat_byte(0x22)), + U256::from(2u64), 1_700_000_000, ) .app_data([0x44; 32]) @@ -68,7 +69,7 @@ mod tests { "v1-signed", &CowIntentBody::V1(CowIntent::Signed(SignedOrder { order: order_body(), - owner: [0x55; 20], + owner: Address::repeat_byte(0x55), signature: vec![0xC0, 0xFF, 0xEE], })), ) diff --git a/crates/cow-venue/src/client.rs b/crates/cow-venue/src/client.rs index 83f56095..92696153 100644 --- a/crates/cow-venue/src/client.rs +++ b/crates/cow-venue/src/client.rs @@ -8,8 +8,6 @@ //! slice so the client that submits an order and the table that //! classifies its rejection version together. -use alloc::string::String; - use videre_sdk::client::{HostVenues, Venue, VenueClient}; use videre_sdk::keeper::submission_key; use videre_sdk::{BodyError, IntentBody as _}; @@ -96,14 +94,16 @@ mod tests { } fn sample_body() -> CowIntentBody { + use alloy_primitives::{Address, U256}; + use crate::body::CowIntent; use crate::order::{BuyToken, OrderBody, SellToken}; CowIntentBody::V1(CowIntent::Order( OrderBody::sell( - SellToken([0x11; 20]), - [0x01; 32], - BuyToken([0x22; 20]), - [0x02; 32], + SellToken(Address::repeat_byte(0x11)), + U256::from(1u64), + BuyToken(Address::repeat_byte(0x22)), + U256::from(2u64), 1_700_000_000, ) .app_data([0x44; 32]) @@ -114,6 +114,7 @@ mod tests { #[test] fn intent_id_is_deterministic_and_body_scoped() { + use alloy_primitives::{Address, U256}; use videre_sdk::IntentBody; use crate::body::CowIntent; @@ -131,14 +132,14 @@ mod tests { let other = CowIntentBody::V1(CowIntent::Signed(SignedOrder { order: OrderBody::sell( - SellToken([0x11; 20]), - [0x01; 32], - BuyToken([0x22; 20]), - [0x02; 32], + SellToken(Address::repeat_byte(0x11)), + U256::from(1u64), + BuyToken(Address::repeat_byte(0x22)), + U256::from(2u64), 1_700_000_000, ) .build(), - owner: [0x55; 20], + owner: Address::repeat_byte(0x55), signature: vec![0xC0], })); assert_ne!(id, intent_id(&other).expect("body encodes")); diff --git a/crates/cow-venue/src/lib.rs b/crates/cow-venue/src/lib.rs index 990c4363..e7852a36 100644 --- a/crates/cow-venue/src/lib.rs +++ b/crates/cow-venue/src/lib.rs @@ -7,12 +7,10 @@ //! 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) -//! and borsh, so a venue adapter component or a strategy module can carry -//! the body types and codec without dragging in the host-side CoW -//! machinery. The crate is `#![no_std]` (tests and the `adapter` slice -//! aside): the derive's generated code reaches `alloc` through the -//! venue SDK re-export, never `::std`. +//! venue SDK (for the [`IntentBody`](videre_sdk::IntentBody) derive), +//! borsh, and the alloy primitives the body fields carry, so a venue +//! adapter component or a strategy module can carry the body types and +//! codec without dragging in the host-side CoW machinery. //! //! With `--no-default-features` the slice drops out entirely and the //! crate compiles empty, so a consumer can depend on a single slice @@ -34,16 +32,12 @@ //! never linked by a keeper module (linking it would export the //! adapter face). -#![cfg_attr(not(any(test, feature = "adapter")), no_std)] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![warn(missing_docs)] // wit_bindgen::generate! expands to host-import shims whose arity can // exceed clippy's too-many-arguments threshold. #![cfg_attr(feature = "adapter", allow(clippy::too_many_arguments))] -#[cfg(feature = "body")] -extern crate alloc; - #[cfg(feature = "body")] pub mod body; diff --git a/crates/cow-venue/src/order.rs b/crates/cow-venue/src/order.rs index dbc0a67a..2b22a0d4 100644 --- a/crates/cow-venue/src/order.rs +++ b/crates/cow-venue/src/order.rs @@ -1,25 +1,18 @@ //! The venue-neutral CoW order body. //! -//! On the wire a CoW order is the 12-field `GPv2Order` tuple. The -//! host-side path speaks it through the on-chain alloy types; this body -//! type is the same shape reduced to plain wire primitives (byte arrays -//! for addresses and 256-bit amounts, small enums for the balance and -//! kind markers) so it borsh-encodes and links without the on-chain -//! stack. The one non-obvious invariant: `amount`, `receiver`, and the -//! marker enums are canonical wire forms, not on-chain keccak markers, -//! so the adapter, not this type, owns the projection to and from chain. - -use alloc::vec::Vec; +//! On the wire a CoW order is the 12-field `GPv2Order` tuple. This body +//! type is the same shape over the alloy primitives (`Address`/`U256`, +//! small enums for the balance and kind markers) so it borsh-encodes +//! and links without the on-chain stack. The one non-obvious invariant: +//! the marker enums are canonical wire forms, not on-chain keccak +//! markers, so the adapter, not this type, owns the projection to and +//! from chain. + use core::fmt; +use alloy_primitives::{Address, U256}; use borsh::{BorshDeserialize, BorshSerialize}; -/// A 20-byte EVM address in wire form. -pub type Address = [u8; 20]; - -/// A 256-bit amount as its 32-byte big-endian representation. -pub type U256 = [u8; 32]; - /// The token an order sells, typed so a builder call cannot swap /// sides with the buy token. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -174,7 +167,7 @@ impl OrderBuilder { buy_amount, valid_to, app_data: [0; 32], - fee_amount: [0; 32], + fee_amount: U256::ZERO, kind, partially_fillable: false, sell_token_balance: SellTokenSource::Erc20, @@ -302,18 +295,14 @@ mod tests { fn sample() -> OrderBody { OrderBody { - sell_token: [0x11; 20], - buy_token: [0x22; 20], - receiver: Some([0x33; 20]), - sell_amount: { - let mut a = [0u8; 32]; - a[31] = 0x2a; - a - }, - buy_amount: [0xff; 32], + sell_token: Address::repeat_byte(0x11), + buy_token: Address::repeat_byte(0x22), + receiver: Some(Address::repeat_byte(0x33)), + sell_amount: U256::from(0x2a_u64), + buy_amount: U256::MAX, valid_to: 0xffff_ffff, app_data: [0x44; 32], - fee_amount: [0u8; 32], + fee_amount: U256::ZERO, kind: OrderKind::Sell, partially_fillable: false, sell_token_balance: SellTokenSource::Erc20, @@ -324,13 +313,13 @@ mod tests { #[test] fn sell_builder_matches_the_literal() { let built = OrderBody::sell( - SellToken([0x11; 20]), + SellToken(Address::repeat_byte(0x11)), sample().sell_amount, - BuyToken([0x22; 20]), - [0xff; 32], + BuyToken(Address::repeat_byte(0x22)), + U256::MAX, 0xffff_ffff, ) - .receiver([0x33; 20]) + .receiver(Address::repeat_byte(0x33)) .app_data([0x44; 32]) .build(); assert_eq!(built, sample()); @@ -339,43 +328,43 @@ mod tests { #[test] fn buy_builder_fixes_the_buy_side() { let built = OrderBody::buy( - BuyToken([0x22; 20]), - [0xff; 32], - SellToken([0x11; 20]), - [0x01; 32], + BuyToken(Address::repeat_byte(0x22)), + U256::MAX, + SellToken(Address::repeat_byte(0x11)), + U256::from(1u64), 100, ) .partially_fillable() .sell_token_balance(SellTokenSource::External) .buy_token_balance(BuyTokenDestination::Internal) - .fee_amount([0x05; 32]) + .fee_amount(U256::from(5u64)) .build(); assert_eq!(built.kind, OrderKind::Buy); - assert_eq!(built.sell_token, [0x11; 20]); - assert_eq!(built.buy_token, [0x22; 20]); - assert_eq!(built.sell_amount, [0x01; 32]); - assert_eq!(built.buy_amount, [0xff; 32]); + assert_eq!(built.sell_token, Address::repeat_byte(0x11)); + assert_eq!(built.buy_token, Address::repeat_byte(0x22)); + assert_eq!(built.sell_amount, U256::from(1u64)); + assert_eq!(built.buy_amount, U256::MAX); assert_eq!(built.valid_to, 100); assert!(built.partially_fillable); assert_eq!(built.sell_token_balance, SellTokenSource::External); assert_eq!(built.buy_token_balance, BuyTokenDestination::Internal); - assert_eq!(built.fee_amount, [0x05; 32]); + assert_eq!(built.fee_amount, U256::from(5u64)); assert_eq!(built.receiver, None); } #[test] fn builder_defaults_are_the_wire_defaults() { let built = OrderBody::sell( - SellToken([0x11; 20]), - [0x01; 32], - BuyToken([0x22; 20]), - [0x02; 32], + SellToken(Address::repeat_byte(0x11)), + U256::from(1u64), + BuyToken(Address::repeat_byte(0x22)), + U256::from(2u64), 1, ) .build(); assert_eq!(built.receiver, None); assert_eq!(built.app_data, [0; 32]); - assert_eq!(built.fee_amount, [0; 32]); + assert_eq!(built.fee_amount, U256::ZERO); assert!(!built.partially_fillable); assert_eq!(built.sell_token_balance, SellTokenSource::Erc20); assert_eq!(built.buy_token_balance, BuyTokenDestination::Erc20); @@ -421,7 +410,7 @@ mod tests { fn signed_order_borsh_round_trips() { let signed = SignedOrder { order: sample(), - owner: [0x55; 20], + owner: Address::repeat_byte(0x55), signature: vec![0xC0, 0xFF, 0xEE], }; let bytes = borsh::to_vec(&signed).expect("encode"); diff --git a/crates/cow-venue/tests/conformance.rs b/crates/cow-venue/tests/conformance.rs index 4c176300..24efdd12 100644 --- a/crates/cow-venue/tests/conformance.rs +++ b/crates/cow-venue/tests/conformance.rs @@ -3,9 +3,12 @@ //! the adapter's own derivation. The files are the contract a non-Rust //! adapter author reads. -use cow_venue::{CowAdapter, CowIntentBody}; -use videre_sdk::VenueAdapter; -use videre_test::{CodecVectors, HeaderGoldens}; +use alloy_primitives::{Address, U256}; +use cow_venue::{ + BuyToken, CowAdapter, CowIntent, CowIntentBody, OrderBody, SellToken, SignedOrder, +}; +use videre_sdk::{IntentBody as _, VenueAdapter}; +use videre_test::{CodecVectors, Expectation, HeaderGoldens}; fn fixture(name: &str) -> std::path::PathBuf { std::path::Path::new(env!("CARGO_MANIFEST_DIR")) @@ -13,6 +16,95 @@ fn fixture(name: &str) -> std::path::PathBuf { .join(name) } +/// The one order every published fixture carries. +fn order_body() -> OrderBody { + OrderBody::sell( + SellToken(Address::repeat_byte(0x11)), + U256::from(42u64), + BuyToken(Address::repeat_byte(0x22)), + U256::from(41u64), + 1_700_000_000, + ) + .app_data([0x44; 32]) + .build() +} + +fn signed_order() -> SignedOrder { + SignedOrder { + order: order_body(), + owner: Address::repeat_byte(0x55), + signature: vec![0xC0, 0xFF, 0xEE], + } +} + +fn encoded(intent: CowIntent) -> Vec { + CowIntentBody::V1(intent).to_bytes().expect("body encodes") +} + +/// Rebuild the published codec vectors from the shipped codec. +fn build_codec_vectors() -> CodecVectors { + let mut vectors = CodecVectors::new("cow-venue/cow-intent-body"); + vectors + .push_round_trip( + "v1-order", + &CowIntentBody::V1(CowIntent::Order(order_body())), + ) + .expect("order body encodes"); + vectors + .push_round_trip( + "v1-signed", + &CowIntentBody::V1(CowIntent::Signed(signed_order())), + ) + .expect("signed order encodes"); + let mut unknown = encoded(CowIntent::Order(order_body())); + unknown[0] = 9; + vectors.push_failure( + "unknown-version", + unknown, + Expectation::UnknownVersion { version: 9 }, + ); + vectors.push_failure("empty", Vec::new(), Expectation::Empty); + let mut truncated = encoded(CowIntent::Order(order_body())); + truncated.truncate(truncated.len() - 1); + vectors.push_failure( + "truncated-payload", + truncated, + Expectation::Malformed { version: 0 }, + ); + let mut trailing = encoded(CowIntent::Order(order_body())); + trailing.push(0); + vectors.push_failure( + "trailing-bytes", + trailing, + Expectation::Malformed { version: 0 }, + ); + vectors +} + +/// Rebuild the published header goldens through the adapter's own +/// mainnet-configured derivation. +fn build_header_goldens() -> HeaderGoldens { + CowAdapter::init(vec![("chain".to_owned(), "1".to_owned())]).expect("config parses"); + let mut goldens = HeaderGoldens::new("cow"); + goldens + .record( + "v1-order-presign", + encoded(CowIntent::Order(order_body())), + CowAdapter::derive_header, + ) + .expect("header derives") + .notes = Some("unsigned order: authorised by host-held keys (pre-sign)".to_owned()); + goldens + .record( + "v1-signed", + encoded(CowIntent::Signed(signed_order())), + CowAdapter::derive_header, + ) + .expect("header derives") + .notes = Some("owner-signed order: EIP-1271".to_owned()); + goldens +} + #[test] fn codec_conforms_to_the_published_vectors() { let vectors = CodecVectors::load(fixture("cow-intent-body.json")).expect("vectors parse"); @@ -27,3 +119,40 @@ fn derive_header_conforms_to_the_published_goldens() { let goldens = HeaderGoldens::load(fixture("cow-header-goldens.json")).expect("goldens parse"); goldens.assert_conforms(CowAdapter::derive_header); } + +#[test] +fn published_vectors_match_regeneration() { + assert_eq!( + CodecVectors::load(fixture("cow-intent-body.json")) + .expect("vectors parse") + .to_json(), + build_codec_vectors().to_json(), + "cow-intent-body.json has drifted; run the ignored \ + regenerate_published_fixtures test and commit the result", + ); +} + +#[test] +fn published_goldens_match_regeneration() { + assert_eq!( + HeaderGoldens::load(fixture("cow-header-goldens.json")) + .expect("goldens parse") + .to_json(), + build_header_goldens().to_json(), + "cow-header-goldens.json has drifted; run the ignored \ + regenerate_published_fixtures test and commit the result", + ); +} + +/// Rewrite the published files after a deliberate wire change, then +/// commit the diff. +#[test] +#[ignore = "writes the published fixture files in place"] +fn regenerate_published_fixtures() { + build_codec_vectors() + .write(fixture("cow-intent-body.json")) + .unwrap(); + build_header_goldens() + .write(fixture("cow-header-goldens.json")) + .unwrap(); +} diff --git a/crates/cow-venue/tests/vectors/cow-header-goldens.json b/crates/cow-venue/tests/vectors/cow-header-goldens.json index d482f6a2..08555774 100644 --- a/crates/cow-venue/tests/vectors/cow-header-goldens.json +++ b/crates/cow-venue/tests/vectors/cow-header-goldens.json @@ -4,7 +4,7 @@ "goldens": [ { "name": "v1-order-presign", - "body": "00001111111111111111111111111111111111111111222222222222222222222222222222222222222200000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002900f153654444444444444444444444444444444444444444444444444444444444444444000000000000000000000000000000000000000000000000000000000000000000000000", + "body": "000011111111111111111111111111111111111111112222222222222222222222222222222222222222002a00000000000000000000000000000000000000000000000000000000000000290000000000000000000000000000000000000000000000000000000000000000f153654444444444444444444444444444444444444444444444444444444444444444000000000000000000000000000000000000000000000000000000000000000000000000", "header": { "gives": { "asset": { @@ -31,7 +31,7 @@ }, { "name": "v1-signed", - "body": "00011111111111111111111111111111111111111111222222222222222222222222222222222222222200000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002900f153654444444444444444444444444444444444444444444444444444444444444444000000000000000000000000000000000000000000000000000000000000000000000000555555555555555555555555555555555555555503000000c0ffee", + "body": "000111111111111111111111111111111111111111112222222222222222222222222222222222222222002a00000000000000000000000000000000000000000000000000000000000000290000000000000000000000000000000000000000000000000000000000000000f153654444444444444444444444444444444444444444444444444444444444444444000000000000000000000000000000000000000000000000000000000000000000000000555555555555555555555555555555555555555503000000c0ffee", "header": { "gives": { "asset": { diff --git a/crates/cow-venue/tests/vectors/cow-intent-body.json b/crates/cow-venue/tests/vectors/cow-intent-body.json index 651231e7..13b58d34 100644 --- a/crates/cow-venue/tests/vectors/cow-intent-body.json +++ b/crates/cow-venue/tests/vectors/cow-intent-body.json @@ -4,17 +4,17 @@ "vectors": [ { "name": "v1-order", - "bytes": "00001111111111111111111111111111111111111111222222222222222222222222222222222222222200000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002900f153654444444444444444444444444444444444444444444444444444444444444444000000000000000000000000000000000000000000000000000000000000000000000000", + "bytes": "000011111111111111111111111111111111111111112222222222222222222222222222222222222222002a00000000000000000000000000000000000000000000000000000000000000290000000000000000000000000000000000000000000000000000000000000000f153654444444444444444444444444444444444444444444444444444444444444444000000000000000000000000000000000000000000000000000000000000000000000000", "expect": "round-trip" }, { "name": "v1-signed", - "bytes": "00011111111111111111111111111111111111111111222222222222222222222222222222222222222200000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002900f153654444444444444444444444444444444444444444444444444444444444444444000000000000000000000000000000000000000000000000000000000000000000000000555555555555555555555555555555555555555503000000c0ffee", + "bytes": "000111111111111111111111111111111111111111112222222222222222222222222222222222222222002a00000000000000000000000000000000000000000000000000000000000000290000000000000000000000000000000000000000000000000000000000000000f153654444444444444444444444444444444444444444444444444444444444444444000000000000000000000000000000000000000000000000000000000000000000000000555555555555555555555555555555555555555503000000c0ffee", "expect": "round-trip" }, { "name": "unknown-version", - "bytes": "09001111111111111111111111111111111111111111222222222222222222222222222222222222222200000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002900f153654444444444444444444444444444444444444444444444444444444444444444000000000000000000000000000000000000000000000000000000000000000000000000", + "bytes": "090011111111111111111111111111111111111111112222222222222222222222222222222222222222002a00000000000000000000000000000000000000000000000000000000000000290000000000000000000000000000000000000000000000000000000000000000f153654444444444444444444444444444444444444444444444444444444444444444000000000000000000000000000000000000000000000000000000000000000000000000", "expect": { "unknown-version": { "version": 9 @@ -28,7 +28,7 @@ }, { "name": "truncated-payload", - "bytes": "00001111111111111111111111111111111111111111222222222222222222222222222222222222222200000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002900f1536544444444444444444444444444444444444444444444444444444444444444440000000000000000000000000000000000000000000000000000000000000000000000", + "bytes": "000011111111111111111111111111111111111111112222222222222222222222222222222222222222002a00000000000000000000000000000000000000000000000000000000000000290000000000000000000000000000000000000000000000000000000000000000f1536544444444444444444444444444444444444444444444444444444444444444440000000000000000000000000000000000000000000000000000000000000000000000", "expect": { "malformed": { "version": 0 @@ -37,7 +37,7 @@ }, { "name": "trailing-bytes", - "bytes": "00001111111111111111111111111111111111111111222222222222222222222222222222222222222200000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002900f15365444444444444444444444444444444444444444444444444444444444444444400000000000000000000000000000000000000000000000000000000000000000000000000", + "bytes": "000011111111111111111111111111111111111111112222222222222222222222222222222222222222002a00000000000000000000000000000000000000000000000000000000000000290000000000000000000000000000000000000000000000000000000000000000f15365444444444444444444444444444444444444444444444444444444444444444400000000000000000000000000000000000000000000000000000000000000000000000000", "expect": { "malformed": { "version": 0 diff --git a/crates/videre-sdk/src/lib.rs b/crates/videre-sdk/src/lib.rs index d38b895b..d32e97f3 100644 --- a/crates/videre-sdk/src/lib.rs +++ b/crates/videre-sdk/src/lib.rs @@ -119,7 +119,7 @@ pub use bindings::videre::types::types::{ UnsignedTx, VenueError, }; /// The value-flow vocabulary intent headers are expressed in. -pub use bindings::videre::value_flow::types as value_flow; +pub mod value_flow; /// The venue status-body codec: decode an `intent-status` event's /// `status` bytes into a typed [`StatusBody`](status_body::StatusBody). pub use videre_status_body as status_body; diff --git a/crates/videre-sdk/src/value_flow.rs b/crates/videre-sdk/src/value_flow.rs new file mode 100644 index 00000000..d72a5e76 --- /dev/null +++ b/crates/videre-sdk/src/value_flow.rs @@ -0,0 +1,19 @@ +//! The value-flow wire types, re-exported from +//! [`bindings`](crate::bindings) with constructors that own the `uint` +//! encoding so callers never hand-roll it. + +pub use crate::bindings::videre::value_flow::types::*; + +impl AssetAmount { + /// An ERC-20 amount. Encodes `amount` as the value-flow `uint`: + /// minimal big-endian, where zero is the empty list. + #[must_use] + pub fn erc20(token: nexum_sdk::prelude::Address, amount: nexum_sdk::prelude::U256) -> Self { + Self { + asset: Asset::Erc20(Erc20 { + token: token.as_slice().to_vec(), + }), + amount: amount.to_be_bytes_trimmed_vec(), + } + } +} diff --git a/modules/twap-monitor/src/keeper.rs b/modules/twap-monitor/src/keeper.rs index ec2ed215..e7719e8a 100644 --- a/modules/twap-monitor/src/keeper.rs +++ b/modules/twap-monitor/src/keeper.rs @@ -371,7 +371,7 @@ fn signed_intent_body( let order_data = gpv2_to_order_data(order)?; Some(CowIntentBody::V1(CowIntent::Signed(SignedOrder { order: order_data_to_body(&order_data), - owner: owner.into_array(), + owner, signature: signature.to_vec(), }))) }