Skip to content
This repository was archived by the owner on Mar 26, 2025. It is now read-only.

Commit a8b8d74

Browse files
committed
adds the supporting methods for Signet bundle types
- adds the Signet bundle impls for Zenith
1 parent 2128a9c commit a8b8d74

File tree

1 file changed

+230
-2
lines changed

1 file changed

+230
-2
lines changed

src/bundle.rs

Lines changed: 230 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
use serde::{Deserialize, Serialize};
22
use 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

710
use 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

Comments
 (0)