diff --git a/Cargo.toml b/Cargo.toml index d68b2831..1984b3b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "2" [workspace.package] version = "0.16.0-rc.4" edition = "2021" -rust-version = "1.85" +rust-version = "1.87" authors = ["init4"] license = "MIT OR Apache-2.0" homepage = "https://github.com/init4tech/signet-sdk" diff --git a/crates/bundle/src/send/bundle.rs b/crates/bundle/src/send/bundle.rs index c52b3a85..7424e67c 100644 --- a/crates/bundle/src/send/bundle.rs +++ b/crates/bundle/src/send/bundle.rs @@ -1,8 +1,8 @@ //! Signet bundle types. use alloy::{ - consensus::TxEnvelope, - eips::Decodable2718, - primitives::{Bytes, B256}, + consensus::{transaction::SignerRecoverable, TxEnvelope}, + eips::{eip2718::Eip2718Result, Decodable2718}, + primitives::{Address, Bytes, TxHash, B256}, rlp::Buf, rpc::types::mev::EthSendBundle, }; @@ -39,10 +39,62 @@ pub struct SignetEthBundle { } impl SignetEthBundle { + /// Creates a new [`SignetEthBundle`] from an existing [`EthSendBundle`]. + pub const fn new(bundle: EthSendBundle, host_txs: Vec) -> Self { + Self { bundle, host_txs } + } + + /// Decomposes the [`SignetEthBundle`] into its parts. + pub fn into_parts(self) -> (EthSendBundle, Vec) { + (self.bundle, self.host_txs) + } + /// Returns the transactions in this bundle. - #[allow(clippy::missing_const_for_fn)] // false positive - pub fn txs(&self) -> &[Bytes] { - &self.bundle.txs + pub const fn txs(&self) -> &[Bytes] { + self.bundle.txs.as_slice() + } + + /// Returns the host transactions in this bundle. + pub const fn host_txs(&self) -> &[Bytes] { + self.host_txs.as_slice() + } + + /// Get a mutable reference to the host transactions. + pub const fn host_txs_mut(&mut self) -> &mut Vec { + &mut self.host_txs + } + + /// Return an iterator over decoded transactions in this bundle. + pub fn decode_txs(&self) -> impl Iterator> + '_ { + self.txs().iter().map(|tx| TxEnvelope::decode_2718(&mut tx.chunk())) + } + + /// Return an iterator over decoded host transactions in this bundle. + /// + /// This may be empty if no host transactions were included. + pub fn decode_host_txs(&self) -> impl Iterator> + '_ { + self.host_txs.iter().map(|tx| TxEnvelope::decode_2718(&mut tx.chunk())) + } + + /// 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. + /// + /// Computing this may be expensive, as it requires decoding and recovering + /// the signer for each transaction. It is recommended to memoize the + /// results + pub fn signers(&self) -> impl Iterator> + '_ { + self.txs().iter().map(|tx| { + TxEnvelope::decode_2718(&mut tx.chunk()) + .ok() + .and_then(|envelope| envelope.recover_signer().ok().map(|s| (*envelope.hash(), s))) + }) + } + + /// Return an iterator over the signers of the transactions in this bundle, + /// skipping any transactions where the signer could not be recovered. + pub fn signers_lossy(&self) -> impl Iterator + '_ { + self.signers().flatten() } /// Returns the block number for this bundle. @@ -61,13 +113,15 @@ impl SignetEthBundle { } /// Returns the reverting tx hashes for this bundle. - pub fn reverting_tx_hashes(&self) -> &[B256] { + pub const fn reverting_tx_hashes(&self) -> &[B256] { self.bundle.reverting_tx_hashes.as_slice() } /// Returns the replacement uuid for this bundle. - pub fn replacement_uuid(&self) -> Option<&str> { - self.bundle.replacement_uuid.as_deref() + pub const fn replacement_uuid(&self) -> Option<&str> { + let Some(uuid) = &self.bundle.replacement_uuid else { return None }; + + Some(uuid.as_str()) } /// Checks if the bundle is valid at a given timestamp. @@ -88,9 +142,7 @@ impl SignetEthBundle { ) -> Result, BundleError> { // Decode and validate the transactions in the bundle let txs = self - .txs() - .iter() - .map(|tx| TxEnvelope::decode_2718(&mut tx.chunk())) + .decode_txs() .collect::, _>>() .map_err(|err| BundleError::TransactionDecodingError(err))?; @@ -105,15 +157,9 @@ impl SignetEthBundle { pub fn decode_and_validate_host_txs( &self, ) -> Result, BundleError> { - // Decode and validate the host transactions in the bundle - let txs = self - .host_txs - .iter() - .map(|tx| TxEnvelope::decode_2718(&mut tx.chunk())) + self.decode_host_txs() .collect::, _>>() - .map_err(|err| BundleError::TransactionDecodingError(err))?; - - Ok(txs) + .map_err(|err| BundleError::TransactionDecodingError(err)) } } @@ -123,8 +169,8 @@ mod test { #[test] fn send_bundle_ser_roundtrip() { - let bundle = SignetEthBundle { - bundle: EthSendBundle { + let bundle = SignetEthBundle::new( + EthSendBundle { txs: vec![b"tx1".into(), b"tx2".into()], block_number: 1, min_timestamp: Some(2), @@ -133,8 +179,8 @@ mod test { replacement_uuid: Some("uuid".to_owned()), ..Default::default() }, - host_txs: vec![b"host_tx1".into(), b"host_tx2".into()], - }; + vec![b"host_tx1".into(), b"host_tx2".into()], + ); let serialized = serde_json::to_string(&bundle).unwrap(); let deserialized: SignetEthBundle = serde_json::from_str(&serialized).unwrap(); @@ -144,8 +190,8 @@ mod test { #[test] fn send_bundle_ser_roundtrip_no_host_no_fills() { - let bundle = SignetEthBundle { - bundle: EthSendBundle { + let bundle = SignetEthBundle::new( + EthSendBundle { txs: vec![b"tx1".into(), b"tx2".into()], block_number: 1, min_timestamp: Some(2), @@ -154,8 +200,8 @@ mod test { replacement_uuid: Some("uuid".to_owned()), ..Default::default() }, - host_txs: vec![], - }; + vec![], + ); let serialized = serde_json::to_string(&bundle).unwrap(); let deserialized: SignetEthBundle = serde_json::from_str(&serialized).unwrap(); diff --git a/crates/evm/src/orders/framed.rs b/crates/evm/src/orders/framed.rs index dfea1baa..c98967b4 100644 --- a/crates/evm/src/orders/framed.rs +++ b/crates/evm/src/orders/framed.rs @@ -33,12 +33,12 @@ impl Framed { /// Returns the number of events found, including those that may yet be /// reverted. - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.events.len() } /// Returns `true` if the run has no events. - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.events.is_empty() } @@ -73,7 +73,7 @@ impl Framed { } /// True if all frames have been exited. - pub fn is_complete(&self) -> bool { + pub const fn is_complete(&self) -> bool { self.frame_boundaries.is_empty() } } diff --git a/crates/evm/src/outcome.rs b/crates/evm/src/outcome.rs index 47380138..ca1e3708 100644 --- a/crates/evm/src/outcome.rs +++ b/crates/evm/src/outcome.rs @@ -40,12 +40,12 @@ impl ExecutionOutcome { } /// Number of blocks in the execution outcome. - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.receipts.len() } /// Check if the execution outcome is empty. - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.receipts.is_empty() } @@ -55,7 +55,7 @@ impl ExecutionOutcome { } /// Return last block of the execution outcome - pub fn last_block(&self) -> BlockNumber { + pub const fn last_block(&self) -> BlockNumber { (self.first_block + self.len() as u64).saturating_sub(1) } diff --git a/crates/sim/src/built.rs b/crates/sim/src/built.rs index c26972d7..243f4dfa 100644 --- a/crates/sim/src/built.rs +++ b/crates/sim/src/built.rs @@ -78,12 +78,12 @@ impl BuiltBlock { } /// Get the number of transactions in the block. - pub fn tx_count(&self) -> usize { + pub const fn tx_count(&self) -> usize { self.transactions.len() } /// Check if the block is empty. - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.transactions.is_empty() } diff --git a/crates/tx-cache/src/types.rs b/crates/tx-cache/src/types.rs index 44f43b01..7d39322d 100644 --- a/crates/tx-cache/src/types.rs +++ b/crates/tx-cache/src/types.rs @@ -238,7 +238,7 @@ impl TxCacheBundlesResponse { } /// Check if the response is empty (has no bundles). - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.bundles.is_empty() } } @@ -315,7 +315,7 @@ impl TxCacheTransactionsResponse { } /// Check if the response is empty (has no transactions). - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.transactions.is_empty() } } @@ -438,7 +438,7 @@ impl TxCacheSendOrderResponse { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct TxKey { - /// The transaction hash + /// The transaction hash pub txn_hash: B256, /// The transaction score pub score: u64, diff --git a/crates/zenith/src/bindings.rs b/crates/zenith/src/bindings.rs index 95da925a..2d87d135 100644 --- a/crates/zenith/src/bindings.rs +++ b/crates/zenith/src/bindings.rs @@ -329,7 +329,7 @@ mod orders { } impl Orders::Filled { - pub fn outputs(&self) -> &[IOrders::Output] { + pub const fn outputs(&self) -> &[IOrders::Output] { self.outputs.as_slice() } }