Skip to main content

cow_signing/
types.rs

1//! Core signing types for `CoW` Protocol orders.
2
3use std::fmt;
4
5use alloy_primitives::Address;
6use cow_types::SigningScheme;
7
8// `UnsignedOrder` lives in `cow-types` so that L2 sibling crates
9// (notably `cow-settlement`) can consume it without depending on
10// `cow-signing`. Re-exported here for backwards compatibility with
11// the pre-split `cow_signing::types::UnsignedOrder` path.
12pub use cow_types::UnsignedOrder;
13
14/// The EIP-712 domain for `CoW` Protocol orders.
15///
16/// Mirrors `TypedDataDomain` from the `TypeScript` SDK.
17#[derive(Debug, Clone)]
18pub struct OrderDomain {
19    /// Protocol name (`"Gnosis Protocol"`).
20    pub name: &'static str,
21    /// Protocol version (`"v2"`).
22    pub version: &'static str,
23    /// Chain ID where orders are settled.
24    pub chain_id: u64,
25    /// `GPv2Settlement` contract address (the EIP-712 verifying contract).
26    pub verifying_contract: Address,
27}
28
29impl OrderDomain {
30    /// Construct the standard `CoW` Protocol EIP-712 domain for `chain_id`.
31    ///
32    /// Uses the canonical [`SETTLEMENT_CONTRACT`](cow_chains::contracts::SETTLEMENT_CONTRACT)
33    /// address as the verifying contract.
34    ///
35    /// # Arguments
36    ///
37    /// * `chain_id` - The EVM chain ID where orders will be settled.
38    ///
39    /// # Returns
40    ///
41    /// An [`OrderDomain`] configured for the given chain.
42    #[must_use]
43    pub const fn for_chain(chain_id: u64) -> Self {
44        Self {
45            name: "Gnosis Protocol",
46            version: "v2",
47            chain_id,
48            verifying_contract: cow_chains::contracts::SETTLEMENT_CONTRACT,
49        }
50    }
51
52    /// Compute the EIP-712 domain separator for this domain.
53    ///
54    /// When all fields are at their default values (`"Gnosis Protocol"`,
55    /// `"v2"`, canonical settlement contract), this is equivalent to
56    /// [`crate::domain_separator`]. When any field has been
57    /// overridden via the `with_*` builder methods, the separator is
58    /// computed from the custom values.
59    ///
60    /// ```no_run
61    /// use cow_signing::types::OrderDomain;
62    ///
63    /// let domain = OrderDomain::for_chain(1);
64    /// let sep = domain.domain_separator();
65    /// assert_ne!(sep, alloy_primitives::B256::ZERO);
66    /// ```
67    #[must_use]
68    pub fn domain_separator(&self) -> alloy_primitives::B256 {
69        crate::domain_separator_from(self)
70    }
71
72    /// Override the protocol name used in the EIP-712 domain separator.
73    ///
74    /// The default is `"Gnosis Protocol"`. Use this when computing
75    /// domain separators for forks or alternative deployments.
76    ///
77    /// # Arguments
78    ///
79    /// * `name` - The protocol name string.
80    ///
81    /// # Returns
82    ///
83    /// `self` with the updated name.
84    #[must_use]
85    pub const fn with_name(mut self, name: &'static str) -> Self {
86        self.name = name;
87        self
88    }
89
90    /// Override the protocol version used in the EIP-712 domain separator.
91    ///
92    /// The default is `"v2"`.
93    ///
94    /// # Arguments
95    ///
96    /// * `version` - The protocol version string.
97    ///
98    /// # Returns
99    ///
100    /// `self` with the updated version.
101    #[must_use]
102    pub const fn with_version(mut self, version: &'static str) -> Self {
103        self.version = version;
104        self
105    }
106
107    /// Override the verifying contract address.
108    ///
109    /// The default is the canonical `GPv2Settlement` contract. Use this
110    /// when computing domain separators for alternative deployments.
111    ///
112    /// # Arguments
113    ///
114    /// * `contract` - The verifying contract address.
115    ///
116    /// # Returns
117    ///
118    /// `self` with the updated verifying contract.
119    #[must_use]
120    pub const fn with_verifying_contract(mut self, contract: Address) -> Self {
121        self.verifying_contract = contract;
122        self
123    }
124
125    /// Override the chain ID.
126    ///
127    /// # Arguments
128    ///
129    /// * `chain_id` - The EVM chain ID.
130    ///
131    /// # Returns
132    ///
133    /// `self` with the updated chain ID.
134    #[must_use]
135    pub const fn with_chain_id(mut self, chain_id: u64) -> Self {
136        self.chain_id = chain_id;
137        self
138    }
139}
140
141/// The full EIP-712 typed data envelope for a `CoW` Protocol order.
142///
143/// Mirrors `OrderTypedData` from the `TypeScript` SDK.  Pass this to a hardware
144/// wallet or any EIP-712-aware signer that needs the structured domain and types
145/// alongside the order message.
146#[derive(Debug, Clone)]
147pub struct OrderTypedData {
148    /// The EIP-712 domain for `CoW` Protocol.
149    pub domain: OrderDomain,
150    /// EIP-712 primary type name (`"GPv2Order.Data"`).
151    pub primary_type: &'static str,
152    /// The order message to sign.
153    pub order: UnsignedOrder,
154}
155
156impl OrderTypedData {
157    /// Construct an [`OrderTypedData`] envelope for the given domain and order.
158    ///
159    /// The primary type is always `"GPv2Order.Data"` per the `CoW` Protocol EIP-712 spec.
160    ///
161    /// # Arguments
162    ///
163    /// * `domain` - The EIP-712 domain for the target chain.
164    /// * `order` - The unsigned order to wrap.
165    ///
166    /// # Returns
167    ///
168    /// An [`OrderTypedData`] envelope ready for signing.
169    #[must_use]
170    pub const fn new(domain: OrderDomain, order: UnsignedOrder) -> Self {
171        Self { domain, primary_type: "GPv2Order.Data", order }
172    }
173
174    /// Returns a reference to the underlying [`UnsignedOrder`].
175    ///
176    /// ```no_run
177    /// use alloy_primitives::{Address, U256};
178    /// use cow_signing::types::{OrderDomain, OrderTypedData};
179    /// use cow_types::UnsignedOrder;
180    ///
181    /// let order = UnsignedOrder::sell(Address::ZERO, Address::ZERO, U256::ZERO, U256::ZERO);
182    /// let typed = OrderTypedData::new(OrderDomain::for_chain(1), order.clone());
183    /// assert_eq!(typed.order_ref().kind, order.kind);
184    /// ```
185    #[must_use]
186    pub const fn order_ref(&self) -> &UnsignedOrder {
187        &self.order
188    }
189
190    /// Returns a reference to the [`OrderDomain`].
191    ///
192    /// # Returns
193    ///
194    /// A shared reference to the inner [`OrderDomain`].
195    #[must_use]
196    pub const fn domain_ref(&self) -> &OrderDomain {
197        &self.domain
198    }
199
200    /// Compute the full EIP-712 signing digest for this typed data.
201    ///
202    /// This is the `keccak256("\x19\x01" ‖ domainSep ‖ orderHash)` value that
203    /// must be signed with a private key to produce a signature accepted by the
204    /// `CoW` Protocol settlement contract.
205    ///
206    /// ```no_run
207    /// use alloy_primitives::{Address, U256};
208    /// use cow_signing::types::{OrderDomain, OrderTypedData};
209    /// use cow_types::UnsignedOrder;
210    ///
211    /// let order = UnsignedOrder::sell(Address::ZERO, Address::ZERO, U256::ZERO, U256::ZERO);
212    /// let typed = OrderTypedData::new(OrderDomain::for_chain(11_155_111), order);
213    /// let digest = typed.signing_digest();
214    /// assert_ne!(digest, alloy_primitives::B256::ZERO);
215    /// ```
216    #[must_use]
217    pub fn signing_digest(&self) -> alloy_primitives::B256 {
218        let domain_sep = crate::domain_separator(self.domain.chain_id);
219        let o_hash = crate::order_hash(&self.order);
220        crate::signing_digest(domain_sep, o_hash)
221    }
222}
223
224/// The result of signing an order — signature bytes and the scheme used.
225#[derive(Debug, Clone)]
226pub struct SigningResult {
227    /// `0x`-prefixed hex-encoded signature.
228    ///
229    /// - EIP-712 / EIP-191: 65-byte `r | s | v` encoding.
230    /// - EIP-1271: arbitrary bytes returned by the smart-contract signer.
231    /// - Pre-sign: the 20-byte owner address.
232    pub signature: String,
233    /// The signing scheme that produced this signature.
234    pub signing_scheme: SigningScheme,
235}
236
237impl SigningResult {
238    /// Construct a [`SigningResult`] from a signature hex string and scheme.
239    ///
240    /// # Arguments
241    ///
242    /// * `signature` - A `0x`-prefixed hex-encoded signature string.
243    /// * `signing_scheme` - The [`SigningScheme`] that produced the signature.
244    ///
245    /// # Returns
246    ///
247    /// A new [`SigningResult`].
248    #[must_use]
249    pub fn new(signature: impl Into<String>, signing_scheme: SigningScheme) -> Self {
250        Self { signature: signature.into(), signing_scheme }
251    }
252
253    /// Returns `true` if this result used the EIP-712 signing scheme.
254    ///
255    /// ```ignore
256    /// use alloy_primitives::{Address, U256};
257    /// use cow_types::EcdsaSigningScheme;
258    /// use cow_signing::types::OrderDomain;
259    /// use cow_types::UnsignedOrder;
260    ///
261    /// let result = cow_rs::SigningResult::new("0xdeadbeef", cow_types::SigningScheme::Eip712);
262    /// assert!(result.is_eip712());
263    /// assert!(!result.is_presign());
264    /// ```
265    #[must_use]
266    pub const fn is_eip712(&self) -> bool {
267        matches!(self.signing_scheme, SigningScheme::Eip712)
268    }
269
270    /// Returns `true` if this result used the EIP-191 (`eth_sign`) scheme.
271    ///
272    /// # Returns
273    ///
274    /// `true` when `signing_scheme` is [`SigningScheme::EthSign`].
275    #[must_use]
276    pub const fn is_eth_sign(&self) -> bool {
277        matches!(self.signing_scheme, SigningScheme::EthSign)
278    }
279
280    /// Returns `true` if this result used the EIP-1271 smart-contract scheme.
281    ///
282    /// ```
283    /// use cow_signing::eip1271_result;
284    ///
285    /// let result = eip1271_result(&[0xde, 0xad]);
286    /// assert!(result.is_eip1271());
287    /// assert!(!result.is_eip712());
288    /// ```
289    #[must_use]
290    pub const fn is_eip1271(&self) -> bool {
291        matches!(self.signing_scheme, SigningScheme::Eip1271)
292    }
293
294    /// Returns `true` if this result used the on-chain pre-sign scheme.
295    ///
296    /// ```
297    /// use alloy_primitives::Address;
298    /// use cow_signing::presign_result;
299    ///
300    /// let result = presign_result(Address::ZERO);
301    /// assert!(result.is_presign());
302    /// assert!(!result.is_eip712());
303    /// ```
304    #[must_use]
305    pub const fn is_presign(&self) -> bool {
306        matches!(self.signing_scheme, SigningScheme::PreSign)
307    }
308
309    /// Returns the length of the signature string in bytes.
310    ///
311    /// ```no_run
312    /// use cow_signing::types::SigningResult;
313    ///
314    /// let result = SigningResult::new("0xdeadbeef", cow_types::SigningScheme::Eip712);
315    /// assert_eq!(result.signature_len(), 10);
316    /// ```
317    #[must_use]
318    pub const fn signature_len(&self) -> usize {
319        self.signature.len()
320    }
321
322    /// Returns the signature as a `0x`-prefixed hex string slice.
323    ///
324    /// ```no_run
325    /// use cow_signing::types::SigningResult;
326    ///
327    /// let result = SigningResult::new("0xdeadbeef", cow_types::SigningScheme::Eip712);
328    /// assert_eq!(result.signature_ref(), "0xdeadbeef");
329    /// ```
330    #[must_use]
331    pub fn signature_ref(&self) -> &str {
332        &self.signature
333    }
334}
335
336impl fmt::Display for OrderDomain {
337    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338        write!(f, "domain(chain={}, contract={:#x})", self.chain_id, self.verifying_contract)
339    }
340}
341
342impl fmt::Display for OrderTypedData {
343    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
344        write!(f, "typed-data(chain={}, {})", self.domain.chain_id, self.order)
345    }
346}
347
348impl fmt::Display for SigningResult {
349    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350        let sig = &self.signature;
351        let short = if sig.len() > 10 { &sig[..10] } else { sig };
352        write!(f, "sig({}, {}…)", self.signing_scheme, short)
353    }
354}
355
356// ── Signing parameter types ──────────────────────────────────────────────────
357
358/// Parameters for signing a `CoW` Protocol order.
359///
360/// Mirrors `SignOrderParams` from the `TypeScript` SDK. In Rust the signer
361/// is typically passed separately to `sign_order`, so this struct bundles
362/// the remaining context needed to produce a valid signature.
363#[derive(Debug, Clone)]
364pub struct SignOrderParams {
365    /// Chain ID on which the order will be settled.
366    pub chain_id: u64,
367    /// The unsigned order intent to sign.
368    pub order: UnsignedOrder,
369    /// The ECDSA signing scheme to use.
370    pub signing_scheme: cow_types::EcdsaSigningScheme,
371}
372
373impl SignOrderParams {
374    /// Construct a [`SignOrderParams`] from its three core fields.
375    ///
376    /// # Arguments
377    ///
378    /// * `chain_id` - Chain ID on which the order will be settled.
379    /// * `order` - The unsigned order intent to sign.
380    /// * `signing_scheme` - The ECDSA signing scheme to use.
381    ///
382    /// # Returns
383    ///
384    /// A new [`SignOrderParams`].
385    #[must_use]
386    pub const fn new(
387        chain_id: u64,
388        order: UnsignedOrder,
389        signing_scheme: cow_types::EcdsaSigningScheme,
390    ) -> Self {
391        Self { chain_id, order, signing_scheme }
392    }
393}
394
395impl fmt::Display for SignOrderParams {
396    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397        write!(f, "sign-order(chain={}, {})", self.chain_id, self.order)
398    }
399}
400
401/// Parameters for signing a single order cancellation.
402///
403/// Mirrors `SignOrderCancellationParams` from the `TypeScript` SDK.
404#[derive(Debug, Clone)]
405pub struct SignOrderCancellationParams {
406    /// Chain ID on which the order was placed.
407    pub chain_id: u64,
408    /// The unique identifier of the order to cancel.
409    pub order_uid: String,
410    /// The ECDSA signing scheme to use.
411    pub signing_scheme: cow_types::EcdsaSigningScheme,
412}
413
414impl SignOrderCancellationParams {
415    /// Construct a [`SignOrderCancellationParams`].
416    ///
417    /// # Arguments
418    ///
419    /// * `chain_id` - Chain ID on which the order was placed.
420    /// * `order_uid` - The unique identifier of the order to cancel.
421    /// * `signing_scheme` - The ECDSA signing scheme to use.
422    ///
423    /// # Returns
424    ///
425    /// A new [`SignOrderCancellationParams`].
426    #[must_use]
427    pub fn new(
428        chain_id: u64,
429        order_uid: impl Into<String>,
430        signing_scheme: cow_types::EcdsaSigningScheme,
431    ) -> Self {
432        Self { chain_id, order_uid: order_uid.into(), signing_scheme }
433    }
434}
435
436impl fmt::Display for SignOrderCancellationParams {
437    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
438        let uid = &self.order_uid;
439        let short = if uid.len() > 10 { &uid[..10] } else { uid };
440        write!(f, "cancel-sign(chain={}, uid={short}…)", self.chain_id)
441    }
442}
443
444/// Parameters for signing multiple order cancellations in bulk.
445///
446/// Mirrors `SignOrderCancellationsParams` from the `TypeScript` SDK.
447#[derive(Debug, Clone)]
448pub struct SignOrderCancellationsParams {
449    /// Chain ID on which the orders were placed.
450    pub chain_id: u64,
451    /// Unique identifiers of the orders to cancel.
452    pub order_uids: Vec<String>,
453    /// The ECDSA signing scheme to use.
454    pub signing_scheme: cow_types::EcdsaSigningScheme,
455}
456
457impl SignOrderCancellationsParams {
458    /// Construct a [`SignOrderCancellationsParams`].
459    ///
460    /// # Arguments
461    ///
462    /// * `chain_id` - Chain ID on which the orders were placed.
463    /// * `order_uids` - Unique identifiers of the orders to cancel.
464    /// * `signing_scheme` - The ECDSA signing scheme to use.
465    ///
466    /// # Returns
467    ///
468    /// A new [`SignOrderCancellationsParams`].
469    #[must_use]
470    pub const fn new(
471        chain_id: u64,
472        order_uids: Vec<String>,
473        signing_scheme: cow_types::EcdsaSigningScheme,
474    ) -> Self {
475        Self { chain_id, order_uids, signing_scheme }
476    }
477
478    /// Returns the number of orders to cancel.
479    ///
480    /// # Returns
481    ///
482    /// The length of the `order_uids` list.
483    #[must_use]
484    pub const fn count(&self) -> usize {
485        self.order_uids.len()
486    }
487}
488
489impl fmt::Display for SignOrderCancellationsParams {
490    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
491        write!(f, "cancel-signs(chain={}, count={})", self.chain_id, self.order_uids.len())
492    }
493}
494
495#[cfg(test)]
496mod tests {
497    use alloy_primitives::{B256, U256, address};
498
499    use super::*;
500    use cow_types::{EcdsaSigningScheme, OrderKind, TokenBalance};
501
502    fn default_order() -> UnsignedOrder {
503        UnsignedOrder::sell(Address::ZERO, Address::ZERO, U256::ZERO, U256::ZERO)
504    }
505
506    // ── UnsignedOrder constructors ──────────────────────────────────────
507
508    #[test]
509    fn sell_order_has_sell_kind() {
510        let o = UnsignedOrder::sell(
511            Address::ZERO,
512            Address::ZERO,
513            U256::from(100u64),
514            U256::from(50u64),
515        );
516        assert_eq!(o.kind, OrderKind::Sell);
517        assert!(o.is_sell());
518        assert!(!o.is_buy());
519        assert_eq!(o.sell_amount, U256::from(100u64));
520        assert_eq!(o.buy_amount, U256::from(50u64));
521    }
522
523    #[test]
524    fn buy_order_has_buy_kind() {
525        let o =
526            UnsignedOrder::buy(Address::ZERO, Address::ZERO, U256::from(100u64), U256::from(50u64));
527        assert_eq!(o.kind, OrderKind::Buy);
528        assert!(o.is_buy());
529        assert!(!o.is_sell());
530    }
531
532    #[test]
533    fn sell_order_defaults() {
534        let o = default_order();
535        assert_eq!(o.receiver, Address::ZERO);
536        assert_eq!(o.valid_to, 0);
537        assert_eq!(o.app_data, B256::ZERO);
538        assert_eq!(o.fee_amount, U256::ZERO);
539        assert!(!o.partially_fillable);
540        assert_eq!(o.sell_token_balance, TokenBalance::Erc20);
541        assert_eq!(o.buy_token_balance, TokenBalance::Erc20);
542    }
543
544    // ── Builder methods ─────────────────────────────────────────────────
545
546    #[test]
547    fn with_receiver() {
548        let addr = address!("0000000000000000000000000000000000000001");
549        let o = default_order().with_receiver(addr);
550        assert_eq!(o.receiver, addr);
551    }
552
553    #[test]
554    fn with_valid_to() {
555        let o = default_order().with_valid_to(1_700_000_000);
556        assert_eq!(o.valid_to, 1_700_000_000);
557    }
558
559    #[test]
560    fn with_app_data() {
561        let data = B256::from([0xab; 32]);
562        let o = default_order().with_app_data(data);
563        assert_eq!(o.app_data, data);
564    }
565
566    #[test]
567    fn with_fee_amount() {
568        let o = default_order().with_fee_amount(U256::from(42u64));
569        assert_eq!(o.fee_amount, U256::from(42u64));
570    }
571
572    #[test]
573    fn with_partially_fillable() {
574        let o = default_order().with_partially_fillable();
575        assert!(o.partially_fillable);
576        assert!(o.is_partially_fillable());
577    }
578
579    #[test]
580    fn with_sell_token_balance() {
581        let o = default_order().with_sell_token_balance(TokenBalance::External);
582        assert_eq!(o.sell_token_balance, TokenBalance::External);
583    }
584
585    #[test]
586    fn with_buy_token_balance() {
587        let o = default_order().with_buy_token_balance(TokenBalance::Internal);
588        assert_eq!(o.buy_token_balance, TokenBalance::Internal);
589    }
590
591    // ── Query methods ───────────────────────────────────────────────────
592
593    #[test]
594    fn is_expired_boundary() {
595        let o = default_order().with_valid_to(1_000_000);
596        assert!(!o.is_expired(999_999));
597        assert!(!o.is_expired(1_000_000));
598        assert!(o.is_expired(1_000_001));
599    }
600
601    #[test]
602    fn has_custom_receiver_false_for_zero() {
603        assert!(!default_order().has_custom_receiver());
604    }
605
606    #[test]
607    fn has_custom_receiver_true_for_nonzero() {
608        let o = default_order().with_receiver(address!("0000000000000000000000000000000000000001"));
609        assert!(o.has_custom_receiver());
610    }
611
612    #[test]
613    fn has_app_data_false_for_zero() {
614        assert!(!default_order().has_app_data());
615    }
616
617    #[test]
618    fn has_app_data_true_for_nonzero() {
619        let o = default_order().with_app_data(B256::from([1u8; 32]));
620        assert!(o.has_app_data());
621    }
622
623    #[test]
624    fn has_fee_false_for_zero() {
625        assert!(!default_order().has_fee());
626    }
627
628    #[test]
629    fn has_fee_true_for_nonzero() {
630        let o = default_order().with_fee_amount(U256::from(1u64));
631        assert!(o.has_fee());
632    }
633
634    #[test]
635    fn is_partially_fillable_default_false() {
636        assert!(!default_order().is_partially_fillable());
637    }
638
639    #[test]
640    fn total_amount_sums_sell_and_buy() {
641        let o = UnsignedOrder::sell(
642            Address::ZERO,
643            Address::ZERO,
644            U256::from(100u64),
645            U256::from(50u64),
646        );
647        assert_eq!(o.total_amount(), U256::from(150u64));
648    }
649
650    #[test]
651    fn total_amount_saturates_on_overflow() {
652        let o = UnsignedOrder::sell(Address::ZERO, Address::ZERO, U256::MAX, U256::from(1u64));
653        assert_eq!(o.total_amount(), U256::MAX);
654    }
655
656    #[test]
657    fn order_hash_is_deterministic() {
658        let o = default_order();
659        let h = crate::order_hash(&o);
660        assert_eq!(h, crate::order_hash(&o));
661        assert_ne!(h, B256::ZERO);
662    }
663
664    // ── OrderDomain ─────────────────────────────────────────────────────
665
666    #[test]
667    fn order_domain_for_chain() {
668        let d = OrderDomain::for_chain(1);
669        assert_eq!(d.name, "Gnosis Protocol");
670        assert_eq!(d.version, "v2");
671        assert_eq!(d.chain_id, 1);
672    }
673
674    #[test]
675    fn domain_separator_is_deterministic() {
676        let d = OrderDomain::for_chain(1);
677        let sep1 = d.domain_separator();
678        let sep2 = d.domain_separator();
679        assert_eq!(sep1, sep2);
680        assert_ne!(sep1, B256::ZERO);
681    }
682
683    #[test]
684    fn different_chains_different_separators() {
685        let s1 = OrderDomain::for_chain(1).domain_separator();
686        let s2 = OrderDomain::for_chain(100).domain_separator();
687        assert_ne!(s1, s2);
688    }
689
690    // ── OrderTypedData ──────────────────────────────────────────────────
691
692    #[test]
693    fn order_typed_data_primary_type() {
694        let td = OrderTypedData::new(OrderDomain::for_chain(1), default_order());
695        assert_eq!(td.primary_type, "GPv2Order.Data");
696    }
697
698    #[test]
699    fn order_typed_data_refs() {
700        let order = default_order();
701        let td = OrderTypedData::new(OrderDomain::for_chain(1), order.clone());
702        assert_eq!(td.order_ref().kind, order.kind);
703        assert_eq!(td.domain_ref().chain_id, 1);
704    }
705
706    #[test]
707    fn signing_digest_is_deterministic_and_nonzero() {
708        let td = OrderTypedData::new(OrderDomain::for_chain(1), default_order());
709        let d1 = td.signing_digest();
710        let d2 = td.signing_digest();
711        assert_eq!(d1, d2);
712        assert_ne!(d1, B256::ZERO);
713    }
714
715    // ── SigningResult ───────────────────────────────────────────────────
716
717    #[test]
718    fn signing_result_new() {
719        let r = SigningResult::new("0xdeadbeef", SigningScheme::Eip712);
720        assert_eq!(r.signature, "0xdeadbeef");
721        assert_eq!(r.signing_scheme, SigningScheme::Eip712);
722    }
723
724    #[test]
725    fn signing_result_scheme_checks() {
726        assert!(SigningResult::new("0x", SigningScheme::Eip712).is_eip712());
727        assert!(SigningResult::new("0x", SigningScheme::EthSign).is_eth_sign());
728        assert!(SigningResult::new("0x", SigningScheme::Eip1271).is_eip1271());
729        assert!(SigningResult::new("0x", SigningScheme::PreSign).is_presign());
730    }
731
732    #[test]
733    fn signing_result_scheme_exclusivity() {
734        let r = SigningResult::new("0x", SigningScheme::Eip712);
735        assert!(r.is_eip712());
736        assert!(!r.is_eth_sign());
737        assert!(!r.is_eip1271());
738        assert!(!r.is_presign());
739    }
740
741    #[test]
742    fn signing_result_len_and_ref() {
743        let r = SigningResult::new("0xdeadbeef", SigningScheme::Eip712);
744        assert_eq!(r.signature_len(), 10);
745        assert_eq!(r.signature_ref(), "0xdeadbeef");
746    }
747
748    // ── SignOrderParams ─────────────────────────────────────────────────
749
750    #[test]
751    fn sign_order_params_new() {
752        let p = SignOrderParams::new(1, default_order(), EcdsaSigningScheme::Eip712);
753        assert_eq!(p.chain_id, 1);
754        assert_eq!(p.signing_scheme, EcdsaSigningScheme::Eip712);
755    }
756
757    // ── SignOrderCancellationParams ─────────────────────────────────────
758
759    #[test]
760    fn sign_order_cancellation_params_new() {
761        let p = SignOrderCancellationParams::new(1, "0xabc123", EcdsaSigningScheme::EthSign);
762        assert_eq!(p.chain_id, 1);
763        assert_eq!(p.order_uid, "0xabc123");
764        assert_eq!(p.signing_scheme, EcdsaSigningScheme::EthSign);
765    }
766
767    // ── SignOrderCancellationsParams ────────────────────────────────────
768
769    #[test]
770    fn sign_order_cancellations_params_count() {
771        let p = SignOrderCancellationsParams::new(
772            1,
773            vec!["a".into(), "b".into(), "c".into()],
774            EcdsaSigningScheme::Eip712,
775        );
776        assert_eq!(p.count(), 3);
777    }
778
779    #[test]
780    fn sign_order_cancellations_params_empty() {
781        let p = SignOrderCancellationsParams::new(1, vec![], EcdsaSigningScheme::Eip712);
782        assert_eq!(p.count(), 0);
783    }
784
785    // ── Display impls ───────────────────────────────────────────────────
786
787    #[test]
788    fn unsigned_order_display() {
789        let o = default_order();
790        let s = o.to_string();
791        assert!(s.contains("sell"), "expected 'sell' in: {s}");
792    }
793
794    #[test]
795    fn order_domain_display() {
796        let d = OrderDomain::for_chain(42);
797        let s = d.to_string();
798        assert!(s.contains("chain=42"), "expected chain=42 in: {s}");
799    }
800
801    #[test]
802    fn order_typed_data_display() {
803        let td = OrderTypedData::new(OrderDomain::for_chain(1), default_order());
804        let s = td.to_string();
805        assert!(s.contains("typed-data"), "expected typed-data in: {s}");
806        assert!(s.contains("chain=1"), "expected chain=1 in: {s}");
807    }
808
809    #[test]
810    fn signing_result_display_truncates() {
811        let r = SigningResult::new("0xdeadbeefcafe1234567890", SigningScheme::Eip712);
812        let s = r.to_string();
813        assert!(s.contains("sig("), "expected sig( in: {s}");
814        assert!(s.contains("0xdeadbee"), "expected truncated sig in: {s}");
815    }
816
817    #[test]
818    fn signing_result_display_short_sig() {
819        let r = SigningResult::new("0xab", SigningScheme::PreSign);
820        let s = r.to_string();
821        assert!(s.contains("0xab"), "expected full short sig in: {s}");
822    }
823
824    #[test]
825    fn sign_order_params_display() {
826        let p = SignOrderParams::new(100, default_order(), EcdsaSigningScheme::Eip712);
827        let s = p.to_string();
828        assert!(s.contains("sign-order"), "expected sign-order in: {s}");
829        assert!(s.contains("chain=100"), "expected chain=100 in: {s}");
830    }
831
832    #[test]
833    fn sign_order_cancellation_params_display() {
834        let p = SignOrderCancellationParams::new(1, "0x1234567890ab", EcdsaSigningScheme::Eip712);
835        let s = p.to_string();
836        assert!(s.contains("cancel-sign"), "expected cancel-sign in: {s}");
837        assert!(s.contains("chain=1"), "expected chain=1 in: {s}");
838    }
839
840    #[test]
841    fn sign_order_cancellations_params_display() {
842        let p = SignOrderCancellationsParams::new(
843            5,
844            vec!["a".into(), "b".into()],
845            EcdsaSigningScheme::Eip712,
846        );
847        let s = p.to_string();
848        assert!(s.contains("cancel-signs"), "expected cancel-signs in: {s}");
849        assert!(s.contains("count=2"), "expected count=2 in: {s}");
850    }
851}