signet_types/signing/
order.rs

1use crate::signing::{permit_signing_info, SignedPermitError, SigningError};
2use alloy::{
3    network::TransactionBuilder,
4    primitives::{keccak256, Address, Bytes, B256, U256},
5    rpc::types::TransactionRequest,
6    signers::Signer,
7    sol_types::{SolCall, SolValue},
8};
9use chrono::Utc;
10use serde::{Deserialize, Serialize};
11use signet_constants::SignetSystemConstants;
12use signet_zenith::RollupOrders::{
13    initiatePermit2Call, Input, Order, Output, Permit2Batch, TokenPermissions,
14};
15use std::{borrow::Cow, sync::OnceLock};
16
17/// A SignedOrder represents a single Order after it has been permit2-encoded and signed.
18/// It is the final format signed by Users and shared with Fillers to request that an Order be filled.
19///
20/// It corresponds to the parameters for `initiatePermit2` on the OrderOrigin contract,
21/// and thus contains all necessary information to initiate the Order.
22///
23/// It can be shared with all Fillers via the Signet Node `signet_sendOrder` RPC call,
24/// or shared directly with specific Filler(s) via private channels.
25/// The type can be signed and published safely, because although the Permit2Batch allows
26/// the Order Inputs to be transferred from the user, the Signet Node ensures that
27/// Inputs cannot be transferred until the Order Outputs have already been filled.
28///
29/// TODO: Link to docs.
30#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
31pub struct SignedOrder {
32    /// The permit batch.
33    #[serde(flatten)]
34    permit: Permit2Batch,
35    /// The desired outputs.
36    outputs: Vec<Output>,
37
38    #[serde(skip)]
39    order_hash: OnceLock<B256>,
40    #[serde(skip)]
41    order_hash_pre_image: OnceLock<Bytes>,
42}
43
44impl SignedOrder {
45    /// Creates a new signed order.
46    pub const fn new(permit: Permit2Batch, outputs: Vec<Output>) -> Self {
47        Self { permit, outputs, order_hash: OnceLock::new(), order_hash_pre_image: OnceLock::new() }
48    }
49
50    /// Get the permit batch.
51    pub const fn permit(&self) -> &Permit2Batch {
52        &self.permit
53    }
54
55    /// Get the outputs.
56    pub fn outputs(&self) -> &[Output] {
57        &self.outputs
58    }
59
60    /// Decompose the SignedOrder into its parts.
61    pub fn into_parts(self) -> (Permit2Batch, Vec<Output>) {
62        (self.permit, self.outputs)
63    }
64
65    /// Check that this can be syntactically used to initiate an order.
66    ///
67    /// For it to be valid:
68    /// - Deadline must be in the future.
69    pub fn validate(&self, timestamp: u64) -> Result<(), SignedPermitError> {
70        let deadline = self.permit.permit.deadline.saturating_to::<u64>();
71        if timestamp > deadline {
72            return Err(SignedPermitError::DeadlinePassed { current: timestamp, deadline });
73        }
74
75        Ok(())
76    }
77
78    /// Generate a TransactionRequest to `initiate` the SignedOrder.
79    pub fn to_initiate_tx(
80        &self,
81        filler_token_recipient: Address,
82        order_contract: Address,
83    ) -> TransactionRequest {
84        // encode initiate data
85        let initiate_data = initiatePermit2Call {
86            tokenRecipient: filler_token_recipient,
87            outputs: self.outputs.clone(),
88            permit2: self.permit.clone(),
89        }
90        .abi_encode();
91
92        // construct an initiate tx request
93        TransactionRequest::default().with_input(initiate_data).with_to(order_contract)
94    }
95
96    /// Get the hash of the order.
97    ///
98    /// # Composition
99    ///
100    /// The order hash is composed of the following:
101    /// - The permit2 batch permit inputs, ABI encoded and hashed.
102    /// - The permit2 batch owner, ABI encoded and hashed.
103    /// - The order outputs, ABI encoded and hashed.
104    /// - The permit2 batch signature, normalized and hashed.
105    ///
106    /// The components are then hashed together.
107    pub fn order_hash(&self) -> &B256 {
108        self.order_hash.get_or_init(|| keccak256(self.order_hash_pre_image()))
109    }
110
111    /// Get the pre-image for the order hash.
112    ///
113    /// This is the raw bytes that are hashed to produce the order hash.
114    #[doc(hidden)]
115    pub fn order_hash_pre_image(&self) -> &Bytes {
116        self.order_hash_pre_image.get_or_init(|| self.compute_order_hash_pre_image())
117    }
118
119    /// Compute the pre-image for the order hash.
120    #[doc(hidden)]
121    fn compute_order_hash_pre_image(&self) -> Bytes {
122        // 4 * 32 bytes = 128 bytes
123        let mut buf = Vec::with_capacity(128);
124
125        buf.extend_from_slice(keccak256(self.permit.permit.abi_encode()).as_slice());
126        buf.extend_from_slice(keccak256(self.permit.owner.abi_encode()).as_slice());
127        buf.extend_from_slice(keccak256(self.outputs.abi_encode()).as_slice());
128
129        // Normalize the signature.
130        let signature =
131            alloy::primitives::Signature::from_raw(&self.permit.signature).unwrap().normalized_s();
132        buf.extend_from_slice(keccak256(signature.as_bytes()).as_slice());
133
134        buf.into()
135    }
136}
137
138/// An UnsignedOrder is a helper type used to easily transform an Order into a
139/// SignedOrder with correct permit2 semantics.
140/// Users can do:
141/// let signed_order = UnsignedOrder::from(order).with_chain(rollup_chain_id, rollup_order_address).sign(signer)?;
142/// TxCache::new(tx_cache_endpoint).forward_order(signed_order);
143#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
144pub struct UnsignedOrder<'a> {
145    order: Cow<'a, Order>,
146    nonce: Option<u64>,
147    rollup_chain_id: Option<u64>,
148    rollup_order_address: Option<Address>,
149}
150
151impl<'a> From<&'a Order> for UnsignedOrder<'a> {
152    fn from(order: &'a Order) -> Self {
153        Self { order: Cow::Borrowed(order), ..Default::default() }
154    }
155}
156
157impl<'a> UnsignedOrder<'a> {
158    /// Get a new UnsignedOrder from an Order.
159    pub fn new() -> Self {
160        Self {
161            order: Cow::Owned(Order::default()),
162            nonce: None,
163            rollup_chain_id: None,
164            rollup_order_address: None,
165        }
166    }
167
168    /// Get the inputs of the UnsignedOrder.
169    pub fn inputs(&self) -> &[Input] {
170        self.order.inputs()
171    }
172
173    /// Add an input to the UnsignedOrder.
174    pub fn with_raw_input(self, input: Input) -> UnsignedOrder<'static> {
175        let order = self.order.into_owned().with_input(input);
176
177        UnsignedOrder { order: Cow::Owned(order), ..self }
178    }
179
180    /// Add an input to the UnsignedOrder.
181    pub fn with_input(self, token: Address, amount: U256) -> UnsignedOrder<'static> {
182        self.with_raw_input(Input { token, amount })
183    }
184
185    /// Get the outputs of the UnsignedOrder.
186    pub fn outputs(&self) -> &[Output] {
187        self.order.outputs()
188    }
189
190    /// Add an output to the UnsignedOrder.
191    pub fn with_raw_output(self, output: Output) -> UnsignedOrder<'static> {
192        let order = self.order.into_owned().with_output(output);
193
194        UnsignedOrder { order: Cow::Owned(order), ..self }
195    }
196
197    /// Add an output to the UnsignedOrder.
198    pub fn with_output(
199        self,
200        token: Address,
201        amount: U256,
202        recipient: Address,
203        chain_id: u32,
204    ) -> UnsignedOrder<'static> {
205        self.with_raw_output(Output { token, amount, recipient, chainId: chain_id })
206    }
207
208    /// Set the deadline on the UnsignedOrder.
209    pub fn with_deadline(self, deadline: u64) -> UnsignedOrder<'static> {
210        let order = self.order.into_owned().with_deadline(deadline);
211
212        UnsignedOrder { order: Cow::Owned(order), ..self }
213    }
214
215    /// Add a Permit2 nonce to the UnsignedOrder.
216    pub fn with_nonce(self, nonce: u64) -> Self {
217        Self { nonce: Some(nonce), ..self }
218    }
219
220    /// Add the chain id  and Order contract address to the UnsignedOrder.
221    /// MUST call before `sign`.
222    pub fn with_chain(self, constants: &SignetSystemConstants) -> Self {
223        Self {
224            rollup_chain_id: Some(constants.ru_chain_id()),
225            rollup_order_address: Some(constants.ru_orders()),
226            ..self
227        }
228    }
229
230    /// Convert the UnsignedOrder into an Order, cloning the inner data if
231    /// necessary.
232    pub fn to_order(&self) -> Order {
233        self.order.clone().into_owned()
234    }
235
236    /// Convert the UnsignedOrder into an Order
237    pub fn into_order(self) -> Cow<'a, Order> {
238        self.order
239    }
240
241    /// Sign the UnsignedOrder, generating a SignedOrder.
242    pub async fn sign<S: Signer>(&self, signer: &S) -> Result<SignedOrder, SigningError> {
243        // if nonce is None, populate it with the current time
244        let nonce = self.nonce.unwrap_or(Utc::now().timestamp_micros() as u64);
245
246        // get chain id and order contract address
247        let rollup_chain_id = self.rollup_chain_id.ok_or(SigningError::MissingChainId)?;
248        let rollup_order_contract =
249            self.rollup_order_address.ok_or(SigningError::MissingOrderContract(rollup_chain_id))?;
250
251        // get the outputs for the Order
252        let outputs = self.order.outputs().to_vec();
253        // generate the permitted tokens from the Inputs on the Order
254        let permitted: Vec<TokenPermissions> = self.order.inputs().iter().map(Into::into).collect();
255
256        // generate the permit2 signing info
257        let permit = permit_signing_info(
258            outputs,
259            permitted,
260            self.order.deadline(),
261            nonce,
262            rollup_chain_id,
263            rollup_order_contract,
264        );
265
266        // sign it
267        let signature = signer.sign_hash(&permit.signing_hash).await?;
268
269        // return as a SignedOrder
270        Ok(SignedOrder::new(
271            Permit2Batch {
272                permit: permit.permit,
273                owner: signer.address(),
274                signature: signature.as_bytes().into(),
275            },
276            permit.outputs,
277        ))
278    }
279}
280
281#[cfg(test)]
282mod tests {
283    use alloy::primitives::{b256, Signature, U256};
284    use signet_zenith::HostOrders::{PermitBatchTransferFrom, TokenPermissions};
285
286    use super::*;
287
288    fn basic_order() -> SignedOrder {
289        SignedOrder::new(
290            Permit2Batch {
291                permit: PermitBatchTransferFrom {
292                    permitted: vec![TokenPermissions { token: Address::ZERO, amount: U256::ZERO }],
293                    nonce: U256::ZERO,
294                    deadline: U256::ZERO,
295                },
296                owner: Address::ZERO,
297                signature: Signature::test_signature().as_bytes().into(),
298            },
299            vec![Output {
300                token: Address::ZERO,
301                amount: U256::ZERO,
302                recipient: Address::ZERO,
303                chainId: 0,
304            }],
305        )
306    }
307
308    #[test]
309    fn test_order_hash() {
310        let order = basic_order();
311        let hash = order.order_hash();
312        let pre_image = order.order_hash_pre_image();
313
314        assert_eq!(hash, &keccak256(pre_image));
315        assert_eq!(
316            hash,
317            &b256!("0xba359dd4f891bed0a2cf87c306e59fb6ee099e02b5b0fa86584cdcc44bf6c272")
318        );
319    }
320}