signet_types/signing/
fill.rs

1use crate::agg::AggregateOrders;
2use crate::signing::{permit_signing_info, SignedPermitError, SigningError};
3use alloy::{
4    network::TransactionBuilder, primitives::Address, rpc::types::TransactionRequest,
5    signers::Signer, sol_types::SolCall,
6};
7use chrono::Utc;
8use serde::{Deserialize, Serialize};
9use signet_zenith::{
10    BundleHelper::{FillPermit2, IOrders},
11    RollupOrders::{fillPermit2Call, Output, Permit2Batch, TokenPermissions},
12};
13use std::{borrow::Cow, collections::HashMap};
14
15/// SignedFill type is constructed by Fillers to fill a batch of Orders.
16/// It represents the Orders' Outputs after they have been permit2-encoded and signed.
17///
18/// A SignedFill corresponds to the parameters for `fillPermit2` on the OrderDestination contract,
19/// and thus contains all necessary information to fill the Order.
20///
21/// SignedFill is an optional part of the SignetEthBundle type.
22/// Fillers sign & send bundles which contain Order initiations & fills.
23/// Filler bundles contain:
24/// - optionally, a host SignedFill (if any Orders contain host Outputs)
25/// - optionally, a rollup transaction that submits a SignedFill (if any Orders contain rollup Outputs)
26/// - rollup transactions that submit the SignedOrders
27///
28/// # Warning ⚠️
29/// A SignedFill *must* remain private until it is mined, as there is no guarantee
30/// that desired Order Inputs will be received in return for the Outputs offered by the signed Permit2Batch.
31/// SignetEthBundles are used to submit SignedFills because they *must* be submitted atomically
32/// with the corresponding SignedOrder(s) in order to claim the Inputs.
33/// It is important to use private transaction relays to send bundles containing SignedFill(s) to Builders.
34/// Bundles can be sent to a *trusted* Signet Node's `signet_sendBundle` endpoint.
35///
36/// TODO: Link to docs.
37#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
38pub struct SignedFill {
39    /// The permit batch.
40    #[serde(flatten)]
41    pub permit: Permit2Batch,
42    /// The desired outputs.
43    pub outputs: Vec<Output>,
44}
45
46impl SignedFill {
47    /// Creates a new signed fill.
48    pub const fn new(permit: Permit2Batch, outputs: Vec<Output>) -> Self {
49        Self { permit, outputs }
50    }
51
52    /// Check that this can be syntactically used as a fill.
53    ///
54    /// For it to be valid:
55    /// - Deadline must be in the future.
56    /// - The permits must exactly match the ordering, token, and amount of the outputs.
57    pub fn validate(&self, timestamp: u64) -> Result<(), SignedPermitError> {
58        let deadline = self.permit.permit.deadline.saturating_to::<u64>();
59        if timestamp > deadline {
60            return Err(SignedPermitError::DeadlinePassed { current: timestamp, deadline });
61        }
62
63        // ensure Permits exactly match Outputs
64        if self.outputs.len() != self.permit.permit.permitted.len() {
65            return Err(SignedPermitError::PermitMismatch);
66        }
67
68        for (output, permit) in self.outputs.iter().zip(self.permit.permit.permitted.iter()) {
69            // check that the token is the same
70            if output.token != permit.token {
71                return Err(SignedPermitError::PermitMismatch);
72            }
73            // check that the amount is exactly equal
74            if output.amount != permit.amount {
75                return Err(SignedPermitError::PermitMismatch);
76            }
77        }
78
79        Ok(())
80    }
81
82    /// Generate a TransactionRequest to `fill` the SignedFill.
83    pub fn to_fill_tx(&self, order_contract: Address) -> TransactionRequest {
84        // encode fill data
85        let fill_data =
86            fillPermit2Call { outputs: self.outputs.clone(), permit2: self.permit.clone() }
87                .abi_encode();
88
89        // construct fill tx request
90        TransactionRequest::default().with_input(fill_data).with_to(order_contract)
91    }
92}
93
94impl From<SignedFill> for FillPermit2 {
95    fn from(fill: SignedFill) -> Self {
96        FillPermit2 {
97            permit2: fill.permit.into(),
98            outputs: fill.outputs.into_iter().map(IOrders::Output::from).collect(),
99        }
100    }
101}
102
103impl From<&SignedFill> for FillPermit2 {
104    fn from(fill: &SignedFill) -> Self {
105        FillPermit2 {
106            permit2: (&fill.permit).into(),
107            outputs: fill.outputs.iter().map(IOrders::Output::from).collect(),
108        }
109    }
110}
111
112/// An UnsignedFill is a helper type used to easily transform an AggregateOrder into a single SignedFill with correct permit2 semantics.
113#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
114pub struct UnsignedFill<'a> {
115    orders: Cow<'a, AggregateOrders>,
116    deadline: Option<u64>,
117    nonce: Option<u64>,
118    destination_chains: HashMap<u64, Address>,
119}
120
121impl<'a> From<&'a AggregateOrders> for UnsignedFill<'a> {
122    fn from(orders: &'a AggregateOrders) -> Self {
123        UnsignedFill::new(orders)
124    }
125}
126
127impl<'a> UnsignedFill<'a> {
128    /// Get a new UnsignedFill from a set of AggregateOrders.
129    pub fn new(orders: &'a AggregateOrders) -> Self {
130        Self {
131            orders: orders.into(),
132            deadline: None,
133            nonce: None,
134            destination_chains: HashMap::new(),
135        }
136    }
137
138    /// Add a Permit2 nonce to the UnsignedFill.
139    pub fn with_nonce(self, nonce: u64) -> Self {
140        Self { nonce: Some(nonce), ..self }
141    }
142
143    /// Add a deadline to the UnsignedFill, after which it cannot be mined.
144    pub fn with_deadline(self, deadline: u64) -> Self {
145        Self { deadline: Some(deadline), ..self }
146    }
147
148    /// Add the chain id  and Order contract address to the UnsignedOrder.
149    pub fn with_chain(mut self, chain_id: u64, order_contract_address: Address) -> Self {
150        self.destination_chains.insert(chain_id, order_contract_address);
151        self
152    }
153
154    /// Sign the UnsignedFill, generating a SignedFill for each destination chain.
155    /// Use if Filling Orders with the same signing key on every chain.
156    pub async fn sign<S: Signer>(
157        &self,
158        signer: &S,
159    ) -> Result<HashMap<u64, SignedFill>, SigningError> {
160        let mut fills = HashMap::new();
161
162        // loop through each destination chain and sign the fills
163        for destination_chain_id in self.orders.destination_chain_ids() {
164            let signed_fill = self.sign_for(destination_chain_id, signer).await?;
165            fills.insert(destination_chain_id, signed_fill);
166        }
167
168        // return the fills
169        Ok(fills)
170    }
171
172    /// Sign the UnsignedFill for a specific destination chain.
173    /// Use if Filling Orders with different signing keys on respective destination chains.
174    /// # Warning ⚠️
175    /// *All* Outputs MUST be filled on all destination chains, else the Order Inputs will not be transferred.
176    /// Take care when using this function to produce SignedFills for every destination chain.
177    pub async fn sign_for<S: Signer>(
178        &self,
179        chain_id: u64,
180        signer: &S,
181    ) -> Result<SignedFill, SigningError> {
182        let now = Utc::now();
183        // if nonce is are None, populate it as the current timestamp in microseconds
184        let nonce = self.nonce.unwrap_or(now.timestamp_micros() as u64);
185        // if deadline is None, populate it as now + 12 seconds (can only mine within the current block)
186        let deadline = self.deadline.unwrap_or(now.timestamp() as u64 + 12);
187
188        // get the destination order address
189        let destination_order_address = self
190            .destination_chains
191            .get(&chain_id)
192            .ok_or(SigningError::MissingOrderContract(chain_id))?;
193
194        // get the outputs for the chain from the AggregateOrders
195        let outputs = self.orders.outputs_for(chain_id);
196        // generate the permitted tokens from the Outputs
197        let permitted: Vec<TokenPermissions> = outputs.iter().map(Into::into).collect();
198
199        // generate the permit2 signing info
200        let permit = permit_signing_info(
201            outputs,
202            permitted,
203            deadline,
204            nonce,
205            chain_id,
206            *destination_order_address,
207        );
208
209        // sign it
210        let signature = signer.sign_hash(&permit.signing_hash).await?;
211
212        // return as a SignedFill
213        Ok(SignedFill {
214            permit: Permit2Batch {
215                permit: permit.permit,
216                owner: signer.address(),
217                signature: signature.as_bytes().into(),
218            },
219            outputs: permit.outputs,
220        })
221    }
222}