From d06a7ac949a6f056d5d348053985775b08e51af7 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Fri, 24 Jul 2026 08:31:09 +0000 Subject: [PATCH] cow-venue: replace OrderBuilder typestate with constructor args --- crates/cow-venue/src/adapter.rs | 14 ++- crates/cow-venue/src/body.rs | 16 +-- crates/cow-venue/src/client.rs | 28 ++++-- crates/cow-venue/src/order.rs | 169 ++++++++++++++------------------ 4 files changed, 113 insertions(+), 114 deletions(-) diff --git a/crates/cow-venue/src/adapter.rs b/crates/cow-venue/src/adapter.rs index 693154ee..aa8c2212 100644 --- a/crates/cow-venue/src/adapter.rs +++ b/crates/cow-venue/src/adapter.rs @@ -557,11 +557,15 @@ mod tests { } fn order_body() -> OrderBody { - OrderBody::sell(SellToken([0x11; 20]), amount(42)) - .for_at_least(BuyToken([0x22; 20]), amount(41)) - .valid_to(1_700_000_000) - .app_data([0x44; 32]) - .build() + OrderBody::sell( + SellToken([0x11; 20]), + amount(42), + BuyToken([0x22; 20]), + amount(41), + 1_700_000_000, + ) + .app_data([0x44; 32]) + .build() } fn amount(value: u8) -> [u8; 32] { diff --git a/crates/cow-venue/src/body.rs b/crates/cow-venue/src/body.rs index 6bb39165..1442089e 100644 --- a/crates/cow-venue/src/body.rs +++ b/crates/cow-venue/src/body.rs @@ -41,12 +41,16 @@ mod tests { use crate::order::{BuyToken, SellToken}; fn order_body() -> OrderBody { - OrderBody::sell(SellToken([0x11; 20]), [0x01; 32]) - .for_at_least(BuyToken([0x22; 20]), [0x02; 32]) - .valid_to(1_700_000_000) - .app_data([0x44; 32]) - .partially_fillable() - .build() + OrderBody::sell( + SellToken([0x11; 20]), + [0x01; 32], + BuyToken([0x22; 20]), + [0x02; 32], + 1_700_000_000, + ) + .app_data([0x44; 32]) + .partially_fillable() + .build() } /// The codec conformance set: the v1 intent as a round-trip vector diff --git a/crates/cow-venue/src/client.rs b/crates/cow-venue/src/client.rs index e61e5b65..83f56095 100644 --- a/crates/cow-venue/src/client.rs +++ b/crates/cow-venue/src/client.rs @@ -99,12 +99,16 @@ mod tests { use crate::body::CowIntent; use crate::order::{BuyToken, OrderBody, SellToken}; CowIntentBody::V1(CowIntent::Order( - OrderBody::sell(SellToken([0x11; 20]), [0x01; 32]) - .for_at_least(BuyToken([0x22; 20]), [0x02; 32]) - .valid_to(1_700_000_000) - .app_data([0x44; 32]) - .partially_fillable() - .build(), + OrderBody::sell( + SellToken([0x11; 20]), + [0x01; 32], + BuyToken([0x22; 20]), + [0x02; 32], + 1_700_000_000, + ) + .app_data([0x44; 32]) + .partially_fillable() + .build(), )) } @@ -126,10 +130,14 @@ mod tests { assert!(id.starts_with("cow:0x")); let other = CowIntentBody::V1(CowIntent::Signed(SignedOrder { - order: OrderBody::sell(SellToken([0x11; 20]), [0x01; 32]) - .for_at_least(BuyToken([0x22; 20]), [0x02; 32]) - .valid_to(1_700_000_000) - .build(), + order: OrderBody::sell( + SellToken([0x11; 20]), + [0x01; 32], + BuyToken([0x22; 20]), + [0x02; 32], + 1_700_000_000, + ) + .build(), owner: [0x55; 20], signature: vec![0xC0], })); diff --git a/crates/cow-venue/src/order.rs b/crates/cow-venue/src/order.rs index 8bb1d725..dbc0a67a 100644 --- a/crates/cow-venue/src/order.rs +++ b/crates/cow-venue/src/order.rs @@ -11,7 +11,6 @@ use alloc::vec::Vec; use core::fmt; -use core::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; @@ -106,46 +105,65 @@ pub struct OrderBody { } impl OrderBody { - /// Start a sell order: `amount` of `token` is the fixed side. + /// A sell order: `sell_amount` of `sell` is the fixed side, at least + /// `buy_amount` of `buy` in return, expiring at `valid_to`. #[must_use] - pub const fn sell(token: SellToken, amount: U256) -> OrderBuilder { - OrderBuilder::start(OrderKind::Sell, token.0, amount, [0; 20], [0; 32]) - } - - /// Start a buy order: `amount` of `token` is the fixed side. + pub const fn sell( + sell: SellToken, + sell_amount: U256, + buy: BuyToken, + buy_amount: U256, + valid_to: u32, + ) -> OrderBuilder { + OrderBuilder::new( + OrderKind::Sell, + sell.0, + sell_amount, + buy.0, + buy_amount, + valid_to, + ) + } + + /// A buy order: `buy_amount` of `buy` is the fixed side, spending at + /// most `sell_amount` of `sell`, expiring at `valid_to`. #[must_use] - pub const fn buy(token: BuyToken, amount: U256) -> OrderBuilder { - OrderBuilder::start(OrderKind::Buy, [0; 20], [0; 32], token.0, amount) + pub const fn buy( + buy: BuyToken, + buy_amount: U256, + sell: SellToken, + sell_amount: U256, + valid_to: u32, + ) -> OrderBuilder { + OrderBuilder::new( + OrderKind::Buy, + sell.0, + sell_amount, + buy.0, + buy_amount, + valid_to, + ) } } -/// Builder state: the buy-side limit is unset. -pub enum NeedsBuy {} -/// Builder state: the sell-side limit is unset. -pub enum NeedsSell {} -/// Builder state: the expiry is unset. -pub enum NeedsValidTo {} -/// Builder state: every required field is set. -pub enum Ready {} - -/// Typestate builder for [`OrderBody`]: [`OrderBody::sell`] or -/// [`OrderBody::buy`] fixes the kind and its side, the counter-side -/// limit and the expiry are compile-time required, and the optionals -/// default (self-receive, zero `app_data` and `fee_amount`, -/// fill-or-kill, ERC-20 balances). +/// Builder for [`OrderBody`]: the required fields (both sides and the +/// expiry) are constructor args, so completeness is compile-time +/// guaranteed without state markers; the optionals default (self-receive, +/// zero `app_data` and `fee_amount`, fill-or-kill, ERC-20 balances). +/// Use [`OrderBody::sell`] or [`OrderBody::buy`] to fix the kind. #[derive(Clone, Debug)] -pub struct OrderBuilder { +pub struct OrderBuilder { body: OrderBody, - state: PhantomData, } -impl OrderBuilder { - const fn start( +impl OrderBuilder { + const fn new( kind: OrderKind, sell_token: Address, sell_amount: U256, buy_token: Address, buy_amount: U256, + valid_to: u32, ) -> Self { Self { body: OrderBody { @@ -154,7 +172,7 @@ impl OrderBuilder { receiver: None, sell_amount, buy_amount, - valid_to: 0, + valid_to, app_data: [0; 32], fee_amount: [0; 32], kind, @@ -162,56 +180,9 @@ impl OrderBuilder { sell_token_balance: SellTokenSource::Erc20, buy_token_balance: BuyTokenDestination::Erc20, }, - state: PhantomData, } } - const fn into_state(self) -> OrderBuilder { - OrderBuilder { - body: self.body, - state: PhantomData, - } - } -} - -impl OrderBuilder { - /// Demand at least `amount` of `token` in return. - #[must_use] - pub const fn for_at_least( - mut self, - token: BuyToken, - amount: U256, - ) -> OrderBuilder { - self.body.buy_token = token.0; - self.body.buy_amount = amount; - self.into_state() - } -} - -impl OrderBuilder { - /// Spend at most `amount` of `token`. - #[must_use] - pub const fn for_at_most( - mut self, - token: SellToken, - amount: U256, - ) -> OrderBuilder { - self.body.sell_token = token.0; - self.body.sell_amount = amount; - self.into_state() - } -} - -impl OrderBuilder { - /// Expire at `valid_to` (Unix seconds). - #[must_use] - pub const fn valid_to(mut self, valid_to: u32) -> OrderBuilder { - self.body.valid_to = valid_to; - self.into_state() - } -} - -impl OrderBuilder { /// Deliver the buy token to `receiver` instead of the owner. #[must_use] pub const fn receiver(mut self, receiver: Address) -> Self { @@ -352,25 +323,33 @@ mod tests { #[test] fn sell_builder_matches_the_literal() { - let built = OrderBody::sell(SellToken([0x11; 20]), sample().sell_amount) - .for_at_least(BuyToken([0x22; 20]), [0xff; 32]) - .valid_to(0xffff_ffff) - .receiver([0x33; 20]) - .app_data([0x44; 32]) - .build(); + let built = OrderBody::sell( + SellToken([0x11; 20]), + sample().sell_amount, + BuyToken([0x22; 20]), + [0xff; 32], + 0xffff_ffff, + ) + .receiver([0x33; 20]) + .app_data([0x44; 32]) + .build(); assert_eq!(built, sample()); } #[test] fn buy_builder_fixes_the_buy_side() { - let built = OrderBody::buy(BuyToken([0x22; 20]), [0xff; 32]) - .for_at_most(SellToken([0x11; 20]), [0x01; 32]) - .valid_to(100) - .partially_fillable() - .sell_token_balance(SellTokenSource::External) - .buy_token_balance(BuyTokenDestination::Internal) - .fee_amount([0x05; 32]) - .build(); + let built = OrderBody::buy( + BuyToken([0x22; 20]), + [0xff; 32], + SellToken([0x11; 20]), + [0x01; 32], + 100, + ) + .partially_fillable() + .sell_token_balance(SellTokenSource::External) + .buy_token_balance(BuyTokenDestination::Internal) + .fee_amount([0x05; 32]) + .build(); assert_eq!(built.kind, OrderKind::Buy); assert_eq!(built.sell_token, [0x11; 20]); assert_eq!(built.buy_token, [0x22; 20]); @@ -386,10 +365,14 @@ mod tests { #[test] fn builder_defaults_are_the_wire_defaults() { - let built = OrderBody::sell(SellToken([0x11; 20]), [0x01; 32]) - .for_at_least(BuyToken([0x22; 20]), [0x02; 32]) - .valid_to(1) - .build(); + let built = OrderBody::sell( + SellToken([0x11; 20]), + [0x01; 32], + BuyToken([0x22; 20]), + [0x02; 32], + 1, + ) + .build(); assert_eq!(built.receiver, None); assert_eq!(built.app_data, [0; 32]); assert_eq!(built.fee_amount, [0; 32]);