Skip to main content

cow_ethflow/
lib.rs

1//! `cow-ethflow` — Layer 2 `EthFlow` contract helpers for the `CoW` Protocol SDK.
2//!
3//! Encode `createOrder` calldata for native-currency orders.
4//!
5//! When a user wants to sell ETH (or another chain's native currency)
6//! instead of an ERC-20, the order is submitted through the `EthFlow`
7//! contract rather than the standard `GPv2Settlement` flow. This module
8//! provides the types and encoding functions for that path.
9//!
10//! # Key items
11//!
12//! | Item | Purpose |
13//! |---|---|
14//! | [`EthFlowOrderData`] | Parameters for a native-currency sell order |
15//! | [`EthFlowTransaction`] | Ready-to-send transaction (to, data, value) |
16//! | [`encode_eth_flow_create_order`] | ABI-encode `createOrder(...)` calldata |
17//! | [`build_eth_flow_transaction`] | Build the complete transaction |
18//! | [`is_eth_flow_order_data`] | Check if on-chain data indicates an EthFlow order |
19
20#![deny(unsafe_code)]
21#![warn(missing_docs)]
22
23use alloy_primitives::{Address, B256, U256, keccak256};
24use cow_types::OnchainOrderData;
25
26/// Parameters for a native-currency sell order submitted through the
27/// `EthFlow` contract.
28///
29/// Maps to the `EthFlowOrder` struct in the Solidity contract. The
30/// sell token is implicitly the chain's native currency (ETH, xDAI, …).
31///
32/// # Example
33///
34/// ```
35/// use alloy_primitives::{Address, B256, U256};
36/// use cow_ethflow::{EthFlowOrderData, encode_eth_flow_create_order};
37///
38/// let order = EthFlowOrderData {
39///     buy_token: Address::ZERO,
40///     receiver: Address::ZERO,
41///     sell_amount: U256::from(1_000_000u64),
42///     buy_amount: U256::from(500_000u64),
43///     app_data: B256::ZERO,
44///     fee_amount: U256::ZERO,
45///     valid_to: 9_999_999,
46///     partially_fillable: false,
47///     quote_id: 42,
48/// };
49/// let cd = encode_eth_flow_create_order(&order);
50/// assert_eq!(cd.len(), 292);
51/// ```
52#[derive(Debug, Clone)]
53pub struct EthFlowOrderData {
54    /// Token to buy (native currency is the sell token).
55    pub buy_token: Address,
56    /// Address that receives the bought tokens.
57    pub receiver: Address,
58    /// Amount of native currency to sell (in wei).
59    pub sell_amount: U256,
60    /// Minimum amount of `buy_token` to receive.
61    pub buy_amount: U256,
62    /// `bytes32` app-data hash.
63    pub app_data: B256,
64    /// Protocol fee (in wei), typically zero for `EthFlow`.
65    pub fee_amount: U256,
66    /// Order expiry as Unix timestamp.
67    pub valid_to: u32,
68    /// Whether the order may be partially filled.
69    pub partially_fillable: bool,
70    /// Quote identifier from the orderbook.
71    pub quote_id: i64,
72}
73
74/// Ready-to-send transaction for submitting a native-currency order.
75///
76/// Produced by [`build_eth_flow_transaction`]. Send this transaction with
77/// the indicated [`value`](Self::value) of native currency attached.
78#[derive(Debug, Clone)]
79pub struct EthFlowTransaction {
80    /// `EthFlow` contract address to call.
81    pub to: Address,
82    /// ABI-encoded `createOrder(EthFlowOrder)` calldata.
83    pub data: Vec<u8>,
84    /// ETH value to attach (= `sell_amount`).
85    pub value: U256,
86}
87
88/// Compute the 4-byte selector from a Solidity function signature.
89fn selector(sig: &[u8]) -> [u8; 4] {
90    let h = keccak256(sig);
91    [h[0], h[1], h[2], h[3]]
92}
93
94/// Left-pad an [`Address`] to a 32-byte ABI word.
95fn abi_address(a: Address) -> [u8; 32] {
96    let mut buf = [0u8; 32];
97    buf[12..].copy_from_slice(a.as_slice());
98    buf
99}
100
101/// Encode a `u32` as a 32-byte big-endian ABI word.
102fn abi_u32(v: u32) -> [u8; 32] {
103    let mut buf = [0u8; 32];
104    buf[28..].copy_from_slice(&v.to_be_bytes());
105    buf
106}
107
108/// Encode a `bool` as a 32-byte ABI word.
109fn abi_bool(v: bool) -> [u8; 32] {
110    let mut buf = [0u8; 32];
111    buf[31] = u8::from(v);
112    buf
113}
114
115/// Encode an `i64` as a 32-byte two's-complement big-endian ABI word.
116fn abi_i64(v: i64) -> [u8; 32] {
117    // Sign-extend i64 to 32 bytes (two's complement big-endian).
118    let fill: u8 = if v < 0 { 0xff } else { 0x00 };
119    let mut buf = [fill; 32];
120    buf[24..].copy_from_slice(&v.to_be_bytes());
121    buf
122}
123
124/// Encode the `EthFlow.createOrder(order, quoteId)` calldata.
125///
126/// Function signature:
127/// `createOrder((address,address,uint256,uint256,bytes32,uint256,uint32,bool,int64))`
128///
129/// Total payload: selector (4) + 9 × 32-byte words = 292 bytes.
130///
131/// # Parameters
132///
133/// * `order` — the [`EthFlowOrderData`] to encode.
134///
135/// # Returns
136///
137/// A 292-byte `Vec<u8>` containing the ABI-encoded calldata.
138///
139/// # Example
140///
141/// ```
142/// use alloy_primitives::{Address, B256, U256};
143/// use cow_ethflow::{EthFlowOrderData, encode_eth_flow_create_order};
144///
145/// let order = EthFlowOrderData {
146///     buy_token: Address::ZERO,
147///     receiver: Address::ZERO,
148///     sell_amount: U256::from(1_000_000_u64),
149///     buy_amount: U256::from(500_000_u64),
150///     app_data: B256::ZERO,
151///     fee_amount: U256::ZERO,
152///     valid_to: 9_999_999_u32,
153///     partially_fillable: false,
154///     quote_id: 42,
155/// };
156/// let cd = encode_eth_flow_create_order(&order);
157/// assert_eq!(cd.len(), 292);
158/// ```
159#[must_use]
160pub fn encode_eth_flow_create_order(order: &EthFlowOrderData) -> Vec<u8> {
161    // The EthFlow contract declares `createOrder` with a single `EthFlowOrder`
162    // struct argument; `quoteId` is the last field of the struct, not a
163    // separate function parameter. The byte layout of the trailing 9 × 32
164    // static words is identical either way — only the selector differs.
165    let sig = b"createOrder((address,address,uint256,uint256,bytes32,uint256,uint32,bool,int64))";
166    let sel = selector(sig);
167
168    let mut buf = Vec::with_capacity(292);
169    buf.extend_from_slice(&sel);
170    buf.extend_from_slice(&abi_address(order.buy_token));
171    buf.extend_from_slice(&abi_address(order.receiver));
172    buf.extend_from_slice(&order.sell_amount.to_be_bytes::<32>());
173    buf.extend_from_slice(&order.buy_amount.to_be_bytes::<32>());
174    buf.extend_from_slice(order.app_data.as_slice());
175    buf.extend_from_slice(&order.fee_amount.to_be_bytes::<32>());
176    buf.extend_from_slice(&abi_u32(order.valid_to));
177    buf.extend_from_slice(&abi_bool(order.partially_fillable));
178    buf.extend_from_slice(&abi_i64(order.quote_id));
179    buf
180}
181
182/// Build a complete [`EthFlowTransaction`] for `contract_address`.
183///
184/// The caller is responsible for sending the returned transaction with the
185/// ETH value indicated by [`EthFlowTransaction::value`].
186///
187/// # Parameters
188///
189/// * `contract` — the [`Address`] of the `EthFlow` contract on the target chain (use
190///   [`eth_flow_for_env`](cow_chains::eth_flow_for_env) to look it up).
191/// * `order` — the [`EthFlowOrderData`] to encode.
192///
193/// # Returns
194///
195/// An [`EthFlowTransaction`] with `to`, `data`, and `value` fields set.
196///
197/// # Example
198///
199/// ```
200/// use alloy_primitives::{Address, B256, U256};
201/// use cow_ethflow::{EthFlowOrderData, build_eth_flow_transaction};
202///
203/// let order = EthFlowOrderData {
204///     buy_token: Address::ZERO,
205///     receiver: Address::ZERO,
206///     sell_amount: U256::from(1_000_u64),
207///     buy_amount: U256::from(500_u64),
208///     app_data: B256::ZERO,
209///     fee_amount: U256::ZERO,
210///     valid_to: 0,
211///     partially_fillable: false,
212///     quote_id: 1,
213/// };
214/// let tx = build_eth_flow_transaction(Address::ZERO, &order);
215/// assert_eq!(tx.value, order.sell_amount);
216/// ```
217#[must_use]
218pub fn build_eth_flow_transaction(
219    contract: Address,
220    order: &EthFlowOrderData,
221) -> EthFlowTransaction {
222    EthFlowTransaction {
223        to: contract,
224        data: encode_eth_flow_create_order(order),
225        value: order.sell_amount,
226    }
227}
228
229/// Check whether on-chain data is present, indicating an `EthFlow` order.
230///
231/// Returns `true` if `onchain_data` is `Some`, which signals that the order
232/// was submitted via the `EthFlow` contract rather than the standard
233/// `GPv2Settlement` flow.
234///
235/// # Parameters
236///
237/// * `onchain_data` — optional reference to [`OnchainOrderData`].
238///
239/// # Returns
240///
241/// `true` if `onchain_data` is `Some`.
242///
243/// # Example
244///
245/// ```
246/// use cow_ethflow::is_eth_flow_order_data;
247///
248/// assert!(!is_eth_flow_order_data(None));
249/// ```
250#[must_use]
251pub const fn is_eth_flow_order_data(onchain_data: Option<&OnchainOrderData>) -> bool {
252    onchain_data.is_some()
253}