1
1
use serde:: { Deserialize , Serialize } ;
2
2
use std:: collections:: BTreeMap ;
3
3
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 } ;
6
9
7
10
use crate :: SignedOrder ;
8
11
@@ -28,6 +31,38 @@ pub struct ZenithEthBundleResponse {
28
31
pub bundle_hash : B256 ,
29
32
}
30
33
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
+
31
66
/// Bundle of transactions for `zenith_callBundle`
32
67
#[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize ) ]
33
68
#[ serde( rename_all = "camelCase" ) ]
@@ -49,3 +84,196 @@ pub struct ZenithCallBundleResponse {
49
84
#[ serde( flatten) ]
50
85
pub response : EthCallBundleResponse ,
51
86
}
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