Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/bundle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ pub use call::{SignetBundleDriver, SignetCallBundle, SignetCallBundleResponse};

mod send;
pub use send::{
BundleInspector, SignetEthBundle, SignetEthBundleDriver, SignetEthBundleError,
SignetEthBundleInsp,
BundleInspector, BundleRecoverError, RecoverError, RecoveredBundle, SignetEthBundle,
SignetEthBundleDriver, SignetEthBundleError, SignetEthBundleInsp,
};
69 changes: 65 additions & 4 deletions crates/bundle/src/send/bundle.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Signet bundle types.
use alloy::{
consensus::{transaction::SignerRecoverable, TxEnvelope},
consensus::{
transaction::{Recovered, SignerRecoverable},
TxEnvelope,
},
eips::{eip2718::Eip2718Result, Decodable2718},
primitives::{Address, Bytes, TxHash, B256},
rlp::Buf,
Expand All @@ -13,6 +16,8 @@ use trevm::{
BundleError,
};

use crate::{BundleRecoverError, RecoveredBundle};

/// The inspector type required by the Signet bundle driver.
pub type BundleInspector<I = NoOpInspector> = Layered<TimeLimit, I>;

Expand Down Expand Up @@ -76,6 +81,61 @@ impl SignetEthBundle {
self.host_txs.iter().map(|tx| TxEnvelope::decode_2718(&mut tx.chunk()))
}

/// Return an iterator over recovered transactions in this bundle. This
/// iterator may include errors.
pub fn recover_txs(
&self,
) -> impl Iterator<Item = Result<Recovered<TxEnvelope>, BundleRecoverError>> + '_ {
self.decode_txs().enumerate().map(|(index, res)| match res {
Ok(tx) => {
tx.try_into_recovered().map_err(|err| BundleRecoverError::new(err, false, index))
}
Err(err) => Err(BundleRecoverError::new(err, false, index)),
})
}

/// Return an iterator over recovered host transactions in this bundle. This
/// iterator may include errors.
pub fn recover_host_txs(
&self,
) -> impl Iterator<Item = Result<Recovered<TxEnvelope>, BundleRecoverError>> + '_ {
self.decode_host_txs().enumerate().map(|(index, res)| match res {
Ok(tx) => {
tx.try_into_recovered().map_err(|err| BundleRecoverError::new(err, true, index))
}
Err(err) => Err(BundleRecoverError::new(err, true, index)),
})
}

/// Create a [`RecoveredBundle`] from this bundle by decoding and recovering
/// all transactions, taking ownership of the bundle.
pub fn try_into_recovered(self) -> Result<RecoveredBundle, BundleRecoverError> {
let txs = self.recover_txs().collect::<Result<Vec<_>, _>>()?;

let host_txs = self.recover_host_txs().collect::<Result<Vec<_>, _>>()?;

Ok(RecoveredBundle {
txs,
host_txs,
block_number: self.bundle.block_number,
min_timestamp: self.bundle.min_timestamp,
max_timestamp: self.bundle.max_timestamp,
reverting_tx_hashes: self.bundle.reverting_tx_hashes,
replacement_uuid: self.bundle.replacement_uuid,
dropping_tx_hashes: self.bundle.dropping_tx_hashes,
refund_percent: self.bundle.refund_percent,
refund_recipient: self.bundle.refund_recipient,
refund_tx_hashes: self.bundle.refund_tx_hashes,
extra_fields: self.bundle.extra_fields,
})
}

/// Create a [`RecoveredBundle`] from this bundle by decoding and recovering
/// all transactions, cloning other fields as necessary.
pub fn try_to_recovered(&self) -> Result<RecoveredBundle, BundleRecoverError> {
self.clone().try_into_recovered()
}

