11use serde:: { Deserialize , Serialize } ;
22use std:: collections:: BTreeMap ;
33
4- use alloy:: rpc:: types:: mev:: { EthCallBundle , EthCallBundleResponse , EthSendBundle } ;
5- use alloy_primitives:: { Address , B256 , U256 } ;
4+ use alloy:: {
5+ eips:: { eip2718:: Encodable2718 , BlockNumberOrTag } ,
6+ rpc:: types:: mev:: { EthCallBundle , EthCallBundleResponse , EthSendBundle } ,
7+ } ;
8+ use alloy_primitives:: { keccak256, Address , Bytes , B256 , U256 } ;
69
710use crate :: SignedOrder ;
811
@@ -28,6 +31,38 @@ pub struct ZenithEthBundleResponse {
2831 pub bundle_hash : B256 ,
2932}
3033
34+ impl ZenithEthBundle {
35+ /// Returns the transactions in this bundle.
36+ pub fn txs ( & self ) -> & [ Bytes ] {
37+ & self . bundle . txs
38+ }
39+
40+ /// Returns the block number for this bundle.
41+ pub fn block_number ( & self ) -> u64 {
42+ self . bundle . block_number
43+ }
44+
45+ /// Returns the minimum timestamp for this bundle.
46+ pub fn min_timestamp ( & self ) -> Option < u64 > {
47+ self . bundle . min_timestamp
48+ }
49+
50+ /// Returns the maximum timestamp for this bundle.
51+ pub fn max_timestamp ( & self ) -> Option < u64 > {
52+ self . bundle . max_timestamp
53+ }
54+
55+ /// Returns the reverting tx hashes for this bundle.
56+ pub fn reverting_tx_hashes ( & self ) -> & [ B256 ] {
57+ self . bundle . reverting_tx_hashes . as_slice ( )
58+ }
59+
60+ /// Returns the replacement uuid for this bundle.
61+ pub fn replacement_uuid ( & self ) -> Option < & str > {
62+ self . bundle . replacement_uuid . as_deref ( )
63+ }
64+ }
65+
3166/// Bundle of transactions for `zenith_callBundle`
3267#[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize ) ]
3368#[ serde( rename_all = "camelCase" ) ]
@@ -49,3 +84,196 @@ pub struct ZenithCallBundleResponse {
4984 #[ serde( flatten) ]
5085 pub response : EthCallBundleResponse ,
5186}
87+
88+ impl ZenithCallBundle {
89+ /// Returns the host fills for this bundle.
90+ pub fn host_fills ( & self ) -> & BTreeMap < Address , BTreeMap < Address , U256 > > {
91+ & self . host_fills
92+ }
93+
94+ /// Returns the transactions in this bundle.
95+ pub fn txs ( & self ) -> & [ Bytes ] {
96+ & self . bundle . txs
97+ }
98+
99+ /// Returns the block number for this bundle.
100+ pub fn block_number ( & self ) -> u64 {
101+ self . bundle . block_number
102+ }
103+
104+ /// Returns the state block number for this bundle.
105+ pub fn state_block_number ( & self ) -> BlockNumberOrTag {
106+ self . bundle . state_block_number
107+ }
108+
109+ /// Returns the timestamp for this bundle.
110+ pub fn timestamp ( & self ) -> Option < u64 > {
111+ self . bundle . timestamp
112+ }
113+
114+ /// Returns the gas limit for this bundle.
115+ pub fn gas_limit ( & self ) -> Option < u64 > {
116+ self . bundle . gas_limit
117+ }
118+
119+ /// Returns the difficulty for this bundle.
120+ pub fn difficulty ( & self ) -> Option < U256 > {
121+ self . bundle . difficulty
122+ }
123+
124+ /// Returns the base fee for this bundle.
125+ pub fn base_fee ( & self ) -> Option < u128 > {
126+ self . bundle . base_fee
127+ }
128+
129+ /// Creates a new bundle from the given [`Encodable2718`] transactions.
130+ pub fn from_2718_and_host_fills < I , T > (
131+ txs : I ,
132+ host_fills : BTreeMap < Address , BTreeMap < Address , U256 > > ,
133+ ) -> Self
134+ where
135+ I : IntoIterator < Item = T > ,
136+ T : Encodable2718 ,
137+ {
138+ Self :: from_raw_txs_and_host_fills ( txs. into_iter ( ) . map ( |tx| tx. encoded_2718 ( ) ) , host_fills)
139+ }
140+
141+ /// Creates a new bundle with the given transactions and host fills.
142+ pub fn from_raw_txs_and_host_fills < I , T > (
143+ txs : I ,
144+ host_fills : BTreeMap < Address , BTreeMap < Address , U256 > > ,
145+ ) -> Self
146+ where
147+ I : IntoIterator < Item = T > ,
148+ T : Into < Bytes > ,
149+ {
150+ Self {
151+ bundle : EthCallBundle {
152+ txs : txs. into_iter ( ) . map ( Into :: into) . collect ( ) ,
153+ ..Default :: default ( )
154+ } ,
155+ host_fills,
156+ }
157+ }
158+
159+ /// Adds an [`Encodable2718`] transaction to the bundle.
160+ pub fn append_2718_tx ( self , tx : impl Encodable2718 ) -> Self {
161+ self . append_raw_tx ( tx. encoded_2718 ( ) )
162+ }
163+
164+ /// Adds an EIP-2718 envelope to the bundle.
165+ pub fn append_raw_tx ( mut self , tx : impl Into < Bytes > ) -> Self {
166+ self . bundle . txs . push ( tx. into ( ) ) ;
167+ self
168+ }
169+
170+ /// Adds multiple [`Encodable2718`] transactions to the bundle.
171+ pub fn extend_2718_txs < I , T > ( self , tx : I ) -> Self
172+ where
173+ I : IntoIterator < Item = T > ,
174+ T : Encodable2718 ,
175+ {
176+ self . extend_raw_txs ( tx. into_iter ( ) . map ( |tx| tx. encoded_2718 ( ) ) )
177+ }
178+
179+ /// Adds multiple calls to the block.
180+ pub fn extend_raw_txs < I , T > ( mut self , txs : I ) -> Self
181+ where
182+ I : IntoIterator < Item = T > ,
183+ T : Into < Bytes > ,
184+ {
185+ self . bundle . txs . extend ( txs. into_iter ( ) . map ( Into :: into) ) ;
186+ self
187+ }
188+
189+ /// Sets the block number for the bundle.
190+ pub const fn with_block_number ( mut self , block_number : u64 ) -> Self {
191+ self . bundle . block_number = block_number;
192+ self
193+ }
194+
195+ /// Sets the state block number for the bundle.
196+ pub fn with_state_block_number (
197+ mut self ,
198+ state_block_number : impl Into < BlockNumberOrTag > ,
199+ ) -> Self {
200+ self . bundle . state_block_number = state_block_number. into ( ) ;
201+ self
202+ }
203+
204+ /// Sets the timestamp for the bundle.
205+ pub const fn with_timestamp ( mut self , timestamp : u64 ) -> Self {
206+ self . bundle . timestamp = Some ( timestamp) ;
207+ self
208+ }
209+
210+ /// Sets the gas limit for the bundle.
211+ pub const fn with_gas_limit ( mut self , gas_limit : u64 ) -> Self {
212+ self . bundle . gas_limit = Some ( gas_limit) ;
213+ self
214+ }
215+
216+ /// Sets the difficulty for the bundle.
217+ pub const fn with_difficulty ( mut self , difficulty : U256 ) -> Self {
218+ self . bundle . difficulty = Some ( difficulty) ;
219+ self
220+ }
221+
222+ /// Sets the base fee for the bundle.
223+ pub const fn with_base_fee ( mut self , base_fee : u128 ) -> Self {
224+ self . bundle . base_fee = Some ( base_fee) ;
225+ self
226+ }
227+
228+ /// Make a bundle hash from the given deserialized transaction array and host fills from this bundle.
229+ /// The hash is calculated as keccak256(tx_preimage + host_preimage).
230+ /// The tx_preimage is calculated as `keccak(tx_hash1 + tx_hash2 + ... + tx_hashn)`.
231+ /// The host_preimage is calculated as
232+ /// `keccak(NUM_OF_ASSETS_LE + asset1 + NUM_OF_FILLS_LE + asset1_user1 + user1_amount2 + ... + asset1_usern + asset1_amountn + ...)`.
233+ /// For the number of users/fills and amounts amounts in the host_preimage, the amounts are serialized as little-endian U256 slice.
234+ pub fn bundle_hash ( & self ) -> B256 {
235+ let mut hasher = alloy_primitives:: Keccak256 :: new ( ) ;
236+
237+ // Concatenate the transaction hashes, to then hash them. This is the tx_preimage.
238+ for tx in self . bundle . txs . iter ( ) {
239+ // Calculate the tx hash (keccak256(encoded_signed_tx)) and append it to the tx_bytes.
240+ hasher. update ( keccak256 ( tx) . as_slice ( ) ) ;
241+ }
242+ let tx_preimage = hasher. finalize ( ) ;
243+
244+ // Now, let's build the host_preimage. We do it in steps:
245+ // 1. Prefix the number of assets, encoded as a little-endian U256 slice.
246+ // 2. For each asset:
247+ // 3. Concatenate the asset address.
248+ // 4. Prefix the number of fills.
249+ // 5. For each fill, concatenate the user and amount, the latter encoded as a little-endian U256 slice.
250+ let mut hasher = alloy_primitives:: Keccak256 :: new ( ) ;
251+
252+ // Prefix the list of users with the number of assets.
253+ hasher. update ( U256 :: from ( self . host_fills . len ( ) ) . as_le_slice ( ) ) ;
254+
255+ for ( asset, fills) in self . host_fills . iter ( ) {
256+ // Concatenate the asset address.
257+ hasher. update ( asset. as_slice ( ) ) ;
258+
259+ // Prefix the list of fills with the number of fills
260+ hasher. update ( U256 :: from ( fills. len ( ) ) . as_le_slice ( ) ) ;
261+
262+ for ( user, amount) in fills. iter ( ) {
263+ // Concatenate the user address and amount for each fill.
264+ hasher. update ( user. as_slice ( ) ) ;
265+ hasher. update ( amount. as_le_slice ( ) ) ;
266+ }
267+ }
268+
269+ // Hash the host pre-image.
270+ let host_preimage = hasher. finalize ( ) ;
271+
272+ let mut pre_image = alloy_primitives:: Keccak256 :: new ( ) ;
273+ pre_image. update ( tx_preimage. as_slice ( ) ) ;
274+ pre_image. update ( host_preimage. as_slice ( ) ) ;
275+
276+ // Hash both tx and host hashes to get the final bundle hash.
277+ pre_image. finalize ( )
278+ }
279+ }
0 commit comments