From 5e8412a3f49e80289a1a6a8288cbf9065492ec19 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 20 Nov 2023 23:50:02 +0100 Subject: [PATCH 1/3] split ethers/alloy providers --- Cargo.lock | 5 +- crates/anvil/src/config.rs | 9 +- crates/anvil/src/eth/api.rs | 68 ++-- crates/anvil/src/eth/backend/fork.rs | 15 +- crates/anvil/src/eth/backend/mem/mod.rs | 94 ++--- crates/anvil/src/lib.rs | 8 +- crates/anvil/src/pubsub.rs | 22 +- crates/cast/bin/cmd/run.rs | 20 +- crates/cast/bin/cmd/storage.rs | 6 +- crates/cheatcodes/src/evm/fork.rs | 2 +- crates/cheatcodes/src/inspector.rs | 60 ++-- crates/cli/src/opts/wallet/multi_wallet.rs | 6 +- crates/cli/src/utils/mod.rs | 10 +- crates/common/src/lib.rs | 1 - .../src/{provider.rs => provider/alloy.rs} | 0 crates/common/src/provider/ethers.rs | 323 ++++++++++++++++++ crates/common/src/provider/mod.rs | 2 + crates/evm/core/src/fork/backend.rs | 2 +- crates/evm/core/src/fork/database.rs | 2 +- crates/evm/core/src/fork/multi.rs | 6 +- crates/evm/core/src/opts.rs | 6 +- crates/forge/bin/cmd/create.rs | 8 +- crates/forge/bin/cmd/script/broadcast.rs | 23 +- crates/forge/bin/cmd/script/cmd.rs | 10 +- crates/forge/bin/cmd/script/executor.rs | 8 +- crates/forge/bin/cmd/script/mod.rs | 25 +- crates/forge/bin/cmd/script/multi.rs | 6 +- crates/forge/bin/cmd/script/providers.rs | 4 +- crates/forge/bin/cmd/script/receipts.rs | 6 +- crates/forge/bin/cmd/script/transaction.rs | 2 +- crates/forge/tests/cli/multi_script.rs | 23 +- crates/forge/tests/cli/script.rs | 10 +- crates/test-utils/Cargo.toml | 5 +- crates/test-utils/src/script.rs | 63 ++-- crates/utils/Cargo.toml | 1 + crates/utils/src/types.rs | 34 ++ 36 files changed, 636 insertions(+), 259 deletions(-) rename crates/common/src/{provider.rs => provider/alloy.rs} (100%) create mode 100644 crates/common/src/provider/ethers.rs create mode 100644 crates/common/src/provider/mod.rs diff --git a/Cargo.lock b/Cargo.lock index fafe9ec6183f..b2961167107b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3173,12 +3173,14 @@ name = "foundry-test-utils" version = "0.2.0" dependencies = [ "alloy-primitives", - "alloy-providers", + "ethers-core", + "ethers-providers", "eyre", "fd-lock 4.0.0", "foundry-common", "foundry-compilers", "foundry-config", + "foundry-utils", "once_cell", "parking_lot", "pretty_assertions", @@ -3195,6 +3197,7 @@ version = "0.2.0" dependencies = [ "alloy-json-abi", "alloy-primitives", + "alloy-rpc-types", "alloy-sol-types", "dunce", "ethers-core", diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index b498e56974c4..35d404cbb6e7 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -30,7 +30,8 @@ use ethers::{ utils::{format_ether, hex, to_checksum, WEI_IN_ETHER}, }; use foundry_common::{ - ProviderBuilder, ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, REQUEST_TIMEOUT, + provider::alloy::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, + REQUEST_TIMEOUT, }; use foundry_config::Config; use foundry_evm::{ @@ -768,7 +769,7 @@ impl NodeConfig { .expect("Failed writing json"); } if self.silent { - return + return; } println!("{}", self.as_string(fork)) @@ -779,7 +780,7 @@ impl NodeConfig { /// See also [ Config::foundry_block_cache_file()] pub fn block_cache_path(&self, block: u64) -> Option { if self.no_storage_caching || self.eth_rpc_url.is_none() { - return None + return None; } let chain_id = self.get_chain_id(); @@ -1195,7 +1196,7 @@ async fn find_latest_fork_block(provider: P) -> Result()) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } } } @@ -641,7 +641,7 @@ impl EthApi { ) .await .map_err(|_| BlockchainError::DataUnavailable)?, - )) + )); } } } @@ -671,7 +671,7 @@ impl EthApi { pub async fn block_by_number(&self, number: BlockNumber) -> Result> { node_info!("eth_getBlockByNumber"); if number == BlockNumber::Pending { - return Ok(Some(self.pending_block().await)) + return Ok(Some(self.pending_block().await)); } self.backend.block_by_number(number).await @@ -683,7 +683,7 @@ impl EthApi { pub async fn block_by_number_full(&self, number: BlockNumber) -> Result> { node_info!("eth_getBlockByNumber"); if number == BlockNumber::Pending { - return Ok(self.pending_block_full().await) + return Ok(self.pending_block_full().await); } self.backend.block_by_number_full(number).await } @@ -728,7 +728,7 @@ impl EthApi { let block_request = self.block_request(Some(block_number.into())).await?; if let BlockRequest::Pending(txs) = block_request { let block = self.backend.pending_block(txs).await; - return Ok(Some(U256::from(block.transactions.len()))) + return Ok(Some(U256::from(block.transactions.len()))); } let block = self.backend.block_by_number(block_number).await?; let txs = block.map(|b| match b.transactions { @@ -775,7 +775,7 @@ impl EthApi { return Ok(fork .get_code(address, number.to::()) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } } } @@ -803,7 +803,7 @@ impl EthApi { return Ok(fork .get_proof(address, keys, Some((*number).into())) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } } } @@ -920,7 +920,7 @@ impl EthApi { node_info!("eth_sendRawTransaction"); let data = tx.as_ref(); if data.is_empty() { - return Err(BlockchainError::EmptyRawTransactionData) + return Err(BlockchainError::EmptyRawTransactionData); } let transaction = if data[0] > 0x7f { // legacy transaction @@ -986,12 +986,12 @@ impl EthApi { if overrides.is_some() { return Err(BlockchainError::StateOverrideError( "not available on past forked blocks".to_string(), - )) + )); } return Ok(fork .call(&request, Some(number.to::().into())) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } } } @@ -1042,7 +1042,7 @@ impl EthApi { return Ok(fork .create_access_list(&request, Some(number.to::().into())) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } } } @@ -1152,7 +1152,7 @@ impl EthApi { node_info!("eth_getTransactionReceipt"); let tx = self.pool.get_transaction(hash); if tx.is_some() { - return Ok(None) + return Ok(None); } self.backend.transaction_receipt(hash).await } @@ -1173,7 +1173,7 @@ impl EthApi { return Ok(fork .uncle_by_block_hash_and_index(block_hash, idx.into()) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } } // It's impossible to have uncles outside of fork mode @@ -1195,7 +1195,7 @@ impl EthApi { return Ok(fork .uncle_by_block_number_and_index(number, idx.into()) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } } // It's impossible to have uncles outside of fork mode @@ -1277,7 +1277,7 @@ impl EthApi { &reward_percentiles, ) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } } @@ -1295,7 +1295,7 @@ impl EthApi { // only support ranges that are in cache range if lowest < self.backend.best_number().to::().saturating_sub(self.fee_history_limit) { - return Err(FeeHistoryError::InvalidBlockRange.into()) + return Err(FeeHistoryError::InvalidBlockRange.into()); } let fee_history = self.fee_history_cache.lock(); @@ -1447,7 +1447,7 @@ impl EthApi { ) -> Result { node_info!("debug_traceTransaction"); if opts.tracer.is_some() { - return Err(RpcError::invalid_params("non-default tracer not supported yet").into()) + return Err(RpcError::invalid_params("non-default tracer not supported yet").into()); } self.backend.debug_trace_transaction(tx_hash, opts).await @@ -1464,7 +1464,7 @@ impl EthApi { ) -> Result { node_info!("debug_traceCall"); if opts.tracer.is_some() { - return Err(RpcError::invalid_params("non-default tracer not supported yet").into()) + return Err(RpcError::invalid_params("non-default tracer not supported yet").into()); } let block_request = self.block_request(block_number).await?; let fees = FeeDetails::new( @@ -1542,7 +1542,7 @@ impl EthApi { node_info!("evm_setAutomine"); if self.miner.is_auto_mine() { if enable_automine { - return Ok(()) + return Ok(()); } self.miner.set_mining_mode(MiningMode::None); } else if enable_automine { @@ -1561,7 +1561,7 @@ impl EthApi { let interval = interval.map(|i| i.to::()); let blocks = num_blocks.unwrap_or(U256::from(1)); if blocks == U256::ZERO { - return Ok(()) + return Ok(()); } // mine all the blocks @@ -1685,7 +1685,7 @@ impl EthApi { return Err(RpcError::invalid_params( "anvil_setMinGasPrice is not supported when EIP-1559 is active", ) - .into()) + .into()); } self.backend.set_gas_price(gas); Ok(()) @@ -1700,7 +1700,7 @@ impl EthApi { return Err(RpcError::invalid_params( "anvil_setNextBlockBaseFeePerGas is only supported when EIP-1559 is active", ) - .into()) + .into()); } self.backend.set_base_fee(basefee); Ok(()) @@ -2167,7 +2167,7 @@ impl EthApi { return Ok(fork .estimate_gas(&request, Some((*number).into())) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } } } @@ -2199,7 +2199,7 @@ impl EthApi { if let Some(to) = request.to { if let Ok(target_code) = self.backend.get_code_with_state(&state, to.to_alloy()) { if target_code.as_ref().is_empty() { - return Ok(MIN_TRANSACTION_GAS) + return Ok(MIN_TRANSACTION_GAS); } } } @@ -2224,7 +2224,7 @@ impl EthApi { self.backend.get_balance_with_state(&state, from.to_alloy())?; if let Some(value) = request.value { if value > available_funds.to_ethers() { - return Err(InvalidTransactionError::InsufficientFunds.into()) + return Err(InvalidTransactionError::InsufficientFunds.into()); } // safe: value < available_funds available_funds -= value.to_alloy(); @@ -2263,7 +2263,7 @@ impl EthApi { block_env, fees, gas_limit.to_alloy(), - )) + )); } } @@ -2293,11 +2293,11 @@ impl EthApi { // the transaction did fail due to lack of gas from the user Err(InvalidTransactionError::Revert(Some(convert_transact_out(&out).0.into())) .into()) - } + }; } reason => { warn!(target: "node", "estimation failed due to {:?}", reason); - return Err(BlockchainError::EvmError(reason)) + return Err(BlockchainError::EvmError(reason)); } } @@ -2336,7 +2336,7 @@ impl EthApi { // new midpoint mid_gas_limit = ((highest_gas_limit + lowest_gas_limit.to_ethers()) / 2).to_alloy(); - continue + continue; } match ethres { @@ -2368,7 +2368,7 @@ impl EthApi { // real error. Err(reason) => { warn!(target: "node", "estimation failed due to {:?}", reason); - return Err(reason) + return Err(reason); } } // new midpoint @@ -2545,7 +2545,7 @@ impl EthApi { return Ok(fork .get_nonce(address, (*number).to::()) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } } } @@ -2607,7 +2607,7 @@ impl EthApi { fn required_marker(provided_nonce: U256, on_chain_nonce: U256, from: Address) -> Vec { if provided_nonce == on_chain_nonce { - return Vec::new() + return Vec::new(); } let prev_nonce = provided_nonce.saturating_sub(U256::from(1)); if on_chain_nonce <= prev_nonce { diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index b53a9d28fc9d..b52d87531e36 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -10,12 +10,9 @@ use alloy_rpc_types::{ TransactionReceipt, }; use alloy_transport::TransportError; -use ethers::{ - providers::ProviderError, - // types::{GethDebugTracingOptions, GethTrace, Trace}, -}; +use ethers::providers::ProviderError; use eyre::Context; -use foundry_common::{ProviderBuilder, RetryProvider}; +use foundry_common::provider::alloy::{ProviderBuilder, RetryProvider}; use parking_lot::{ lock_api::{RwLockReadGuard, RwLockWriteGuard}, RawRwLock, RwLock, @@ -308,12 +305,12 @@ impl ClientFork { match block.transactions { BlockTransactions::Full(txs) => { if let Some(tx) = txs.get(index) { - return Ok(Some(tx.clone())) + return Ok(Some(tx.clone())); } } BlockTransactions::Hashes(hashes) => { if let Some(tx_hash) = hashes.get(index) { - return self.transaction_by_hash(*tx_hash).await + return self.transaction_by_hash(*tx_hash).await; } } // TODO(evalir): Is it possible to reach this case? Should we support it @@ -332,12 +329,12 @@ impl ClientFork { match block.transactions { BlockTransactions::Full(txs) => { if let Some(tx) = txs.get(index) { - return Ok(Some(tx.clone())) + return Ok(Some(tx.clone())); } } BlockTransactions::Hashes(hashes) => { if let Some(tx_hash) = hashes.get(index) { - return self.transaction_by_hash(*tx_hash).await + return self.transaction_by_hash(*tx_hash).await; } } // TODO(evalir): Is it possible to reach this case? Should we support it diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 191f2fc8ca70..9cc02c6eb54c 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -323,7 +323,7 @@ impl Backend { /// Returns `true` if the account is already impersonated pub async fn impersonate(&self, addr: Address) -> DatabaseResult { if self.cheats.impersonated_accounts().contains(&addr.to_ethers()) { - return Ok(true) + return Ok(true); } // Ensure EIP-3607 is disabled let mut env = self.env.write(); @@ -392,7 +392,7 @@ impl Backend { return Err(RpcError::invalid_params( "Forking not enabled and RPC URL not provided to start forking", ) - .into()) + .into()); } } @@ -576,7 +576,7 @@ impl Backend { /// Returns an error if EIP1559 is not active (pre Berlin) pub fn ensure_eip1559_active(&self) -> Result<(), BlockchainError> { if self.is_eip1559() { - return Ok(()) + return Ok(()); } Err(BlockchainError::EIP1559TransactionUnsupportedAtHardfork) } @@ -584,7 +584,7 @@ impl Backend { /// Returns an error if EIP1559 is not active (pre muirGlacier) pub fn ensure_eip2930_active(&self) -> Result<(), BlockchainError> { if self.is_eip2930() { - return Ok(()) + return Ok(()); } Err(BlockchainError::EIP2930TransactionUnsupportedAtHardfork) } @@ -1223,11 +1223,11 @@ impl Backend { hash: B256, ) -> Result, BlockchainError> { if let Some(block) = self.blockchain.get_block_by_hash(&hash) { - return Ok(self.mined_logs_for_block(filter, block)) + return Ok(self.mined_logs_for_block(filter, block)); } if let Some(fork) = self.get_fork() { - return Ok(fork.logs(&filter).await.map_err(|_| BlockchainError::DataUnavailable)?) + return Ok(fork.logs(&filter).await.map_err(|_| BlockchainError::DataUnavailable)?); } Ok(Vec::new()) @@ -1346,7 +1346,7 @@ impl Backend { self.convert_block_number(filter.block_option.get_from_block().copied()); if from_block > best { // requested log range does not exist yet - return Ok(vec![]) + return Ok(vec![]); } self.logs_for_range(&filter, from_block, to_block).await @@ -1356,14 +1356,14 @@ impl Backend { pub async fn block_by_hash(&self, hash: B256) -> Result, BlockchainError> { trace!(target: "backend", "get block by hash {:?}", hash); if let tx @ Some(_) = self.mined_block_by_hash(hash) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { return Ok(fork .block_by_hash(hash) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } Ok(None) @@ -1375,14 +1375,14 @@ impl Backend { ) -> Result, BlockchainError> { trace!(target: "backend", "get block by hash {:?}", hash); if let tx @ Some(_) = self.get_full_block(hash) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { return Ok(fork .block_by_hash_full(hash) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } Ok(None) @@ -1398,7 +1398,7 @@ impl Backend { number: BlockNumber, ) -> Option> { if let Some(block) = self.get_block(number) { - return self.mined_transactions_in_block(&block) + return self.mined_transactions_in_block(&block); } None } @@ -1430,7 +1430,7 @@ impl Backend { ) -> Result, BlockchainError> { trace!(target: "backend", "get block by number {:?}", number); if let tx @ Some(_) = self.mined_block_by_number(number) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { @@ -1439,7 +1439,7 @@ impl Backend { return Ok(fork .block_by_number(number) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } } @@ -1452,7 +1452,7 @@ impl Backend { ) -> Result, BlockchainError> { trace!(target: "backend", "get block by number {:?}", number); if let tx @ Some(_) = self.get_full_block(number) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { @@ -1461,7 +1461,7 @@ impl Backend { return Ok(fork .block_by_number_full(number) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } } @@ -1489,8 +1489,8 @@ impl Backend { BlockNumber::Finalized => { if storage.best_number.to_ethers() > (slots_in_an_epoch.to_ethers() * 2) { *storage.hashes.get( - &(storage.best_number.to_ethers() - - (slots_in_an_epoch.to_ethers() * 2)) + &(storage.best_number.to_ethers() + - (slots_in_an_epoch.to_ethers() * 2)) .to_alloy(), )? } else { @@ -1658,7 +1658,7 @@ impl Backend { f(state, block) }) .await; - return Ok(result) + return Ok(result); } Some(BlockRequest::Number(bn)) => Some(BlockNumber::Number(bn)), None => None, @@ -1683,7 +1683,7 @@ impl Backend { gas_limit: block.header.gas_limit.to_alloy(), ..Default::default() }; - return Ok(f(Box::new(state), block)) + return Ok(f(Box::new(state), block)); } } @@ -1702,7 +1702,7 @@ impl Backend { block.timestamp = rU256::from(fork.timestamp()); block.basefee = fork.base_fee().unwrap_or_default(); - return Ok(f(Box::new(&gen_db), block)) + return Ok(f(Box::new(&gen_db), block)); } } @@ -1710,7 +1710,7 @@ impl Backend { return Err(BlockchainError::BlockOutOfRange( self.env.read().block.number.to_ethers().as_u64(), block_number.to::(), - )) + )); } let db = self.db.read().await; @@ -1756,7 +1756,7 @@ impl Backend { let account = state.basic_ref(address)?.unwrap_or_default(); if account.code_hash == KECCAK_EMPTY { // if the code hash is `KECCAK_EMPTY`, we check no further - return Ok(Default::default()) + return Ok(Default::default()); } let code = if let Some(code) = account.code { code @@ -1801,7 +1801,7 @@ impl Backend { if let Some(BlockRequest::Pending(pool_transactions)) = block_request.as_ref() { if let Some(value) = get_pool_transactions_nonce(pool_transactions, address.to_ethers()) { - return Ok(value) + return Ok(value); } } let final_block_request = match block_request { @@ -1819,7 +1819,7 @@ impl Backend { /// Returns the traces for the given transaction pub async fn trace_transaction(&self, hash: B256) -> Result, BlockchainError> { if let Some(traces) = self.mined_parity_trace_transaction(hash) { - return Ok(traces) + return Ok(traces); } // if let Some(fork) = self.get_fork() { @@ -1857,7 +1857,7 @@ impl Backend { opts: GethDebugTracingOptions, ) -> Result { if let Some(traces) = self.mined_geth_trace_transaction(hash, opts.clone()) { - return Ok(GethTrace::Known(GethTraceFrame::Default(traces))) + return Ok(GethTrace::Known(GethTraceFrame::Default(traces))); } // if let Some(fork) = self.get_fork() { @@ -1879,7 +1879,7 @@ impl Backend { pub async fn trace_block(&self, block: BlockNumber) -> Result, BlockchainError> { let number = self.convert_block_number(Some(block)); if let Some(traces) = self.mined_parity_trace_block(number) { - return Ok(traces) + return Ok(traces); } // if let Some(fork) = self.get_fork() { @@ -1896,7 +1896,7 @@ impl Backend { hash: B256, ) -> Result, BlockchainError> { if let Some(receipt) = self.mined_transaction_receipt(hash) { - return Ok(Some(receipt.inner)) + return Ok(Some(receipt.inner)); } if let Some(fork) = self.get_fork() { @@ -1912,7 +1912,7 @@ impl Backend { ); if fork.predates_fork_inclusive(number) { - return Ok(receipt) + return Ok(receipt); } } @@ -2024,7 +2024,7 @@ impl Backend { index: Index, ) -> Result, BlockchainError> { if let Some(hash) = self.mined_block_by_number(number).and_then(|b| b.header.hash) { - return Ok(self.mined_transaction_by_block_hash_and_index(hash, index)) + return Ok(self.mined_transaction_by_block_hash_and_index(hash, index)); } if let Some(fork) = self.get_fork() { @@ -2033,7 +2033,7 @@ impl Backend { return Ok(fork .transaction_by_block_number_and_index(number, index.into()) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } } @@ -2046,14 +2046,14 @@ impl Backend { index: Index, ) -> Result, BlockchainError> { if let tx @ Some(_) = self.mined_transaction_by_block_hash_and_index(hash, index) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { return Ok(fork .transaction_by_block_hash_and_index(hash, index.into()) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } Ok(None) @@ -2088,14 +2088,14 @@ impl Backend { ) -> Result, BlockchainError> { trace!(target: "backend", "transaction_by_hash={:?}", hash); if let tx @ Some(_) = self.mined_transaction_by_hash(hash) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { return Ok(fork .transaction_by_hash(hash) .await - .map_err(|_| BlockchainError::DataUnavailable)?) + .map_err(|_| BlockchainError::DataUnavailable)?); } Ok(None) @@ -2246,7 +2246,7 @@ fn get_pool_transactions_nonce( if let Some(highest_nonce_tx) = highest_nonce_tx { return Some( highest_nonce_tx.pending_transaction.nonce().to_alloy().saturating_add(U256::from(1)), - ) + ); } None } @@ -2276,22 +2276,22 @@ impl TransactionValidator for Backend { if chain_id.to::() != tx_chain_id { if let Some(legacy) = tx.as_legacy() { // - if env.cfg.spec_id >= SpecId::SPURIOUS_DRAGON && - !legacy.meets_eip155(chain_id.to::()) + if env.cfg.spec_id >= SpecId::SPURIOUS_DRAGON + && !legacy.meets_eip155(chain_id.to::()) { warn!(target: "backend", ?chain_id, ?tx_chain_id, "incompatible EIP155-based V"); - return Err(InvalidTransactionError::IncompatibleEIP155) + return Err(InvalidTransactionError::IncompatibleEIP155); } } else { warn!(target: "backend", ?chain_id, ?tx_chain_id, "invalid chain id"); - return Err(InvalidTransactionError::InvalidChainId) + return Err(InvalidTransactionError::InvalidChainId); } } } if tx.gas_limit() < MIN_TRANSACTION_GAS.to_ethers() { warn!(target: "backend", "[{:?}] gas too low", tx.hash()); - return Err(InvalidTransactionError::GasTooLow) + return Err(InvalidTransactionError::GasTooLow); } // Check gas limit, iff block gas limit is set. @@ -2299,7 +2299,7 @@ impl TransactionValidator for Backend { warn!(target: "backend", "[{:?}] gas too high", tx.hash()); return Err(InvalidTransactionError::GasTooHigh(ErrDetail { detail: String::from("tx.gas_limit > env.block.gas_limit"), - })) + })); } // check nonce @@ -2307,13 +2307,13 @@ impl TransactionValidator for Backend { (*tx.nonce()).try_into().map_err(|_| InvalidTransactionError::NonceMaxValue)?; if nonce < account.nonce { warn!(target: "backend", "[{:?}] nonce too low", tx.hash()); - return Err(InvalidTransactionError::NonceTooLow) + return Err(InvalidTransactionError::NonceTooLow); } if (env.cfg.spec_id as u8) >= (SpecId::LONDON as u8) { if tx.gas_price() < env.block.basefee.to_ethers() { warn!(target: "backend", "max fee per gas={}, too low, block basefee={}",tx.gas_price(), env.block.basefee); - return Err(InvalidTransactionError::FeeCapTooLow) + return Err(InvalidTransactionError::FeeCapTooLow); } if let (Some(max_priority_fee_per_gas), Some(max_fee_per_gas)) = @@ -2321,7 +2321,7 @@ impl TransactionValidator for Backend { { if max_priority_fee_per_gas > max_fee_per_gas { warn!(target: "backend", "max priority fee per gas={}, too high, max fee per gas={}", max_priority_fee_per_gas, max_fee_per_gas); - return Err(InvalidTransactionError::TipAboveFeeCap) + return Err(InvalidTransactionError::TipAboveFeeCap); } } } @@ -2337,7 +2337,7 @@ impl TransactionValidator for Backend { if account.balance < req_funds.to_alloy() { warn!(target: "backend", "[{:?}] insufficient allowance={}, required={} account={:?}", tx.hash(), account.balance, req_funds, *pending.sender()); - return Err(InvalidTransactionError::InsufficientFunds) + return Err(InvalidTransactionError::InsufficientFunds); } Ok(()) } @@ -2350,7 +2350,7 @@ impl TransactionValidator for Backend { ) -> Result<(), InvalidTransactionError> { self.validate_pool_transaction_for(tx, account, env)?; if tx.nonce().as_u64() > account.nonce { - return Err(InvalidTransactionError::NonceTooHigh) + return Err(InvalidTransactionError::NonceTooHigh); } Ok(()) } diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index 50c7d306ed85..3e5d91b8f177 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -23,7 +23,7 @@ use ethers::{ signers::Signer, types::{Address, U256}, }; -use foundry_common::{ProviderBuilder, RetryProvider}; +use foundry_common::provider::alloy::{ProviderBuilder, RetryProvider}; use foundry_evm::revm; use futures::{FutureExt, TryFutureExt}; use parking_lot::Mutex; @@ -351,7 +351,7 @@ impl Future for NodeHandle { // poll the ipc task if let Some(mut ipc) = pin.ipc_task.take() { if let Poll::Ready(res) = ipc.poll_unpin(cx) { - return Poll::Ready(res.map(|res| res.map_err(NodeError::from))) + return Poll::Ready(res.map(|res| res.map_err(NodeError::from))); } else { pin.ipc_task = Some(ipc); } @@ -359,13 +359,13 @@ impl Future for NodeHandle { // poll the node service task if let Poll::Ready(res) = pin.node_service.poll_unpin(cx) { - return Poll::Ready(res) + return Poll::Ready(res); } // poll the axum server handles for server in pin.servers.iter_mut() { if let Poll::Ready(res) = server.poll_unpin(cx) { - return Poll::Ready(res) + return Poll::Ready(res); } } diff --git a/crates/anvil/src/pubsub.rs b/crates/anvil/src/pubsub.rs index b9bf083c750c..e9543e6e6b0b 100644 --- a/crates/anvil/src/pubsub.rs +++ b/crates/anvil/src/pubsub.rs @@ -39,7 +39,7 @@ impl LogsSubscription { subscription: self.id.clone(), result: to_rpc_result(log), }; - return Poll::Ready(Some(EthSubscriptionResponse::new(params))) + return Poll::Ready(Some(EthSubscriptionResponse::new(params))); } if let Some(block) = ready!(self.blocks.poll_next_unpin(cx)) { @@ -51,16 +51,16 @@ impl LogsSubscription { // this ensures we poll the receiver until it is pending, in which case the // underlying `UnboundedReceiver` will register the new waker, see // [`futures::channel::mpsc::UnboundedReceiver::poll_next()`] - continue + continue; } self.queued.extend(logs) } } else { - return Poll::Ready(None) + return Poll::Ready(None); } if self.queued.is_empty() { - return Poll::Pending + return Poll::Pending; } } } @@ -114,10 +114,10 @@ impl EthSubscription { subscription: id.clone(), result: to_rpc_result(block), }; - return Poll::Ready(Some(EthSubscriptionResponse::new(params))) + return Poll::Ready(Some(EthSubscriptionResponse::new(params))); } } else { - return Poll::Ready(None) + return Poll::Ready(None); } } } @@ -168,12 +168,12 @@ pub fn filter_logs( }; if params.filter.is_some() { let block_number = block.header.number.as_u64(); - if !params.filter_block_range(U64::from(block_number)) || - !params.filter_block_hash(block_hash) || - !params.filter_address(&log) || - !params.filter_topics(&log) + if !params.filter_block_range(U64::from(block_number)) + || !params.filter_block_hash(block_hash) + || !params.filter_address(&log) + || !params.filter_topics(&log) { - return false + return false; } } true diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index a715b20faa6a..822773e8973c 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -99,13 +99,13 @@ impl RunArgs { .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))?; // check if the tx is a system transaction - if is_known_system_sender(tx.from.to_alloy()) || - tx.transaction_type.map(|ty| ty.as_u64()) == Some(SYSTEM_TRANSACTION_TYPE) + if is_known_system_sender(tx.from.to_alloy()) + || tx.transaction_type.map(|ty| ty.as_u64()) == Some(SYSTEM_TRANSACTION_TYPE) { return Err(eyre::eyre!( "{:?} is a system transaction.\nReplaying system transactions is currently not supported.", tx.hash - )) + )); } let tx_block_number = tx @@ -144,18 +144,18 @@ impl RunArgs { for (index, tx) in block.transactions.into_iter().enumerate() { // System transactions such as on L2s don't contain any pricing info so we skip // them otherwise this would cause reverts - if is_known_system_sender(tx.from.to_alloy()) || - tx.transaction_type.map(|ty| ty.as_u64()) == - Some(SYSTEM_TRANSACTION_TYPE) + if is_known_system_sender(tx.from.to_alloy()) + || tx.transaction_type.map(|ty| ty.as_u64()) + == Some(SYSTEM_TRANSACTION_TYPE) { update_progress!(pb, index); - continue + continue; } if tx.hash.eq(&tx_hash) { - break + break; } - configure_tx_env(&mut env, &tx); + configure_tx_env(&mut env, &tx.to_alloy()); if let Some(to) = tx.to { trace!(tx=?tx.hash,?to, "executing previous call transaction"); @@ -192,7 +192,7 @@ impl RunArgs { let result = { executor.set_trace_printer(self.trace_printer); - configure_tx_env(&mut env, &tx); + configure_tx_env(&mut env, &tx.to_alloy()); if let Some(to) = tx.to { trace!(tx=?tx.hash, to=?to, "executing call transaction"); diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index 688af2c4c630..607e6741ab2d 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -14,7 +14,7 @@ use foundry_cli::{ use foundry_common::{ abi::find_source, compile::{compile, etherscan_project, suppress_compile}, - RetryProvider, + provider::ethers::RetryProvider, }; use foundry_compilers::{artifacts::StorageLayout, ConfigurableContractArtifact, Project, Solc}; use foundry_config::{ @@ -86,7 +86,7 @@ impl StorageArgs { if let Some(slot) = slot { let cast = Cast::new(provider); println!("{}", cast.storage(address, slot.to_ethers(), block).await?); - return Ok(()) + return Ok(()); } // No slot was provided @@ -111,7 +111,7 @@ impl StorageArgs { let artifact = out.artifacts().find(|(_, artifact)| match_code(artifact).unwrap_or_default()); if let Some((_, artifact)) = artifact { - return fetch_and_print_storage(provider, address.clone(), artifact, true).await + return fetch_and_print_storage(provider, address.clone(), artifact, true).await; } } diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index 836aceb86129..18ed693012e5 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -4,7 +4,7 @@ use alloy_providers::provider::TempProvider; use alloy_rpc_types::{Filter, Topic}; use alloy_sol_types::SolValue; use eyre::WrapErr; -use foundry_common::ProviderBuilder; +use foundry_common::provider::alloy::ProviderBuilder; use foundry_compilers::utils::RuntimeOrHandle; use foundry_evm_core::fork::CreateFork; use foundry_utils::types::{ToAlloy, ToEthers}; diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 3ff0d563c32d..889199cba7d1 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -19,7 +19,7 @@ use ethers_core::types::{ transaction::eip2718::TypedTransaction, NameOrAddress, TransactionRequest, }; use ethers_signers::LocalWallet; -use foundry_common::{evm::Breakpoints, RpcUrl}; +use foundry_common::{evm::Breakpoints, provider::alloy::RpcUrl}; use foundry_evm_core::{ backend::{DatabaseError, DatabaseExt, RevertDiagnostic}, constants::{CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, MAGIC_SKIP}, @@ -237,7 +237,7 @@ impl Cheatcodes { if data.journaled_state.depth > 1 && !data.db.has_cheatcode_access(inputs.caller) { // we only grant cheat code access for new contracts if the caller also has // cheatcode access and the new contract is created in top most call - return + return; } let old_nonce = data @@ -260,12 +260,12 @@ impl Cheatcodes { // Delay revert clean up until expected revert is handled, if set. if self.expected_revert.is_some() { - return + return; } // we only want to apply cleanup top level if data.journaled_state.depth() > 0 { - return + return; } // Roll back all previously applied deals @@ -541,11 +541,11 @@ impl Inspector for Cheatcodes { return match self.apply_cheatcode(data, call) { Ok(retdata) => (InstructionResult::Return, gas, retdata.into()), Err(err) => (InstructionResult::Revert, gas, err.abi_encode().into()), - } + }; } if call.contract == HARDHAT_CONSOLE_ADDRESS { - return (InstructionResult::Continue, gas, Bytes::new()) + return (InstructionResult::Continue, gas, Bytes::new()); } // Handle expected calls @@ -583,19 +583,19 @@ impl Inspector for Cheatcodes { mocks .iter() .find(|(mock, _)| { - call.input.get(..mock.calldata.len()) == Some(&mock.calldata[..]) && - mock.value.map_or(true, |value| value == call.transfer.value) + call.input.get(..mock.calldata.len()) == Some(&mock.calldata[..]) + && mock.value.map_or(true, |value| value == call.transfer.value) }) .map(|(_, v)| v) }) { - return (return_data.ret_type, gas, return_data.data.clone()) + return (return_data.ret_type, gas, return_data.data.clone()); } } // Apply our prank if let Some(prank) = &self.prank { - if data.journaled_state.depth() >= prank.depth && - call.context.caller == prank.prank_caller + if data.journaled_state.depth() >= prank.depth + && call.context.caller == prank.prank_caller { let mut prank_applied = false; @@ -627,8 +627,8 @@ impl Inspector for Cheatcodes { // // We do this because any subsequent contract calls *must* exist on chain and // we only want to grab *this* call, not internal ones - if data.journaled_state.depth() == broadcast.depth && - call.context.caller == broadcast.original_caller + if data.journaled_state.depth() == broadcast.depth + && call.context.caller == broadcast.original_caller { // At the target depth we set `msg.sender` & tx.origin. // We are simulating the caller as being an EOA, so *both* must be set to the @@ -645,7 +645,7 @@ impl Inspector for Cheatcodes { if let Err(err) = data.journaled_state.load_account(broadcast.new_origin, data.db) { - return (InstructionResult::Revert, gas, Error::encode(err)) + return (InstructionResult::Revert, gas, Error::encode(err)); } let is_fixed_gas_limit = check_if_fixed_gas_limit(data, call.gas_limit); @@ -676,7 +676,7 @@ impl Inspector for Cheatcodes { debug!(target: "cheatcodes", address=%broadcast.new_origin, nonce=prev+1, prev, "incremented nonce"); } else if broadcast.single_call { let msg = "`staticcall`s are not allowed after `broadcast`; use `startBroadcast` instead"; - return (InstructionResult::Revert, Gas::new(0), Error::encode(msg)) + return (InstructionResult::Revert, Gas::new(0), Error::encode(msg)); } } } @@ -693,7 +693,7 @@ impl Inspector for Cheatcodes { retdata: Bytes, ) -> (InstructionResult, Gas, Bytes) { if call.contract == CHEATCODE_ADDRESS || call.contract == HARDHAT_CONSOLE_ADDRESS { - return (status, remaining_gas, retdata) + return (status, remaining_gas, retdata); } if data.journaled_state.depth() == 0 && self.skip { @@ -701,7 +701,7 @@ impl Inspector for Cheatcodes { InstructionResult::Revert, remaining_gas, super::Error::from(MAGIC_SKIP).abi_encode().into(), - ) + ); } // Clean up pranks @@ -743,7 +743,7 @@ impl Inspector for Cheatcodes { (InstructionResult::Revert, remaining_gas, error.abi_encode().into()) } Ok((_, retdata)) => (InstructionResult::Return, remaining_gas, retdata), - } + }; } } @@ -771,7 +771,7 @@ impl Inspector for Cheatcodes { InstructionResult::Revert, remaining_gas, "log != expected log".abi_encode().into(), - ) + ); } else { // All emits were found, we're good. // Clear the queue, as we expect the user to declare more events for the next call @@ -788,7 +788,7 @@ impl Inspector for Cheatcodes { // return a better error here if status == InstructionResult::Revert { if let Some(err) = diag { - return (status, remaining_gas, Error::encode(err.to_error_msg(&self.labels))) + return (status, remaining_gas, Error::encode(err.to_error_msg(&self.labels))); } } @@ -797,9 +797,9 @@ impl Inspector for Cheatcodes { if let TransactTo::Call(test_contract) = data.env.tx.transact_to { // if a call to a different contract than the original test contract returned with // `Stop` we check if the contract actually exists on the active fork - if data.db.is_forked_mode() && - status == InstructionResult::Stop && - call.contract != test_contract + if data.db.is_forked_mode() + && status == InstructionResult::Stop + && call.contract != test_contract { self.fork_revert_diagnostic = data.db.diagnose_revert(call.contract, &data.journaled_state); @@ -812,7 +812,7 @@ impl Inspector for Cheatcodes { // earlier error that happened first with unrelated information about // another error when using cheatcodes. if status == InstructionResult::Revert { - return (status, remaining_gas, retdata) + return (status, remaining_gas, retdata); } // If there's not a revert, we can continue on to run the last logic for expect* @@ -857,7 +857,7 @@ impl Inspector for Cheatcodes { "Expected call to {address} with {expected_values} \ to be called {count} time{s}, but {but}" ); - return (InstructionResult::Revert, remaining_gas, Error::encode(msg)) + return (InstructionResult::Revert, remaining_gas, Error::encode(msg)); } } } @@ -874,7 +874,7 @@ impl Inspector for Cheatcodes { "expected an emit, but the call reverted instead. \ ensure you're testing the happy path when using `expectEmit`" }; - return (InstructionResult::Revert, remaining_gas, Error::encode(msg)) + return (InstructionResult::Revert, remaining_gas, Error::encode(msg)); } } @@ -908,11 +908,11 @@ impl Inspector for Cheatcodes { // Apply our broadcast if let Some(broadcast) = &self.broadcast { - if data.journaled_state.depth() >= broadcast.depth && - call.caller == broadcast.original_caller + if data.journaled_state.depth() >= broadcast.depth + && call.caller == broadcast.original_caller { if let Err(err) = data.journaled_state.load_account(broadcast.new_origin, data.db) { - return (InstructionResult::Revert, None, gas, Error::encode(err)) + return (InstructionResult::Revert, None, gas, Error::encode(err)); } data.env.tx.caller = broadcast.new_origin; @@ -1009,7 +1009,7 @@ impl Inspector for Cheatcodes { Err(err) => { (InstructionResult::Revert, None, remaining_gas, err.abi_encode().into()) } - } + }; } } diff --git a/crates/cli/src/opts/wallet/multi_wallet.rs b/crates/cli/src/opts/wallet/multi_wallet.rs index 9c2ff7312c52..97047afa3298 100644 --- a/crates/cli/src/opts/wallet/multi_wallet.rs +++ b/crates/cli/src/opts/wallet/multi_wallet.rs @@ -1,12 +1,12 @@ use super::{WalletSigner, WalletTrait}; use alloy_primitives::Address; -use alloy_providers::provider::TempProvider; use clap::Parser; +use ethers_providers::Middleware; use ethers_signers::{ AwsSigner, HDPath as LedgerHDPath, Ledger, LocalWallet, Signer, Trezor, TrezorHDPath, }; use eyre::{Context, ContextCompat, Result}; -use foundry_common::RetryProvider; +use foundry_common::provider::ethers::RetryProvider; use foundry_config::Config; use foundry_utils::types::ToAlloy; use itertools::izip; @@ -220,7 +220,7 @@ impl MultiWallet { script_wallets: &[LocalWallet], ) -> Result> { println!("\n###\nFinding wallets for all the necessary addresses..."); - let chain = provider.get_chain_id().await.wrap_err("could not fetch chain id")?.to(); + let chain = provider.get_chainid().await?.as_u64(); let mut local_wallets = HashMap::new(); let mut unused_wallets = vec![]; diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 124df64537cc..3a7b042aeb93 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -79,7 +79,7 @@ pub fn subscriber() { /// and chain. /// /// Defaults to `http://localhost:8545` and `Mainnet`. -pub fn get_provider(config: &Config) -> Result { +pub fn get_provider(config: &Config) -> Result { get_provider_builder(config)?.build() } @@ -87,9 +87,11 @@ pub fn get_provider(config: &Config) -> Result { /// URL and chain. /// /// Defaults to `http://localhost:8545` and `Mainnet`. -pub fn get_provider_builder(config: &Config) -> Result { +pub fn get_provider_builder( + config: &Config, +) -> Result { let url = config.get_rpc_url_or_localhost_http()?; - let mut builder = foundry_common::ProviderBuilder::new(url.as_ref()); + let mut builder = foundry_common::provider::ethers::ProviderBuilder::new(url.as_ref()); if let Ok(chain) = config.chain.unwrap_or_default().try_into() { builder = builder.chain(chain); @@ -469,7 +471,7 @@ impl<'a> Git<'a> { output.status.code(), stdout.trim(), stderr.trim() - )) + )); } } Ok(()) diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 584a3638981d..c6782aaf6740 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -27,6 +27,5 @@ pub mod units; pub use constants::*; pub use contracts::*; -pub use provider::*; pub use traits::*; pub use transactions::*; diff --git a/crates/common/src/provider.rs b/crates/common/src/provider/alloy.rs similarity index 100% rename from crates/common/src/provider.rs rename to crates/common/src/provider/alloy.rs diff --git a/crates/common/src/provider/ethers.rs b/crates/common/src/provider/ethers.rs new file mode 100644 index 000000000000..201b5d3c695b --- /dev/null +++ b/crates/common/src/provider/ethers.rs @@ -0,0 +1,323 @@ +//! Commonly used helpers to construct `Provider`s + +use crate::{ + runtime_client::{RuntimeClient, RuntimeClientBuilder}, + ALCHEMY_FREE_TIER_CUPS, REQUEST_TIMEOUT, +}; +use ethers_core::types::U256; +use ethers_middleware::gas_oracle::{GasCategory, GasOracle, Polygon}; +use ethers_providers::{is_local_endpoint, Middleware, Provider, DEFAULT_LOCAL_POLL_INTERVAL}; +use eyre::{Result, WrapErr}; +use foundry_config::NamedChain; +use reqwest::Url; +use std::{ + path::{Path, PathBuf}, + time::Duration, +}; +use url::ParseError; + +/// Helper type alias for a retry provider +pub type RetryProvider = Provider; + +/// Helper type alias for a rpc url +pub type RpcUrl = String; + +/// Constructs a provider with a 100 millisecond interval poll if it's a localhost URL (most likely +/// an anvil or other dev node) and with the default, or 7 second otherwise. +/// +/// See [`try_get_http_provider`] for more details. +/// +/// # Panics +/// +/// Panics if the URL is invalid. +/// +/// # Examples +/// +/// ``` +/// use foundry_common::get_http_provider; +/// +/// let retry_provider = get_http_provider("http://localhost:8545"); +/// ``` +#[inline] +#[track_caller] +pub fn get_http_provider(builder: impl AsRef) -> RetryProvider { + try_get_http_provider(builder).unwrap() +} + +/// Constructs a provider with a 100 millisecond interval poll if it's a localhost URL (most likely +/// an anvil or other dev node) and with the default, or 7 second otherwise. +#[inline] +pub fn try_get_http_provider(builder: impl AsRef) -> Result { + ProviderBuilder::new(builder.as_ref()).build() +} + +/// Helper type to construct a `RetryProvider` +#[derive(Debug)] +pub struct ProviderBuilder { + // Note: this is a result, so we can easily chain builder calls + url: Result, + chain: NamedChain, + max_retry: u32, + timeout_retry: u32, + initial_backoff: u64, + timeout: Duration, + /// available CUPS + compute_units_per_second: u64, + /// JWT Secret + jwt: Option, + headers: Vec, +} + +// === impl ProviderBuilder === + +impl ProviderBuilder { + /// Creates a new builder instance + pub fn new(url_str: &str) -> Self { + // a copy is needed for the next lines to work + let mut url_str = url_str; + + // invalid url: non-prefixed URL scheme is not allowed, so we prepend the default http + // prefix + let storage; + if url_str.starts_with("localhost:") { + storage = format!("http://{url_str}"); + url_str = storage.as_str(); + } + + let url = Url::parse(url_str) + .or_else(|err| match err { + ParseError::RelativeUrlWithoutBase => { + let path = Path::new(url_str); + + if let Ok(path) = resolve_path(path) { + Url::parse(&format!("file://{}", path.display())) + } else { + Err(err) + } + } + _ => Err(err), + }) + .wrap_err_with(|| format!("invalid provider URL: {url_str:?}")); + + Self { + url, + chain: NamedChain::Mainnet, + max_retry: 8, + timeout_retry: 8, + initial_backoff: 800, + timeout: REQUEST_TIMEOUT, + // alchemy max cpus + compute_units_per_second: ALCHEMY_FREE_TIER_CUPS, + jwt: None, + headers: vec![], + } + } + + /// Enables a request timeout. + /// + /// The timeout is applied from when the request starts connecting until the + /// response body has finished. + /// + /// Default is no timeout. + pub fn timeout(mut self, timeout: Duration) -> Self { + self.timeout = timeout; + self + } + + /// Sets the chain of the node the provider will connect to + pub fn chain(mut self, chain: NamedChain) -> Self { + self.chain = chain; + self + } + + /// How often to retry a failed request + pub fn max_retry(mut self, max_retry: u32) -> Self { + self.max_retry = max_retry; + self + } + + /// How often to retry a failed request. If `None`, defaults to the already-set value. + pub fn maybe_max_retry(mut self, max_retry: Option) -> Self { + self.max_retry = max_retry.unwrap_or(self.max_retry); + self + } + + /// The starting backoff delay to use after the first failed request. If `None`, defaults to + /// the already-set value. + pub fn maybe_initial_backoff(mut self, initial_backoff: Option) -> Self { + self.initial_backoff = initial_backoff.unwrap_or(self.initial_backoff); + self + } + + /// How often to retry a failed request due to connection issues + pub fn timeout_retry(mut self, timeout_retry: u32) -> Self { + self.timeout_retry = timeout_retry; + self + } + + /// The starting backoff delay to use after the first failed request + pub fn initial_backoff(mut self, initial_backoff: u64) -> Self { + self.initial_backoff = initial_backoff; + self + } + + /// Sets the number of assumed available compute units per second + /// + /// See also, + pub fn compute_units_per_second(mut self, compute_units_per_second: u64) -> Self { + self.compute_units_per_second = compute_units_per_second; + self + } + + /// Sets the number of assumed available compute units per second + /// + /// See also, + pub fn compute_units_per_second_opt(mut self, compute_units_per_second: Option) -> Self { + if let Some(cups) = compute_units_per_second { + self.compute_units_per_second = cups; + } + self + } + + /// Sets aggressive `max_retry` and `initial_backoff` values + /// + /// This is only recommend for local dev nodes + pub fn aggressive(self) -> Self { + self.max_retry(100).initial_backoff(100) + } + + /// Sets the JWT secret + pub fn jwt(mut self, jwt: impl Into) -> Self { + self.jwt = Some(jwt.into()); + self + } + + /// Sets http headers + pub fn headers(mut self, headers: Vec) -> Self { + self.headers = headers; + + self + } + + /// Same as [`Self:build()`] but also retrieves the `chainId` in order to derive an appropriate + /// interval. + pub async fn connect(self) -> Result { + let mut provider = self.build()?; + if let Some(blocktime) = provider.get_chainid().await.ok().and_then(|id| { + NamedChain::try_from(id.as_u64()).ok().and_then(|chain| chain.average_blocktime_hint()) + }) { + provider = provider.interval(blocktime / 2); + } + Ok(provider) + } + + /// Constructs the `RetryProvider` taking all configs into account. + pub fn build(self) -> Result { + let ProviderBuilder { + url, + chain, + max_retry, + timeout_retry, + initial_backoff, + timeout, + compute_units_per_second, + jwt, + headers, + } = self; + let url = url?; + + let client_builder = RuntimeClientBuilder::new( + url.clone(), + max_retry, + timeout_retry, + initial_backoff, + timeout, + compute_units_per_second, + ) + .with_headers(headers) + .with_jwt(jwt); + + let mut provider = Provider::new(client_builder.build()); + + let is_local = is_local_endpoint(url.as_str()); + + if is_local { + provider = provider.interval(DEFAULT_LOCAL_POLL_INTERVAL); + } else if let Some(blocktime) = chain.average_blocktime_hint() { + provider = provider.interval(blocktime / 2); + } + + Ok(provider) + } +} + +/// Estimates EIP1559 fees depending on the chain +/// +/// Uses custom gas oracles for +/// - polygon +/// +/// Fallback is the default [`Provider::estimate_eip1559_fees`] implementation +pub async fn estimate_eip1559_fees( + provider: &M, + chain: Option, +) -> Result<(U256, U256)> +where + M::Error: 'static, +{ + let chain = if let Some(chain) = chain { + chain + } else { + provider.get_chainid().await.wrap_err("Failed to get chain id")?.as_u64() + }; + + if let Ok(chain) = NamedChain::try_from(chain) { + // handle chains that deviate from `eth_feeHistory` and have their own oracle + match chain { + NamedChain::Polygon | NamedChain::PolygonMumbai => { + // TODO: phase this out somehow + let chain = match chain { + NamedChain::Polygon => ethers_core::types::Chain::Polygon, + NamedChain::PolygonMumbai => ethers_core::types::Chain::PolygonMumbai, + _ => unreachable!(), + }; + let estimator = Polygon::new(chain)?.category(GasCategory::Standard); + return Ok(estimator.estimate_eip1559_fees().await?); + } + _ => {} + } + } + provider.estimate_eip1559_fees(None).await.wrap_err("Failed fetch EIP1559 fees") +} + +#[cfg(not(windows))] +fn resolve_path(path: &Path) -> Result { + if path.is_absolute() { + Ok(path.to_path_buf()) + } else { + std::env::current_dir().map(|d| d.join(path)).map_err(drop) + } +} + +#[cfg(windows)] +fn resolve_path(path: &Path) -> Result { + if let Some(s) = path.to_str() { + if s.starts_with(r"\\.\pipe\") { + return Ok(path.to_path_buf()); + } + } + Err(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_auto_correct_missing_prefix() { + let builder = ProviderBuilder::new("localhost:8545"); + assert!(builder.url.is_ok()); + + let url = builder.url.unwrap(); + assert_eq!(url, Url::parse("http://localhost:8545").unwrap()); + } +} diff --git a/crates/common/src/provider/mod.rs b/crates/common/src/provider/mod.rs new file mode 100644 index 000000000000..c151edf8550f --- /dev/null +++ b/crates/common/src/provider/mod.rs @@ -0,0 +1,2 @@ +pub mod alloy; +pub mod ethers; diff --git a/crates/evm/core/src/fork/backend.rs b/crates/evm/core/src/fork/backend.rs index 36ced0c424e8..1424bbc262d2 100644 --- a/crates/evm/core/src/fork/backend.rs +++ b/crates/evm/core/src/fork/backend.rs @@ -688,7 +688,7 @@ mod tests { fork::{BlockchainDbMeta, CreateFork, JsonBlockCacheDB}, opts::EvmOpts, }; - use foundry_common::get_http_provider; + use foundry_common::provider::alloy::get_http_provider; use foundry_config::{Config, NamedChain}; use std::{collections::BTreeSet, path::PathBuf, sync::Arc}; const ENDPOINT: &str = "https://mainnet.infura.io/v3/40bee2d557ed4b52908c3e62345a3d8b"; diff --git a/crates/evm/core/src/fork/database.rs b/crates/evm/core/src/fork/database.rs index 00e3294f321b..c52292b2aa92 100644 --- a/crates/evm/core/src/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -264,7 +264,7 @@ impl DatabaseRef for ForkDbSnapshot { mod tests { use super::*; use crate::fork::BlockchainDbMeta; - use foundry_common::get_http_provider; + use foundry_common::provider::alloy::get_http_provider; use std::collections::BTreeSet; /// Demonstrates that `Database::basic` for `ForkedDatabase` will always return the diff --git a/crates/evm/core/src/fork/multi.rs b/crates/evm/core/src/fork/multi.rs index 7eab0cf15e8e..48f9b929fec7 100644 --- a/crates/evm/core/src/fork/multi.rs +++ b/crates/evm/core/src/fork/multi.rs @@ -7,7 +7,7 @@ use crate::fork::{BackendHandler, BlockchainDb, BlockchainDbMeta, CreateFork, Sh use alloy_providers::provider::Provider; use alloy_transport::BoxTransport; use ethers::types::BlockNumber; -use foundry_common::ProviderBuilder; +use foundry_common::provider::alloy::ProviderBuilder; use foundry_config::Config; use futures::{ channel::mpsc::{channel, Receiver, Sender}, @@ -374,8 +374,8 @@ impl Future for MultiForkHandler { .flush_cache_interval .as_mut() .map(|interval| interval.poll_tick(cx).is_ready()) - .unwrap_or_default() && - !pin.forks.is_empty() + .unwrap_or_default() + && !pin.forks.is_empty() { trace!(target: "fork::multi", "tick flushing caches"); let forks = pin.forks.values().map(|f| f.backend.clone()).collect::>(); diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index 05aee492a488..796f2bdd5009 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -4,7 +4,11 @@ use alloy_primitives::{Address, B256, U256}; use alloy_rpc_types::Block; use ethers::providers::{Middleware, Provider}; use eyre::WrapErr; -use foundry_common::{self, ProviderBuilder, RpcUrl, ALCHEMY_FREE_TIER_CUPS}; +use foundry_common::{ + self, + provider::alloy::{ProviderBuilder, RpcUrl}, + ALCHEMY_FREE_TIER_CUPS, +}; use foundry_compilers::utils::RuntimeOrHandle; use foundry_config::{Chain, Config}; use revm::primitives::{BlockEnv, CfgEnv, SpecId, TxEnv}; diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index a187148b8891..45f077b03bc1 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -18,7 +18,7 @@ use foundry_cli::{ opts::{CoreBuildArgs, EthereumOpts, EtherscanOpts, TransactionOpts}, utils::{self, read_constructor_args_file, remove_contract, LoadConfig}, }; -use foundry_common::{compile, estimate_eip1559_fees, fmt::parse_tokens}; +use foundry_common::{compile, fmt::parse_tokens, provider::ethers::estimate_eip1559_fees}; use foundry_compilers::{artifacts::BytecodeObject, info::ContractInfo, utils::canonicalized}; use foundry_utils::types::{ToAlloy, ToEthers}; use serde_json::json; @@ -212,8 +212,8 @@ impl CreateArgs { e } })?; - let is_legacy = self.tx.legacy || - Chain::try_from(chain).map(|x| Chain::is_legacy(&x)).unwrap_or_default(); + let is_legacy = self.tx.legacy + || Chain::try_from(chain).map(|x| Chain::is_legacy(&x)).unwrap_or_default(); let mut deployer = if is_legacy { deployer.legacy() } else { deployer }; // set tx value if specified @@ -297,7 +297,7 @@ impl CreateArgs { }; if !self.verify { - return Ok(()) + return Ok(()); } println!("Starting contract verification..."); diff --git a/crates/forge/bin/cmd/script/broadcast.rs b/crates/forge/bin/cmd/script/broadcast.rs index c57e1ec383bc..13838fce4126 100644 --- a/crates/forge/bin/cmd/script/broadcast.rs +++ b/crates/forge/bin/cmd/script/broadcast.rs @@ -12,7 +12,10 @@ use foundry_cli::{ update_progress, utils::{has_batch_support, has_different_gas_calc}, }; -use foundry_common::{estimate_eip1559_fees, shell, try_get_http_provider, RetryProvider}; +use foundry_common::{ + provider::ethers::estimate_eip1559_fees, provider::ethers::try_get_http_provider, + provider::ethers::RetryProvider, shell, +}; use futures::StreamExt; use std::{cmp::min, collections::HashSet, ops::Mul, sync::Arc}; @@ -247,9 +250,9 @@ impl ScriptArgs { // Chains which use `eth_estimateGas` are being sent sequentially and require their // gas to be re-estimated right before broadcasting. - if !is_fixed_gas_limit && - (has_different_gas_calc(provider.get_chainid().await?.as_u64()) || - self.skip_simulation) + if !is_fixed_gas_limit + && (has_different_gas_calc(provider.get_chainid().await?.as_u64()) + || self.skip_simulation) { self.estimate_gas(&mut tx, &provider).await?; } @@ -351,7 +354,7 @@ impl ScriptArgs { self.send_transactions(deployment_sequence, &rpc, &result.script_wallets).await?; if self.verify { - return deployment_sequence.verify_contracts(&script_config.config, verify).await + return deployment_sequence.verify_contracts(&script_config.config, verify).await; } Ok(()) } @@ -383,7 +386,7 @@ impl ScriptArgs { &mut script_config.config, returns, ) - .await + .await; } else if self.broadcast { eyre::bail!("No onchain transactions generated in script"); } @@ -506,7 +509,7 @@ impl ScriptArgs { // transactions. if let Some(next_tx) = txes_iter.peek() { if next_tx.rpc == Some(tx_rpc) { - continue + continue; } } @@ -611,9 +614,9 @@ impl ScriptArgs { provider .estimate_gas(tx, None) .await - .wrap_err_with(|| format!("Failed to estimate gas for tx: {:?}", tx.sighash()))? * - self.gas_estimate_multiplier / - 100, + .wrap_err_with(|| format!("Failed to estimate gas for tx: {:?}", tx.sighash()))? + * self.gas_estimate_multiplier + / 100, ); Ok(()) } diff --git a/crates/forge/bin/cmd/script/cmd.rs b/crates/forge/bin/cmd/script/cmd.rs index cbdd94ef829b..e21a8f2efeab 100644 --- a/crates/forge/bin/cmd/script/cmd.rs +++ b/crates/forge/bin/cmd/script/cmd.rs @@ -5,7 +5,7 @@ use ethers_providers::Middleware; use ethers_signers::Signer; use eyre::Result; use foundry_cli::utils::LoadConfig; -use foundry_common::{contracts::flatten_contracts, try_get_http_provider}; +use foundry_common::{contracts::flatten_contracts, provider::ethers::try_get_http_provider}; use foundry_debugger::DebuggerArgs; use foundry_utils::types::ToAlloy; use std::sync::Arc; @@ -78,7 +78,7 @@ impl ScriptArgs { result, verify, ) - .await + .await; } let known_contracts = flatten_contracts(&highlevel_known_contracts, true); @@ -155,7 +155,7 @@ impl ScriptArgs { &flatten_contracts(&highlevel_known_contracts, true), )?; - return Ok(Some((new_traces, libraries, highlevel_known_contracts))) + return Ok(Some((new_traces, libraries, highlevel_known_contracts))); } // Add predeploy libraries to the list of broadcastable transactions. @@ -202,7 +202,7 @@ impl ScriptArgs { result.script_wallets, verify, ) - .await + .await; } self.resume_single_deployment( script_config, @@ -213,7 +213,7 @@ impl ScriptArgs { ) .await .map_err(|err| { - eyre::eyre!("{err}\n\nIf you were trying to resume or verify a multi chain deployment, add `--multi` to your command invocation.") + eyre::eyre!("{err}\n\nIf you were trying to resume or verify a multi chain deployment, add `--multi` to your command invocation.") }) } diff --git a/crates/forge/bin/cmd/script/executor.rs b/crates/forge/bin/cmd/script/executor.rs index 4d8c4b33031b..66cb5440e408 100644 --- a/crates/forge/bin/cmd/script/executor.rs +++ b/crates/forge/bin/cmd/script/executor.rs @@ -15,7 +15,7 @@ use forge::{ utils::CallKind, }; use foundry_cli::utils::{ensure_clean_constructor, needs_setup}; -use foundry_common::{shell, RpcUrl}; +use foundry_common::{provider::ethers::RpcUrl, shell}; use foundry_compilers::artifacts::CompactContractBytecode; use foundry_utils::types::ToEthers; use futures::future::join_all; @@ -129,7 +129,7 @@ impl ScriptArgs { abi, code, }; - return Some((*addr, info)) + return Some((*addr, info)); } None }) @@ -160,7 +160,7 @@ impl ScriptArgs { .expect("Internal EVM error"); if !result.success || result.traces.is_empty() { - return Ok((None, result.traces)) + return Ok((None, result.traces)); } let created_contracts = result @@ -173,7 +173,7 @@ impl ScriptArgs { opcode: node.kind(), address: node.trace.address, init_code: node.trace.data.as_bytes().to_vec(), - }) + }); } None }) diff --git a/crates/forge/bin/cmd/script/mod.rs b/crates/forge/bin/cmd/script/mod.rs index 8a5dcbc18b65..10f3e48739e7 100644 --- a/crates/forge/bin/cmd/script/mod.rs +++ b/crates/forge/bin/cmd/script/mod.rs @@ -29,7 +29,8 @@ use foundry_common::{ errors::UnlinkedByteCode, evm::{Breakpoints, EvmArgs}, fmt::format_token, - shell, ContractsByArtifact, RpcUrl, CONTRACT_MAX_SIZE, SELECTOR_LEN, + provider::ethers::RpcUrl, + shell, ContractsByArtifact, CONTRACT_MAX_SIZE, SELECTOR_LEN, }; use foundry_compilers::{ artifacts::{ContractBytecodeSome, Libraries}, @@ -367,7 +368,7 @@ impl ScriptArgs { return Err(eyre::eyre!( "script failed: {}", decode::decode_revert(&result.returned[..], None, None) - )) + )); } Ok(()) @@ -407,7 +408,7 @@ impl ScriptArgs { if let Some(ns) = new_sender { if sender != ns { shell::println("You have more than one deployer who could predeploy libraries. Using `--sender` instead.")?; - return Ok(None) + return Ok(None); } } else if sender != evm_opts.sender { new_sender = Some(sender); @@ -495,13 +496,13 @@ impl ScriptArgs { // From artifacts for (artifact, bytecode) in known_contracts.iter() { if bytecode.bytecode.object.is_unlinked() { - return Err(UnlinkedByteCode::Bytecode(artifact.identifier()).into()) + return Err(UnlinkedByteCode::Bytecode(artifact.identifier()).into()); } let init_code = bytecode.bytecode.object.as_bytes().unwrap(); // Ignore abstract contracts if let Some(ref deployed_code) = bytecode.deployed_bytecode.bytecode { if deployed_code.object.is_unlinked() { - return Err(UnlinkedByteCode::DeployedBytecode(artifact.identifier()).into()) + return Err(UnlinkedByteCode::DeployedBytecode(artifact.identifier()).into()); } let deployed_code = deployed_code.object.as_bytes().unwrap(); bytecodes.push((artifact.name.clone(), init_code, deployed_code)); @@ -526,7 +527,7 @@ impl ScriptArgs { bytecodes.push((format!("Unknown{unknown_c}"), init_code, deployed_code)); unknown_c += 1; } - continue + continue; } } // Both should be raw and not decoded since it's just bytecode @@ -556,7 +557,7 @@ impl ScriptArgs { offset = 32; } } else if to.is_some() { - continue + continue; } // Find artifact with a deployment code same as the data. @@ -578,9 +579,9 @@ impl ScriptArgs { } // Only prompt if we're broadcasting and we've not disabled interactivity. - if prompt_user && - !self.non_interactive && - !Confirm::new().with_prompt("Do you wish to continue?".to_string()).interact()? + if prompt_user + && !self.non_interactive + && !Confirm::new().with_prompt("Do you wish to continue?".to_string()).interact()? { eyre::bail!("User canceled the script."); } @@ -700,7 +701,9 @@ impl ScriptConfig { if let Ok(provider) = ethers_providers::Provider::::try_from(rpc) { match provider.get_chainid().await { Ok(chain_id) => match TryInto::::try_into(chain_id) { - Ok(chain) => return Some((SHANGHAI_ENABLED_CHAINS.contains(&chain), chain)), + Ok(chain) => { + return Some((SHANGHAI_ENABLED_CHAINS.contains(&chain), chain)) + } Err(_) => return None, }, Err(_) => return None, diff --git a/crates/forge/bin/cmd/script/multi.rs b/crates/forge/bin/cmd/script/multi.rs index e98f01f38685..8e43e04254b8 100644 --- a/crates/forge/bin/cmd/script/multi.rs +++ b/crates/forge/bin/cmd/script/multi.rs @@ -7,7 +7,7 @@ use super::{ use ethers_signers::LocalWallet; use eyre::{ContextCompat, Report, Result, WrapErr}; use foundry_cli::utils::now; -use foundry_common::{fs, get_http_provider}; +use foundry_common::{fs, provider::ethers::get_http_provider}; use foundry_compilers::{artifacts::Libraries, ArtifactId}; use foundry_config::Config; use futures::future::join_all; @@ -157,7 +157,7 @@ impl ScriptArgs { { Ok(_) => { if self.verify { - return sequence.verify_contracts(config, verify.clone()).await + return sequence.verify_contracts(config, verify.clone()).await; } Ok(()) } @@ -169,7 +169,7 @@ impl ScriptArgs { let errors = results.into_iter().filter(|res| res.is_err()).collect::>(); if !errors.is_empty() { - return Err(eyre::eyre!("{errors:?}")) + return Err(eyre::eyre!("{errors:?}")); } Ok(()) diff --git a/crates/forge/bin/cmd/script/providers.rs b/crates/forge/bin/cmd/script/providers.rs index ab417ca156bb..c2463065c0dc 100644 --- a/crates/forge/bin/cmd/script/providers.rs +++ b/crates/forge/bin/cmd/script/providers.rs @@ -1,7 +1,9 @@ use alloy_primitives::U256; use ethers_providers::{Middleware, Provider}; use eyre::{Result, WrapErr}; -use foundry_common::{get_http_provider, runtime_client::RuntimeClient, RpcUrl}; +use foundry_common::{ + provider::ethers::get_http_provider, provider::ethers::RpcUrl, runtime_client::RuntimeClient, +}; use foundry_config::Chain; use foundry_utils::types::ToAlloy; use std::{ diff --git a/crates/forge/bin/cmd/script/receipts.rs b/crates/forge/bin/cmd/script/receipts.rs index 638e92f91f86..a797eff4a0fd 100644 --- a/crates/forge/bin/cmd/script/receipts.rs +++ b/crates/forge/bin/cmd/script/receipts.rs @@ -4,7 +4,7 @@ use ethers_core::types::TransactionReceipt; use ethers_providers::{Middleware, PendingTransaction}; use eyre::Result; use foundry_cli::{init_progress, update_progress, utils::print_receipt}; -use foundry_common::RetryProvider; +use foundry_common::provider::ethers::RetryProvider; use foundry_utils::types::{ToAlloy, ToEthers}; use futures::StreamExt; use std::sync::Arc; @@ -34,7 +34,7 @@ pub async fn wait_for_pending( deployment_sequence: &mut ScriptSequence, ) -> Result<()> { if deployment_sequence.pending.is_empty() { - return Ok(()) + return Ok(()); } println!("##\nChecking previously pending transactions."); clear_pendings(provider, deployment_sequence, None).await @@ -136,7 +136,7 @@ async fn check_tx_status( // First check if there's a receipt let receipt_opt = provider.get_transaction_receipt(hash.to_ethers()).await?; if let Some(receipt) = receipt_opt { - return Ok(receipt.into()) + return Ok(receipt.into()); } // If the tx is present in the mempool, run the pending tx future, and diff --git a/crates/forge/bin/cmd/script/transaction.rs b/crates/forge/bin/cmd/script/transaction.rs index 74fe0a75d297..6dcdc9e65467 100644 --- a/crates/forge/bin/cmd/script/transaction.rs +++ b/crates/forge/bin/cmd/script/transaction.rs @@ -4,7 +4,7 @@ use alloy_json_abi::Function; use alloy_primitives::{Address, B256}; use ethers_core::types::{transaction::eip2718::TypedTransaction, NameOrAddress}; use eyre::{ContextCompat, Result, WrapErr}; -use foundry_common::{fmt::format_token_raw, RpcUrl, SELECTOR_LEN}; +use foundry_common::{fmt::format_token_raw, provider::ethers::RpcUrl, SELECTOR_LEN}; use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, traces::CallTraceDecoder, utils::CallKind}; use foundry_utils::types::{ToAlloy, ToEthers}; use serde::{Deserialize, Serialize}; diff --git a/crates/forge/tests/cli/multi_script.rs b/crates/forge/tests/cli/multi_script.rs index c9b78e381cb1..3987018374da 100644 --- a/crates/forge/tests/cli/multi_script.rs +++ b/crates/forge/tests/cli/multi_script.rs @@ -1,7 +1,6 @@ //! Contains various tests related to forge script use anvil::{spawn, NodeConfig}; use foundry_test_utils::{ScriptOutcome, ScriptTester}; -use foundry_utils::types::ToEthers; forgetest_async!(can_deploy_multi_chain_script_without_lib, |prj, cmd| { let (api1, handle1) = spawn(NodeConfig::test()).await; @@ -15,23 +14,11 @@ forgetest_async!(can_deploy_multi_chain_script_without_lib, |prj, cmd| { .args(&[&handle1.http_endpoint(), &handle2.http_endpoint()]) .broadcast(ScriptOutcome::OkBroadcast); - assert_eq!( - api1.transaction_count(tester.accounts_pub[0].to_ethers(), None).await.unwrap().as_u32(), - 1 - ); - assert_eq!( - api1.transaction_count(tester.accounts_pub[1].to_ethers(), None).await.unwrap().as_u32(), - 1 - ); - - assert_eq!( - api2.transaction_count(tester.accounts_pub[0].to_ethers(), None).await.unwrap().as_u32(), - 2 - ); - assert_eq!( - api2.transaction_count(tester.accounts_pub[1].to_ethers(), None).await.unwrap().as_u32(), - 3 - ); + assert_eq!(api1.transaction_count(tester.accounts_pub[0], None).await.unwrap().to::(), 1); + assert_eq!(api1.transaction_count(tester.accounts_pub[1], None).await.unwrap().to::(), 1); + + assert_eq!(api2.transaction_count(tester.accounts_pub[0], None).await.unwrap().to::(), 2); + assert_eq!(api2.transaction_count(tester.accounts_pub[1], None).await.unwrap().to::(), 3); }); forgetest_async!(can_not_deploy_multi_chain_script_with_lib, |prj, cmd| { diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index c8bb085c13e4..606b21bfc914 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -1,7 +1,7 @@ //! Contains various tests related to `forge script`. use crate::constants::TEMPLATE_CONTRACT; -use alloy_primitives::Address; +use alloy_primitives::{Address, Bytes}; use anvil::{spawn, NodeConfig}; use foundry_test_utils::{util::OutputExt, ScriptOutcome, ScriptTester}; use foundry_utils::{rpc, types::ToEthers}; @@ -561,10 +561,8 @@ forgetest_async!(can_deploy_with_create2, |prj, cmd| { // Prepare CREATE2 Deployer api.anvil_set_code( - foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER.to_ethers(), - ethers_core::types::Bytes::from_static( - foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE, - ), + foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER, + Bytes::from_static(foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE), ) .await .unwrap(); @@ -653,7 +651,7 @@ forgetest_async!( // Prepare CREATE2 Deployer let addr = Address::from_str("0x4e59b44847b379578588920ca78fbf26c0b4956c").unwrap(); let code = hex::decode("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3").expect("Could not decode create2 deployer init_code").into(); - api.anvil_set_code(addr.to_ethers(), code).await.unwrap(); + api.anvil_set_code(addr, code).await.unwrap(); tester .load_private_keys(&[0]) diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 5d1b17726264..7c555927ea8b 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -15,9 +15,12 @@ repository.workspace = true foundry-common.workspace = true foundry-compilers = { workspace = true, features = ["project-util"] } foundry-config.workspace = true +foundry-utils.workspace = true alloy-primitives.workspace = true -alloy-providers.workspace = true + +ethers-core.workspace = true +ethers-providers.workspace = true eyre.workspace = true fd-lock = "4.0.0" diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 15f11004b9cd..e9061e32d40f 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -1,8 +1,10 @@ use crate::{init_tracing, TestCommand}; use alloy_primitives::{Address, U256}; -use alloy_providers::provider::TempProvider; +use ethers_core::types::NameOrAddress; +use ethers_providers::Middleware; use eyre::Result; -use foundry_common::{get_http_provider, RetryProvider}; +use foundry_common::provider::ethers::{get_http_provider, RetryProvider}; +use foundry_utils::types::{ToAlloy, ToEthers}; use std::{collections::BTreeMap, fs, path::Path, str::FromStr}; const BROADCAST_TEST_PATH: &str = "src/Broadcast.t.sol"; @@ -111,20 +113,28 @@ impl ScriptTester { if let Some(provider) = &self.provider { let nonce = provider - .get_transaction_count(self.accounts_pub[index as usize], None) + .get_transaction_count( + NameOrAddress::Address(self.accounts_pub[index as usize].to_ethers()), + None, + ) .await .unwrap(); - self.nonces.insert(index, nonce); + self.nonces.insert(index, nonce.to_alloy()); } } self } - pub async fn load_addresses(&mut self, addresses: Vec
) -> &mut Self { - for address in addresses { - let nonce = - self.provider.as_ref().unwrap().get_transaction_count(address, None).await.unwrap(); - self.address_nonces.insert(address, nonce); + pub async fn load_addresses(&mut self, addresses: &[Address]) -> &mut Self { + for &address in addresses { + let nonce = self + .provider + .as_ref() + .unwrap() + .get_transaction_count(NameOrAddress::Address(address.to_ethers()), None) + .await + .unwrap(); + self.address_nonces.insert(address, nonce.to_alloy()); } self } @@ -163,13 +173,18 @@ impl ScriptTester { pub async fn assert_nonce_increment(&mut self, keys_indexes: &[(u32, u32)]) -> &mut Self { for &(private_key_slot, expected_increment) in keys_indexes { let addr = self.accounts_pub[private_key_slot as usize]; - let nonce = - self.provider.as_ref().unwrap().get_transaction_count(addr, None).await.unwrap(); + let nonce = self + .provider + .as_ref() + .unwrap() + .get_transaction_count(NameOrAddress::Address(addr.to_ethers()), None) + .await + .unwrap(); let prev_nonce = self.nonces.get(&private_key_slot).unwrap(); assert_eq!( nonce, - (prev_nonce + U256::from(expected_increment)), + (prev_nonce + U256::from(expected_increment)).to_ethers(), "nonce not incremented correctly for {addr}: \ {prev_nonce} + {expected_increment} != {nonce}" ); @@ -187,12 +202,12 @@ impl ScriptTester { .provider .as_ref() .unwrap() - .get_transaction_count(*address, None) + .get_transaction_count(NameOrAddress::Address(address.to_ethers()), None) .await .unwrap(); let prev_nonce = self.address_nonces.get(address).unwrap(); - assert_eq!(nonce, (prev_nonce + U256::from(*expected_increment))); + assert_eq!(nonce, (prev_nonce + U256::from(*expected_increment)).to_ethers()); } self } @@ -261,16 +276,16 @@ impl ScriptOutcome { pub fn is_err(&self) -> bool { match self { - ScriptOutcome::OkNoEndpoint | - ScriptOutcome::OkSimulation | - ScriptOutcome::OkBroadcast | - ScriptOutcome::WarnSpecifyDeployer => false, - ScriptOutcome::MissingSender | - ScriptOutcome::MissingWallet | - ScriptOutcome::StaticCallNotAllowed | - ScriptOutcome::UnsupportedLibraries | - ScriptOutcome::ErrorSelectForkOnBroadcast | - ScriptOutcome::ScriptFailed => true, + ScriptOutcome::OkNoEndpoint + | ScriptOutcome::OkSimulation + | ScriptOutcome::OkBroadcast + | ScriptOutcome::WarnSpecifyDeployer => false, + ScriptOutcome::MissingSender + | ScriptOutcome::MissingWallet + | ScriptOutcome::StaticCallNotAllowed + | ScriptOutcome::UnsupportedLibraries + | ScriptOutcome::ErrorSelectForkOnBroadcast + | ScriptOutcome::ScriptFailed => true, } } } diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index acbb6041fffd..0900965ef3f0 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -16,6 +16,7 @@ forge-fmt.workspace = true alloy-json-abi.workspace = true alloy-primitives.workspace = true alloy-sol-types.workspace = true +alloy-rpc-types.workspace = true ethers-core.workspace = true ethers-providers.workspace = true diff --git a/crates/utils/src/types.rs b/crates/utils/src/types.rs index a450ee688e88..e0934450b31d 100644 --- a/crates/utils/src/types.rs +++ b/crates/utils/src/types.rs @@ -2,6 +2,7 @@ use alloy_json_abi::{Event, EventParam, Function, InternalType, Param, StateMutability}; use alloy_primitives::{Address, B256, I256, U256, U64}; +use alloy_rpc_types::{Signature, Transaction}; use ethers_core::{ abi as ethabi, types::{H160, H256, I256 as EthersI256, U256 as EthersU256, U64 as EthersU64}, @@ -69,6 +70,39 @@ impl ToAlloy for u64 { } } +impl ToAlloy for ethers_core::types::Transaction { + type To = Transaction; + + fn to_alloy(self) -> Self::To { + Transaction { + hash: self.hash.to_alloy(), + nonce: self.nonce.to_alloy(), + block_hash: self.block_hash.map(ToAlloy::to_alloy), + block_number: self.block_number.map(ToAlloy::to_alloy), + transaction_index: self.transaction_index.map(ToAlloy::to_alloy), + from: self.from.to_alloy(), + to: self.to.map(ToAlloy::to_alloy), + value: self.value.to_alloy(), + gas_price: self.gas_price.to_alloy(), + gas: self.gas.to_alloy(), + max_fee_per_gas: self.max_fee_per_gas.to_alloy(), + max_priority_fee_per_gas: self.max_priority_fee_per_gas.to_alloy(), + max_fee_per_blob_gas: None, + input: self.input.to_alloy(), + signature: Some(Signature { + r: self.r.to_alloy(), + s: self.s.to_alloy(), + v: self.v.to_alloy(), + y_parity: None, + }), + chain_id: self.chain_id.to_alloy(), + blob_versioned_hashes: Vec::new(), + access_list: self.access_list.to_alloy(), + transaction_type: self.transaction_type.to_alloy(), + } + } +} + impl ToAlloy for ethabi::Event { type To = Event; From 2504af6ce03dc9c660497c183d676c172b8a7081 Mon Sep 17 00:00:00 2001 From: Enrique Ortiz Date: Mon, 20 Nov 2023 19:21:52 -0400 Subject: [PATCH 2/3] chore: complete Transaction ToAlloy compat trait --- crates/utils/src/types.rs | 50 ++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/crates/utils/src/types.rs b/crates/utils/src/types.rs index e0934450b31d..c481d375203e 100644 --- a/crates/utils/src/types.rs +++ b/crates/utils/src/types.rs @@ -1,11 +1,11 @@ //! Temporary utility conversion traits between ethers-rs and alloy types. use alloy_json_abi::{Event, EventParam, Function, InternalType, Param, StateMutability}; -use alloy_primitives::{Address, B256, I256, U256, U64}; -use alloy_rpc_types::{Signature, Transaction}; +use alloy_primitives::{Address, B256, I256, U128, U256, U64}; +use alloy_rpc_types::{Signature, Transaction, AccessList, AccessListItem}; use ethers_core::{ abi as ethabi, - types::{H160, H256, I256 as EthersI256, U256 as EthersU256, U64 as EthersU64}, + types::{H160, H256, I256 as EthersI256, U256 as EthersU256, U64 as EthersU64, transaction::eip2930::{AccessList as EthersAccessList, AccessListItem as EthersAccessListItem}}, }; /// Conversion trait to easily convert from Ethers types to Alloy types. @@ -76,29 +76,51 @@ impl ToAlloy for ethers_core::types::Transaction { fn to_alloy(self) -> Self::To { Transaction { hash: self.hash.to_alloy(), - nonce: self.nonce.to_alloy(), + nonce: U64::from(self.nonce.as_u64()), block_hash: self.block_hash.map(ToAlloy::to_alloy), - block_number: self.block_number.map(ToAlloy::to_alloy), - transaction_index: self.transaction_index.map(ToAlloy::to_alloy), + block_number: self.block_number.map(|b| U256::from(b.as_u64())), + transaction_index: self.transaction_index.map(|b| U256::from(b.as_u64())), from: self.from.to_alloy(), to: self.to.map(ToAlloy::to_alloy), value: self.value.to_alloy(), - gas_price: self.gas_price.to_alloy(), + gas_price: self.gas_price.map(|a| U128::from(a.as_u128())), gas: self.gas.to_alloy(), - max_fee_per_gas: self.max_fee_per_gas.to_alloy(), - max_priority_fee_per_gas: self.max_priority_fee_per_gas.to_alloy(), + max_fee_per_gas: self.max_fee_per_gas.map(|f| U128::from(f.as_u128())), + max_priority_fee_per_gas: self.max_priority_fee_per_gas.map(|f| U128::from(f.as_u128())), max_fee_per_blob_gas: None, - input: self.input.to_alloy(), + input: self.input.0.into(), signature: Some(Signature { r: self.r.to_alloy(), s: self.s.to_alloy(), - v: self.v.to_alloy(), + v: U256::from(self.v.as_u64()), y_parity: None, }), - chain_id: self.chain_id.to_alloy(), + chain_id: self.chain_id.map(|c| U64::from(c.as_u64())), blob_versioned_hashes: Vec::new(), - access_list: self.access_list.to_alloy(), - transaction_type: self.transaction_type.to_alloy(), + access_list: self.access_list.map(|a| a.0.into_iter().map(ToAlloy::to_alloy).collect()), + transaction_type: self.transaction_type.map(|t| t.to_alloy()), + } + } +} + +impl ToAlloy for EthersAccessList { + type To = AccessList; + fn to_alloy(self) -> Self::To { + AccessList(self.0.into_iter().map(ToAlloy::to_alloy).collect()) + } +} + +impl ToAlloy for EthersAccessListItem { + type To = AccessListItem; + + fn to_alloy(self) -> Self::To { + AccessListItem { + address: self.address.to_alloy(), + storage_keys: self + .storage_keys + .into_iter() + .map(|k| U256::from_be_bytes(k.to_alloy().0)) + .collect(), } } } From 333868bfb602b05f8b9a448867e6187cdf9bbc06 Mon Sep 17 00:00:00 2001 From: Enrique Ortiz Date: Mon, 20 Nov 2023 19:22:16 -0400 Subject: [PATCH 3/3] chore: clone txs --- crates/cast/bin/cmd/run.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index 822773e8973c..cc91870253c8 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -155,7 +155,7 @@ impl RunArgs { break; } - configure_tx_env(&mut env, &tx.to_alloy()); + configure_tx_env(&mut env, &tx.clone().to_alloy()); if let Some(to) = tx.to { trace!(tx=?tx.hash,?to, "executing previous call transaction"); @@ -192,7 +192,7 @@ impl RunArgs { let result = { executor.set_trace_printer(self.trace_printer); - configure_tx_env(&mut env, &tx.to_alloy()); + configure_tx_env(&mut env, &tx.clone().to_alloy()); if let Some(to) = tx.to { trace!(tx=?tx.hash, to=?to, "executing call transaction");