Skip to content
Merged
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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion crates/composable-cow/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
40 changes: 33 additions & 7 deletions crates/composable-cow/src/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>` 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<u8>,
pub static_input: Bytes,
}

#[cfg(test)]
Expand All @@ -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]),
}
}

Expand All @@ -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());
}
}
Loading