From d030974e1d1cbe891debf984af0f5e1150efba36 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 16:10:19 +0100 Subject: [PATCH 01/65] refactor: remove `TempProvider` --- crates/providers/Cargo.toml | 1 + crates/providers/src/chain.rs | 13 +- crates/providers/src/lib.rs | 12 +- crates/providers/src/new.rs | 564 +++++++++++++++++++++++- crates/providers/src/tmp.rs | 790 ---------------------------------- 5 files changed, 562 insertions(+), 818 deletions(-) delete mode 100644 crates/providers/src/tmp.rs diff --git a/crates/providers/Cargo.toml b/crates/providers/Cargo.toml index b97c0958f9b..a6b7efe2be2 100644 --- a/crates/providers/Cargo.toml +++ b/crates/providers/Cargo.toml @@ -32,6 +32,7 @@ thiserror.workspace = true tokio = { workspace = true, features = ["sync", "macros"] } tracing.workspace = true + [dev-dependencies] alloy-consensus.workspace = true alloy-node-bindings.workspace = true diff --git a/crates/providers/src/chain.rs b/crates/providers/src/chain.rs index a233b2a9894..427260ec359 100644 --- a/crates/providers/src/chain.rs +++ b/crates/providers/src/chain.rs @@ -92,17 +92,26 @@ impl ChainStreamPoller { let mut retries = MAX_RETRIES; for number in self.next_yield..=block_number { debug!(number, "fetching block"); - let block = match provider.get_block_by_number(number, false).await { - Ok(block) => block, + let block = match provider.get_block_by_number(number.into(), false).await { + Ok(Some(block)) => block, Err(RpcError::Transport(err)) if retries > 0 && err.recoverable() => { debug!(number, %err, "failed to fetch block, retrying"); retries -= 1; continue; } + Ok(None) if retries > 0 => { + debug!(number, "failed to fetch block (doesn't exist), retrying"); + retries -= 1; + continue; + } Err(err) => { error!(number, %err, "failed to fetch block"); break 'task; } + Ok(None) => { + error!(number, "failed to fetch block (doesn't exist)"); + break 'task; + } }; self.known_blocks.put(number, block); } diff --git a/crates/providers/src/lib.rs b/crates/providers/src/lib.rs index dab33965640..8b376392a14 100644 --- a/crates/providers/src/lib.rs +++ b/crates/providers/src/lib.rs @@ -16,10 +16,17 @@ #![deny(unused_must_use, rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +use alloy_transport_http::Http; +use reqwest::Client as ReqwestClient; + +/// Type alias for a [`RootProvider`] using the [`Http`] transport. +pub type HttpProvider = RootProvider>; + #[macro_use] extern crate tracing; mod builder; + pub use builder::{ProviderBuilder, ProviderLayer, Stack}; mod chain; @@ -28,9 +35,6 @@ mod heart; pub use heart::{PendingTransaction, WatchConfig}; pub mod new; -pub use new::{Provider, ProviderRef, RootProvider, WeakProvider}; +pub use new::{Provider, ProviderRef, RawProvider, RootProvider, WeakProvider}; pub mod utils; - -// TODO: remove -pub mod tmp; diff --git a/crates/providers/src/new.rs b/crates/providers/src/new.rs index 71b37233e6d..61da782fef5 100644 --- a/crates/providers/src/new.rs +++ b/crates/providers/src/new.rs @@ -1,12 +1,24 @@ use crate::{ chain::ChainStreamPoller, heart::{Heartbeat, HeartbeatHandle, PendingTransaction, WatchConfig}, + utils, + utils::EstimatorFunction, +}; +use alloy_network::Network; +use alloy_primitives::{ + hex, Address, BlockHash, BlockNumber, Bytes, StorageKey, StorageValue, TxHash, B256, U256, U64, }; -use alloy_network::{Network, Transaction}; -use alloy_primitives::{hex, Address, BlockNumber, B256, U256, U64}; use alloy_rpc_client::{ClientRef, RpcClient, WeakClient}; -use alloy_rpc_types::Block; +use alloy_rpc_trace_types::{ + geth::{GethDebugTracingOptions, GethTrace}, + parity::LocalizedTransactionTrace, +}; +use alloy_rpc_types::{ + state::StateOverride, AccessListWithGasUsed, Block, BlockId, BlockNumberOrTag, + EIP1186AccountProofResponse, FeeHistory, Filter, Log, SyncStatus, +}; use alloy_transport::{BoxTransport, Transport, TransportErrorKind, TransportResult}; +use serde::{de::DeserializeOwned, Serialize}; use std::{ marker::PhantomData, sync::{Arc, OnceLock, Weak}, @@ -70,6 +82,8 @@ impl RootProviderInner { } } +// todo: adjust docs +// todo: reorder /// Provider is parameterized with a network and a transport. The default /// transport is type-erased, but you can do `Provider`. #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] @@ -84,21 +98,18 @@ pub trait Provider: Send + Sync async fn new_pending_transaction(&self, tx_hash: B256) -> TransportResult; - async fn estimate_gas(&self, tx: &N::TransactionRequest) -> TransportResult { - self.client().prepare("eth_estimateGas", (tx,)).await - } - /// Get the last block number available. async fn get_block_number(&self) -> TransportResult { self.client().prepare("eth_blockNumber", ()).await.map(|num: U64| num.to::()) } - /// Get the transaction count for an address. Used for finding the - /// appropriate nonce. - /// - /// TODO: block number/hash/tag - async fn get_transaction_count(&self, address: Address) -> TransportResult { - self.client().prepare("eth_getTransactionCount", (address, "latest")).await + /// Gets the transaction count of the corresponding address. + async fn get_transaction_count( + &self, + address: Address, + tag: Option, + ) -> TransportResult { + self.client().prepare("eth_getTransactionCount", (address, tag.unwrap_or_default())).await } /// Get a block by its number. @@ -106,19 +117,21 @@ pub trait Provider: Send + Sync /// TODO: Network associate async fn get_block_by_number( &self, - number: BlockNumber, + number: BlockNumberOrTag, hydrate: bool, - ) -> TransportResult { + ) -> TransportResult> { self.client().prepare("eth_getBlockByNumber", (number, hydrate)).await } - /// Populate the gas limit for a transaction. - async fn populate_gas(&self, tx: &mut N::TransactionRequest) -> TransportResult<()> { - let gas = self.estimate_gas(&*tx).await?; - if let Ok(gas) = gas.try_into() { - tx.set_gas_limit(gas); - } - Ok(()) + async fn populate_gas( + &self, + tx: &mut N::TransactionRequest, + block: Option, + ) -> TransportResult<()> { + let gas = self.estimate_gas(&*tx, block).await; + + todo!() + // gas.map(|gas| tx.set_gas_limit(gas.try_into().unwrap())) } /// Broadcasts a transaction, returning a [`PendingTransaction`] that resolves once the @@ -138,6 +151,278 @@ pub trait Provider: Send + Sync let tx_hash = self.client().prepare("eth_sendRawTransaction", (rlp_hex,)).await?; self.new_pending_transaction(tx_hash).await } + + /// Gets the balance of the account at the specified tag, which defaults to latest. + async fn get_balance(&self, address: Address, tag: Option) -> TransportResult { + self.client() + .prepare( + "eth_getBalance", + (address, tag.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest))), + ) + .await + } + + /// Gets a block by either its hash, tag, or number, with full transactions or only hashes. + async fn get_block(&self, id: BlockId, full: bool) -> TransportResult> { + match id { + BlockId::Hash(hash) => self.get_block_by_hash(hash.into(), full).await, + BlockId::Number(number) => self.get_block_by_number(number, full).await, + } + } + + /// Gets a block by its [BlockHash], with full transactions or only hashes. + async fn get_block_by_hash( + &self, + hash: BlockHash, + full: bool, + ) -> TransportResult> { + self.client().prepare("eth_getBlockByHash", (hash, full)).await + } + + /// Gets the client version of the chain client(). + async fn get_client_version(&self) -> TransportResult { + self.client().prepare("web3_clientVersion", ()).await + } + + /// Gets the chain ID. + async fn get_chain_id(&self) -> TransportResult { + self.client().prepare("eth_chainId", ()).await + } + + /// Gets the network ID. Same as `eth_chainId`. + async fn get_net_version(&self) -> TransportResult { + self.client().prepare("net_version", ()).await + } + + /// Gets the specified storage value from [Address]. + async fn get_storage_at( + &self, + address: Address, + key: U256, + tag: Option, + ) -> TransportResult { + self.client().prepare("eth_getStorageAt", (address, key, tag.unwrap_or_default())).await + } + + /// Gets the bytecode located at the corresponding [Address]. + async fn get_code_at(&self, address: Address, tag: BlockId) -> TransportResult { + self.client().prepare("eth_getCode", (address, tag)).await + } + + /// Gets a [Transaction] by its [TxHash]. + async fn get_transaction_by_hash( + &self, + hash: TxHash, + ) -> TransportResult { + self.client().prepare("eth_getTransactionByHash", (hash,)).await + } + + /// Retrieves a [`Vec`] with the given [Filter]. + async fn get_logs(&self, filter: Filter) -> TransportResult> { + self.client().prepare("eth_getLogs", (filter,)).await + } + + /// Gets the accounts in the remote node. This is usually empty unless you're using a local + /// node. + async fn get_accounts(&self) -> TransportResult> { + self.client().prepare("eth_accounts", ()).await + } + + /// Gets the current gas price. + async fn get_gas_price(&self) -> TransportResult { + self.client().prepare("eth_gasPrice", ()).await + } + + /// Gets a [TransactionReceipt] if it exists, by its [TxHash]. + async fn get_transaction_receipt( + &self, + hash: TxHash, + ) -> TransportResult> { + self.client().prepare("eth_getTransactionReceipt", (hash,)).await + } + + /// Returns a collection of historical gas information [FeeHistory] which + /// can be used to calculate the EIP1559 fields `maxFeePerGas` and `maxPriorityFeePerGas`. + async fn get_fee_history( + &self, + block_count: U256, + last_block: BlockNumberOrTag, + reward_percentiles: &[f64], + ) -> TransportResult { + self.client().prepare("eth_feeHistory", (block_count, last_block, reward_percentiles)).await + } + + /// Gets the selected block [BlockNumberOrTag] receipts. + async fn get_block_receipts( + &self, + block: BlockNumberOrTag, + ) -> TransportResult>> { + self.client().prepare("eth_getBlockReceipts", (block,)).await + } + + /// Gets an uncle block through the tag [BlockId] and index [U64]. + async fn get_uncle(&self, tag: BlockId, idx: U64) -> TransportResult> { + match tag { + BlockId::Hash(hash) => { + self.client().prepare("eth_getUncleByBlockHashAndIndex", (hash, idx)).await + } + BlockId::Number(number) => { + self.client().prepare("eth_getUncleByBlockNumberAndIndex", (number, idx)).await + } + } + } + + /// Gets syncing info. + async fn syncing(&self) -> TransportResult { + self.client().prepare("eth_syncing", ()).await + } + + /// Execute a smart contract call with [TransactionRequest] without publishing a transaction. + async fn call( + &self, + tx: &N::TransactionRequest, + block: Option, + ) -> TransportResult { + self.client().prepare("eth_call", (tx, block.unwrap_or_default())).await + } + + /// Execute a smart contract call with [TransactionRequest] and state overrides, without + /// publishing a transaction. + /// + /// # Note + /// + /// Not all client implementations support state overrides. + async fn call_with_overrides( + &self, + tx: &N::TransactionRequest, + block: Option, + state: StateOverride, + ) -> TransportResult { + self.client().prepare("eth_call", (tx, block.unwrap_or_default(), state)).await + } + + /// Estimate the gas needed for a transaction. + async fn estimate_gas( + &self, + tx: &N::TransactionRequest, + block: Option, + ) -> TransportResult { + if let Some(block_id) = block { + self.client().prepare("eth_estimateGas", (tx, block_id)).await + } else { + self.client().prepare("eth_estimateGas", (tx,)).await + } + } + + /// Estimates the EIP1559 `maxFeePerGas` and `maxPriorityFeePerGas` fields. + /// Receives an optional [EstimatorFunction] that can be used to modify + /// how to estimate these fees. + async fn estimate_eip1559_fees( + &self, + estimator: Option, + ) -> TransportResult<(U256, U256)> { + let base_fee_per_gas = match self.get_block_by_number(BlockNumberOrTag::Latest, false).await + { + Ok(Some(block)) => match block.header.base_fee_per_gas { + Some(base_fee_per_gas) => base_fee_per_gas, + None => return Err(TransportErrorKind::custom_str("EIP-1559 not activated")), + }, + + Ok(None) => return Err(TransportErrorKind::custom_str("Latest block not found")), + + Err(err) => return Err(err), + }; + + let fee_history = match self + .get_fee_history( + U256::from(utils::EIP1559_FEE_ESTIMATION_PAST_BLOCKS), + BlockNumberOrTag::Latest, + &[utils::EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE], + ) + .await + { + Ok(fee_history) => fee_history, + Err(err) => return Err(err), + }; + + // use the provided fee estimator function, or fallback to the default implementation. + let (max_fee_per_gas, max_priority_fee_per_gas) = if let Some(es) = estimator { + es(base_fee_per_gas, fee_history.reward.unwrap_or_default()) + } else { + utils::eip1559_default_estimator( + base_fee_per_gas, + fee_history.reward.unwrap_or_default(), + ) + }; + + Ok((max_fee_per_gas, max_priority_fee_per_gas)) + } + + // todo: move to extension trait + #[cfg(feature = "anvil")] + async fn set_code(&self, address: Address, code: &'static str) -> TransportResult<()> { + self.client().prepare("anvil_setCode", (address, code)).await + } + + async fn get_proof( + &self, + address: Address, + keys: Vec, + block: Option, + ) -> TransportResult { + self.client().prepare("eth_getProof", (address, keys, block.unwrap_or_default())).await + } + + async fn create_access_list( + &self, + request: &N::TransactionRequest, + block: Option, + ) -> TransportResult { + self.client().prepare("eth_createAccessList", (request, block.unwrap_or_default())).await + } + + // todo: move to extension trait + /// Parity trace transaction. + async fn trace_transaction( + &self, + hash: TxHash, + ) -> TransportResult> { + self.client().prepare("trace_transaction", (hash,)).await + } + + // todo: move to extension trait + async fn debug_trace_transaction( + &self, + hash: TxHash, + trace_options: GethDebugTracingOptions, + ) -> TransportResult { + self.client().prepare("debug_traceTransaction", (hash, trace_options)).await + } + + // todo: move to extension trait + async fn trace_block( + &self, + block: BlockNumberOrTag, + ) -> TransportResult> { + self.client().prepare("trace_block", (block,)).await + } +} + +/// Extension trait for raw RPC requests. +#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] +#[auto_impl::auto_impl(&, &mut, Rc, Arc, Box)] +pub trait RawProvider: Provider { + /// Sends a raw JSON-RPC request. + async fn raw_request(&self, method: &'static str, params: P) -> TransportResult + where + P: Serialize + Send + Sync + Clone, + R: Serialize + DeserializeOwned + Send + Sync + Unpin + 'static, + Self: Sync, + { + let res: R = self.client().prepare(method, ¶ms).await?; + Ok(res) + } } #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] @@ -360,4 +645,239 @@ mod tests { let hash2 = pending_tx.await.expect("failed to await pending tx"); assert_eq!(hash1, hash2); } + + /* + #[tokio::test] + async fn gets_block_number() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let num = provider.get_block_number().await.unwrap(); + assert_eq!(0, num) + } + + #[tokio::test] + async fn gets_block_number_with_raw_req() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let num: U64 = provider.raw_request("eth_blockNumber", ()).await.unwrap(); + assert_eq!(0, num.to::()) + } + + #[tokio::test] + async fn gets_transaction_count() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let count = provider + .get_transaction_count( + address!("328375e18E7db8F1CA9d9bA8bF3E9C94ee34136A"), + Some(BlockNumberOrTag::Latest.into()), + ) + .await + .unwrap(); + assert_eq!(count, U256::from(0)); + } + + #[tokio::test] + async fn gets_block_by_hash() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let num = 0; + let tag: BlockNumberOrTag = num.into(); + let block = provider.get_block_by_number(tag, true).await.unwrap().unwrap(); + let hash = block.header.hash.unwrap(); + let block = provider.get_block_by_hash(hash, true).await.unwrap().unwrap(); + assert_eq!(block.header.hash.unwrap(), hash); + } + + #[tokio::test] + async fn gets_block_by_hash_with_raw_req() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let num = 0; + let tag: BlockNumberOrTag = num.into(); + let block = provider.get_block_by_number(tag, true).await.unwrap().unwrap(); + let hash = block.header.hash.unwrap(); + let block: Block = provider + .raw_request::<(alloy_primitives::FixedBytes<32>, bool), Block>( + "eth_getBlockByHash", + (hash, true), + ) + .await + .unwrap(); + assert_eq!(block.header.hash.unwrap(), hash); + } + + #[tokio::test] + async fn gets_block_by_number_full() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let num = 0; + let tag: BlockNumberOrTag = num.into(); + let block = provider.get_block_by_number(tag, true).await.unwrap().unwrap(); + assert_eq!(block.header.number.unwrap(), U256::from(num)); + } + + #[tokio::test] + async fn gets_block_by_number() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let num = 0; + let tag: BlockNumberOrTag = num.into(); + let block = provider.get_block_by_number(tag, true).await.unwrap().unwrap(); + assert_eq!(block.header.number.unwrap(), U256::from(num)); + } + + #[tokio::test] + async fn gets_client_version() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let version = provider.get_client_version().await.unwrap(); + assert!(version.contains("anvil")); + } + + #[tokio::test] + async fn gets_chain_id() { + let chain_id: u64 = 13371337; + let anvil = Anvil::new().args(["--chain-id", chain_id.to_string().as_str()]).spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let chain_id = provider.get_chain_id().await.unwrap(); + assert_eq!(chain_id, U64::from(chain_id)); + } + + #[tokio::test] + async fn gets_network_id() { + let chain_id: u64 = 13371337; + let anvil = Anvil::new().args(["--chain-id", chain_id.to_string().as_str()]).spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let chain_id = provider.get_net_version().await.unwrap(); + assert_eq!(chain_id, U64::from(chain_id)); + } + + #[tokio::test] + #[cfg(feature = "anvil")] + async fn gets_code_at() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + // Set the code + let addr = alloy_primitives::Address::with_last_byte(16); + provider.set_code(addr, "0xbeef").await.unwrap(); + let _code = provider + .get_code_at( + addr, + crate::provider::BlockId::Number(alloy_rpc_types::BlockNumberOrTag::Latest), + ) + .await + .unwrap(); + } + + #[tokio::test] + async fn gets_storage_at() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let addr = alloy_primitives::Address::with_last_byte(16); + let storage = provider.get_storage_at(addr, U256::ZERO, None).await.unwrap(); + assert_eq!(storage, U256::ZERO); + } + + #[tokio::test] + #[ignore] + async fn gets_transaction_by_hash() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let tx = provider + .get_transaction_by_hash(b256!( + "5c03fab9114ceb98994b43892ade87ddfd9ae7e8f293935c3bd29d435dc9fd95" + )) + .await + .unwrap(); + assert_eq!( + tx.block_hash.unwrap(), + b256!("b20e6f35d4b46b3c4cd72152faec7143da851a0dc281d390bdd50f58bfbdb5d3") + ); + assert_eq!(tx.block_number.unwrap(), U256::from(4571819)); + } + + #[tokio::test] + #[ignore] + async fn gets_logs() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let filter = Filter::new() + .at_block_hash(b256!( + "b20e6f35d4b46b3c4cd72152faec7143da851a0dc281d390bdd50f58bfbdb5d3" + )) + .event_signature(b256!( + "e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c" + )); + let logs = provider.get_logs(filter).await.unwrap(); + assert_eq!(logs.len(), 1); + } + + #[tokio::test] + #[ignore] + async fn gets_tx_receipt() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let receipt = provider + .get_transaction_receipt(b256!( + "5c03fab9114ceb98994b43892ade87ddfd9ae7e8f293935c3bd29d435dc9fd95" + )) + .await + .unwrap(); + assert!(receipt.is_some()); + let receipt = receipt.unwrap(); + assert_eq!( + receipt.transaction_hash.unwrap(), + b256!("5c03fab9114ceb98994b43892ade87ddfd9ae7e8f293935c3bd29d435dc9fd95") + ); + } + + #[tokio::test] + async fn gets_fee_history() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let block_number = provider.get_block_number().await.unwrap(); + let fee_history = provider + .get_fee_history( + U256::from(utils::EIP1559_FEE_ESTIMATION_PAST_BLOCKS), + BlockNumberOrTag::Number(block_number), + &[utils::EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE], + ) + .await + .unwrap(); + assert_eq!(fee_history.oldest_block, U256::ZERO); + } + + #[tokio::test] + #[ignore] // Anvil has yet to implement the `eth_getBlockReceipts` method. + async fn gets_block_receipts() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let receipts = provider.get_block_receipts(BlockNumberOrTag::Latest).await.unwrap(); + assert!(receipts.is_some()); + } + + #[tokio::test] + async fn gets_block_traces() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let traces = provider.trace_block(BlockNumberOrTag::Latest).await.unwrap(); + assert_eq!(traces.len(), 0); + } + + #[tokio::test] + async fn sends_raw_transaction() { + let anvil = Anvil::new().spawn(); + let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let tx_hash = provider + .send_raw_transaction( + // Transfer 1 ETH from default EOA address to the Genesis address. + bytes!("f865808477359400825208940000000000000000000000000000000000000000018082f4f5a00505e227c1c636c76fac55795db1a40a4d24840d81b40d2fe0cc85767f6bd202a01e91b437099a8a90234ac5af3cb7ca4fb1432e133f75f9a91678eaf5f487c74b") + ) + .await.unwrap(); + assert_eq!( + tx_hash.to_string(), + "0x9dae5cf33694a02e8a7d5de3fe31e9d05ca0ba6e9180efac4ab20a06c9e598a3" + ); + }*/ } diff --git a/crates/providers/src/tmp.rs b/crates/providers/src/tmp.rs deleted file mode 100644 index 25c054e52dd..00000000000 --- a/crates/providers/src/tmp.rs +++ /dev/null @@ -1,790 +0,0 @@ -//! Alloy main Provider abstraction. - -use crate::utils::{self, EstimatorFunction}; -use alloy_primitives::{Address, BlockHash, Bytes, StorageKey, StorageValue, TxHash, U256, U64}; -use alloy_rpc_client::{ClientBuilder, RpcClient}; -use alloy_rpc_trace_types::{ - geth::{GethDebugTracingOptions, GethTrace}, - parity::LocalizedTransactionTrace, -}; -use alloy_rpc_types::{ - request::TransactionRequest, state::StateOverride, AccessListWithGasUsed, Block, BlockId, - BlockNumberOrTag, EIP1186AccountProofResponse, FeeHistory, Filter, Log, SyncStatus, - Transaction, TransactionReceipt, -}; -use alloy_transport::{BoxTransport, Transport, TransportErrorKind, TransportResult}; -use alloy_transport_http::Http; -use auto_impl::auto_impl; -use reqwest::Client; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use thiserror::Error; - -#[derive(Debug, Error, Serialize, Deserialize)] -pub enum ClientError { - #[error("Could not parse URL")] - ParseError, - #[error("Unsupported Tag")] - UnsupportedBlockIdError, -} - -/// Type alias for a [`Provider`] using the [`Http`] transport. -pub type HttpProvider = Provider>; - -/// An abstract provider for interacting with the [Ethereum JSON RPC -/// API](https://github.com/ethereum/wiki/wiki/JSON-RPC). Must be instantiated -/// with a transport which implements the [Transport] trait. -#[derive(Debug, Clone)] -pub struct Provider { - inner: RpcClient, - from: Option
, -} - -/// Temporary Provider trait to be used until the new Provider trait with -/// the Network abstraction is stable. -/// Once the new Provider trait is stable, this trait will be removed. -#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] -#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] -#[auto_impl(&, &mut, Rc, Arc, Box)] -pub trait TempProvider: Send + Sync { - /// Gets the transaction count of the corresponding address. - async fn get_transaction_count( - &self, - address: Address, - tag: Option, - ) -> TransportResult; - - /// Gets the last block number available. - async fn get_block_number(&self) -> TransportResult; - - /// Gets the balance of the account at the specified tag, which defaults to latest. - async fn get_balance(&self, address: Address, tag: Option) -> TransportResult; - - /// Gets a block by either its hash, tag, or number, with full transactions or only hashes. - async fn get_block(&self, id: BlockId, full: bool) -> TransportResult> { - match id { - BlockId::Hash(hash) => self.get_block_by_hash(hash.into(), full).await, - BlockId::Number(number) => self.get_block_by_number(number, full).await, - } - } - - /// Gets a block by its [BlockHash], with full transactions or only hashes. - async fn get_block_by_hash( - &self, - hash: BlockHash, - full: bool, - ) -> TransportResult>; - - /// Gets a block by [BlockNumberOrTag], with full transactions or only hashes. - async fn get_block_by_number( - &self, - number: BlockNumberOrTag, - full: bool, - ) -> TransportResult>; - - /// Gets the client version of the chain client. - async fn get_client_version(&self) -> TransportResult; - - /// Gets the chain ID. - async fn get_chain_id(&self) -> TransportResult; - - /// Gets the network ID. Same as `eth_chainId`. - async fn get_net_version(&self) -> TransportResult; - - /// Gets the specified storage value from [Address]. - async fn get_storage_at( - &self, - address: Address, - key: U256, - tag: Option, - ) -> TransportResult; - - /// Gets the bytecode located at the corresponding [Address]. - async fn get_code_at(&self, address: Address, tag: Option) -> TransportResult; - - /// Gets a [Transaction] by its [TxHash]. - async fn get_transaction_by_hash(&self, hash: TxHash) -> TransportResult; - - /// Retrieves a [`Vec`] with the given [Filter]. - async fn get_logs(&self, filter: Filter) -> TransportResult>; - - /// Gets the accounts in the remote node. This is usually empty unless you're using a local - /// node. - async fn get_accounts(&self) -> TransportResult>; - - /// Gets the current gas price. - async fn get_gas_price(&self) -> TransportResult; - - /// Gets a [TransactionReceipt] if it exists, by its [TxHash]. - async fn get_transaction_receipt( - &self, - hash: TxHash, - ) -> TransportResult>; - - /// Returns a collection of historical gas information [FeeHistory] which - /// can be used to calculate the EIP1559 fields `maxFeePerGas` and `maxPriorityFeePerGas`. - async fn get_fee_history( - &self, - block_count: U256, - last_block: BlockNumberOrTag, - reward_percentiles: &[f64], - ) -> TransportResult; - - /// Gets the selected block [BlockNumberOrTag] receipts. - async fn get_block_receipts( - &self, - block: BlockNumberOrTag, - ) -> TransportResult>>; - - /// Gets an uncle block through the tag [BlockId] and index [U64]. - async fn get_uncle(&self, tag: BlockId, idx: U64) -> TransportResult>; - - /// Gets syncing info. - async fn syncing(&self) -> TransportResult; - - /// Execute a smart contract call with [TransactionRequest] without publishing a transaction. - async fn call(&self, tx: TransactionRequest, block: Option) -> TransportResult; - - /// Execute a smart contract call with [TransactionRequest] and state overrides, without - /// publishing a transaction. - /// - /// # Note - /// - /// Not all client implementations support state overrides. - async fn call_with_overrides( - &self, - tx: TransactionRequest, - block: Option, - state: StateOverride, - ) -> TransportResult; - - /// Estimate the gas needed for a transaction. - async fn estimate_gas( - &self, - tx: TransactionRequest, - block: Option, - ) -> TransportResult; - - /// Sends an already-signed transaction. - async fn send_raw_transaction(&self, tx: Bytes) -> TransportResult; - - /// Estimates the EIP1559 `maxFeePerGas` and `maxPriorityFeePerGas` fields. - /// Receives an optional [EstimatorFunction] that can be used to modify - /// how to estimate these fees. - async fn estimate_eip1559_fees( - &self, - estimator: Option, - ) -> TransportResult<(U256, U256)>; - - #[cfg(feature = "anvil")] - async fn set_code(&self, address: Address, code: &'static str) -> TransportResult<()>; - - async fn get_proof( - &self, - address: Address, - keys: Vec, - block: Option, - ) -> TransportResult; - - async fn create_access_list( - &self, - request: TransactionRequest, - block: Option, - ) -> TransportResult; - - /// Parity trace transaction. - async fn trace_transaction( - &self, - hash: TxHash, - ) -> TransportResult>; - - async fn debug_trace_transaction( - &self, - hash: TxHash, - trace_options: GethDebugTracingOptions, - ) -> TransportResult; - - async fn trace_block( - &self, - block: BlockNumberOrTag, - ) -> TransportResult>; - - async fn raw_request(&self, method: &'static str, params: P) -> TransportResult - where - P: Serialize + Send + Sync + Clone, - R: Serialize + DeserializeOwned + Send + Sync + Unpin + 'static, - Self: Sync; -} - -impl Provider { - pub fn new(transport: T) -> Self { - Self { - // todo(onbjerg): do we just default to false - inner: RpcClient::new(transport, false), - from: None, - } - } - - pub fn new_with_client(client: RpcClient) -> Self { - Self { inner: client, from: None } - } - - pub fn with_sender(mut self, from: Address) -> Self { - self.from = Some(from); - self - } - - pub fn inner(&self) -> &RpcClient { - &self.inner - } -} - -// todo: validate usage of BlockId vs BlockNumberOrTag vs Option etc. -// Simple JSON-RPC bindings. -// In the future, this will be replaced by a Provider trait, -// but as the interface is not stable yet, we define the bindings ourselves -// until we can use the trait and the client abstraction that will use it. -#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] -#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] -impl TempProvider for Provider { - /// Gets the transaction count of the corresponding address. - async fn get_transaction_count( - &self, - address: Address, - tag: Option, - ) -> TransportResult { - self.inner.prepare("eth_getTransactionCount", (address, tag.unwrap_or_default())).await - } - - /// Gets the last block number available. - /// Gets the last block number available. - async fn get_block_number(&self) -> TransportResult { - self.inner.prepare("eth_blockNumber", ()).await.map(|num: U64| num.to::()) - } - - /// Gets the balance of the account at the specified tag, which defaults to latest. - async fn get_balance(&self, address: Address, tag: Option) -> TransportResult { - self.inner - .prepare( - "eth_getBalance", - (address, tag.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest))), - ) - .await - } - - /// Gets a block by its [BlockHash], with full transactions or only hashes. - async fn get_block_by_hash( - &self, - hash: BlockHash, - full: bool, - ) -> TransportResult> { - self.inner.prepare("eth_getBlockByHash", (hash, full)).await - } - - /// Gets a block by [BlockNumberOrTag], with full transactions or only hashes. - async fn get_block_by_number( - &self, - number: BlockNumberOrTag, - full: bool, - ) -> TransportResult> { - self.inner.prepare("eth_getBlockByNumber", (number, full)).await - } - - /// Gets the client version of the chain client. - async fn get_client_version(&self) -> TransportResult { - self.inner.prepare("web3_clientVersion", ()).await - } - - /// Gets the chain ID. - async fn get_chain_id(&self) -> TransportResult { - self.inner.prepare("eth_chainId", ()).await - } - - async fn get_net_version(&self) -> TransportResult { - self.inner.prepare("net_version", ()).await - } - - /// Gets the specified storage value from [Address]. - async fn get_storage_at( - &self, - address: Address, - key: U256, - tag: Option, - ) -> TransportResult { - self.inner.prepare("eth_getStorageAt", (address, key, tag.unwrap_or_default())).await - } - - /// Gets the bytecode located at the corresponding [Address]. - async fn get_code_at(&self, address: Address, tag: Option) -> TransportResult { - self.inner.prepare("eth_getCode", (address, tag.unwrap_or_default())).await - } - - /// Gets a [Transaction] by its [TxHash]. - async fn get_transaction_by_hash(&self, hash: TxHash) -> TransportResult { - self.inner.prepare("eth_getTransactionByHash", (hash,)).await - } - - /// Retrieves a [`Vec`] with the given [Filter]. - async fn get_logs(&self, filter: Filter) -> TransportResult> { - self.inner.prepare("eth_getLogs", (filter,)).await - } - - /// Gets the accounts in the remote node. This is usually empty unless you're using a local - /// node. - async fn get_accounts(&self) -> TransportResult> { - self.inner.prepare("eth_accounts", ()).await - } - - /// Gets the current gas price. - async fn get_gas_price(&self) -> TransportResult { - self.inner.prepare("eth_gasPrice", ()).await - } - - /// Gets a [TransactionReceipt] if it exists, by its [TxHash]. - async fn get_transaction_receipt( - &self, - hash: TxHash, - ) -> TransportResult> { - self.inner.prepare("eth_getTransactionReceipt", (hash,)).await - } - - /// Returns a collection of historical gas information [FeeHistory] which - /// can be used to calculate the EIP1559 fields `maxFeePerGas` and `maxPriorityFeePerGas`. - async fn get_fee_history( - &self, - block_count: U256, - last_block: BlockNumberOrTag, - reward_percentiles: &[f64], - ) -> TransportResult { - self.inner.prepare("eth_feeHistory", (block_count, last_block, reward_percentiles)).await - } - - /// Gets the selected block [BlockNumberOrTag] receipts. - async fn get_block_receipts( - &self, - block: BlockNumberOrTag, - ) -> TransportResult>> { - self.inner.prepare("eth_getBlockReceipts", (block,)).await - } - - /// Gets an uncle block through the tag [BlockId] and index [U64]. - async fn get_uncle(&self, tag: BlockId, idx: U64) -> TransportResult> { - match tag { - BlockId::Hash(hash) => { - self.inner.prepare("eth_getUncleByBlockHashAndIndex", (hash, idx)).await - } - BlockId::Number(number) => { - self.inner.prepare("eth_getUncleByBlockNumberAndIndex", (number, idx)).await - } - } - } - - /// Gets syncing info. - async fn syncing(&self) -> TransportResult { - self.inner.prepare("eth_syncing", ()).await - } - - /// Execute a smart contract call with [TransactionRequest] without publishing a transaction. - async fn call(&self, tx: TransactionRequest, block: Option) -> TransportResult { - self.inner.prepare("eth_call", (tx, block.unwrap_or_default())).await - } - - /// Execute a smart contract call with [TransactionRequest] and state overrides, without - /// publishing a transaction. - /// - /// # Note - /// - /// Not all client implementations support state overrides. - async fn call_with_overrides( - &self, - tx: TransactionRequest, - block: Option, - state: StateOverride, - ) -> TransportResult { - self.inner.prepare("eth_call", (tx, block.unwrap_or_default(), state)).await - } - - /// Estimate the gas needed for a transaction. - async fn estimate_gas( - &self, - tx: TransactionRequest, - block: Option, - ) -> TransportResult { - if let Some(block_id) = block { - self.inner.prepare("eth_estimateGas", (tx, block_id)).await - } else { - self.inner.prepare("eth_estimateGas", (tx,)).await - } - } - - /// Sends an already-signed transaction. - async fn send_raw_transaction(&self, tx: Bytes) -> TransportResult { - self.inner.prepare("eth_sendRawTransaction", (tx,)).await - } - - /// Estimates the EIP1559 `maxFeePerGas` and `maxPriorityFeePerGas` fields. - /// Receives an optional [EstimatorFunction] that can be used to modify - /// how to estimate these fees. - async fn estimate_eip1559_fees( - &self, - estimator: Option, - ) -> TransportResult<(U256, U256)> { - let base_fee_per_gas = match self.get_block_by_number(BlockNumberOrTag::Latest, false).await - { - Ok(Some(block)) => match block.header.base_fee_per_gas { - Some(base_fee_per_gas) => base_fee_per_gas, - None => return Err(TransportErrorKind::custom_str("EIP-1559 not activated")), - }, - - Ok(None) => return Err(TransportErrorKind::custom_str("Latest block not found")), - - Err(err) => return Err(err), - }; - - let fee_history = match self - .get_fee_history( - U256::from(utils::EIP1559_FEE_ESTIMATION_PAST_BLOCKS), - BlockNumberOrTag::Latest, - &[utils::EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE], - ) - .await - { - Ok(fee_history) => fee_history, - Err(err) => return Err(err), - }; - - // use the provided fee estimator function, or fallback to the default implementation. - let (max_fee_per_gas, max_priority_fee_per_gas) = if let Some(es) = estimator { - es(base_fee_per_gas, fee_history.reward.unwrap_or_default()) - } else { - utils::eip1559_default_estimator( - base_fee_per_gas, - fee_history.reward.unwrap_or_default(), - ) - }; - - Ok((max_fee_per_gas, max_priority_fee_per_gas)) - } - - async fn get_proof( - &self, - address: Address, - keys: Vec, - block: Option, - ) -> TransportResult { - self.inner.prepare("eth_getProof", (address, keys, block.unwrap_or_default())).await - } - - async fn create_access_list( - &self, - request: TransactionRequest, - block: Option, - ) -> TransportResult { - self.inner.prepare("eth_createAccessList", (request, block.unwrap_or_default())).await - } - - /// Parity trace transaction. - async fn trace_transaction( - &self, - hash: TxHash, - ) -> TransportResult> { - self.inner.prepare("trace_transaction", (hash,)).await - } - - async fn debug_trace_transaction( - &self, - hash: TxHash, - trace_options: GethDebugTracingOptions, - ) -> TransportResult { - self.inner.prepare("debug_traceTransaction", (hash, trace_options)).await - } - - async fn trace_block( - &self, - block: BlockNumberOrTag, - ) -> TransportResult> { - self.inner.prepare("trace_block", (block,)).await - } - - /// Sends a raw request with the methods and params specified to the internal connection, - /// and returns the result. - async fn raw_request(&self, method: &'static str, params: P) -> TransportResult - where - P: Serialize + Send + Sync + Clone, - R: Serialize + DeserializeOwned + Send + Sync + Unpin + 'static, - { - let res: R = self.inner.prepare(method, ¶ms).await?; - Ok(res) - } - - #[cfg(feature = "anvil")] - async fn set_code(&self, address: Address, code: &'static str) -> TransportResult<()> { - self.inner.prepare("anvil_setCode", (address, code)).await - } -} - -impl TryFrom<&str> for Provider> { - type Error = ClientError; - - fn try_from(url: &str) -> Result { - let url = url.parse().map_err(|_e| ClientError::ParseError)?; - let inner = ClientBuilder::default().reqwest_http(url); - - Ok(Self { inner, from: None }) - } -} - -impl TryFrom for Provider> { - type Error = ClientError; - - fn try_from(value: String) -> Result { - Provider::try_from(value.as_str()) - } -} - -impl<'a> TryFrom<&'a String> for Provider> { - type Error = ClientError; - - fn try_from(value: &'a String) -> Result { - Provider::try_from(value.as_str()) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - tmp::{Provider, TempProvider}, - utils, - }; - use alloy_node_bindings::Anvil; - use alloy_primitives::{address, b256, bytes, U256, U64}; - use alloy_rpc_types::{Block, BlockNumberOrTag, Filter}; - - #[tokio::test] - async fn gets_block_number() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let num = provider.get_block_number().await.unwrap(); - assert_eq!(0, num) - } - - #[tokio::test] - async fn gets_block_number_with_raw_req() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let num: U64 = provider.raw_request("eth_blockNumber", ()).await.unwrap(); - assert_eq!(0, num.to::()) - } - - #[tokio::test] - async fn gets_transaction_count() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let count = provider - .get_transaction_count( - address!("328375e18E7db8F1CA9d9bA8bF3E9C94ee34136A"), - Some(BlockNumberOrTag::Latest.into()), - ) - .await - .unwrap(); - assert_eq!(count, U256::from(0)); - } - - #[tokio::test] - async fn gets_block_by_hash() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let num = 0; - let tag: BlockNumberOrTag = num.into(); - let block = provider.get_block_by_number(tag, true).await.unwrap().unwrap(); - let hash = block.header.hash.unwrap(); - let block = provider.get_block_by_hash(hash, true).await.unwrap().unwrap(); - assert_eq!(block.header.hash.unwrap(), hash); - } - - #[tokio::test] - async fn gets_block_by_hash_with_raw_req() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let num = 0; - let tag: BlockNumberOrTag = num.into(); - let block = provider.get_block_by_number(tag, true).await.unwrap().unwrap(); - let hash = block.header.hash.unwrap(); - let block: Block = provider - .raw_request::<(alloy_primitives::FixedBytes<32>, bool), Block>( - "eth_getBlockByHash", - (hash, true), - ) - .await - .unwrap(); - assert_eq!(block.header.hash.unwrap(), hash); - } - - #[tokio::test] - async fn gets_block_by_number_full() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let num = 0; - let tag: BlockNumberOrTag = num.into(); - let block = provider.get_block_by_number(tag, true).await.unwrap().unwrap(); - assert_eq!(block.header.number.unwrap(), U256::from(num)); - } - - #[tokio::test] - async fn gets_block_by_number() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let num = 0; - let tag: BlockNumberOrTag = num.into(); - let block = provider.get_block_by_number(tag, true).await.unwrap().unwrap(); - assert_eq!(block.header.number.unwrap(), U256::from(num)); - } - - #[tokio::test] - async fn gets_client_version() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let version = provider.get_client_version().await.unwrap(); - assert!(version.contains("anvil")); - } - - #[tokio::test] - async fn gets_chain_id() { - let chain_id: u64 = 13371337; - let anvil = Anvil::new().args(["--chain-id", chain_id.to_string().as_str()]).spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let chain_id = provider.get_chain_id().await.unwrap(); - assert_eq!(chain_id, U64::from(chain_id)); - } - - #[tokio::test] - async fn gets_network_id() { - let chain_id: u64 = 13371337; - let anvil = Anvil::new().args(["--chain-id", chain_id.to_string().as_str()]).spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let chain_id = provider.get_net_version().await.unwrap(); - assert_eq!(chain_id, U64::from(chain_id)); - } - - #[tokio::test] - #[cfg(feature = "anvil")] - async fn gets_code_at() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - // Set the code - let addr = alloy_primitives::Address::with_last_byte(16); - provider.set_code(addr, "0xbeef").await.unwrap(); - let code = provider.get_code_at(addr, None).await.unwrap(); - assert_eq!(code, bytes!("beef")); - } - - #[tokio::test] - async fn gets_storage_at() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let addr = alloy_primitives::Address::with_last_byte(16); - let storage = provider.get_storage_at(addr, U256::ZERO, None).await.unwrap(); - assert_eq!(storage, U256::ZERO); - } - - #[tokio::test] - #[ignore] - async fn gets_transaction_by_hash() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let tx = provider - .get_transaction_by_hash(b256!( - "5c03fab9114ceb98994b43892ade87ddfd9ae7e8f293935c3bd29d435dc9fd95" - )) - .await - .unwrap(); - assert_eq!( - tx.block_hash.unwrap(), - b256!("b20e6f35d4b46b3c4cd72152faec7143da851a0dc281d390bdd50f58bfbdb5d3") - ); - assert_eq!(tx.block_number.unwrap(), U256::from(4571819)); - } - - #[tokio::test] - #[ignore] - async fn gets_logs() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let filter = Filter::new() - .at_block_hash(b256!( - "b20e6f35d4b46b3c4cd72152faec7143da851a0dc281d390bdd50f58bfbdb5d3" - )) - .event_signature(b256!( - "e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c" - )); - let logs = provider.get_logs(filter).await.unwrap(); - assert_eq!(logs.len(), 1); - } - - #[tokio::test] - #[ignore] - async fn gets_tx_receipt() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let receipt = provider - .get_transaction_receipt(b256!( - "5c03fab9114ceb98994b43892ade87ddfd9ae7e8f293935c3bd29d435dc9fd95" - )) - .await - .unwrap(); - assert!(receipt.is_some()); - let receipt = receipt.unwrap(); - assert_eq!( - receipt.transaction_hash.unwrap(), - b256!("5c03fab9114ceb98994b43892ade87ddfd9ae7e8f293935c3bd29d435dc9fd95") - ); - } - - #[tokio::test] - async fn gets_fee_history() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let block_number = provider.get_block_number().await.unwrap(); - let fee_history = provider - .get_fee_history( - U256::from(utils::EIP1559_FEE_ESTIMATION_PAST_BLOCKS), - BlockNumberOrTag::Number(block_number), - &[utils::EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE], - ) - .await - .unwrap(); - assert_eq!(fee_history.oldest_block, U256::ZERO); - } - - #[tokio::test] - #[ignore] // Anvil has yet to implement the `eth_getBlockReceipts` method. - async fn gets_block_receipts() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let receipts = provider.get_block_receipts(BlockNumberOrTag::Latest).await.unwrap(); - assert!(receipts.is_some()); - } - - #[tokio::test] - async fn gets_block_traces() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let traces = provider.trace_block(BlockNumberOrTag::Latest).await.unwrap(); - assert_eq!(traces.len(), 0); - } - - #[tokio::test] - async fn sends_raw_transaction() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let tx_hash = provider - .send_raw_transaction( - // Transfer 1 ETH from default EOA address to the Genesis address. - bytes!("f865808477359400825208940000000000000000000000000000000000000000018082f4f5a00505e227c1c636c76fac55795db1a40a4d24840d81b40d2fe0cc85767f6bd202a01e91b437099a8a90234ac5af3cb7ca4fb1432e133f75f9a91678eaf5f487c74b") - ) - .await.unwrap(); - assert_eq!( - tx_hash.to_string(), - "0x9dae5cf33694a02e8a7d5de3fe31e9d05ca0ba6e9180efac4ab20a06c9e598a3" - ); - } -} From 668d3d05f126c3c0175b949b13a83a466c6fcaf0 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 16:11:24 +0100 Subject: [PATCH 02/65] chore: bump alloy to 0.6.4 --- Cargo.toml | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8c766579c3b..07cd0c00826 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,10 +41,18 @@ alloy-transport-ipc = { version = "0.1.0", default-features = false, path = "cra alloy-transport-ws = { version = "0.1.0", default-features = false, path = "crates/transport-ws" } alloy-core = { version = "0.6.4", default-features = false, features = ["std"] } -alloy-dyn-abi = { version = "0.6.4", default-features = false, features = ["std"] } -alloy-json-abi = { version = "0.6.4", default-features = false, features = ["std"] } -alloy-primitives = { version = "0.6.4", default-features = false, features = ["std"] } -alloy-sol-types = { version = "0.6.4", default-features = false, features = ["std"] } +alloy-dyn-abi = { version = "0.6.4", default-features = false, features = [ + "std", +] } +alloy-json-abi = { version = "0.6.4", default-features = false, features = [ + "std", +] } +alloy-primitives = { version = "0.6.4", default-features = false, features = [ + "std", +] } +alloy-sol-types = { version = "0.6.4", default-features = false, features = [ + "std", +] } alloy-rlp = "0.3" @@ -53,8 +61,13 @@ ethereum_ssz_derive = "0.5" ethereum_ssz = "0.5" # crypto -elliptic-curve = { version = "0.13", default-features = false, features = ["std"] } -k256 = { version = "0.13", default-features = false, features = ["ecdsa", "std"] } +elliptic-curve = { version = "0.13", default-features = false, features = [ + "std", +] } +k256 = { version = "0.13", default-features = false, features = [ + "ecdsa", + "std", +] } sha2 = { version = "0.10", default-features = false, features = ["std"] } spki = { version = "0.7", default-features = false, features = ["std"] } From a698dee9954ba7a9b21af2449e670246a763897d Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 16:13:30 +0100 Subject: [PATCH 03/65] refactor: add `TypedTransaction` and tx traits --- crates/consensus/Cargo.toml | 6 +- crates/consensus/src/header.rs | 2 +- crates/consensus/src/lib.rs | 10 +- crates/consensus/src/receipt/receipts.rs | 40 --- crates/{network => consensus}/src/sealed.rs | 0 crates/consensus/src/signed.rs | 85 +++++ crates/consensus/src/transaction/eip1559.rs | 119 +++---- crates/consensus/src/transaction/eip2930.rs | 125 +++---- crates/consensus/src/transaction/eip4844.rs | 344 +++++-------------- crates/consensus/src/transaction/envelope.rs | 26 +- crates/consensus/src/transaction/legacy.rs | 166 ++++----- crates/consensus/src/transaction/mod.rs | 95 ++++- crates/consensus/src/transaction/typed.rs | 146 ++++++++ 13 files changed, 590 insertions(+), 574 deletions(-) rename crates/{network => consensus}/src/sealed.rs (100%) create mode 100644 crates/consensus/src/signed.rs create mode 100644 crates/consensus/src/transaction/typed.rs diff --git a/crates/consensus/Cargo.toml b/crates/consensus/Cargo.toml index 20532ad64cd..7d79f8e526f 100644 --- a/crates/consensus/Cargo.toml +++ b/crates/consensus/Cargo.toml @@ -12,7 +12,6 @@ repository.workspace = true exclude.workspace = true [dependencies] -alloy-network.workspace = true alloy-primitives = { workspace = true, features = ["rlp"] } alloy-rlp.workspace = true alloy-eips.workspace = true @@ -24,10 +23,13 @@ sha2 = { version = "0.10", optional = true } arbitrary = { workspace = true, features = ["derive"], optional = true } [dev-dependencies] +alloy-signer.workspace = true # arbitrary arbitrary = { workspace = true, features = ["derive"] } +k256.workspace = true +tokio = { workspace = true, features = ["macros"] } [features] -k256 = ["alloy-primitives/k256", "alloy-network/k256"] +k256 = ["alloy-primitives/k256"] kzg = ["dep:c-kzg", "dep:sha2", "dep:thiserror"] arbitrary = ["dep:arbitrary", "alloy-eips/arbitrary"] diff --git a/crates/consensus/src/header.rs b/crates/consensus/src/header.rs index 8a0c5561bdf..ea316c53255 100644 --- a/crates/consensus/src/header.rs +++ b/crates/consensus/src/header.rs @@ -1,8 +1,8 @@ +use crate::Sealable; use alloy_eips::{ eip1559::{calc_next_block_base_fee, BaseFeeParams}, eip4844::{calc_blob_gasprice, calc_excess_blob_gas}, }; -use alloy_network::Sealable; use alloy_primitives::{b256, keccak256, Address, BlockNumber, Bloom, Bytes, B256, B64, U256}; use alloy_rlp::{ length_of_length, Buf, BufMut, Decodable, Encodable, EMPTY_LIST_CODE, EMPTY_STRING_CODE, diff --git a/crates/consensus/src/lib.rs b/crates/consensus/src/lib.rs index 6b253d00dd5..fde0e51b4d6 100644 --- a/crates/consensus/src/lib.rs +++ b/crates/consensus/src/lib.rs @@ -25,11 +25,15 @@ pub use receipt::{Receipt, ReceiptEnvelope, ReceiptWithBloom}; mod transaction; pub use transaction::{ - BlobTransactionSidecar, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, - TxEip4844WithSidecar, TxEnvelope, TxLegacy, TxType, + BlobTransactionSidecar, SignableTransaction, Transaction, TxEip1559, TxEip2930, TxEip4844, + TxEip4844Variant, TxEip4844WithSidecar, TxEnvelope, TxLegacy, TxType, TypedTransaction, }; #[cfg(feature = "kzg")] pub use transaction::BlobTransactionValidationError; -pub use alloy_network::TxKind; +mod sealed; +pub use sealed::{Sealable, Sealed}; + +mod signed; +pub use signed::Signed; diff --git a/crates/consensus/src/receipt/receipts.rs b/crates/consensus/src/receipt/receipts.rs index 5e9022cb9cc..d33d8343d72 100644 --- a/crates/consensus/src/receipt/receipts.rs +++ b/crates/consensus/src/receipt/receipts.rs @@ -28,24 +28,6 @@ impl Receipt { } } -impl alloy_network::Receipt for Receipt { - fn success(&self) -> bool { - self.success - } - - fn bloom(&self) -> Bloom { - self.bloom_slow() - } - - fn cumulative_gas_used(&self) -> u64 { - self.cumulative_gas_used - } - - fn logs(&self) -> &[Log] { - &self.logs - } -} - /// [`Receipt`] with calculated bloom filter. /// /// This convenience type allows us to lazily calculate the bloom filter for a @@ -135,28 +117,6 @@ impl ReceiptWithBloom { } } -impl alloy_network::Receipt for ReceiptWithBloom { - fn success(&self) -> bool { - self.receipt.success - } - - fn bloom(&self) -> Bloom { - self.bloom - } - - fn bloom_cheap(&self) -> Option { - Some(self.bloom) - } - - fn cumulative_gas_used(&self) -> u64 { - self.receipt.cumulative_gas_used - } - - fn logs(&self) -> &[Log] { - &self.receipt.logs - } -} - impl alloy_rlp::Encodable for ReceiptWithBloom { fn encode(&self, out: &mut dyn BufMut) { self.encode_fields(out); diff --git a/crates/network/src/sealed.rs b/crates/consensus/src/sealed.rs similarity index 100% rename from crates/network/src/sealed.rs rename to crates/consensus/src/sealed.rs diff --git a/crates/consensus/src/signed.rs b/crates/consensus/src/signed.rs new file mode 100644 index 00000000000..390d14a3a50 --- /dev/null +++ b/crates/consensus/src/signed.rs @@ -0,0 +1,85 @@ +use crate::transaction::SignableTransaction; +use alloy_primitives::{Signature, B256}; +use alloy_rlp::BufMut; + +/// A transaction with a signature and hash seal. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Signed { + tx: T, + signature: Sig, + hash: B256, +} + +impl std::ops::Deref for Signed { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.tx + } +} + +impl Signed { + /// Returns a reference to the transaction. + pub const fn tx(&self) -> &T { + &self.tx + } + + /// Returns a reference to the signature. + pub const fn signature(&self) -> &Sig { + &self.signature + } + + /// Returns a reference to the transaction hash. + pub const fn hash(&self) -> &B256 { + &self.hash + } +} + +impl, Sig> Signed { + /// Instantiate from a transaction and signature. Does not verify the signature. + pub const fn new_unchecked(tx: T, signature: Sig, hash: B256) -> Self { + Self { tx, signature, hash } + } + + /// Calculate the signing hash for the transaction. + pub fn signature_hash(&self) -> B256 { + self.tx.signature_hash() + } + + /// Output the signed RLP for the transaction. + pub fn encode_signed(&self, out: &mut dyn BufMut) { + self.tx.encode_signed(&self.signature, out); + } + + /// Produce the RLP encoded signed transaction. + pub fn rlp_signed(&self) -> Vec { + let mut buf = vec![]; + self.encode_signed(&mut buf); + buf + } +} + +impl, Sig> alloy_rlp::Encodable for Signed { + fn encode(&self, out: &mut dyn BufMut) { + self.tx.encode_signed(&self.signature, out) + } + + // TODO: impl length +} + +impl, Sig> alloy_rlp::Decodable for Signed { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + >::decode_signed(buf) + } +} + +#[cfg(feature = "k256")] +impl> Signed { + /// Recover the signer of the transaction + pub fn recover_signer( + &self, + ) -> Result { + let sighash = self.tx.signature_hash(); + self.signature.recover_address_from_prehash(&sighash) + } +} diff --git a/crates/consensus/src/transaction/eip1559.rs b/crates/consensus/src/transaction/eip1559.rs index ea2ae383ac2..33508e0bed8 100644 --- a/crates/consensus/src/transaction/eip1559.rs +++ b/crates/consensus/src/transaction/eip1559.rs @@ -1,7 +1,6 @@ -use crate::{TxKind, TxType}; +use crate::{SignableTransaction, Signed, Transaction, TxType}; use alloy_eips::eip2930::AccessList; -use alloy_network::{Signed, Transaction}; -use alloy_primitives::{keccak256, Bytes, ChainId, Signature, U256}; +use alloy_primitives::{keccak256, Bytes, ChainId, Signature, TxKind, U256}; use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable, Header}; use std::mem; @@ -182,34 +181,37 @@ impl TxEip1559 { } } -impl Encodable for TxEip1559 { - fn encode(&self, out: &mut dyn BufMut) { - Header { list: true, payload_length: self.fields_len() }.encode(out); - self.encode_fields(out); +impl Transaction for TxEip1559 { + fn input(&self) -> &[u8] { + &self.input } - fn length(&self) -> usize { - let payload_length = self.fields_len(); - length_of_length(payload_length) + payload_length + fn to(&self) -> TxKind { + self.to } -} -impl Decodable for TxEip1559 { - fn decode(data: &mut &[u8]) -> alloy_rlp::Result { - let header = Header::decode(data)?; - let remaining_len = data.len(); + fn value(&self) -> U256 { + self.value + } - if header.payload_length > remaining_len { - return Err(alloy_rlp::Error::InputTooShort); - } + fn chain_id(&self) -> Option { + Some(self.chain_id) + } - Self::decode_inner(data) + fn nonce(&self) -> u64 { + self.nonce } -} -impl Transaction for TxEip1559 { - type Signature = Signature; + fn gas_limit(&self) -> u64 { + self.gas_limit + } + fn gas_price(&self) -> Option { + None + } +} + +impl SignableTransaction for TxEip1559 { fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) { out.put_u8(self.tx_type() as u8); Header { list: true, payload_length: self.fields_len() }.encode(out); @@ -239,7 +241,7 @@ impl Transaction for TxEip1559 { TxEip1559::encode_with_signature(self, signature, out) } - fn decode_signed(buf: &mut &[u8]) -> alloy_rlp::Result> { + fn decode_signed(buf: &mut &[u8]) -> alloy_rlp::Result> { let header = Header::decode(buf)?; if !header.list { return Err(alloy_rlp::Error::UnexpectedString); @@ -250,65 +252,30 @@ impl Transaction for TxEip1559 { Ok(tx.into_signed(signature)) } +} - fn input(&self) -> &[u8] { - &self.input - } - - fn input_mut(&mut self) -> &mut Bytes { - &mut self.input - } - - fn set_input(&mut self, input: Bytes) { - self.input = input; - } - - fn to(&self) -> TxKind { - self.to - } - - fn set_to(&mut self, to: TxKind) { - self.to = to; - } - - fn value(&self) -> U256 { - self.value - } - - fn set_value(&mut self, value: U256) { - self.value = value; - } - - fn chain_id(&self) -> Option { - Some(self.chain_id) - } - - fn set_chain_id(&mut self, chain_id: ChainId) { - self.chain_id = chain_id; - } - - fn nonce(&self) -> u64 { - self.nonce - } - - fn set_nonce(&mut self, nonce: u64) { - self.nonce = nonce; +impl Encodable for TxEip1559 { + fn encode(&self, out: &mut dyn BufMut) { + Header { list: true, payload_length: self.fields_len() }.encode(out); + self.encode_fields(out); } - fn gas_limit(&self) -> u64 { - self.gas_limit + fn length(&self) -> usize { + let payload_length = self.fields_len(); + length_of_length(payload_length) + payload_length } +} - fn set_gas_limit(&mut self, limit: u64) { - self.gas_limit = limit; - } +impl Decodable for TxEip1559 { + fn decode(data: &mut &[u8]) -> alloy_rlp::Result { + let header = Header::decode(data)?; + let remaining_len = data.len(); - fn gas_price(&self) -> Option { - None - } + if header.payload_length > remaining_len { + return Err(alloy_rlp::Error::InputTooShort); + } - fn set_gas_price(&mut self, price: U256) { - let _ = price; + Self::decode_inner(data) } } @@ -317,7 +284,7 @@ mod tests { use super::TxEip1559; use crate::TxKind; use alloy_eips::eip2930::AccessList; - use alloy_network::Transaction; + use alloy_network::SignableTransaction; use alloy_primitives::{address, b256, hex, Address, Signature, B256, U256}; use alloy_rlp::Encodable; diff --git a/crates/consensus/src/transaction/eip2930.rs b/crates/consensus/src/transaction/eip2930.rs index 410b187af5a..aa3a988b898 100644 --- a/crates/consensus/src/transaction/eip2930.rs +++ b/crates/consensus/src/transaction/eip2930.rs @@ -1,7 +1,6 @@ -use crate::{TxKind, TxType}; +use crate::{SignableTransaction, Signed, Transaction, TxType}; use alloy_eips::eip2930::AccessList; -use alloy_network::{Signed, Transaction}; -use alloy_primitives::{keccak256, Bytes, ChainId, Signature, U256}; +use alloy_primitives::{keccak256, Bytes, ChainId, Signature, TxKind, U256}; use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable, Header}; use std::mem; @@ -143,35 +142,37 @@ impl TxEip2930 { } } -impl Encodable for TxEip2930 { - fn encode(&self, out: &mut dyn BufMut) { - Header { list: true, payload_length: self.fields_len() }.encode(out); - self.encode_fields(out); +impl Transaction for TxEip2930 { + fn input(&self) -> &[u8] { + &self.input } - fn length(&self) -> usize { - let payload_length = self.fields_len(); - length_of_length(payload_length) + payload_length + fn to(&self) -> TxKind { + self.to } -} -impl Decodable for TxEip2930 { - fn decode(data: &mut &[u8]) -> alloy_rlp::Result { - let header = Header::decode(data)?; - let remaining_len = data.len(); + fn value(&self) -> U256 { + self.value + } - if header.payload_length > remaining_len { - return Err(alloy_rlp::Error::InputTooShort); - } + fn chain_id(&self) -> Option { + Some(self.chain_id) + } - Self::decode_inner(data) + fn nonce(&self) -> u64 { + self.nonce } -} -impl Transaction for TxEip2930 { - type Signature = Signature; - // type Receipt = ReceiptWithBloom; + fn gas_limit(&self) -> u64 { + self.gas_limit + } + fn gas_price(&self) -> Option { + Some(U256::from(self.gas_price)) + } +} + +impl SignableTransaction for TxEip2930 { fn encode_for_signing(&self, out: &mut dyn BufMut) { out.put_u8(self.tx_type() as u8); Header { list: true, payload_length: self.fields_len() }.encode(out); @@ -201,7 +202,7 @@ impl Transaction for TxEip2930 { self.encode_with_signature(signature, out) } - fn decode_signed(buf: &mut &[u8]) -> alloy_rlp::Result> { + fn decode_signed(buf: &mut &[u8]) -> alloy_rlp::Result> { let header = Header::decode(buf)?; if !header.list { return Err(alloy_rlp::Error::UnexpectedString); @@ -212,76 +213,38 @@ impl Transaction for TxEip2930 { Ok(tx.into_signed(signature)) } +} - fn input(&self) -> &[u8] { - &self.input - } - - fn input_mut(&mut self) -> &mut Bytes { - &mut self.input - } - - fn set_input(&mut self, input: Bytes) { - self.input = input; - } - - fn to(&self) -> TxKind { - self.to - } - - fn set_to(&mut self, to: TxKind) { - self.to = to; - } - - fn value(&self) -> U256 { - self.value - } - - fn set_value(&mut self, value: U256) { - self.value = value; - } - - fn chain_id(&self) -> Option { - Some(self.chain_id) - } - - fn set_chain_id(&mut self, chain_id: ChainId) { - self.chain_id = chain_id; - } - - fn nonce(&self) -> u64 { - self.nonce - } - - fn set_nonce(&mut self, nonce: u64) { - self.nonce = nonce; - } - - fn gas_limit(&self) -> u64 { - self.gas_limit +impl Encodable for TxEip2930 { + fn encode(&self, out: &mut dyn BufMut) { + Header { list: true, payload_length: self.fields_len() }.encode(out); + self.encode_fields(out); } - fn set_gas_limit(&mut self, limit: u64) { - self.gas_limit = limit; + fn length(&self) -> usize { + let payload_length = self.fields_len(); + length_of_length(payload_length) + payload_length } +} - fn gas_price(&self) -> Option { - Some(U256::from(self.gas_price)) - } +impl Decodable for TxEip2930 { + fn decode(data: &mut &[u8]) -> alloy_rlp::Result { + let header = Header::decode(data)?; + let remaining_len = data.len(); - fn set_gas_price(&mut self, price: U256) { - if let Ok(price) = price.try_into() { - self.gas_price = price; + if header.payload_length > remaining_len { + return Err(alloy_rlp::Error::InputTooShort); } + + Self::decode_inner(data) } } #[cfg(test)] mod tests { use super::TxEip2930; - use crate::{TxEnvelope, TxKind}; - use alloy_network::{Signed, Transaction}; - use alloy_primitives::{Address, Bytes, Signature, U256}; + use crate::{SignableTransaction, Signed, TxEnvelope}; + use alloy_primitives::{Address, Bytes, Signature, TxKind, U256}; use alloy_rlp::{Decodable, Encodable}; #[test] diff --git a/crates/consensus/src/transaction/eip4844.rs b/crates/consensus/src/transaction/eip4844.rs index 93ffef3c118..37f198c8757 100644 --- a/crates/consensus/src/transaction/eip4844.rs +++ b/crates/consensus/src/transaction/eip4844.rs @@ -1,10 +1,9 @@ -use crate::{TxKind, TxType}; +use crate::{SignableTransaction, Signed, Transaction, TxType}; use alloy_eips::{ eip2930::AccessList, eip4844::{BYTES_PER_BLOB, BYTES_PER_COMMITMENT, BYTES_PER_PROOF, DATA_GAS_PER_BLOB}, }; -use alloy_network::{Signed, Transaction}; -use alloy_primitives::{keccak256, Bytes, ChainId, Signature, B256, U256}; +use alloy_primitives::{keccak256, Bytes, ChainId, Signature, TxKind, B256, U256}; use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable, Header}; use std::mem; @@ -50,7 +49,7 @@ pub enum BlobTransactionValidationError { /// It can either be a standalone transaction, mainly seen when retrieving historical transactions, /// or a transaction with a sidecar, which is used when submitting a transaction to the network and /// when receiving and sending transactions during the gossip stage. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum TxEip4844Variant { /// A standalone transaction with blob hashes and max blob fee. TxEip4844(TxEip4844), @@ -153,8 +152,54 @@ impl TxEip4844Variant { } impl Transaction for TxEip4844Variant { - type Signature = Signature; + fn chain_id(&self) -> Option { + match self { + TxEip4844Variant::TxEip4844(tx) => Some(tx.chain_id), + TxEip4844Variant::TxEip4844WithSidecar(tx) => Some(tx.tx().chain_id), + } + } + + fn gas_limit(&self) -> u64 { + match self { + TxEip4844Variant::TxEip4844(tx) => tx.gas_limit, + TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx().gas_limit, + } + } + + fn gas_price(&self) -> Option { + None + } + + fn input(&self) -> &[u8] { + match self { + TxEip4844Variant::TxEip4844(tx) => tx.input.as_ref(), + TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx().input.as_ref(), + } + } + + fn nonce(&self) -> u64 { + match self { + TxEip4844Variant::TxEip4844(tx) => tx.nonce, + TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx().nonce, + } + } + fn to(&self) -> TxKind { + match self { + TxEip4844Variant::TxEip4844(tx) => tx.to, + TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx.to, + } + } + + fn value(&self) -> U256 { + match self { + TxEip4844Variant::TxEip4844(tx) => tx.value, + TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx.value, + } + } +} + +impl SignableTransaction for TxEip4844Variant { fn payload_len_for_signature(&self) -> usize { let payload_length = self.fields_len(); // 'transaction type byte length' + 'header length' + 'payload length' @@ -213,105 +258,6 @@ impl Transaction for TxEip4844Variant { fn encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) { Self::encode_with_signature(self, signature, out, true); } - - fn chain_id(&self) -> Option { - match self { - TxEip4844Variant::TxEip4844(tx) => Some(tx.chain_id), - TxEip4844Variant::TxEip4844WithSidecar(tx) => Some(tx.tx().chain_id), - } - } - - fn gas_limit(&self) -> u64 { - match self { - TxEip4844Variant::TxEip4844(tx) => tx.gas_limit, - TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx().gas_limit, - } - } - - fn gas_price(&self) -> Option { - None - } - - fn set_chain_id(&mut self, chain_id: ChainId) { - match self { - TxEip4844Variant::TxEip4844(tx) => tx.chain_id = chain_id, - TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx.chain_id = chain_id, - } - } - - fn input(&self) -> &[u8] { - match self { - TxEip4844Variant::TxEip4844(tx) => tx.input.as_ref(), - TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx().input.as_ref(), - } - } - - fn input_mut(&mut self) -> &mut Bytes { - match self { - TxEip4844Variant::TxEip4844(tx) => &mut tx.input, - TxEip4844Variant::TxEip4844WithSidecar(tx) => &mut tx.tx.input, - } - } - - fn nonce(&self) -> u64 { - match self { - TxEip4844Variant::TxEip4844(tx) => tx.nonce, - TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx().nonce, - } - } - - fn set_gas_limit(&mut self, limit: u64) { - match self { - TxEip4844Variant::TxEip4844(tx) => tx.gas_limit = limit, - TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx.gas_limit = limit, - } - } - - fn set_gas_price(&mut self, price: U256) { - let _ = price; - } - - fn set_input(&mut self, data: Bytes) { - match self { - TxEip4844Variant::TxEip4844(tx) => tx.input = data, - TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx.input = data, - } - } - - fn set_nonce(&mut self, nonce: u64) { - match self { - TxEip4844Variant::TxEip4844(tx) => tx.nonce = nonce, - TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx.nonce = nonce, - } - } - - fn set_to(&mut self, to: TxKind) { - match self { - TxEip4844Variant::TxEip4844(tx) => tx.to = to, - TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx.to = to, - } - } - - fn set_value(&mut self, value: U256) { - match self { - TxEip4844Variant::TxEip4844(tx) => tx.value = value, - TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx.value = value, - } - } - - fn to(&self) -> TxKind { - match self { - TxEip4844Variant::TxEip4844(tx) => tx.to, - TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx.to, - } - } - - fn value(&self) -> U256 { - match self { - TxEip4844Variant::TxEip4844(tx) => tx.value, - TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx.value, - } - } } /// [EIP-4844 Blob Transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction) @@ -587,7 +533,7 @@ impl TxEip4844 { TxType::Eip4844 } - /// Encodes the 4844 transaction in RLP for signing. + /// Encodes the EIP-4844 transaction in RLP for signing. /// /// This encodes the transaction as: /// `tx_type || rlp(chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, @@ -609,105 +555,57 @@ impl TxEip4844 { } impl Transaction for TxEip4844 { - type Signature = Signature; - - fn chain_id(&self) -> Option { - Some(self.chain_id) - } - - fn payload_len_for_signature(&self) -> usize { - let payload_length = self.fields_len(); - // 'transaction type byte length' + 'header length' + 'payload length' - 1 + length_of_length(payload_length) + payload_length - } - - fn into_signed(self, signature: Signature) -> Signed { - let payload_length = 1 + self.fields_len() + signature.rlp_vrs_len(); - let mut buf = Vec::with_capacity(payload_length); - buf.put_u8(TxType::Eip4844 as u8); - self.encode_signed(&signature, &mut buf); - let hash = keccak256(&buf); - - // Drop any v chain id value to ensure the signature format is correct at the time of - // combination for an EIP-4844 transaction. V should indicate the y-parity of the - // signature. - Signed::new_unchecked(self, signature.with_parity_bool(), hash) - } - - fn decode_signed(buf: &mut &[u8]) -> alloy_rlp::Result> { - let header = Header::decode(buf)?; - if !header.list { - return Err(alloy_rlp::Error::UnexpectedString); - } - - let tx = Self::decode_inner(buf)?; - let signature = Signature::decode_rlp_vrs(buf)?; - - Ok(tx.into_signed(signature)) - } - - fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) { - self.encode_for_signing(out); - } - - fn encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) { - TxEip4844::encode_with_signature(self, signature, out, true); - } - fn input(&self) -> &[u8] { &self.input } - fn input_mut(&mut self) -> &mut Bytes { - &mut self.input - } - - fn set_input(&mut self, input: Bytes) { - self.input = input; - } - fn to(&self) -> TxKind { self.to } - fn set_to(&mut self, to: TxKind) { - self.to = to; - } - fn value(&self) -> U256 { self.value } - fn set_value(&mut self, value: U256) { - self.value = value; - } - - fn set_chain_id(&mut self, chain_id: ChainId) { - self.chain_id = chain_id; + fn chain_id(&self) -> Option { + Some(self.chain_id) } fn nonce(&self) -> u64 { self.nonce } - fn set_nonce(&mut self, nonce: u64) { - self.nonce = nonce; - } - fn gas_limit(&self) -> u64 { self.gas_limit } - fn set_gas_limit(&mut self, limit: u64) { - self.gas_limit = limit; - } - fn gas_price(&self) -> Option { None } +} - fn set_gas_price(&mut self, price: U256) { - let _ = price; +impl Encodable for TxEip4844 { + fn encode(&self, out: &mut dyn BufMut) { + Header { list: true, payload_length: self.fields_len() }.encode(out); + self.encode_fields(out); + } + + fn length(&self) -> usize { + let payload_length = self.fields_len(); + length_of_length(payload_length) + payload_length + } +} + +impl Decodable for TxEip4844 { + fn decode(data: &mut &[u8]) -> alloy_rlp::Result { + let header = Header::decode(data)?; + let remaining_len = data.len(); + + if header.payload_length > remaining_len { + return Err(alloy_rlp::Error::InputTooShort); + } + + Self::decode_inner(data) } } @@ -720,7 +618,7 @@ impl Transaction for TxEip4844 { /// This is defined in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#networking) as an element /// of a `PooledTransactions` response, and is also used as the format for sending raw transactions /// through the network (eth_sendRawTransaction/eth_sendTransaction). -#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] pub struct TxEip4844WithSidecar { /// The actual transaction. pub tx: TxEip4844, @@ -802,56 +700,6 @@ impl TxEip4844WithSidecar { } impl Transaction for TxEip4844WithSidecar { - type Signature = Signature; - - fn decode_signed(buf: &mut &[u8]) -> alloy_rlp::Result> { - let header = Header::decode(buf)?; - if !header.list { - return Err(alloy_rlp::Error::UnexpectedString); - } - - let tx = TxEip4844::decode_inner(buf)?; - let signature = Signature::decode_rlp_vrs(buf)?; - let sidecar = BlobTransactionSidecar::decode_inner(buf).unwrap_or_default(); - - Ok(Self::from_tx_and_sidecar(tx, sidecar).into_signed(signature)) - } - - fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) { - // A signature for a [TxEip4844WithSidecar] is a signature over the [TxEip4844] EIP-2718 - // payload fields: - // (BLOB_TX_TYPE || - // rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, - // data, access_list, max_fee_per_blob_gas, blob_versioned_hashes])) - self.tx.encode_for_signing(out); - } - - fn encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) { - self.encode_with_signature(signature, out, true) - } - - fn into_signed(self, signature: Signature) -> Signed { - let payload_length = 1 + self.tx.fields_len() + signature.rlp_vrs_len(); - let mut buf = Vec::with_capacity(payload_length); - // The sidecar is NOT included in the signed payload, only the transaction fields and the - // type byte. Include the type byte. - buf.put_u8(TxType::Eip4844 as u8); - // Include the transaction fields. - self.tx.encode_signed(&signature, &mut buf); - let hash = keccak256(&buf); - - // Drop any v chain id value to ensure the signature format is correct at the time of - // combination for an EIP-4844 transaction. V should indicate the y-parity of the - // signature. - Signed::new_unchecked(self, signature.with_parity_bool(), hash) - } - - fn payload_len_for_signature(&self) -> usize { - // The payload length is the length of the `transaction_payload_body` list. - // The sidecar is NOT included. - self.tx.payload_len_for_signature() - } - fn chain_id(&self) -> Option { self.tx.chain_id() } @@ -868,42 +716,10 @@ impl Transaction for TxEip4844WithSidecar { self.tx.nonce() } - fn set_chain_id(&mut self, chain_id: ChainId) { - self.tx.set_chain_id(chain_id); - } - - fn set_gas_limit(&mut self, limit: u64) { - self.tx.set_gas_limit(limit); - } - - fn set_gas_price(&mut self, price: U256) { - self.tx.set_gas_price(price); - } - - fn set_to(&mut self, to: TxKind) { - self.tx.set_to(to); - } - - fn set_input(&mut self, data: Bytes) { - self.tx.set_input(data); - } - - fn set_nonce(&mut self, nonce: u64) { - self.tx.set_nonce(nonce); - } - - fn set_value(&mut self, value: U256) { - self.tx.set_value(value); - } - fn to(&self) -> TxKind { self.tx.to() } - fn signature_hash(&self) -> B256 { - self.tx.signature_hash() - } - fn value(&self) -> U256 { self.tx.value() } @@ -911,14 +727,10 @@ impl Transaction for TxEip4844WithSidecar { fn input(&self) -> &[u8] { self.tx.input() } - - fn input_mut(&mut self) -> &mut Bytes { - self.tx.input_mut() - } } /// This represents a set of blobs, and its corresponding commitments and proofs. -#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] #[repr(C)] pub struct BlobTransactionSidecar { /// The blob data. diff --git a/crates/consensus/src/transaction/envelope.rs b/crates/consensus/src/transaction/envelope.rs index 97bf3efdae7..f548aa93ec5 100644 --- a/crates/consensus/src/transaction/envelope.rs +++ b/crates/consensus/src/transaction/envelope.rs @@ -1,6 +1,7 @@ -use crate::{TxEip1559, TxEip2930, TxEip4844Variant, TxLegacy}; +use crate::{ + Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip4844WithSidecar, TxLegacy, +}; use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718}; -use alloy_network::Signed; use alloy_rlp::{length_of_length, Decodable, Encodable}; /// Ethereum `TransactionType` flags as specified in EIPs [2718], [1559], and @@ -77,6 +78,12 @@ pub enum TxEnvelope { Eip4844(Signed), } +impl From> for TxEnvelope { + fn from(v: Signed) -> Self { + Self::Legacy(v) + } +} + impl From> for TxEnvelope { fn from(v: Signed) -> Self { Self::Eip2930(v) @@ -89,6 +96,12 @@ impl From> for TxEnvelope { } } +impl From> for TxEnvelope { + fn from(v: Signed) -> Self { + Self::Eip4844(v) + } +} + impl TxEnvelope { /// Return the [`TxType`] of the inner txn. pub const fn tx_type(&self) -> TxType { @@ -201,10 +214,11 @@ impl Encodable2718 for TxEnvelope { #[cfg(test)] mod tests { + use crate::transaction::SignableTransaction; + use super::*; use alloy_eips::eip2930::{AccessList, AccessListItem}; - use alloy_network::{Transaction, TxKind}; - use alloy_primitives::{Address, Bytes, Signature, B256, U256}; + use alloy_primitives::{Address, Bytes, Signature, TxKind, B256, U256}; #[test] #[cfg(feature = "k256")] @@ -287,9 +301,9 @@ mod tests { assert_eq!(from, address!("A83C816D4f9b2783761a22BA6FADB0eB0606D7B2")); } - fn test_encode_decode_roundtrip(tx: T) + fn test_encode_decode_roundtrip>(tx: T) where - Signed: Into, + Signed: Into, { let signature = Signature::test_signature(); let tx_signed = tx.into_signed(signature); diff --git a/crates/consensus/src/transaction/legacy.rs b/crates/consensus/src/transaction/legacy.rs index 01f143860ce..13e28b9f016 100644 --- a/crates/consensus/src/transaction/legacy.rs +++ b/crates/consensus/src/transaction/legacy.rs @@ -1,6 +1,5 @@ -use crate::TxKind; -use alloy_network::{Signed, Transaction}; -use alloy_primitives::{keccak256, Bytes, ChainId, Signature, U256}; +use crate::{SignableTransaction, Signed, Transaction}; +use alloy_primitives::{keccak256, Bytes, ChainId, Signature, TxKind, U256}; use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable, Header, Result}; use std::mem; @@ -138,58 +137,55 @@ impl TxLegacy { chain_id: None, }) } -} - -impl Encodable for TxLegacy { - fn encode(&self, out: &mut dyn BufMut) { - self.encode_for_signing(out) - } - fn length(&self) -> usize { - let payload_length = self.fields_len() + self.eip155_fields_len(); - // 'header length' + 'payload length' - length_of_length(payload_length) + payload_length + /// Encodes the legacy transaction in RLP for signing. + /// + /// This encodes the transaction as: + /// `tx_type || rlp(chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, + /// value, input, access_list, max_fee_per_blob_gas, blob_versioned_hashes)` + /// + /// Note that there is no rlp header before the transaction type byte. + pub fn encode_for_signing(&self, out: &mut dyn BufMut) { + Header { list: true, payload_length: self.fields_len() + self.eip155_fields_len() } + .encode(out); + self.encode_fields(out); + self.encode_eip155_signing_fields(out); } } -impl Decodable for TxLegacy { - fn decode(data: &mut &[u8]) -> Result { - let header = Header::decode(data)?; - let remaining_len = data.len(); +impl Transaction for TxLegacy { + fn input(&self) -> &[u8] { + &self.input + } - let transaction_payload_len = header.payload_length; + fn to(&self) -> TxKind { + self.to + } - if transaction_payload_len > remaining_len { - return Err(alloy_rlp::Error::InputTooShort); - } + fn value(&self) -> U256 { + self.value + } - let mut transaction = Self::decode_fields(data)?; + fn chain_id(&self) -> Option { + self.chain_id + } - // If we still have data, it should be an eip-155 encoded chain_id - if !data.is_empty() { - transaction.chain_id = Some(Decodable::decode(data)?); - let _: U256 = Decodable::decode(data)?; // r - let _: U256 = Decodable::decode(data)?; // s - } + fn nonce(&self) -> u64 { + self.nonce + } - let decoded = remaining_len - data.len(); - if decoded != transaction_payload_len { - return Err(alloy_rlp::Error::UnexpectedLength); - } + fn gas_limit(&self) -> u64 { + self.gas_limit + } - Ok(transaction) + fn gas_price(&self) -> Option { + Some(U256::from(self.gas_price)) } } -impl Transaction for TxLegacy { - type Signature = Signature; - // type Receipt = ReceiptWithBloom; - +impl SignableTransaction for TxLegacy { fn encode_for_signing(&self, out: &mut dyn BufMut) { - Header { list: true, payload_length: self.fields_len() + self.eip155_fields_len() } - .encode(out); - self.encode_fields(out); - self.encode_eip155_signing_fields(out); + self.encode_for_signing(out) } fn payload_len_for_signature(&self) -> usize { @@ -225,67 +221,46 @@ impl Transaction for TxLegacy { Ok(tx.into_signed(signature)) } +} - fn input(&self) -> &[u8] { - &self.input - } - - fn input_mut(&mut self) -> &mut Bytes { - &mut self.input - } - - fn set_input(&mut self, data: Bytes) { - self.input = data; - } - - fn to(&self) -> TxKind { - self.to - } - - fn set_to(&mut self, to: TxKind) { - self.to = to; - } - - fn value(&self) -> U256 { - self.value - } - - fn set_value(&mut self, value: U256) { - self.value = value; - } - - fn chain_id(&self) -> Option { - self.chain_id +impl Encodable for TxLegacy { + fn encode(&self, out: &mut dyn BufMut) { + self.encode_for_signing(out) } - fn set_chain_id(&mut self, chain_id: ChainId) { - self.chain_id = Some(chain_id); + fn length(&self) -> usize { + let payload_length = self.fields_len() + self.eip155_fields_len(); + // 'header length' + 'payload length' + length_of_length(payload_length) + payload_length } +} - fn nonce(&self) -> u64 { - self.nonce - } +impl Decodable for TxLegacy { + fn decode(data: &mut &[u8]) -> Result { + let header = Header::decode(data)?; + let remaining_len = data.len(); - fn set_nonce(&mut self, nonce: u64) { - self.nonce = nonce; - } + let transaction_payload_len = header.payload_length; - fn gas_limit(&self) -> u64 { - self.gas_limit - } + if transaction_payload_len > remaining_len { + return Err(alloy_rlp::Error::InputTooShort); + } - fn set_gas_limit(&mut self, gas_limit: u64) { - self.gas_limit = gas_limit; - } + let mut transaction = Self::decode_fields(data)?; - fn gas_price(&self) -> Option { - Some(U256::from(self.gas_price)) - } + // If we still have data, it should be an eip-155 encoded chain_id + if !data.is_empty() { + transaction.chain_id = Some(Decodable::decode(data)?); + let _: U256 = Decodable::decode(data)?; // r + let _: U256 = Decodable::decode(data)?; // s + } - fn set_gas_price(&mut self, price: U256) { - if let Ok(price) = price.try_into() { - self.gas_price = price; + let decoded = remaining_len - data.len(); + if decoded != transaction_payload_len { + return Err(alloy_rlp::Error::UnexpectedLength); } + + Ok(transaction) } } @@ -295,12 +270,11 @@ mod tests { #[cfg(feature = "k256")] fn recover_signer_legacy() { use crate::{TxKind, TxLegacy}; - use alloy_network::Transaction; - use alloy_primitives::{b256, hex, Address, Signature, B256, U256}; + use alloy_network::SignableTransaction; + use alloy_primitives::{address, b256, hex, Signature, U256}; - let signer: Address = hex!("398137383b3d25c92898c656696e41950e47316b").into(); - let hash: B256 = - hex!("bb3a336e3f823ec18197f1e13ee875700f08f03e2cab75f0d0b118dabb44cba0").into(); + let signer = address!("398137383b3d25c92898c656696e41950e47316b"); + let hash = b256!("bb3a336e3f823ec18197f1e13ee875700f08f03e2cab75f0d0b118dabb44cba0"); let tx = TxLegacy { chain_id: Some(1), diff --git a/crates/consensus/src/transaction/mod.rs b/crates/consensus/src/transaction/mod.rs index ef7c2012b11..7ce3dd06c65 100644 --- a/crates/consensus/src/transaction/mod.rs +++ b/crates/consensus/src/transaction/mod.rs @@ -1,12 +1,11 @@ mod eip1559; +use alloy_primitives::{keccak256, ChainId, TxKind, B256, U256}; +use alloy_rlp::BufMut; pub use eip1559::TxEip1559; mod eip2930; pub use eip2930::TxEip2930; -mod legacy; -pub use legacy::TxLegacy; - mod eip4844; #[cfg(feature = "kzg")] pub use eip4844::BlobTransactionValidationError; @@ -14,3 +13,93 @@ pub use eip4844::{BlobTransactionSidecar, TxEip4844, TxEip4844Variant, TxEip4844 mod envelope; pub use envelope::{TxEnvelope, TxType}; + +mod legacy; +pub use legacy::TxLegacy; + +mod typed; +pub use typed::TypedTransaction; + +use crate::Signed; + +/// Represents a minimal EVM transaction. +pub trait Transaction: std::any::Any + Send + Sync + 'static { + /// Get `data`. + fn input(&self) -> &[u8]; + + /// Get `to`. + fn to(&self) -> TxKind; + + /// Get `value`. + fn value(&self) -> U256; + + /// Get `chain_id`. + fn chain_id(&self) -> Option; + + /// Get `nonce`. + fn nonce(&self) -> u64; + + /// Get `gas_limit`. + fn gas_limit(&self) -> u64; + + /// Get `gas_price`. + fn gas_price(&self) -> Option; +} + +/// A signable transaction. +/// +/// A transaction can have multiple signature types. This is usually +/// [`alloy_primitives::Signature`], however, it may be different for future EIP-2718 transaction +/// types, or in other networks. For example, in Optimism, the deposit transaction signature is the +/// unit type `()`. +pub trait SignableTransaction: Transaction { + /// RLP-encodes the transaction for signing. + fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut); + + /// Outputs the length of the signature RLP encoding for the transaction. + fn payload_len_for_signature(&self) -> usize; + + /// RLP-encodes the transaction for signing it. Used to calculate `signature_hash`. + /// + /// See [`Transaction::encode_for_signing`]. + fn encoded_for_signing(&self) -> Vec { + let mut buf = Vec::with_capacity(self.payload_len_for_signature()); + self.encode_for_signing(&mut buf); + buf + } + + /// Calculate the signing hash for the transaction. + fn signature_hash(&self) -> B256 { + keccak256(self.encoded_for_signing()) + } + + /// Convert to a signed transaction by adding a signature and computing the + /// hash. + fn into_signed(self, signature: Signature) -> Signed + where + Self: Sized; + + /// Encode with a signature. This encoding is usually RLP, but may be + /// different for future EIP-2718 transaction types. + fn encode_signed(&self, signature: &Signature, out: &mut dyn BufMut); + + /// Decode a signed transaction. This decoding is usually RLP, but may be + /// different for future EIP-2718 transaction types. + /// + /// This MUST be the inverse of [`Transaction::encode_signed`]. + fn decode_signed(buf: &mut &[u8]) -> alloy_rlp::Result> + where + Self: Sized; +} + +// TODO: Remove in favor of dyn trait upcasting (TBD, see https://github.com/rust-lang/rust/issues/65991#issuecomment-1903120162) +#[doc(hidden)] +impl dyn SignableTransaction { + pub fn __downcast_ref(&self) -> Option<&T> { + if std::any::Any::type_id(self) == std::any::TypeId::of::() { + unsafe { Some(&*(self as *const _ as *const T)) } + } else { + None + } + } +} diff --git a/crates/consensus/src/transaction/typed.rs b/crates/consensus/src/transaction/typed.rs new file mode 100644 index 00000000000..90a9b12ff9d --- /dev/null +++ b/crates/consensus/src/transaction/typed.rs @@ -0,0 +1,146 @@ +use crate::{Transaction, TxEip1559, TxEip2930, TxEip4844Variant, TxLegacy, TxType}; +use alloy_primitives::TxKind; + +/// The TypedTransaction enum represents all Ethereum transaction request types. +/// +/// Its variants correspond to specific allowed transactions: +/// 1. Legacy (pre-EIP2718) [`TxLegacy`] +/// 2. EIP2930 (state access lists) [`TxEip2930`] +/// 3. EIP1559 [`TxEip1559`] +/// 4. EIP4844 [`TxEip4844`] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum TypedTransaction { + /// Legacy transaction + Legacy(TxLegacy), + /// EIP-2930 transaction + Eip2930(TxEip2930), + /// EIP-1559 transaction + Eip1559(TxEip1559), + /// EIP-4844 transaction + Eip4844(TxEip4844Variant), +} + +impl From for TypedTransaction { + fn from(tx: TxLegacy) -> Self { + Self::Legacy(tx) + } +} + +impl From for TypedTransaction { + fn from(tx: TxEip2930) -> Self { + Self::Eip2930(tx) + } +} + +impl From for TypedTransaction { + fn from(tx: TxEip1559) -> Self { + Self::Eip1559(tx) + } +} + +impl From for TypedTransaction { + fn from(tx: TxEip4844Variant) -> Self { + Self::Eip4844(tx) + } +} + +impl TypedTransaction { + /// Return the [`TxType`] of the inner txn. + pub const fn tx_type(&self) -> TxType { + match self { + Self::Legacy(_) => TxType::Legacy, + Self::Eip2930(_) => TxType::Eip2930, + Self::Eip1559(_) => TxType::Eip1559, + Self::Eip4844(_) => TxType::Eip4844, + } + } + + /// Return the inner legacy transaction if it exists. + pub const fn legacy(&self) -> Option<&TxLegacy> { + match self { + Self::Legacy(tx) => Some(tx), + _ => None, + } + } + + /// Return the inner EIP-2930 transaction if it exists. + pub const fn eip2930(&self) -> Option<&TxEip2930> { + match self { + Self::Eip2930(tx) => Some(tx), + _ => None, + } + } + + /// Return the inner EIP-1559 transaction if it exists. + pub const fn eip1559(&self) -> Option<&TxEip1559> { + match self { + Self::Eip1559(tx) => Some(tx), + _ => None, + } + } +} + +impl Transaction for TypedTransaction { + fn chain_id(&self) -> Option { + match self { + Self::Legacy(tx) => tx.chain_id(), + Self::Eip2930(tx) => tx.chain_id(), + Self::Eip1559(tx) => tx.chain_id(), + Self::Eip4844(tx) => tx.chain_id(), + } + } + + fn gas_limit(&self) -> u64 { + match self { + Self::Legacy(tx) => tx.gas_limit(), + Self::Eip2930(tx) => tx.gas_limit(), + Self::Eip1559(tx) => tx.gas_limit(), + Self::Eip4844(tx) => tx.gas_limit(), + } + } + + fn gas_price(&self) -> Option { + match self { + Self::Legacy(tx) => tx.gas_price(), + Self::Eip2930(tx) => tx.gas_price(), + Self::Eip1559(tx) => tx.gas_price(), + Self::Eip4844(tx) => tx.gas_price(), + } + } + + fn input(&self) -> &[u8] { + match self { + Self::Legacy(tx) => tx.input(), + Self::Eip2930(tx) => tx.input(), + Self::Eip1559(tx) => tx.input(), + Self::Eip4844(tx) => tx.input(), + } + } + + fn nonce(&self) -> u64 { + match self { + Self::Legacy(tx) => tx.nonce(), + Self::Eip2930(tx) => tx.nonce(), + Self::Eip1559(tx) => tx.nonce(), + Self::Eip4844(tx) => tx.nonce(), + } + } + + fn to(&self) -> TxKind { + match self { + Self::Legacy(tx) => tx.to(), + Self::Eip2930(tx) => tx.to(), + Self::Eip1559(tx) => tx.to(), + Self::Eip4844(tx) => tx.to(), + } + } + + fn value(&self) -> alloy_primitives::U256 { + match self { + Self::Legacy(tx) => tx.value(), + Self::Eip2930(tx) => tx.value(), + Self::Eip1559(tx) => tx.value(), + Self::Eip4844(tx) => tx.value(), + } + } +} From dfbc39491b2e811d5f982e94b1268eb4f9f5fbb6 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 16:18:20 +0100 Subject: [PATCH 04/65] feat: tx builder --- crates/network/src/lib.rs | 5 +- crates/network/src/transaction/builder.rs | 160 ++++++++++++++++++++++ 2 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 crates/network/src/transaction/builder.rs diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs index c7042b550d0..53f7c8f040f 100644 --- a/crates/network/src/lib.rs +++ b/crates/network/src/lib.rs @@ -23,7 +23,10 @@ mod sealed; pub use sealed::{Sealable, Sealed}; mod transaction; -pub use transaction::{Eip1559Transaction, Signed, Transaction, TxKind}; +pub use transaction::{ + BuilderResult, NetworkSigner, TransactionBuilder, TransactionBuilderError, TxSigner, + TxSignerSync, +}; mod receipt; pub use receipt::Receipt; diff --git a/crates/network/src/transaction/builder.rs b/crates/network/src/transaction/builder.rs new file mode 100644 index 00000000000..217eac1e04f --- /dev/null +++ b/crates/network/src/transaction/builder.rs @@ -0,0 +1,160 @@ +use super::signer::NetworkSigner; +use crate::Network; +use alloy_primitives::{Address, Bytes, ChainId, TxKind, U256, U64}; + +/// Error type for transaction builders. +#[derive(Debug, thiserror::Error)] +pub enum TransactionBuilderError { + /// A required key is missing. + #[error("A required key is missing: {0}")] + MissingKey(&'static str), + + /// Signer cannot produce signature type required for transaction. + #[error("Signer cannot produce signature type required for transaction")] + UnsupportedSignatureType, + + /// Signer error. + #[error(transparent)] + Signer(#[from] alloy_signer::Error), + + /// A custom error. + #[error("{0}")] + Custom(#[source] Box), +} + +impl TransactionBuilderError { + /// Instantiate a custom error. + pub fn custom(e: E) -> Self + where + E: std::error::Error + Send + Sync + 'static, + { + Self::Custom(Box::new(e)) + } +} + +/// [`TransactionBuilder`] result type. +pub type BuilderResult = std::result::Result; + +/// A Transaction builder for a network. +/// +/// Transaction builders are primarily used to construct typed transactions that can be signed with +/// [`Builder::build`], or unsigned typed transactions with [`Builder::build_unsigned`]. +/// +/// Transaction builders should be able to construct all available transaction types on a given +/// network. +pub trait TransactionBuilder: Default + Sized + Send + Sync + 'static { + /// Get the chain ID for the transaction. + fn chain_id(&self) -> Option; + + /// Set the chain ID for the transaction. + fn set_chain_id(&mut self, chain_id: ChainId); + + /// Builder-pattern method for setting the chain ID. + fn with_chain_id(mut self, chain_id: alloy_primitives::ChainId) -> Self { + self.set_chain_id(chain_id); + self + } + + /// Get the nonce for the transaction. + fn nonce(&self) -> Option; + + /// Set the nonce for the transaction. + fn set_nonce(&mut self, nonce: U64); + + /// Builder-pattern method for setting the nonce. + fn with_nonce(mut self, nonce: U64) -> Self { + self.set_nonce(nonce); + self + } + + /// Get the input data for the transaction. + fn input(&self) -> Option<&Bytes>; + + /// Set the input data for the transaction. + fn set_input(&mut self, input: Bytes); + + /// Builder-pattern method for setting the input data. + fn with_input(mut self, input: Bytes) -> Self { + self.set_input(input); + self + } + + /// Get the sender for the transaction. + fn from(&self) -> Option
; + + /// Set the sender for the transaction. + fn set_from(&mut self, from: Address); + + /// Builder-pattern method for setting the sender. + fn with_from(mut self, from: Address) -> Self { + self.set_from(from); + self + } + + /// Get the recipient for the transaction. + fn to(&self) -> Option; + + /// Set the recipient for the transaction. + fn set_to(&mut self, to: TxKind); + + /// Builder-pattern method for setting the recipient. + fn with_to(mut self, to: TxKind) -> Self { + self.set_to(to); + self + } + + /// Calculates the address that will be created by the transaction, if any. + /// + /// Returns `None` if the transaction is not a contract creation (the `to` field is set), or if + /// the `from` or `nonce` fields are not set. + fn calculate_create_address(&self) -> Option
{ + if !self.to().is_some_and(|to| to.is_create()) { + return None; + } + let from = self.from()?; + let nonce = self.nonce()?; + Some(from.create(nonce.to())) + } + + /// Get the value for the transaction. + fn value(&self) -> Option; + + /// Set the value for the transaction. + fn set_value(&mut self, value: U256); + + /// Builder-pattern method for setting the value. + fn with_value(mut self, value: U256) -> Self { + self.set_value(value); + self + } + + /// Get the gas price for the transaction. + fn gas_price(&self) -> Option; + + /// Set the gas price for the transaction. + fn set_gas_price(&mut self, gas_price: U256); + + /// Builder-pattern method for setting the gas price. + fn with_gas_price(mut self, gas_price: U256) -> Self { + self.set_gas_price(gas_price); + self + } + + /// Get the gas limit for the transaction. + fn gas_limit(&self) -> Option; + + /// Set the gas limit for the transaction. + fn set_gas_limit(&mut self, gas_limit: U256); + + /// Builder-pattern method for setting the gas limit. + fn with_gas_limit(mut self, gas_limit: U256) -> Self { + self.set_gas_limit(gas_limit); + self + } + + /// Build an unsigned, but typed, transaction. + fn build_unsigned(self) -> BuilderResult; + + /// Build a signed transaction. + fn build>(self, signer: &S) -> BuilderResult; +} From 6e1b1ee34a9fd6139c3f6f227c7a9fc70b061bb7 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 16:19:21 +0100 Subject: [PATCH 05/65] feat: network signer trait --- crates/network/src/transaction/mod.rs | 123 +---------------------- crates/network/src/transaction/signed.rs | 85 ---------------- crates/network/src/transaction/signer.rs | 32 ++++++ 3 files changed, 36 insertions(+), 204 deletions(-) delete mode 100644 crates/network/src/transaction/signed.rs create mode 100644 crates/network/src/transaction/signer.rs diff --git a/crates/network/src/transaction/mod.rs b/crates/network/src/transaction/mod.rs index 109b238cba5..b0e3860945d 100644 --- a/crates/network/src/transaction/mod.rs +++ b/crates/network/src/transaction/mod.rs @@ -1,120 +1,5 @@ -use alloy_primitives::{keccak256, Bytes, ChainId, Signature, B256, U256}; -use alloy_rlp::BufMut; +mod builder; +pub use builder::{BuilderResult, TransactionBuilder, TransactionBuilderError}; -mod common; -pub use common::TxKind; - -mod signed; -pub use signed::Signed; - -/// Represents a minimal EVM transaction. -pub trait Transaction: std::any::Any + Send + Sync + 'static { - /// The signature type for this transaction. - /// - /// This is usually [`alloy_primitives::Signature`], however, it may be different for future - /// EIP-2718 transaction types, or in other networks. For example, in Optimism, the deposit - /// transaction signature is the unit type `()`. - type Signature; - - /// RLP-encodes the transaction for signing. - fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut); - - /// Outputs the length of the signature RLP encoding for the transaction. - fn payload_len_for_signature(&self) -> usize; - - /// RLP-encodes the transaction for signing it. Used to calculate `signature_hash`. - /// - /// See [`Transaction::encode_for_signing`]. - fn encoded_for_signing(&self) -> Vec { - let mut buf = Vec::with_capacity(self.payload_len_for_signature()); - self.encode_for_signing(&mut buf); - buf - } - - /// Calculate the signing hash for the transaction. - fn signature_hash(&self) -> B256 { - keccak256(self.encoded_for_signing()) - } - - /// Convert to a signed transaction by adding a signature and computing the - /// hash. - fn into_signed(self, signature: Signature) -> Signed - where - Self: Sized; - - /// Encode with a signature. This encoding is usually RLP, but may be - /// different for future EIP-2718 transaction types. - fn encode_signed(&self, signature: &Signature, out: &mut dyn BufMut); - - /// Decode a signed transaction. This decoding is usually RLP, but may be - /// different for future EIP-2718 transaction types. - /// - /// This MUST be the inverse of [`Transaction::encode_signed`]. - fn decode_signed(buf: &mut &[u8]) -> alloy_rlp::Result> - where - Self: Sized; - - /// Get `data`. - fn input(&self) -> &[u8]; - /// Get `data`. - fn input_mut(&mut self) -> &mut Bytes; - /// Set `data`. - fn set_input(&mut self, data: Bytes); - - /// Get `to`. - fn to(&self) -> TxKind; - /// Set `to`. - fn set_to(&mut self, to: TxKind); - - /// Get `value`. - fn value(&self) -> U256; - /// Set `value`. - fn set_value(&mut self, value: U256); - - /// Get `chain_id`. - fn chain_id(&self) -> Option; - /// Set `chain_id`. - fn set_chain_id(&mut self, chain_id: ChainId); - - /// Get `nonce`. - fn nonce(&self) -> u64; - /// Set `nonce`. - fn set_nonce(&mut self, nonce: u64); - - /// Get `gas_limit`. - fn gas_limit(&self) -> u64; - /// Set `gas_limit`. - fn set_gas_limit(&mut self, limit: u64); - - /// Get `gas_price`. - fn gas_price(&self) -> Option; - /// Set `gas_price`. - fn set_gas_price(&mut self, price: U256); -} - -// TODO: Remove in favor of dyn trait upcasting (TBD, see https://github.com/rust-lang/rust/issues/65991#issuecomment-1903120162) -#[doc(hidden)] -impl dyn Transaction { - pub fn __downcast_ref(&self) -> Option<&T> { - if std::any::Any::type_id(self) == std::any::TypeId::of::() { - unsafe { Some(&*(self as *const _ as *const T)) } - } else { - None - } - } -} - -/// Captures getters and setters common across EIP-1559 transactions across all networks -pub trait Eip1559Transaction: Transaction { - /// Get `max_priority_fee_per_gas`. - #[doc(alias = "max_tip")] - fn max_priority_fee_per_gas(&self) -> U256; - /// Set `max_priority_fee_per_gas`. - #[doc(alias = "set_max_tip")] - fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: U256); - - /// Get `max_fee_per_gas`. - fn max_fee_per_gas(&self) -> U256; - /// Set `max_fee_per_gas`. - fn set_max_fee_per_gas(&mut self, max_fee_per_gas: U256); -} +mod signer; +pub use signer::{NetworkSigner, TxSigner, TxSignerSync}; diff --git a/crates/network/src/transaction/signed.rs b/crates/network/src/transaction/signed.rs deleted file mode 100644 index 92cecd43150..00000000000 --- a/crates/network/src/transaction/signed.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::Transaction; -use alloy_primitives::{Signature, B256}; -use alloy_rlp::BufMut; - -/// A transaction with a signature and hash seal. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct Signed { - tx: T, - signature: Sig, - hash: B256, -} - -impl std::ops::Deref for Signed { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.tx - } -} - -impl Signed { - /// Returns a reference to the transaction. - pub const fn tx(&self) -> &T { - &self.tx - } - - /// Returns a reference to the signature. - pub const fn signature(&self) -> &Sig { - &self.signature - } - - /// Returns a reference to the transaction hash. - pub const fn hash(&self) -> &B256 { - &self.hash - } -} - -impl Signed { - /// Instantiate from a transaction and signature. Does not verify the signature. - pub const fn new_unchecked(tx: T, signature: Signature, hash: B256) -> Self { - Self { tx, signature, hash } - } - - /// Calculate the signing hash for the transaction. - pub fn signature_hash(&self) -> B256 { - self.tx.signature_hash() - } - - /// Output the signed RLP for the transaction. - pub fn encode_signed(&self, out: &mut dyn BufMut) { - self.tx.encode_signed(&self.signature, out); - } - - /// Produce the RLP encoded signed transaction. - pub fn rlp_signed(&self) -> Vec { - let mut buf = vec![]; - self.encode_signed(&mut buf); - buf - } -} - -impl alloy_rlp::Encodable for Signed { - fn encode(&self, out: &mut dyn BufMut) { - self.tx.encode_signed(&self.signature, out) - } - - // TODO: impl length -} - -impl alloy_rlp::Decodable for Signed { - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - T::decode_signed(buf) - } -} - -#[cfg(feature = "k256")] -impl Signed { - /// Recover the signer of the transaction - pub fn recover_signer( - &self, - ) -> Result { - let sighash = self.tx.signature_hash(); - self.signature.recover_address_from_prehash(&sighash) - } -} diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs new file mode 100644 index 00000000000..94d12a61cc3 --- /dev/null +++ b/crates/network/src/transaction/signer.rs @@ -0,0 +1,32 @@ +use crate::Network; +use alloy_consensus::SignableTransaction; +use async_trait::async_trait; + +// todo: move +/// A signer capable of signing any transaction for the given network. +#[async_trait] +pub trait NetworkSigner { + /// Asynchronously sign an unsigned transaction. + async fn sign(&self, tx: N::UnsignedTx) -> alloy_signer::Result; +} + +// todo: move +/// An async signer capable of signing any [SignableTransaction] for the given [Signature] type. +#[async_trait] +pub trait TxSigner { + /// Asynchronously sign an unsigned transaction. + async fn sign_transaction( + &self, + tx: &mut dyn SignableTransaction, + ) -> alloy_signer::Result; +} + +// todo: move +/// A sync signer capable of signing any [SignableTransaction] for the given [Signature] type. +pub trait TxSignerSync { + /// Synchronously sign an unsigned transaction. + fn sign_transaction_sync( + &self, + tx: &mut dyn SignableTransaction, + ) -> alloy_signer::Result; +} From 8c5c484d628a359728ce9f90cf47b7d611cf697b Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 16:19:33 +0100 Subject: [PATCH 06/65] feat: `Ethereum` network --- crates/network/Cargo.toml | 8 +- crates/network/src/ethereum/mod.rs | 108 +++++++++++++++++ crates/network/src/ethereum/receipt.rs | 43 +++++++ crates/network/src/ethereum/signer.rs | 141 +++++++++++++++++++++++ crates/network/src/lib.rs | 14 ++- crates/network/src/transaction/common.rs | 91 --------------- 6 files changed, 308 insertions(+), 97 deletions(-) create mode 100644 crates/network/src/ethereum/mod.rs create mode 100644 crates/network/src/ethereum/receipt.rs create mode 100644 crates/network/src/ethereum/signer.rs delete mode 100644 crates/network/src/transaction/common.rs diff --git a/crates/network/Cargo.toml b/crates/network/Cargo.toml index 51ecf79cbc8..d77e48cabfb 100644 --- a/crates/network/Cargo.toml +++ b/crates/network/Cargo.toml @@ -12,11 +12,15 @@ repository.workspace = true exclude.workspace = true [dependencies] +alloy-consensus.workspace = true alloy-eips = { workspace = true, features = ["serde"] } alloy-json-rpc.workspace = true alloy-primitives.workspace = true -alloy-rlp.workspace = true +alloy-rpc-types.workspace = true +alloy-signer.workspace = true +async-trait.workspace = true serde = { workspace = true, features = ["derive"] } +thiserror.workspace = true [features] -k256 = ["alloy-primitives/k256"] +k256 = ["alloy-primitives/k256", "alloy-consensus/k256"] diff --git a/crates/network/src/ethereum/mod.rs b/crates/network/src/ethereum/mod.rs new file mode 100644 index 00000000000..8899488b161 --- /dev/null +++ b/crates/network/src/ethereum/mod.rs @@ -0,0 +1,108 @@ +use crate::{BuilderResult, Network, NetworkSigner, TransactionBuilder}; + +mod receipt; +mod signer; +use alloy_primitives::{Address, TxKind, U256, U64}; +pub use signer::EthereumSigner; + +/// Types for a mainnet-like Ethereum network. +#[derive(Debug, Clone, Copy)] +pub struct Ethereum; + +impl Network for Ethereum { + type TxEnvelope = alloy_consensus::TxEnvelope; + + type UnsignedTx = alloy_consensus::TypedTransaction; + + type ReceiptEnvelope = alloy_consensus::ReceiptEnvelope; + + type Header = alloy_consensus::Header; + + type TransactionRequest = alloy_rpc_types::transaction::TransactionRequest; + + type TransactionResponse = alloy_rpc_types::Transaction; + + type ReceiptResponse = alloy_rpc_types::TransactionReceipt; + + type HeaderResponse = alloy_rpc_types::Header; +} + +impl TransactionBuilder for alloy_rpc_types::TransactionRequest { + fn chain_id(&self) -> Option { + self.chain_id + } + + fn set_chain_id(&mut self, chain_id: alloy_primitives::ChainId) { + self.chain_id = Some(chain_id); + } + + fn nonce(&self) -> Option { + self.nonce + } + + fn set_nonce(&mut self, nonce: U64) { + self.nonce = Some(nonce); + } + + fn input(&self) -> Option<&alloy_primitives::Bytes> { + self.input.input() + } + + fn set_input(&mut self, input: alloy_primitives::Bytes) { + self.input.input = Some(input); + } + + fn to(&self) -> Option { + self.to.map(TxKind::Call).or(Some(TxKind::Create)) + } + + fn from(&self) -> Option
{ + self.from + } + + fn set_from(&mut self, from: Address) { + self.from = Some(from); + } + + fn set_to(&mut self, to: alloy_primitives::TxKind) { + match to { + TxKind::Create => self.to = None, + TxKind::Call(to) => self.to = Some(to), + } + } + + fn value(&self) -> Option { + self.value + } + + fn set_value(&mut self, value: alloy_primitives::U256) { + self.value = Some(value) + } + + fn gas_price(&self) -> Option { + todo!() + } + + fn set_gas_price(&mut self, gas_price: U256) { + todo!() + } + + fn gas_limit(&self) -> Option { + self.gas + } + + fn set_gas_limit(&mut self, gas_limit: U256) { + self.gas = Some(gas_limit); + } + + fn build_unsigned(self) -> BuilderResult<::UnsignedTx> { + todo!() + } + + fn build>( + self, + signer: &S, + ) -> BuilderResult<::TxEnvelope> { + todo!() + } +} diff --git a/crates/network/src/ethereum/receipt.rs b/crates/network/src/ethereum/receipt.rs new file mode 100644 index 00000000000..6e47ff80da1 --- /dev/null +++ b/crates/network/src/ethereum/receipt.rs @@ -0,0 +1,43 @@ +use crate::Receipt; +use alloy_consensus::ReceiptWithBloom; +use alloy_primitives::{Bloom, Log}; + +impl Receipt for alloy_consensus::Receipt { + fn success(&self) -> bool { + self.success + } + + fn bloom(&self) -> Bloom { + self.bloom_slow() + } + + fn cumulative_gas_used(&self) -> u64 { + self.cumulative_gas_used + } + + fn logs(&self) -> &[Log] { + &self.logs + } +} + +impl Receipt for ReceiptWithBloom { + fn success(&self) -> bool { + self.receipt.success + } + + fn bloom(&self) -> Bloom { + self.bloom + } + + fn bloom_cheap(&self) -> Option { + Some(self.bloom) + } + + fn cumulative_gas_used(&self) -> u64 { + self.receipt.cumulative_gas_used + } + + fn logs(&self) -> &[Log] { + &self.receipt.logs + } +} diff --git a/crates/network/src/ethereum/signer.rs b/crates/network/src/ethereum/signer.rs new file mode 100644 index 00000000000..1b72ac0dc8e --- /dev/null +++ b/crates/network/src/ethereum/signer.rs @@ -0,0 +1,141 @@ +use super::Ethereum; +use crate::{NetworkSigner, TxSigner}; +use alloy_consensus::{SignableTransaction, TxEnvelope, TypedTransaction}; +use alloy_signer::Signature; +use async_trait::async_trait; + +/// A signer capable of signing any transaction for the Ethereum network. +pub struct EthereumSigner(Box + Sync>); + +impl std::fmt::Debug for EthereumSigner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("EthereumSigner").finish() + } +} + +impl From for EthereumSigner +where + S: TxSigner + Sync + 'static, +{ + fn from(signer: S) -> Self { + Self(Box::new(signer)) + } +} + +impl EthereumSigner { + async fn sign_transaction( + &self, + tx: &mut dyn SignableTransaction, + ) -> alloy_signer::Result { + self.0.sign_transaction(tx).await + } +} + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl NetworkSigner for EthereumSigner { + async fn sign(&self, tx: TypedTransaction) -> alloy_signer::Result { + match tx { + TypedTransaction::Legacy(mut t) => { + let sig = self.sign_transaction(&mut t).await?; + Ok(t.into_signed(sig).into()) + } + TypedTransaction::Eip2930(mut t) => { + let sig = self.sign_transaction(&mut t).await?; + Ok(t.into_signed(sig).into()) + } + TypedTransaction::Eip1559(mut t) => { + let sig = self.sign_transaction(&mut t).await?; + Ok(t.into_signed(sig).into()) + } + TypedTransaction::Eip4844(mut t) => { + let sig = self.sign_transaction(&mut t).await?; + Ok(t.into_signed(sig).into()) + } + } + } +} + +#[cfg(test)] +mod test { + use alloy_consensus::{SignableTransaction, TxLegacy}; + use alloy_primitives::{address, ChainId, Signature, U256}; + use alloy_signer::{k256, Result, Signer, TxSigner, TxSignerSync}; + + #[tokio::test] + async fn signs_tx() { + async fn sign_tx_test(tx: &mut TxLegacy, chain_id: Option) -> Result { + let mut before = tx.clone(); + let sig = sign_dyn_tx_test(tx, chain_id).await?; + if let Some(chain_id) = chain_id { + assert_eq!(tx.chain_id, Some(chain_id), "chain ID was not set"); + before.chain_id = Some(chain_id); + } + assert_eq!(*tx, before); + Ok(sig) + } + + async fn sign_dyn_tx_test( + tx: &mut dyn SignableTransaction, + chain_id: Option, + ) -> Result { + let mut wallet: alloy_signer::Wallet = + "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318".parse().unwrap(); + wallet.set_chain_id(chain_id); + + let sig = wallet.sign_transaction_sync(tx)?; + let sighash = tx.signature_hash(); + assert_eq!(sig.recover_address_from_prehash(&sighash).unwrap(), wallet.address()); + + let sig_async = wallet.sign_transaction(tx).await.unwrap(); + assert_eq!(sig_async, sig); + + Ok(sig) + } + + // retrieved test vector from: + // https://web3js.readthedocs.io/en/v1.2.0/web3-eth-accounts.html#eth-accounts-signtransaction + let mut tx = TxLegacy { + to: alloy_primitives::TxKind::Call(address!( + "F0109fC8DF283027b6285cc889F5aA624EaC1F55" + )), + value: U256::from(1_000_000_000), + gas_limit: 2_000_000, + nonce: 0, + gas_price: 21_000_000_000, + input: Default::default(), + chain_id: None, + }; + let sig_none = sign_tx_test(&mut tx, None).await.unwrap(); + + tx.chain_id = Some(1); + let sig_1 = sign_tx_test(&mut tx, None).await.unwrap(); + let expected = "c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa6825".parse().unwrap(); + assert_eq!(sig_1, expected); + assert_ne!(sig_1, sig_none); + + tx.chain_id = Some(2); + let sig_2 = sign_tx_test(&mut tx, None).await.unwrap(); + assert_ne!(sig_2, sig_1); + assert_ne!(sig_2, sig_none); + + // Sets chain ID. + tx.chain_id = None; + let sig_none_none = sign_tx_test(&mut tx, None).await.unwrap(); + assert_eq!(sig_none_none, sig_none); + + tx.chain_id = None; + let sig_none_1 = sign_tx_test(&mut tx, Some(1)).await.unwrap(); + assert_eq!(sig_none_1, sig_1); + + tx.chain_id = None; + let sig_none_2 = sign_tx_test(&mut tx, Some(2)).await.unwrap(); + assert_eq!(sig_none_2, sig_2); + + // Errors on mismatch. + tx.chain_id = Some(2); + let error = sign_tx_test(&mut tx, Some(1)).await.unwrap_err(); + let expected_error = alloy_signer::Error::TransactionChainIdMismatch { signer: 1, tx: 2 }; + assert_eq!(error.to_string(), expected_error.to_string()); + } +} diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs index 53f7c8f040f..a4193e2e3fd 100644 --- a/crates/network/src/lib.rs +++ b/crates/network/src/lib.rs @@ -19,9 +19,6 @@ use alloy_eips::eip2718::Eip2718Envelope; use alloy_json_rpc::RpcObject; use alloy_primitives::B256; -mod sealed; -pub use sealed::{Sealable, Sealed}; - mod transaction; pub use transaction::{ BuilderResult, NetworkSigner, TransactionBuilder, TransactionBuilderError, TxSigner, @@ -33,6 +30,9 @@ pub use receipt::Receipt; pub use alloy_eips::eip2718; +mod ethereum; +pub use ethereum::{Ethereum, EthereumSigner}; + /// A list of transactions, either hydrated or hashes. #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[serde(untagged)] @@ -54,6 +54,8 @@ pub struct BlockResponse { } /// Captures type info for network-specific RPC requests/responses. +// todo: block responses are ethereum only, so we need to include this in here too, or make `Block` +// generic over tx/header type pub trait Network: Sized + Send + Sync + 'static { #[doc(hidden)] /// Asserts that this trait can only be implemented on a ZST. @@ -65,6 +67,10 @@ pub trait Network: Sized + Send + Sync + 'static { /// The network transaction envelope type. type TxEnvelope: Eip2718Envelope; + + /// An enum over the various transaction types. + type UnsignedTx; + /// The network receipt envelope type. type ReceiptEnvelope: Eip2718Envelope; /// The network header type. @@ -73,7 +79,7 @@ pub trait Network: Sized + Send + Sync + 'static { // -- JSON RPC types -- /// The JSON body of a transaction request. - type TransactionRequest: RpcObject + Transaction; // + TransactionBuilder + type TransactionRequest: RpcObject + TransactionBuilder + std::fmt::Debug; /// The JSON body of a transaction response. type TransactionResponse: RpcObject; /// The JSON body of a transaction receipt. diff --git a/crates/network/src/transaction/common.rs b/crates/network/src/transaction/common.rs deleted file mode 100644 index 45d637a655f..00000000000 --- a/crates/network/src/transaction/common.rs +++ /dev/null @@ -1,91 +0,0 @@ -use alloy_primitives::Address; -use alloy_rlp::{Buf, BufMut, Decodable, Encodable, EMPTY_STRING_CODE}; - -/// The `to` field of a transaction. Either a target address, or empty for a -/// contract creation. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] -pub enum TxKind { - /// A transaction that creates a contract. - #[default] - Create, - /// A transaction that calls a contract or transfer. - Call(Address), -} - -impl From> for TxKind { - /// Creates a `TxKind::Call` with the `Some` address, `None` otherwise. - #[inline] - fn from(value: Option
) -> Self { - match value { - None => TxKind::Create, - Some(addr) => TxKind::Call(addr), - } - } -} - -impl From
for TxKind { - /// Creates a `TxKind::Call` with the given address. - #[inline] - fn from(value: Address) -> Self { - TxKind::Call(value) - } -} - -impl TxKind { - /// Returns the address of the contract that will be called or will receive the transfer. - pub const fn to(self) -> Option
{ - match self { - TxKind::Create => None, - TxKind::Call(to) => Some(to), - } - } - - /// Returns true if the transaction is a contract creation. - #[inline] - pub const fn is_create(self) -> bool { - matches!(self, TxKind::Create) - } - - /// Returns true if the transaction is a contract call. - #[inline] - pub const fn is_call(self) -> bool { - matches!(self, TxKind::Call(_)) - } - - /// Calculates a heuristic for the in-memory size of this object. - #[inline] - pub const fn size(self) -> usize { - std::mem::size_of::() - } -} - -impl Encodable for TxKind { - fn encode(&self, out: &mut dyn BufMut) { - match self { - TxKind::Call(to) => to.encode(out), - TxKind::Create => out.put_u8(EMPTY_STRING_CODE), - } - } - fn length(&self) -> usize { - match self { - TxKind::Call(to) => to.length(), - TxKind::Create => 1, // EMPTY_STRING_CODE is a single byte - } - } -} - -impl Decodable for TxKind { - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - if let Some(&first) = buf.first() { - if first == EMPTY_STRING_CODE { - buf.advance(1); - Ok(TxKind::Create) - } else { - let addr =
::decode(buf)?; - Ok(TxKind::Call(addr)) - } - } else { - Err(alloy_rlp::Error::InputTooShort) - } - } -} From 66cf9945d78b0e45e542687935f85f4793b1f10f Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 16:19:56 +0100 Subject: [PATCH 07/65] refactor: adjust alloy-contract to new provider --- crates/contract/Cargo.toml | 1 + crates/contract/src/call.rs | 83 ++++++++++--------- crates/contract/src/instance.rs | 32 ++++--- crates/contract/src/interface.rs | 2 +- crates/contract/src/lib.rs | 7 -- crates/rpc-types/src/eth/transaction/mod.rs | 1 + .../rpc-types/src/eth/transaction/request.rs | 17 +--- 7 files changed, 68 insertions(+), 75 deletions(-) diff --git a/crates/contract/Cargo.toml b/crates/contract/Cargo.toml index 23bb2f17f2c..315b0847ea3 100644 --- a/crates/contract/Cargo.toml +++ b/crates/contract/Cargo.toml @@ -12,6 +12,7 @@ repository.workspace = true exclude.workspace = true [dependencies] +alloy-network.workspace = true alloy-providers.workspace = true alloy-rpc-types.workspace = true alloy-transport.workspace = true diff --git a/crates/contract/src/call.rs b/crates/contract/src/call.rs index ef5e1b6ac0b..4c225f2dbae 100644 --- a/crates/contract/src/call.rs +++ b/crates/contract/src/call.rs @@ -1,13 +1,10 @@ use crate::{Error, Result}; use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt}; use alloy_json_abi::Function; +use alloy_network::{Network, TransactionBuilder}; use alloy_primitives::{Address, Bytes, U256, U64}; -use alloy_providers::tmp::TempProvider; -use alloy_rpc_types::{ - request::{TransactionInput, TransactionRequest}, - state::StateOverride, - BlockId, TransactionReceipt, -}; +use alloy_providers::Provider; +use alloy_rpc_types::{state::StateOverride, BlockId, TransactionReceipt}; use alloy_sol_types::SolCall; use std::{ future::{Future, IntoFuture}, @@ -17,13 +14,13 @@ use std::{ /// [`CallBuilder`] using a [`SolCall`] type as the call decoder. // NOTE: please avoid changing this type due to its use in the `sol!` macro. -pub type SolCallBuilder = CallBuilder>; +pub type SolCallBuilder = CallBuilder>; /// [`CallBuilder`] using a [`Function`] as the call decoder. -pub type DynCallBuilder

= CallBuilder; +pub type DynCallBuilder = CallBuilder; /// [`CallBuilder`] that does not have a call decoder. -pub type RawCallBuilder

= CallBuilder; +pub type RawCallBuilder = CallBuilder; mod private { pub trait Sealed {} @@ -190,10 +187,10 @@ impl CallDecoder for () { /// [sol]: alloy_sol_types::sol #[derive(Clone)] #[must_use = "call builders do nothing unless you `.call`, `.send`, or `.await` them"] -pub struct CallBuilder { +pub struct CallBuilder { // TODO: this will not work with `send_transaction` and does not differentiate between EIP-1559 // and legacy tx - request: TransactionRequest, + request: N::TransactionRequest, block: Option, state: Option, provider: P, @@ -201,14 +198,14 @@ pub struct CallBuilder { } // See [`ContractInstance`]. -impl DynCallBuilder

{ +impl> DynCallBuilder { pub(crate) fn new_dyn(provider: P, function: &Function, args: &[DynSolValue]) -> Result { Ok(Self::new_inner(provider, function.abi_encode_input(args)?.into(), function.clone())) } /// Clears the decoder, returning a raw call builder. #[inline] - pub fn clear_decoder(self) -> RawCallBuilder

{ + pub fn clear_decoder(self) -> RawCallBuilder { RawCallBuilder { request: self.request, block: self.block, @@ -219,7 +216,7 @@ impl DynCallBuilder

{ } } -impl SolCallBuilder { +impl, C: SolCall> SolCallBuilder { // `sol!` macro constructor, see `#[sol(rpc)]`. Not public API. // NOTE: please avoid changing this function due to its use in the `sol!` macro. #[doc(hidden)] @@ -229,7 +226,7 @@ impl SolCallBuilder { /// Clears the decoder, returning a raw call builder. #[inline] - pub fn clear_decoder(self) -> RawCallBuilder

{ + pub fn clear_decoder(self) -> RawCallBuilder { RawCallBuilder { request: self.request, block: self.block, @@ -240,7 +237,7 @@ impl SolCallBuilder { } } -impl RawCallBuilder

{ +impl> RawCallBuilder { /// Creates a new call builder with the provided provider and ABI encoded input. /// /// Will not decode the output of the call, meaning that [`call`](Self::call) will behave the @@ -251,22 +248,26 @@ impl RawCallBuilder

{ } } -impl CallBuilder { +impl, D: CallDecoder> CallBuilder { fn new_inner(provider: P, input: Bytes, decoder: D) -> Self { - let request = - TransactionRequest { input: TransactionInput::new(input), ..Default::default() }; - Self { request, decoder, provider, block: None, state: None } + Self { + request: ::default().with_input(input), + decoder, + provider, + block: None, + state: None, + } } /// Sets the `from` field in the transaction to the provided value. Defaults to [Address::ZERO]. pub fn from(mut self, from: Address) -> Self { - self.request = self.request.from(from); + self.request.set_from(from); self } /// Sets the `to` field in the transaction to the provided address. pub fn to(mut self, to: Option

) -> Self { - self.request = self.request.to(to); + self.request.set_to(to.into()); self } @@ -277,7 +278,7 @@ impl CallBuilder { /// Sets the `gas` field in the transaction to the provided value pub fn gas(mut self, gas: U256) -> Self { - self.request = self.request.gas_limit(gas); + self.request.set_gas_limit(gas); self } @@ -285,20 +286,19 @@ impl CallBuilder { /// If the internal transaction is an EIP-1559 one, then it sets both /// `max_fee_per_gas` and `max_priority_fee_per_gas` to the same value pub fn gas_price(mut self, gas_price: U256) -> Self { - self.request = self.request.max_fee_per_gas(gas_price); - self.request = self.request.max_priority_fee_per_gas(gas_price); + self.request.set_gas_price(gas_price); self } /// Sets the `value` field in the transaction to the provided value pub fn value(mut self, value: U256) -> Self { - self.request = self.request.value(value); + self.request.set_value(value); self } /// Sets the `nonce` field in the transaction to the provided value pub fn nonce(mut self, nonce: U64) -> Self { - self.request = self.request.nonce(nonce); + self.request.set_nonce(nonce); self } @@ -308,6 +308,8 @@ impl CallBuilder { self } + // todo map function fn(req) -> req + /// Sets the [state override set](https://geth.ethereum.org/docs/rpc/ns-eth#3-object---state-override-set). /// /// # Note @@ -320,12 +322,12 @@ impl CallBuilder { /// Returns the underlying transaction's ABI-encoded data. pub fn calldata(&self) -> &Bytes { - self.request.input.input().expect("set in the constructor") + self.request.input().expect("set in the constructor") } /// Returns the estimated gas cost for the underlying transaction to be executed pub async fn estimate_gas(&self) -> Result { - self.provider.estimate_gas(self.request.clone(), self.block).await.map_err(Into::into) + self.provider.estimate_gas(&self.request, self.block).await.map_err(Into::into) } /// Queries the blockchain via an `eth_call` without submitting a transaction to the network. @@ -344,9 +346,9 @@ impl CallBuilder { /// See [`call`](Self::call) for more information. pub async fn call_raw(&self) -> Result { if let Some(state) = &self.state { - self.provider.call_with_overrides(self.request.clone(), self.block, state.clone()).await + self.provider.call_with_overrides(&self.request, self.block, state.clone()).await } else { - self.provider.call(self.request.clone(), self.block).await + self.provider.call(&self.request, self.block).await } .map_err(Into::into) } @@ -368,7 +370,7 @@ impl CallBuilder { /// Note that the deployment address can be pre-calculated if the `from` address and `nonce` are /// known using [`calculate_create_address`](Self::calculate_create_address). pub async fn deploy(&self) -> Result
{ - if self.request.to.is_some() { + if !self.request.to().is_some_and(|to| to.is_create()) { return Err(Error::NotADeploymentTransaction); } let pending_tx = self.send().await?; @@ -403,9 +405,9 @@ impl CallBuilder { } } -impl CallBuilder<&P, D> { +impl CallBuilder { /// Clones the provider and returns a new builder with the cloned provider. - pub fn with_cloned_provider(self) -> CallBuilder { + pub fn with_cloned_provider(self) -> CallBuilder { CallBuilder { request: self.request, block: self.block, @@ -419,9 +421,10 @@ impl CallBuilder<&P, D> { /// [`CallBuilder`] can be turned into a [`Future`] automatically with `.await`. /// /// Defaults to calling [`CallBuilder::call`]. -impl IntoFuture for CallBuilder +impl IntoFuture for CallBuilder where - P: TempProvider, + N: Network, + P: Provider, D: CallDecoder + Send + Sync, Self: 'static, { @@ -438,7 +441,7 @@ where } } -impl std::fmt::Debug for CallBuilder { +impl std::fmt::Debug for CallBuilder { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("CallBuilder") @@ -456,10 +459,10 @@ mod tests { use super::*; use alloy_node_bindings::{Anvil, AnvilInstance}; use alloy_primitives::{address, b256, bytes, hex}; - use alloy_providers::tmp::{HttpProvider, Provider}; + use alloy_providers::{HttpProvider, Provider}; use alloy_sol_types::sol; - #[test] + /*#[test] fn empty_constructor() { sol! { #[sol(rpc, bytecode = "6942")] @@ -564,5 +567,5 @@ mod tests { let anvil = Anvil::new().spawn(); let provider = Provider::try_from(anvil.endpoint()).unwrap(); (provider, anvil) - } + }*/ } diff --git a/crates/contract/src/instance.rs b/crates/contract/src/instance.rs index e94ee9d9cae..fdcefd85f76 100644 --- a/crates/contract/src/instance.rs +++ b/crates/contract/src/instance.rs @@ -1,25 +1,28 @@ use crate::{CallBuilder, Interface, Result}; use alloy_dyn_abi::DynSolValue; use alloy_json_abi::{Function, JsonAbi}; +use alloy_network::Network; use alloy_primitives::{Address, Selector}; -use alloy_providers::tmp::TempProvider; +use alloy_providers::Provider; +use std::marker::PhantomData; /// A handle to an Ethereum contract at a specific address. /// /// A contract is an abstraction of an executable program on Ethereum. Every deployed contract has /// an address, which is used to connect to it so that it may receive messages (transactions). #[derive(Clone)] -pub struct ContractInstance

{ +pub struct ContractInstance { address: Address, provider: P, interface: Interface, + network: PhantomData, } -impl

ContractInstance

{ +impl ContractInstance { /// Creates a new contract from the provided address, provider, and interface. #[inline] pub const fn new(address: Address, provider: P, interface: Interface) -> Self { - Self { address, provider, interface } + Self { address, provider, interface, network: PhantomData } } /// Returns a reference to the contract's address. @@ -36,7 +39,7 @@ impl

ContractInstance

{ /// Returns a new contract instance at `address`. #[inline] - pub fn at(mut self, address: Address) -> ContractInstance

{ + pub fn at(mut self, address: Address) -> ContractInstance { self.set_address(address); self } @@ -54,25 +57,30 @@ impl

ContractInstance

{ } } -impl ContractInstance<&P> { +impl ContractInstance { /// Clones the provider and returns a new contract instance with the cloned provider. #[inline] - pub fn with_cloned_provider(self) -> ContractInstance

{ + pub fn with_cloned_provider(self) -> ContractInstance { ContractInstance { address: self.address, provider: self.provider.clone(), interface: self.interface, + network: PhantomData, } } } -impl ContractInstance

{ +impl> ContractInstance { /// Returns a transaction builder for the provided function name. /// /// If there are multiple functions with the same name due to overloading, consider using /// the [`ContractInstance::function_from_selector`] method instead, since this will use the /// first match. - pub fn function(&self, name: &str, args: &[DynSolValue]) -> Result> { + pub fn function( + &self, + name: &str, + args: &[DynSolValue], + ) -> Result> { let function = self.interface.get_from_name(name)?; CallBuilder::new_dyn(&self.provider, function, args) } @@ -82,13 +90,13 @@ impl ContractInstance

{ &self, selector: &Selector, args: &[DynSolValue], - ) -> Result> { + ) -> Result> { let function = self.interface.get_from_selector(selector)?; CallBuilder::new_dyn(&self.provider, function, args) } } -impl

std::ops::Deref for ContractInstance

{ +impl std::ops::Deref for ContractInstance { type Target = Interface; fn deref(&self) -> &Self::Target { @@ -96,7 +104,7 @@ impl

std::ops::Deref for ContractInstance

{ } } -impl

std::fmt::Debug for ContractInstance

{ +impl std::fmt::Debug for ContractInstance { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ContractInstance").field("address", &self.address).finish() } diff --git a/crates/contract/src/interface.rs b/crates/contract/src/interface.rs index cf719d0795c..97bdd3469c3 100644 --- a/crates/contract/src/interface.rs +++ b/crates/contract/src/interface.rs @@ -116,7 +116,7 @@ impl Interface { } /// Create a [`ContractInstance`] from this ABI for a contract at the given address. - pub const fn connect

(self, address: Address, provider: P) -> ContractInstance

{ + pub const fn connect(self, address: Address, provider: P) -> ContractInstance { ContractInstance::new(address, provider, self) } } diff --git a/crates/contract/src/lib.rs b/crates/contract/src/lib.rs index 64561fdce9b..478d320fb89 100644 --- a/crates/contract/src/lib.rs +++ b/crates/contract/src/lib.rs @@ -29,10 +29,3 @@ pub use instance::*; mod call; pub use call::*; - -// Not public API. -// NOTE: please avoid changing the API of this module due to its use in the `sol!` macro. -#[doc(hidden)] -pub mod private { - pub use alloy_providers::tmp::TempProvider as Provider; -} diff --git a/crates/rpc-types/src/eth/transaction/mod.rs b/crates/rpc-types/src/eth/transaction/mod.rs index 52e6e47ae87..acef878b249 100644 --- a/crates/rpc-types/src/eth/transaction/mod.rs +++ b/crates/rpc-types/src/eth/transaction/mod.rs @@ -7,6 +7,7 @@ pub use blob::BlobTransactionSidecar; pub use common::TransactionInfo; pub use optimism::OptimismTransactionReceiptFields; pub use receipt::TransactionReceipt; +pub use request::{TransactionInput, TransactionRequest}; use serde::{Deserialize, Serialize}; pub use signature::{Parity, Signature}; diff --git a/crates/rpc-types/src/eth/transaction/request.rs b/crates/rpc-types/src/eth/transaction/request.rs index a15f125e548..77d2978f33d 100644 --- a/crates/rpc-types/src/eth/transaction/request.rs +++ b/crates/rpc-types/src/eth/transaction/request.rs @@ -2,7 +2,7 @@ use std::hash::Hash; use crate::{eth::transaction::AccessList, other::OtherFields, BlobTransactionSidecar}; -use alloy_primitives::{Address, Bytes, B256, U256, U64, U8}; +use alloy_primitives::{Address, Bytes, ChainId, B256, U256, U64, U8}; use serde::{Deserialize, Serialize}; /// Represents _all_ transaction requests to/from RPC. @@ -35,7 +35,7 @@ pub struct TransactionRequest { /// The nonce of the transaction. pub nonce: Option, /// The chain ID for the transaction. - pub chain_id: Option, + pub chain_id: Option, /// An EIP-2930 access list, which lowers cost for accessing accounts and storages in the list. See [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) for more information. #[serde(default)] pub access_list: Option, @@ -155,19 +155,6 @@ impl TransactionRequest { self.transaction_type = Some(U8::from(transaction_type)); self } - - /// Calculates the address that will be created by the transaction, if any. - /// - /// Returns `None` if the transaction is not a contract creation (the `to` field is set), or if - /// the `from` or `nonce` fields are not set. - pub fn calculate_create_address(&self) -> Option

{ - if self.to.is_some() { - return None; - } - let from = self.from.as_ref()?; - let nonce = self.nonce?; - Some(from.create(nonce.to())) - } } /// Helper type that supports both `data` and `input` fields that map to transaction input data. From bc5f08401a0dcddcdedaabd2efb23bfb5ab58de1 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 16:20:03 +0100 Subject: [PATCH 08/65] refactor: adjust signers to new signer traits --- crates/signer-aws/src/signer.rs | 18 ++--- crates/signer-gcp/src/signer.rs | 4 +- crates/signer-ledger/Cargo.toml | 2 + crates/signer-ledger/src/signer.rs | 55 ++++++++----- crates/signer-trezor/src/signer.rs | 47 ++++++----- crates/signer-trezor/src/types.rs | 6 ++ crates/signer/Cargo.toml | 13 ++- crates/signer/src/lib.rs | 2 +- crates/signer/src/signer.rs | 101 ++++-------------------- crates/signer/src/wallet/mod.rs | 4 +- crates/signer/src/wallet/private_key.rs | 82 +------------------ 11 files changed, 117 insertions(+), 217 deletions(-) diff --git a/crates/signer-aws/src/signer.rs b/crates/signer-aws/src/signer.rs index 9cfc7154600..cfad75676c6 100644 --- a/crates/signer-aws/src/signer.rs +++ b/crates/signer-aws/src/signer.rs @@ -96,8 +96,8 @@ pub enum AwsSignerError { #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl Signer for AwsSigner { #[instrument(err)] - #[allow(clippy::blocks_in_conditions)] // instrument on async fn - async fn sign_hash(&self, hash: B256) -> Result { + #[allow(clippy::blocks_in_conditions)] // tracing::instrument on async fn + async fn sign_hash(&self, hash: &B256) -> Result { self.sign_digest_inner(hash).await.map_err(alloy_signer::Error::other) } @@ -148,19 +148,19 @@ impl AwsSigner { pub async fn sign_digest_with_key( &self, key_id: String, - digest: B256, + digest: &B256, ) -> Result { request_sign_digest(&self.kms, key_id, digest).await.and_then(decode_signature) } /// Sign a digest with this signer's key - pub async fn sign_digest(&self, digest: B256) -> Result { + pub async fn sign_digest(&self, digest: &B256) -> Result { self.sign_digest_with_key(self.key_id.clone(), digest).await } /// Sign a digest with this signer's key and applies EIP-155. #[instrument(err, skip(digest), fields(digest = %hex::encode(digest)))] - async fn sign_digest_inner(&self, digest: B256) -> Result { + async fn sign_digest_inner(&self, digest: &B256) -> Result { let sig = self.sign_digest(digest).await?; let mut sig = sig_from_digest_bytes_trial_recovery(sig, digest, &self.pubkey); if let Some(chain_id) = self.chain_id { @@ -182,7 +182,7 @@ async fn request_get_pubkey( async fn request_sign_digest( kms: &Client, key_id: String, - digest: B256, + digest: &B256, ) -> Result { kms.sign() .key_id(key_id) @@ -212,7 +212,7 @@ fn decode_signature(resp: SignOutput) -> Result Signature { let signature = Signature::from_signature_and_parity(sig, false).unwrap(); @@ -229,8 +229,8 @@ fn sig_from_digest_bytes_trial_recovery( } /// Makes a trial recovery to check whether an RSig corresponds to a known `VerifyingKey`. -fn check_candidate(signature: &Signature, hash: B256, pubkey: &VerifyingKey) -> bool { - signature.recover_from_prehash(&hash).map(|key| key == *pubkey).unwrap_or(false) +fn check_candidate(signature: &Signature, hash: &B256, pubkey: &VerifyingKey) -> bool { + signature.recover_from_prehash(hash).map(|key| key == *pubkey).unwrap_or(false) } #[cfg(test)] diff --git a/crates/signer-gcp/src/signer.rs b/crates/signer-gcp/src/signer.rs index 0758c93a0bb..cefbdf45051 100644 --- a/crates/signer-gcp/src/signer.rs +++ b/crates/signer-gcp/src/signer.rs @@ -149,8 +149,8 @@ pub enum GcpSignerError { impl Signer for GcpSigner { #[instrument(err)] #[allow(clippy::blocks_in_conditions)] - async fn sign_hash(&self, hash: B256) -> Result { - self.sign_digest_inner(&hash).await.map_err(alloy_signer::Error::other) + async fn sign_hash(&self, hash: &B256) -> Result { + self.sign_digest_inner(hash).await.map_err(alloy_signer::Error::other) } #[inline] diff --git a/crates/signer-ledger/Cargo.toml b/crates/signer-ledger/Cargo.toml index e83580716b1..c9727479b63 100644 --- a/crates/signer-ledger/Cargo.toml +++ b/crates/signer-ledger/Cargo.toml @@ -12,6 +12,7 @@ repository.workspace = true exclude.workspace = true [dependencies] +alloy-consensus.workspace = true alloy-primitives.workspace = true alloy-signer.workspace = true @@ -24,6 +25,7 @@ tracing.workspace = true # eip712 alloy-sol-types = { workspace = true, optional = true } +alloy-network.workspace = true [dev-dependencies] alloy-consensus.workspace = true diff --git a/crates/signer-ledger/src/signer.rs b/crates/signer-ledger/src/signer.rs index c31910adac4..5fb38dab555 100644 --- a/crates/signer-ledger/src/signer.rs +++ b/crates/signer-ledger/src/signer.rs @@ -1,8 +1,9 @@ //! Ledger Ethereum app wrapper. use crate::types::{DerivationType, LedgerError, INS, P1, P1_FIRST, P2}; +use alloy_consensus::SignableTransaction; use alloy_primitives::{hex, Address, ChainId, B256}; -use alloy_signer::{Result, SignableTx, Signature, Signer, TransactionExt}; +use alloy_signer::{Result, Signature, Signer}; use async_trait::async_trait; use coins_ledger::{ common::{APDUCommand, APDUData}, @@ -27,10 +28,37 @@ pub struct LedgerSigner { pub(crate) address: Address, } +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl alloy_network::TxSigner for LedgerSigner { + #[inline] + async fn sign_transaction( + &self, + tx: &mut dyn SignableTransaction, + ) -> Result { + let chain_id = match (self.chain_id(), tx.chain_id()) { + (Some(signer), Some(tx)) if signer != tx => { + return Err(alloy_signer::Error::TransactionChainIdMismatch { signer, tx }) + } + (Some(signer), _) => Some(signer), + (None, Some(tx)) => Some(tx), + _ => None, + }; + + let rlp = tx.encoded_for_signing(); + let mut sig = self.sign_tx_rlp(&rlp).await.map_err(alloy_signer::Error::other)?; + + if let Some(chain_id) = chain_id.or_else(|| tx.chain_id()) { + sig = sig.with_chain_id(chain_id); + } + Ok(sig) + } +} + #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl Signer for LedgerSigner { - async fn sign_hash(&self, _hash: B256) -> Result { + async fn sign_hash(&self, _hash: &B256) -> Result { Err(alloy_signer::Error::UnsupportedOperation( alloy_signer::UnsupportedSignerOperation::SignHash, )) @@ -47,20 +75,6 @@ impl Signer for LedgerSigner { .map_err(alloy_signer::Error::other) } - #[inline] - async fn sign_transaction(&self, tx: &mut SignableTx) -> Result { - let chain_id = self.chain_id(); - if let Some(chain_id) = chain_id { - tx.set_chain_id_checked(chain_id)?; - } - let rlp = tx.encoded_for_signing(); - let mut sig = self.sign_tx_rlp(&rlp).await.map_err(alloy_signer::Error::other)?; - if let Some(chain_id) = chain_id.or_else(|| tx.chain_id()) { - sig = sig.with_chain_id(chain_id); - } - Ok(sig) - } - #[cfg(feature = "eip712")] #[inline] async fn sign_typed_data( @@ -285,9 +299,10 @@ mod tests { use super::*; use alloy_primitives::{address, bytes, U256}; use alloy_rlp::Decodable; - use alloy_signer::Transaction; use std::sync::OnceLock; + use alloy_network::TxSigner; + const DTYPE: DerivationType = DerivationType::LedgerLive(0); fn my_address() -> Address { @@ -334,7 +349,9 @@ mod tests { nonce: 5, gas_price: 400e9 as u128, gas_limit: 1000000, - to: alloy_consensus::TxKind::Call(address!("2ed7afa17473e17ac59908f088b4371d28585476")), + to: alloy_primitives::TxKind::Call(address!( + "2ed7afa17473e17ac59908f088b4371d28585476" + )), // TODO: this fails for some reason with 6a80 APDU_CODE_BAD_KEY_HANDLE // approve uni v2 router 0xff // input: bytes!("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), @@ -376,7 +393,7 @@ mod tests { test_sign_tx_generic(&mut tx).await; } - async fn test_sign_tx_generic(tx: &mut SignableTx) { + async fn test_sign_tx_generic(tx: &mut dyn SignableTransaction) { let sighash = tx.signature_hash(); let ledger = init_ledger().await; let sig = match ledger.sign_transaction(tx).await { diff --git a/crates/signer-trezor/src/signer.rs b/crates/signer-trezor/src/signer.rs index 200071f40fa..27a39daae6d 100644 --- a/crates/signer-trezor/src/signer.rs +++ b/crates/signer-trezor/src/signer.rs @@ -1,8 +1,7 @@ use super::types::{DerivationType, TrezorError}; -use alloy_consensus::TxEip1559; -use alloy_network::{Transaction, TxKind}; -use alloy_primitives::{hex, Address, ChainId, Parity, B256, U256}; -use alloy_signer::{Result, SignableTx, Signature, Signer, TransactionExt}; +use alloy_consensus::{SignableTransaction, TxEip1559}; +use alloy_primitives::{hex, Address, ChainId, Parity, TxKind, B256, U256}; +use alloy_signer::{Result, Signature, Signer}; use async_trait::async_trait; use std::fmt; use trezor_client::client::Trezor; @@ -38,7 +37,7 @@ impl fmt::Debug for TrezorSigner { #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl Signer for TrezorSigner { #[inline] - async fn sign_hash(&self, _hash: B256) -> Result { + async fn sign_hash(&self, _hash: &B256) -> Result { Err(alloy_signer::Error::UnsupportedOperation( alloy_signer::UnsupportedSignerOperation::SignHash, )) @@ -49,18 +48,6 @@ impl Signer for TrezorSigner { self.sign_message_inner(message).await.map_err(alloy_signer::Error::other) } - #[inline] - async fn sign_transaction(&self, tx: &mut SignableTx) -> Result { - if let Some(chain_id) = self.chain_id { - tx.set_chain_id_checked(chain_id)?; - } - let mut sig = self.sign_tx_inner(tx).await.map_err(alloy_signer::Error::other)?; - if let Some(chain_id) = self.chain_id.or_else(|| tx.chain_id()) { - sig = sig.with_chain_id(chain_id); - } - Ok(sig) - } - #[inline] fn address(&self) -> Address { self.address @@ -77,6 +64,30 @@ impl Signer for TrezorSigner { } } +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl alloy_network::TxSigner for TrezorSigner { + #[inline] + async fn sign_transaction( + &self, + tx: &mut dyn SignableTransaction, + ) -> Result { + let chain_id = match (self.chain_id(), tx.chain_id()) { + (Some(id), None) | (None, Some(id)) => id, + (Some(signer), Some(tx)) if signer != tx => { + return Err(alloy_signer::Error::TransactionChainIdMismatch { signer, tx }); + } + _ => { + return Err(TrezorError::MissingChainId.into()); + } + }; + + let mut sig = self.sign_tx_inner(tx).await.map_err(alloy_signer::Error::other)?; + sig = sig.with_chain_id(chain_id); + Ok(sig) + } +} + impl TrezorSigner { /// Instantiates a new Trezor signer. #[instrument(ret)] @@ -157,7 +168,7 @@ impl TrezorSigner { /// Does not apply EIP-155. async fn sign_tx_inner( &self, - tx: &dyn Transaction, + tx: &dyn SignableTransaction, ) -> Result { let mut client = self.get_client()?; let path = Self::convert_path(&self.derivation); diff --git a/crates/signer-trezor/src/types.rs b/crates/signer-trezor/src/types.rs index 7ab3315b737..58df0083d79 100644 --- a/crates/signer-trezor/src/types.rs +++ b/crates/signer-trezor/src/types.rs @@ -53,3 +53,9 @@ pub enum TrezorError { #[error("could not retrieve device features")] Features, } + +impl Into for TrezorError { + fn into(self) -> alloy_signer::Error { + alloy_signer::Error::other(self) + } +} diff --git a/crates/signer/Cargo.toml b/crates/signer/Cargo.toml index 5e075a1b49b..62d87ad7276 100644 --- a/crates/signer/Cargo.toml +++ b/crates/signer/Cargo.toml @@ -12,7 +12,6 @@ repository.workspace = true exclude.workspace = true [dependencies] -alloy-network.workspace = true alloy-primitives = { workspace = true, features = ["k256"] } auto_impl.workspace = true @@ -33,9 +32,15 @@ coins-bip32 = { version = "0.8.7", default-features = false, optional = true } coins-bip39 = { version = "0.8.7", default-features = false, optional = true } # yubi -yubihsm = { version = "0.42", features = ["secp256k1", "http", "usb"], optional = true } +yubihsm = { version = "0.42", features = [ + "secp256k1", + "http", + "usb", +], optional = true } [dev-dependencies] +# todo: circular dep v_v +# alloy-consensus needs alloy-signer for the SignableTransaction trait alloy-consensus.workspace = true assert_matches.workspace = true serde_json.workspace = true @@ -44,7 +49,9 @@ tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } # need to enable features for tests yubihsm = { version = "0.42", features = ["mockhsm"] } -coins-bip39 = { version = "0.8.7", default-features = false, features = ["english"] } +coins-bip39 = { version = "0.8.7", default-features = false, features = [ + "english", +] } [features] eip712 = ["dep:alloy-sol-types"] diff --git a/crates/signer/src/lib.rs b/crates/signer/src/lib.rs index 2f3c65151c7..df681f74b56 100644 --- a/crates/signer/src/lib.rs +++ b/crates/signer/src/lib.rs @@ -19,7 +19,7 @@ mod error; pub use error::{Error, Result, UnsupportedSignerOperation}; mod signer; -pub use signer::{SignableTx, Signer, SignerSync, Transaction, TransactionExt}; +pub use signer::{Signer, SignerSync}; mod wallet; #[cfg(feature = "mnemonic")] diff --git a/crates/signer/src/signer.rs b/crates/signer/src/signer.rs index cf290bfbf6d..9b633949fd9 100644 --- a/crates/signer/src/signer.rs +++ b/crates/signer/src/signer.rs @@ -1,4 +1,5 @@ use crate::Result; + use alloy_primitives::{eip191_hash_message, Address, ChainId, Signature, B256}; use async_trait::async_trait; use auto_impl::auto_impl; @@ -6,38 +7,6 @@ use auto_impl::auto_impl; #[cfg(feature = "eip712")] use alloy_sol_types::{Eip712Domain, SolStruct}; -pub use alloy_network::Transaction; - -/// A signable transaction. -pub type SignableTx = dyn Transaction; - -/// Extension trait for utilities for signable transactions. -/// -/// This trait is implemented for all types that implement [`Transaction`] with [`Signature`] as the -/// signature associated type. -pub trait TransactionExt: Transaction { - /// Set `chain_id` if it is not already set. Checks that the provided `chain_id` matches the - /// existing `chain_id` if it is already set. - fn set_chain_id_checked(&mut self, chain_id: ChainId) -> Result<()> { - match self.chain_id() { - Some(tx_chain_id) => { - if tx_chain_id != chain_id { - return Err(crate::Error::TransactionChainIdMismatch { - signer: chain_id, - tx: tx_chain_id, - }); - } - } - None => { - self.set_chain_id(chain_id); - } - } - Ok(()) - } -} - -impl> TransactionExt for T {} - /// Asynchronous Ethereum signer. /// /// All provided implementations rely on [`sign_hash`](Signer::sign_hash). A signer may not always @@ -58,30 +27,16 @@ impl> TransactionExt for T {} #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[auto_impl(&mut, Box)] -pub trait Signer: Send + Sync { +pub trait Signer: Send + Sync { /// Signs the given hash. - async fn sign_hash(&self, hash: B256) -> Result; + async fn sign_hash(&self, hash: &B256) -> Result; /// Signs the hash of the provided message after prefixing it, as specified in [EIP-191]. /// /// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191 #[inline] - async fn sign_message(&self, message: &[u8]) -> Result { - self.sign_hash(eip191_hash_message(message)).await - } - - /// Signs the transaction. - #[inline] - async fn sign_transaction(&self, tx: &mut SignableTx) -> Result { - let chain_id = self.chain_id(); - if let Some(chain_id) = chain_id { - tx.set_chain_id_checked(chain_id)?; - } - let mut sig = self.sign_hash(tx.signature_hash()).await?; - if let Some(chain_id) = chain_id.or_else(|| tx.chain_id()) { - sig = sig.with_chain_id(chain_id); - } - Ok(sig) + async fn sign_message(&self, message: &[u8]) -> Result { + self.sign_hash(&eip191_hash_message(message)).await } /// Encodes and signs the typed data according to [EIP-712]. @@ -93,11 +48,11 @@ pub trait Signer: Send + Sync { &self, payload: &T, domain: &Eip712Domain, - ) -> Result + ) -> Result where Self: Sized, { - self.sign_hash(payload.eip712_signing_hash(domain)).await + self.sign_hash(&payload.eip712_signing_hash(domain)).await } /// Returns the signer's Ethereum Address. @@ -141,30 +96,16 @@ pub trait Signer: Send + Sync { /// /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 #[auto_impl(&, &mut, Box, Rc, Arc)] -pub trait SignerSync { +pub trait SignerSync { /// Signs the given hash. - fn sign_hash_sync(&self, hash: B256) -> Result; + fn sign_hash_sync(&self, hash: &B256) -> Result; /// Signs the hash of the provided message after prefixing it, as specified in [EIP-191]. /// /// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191 #[inline] - fn sign_message_sync(&self, message: &[u8]) -> Result { - self.sign_hash_sync(eip191_hash_message(message)) - } - - /// Signs the transaction. - #[inline] - fn sign_transaction_sync(&self, tx: &mut SignableTx) -> Result { - let chain_id = self.chain_id_sync(); - if let Some(chain_id) = chain_id { - tx.set_chain_id_checked(chain_id)?; - } - let mut sig = self.sign_hash_sync(tx.signature_hash())?; - if let Some(chain_id) = chain_id.or_else(|| tx.chain_id()) { - sig = sig.with_chain_id(chain_id); - } - Ok(sig) + fn sign_message_sync(&self, message: &[u8]) -> Result { + self.sign_hash_sync(&eip191_hash_message(message)) } /// Encodes and signs the typed data according to [EIP-712]. @@ -172,15 +113,11 @@ pub trait SignerSync { /// [EIP-712]: https://eips.ethereum.org/EIPS/eip-712 #[cfg(feature = "eip712")] #[inline] - fn sign_typed_data_sync( - &self, - payload: &T, - domain: &Eip712Domain, - ) -> Result + fn sign_typed_data_sync(&self, payload: &T, domain: &Eip712Domain) -> Result where Self: Sized, { - self.sign_hash_sync(payload.eip712_signing_hash(domain)) + self.sign_hash_sync(&payload.eip712_signing_hash(domain)) } /// Returns the signer's chain ID. @@ -222,7 +159,7 @@ mod tests { async fn test_unsized_unimplemented_signer(s: &S) { assert_matches!( - s.sign_hash(B256::ZERO).await, + s.sign_hash(&B256::ZERO).await, Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash)) ); @@ -230,13 +167,11 @@ mod tests { s.sign_message(&[]).await, Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash)) ); - - assert!(s.sign_transaction(&mut alloy_consensus::TxLegacy::default()).await.is_err()); } fn test_unsized_unimplemented_signer_sync(s: &S) { assert_matches!( - s.sign_hash_sync(B256::ZERO), + s.sign_hash_sync(&B256::ZERO), Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash)) ); @@ -244,8 +179,6 @@ mod tests { s.sign_message_sync(&[]), Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash)) ); - - assert!(s.sign_transaction_sync(&mut alloy_consensus::TxLegacy::default()).is_err()); } struct UnimplementedSigner; @@ -253,7 +186,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl Signer for UnimplementedSigner { - async fn sign_hash(&self, _hash: B256) -> Result { + async fn sign_hash(&self, _hash: &B256) -> Result { Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash)) } @@ -269,7 +202,7 @@ mod tests { } impl SignerSync for UnimplementedSigner { - fn sign_hash_sync(&self, _hash: B256) -> Result { + fn sign_hash_sync(&self, _hash: &B256) -> Result { Err(Error::UnsupportedOperation(UnsupportedSignerOperation::SignHash)) } diff --git a/crates/signer/src/wallet/mod.rs b/crates/signer/src/wallet/mod.rs index 46b7af6de55..3845b1b9bab 100644 --- a/crates/signer/src/wallet/mod.rs +++ b/crates/signer/src/wallet/mod.rs @@ -64,7 +64,7 @@ pub struct Wallet { #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl + Send + Sync> Signer for Wallet { #[inline] - async fn sign_hash(&self, hash: B256) -> Result { + async fn sign_hash(&self, hash: &B256) -> Result { self.sign_hash_sync(hash) } @@ -86,7 +86,7 @@ impl + Send + Sync> Signer for impl> SignerSync for Wallet { #[inline] - fn sign_hash_sync(&self, hash: B256) -> Result { + fn sign_hash_sync(&self, hash: &B256) -> Result { let (recoverable_sig, recovery_id) = self.signer.sign_prehash(hash.as_ref())?; let mut sig = Signature::from_signature_and_parity(recoverable_sig, recovery_id)?; if let Some(chain_id) = self.chain_id { diff --git a/crates/signer/src/wallet/private_key.rs b/crates/signer/src/wallet/private_key.rs index 4fa52658836..3e5f8fd51ab 100644 --- a/crates/signer/src/wallet/private_key.rs +++ b/crates/signer/src/wallet/private_key.rs @@ -168,9 +168,8 @@ impl FromStr for Wallet { #[cfg(test)] mod tests { use super::*; - use crate::{LocalWallet, Result, SignableTx, Signer, SignerSync}; - use alloy_consensus::TxLegacy; - use alloy_primitives::{address, b256, ChainId, Signature, U256}; + use crate::{LocalWallet, SignerSync}; + use alloy_primitives::{address, b256}; #[cfg(feature = "keystore")] use tempfile::tempdir; @@ -258,81 +257,6 @@ mod tests { assert_eq!(recovered2, address); } - #[tokio::test] - async fn signs_tx() { - async fn sign_tx_test(tx: &mut TxLegacy, chain_id: Option) -> Result { - let mut before = tx.clone(); - let sig = sign_dyn_tx_test(tx, chain_id).await?; - if let Some(chain_id) = chain_id { - assert_eq!(tx.chain_id, Some(chain_id), "chain ID was not set"); - before.chain_id = Some(chain_id); - } - assert_eq!(*tx, before); - Ok(sig) - } - - async fn sign_dyn_tx_test( - tx: &mut SignableTx, - chain_id: Option, - ) -> Result { - let mut wallet: Wallet = - "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318".parse().unwrap(); - wallet.set_chain_id(chain_id); - - let sig = wallet.sign_transaction_sync(tx)?; - let sighash = tx.signature_hash(); - assert_eq!(sig.recover_address_from_prehash(&sighash).unwrap(), wallet.address); - - let sig_async = wallet.sign_transaction(tx).await.unwrap(); - assert_eq!(sig_async, sig); - - Ok(sig) - } - - // retrieved test vector from: - // https://web3js.readthedocs.io/en/v1.2.0/web3-eth-accounts.html#eth-accounts-signtransaction - let mut tx = TxLegacy { - to: alloy_consensus::TxKind::Call(address!("F0109fC8DF283027b6285cc889F5aA624EaC1F55")), - value: U256::from(1_000_000_000), - gas_limit: 2_000_000, - nonce: 0, - gas_price: 21_000_000_000, - input: Default::default(), - chain_id: None, - }; - let sig_none = sign_tx_test(&mut tx, None).await.unwrap(); - - tx.chain_id = Some(1); - let sig_1 = sign_tx_test(&mut tx, None).await.unwrap(); - let expected = "c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa6825".parse().unwrap(); - assert_eq!(sig_1, expected); - assert_ne!(sig_1, sig_none); - - tx.chain_id = Some(2); - let sig_2 = sign_tx_test(&mut tx, None).await.unwrap(); - assert_ne!(sig_2, sig_1); - assert_ne!(sig_2, sig_none); - - // Sets chain ID. - tx.chain_id = None; - let sig_none_none = sign_tx_test(&mut tx, None).await.unwrap(); - assert_eq!(sig_none_none, sig_none); - - tx.chain_id = None; - let sig_none_1 = sign_tx_test(&mut tx, Some(1)).await.unwrap(); - assert_eq!(sig_none_1, sig_1); - - tx.chain_id = None; - let sig_none_2 = sign_tx_test(&mut tx, Some(2)).await.unwrap(); - assert_eq!(sig_none_2, sig_2); - - // Errors on mismatch. - tx.chain_id = Some(2); - let error = sign_tx_test(&mut tx, Some(1)).await.unwrap_err(); - let expected_error = crate::Error::TransactionChainIdMismatch { signer: 1, tx: 2 }; - assert_eq!(error.to_string(), expected_error.to_string()); - } - #[test] #[cfg(feature = "eip712")] fn typed_data() { @@ -371,7 +295,7 @@ mod tests { let hash = foo_bar.eip712_signing_hash(&domain); let sig = wallet.sign_typed_data_sync(&foo_bar, &domain).unwrap(); assert_eq!(sig.recover_address_from_prehash(&hash).unwrap(), wallet.address()); - assert_eq!(wallet.sign_hash_sync(hash).unwrap(), sig); + assert_eq!(wallet.sign_hash_sync(&hash).unwrap(), sig); } #[test] From 6e4e7d2c23d4451ed07e1e8f7350fe7a0fde56f4 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 16:25:00 +0100 Subject: [PATCH 09/65] chore: remove unused imports --- crates/consensus/src/transaction/envelope.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/consensus/src/transaction/envelope.rs b/crates/consensus/src/transaction/envelope.rs index f548aa93ec5..0160978d039 100644 --- a/crates/consensus/src/transaction/envelope.rs +++ b/crates/consensus/src/transaction/envelope.rs @@ -1,6 +1,4 @@ -use crate::{ - Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip4844WithSidecar, TxLegacy, -}; +use crate::{Signed, TxEip1559, TxEip2930, TxEip4844Variant, TxLegacy}; use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718}; use alloy_rlp::{length_of_length, Decodable, Encodable}; From b809324c98713a792baac76d203aa7d4c7bae9a2 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 16:31:33 +0100 Subject: [PATCH 10/65] feat: EIP-1559 gas methods in builder --- crates/network/src/ethereum/mod.rs | 20 +++++++++++++-- crates/network/src/transaction/builder.rs | 30 ++++++++++++++++++++--- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/crates/network/src/ethereum/mod.rs b/crates/network/src/ethereum/mod.rs index 8899488b161..3c6fbe56ee6 100644 --- a/crates/network/src/ethereum/mod.rs +++ b/crates/network/src/ethereum/mod.rs @@ -80,11 +80,27 @@ impl TransactionBuilder for alloy_rpc_types::TransactionRequest { } fn gas_price(&self) -> Option { - todo!() + self.gas_price } fn set_gas_price(&mut self, gas_price: U256) { - todo!() + self.gas_price = Some(gas_price); + } + + fn max_fee_per_gas(&self) -> Option { + self.max_fee_per_gas + } + + fn set_max_fee_per_gas(&mut self, max_fee_per_gas: U256) { + self.max_fee_per_gas = Some(max_fee_per_gas); + } + + fn max_priority_fee_per_gas(&self) -> Option { + self.max_priority_fee_per_gas + } + + fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: U256) { + self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas); } fn gas_limit(&self) -> Option { diff --git a/crates/network/src/transaction/builder.rs b/crates/network/src/transaction/builder.rs index 217eac1e04f..c77f3444367 100644 --- a/crates/network/src/transaction/builder.rs +++ b/crates/network/src/transaction/builder.rs @@ -128,18 +128,42 @@ pub trait TransactionBuilder: Default + Sized + Send + Sync + 'stati self } - /// Get the gas price for the transaction. + /// Get the legacy gas price for the transaction. fn gas_price(&self) -> Option; - /// Set the gas price for the transaction. + /// Set the legacy gas price for the transaction. fn set_gas_price(&mut self, gas_price: U256); - /// Builder-pattern method for setting the gas price. + /// Builder-pattern method for setting the legacy gas price. fn with_gas_price(mut self, gas_price: U256) -> Self { self.set_gas_price(gas_price); self } + /// Get the max fee per gas for the transaction. + fn max_fee_per_gas(&self) -> Option; + + /// Set the max fee per gas for the transaction. + fn set_max_fee_per_gas(&mut self, max_fee_per_gas: U256); + + /// Builder-pattern method for setting max fee per gas . + fn with_max_fee_per_gas(mut self, max_fee_per_gas: U256) -> Self { + self.set_max_fee_per_gas(max_fee_per_gas); + self + } + + /// Get the max priority fee per gas for the transaction. + fn max_priority_fee_per_gas(&self) -> Option; + + /// Set the max priority fee per gas for the transaction. + fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: U256); + + /// Builder-pattern method for setting max priority fee per gas. + fn with_max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: U256) -> Self { + self.set_max_priority_fee_per_gas(max_priority_fee_per_gas); + self + } + /// Get the gas limit for the transaction. fn gas_limit(&self) -> Option; From 98b7c67492648b452652fed868262c5883f8cce6 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 16:32:50 +0100 Subject: [PATCH 11/65] feat: EIP-4844 gas methods in builder --- crates/network/src/ethereum/mod.rs | 8 ++++++++ crates/network/src/transaction/builder.rs | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/crates/network/src/ethereum/mod.rs b/crates/network/src/ethereum/mod.rs index 3c6fbe56ee6..52d11ccef94 100644 --- a/crates/network/src/ethereum/mod.rs +++ b/crates/network/src/ethereum/mod.rs @@ -103,6 +103,14 @@ impl TransactionBuilder for alloy_rpc_types::TransactionRequest { self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas); } + fn max_fee_per_blob_gas(&self) -> Option { + self.max_fee_per_blob_gas + } + + fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: U256) { + self.max_fee_per_blob_gas = Some(max_fee_per_blob_gas) + } + fn gas_limit(&self) -> Option { self.gas } diff --git a/crates/network/src/transaction/builder.rs b/crates/network/src/transaction/builder.rs index c77f3444367..7db88a7ff0b 100644 --- a/crates/network/src/transaction/builder.rs +++ b/crates/network/src/transaction/builder.rs @@ -164,6 +164,18 @@ pub trait TransactionBuilder: Default + Sized + Send + Sync + 'stati self } + /// Get the max fee per blob gas for the transaction. + fn max_fee_per_blob_gas(&self) -> Option; + + /// Set the max fee per blob gas for the transaction. + fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: U256); + + /// Builder-pattern method for setting max fee per blob gas . + fn with_max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: U256) -> Self { + self.set_max_fee_per_blob_gas(max_fee_per_blob_gas); + self + } + /// Get the gas limit for the transaction. fn gas_limit(&self) -> Option; From 13e9a3b4eb60ad3ac1766e5e417e087a70e31106 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 16:59:39 +0100 Subject: [PATCH 12/65] feat: impl ethereum builder --- crates/network/src/ethereum/mod.rs | 144 +++++++++++++++++++++- crates/network/src/transaction/builder.rs | 4 +- crates/network/src/transaction/signer.rs | 2 +- crates/rpc-types/Cargo.toml | 16 ++- 4 files changed, 156 insertions(+), 10 deletions(-) diff --git a/crates/network/src/ethereum/mod.rs b/crates/network/src/ethereum/mod.rs index 52d11ccef94..ceea5642ea4 100644 --- a/crates/network/src/ethereum/mod.rs +++ b/crates/network/src/ethereum/mod.rs @@ -1,8 +1,11 @@ -use crate::{BuilderResult, Network, NetworkSigner, TransactionBuilder}; +use crate::{BuilderResult, Network, NetworkSigner, TransactionBuilder, TransactionBuilderError}; +use alloy_consensus::{TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxLegacy}; +use alloy_primitives::{Address, TxKind, U256, U64}; +use alloy_rpc_types::request::TransactionRequest; +use async_trait::async_trait; mod receipt; mod signer; -use alloy_primitives::{Address, TxKind, U256, U64}; pub use signer::EthereumSigner; /// Types for a mainnet-like Ethereum network. @@ -27,6 +30,7 @@ impl Network for Ethereum { type HeaderResponse = alloy_rpc_types::Header; } +#[async_trait] impl TransactionBuilder for alloy_rpc_types::TransactionRequest { fn chain_id(&self) -> Option { self.chain_id @@ -120,13 +124,143 @@ impl TransactionBuilder for alloy_rpc_types::TransactionRequest { } fn build_unsigned(self) -> BuilderResult<::UnsignedTx> { - todo!() + match ( + self.gas_price.as_ref(), + self.max_fee_per_gas.as_ref(), + self.access_list.as_ref(), + self.max_fee_per_blob_gas.as_ref(), + self.blob_versioned_hashes.as_ref(), + self.sidecar.as_ref(), + ) { + // Legacy transaction + (Some(_), None, None, None, None, None) => build_legacy(self).map(Into::into), + // EIP-2930 + // If only accesslist is set, and there are no EIP-1559 fees + (_, None, Some(_), None, None, None) => build_2930(self).map(Into::into), + // EIP-1559 + // If EIP-4844 fields are missing + (None, _, _, None, None, None) => build_1559(self).map(Into::into), + // EIP-4844 + // All blob fields required + (None, _, _, Some(_), Some(_), Some(_)) => { + build_4844(self).map(TxEip4844Variant::from).map(Into::into) + } + _ => build_legacy(self).map(Into::into), + } } - fn build>( + async fn build>( self, signer: &S, ) -> BuilderResult<::TxEnvelope> { - todo!() + // todo: BuilderResult + SignerResult + Ok(signer.sign(self.build_unsigned()?).await.unwrap()) } } + +/// Build a legacy transaction. +fn build_legacy(request: TransactionRequest) -> Result { + Ok(TxLegacy { + chain_id: request.chain_id, + nonce: request.nonce.ok_or_else(|| TransactionBuilderError::MissingKey("nonce"))?.to(), + gas_price: request + .gas_price + .ok_or_else(|| TransactionBuilderError::MissingKey("gas_price"))? + .to(), + gas_limit: request + .gas + .ok_or_else(|| TransactionBuilderError::MissingKey("gas_limit"))? + .to(), + to: request.to.into(), + value: request.value.unwrap_or_default(), + input: request.input.into_input().unwrap_or_default(), + }) +} + +/// Build an EIP-1559 transaction. +fn build_1559(request: TransactionRequest) -> Result { + Ok(TxEip1559 { + chain_id: request.chain_id.unwrap_or(1), + nonce: request.nonce.ok_or_else(|| TransactionBuilderError::MissingKey("nonce"))?.to(), + max_priority_fee_per_gas: request + .max_priority_fee_per_gas + .ok_or_else(|| TransactionBuilderError::MissingKey("max_priority_fee_per_gas"))? + .to(), + max_fee_per_gas: request + .max_fee_per_gas + .ok_or_else(|| TransactionBuilderError::MissingKey("max_fee_per_gas"))? + .to(), + gas_limit: request + .gas + .ok_or_else(|| TransactionBuilderError::MissingKey("gas_limit"))? + .to(), + to: request.to.into(), + value: request.value.unwrap_or_default(), + input: request.input.into_input().unwrap_or_default(), + access_list: convert_access_list(request.access_list.unwrap_or_default()), + }) +} + +/// Build an EIP-2930 transaction. +fn build_2930(request: TransactionRequest) -> Result { + Ok(TxEip2930 { + chain_id: request.chain_id.unwrap_or(1), + nonce: request.nonce.ok_or_else(|| TransactionBuilderError::MissingKey("nonce"))?.to(), + gas_price: request + .gas_price + .ok_or_else(|| TransactionBuilderError::MissingKey("gas_price"))? + .to(), + gas_limit: request + .gas + .ok_or_else(|| TransactionBuilderError::MissingKey("gas_limit"))? + .to(), + to: request.to.into(), + value: request.value.unwrap_or_default(), + input: request.input.into_input().unwrap_or_default(), + access_list: convert_access_list(request.access_list.unwrap_or_default()), + }) +} + +/// Build an EIP-4844 transaction. +fn build_4844(request: TransactionRequest) -> Result { + Ok(TxEip4844 { + chain_id: request.chain_id.unwrap_or(1), + nonce: request.nonce.ok_or_else(|| TransactionBuilderError::MissingKey("nonce"))?.to(), + gas_limit: request + .gas + .ok_or_else(|| TransactionBuilderError::MissingKey("gas_limit"))? + .to(), + max_fee_per_gas: request + .max_fee_per_gas + .ok_or_else(|| TransactionBuilderError::MissingKey("max_fee_per_gas"))? + .to(), + max_priority_fee_per_gas: request + .max_priority_fee_per_gas + .ok_or_else(|| TransactionBuilderError::MissingKey("max_priority_fee_per_gas"))? + .to(), + to: request.to.into(), + value: request.value.unwrap_or_default(), + access_list: convert_access_list(request.access_list.unwrap_or_default()), + blob_versioned_hashes: request + .blob_versioned_hashes + .ok_or_else(|| TransactionBuilderError::MissingKey("blob_versioned_hashes"))?, + max_fee_per_blob_gas: request + .max_fee_per_blob_gas + .ok_or_else(|| TransactionBuilderError::MissingKey("max_fee_per_blob_gas"))? + .to(), + input: request.input.into_input().unwrap_or_default(), + }) +} + +// todo: these types are almost 1:1, minus rlp decoding and ser/de, should dedupe +fn convert_access_list(list: alloy_rpc_types::AccessList) -> alloy_eips::eip2930::AccessList { + alloy_eips::eip2930::AccessList( + list.0 + .into_iter() + .map(|item| alloy_eips::eip2930::AccessListItem { + address: item.address, + storage_keys: item.storage_keys, + }) + .collect(), + ) +} diff --git a/crates/network/src/transaction/builder.rs b/crates/network/src/transaction/builder.rs index 7db88a7ff0b..f608fe916dc 100644 --- a/crates/network/src/transaction/builder.rs +++ b/crates/network/src/transaction/builder.rs @@ -1,6 +1,7 @@ use super::signer::NetworkSigner; use crate::Network; use alloy_primitives::{Address, Bytes, ChainId, TxKind, U256, U64}; +use async_trait::async_trait; /// Error type for transaction builders. #[derive(Debug, thiserror::Error)] @@ -42,6 +43,7 @@ pub type BuilderResult = std::result::Result: Default + Sized + Send + Sync + 'static { /// Get the chain ID for the transaction. fn chain_id(&self) -> Option; @@ -192,5 +194,5 @@ pub trait TransactionBuilder: Default + Sized + Send + Sync + 'stati fn build_unsigned(self) -> BuilderResult; /// Build a signed transaction. - fn build>(self, signer: &S) -> BuilderResult; + async fn build>(self, signer: &S) -> BuilderResult; } diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index 94d12a61cc3..4db4b51e700 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -5,7 +5,7 @@ use async_trait::async_trait; // todo: move /// A signer capable of signing any transaction for the given network. #[async_trait] -pub trait NetworkSigner { +pub trait NetworkSigner: Sync { /// Asynchronously sign an unsigned transaction. async fn sign(&self, tx: N::UnsignedTx) -> alloy_signer::Result; } diff --git a/crates/rpc-types/Cargo.toml b/crates/rpc-types/Cargo.toml index 3422365bd9d..0a0a62026b7 100644 --- a/crates/rpc-types/Cargo.toml +++ b/crates/rpc-types/Cargo.toml @@ -33,12 +33,22 @@ proptest-derive = { version = "0.4", optional = true } jsonrpsee-types = { version = "0.20", optional = true } [features] -arbitrary = ["dep:arbitrary", "dep:proptest-derive", "dep:proptest", "alloy-primitives/arbitrary"] +arbitrary = [ + "dep:arbitrary", + "dep:proptest-derive", + "dep:proptest", + "alloy-primitives/arbitrary", +] jsonrpsee-types = ["dep:jsonrpsee-types"] -ssz = ["dep:ethereum_ssz" ,"dep:ethereum_ssz_derive", "alloy-primitives/ssz"] +ssz = ["dep:ethereum_ssz", "dep:ethereum_ssz_derive", "alloy-primitives/ssz"] [dev-dependencies] -alloy-primitives = { workspace = true, features = ["rand", "rlp", "serde", "arbitrary"] } +alloy-primitives = { workspace = true, features = [ + "rand", + "rlp", + "serde", + "arbitrary", +] } arbitrary = { workspace = true, features = ["derive"] } proptest.workspace = true From f6b8e00111659d2c9d4d634b326e30c541ec747d Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 16:59:59 +0100 Subject: [PATCH 13/65] chore: rm unused dep --- crates/providers/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/providers/Cargo.toml b/crates/providers/Cargo.toml index a6b7efe2be2..a5f0e81610b 100644 --- a/crates/providers/Cargo.toml +++ b/crates/providers/Cargo.toml @@ -28,7 +28,6 @@ futures.workspace = true lru = "0.12" reqwest.workspace = true serde.workspace = true -thiserror.workspace = true tokio = { workspace = true, features = ["sync", "macros"] } tracing.workspace = true From 16b328e995a5b0ef6261bad9f34aebb69787ee6f Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 17:07:32 +0100 Subject: [PATCH 14/65] temp: remove `Hash` derive --- crates/consensus/src/transaction/eip4844.rs | 6 +++--- crates/consensus/src/transaction/typed.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/consensus/src/transaction/eip4844.rs b/crates/consensus/src/transaction/eip4844.rs index 37f198c8757..65c1c5ecf7e 100644 --- a/crates/consensus/src/transaction/eip4844.rs +++ b/crates/consensus/src/transaction/eip4844.rs @@ -49,7 +49,7 @@ pub enum BlobTransactionValidationError { /// It can either be a standalone transaction, mainly seen when retrieving historical transactions, /// or a transaction with a sidecar, which is used when submitting a transaction to the network and /// when receiving and sending transactions during the gossip stage. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum TxEip4844Variant { /// A standalone transaction with blob hashes and max blob fee. TxEip4844(TxEip4844), @@ -618,7 +618,7 @@ impl Decodable for TxEip4844 { /// This is defined in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#networking) as an element /// of a `PooledTransactions` response, and is also used as the format for sending raw transactions /// through the network (eth_sendRawTransaction/eth_sendTransaction). -#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct TxEip4844WithSidecar { /// The actual transaction. pub tx: TxEip4844, @@ -730,7 +730,7 @@ impl Transaction for TxEip4844WithSidecar { } /// This represents a set of blobs, and its corresponding commitments and proofs. -#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] #[repr(C)] pub struct BlobTransactionSidecar { /// The blob data. diff --git a/crates/consensus/src/transaction/typed.rs b/crates/consensus/src/transaction/typed.rs index 90a9b12ff9d..b0ccb1f7dda 100644 --- a/crates/consensus/src/transaction/typed.rs +++ b/crates/consensus/src/transaction/typed.rs @@ -8,7 +8,7 @@ use alloy_primitives::TxKind; /// 2. EIP2930 (state access lists) [`TxEip2930`] /// 3. EIP1559 [`TxEip1559`] /// 4. EIP4844 [`TxEip4844`] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum TypedTransaction { /// Legacy transaction Legacy(TxLegacy), From b3db0fe3fc09110c5d160a86919f7c07ab351abb Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 17:21:18 +0100 Subject: [PATCH 15/65] fix: cfg attr for wasm --- crates/network/Cargo.toml | 3 +++ crates/network/src/ethereum/mod.rs | 3 ++- crates/network/src/transaction/builder.rs | 3 ++- crates/network/src/transaction/signer.rs | 6 ++++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/network/Cargo.toml b/crates/network/Cargo.toml index d77e48cabfb..d9142a496ae 100644 --- a/crates/network/Cargo.toml +++ b/crates/network/Cargo.toml @@ -22,5 +22,8 @@ async-trait.workspace = true serde = { workspace = true, features = ["derive"] } thiserror.workspace = true +[dev-dependencies] +tokio.workspace = true + [features] k256 = ["alloy-primitives/k256", "alloy-consensus/k256"] diff --git a/crates/network/src/ethereum/mod.rs b/crates/network/src/ethereum/mod.rs index ceea5642ea4..68a2f65dfc2 100644 --- a/crates/network/src/ethereum/mod.rs +++ b/crates/network/src/ethereum/mod.rs @@ -30,7 +30,8 @@ impl Network for Ethereum { type HeaderResponse = alloy_rpc_types::Header; } -#[async_trait] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl TransactionBuilder for alloy_rpc_types::TransactionRequest { fn chain_id(&self) -> Option { self.chain_id diff --git a/crates/network/src/transaction/builder.rs b/crates/network/src/transaction/builder.rs index f608fe916dc..0e5b9235c8d 100644 --- a/crates/network/src/transaction/builder.rs +++ b/crates/network/src/transaction/builder.rs @@ -43,7 +43,8 @@ pub type BuilderResult = std::result::Result: Default + Sized + Send + Sync + 'static { /// Get the chain ID for the transaction. fn chain_id(&self) -> Option; diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index 4db4b51e700..3c36b412dfa 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -4,7 +4,8 @@ use async_trait::async_trait; // todo: move /// A signer capable of signing any transaction for the given network. -#[async_trait] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait NetworkSigner: Sync { /// Asynchronously sign an unsigned transaction. async fn sign(&self, tx: N::UnsignedTx) -> alloy_signer::Result; @@ -12,7 +13,8 @@ pub trait NetworkSigner: Sync { // todo: move /// An async signer capable of signing any [SignableTransaction] for the given [Signature] type. -#[async_trait] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait TxSigner { /// Asynchronously sign an unsigned transaction. async fn sign_transaction( From 126a4b7235c200ec11573cc2ebf08ac85ceb9d8f Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 17:26:05 +0100 Subject: [PATCH 16/65] feat: tx signer trait impls for `Wallet` --- crates/network/src/ethereum/signer.rs | 3 ++- crates/network/src/transaction/signer.rs | 32 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/crates/network/src/ethereum/signer.rs b/crates/network/src/ethereum/signer.rs index 1b72ac0dc8e..75f26f94e21 100644 --- a/crates/network/src/ethereum/signer.rs +++ b/crates/network/src/ethereum/signer.rs @@ -58,9 +58,10 @@ impl NetworkSigner for EthereumSigner { #[cfg(test)] mod test { + use crate::{TxSigner, TxSignerSync}; use alloy_consensus::{SignableTransaction, TxLegacy}; use alloy_primitives::{address, ChainId, Signature, U256}; - use alloy_signer::{k256, Result, Signer, TxSigner, TxSignerSync}; + use alloy_signer::{k256, Result, Signer}; #[tokio::test] async fn signs_tx() { diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index 3c36b412dfa..33c7c926425 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -1,5 +1,9 @@ use crate::Network; use alloy_consensus::SignableTransaction; +use alloy_signer::{ + k256::ecdsa::{self, signature::hazmat::PrehashSigner, RecoveryId}, + Signature, Wallet, +}; use async_trait::async_trait; // todo: move @@ -32,3 +36,31 @@ pub trait TxSignerSync { tx: &mut dyn SignableTransaction, ) -> alloy_signer::Result; } + +// todo: these are implemented here because of a potential circular dep +// we should move wallet/yubi etc. into its own crate +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl TxSigner for Wallet +where + D: PrehashSigner<(ecdsa::Signature, RecoveryId)> + Send + Sync, +{ + async fn sign_transaction( + &self, + tx: &mut dyn SignableTransaction, + ) -> alloy_signer::Result { + todo!() + } +} + +impl TxSignerSync for Wallet +where + D: PrehashSigner<(ecdsa::Signature, RecoveryId)>, +{ + fn sign_transaction_sync( + &self, + tx: &mut dyn SignableTransaction, + ) -> alloy_signer::Result { + todo!() + } +} From 92cf29398103dedf45b00d0756279733616d269b Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 17:30:54 +0100 Subject: [PATCH 17/65] test: use ethereum network in provider test --- crates/providers/src/new.rs | 161 ++---------------------------------- 1 file changed, 8 insertions(+), 153 deletions(-) diff --git a/crates/providers/src/new.rs b/crates/providers/src/new.rs index 61da782fef5..0b42ef04b17 100644 --- a/crates/providers/src/new.rs +++ b/crates/providers/src/new.rs @@ -467,159 +467,14 @@ impl Provider for RootProviderInner(dyn Provider); - #[derive(Clone)] - struct TxLegacy(alloy_consensus::TxLegacy); - impl serde::Serialize for TxLegacy { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let tx = &self.0; - TransactionRequest { - from: None, - to: tx.to().to(), - gas_price: tx.gas_price(), - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - max_fee_per_blob_gas: None, - gas: Some(U256::from(tx.gas_limit())), - value: Some(tx.value()), - input: TransactionInput::new(tx.input().to_vec().into()), - nonce: Some(U64::from(tx.nonce())), - chain_id: tx.chain_id().map(U64::from), - access_list: None, - transaction_type: None, - blob_versioned_hashes: None, - sidecar: None, - other: Default::default(), - } - .serialize(serializer) - } - } - impl<'de> serde::Deserialize<'de> for TxLegacy { - fn deserialize(_deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - unimplemented!() - } - } - #[allow(unused)] - impl alloy_network::Transaction for TxLegacy { - type Signature = (); - - fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) { - todo!() - } - - fn payload_len_for_signature(&self) -> usize { - todo!() - } - - fn into_signed( - self, - signature: alloy_primitives::Signature, - ) -> alloy_network::Signed - where - Self: Sized, - { - todo!() - } - - fn encode_signed( - &self, - signature: &alloy_primitives::Signature, - out: &mut dyn alloy_primitives::bytes::BufMut, - ) { - todo!() - } - - fn decode_signed(buf: &mut &[u8]) -> alloy_rlp::Result> - where - Self: Sized, - { - todo!() - } - - fn input(&self) -> &[u8] { - todo!() - } - - fn input_mut(&mut self) -> &mut alloy_primitives::Bytes { - todo!() - } - - fn set_input(&mut self, data: alloy_primitives::Bytes) { - todo!() - } - - fn to(&self) -> alloy_network::TxKind { - todo!() - } - - fn set_to(&mut self, to: alloy_network::TxKind) { - todo!() - } - - fn value(&self) -> U256 { - todo!() - } - - fn set_value(&mut self, value: U256) { - todo!() - } - - fn chain_id(&self) -> Option { - todo!() - } - - fn set_chain_id(&mut self, chain_id: alloy_primitives::ChainId) { - todo!() - } - - fn nonce(&self) -> u64 { - todo!() - } - - fn set_nonce(&mut self, nonce: u64) { - todo!() - } - - fn gas_limit(&self) -> u64 { - todo!() - } - - fn set_gas_limit(&mut self, limit: u64) { - todo!() - } - - fn gas_price(&self) -> Option { - todo!() - } - - fn set_gas_price(&mut self, price: U256) { - todo!() - } - } - - struct TmpNetwork; - impl Network for TmpNetwork { - type TxEnvelope = alloy_consensus::TxEnvelope; - type ReceiptEnvelope = alloy_consensus::ReceiptEnvelope; - type Header = (); - type TransactionRequest = TxLegacy; - type TransactionResponse = (); - type ReceiptResponse = (); - type HeaderResponse = (); - } - fn init_tracing() { let _ = tracing_subscriber::fmt::try_init(); } @@ -631,16 +486,16 @@ mod tests { let anvil = alloy_node_bindings::Anvil::new().spawn(); let url = anvil.endpoint().parse().unwrap(); let http = Http::::new(url); - let provider = RootProvider::::new(RpcClient::new(http, true)); + let provider = RootProvider::::new(RpcClient::new(http, true)); - let tx = alloy_consensus::TxLegacy { - value: U256::from(100), + let tx = TransactionRequest { + value: Some(U256::from(100)), to: address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into(), - gas_price: 20e9 as u128, - gas_limit: 21000, + gas_price: Some(U256::from(20e9)), + gas: Some(U256::from(21000)), ..Default::default() }; - let pending_tx = provider.send_transaction(&TxLegacy(tx)).await.expect("failed to send tx"); + let pending_tx = provider.send_transaction(&tx).await.expect("failed to send tx"); let hash1 = pending_tx.tx_hash; let hash2 = pending_tx.await.expect("failed to await pending tx"); assert_eq!(hash1, hash2); From 5f195bab8c425d40de92d3d22aa3e378a881f030 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 17:37:38 +0100 Subject: [PATCH 18/65] docs: adjust provider docs --- crates/providers/src/new.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/providers/src/new.rs b/crates/providers/src/new.rs index 0b42ef04b17..1659bae4662 100644 --- a/crates/providers/src/new.rs +++ b/crates/providers/src/new.rs @@ -209,7 +209,7 @@ pub trait Provider: Send + Sync self.client().prepare("eth_getCode", (address, tag)).await } - /// Gets a [Transaction] by its [TxHash]. + /// Gets a transaction by its [TxHash]. async fn get_transaction_by_hash( &self, hash: TxHash, @@ -233,7 +233,7 @@ pub trait Provider: Send + Sync self.client().prepare("eth_gasPrice", ()).await } - /// Gets a [TransactionReceipt] if it exists, by its [TxHash]. + /// Gets a transaction receipt if it exists, by its [TxHash]. async fn get_transaction_receipt( &self, hash: TxHash, @@ -277,7 +277,7 @@ pub trait Provider: Send + Sync self.client().prepare("eth_syncing", ()).await } - /// Execute a smart contract call with [TransactionRequest] without publishing a transaction. + /// Execute a smart contract call with a transaction request, without publishing a transaction. async fn call( &self, tx: &N::TransactionRequest, @@ -286,7 +286,7 @@ pub trait Provider: Send + Sync self.client().prepare("eth_call", (tx, block.unwrap_or_default())).await } - /// Execute a smart contract call with [TransactionRequest] and state overrides, without + /// Execute a smart contract call with a transaction request and state overrides, without /// publishing a transaction. /// /// # Note From 9d3f6d396eaab105a4e454a9973a27b436431305 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 17:41:28 +0100 Subject: [PATCH 19/65] temp: temporarily disable contract tests --- crates/contract/src/lib.rs | 5 +++-- crates/providers/Cargo.toml | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/contract/src/lib.rs b/crates/contract/src/lib.rs index 478d320fb89..4b049061af5 100644 --- a/crates/contract/src/lib.rs +++ b/crates/contract/src/lib.rs @@ -15,8 +15,9 @@ #![deny(unused_must_use, rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -#[cfg(test)] -extern crate self as alloy_contract; +// todo: re-enable when alloy-sol-macro is adjusted +//#[cfg(test)] +//extern crate self as alloy_contract; mod error; pub use error::*; diff --git a/crates/providers/Cargo.toml b/crates/providers/Cargo.toml index a5f0e81610b..48c5a61bb04 100644 --- a/crates/providers/Cargo.toml +++ b/crates/providers/Cargo.toml @@ -31,7 +31,6 @@ serde.workspace = true tokio = { workspace = true, features = ["sync", "macros"] } tracing.workspace = true - [dev-dependencies] alloy-consensus.workspace = true alloy-node-bindings.workspace = true From b419d2534b3d7dec7535a167430fe5e2b2e0f922 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 17:42:56 +0100 Subject: [PATCH 20/65] chore: clippy --- crates/consensus/src/transaction/eip4844.rs | 47 --------------------- 1 file changed, 47 deletions(-) diff --git a/crates/consensus/src/transaction/eip4844.rs b/crates/consensus/src/transaction/eip4844.rs index 65c1c5ecf7e..3537b63e094 100644 --- a/crates/consensus/src/transaction/eip4844.rs +++ b/crates/consensus/src/transaction/eip4844.rs @@ -492,29 +492,6 @@ impl TxEip4844 { mem::size_of::() // max_fee_per_data_gas } - /// Inner encoding function that is used for both rlp [`Encodable`] trait and for calculating - /// hash that for eip2718 does not require rlp header - pub(crate) fn encode_with_signature( - &self, - signature: &Signature, - out: &mut dyn BufMut, - with_header: bool, - ) { - let payload_length = self.fields_len() + signature.rlp_vrs_len(); - if with_header { - Header { - list: false, - payload_length: 1 + length_of_length(payload_length) + payload_length, - } - .encode(out); - } - out.put_u8(self.tx_type() as u8); - let header = Header { list: true, payload_length }; - header.encode(out); - self.encode_fields(out); - signature.encode(out); - } - /// Output the length of the RLP signed transaction encoding. This encodes with a RLP header. pub fn payload_len_with_signature(&self, signature: &Signature) -> usize { let len = self.payload_len_with_signature_without_header(signature); @@ -673,30 +650,6 @@ impl TxEip4844WithSidecar { pub fn into_parts(self) -> (TxEip4844, BlobTransactionSidecar) { (self.tx, self.sidecar) } - - /// Inner encoding function that is used for both rlp [`Encodable`] trait and for calculating - /// hash that for eip2718 does not require rlp header - pub(crate) fn encode_with_signature( - &self, - signature: &Signature, - out: &mut dyn BufMut, - with_header: bool, - ) { - let payload_length = self.tx.fields_len() + signature.rlp_vrs_len(); - if with_header { - Header { - list: false, - payload_length: 1 + length_of_length(payload_length) + payload_length, - } - .encode(out); - } - out.put_u8(self.tx.tx_type() as u8); - let header = Header { list: true, payload_length }; - header.encode(out); - self.tx.encode_fields(out); - signature.encode(out); - self.sidecar.encode_inner(out); - } } impl Transaction for TxEip4844WithSidecar { From 105fc61bee993501451d4462b071d2e7ff6dc5b9 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 17:59:55 +0100 Subject: [PATCH 21/65] feat: finish tx signer trait impls --- crates/network/src/transaction/signer.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index 33c7c926425..16173a2cc88 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -2,7 +2,7 @@ use crate::Network; use alloy_consensus::SignableTransaction; use alloy_signer::{ k256::ecdsa::{self, signature::hazmat::PrehashSigner, RecoveryId}, - Signature, Wallet, + Signature, Signer, SignerSync, Wallet, }; use async_trait::async_trait; @@ -49,7 +49,7 @@ where &self, tx: &mut dyn SignableTransaction, ) -> alloy_signer::Result { - todo!() + self.sign_hash(&tx.signature_hash()).await } } @@ -61,6 +61,6 @@ where &self, tx: &mut dyn SignableTransaction, ) -> alloy_signer::Result { - todo!() + self.sign_hash_sync(&tx.signature_hash()) } } From c89e5a474643759f52251ae07c5ac9a84a403611 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 18:21:14 +0100 Subject: [PATCH 22/65] fix: set tx chain id --- crates/consensus/src/transaction/eip1559.rs | 4 ++ crates/consensus/src/transaction/eip2930.rs | 4 ++ crates/consensus/src/transaction/eip4844.rs | 11 +++++ crates/consensus/src/transaction/legacy.rs | 23 ++++------- crates/consensus/src/transaction/mod.rs | 3 ++ crates/network/src/transaction/signer.rs | 46 ++++++++++++++++++++- 6 files changed, 74 insertions(+), 17 deletions(-) diff --git a/crates/consensus/src/transaction/eip1559.rs b/crates/consensus/src/transaction/eip1559.rs index 33508e0bed8..ae517425c11 100644 --- a/crates/consensus/src/transaction/eip1559.rs +++ b/crates/consensus/src/transaction/eip1559.rs @@ -212,6 +212,10 @@ impl Transaction for TxEip1559 { } impl SignableTransaction for TxEip1559 { + fn set_chain_id(&mut self, chain_id: ChainId) { + self.chain_id = chain_id; + } + fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) { out.put_u8(self.tx_type() as u8); Header { list: true, payload_length: self.fields_len() }.encode(out); diff --git a/crates/consensus/src/transaction/eip2930.rs b/crates/consensus/src/transaction/eip2930.rs index aa3a988b898..92a4675596a 100644 --- a/crates/consensus/src/transaction/eip2930.rs +++ b/crates/consensus/src/transaction/eip2930.rs @@ -173,6 +173,10 @@ impl Transaction for TxEip2930 { } impl SignableTransaction for TxEip2930 { + fn set_chain_id(&mut self, chain_id: ChainId) { + self.chain_id = chain_id; + } + fn encode_for_signing(&self, out: &mut dyn BufMut) { out.put_u8(self.tx_type() as u8); Header { list: true, payload_length: self.fields_len() }.encode(out); diff --git a/crates/consensus/src/transaction/eip4844.rs b/crates/consensus/src/transaction/eip4844.rs index 3537b63e094..ad2de87819c 100644 --- a/crates/consensus/src/transaction/eip4844.rs +++ b/crates/consensus/src/transaction/eip4844.rs @@ -200,6 +200,17 @@ impl Transaction for TxEip4844Variant { } impl SignableTransaction for TxEip4844Variant { + fn set_chain_id(&mut self, chain_id: ChainId) { + match self { + TxEip4844Variant::TxEip4844(ref mut inner) => { + inner.chain_id = chain_id; + } + TxEip4844Variant::TxEip4844WithSidecar(ref mut inner) => { + inner.tx.chain_id = chain_id; + } + } + } + fn payload_len_for_signature(&self) -> usize { let payload_length = self.fields_len(); // 'transaction type byte length' + 'header length' + 'payload length' diff --git a/crates/consensus/src/transaction/legacy.rs b/crates/consensus/src/transaction/legacy.rs index 13e28b9f016..ca75dc8f321 100644 --- a/crates/consensus/src/transaction/legacy.rs +++ b/crates/consensus/src/transaction/legacy.rs @@ -137,20 +137,6 @@ impl TxLegacy { chain_id: None, }) } - - /// Encodes the legacy transaction in RLP for signing. - /// - /// This encodes the transaction as: - /// `tx_type || rlp(chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, - /// value, input, access_list, max_fee_per_blob_gas, blob_versioned_hashes)` - /// - /// Note that there is no rlp header before the transaction type byte. - pub fn encode_for_signing(&self, out: &mut dyn BufMut) { - Header { list: true, payload_length: self.fields_len() + self.eip155_fields_len() } - .encode(out); - self.encode_fields(out); - self.encode_eip155_signing_fields(out); - } } impl Transaction for TxLegacy { @@ -184,8 +170,15 @@ impl Transaction for TxLegacy { } impl SignableTransaction for TxLegacy { + fn set_chain_id(&mut self, chain_id: ChainId) { + self.chain_id = Some(chain_id); + } + fn encode_for_signing(&self, out: &mut dyn BufMut) { - self.encode_for_signing(out) + Header { list: true, payload_length: self.fields_len() + self.eip155_fields_len() } + .encode(out); + self.encode_fields(out); + self.encode_eip155_signing_fields(out); } fn payload_len_for_signature(&self) -> usize { diff --git a/crates/consensus/src/transaction/mod.rs b/crates/consensus/src/transaction/mod.rs index 7ce3dd06c65..a232bd814a3 100644 --- a/crates/consensus/src/transaction/mod.rs +++ b/crates/consensus/src/transaction/mod.rs @@ -53,6 +53,9 @@ pub trait Transaction: std::any::Any + Send + Sync + 'static { /// types, or in other networks. For example, in Optimism, the deposit transaction signature is the /// unit type `()`. pub trait SignableTransaction: Transaction { + /// Set `chain_id` if it is not already set. + fn set_chain_id(&mut self, chain_id: ChainId); + /// RLP-encodes the transaction for signing. fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut); diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index 16173a2cc88..4a5a90ddeb1 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -49,7 +49,28 @@ where &self, tx: &mut dyn SignableTransaction, ) -> alloy_signer::Result { - self.sign_hash(&tx.signature_hash()).await + let chain_id = self.chain_id_sync(); + if let Some(chain_id) = chain_id { + match tx.chain_id() { + Some(tx_chain_id) => { + if tx_chain_id != chain_id { + return Err(alloy_signer::Error::TransactionChainIdMismatch { + signer: chain_id, + tx: tx_chain_id, + }); + } + } + None => { + tx.set_chain_id(chain_id); + } + } + } + + let mut sig = self.sign_hash(&tx.signature_hash()).await?; + if let Some(chain_id) = chain_id.or_else(|| tx.chain_id()) { + sig = sig.with_chain_id(chain_id); + } + Ok(sig) } } @@ -61,6 +82,27 @@ where &self, tx: &mut dyn SignableTransaction, ) -> alloy_signer::Result { - self.sign_hash_sync(&tx.signature_hash()) + let chain_id = self.chain_id_sync(); + if let Some(chain_id) = chain_id { + match tx.chain_id() { + Some(tx_chain_id) => { + if tx_chain_id != chain_id { + return Err(alloy_signer::Error::TransactionChainIdMismatch { + signer: chain_id, + tx: tx_chain_id, + }); + } + } + None => { + tx.set_chain_id(chain_id); + } + } + } + + let mut sig = self.sign_hash_sync(&tx.signature_hash())?; + if let Some(chain_id) = chain_id.or_else(|| tx.chain_id()) { + sig = sig.with_chain_id(chain_id); + } + Ok(sig) } } From f3541d219477b8bf7e8fd0150823ef5100953416 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 18:39:16 +0100 Subject: [PATCH 23/65] test: port old provider tests --- crates/contract/src/lib.rs | 7 ++ crates/providers/src/new.rs | 129 ++++++++++++++++++++++-------------- 2 files changed, 88 insertions(+), 48 deletions(-) diff --git a/crates/contract/src/lib.rs b/crates/contract/src/lib.rs index 4b049061af5..37b580a024a 100644 --- a/crates/contract/src/lib.rs +++ b/crates/contract/src/lib.rs @@ -30,3 +30,10 @@ pub use instance::*; mod call; pub use call::*; + +// Not public API. +// NOTE: please avoid changing the API of this module due to its use in the `sol!` macro. +#[doc(hidden)] +pub mod private { + pub use alloy_providers::Provider; +} diff --git a/crates/providers/src/new.rs b/crates/providers/src/new.rs index 1659bae4662..aba2ba4031b 100644 --- a/crates/providers/src/new.rs +++ b/crates/providers/src/new.rs @@ -411,7 +411,6 @@ pub trait Provider: Send + Sync /// Extension trait for raw RPC requests. #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] -#[auto_impl::auto_impl(&, &mut, Rc, Arc, Box)] pub trait RawProvider: Provider { /// Sends a raw JSON-RPC request. async fn raw_request(&self, method: &'static str, params: P) -> TransportResult @@ -425,6 +424,8 @@ pub trait RawProvider: Provider } } +impl RawProvider for P where P: Provider {} + #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] impl Provider for RootProvider { @@ -466,9 +467,12 @@ impl Provider for RootProviderInner HttpProvider { let anvil = alloy_node_bindings::Anvil::new().spawn(); let url = anvil.endpoint().parse().unwrap(); let http = Http::::new(url); - let provider = RootProvider::::new(RpcClient::new(http, true)); + RootProvider::::new(RpcClient::new(http, true)) + } + + #[tokio::test] + async fn test_send_tx() { + init_tracing(); + let provider = anvil_provider(); let tx = TransactionRequest { value: Some(U256::from(100)), @@ -501,27 +508,31 @@ mod tests { assert_eq!(hash1, hash2); } - /* #[tokio::test] async fn gets_block_number() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + init_tracing(); + let provider = anvil_provider(); + let num = provider.get_block_number().await.unwrap(); assert_eq!(0, num) } #[tokio::test] async fn gets_block_number_with_raw_req() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + use super::RawProvider; + + init_tracing(); + let provider = anvil_provider(); + let num: U64 = provider.raw_request("eth_blockNumber", ()).await.unwrap(); assert_eq!(0, num.to::()) } #[tokio::test] async fn gets_transaction_count() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + init_tracing(); + let provider = anvil_provider(); + let count = provider .get_transaction_count( address!("328375e18E7db8F1CA9d9bA8bF3E9C94ee34136A"), @@ -534,8 +545,9 @@ mod tests { #[tokio::test] async fn gets_block_by_hash() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + init_tracing(); + let provider = anvil_provider(); + let num = 0; let tag: BlockNumberOrTag = num.into(); let block = provider.get_block_by_number(tag, true).await.unwrap().unwrap(); @@ -546,8 +558,11 @@ mod tests { #[tokio::test] async fn gets_block_by_hash_with_raw_req() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + use super::RawProvider; + + init_tracing(); + let provider = anvil_provider(); + let num = 0; let tag: BlockNumberOrTag = num.into(); let block = provider.get_block_by_number(tag, true).await.unwrap().unwrap(); @@ -564,8 +579,9 @@ mod tests { #[tokio::test] async fn gets_block_by_number_full() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + init_tracing(); + let provider = anvil_provider(); + let num = 0; let tag: BlockNumberOrTag = num.into(); let block = provider.get_block_by_number(tag, true).await.unwrap().unwrap(); @@ -574,8 +590,9 @@ mod tests { #[tokio::test] async fn gets_block_by_number() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + init_tracing(); + let provider = anvil_provider(); + let num = 0; let tag: BlockNumberOrTag = num.into(); let block = provider.get_block_by_number(tag, true).await.unwrap().unwrap(); @@ -584,8 +601,9 @@ mod tests { #[tokio::test] async fn gets_client_version() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + init_tracing(); + let provider = anvil_provider(); + let version = provider.get_client_version().await.unwrap(); assert!(version.contains("anvil")); } @@ -594,7 +612,10 @@ mod tests { async fn gets_chain_id() { let chain_id: u64 = 13371337; let anvil = Anvil::new().args(["--chain-id", chain_id.to_string().as_str()]).spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let url = anvil.endpoint().parse().unwrap(); + let http = Http::::new(url); + let provider = RootProvider::::new(RpcClient::new(http, true)); + let chain_id = provider.get_chain_id().await.unwrap(); assert_eq!(chain_id, U64::from(chain_id)); } @@ -603,7 +624,10 @@ mod tests { async fn gets_network_id() { let chain_id: u64 = 13371337; let anvil = Anvil::new().args(["--chain-id", chain_id.to_string().as_str()]).spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + let url = anvil.endpoint().parse().unwrap(); + let http = Http::::new(url); + let provider = RootProvider::::new(RpcClient::new(http, true)); + let chain_id = provider.get_net_version().await.unwrap(); assert_eq!(chain_id, U64::from(chain_id)); } @@ -611,8 +635,9 @@ mod tests { #[tokio::test] #[cfg(feature = "anvil")] async fn gets_code_at() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + init_tracing(); + let provider = anvil_provider(); + // Set the code let addr = alloy_primitives::Address::with_last_byte(16); provider.set_code(addr, "0xbeef").await.unwrap(); @@ -627,8 +652,9 @@ mod tests { #[tokio::test] async fn gets_storage_at() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + init_tracing(); + let provider = anvil_provider(); + let addr = alloy_primitives::Address::with_last_byte(16); let storage = provider.get_storage_at(addr, U256::ZERO, None).await.unwrap(); assert_eq!(storage, U256::ZERO); @@ -637,8 +663,9 @@ mod tests { #[tokio::test] #[ignore] async fn gets_transaction_by_hash() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + init_tracing(); + let provider = anvil_provider(); + let tx = provider .get_transaction_by_hash(b256!( "5c03fab9114ceb98994b43892ade87ddfd9ae7e8f293935c3bd29d435dc9fd95" @@ -655,8 +682,9 @@ mod tests { #[tokio::test] #[ignore] async fn gets_logs() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + init_tracing(); + let provider = anvil_provider(); + let filter = Filter::new() .at_block_hash(b256!( "b20e6f35d4b46b3c4cd72152faec7143da851a0dc281d390bdd50f58bfbdb5d3" @@ -671,8 +699,9 @@ mod tests { #[tokio::test] #[ignore] async fn gets_tx_receipt() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + init_tracing(); + let provider = anvil_provider(); + let receipt = provider .get_transaction_receipt(b256!( "5c03fab9114ceb98994b43892ade87ddfd9ae7e8f293935c3bd29d435dc9fd95" @@ -689,8 +718,9 @@ mod tests { #[tokio::test] async fn gets_fee_history() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + init_tracing(); + let provider = anvil_provider(); + let block_number = provider.get_block_number().await.unwrap(); let fee_history = provider .get_fee_history( @@ -706,33 +736,36 @@ mod tests { #[tokio::test] #[ignore] // Anvil has yet to implement the `eth_getBlockReceipts` method. async fn gets_block_receipts() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + init_tracing(); + let provider = anvil_provider(); + let receipts = provider.get_block_receipts(BlockNumberOrTag::Latest).await.unwrap(); assert!(receipts.is_some()); } #[tokio::test] async fn gets_block_traces() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); + init_tracing(); + let provider = anvil_provider(); + let traces = provider.trace_block(BlockNumberOrTag::Latest).await.unwrap(); assert_eq!(traces.len(), 0); } #[tokio::test] async fn sends_raw_transaction() { - let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(&anvil.endpoint()).unwrap(); - let tx_hash = provider + init_tracing(); + let provider = anvil_provider(); + + let pending = provider .send_raw_transaction( // Transfer 1 ETH from default EOA address to the Genesis address. - bytes!("f865808477359400825208940000000000000000000000000000000000000000018082f4f5a00505e227c1c636c76fac55795db1a40a4d24840d81b40d2fe0cc85767f6bd202a01e91b437099a8a90234ac5af3cb7ca4fb1432e133f75f9a91678eaf5f487c74b") + bytes!("f865808477359400825208940000000000000000000000000000000000000000018082f4f5a00505e227c1c636c76fac55795db1a40a4d24840d81b40d2fe0cc85767f6bd202a01e91b437099a8a90234ac5af3cb7ca4fb1432e133f75f9a91678eaf5f487c74b").as_ref() ) .await.unwrap(); assert_eq!( - tx_hash.to_string(), + pending.tx_hash().to_string(), "0x9dae5cf33694a02e8a7d5de3fe31e9d05ca0ba6e9180efac4ab20a06c9e598a3" ); - }*/ + } } From 07d5aea3f8a225699a4dfa66cd3790f1880989bb Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 18:46:05 +0100 Subject: [PATCH 24/65] test: don't drop `AnvilInstance` too early --- crates/providers/src/new.rs | 44 ++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/crates/providers/src/new.rs b/crates/providers/src/new.rs index aba2ba4031b..e6f399575c9 100644 --- a/crates/providers/src/new.rs +++ b/crates/providers/src/new.rs @@ -471,7 +471,7 @@ mod tests { use super::*; use alloy_network::Ethereum; - use alloy_node_bindings::Anvil; + use alloy_node_bindings::{Anvil, AnvilInstance}; use alloy_primitives::{address, b256, bytes}; use alloy_rpc_types::request::TransactionRequest; use alloy_transport_http::Http; @@ -483,17 +483,17 @@ mod tests { let _ = tracing_subscriber::fmt::try_init(); } - fn anvil_provider() -> HttpProvider { - let anvil = alloy_node_bindings::Anvil::new().spawn(); + fn anvil_provider() -> (HttpProvider, AnvilInstance) { + let anvil = Anvil::new().spawn(); let url = anvil.endpoint().parse().unwrap(); let http = Http::::new(url); - RootProvider::::new(RpcClient::new(http, true)) + (RootProvider::::new(RpcClient::new(http, true)), anvil) } #[tokio::test] async fn test_send_tx() { init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let tx = TransactionRequest { value: Some(U256::from(100)), @@ -511,7 +511,7 @@ mod tests { #[tokio::test] async fn gets_block_number() { init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let num = provider.get_block_number().await.unwrap(); assert_eq!(0, num) @@ -522,7 +522,7 @@ mod tests { use super::RawProvider; init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let num: U64 = provider.raw_request("eth_blockNumber", ()).await.unwrap(); assert_eq!(0, num.to::()) @@ -531,7 +531,7 @@ mod tests { #[tokio::test] async fn gets_transaction_count() { init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let count = provider .get_transaction_count( @@ -546,7 +546,7 @@ mod tests { #[tokio::test] async fn gets_block_by_hash() { init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let num = 0; let tag: BlockNumberOrTag = num.into(); @@ -561,7 +561,7 @@ mod tests { use super::RawProvider; init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let num = 0; let tag: BlockNumberOrTag = num.into(); @@ -580,7 +580,7 @@ mod tests { #[tokio::test] async fn gets_block_by_number_full() { init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let num = 0; let tag: BlockNumberOrTag = num.into(); @@ -591,7 +591,7 @@ mod tests { #[tokio::test] async fn gets_block_by_number() { init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let num = 0; let tag: BlockNumberOrTag = num.into(); @@ -602,7 +602,7 @@ mod tests { #[tokio::test] async fn gets_client_version() { init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let version = provider.get_client_version().await.unwrap(); assert!(version.contains("anvil")); @@ -636,7 +636,7 @@ mod tests { #[cfg(feature = "anvil")] async fn gets_code_at() { init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); // Set the code let addr = alloy_primitives::Address::with_last_byte(16); @@ -653,7 +653,7 @@ mod tests { #[tokio::test] async fn gets_storage_at() { init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let addr = alloy_primitives::Address::with_last_byte(16); let storage = provider.get_storage_at(addr, U256::ZERO, None).await.unwrap(); @@ -664,7 +664,7 @@ mod tests { #[ignore] async fn gets_transaction_by_hash() { init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let tx = provider .get_transaction_by_hash(b256!( @@ -683,7 +683,7 @@ mod tests { #[ignore] async fn gets_logs() { init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let filter = Filter::new() .at_block_hash(b256!( @@ -700,7 +700,7 @@ mod tests { #[ignore] async fn gets_tx_receipt() { init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let receipt = provider .get_transaction_receipt(b256!( @@ -719,7 +719,7 @@ mod tests { #[tokio::test] async fn gets_fee_history() { init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let block_number = provider.get_block_number().await.unwrap(); let fee_history = provider @@ -737,7 +737,7 @@ mod tests { #[ignore] // Anvil has yet to implement the `eth_getBlockReceipts` method. async fn gets_block_receipts() { init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let receipts = provider.get_block_receipts(BlockNumberOrTag::Latest).await.unwrap(); assert!(receipts.is_some()); @@ -746,7 +746,7 @@ mod tests { #[tokio::test] async fn gets_block_traces() { init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let traces = provider.trace_block(BlockNumberOrTag::Latest).await.unwrap(); assert_eq!(traces.len(), 0); @@ -755,7 +755,7 @@ mod tests { #[tokio::test] async fn sends_raw_transaction() { init_tracing(); - let provider = anvil_provider(); + let (provider, _anvil) = anvil_provider(); let pending = provider .send_raw_transaction( From d88eba17da964682c1110e22eaae476cb6677cf3 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 18:53:21 +0100 Subject: [PATCH 25/65] test: fix k256 gated tests --- crates/consensus/src/transaction/eip1559.rs | 4 ++-- crates/consensus/src/transaction/envelope.rs | 1 + crates/consensus/src/transaction/legacy.rs | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/consensus/src/transaction/eip1559.rs b/crates/consensus/src/transaction/eip1559.rs index ae517425c11..41ecd259501 100644 --- a/crates/consensus/src/transaction/eip1559.rs +++ b/crates/consensus/src/transaction/eip1559.rs @@ -286,9 +286,9 @@ impl Decodable for TxEip1559 { #[cfg(all(test, feature = "k256"))] mod tests { use super::TxEip1559; - use crate::TxKind; + use crate::SignableTransaction; use alloy_eips::eip2930::AccessList; - use alloy_network::SignableTransaction; + use alloy_primitives::TxKind; use alloy_primitives::{address, b256, hex, Address, Signature, B256, U256}; use alloy_rlp::Encodable; diff --git a/crates/consensus/src/transaction/envelope.rs b/crates/consensus/src/transaction/envelope.rs index 0160978d039..8e233502712 100644 --- a/crates/consensus/src/transaction/envelope.rs +++ b/crates/consensus/src/transaction/envelope.rs @@ -264,6 +264,7 @@ mod tests { // Test vector from https://sepolia.etherscan.io/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 // Blobscan: https://sepolia.blobscan.com/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 fn test_decode_live_4844_tx() { + use crate::Transaction; use alloy_primitives::{address, b256}; // https://sepolia.etherscan.io/getRawTx?tx=0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 diff --git a/crates/consensus/src/transaction/legacy.rs b/crates/consensus/src/transaction/legacy.rs index ca75dc8f321..f4008c7282b 100644 --- a/crates/consensus/src/transaction/legacy.rs +++ b/crates/consensus/src/transaction/legacy.rs @@ -262,9 +262,9 @@ mod tests { #[test] #[cfg(feature = "k256")] fn recover_signer_legacy() { - use crate::{TxKind, TxLegacy}; - use alloy_network::SignableTransaction; - use alloy_primitives::{address, b256, hex, Signature, U256}; + use crate::SignableTransaction; + use crate::TxLegacy; + use alloy_primitives::{address, b256, hex, Signature, TxKind, U256}; let signer = address!("398137383b3d25c92898c656696e41950e47316b"); let hash = b256!("bb3a336e3f823ec18197f1e13ee875700f08f03e2cab75f0d0b118dabb44cba0"); @@ -296,8 +296,8 @@ mod tests { #[cfg(feature = "k256")] // Test vector from https://github.com/alloy-rs/alloy/issues/125 fn decode_legacy_and_recover_signer() { + use crate::Signed; use crate::TxLegacy; - use alloy_network::Signed; use alloy_primitives::address; use alloy_rlp::Decodable; From 5dcd259b8fa937ec34069688536c355576902e68 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 18:53:29 +0100 Subject: [PATCH 26/65] chore: fmt --- crates/consensus/src/transaction/eip1559.rs | 3 +-- crates/consensus/src/transaction/legacy.rs | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/consensus/src/transaction/eip1559.rs b/crates/consensus/src/transaction/eip1559.rs index 41ecd259501..9aae947b249 100644 --- a/crates/consensus/src/transaction/eip1559.rs +++ b/crates/consensus/src/transaction/eip1559.rs @@ -288,8 +288,7 @@ mod tests { use super::TxEip1559; use crate::SignableTransaction; use alloy_eips::eip2930::AccessList; - use alloy_primitives::TxKind; - use alloy_primitives::{address, b256, hex, Address, Signature, B256, U256}; + use alloy_primitives::{address, b256, hex, Address, Signature, TxKind, B256, U256}; use alloy_rlp::Encodable; #[test] diff --git a/crates/consensus/src/transaction/legacy.rs b/crates/consensus/src/transaction/legacy.rs index f4008c7282b..cf44418aebe 100644 --- a/crates/consensus/src/transaction/legacy.rs +++ b/crates/consensus/src/transaction/legacy.rs @@ -262,8 +262,7 @@ mod tests { #[test] #[cfg(feature = "k256")] fn recover_signer_legacy() { - use crate::SignableTransaction; - use crate::TxLegacy; + use crate::{SignableTransaction, TxLegacy}; use alloy_primitives::{address, b256, hex, Signature, TxKind, U256}; let signer = address!("398137383b3d25c92898c656696e41950e47316b"); @@ -296,8 +295,7 @@ mod tests { #[cfg(feature = "k256")] // Test vector from https://github.com/alloy-rs/alloy/issues/125 fn decode_legacy_and_recover_signer() { - use crate::Signed; - use crate::TxLegacy; + use crate::{Signed, TxLegacy}; use alloy_primitives::address; use alloy_rlp::Decodable; From 5d0a54847f8de6cafb8bca8914d1a32eb83ae77b Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 18:56:28 +0100 Subject: [PATCH 27/65] chore: clippy --- crates/providers/src/new.rs | 11 ++++------- crates/signer-trezor/src/types.rs | 6 +++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/providers/src/new.rs b/crates/providers/src/new.rs index e6f399575c9..333b630b29d 100644 --- a/crates/providers/src/new.rs +++ b/crates/providers/src/new.rs @@ -123,12 +123,13 @@ pub trait Provider: Send + Sync self.client().prepare("eth_getBlockByNumber", (number, hydrate)).await } + // todo eip-1559 and blobs as well async fn populate_gas( &self, tx: &mut N::TransactionRequest, block: Option, ) -> TransportResult<()> { - let gas = self.estimate_gas(&*tx, block).await; + let _ = self.estimate_gas(&*tx, block).await; todo!() // gas.map(|gas| tx.set_gas_limit(gas.try_into().unwrap())) @@ -467,9 +468,8 @@ impl Provider for RootProviderInner for TrezorError { - fn into(self) -> alloy_signer::Error { - alloy_signer::Error::other(self) +impl From for alloy_signer::Error { + fn from(error: TrezorError) -> Self { + alloy_signer::Error::other(error) } } From ac07332f6061c084445cc8998e4d13df47659cc8 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 19:01:23 +0100 Subject: [PATCH 28/65] chore: docs lint --- crates/network/src/transaction/builder.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/network/src/transaction/builder.rs b/crates/network/src/transaction/builder.rs index 0e5b9235c8d..14d1eac6d0f 100644 --- a/crates/network/src/transaction/builder.rs +++ b/crates/network/src/transaction/builder.rs @@ -39,7 +39,8 @@ pub type BuilderResult = std::result::Result Date: Thu, 7 Mar 2024 19:58:58 +0100 Subject: [PATCH 29/65] ci: do not check wasm support for `Network` crate a temporary fix since `alloy-signer` is now a dep of `alloy-network`, and `alloy-signer` is not wasm-compatible. a better long term fix is to move `Wallet` and related types out of `alloy-signer`, since these are the types that require `rand` --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8e64e23dd9..ebb2c5011cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,7 @@ jobs: run: | cargo hack check --workspace --target wasm32-unknown-unknown \ --exclude alloy-contract \ + --exclude alloy-network \ --exclude alloy-node-bindings \ --exclude alloy-providers \ --exclude alloy-signer \ From 136cf84d8a40635d4121e8f03d27267b841ac3c9 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 20:48:13 +0100 Subject: [PATCH 30/65] docs: adjust all docs --- crates/consensus/README.md | 5 ++-- crates/consensus/src/receipt/receipts.rs | 2 +- crates/consensus/src/transaction/mod.rs | 4 +-- crates/consensus/src/transaction/typed.rs | 2 +- crates/network/src/transaction/signer.rs | 34 +++++++++++++++++++---- crates/signer/src/signer.rs | 14 ---------- 6 files changed, 36 insertions(+), 25 deletions(-) diff --git a/crates/consensus/README.md b/crates/consensus/README.md index e16ce91961f..ff033c238ca 100644 --- a/crates/consensus/README.md +++ b/crates/consensus/README.md @@ -4,8 +4,7 @@ Ethereum consensus interface. This crate contains constants, types, and functions for implementing Ethereum EL consensus and communication. This includes headers, blocks, transactions, -eip2718 envelopes, eip2930, eip4844, and more. The types in this crate -implement many of the traits found in [alloy_network]. +[EIP-2718] envelopes, [EIP-2930], [EIP-4844], and more. In general a type belongs in this crate if it is committed to in the EL block header. This includes: @@ -18,6 +17,8 @@ header. This includes: [alloy-network]: ../network [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 +[EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 +[EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 ## Provenance diff --git a/crates/consensus/src/receipt/receipts.rs b/crates/consensus/src/receipt/receipts.rs index d33d8343d72..8a9648418ea 100644 --- a/crates/consensus/src/receipt/receipts.rs +++ b/crates/consensus/src/receipt/receipts.rs @@ -33,7 +33,7 @@ impl Receipt { /// This convenience type allows us to lazily calculate the bloom filter for a /// receipt, similar to [`Sealed`]. /// -/// [`Sealed`]: ::alloy_network::Sealed +/// [`Sealed`]: crate::sealed::Sealed #[derive(Clone, Debug, PartialEq, Eq, Default)] pub struct ReceiptWithBloom { /// The receipt. diff --git a/crates/consensus/src/transaction/mod.rs b/crates/consensus/src/transaction/mod.rs index a232bd814a3..d326419c751 100644 --- a/crates/consensus/src/transaction/mod.rs +++ b/crates/consensus/src/transaction/mod.rs @@ -64,7 +64,7 @@ pub trait SignableTransaction: Transaction { /// RLP-encodes the transaction for signing it. Used to calculate `signature_hash`. /// - /// See [`Transaction::encode_for_signing`]. + /// See [`SignableTransaction::encode_for_signing`]. fn encoded_for_signing(&self) -> Vec { let mut buf = Vec::with_capacity(self.payload_len_for_signature()); self.encode_for_signing(&mut buf); @@ -89,7 +89,7 @@ pub trait SignableTransaction: Transaction { /// Decode a signed transaction. This decoding is usually RLP, but may be /// different for future EIP-2718 transaction types. /// - /// This MUST be the inverse of [`Transaction::encode_signed`]. + /// This MUST be the inverse of [`SignableTransaction::encode_signed`]. fn decode_signed(buf: &mut &[u8]) -> alloy_rlp::Result> where Self: Sized; diff --git a/crates/consensus/src/transaction/typed.rs b/crates/consensus/src/transaction/typed.rs index b0ccb1f7dda..adf3f5d10b3 100644 --- a/crates/consensus/src/transaction/typed.rs +++ b/crates/consensus/src/transaction/typed.rs @@ -7,7 +7,7 @@ use alloy_primitives::TxKind; /// 1. Legacy (pre-EIP2718) [`TxLegacy`] /// 2. EIP2930 (state access lists) [`TxEip2930`] /// 3. EIP1559 [`TxEip1559`] -/// 4. EIP4844 [`TxEip4844`] +/// 4. EIP4844 [`TxEip4844Variant`] #[derive(Debug, Clone, PartialEq, Eq)] pub enum TypedTransaction { /// Legacy transaction diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index 4a5a90ddeb1..5d5c78dda8d 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -6,7 +6,6 @@ use alloy_signer::{ }; use async_trait::async_trait; -// todo: move /// A signer capable of signing any transaction for the given network. #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] @@ -15,8 +14,20 @@ pub trait NetworkSigner: Sync { async fn sign(&self, tx: N::UnsignedTx) -> alloy_signer::Result; } -// todo: move -/// An async signer capable of signing any [SignableTransaction] for the given [Signature] type. +/// Asynchronous transaction signer, capable of signing any [`SignableTransaction`] for the given +/// `Signature` type. +/// +/// A signer should hold an optional [`ChainId`] value, which is used for [EIP-155] replay +/// protection. +/// +/// If `chain_id` is Some, [EIP-155] should be applied to the input transaction in +/// [`sign_transaction`](Self::sign_transaction), and to the resulting signature in all the methods. +/// If `chain_id` is None, [EIP-155] should not be applied. +/// +/// Synchronous signers should implement both this trait and [`TxSignerSync`]. +/// +/// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 +/// [`ChainId`]: alloy_primitives::ChainId #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait TxSigner { @@ -27,8 +38,21 @@ pub trait TxSigner { ) -> alloy_signer::Result; } -// todo: move -/// A sync signer capable of signing any [SignableTransaction] for the given [Signature] type. +/// Synchronous transaction signer, capable of signing any [`SignableTransaction`] for the given +/// `Signature` type. +/// +/// A signer should hold an optional [`ChainId`] value, which is used for [EIP-155] replay +/// protection. +/// +/// If `chain_id` is Some, [EIP-155] should be applied to the input transaction in +/// [`sign_transaction_sync`](Self::sign_transaction_sync), and to the resulting signature in all +/// the methods. If `chain_id` is None, [EIP-155] should not be applied. +/// +/// Synchronous signers should also implement [`TxSigner`], as they are always able to by delegating +/// the asynchronous methods to the synchronous ones. +/// +/// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 +/// [`ChainId`]: alloy_primitives::ChainId pub trait TxSignerSync { /// Synchronously sign an unsigned transaction. fn sign_transaction_sync( diff --git a/crates/signer/src/signer.rs b/crates/signer/src/signer.rs index 9b633949fd9..7a7dfbdac93 100644 --- a/crates/signer/src/signer.rs +++ b/crates/signer/src/signer.rs @@ -14,13 +14,6 @@ use alloy_sol_types::{Eip712Domain, SolStruct}; /// [`UnsupportedOperation`](crate::Error::UnsupportedOperation), and implement all the signing /// methods directly. /// -/// A signer should hold an optional [`ChainId`] value, which is used for [EIP-155] replay -/// protection. -/// -/// If `chain_id` is Some, [EIP-155] should be applied to the input transaction in -/// [`sign_transaction`](Self::sign_transaction), and to the resulting signature in all the methods. -/// If `chain_id` is None, [EIP-155] should not be applied. -/// /// Synchronous signers should implement both this trait and [`SignerSync`]. /// /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 @@ -84,13 +77,6 @@ pub trait Signer: Send + Sync { /// [`UnsupportedOperation`](crate::Error::UnsupportedOperation), and implement all the signing /// methods directly. /// -/// A signer should hold an optional [`ChainId`] value, which is used for [EIP-155] replay -/// protection. -/// -/// If `chain_id` is Some, [EIP-155] should be applied to the input transaction in -/// [`sign_transaction_sync`](Self::sign_transaction_sync), and to the resulting signature in all -/// the methods. If `chain_id` is None, [EIP-155] should not be applied. -/// /// Synchronous signers should also implement [`Signer`], as they are always able to by delegating /// the asynchronous methods to the synchronous ones. /// From 043cef32be7c610a5dd8042ad29907944e190d57 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 20:56:55 +0100 Subject: [PATCH 31/65] chore: rm unneeded `'static` --- crates/signer/Cargo.toml | 2 -- crates/signer/src/signer.rs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/signer/Cargo.toml b/crates/signer/Cargo.toml index 62d87ad7276..0c486019c14 100644 --- a/crates/signer/Cargo.toml +++ b/crates/signer/Cargo.toml @@ -39,8 +39,6 @@ yubihsm = { version = "0.42", features = [ ], optional = true } [dev-dependencies] -# todo: circular dep v_v -# alloy-consensus needs alloy-signer for the SignableTransaction trait alloy-consensus.workspace = true assert_matches.workspace = true serde_json.workspace = true diff --git a/crates/signer/src/signer.rs b/crates/signer/src/signer.rs index 7a7dfbdac93..e72c00c81f5 100644 --- a/crates/signer/src/signer.rs +++ b/crates/signer/src/signer.rs @@ -20,7 +20,7 @@ use alloy_sol_types::{Eip712Domain, SolStruct}; #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[auto_impl(&mut, Box)] -pub trait Signer: Send + Sync { +pub trait Signer: Send + Sync { /// Signs the given hash. async fn sign_hash(&self, hash: &B256) -> Result; From 34b76ee7a102e4aaa4f14ead18e6edf4ae63c2d5 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 23:07:06 +0100 Subject: [PATCH 32/65] test: re-enable contract tests --- Cargo.toml | 2 + crates/contract/Cargo.toml | 3 ++ crates/contract/src/call.rs | 74 +++++++++++++++++++------------- crates/contract/src/instance.rs | 25 ++++++----- crates/contract/src/interface.rs | 6 ++- crates/contract/src/lib.rs | 7 +-- crates/network/src/lib.rs | 2 +- crates/providers/src/new.rs | 40 ++++++++--------- 8 files changed, 92 insertions(+), 67 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 07cd0c00826..976f3411a8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,3 +117,5 @@ tempfile = "3.10" # This should only be used in `alloy-contract` tests. # [patch.crates-io] # alloy-sol-macro = { git = "https://github.com/alloy-rs/core", rev = "18b0509950c90d9ec38f25913b692ae4cdd6f227" } +[patch.crates-io] +alloy-sol-macro = { path = "../core/crates/sol-macro" } diff --git a/crates/contract/Cargo.toml b/crates/contract/Cargo.toml index 315b0847ea3..be7c3f97224 100644 --- a/crates/contract/Cargo.toml +++ b/crates/contract/Cargo.toml @@ -25,5 +25,8 @@ alloy-sol-types.workspace = true thiserror.workspace = true [dev-dependencies] +alloy-rpc-client.workspace = true +alloy-transport-http.workspace = true alloy-node-bindings.workspace = true +reqwest.workspace = true tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/crates/contract/src/call.rs b/crates/contract/src/call.rs index 4c225f2dbae..267f4864e6f 100644 --- a/crates/contract/src/call.rs +++ b/crates/contract/src/call.rs @@ -6,6 +6,7 @@ use alloy_primitives::{Address, Bytes, U256, U64}; use alloy_providers::Provider; use alloy_rpc_types::{state::StateOverride, BlockId, TransactionReceipt}; use alloy_sol_types::SolCall; +use alloy_transport::Transport; use std::{ future::{Future, IntoFuture}, marker::PhantomData, @@ -14,13 +15,13 @@ use std::{ /// [`CallBuilder`] using a [`SolCall`] type as the call decoder. // NOTE: please avoid changing this type due to its use in the `sol!` macro. -pub type SolCallBuilder = CallBuilder>; +pub type SolCallBuilder = CallBuilder>; /// [`CallBuilder`] using a [`Function`] as the call decoder. -pub type DynCallBuilder = CallBuilder; +pub type DynCallBuilder = CallBuilder; /// [`CallBuilder`] that does not have a call decoder. -pub type RawCallBuilder = CallBuilder; +pub type RawCallBuilder = CallBuilder; mod private { pub trait Sealed {} @@ -117,7 +118,7 @@ impl CallDecoder for () { /// Using [`sol!`][sol]: /// /// ```no_run -/// # async fn test(provider: P) -> Result<(), Box> { +/// # async fn test>(provider: P) -> Result<(), Box> { /// use alloy_contract::SolCallBuilder; /// use alloy_primitives::{Address, U256}; /// use alloy_sol_types::sol; @@ -138,13 +139,13 @@ impl CallDecoder for () { /// // Through `contract.(args...)` /// let a = U256::ZERO; /// let b = true; -/// let builder: SolCallBuilder<_, MyContract::doStuffCall> = contract.doStuff(a, b); +/// let builder: SolCallBuilder<_, _, _, MyContract::doStuffCall> = contract.doStuff(a, b); /// let MyContract::doStuffReturn { c: _, d: _ } = builder.call().await?; /// /// // Through `contract.call_builder(&)`: /// // (note that this is discouraged because it's inherently less type-safe) /// let call = MyContract::doStuffCall { a, b }; -/// let builder: SolCallBuilder<_, MyContract::doStuffCall> = contract.call_builder(&call); +/// let builder: SolCallBuilder<_, _, _, MyContract::doStuffCall> = contract.call_builder(&call); /// let MyContract::doStuffReturn { c: _, d: _ } = builder.call().await?; /// # Ok(()) /// # } @@ -153,7 +154,7 @@ impl CallDecoder for () { /// Using [`ContractInstance`](crate::ContractInstance): /// /// ```no_run -/// # async fn test(provider: P, dynamic_abi: alloy_json_abi::JsonAbi) -> Result<(), Box> { +/// # async fn test>(provider: P, dynamic_abi: alloy_json_abi::JsonAbi) -> Result<(), Box> { /// use alloy_primitives::{Address, Bytes, U256}; /// use alloy_dyn_abi::DynSolValue; /// use alloy_contract::{CallBuilder, ContractInstance, DynCallBuilder, Interface, RawCallBuilder}; @@ -167,16 +168,16 @@ impl CallDecoder for () { /// let provider = ...; /// # ); /// let address = Address::ZERO; -/// let contract: ContractInstance<_> = interface.connect(address, &provider); +/// let contract: ContractInstance<_, _, _> = interface.connect(address, &provider); /// /// // Build and call the function: -/// let call_builder: DynCallBuilder<_> = contract.function("doStuff", &[U256::ZERO.into(), true.into()])?; +/// let call_builder: DynCallBuilder<_, _, _> = contract.function("doStuff", &[U256::ZERO.into(), true.into()])?; /// let result: Vec = call_builder.call().await?; /// /// // You can also decode the output manually. Get the raw bytes: /// let raw_result: Bytes = call_builder.call_raw().await?; /// // Or, equivalently: -/// let raw_builder: RawCallBuilder<_> = call_builder.clone().clear_decoder(); +/// let raw_builder: RawCallBuilder<_, _, _> = call_builder.clone().clear_decoder(); /// let raw_result: Bytes = raw_builder.call().await?; /// // Decode the raw bytes: /// let decoded_result: Vec = call_builder.decode_output(raw_result, false)?; @@ -187,7 +188,7 @@ impl CallDecoder for () { /// [sol]: alloy_sol_types::sol #[derive(Clone)] #[must_use = "call builders do nothing unless you `.call`, `.send`, or `.await` them"] -pub struct CallBuilder { +pub struct CallBuilder { // TODO: this will not work with `send_transaction` and does not differentiate between EIP-1559 // and legacy tx request: N::TransactionRequest, @@ -195,28 +196,30 @@ pub struct CallBuilder { state: Option, provider: P, decoder: D, + transport: PhantomData, } // See [`ContractInstance`]. -impl> DynCallBuilder { +impl> DynCallBuilder { pub(crate) fn new_dyn(provider: P, function: &Function, args: &[DynSolValue]) -> Result { Ok(Self::new_inner(provider, function.abi_encode_input(args)?.into(), function.clone())) } /// Clears the decoder, returning a raw call builder. #[inline] - pub fn clear_decoder(self) -> RawCallBuilder { + pub fn clear_decoder(self) -> RawCallBuilder { RawCallBuilder { request: self.request, block: self.block, state: self.state, provider: self.provider, decoder: (), + transport: PhantomData, } } } -impl, C: SolCall> SolCallBuilder { +impl, C: SolCall> SolCallBuilder { // `sol!` macro constructor, see `#[sol(rpc)]`. Not public API. // NOTE: please avoid changing this function due to its use in the `sol!` macro. #[doc(hidden)] @@ -226,18 +229,19 @@ impl, C: SolCall> SolCallBuilder { /// Clears the decoder, returning a raw call builder. #[inline] - pub fn clear_decoder(self) -> RawCallBuilder { + pub fn clear_decoder(self) -> RawCallBuilder { RawCallBuilder { request: self.request, block: self.block, state: self.state, provider: self.provider, decoder: (), + transport: PhantomData, } } } -impl> RawCallBuilder { +impl> RawCallBuilder { /// Creates a new call builder with the provided provider and ABI encoded input. /// /// Will not decode the output of the call, meaning that [`call`](Self::call) will behave the @@ -248,7 +252,7 @@ impl> RawCallBuilder { } } -impl, D: CallDecoder> CallBuilder { +impl, D: CallDecoder> CallBuilder { fn new_inner(provider: P, input: Bytes, decoder: D) -> Self { Self { request: ::default().with_input(input), @@ -256,6 +260,7 @@ impl, D: CallDecoder> CallBuilder { provider, block: None, state: None, + transport: PhantomData, } } @@ -405,15 +410,16 @@ impl, D: CallDecoder> CallBuilder { } } -impl CallBuilder { +impl CallBuilder { /// Clones the provider and returns a new builder with the cloned provider. - pub fn with_cloned_provider(self) -> CallBuilder { + pub fn with_cloned_provider(self) -> CallBuilder { CallBuilder { request: self.request, block: self.block, state: self.state, provider: self.provider.clone(), decoder: self.decoder, + transport: PhantomData, } } } @@ -421,10 +427,11 @@ impl CallBuilder { /// [`CallBuilder`] can be turned into a [`Future`] automatically with `.await`. /// /// Defaults to calling [`CallBuilder::call`]. -impl IntoFuture for CallBuilder +impl IntoFuture for CallBuilder where N: Network, - P: Provider, + T: Transport + Clone, + P: Provider, D: CallDecoder + Send + Sync, Self: 'static, { @@ -441,7 +448,7 @@ where } } -impl std::fmt::Debug for CallBuilder { +impl std::fmt::Debug for CallBuilder { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("CallBuilder") @@ -457,12 +464,16 @@ impl std::fmt::Debug for CallBuilder { #[allow(unused_imports)] mod tests { use super::*; + use alloy_network::Ethereum; use alloy_node_bindings::{Anvil, AnvilInstance}; use alloy_primitives::{address, b256, bytes, hex}; - use alloy_providers::{HttpProvider, Provider}; + use alloy_providers::{HttpProvider, Provider, RootProvider}; + use alloy_rpc_client::RpcClient; use alloy_sol_types::sol; + use alloy_transport_http::Http; + use reqwest::Client; - /*#[test] + #[test] fn empty_constructor() { sol! { #[sol(rpc, bytecode = "6942")] @@ -471,7 +482,7 @@ mod tests { } } - let provider = Provider::try_from("http://localhost:8545").unwrap(); + let (provider, _anvil) = spawn_anvil(); let call_builder = EmptyConstructor::deploy_builder(&provider); assert_eq!(*call_builder.calldata(), bytes!("6942")); } @@ -495,7 +506,7 @@ mod tests { #[test] fn call_encoding() { - let provider = Provider::try_from("http://localhost:8545").unwrap(); + let (provider, _anvil) = spawn_anvil(); let contract = MyContract::new(Address::ZERO, &&provider).with_cloned_provider(); let call_builder = contract.doStuff(U256::ZERO, true).with_cloned_provider(); assert_eq!( @@ -513,7 +524,7 @@ mod tests { #[test] fn deploy_encoding() { - let provider = Provider::try_from("http://localhost:8545").unwrap(); + let (provider, _anvil) = spawn_anvil(); let bytecode = &MyContract::BYTECODE[..]; let call_builder = MyContract::deploy_builder(&provider, false); assert_eq!( @@ -563,9 +574,10 @@ mod tests { ); } - fn spawn_anvil() -> (HttpProvider, AnvilInstance) { + fn spawn_anvil() -> (HttpProvider, AnvilInstance) { let anvil = Anvil::new().spawn(); - let provider = Provider::try_from(anvil.endpoint()).unwrap(); - (provider, anvil) - }*/ + let url = anvil.endpoint().parse().unwrap(); + let http = Http::::new(url); + (RootProvider::::new(RpcClient::new(http, true)), anvil) + } } diff --git a/crates/contract/src/instance.rs b/crates/contract/src/instance.rs index fdcefd85f76..0382a9f9371 100644 --- a/crates/contract/src/instance.rs +++ b/crates/contract/src/instance.rs @@ -4,6 +4,7 @@ use alloy_json_abi::{Function, JsonAbi}; use alloy_network::Network; use alloy_primitives::{Address, Selector}; use alloy_providers::Provider; +use alloy_transport::Transport; use std::marker::PhantomData; /// A handle to an Ethereum contract at a specific address. @@ -11,18 +12,19 @@ use std::marker::PhantomData; /// A contract is an abstraction of an executable program on Ethereum. Every deployed contract has /// an address, which is used to connect to it so that it may receive messages (transactions). #[derive(Clone)] -pub struct ContractInstance { +pub struct ContractInstance { address: Address, provider: P, interface: Interface, + transport: PhantomData, network: PhantomData, } -impl ContractInstance { +impl ContractInstance { /// Creates a new contract from the provided address, provider, and interface. #[inline] pub const fn new(address: Address, provider: P, interface: Interface) -> Self { - Self { address, provider, interface, network: PhantomData } + Self { address, provider, interface, transport: PhantomData, network: PhantomData } } /// Returns a reference to the contract's address. @@ -39,7 +41,7 @@ impl ContractInstance { /// Returns a new contract instance at `address`. #[inline] - pub fn at(mut self, address: Address) -> ContractInstance { + pub fn at(mut self, address: Address) -> ContractInstance { self.set_address(address); self } @@ -57,20 +59,21 @@ impl ContractInstance { } } -impl ContractInstance { +impl ContractInstance { /// Clones the provider and returns a new contract instance with the cloned provider. #[inline] - pub fn with_cloned_provider(self) -> ContractInstance { + pub fn with_cloned_provider(self) -> ContractInstance { ContractInstance { address: self.address, provider: self.provider.clone(), interface: self.interface, + transport: PhantomData, network: PhantomData, } } } -impl> ContractInstance { +impl> ContractInstance { /// Returns a transaction builder for the provided function name. /// /// If there are multiple functions with the same name due to overloading, consider using @@ -80,7 +83,7 @@ impl> ContractInstance { &self, name: &str, args: &[DynSolValue], - ) -> Result> { + ) -> Result> { let function = self.interface.get_from_name(name)?; CallBuilder::new_dyn(&self.provider, function, args) } @@ -90,13 +93,13 @@ impl> ContractInstance { &self, selector: &Selector, args: &[DynSolValue], - ) -> Result> { + ) -> Result> { let function = self.interface.get_from_selector(selector)?; CallBuilder::new_dyn(&self.provider, function, args) } } -impl std::ops::Deref for ContractInstance { +impl std::ops::Deref for ContractInstance { type Target = Interface; fn deref(&self) -> &Self::Target { @@ -104,7 +107,7 @@ impl std::ops::Deref for ContractInstance { } } -impl std::fmt::Debug for ContractInstance { +impl std::fmt::Debug for ContractInstance { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ContractInstance").field("address", &self.address).finish() } diff --git a/crates/contract/src/interface.rs b/crates/contract/src/interface.rs index 97bdd3469c3..836b7317cff 100644 --- a/crates/contract/src/interface.rs +++ b/crates/contract/src/interface.rs @@ -116,7 +116,11 @@ impl Interface { } /// Create a [`ContractInstance`] from this ABI for a contract at the given address. - pub const fn connect(self, address: Address, provider: P) -> ContractInstance { + pub const fn connect( + self, + address: Address, + provider: P, + ) -> ContractInstance { ContractInstance::new(address, provider, self) } } diff --git a/crates/contract/src/lib.rs b/crates/contract/src/lib.rs index 37b580a024a..a7c77710b46 100644 --- a/crates/contract/src/lib.rs +++ b/crates/contract/src/lib.rs @@ -15,9 +15,8 @@ #![deny(unused_must_use, rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -// todo: re-enable when alloy-sol-macro is adjusted -//#[cfg(test)] -//extern crate self as alloy_contract; +#[cfg(test)] +extern crate self as alloy_contract; mod error; pub use error::*; @@ -35,5 +34,7 @@ pub use call::*; // NOTE: please avoid changing the API of this module due to its use in the `sol!` macro. #[doc(hidden)] pub mod private { + pub use alloy_network::Network; pub use alloy_providers::Provider; + pub use alloy_transport::Transport; } diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs index a4193e2e3fd..81b8f5b41a0 100644 --- a/crates/network/src/lib.rs +++ b/crates/network/src/lib.rs @@ -56,7 +56,7 @@ pub struct BlockResponse { /// Captures type info for network-specific RPC requests/responses. // todo: block responses are ethereum only, so we need to include this in here too, or make `Block` // generic over tx/header type -pub trait Network: Sized + Send + Sync + 'static { +pub trait Network: Clone + Copy + Sized + Send + Sync + 'static { #[doc(hidden)] /// Asserts that this trait can only be implemented on a ZST. const __ASSERT_ZST: () = { diff --git a/crates/providers/src/new.rs b/crates/providers/src/new.rs index 333b630b29d..7e594f6cab6 100644 --- a/crates/providers/src/new.rs +++ b/crates/providers/src/new.rs @@ -38,7 +38,7 @@ pub struct RootProvider { } impl RootProvider { - pub(crate) fn new(client: RpcClient) -> Self { + pub fn new(client: RpcClient) -> Self { Self { inner: Arc::new(RootProviderInner::new(client)) } } } @@ -483,7 +483,7 @@ mod tests { let _ = tracing_subscriber::fmt::try_init(); } - fn anvil_provider() -> (HttpProvider, AnvilInstance) { + fn spawn_anvil() -> (HttpProvider, AnvilInstance) { let anvil = Anvil::new().spawn(); let url = anvil.endpoint().parse().unwrap(); let http = Http::::new(url); @@ -493,7 +493,7 @@ mod tests { #[tokio::test] async fn test_send_tx() { init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let tx = TransactionRequest { value: Some(U256::from(100)), @@ -511,7 +511,7 @@ mod tests { #[tokio::test] async fn gets_block_number() { init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let num = provider.get_block_number().await.unwrap(); assert_eq!(0, num) @@ -522,7 +522,7 @@ mod tests { use super::RawProvider; init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let num: U64 = provider.raw_request("eth_blockNumber", ()).await.unwrap(); assert_eq!(0, num.to::()) @@ -531,7 +531,7 @@ mod tests { #[tokio::test] async fn gets_transaction_count() { init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let count = provider .get_transaction_count( @@ -546,7 +546,7 @@ mod tests { #[tokio::test] async fn gets_block_by_hash() { init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let num = 0; let tag: BlockNumberOrTag = num.into(); @@ -561,7 +561,7 @@ mod tests { use super::RawProvider; init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let num = 0; let tag: BlockNumberOrTag = num.into(); @@ -580,7 +580,7 @@ mod tests { #[tokio::test] async fn gets_block_by_number_full() { init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let num = 0; let tag: BlockNumberOrTag = num.into(); @@ -591,7 +591,7 @@ mod tests { #[tokio::test] async fn gets_block_by_number() { init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let num = 0; let tag: BlockNumberOrTag = num.into(); @@ -602,7 +602,7 @@ mod tests { #[tokio::test] async fn gets_client_version() { init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let version = provider.get_client_version().await.unwrap(); assert!(version.contains("anvil")); @@ -636,7 +636,7 @@ mod tests { #[cfg(feature = "anvil")] async fn gets_code_at() { init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); // Set the code let addr = alloy_primitives::Address::with_last_byte(16); @@ -650,7 +650,7 @@ mod tests { #[tokio::test] async fn gets_storage_at() { init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let addr = alloy_primitives::Address::with_last_byte(16); let storage = provider.get_storage_at(addr, U256::ZERO, None).await.unwrap(); @@ -661,7 +661,7 @@ mod tests { #[ignore] async fn gets_transaction_by_hash() { init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let tx = provider .get_transaction_by_hash(b256!( @@ -680,7 +680,7 @@ mod tests { #[ignore] async fn gets_logs() { init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let filter = Filter::new() .at_block_hash(b256!( @@ -697,7 +697,7 @@ mod tests { #[ignore] async fn gets_tx_receipt() { init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let receipt = provider .get_transaction_receipt(b256!( @@ -716,7 +716,7 @@ mod tests { #[tokio::test] async fn gets_fee_history() { init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let block_number = provider.get_block_number().await.unwrap(); let fee_history = provider @@ -734,7 +734,7 @@ mod tests { #[ignore] // Anvil has yet to implement the `eth_getBlockReceipts` method. async fn gets_block_receipts() { init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let receipts = provider.get_block_receipts(BlockNumberOrTag::Latest).await.unwrap(); assert!(receipts.is_some()); @@ -743,7 +743,7 @@ mod tests { #[tokio::test] async fn gets_block_traces() { init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let traces = provider.trace_block(BlockNumberOrTag::Latest).await.unwrap(); assert_eq!(traces.len(), 0); @@ -752,7 +752,7 @@ mod tests { #[tokio::test] async fn sends_raw_transaction() { init_tracing(); - let (provider, _anvil) = anvil_provider(); + let (provider, _anvil) = spawn_anvil(); let pending = provider .send_raw_transaction( From 2404e8833a40c112eb2b970be6ce54dd34323ca6 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 23:10:12 +0100 Subject: [PATCH 33/65] chore: use patched sol-macro --- Cargo.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 976f3411a8a..ff0904de6ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,7 +115,5 @@ tempfile = "3.10" # TODO: Keep around until alloy-contract is stable. # This should only be used in `alloy-contract` tests. -# [patch.crates-io] -# alloy-sol-macro = { git = "https://github.com/alloy-rs/core", rev = "18b0509950c90d9ec38f25913b692ae4cdd6f227" } [patch.crates-io] -alloy-sol-macro = { path = "../core/crates/sol-macro" } +alloy-sol-macro = { git = "https://github.com/alloy-rs/core", rev = "1882c819a2d26ad04d4720d3751c9833d3bbc54a" } From 288959fd43392a6d077fbd6832e3947afef31a66 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 7 Mar 2024 23:33:10 +0100 Subject: [PATCH 34/65] chore: update sol-macro patch --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ff0904de6ef..02b391eb1fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,4 +116,4 @@ tempfile = "3.10" # TODO: Keep around until alloy-contract is stable. # This should only be used in `alloy-contract` tests. [patch.crates-io] -alloy-sol-macro = { git = "https://github.com/alloy-rs/core", rev = "1882c819a2d26ad04d4720d3751c9833d3bbc54a" } +alloy-sol-macro = { git = "https://github.com/alloy-rs/core", rev = "ab0cab1047a088be6cffa4e7a2fcde7cf77aa460" } From 201df2b8eaa14584e0868f44f10103e53e63f60f Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 00:24:50 +0100 Subject: [PATCH 35/65] refactor: move anvil methods to extension trait --- crates/providers/Cargo.toml | 3 --- crates/providers/src/new.rs | 17 +++++++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/providers/Cargo.toml b/crates/providers/Cargo.toml index 48c5a61bb04..24b5fee66e4 100644 --- a/crates/providers/Cargo.toml +++ b/crates/providers/Cargo.toml @@ -37,6 +37,3 @@ alloy-node-bindings.workspace = true alloy-rlp.workspace = true tokio = { workspace = true, features = ["macros"] } tracing-subscriber = { workspace = true, features = ["fmt"] } - -[features] -anvil = [] diff --git a/crates/providers/src/new.rs b/crates/providers/src/new.rs index 7e594f6cab6..ae89139b305 100644 --- a/crates/providers/src/new.rs +++ b/crates/providers/src/new.rs @@ -359,12 +359,6 @@ pub trait Provider: Send + Sync Ok((max_fee_per_gas, max_priority_fee_per_gas)) } - // todo: move to extension trait - #[cfg(feature = "anvil")] - async fn set_code(&self, address: Address, code: &'static str) -> TransportResult<()> { - self.client().prepare("anvil_setCode", (address, code)).await - } - async fn get_proof( &self, address: Address, @@ -409,6 +403,17 @@ pub trait Provider: Send + Sync } } +/// Extension trait for Anvil specific JSON-RPC methods. +pub trait AnvilProvider: Provider { + /// Set the bytecode of a given account. + #[cfg(feature = "anvil")] + async fn set_code(&self, address: Address, code: &'static str) -> TransportResult<()> { + self.client().prepare("anvil_setCode", (address, code)).await + } +} + +impl AnvilProvider for P where P: Provider {} + /// Extension trait for raw RPC requests. #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] From 14d8329c9df6028e7bc23494e5d09be67ce0212e Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 00:29:26 +0100 Subject: [PATCH 36/65] feat: populate gas helpers --- crates/providers/src/new.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/crates/providers/src/new.rs b/crates/providers/src/new.rs index ae89139b305..0eec32fdfc9 100644 --- a/crates/providers/src/new.rs +++ b/crates/providers/src/new.rs @@ -123,16 +123,31 @@ pub trait Provider: Send + Sync self.client().prepare("eth_getBlockByNumber", (number, hydrate)).await } - // todo eip-1559 and blobs as well + /// Populates the legacy gas price field of the given transaction request. async fn populate_gas( &self, tx: &mut N::TransactionRequest, block: Option, ) -> TransportResult<()> { - let _ = self.estimate_gas(&*tx, block).await; + use alloy_network::TransactionBuilder; + let gas = self.estimate_gas(&*tx, block).await; - todo!() - // gas.map(|gas| tx.set_gas_limit(gas.try_into().unwrap())) + gas.map(|gas| tx.set_gas_limit(gas.try_into().unwrap())) + } + + /// Populates the EIP-1559 gas price fields of the given transaction request. + async fn populate_gas_eip1559( + &self, + tx: &mut N::TransactionRequest, + estimator: Option, + ) -> TransportResult<()> { + use alloy_network::TransactionBuilder; + let gas = self.estimate_eip1559_fees(estimator).await; + + gas.map(|(max_fee_per_gas, max_priority_fee_per_gas)| { + tx.set_max_fee_per_gas(max_fee_per_gas.try_into().unwrap()); + tx.set_max_priority_fee_per_gas(max_priority_fee_per_gas.try_into().unwrap()); + }) } /// Broadcasts a transaction, returning a [`PendingTransaction`] that resolves once the @@ -316,6 +331,7 @@ pub trait Provider: Send + Sync } /// Estimates the EIP1559 `maxFeePerGas` and `maxPriorityFeePerGas` fields. + /// /// Receives an optional [EstimatorFunction] that can be used to modify /// how to estimate these fees. async fn estimate_eip1559_fees( From 5372ae0908a84c7db974e56c55232c672502d2c0 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 00:35:20 +0100 Subject: [PATCH 37/65] fix: remove anvil cfg --- crates/providers/src/lib.rs | 4 +++- crates/providers/src/new.rs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/providers/src/lib.rs b/crates/providers/src/lib.rs index 8b376392a14..41e7d9445cb 100644 --- a/crates/providers/src/lib.rs +++ b/crates/providers/src/lib.rs @@ -35,6 +35,8 @@ mod heart; pub use heart::{PendingTransaction, WatchConfig}; pub mod new; -pub use new::{Provider, ProviderRef, RawProvider, RootProvider, WeakProvider}; + +#[doc(inline)] +pub use new::{AnvilProvider, Provider, ProviderRef, RawProvider, RootProvider, WeakProvider}; pub mod utils; diff --git a/crates/providers/src/new.rs b/crates/providers/src/new.rs index 0eec32fdfc9..8e26cd27486 100644 --- a/crates/providers/src/new.rs +++ b/crates/providers/src/new.rs @@ -420,9 +420,10 @@ pub trait Provider: Send + Sync } /// Extension trait for Anvil specific JSON-RPC methods. +#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] pub trait AnvilProvider: Provider { /// Set the bytecode of a given account. - #[cfg(feature = "anvil")] async fn set_code(&self, address: Address, code: &'static str) -> TransportResult<()> { self.client().prepare("anvil_setCode", (address, code)).await } From abf6e4bf505533fe76c11ea803e93f2b0def95a8 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 00:37:59 +0100 Subject: [PATCH 38/65] chore: clippy --- crates/providers/src/new.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/providers/src/new.rs b/crates/providers/src/new.rs index 8e26cd27486..1804d9e59e8 100644 --- a/crates/providers/src/new.rs +++ b/crates/providers/src/new.rs @@ -132,7 +132,7 @@ pub trait Provider: Send + Sync use alloy_network::TransactionBuilder; let gas = self.estimate_gas(&*tx, block).await; - gas.map(|gas| tx.set_gas_limit(gas.try_into().unwrap())) + gas.map(|gas| tx.set_gas_limit(gas)) } /// Populates the EIP-1559 gas price fields of the given transaction request. @@ -145,8 +145,8 @@ pub trait Provider: Send + Sync let gas = self.estimate_eip1559_fees(estimator).await; gas.map(|(max_fee_per_gas, max_priority_fee_per_gas)| { - tx.set_max_fee_per_gas(max_fee_per_gas.try_into().unwrap()); - tx.set_max_priority_fee_per_gas(max_priority_fee_per_gas.try_into().unwrap()); + tx.set_max_fee_per_gas(max_fee_per_gas); + tx.set_max_priority_fee_per_gas(max_priority_fee_per_gas); }) } From 04158eab13e4fc139c4f43f4ecb71ed873afedef Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 01:20:39 +0100 Subject: [PATCH 39/65] feat: impl `TxSigner` for aws, gcp --- crates/providers/src/lib.rs | 1 - crates/signer-aws/Cargo.toml | 2 ++ crates/signer-aws/src/signer.rs | 28 ++++++++++++++++++++++++++++ crates/signer-gcp/Cargo.toml | 2 ++ crates/signer-gcp/src/signer.rs | 28 ++++++++++++++++++++++++++++ crates/signer-ledger/src/signer.rs | 3 +-- 6 files changed, 61 insertions(+), 3 deletions(-) diff --git a/crates/providers/src/lib.rs b/crates/providers/src/lib.rs index 41e7d9445cb..a72b12da36b 100644 --- a/crates/providers/src/lib.rs +++ b/crates/providers/src/lib.rs @@ -26,7 +26,6 @@ pub type HttpProvider = RootProvider>; extern crate tracing; mod builder; - pub use builder::{ProviderBuilder, ProviderLayer, Stack}; mod chain; diff --git a/crates/signer-aws/Cargo.toml b/crates/signer-aws/Cargo.toml index c2e9b2e1083..df873e2a666 100644 --- a/crates/signer-aws/Cargo.toml +++ b/crates/signer-aws/Cargo.toml @@ -12,6 +12,8 @@ repository.workspace = true exclude.workspace = true [dependencies] +alloy-consensus.workspace = true +alloy-network.workspace = true alloy-primitives.workspace = true alloy-signer.workspace = true diff --git a/crates/signer-aws/src/signer.rs b/crates/signer-aws/src/signer.rs index cfad75676c6..dcf0d7b4f61 100644 --- a/crates/signer-aws/src/signer.rs +++ b/crates/signer-aws/src/signer.rs @@ -1,3 +1,4 @@ +use alloy_consensus::SignableTransaction; use alloy_primitives::{hex, Address, ChainId, B256}; use alloy_signer::{Result, Signature, Signer}; use async_trait::async_trait; @@ -92,6 +93,33 @@ pub enum AwsSignerError { PublicKeyNotFound, } +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl alloy_network::TxSigner for AwsSigner { + #[inline] + async fn sign_transaction( + &self, + tx: &mut dyn SignableTransaction, + ) -> Result { + let chain_id = match (self.chain_id(), tx.chain_id()) { + (Some(signer), Some(tx)) if signer != tx => { + return Err(alloy_signer::Error::TransactionChainIdMismatch { signer, tx }) + } + (Some(signer), _) => Some(signer), + (None, Some(tx)) => Some(tx), + _ => None, + }; + + let mut sig = + self.sign_hash(&tx.signature_hash()).await.map_err(alloy_signer::Error::other)?; + + if let Some(chain_id) = chain_id.or_else(|| tx.chain_id()) { + sig = sig.with_chain_id(chain_id); + } + Ok(sig) + } +} + #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl Signer for AwsSigner { diff --git a/crates/signer-gcp/Cargo.toml b/crates/signer-gcp/Cargo.toml index c50534c3ead..890c305f3bd 100644 --- a/crates/signer-gcp/Cargo.toml +++ b/crates/signer-gcp/Cargo.toml @@ -12,6 +12,8 @@ repository.workspace = true exclude.workspace = true [dependencies] +alloy-consensus.workspace = true +alloy-network.workspace = true alloy-primitives.workspace = true alloy-signer.workspace = true diff --git a/crates/signer-gcp/src/signer.rs b/crates/signer-gcp/src/signer.rs index cefbdf45051..7a51106f052 100644 --- a/crates/signer-gcp/src/signer.rs +++ b/crates/signer-gcp/src/signer.rs @@ -1,3 +1,4 @@ +use alloy_consensus::SignableTransaction; use alloy_primitives::{hex, Address, B256}; use alloy_signer::{Result, Signature, Signer}; use async_trait::async_trait; @@ -144,6 +145,33 @@ pub enum GcpSignerError { K256(#[from] ecdsa::Error), } +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl alloy_network::TxSigner for GcpSigner { + #[inline] + async fn sign_transaction( + &self, + tx: &mut dyn SignableTransaction, + ) -> Result { + let chain_id = match (self.chain_id(), tx.chain_id()) { + (Some(signer), Some(tx)) if signer != tx => { + return Err(alloy_signer::Error::TransactionChainIdMismatch { signer, tx }) + } + (Some(signer), _) => Some(signer), + (None, Some(tx)) => Some(tx), + _ => None, + }; + + let mut sig = + self.sign_hash(&tx.signature_hash()).await.map_err(alloy_signer::Error::other)?; + + if let Some(chain_id) = chain_id.or_else(|| tx.chain_id()) { + sig = sig.with_chain_id(chain_id); + } + Ok(sig) + } +} + #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl Signer for GcpSigner { diff --git a/crates/signer-ledger/src/signer.rs b/crates/signer-ledger/src/signer.rs index 5fb38dab555..f8bf2399d9b 100644 --- a/crates/signer-ledger/src/signer.rs +++ b/crates/signer-ledger/src/signer.rs @@ -297,12 +297,11 @@ impl LedgerSigner { #[cfg(test)] mod tests { use super::*; + use alloy_network::TxSigner; use alloy_primitives::{address, bytes, U256}; use alloy_rlp::Decodable; use std::sync::OnceLock; - use alloy_network::TxSigner; - const DTYPE: DerivationType = DerivationType::LedgerLive(0); fn my_address() -> Address { From 4715b3e0298a7f91a7c8ec215e0611f356f2dccb Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 02:28:45 +0100 Subject: [PATCH 40/65] refactor: move `Receipt` trait --- crates/consensus/src/lib.rs | 2 +- crates/consensus/src/receipt/mod.rs | 24 +++++++++++++ crates/consensus/src/receipt/receipts.rs | 42 +++++++++++++++++++++++ crates/network/src/ethereum/mod.rs | 1 - crates/network/src/ethereum/receipt.rs | 43 ------------------------ crates/network/src/lib.rs | 3 -- crates/network/src/receipt.rs | 23 ------------- 7 files changed, 67 insertions(+), 71 deletions(-) delete mode 100644 crates/network/src/ethereum/receipt.rs delete mode 100644 crates/network/src/receipt.rs diff --git a/crates/consensus/src/lib.rs b/crates/consensus/src/lib.rs index fde0e51b4d6..edebfddd184 100644 --- a/crates/consensus/src/lib.rs +++ b/crates/consensus/src/lib.rs @@ -21,7 +21,7 @@ mod header; pub use header::{Header, EMPTY_OMMER_ROOT_HASH, EMPTY_ROOT_HASH}; mod receipt; -pub use receipt::{Receipt, ReceiptEnvelope, ReceiptWithBloom}; +pub use receipt::{Receipt, ReceiptEnvelope, ReceiptWithBloom, TxReceipt}; mod transaction; pub use transaction::{ diff --git a/crates/consensus/src/receipt/mod.rs b/crates/consensus/src/receipt/mod.rs index a1e4de4dcfa..a3a13d0976b 100644 --- a/crates/consensus/src/receipt/mod.rs +++ b/crates/consensus/src/receipt/mod.rs @@ -1,9 +1,33 @@ +use alloy_primitives::{Bloom, Log}; + mod envelope; pub use envelope::ReceiptEnvelope; mod receipts; pub use receipts::{Receipt, ReceiptWithBloom}; +/// Receipt is the result of a transaction execution. +pub trait TxReceipt { + /// Returns true if the transaction was successful. + fn success(&self) -> bool; + + /// Returns the bloom filter for the logs in the receipt. This operation + /// may be expensive. + fn bloom(&self) -> Bloom; + + /// Returns the bloom filter for the logs in the receipt, if it is cheap to + /// compute. + fn bloom_cheap(&self) -> Option { + None + } + + /// Returns the cumulative gas used in the block after this transaction was executed. + fn cumulative_gas_used(&self) -> u64; + + /// Returns the logs emitted by this transaction. + fn logs(&self) -> &[Log]; +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/consensus/src/receipt/receipts.rs b/crates/consensus/src/receipt/receipts.rs index 8a9648418ea..26ace859da6 100644 --- a/crates/consensus/src/receipt/receipts.rs +++ b/crates/consensus/src/receipt/receipts.rs @@ -1,6 +1,8 @@ use alloy_primitives::{Bloom, Log}; use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable}; +use super::TxReceipt; + /// Receipt containing result of transaction execution. #[derive(Clone, Debug, PartialEq, Eq, Default)] pub struct Receipt { @@ -28,6 +30,24 @@ impl Receipt { } } +impl TxReceipt for Receipt { + fn success(&self) -> bool { + self.success + } + + fn bloom(&self) -> Bloom { + self.bloom_slow() + } + + fn cumulative_gas_used(&self) -> u64 { + self.cumulative_gas_used + } + + fn logs(&self) -> &[Log] { + &self.logs + } +} + /// [`Receipt`] with calculated bloom filter. /// /// This convenience type allows us to lazily calculate the bloom filter for a @@ -42,6 +62,28 @@ pub struct ReceiptWithBloom { pub bloom: Bloom, } +impl TxReceipt for ReceiptWithBloom { + fn success(&self) -> bool { + self.receipt.success + } + + fn bloom(&self) -> Bloom { + self.bloom + } + + fn bloom_cheap(&self) -> Option { + Some(self.bloom) + } + + fn cumulative_gas_used(&self) -> u64 { + self.receipt.cumulative_gas_used + } + + fn logs(&self) -> &[Log] { + &self.receipt.logs + } +} + impl From for ReceiptWithBloom { fn from(receipt: Receipt) -> Self { let bloom = receipt.bloom_slow(); diff --git a/crates/network/src/ethereum/mod.rs b/crates/network/src/ethereum/mod.rs index 68a2f65dfc2..28f5f3fe733 100644 --- a/crates/network/src/ethereum/mod.rs +++ b/crates/network/src/ethereum/mod.rs @@ -4,7 +4,6 @@ use alloy_primitives::{Address, TxKind, U256, U64}; use alloy_rpc_types::request::TransactionRequest; use async_trait::async_trait; -mod receipt; mod signer; pub use signer::EthereumSigner; diff --git a/crates/network/src/ethereum/receipt.rs b/crates/network/src/ethereum/receipt.rs deleted file mode 100644 index 6e47ff80da1..00000000000 --- a/crates/network/src/ethereum/receipt.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::Receipt; -use alloy_consensus::ReceiptWithBloom; -use alloy_primitives::{Bloom, Log}; - -impl Receipt for alloy_consensus::Receipt { - fn success(&self) -> bool { - self.success - } - - fn bloom(&self) -> Bloom { - self.bloom_slow() - } - - fn cumulative_gas_used(&self) -> u64 { - self.cumulative_gas_used - } - - fn logs(&self) -> &[Log] { - &self.logs - } -} - -impl Receipt for ReceiptWithBloom { - fn success(&self) -> bool { - self.receipt.success - } - - fn bloom(&self) -> Bloom { - self.bloom - } - - fn bloom_cheap(&self) -> Option { - Some(self.bloom) - } - - fn cumulative_gas_used(&self) -> u64 { - self.receipt.cumulative_gas_used - } - - fn logs(&self) -> &[Log] { - &self.receipt.logs - } -} diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs index 81b8f5b41a0..bb7db9206de 100644 --- a/crates/network/src/lib.rs +++ b/crates/network/src/lib.rs @@ -25,9 +25,6 @@ pub use transaction::{ TxSignerSync, }; -mod receipt; -pub use receipt::Receipt; - pub use alloy_eips::eip2718; mod ethereum; diff --git a/crates/network/src/receipt.rs b/crates/network/src/receipt.rs deleted file mode 100644 index 2648cce6623..00000000000 --- a/crates/network/src/receipt.rs +++ /dev/null @@ -1,23 +0,0 @@ -use alloy_primitives::{Bloom, Log}; - -/// Receipt is the result of a transaction execution. -pub trait Receipt { - /// Returns true if the transaction was successful. - fn success(&self) -> bool; - - /// Returns the bloom filter for the logs in the receipt. This operation - /// may be expensive. - fn bloom(&self) -> Bloom; - - /// Returns the bloom filter for the logs in the receipt, if it is cheap to - /// compute. - fn bloom_cheap(&self) -> Option { - None - } - - /// Returns the cumulative gas used in the block after this transaction was executed. - fn cumulative_gas_used(&self) -> u64; - - /// Returns the logs emitted by this transaction. - fn logs(&self) -> &[Log]; -} From b04e966e0dcdd735c3b76ef24831032bdec8bd71 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 02:30:13 +0100 Subject: [PATCH 41/65] refactor: move tx builder impl to own module --- crates/network/src/ethereum/builder.rs | 243 ++++++++++++++++++++++++ crates/network/src/ethereum/mod.rs | 244 +------------------------ 2 files changed, 246 insertions(+), 241 deletions(-) create mode 100644 crates/network/src/ethereum/builder.rs diff --git a/crates/network/src/ethereum/builder.rs b/crates/network/src/ethereum/builder.rs new file mode 100644 index 00000000000..9512f671e8b --- /dev/null +++ b/crates/network/src/ethereum/builder.rs @@ -0,0 +1,243 @@ +use crate::{ + BuilderResult, Ethereum, Network, NetworkSigner, TransactionBuilder, TransactionBuilderError, +}; +use alloy_consensus::{TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxLegacy}; +use alloy_primitives::{Address, TxKind, U256, U64}; +use alloy_rpc_types::request::TransactionRequest; +use async_trait::async_trait; + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl TransactionBuilder for alloy_rpc_types::TransactionRequest { + fn chain_id(&self) -> Option { + self.chain_id + } + + fn set_chain_id(&mut self, chain_id: alloy_primitives::ChainId) { + self.chain_id = Some(chain_id); + } + + fn nonce(&self) -> Option { + self.nonce + } + + fn set_nonce(&mut self, nonce: U64) { + self.nonce = Some(nonce); + } + + fn input(&self) -> Option<&alloy_primitives::Bytes> { + self.input.input() + } + + fn set_input(&mut self, input: alloy_primitives::Bytes) { + self.input.input = Some(input); + } + + fn to(&self) -> Option { + self.to.map(TxKind::Call).or(Some(TxKind::Create)) + } + + fn from(&self) -> Option
{ + self.from + } + + fn set_from(&mut self, from: Address) { + self.from = Some(from); + } + + fn set_to(&mut self, to: alloy_primitives::TxKind) { + match to { + TxKind::Create => self.to = None, + TxKind::Call(to) => self.to = Some(to), + } + } + + fn value(&self) -> Option { + self.value + } + + fn set_value(&mut self, value: alloy_primitives::U256) { + self.value = Some(value) + } + + fn gas_price(&self) -> Option { + self.gas_price + } + + fn set_gas_price(&mut self, gas_price: U256) { + self.gas_price = Some(gas_price); + } + + fn max_fee_per_gas(&self) -> Option { + self.max_fee_per_gas + } + + fn set_max_fee_per_gas(&mut self, max_fee_per_gas: U256) { + self.max_fee_per_gas = Some(max_fee_per_gas); + } + + fn max_priority_fee_per_gas(&self) -> Option { + self.max_priority_fee_per_gas + } + + fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: U256) { + self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas); + } + + fn max_fee_per_blob_gas(&self) -> Option { + self.max_fee_per_blob_gas + } + + fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: U256) { + self.max_fee_per_blob_gas = Some(max_fee_per_blob_gas) + } + + fn gas_limit(&self) -> Option { + self.gas + } + + fn set_gas_limit(&mut self, gas_limit: U256) { + self.gas = Some(gas_limit); + } + + fn build_unsigned(self) -> BuilderResult<::UnsignedTx> { + match ( + self.gas_price.as_ref(), + self.max_fee_per_gas.as_ref(), + self.access_list.as_ref(), + self.max_fee_per_blob_gas.as_ref(), + self.blob_versioned_hashes.as_ref(), + self.sidecar.as_ref(), + ) { + // Legacy transaction + (Some(_), None, None, None, None, None) => build_legacy(self).map(Into::into), + // EIP-2930 + // If only accesslist is set, and there are no EIP-1559 fees + (_, None, Some(_), None, None, None) => build_2930(self).map(Into::into), + // EIP-1559 + // If EIP-4844 fields are missing + (None, _, _, None, None, None) => build_1559(self).map(Into::into), + // EIP-4844 + // All blob fields required + (None, _, _, Some(_), Some(_), Some(_)) => { + build_4844(self).map(TxEip4844Variant::from).map(Into::into) + } + _ => build_legacy(self).map(Into::into), + } + } + + async fn build>( + self, + signer: &S, + ) -> BuilderResult<::TxEnvelope> { + // todo: BuilderResult + SignerResult + Ok(signer.sign(self.build_unsigned()?).await.unwrap()) + } +} + +/// Build a legacy transaction. +fn build_legacy(request: TransactionRequest) -> Result { + Ok(TxLegacy { + chain_id: request.chain_id, + nonce: request.nonce.ok_or_else(|| TransactionBuilderError::MissingKey("nonce"))?.to(), + gas_price: request + .gas_price + .ok_or_else(|| TransactionBuilderError::MissingKey("gas_price"))? + .to(), + gas_limit: request + .gas + .ok_or_else(|| TransactionBuilderError::MissingKey("gas_limit"))? + .to(), + to: request.to.into(), + value: request.value.unwrap_or_default(), + input: request.input.into_input().unwrap_or_default(), + }) +} + +/// Build an EIP-1559 transaction. +fn build_1559(request: TransactionRequest) -> Result { + Ok(TxEip1559 { + chain_id: request.chain_id.unwrap_or(1), + nonce: request.nonce.ok_or_else(|| TransactionBuilderError::MissingKey("nonce"))?.to(), + max_priority_fee_per_gas: request + .max_priority_fee_per_gas + .ok_or_else(|| TransactionBuilderError::MissingKey("max_priority_fee_per_gas"))? + .to(), + max_fee_per_gas: request + .max_fee_per_gas + .ok_or_else(|| TransactionBuilderError::MissingKey("max_fee_per_gas"))? + .to(), + gas_limit: request + .gas + .ok_or_else(|| TransactionBuilderError::MissingKey("gas_limit"))? + .to(), + to: request.to.into(), + value: request.value.unwrap_or_default(), + input: request.input.into_input().unwrap_or_default(), + access_list: convert_access_list(request.access_list.unwrap_or_default()), + }) +} + +/// Build an EIP-2930 transaction. +fn build_2930(request: TransactionRequest) -> Result { + Ok(TxEip2930 { + chain_id: request.chain_id.unwrap_or(1), + nonce: request.nonce.ok_or_else(|| TransactionBuilderError::MissingKey("nonce"))?.to(), + gas_price: request + .gas_price + .ok_or_else(|| TransactionBuilderError::MissingKey("gas_price"))? + .to(), + gas_limit: request + .gas + .ok_or_else(|| TransactionBuilderError::MissingKey("gas_limit"))? + .to(), + to: request.to.into(), + value: request.value.unwrap_or_default(), + input: request.input.into_input().unwrap_or_default(), + access_list: convert_access_list(request.access_list.unwrap_or_default()), + }) +} + +/// Build an EIP-4844 transaction. +fn build_4844(request: TransactionRequest) -> Result { + Ok(TxEip4844 { + chain_id: request.chain_id.unwrap_or(1), + nonce: request.nonce.ok_or_else(|| TransactionBuilderError::MissingKey("nonce"))?.to(), + gas_limit: request + .gas + .ok_or_else(|| TransactionBuilderError::MissingKey("gas_limit"))? + .to(), + max_fee_per_gas: request + .max_fee_per_gas + .ok_or_else(|| TransactionBuilderError::MissingKey("max_fee_per_gas"))? + .to(), + max_priority_fee_per_gas: request + .max_priority_fee_per_gas + .ok_or_else(|| TransactionBuilderError::MissingKey("max_priority_fee_per_gas"))? + .to(), + to: request.to.into(), + value: request.value.unwrap_or_default(), + access_list: convert_access_list(request.access_list.unwrap_or_default()), + blob_versioned_hashes: request + .blob_versioned_hashes + .ok_or_else(|| TransactionBuilderError::MissingKey("blob_versioned_hashes"))?, + max_fee_per_blob_gas: request + .max_fee_per_blob_gas + .ok_or_else(|| TransactionBuilderError::MissingKey("max_fee_per_blob_gas"))? + .to(), + input: request.input.into_input().unwrap_or_default(), + }) +} + +// todo: these types are almost 1:1, minus rlp decoding and ser/de, should dedupe +fn convert_access_list(list: alloy_rpc_types::AccessList) -> alloy_eips::eip2930::AccessList { + alloy_eips::eip2930::AccessList( + list.0 + .into_iter() + .map(|item| alloy_eips::eip2930::AccessListItem { + address: item.address, + storage_keys: item.storage_keys, + }) + .collect(), + ) +} diff --git a/crates/network/src/ethereum/mod.rs b/crates/network/src/ethereum/mod.rs index 28f5f3fe733..d1721c1082f 100644 --- a/crates/network/src/ethereum/mod.rs +++ b/crates/network/src/ethereum/mod.rs @@ -1,8 +1,6 @@ -use crate::{BuilderResult, Network, NetworkSigner, TransactionBuilder, TransactionBuilderError}; -use alloy_consensus::{TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxLegacy}; -use alloy_primitives::{Address, TxKind, U256, U64}; -use alloy_rpc_types::request::TransactionRequest; -use async_trait::async_trait; +use crate::Network; + +mod builder; mod signer; pub use signer::EthereumSigner; @@ -28,239 +26,3 @@ impl Network for Ethereum { type HeaderResponse = alloy_rpc_types::Header; } - -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl TransactionBuilder for alloy_rpc_types::TransactionRequest { - fn chain_id(&self) -> Option { - self.chain_id - } - - fn set_chain_id(&mut self, chain_id: alloy_primitives::ChainId) { - self.chain_id = Some(chain_id); - } - - fn nonce(&self) -> Option { - self.nonce - } - - fn set_nonce(&mut self, nonce: U64) { - self.nonce = Some(nonce); - } - - fn input(&self) -> Option<&alloy_primitives::Bytes> { - self.input.input() - } - - fn set_input(&mut self, input: alloy_primitives::Bytes) { - self.input.input = Some(input); - } - - fn to(&self) -> Option { - self.to.map(TxKind::Call).or(Some(TxKind::Create)) - } - - fn from(&self) -> Option
{ - self.from - } - - fn set_from(&mut self, from: Address) { - self.from = Some(from); - } - - fn set_to(&mut self, to: alloy_primitives::TxKind) { - match to { - TxKind::Create => self.to = None, - TxKind::Call(to) => self.to = Some(to), - } - } - - fn value(&self) -> Option { - self.value - } - - fn set_value(&mut self, value: alloy_primitives::U256) { - self.value = Some(value) - } - - fn gas_price(&self) -> Option { - self.gas_price - } - - fn set_gas_price(&mut self, gas_price: U256) { - self.gas_price = Some(gas_price); - } - - fn max_fee_per_gas(&self) -> Option { - self.max_fee_per_gas - } - - fn set_max_fee_per_gas(&mut self, max_fee_per_gas: U256) { - self.max_fee_per_gas = Some(max_fee_per_gas); - } - - fn max_priority_fee_per_gas(&self) -> Option { - self.max_priority_fee_per_gas - } - - fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: U256) { - self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas); - } - - fn max_fee_per_blob_gas(&self) -> Option { - self.max_fee_per_blob_gas - } - - fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: U256) { - self.max_fee_per_blob_gas = Some(max_fee_per_blob_gas) - } - - fn gas_limit(&self) -> Option { - self.gas - } - - fn set_gas_limit(&mut self, gas_limit: U256) { - self.gas = Some(gas_limit); - } - - fn build_unsigned(self) -> BuilderResult<::UnsignedTx> { - match ( - self.gas_price.as_ref(), - self.max_fee_per_gas.as_ref(), - self.access_list.as_ref(), - self.max_fee_per_blob_gas.as_ref(), - self.blob_versioned_hashes.as_ref(), - self.sidecar.as_ref(), - ) { - // Legacy transaction - (Some(_), None, None, None, None, None) => build_legacy(self).map(Into::into), - // EIP-2930 - // If only accesslist is set, and there are no EIP-1559 fees - (_, None, Some(_), None, None, None) => build_2930(self).map(Into::into), - // EIP-1559 - // If EIP-4844 fields are missing - (None, _, _, None, None, None) => build_1559(self).map(Into::into), - // EIP-4844 - // All blob fields required - (None, _, _, Some(_), Some(_), Some(_)) => { - build_4844(self).map(TxEip4844Variant::from).map(Into::into) - } - _ => build_legacy(self).map(Into::into), - } - } - - async fn build>( - self, - signer: &S, - ) -> BuilderResult<::TxEnvelope> { - // todo: BuilderResult + SignerResult - Ok(signer.sign(self.build_unsigned()?).await.unwrap()) - } -} - -/// Build a legacy transaction. -fn build_legacy(request: TransactionRequest) -> Result { - Ok(TxLegacy { - chain_id: request.chain_id, - nonce: request.nonce.ok_or_else(|| TransactionBuilderError::MissingKey("nonce"))?.to(), - gas_price: request - .gas_price - .ok_or_else(|| TransactionBuilderError::MissingKey("gas_price"))? - .to(), - gas_limit: request - .gas - .ok_or_else(|| TransactionBuilderError::MissingKey("gas_limit"))? - .to(), - to: request.to.into(), - value: request.value.unwrap_or_default(), - input: request.input.into_input().unwrap_or_default(), - }) -} - -/// Build an EIP-1559 transaction. -fn build_1559(request: TransactionRequest) -> Result { - Ok(TxEip1559 { - chain_id: request.chain_id.unwrap_or(1), - nonce: request.nonce.ok_or_else(|| TransactionBuilderError::MissingKey("nonce"))?.to(), - max_priority_fee_per_gas: request - .max_priority_fee_per_gas - .ok_or_else(|| TransactionBuilderError::MissingKey("max_priority_fee_per_gas"))? - .to(), - max_fee_per_gas: request - .max_fee_per_gas - .ok_or_else(|| TransactionBuilderError::MissingKey("max_fee_per_gas"))? - .to(), - gas_limit: request - .gas - .ok_or_else(|| TransactionBuilderError::MissingKey("gas_limit"))? - .to(), - to: request.to.into(), - value: request.value.unwrap_or_default(), - input: request.input.into_input().unwrap_or_default(), - access_list: convert_access_list(request.access_list.unwrap_or_default()), - }) -} - -/// Build an EIP-2930 transaction. -fn build_2930(request: TransactionRequest) -> Result { - Ok(TxEip2930 { - chain_id: request.chain_id.unwrap_or(1), - nonce: request.nonce.ok_or_else(|| TransactionBuilderError::MissingKey("nonce"))?.to(), - gas_price: request - .gas_price - .ok_or_else(|| TransactionBuilderError::MissingKey("gas_price"))? - .to(), - gas_limit: request - .gas - .ok_or_else(|| TransactionBuilderError::MissingKey("gas_limit"))? - .to(), - to: request.to.into(), - value: request.value.unwrap_or_default(), - input: request.input.into_input().unwrap_or_default(), - access_list: convert_access_list(request.access_list.unwrap_or_default()), - }) -} - -/// Build an EIP-4844 transaction. -fn build_4844(request: TransactionRequest) -> Result { - Ok(TxEip4844 { - chain_id: request.chain_id.unwrap_or(1), - nonce: request.nonce.ok_or_else(|| TransactionBuilderError::MissingKey("nonce"))?.to(), - gas_limit: request - .gas - .ok_or_else(|| TransactionBuilderError::MissingKey("gas_limit"))? - .to(), - max_fee_per_gas: request - .max_fee_per_gas - .ok_or_else(|| TransactionBuilderError::MissingKey("max_fee_per_gas"))? - .to(), - max_priority_fee_per_gas: request - .max_priority_fee_per_gas - .ok_or_else(|| TransactionBuilderError::MissingKey("max_priority_fee_per_gas"))? - .to(), - to: request.to.into(), - value: request.value.unwrap_or_default(), - access_list: convert_access_list(request.access_list.unwrap_or_default()), - blob_versioned_hashes: request - .blob_versioned_hashes - .ok_or_else(|| TransactionBuilderError::MissingKey("blob_versioned_hashes"))?, - max_fee_per_blob_gas: request - .max_fee_per_blob_gas - .ok_or_else(|| TransactionBuilderError::MissingKey("max_fee_per_blob_gas"))? - .to(), - input: request.input.into_input().unwrap_or_default(), - }) -} - -// todo: these types are almost 1:1, minus rlp decoding and ser/de, should dedupe -fn convert_access_list(list: alloy_rpc_types::AccessList) -> alloy_eips::eip2930::AccessList { - alloy_eips::eip2930::AccessList( - list.0 - .into_iter() - .map(|item| alloy_eips::eip2930::AccessListItem { - address: item.address, - storage_keys: item.storage_keys, - }) - .collect(), - ) -} From 89c8c01bc31e4f9c93f6b1ee02d8805ba198e5de Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 02:43:40 +0100 Subject: [PATCH 42/65] feat: `CallBuilder::map` --- crates/contract/src/call.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/contract/src/call.rs b/crates/contract/src/call.rs index 267f4864e6f..a2db38ceda5 100644 --- a/crates/contract/src/call.rs +++ b/crates/contract/src/call.rs @@ -307,14 +307,21 @@ impl, D: CallDecoder> CallBu self } + /// Applies a function to the internal transaction request. + pub fn map(mut self, f: F) -> Self + where + F: FnOnce(N::TransactionRequest) -> N::TransactionRequest, + { + self.request = f(self.request); + self + } + /// Sets the `block` field for sending the tx to the chain pub const fn block(mut self, block: BlockId) -> Self { self.block = Some(block); self } - // todo map function fn(req) -> req - /// Sets the [state override set](https://geth.ethereum.org/docs/rpc/ns-eth#3-object---state-override-set). /// /// # Note From d5585adb4938637010463a017e9699b021e51808 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 02:45:07 +0100 Subject: [PATCH 43/65] test: fix double import --- crates/consensus/src/receipt/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/consensus/src/receipt/mod.rs b/crates/consensus/src/receipt/mod.rs index a3a13d0976b..c2fca3d0766 100644 --- a/crates/consensus/src/receipt/mod.rs +++ b/crates/consensus/src/receipt/mod.rs @@ -32,7 +32,7 @@ pub trait TxReceipt { mod tests { use super::*; use alloy_eips::eip2718::Encodable2718; - use alloy_primitives::{address, b256, bytes, hex, Bytes, Log, LogData}; + use alloy_primitives::{address, b256, bytes, hex, Bytes, LogData}; use alloy_rlp::{Decodable, Encodable}; // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 From 290beabbb1dc877922167a377a1f1826db01bd72 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 02:54:21 +0100 Subject: [PATCH 44/65] chore: last nit --- crates/network/src/ethereum/builder.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/network/src/ethereum/builder.rs b/crates/network/src/ethereum/builder.rs index 9512f671e8b..88d1588a8fc 100644 --- a/crates/network/src/ethereum/builder.rs +++ b/crates/network/src/ethereum/builder.rs @@ -130,8 +130,7 @@ impl TransactionBuilder for alloy_rpc_types::TransactionRequest { self, signer: &S, ) -> BuilderResult<::TxEnvelope> { - // todo: BuilderResult + SignerResult - Ok(signer.sign(self.build_unsigned()?).await.unwrap()) + Ok(signer.sign(self.build_unsigned()?).await?) } } From d897d2ea9f44dc1f62ad81cc89ce289837279f01 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 05:26:25 +0100 Subject: [PATCH 45/65] feat: signer layer --- crates/network/src/ethereum/signer.rs | 9 +- crates/network/src/transaction/signer.rs | 2 +- crates/providers/Cargo.toml | 1 + crates/providers/src/builder.rs | 29 ++++- crates/providers/src/lib.rs | 5 +- crates/providers/src/new.rs | 4 +- crates/providers/src/signer.rs | 155 +++++++++++++++++++++++ 7 files changed, 197 insertions(+), 8 deletions(-) create mode 100644 crates/providers/src/signer.rs diff --git a/crates/network/src/ethereum/signer.rs b/crates/network/src/ethereum/signer.rs index 75f26f94e21..0c29d200b1a 100644 --- a/crates/network/src/ethereum/signer.rs +++ b/crates/network/src/ethereum/signer.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use super::Ethereum; use crate::{NetworkSigner, TxSigner}; use alloy_consensus::{SignableTransaction, TxEnvelope, TypedTransaction}; @@ -5,7 +7,8 @@ use alloy_signer::Signature; use async_trait::async_trait; /// A signer capable of signing any transaction for the Ethereum network. -pub struct EthereumSigner(Box + Sync>); +#[derive(Clone)] +pub struct EthereumSigner(Arc + Send + Sync>); impl std::fmt::Debug for EthereumSigner { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -15,10 +18,10 @@ impl std::fmt::Debug for EthereumSigner { impl From for EthereumSigner where - S: TxSigner + Sync + 'static, + S: TxSigner + Send + Sync + 'static, { fn from(signer: S) -> Self { - Self(Box::new(signer)) + Self(Arc::new(signer)) } } diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index 5d5c78dda8d..dd4a167bdb7 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -9,7 +9,7 @@ use async_trait::async_trait; /// A signer capable of signing any transaction for the given network. #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] -pub trait NetworkSigner: Sync { +pub trait NetworkSigner: Send + Sync { /// Asynchronously sign an unsigned transaction. async fn sign(&self, tx: N::UnsignedTx) -> alloy_signer::Result; } diff --git a/crates/providers/Cargo.toml b/crates/providers/Cargo.toml index 24b5fee66e4..73405c4bd23 100644 --- a/crates/providers/Cargo.toml +++ b/crates/providers/Cargo.toml @@ -35,5 +35,6 @@ tracing.workspace = true alloy-consensus.workspace = true alloy-node-bindings.workspace = true alloy-rlp.workspace = true +alloy-signer.workspace = true tokio = { workspace = true, features = ["macros"] } tracing-subscriber = { workspace = true, features = ["fmt"] } diff --git a/crates/providers/src/builder.rs b/crates/providers/src/builder.rs index 6765cf2efd8..89bf14a0e3c 100644 --- a/crates/providers/src/builder.rs +++ b/crates/providers/src/builder.rs @@ -13,6 +13,22 @@ pub trait ProviderLayer, N: Network, T: Transport + Clone> { fn layer(&self, inner: P) -> Self::Provider; } +/// An identity layer that does nothing. +pub struct Identity; + +impl ProviderLayer for Identity +where + T: Transport + Clone, + N: Network, + P: Provider, +{ + type Provider = P; + + fn layer(&self, inner: P) -> Self::Provider { + inner + } +} + pub struct Stack { inner: Inner, outer: Outer, @@ -54,6 +70,18 @@ pub struct ProviderBuilder { network: PhantomData, } +impl ProviderBuilder { + pub fn new() -> Self { + ProviderBuilder { layer: Identity, network: PhantomData } + } +} + +impl Default for ProviderBuilder { + fn default() -> Self { + Self::new() + } +} + impl ProviderBuilder { /// Add a layer to the stack being built. This is similar to /// [`tower::ServiceBuilder::layer`]. @@ -67,7 +95,6 @@ impl ProviderBuilder { /// /// [`tower::ServiceBuilder::layer`]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html#method.layer /// [`tower::ServiceBuilder`]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html - pub fn layer(self, layer: Inner) -> ProviderBuilder> { ProviderBuilder { layer: Stack::new(layer, self.layer), network: PhantomData } } diff --git a/crates/providers/src/lib.rs b/crates/providers/src/lib.rs index a72b12da36b..cdc448d0872 100644 --- a/crates/providers/src/lib.rs +++ b/crates/providers/src/lib.rs @@ -26,7 +26,10 @@ pub type HttpProvider = RootProvider>; extern crate tracing; mod builder; -pub use builder::{ProviderBuilder, ProviderLayer, Stack}; +pub use builder::{Identity, ProviderBuilder, ProviderLayer, Stack}; + +mod signer; +pub use signer::{SignerLayer, SignerProvider}; mod chain; diff --git a/crates/providers/src/new.rs b/crates/providers/src/new.rs index 1804d9e59e8..5a572f2aa34 100644 --- a/crates/providers/src/new.rs +++ b/crates/providers/src/new.rs @@ -154,7 +154,7 @@ pub trait Provider: Send + Sync /// transaction has been confirmed. async fn send_transaction( &self, - tx: &N::TransactionRequest, + tx: N::TransactionRequest, ) -> TransportResult { let tx_hash = self.client().prepare("eth_sendTransaction", (tx,)).await?; self.new_pending_transaction(tx_hash).await @@ -524,7 +524,7 @@ mod tests { gas: Some(U256::from(21000)), ..Default::default() }; - let pending_tx = provider.send_transaction(&tx).await.expect("failed to send tx"); + let pending_tx = provider.send_transaction(tx).await.expect("failed to send tx"); let hash1 = pending_tx.tx_hash; let hash2 = pending_tx.await.expect("failed to await pending tx"); assert_eq!(hash1, hash2); diff --git a/crates/providers/src/signer.rs b/crates/providers/src/signer.rs new file mode 100644 index 00000000000..14437eb7f29 --- /dev/null +++ b/crates/providers/src/signer.rs @@ -0,0 +1,155 @@ +use crate::{PendingTransaction, Provider, ProviderLayer}; +use alloy_network::{eip2718::Encodable2718, Network, NetworkSigner, TransactionBuilder}; +use alloy_primitives::B256; +use alloy_rpc_client::{ClientRef, WeakClient}; +use alloy_transport::{Transport, TransportErrorKind, TransportResult}; +use async_trait::async_trait; +use std::marker::PhantomData; + +/// A layer that signs transactions locally. +/// +/// The layer uses a [`NetworkSigner`] to sign transactions sent using +/// [`Provider::send_transaction`] locally before passing them to the node with +/// [`Provider::send_raw_transaction`]. +/// +/// If you have other layers that depend on [`Provider::send_transaction`] being invoked, add those +/// first. +/// +/// # Example +/// +/// ```rs +/// # async fn test>(transport: T, signer: S) { +/// let provider = ProviderBuilder::<_, Ethereum>::new() +/// .layer(SignerLayer::new(EthereumSigner::from(signer))) +/// .network::() +/// .provider(RootProvider::new(transport)); +/// +/// provider.send_transaction(TransactionRequest::default()).await; +/// # } +/// ``` +pub struct SignerLayer { + signer: S, +} + +impl SignerLayer { + /// Creates a new signing layer with the given signer. + pub fn new(signer: S) -> Self { + Self { signer } + } +} + +impl ProviderLayer for SignerLayer +where + P: Provider, + N: Network, + T: Transport + Clone, + S: NetworkSigner + Clone, +{ + type Provider = SignerProvider; + + fn layer(&self, inner: P) -> Self::Provider { + SignerProvider { inner, signer: self.signer.clone(), _phantom: PhantomData } + } +} + +/// A locally-signing provider. +/// +/// Signs transactions locally using a [`NetworkSigner`] +/// +/// # Note +/// +/// You cannot construct this provider directly. Use [`ProviderBuilder`] with a [`SignerLayer`]. +/// +/// [`ProviderBuilder`]: crate::ProviderBuilder +pub struct SignerProvider +where + N: Network, + T: Transport + Clone, + P: Provider, +{ + inner: P, + signer: S, + _phantom: PhantomData<(N, T)>, +} + +#[async_trait] +impl Provider for SignerProvider +where + N: Network, + T: Transport + Clone, + P: Provider, + S: NetworkSigner, +{ + fn client(&self) -> ClientRef<'_, T> { + self.inner.client() + } + + fn weak_client(&self) -> WeakClient { + self.inner.weak_client() + } + + async fn new_pending_transaction(&self, tx_hash: B256) -> TransportResult { + self.inner.new_pending_transaction(tx_hash).await + } + + async fn send_transaction( + &self, + tx: N::TransactionRequest, + ) -> TransportResult { + let envelope = tx.build(&self.signer).await.map_err(TransportErrorKind::custom)?; + let rlp = envelope.encoded_2718(); + + self.inner.send_raw_transaction(&rlp).await + } +} + +#[cfg(test)] +mod tests { + use super::SignerLayer; + use crate::{Provider, ProviderBuilder, RootProvider}; + use alloy_network::{Ethereum, EthereumSigner}; + use alloy_node_bindings::Anvil; + use alloy_primitives::{address, U256, U64}; + use alloy_rpc_client::RpcClient; + use alloy_rpc_types::TransactionRequest; + use alloy_signer::k256; + use alloy_transport_http::Http; + use reqwest::Client; + + #[tokio::test] + async fn poc() { + let anvil = Anvil::new().spawn(); + let url = anvil.endpoint().parse().unwrap(); + let http = Http::::new(url); + + // todo: is there a more ergo way? + let wallet: alloy_signer::Wallet = + alloy_signer::Wallet::from_slice(&anvil.keys().first().unwrap().to_bytes()).unwrap(); + + // can we somehow remove the need for <_, Ethereum>? we NEED to call .network + // note: we need to 1) add <_, Ethereum> 2) layer things, and then 3) call .network before + // we can call provider + let provider = ProviderBuilder::<_, Ethereum>::new() + .layer(SignerLayer::new(EthereumSigner::from(wallet))) + .network::() + .provider(RootProvider::new(RpcClient::new(http, true))); + + let tx = TransactionRequest { + nonce: Some(U64::from(0)), + value: Some(U256::from(100)), + to: address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into(), + gas_price: Some(U256::from(20e9)), + gas: Some(U256::from(21000)), + ..Default::default() + }; + + let pending = provider.send_transaction(tx).await.unwrap(); + let local_hash = pending.tx_hash; + let node_hash = pending.await.unwrap(); + assert_eq!(local_hash, node_hash); + assert_eq!( + node_hash.to_string(), + "0xeb56033eab0279c6e9b685a5ec55ea0ff8d06056b62b7f36974898d4fbb57e64" + ); + } +} From e228a61a1f87ffee3e58d1c4b2815f65ece1e71e Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 08:36:10 +0100 Subject: [PATCH 46/65] feat: `CallBuilder::send` --- crates/contract/Cargo.toml | 2 ++ crates/contract/src/call.rs | 45 +++++++++++++----------------- crates/network/src/ethereum/mod.rs | 8 +++++- crates/network/src/lib.rs | 14 ++++++++-- 4 files changed, 41 insertions(+), 28 deletions(-) diff --git a/crates/contract/Cargo.toml b/crates/contract/Cargo.toml index be7c3f97224..f8963a965e6 100644 --- a/crates/contract/Cargo.toml +++ b/crates/contract/Cargo.toml @@ -22,6 +22,8 @@ alloy-json-abi.workspace = true alloy-primitives.workspace = true alloy-sol-types.workspace = true +futures-util.workspace = true + thiserror.workspace = true [dev-dependencies] diff --git a/crates/contract/src/call.rs b/crates/contract/src/call.rs index a2db38ceda5..0e86f8c4b0f 100644 --- a/crates/contract/src/call.rs +++ b/crates/contract/src/call.rs @@ -1,12 +1,13 @@ use crate::{Error, Result}; use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt}; use alloy_json_abi::Function; -use alloy_network::{Network, TransactionBuilder}; +use alloy_network::{Network, ReceiptResponse, TransactionBuilder}; use alloy_primitives::{Address, Bytes, U256, U64}; use alloy_providers::Provider; -use alloy_rpc_types::{state::StateOverride, BlockId, TransactionReceipt}; +use alloy_rpc_types::{state::StateOverride, BlockId}; use alloy_sol_types::SolCall; use alloy_transport::Transport; +use futures_util::TryFutureExt; use std::{ future::{Future, IntoFuture}, marker::PhantomData, @@ -387,25 +388,20 @@ impl, D: CallDecoder> CallBu } let pending_tx = self.send().await?; let receipt = pending_tx.await?; - receipt.contract_address.ok_or(Error::ContractNotDeployed) + receipt + .ok_or(Error::ContractNotDeployed)? + .contract_address() + .ok_or(Error::ContractNotDeployed) } /// Broadcasts the underlying transaction to the network. // TODO: more docs referring to customizing PendingTransaction - pub async fn send(&self) -> Result>> { - // TODO: send_transaction, PendingTransaction - // NOTE: This struct is needed to have a concrete type for the `Future` trait. - struct Tmp(PhantomData); - impl Future for Tmp { - type Output = T; - fn poll( - self: Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> std::task::Poll { - todo!() - } - } - Ok(Tmp(PhantomData)) + pub async fn send( + &self, + ) -> Result>> + '_> { + let pending = self.provider.send_transaction(self.request.clone()).await?; + + Ok(pending.and_then(|hash| self.provider.get_transaction_receipt(hash)).map_err(Into::into)) } /// Calculates the address that will be created by the transaction, if any. @@ -480,6 +476,13 @@ mod tests { use alloy_transport_http::Http; use reqwest::Client; + fn spawn_anvil() -> (HttpProvider, AnvilInstance) { + let anvil = Anvil::new().spawn(); + let url = anvil.endpoint().parse().unwrap(); + let http = Http::::new(url); + (RootProvider::::new(RpcClient::new(http, true)), anvil) + } + #[test] fn empty_constructor() { sol! { @@ -555,7 +558,6 @@ mod tests { // TODO: send_transaction, PendingTransaction #[tokio::test(flavor = "multi_thread")] - #[ignore = "TODO"] async fn deploy_and_call() { let (provider, anvil) = spawn_anvil(); @@ -580,11 +582,4 @@ mod tests { b256!("0000000000000000000000000000000000000000000000000000000000000001"), ); } - - fn spawn_anvil() -> (HttpProvider, AnvilInstance) { - let anvil = Anvil::new().spawn(); - let url = anvil.endpoint().parse().unwrap(); - let http = Http::::new(url); - (RootProvider::::new(RpcClient::new(http, true)), anvil) - } } diff --git a/crates/network/src/ethereum/mod.rs b/crates/network/src/ethereum/mod.rs index d1721c1082f..92fec949fec 100644 --- a/crates/network/src/ethereum/mod.rs +++ b/crates/network/src/ethereum/mod.rs @@ -1,4 +1,4 @@ -use crate::Network; +use crate::{Network, ReceiptResponse}; mod builder; @@ -26,3 +26,9 @@ impl Network for Ethereum { type HeaderResponse = alloy_rpc_types::Header; } + +impl ReceiptResponse for alloy_rpc_types::TransactionReceipt { + fn contract_address(&self) -> Option { + self.contract_address + } +} diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs index bb7db9206de..cf991aeb751 100644 --- a/crates/network/src/lib.rs +++ b/crates/network/src/lib.rs @@ -17,7 +17,7 @@ use alloy_eips::eip2718::Eip2718Envelope; use alloy_json_rpc::RpcObject; -use alloy_primitives::B256; +use alloy_primitives::{Address, B256}; mod transaction; pub use transaction::{ @@ -50,6 +50,16 @@ pub struct BlockResponse { transactions: TransactionList, } +/// A receipt response. +/// +/// This is distinct from [`TxReceipt`], since this is for JSON-RPC receipts. +/// +/// [`TxReceipt`]: alloy_consensus::TxReceipt +pub trait ReceiptResponse { + /// Address of the created contract, or `None` if the transaction was not a deployment. + fn contract_address(&self) -> Option
; +} + /// Captures type info for network-specific RPC requests/responses. // todo: block responses are ethereum only, so we need to include this in here too, or make `Block` // generic over tx/header type @@ -80,7 +90,7 @@ pub trait Network: Clone + Copy + Sized + Send + Sync + 'static { /// The JSON body of a transaction response. type TransactionResponse: RpcObject; /// The JSON body of a transaction receipt. - type ReceiptResponse: RpcObject; + type ReceiptResponse: RpcObject + ReceiptResponse; /// The JSON body of a header response, as flattened into /// [`BlockResponse`]. type HeaderResponse: RpcObject; From ff555abd058cae5371bd3d4f4c785fc991ae469d Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:31:49 +0100 Subject: [PATCH 47/65] chore: nits --- crates/consensus/src/receipt/receipts.rs | 3 +-- crates/consensus/src/transaction/envelope.rs | 3 +-- crates/consensus/src/transaction/mod.rs | 6 +++--- crates/eips/src/eip1559/helpers.rs | 3 +-- crates/network/src/ethereum/signer.rs | 3 +-- crates/rpc-types/src/eth/transaction/optimism.rs | 9 ++++----- crates/rpc-types/src/eth/transaction/request.rs | 2 +- crates/signer/src/signer.rs | 1 - 8 files changed, 12 insertions(+), 18 deletions(-) diff --git a/crates/consensus/src/receipt/receipts.rs b/crates/consensus/src/receipt/receipts.rs index 26ace859da6..ed7e5b909f2 100644 --- a/crates/consensus/src/receipt/receipts.rs +++ b/crates/consensus/src/receipt/receipts.rs @@ -1,8 +1,7 @@ +use super::TxReceipt; use alloy_primitives::{Bloom, Log}; use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable}; -use super::TxReceipt; - /// Receipt containing result of transaction execution. #[derive(Clone, Debug, PartialEq, Eq, Default)] pub struct Receipt { diff --git a/crates/consensus/src/transaction/envelope.rs b/crates/consensus/src/transaction/envelope.rs index 8e233502712..40702a78d11 100644 --- a/crates/consensus/src/transaction/envelope.rs +++ b/crates/consensus/src/transaction/envelope.rs @@ -212,9 +212,8 @@ impl Encodable2718 for TxEnvelope { #[cfg(test)] mod tests { - use crate::transaction::SignableTransaction; - use super::*; + use crate::transaction::SignableTransaction; use alloy_eips::eip2930::{AccessList, AccessListItem}; use alloy_primitives::{Address, Bytes, Signature, TxKind, B256, U256}; diff --git a/crates/consensus/src/transaction/mod.rs b/crates/consensus/src/transaction/mod.rs index d326419c751..9d919ea49ad 100644 --- a/crates/consensus/src/transaction/mod.rs +++ b/crates/consensus/src/transaction/mod.rs @@ -1,6 +1,8 @@ -mod eip1559; +use crate::Signed; use alloy_primitives::{keccak256, ChainId, TxKind, B256, U256}; use alloy_rlp::BufMut; + +mod eip1559; pub use eip1559::TxEip1559; mod eip2930; @@ -20,8 +22,6 @@ pub use legacy::TxLegacy; mod typed; pub use typed::TypedTransaction; -use crate::Signed; - /// Represents a minimal EVM transaction. pub trait Transaction: std::any::Any + Send + Sync + 'static { /// Get `data`. diff --git a/crates/eips/src/eip1559/helpers.rs b/crates/eips/src/eip1559/helpers.rs index a7d622face5..98980e2371a 100644 --- a/crates/eips/src/eip1559/helpers.rs +++ b/crates/eips/src/eip1559/helpers.rs @@ -63,9 +63,8 @@ pub fn calc_next_block_base_fee( #[cfg(test)] mod tests { - use crate::eip1559::constants::{MIN_PROTOCOL_BASE_FEE, MIN_PROTOCOL_BASE_FEE_U256}; - use super::*; + use crate::eip1559::constants::{MIN_PROTOCOL_BASE_FEE, MIN_PROTOCOL_BASE_FEE_U256}; #[test] fn min_protocol_sanity() { diff --git a/crates/network/src/ethereum/signer.rs b/crates/network/src/ethereum/signer.rs index 0c29d200b1a..da74cc14316 100644 --- a/crates/network/src/ethereum/signer.rs +++ b/crates/network/src/ethereum/signer.rs @@ -1,10 +1,9 @@ -use std::sync::Arc; - use super::Ethereum; use crate::{NetworkSigner, TxSigner}; use alloy_consensus::{SignableTransaction, TxEnvelope, TypedTransaction}; use alloy_signer::Signature; use async_trait::async_trait; +use std::sync::Arc; /// A signer capable of signing any transaction for the Ethereum network. #[derive(Clone)] diff --git a/crates/rpc-types/src/eth/transaction/optimism.rs b/crates/rpc-types/src/eth/transaction/optimism.rs index 075b0ef6a6d..cbc8b6594ad 100644 --- a/crates/rpc-types/src/eth/transaction/optimism.rs +++ b/crates/rpc-types/src/eth/transaction/optimism.rs @@ -1,8 +1,8 @@ -//! Misc Optimism-specific types -use alloy_primitives::{B256, U128, U256, U64}; -use serde::{Deserialize, Serialize}; +//! Misc Optimism-specific types. use crate::other::OtherFields; +use alloy_primitives::{B256, U128, U256, U64}; +use serde::{Deserialize, Serialize}; /// Optimism specific transaction fields #[derive(Debug, Copy, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -84,9 +84,8 @@ mod l1_fee_scalar_serde { #[cfg(test)] mod tests { - use serde_json::{json, Value}; - use super::*; + use serde_json::{json, Value}; #[test] fn serialize_empty_optimism_transaction_receipt_fields_struct() { diff --git a/crates/rpc-types/src/eth/transaction/request.rs b/crates/rpc-types/src/eth/transaction/request.rs index 77d2978f33d..01fd9679b5b 100644 --- a/crates/rpc-types/src/eth/transaction/request.rs +++ b/crates/rpc-types/src/eth/transaction/request.rs @@ -1,9 +1,9 @@ //! Alloy basic Transaction Request type. -use std::hash::Hash; use crate::{eth::transaction::AccessList, other::OtherFields, BlobTransactionSidecar}; use alloy_primitives::{Address, Bytes, ChainId, B256, U256, U64, U8}; use serde::{Deserialize, Serialize}; +use std::hash::Hash; /// Represents _all_ transaction requests to/from RPC. #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] diff --git a/crates/signer/src/signer.rs b/crates/signer/src/signer.rs index e72c00c81f5..6c40485ff73 100644 --- a/crates/signer/src/signer.rs +++ b/crates/signer/src/signer.rs @@ -1,5 +1,4 @@ use crate::Result; - use alloy_primitives::{eip191_hash_message, Address, ChainId, Signature, B256}; use async_trait::async_trait; use auto_impl::auto_impl; From dbf7f7f6d1192187c41437793d29df813abcdbe4 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:31:59 +0100 Subject: [PATCH 48/65] chore: ZST cannot be enforced by a trait --- crates/network/src/ethereum/mod.rs | 4 +++- crates/network/src/lib.rs | 6 ------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/network/src/ethereum/mod.rs b/crates/network/src/ethereum/mod.rs index 92fec949fec..2479ef19cca 100644 --- a/crates/network/src/ethereum/mod.rs +++ b/crates/network/src/ethereum/mod.rs @@ -7,7 +7,9 @@ pub use signer::EthereumSigner; /// Types for a mainnet-like Ethereum network. #[derive(Debug, Clone, Copy)] -pub struct Ethereum; +pub struct Ethereum { + _private: (), +} impl Network for Ethereum { type TxEnvelope = alloy_consensus::TxEnvelope; diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs index cf991aeb751..4490a103804 100644 --- a/crates/network/src/lib.rs +++ b/crates/network/src/lib.rs @@ -64,12 +64,6 @@ pub trait ReceiptResponse { // todo: block responses are ethereum only, so we need to include this in here too, or make `Block` // generic over tx/header type pub trait Network: Clone + Copy + Sized + Send + Sync + 'static { - #[doc(hidden)] - /// Asserts that this trait can only be implemented on a ZST. - const __ASSERT_ZST: () = { - assert!(std::mem::size_of::() == 0, "Network must be a ZST"); - }; - // -- Consensus types -- /// The network transaction envelope type. From dc18b0de97474857f23b09fe06e90b7d71cf31e2 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:42:30 +0100 Subject: [PATCH 49/65] feat: better private key instantiation discoverability --- crates/providers/src/signer.rs | 5 +---- crates/signer/src/wallet/private_key.rs | 29 +++++++++++++++---------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/providers/src/signer.rs b/crates/providers/src/signer.rs index 14437eb7f29..24d739de356 100644 --- a/crates/providers/src/signer.rs +++ b/crates/providers/src/signer.rs @@ -112,7 +112,6 @@ mod tests { use alloy_primitives::{address, U256, U64}; use alloy_rpc_client::RpcClient; use alloy_rpc_types::TransactionRequest; - use alloy_signer::k256; use alloy_transport_http::Http; use reqwest::Client; @@ -122,9 +121,7 @@ mod tests { let url = anvil.endpoint().parse().unwrap(); let http = Http::::new(url); - // todo: is there a more ergo way? - let wallet: alloy_signer::Wallet = - alloy_signer::Wallet::from_slice(&anvil.keys().first().unwrap().to_bytes()).unwrap(); + let wallet = alloy_signer::Wallet::from(anvil.keys()[0].clone()); // can we somehow remove the need for <_, Ethereum>? we NEED to call .network // note: we need to 1) add <_, Ethereum> 2) layer things, and then 3) call .network before diff --git a/crates/signer/src/wallet/private_key.rs b/crates/signer/src/wallet/private_key.rs index 3e5f8fd51ab..2b36f1237dd 100644 --- a/crates/signer/src/wallet/private_key.rs +++ b/crates/signer/src/wallet/private_key.rs @@ -14,6 +14,19 @@ use std::str::FromStr; use {elliptic_curve::rand_core, std::path::Path}; impl Wallet { + /// Creates a new Wallet instance from a [`SigningKey`]. + /// + /// This can also be used to create a Wallet from a [`SecretKey`](K256SecretKey). + /// See also the `From` implementations. + #[doc(alias = "from_private_key")] + #[doc(alias = "new_private_key")] + #[doc(alias = "new_pk")] + #[inline] + pub fn from_signing_key(signer: SigningKey) -> Self { + let address = secret_key_to_address(&signer); + Self::new_with_signer(signer, address, None) + } + /// Creates a new Wallet instance from a raw scalar serialized as a [`B256`] byte array. /// /// This is identical to [`from_field_bytes`](Self::from_field_bytes). @@ -25,7 +38,7 @@ impl Wallet { /// Creates a new Wallet instance from a raw scalar serialized as a [`FieldBytes`] byte array. #[inline] pub fn from_field_bytes(bytes: &FieldBytes) -> Result { - SigningKey::from_bytes(bytes).map(Self::new_pk) + SigningKey::from_bytes(bytes).map(Self::from_signing_key) } /// Creates a new Wallet instance from a raw scalar serialized as a byte slice. @@ -33,7 +46,7 @@ impl Wallet { /// Byte slices shorter than the field size (32 bytes) are handled by zero padding the input. #[inline] pub fn from_slice(bytes: &[u8]) -> Result { - SigningKey::from_slice(bytes).map(Self::new_pk) + SigningKey::from_slice(bytes).map(Self::from_signing_key) } /// Creates a new random keypair seeded with [`rand::thread_rng()`]. @@ -45,13 +58,7 @@ impl Wallet { /// Creates a new random keypair seeded with the provided RNG. #[inline] pub fn random_with(rng: &mut R) -> Self { - Self::new_pk(SigningKey::random(rng)) - } - - #[inline] - fn new_pk(signer: SigningKey) -> Self { - let address = secret_key_to_address(&signer); - Self::new_with_signer(signer, address, None) + Self::from_signing_key(SigningKey::random(rng)) } /// Borrow the secret [`NonZeroScalar`] value for this key. @@ -146,13 +153,13 @@ impl PartialEq for Wallet { impl From for Wallet { fn from(value: SigningKey) -> Self { - Self::new_pk(value) + Self::from_signing_key(value) } } impl From for Wallet { fn from(value: K256SecretKey) -> Self { - Self::new_pk(value.into()) + Self::from_signing_key(value.into()) } } From 8afa99c0f8068328d71ae96ea1b0786084c605fc Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 21:23:04 +0100 Subject: [PATCH 50/65] chore: rm outdated todo --- crates/contract/src/call.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/contract/src/call.rs b/crates/contract/src/call.rs index 0e86f8c4b0f..65979fb3595 100644 --- a/crates/contract/src/call.rs +++ b/crates/contract/src/call.rs @@ -190,8 +190,6 @@ impl CallDecoder for () { #[derive(Clone)] #[must_use = "call builders do nothing unless you `.call`, `.send`, or `.await` them"] pub struct CallBuilder { - // TODO: this will not work with `send_transaction` and does not differentiate between EIP-1559 - // and legacy tx request: N::TransactionRequest, block: Option, state: Option, From 6f9b180646c49147c9abfa90d32888b782ee0455 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 21:25:30 +0100 Subject: [PATCH 51/65] feat: `EthereumSigner::new` --- crates/network/src/ethereum/signer.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/network/src/ethereum/signer.rs b/crates/network/src/ethereum/signer.rs index da74cc14316..0ba012fe1b2 100644 --- a/crates/network/src/ethereum/signer.rs +++ b/crates/network/src/ethereum/signer.rs @@ -20,11 +20,19 @@ where S: TxSigner + Send + Sync + 'static, { fn from(signer: S) -> Self { - Self(Arc::new(signer)) + Self::new(signer) } } impl EthereumSigner { + /// Create a new Ethereum signer. + pub fn new(signer: S) -> Self + where + S: TxSigner + Send + Sync + 'static, + { + Self(Arc::new(signer)) + } + async fn sign_transaction( &self, tx: &mut dyn SignableTransaction, From a7be381fe9fa7574b0ee4e3421e78417b74164ba Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 21:27:43 +0100 Subject: [PATCH 52/65] chore: code nit --- crates/signer-aws/src/signer.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/crates/signer-aws/src/signer.rs b/crates/signer-aws/src/signer.rs index dcf0d7b4f61..2b379660182 100644 --- a/crates/signer-aws/src/signer.rs +++ b/crates/signer-aws/src/signer.rs @@ -101,14 +101,7 @@ impl alloy_network::TxSigner for AwsSigner { &self, tx: &mut dyn SignableTransaction, ) -> Result { - let chain_id = match (self.chain_id(), tx.chain_id()) { - (Some(signer), Some(tx)) if signer != tx => { - return Err(alloy_signer::Error::TransactionChainIdMismatch { signer, tx }) - } - (Some(signer), _) => Some(signer), - (None, Some(tx)) => Some(tx), - _ => None, - }; + let chain_id = self.chain_id().or(tx.chain_id()); let mut sig = self.sign_hash(&tx.signature_hash()).await.map_err(alloy_signer::Error::other)?; From 17eb93af2e6abb2666b1acbf86f838f70e829869 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 21:29:29 +0100 Subject: [PATCH 53/65] docs: note about network zst --- crates/network/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs index 4490a103804..6255e840973 100644 --- a/crates/network/src/lib.rs +++ b/crates/network/src/lib.rs @@ -61,6 +61,8 @@ pub trait ReceiptResponse { } /// Captures type info for network-specific RPC requests/responses. +/// +/// Networks are only containers for types, so it is recommended to use ZSTs for their definition. // todo: block responses are ethereum only, so we need to include this in here too, or make `Block` // generic over tx/header type pub trait Network: Clone + Copy + Sized + Send + Sync + 'static { From bf86056773d067b413b57db0facc189411a42535 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 21:35:32 +0100 Subject: [PATCH 54/65] chore: nit --- crates/signer-aws/src/signer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/signer-aws/src/signer.rs b/crates/signer-aws/src/signer.rs index 2b379660182..19599efcadd 100644 --- a/crates/signer-aws/src/signer.rs +++ b/crates/signer-aws/src/signer.rs @@ -106,7 +106,7 @@ impl alloy_network::TxSigner for AwsSigner { let mut sig = self.sign_hash(&tx.signature_hash()).await.map_err(alloy_signer::Error::other)?; - if let Some(chain_id) = chain_id.or_else(|| tx.chain_id()) { + if let Some(chain_id) = chain_id { sig = sig.with_chain_id(chain_id); } Ok(sig) From 5210141cd8082c6c1a708bb7586adac5cabaafba Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 21:36:17 +0100 Subject: [PATCH 55/65] refactor: rename `sign` to `sign_transaction` --- crates/network/src/ethereum/builder.rs | 2 +- crates/network/src/ethereum/signer.rs | 2 +- crates/network/src/transaction/signer.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/network/src/ethereum/builder.rs b/crates/network/src/ethereum/builder.rs index 88d1588a8fc..b8a2e23fb7b 100644 --- a/crates/network/src/ethereum/builder.rs +++ b/crates/network/src/ethereum/builder.rs @@ -130,7 +130,7 @@ impl TransactionBuilder for alloy_rpc_types::TransactionRequest { self, signer: &S, ) -> BuilderResult<::TxEnvelope> { - Ok(signer.sign(self.build_unsigned()?).await?) + Ok(signer.sign_transaction(self.build_unsigned()?).await?) } } diff --git a/crates/network/src/ethereum/signer.rs b/crates/network/src/ethereum/signer.rs index 0ba012fe1b2..c60100dcb4d 100644 --- a/crates/network/src/ethereum/signer.rs +++ b/crates/network/src/ethereum/signer.rs @@ -44,7 +44,7 @@ impl EthereumSigner { #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl NetworkSigner for EthereumSigner { - async fn sign(&self, tx: TypedTransaction) -> alloy_signer::Result { + async fn sign_transaction(&self, tx: TypedTransaction) -> alloy_signer::Result { match tx { TypedTransaction::Legacy(mut t) => { let sig = self.sign_transaction(&mut t).await?; diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index dd4a167bdb7..fab52217c0b 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -11,7 +11,7 @@ use async_trait::async_trait; #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait NetworkSigner: Send + Sync { /// Asynchronously sign an unsigned transaction. - async fn sign(&self, tx: N::UnsignedTx) -> alloy_signer::Result; + async fn sign_transaction(&self, tx: N::UnsignedTx) -> alloy_signer::Result; } /// Asynchronous transaction signer, capable of signing any [`SignableTransaction`] for the given From 69bb2e11421289720e4dfdde2211ea7b3486004b Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 8 Mar 2024 21:39:18 +0100 Subject: [PATCH 56/65] feat: `ProviderBuilder::signer` --- crates/providers/src/builder.rs | 15 ++++++++++++++- crates/providers/src/signer.rs | 3 +-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/crates/providers/src/builder.rs b/crates/providers/src/builder.rs index 89bf14a0e3c..b86d99a6cb8 100644 --- a/crates/providers/src/builder.rs +++ b/crates/providers/src/builder.rs @@ -1,4 +1,7 @@ -use crate::new::{Provider, RootProvider}; +use crate::{ + new::{Provider, RootProvider}, + SignerLayer, +}; use alloy_network::Network; use alloy_rpc_client::RpcClient; use alloy_transport::Transport; @@ -99,6 +102,16 @@ impl ProviderBuilder { ProviderBuilder { layer: Stack::new(layer, self.layer), network: PhantomData } } + /// Add a signer layer to the stack being built. + /// + /// See [`SignerLayer`]. + pub fn signer(self, signer: S) -> ProviderBuilder, L>> { + ProviderBuilder { + layer: Stack::new(SignerLayer::new(signer), self.layer), + network: PhantomData, + } + } + /// Change the network. /// /// By default, the network is invalid, and contains the unit type `()`. diff --git a/crates/providers/src/signer.rs b/crates/providers/src/signer.rs index 24d739de356..cb2ff1d4e4e 100644 --- a/crates/providers/src/signer.rs +++ b/crates/providers/src/signer.rs @@ -105,7 +105,6 @@ where #[cfg(test)] mod tests { - use super::SignerLayer; use crate::{Provider, ProviderBuilder, RootProvider}; use alloy_network::{Ethereum, EthereumSigner}; use alloy_node_bindings::Anvil; @@ -127,7 +126,7 @@ mod tests { // note: we need to 1) add <_, Ethereum> 2) layer things, and then 3) call .network before // we can call provider let provider = ProviderBuilder::<_, Ethereum>::new() - .layer(SignerLayer::new(EthereumSigner::from(wallet))) + .signer(EthereumSigner::from(wallet)) .network::() .provider(RootProvider::new(RpcClient::new(http, true))); From 932486197651f3cd4e21d5c1a2607f330790c3b5 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 11 Mar 2024 12:09:45 +0100 Subject: [PATCH 57/65] docs: more network signer docs --- crates/network/src/transaction/signer.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index fab52217c0b..f86d81a5e16 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -7,6 +7,10 @@ use alloy_signer::{ use async_trait::async_trait; /// A signer capable of signing any transaction for the given network. +/// +/// Network crate authors should implement this trait on a type capable of signing any transaction +/// (regardless of signature type) on a given network. Signer crate authors should instead implement +/// [`TxSigner`] to signify signing capability for specific signature types. #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait NetworkSigner: Send + Sync { From 2425bc96e2df186fce12366d22b303bf3dcb3624 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 11 Mar 2024 12:09:54 +0100 Subject: [PATCH 58/65] refactor: remove unneeded `'static` --- crates/signer/src/signer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/signer/src/signer.rs b/crates/signer/src/signer.rs index 6c40485ff73..46dbd80a078 100644 --- a/crates/signer/src/signer.rs +++ b/crates/signer/src/signer.rs @@ -81,7 +81,7 @@ pub trait Signer: Send + Sync { /// /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 #[auto_impl(&, &mut, Box, Rc, Arc)] -pub trait SignerSync { +pub trait SignerSync { /// Signs the given hash. fn sign_hash_sync(&self, hash: &B256) -> Result; From 95f22dbeefa15037596f340714296644698ba31e Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 11 Mar 2024 12:35:15 +0100 Subject: [PATCH 59/65] refactor: `set_chain_id_checked` sort of it would be nicer if we had the error, but that would cause cyclical dep --- crates/consensus/src/transaction/mod.rs | 21 ++++++++++++++++++++- crates/signer-gcp/src/signer.rs | 17 +++++++++-------- crates/signer-ledger/src/signer.rs | 18 +++++++++--------- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/crates/consensus/src/transaction/mod.rs b/crates/consensus/src/transaction/mod.rs index 9d919ea49ad..de47fbde6ae 100644 --- a/crates/consensus/src/transaction/mod.rs +++ b/crates/consensus/src/transaction/mod.rs @@ -53,9 +53,28 @@ pub trait Transaction: std::any::Any + Send + Sync + 'static { /// types, or in other networks. For example, in Optimism, the deposit transaction signature is the /// unit type `()`. pub trait SignableTransaction: Transaction { - /// Set `chain_id` if it is not already set. + /// Sets `chain_id`. + /// + /// Prefer [`set_chain_id_checked`]. fn set_chain_id(&mut self, chain_id: ChainId); + /// Set `chain_id` if it is not already set. Checks that the provided `chain_id` matches the + /// existing `chain_id` if it is already set, returning `false` if they do not match. + fn set_chain_id_checked(&mut self, chain_id: ChainId) -> bool { + match self.chain_id() { + Some(tx_chain_id) => { + if tx_chain_id != chain_id { + return false; + } + self.set_chain_id(chain_id); + } + None => { + self.set_chain_id(chain_id); + } + } + true + } + /// RLP-encodes the transaction for signing. fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut); diff --git a/crates/signer-gcp/src/signer.rs b/crates/signer-gcp/src/signer.rs index 7a51106f052..db4b0c15cee 100644 --- a/crates/signer-gcp/src/signer.rs +++ b/crates/signer-gcp/src/signer.rs @@ -153,19 +153,20 @@ impl alloy_network::TxSigner for GcpSigner { &self, tx: &mut dyn SignableTransaction, ) -> Result { - let chain_id = match (self.chain_id(), tx.chain_id()) { - (Some(signer), Some(tx)) if signer != tx => { - return Err(alloy_signer::Error::TransactionChainIdMismatch { signer, tx }) + if let Some(chain_id) = self.chain_id { + if !tx.set_chain_id_checked(chain_id) { + return Err(alloy_signer::Error::TransactionChainIdMismatch { + signer: chain_id, + // we can only end up here if the tx has a chain id + tx: tx.chain_id().unwrap(), + }); } - (Some(signer), _) => Some(signer), - (None, Some(tx)) => Some(tx), - _ => None, - }; + } let mut sig = self.sign_hash(&tx.signature_hash()).await.map_err(alloy_signer::Error::other)?; - if let Some(chain_id) = chain_id.or_else(|| tx.chain_id()) { + if let Some(chain_id) = self.chain_id.or_else(|| tx.chain_id()) { sig = sig.with_chain_id(chain_id); } Ok(sig) diff --git a/crates/signer-ledger/src/signer.rs b/crates/signer-ledger/src/signer.rs index f8bf2399d9b..90ce122f11d 100644 --- a/crates/signer-ledger/src/signer.rs +++ b/crates/signer-ledger/src/signer.rs @@ -36,19 +36,19 @@ impl alloy_network::TxSigner for LedgerSigner { &self, tx: &mut dyn SignableTransaction, ) -> Result { - let chain_id = match (self.chain_id(), tx.chain_id()) { - (Some(signer), Some(tx)) if signer != tx => { - return Err(alloy_signer::Error::TransactionChainIdMismatch { signer, tx }) + if let Some(chain_id) = self.chain_id { + if !tx.set_chain_id_checked(chain_id) { + return Err(alloy_signer::Error::TransactionChainIdMismatch { + signer: chain_id, + // we can only end up here if the tx has a chain id + tx: tx.chain_id().unwrap(), + }); } - (Some(signer), _) => Some(signer), - (None, Some(tx)) => Some(tx), - _ => None, - }; - + } let rlp = tx.encoded_for_signing(); let mut sig = self.sign_tx_rlp(&rlp).await.map_err(alloy_signer::Error::other)?; - if let Some(chain_id) = chain_id.or_else(|| tx.chain_id()) { + if let Some(chain_id) = self.chain_id.or_else(|| tx.chain_id()) { sig = sig.with_chain_id(chain_id); } Ok(sig) From 5a41fbb2915f81b3f5eec24ce290263356b3c465 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:07:02 +0100 Subject: [PATCH 60/65] chore: use LocalWallet --- crates/network/src/ethereum/signer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/network/src/ethereum/signer.rs b/crates/network/src/ethereum/signer.rs index c60100dcb4d..352caf30271 100644 --- a/crates/network/src/ethereum/signer.rs +++ b/crates/network/src/ethereum/signer.rs @@ -71,7 +71,7 @@ mod test { use crate::{TxSigner, TxSignerSync}; use alloy_consensus::{SignableTransaction, TxLegacy}; use alloy_primitives::{address, ChainId, Signature, U256}; - use alloy_signer::{k256, Result, Signer}; + use alloy_signer::{LocalWallet, Result, Signer}; #[tokio::test] async fn signs_tx() { @@ -90,7 +90,7 @@ mod test { tx: &mut dyn SignableTransaction, chain_id: Option, ) -> Result { - let mut wallet: alloy_signer::Wallet = + let mut wallet: LocalWallet = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318".parse().unwrap(); wallet.set_chain_id(chain_id); From 6e0386701dbc0fdd52a18f0bde6fe22d4dffeb0b Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:10:17 +0100 Subject: [PATCH 61/65] fix: docs --- crates/consensus/src/transaction/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/consensus/src/transaction/mod.rs b/crates/consensus/src/transaction/mod.rs index de47fbde6ae..bcc36fddbbb 100644 --- a/crates/consensus/src/transaction/mod.rs +++ b/crates/consensus/src/transaction/mod.rs @@ -55,7 +55,7 @@ pub trait Transaction: std::any::Any + Send + Sync + 'static { pub trait SignableTransaction: Transaction { /// Sets `chain_id`. /// - /// Prefer [`set_chain_id_checked`]. + /// Prefer [`set_chain_id_checked`](Self::set_chain_id_checked). fn set_chain_id(&mut self, chain_id: ChainId); /// Set `chain_id` if it is not already set. Checks that the provided `chain_id` matches the From ceb865a8b8a7cec2c72eb2163b1aee1a8ce6fa8b Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:28:30 +0100 Subject: [PATCH 62/65] chore: dedup signing code --- crates/network/src/transaction/signer.rs | 25 ++------------------ crates/signer-aws/src/signer.rs | 12 ++-------- crates/signer-gcp/src/signer.rs | 20 ++-------------- crates/signer-ledger/src/signer.rs | 19 ++------------- crates/signer-trezor/src/signer.rs | 16 ++----------- crates/signer/src/lib.rs | 30 ++++++++++++++++++++++++ 6 files changed, 40 insertions(+), 82 deletions(-) diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index f86d81a5e16..0f6280a56cd 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -2,7 +2,7 @@ use crate::Network; use alloy_consensus::SignableTransaction; use alloy_signer::{ k256::ecdsa::{self, signature::hazmat::PrehashSigner, RecoveryId}, - Signature, Signer, SignerSync, Wallet, + sign_transaction_with_chain_id, Signature, Signer, SignerSync, Wallet, }; use async_trait::async_trait; @@ -77,28 +77,7 @@ where &self, tx: &mut dyn SignableTransaction, ) -> alloy_signer::Result { - let chain_id = self.chain_id_sync(); - if let Some(chain_id) = chain_id { - match tx.chain_id() { - Some(tx_chain_id) => { - if tx_chain_id != chain_id { - return Err(alloy_signer::Error::TransactionChainIdMismatch { - signer: chain_id, - tx: tx_chain_id, - }); - } - } - None => { - tx.set_chain_id(chain_id); - } - } - } - - let mut sig = self.sign_hash(&tx.signature_hash()).await?; - if let Some(chain_id) = chain_id.or_else(|| tx.chain_id()) { - sig = sig.with_chain_id(chain_id); - } - Ok(sig) + sign_transaction_with_chain_id!(self, tx, self.sign_hash(&tx.signature_hash())) } } diff --git a/crates/signer-aws/src/signer.rs b/crates/signer-aws/src/signer.rs index 19599efcadd..d4ceae4906b 100644 --- a/crates/signer-aws/src/signer.rs +++ b/crates/signer-aws/src/signer.rs @@ -1,6 +1,6 @@ use alloy_consensus::SignableTransaction; use alloy_primitives::{hex, Address, ChainId, B256}; -use alloy_signer::{Result, Signature, Signer}; +use alloy_signer::{sign_transaction_with_chain_id, Result, Signature, Signer}; use async_trait::async_trait; use aws_sdk_kms::{ error::SdkError, @@ -101,15 +101,7 @@ impl alloy_network::TxSigner for AwsSigner { &self, tx: &mut dyn SignableTransaction, ) -> Result { - let chain_id = self.chain_id().or(tx.chain_id()); - - let mut sig = - self.sign_hash(&tx.signature_hash()).await.map_err(alloy_signer::Error::other)?; - - if let Some(chain_id) = chain_id { - sig = sig.with_chain_id(chain_id); - } - Ok(sig) + sign_transaction_with_chain_id!(self, tx, self.sign_hash(&tx.signature_hash())) } } diff --git a/crates/signer-gcp/src/signer.rs b/crates/signer-gcp/src/signer.rs index db4b0c15cee..81bba7d12f8 100644 --- a/crates/signer-gcp/src/signer.rs +++ b/crates/signer-gcp/src/signer.rs @@ -1,6 +1,6 @@ use alloy_consensus::SignableTransaction; use alloy_primitives::{hex, Address, B256}; -use alloy_signer::{Result, Signature, Signer}; +use alloy_signer::{sign_transaction_with_chain_id, Result, Signature, Signer}; use async_trait::async_trait; use gcloud_sdk::{ google::cloud::kms::{ @@ -153,23 +153,7 @@ impl alloy_network::TxSigner for GcpSigner { &self, tx: &mut dyn SignableTransaction, ) -> Result { - if let Some(chain_id) = self.chain_id { - if !tx.set_chain_id_checked(chain_id) { - return Err(alloy_signer::Error::TransactionChainIdMismatch { - signer: chain_id, - // we can only end up here if the tx has a chain id - tx: tx.chain_id().unwrap(), - }); - } - } - - let mut sig = - self.sign_hash(&tx.signature_hash()).await.map_err(alloy_signer::Error::other)?; - - if let Some(chain_id) = self.chain_id.or_else(|| tx.chain_id()) { - sig = sig.with_chain_id(chain_id); - } - Ok(sig) + sign_transaction_with_chain_id!(self, tx, self.sign_hash(&tx.signature_hash())) } } diff --git a/crates/signer-ledger/src/signer.rs b/crates/signer-ledger/src/signer.rs index 90ce122f11d..625c8e699d7 100644 --- a/crates/signer-ledger/src/signer.rs +++ b/crates/signer-ledger/src/signer.rs @@ -3,7 +3,7 @@ use crate::types::{DerivationType, LedgerError, INS, P1, P1_FIRST, P2}; use alloy_consensus::SignableTransaction; use alloy_primitives::{hex, Address, ChainId, B256}; -use alloy_signer::{Result, Signature, Signer}; +use alloy_signer::{sign_transaction_with_chain_id, Result, Signature, Signer}; use async_trait::async_trait; use coins_ledger::{ common::{APDUCommand, APDUData}, @@ -36,22 +36,7 @@ impl alloy_network::TxSigner for LedgerSigner { &self, tx: &mut dyn SignableTransaction, ) -> Result { - if let Some(chain_id) = self.chain_id { - if !tx.set_chain_id_checked(chain_id) { - return Err(alloy_signer::Error::TransactionChainIdMismatch { - signer: chain_id, - // we can only end up here if the tx has a chain id - tx: tx.chain_id().unwrap(), - }); - } - } - let rlp = tx.encoded_for_signing(); - let mut sig = self.sign_tx_rlp(&rlp).await.map_err(alloy_signer::Error::other)?; - - if let Some(chain_id) = self.chain_id.or_else(|| tx.chain_id()) { - sig = sig.with_chain_id(chain_id); - } - Ok(sig) + sign_transaction_with_chain_id!(self, tx, self.sign_tx_rlp(&tx.encoded_for_signing())) } } diff --git a/crates/signer-trezor/src/signer.rs b/crates/signer-trezor/src/signer.rs index 27a39daae6d..68832610547 100644 --- a/crates/signer-trezor/src/signer.rs +++ b/crates/signer-trezor/src/signer.rs @@ -1,7 +1,7 @@ use super::types::{DerivationType, TrezorError}; use alloy_consensus::{SignableTransaction, TxEip1559}; use alloy_primitives::{hex, Address, ChainId, Parity, TxKind, B256, U256}; -use alloy_signer::{Result, Signature, Signer}; +use alloy_signer::{sign_transaction_with_chain_id, Result, Signature, Signer}; use async_trait::async_trait; use std::fmt; use trezor_client::client::Trezor; @@ -72,19 +72,7 @@ impl alloy_network::TxSigner for TrezorSigner { &self, tx: &mut dyn SignableTransaction, ) -> Result { - let chain_id = match (self.chain_id(), tx.chain_id()) { - (Some(id), None) | (None, Some(id)) => id, - (Some(signer), Some(tx)) if signer != tx => { - return Err(alloy_signer::Error::TransactionChainIdMismatch { signer, tx }); - } - _ => { - return Err(TrezorError::MissingChainId.into()); - } - }; - - let mut sig = self.sign_tx_inner(tx).await.map_err(alloy_signer::Error::other)?; - sig = sig.with_chain_id(chain_id); - Ok(sig) + sign_transaction_with_chain_id!(self, tx, self.sign_tx_inner(tx)) } } diff --git a/crates/signer/src/lib.rs b/crates/signer/src/lib.rs index df681f74b56..36ee0207e0a 100644 --- a/crates/signer/src/lib.rs +++ b/crates/signer/src/lib.rs @@ -43,3 +43,33 @@ pub type LocalWallet = Wallet; /// A wallet instantiated with a YubiHSM #[cfg(feature = "yubihsm")] pub type YubiWallet = Wallet>; + +/// Utility to get and set the chain ID on a transaction and the resulting signature within a +/// signer's `sign_transaction`. +#[macro_export] +macro_rules! sign_transaction_with_chain_id { + // async ( + // signer: impl Signer, + // tx: &mut impl SignableTransaction, + // sign: lazy Signature, + // ) + ($signer:expr, $tx:expr, $sign:expr) => {{ + if let Some(chain_id) = $signer.chain_id() { + if !$tx.set_chain_id_checked(chain_id) { + return Err(alloy_signer::Error::TransactionChainIdMismatch { + signer: chain_id, + // we can only end up here if the tx has a chain id + tx: $tx.chain_id().unwrap(), + }); + } + } + + let mut sig = $sign.await.map_err(alloy_signer::Error::other)?; + + if let Some(chain_id) = $signer.chain_id().or_else(|| $tx.chain_id()) { + sig = sig.with_chain_id(chain_id); + } + + Ok(sig) + }}; +} From ad9c5206811cae724ccd164816fdd10254d464c2 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:36:27 +0100 Subject: [PATCH 63/65] chore: more dedup --- crates/network/src/transaction/signer.rs | 27 +++--------------------- crates/signer-aws/src/signer.rs | 2 +- crates/signer-gcp/src/signer.rs | 2 +- crates/signer-ledger/src/signer.rs | 2 +- crates/signer-trezor/src/signer.rs | 2 +- crates/signer/src/lib.rs | 2 +- crates/signer/src/wallet/mod.rs | 15 ++++++++++++- crates/signer/src/wallet/private_key.rs | 1 - crates/signer/src/wallet/yubi.rs | 2 +- 9 files changed, 23 insertions(+), 32 deletions(-) diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index 0f6280a56cd..8bb06e83496 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -2,7 +2,7 @@ use crate::Network; use alloy_consensus::SignableTransaction; use alloy_signer::{ k256::ecdsa::{self, signature::hazmat::PrehashSigner, RecoveryId}, - sign_transaction_with_chain_id, Signature, Signer, SignerSync, Wallet, + sign_transaction_with_chain_id, Signature, SignerSync, Wallet, }; use async_trait::async_trait; @@ -77,7 +77,7 @@ where &self, tx: &mut dyn SignableTransaction, ) -> alloy_signer::Result { - sign_transaction_with_chain_id!(self, tx, self.sign_hash(&tx.signature_hash())) + sign_transaction_with_chain_id!(self, tx, self.sign_hash_sync(&tx.signature_hash())) } } @@ -89,27 +89,6 @@ where &self, tx: &mut dyn SignableTransaction, ) -> alloy_signer::Result { - let chain_id = self.chain_id_sync(); - if let Some(chain_id) = chain_id { - match tx.chain_id() { - Some(tx_chain_id) => { - if tx_chain_id != chain_id { - return Err(alloy_signer::Error::TransactionChainIdMismatch { - signer: chain_id, - tx: tx_chain_id, - }); - } - } - None => { - tx.set_chain_id(chain_id); - } - } - } - - let mut sig = self.sign_hash_sync(&tx.signature_hash())?; - if let Some(chain_id) = chain_id.or_else(|| tx.chain_id()) { - sig = sig.with_chain_id(chain_id); - } - Ok(sig) + sign_transaction_with_chain_id!(self, tx, self.sign_hash_sync(&tx.signature_hash())) } } diff --git a/crates/signer-aws/src/signer.rs b/crates/signer-aws/src/signer.rs index d4ceae4906b..0441806cb89 100644 --- a/crates/signer-aws/src/signer.rs +++ b/crates/signer-aws/src/signer.rs @@ -101,7 +101,7 @@ impl alloy_network::TxSigner for AwsSigner { &self, tx: &mut dyn SignableTransaction, ) -> Result { - sign_transaction_with_chain_id!(self, tx, self.sign_hash(&tx.signature_hash())) + sign_transaction_with_chain_id!(self, tx, self.sign_hash(&tx.signature_hash()).await) } } diff --git a/crates/signer-gcp/src/signer.rs b/crates/signer-gcp/src/signer.rs index 81bba7d12f8..849c4b6db45 100644 --- a/crates/signer-gcp/src/signer.rs +++ b/crates/signer-gcp/src/signer.rs @@ -153,7 +153,7 @@ impl alloy_network::TxSigner for GcpSigner { &self, tx: &mut dyn SignableTransaction, ) -> Result { - sign_transaction_with_chain_id!(self, tx, self.sign_hash(&tx.signature_hash())) + sign_transaction_with_chain_id!(self, tx, self.sign_hash(&tx.signature_hash()).await) } } diff --git a/crates/signer-ledger/src/signer.rs b/crates/signer-ledger/src/signer.rs index 625c8e699d7..b61a42a2545 100644 --- a/crates/signer-ledger/src/signer.rs +++ b/crates/signer-ledger/src/signer.rs @@ -36,7 +36,7 @@ impl alloy_network::TxSigner for LedgerSigner { &self, tx: &mut dyn SignableTransaction, ) -> Result { - sign_transaction_with_chain_id!(self, tx, self.sign_tx_rlp(&tx.encoded_for_signing())) + sign_transaction_with_chain_id!(self, tx, self.sign_tx_rlp(&tx.encoded_for_signing()).await) } } diff --git a/crates/signer-trezor/src/signer.rs b/crates/signer-trezor/src/signer.rs index 68832610547..d7d7231d92f 100644 --- a/crates/signer-trezor/src/signer.rs +++ b/crates/signer-trezor/src/signer.rs @@ -72,7 +72,7 @@ impl alloy_network::TxSigner for TrezorSigner { &self, tx: &mut dyn SignableTransaction, ) -> Result { - sign_transaction_with_chain_id!(self, tx, self.sign_tx_inner(tx)) + sign_transaction_with_chain_id!(self, tx, self.sign_tx_inner(tx).await) } } diff --git a/crates/signer/src/lib.rs b/crates/signer/src/lib.rs index 36ee0207e0a..4f7b81772bf 100644 --- a/crates/signer/src/lib.rs +++ b/crates/signer/src/lib.rs @@ -64,7 +64,7 @@ macro_rules! sign_transaction_with_chain_id { } } - let mut sig = $sign.await.map_err(alloy_signer::Error::other)?; + let mut sig = $sign.map_err(alloy_signer::Error::other)?; if let Some(chain_id) = $signer.chain_id().or_else(|| $tx.chain_id()) { sig = sig.with_chain_id(chain_id); diff --git a/crates/signer/src/wallet/mod.rs b/crates/signer/src/wallet/mod.rs index 3845b1b9bab..61f018c2486 100644 --- a/crates/signer/src/wallet/mod.rs +++ b/crates/signer/src/wallet/mod.rs @@ -101,7 +101,7 @@ impl> SignerSync for Wallet } } -impl + Send + Sync> Wallet { +impl> Wallet { /// Construct a new wallet with an external [`PrehashSigner`]. #[inline] pub const fn new_with_signer(signer: D, address: Address, chain_id: Option) -> Self { @@ -115,9 +115,22 @@ impl + Send + Sync> Wallet { } /// Consumes this wallet and returns its signer. + #[inline] pub fn into_signer(self) -> D { self.signer } + + /// Returns this wallet's chain ID. + #[inline] + pub const fn address(&self) -> Address { + self.address + } + + /// Returns this wallet's chain ID. + #[inline] + pub const fn chain_id(&self) -> Option { + self.chain_id + } } // do not log the signer diff --git a/crates/signer/src/wallet/private_key.rs b/crates/signer/src/wallet/private_key.rs index 2b36f1237dd..dd9a4ad9d4d 100644 --- a/crates/signer/src/wallet/private_key.rs +++ b/crates/signer/src/wallet/private_key.rs @@ -267,7 +267,6 @@ mod tests { #[test] #[cfg(feature = "eip712")] fn typed_data() { - use crate::Signer; use alloy_primitives::{keccak256, Address, I256, U256}; use alloy_sol_types::{eip712_domain, sol, SolStruct}; diff --git a/crates/signer/src/wallet/yubi.rs b/crates/signer/src/wallet/yubi.rs index b6d81c7d1a7..fc81c741a06 100644 --- a/crates/signer/src/wallet/yubi.rs +++ b/crates/signer/src/wallet/yubi.rs @@ -66,7 +66,7 @@ impl From> for Wallet> { #[cfg(test)] mod tests { use super::*; - use crate::{Signer, SignerSync}; + use crate::SignerSync; use alloy_primitives::{address, hex}; #[test] From 81c98a572c037eb438c3a328d5d5a3aa8901bf48 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:36:36 +0100 Subject: [PATCH 64/65] ci: run doctests --- .github/workflows/ci.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ebb2c5011cb..a5d3f3a536d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,6 +52,18 @@ jobs: if: ${{ matrix.rust != '1.75' }} # MSRV run: cargo nextest run --workspace ${{ matrix.flags }} + doctest: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - run: cargo test --workspace --doc + - run: cargo test --all-features --workspace --doc + wasm: runs-on: ubuntu-latest timeout-minutes: 30 From d2a349e151ee7ecf02cec608bdd51cd0304ecaa0 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 11 Mar 2024 15:06:06 +0100 Subject: [PATCH 65/65] test: fix doctest --- crates/signer/Cargo.toml | 1 + crates/signer/README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/signer/Cargo.toml b/crates/signer/Cargo.toml index 0c486019c14..464dccf97d2 100644 --- a/crates/signer/Cargo.toml +++ b/crates/signer/Cargo.toml @@ -40,6 +40,7 @@ yubihsm = { version = "0.42", features = [ [dev-dependencies] alloy-consensus.workspace = true +alloy-network.workspace = true assert_matches.workspace = true serde_json.workspace = true tempfile.workspace = true diff --git a/crates/signer/README.md b/crates/signer/README.md index bb14922cb70..a482563501b 100644 --- a/crates/signer/README.md +++ b/crates/signer/README.md @@ -44,6 +44,7 @@ Sign a transaction: use alloy_consensus::TxLegacy; use alloy_primitives::{U256, address, bytes}; use alloy_signer::{LocalWallet, Signer, SignerSync}; +use alloy_network::{TxSignerSync}; // Instantiate the wallet. let wallet = "dcf2cbdd171a21c480aa7f53d77f31bb102282b3ff099c78e3118b37348c72f7"