Skip to main content

cow_rs/composable/
factory.rs

1//! [`ConditionalOrderFactory`] — instantiate conditional orders from on-chain params.
2
3use std::fmt;
4
5use crate::error::CowError;
6
7use super::{
8    gat::{GatOrder, decode_gat_static_input},
9    stop_loss::{STOP_LOSS_HANDLER_ADDRESS, StopLossOrder, decode_stop_loss_static_input},
10    twap::{TwapOrder, decode_twap_static_input},
11    types::{ConditionalOrderParams, TWAP_HANDLER_ADDRESS},
12};
13
14/// A conditional order decoded from on-chain [`ConditionalOrderParams`].
15#[derive(Debug, Clone)]
16pub enum ConditionalOrderKind {
17    /// A `TWAP` (Time-Weighted Average Price) order.
18    Twap(TwapOrder),
19    /// A stop-loss order that triggers when the price falls below a strike price.
20    StopLoss(StopLossOrder),
21    /// A `GoodAfterTime` order that becomes valid only after a given timestamp.
22    GoodAfterTime(GatOrder),
23    /// An order whose handler is not recognised by this factory.
24    Unknown(ConditionalOrderParams),
25}
26
27impl ConditionalOrderKind {
28    /// Returns a short string label for the order kind.
29    ///
30    /// # Returns
31    ///
32    /// A `&'static str` identifying the variant: `"twap"`, `"stop-loss"`,
33    /// `"good-after-time"`, or `"unknown"`.
34    #[must_use]
35    pub const fn as_str(&self) -> &'static str {
36        match self {
37            Self::Twap(_) => "twap",
38            Self::StopLoss(_) => "stop-loss",
39            Self::GoodAfterTime(_) => "good-after-time",
40            Self::Unknown(_) => "unknown",
41        }
42    }
43
44    /// Returns `true` if this is a `TWAP` conditional order.
45    ///
46    /// ```
47    /// use alloy_primitives::B256;
48    /// use cow_rs::composable::{
49    ///     ConditionalOrderFactory, ConditionalOrderParams, TWAP_HANDLER_ADDRESS,
50    /// };
51    ///
52    /// let params = ConditionalOrderParams {
53    ///     handler: TWAP_HANDLER_ADDRESS,
54    ///     salt: B256::ZERO,
55    ///     static_input: vec![],
56    /// };
57    /// // An unknown handler resolves to Unknown, not Twap.
58    /// use alloy_primitives::Address;
59    /// let unknown = cow_rs::composable::ConditionalOrderKind::Unknown(ConditionalOrderParams {
60    ///     handler: Address::ZERO,
61    ///     salt: B256::ZERO,
62    ///     static_input: vec![],
63    /// });
64    /// assert!(!unknown.is_twap());
65    /// assert!(unknown.is_unknown());
66    /// ```
67    #[must_use]
68    pub const fn is_twap(&self) -> bool {
69        matches!(self, Self::Twap(_))
70    }
71
72    /// Returns `true` if this is a stop-loss conditional order.
73    ///
74    /// ```
75    /// use alloy_primitives::{Address, B256};
76    /// use cow_rs::composable::{ConditionalOrderKind, ConditionalOrderParams};
77    ///
78    /// let unknown = ConditionalOrderKind::Unknown(ConditionalOrderParams {
79    ///     handler: Address::ZERO,
80    ///     salt: B256::ZERO,
81    ///     static_input: vec![],
82    /// });
83    /// assert!(!unknown.is_stop_loss());
84    /// ```
85    #[must_use]
86    pub const fn is_stop_loss(&self) -> bool {
87        matches!(self, Self::StopLoss(_))
88    }
89
90    /// Returns `true` if this is a `GoodAfterTime` conditional order.
91    ///
92    /// ```
93    /// use alloy_primitives::{Address, B256};
94    /// use cow_rs::composable::{ConditionalOrderKind, ConditionalOrderParams};
95    ///
96    /// let unknown = ConditionalOrderKind::Unknown(ConditionalOrderParams {
97    ///     handler: Address::ZERO,
98    ///     salt: B256::ZERO,
99    ///     static_input: vec![],
100    /// });
101    /// assert!(!unknown.is_good_after_time());
102    /// ```
103    #[must_use]
104    pub const fn is_good_after_time(&self) -> bool {
105        matches!(self, Self::GoodAfterTime(_))
106    }
107
108    /// Returns `true` if this order's handler is not recognised by the factory.
109    ///
110    /// # Returns
111    ///
112    /// `true` when the variant is [`ConditionalOrderKind::Unknown`], meaning the
113    /// handler address did not match any known conditional order type.
114    #[must_use]
115    pub const fn is_unknown(&self) -> bool {
116        matches!(self, Self::Unknown(_))
117    }
118}
119
120impl fmt::Display for ConditionalOrderKind {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        match self {
123            Self::Twap(order) => write!(f, "twap({order})"),
124            Self::StopLoss(_) => f.write_str("stop-loss"),
125            Self::GoodAfterTime(_) => f.write_str("good-after-time"),
126            Self::Unknown(params) => write!(f, "unknown({:#x})", params.handler),
127        }
128    }
129}
130
131/// Instantiates conditional orders from [`ConditionalOrderParams`].
132///
133/// Mirrors `ConditionalOrderFactory` from the `TypeScript` SDK.
134/// Extend by matching additional handler addresses in [`from_params`].
135///
136/// [`from_params`]: ConditionalOrderFactory::from_params
137#[derive(Debug, Clone, Default)]
138pub struct ConditionalOrderFactory;
139
140impl ConditionalOrderFactory {
141    /// Create a new factory.
142    ///
143    /// # Returns
144    ///
145    /// A zero-sized [`ConditionalOrderFactory`] instance that can decode
146    /// [`ConditionalOrderParams`] via [`from_params`](Self::from_params).
147    #[must_use]
148    pub const fn new() -> Self {
149        Self
150    }
151
152    /// Decode [`ConditionalOrderParams`] into a typed [`ConditionalOrderKind`].
153    ///
154    /// Handler addresses are matched exactly.  Unknown handlers return
155    /// [`ConditionalOrderKind::Unknown`] rather than an error.
156    ///
157    /// Recognised handlers:
158    /// - [`TWAP_HANDLER_ADDRESS`] → [`ConditionalOrderKind::Twap`]
159    /// - [`STOP_LOSS_HANDLER_ADDRESS`] → [`ConditionalOrderKind::StopLoss`]
160    ///
161    /// Note: the `GoodAfterTime` handler (`GAT_HANDLER_ADDRESS`) shares the
162    /// same on-chain address as the `TWAP` handler, so `TWAP` decoding takes
163    /// priority for that address.
164    ///
165    /// # Errors
166    ///
167    /// Returns [`CowError::AppData`] only if a known handler's static input
168    /// fails ABI decoding.
169    pub fn from_params(
170        &self,
171        params: ConditionalOrderParams,
172    ) -> Result<ConditionalOrderKind, CowError> {
173        if params.handler == TWAP_HANDLER_ADDRESS {
174            // Try TWAP decoding first (TWAP and GAT share the same address).
175            // If the input is TWAP-sized (320 bytes), decode as TWAP.
176            // If it is GAT-sized (448 bytes), decode as GAT.
177            if params.static_input.len() == 14 * 32 {
178                let data = decode_gat_static_input(&params.static_input)?;
179                return Ok(ConditionalOrderKind::GoodAfterTime(GatOrder::with_salt(
180                    data,
181                    params.salt,
182                )));
183            }
184            let data = decode_twap_static_input(&params.static_input)?;
185            return Ok(ConditionalOrderKind::Twap(TwapOrder::with_salt(data, params.salt)));
186        }
187        if params.handler == STOP_LOSS_HANDLER_ADDRESS {
188            let data = decode_stop_loss_static_input(&params.static_input)?;
189            return Ok(ConditionalOrderKind::StopLoss(StopLossOrder::with_salt(data, params.salt)));
190        }
191        Ok(ConditionalOrderKind::Unknown(params))
192    }
193}
194impl fmt::Display for ConditionalOrderFactory {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        f.write_str("conditional-order-factory")
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use alloy_primitives::{Address, B256};
203
204    use super::*;
205
206    #[test]
207    fn factory_new() {
208        let factory = ConditionalOrderFactory::new();
209        assert_eq!(factory.to_string(), "conditional-order-factory");
210    }
211
212    #[test]
213    fn factory_unknown_handler() {
214        let factory = ConditionalOrderFactory::new();
215        let params = ConditionalOrderParams {
216            handler: Address::ZERO,
217            salt: B256::ZERO,
218            static_input: vec![],
219        };
220        let result = factory.from_params(params).unwrap();
221        assert!(result.is_unknown());
222        assert!(!result.is_twap());
223        assert!(!result.is_stop_loss());
224        assert!(!result.is_good_after_time());
225        assert_eq!(result.as_str(), "unknown");
226    }
227
228    #[test]
229    fn factory_twap_handler_empty_static_input_errors() {
230        let factory = ConditionalOrderFactory::new();
231        let params = ConditionalOrderParams {
232            handler: TWAP_HANDLER_ADDRESS,
233            salt: B256::ZERO,
234            static_input: vec![],
235        };
236        // Empty static input is not valid for TWAP
237        assert!(factory.from_params(params).is_err());
238    }
239
240    #[test]
241    fn factory_stop_loss_handler_empty_static_input_errors() {
242        let factory = ConditionalOrderFactory::new();
243        let params = ConditionalOrderParams {
244            handler: STOP_LOSS_HANDLER_ADDRESS,
245            salt: B256::ZERO,
246            static_input: vec![],
247        };
248        assert!(factory.from_params(params).is_err());
249    }
250
251    #[test]
252    fn conditional_order_kind_display_unknown() {
253        let kind = ConditionalOrderKind::Unknown(ConditionalOrderParams {
254            handler: Address::ZERO,
255            salt: B256::ZERO,
256            static_input: vec![],
257        });
258        let s = kind.to_string();
259        assert!(s.contains("unknown"));
260    }
261
262    #[test]
263    fn conditional_order_kind_display_stop_loss() {
264        let kind = ConditionalOrderKind::Unknown(ConditionalOrderParams {
265            handler: Address::ZERO,
266            salt: B256::ZERO,
267            static_input: vec![],
268        });
269        // We can't easily construct a StopLoss without valid data,
270        // so we test the other Display variants through the Unknown variant
271        assert_eq!(kind.as_str(), "unknown");
272    }
273}