signet_types/agg/
order.rs

1use crate::SignedOrder;
2use alloy::primitives::{Address, U256};
3use serde::{Deserialize, Serialize};
4use signet_zenith::RollupOrders;
5use std::borrow::Cow;
6use std::collections::{HashMap, HashSet};
7
8/// Aggregated orders for a transaction or set of transactions.
9#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)]
10pub struct AggregateOrders {
11    /// Outputs to be transferred to the user. These may be on the rollup or
12    /// the host or potentially elsewhere in the future.
13    /// (chain_id, token) -> recipient -> amount
14    pub outputs: HashMap<(u64, Address), HashMap<Address, U256>>,
15    /// Inputs to be transferred to the filler. These are always on the
16    /// rollup.
17    pub inputs: HashMap<Address, U256>,
18}
19
20impl AggregateOrders {
21    /// Instantiate a new [`AggregateOrders`].
22    pub fn new() -> Self {
23        Default::default()
24    }
25
26    /// Instantiate a new [`AggregateOrders`] with a custom capacity. The
27    /// capcity is for the number of assets in inputs or outputs.
28    pub fn with_capacity(capacity: usize) -> Self {
29        Self { outputs: HashMap::with_capacity(capacity), inputs: HashMap::with_capacity(capacity) }
30    }
31
32    /// Ingest an output into the aggregate orders.
33    pub(crate) fn ingest_output(&mut self, output: &RollupOrders::Output) {
34        self.ingest_raw_output(
35            output.chainId as u64,
36            output.token,
37            output.recipient,
38            output.amount,
39        );
40    }
41
42    /// Ingest raw output values into the aggregate orders.
43    fn ingest_raw_output(
44        &mut self,
45        chain_id: u64,
46        token: Address,
47        recipient: Address,
48        amount: U256,
49    ) {
50        let entry =
51            self.outputs.entry((chain_id, token)).or_default().entry(recipient).or_default();
52        *entry = entry.saturating_add(amount);
53    }
54
55    /// Ingest an input into the aggregate orders.
56    pub(crate) fn ingest_input(&mut self, input: &RollupOrders::Input) {
57        self.ingest_raw_input(input.token, input.amount);
58    }
59
60    /// Ingest raw input values into the aggregate orders.
61    fn ingest_raw_input(&mut self, token: Address, amount: U256) {
62        let entry = self.inputs.entry(token).or_default();
63        *entry = entry.saturating_add(amount);
64    }
65
66    /// Ingest a new order into the aggregate orders.
67    pub fn ingest(&mut self, order: &RollupOrders::Order) {
68        order.outputs.iter().for_each(|o| self.ingest_output(o));
69        order.inputs.iter().for_each(|i| self.ingest_input(i));
70    }
71
72    /// Ingest a signed order into the aggregate orders.
73    pub fn ingest_signed(&mut self, order: &SignedOrder) {
74        order
75            .outputs()
76            .iter()
77            .for_each(|o| self.ingest_raw_output(o.chainId as u64, o.token, o.recipient, o.amount));
78        order
79            .permit()
80            .permit
81            .permitted
82            .iter()
83            .for_each(|tp| self.ingest_raw_input(tp.token, tp.amount));
84    }
85
86    /// Extend the orders with a new set of orders.
87    pub fn extend<'a>(&mut self, orders: impl IntoIterator<Item = &'a RollupOrders::Order>) {
88        for order in orders {
89            self.ingest(order);
90        }
91    }
92
93    /// Extend the orders with a new set of signed orders.
94    pub fn extend_signed<'a>(&mut self, orders: impl IntoIterator<Item = &'a SignedOrder>) {
95        for order in orders {
96            self.ingest_signed(order);
97        }
98    }
99
100    /// Get the unique target chain ids for the aggregated outputs.
101    pub fn target_chain_ids(&self) -> Vec<u64> {
102        HashSet::<u64>::from_iter(self.outputs.keys().map(|(chain_id, _)| *chain_id))
103            .into_iter()
104            .collect()
105    }
106
107    /// Get the aggregated Outputs for a given target chain id.
108    /// # Warning ⚠️
109    /// All Orders in the AggregateOrders MUST have originated on the same rollup.
110    /// Otherwise, the aggregated Outputs will be incorrectly credited.
111    pub fn outputs_for(&self, target_chain_id: u64, ru_chain_id: u64) -> Vec<RollupOrders::Output> {
112        let mut o = Vec::new();
113        for ((chain_id, token), recipient_map) in &self.outputs {
114            if *chain_id == target_chain_id {
115                for (recipient, amount) in recipient_map {
116                    o.push(RollupOrders::Output {
117                        token: *token,
118                        amount: U256::from(*amount),
119                        recipient: *recipient,
120                        chainId: ru_chain_id as u32,
121                    });
122                }
123            }
124        }
125        o
126    }
127
128    /// Absorb the orders from another context.
129    pub fn absorb(&mut self, other: &Self) {
130        for (address, amount) in other.inputs.iter() {
131            self.ingest_raw_input(*address, *amount);
132        }
133
134        for ((chain_id, output_asset), recipients) in other.outputs.iter() {
135            for (recipient, value) in recipients {
136                self.ingest_raw_output(*chain_id, *output_asset, *recipient, *value);
137            }
138        }
139    }
140}
141
142impl<'a> FromIterator<&'a RollupOrders::Order> for AggregateOrders {
143    fn from_iter<T: IntoIterator<Item = &'a RollupOrders::Order>>(iter: T) -> Self {
144        let mut orders = AggregateOrders::new();
145        orders.extend(iter);
146        orders
147    }
148}
149
150impl<'a> FromIterator<&'a SignedOrder> for AggregateOrders {
151    fn from_iter<T: IntoIterator<Item = &'a SignedOrder>>(iter: T) -> Self {
152        let mut orders = AggregateOrders::new();
153        orders.extend_signed(iter);
154        orders
155    }
156}
157
158impl<'a> From<&'a RollupOrders::Order> for AggregateOrders {
159    fn from(order: &'a RollupOrders::Order) -> Self {
160        let mut orders = AggregateOrders::new();
161        orders.ingest(order);
162        orders
163    }
164}
165
166impl<'a> From<&'a SignedOrder> for AggregateOrders {
167    fn from(order: &'a SignedOrder) -> Self {
168        let mut orders = AggregateOrders::new();
169        orders.ingest_signed(order);
170        orders
171    }
172}
173
174impl<'a> From<&'a AggregateOrders> for Cow<'a, AggregateOrders> {
175    fn from(orders: &'a AggregateOrders) -> Self {
176        Cow::Borrowed(orders)
177    }
178}
179
180#[cfg(test)]
181mod test {
182    use super::*;
183    use alloy::primitives::{Address, U256};
184
185    const ASSET_A: Address = Address::repeat_byte(1);
186    const ASSET_B: Address = Address::repeat_byte(2);
187    const ASSET_C: Address = Address::repeat_byte(3);
188
189    const USER_A: Address = Address::repeat_byte(4);
190    const USER_B: Address = Address::repeat_byte(5);
191    const USER_C: Address = Address::repeat_byte(6);
192
193    fn input(asset: Address, amount: u64) -> RollupOrders::Input {
194        RollupOrders::Input { token: asset, amount: U256::from(amount) }
195    }
196
197    fn output(asset: Address, recipient: Address, amount: u64) -> RollupOrders::Output {
198        RollupOrders::Output { chainId: 1, token: asset, recipient, amount: U256::from(amount) }
199    }
200
201    #[test]
202    fn test_single_order() {
203        let order = RollupOrders::Order {
204            inputs: vec![input(ASSET_A, 100), input(ASSET_B, 200)],
205            outputs: vec![
206                output(ASSET_A, USER_A, 50),
207                output(ASSET_A, USER_B, 50),
208                output(ASSET_B, USER_B, 100),
209                output(ASSET_C, USER_C, 200),
210                output(ASSET_C, USER_C, 200),
211            ],
212            deadline: U256::ZERO,
213        };
214
215        let agg: AggregateOrders = [&order].into_iter().collect();
216        assert_eq!(agg.inputs.get(&ASSET_A), Some(&U256::from(100)), "ASSET_A input");
217        assert_eq!(agg.inputs.get(&ASSET_B), Some(&U256::from(200)), "ASSET_B input");
218
219        assert_eq!(
220            agg.outputs.get(&(1, ASSET_A)).map(|m| m.get(&USER_A)),
221            Some(Some(&U256::from(50))),
222            "ASSET_A USER_A output"
223        );
224        assert_eq!(
225            agg.outputs.get(&(1, ASSET_A)).map(|m| m.get(&USER_B)),
226            Some(Some(&U256::from(50))),
227            "ASSET_A USER_B output"
228        );
229        assert_eq!(
230            agg.outputs.get(&(1, ASSET_B)).map(|m| m.get(&USER_B)),
231            Some(Some(&U256::from(100))),
232            "ASSET_B USER_B output"
233        );
234        assert_eq!(
235            agg.outputs.get(&(1, ASSET_C)).map(|m| m.get(&USER_C)),
236            Some(Some(&U256::from(400))),
237            "ASSET_C USER_C output"
238        );
239    }
240
241    #[test]
242    fn test_two_orders() {
243        let order_1 = RollupOrders::Order {
244            inputs: vec![input(ASSET_A, 100), input(ASSET_B, 200)],
245            outputs: vec![
246                output(ASSET_A, USER_A, 50),
247                output(ASSET_A, USER_B, 50),
248                output(ASSET_B, USER_B, 100),
249                output(ASSET_C, USER_C, 200),
250                output(ASSET_C, USER_C, 200),
251            ],
252            deadline: U256::ZERO,
253        };
254        let order_2 = RollupOrders::Order {
255            inputs: vec![input(ASSET_A, 50), input(ASSET_C, 100)],
256            outputs: vec![
257                output(ASSET_A, USER_A, 50),
258                output(ASSET_B, USER_B, 100),
259                output(ASSET_C, USER_C, 100),
260            ],
261            deadline: U256::ZERO,
262        };
263
264        let agg: AggregateOrders = [&order_1, &order_2].into_iter().collect();
265
266        assert_eq!(agg.inputs.get(&ASSET_A), Some(&U256::from(150)), "ASSET_A input");
267        assert_eq!(agg.inputs.get(&ASSET_B), Some(&U256::from(200)), "ASSET_B input");
268        assert_eq!(agg.inputs.get(&ASSET_C), Some(&U256::from(100)), "ASSET_C input");
269
270        assert_eq!(
271            agg.outputs.get(&(1, ASSET_A)).map(|m| m.get(&USER_A)),
272            Some(Some(&U256::from(100))),
273            "ASSET_A USER_A output"
274        );
275        assert_eq!(
276            agg.outputs.get(&(1, ASSET_A)).map(|m| m.get(&USER_B)),
277            Some(Some(&U256::from(50))),
278            "ASSET_A USER_B output"
279        );
280        assert_eq!(
281            agg.outputs.get(&(1, ASSET_B)).map(|m| m.get(&USER_B)),
282            Some(Some(&U256::from(200))),
283            "ASSET_B USER_B output"
284        );
285        assert_eq!(
286            agg.outputs.get(&(1, ASSET_C)).map(|m| m.get(&USER_C)),
287            Some(Some(&U256::from(500))),
288            "ASSET_C USER_C output"
289        );
290    }
291}