diff --git a/Cargo.lock b/Cargo.lock index d63586bc..864e0dcc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -227,6 +227,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4885c1409b6936c4898e646ef58baf6ec54edaf6d8179f79df805a7b85b7cf3e" dependencies = [ "alloy-rlp", + "borsh", "bytes", "cfg-if", "const-hex", @@ -4649,6 +4650,7 @@ dependencies = [ "ark-ff 0.3.0", "ark-ff 0.4.2", "ark-ff 0.5.0", + "borsh", "bytes", "fastrlp 0.3.1", "fastrlp 0.4.0", diff --git a/crates/composable-cow/Cargo.toml b/crates/composable-cow/Cargo.toml index ceb66cc6..f12a385e 100644 --- a/crates/composable-cow/Cargo.toml +++ b/crates/composable-cow/Cargo.toml @@ -14,7 +14,9 @@ description = "ComposableCoW keeper machinery: the conditional-order body and th workspace = true [dependencies] -alloy-primitives.workspace = true +# `borsh` supplies the `Address`/`B256`/`Bytes` borsh impls the body +# derives against, byte-identical to the fields they replace. +alloy-primitives = { workspace = true, features = ["borsh"] } alloy-sol-types.workspace = true borsh.workspace = true cowprotocol = { version = "0.2.0", default-features = false } diff --git a/crates/composable-cow/src/body.rs b/crates/composable-cow/src/body.rs index b5f6bf02..1ca8466c 100644 --- a/crates/composable-cow/src/body.rs +++ b/crates/composable-cow/src/body.rs @@ -7,19 +7,25 @@ //! 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. +//! +//! Borsh comes from `alloy-primitives`' own `borsh` feature, so no +//! adapter is needed: `Address` and `B256` encode as their bare bytes +//! and `Bytes` length-prefixed, leaving the wire byte-identical to the +//! `[u8; 20]`/`[u8; 32]`/`Vec` these fields replaced. +use alloy_primitives::{Address, B256, Bytes}; use borsh::{BorshDeserialize, BorshSerialize}; /// 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: [u8; 20], + pub handler: Address, /// Salt distinguishing otherwise-identical conditional orders. - pub salt: [u8; 32], + pub salt: B256, /// Handler-specific static input; opaque to everything but the /// named handler. - pub static_input: Vec, + pub static_input: Bytes, } #[cfg(test)] @@ -28,9 +34,9 @@ mod tests { fn sample() -> ComposableBody { ComposableBody { - handler: [0xab; 20], - salt: [0xcd; 32], - static_input: vec![1, 2, 3, 4, 5], + handler: Address::repeat_byte(0xab), + salt: B256::repeat_byte(0xcd), + static_input: Bytes::from_static(&[1, 2, 3, 4, 5]), } } @@ -47,11 +53,31 @@ mod tests { #[test] fn empty_static_input_round_trips() { let mut body = sample(); - body.static_input = Vec::new(); + body.static_input = Bytes::new(); let bytes = borsh::to_vec(&body).expect("encode"); assert_eq!( ComposableBody::try_from_slice(&bytes).expect("decode"), body ); } + + /// The alloy types must encode exactly as the raw arrays did: 20 + /// bare handler bytes, 32 bare salt bytes, then a `u32`-length- + /// prefixed static input. + #[test] + fn wire_matches_the_raw_array_layout() { + let mut expected = Vec::new(); + expected.extend_from_slice(&[0xab; 20]); + expected.extend_from_slice(&[0xcd; 32]); + expected.extend_from_slice(&5_u32.to_le_bytes()); + expected.extend_from_slice(&[1, 2, 3, 4, 5]); + assert_eq!(borsh::to_vec(&sample()).expect("encode"), expected); + } + + /// A truncated handler is a decode error, not a silent short read. + #[test] + fn truncated_input_fails_to_decode() { + let bytes = borsh::to_vec(&sample()).expect("encode"); + assert!(ComposableBody::try_from_slice(&bytes[..10]).is_err()); + } }