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}