/// Return an iterator over the signers of the transactions in this bundle.
/// The iterator yields `Option<(TxHash, Address)>` for each transaction,
/// where `None` indicates that the signer could not be recovered.
Expand Down Expand Up @@ -126,9 +186,10 @@ impl SignetEthBundle {

/// Checks if the bundle is valid at a given timestamp.
pub fn is_valid_at_timestamp(&self, timestamp: u64) -> bool {
let min_timestamp = self.bundle.min_timestamp.unwrap_or(0);
let max_timestamp = self.bundle.max_timestamp.unwrap_or(u64::MAX);
timestamp >= min_timestamp && timestamp <= max_timestamp
let min_timestamp = self.min_timestamp().unwrap_or(0);
let max_timestamp = self.max_timestamp().unwrap_or(u64::MAX);

(min_timestamp..=max_timestamp).contains(&timestamp)
}

/// Checks if the bundle is valid at a given block number.
Expand Down
175 changes: 175 additions & 0 deletions crates/bundle/src/send/decoded.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use alloy::{
consensus::{transaction::Recovered, TxEnvelope},
primitives::{Address, TxHash},
serde::OtherFields,
};

/// Version of [`SignetEthBundle`] with decoded transactions.
///
/// [`SignetEthBundle`]: crate::send::bundle::SignetEthBundle
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RecoveredBundle {
/// Transactions in this bundle.
pub(crate) txs: Vec<Recovered<TxEnvelope>>,

/// Host transactions to be included in the host bundle.
pub(crate) host_txs: Vec<Recovered<TxEnvelope>>,

/// Block number for which this bundle is valid
pub(crate) block_number: u64,

/// unix timestamp when this bundle becomes active
pub(crate) min_timestamp: Option<u64>,

/// unix timestamp how long this bundle stays valid
pub(crate) max_timestamp: Option<u64>,

/// list of hashes of possibly reverting txs
pub(crate) reverting_tx_hashes: Vec<TxHash>,

/// UUID that can be used to cancel/replace this bundle
pub(crate) replacement_uuid: Option<String>,

/// A list of tx hashes that are allowed to be discarded
pub(crate) dropping_tx_hashes: Vec<TxHash>,

/// The percent that should be refunded to refund recipient
pub(crate) refund_percent: Option<u8>,

/// The address that receives the refund
pub(crate) refund_recipient: Option<Address>,

/// A list of tx hashes used to determine the refund
pub(crate) refund_tx_hashes: Vec<TxHash>,

/// Additional fields that are specific to the builder
pub(crate) extra_fields: OtherFields,
}

impl RecoveredBundle {
/// Instantiator. Generally recommend instantiating via conversion from
/// [`SignetEthBundle`] via [`SignetEthBundle::try_into_recovered`] or
/// [`SignetEthBundle::try_to_recovered`].
///
/// [`SignetEthBundle`]: crate::send::bundle::SignetEthBundle
/// [`SignetEthBundle::try_into_recovered`]: crate::send::bundle::SignetEthBundle::try_into_recovered
/// [`SignetEthBundle::try_to_recovered`]: crate::send::bundle::SignetEthBundle::try_to_recovered
#[doc(hidden)]
#[allow(clippy::too_many_arguments)]
pub const fn new(
txs: Vec<Recovered<TxEnvelope>>,
host_txs: Vec<Recovered<TxEnvelope>>,
block_number: u64,
min_timestamp: Option<u64>,
max_timestamp: Option<u64>,
reverting_tx_hashes: Vec<TxHash>,
replacement_uuid: Option<String>,
dropping_tx_hashes: Vec<TxHash>,
refund_percent: Option<u8>,
refund_recipient: Option<Address>,
refund_tx_hashes: Vec<TxHash>,
extra_fields: OtherFields,
) -> Self {
Self {
txs,
host_txs,
block_number,
min_timestamp,
max_timestamp,
reverting_tx_hashes,
replacement_uuid,
dropping_tx_hashes,
refund_percent,
refund_recipient,
refund_tx_hashes,
extra_fields,
}
}

/// Get the transactions.
pub const fn txs(&self) -> &[Recovered<TxEnvelope>] {
self.txs.as_slice()
}

/// Get the host transactions.
pub const fn host_txs(&self) -> &[Recovered<TxEnvelope>] {
self.host_txs.as_slice()
}

/// Get an iterator draining the transactions.
pub fn drain_txns(&mut self) -> impl Iterator<Item = Recovered<TxEnvelope>> + '_ {
self.txs.drain(..)
}

/// Get an iterator draining the host transactions.
pub fn drain_host_txns(&mut self) -> impl Iterator<Item = Recovered<TxEnvelope>> + '_ {
self.host_txs.drain(..)
}

/// Getter for block_number, a standard bundle prop.
pub const fn block_number(&self) -> u64 {
self.block_number
}

/// Getter for min_timestamp, a standard bundle prop.
pub const fn min_timestamp(&self) -> Option<u64> {
self.min_timestamp
}

/// Getter for max_timestamp, a standard bundle prop.
pub const fn max_timestamp(&self) -> Option<u64> {
self.max_timestamp
}

/// Getter for reverting_tx_hashes, a standard bundle prop.
pub const fn reverting_tx_hashes(&self) -> &[TxHash] {
self.reverting_tx_hashes.as_slice()
}

/// Getter for replacement_uuid, a standard bundle prop.
pub const fn replacement_uuid(&self) -> Option<&str> {
if let Some(ref uuid) = self.replacement_uuid {
Some(uuid.as_str())
} else {
None
}
}

/// Getter for dropping_tx_hashes, a standard bundle prop.
pub const fn dropping_tx_hashes(&self) -> &[TxHash] {
self.dropping_tx_hashes.as_slice()
}

/// Getter for refund_percent, a standard bundle prop.
pub const fn refund_percent(&self) -> Option<u8> {
self.refund_percent
}

/// Getter for refund_recipient, a standard bundle prop.
pub const fn refund_recipient(&self) -> Option<Address> {
self.refund_recipient
}

/// Getter for refund_tx_hashes, a standard bundle prop.
pub const fn refund_tx_hashes(&self) -> &[TxHash] {
self.refund_tx_hashes.as_slice()
}

/// Getter for extra_fields, a standard bundle prop.
pub const fn extra_fields(&self) -> &OtherFields {
&self.extra_fields
}

/// Checks if the bundle is valid at a given timestamp.
pub fn is_valid_at_timestamp(&self, timestamp: u64) -> bool {
let min_timestamp = self.min_timestamp.unwrap_or(0);
let max_timestamp = self.max_timestamp.unwrap_or(u64::MAX);

(min_timestamp..=max_timestamp).contains(&timestamp)
}

/// Checks if the bundle is valid at a given block number.
pub const fn is_valid_at_block_number(&self, block_number: u64) -> bool {
self.block_number == block_number
}
}
Loading