signet_types/signing/
fill.rs

1use crate::agg::AggregateOrders;
2use crate::signing::{permit_signing_info, SignedPermitError, SigningError};
3use crate::SignedOrder;
4use alloy::{
5    network::TransactionBuilder, primitives::Address, rpc::types::TransactionRequest,
6    signers::Signer, sol_types::SolCall,
7};
8use chrono::Utc;
9use serde::{Deserialize, Serialize};
10use signet_constants::SignetSystemConstants;
11use signet_zenith::RollupOrders::Order;
12use signet_zenith::{
13    BundleHelper::{FillPermit2, IOrders},
14    RollupOrders::{fillPermit2Call, Output, Permit2Batch, TokenPermissions},
15};
16use std::{borrow::Cow, collections::HashMap};
17
18/// SignedFill type is constructed by Fillers to fill a batch of Orders.
19/// It represents the Orders' Outputs after they have been permit2-encoded and signed.
20///
21/// A SignedFill corresponds to the parameters for `fillPermit2` on the OrderDestination contract,
22/// and thus contains all necessary information to fill the Order.
23///
24/// SignedFill is an optional part of the SignetEthBundle type.
25/// Fillers sign & send bundles which contain Order initiations & fills.
26/// Filler bundles contain:
27/// - optionally, a host SignedFill (if any Orders contain host Outputs)
28/// - optionally, a rollup transaction that submits a SignedFill (if any Orders contain rollup Outputs)
29/// - rollup transactions that submit the SignedOrders
30///
31/// # Warning ⚠️
32/// A SignedFill *must* remain private until it is mined, as there is no guarantee
33/// that desired Order Inputs will be received in return for the Outputs offered by the signed Permit2Batch.
34/// SignetEthBundles are used to submit SignedFills because they *must* be submitted atomically
35/// with the corresponding SignedOrder(s) in order to claim the Inputs.
36/// It is important to use private transaction relays to send bundles containing SignedFill(s) to Builders.
37/// Bundles can be sent to a *trusted* Signet Node's `signet_sendBundle` endpoint.
38///
39/// TODO: Link to docs.
40#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
41pub struct SignedFill {
42    /// The permit batch.
43    #[serde(flatten)]
44    pub permit: Permit2Batch,
45    /// The desired outputs.
46    pub outputs: Vec<Output>,
47}
48
49impl SignedFill {
50    /// Creates a new signed fill.
51    pub const fn new(permit: Permit2Batch, outputs: Vec<Output>) -> Self {
52        Self { permit, outputs }
53    }
54
55    /// Check that this can be syntactically used as a fill.
56    ///
57    /// For it to be valid:
58    /// - Deadline must be in the future.
59    /// - The permits must exactly match the ordering, token, and amount of the
60    ///   outputs.
61    pub fn validate(&self, timestamp: u64) -> Result<(), SignedPermitError> {
62        let deadline = self.permit.permit.deadline.saturating_to::<u64>();
63        if timestamp > deadline {
64            return Err(SignedPermitError::DeadlinePassed { current: timestamp, deadline });
65        }
66
67        // ensure Permits exactly match Outputs
68        if self.outputs.len() != self.permit.permit.permitted.len() {
69            return Err(SignedPermitError::PermitMismatch);
70        }
71
72        for (output, permit) in self.outputs.iter().zip(self.permit.permit.permitted.iter()) {
73            // check that the token is the same
74            if output.token != permit.token {
75                return Err(SignedPermitError::PermitMismatch);
76            }
77            // check that the amount is exactly equal
78            if output.amount != permit.amount {
79                return Err(SignedPermitError::PermitMismatch);
80            }
81        }
82
83        Ok(())
84    }
85
86    /// Generate a TransactionRequest to `fill` the SignedFill.
87    pub fn to_fill_tx(&self, order_contract: Address) -> TransactionRequest {
88        // encode fill data
89        let fill_data =
90            fillPermit2Call { outputs: self.outputs.clone(), permit2: self.permit.clone() }
91                .abi_encode();
92
93        // construct fill tx request
94        TransactionRequest::default().with_input(fill_data).with_to(order_contract)
95    }
96}
97
98impl From<SignedFill> for FillPermit2 {
99    fn from(fill: SignedFill) -> Self {
100        FillPermit2 {
101            permit2: fill.permit.into(),
102            outputs: fill.outputs.into_iter().map(IOrders::Output::from).collect(),
103        }
104    }
105}
106
107impl From<&SignedFill> for FillPermit2 {
108    fn from(fill: &SignedFill) -> Self {
109        FillPermit2 {
110            permit2: (&fill.permit).into(),
111            outputs: fill.outputs.iter().map(IOrders::Output::from).collect(),
112        }
113    }
114}
115
116/// An [`UnsignedFill`] is a builder type used to easily transform [`Order`]s
117/// or [`AggregateOrders`] into a [`SignedFill`] with correct permit2 semantics.
118#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
119pub struct UnsignedFill<'a> {
120    /// The rollup chain id from which the Orders originated
121    /// to which the Fill will be credited.
122    ru_chain_id: Option<u64>,
123    /// The set of Orders to fill. Multiple Orders can be aggregated into a single Fill,
124    /// but they MUST all originate on the same rollup chain indicated by `ru_chain_id`.
125    orders: Cow<'a, AggregateOrders>,
126    /// The deadline for the Fill, after which it cannot be mined.
127    deadline: Option<u64>,
128    /// The Permit2 nonce for the Fill, used to prevent replay attacks.
129    nonce: Option<u64>,
130    /// The (chain_id, order_contract_address) for each target chain on which Fills will be submitted.
131    target_chains: HashMap<u64, Address>,
132}
133
134impl Default for UnsignedFill<'_> {
135    fn default() -> Self {
136        Self {
137            ru_chain_id: None,
138            orders: Cow::Owned(AggregateOrders::default()),
139            deadline: None,
140            nonce: None,
141            target_chains: HashMap::new(),
142        }
143    }
144}
145
146impl<'a> From<&'a AggregateOrders> for UnsignedFill<'a> {
147    fn from(orders: &'a AggregateOrders) -> Self {
148        Self {
149            ru_chain_id: None,
150            orders: Cow::Borrowed(orders),
151            deadline: None,
152            nonce: None,
153            target_chains: HashMap::new(),
154        }
155    }
156}
157
158impl From<Order> for UnsignedFill<'static> {
159    fn from(order: Order) -> Self {
160        let mut aggregate_orders = AggregateOrders::default();
161        aggregate_orders.ingest(&order);
162        Self {
163            ru_chain_id: None,
164            orders: Cow::Owned(aggregate_orders),
165            deadline: None,
166            nonce: None,
167            target_chains: HashMap::new(),
168        }
169    }
170}
171
172impl<'a> UnsignedFill<'a> {
173    /// Get a new UnsignedFill from a set of [`AggregateOrders`].
174    pub fn new() -> Self {
175        Self {
176            ru_chain_id: None,
177            orders: Cow::Owned(AggregateOrders::default()),
178            deadline: None,
179            nonce: None,
180            target_chains: HashMap::new(),
181        }
182    }
183
184    /// Add an [`Order`] to the [`UnsignedFill`].
185    pub fn fill_raw(mut self, order: &Order) -> UnsignedFill<'static> {
186        self.orders.to_mut().ingest(order);
187        UnsignedFill {
188            ru_chain_id: self.ru_chain_id,
189            orders: Cow::Owned(self.orders.into_owned()),
190            deadline: self.deadline,
191            nonce: self.nonce,
192            target_chains: self.target_chains,
193        }
194    }
195
196    /// Add a [`SignedOrder`] to the [`UnsignedFill`].
197    pub fn fill(mut self, order: &SignedOrder) -> UnsignedFill<'static> {
198        self.orders.to_mut().ingest_signed(order);
199        UnsignedFill {
200            ru_chain_id: self.ru_chain_id,
201            orders: Cow::Owned(self.orders.into_owned()),
202            deadline: self.deadline,
203            nonce: self.nonce,
204            target_chains: self.target_chains,
205        }
206    }
207
208    /// Add a Permit2 nonce to the UnsignedFill.
209    pub fn with_nonce(self, nonce: u64) -> Self {
210        Self { nonce: Some(nonce), ..self }
211    }
212
213    /// Add a deadline to the UnsignedFill, after which it cannot be mined.
214    pub fn with_deadline(self, deadline: u64) -> Self {
215        Self { deadline: Some(deadline), ..self }
216    }
217
218    /// Add the rollup chain id to the UnsignedFill.
219    #[deprecated(since = "0.14.1", note = "Use `with_chain` instead.")]
220    pub fn with_ru_chain_id(self, ru_chain_id: u64) -> Self {
221        Self { ru_chain_id: Some(ru_chain_id), ..self }
222    }
223
224    /// Add the chain ids and Orders contract addresses
225    /// used for signing the Fill.
226    /// MUST call before `sign` or `sign_for`.
227    pub fn with_chain(mut self, constants: SignetSystemConstants) -> Self {
228        self.target_chains.insert(constants.ru_chain_id(), constants.ru_orders());
229        self.target_chains.insert(constants.host_chain_id(), constants.host_orders());
230        Self { ru_chain_id: Some(constants.ru_chain_id()), ..self }
231    }
232
233    /// Sign the UnsignedFill, generating a SignedFill for each target chain.
234    /// Use if Filling Orders with the same signing key on every chain.
235    pub async fn sign<S: Signer>(
236        &self,
237        signer: &S,
238    ) -> Result<HashMap<u64, SignedFill>, SigningError> {
239        let mut fills = HashMap::new();
240
241        // loop through each target chain and sign the fills
242        for target_chain_id in self.orders.target_chain_ids() {
243            let signed_fill = self.sign_for(target_chain_id, signer).await?;
244            fills.insert(target_chain_id, signed_fill);
245        }
246
247        // return the fills
248        Ok(fills)
249    }
250
251    /// Sign the UnsignedFill for a specific target chain.
252    /// Use if Filling Orders with different signing keys on respective target chains.
253    /// # Warning ⚠️
254    /// *All* Outputs MUST be filled on all target chains, else the Order Inputs will not be transferred on the rollup.
255    /// Take care when using this function to produce SignedFills for every target chain.
256    pub async fn sign_for<S: Signer>(
257        &self,
258        target_chain_id: u64,
259        signer: &S,
260    ) -> Result<SignedFill, SigningError> {
261        let now = Utc::now();
262        // if nonce is are None, populate it as the current timestamp in microseconds
263        let nonce = self.nonce.unwrap_or(now.timestamp_micros() as u64);
264        // if deadline is None, populate it as now + 12 seconds (can only mine within the current block)
265        let deadline = self.deadline.unwrap_or(now.timestamp() as u64 + 12);
266
267        // get the target order address
268        let target_order_address = self
269            .target_chains
270            .get(&target_chain_id)
271            .ok_or(SigningError::MissingOrderContract(target_chain_id))?;
272
273        // get the rollup chain id, or throw an error if not set
274        let ru_chain_id = self.ru_chain_id.ok_or(SigningError::MissingChainId)?;
275
276        // get the outputs for the target chain from the AggregateOrders
277        let outputs = self.orders.outputs_for(target_chain_id, ru_chain_id);
278        // generate the permitted tokens from the Outputs
279        let permitted: Vec<TokenPermissions> = outputs.iter().map(Into::into).collect();
280
281        // generate the permit2 signing info
282        let permit = permit_signing_info(
283            outputs,
284            permitted,
285            deadline,
286            nonce,
287            target_chain_id,
288            *target_order_address,
289        );
290
291        // sign it
292        let signature = signer.sign_hash(&permit.signing_hash).await?;
293
294        // return as a SignedFill
295        Ok(SignedFill {
296            permit: Permit2Batch {
297                permit: permit.permit,
298                owner: signer.address(),
299                signature: signature.as_bytes().into(),
300            },
301            outputs: permit.outputs,
302        })
303    }
304}