From 443b5377f12a872f18b452b845d3edf19367b346 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Wed, 14 Feb 2024 14:26:14 +0400 Subject: [PATCH 01/18] refactor foundry-wallets --- Cargo.lock | 1 + crates/cast/bin/cmd/send.rs | 6 +- crates/cast/bin/cmd/wallet/mod.rs | 23 +- crates/cli/src/opts/ethereum.rs | 8 +- crates/forge/bin/cmd/create.rs | 6 +- crates/forge/bin/cmd/script/broadcast.rs | 39 +- crates/forge/bin/cmd/script/mod.rs | 4 +- crates/wallets/Cargo.toml | 3 + crates/wallets/src/error.rs | 2 + crates/wallets/src/lib.rs | 7 +- crates/wallets/src/multi_wallet.rs | 387 +++++++++---------- crates/wallets/src/raw_wallet.rs | 23 ++ crates/wallets/src/utils.rs | 145 +++++++ crates/wallets/src/wallet.rs | 470 +++-------------------- crates/wallets/src/wallet_signer.rs | 174 +++++++++ 15 files changed, 642 insertions(+), 656 deletions(-) create mode 100644 crates/wallets/src/utils.rs create mode 100644 crates/wallets/src/wallet_signer.rs diff --git a/Cargo.lock b/Cargo.lock index ec2e562b8f2f..19e71396df8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3360,6 +3360,7 @@ dependencies = [ "rusoto_kms", "serde", "thiserror", + "tokio", "tracing", ] diff --git a/crates/cast/bin/cmd/send.rs b/crates/cast/bin/cmd/send.rs index b79bf1d7cd9b..b68ba5c7566d 100644 --- a/crates/cast/bin/cmd/send.rs +++ b/crates/cast/bin/cmd/send.rs @@ -1,7 +1,7 @@ use cast::{Cast, TxBuilder}; use clap::Parser; use ethers_core::types::NameOrAddress; -use ethers_middleware::MiddlewareBuilder; +use ethers_middleware::SignerMiddleware; use ethers_providers::Middleware; use ethers_signers::Signer; use eyre::Result; @@ -170,7 +170,7 @@ impl SendTxArgs { // enough information to sign and we must bail. } else { // Retrieve the signer, and bail if it can't be constructed. - let signer = eth.wallet.signer(chain.id()).await?; + let signer = eth.wallet.signer().await?; let from = signer.address(); // prevent misconfigured hwlib from sending a transaction that defies @@ -191,7 +191,7 @@ corresponds to the sender, or let foundry automatically detect it by not specify tx.nonce = Some(provider.get_transaction_count(from, None).await?.to_alloy()); } - let provider = provider.with_signer(signer); + let provider = SignerMiddleware::new_with_provider_chain(provider, signer).await?; cast_send( provider, diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index acf578dc02bb..ce167f31e00e 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -9,7 +9,7 @@ use ethers_signers::Signer; use eyre::{Context, Result}; use foundry_common::{fs, types::ToAlloy}; use foundry_config::Config; -use foundry_wallets::{RawWallet, Wallet}; +use foundry_wallets::{RawWallet, Wallet, WalletSigner}; use rand::thread_rng; use serde_json::json; use std::{path::Path, str::FromStr}; @@ -242,13 +242,13 @@ impl WalletSubcommands { ..Default::default() }) .unwrap_or(wallet) - .signer(0) + .signer() .await?; let addr = wallet.address(); println!("{}", addr.to_alloy().to_checksum(None)); } WalletSubcommands::Sign { message, data, from_file, wallet } => { - let wallet = wallet.signer(0).await?; + let wallet = wallet.signer().await?; let sig = if data { let typed_data: TypedData = if from_file { // data is a file name, read json from file @@ -291,16 +291,21 @@ impl WalletSubcommands { } // get wallet - let wallet: Wallet = raw_wallet_options.into(); - let wallet = wallet.try_resolve_local_wallet()?.ok_or_else(|| { - eyre::eyre!( - "\ + let wallet = raw_wallet_options + .signer()? + .and_then(|s| match s { + WalletSigner::Local(s) => Some(s), + _ => None, + }) + .ok_or_else(|| { + eyre::eyre!( + "\ Did you set a private key or mnemonic? Run `cast wallet import --help` and use the corresponding CLI flag to set your key via: --private-key, --mnemonic-path or --interactive." - ) - })?; + ) + })?; let private_key = wallet.signer().to_bytes(); let password = rpassword::prompt_password("Enter password: ")?; diff --git a/crates/cli/src/opts/ethereum.rs b/crates/cli/src/opts/ethereum.rs index 9a1d8653bf47..198da6c6a2f8 100644 --- a/crates/cli/src/opts/ethereum.rs +++ b/crates/cli/src/opts/ethereum.rs @@ -9,7 +9,7 @@ use foundry_config::{ }, impl_figment_convert_cast, Chain, Config, }; -use foundry_wallets::{Wallet, WalletSigner}; +use foundry_wallets::Wallet; use serde::Serialize; use std::borrow::Cow; @@ -155,12 +155,6 @@ pub struct EthereumOpts { impl_figment_convert_cast!(EthereumOpts); -impl EthereumOpts { - pub async fn signer(&self) -> Result { - self.wallet.signer(self.etherscan.chain.unwrap_or_default().id()).await - } -} - // Make this args a `Figment` so that it can be merged into the `Config` impl figment::Provider for EthereumOpts { fn metadata(&self) -> Metadata { diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index a6510e0ce1f1..ae72a3b746e5 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -11,7 +11,7 @@ use ethers_core::{ TransactionReceipt, TransactionRequest, }, }; -use ethers_middleware::MiddlewareBuilder; +use ethers_middleware::SignerMiddleware; use ethers_providers::Middleware; use eyre::{Context, Result}; use foundry_cli::{ @@ -145,8 +145,8 @@ impl CreateArgs { self.deploy(abi, bin, params, provider, chain_id).await } else { // Deploy with signer - let signer = self.eth.wallet.signer(chain_id).await?; - let provider = provider.with_signer(signer); + let signer = self.eth.wallet.signer().await?; + let provider = SignerMiddleware::new_with_provider_chain(provider, signer).await?; self.deploy(abi, bin, params, provider, chain_id).await } } diff --git a/crates/forge/bin/cmd/script/broadcast.rs b/crates/forge/bin/cmd/script/broadcast.rs index 7488bbd1071b..31331a995284 100644 --- a/crates/forge/bin/cmd/script/broadcast.rs +++ b/crates/forge/bin/cmd/script/broadcast.rs @@ -54,12 +54,39 @@ impl ScriptArgs { ); (SendTransactionsKind::Unlocked(senders), chain.as_u64()) } else { - let local_wallets = self - .wallets - .find_all(provider.clone(), required_addresses, script_wallets) - .await?; - let chain = local_wallets.values().last().wrap_err("Error accessing local wallet when trying to send onchain transaction, did you set a private key, mnemonic or keystore?")?.chain_id(); - (SendTransactionsKind::Raw(local_wallets), chain) + let mut multi_wallet = self.wallets.get_multi_wallet().await?; + multi_wallet.add_signers(script_wallets.iter().cloned().map(WalletSigner::Local)); + + let signers = multi_wallet.into_signers()?; + + let mut missing_addresses = Vec::new(); + + println!("\n###\nFinding wallets for all the necessary addresses..."); + for addr in &required_addresses { + if !signers.contains_key(addr) { + missing_addresses.push(addr); + } + } + + if !missing_addresses.is_empty() { + let mut error_msg = String::new(); + + // This is an actual used address + if required_addresses.contains(&Config::DEFAULT_SENDER) { + error_msg += "\nYou seem to be using Foundry's default sender. Be sure to set your own --sender.\n"; + } + + eyre::bail!( + "{}No associated wallet for addresses: {:?}. Unlocked wallets: {:?}", + error_msg, + missing_addresses, + signers.keys().collect::>() + ); + } + + let chain = provider.get_chainid().await?.as_u64(); + + (SendTransactionsKind::Raw(signers), chain) }; // We only wait for a transaction receipt before sending the next transaction, if there diff --git a/crates/forge/bin/cmd/script/mod.rs b/crates/forge/bin/cmd/script/mod.rs index 9d641abe356e..d6fcd3836aa4 100644 --- a/crates/forge/bin/cmd/script/mod.rs +++ b/crates/forge/bin/cmd/script/mod.rs @@ -46,7 +46,7 @@ use foundry_evm::{ decode, inspectors::cheatcodes::{BroadcastableTransaction, BroadcastableTransactions}, }; -use foundry_wallets::MultiWallet; +use foundry_wallets::MultiWalletOpts; use futures::future; use itertools::Itertools; use serde::{Deserialize, Serialize}; @@ -180,7 +180,7 @@ pub struct ScriptArgs { pub opts: BuildArgs, #[clap(flatten)] - pub wallets: MultiWallet, + pub wallets: MultiWalletOpts, #[clap(flatten)] pub evm_opts: EvmArgs, diff --git a/crates/wallets/Cargo.toml b/crates/wallets/Cargo.toml index f531fd343ce6..501d0777f0fb 100644 --- a/crates/wallets/Cargo.toml +++ b/crates/wallets/Cargo.toml @@ -32,6 +32,9 @@ serde.workspace = true thiserror = "1" tracing.workspace = true +[dev-dependencies] +tokio = { version = "1", features = ["macros"] } + [features] default = ["rustls"] rustls = ["ethers-providers/rustls", "rusoto_core/rustls"] diff --git a/crates/wallets/src/error.rs b/crates/wallets/src/error.rs index 6e6bb5f145db..9ca867f47e75 100644 --- a/crates/wallets/src/error.rs +++ b/crates/wallets/src/error.rs @@ -19,4 +19,6 @@ pub enum WalletSignerError { Trezor(#[from] TrezorError), #[error(transparent)] Aws(#[from] AwsSignerError), + #[error(transparent)] + Io(#[from] std::io::Error), } diff --git a/crates/wallets/src/lib.rs b/crates/wallets/src/lib.rs index 0e17c4ced3be..995002f97691 100644 --- a/crates/wallets/src/lib.rs +++ b/crates/wallets/src/lib.rs @@ -4,8 +4,11 @@ extern crate tracing; pub mod error; pub mod multi_wallet; pub mod raw_wallet; +pub mod utils; pub mod wallet; +pub mod wallet_signer; -pub use multi_wallet::MultiWallet; +pub use multi_wallet::MultiWalletOpts; pub use raw_wallet::RawWallet; -pub use wallet::{Wallet, WalletSigner}; +pub use wallet::Wallet; +pub use wallet_signer::{PendingSigner, WalletSigner}; diff --git a/crates/wallets/src/multi_wallet.rs b/crates/wallets/src/multi_wallet.rs index 4b7280731e5f..1c2b971aef32 100644 --- a/crates/wallets/src/multi_wallet.rs +++ b/crates/wallets/src/multi_wallet.rs @@ -1,63 +1,85 @@ -use crate::wallet::{WalletSigner, WalletTrait}; +use crate::{ + utils, + wallet_signer::{PendingSigner, WalletSigner}, +}; use alloy_primitives::Address; 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::{provider::ethers::RetryProvider, types::ToAlloy}; + +use ethers_signers::Signer; +use eyre::Result; + +use foundry_common::types::ToAlloy; use foundry_config::Config; -use itertools::izip; -use rusoto_core::{ - credential::ChainProvider as AwsChainProvider, region::Region as AwsRegion, - request::HttpClient as AwsHttpClient, Client as AwsClient, -}; -use rusoto_kms::KmsClient; + use serde::Serialize; -use std::{ - collections::{HashMap, HashSet}, - iter::repeat, - sync::Arc, -}; +use std::{collections::HashMap, iter::repeat, path::PathBuf}; -macro_rules! get_wallets { - ($id:ident, [ $($wallets:expr),+ ], $call:expr) => { - $( - if let Some($id) = $wallets { - $call; - } - )+ - }; +/// Container for multiple wallets. +#[derive(Debug)] +pub struct MultiWallet { + /// Vector of wallets that require an action to be unlocked. + /// Those are lazily unlocked on the first access of the signers. + pending_signers: Vec, + /// Contains unlocked signers. + signers: HashMap, +} + +impl MultiWallet { + pub fn new(pending_signers: Vec, signers: Vec) -> Self { + let signers = + signers.into_iter().map(|signer| (signer.address().to_alloy(), signer)).collect(); + Self { pending_signers, signers } + } + + fn maybe_unlock_pending(&mut self) -> Result<()> { + for pending in self.pending_signers.drain(..) { + let signer = pending.unlock()?; + self.signers.insert(signer.address().to_alloy(), signer); + } + Ok(()) + } + + pub fn signers(&mut self) -> Result<&HashMap> { + self.maybe_unlock_pending()?; + Ok(&self.signers) + } + + pub fn into_signers(mut self) -> Result> { + self.maybe_unlock_pending()?; + Ok(self.signers) + } + + pub fn add_signers(&mut self, signers: impl IntoIterator) { + for signer in signers { + self.signers.insert(signer.address().to_alloy(), signer); + } + } } /// A macro that initializes multiple wallets /// /// Should be used with a [`MultiWallet`] instance macro_rules! create_hw_wallets { - ($self:ident, $chain_id:ident ,$get_wallet:ident, $wallets:ident) => { - let mut $wallets = vec![]; + ($self:ident, $create_signer:expr, $signers:ident) => { + let mut $signers = vec![]; if let Some(hd_paths) = &$self.hd_paths { for path in hd_paths { - if let Some(hw) = $self.$get_wallet($chain_id, Some(path), None).await? { - $wallets.push(hw); - } + let hw = $create_signer(Some(path), 0).await?; + $signers.push(hw); } } if let Some(mnemonic_indexes) = &$self.mnemonic_indexes { for index in mnemonic_indexes { - if let Some(hw) = $self.$get_wallet($chain_id, None, Some(*index as usize)).await? { - $wallets.push(hw); - } + let hw = $create_signer(None, *index).await?; + $signers.push(hw); } } - if $wallets.is_empty() { - if let Some(hw) = $self.$get_wallet($chain_id, None, Some(0)).await? { - $wallets.push(hw); - } + if $signers.is_empty() { + let hw = $create_signer(None, 0).await?; + $signers.push(hw); } }; } @@ -72,7 +94,7 @@ macro_rules! create_hw_wallets { /// 7. AWS KMS #[derive(Clone, Debug, Default, Serialize, Parser)] #[clap(next_help_heading = "Wallet options", about = None, long_about = None)] -pub struct MultiWallet { +pub struct MultiWalletOpts { /// The sender accounts. #[clap( long, @@ -197,91 +219,71 @@ pub struct MultiWallet { pub aws: bool, } -impl WalletTrait for MultiWallet { - fn sender(&self) -> Option
{ - self.froms.as_ref()?.first().copied() - } -} - -impl MultiWallet { - /// Given a list of addresses, it finds all the associated wallets if they exist. Throws an - /// error, if it can't find all. - pub async fn find_all( - &self, - provider: Arc, - mut addresses: HashSet
, - script_wallets: &[LocalWallet], - ) -> Result> { - println!("\n###\nFinding wallets for all the necessary addresses..."); - let chain = provider.get_chainid().await?.as_u64(); - - let mut local_wallets = HashMap::new(); - let mut unused_wallets = vec![]; - - get_wallets!( - wallets, - [ - self.trezors(chain).await?, - self.ledgers(chain).await?, - self.private_keys()?, - self.interactives()?, - self.mnemonics()?, - self.keystores()?, - self.aws_signers(chain).await?, - (!script_wallets.is_empty()).then(|| script_wallets.to_vec()) - ], - for wallet in wallets.into_iter() { - let address = wallet.address(); - if addresses.contains(&address.to_alloy()) { - addresses.remove(&address.to_alloy()); - - let signer = WalletSigner::from(wallet.with_chain_id(chain)); - local_wallets.insert(address.to_alloy(), signer); - - if addresses.is_empty() { - return Ok(local_wallets); - } - } else { - // Just to show on error. - unused_wallets.push(address.to_alloy()); - } - } - ); - - let mut error_msg = String::new(); +impl MultiWalletOpts { + pub async fn get_multi_wallet(&self) -> Result { + let mut pending = Vec::new(); + let mut signers: Vec = Vec::new(); - // This is an actual used address - if addresses.contains(&Config::DEFAULT_SENDER) { - error_msg += "\nYou seem to be using Foundry's default sender. Be sure to set your own --sender.\n"; + if let Some(ledgers) = self.ledgers().await? { + signers.extend(ledgers); + } + if let Some(trezors) = self.trezors().await? { + signers.extend(trezors); + } + if let Some(aws_signers) = self.aws_signers().await? { + signers.extend(aws_signers); + } + if let Some((pending_keystores, unlocked)) = self.keystores()? { + pending.extend(pending_keystores); + signers.extend(unlocked); + } + if let Some(pks) = self.private_keys()? { + signers.extend(pks); + } + if let Some(mnemonics) = self.mnemonics()? { + signers.extend(mnemonics); + } + if self.interactives > 0 { + pending.extend(repeat(PendingSigner::Interactive).take(self.interactives as usize)); } - unused_wallets.extend(local_wallets.into_keys()); - eyre::bail!( - "{}No associated wallet for addresses: {:?}. Unlocked wallets: {:?}", - error_msg, - addresses, - unused_wallets - ) + Ok(MultiWallet::new(pending, signers)) } - pub fn interactives(&self) -> Result>> { - if self.interactives != 0 { - let mut wallets = vec![]; - for _ in 0..self.interactives { - wallets.push(self.get_from_interactive()?); + pub fn private_keys(&self) -> Result>> { + let mut pks = vec![]; + if let Some(private_key) = &self.private_key { + pks.push(private_key); + } + if let Some(private_keys) = &self.private_keys { + for pk in private_keys { + pks.push(pk); } - return Ok(Some(wallets)); } - Ok(None) + if !pks.is_empty() { + let wallets = pks + .into_iter() + .map(|pk| utils::create_private_key_signer(pk)) + .collect::>>()?; + Ok(Some(wallets)) + } else { + Ok(None) + } } - pub fn private_keys(&self) -> Result>> { - if let Some(private_keys) = &self.private_keys { - let mut wallets = vec![]; - for private_key in private_keys.iter() { - wallets.push(self.get_from_private_key(private_key.trim())?); - } - return Ok(Some(wallets)); + fn keystore_paths(&self) -> Result>> { + if let Some(keystore_paths) = &self.keystore_paths { + return Ok(Some(keystore_paths.iter().map(PathBuf::from).collect())); + } + if let Some(keystore_account_names) = &self.keystore_account_names { + let default_keystore_dir = Config::foundry_keystores_dir() + .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; + return Ok(Some( + keystore_account_names + .iter() + .map(|keystore_name| default_keystore_dir.join(keystore_name)) + .collect(), + )); } Ok(None) } @@ -289,23 +291,10 @@ impl MultiWallet { /// Returns all wallets read from the provided keystores arguments /// /// Returns `Ok(None)` if no keystore provided. - pub fn keystores(&self) -> Result>> { - let default_keystore_dir = Config::foundry_keystores_dir() - .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; - // If keystore paths are provided, use them, else, use default path + keystore account names - let keystore_paths = self.keystore_paths.clone().or_else(|| { - self.keystore_account_names.as_ref().map(|keystore_names| { - keystore_names - .iter() - .map(|keystore_name| { - default_keystore_dir.join(keystore_name).to_string_lossy().into_owned() - }) - .collect() - }) - }); - - if let Some(keystore_paths) = keystore_paths { - let mut wallets = Vec::with_capacity(keystore_paths.len()); + pub fn keystores(&self) -> Result, Vec)>> { + if let Some(keystore_paths) = self.keystore_paths()? { + let mut pending = Vec::new(); + let mut signers = Vec::new(); let mut passwords_iter = self.keystore_passwords.clone().unwrap_or_default().into_iter(); @@ -313,51 +302,49 @@ impl MultiWallet { let mut password_files_iter = self.keystore_password_files.clone().unwrap_or_default().into_iter(); - for path in keystore_paths { - let wallet = self.get_from_keystore(Some(&path), passwords_iter.next().as_ref(), password_files_iter.next().as_ref())?.wrap_err("Keystore paths do not have the same length as provided passwords or password files.")?; - wallets.push(wallet); + for path in &keystore_paths { + let (maybe_signer, maybe_pending) = utils::create_keystore_signer( + path, + passwords_iter.next().as_deref(), + password_files_iter.next().as_deref(), + )?; + if let Some(pending_signer) = maybe_pending { + pending.push(pending_signer); + } else if let Some(signer) = maybe_signer { + signers.push(signer); + } } - return Ok(Some(wallets)); + return Ok(Some((pending, signers))); } Ok(None) } - pub fn mnemonics(&self) -> Result>> { + pub fn mnemonics(&self) -> Result>> { if let Some(ref mnemonics) = self.mnemonics { let mut wallets = vec![]; - let hd_paths: Vec<_> = if let Some(ref hd_paths) = self.hd_paths { - hd_paths.iter().map(Some).collect() - } else { - repeat(None).take(mnemonics.len()).collect() - }; - let mnemonic_passphrases: Vec<_> = - if let Some(ref mnemonic_passphrases) = self.mnemonic_passphrases { - mnemonic_passphrases.iter().map(Some).collect() - } else { - repeat(None).take(mnemonics.len()).collect() - }; - let mnemonic_indexes: Vec<_> = if let Some(ref mnemonic_indexes) = self.mnemonic_indexes - { - mnemonic_indexes.to_vec() - } else { - repeat(0).take(mnemonics.len()).collect() - }; - for (mnemonic, mnemonic_passphrase, hd_path, mnemonic_index) in - izip!(mnemonics, mnemonic_passphrases, hd_paths, mnemonic_indexes) - { - wallets.push(self.get_from_mnemonic( + + let mut hd_paths_iter = self.hd_paths.clone().unwrap_or_default().into_iter(); + + let mut passphrases_iter = + self.mnemonic_passphrases.clone().unwrap_or_default().into_iter(); + + let mut indexes_iter = self.mnemonic_indexes.clone().unwrap_or_default().into_iter(); + + for mnemonic in mnemonics { + let wallet = utils::create_mnemonic_signer( mnemonic, - mnemonic_passphrase, - hd_path, - mnemonic_index, - )?) + passphrases_iter.next().as_deref(), + hd_paths_iter.next().as_deref(), + indexes_iter.next().unwrap_or(0), + )?; + wallets.push(wallet); } return Ok(Some(wallets)); } Ok(None) } - pub async fn ledgers(&self, chain_id: u64) -> Result>> { + pub async fn ledgers(&self) -> Result>> { if self.ledger { let mut args = self.clone(); @@ -368,34 +355,31 @@ impl MultiWallet { args.mnemonic_indexes = None; } - create_hw_wallets!(args, chain_id, get_from_ledger, wallets); + create_hw_wallets!(args, utils::create_ledger_signer, wallets); return Ok(Some(wallets)); } Ok(None) } - pub async fn trezors(&self, chain_id: u64) -> Result>> { + pub async fn trezors(&self) -> Result>> { if self.trezor { - create_hw_wallets!(self, chain_id, get_from_trezor, wallets); + create_hw_wallets!(self, utils::create_trezor_signer, wallets); return Ok(Some(wallets)); } Ok(None) } - pub async fn aws_signers(&self, chain_id: u64) -> Result>> { + pub async fn aws_signers(&self) -> Result>> { if self.aws { let mut wallets = vec![]; - let client = - AwsClient::new_with(AwsChainProvider::default(), AwsHttpClient::new().unwrap()); - - let kms = KmsClient::new_with_client(client, AwsRegion::default()); - - let env_key_ids = std::env::var("AWS_KMS_KEY_IDS"); - let key_ids = - if env_key_ids.is_ok() { env_key_ids? } else { std::env::var("AWS_KMS_KEY_ID")? }; - - for key in key_ids.split(',') { - let aws_signer = AwsSigner::new(kms.clone(), key, chain_id).await?; + let aws_keys = std::env::var("AWS_KMS_KEY_IDS") + .or(std::env::var("AWS_KMS_KEY_ID"))? + .split(',') + .map(|k| k.to_string()) + .collect::>(); + + for key in aws_keys { + let aws_signer = WalletSigner::from_aws(&key).await?; wallets.push(aws_signer) } @@ -403,50 +387,23 @@ impl MultiWallet { } Ok(None) } - - async fn get_from_trezor( - &self, - chain_id: u64, - hd_path: Option<&str>, - mnemonic_index: Option, - ) -> Result> { - let derivation = match &hd_path { - Some(hd_path) => TrezorHDPath::Other(hd_path.to_string()), - None => TrezorHDPath::TrezorLive(mnemonic_index.unwrap_or(0)), - }; - - Ok(Some(Trezor::new(derivation, chain_id, None).await?)) - } - - async fn get_from_ledger( - &self, - chain_id: u64, - hd_path: Option<&str>, - mnemonic_index: Option, - ) -> Result> { - let derivation = match hd_path { - Some(hd_path) => LedgerHDPath::Other(hd_path.to_string()), - None => LedgerHDPath::LedgerLive(mnemonic_index.unwrap_or(0)), - }; - - trace!(?chain_id, "Creating new ledger signer"); - Ok(Some(Ledger::new(derivation, chain_id).await.wrap_err("Ledger device not available.")?)) - } } #[cfg(test)] mod tests { + use ethers_signers::Signer; + use super::*; use std::path::Path; #[test] fn parse_keystore_args() { - let args: MultiWallet = - MultiWallet::parse_from(["foundry-cli", "--keystores", "my/keystore/path"]); + let args: MultiWalletOpts = + MultiWalletOpts::parse_from(["foundry-cli", "--keystores", "my/keystore/path"]); assert_eq!(args.keystore_paths, Some(vec!["my/keystore/path".to_string()])); std::env::set_var("ETH_KEYSTORE", "MY_KEYSTORE"); - let args: MultiWallet = MultiWallet::parse_from(["foundry-cli"]); + let args: MultiWalletOpts = MultiWalletOpts::parse_from(["foundry-cli"]); assert_eq!(args.keystore_paths, Some(vec!["MY_KEYSTORE".to_string()])); std::env::remove_var("ETH_KEYSTORE"); @@ -461,7 +418,7 @@ mod tests { let keystore_password_file = keystore.join("password-ec554").into_os_string(); - let args: MultiWallet = MultiWallet::parse_from([ + let args: MultiWalletOpts = MultiWalletOpts::parse_from([ "foundry-cli", "--keystores", keystore_file.to_str().unwrap(), @@ -473,10 +430,10 @@ mod tests { Some(vec![keystore_password_file.to_str().unwrap().to_string()]) ); - let wallets = args.keystores().unwrap().unwrap(); - assert_eq!(wallets.len(), 1); + let (_, unlocked) = args.keystores().unwrap().unwrap(); + assert_eq!(unlocked.len(), 1); assert_eq!( - wallets[0].address(), + unlocked[0].address(), "ec554aeafe75601aaab43bd4621a22284db566c2".parse().unwrap() ); } @@ -491,7 +448,7 @@ mod tests { ]; for test_case in wallet_options { - let args: MultiWallet = MultiWallet::parse_from([ + let args: MultiWalletOpts = MultiWalletOpts::parse_from([ "foundry-cli", &format!("--{}", test_case.0), test_case.1, diff --git a/crates/wallets/src/raw_wallet.rs b/crates/wallets/src/raw_wallet.rs index e33dc22876c5..510da22e6df1 100644 --- a/crates/wallets/src/raw_wallet.rs +++ b/crates/wallets/src/raw_wallet.rs @@ -1,6 +1,9 @@ use clap::Parser; +use eyre::Result; use serde::Serialize; +use crate::{utils, PendingSigner, WalletSigner}; + /// A wrapper for the raw data options for `Wallet`, extracted to also be used standalone. /// The raw wallet options can either be: /// 1. Private Key (cleartext in CLI) @@ -37,3 +40,23 @@ pub struct RawWallet { #[clap(long, conflicts_with = "hd_path", default_value_t = 0, value_name = "INDEX")] pub mnemonic_index: u32, } + +impl RawWallet { + pub fn signer(&self) -> Result> { + if self.interactive { + return Ok(Some(PendingSigner::Interactive.unlock()?)); + } + if let Some(private_key) = &self.private_key { + return Ok(Some(utils::create_private_key_signer(private_key)?)) + } + if let Some(mnemonic) = &self.mnemonic { + return Ok(Some(utils::create_mnemonic_signer( + mnemonic, + self.mnemonic_passphrase.as_deref(), + self.hd_path.as_deref(), + self.mnemonic_index, + )?)) + } + Ok(None) + } +} diff --git a/crates/wallets/src/utils.rs b/crates/wallets/src/utils.rs new file mode 100644 index 000000000000..8c805c5cc652 --- /dev/null +++ b/crates/wallets/src/utils.rs @@ -0,0 +1,145 @@ +use ethers_signers::{HDPath as LedgerHDPath, LocalWallet, TrezorHDPath, WalletError}; +use foundry_config::Config; +use std::{ + fs, + path::{Path, PathBuf}, + str::FromStr, +}; + +use eyre::{Context, Result}; + +use crate::{error::PrivateKeyError, PendingSigner, WalletSigner}; + +pub fn create_private_key_signer(private_key: &str) -> Result { + let privk = private_key.trim().strip_prefix("0x").unwrap_or(private_key); + match LocalWallet::from_str(privk) { + Ok(pk) => Ok(WalletSigner::Local(pk)), + Err(err) => { + // helper closure to check if pk was meant to be an env var, this usually happens if + // `$` is missing + let ensure_not_env = |pk: &str| { + // check if pk was meant to be an env var + if !pk.starts_with("0x") && std::env::var(pk).is_ok() { + // SAFETY: at this point we know the user actually wanted to use an env var + // and most likely forgot the `$` anchor, so the + // `private_key` here is an unresolved env var + return Err(PrivateKeyError::ExistsAsEnvVar(pk.to_string())) + } + Ok(()) + }; + match err { + WalletError::HexError(err) => { + ensure_not_env(private_key)?; + return Err(PrivateKeyError::InvalidHex(err).into()); + } + WalletError::EcdsaError(_) => ensure_not_env(private_key)?, + _ => {} + }; + eyre::bail!("Failed to create wallet from private key: {err}") + } + } +} + +pub fn create_mnemonic_signer( + mnemonic: &str, + passphrase: Option<&str>, + hd_path: Option<&str>, + index: u32, +) -> Result { + let mnemonic = if Path::new(mnemonic).is_file() { + fs::read_to_string(mnemonic)?.replace('\n', "") + } else { + mnemonic.to_owned() + }; + + Ok(WalletSigner::from_mnemonic(&mnemonic, passphrase, hd_path, index)?) +} + +pub async fn create_ledger_signer( + hd_path: Option<&str>, + mnemonic_index: u32, +) -> Result { + let derivation = if let Some(hd_path) = hd_path { + LedgerHDPath::Other(hd_path.to_owned()) + } else { + LedgerHDPath::LedgerLive(mnemonic_index as usize) + }; + + WalletSigner::from_ledger_path(derivation).await.wrap_err_with(|| { + "\ +Could not connect to Ledger device. +Make sure it's connected and unlocked, with no other desktop wallet apps open." + }) +} + +pub async fn create_trezor_signer( + hd_path: Option<&str>, + mnemonic_index: u32, +) -> Result { + let derivation = if let Some(hd_path) = hd_path { + TrezorHDPath::Other(hd_path.to_owned()) + } else { + TrezorHDPath::TrezorLive(mnemonic_index as usize) + }; + + WalletSigner::from_trezor_path(derivation).await.wrap_err_with(|| { + "\ +Could not connect to Trezor device. +Make sure it's connected and unlocked, with no other conflicting desktop wallet apps open." + }) +} + +pub fn maybe_get_keystore_path( + maybe_path: Option<&str>, + maybe_name: Option<&str>, +) -> Result> { + let default_keystore_dir = Config::foundry_keystores_dir() + .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; + Ok(maybe_path + .map(PathBuf::from) + .or_else(|| maybe_name.map(|name| default_keystore_dir.join(name)))) +} + +pub fn create_keystore_signer( + path: &PathBuf, + maybe_password: Option<&str>, + maybe_password_file: Option<&str>, +) -> Result<(Option, Option)> { + if !path.exists() { + eyre::bail!("Keystore file `{path:?}` does not exist") + } + + if path.is_dir() { + eyre::bail!( + "Keystore path `{path:?}` is a directory. Please specify the keystore file directly." + ) + } + + let password = match (maybe_password, maybe_password_file) { + (Some(password), _) => Ok(Some(password.to_string())), + (_, Some(password_file)) => { + let password_file = Path::new(password_file); + if !password_file.is_file() { + Err(eyre::eyre!("Keystore password file `{password_file:?}` does not exist")) + } else { + Ok(Some( + fs::read_to_string(password_file) + .wrap_err_with(|| { + format!("Failed to read keystore password file at {password_file:?}") + })? + .trim_end() + .to_string(), + )) + } + } + (None, None) => Ok(None), + }?; + + if let Some(password) = password { + let wallet = LocalWallet::decrypt_keystore(path, password) + .wrap_err_with(|| format!("Failed to decrypt keystore {path:?}"))?; + Ok((Some(WalletSigner::Local(wallet)), None)) + } else { + Ok((None, Some(PendingSigner::Keystore(path.clone())))) + } +} diff --git a/crates/wallets/src/wallet.rs b/crates/wallets/src/wallet.rs index 1f40b980fe74..a3873206a788 100644 --- a/crates/wallets/src/wallet.rs +++ b/crates/wallets/src/wallet.rs @@ -1,31 +1,13 @@ -use crate::{ - error::{PrivateKeyError, WalletSignerError}, - raw_wallet::RawWallet, -}; +use crate::{raw_wallet::RawWallet, utils, wallet_signer::WalletSigner}; use alloy_primitives::Address; -use async_trait::async_trait; + use clap::Parser; -use ethers_core::types::{ - transaction::{eip2718::TypedTransaction, eip712::Eip712}, - Signature, -}; -use ethers_signers::{ - coins_bip39::English, AwsSigner, HDPath as LedgerHDPath, Ledger, LocalWallet, MnemonicBuilder, - Signer, Trezor, TrezorHDPath, WalletError, -}; -use eyre::{bail, Result, WrapErr}; -use foundry_common::{fs, types::ToAlloy}; -use foundry_config::Config; -use rusoto_core::{ - credential::ChainProvider as AwsChainProvider, region::Region as AwsRegion, - request::HttpClient as AwsHttpClient, Client as AwsClient, -}; -use rusoto_kms::KmsClient; -use serde::{Deserialize, Serialize}; -use std::{ - path::{Path, PathBuf}, - str::FromStr, -}; + +use ethers_signers::Signer; +use eyre::Result; +use foundry_common::types::ToAlloy; + +use serde::Serialize; /// The wallet options can either be: /// 1. Raw (via private key / mnemonic file, see `RawWallet`) @@ -105,117 +87,39 @@ pub struct Wallet { } impl Wallet { - pub fn interactive(&self) -> Result> { - Ok(if self.raw.interactive { Some(self.get_from_interactive()?) } else { None }) - } - - pub fn private_key(&self) -> Result> { - Ok(if let Some(ref private_key) = self.raw.private_key { - Some(self.get_from_private_key(private_key)?) - } else { - None - }) - } - - pub fn keystore(&self) -> Result> { - let default_keystore_dir = Config::foundry_keystores_dir() - .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; - // If keystore path is provided, use it, otherwise use default path + keystore account name - let keystore_path: Option = self.keystore_path.clone().or_else(|| { - self.keystore_account_name.as_ref().map(|keystore_name| { - default_keystore_dir.join(keystore_name).to_string_lossy().into_owned() - }) - }); - - self.get_from_keystore( - keystore_path.as_ref(), - self.keystore_password.as_ref(), - self.keystore_password_file.as_ref(), - ) - } - - pub fn mnemonic(&self) -> Result> { - Ok(if let Some(ref mnemonic) = self.raw.mnemonic { - Some(self.get_from_mnemonic( - mnemonic, - self.raw.mnemonic_passphrase.as_ref(), - self.raw.hd_path.as_ref(), - self.raw.mnemonic_index, - )?) - } else { - None - }) - } - - /// Returns the sender address of the signer or `from`. - pub async fn sender(&self) -> Address { - if let Ok(signer) = self.signer(0).await { - signer.address().to_alloy() - } else { - self.from.unwrap_or(Address::ZERO) - } - } - - /// Tries to resolve a local wallet from the provided options. - #[track_caller] - pub fn try_resolve_local_wallet(&self) -> Result> { - self.private_key() - .transpose() - .or_else(|| self.interactive().transpose()) - .or_else(|| self.mnemonic().transpose()) - .or_else(|| self.keystore().transpose()) - .transpose() - } - /// Returns a [Signer] corresponding to the provided private key, mnemonic or hardware signer. - #[instrument(skip(self), level = "trace")] - pub async fn signer(&self, chain_id: u64) -> Result { + pub async fn signer(&self) -> Result { trace!("start finding signer"); - if self.ledger { - let derivation = match self.raw.hd_path.as_ref() { - Some(hd_path) => LedgerHDPath::Other(hd_path.clone()), - None => LedgerHDPath::LedgerLive(self.raw.mnemonic_index as usize), - }; - let ledger = Ledger::new(derivation, chain_id).await.wrap_err_with(|| { - "\ -Could not connect to Ledger device. -Make sure it's connected and unlocked, with no other desktop wallet apps open." - })?; - - Ok(WalletSigner::Ledger(ledger)) + let signer = if self.ledger { + utils::create_ledger_signer(self.raw.hd_path.as_deref(), self.raw.mnemonic_index) + .await? } else if self.trezor { - let derivation = match self.raw.hd_path.as_ref() { - Some(hd_path) => TrezorHDPath::Other(hd_path.clone()), - None => TrezorHDPath::TrezorLive(self.raw.mnemonic_index as usize), - }; - - // cached to ~/.ethers-rs/trezor/cache/trezor.session - let trezor = Trezor::new(derivation, chain_id, None).await.wrap_err_with(|| { - "\ -Could not connect to Trezor device. -Make sure it's connected and unlocked, with no other conflicting desktop wallet apps open." - })?; - - Ok(WalletSigner::Trezor(trezor)) + utils::create_trezor_signer(self.raw.hd_path.as_deref(), self.raw.mnemonic_index) + .await? } else if self.aws { - let client = - AwsClient::new_with(AwsChainProvider::default(), AwsHttpClient::new().unwrap()); - - let kms = KmsClient::new_with_client(client, AwsRegion::default()); - let key_id = std::env::var("AWS_KMS_KEY_ID")?; - - let aws_signer = AwsSigner::new(kms, key_id, chain_id).await?; - - Ok(WalletSigner::Aws(aws_signer)) + WalletSigner::from_aws(&key_id).await? + } else if let Some(raw_wallet) = self.raw.signer()? { + raw_wallet + } else if let Some(path) = utils::maybe_get_keystore_path( + self.keystore_path.as_deref(), + self.keystore_account_name.as_deref(), + )? { + let (maybe_signer, maybe_pending) = utils::create_keystore_signer( + &path, + self.keystore_password.as_deref(), + self.keystore_password_file.as_deref(), + )?; + if let Some(pending) = maybe_pending { + pending.unlock()? + } else if let Some(signer) = maybe_signer { + signer + } else { + unreachable!() + } } else { - trace!("finding local key"); - - let maybe_local = self.try_resolve_local_wallet()?; - - let local = maybe_local.ok_or_else(|| { - eyre::eyre!( - "\ + eyre::bail!( + "\ Error accessing local wallet. Did you set a private key, mnemonic or keystore? Run `cast send --help` or `forge create --help` and use the corresponding CLI flag to set your key via: @@ -223,142 +127,19 @@ flag to set your key via: Alternatively, if you're using a local node with unlocked accounts, use the --unlocked flag and either set the `ETH_FROM` environment variable to the address of the unlocked account you want to use, or provide the --from flag with the address directly." - ) - })?; - - Ok(WalletSigner::Local(local.with_chain_id(chain_id))) - } - } -} - -pub trait WalletTrait { - /// Returns the configured sender. - fn sender(&self) -> Option
; - - fn get_from_interactive(&self) -> Result { - let private_key = rpassword::prompt_password("Enter private key: ")?; - let private_key = private_key.strip_prefix("0x").unwrap_or(&private_key); - Ok(LocalWallet::from_str(private_key)?) - } - - #[track_caller] - fn get_from_private_key(&self, private_key: &str) -> Result { - let privk = private_key.trim().strip_prefix("0x").unwrap_or(private_key); - match LocalWallet::from_str(privk) { - Ok(pk) => Ok(pk), - Err(err) => { - // helper closure to check if pk was meant to be an env var, this usually happens if - // `$` is missing - let ensure_not_env = |pk: &str| { - // check if pk was meant to be an env var - if !pk.starts_with("0x") && std::env::var(pk).is_ok() { - // SAFETY: at this point we know the user actually wanted to use an env var - // and most likely forgot the `$` anchor, so the - // `private_key` here is an unresolved env var - return Err(PrivateKeyError::ExistsAsEnvVar(pk.to_string())) - } - Ok(()) - }; - match err { - WalletError::HexError(err) => { - ensure_not_env(private_key)?; - return Err(PrivateKeyError::InvalidHex(err).into()) - } - WalletError::EcdsaError(_) => { - ensure_not_env(private_key)?; - } - _ => {} - }; - bail!("Failed to create wallet from private key: {err}") - } - } - } - - fn get_from_mnemonic( - &self, - mnemonic: &String, - passphrase: Option<&String>, - derivation_path: Option<&String>, - index: u32, - ) -> Result { - let mnemonic = if Path::new(mnemonic).is_file() { - fs::read_to_string(mnemonic)?.replace('\n', "") - } else { - mnemonic.to_owned() - }; - let builder = MnemonicBuilder::::default().phrase(mnemonic.as_str()); - let builder = if let Some(passphrase) = passphrase { - builder.password(passphrase.as_str()) - } else { - builder + ) }; - let builder = if let Some(hd_path) = derivation_path { - builder.derivation_path(hd_path.as_str())? - } else { - builder.index(index)? - }; - Ok(builder.build()?) - } - /// Ensures the path to the keystore exists. - /// - /// if the path is a directory, it bails and asks the user to specify the keystore file - /// directly. - fn find_keystore_file(&self, path: impl AsRef) -> Result { - let path = path.as_ref(); - if !path.exists() { - bail!("Keystore file `{path:?}` does not exist") - } - - if path.is_dir() { - bail!("Keystore path `{path:?}` is a directory. Please specify the keystore file directly.") - } - - Ok(path.to_path_buf()) - } - - fn get_from_keystore( - &self, - keystore_path: Option<&String>, - keystore_password: Option<&String>, - keystore_password_file: Option<&String>, - ) -> Result> { - Ok(match (keystore_path, keystore_password, keystore_password_file) { - // Path and password provided - (Some(path), Some(password), _) => { - let path = self.find_keystore_file(path)?; - Some( - LocalWallet::decrypt_keystore(&path, password) - .wrap_err_with(|| format!("Failed to decrypt keystore {path:?}"))?, - ) - } - // Path and password file provided - (Some(path), _, Some(password_file)) => { - let path = self.find_keystore_file(path)?; - Some( - LocalWallet::decrypt_keystore(&path, self.password_from_file(password_file)?) - .wrap_err_with(|| format!("Failed to decrypt keystore {path:?} with password file {password_file:?}"))?, - ) - } - // Only Path provided -> interactive - (Some(path), None, None) => { - let path = self.find_keystore_file(path)?; - let password = rpassword::prompt_password("Enter keystore password:")?; - Some(LocalWallet::decrypt_keystore(path, password)?) - } - // Nothing provided - (None, _, _) => None, - }) + Ok(signer) } - /// Attempts to read the keystore password from the password file. - fn password_from_file(&self, password_file: impl AsRef) -> Result { - let password_file = password_file.as_ref(); - if !password_file.is_file() { - bail!("Keystore password file `{password_file:?}` does not exist") + /// Returns the sender address of the signer or `from`. + pub async fn sender(&self) -> Address { + if let Ok(signer) = self.signer().await { + signer.address().to_alloy() + } else { + self.from.unwrap_or(Address::ZERO) } - - Ok(fs::read_to_string(password_file)?.trim_end().to_string()) } } @@ -368,158 +149,37 @@ impl From for Wallet { } } -impl WalletTrait for Wallet { - fn sender(&self) -> Option
{ - self.from - } -} - -#[derive(Debug)] -pub enum WalletSigner { - Local(LocalWallet), - Ledger(Ledger), - Trezor(Trezor), - Aws(AwsSigner), -} - -impl From for WalletSigner { - fn from(wallet: LocalWallet) -> Self { - Self::Local(wallet) - } -} - -impl From for WalletSigner { - fn from(hw: Ledger) -> Self { - Self::Ledger(hw) - } -} - -impl From for WalletSigner { - fn from(hw: Trezor) -> Self { - Self::Trezor(hw) - } -} - -impl From for WalletSigner { - fn from(wallet: AwsSigner) -> Self { - Self::Aws(wallet) - } -} - -macro_rules! delegate { - ($s:ident, $inner:ident => $e:expr) => { - match $s { - Self::Local($inner) => $e, - Self::Ledger($inner) => $e, - Self::Trezor($inner) => $e, - Self::Aws($inner) => $e, - } - }; -} - -#[async_trait] -impl Signer for WalletSigner { - type Error = WalletSignerError; - - async fn sign_message>( - &self, - message: S, - ) -> Result { - delegate!(self, inner => inner.sign_message(message).await.map_err(Into::into)) - } - - async fn sign_transaction(&self, message: &TypedTransaction) -> Result { - delegate!(self, inner => inner.sign_transaction(message).await.map_err(Into::into)) - } - - async fn sign_typed_data( - &self, - payload: &T, - ) -> Result { - delegate!(self, inner => inner.sign_typed_data(payload).await.map_err(Into::into)) - } - - fn address(&self) -> ethers_core::types::Address { - delegate!(self, inner => inner.address()) - } - - fn chain_id(&self) -> u64 { - delegate!(self, inner => inner.chain_id()) - } - - fn with_chain_id>(self, chain_id: T) -> Self { - match self { - Self::Local(inner) => Self::Local(inner.with_chain_id(chain_id)), - Self::Ledger(inner) => Self::Ledger(inner.with_chain_id(chain_id)), - Self::Trezor(inner) => Self::Trezor(inner.with_chain_id(chain_id)), - Self::Aws(inner) => Self::Aws(inner.with_chain_id(chain_id)), - } - } -} - -#[async_trait] -impl Signer for &WalletSigner { - type Error = WalletSignerError; - - async fn sign_message>( - &self, - message: S, - ) -> Result { - (*self).sign_message(message).await - } - - async fn sign_transaction(&self, message: &TypedTransaction) -> Result { - (*self).sign_transaction(message).await - } - - async fn sign_typed_data( - &self, - payload: &T, - ) -> Result { - (*self).sign_typed_data(payload).await - } - - fn address(&self) -> ethers_core::types::Address { - (*self).address() - } - - fn chain_id(&self) -> u64 { - (*self).chain_id() - } - - fn with_chain_id>(self, chain_id: T) -> Self { - let _ = chain_id; - self - } -} - -/// Excerpt of a keystore file. -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct KeystoreFile { - pub address: Address, -} - #[cfg(test)] mod tests { + use std::{path::Path, str::FromStr}; + use super::*; - #[test] - fn find_keystore() { + #[tokio::test] + async fn find_keystore() { let keystore = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../cast/tests/fixtures/keystore")); let keystore_file = keystore - .join("UTC--2022-10-30T06-51-20.130356000Z--560d246fcddc9ea98a8b032c9a2f474efb493c28"); + .join("UTC--2022-12-20T10-30-43.591916000Z--ec554aeafe75601aaab43bd4621a22284db566c2"); + let password_file = keystore.join("password-ec554"); let wallet: Wallet = Wallet::parse_from([ "foundry-cli", "--from", "560d246fcddc9ea98a8b032c9a2f474efb493c28", + "--keystore", + keystore_file.to_str().unwrap(), + "--password-file", + password_file.to_str().unwrap(), ]); - let file = wallet.find_keystore_file(&keystore_file).unwrap(); - assert_eq!(file, keystore_file); + let signer = wallet.signer().await.unwrap(); + assert_eq!( + signer.address().to_alloy(), + Address::from_str("ec554aeafe75601aaab43bd4621a22284db566c2").unwrap() + ); } - #[test] - fn illformed_private_key_generates_user_friendly_error() { + #[tokio::test] + async fn illformed_private_key_generates_user_friendly_error() { let wallet = Wallet { raw: RawWallet { interactive: false, @@ -538,7 +198,7 @@ mod tests { trezor: false, aws: false, }; - match wallet.private_key() { + match wallet.signer().await { Ok(_) => { panic!("illformed private key shouldn't decode") } @@ -550,12 +210,4 @@ mod tests { } } } - - #[test] - fn gets_password_from_file() { - let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../cast/tests/fixtures/keystore/password"); - let wallet: Wallet = Wallet::parse_from(["foundry-cli"]); - let password = wallet.password_from_file(path).unwrap(); - assert_eq!(password, "this is keystore password") - } } diff --git a/crates/wallets/src/wallet_signer.rs b/crates/wallets/src/wallet_signer.rs new file mode 100644 index 000000000000..cb99d69aa486 --- /dev/null +++ b/crates/wallets/src/wallet_signer.rs @@ -0,0 +1,174 @@ +use std::{path::PathBuf, str::FromStr}; + +use async_trait::async_trait; +use ethers_core::types::{ + transaction::{eip2718::TypedTransaction, eip712::Eip712}, + Signature, +}; +use ethers_signers::{ + coins_bip39::English, AwsSigner, HDPath as LedgerHDPath, Ledger, LocalWallet, MnemonicBuilder, + Signer, Trezor, TrezorHDPath, +}; +use rusoto_core::{ + credential::ChainProvider as AwsChainProvider, region::Region as AwsRegion, + request::HttpClient as AwsHttpClient, Client as AwsClient, +}; +use rusoto_kms::KmsClient; + +use crate::error::WalletSignerError; + +pub type Result = std::result::Result; + +#[derive(Debug)] +pub enum WalletSigner { + Local(LocalWallet), + Ledger(Ledger), + Trezor(Trezor), + Aws(AwsSigner), +} + +impl WalletSigner { + pub async fn from_ledger_path(path: LedgerHDPath) -> Result { + let ledger = Ledger::new(path, 1).await?; + Ok(Self::Ledger(ledger)) + } + + pub async fn from_trezor_path(path: TrezorHDPath) -> Result { + // cached to ~/.ethers-rs/trezor/cache/trezor.session + let trezor = Trezor::new(path, 1, None).await?; + Ok(Self::Trezor(trezor)) + } + + pub async fn from_aws(key_id: &str) -> Result { + let client = + AwsClient::new_with(AwsChainProvider::default(), AwsHttpClient::new().unwrap()); + + let kms = KmsClient::new_with_client(client, AwsRegion::default()); + + Ok(Self::Aws(AwsSigner::new(kms, key_id, 1).await?)) + } + + pub fn from_private_key(private_key: &str) -> Result { + let wallet = LocalWallet::from_str(private_key)?; + Ok(Self::Local(wallet)) + } + + pub fn from_mnemonic( + mnemonic: &str, + passphrase: Option<&str>, + derivation_path: Option<&str>, + index: u32, + ) -> Result { + let mut builder = MnemonicBuilder::::default().phrase(mnemonic); + + if let Some(passphrase) = passphrase { + builder = builder.password(passphrase) + } + + builder = if let Some(hd_path) = derivation_path { + builder.derivation_path(hd_path)? + } else { + builder.index(index)? + }; + + Ok(Self::Local(builder.build()?)) + } +} + +macro_rules! delegate { + ($s:ident, $inner:ident => $e:expr) => { + match $s { + Self::Local($inner) => $e, + Self::Ledger($inner) => $e, + Self::Trezor($inner) => $e, + Self::Aws($inner) => $e, + } + }; +} + +#[async_trait] +impl Signer for WalletSigner { + type Error = WalletSignerError; + + async fn sign_message>(&self, message: S) -> Result { + delegate!(self, inner => inner.sign_message(message).await.map_err(Into::into)) + } + + async fn sign_transaction(&self, message: &TypedTransaction) -> Result { + delegate!(self, inner => inner.sign_transaction(message).await.map_err(Into::into)) + } + + async fn sign_typed_data(&self, payload: &T) -> Result { + delegate!(self, inner => inner.sign_typed_data(payload).await.map_err(Into::into)) + } + + fn address(&self) -> ethers_core::types::Address { + delegate!(self, inner => inner.address()) + } + + fn chain_id(&self) -> u64 { + delegate!(self, inner => inner.chain_id()) + } + + fn with_chain_id>(self, chain_id: T) -> Self { + match self { + Self::Local(inner) => Self::Local(inner.with_chain_id(chain_id)), + Self::Ledger(inner) => Self::Ledger(inner.with_chain_id(chain_id)), + Self::Trezor(inner) => Self::Trezor(inner.with_chain_id(chain_id)), + Self::Aws(inner) => Self::Aws(inner.with_chain_id(chain_id)), + } + } +} + +#[async_trait] +impl Signer for &WalletSigner { + type Error = WalletSignerError; + + async fn sign_message>(&self, message: S) -> Result { + (*self).sign_message(message).await + } + + async fn sign_transaction(&self, message: &TypedTransaction) -> Result { + (*self).sign_transaction(message).await + } + + async fn sign_typed_data(&self, payload: &T) -> Result { + (*self).sign_typed_data(payload).await + } + + fn address(&self) -> ethers_core::types::Address { + (*self).address() + } + + fn chain_id(&self) -> u64 { + (*self).chain_id() + } + + fn with_chain_id>(self, chain_id: T) -> Self { + let _ = chain_id; + self + } +} + +/// Signers that require user action to be obtained. +#[derive(Debug, Clone)] +pub enum PendingSigner { + Keystore(PathBuf), + Interactive, +} + +impl PendingSigner { + pub fn unlock(self) -> Result { + match self { + Self::Keystore(path) => { + let password = rpassword::prompt_password("Enter keystore password:")?; + Ok(WalletSigner::Local(LocalWallet::decrypt_keystore(path, password)?)) + } + Self::Interactive => { + let private_key = rpassword::prompt_password("Enter private key:")?; + let private_key = private_key.strip_prefix("0x").unwrap_or(&private_key); + Ok(WalletSigner::from_private_key(private_key)?) + } + } + } +} From 7b3d91b7a4f1d61221841415292174b39447fae0 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 15 Feb 2024 17:12:41 +0400 Subject: [PATCH 02/18] Use MultiWallet in cheats --- Cargo.lock | 2 +- crates/cheatcodes/Cargo.toml | 1 + crates/cheatcodes/src/config.rs | 14 +++- crates/cheatcodes/src/error.rs | 2 + crates/cheatcodes/src/inspector.rs | 11 +-- crates/cheatcodes/src/lib.rs | 1 + crates/cheatcodes/src/script.rs | 27 ++++++- crates/cheatcodes/src/utils.rs | 10 ++- crates/chisel/src/executor.rs | 8 +- crates/evm/evm/Cargo.toml | 1 - crates/evm/evm/src/executors/mod.rs | 33 ++------- crates/evm/evm/src/inspectors/stack.rs | 10 +-- crates/forge/bin/cmd/coverage.rs | 2 +- crates/forge/bin/cmd/script/broadcast.rs | 22 +++--- crates/forge/bin/cmd/script/cmd.rs | 93 +++++++++++++++--------- crates/forge/bin/cmd/script/executor.rs | 16 +++- crates/forge/bin/cmd/script/mod.rs | 11 ++- crates/forge/bin/cmd/script/multi.rs | 11 +-- crates/forge/bin/cmd/script/runner.rs | 31 +------- crates/forge/bin/cmd/test/mod.rs | 2 +- crates/forge/tests/it/config.rs | 2 +- crates/wallets/src/multi_wallet.rs | 8 +- 22 files changed, 172 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19e71396df8c..60153e2c9b54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2979,6 +2979,7 @@ dependencies = [ "foundry-compilers", "foundry-config", "foundry-evm-core", + "foundry-wallets", "itertools 0.11.0", "jsonpath_lib", "k256", @@ -3182,7 +3183,6 @@ dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-rpc-types", - "alloy-signer", "alloy-sol-types", "const-hex", "eyre", diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index 4c2e69a36483..c2b1794f8776 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -17,6 +17,7 @@ foundry-common.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true foundry-evm-core.workspace = true +foundry-wallets.workspace = true alloy-dyn-abi.workspace = true alloy-json-abi.workspace = true diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 85b2dcaab61f..969eb583959e 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -1,5 +1,5 @@ use super::Result; -use crate::Vm::Rpc; +use crate::{ScriptWalletsData, Vm::Rpc}; use alloy_primitives::Address; use foundry_common::fs::normalize_path; use foundry_compilers::{utils::canonicalize, ProjectPathsConfig}; @@ -11,6 +11,7 @@ use foundry_evm_core::opts::EvmOpts; use std::{ collections::HashMap, path::{Path, PathBuf}, + sync::{Arc, Mutex}, }; /// Additional, configurable context the `Cheatcodes` inspector has access to @@ -36,11 +37,17 @@ pub struct CheatsConfig { pub evm_opts: EvmOpts, /// Address labels from config pub labels: HashMap, + /// Script wallets + pub script_wallets: Option>>, } impl CheatsConfig { /// Extracts the necessary settings from the Config - pub fn new(config: &Config, evm_opts: EvmOpts) -> Self { + pub fn new( + config: &Config, + evm_opts: EvmOpts, + script_wallets: Option>>, + ) -> Self { let mut allowed_paths = vec![config.__root.0.clone()]; allowed_paths.extend(config.libs.clone()); allowed_paths.extend(config.allow_paths.clone()); @@ -58,6 +65,7 @@ impl CheatsConfig { allowed_paths, evm_opts, labels: config.labels.clone(), + script_wallets, } } @@ -172,6 +180,7 @@ impl Default for CheatsConfig { allowed_paths: vec![], evm_opts: Default::default(), labels: Default::default(), + script_wallets: None, } } } @@ -185,6 +194,7 @@ mod tests { CheatsConfig::new( &Config { __root: PathBuf::from(root).into(), fs_permissions, ..Default::default() }, Default::default(), + None, ) } diff --git a/crates/cheatcodes/src/error.rs b/crates/cheatcodes/src/error.rs index 19475982f3ed..66796026d567 100644 --- a/crates/cheatcodes/src/error.rs +++ b/crates/cheatcodes/src/error.rs @@ -5,6 +5,7 @@ use alloy_sol_types::SolError; use foundry_common::errors::FsPathError; use foundry_config::UnresolvedEnvVarError; use foundry_evm_core::backend::DatabaseError; +use foundry_wallets::error::WalletSignerError; use k256::ecdsa::signature::Error as SignatureError; use std::{borrow::Cow, fmt}; @@ -298,6 +299,7 @@ impl_from!( UnresolvedEnvVarError, WalletError, SignerError, + WalletSignerError, ); #[cfg(test)] diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 8290b9b951de..93957a3ac5d8 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -7,7 +7,7 @@ use crate::{ prank::Prank, DealRecord, RecordAccess, }, - script::Broadcast, + script::{Broadcast, ScriptWalletsData}, test::expect::{ self, ExpectedCallData, ExpectedCallTracker, ExpectedCallType, ExpectedEmit, ExpectedRevert, ExpectedRevertKind, @@ -16,7 +16,7 @@ use crate::{ }; use alloy_primitives::{Address, Bytes, B256, U256, U64}; use alloy_rpc_types::request::{TransactionInput, TransactionRequest}; -use alloy_signer::LocalWallet; + use alloy_sol_types::{SolInterface, SolValue}; use foundry_common::{evm::Breakpoints, provider::alloy::RpcUrl}; use foundry_evm_core::{ @@ -38,7 +38,7 @@ use std::{ io::BufReader, ops::Range, path::PathBuf, - sync::Arc, + sync::{Arc, Mutex}, }; macro_rules! try_or_continue { @@ -127,7 +127,7 @@ pub struct Cheatcodes { pub labels: HashMap, /// Remembered private keys - pub script_wallets: Vec, + pub script_wallets: Option>>, /// Prank information pub prank: Option, @@ -218,7 +218,8 @@ impl Cheatcodes { #[inline] pub fn new(config: Arc) -> Self { let labels = config.labels.clone(); - Self { config, fs_commit: true, labels, ..Default::default() } + let script_wallets = config.script_wallets.clone(); + Self { config, fs_commit: true, labels, script_wallets, ..Default::default() } } fn apply_cheatcode( diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index caaef1d8fee4..daeba3e55c8a 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -36,6 +36,7 @@ mod string; mod test; mod utils; +pub use script::ScriptWalletsData; pub use test::expect::ExpectedCallTracker; /// Cheatcode implementation. diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index 820d3ffd9a18..14ff4aca911b 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -4,6 +4,7 @@ use crate::{Cheatcode, CheatsCtxt, DatabaseExt, Result, Vm::*}; use alloy_primitives::{Address, U256}; use alloy_signer::Signer; use foundry_config::Config; +use foundry_wallets::{multi_wallet::MultiWallet, WalletSigner}; impl Cheatcode for broadcast_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { @@ -72,6 +73,12 @@ pub struct Broadcast { pub single_call: bool, } +#[derive(Debug)] +pub struct ScriptWalletsData { + pub multi_wallet: MultiWallet, + pub provided_sender: Option
, +} + /// Sets up broadcasting from a script using `new_origin` as the sender. fn broadcast( ccx: &mut CheatsCtxt, @@ -86,8 +93,21 @@ fn broadcast( correct_sender_nonce(ccx)?; + let mut new_origin = new_origin.cloned(); + + if new_origin.is_none() { + if let Some(script_wallets) = &ccx.state.script_wallets { + let mut script_wallets = script_wallets.lock().unwrap(); + let signers = script_wallets.multi_wallet.signers()?; + if signers.len() == 1 { + let address = signers.keys().next().unwrap(); + new_origin = Some(address.clone()); + } + } + } + let broadcast = Broadcast { - new_origin: *new_origin.unwrap_or(&ccx.data.env.tx.caller), + new_origin: new_origin.unwrap_or(ccx.data.env.tx.caller), original_caller: ccx.caller, original_origin: ccx.data.env.tx.caller, depth: ccx.data.journaled_state.depth(), @@ -112,7 +132,10 @@ fn broadcast_key( let result = broadcast(ccx, Some(new_origin), single_call); if result.is_ok() { - ccx.state.script_wallets.push(wallet); + let signer = WalletSigner::from_private_key(&private_key.to_string())?; + if let Some(script_wallets) = &ccx.state.script_wallets { + script_wallets.lock().unwrap().multi_wallet.add_signer(signer); + } } result } diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs index bb4726431188..8d042715380e 100644 --- a/crates/cheatcodes/src/utils.rs +++ b/crates/cheatcodes/src/utils.rs @@ -11,6 +11,7 @@ use alloy_signer::{ }; use alloy_sol_types::SolValue; use foundry_evm_core::constants::DEFAULT_CREATE2_DEPLOYER; +use foundry_wallets::WalletSigner; use k256::{ ecdsa::SigningKey, elliptic_curve::{sec1::ToEncodedPoint, Curve}, @@ -87,9 +88,12 @@ impl Cheatcode for deriveKey_3Call { impl Cheatcode for rememberKeyCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { privateKey } = self; - let wallet = parse_wallet(privateKey)?.with_chain_id(Some(ccx.data.env.cfg.chain_id)); - let address = wallet.address(); - ccx.state.script_wallets.push(wallet); + let key = parse_private_key(privateKey)?; + let address = LocalWallet::from(key.clone()).address(); + let signer = WalletSigner::from_private_key(&hex::encode(key.to_bytes()))?; + if let Some(script_wallets) = &ccx.state.script_wallets { + script_wallets.lock().unwrap().multi_wallet.add_signer(signer); + } Ok(address.abi_encode()) } } diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index f3dcb40f3781..7a9ecb33bc69 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -305,8 +305,12 @@ impl SessionSource { let executor = ExecutorBuilder::new() .inspectors(|stack| { stack.chisel_state(final_pc).trace(true).cheatcodes( - CheatsConfig::new(&self.config.foundry_config, self.config.evm_opts.clone()) - .into(), + CheatsConfig::new( + &self.config.foundry_config, + self.config.evm_opts.clone(), + None, + ) + .into(), ) }) .gas_limit(self.config.evm_opts.gas_limit()) diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index 9ea7dd0fa51e..33e491e113d7 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -25,7 +25,6 @@ alloy-json-abi.workspace = true alloy-primitives = { workspace = true, features = ["serde", "getrandom", "arbitrary", "rlp"] } alloy-sol-types.workspace = true alloy-rpc-types.workspace = true -alloy-signer.workspace = true hashbrown = { version = "0.14", features = ["serde"] } revm = { workspace = true, default-features = false, features = [ "std", diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index f3aae1330002..089c575b7c3e 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -12,7 +12,8 @@ use crate::inspectors::{ use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt}; use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Bytes, Log, U256}; -use alloy_signer::LocalWallet; + + use foundry_common::{abi::IntoFunction, evm::Breakpoints}; use foundry_evm_core::{ backend::{Backend, DatabaseError, DatabaseExt, DatabaseResult, FuzzBackendWrapper}, @@ -32,7 +33,9 @@ use revm::{ BlockEnv, Bytecode, Env, ExecutionResult, Output, ResultAndState, SpecId, TransactTo, TxEnv, }, }; -use std::collections::HashMap; +use std::{ + collections::HashMap, +}; mod builder; pub use builder::ExecutorBuilder; @@ -204,7 +207,6 @@ impl Executor { labels: res.labels, state_changeset: None, transactions: None, - script_wallets: res.script_wallets, }))) } } @@ -372,7 +374,6 @@ impl Executor { labels, traces, debug, - script_wallets, env, coverage, .. @@ -400,7 +401,6 @@ impl Executor { labels, state_changeset: None, transactions: None, - script_wallets }))); } } @@ -418,7 +418,6 @@ impl Executor { labels, state_changeset: None, transactions: None, - script_wallets, }))) } }; @@ -595,7 +594,6 @@ pub struct ExecutionErr { pub labels: HashMap, pub transactions: Option, pub state_changeset: Option, - pub script_wallets: Vec, } #[derive(Debug, thiserror::Error)] @@ -666,8 +664,6 @@ pub struct CallResult { /// This is only present if the changed state was not committed to the database (i.e. if you /// used `call` and `call_raw` not `call_committing` or `call_raw_committing`). pub state_changeset: Option, - /// The wallets added during the call using the `rememberKey` cheatcode - pub script_wallets: Vec, /// The `revm::Env` after the call pub env: Env, /// breakpoints @@ -711,8 +707,6 @@ pub struct RawCallResult { /// This is only present if the changed state was not committed to the database (i.e. if you /// used `call` and `call_raw` not `call_committing` or `call_raw_committing`). pub state_changeset: Option, - /// The wallets added during the call using the `rememberKey` cheatcode - pub script_wallets: Vec, /// The `revm::Env` after the call pub env: Env, /// The cheatcode states after execution @@ -740,7 +734,6 @@ impl Default for RawCallResult { debug: None, transactions: None, state_changeset: None, - script_wallets: Vec::new(), env: Default::default(), cheatcodes: Default::default(), out: None, @@ -782,16 +775,8 @@ fn convert_executed_result( _ => Bytes::new(), }; - let InspectorData { - logs, - labels, - traces, - coverage, - debug, - cheatcodes, - script_wallets, - chisel_state, - } = inspector.collect(); + let InspectorData { logs, labels, traces, coverage, debug, cheatcodes, chisel_state } = + inspector.collect(); let transactions = match cheatcodes.as_ref() { Some(cheats) if !cheats.broadcastable_transactions.is_empty() => { @@ -815,7 +800,6 @@ fn convert_executed_result( debug, transactions, state_changeset: Some(state_changeset), - script_wallets, env, cheatcodes, out, @@ -842,7 +826,6 @@ fn convert_call_result( debug, transactions, state_changeset, - script_wallets, env, .. } = call_result; @@ -875,7 +858,6 @@ fn convert_call_result( debug, transactions, state_changeset, - script_wallets, env, breakpoints, skipped: false, @@ -898,7 +880,6 @@ fn convert_call_result( labels, transactions, state_changeset, - script_wallets, }))) } } diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 6ece0f2da4ff..1d229c220f77 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -3,7 +3,8 @@ use super::{ StackSnapshotType, TracePrinter, TracingInspector, TracingInspectorConfig, }; use alloy_primitives::{Address, Bytes, Log, B256, U256}; -use alloy_signer::LocalWallet; + + use foundry_evm_core::{backend::DatabaseExt, debug::DebugArena}; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::CallTraceArena; @@ -189,7 +190,6 @@ pub struct InspectorData { pub debug: Option, pub coverage: Option, pub cheatcodes: Option, - pub script_wallets: Vec, pub chisel_state: Option<(Stack, Vec, InstructionResult)>, } @@ -316,12 +316,6 @@ impl InspectorStack { traces: self.tracer.map(|tracer| tracer.get_traces().clone()), debug: self.debugger.map(|debugger| debugger.arena), coverage: self.coverage.map(|coverage| coverage.maps), - #[allow(clippy::useless_asref)] // https://github.com/rust-lang/rust-clippy/issues/12135 - script_wallets: self - .cheatcodes - .as_ref() - .map(|cheatcodes| cheatcodes.script_wallets.clone()) - .unwrap_or_default(), cheatcodes: self.cheatcodes, chisel_state: self.chisel_state.and_then(|state| state.state), } diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index 771bd8bb0906..87acf67960c2 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -303,7 +303,7 @@ impl CoverageArgs { .evm_spec(config.evm_spec_id()) .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) - .with_cheats_config(CheatsConfig::new(&config, evm_opts.clone())) + .with_cheats_config(CheatsConfig::new(&config, evm_opts.clone(), None)) .with_test_options(TestOptions { fuzz: config.fuzz, invariant: config.invariant, diff --git a/crates/forge/bin/cmd/script/broadcast.rs b/crates/forge/bin/cmd/script/broadcast.rs index 31331a995284..10a2758c28ea 100644 --- a/crates/forge/bin/cmd/script/broadcast.rs +++ b/crates/forge/bin/cmd/script/broadcast.rs @@ -26,7 +26,7 @@ impl ScriptArgs { &self, deployment_sequence: &mut ScriptSequence, fork_url: &str, - script_wallets: &[LocalWallet], + signers: &HashMap, ) -> Result<()> { let provider = Arc::new(try_get_http_provider(fork_url)?); let already_broadcasted = deployment_sequence.receipts.len(); @@ -54,11 +54,6 @@ impl ScriptArgs { ); (SendTransactionsKind::Unlocked(senders), chain.as_u64()) } else { - let mut multi_wallet = self.wallets.get_multi_wallet().await?; - multi_wallet.add_signers(script_wallets.iter().cloned().map(WalletSigner::Local)); - - let signers = multi_wallet.into_signers()?; - let mut missing_addresses = Vec::new(); println!("\n###\nFinding wallets for all the necessary addresses..."); @@ -297,6 +292,7 @@ impl ScriptArgs { decoder: &CallTraceDecoder, mut script_config: ScriptConfig, verify: VerifyBundle, + signers: HashMap, ) -> Result<()> { if let Some(txs) = result.transactions.take() { script_config.collect_rpcs(&txs); @@ -332,8 +328,8 @@ impl ScriptArgs { multi, libraries, &script_config.config, - result.script_wallets, verify, + signers, ) .await?; } @@ -342,8 +338,8 @@ impl ScriptArgs { deployments.first_mut().expect("to be set."), script_config, libraries, - result, verify, + signers, ) .await?; } @@ -364,8 +360,8 @@ impl ScriptArgs { deployment_sequence: &mut ScriptSequence, script_config: ScriptConfig, libraries: Libraries, - result: ScriptResult, verify: VerifyBundle, + signers: HashMap, ) -> Result<()> { trace!(target: "script", "broadcasting single chain deployment"); @@ -377,7 +373,7 @@ impl ScriptArgs { deployment_sequence.add_libraries(libraries); - self.send_transactions(deployment_sequence, &rpc, &result.script_wallets).await?; + self.send_transactions(deployment_sequence, &rpc, &signers).await?; if self.verify { return deployment_sequence.verify_contracts(&script_config.config, verify).await; @@ -656,14 +652,14 @@ enum SendTransactionKind<'a> { } /// Represents how to send _all_ transactions -enum SendTransactionsKind { +enum SendTransactionsKind<'a> { /// Send via `eth_sendTransaction` and rely on the `from` address being unlocked. Unlocked(HashSet
), /// Send a signed transaction via `eth_sendRawTransaction` - Raw(HashMap), + Raw(&'a HashMap), } -impl SendTransactionsKind { +impl SendTransactionsKind<'_> { /// Returns the [`SendTransactionKind`] for the given address /// /// Returns an error if no matching signer is found or the address is not unlocked diff --git a/crates/forge/bin/cmd/script/cmd.rs b/crates/forge/bin/cmd/script/cmd.rs index ebb9f97aa592..2bfc1662abcc 100644 --- a/crates/forge/bin/cmd/script/cmd.rs +++ b/crates/forge/bin/cmd/script/cmd.rs @@ -2,14 +2,14 @@ use super::{multi::MultiChainSequence, sequence::ScriptSequence, verify::VerifyB use alloy_primitives::Bytes; use ethers_providers::Middleware; -use ethers_signers::Signer; use eyre::{OptionExt, Result}; use forge::link::Linker; use foundry_cli::utils::LoadConfig; use foundry_common::{ - contracts::flatten_contracts, provider::ethers::try_get_http_provider, types::ToAlloy, + contracts::flatten_contracts, provider::ethers::try_get_http_provider, }; use foundry_debugger::Debugger; +use foundry_wallets::WalletSigner; use std::sync::Arc; /// Helper alias type for the collection of data changed due to the new sender. @@ -30,8 +30,6 @@ impl ScriptArgs { ..Default::default() }; - self.maybe_load_private_key(&mut script_config)?; - if let Some(ref fork_url) = script_config.evm_opts.fork_url { // when forking, override the sender's nonce to the onchain value script_config.sender_nonce = @@ -64,13 +62,31 @@ impl ScriptArgs { // Execute once with default sender. let sender = script_config.evm_opts.sender; + let multi_wallet = self.wallets.get_multi_wallet().await?; + let script_wallets = + ScriptWalletsData { multi_wallet, provided_sender: self.evm_opts.sender }; + let script_wallets = Arc::new(Mutex::new(script_wallets)); + // We need to execute the script even if just resuming, in case we need to collect private // keys from the execution. - let mut result = - self.execute(&mut script_config, contract, sender, &predeploy_libraries).await?; + let mut result = self + .execute( + &mut script_config, + contract, + sender, + &predeploy_libraries, + script_wallets.clone(), + ) + .await?; if self.resume || (self.verify && !self.broadcast) { - return self.resume_deployment(script_config, linker, libraries, result, verify).await; + let signers = Arc::try_unwrap(script_wallets) + .unwrap() + .into_inner() + .unwrap() + .multi_wallet + .into_signers()?; + return self.resume_deployment(script_config, linker, libraries, verify, signers).await; } let known_contracts = flatten_contracts(&highlevel_known_contracts, true); @@ -87,7 +103,13 @@ impl ScriptArgs { } if let Some((new_traces, updated_libraries, updated_contracts)) = self - .maybe_prepare_libraries(&mut script_config, linker, predeploy_libraries, &mut result) + .maybe_prepare_libraries( + &mut script_config, + linker, + predeploy_libraries, + &mut result, + script_wallets.clone(), + ) .await? { decoder = new_traces; @@ -104,8 +126,22 @@ impl ScriptArgs { verify.known_contracts = flatten_contracts(&highlevel_known_contracts, false); self.check_contract_sizes(&result, &highlevel_known_contracts)?; - self.handle_broadcastable_transactions(result, libraries, &decoder, script_config, verify) - .await + let signers = Arc::try_unwrap(script_wallets) + .unwrap() + .into_inner() + .unwrap() + .multi_wallet + .into_signers()?; + + self.handle_broadcastable_transactions( + result, + libraries, + &decoder, + script_config, + verify, + signers, + ) + .await } // In case there are libraries to be deployed, it makes sure that these are added to the list of @@ -116,6 +152,7 @@ impl ScriptArgs { linker: Linker, predeploy_libraries: Vec, result: &mut ScriptResult, + script_wallets: Arc>, ) -> Result> { if let Some(new_sender) = self.maybe_new_sender( &script_config.evm_opts, @@ -123,8 +160,9 @@ impl ScriptArgs { &predeploy_libraries, )? { // We have a new sender, so we need to relink all the predeployed libraries. - let (libraries, highlevel_known_contracts) = - self.rerun_with_new_deployer(script_config, new_sender, result, linker).await?; + let (libraries, highlevel_known_contracts) = self + .rerun_with_new_deployer(script_config, new_sender, result, linker, script_wallets) + .await?; // redo traces for the new addresses let new_traces = self.decode_traces( @@ -163,8 +201,8 @@ impl ScriptArgs { script_config: ScriptConfig, linker: Linker, libraries: Libraries, - result: ScriptResult, verify: VerifyBundle, + signers: HashMap, ) -> Result<()> { if self.multi { return self @@ -176,16 +214,16 @@ impl ScriptArgs { )?, libraries, &script_config.config, - result.script_wallets, verify, + signers, ) .await; } self.resume_single_deployment( script_config, linker, - result, verify, + signers, ) .await .map_err(|err| { @@ -198,8 +236,8 @@ impl ScriptArgs { &mut self, script_config: ScriptConfig, linker: Linker, - result: ScriptResult, mut verify: VerifyBundle, + signers: HashMap, ) -> Result<()> { trace!(target: "script", "resuming single deployment"); @@ -241,8 +279,7 @@ impl ScriptArgs { receipts::wait_for_pending(provider, &mut deployment_sequence).await?; if self.resume { - self.send_transactions(&mut deployment_sequence, fork_url, &result.script_wallets) - .await?; + self.send_transactions(&mut deployment_sequence, fork_url, &signers).await?; } if self.verify { @@ -279,6 +316,7 @@ impl ScriptArgs { new_sender: Address, first_run_result: &mut ScriptResult, linker: Linker, + script_wallets: Arc>, ) -> Result<(Libraries, ArtifactContracts)> { // if we had a new sender that requires relinking, we need to // get the nonce mainnet for accurate addresses for predeploy libs @@ -310,8 +348,9 @@ impl ScriptArgs { &script_config.evm_opts.fork_url, ); - let result = - self.execute(script_config, contract, new_sender, &predeploy_libraries).await?; + let result = self + .execute(script_config, contract, new_sender, &predeploy_libraries, script_wallets) + .await?; if let Some(new_txs) = &result.transactions { for new_tx in new_txs.iter() { @@ -327,18 +366,4 @@ impl ScriptArgs { Ok((libraries, highlevel_known_contracts)) } - - /// In case the user has loaded *only* one private-key, we can assume that he's using it as the - /// `--sender` - fn maybe_load_private_key(&mut self, script_config: &mut ScriptConfig) -> Result<()> { - if let Some(ref private_key) = self.wallets.private_key { - self.wallets.private_keys = Some(vec![private_key.clone()]); - } - if let Some(wallets) = self.wallets.private_keys()? { - if wallets.len() == 1 { - script_config.evm_opts.sender = wallets.first().unwrap().address().to_alloy() - } - } - Ok(()) - } } diff --git a/crates/forge/bin/cmd/script/executor.rs b/crates/forge/bin/cmd/script/executor.rs index 588af4c10d1e..0ebe26bae937 100644 --- a/crates/forge/bin/cmd/script/executor.rs +++ b/crates/forge/bin/cmd/script/executor.rs @@ -27,6 +27,7 @@ impl ScriptArgs { contract: ContractBytecodeSome, sender: Address, predeploy_libraries: &[Bytes], + script_wallets: Arc>, ) -> Result { trace!(target: "script", "start executing script"); @@ -38,7 +39,9 @@ impl ScriptArgs { ensure_clean_constructor(&abi)?; - let mut runner = self.prepare_runner(script_config, sender, SimulationStage::Local).await?; + let mut runner = self + .prepare_runner(script_config, sender, SimulationStage::Local, Some(script_wallets)) + .await?; let (address, mut result) = runner.setup( predeploy_libraries, bytecode, @@ -62,7 +65,6 @@ impl ScriptArgs { result.debug = script_result.debug; result.labeled_addresses.extend(script_result.labeled_addresses); result.returned = script_result.returned; - result.script_wallets.extend(script_result.script_wallets); result.breakpoints = script_result.breakpoints; match (&mut result.transactions, script_result.transactions) { @@ -248,7 +250,7 @@ impl ScriptArgs { let mut script_config = script_config.clone(); script_config.evm_opts.fork_url = Some(rpc.clone()); let runner = self - .prepare_runner(&mut script_config, sender, SimulationStage::OnChain) + .prepare_runner(&mut script_config, sender, SimulationStage::OnChain, None) .await?; Ok((rpc.clone(), runner)) }) @@ -263,6 +265,7 @@ impl ScriptArgs { script_config: &mut ScriptConfig, sender: Address, stage: SimulationStage, + script_wallets: Option>>, ) -> Result { trace!("preparing script runner"); let env = script_config.evm_opts.evm_env().await?; @@ -296,7 +299,12 @@ impl ScriptArgs { if let SimulationStage::Local = stage { builder = builder.inspectors(|stack| { stack.debug(self.debug).cheatcodes( - CheatsConfig::new(&script_config.config, script_config.evm_opts.clone()).into(), + CheatsConfig::new( + &script_config.config, + script_config.evm_opts.clone(), + script_wallets, + ) + .into(), ) }); } diff --git a/crates/forge/bin/cmd/script/mod.rs b/crates/forge/bin/cmd/script/mod.rs index d6fcd3836aa4..697f18f24dcf 100644 --- a/crates/forge/bin/cmd/script/mod.rs +++ b/crates/forge/bin/cmd/script/mod.rs @@ -7,7 +7,6 @@ use alloy_rpc_types::request::TransactionRequest; use clap::{Parser, ValueHint}; use dialoguer::Confirm; use ethers_providers::{Http, Middleware}; -use ethers_signers::LocalWallet; use eyre::{ContextCompat, Result, WrapErr}; use forge::{ backend::Backend, @@ -44,13 +43,18 @@ use foundry_config::{ use foundry_evm::{ constants::DEFAULT_CREATE2_DEPLOYER, decode, - inspectors::cheatcodes::{BroadcastableTransaction, BroadcastableTransactions}, + inspectors::cheatcodes::{ + BroadcastableTransaction, BroadcastableTransactions, ScriptWalletsData, + }, }; use foundry_wallets::MultiWalletOpts; use futures::future; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; +use std::{ + collections::{BTreeMap, HashMap, HashSet, VecDeque}, + sync::{Mutex}, +}; use yansi::Paint; mod artifacts; @@ -596,7 +600,6 @@ pub struct ScriptResult { pub transactions: Option, pub returned: Bytes, pub address: Option
, - pub script_wallets: Vec, pub breakpoints: Breakpoints, } diff --git a/crates/forge/bin/cmd/script/multi.rs b/crates/forge/bin/cmd/script/multi.rs index 9fd3427949b2..72d625c97a88 100644 --- a/crates/forge/bin/cmd/script/multi.rs +++ b/crates/forge/bin/cmd/script/multi.rs @@ -4,18 +4,19 @@ use super::{ verify::VerifyBundle, ScriptArgs, }; -use ethers_signers::LocalWallet; +use alloy_primitives::Address; use eyre::{ContextCompat, Report, Result, WrapErr}; use foundry_cli::utils::now; use foundry_common::{fs, provider::ethers::get_http_provider}; use foundry_compilers::{artifacts::Libraries, ArtifactId}; use foundry_config::Config; +use foundry_wallets::WalletSigner; use futures::future::join_all; use serde::{Deserialize, Serialize}; use std::{ + collections::HashMap, io::{BufWriter, Write}, - path::{Path, PathBuf}, - sync::Arc, + path::{Path, PathBuf}, sync::Arc, }; /// Holds the sequences of multiple chain deployments. @@ -179,8 +180,8 @@ impl ScriptArgs { mut deployments: MultiChainSequence, libraries: Libraries, config: &Config, - script_wallets: Vec, verify: VerifyBundle, + signers: HashMap, ) -> Result<()> { if !libraries.is_empty() { eyre::bail!("Libraries are currently not supported on multi deployment setups."); @@ -223,7 +224,7 @@ impl ScriptArgs { .send_transactions( sequence, &sequence.typed_transactions().first().unwrap().0.clone(), - &script_wallets, + &signers, ) .await { diff --git a/crates/forge/bin/cmd/script/runner.rs b/crates/forge/bin/cmd/script/runner.rs index 6afb1a4441b0..048133df0a67 100644 --- a/crates/forge/bin/cmd/script/runner.rs +++ b/crates/forge/bin/cmd/script/runner.rs @@ -7,7 +7,6 @@ use forge::{ revm::interpreter::{return_ok, InstructionResult}, traces::{TraceKind, Traces}, }; -use foundry_common::types::ToEthers; /// Represents which simulation stage is the script execution at. pub enum SimulationStage { @@ -91,17 +90,9 @@ impl ScriptRunner { traces.extend(constructor_traces.map(|traces| (TraceKind::Deployment, traces))); // Optionally call the `setUp` function - let (success, gas_used, labeled_addresses, transactions, debug, script_wallets) = if !setup - { + let (success, gas_used, labeled_addresses, transactions, debug) = if !setup { self.executor.backend.set_test_contract(address); - ( - true, - 0, - Default::default(), - None, - vec![constructor_debug].into_iter().collect(), - vec![], - ) + (true, 0, Default::default(), None, vec![constructor_debug].into_iter().collect()) } else { match self.executor.setup(Some(self.sender), address) { Ok(CallResult { @@ -112,7 +103,6 @@ impl ScriptRunner { debug, gas_used, transactions, - script_wallets, .. }) => { traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces))); @@ -126,7 +116,6 @@ impl ScriptRunner { labels, transactions, vec![constructor_debug, debug].into_iter().collect(), - script_wallets, ) } Err(EvmError::Execution(err)) => { @@ -138,7 +127,6 @@ impl ScriptRunner { debug, gas_used, transactions, - script_wallets, .. } = *err; traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces))); @@ -152,7 +140,6 @@ impl ScriptRunner { labels, transactions, vec![constructor_debug, debug].into_iter().collect(), - script_wallets, ) } Err(e) => return Err(e.into()), @@ -171,7 +158,6 @@ impl ScriptRunner { traces, debug, address: None, - script_wallets: script_wallets.to_ethers(), ..Default::default() }, )) @@ -277,17 +263,7 @@ impl ScriptRunner { res = self.executor.call_raw_committing(from, to, calldata, value)?; } - let RawCallResult { - result, - reverted, - logs, - traces, - labels, - debug, - transactions, - script_wallets, - .. - } = res; + let RawCallResult { result, reverted, logs, traces, labels, debug, transactions, .. } = res; let breakpoints = res.cheatcodes.map(|cheats| cheats.breakpoints).unwrap_or_default(); Ok(ScriptResult { @@ -306,7 +282,6 @@ impl ScriptRunner { labeled_addresses: labels, transactions, address: None, - script_wallets: script_wallets.to_ethers(), breakpoints, }) } diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 1a8cf9eed418..674cee788b04 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -192,7 +192,7 @@ impl TestArgs { .evm_spec(config.evm_spec_id()) .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) - .with_cheats_config(CheatsConfig::new(&config, evm_opts.clone())) + .with_cheats_config(CheatsConfig::new(&config, evm_opts.clone(), None)) .with_test_options(test_options.clone()); let runner = runner_builder.clone().build( diff --git a/crates/forge/tests/it/config.rs b/crates/forge/tests/it/config.rs index 22c01f67e155..04f58234fcf4 100644 --- a/crates/forge/tests/it/config.rs +++ b/crates/forge/tests/it/config.rs @@ -178,7 +178,7 @@ pub async fn runner_with_config(mut config: Config) -> MultiContractRunner { let output = COMPILED.clone(); base_runner() .with_test_options(test_opts()) - .with_cheats_config(CheatsConfig::new(&config, opts.clone())) + .with_cheats_config(CheatsConfig::new(&config, opts.clone(), None)) .sender(config.sender) .build(root, output, env, opts.clone()) .unwrap() diff --git a/crates/wallets/src/multi_wallet.rs b/crates/wallets/src/multi_wallet.rs index 1c2b971aef32..f934696c05c0 100644 --- a/crates/wallets/src/multi_wallet.rs +++ b/crates/wallets/src/multi_wallet.rs @@ -15,7 +15,7 @@ use serde::Serialize; use std::{collections::HashMap, iter::repeat, path::PathBuf}; /// Container for multiple wallets. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct MultiWallet { /// Vector of wallets that require an action to be unlocked. /// Those are lazily unlocked on the first access of the signers. @@ -49,10 +49,8 @@ impl MultiWallet { Ok(self.signers) } - pub fn add_signers(&mut self, signers: impl IntoIterator) { - for signer in signers { - self.signers.insert(signer.address().to_alloy(), signer); - } + pub fn add_signer(&mut self, signer: WalletSigner) { + self.signers.insert(signer.address().to_alloy(), signer); } } From 7503ce77301f21760b9ce2f8b0143e826078e79a Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 15 Feb 2024 17:31:12 +0400 Subject: [PATCH 03/18] Add comments --- crates/cheatcodes/src/script.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index 14ff4aca911b..781ace8344ec 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -73,9 +73,12 @@ pub struct Broadcast { pub single_call: bool, } +/// Contains context for wallet management. #[derive(Debug)] pub struct ScriptWalletsData { + /// All signers in scope of the script. pub multi_wallet: MultiWallet, + /// Optional signer provided as `--sender` flag. pub provided_sender: Option
, } @@ -98,10 +101,14 @@ fn broadcast( if new_origin.is_none() { if let Some(script_wallets) = &ccx.state.script_wallets { let mut script_wallets = script_wallets.lock().unwrap(); - let signers = script_wallets.multi_wallet.signers()?; - if signers.len() == 1 { - let address = signers.keys().next().unwrap(); - new_origin = Some(address.clone()); + if let Some(provided_sender) = script_wallets.provided_sender { + new_origin = Some(provided_sender); + } else { + let signers = script_wallets.multi_wallet.signers()?; + if signers.len() == 1 { + let address = signers.keys().next().unwrap(); + new_origin = Some(address.clone()); + } } } } From 98f9b756e2f7f3075cdc2ba1e065a0b587b74fb5 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 15 Feb 2024 19:54:24 +0400 Subject: [PATCH 04/18] clippy + fmt --- crates/cheatcodes/src/script.rs | 2 +- crates/evm/evm/src/executors/mod.rs | 5 +---- crates/evm/evm/src/inspectors/stack.rs | 1 - crates/forge/bin/cmd/script/cmd.rs | 4 +--- crates/forge/bin/cmd/script/mod.rs | 2 +- crates/forge/bin/cmd/script/multi.rs | 3 ++- 6 files changed, 6 insertions(+), 11 deletions(-) diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index 781ace8344ec..4b7f91c039f1 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -107,7 +107,7 @@ fn broadcast( let signers = script_wallets.multi_wallet.signers()?; if signers.len() == 1 { let address = signers.keys().next().unwrap(); - new_origin = Some(address.clone()); + new_origin = Some(*address); } } } diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index d87fd2adb55b..c92a2db977ff 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -13,7 +13,6 @@ use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt}; use alloy_json_abi::Function; use alloy_primitives::{Address, Bytes, Log, U256}; - use foundry_common::{abi::IntoFunction, evm::Breakpoints}; use foundry_evm_core::{ backend::{Backend, DatabaseError, DatabaseExt, DatabaseResult, FuzzBackendWrapper}, @@ -33,9 +32,7 @@ use revm::{ BlockEnv, Bytecode, Env, ExecutionResult, Output, ResultAndState, SpecId, TransactTo, TxEnv, }, }; -use std::{ - collections::HashMap, -}; +use std::collections::HashMap; mod builder; pub use builder::ExecutorBuilder; diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 1d229c220f77..5c1e1e032551 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -4,7 +4,6 @@ use super::{ }; use alloy_primitives::{Address, Bytes, Log, B256, U256}; - use foundry_evm_core::{backend::DatabaseExt, debug::DebugArena}; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::CallTraceArena; diff --git a/crates/forge/bin/cmd/script/cmd.rs b/crates/forge/bin/cmd/script/cmd.rs index 2bfc1662abcc..b4bafb3a5de5 100644 --- a/crates/forge/bin/cmd/script/cmd.rs +++ b/crates/forge/bin/cmd/script/cmd.rs @@ -5,9 +5,7 @@ use ethers_providers::Middleware; use eyre::{OptionExt, Result}; use forge::link::Linker; use foundry_cli::utils::LoadConfig; -use foundry_common::{ - contracts::flatten_contracts, provider::ethers::try_get_http_provider, -}; +use foundry_common::{contracts::flatten_contracts, provider::ethers::try_get_http_provider}; use foundry_debugger::Debugger; use foundry_wallets::WalletSigner; use std::sync::Arc; diff --git a/crates/forge/bin/cmd/script/mod.rs b/crates/forge/bin/cmd/script/mod.rs index ce8acfce70ab..b214a15b51e3 100644 --- a/crates/forge/bin/cmd/script/mod.rs +++ b/crates/forge/bin/cmd/script/mod.rs @@ -53,7 +53,7 @@ use itertools::Itertools; use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, HashMap, HashSet, VecDeque}, - sync::{Mutex}, + sync::Mutex, }; use yansi::Paint; diff --git a/crates/forge/bin/cmd/script/multi.rs b/crates/forge/bin/cmd/script/multi.rs index 72d625c97a88..64b77c7fdb77 100644 --- a/crates/forge/bin/cmd/script/multi.rs +++ b/crates/forge/bin/cmd/script/multi.rs @@ -16,7 +16,8 @@ use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, io::{BufWriter, Write}, - path::{Path, PathBuf}, sync::Arc, + path::{Path, PathBuf}, + sync::Arc, }; /// Holds the sequences of multiple chain deployments. From 561624635a87d30f34df5066c6ff60976221bf78 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 15 Feb 2024 20:21:41 +0400 Subject: [PATCH 05/18] maybe_load_private_key --- crates/forge/bin/cmd/script/cmd.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/forge/bin/cmd/script/cmd.rs b/crates/forge/bin/cmd/script/cmd.rs index b4bafb3a5de5..aad9e85e4563 100644 --- a/crates/forge/bin/cmd/script/cmd.rs +++ b/crates/forge/bin/cmd/script/cmd.rs @@ -2,10 +2,11 @@ use super::{multi::MultiChainSequence, sequence::ScriptSequence, verify::VerifyB use alloy_primitives::Bytes; use ethers_providers::Middleware; +use ethers_signers::Signer; use eyre::{OptionExt, Result}; use forge::link::Linker; use foundry_cli::utils::LoadConfig; -use foundry_common::{contracts::flatten_contracts, provider::ethers::try_get_http_provider}; +use foundry_common::{contracts::flatten_contracts, provider::ethers::try_get_http_provider, types::ToAlloy}; use foundry_debugger::Debugger; use foundry_wallets::WalletSigner; use std::sync::Arc; @@ -57,6 +58,10 @@ impl ScriptArgs { .. } = build_output; + if let Some(sender) = self.maybe_load_private_key()? { + script_config.evm_opts.sender = sender; + } + // Execute once with default sender. let sender = script_config.evm_opts.sender; @@ -364,4 +369,15 @@ impl ScriptArgs { Ok((libraries, highlevel_known_contracts)) } + + /// In case the user has loaded *only* one private-key, we can assume that he's using it as the + /// `--sender` + fn maybe_load_private_key(&mut self) -> Result> { + let maybe_sender = self + .wallets + .private_keys()? + .filter(|pks| pks.len() == 1) + .map(|pks| pks.first().unwrap().address().to_alloy()); + Ok(maybe_sender) + } } From 68769067f21e1f083b59e86435faadcb11f509e2 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 15 Feb 2024 20:23:31 +0400 Subject: [PATCH 06/18] fmt --- crates/forge/bin/cmd/script/cmd.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/forge/bin/cmd/script/cmd.rs b/crates/forge/bin/cmd/script/cmd.rs index aad9e85e4563..bfa97eb2fbad 100644 --- a/crates/forge/bin/cmd/script/cmd.rs +++ b/crates/forge/bin/cmd/script/cmd.rs @@ -6,7 +6,9 @@ use ethers_signers::Signer; use eyre::{OptionExt, Result}; use forge::link::Linker; use foundry_cli::utils::LoadConfig; -use foundry_common::{contracts::flatten_contracts, provider::ethers::try_get_http_provider, types::ToAlloy}; +use foundry_common::{ + contracts::flatten_contracts, provider::ethers::try_get_http_provider, types::ToAlloy, +}; use foundry_debugger::Debugger; use foundry_wallets::WalletSigner; use std::sync::Arc; From 53273318b4fe7916277005b8166042e86eb6ecd0 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 15 Feb 2024 21:17:30 +0400 Subject: [PATCH 07/18] fix ci --- crates/cheatcodes/src/script.rs | 12 ++++++------ crates/cheatcodes/src/utils.rs | 2 +- crates/forge/bin/cmd/script/cmd.rs | 8 ++++---- crates/wallets/src/error.rs | 2 ++ crates/wallets/src/wallet_signer.rs | 9 ++++----- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index 4b7f91c039f1..4f06b937217a 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -2,7 +2,7 @@ use crate::{Cheatcode, CheatsCtxt, DatabaseExt, Result, Vm::*}; use alloy_primitives::{Address, U256}; -use alloy_signer::Signer; +use alloy_signer::{LocalWallet, Signer}; use foundry_config::Config; use foundry_wallets::{multi_wallet::MultiWallet, WalletSigner}; @@ -133,13 +133,13 @@ fn broadcast_key( private_key: &U256, single_call: bool, ) -> Result { - let mut wallet = super::utils::parse_wallet(private_key)?; - wallet.set_chain_id(Some(ccx.data.env.cfg.chain_id)); - let new_origin = &wallet.address(); + let key = super::utils::parse_private_key(private_key)?; + let new_origin = LocalWallet::from(key.clone()).address(); + + let result = broadcast(ccx, Some(&new_origin), single_call); - let result = broadcast(ccx, Some(new_origin), single_call); if result.is_ok() { - let signer = WalletSigner::from_private_key(&private_key.to_string())?; + let signer = WalletSigner::from_private_key(key.to_bytes())?; if let Some(script_wallets) = &ccx.state.script_wallets { script_wallets.lock().unwrap().multi_wallet.add_signer(signer); } diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs index 8d042715380e..04461c1cbd8c 100644 --- a/crates/cheatcodes/src/utils.rs +++ b/crates/cheatcodes/src/utils.rs @@ -90,7 +90,7 @@ impl Cheatcode for rememberKeyCall { let Self { privateKey } = self; let key = parse_private_key(privateKey)?; let address = LocalWallet::from(key.clone()).address(); - let signer = WalletSigner::from_private_key(&hex::encode(key.to_bytes()))?; + let signer = WalletSigner::from_private_key(key.to_bytes())?; if let Some(script_wallets) = &ccx.state.script_wallets { script_wallets.lock().unwrap().multi_wallet.add_signer(signer); } diff --git a/crates/forge/bin/cmd/script/cmd.rs b/crates/forge/bin/cmd/script/cmd.rs index bfa97eb2fbad..cf569cb6ad98 100644 --- a/crates/forge/bin/cmd/script/cmd.rs +++ b/crates/forge/bin/cmd/script/cmd.rs @@ -31,6 +31,10 @@ impl ScriptArgs { ..Default::default() }; + if let Some(sender) = self.maybe_load_private_key()? { + script_config.evm_opts.sender = sender; + } + if let Some(ref fork_url) = script_config.evm_opts.fork_url { // when forking, override the sender's nonce to the onchain value script_config.sender_nonce = @@ -60,10 +64,6 @@ impl ScriptArgs { .. } = build_output; - if let Some(sender) = self.maybe_load_private_key()? { - script_config.evm_opts.sender = sender; - } - // Execute once with default sender. let sender = script_config.evm_opts.sender; diff --git a/crates/wallets/src/error.rs b/crates/wallets/src/error.rs index 9ca867f47e75..bee0796fd622 100644 --- a/crates/wallets/src/error.rs +++ b/crates/wallets/src/error.rs @@ -21,4 +21,6 @@ pub enum WalletSignerError { Aws(#[from] AwsSignerError), #[error(transparent)] Io(#[from] std::io::Error), + #[error(transparent)] + InvalidHex(#[from] FromHexError), } diff --git a/crates/wallets/src/wallet_signer.rs b/crates/wallets/src/wallet_signer.rs index cb99d69aa486..e71f8eddfc58 100644 --- a/crates/wallets/src/wallet_signer.rs +++ b/crates/wallets/src/wallet_signer.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, str::FromStr}; +use std::path::PathBuf; use async_trait::async_trait; use ethers_core::types::{ @@ -48,8 +48,8 @@ impl WalletSigner { Ok(Self::Aws(AwsSigner::new(kms, key_id, 1).await?)) } - pub fn from_private_key(private_key: &str) -> Result { - let wallet = LocalWallet::from_str(private_key)?; + pub fn from_private_key(private_key: impl AsRef<[u8]>) -> Result { + let wallet = LocalWallet::from_bytes(private_key.as_ref())?; Ok(Self::Local(wallet)) } @@ -166,8 +166,7 @@ impl PendingSigner { } Self::Interactive => { let private_key = rpassword::prompt_password("Enter private key:")?; - let private_key = private_key.strip_prefix("0x").unwrap_or(&private_key); - Ok(WalletSigner::from_private_key(private_key)?) + Ok(WalletSigner::from_private_key(&hex::decode(private_key)?)?) } } } From 8f42863892cfa635d5aac7cca0bbdfbe691d0897 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 15 Feb 2024 21:20:17 +0400 Subject: [PATCH 08/18] clippy --- crates/wallets/src/wallet_signer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wallets/src/wallet_signer.rs b/crates/wallets/src/wallet_signer.rs index e71f8eddfc58..7fbb9648f718 100644 --- a/crates/wallets/src/wallet_signer.rs +++ b/crates/wallets/src/wallet_signer.rs @@ -166,7 +166,7 @@ impl PendingSigner { } Self::Interactive => { let private_key = rpassword::prompt_password("Enter private key:")?; - Ok(WalletSigner::from_private_key(&hex::decode(private_key)?)?) + Ok(WalletSigner::from_private_key(hex::decode(private_key)?)?) } } } From ad2df8f5d11cf9e3aa89ef2bf0269a50a69b327e Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 16 Feb 2024 01:13:48 +0400 Subject: [PATCH 09/18] refactor --- Cargo.lock | 1 + crates/cheatcodes/Cargo.toml | 1 + crates/cheatcodes/src/config.rs | 11 +++------ crates/cheatcodes/src/inspector.rs | 6 ++--- crates/cheatcodes/src/lib.rs | 2 +- crates/cheatcodes/src/script.rs | 32 ++++++++++++++++++++++--- crates/cheatcodes/src/utils.rs | 2 +- crates/forge/bin/cmd/script/cmd.rs | 23 +++++------------- crates/forge/bin/cmd/script/executor.rs | 5 ++-- crates/forge/bin/cmd/script/mod.rs | 9 ++----- 10 files changed, 50 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8428935896b6..1a25beff5209 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2984,6 +2984,7 @@ dependencies = [ "jsonpath_lib", "k256", "p256", + "parking_lot", "revm", "serde_json", "thiserror", diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index c2b1794f8776..00cfaf7c39cf 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -27,6 +27,7 @@ alloy-sol-types.workspace = true alloy-providers.workspace = true alloy-rpc-types.workspace = true alloy-signer = { workspace = true, features = ["mnemonic", "keystore"] } +parking_lot = "0.12" eyre.workspace = true diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 969eb583959e..1fdb9d4abc80 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -1,5 +1,5 @@ use super::Result; -use crate::{ScriptWalletsData, Vm::Rpc}; +use crate::{script::ScriptWallets, Vm::Rpc}; use alloy_primitives::Address; use foundry_common::fs::normalize_path; use foundry_compilers::{utils::canonicalize, ProjectPathsConfig}; @@ -11,7 +11,6 @@ use foundry_evm_core::opts::EvmOpts; use std::{ collections::HashMap, path::{Path, PathBuf}, - sync::{Arc, Mutex}, }; /// Additional, configurable context the `Cheatcodes` inspector has access to @@ -38,16 +37,12 @@ pub struct CheatsConfig { /// Address labels from config pub labels: HashMap, /// Script wallets - pub script_wallets: Option>>, + pub script_wallets: Option, } impl CheatsConfig { /// Extracts the necessary settings from the Config - pub fn new( - config: &Config, - evm_opts: EvmOpts, - script_wallets: Option>>, - ) -> Self { + pub fn new(config: &Config, evm_opts: EvmOpts, script_wallets: Option) -> Self { let mut allowed_paths = vec![config.__root.0.clone()]; allowed_paths.extend(config.libs.clone()); allowed_paths.extend(config.allow_paths.clone()); diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 93957a3ac5d8..2db0ea41d64c 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -7,7 +7,7 @@ use crate::{ prank::Prank, DealRecord, RecordAccess, }, - script::{Broadcast, ScriptWalletsData}, + script::{Broadcast, ScriptWallets}, test::expect::{ self, ExpectedCallData, ExpectedCallTracker, ExpectedCallType, ExpectedEmit, ExpectedRevert, ExpectedRevertKind, @@ -38,7 +38,7 @@ use std::{ io::BufReader, ops::Range, path::PathBuf, - sync::{Arc, Mutex}, + sync::Arc, }; macro_rules! try_or_continue { @@ -127,7 +127,7 @@ pub struct Cheatcodes { pub labels: HashMap, /// Remembered private keys - pub script_wallets: Option>>, + pub script_wallets: Option, /// Prank information pub prank: Option, diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index daeba3e55c8a..86cfb0e5d00b 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -36,7 +36,7 @@ mod string; mod test; mod utils; -pub use script::ScriptWalletsData; +pub use script::ScriptWallets; pub use test::expect::ExpectedCallTracker; /// Cheatcode implementation. diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index 4f06b937217a..2170c472f308 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -1,10 +1,13 @@ //! Implementations of [`Scripting`](crate::Group::Scripting) cheatcodes. +use std::sync::Arc; + use crate::{Cheatcode, CheatsCtxt, DatabaseExt, Result, Vm::*}; use alloy_primitives::{Address, U256}; use alloy_signer::{LocalWallet, Signer}; use foundry_config::Config; use foundry_wallets::{multi_wallet::MultiWallet, WalletSigner}; +use parking_lot::Mutex; impl Cheatcode for broadcast_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { @@ -75,13 +78,36 @@ pub struct Broadcast { /// Contains context for wallet management. #[derive(Debug)] -pub struct ScriptWalletsData { +pub struct ScriptWalletsInner { /// All signers in scope of the script. pub multi_wallet: MultiWallet, /// Optional signer provided as `--sender` flag. pub provided_sender: Option
, } +/// Clonable wrapper around [ScriptWalletsInner]. +#[derive(Debug, Clone)] +pub struct ScriptWallets { + /// Inner data. + pub inner: Arc>, +} + +impl ScriptWallets { + #[allow(missing_docs)] + pub fn new(multi_wallet: MultiWallet, provided_sender: Option
) -> Self { + Self { inner: Arc::new(Mutex::new(ScriptWalletsInner { multi_wallet, provided_sender })) } + } + + /// Consumes [ScriptWallets] and returns [MultiWallet]. + /// + /// Panics if [ScriptWallets] is still in use. + pub fn into_multi_wallet(self) -> MultiWallet { + Arc::into_inner(self.inner) + .map(|m| m.into_inner().multi_wallet) + .unwrap_or_else(|| panic!("not all instances were dropped")) + } +} + /// Sets up broadcasting from a script using `new_origin` as the sender. fn broadcast( ccx: &mut CheatsCtxt, @@ -100,7 +126,7 @@ fn broadcast( if new_origin.is_none() { if let Some(script_wallets) = &ccx.state.script_wallets { - let mut script_wallets = script_wallets.lock().unwrap(); + let mut script_wallets = script_wallets.inner.lock(); if let Some(provided_sender) = script_wallets.provided_sender { new_origin = Some(provided_sender); } else { @@ -141,7 +167,7 @@ fn broadcast_key( if result.is_ok() { let signer = WalletSigner::from_private_key(key.to_bytes())?; if let Some(script_wallets) = &ccx.state.script_wallets { - script_wallets.lock().unwrap().multi_wallet.add_signer(signer); + script_wallets.inner.lock().multi_wallet.add_signer(signer); } } result diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs index 04461c1cbd8c..9458a9b5177b 100644 --- a/crates/cheatcodes/src/utils.rs +++ b/crates/cheatcodes/src/utils.rs @@ -92,7 +92,7 @@ impl Cheatcode for rememberKeyCall { let address = LocalWallet::from(key.clone()).address(); let signer = WalletSigner::from_private_key(key.to_bytes())?; if let Some(script_wallets) = &ccx.state.script_wallets { - script_wallets.lock().unwrap().multi_wallet.add_signer(signer); + script_wallets.inner.lock().multi_wallet.add_signer(signer); } Ok(address.abi_encode()) } diff --git a/crates/forge/bin/cmd/script/cmd.rs b/crates/forge/bin/cmd/script/cmd.rs index cf569cb6ad98..2ef2467bff79 100644 --- a/crates/forge/bin/cmd/script/cmd.rs +++ b/crates/forge/bin/cmd/script/cmd.rs @@ -10,6 +10,7 @@ use foundry_common::{ contracts::flatten_contracts, provider::ethers::try_get_http_provider, types::ToAlloy, }; use foundry_debugger::Debugger; +use foundry_evm::inspectors::cheatcodes::ScriptWallets; use foundry_wallets::WalletSigner; use std::sync::Arc; @@ -68,9 +69,7 @@ impl ScriptArgs { let sender = script_config.evm_opts.sender; let multi_wallet = self.wallets.get_multi_wallet().await?; - let script_wallets = - ScriptWalletsData { multi_wallet, provided_sender: self.evm_opts.sender }; - let script_wallets = Arc::new(Mutex::new(script_wallets)); + let script_wallets = ScriptWallets::new(multi_wallet, self.evm_opts.sender); // We need to execute the script even if just resuming, in case we need to collect private // keys from the execution. @@ -85,12 +84,7 @@ impl ScriptArgs { .await?; if self.resume || (self.verify && !self.broadcast) { - let signers = Arc::try_unwrap(script_wallets) - .unwrap() - .into_inner() - .unwrap() - .multi_wallet - .into_signers()?; + let signers = script_wallets.into_multi_wallet().into_signers()?; return self.resume_deployment(script_config, linker, libraries, verify, signers).await; } @@ -131,12 +125,7 @@ impl ScriptArgs { verify.known_contracts = flatten_contracts(&highlevel_known_contracts, false); self.check_contract_sizes(&result, &highlevel_known_contracts)?; - let signers = Arc::try_unwrap(script_wallets) - .unwrap() - .into_inner() - .unwrap() - .multi_wallet - .into_signers()?; + let signers = script_wallets.into_multi_wallet().into_signers()?; self.handle_broadcastable_transactions( result, @@ -157,7 +146,7 @@ impl ScriptArgs { linker: Linker, predeploy_libraries: Vec, result: &mut ScriptResult, - script_wallets: Arc>, + script_wallets: ScriptWallets, ) -> Result> { if let Some(new_sender) = self.maybe_new_sender( &script_config.evm_opts, @@ -321,7 +310,7 @@ impl ScriptArgs { new_sender: Address, first_run_result: &mut ScriptResult, linker: Linker, - script_wallets: Arc>, + script_wallets: ScriptWallets, ) -> Result<(Libraries, ArtifactContracts)> { // if we had a new sender that requires relinking, we need to // get the nonce mainnet for accurate addresses for predeploy libs diff --git a/crates/forge/bin/cmd/script/executor.rs b/crates/forge/bin/cmd/script/executor.rs index 0ebe26bae937..e2e89a900f45 100644 --- a/crates/forge/bin/cmd/script/executor.rs +++ b/crates/forge/bin/cmd/script/executor.rs @@ -14,6 +14,7 @@ use forge::{ }; use foundry_cli::utils::{ensure_clean_constructor, needs_setup}; use foundry_common::{provider::ethers::RpcUrl, shell}; +use foundry_evm::inspectors::cheatcodes::ScriptWallets; use futures::future::join_all; use parking_lot::RwLock; use std::{collections::VecDeque, sync::Arc}; @@ -27,7 +28,7 @@ impl ScriptArgs { contract: ContractBytecodeSome, sender: Address, predeploy_libraries: &[Bytes], - script_wallets: Arc>, + script_wallets: ScriptWallets, ) -> Result { trace!(target: "script", "start executing script"); @@ -265,7 +266,7 @@ impl ScriptArgs { script_config: &mut ScriptConfig, sender: Address, stage: SimulationStage, - script_wallets: Option>>, + script_wallets: Option, ) -> Result { trace!("preparing script runner"); let env = script_config.evm_opts.evm_env().await?; diff --git a/crates/forge/bin/cmd/script/mod.rs b/crates/forge/bin/cmd/script/mod.rs index b214a15b51e3..bb4572ef3bcc 100644 --- a/crates/forge/bin/cmd/script/mod.rs +++ b/crates/forge/bin/cmd/script/mod.rs @@ -43,18 +43,13 @@ use foundry_config::{ use foundry_evm::{ constants::DEFAULT_CREATE2_DEPLOYER, decode::RevertDecoder, - inspectors::cheatcodes::{ - BroadcastableTransaction, BroadcastableTransactions, ScriptWalletsData, - }, + inspectors::cheatcodes::{BroadcastableTransaction, BroadcastableTransactions}, }; use foundry_wallets::MultiWalletOpts; use futures::future; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use std::{ - collections::{BTreeMap, HashMap, HashSet, VecDeque}, - sync::Mutex, -}; +use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; use yansi::Paint; mod artifacts; From 1460d414bb89b7b0d3b4dbc81c001a4e45d2c6a0 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 16 Feb 2024 01:16:41 +0400 Subject: [PATCH 10/18] Wallet -> WalletOpts --- crates/cast/bin/cmd/wallet/mod.rs | 8 ++++---- crates/cli/src/opts/ethereum.rs | 4 ++-- crates/wallets/src/lib.rs | 2 +- crates/wallets/src/wallet.rs | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index ce167f31e00e..3863f9173e24 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -9,7 +9,7 @@ use ethers_signers::Signer; use eyre::{Context, Result}; use foundry_common::{fs, types::ToAlloy}; use foundry_config::Config; -use foundry_wallets::{RawWallet, Wallet, WalletSigner}; +use foundry_wallets::{RawWallet, WalletOpts, WalletSigner}; use rand::thread_rng; use serde_json::json; use std::{path::Path, str::FromStr}; @@ -72,7 +72,7 @@ pub enum WalletSubcommands { private_key_override: Option, #[clap(flatten)] - wallet: Wallet, + wallet: WalletOpts, }, /// Sign a message or typed data. @@ -102,7 +102,7 @@ pub enum WalletSubcommands { from_file: bool, #[clap(flatten)] - wallet: Wallet, + wallet: WalletOpts, }, /// Verify the signature of a message. @@ -237,7 +237,7 @@ impl WalletSubcommands { } WalletSubcommands::Address { wallet, private_key_override } => { let wallet = private_key_override - .map(|pk| Wallet { + .map(|pk| WalletOpts { raw: RawWallet { private_key: Some(pk), ..Default::default() }, ..Default::default() }) diff --git a/crates/cli/src/opts/ethereum.rs b/crates/cli/src/opts/ethereum.rs index 198da6c6a2f8..62cebeaec73f 100644 --- a/crates/cli/src/opts/ethereum.rs +++ b/crates/cli/src/opts/ethereum.rs @@ -9,7 +9,7 @@ use foundry_config::{ }, impl_figment_convert_cast, Chain, Config, }; -use foundry_wallets::Wallet; +use foundry_wallets::WalletOpts; use serde::Serialize; use std::borrow::Cow; @@ -150,7 +150,7 @@ pub struct EthereumOpts { pub etherscan: EtherscanOpts, #[clap(flatten)] - pub wallet: Wallet, + pub wallet: WalletOpts, } impl_figment_convert_cast!(EthereumOpts); diff --git a/crates/wallets/src/lib.rs b/crates/wallets/src/lib.rs index 995002f97691..52d24c2389c7 100644 --- a/crates/wallets/src/lib.rs +++ b/crates/wallets/src/lib.rs @@ -10,5 +10,5 @@ pub mod wallet_signer; pub use multi_wallet::MultiWalletOpts; pub use raw_wallet::RawWallet; -pub use wallet::Wallet; +pub use wallet::WalletOpts; pub use wallet_signer::{PendingSigner, WalletSigner}; diff --git a/crates/wallets/src/wallet.rs b/crates/wallets/src/wallet.rs index a3873206a788..405c2d097910 100644 --- a/crates/wallets/src/wallet.rs +++ b/crates/wallets/src/wallet.rs @@ -17,7 +17,7 @@ use serde::Serialize; /// 5. AWS KMS #[derive(Clone, Debug, Default, Serialize, Parser)] #[clap(next_help_heading = "Wallet options", about = None, long_about = None)] -pub struct Wallet { +pub struct WalletOpts { /// The sender account. #[clap( long, @@ -86,7 +86,7 @@ pub struct Wallet { pub aws: bool, } -impl Wallet { +impl WalletOpts { pub async fn signer(&self) -> Result { trace!("start finding signer"); @@ -143,7 +143,7 @@ of the unlocked account you want to use, or provide the --from flag with the add } } -impl From for Wallet { +impl From for WalletOpts { fn from(options: RawWallet) -> Self { Self { raw: options, ..Default::default() } } @@ -162,7 +162,7 @@ mod tests { let keystore_file = keystore .join("UTC--2022-12-20T10-30-43.591916000Z--ec554aeafe75601aaab43bd4621a22284db566c2"); let password_file = keystore.join("password-ec554"); - let wallet: Wallet = Wallet::parse_from([ + let wallet: WalletOpts = WalletOpts::parse_from([ "foundry-cli", "--from", "560d246fcddc9ea98a8b032c9a2f474efb493c28", @@ -180,7 +180,7 @@ mod tests { #[tokio::test] async fn illformed_private_key_generates_user_friendly_error() { - let wallet = Wallet { + let wallet = WalletOpts { raw: RawWallet { interactive: false, private_key: Some("123".to_string()), From 327522e8755c5a1eeefd90b451fbc2ab12248ede Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 16 Feb 2024 03:14:21 +0400 Subject: [PATCH 11/18] rm blank lines --- crates/cheatcodes/src/inspector.rs | 1 - crates/cheatcodes/src/script.rs | 3 +-- crates/evm/evm/src/inspectors/stack.rs | 1 - crates/wallets/src/multi_wallet.rs | 3 --- crates/wallets/src/raw_wallet.rs | 3 +-- crates/wallets/src/utils.rs | 6 ++---- crates/wallets/src/wallet.rs | 3 --- crates/wallets/src/wallet_signer.rs | 4 +--- 8 files changed, 5 insertions(+), 19 deletions(-) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 2db0ea41d64c..403a5115c330 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -16,7 +16,6 @@ use crate::{ }; use alloy_primitives::{Address, Bytes, B256, U256, U64}; use alloy_rpc_types::request::{TransactionInput, TransactionRequest}; - use alloy_sol_types::{SolInterface, SolValue}; use foundry_common::{evm::Breakpoints, provider::alloy::RpcUrl}; use foundry_evm_core::{ diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index 2170c472f308..aeaa09eb0981 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -1,8 +1,7 @@ //! Implementations of [`Scripting`](crate::Group::Scripting) cheatcodes. -use std::sync::Arc; - use crate::{Cheatcode, CheatsCtxt, DatabaseExt, Result, Vm::*}; +use std::sync::Arc; use alloy_primitives::{Address, U256}; use alloy_signer::{LocalWallet, Signer}; use foundry_config::Config; diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 5c1e1e032551..a1fc13d70619 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -3,7 +3,6 @@ use super::{ StackSnapshotType, TracePrinter, TracingInspector, TracingInspectorConfig, }; use alloy_primitives::{Address, Bytes, Log, B256, U256}; - use foundry_evm_core::{backend::DatabaseExt, debug::DebugArena}; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::CallTraceArena; diff --git a/crates/wallets/src/multi_wallet.rs b/crates/wallets/src/multi_wallet.rs index f934696c05c0..ca0dfa4517fd 100644 --- a/crates/wallets/src/multi_wallet.rs +++ b/crates/wallets/src/multi_wallet.rs @@ -4,13 +4,10 @@ use crate::{ }; use alloy_primitives::Address; use clap::Parser; - use ethers_signers::Signer; use eyre::Result; - use foundry_common::types::ToAlloy; use foundry_config::Config; - use serde::Serialize; use std::{collections::HashMap, iter::repeat, path::PathBuf}; diff --git a/crates/wallets/src/raw_wallet.rs b/crates/wallets/src/raw_wallet.rs index 510da22e6df1..1b44db1d3f68 100644 --- a/crates/wallets/src/raw_wallet.rs +++ b/crates/wallets/src/raw_wallet.rs @@ -1,9 +1,8 @@ +use crate::{utils, PendingSigner, WalletSigner}; use clap::Parser; use eyre::Result; use serde::Serialize; -use crate::{utils, PendingSigner, WalletSigner}; - /// A wrapper for the raw data options for `Wallet`, extracted to also be used standalone. /// The raw wallet options can either be: /// 1. Private Key (cleartext in CLI) diff --git a/crates/wallets/src/utils.rs b/crates/wallets/src/utils.rs index 8c805c5cc652..969c208d60f5 100644 --- a/crates/wallets/src/utils.rs +++ b/crates/wallets/src/utils.rs @@ -1,4 +1,6 @@ +use crate::{error::PrivateKeyError, PendingSigner, WalletSigner}; use ethers_signers::{HDPath as LedgerHDPath, LocalWallet, TrezorHDPath, WalletError}; +use eyre::{Context, Result}; use foundry_config::Config; use std::{ fs, @@ -6,10 +8,6 @@ use std::{ str::FromStr, }; -use eyre::{Context, Result}; - -use crate::{error::PrivateKeyError, PendingSigner, WalletSigner}; - pub fn create_private_key_signer(private_key: &str) -> Result { let privk = private_key.trim().strip_prefix("0x").unwrap_or(private_key); match LocalWallet::from_str(privk) { diff --git a/crates/wallets/src/wallet.rs b/crates/wallets/src/wallet.rs index 405c2d097910..5df745a03cc7 100644 --- a/crates/wallets/src/wallet.rs +++ b/crates/wallets/src/wallet.rs @@ -1,12 +1,9 @@ use crate::{raw_wallet::RawWallet, utils, wallet_signer::WalletSigner}; use alloy_primitives::Address; - use clap::Parser; - use ethers_signers::Signer; use eyre::Result; use foundry_common::types::ToAlloy; - use serde::Serialize; /// The wallet options can either be: diff --git a/crates/wallets/src/wallet_signer.rs b/crates/wallets/src/wallet_signer.rs index 7fbb9648f718..1050b0c984c1 100644 --- a/crates/wallets/src/wallet_signer.rs +++ b/crates/wallets/src/wallet_signer.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use async_trait::async_trait; use ethers_core::types::{ transaction::{eip2718::TypedTransaction, eip712::Eip712}, @@ -14,7 +12,7 @@ use rusoto_core::{ request::HttpClient as AwsHttpClient, Client as AwsClient, }; use rusoto_kms::KmsClient; - +use std::path::PathBuf; use crate::error::WalletSignerError; pub type Result = std::result::Result; From 894a1d6c6e5c26ba5867b9d847925844de71c784 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 16 Feb 2024 05:41:21 +0400 Subject: [PATCH 12/18] Review fixes --- crates/cheatcodes/src/script.rs | 11 ++++++++--- crates/cheatcodes/src/utils.rs | 4 +--- crates/wallets/src/wallet_signer.rs | 7 ++++++- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index aeaa09eb0981..69501435df8b 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -1,12 +1,12 @@ //! Implementations of [`Scripting`](crate::Group::Scripting) cheatcodes. use crate::{Cheatcode, CheatsCtxt, DatabaseExt, Result, Vm::*}; -use std::sync::Arc; use alloy_primitives::{Address, U256}; use alloy_signer::{LocalWallet, Signer}; use foundry_config::Config; use foundry_wallets::{multi_wallet::MultiWallet, WalletSigner}; use parking_lot::Mutex; +use std::sync::Arc; impl Cheatcode for broadcast_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { @@ -105,6 +105,12 @@ impl ScriptWallets { .map(|m| m.into_inner().multi_wallet) .unwrap_or_else(|| panic!("not all instances were dropped")) } + + /// Locks [MultiWallet] Mutex and + pub fn add_signer(&self, private_key: impl AsRef<[u8]>) -> Result { + self.inner.lock().multi_wallet.add_signer(WalletSigner::from_private_key(private_key)?); + Ok(Default::default()) + } } /// Sets up broadcasting from a script using `new_origin` as the sender. @@ -164,9 +170,8 @@ fn broadcast_key( let result = broadcast(ccx, Some(&new_origin), single_call); if result.is_ok() { - let signer = WalletSigner::from_private_key(key.to_bytes())?; if let Some(script_wallets) = &ccx.state.script_wallets { - script_wallets.inner.lock().multi_wallet.add_signer(signer); + script_wallets.add_signer(key.to_bytes())?; } } result diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs index 9458a9b5177b..5975cdc51772 100644 --- a/crates/cheatcodes/src/utils.rs +++ b/crates/cheatcodes/src/utils.rs @@ -11,7 +11,6 @@ use alloy_signer::{ }; use alloy_sol_types::SolValue; use foundry_evm_core::constants::DEFAULT_CREATE2_DEPLOYER; -use foundry_wallets::WalletSigner; use k256::{ ecdsa::SigningKey, elliptic_curve::{sec1::ToEncodedPoint, Curve}, @@ -90,9 +89,8 @@ impl Cheatcode for rememberKeyCall { let Self { privateKey } = self; let key = parse_private_key(privateKey)?; let address = LocalWallet::from(key.clone()).address(); - let signer = WalletSigner::from_private_key(key.to_bytes())?; if let Some(script_wallets) = &ccx.state.script_wallets { - script_wallets.inner.lock().multi_wallet.add_signer(signer); + script_wallets.add_signer(key.to_bytes())?; } Ok(address.abi_encode()) } diff --git a/crates/wallets/src/wallet_signer.rs b/crates/wallets/src/wallet_signer.rs index 1050b0c984c1..baafebeb5f56 100644 --- a/crates/wallets/src/wallet_signer.rs +++ b/crates/wallets/src/wallet_signer.rs @@ -1,3 +1,4 @@ +use crate::error::WalletSignerError; use async_trait::async_trait; use ethers_core::types::{ transaction::{eip2718::TypedTransaction, eip712::Eip712}, @@ -13,15 +14,19 @@ use rusoto_core::{ }; use rusoto_kms::KmsClient; use std::path::PathBuf; -use crate::error::WalletSignerError; pub type Result = std::result::Result; +/// Wrapper enum around different signers. #[derive(Debug)] pub enum WalletSigner { + /// Wrapper around local wallet. e.g. private key, mnemonic Local(LocalWallet), + /// Wrapper around Ledger signer. Ledger(Ledger), + /// Wrapper around Trezor signer. Trezor(Trezor), + /// Wrapper around AWS KMS signer. Aws(AwsSigner), } From f74a92ff62a181ebe6cbe82773abc72f4dca480e Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 16 Feb 2024 05:42:28 +0400 Subject: [PATCH 13/18] fix comment --- crates/cheatcodes/src/script.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index 69501435df8b..62a751837257 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -106,7 +106,7 @@ impl ScriptWallets { .unwrap_or_else(|| panic!("not all instances were dropped")) } - /// Locks [MultiWallet] Mutex and + /// Locks inner Mutex and adds a signer to the [MultiWallet]. pub fn add_signer(&self, private_key: impl AsRef<[u8]>) -> Result { self.inner.lock().multi_wallet.add_signer(WalletSigner::from_private_key(private_key)?); Ok(Default::default()) From dd11855252d8e79d837929114a168bc0f78266c0 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 16 Feb 2024 14:42:48 +0400 Subject: [PATCH 14/18] comments --- crates/cast/bin/cmd/wallet/mod.rs | 6 +++--- crates/wallets/src/lib.rs | 2 +- crates/wallets/src/multi_wallet.rs | 1 + crates/wallets/src/raw_wallet.rs | 5 +++-- crates/wallets/src/utils.rs | 13 +++++++++++++ crates/wallets/src/wallet.rs | 10 +++++----- 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index 3863f9173e24..051eb3d4295b 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -9,7 +9,7 @@ use ethers_signers::Signer; use eyre::{Context, Result}; use foundry_common::{fs, types::ToAlloy}; use foundry_config::Config; -use foundry_wallets::{RawWallet, WalletOpts, WalletSigner}; +use foundry_wallets::{RawWalletOpts, WalletOpts, WalletSigner}; use rand::thread_rng; use serde_json::json; use std::{path::Path, str::FromStr}; @@ -129,7 +129,7 @@ pub enum WalletSubcommands { #[clap(long, short)] keystore_dir: Option, #[clap(flatten)] - raw_wallet_options: RawWallet, + raw_wallet_options: RawWalletOpts, }, /// List all the accounts in the keystore default directory #[clap(visible_alias = "ls")] @@ -238,7 +238,7 @@ impl WalletSubcommands { WalletSubcommands::Address { wallet, private_key_override } => { let wallet = private_key_override .map(|pk| WalletOpts { - raw: RawWallet { private_key: Some(pk), ..Default::default() }, + raw: RawWalletOpts { private_key: Some(pk), ..Default::default() }, ..Default::default() }) .unwrap_or(wallet) diff --git a/crates/wallets/src/lib.rs b/crates/wallets/src/lib.rs index 52d24c2389c7..38ff5e7fa91a 100644 --- a/crates/wallets/src/lib.rs +++ b/crates/wallets/src/lib.rs @@ -9,6 +9,6 @@ pub mod wallet; pub mod wallet_signer; pub use multi_wallet::MultiWalletOpts; -pub use raw_wallet::RawWallet; +pub use raw_wallet::RawWalletOpts; pub use wallet::WalletOpts; pub use wallet_signer::{PendingSigner, WalletSigner}; diff --git a/crates/wallets/src/multi_wallet.rs b/crates/wallets/src/multi_wallet.rs index ca0dfa4517fd..21692eb04794 100644 --- a/crates/wallets/src/multi_wallet.rs +++ b/crates/wallets/src/multi_wallet.rs @@ -215,6 +215,7 @@ pub struct MultiWalletOpts { } impl MultiWalletOpts { + /// Returns [MultiWallet] container configured with provided options. pub async fn get_multi_wallet(&self) -> Result { let mut pending = Vec::new(); let mut signers: Vec = Vec::new(); diff --git a/crates/wallets/src/raw_wallet.rs b/crates/wallets/src/raw_wallet.rs index 1b44db1d3f68..ccb1d6388dc1 100644 --- a/crates/wallets/src/raw_wallet.rs +++ b/crates/wallets/src/raw_wallet.rs @@ -10,7 +10,7 @@ use serde::Serialize; /// 3. Mnemonic (via file path) #[derive(Clone, Debug, Default, Serialize, Parser)] #[clap(next_help_heading = "Wallet options - raw", about = None, long_about = None)] -pub struct RawWallet { +pub struct RawWalletOpts { /// Open an interactive prompt to enter your private key. #[clap(long, short)] pub interactive: bool, @@ -40,7 +40,8 @@ pub struct RawWallet { pub mnemonic_index: u32, } -impl RawWallet { +impl RawWalletOpts { + /// Returns signer configured by provided parameters. pub fn signer(&self) -> Result> { if self.interactive { return Ok(Some(PendingSigner::Interactive.unlock()?)); diff --git a/crates/wallets/src/utils.rs b/crates/wallets/src/utils.rs index 969c208d60f5..a10903313034 100644 --- a/crates/wallets/src/utils.rs +++ b/crates/wallets/src/utils.rs @@ -8,6 +8,7 @@ use std::{ str::FromStr, }; +/// Validates and sanitizes user inputs, returning configured [WalletSigner]. pub fn create_private_key_signer(private_key: &str) -> Result { let privk = private_key.trim().strip_prefix("0x").unwrap_or(private_key); match LocalWallet::from_str(privk) { @@ -38,6 +39,9 @@ pub fn create_private_key_signer(private_key: &str) -> Result { } } +/// Creates [WalletSigner] instance from given mnemonic parameters. +/// +/// Mnemonic can be either a file path or a mnemonic phrase. pub fn create_mnemonic_signer( mnemonic: &str, passphrase: Option<&str>, @@ -53,6 +57,7 @@ pub fn create_mnemonic_signer( Ok(WalletSigner::from_mnemonic(&mnemonic, passphrase, hd_path, index)?) } +/// Creates [WalletSigner] instance from given Ledger parameters. pub async fn create_ledger_signer( hd_path: Option<&str>, mnemonic_index: u32, @@ -70,6 +75,7 @@ Make sure it's connected and unlocked, with no other desktop wallet apps open." }) } +/// Creates [WalletSigner] instance from given Trezor parameters. pub async fn create_trezor_signer( hd_path: Option<&str>, mnemonic_index: u32, @@ -98,6 +104,13 @@ pub fn maybe_get_keystore_path( .or_else(|| maybe_name.map(|name| default_keystore_dir.join(name)))) } +/// Creates keystore signer from given parameters. +/// +/// If correct password or password file is provided, the keystore is decrypted and a [WalletSigner] +/// is returned. +/// +/// Otherwise, a [PendingSigner] is returned, which can be used to unlock the keystore later, +/// prompting user for password. pub fn create_keystore_signer( path: &PathBuf, maybe_password: Option<&str>, diff --git a/crates/wallets/src/wallet.rs b/crates/wallets/src/wallet.rs index 5df745a03cc7..a15f805b93fc 100644 --- a/crates/wallets/src/wallet.rs +++ b/crates/wallets/src/wallet.rs @@ -1,4 +1,4 @@ -use crate::{raw_wallet::RawWallet, utils, wallet_signer::WalletSigner}; +use crate::{raw_wallet::RawWalletOpts, utils, wallet_signer::WalletSigner}; use alloy_primitives::Address; use clap::Parser; use ethers_signers::Signer; @@ -26,7 +26,7 @@ pub struct WalletOpts { pub from: Option
, #[clap(flatten)] - pub raw: RawWallet, + pub raw: RawWalletOpts, /// Use the keystore in the given folder or file. #[clap( @@ -140,8 +140,8 @@ of the unlocked account you want to use, or provide the --from flag with the add } } -impl From for WalletOpts { - fn from(options: RawWallet) -> Self { +impl From for WalletOpts { + fn from(options: RawWalletOpts) -> Self { Self { raw: options, ..Default::default() } } } @@ -178,7 +178,7 @@ mod tests { #[tokio::test] async fn illformed_private_key_generates_user_friendly_error() { let wallet = WalletOpts { - raw: RawWallet { + raw: RawWalletOpts { interactive: false, private_key: Some("123".to_string()), mnemonic: None, From 059e7906a1692f1e8bfda49219ed4fca025365f7 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Sat, 17 Feb 2024 12:45:32 +0400 Subject: [PATCH 15/18] review fixes --- crates/evm/evm/src/executors/mod.rs | 1 - crates/forge/bin/cmd/script/broadcast.rs | 4 ++-- crates/forge/bin/cmd/script/cmd.rs | 8 ++++---- crates/forge/bin/cmd/script/multi.rs | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index c92a2db977ff..628bc502ebb6 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -12,7 +12,6 @@ use crate::inspectors::{ use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt}; use alloy_json_abi::Function; use alloy_primitives::{Address, Bytes, Log, U256}; - use foundry_common::{abi::IntoFunction, evm::Breakpoints}; use foundry_evm_core::{ backend::{Backend, DatabaseError, DatabaseExt, DatabaseResult, FuzzBackendWrapper}, diff --git a/crates/forge/bin/cmd/script/broadcast.rs b/crates/forge/bin/cmd/script/broadcast.rs index 10a2758c28ea..dcea6b0274f1 100644 --- a/crates/forge/bin/cmd/script/broadcast.rs +++ b/crates/forge/bin/cmd/script/broadcast.rs @@ -292,7 +292,7 @@ impl ScriptArgs { decoder: &CallTraceDecoder, mut script_config: ScriptConfig, verify: VerifyBundle, - signers: HashMap, + signers: &HashMap, ) -> Result<()> { if let Some(txs) = result.transactions.take() { script_config.collect_rpcs(&txs); @@ -361,7 +361,7 @@ impl ScriptArgs { script_config: ScriptConfig, libraries: Libraries, verify: VerifyBundle, - signers: HashMap, + signers: &HashMap, ) -> Result<()> { trace!(target: "script", "broadcasting single chain deployment"); diff --git a/crates/forge/bin/cmd/script/cmd.rs b/crates/forge/bin/cmd/script/cmd.rs index 2ef2467bff79..3742ec001e43 100644 --- a/crates/forge/bin/cmd/script/cmd.rs +++ b/crates/forge/bin/cmd/script/cmd.rs @@ -85,7 +85,7 @@ impl ScriptArgs { if self.resume || (self.verify && !self.broadcast) { let signers = script_wallets.into_multi_wallet().into_signers()?; - return self.resume_deployment(script_config, linker, libraries, verify, signers).await; + return self.resume_deployment(script_config, linker, libraries, verify, &signers).await; } let known_contracts = flatten_contracts(&highlevel_known_contracts, true); @@ -133,7 +133,7 @@ impl ScriptArgs { &decoder, script_config, verify, - signers, + &signers, ) .await } @@ -196,7 +196,7 @@ impl ScriptArgs { linker: Linker, libraries: Libraries, verify: VerifyBundle, - signers: HashMap, + signers: &HashMap, ) -> Result<()> { if self.multi { return self @@ -231,7 +231,7 @@ impl ScriptArgs { script_config: ScriptConfig, linker: Linker, mut verify: VerifyBundle, - signers: HashMap, + signers: &HashMap, ) -> Result<()> { trace!(target: "script", "resuming single deployment"); diff --git a/crates/forge/bin/cmd/script/multi.rs b/crates/forge/bin/cmd/script/multi.rs index 64b77c7fdb77..51a87618b29c 100644 --- a/crates/forge/bin/cmd/script/multi.rs +++ b/crates/forge/bin/cmd/script/multi.rs @@ -182,7 +182,7 @@ impl ScriptArgs { libraries: Libraries, config: &Config, verify: VerifyBundle, - signers: HashMap, + signers: &HashMap, ) -> Result<()> { if !libraries.is_empty() { eyre::bail!("Libraries are currently not supported on multi deployment setups."); @@ -225,7 +225,7 @@ impl ScriptArgs { .send_transactions( sequence, &sequence.typed_transactions().first().unwrap().0.clone(), - &signers, + signers, ) .await { From 37e41bd3b9fb5595b9a44733ba09dce0e8e7d748 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Sat, 17 Feb 2024 14:35:56 +0400 Subject: [PATCH 16/18] clippy --- crates/forge/bin/cmd/script/broadcast.rs | 2 +- crates/forge/bin/cmd/script/cmd.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/forge/bin/cmd/script/broadcast.rs b/crates/forge/bin/cmd/script/broadcast.rs index dcea6b0274f1..f6228d8eb304 100644 --- a/crates/forge/bin/cmd/script/broadcast.rs +++ b/crates/forge/bin/cmd/script/broadcast.rs @@ -373,7 +373,7 @@ impl ScriptArgs { deployment_sequence.add_libraries(libraries); - self.send_transactions(deployment_sequence, &rpc, &signers).await?; + self.send_transactions(deployment_sequence, &rpc, signers).await?; if self.verify { return deployment_sequence.verify_contracts(&script_config.config, verify).await; diff --git a/crates/forge/bin/cmd/script/cmd.rs b/crates/forge/bin/cmd/script/cmd.rs index 3742ec001e43..e7c1428b8151 100644 --- a/crates/forge/bin/cmd/script/cmd.rs +++ b/crates/forge/bin/cmd/script/cmd.rs @@ -273,7 +273,7 @@ impl ScriptArgs { receipts::wait_for_pending(provider, &mut deployment_sequence).await?; if self.resume { - self.send_transactions(&mut deployment_sequence, fork_url, &signers).await?; + self.send_transactions(&mut deployment_sequence, fork_url, signers).await?; } if self.verify { From e99259082fe97f169e0ad5757dbb491d918f7e4f Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Mon, 19 Feb 2024 23:48:15 +0400 Subject: [PATCH 17/18] fixes --- crates/forge/bin/cmd/script/broadcast.rs | 2 +- crates/forge/bin/cmd/script/cmd.rs | 2 +- crates/forge/bin/cmd/script/runner.rs | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/forge/bin/cmd/script/broadcast.rs b/crates/forge/bin/cmd/script/broadcast.rs index 19e5e3cf9361..f2b737e37788 100644 --- a/crates/forge/bin/cmd/script/broadcast.rs +++ b/crates/forge/bin/cmd/script/broadcast.rs @@ -6,7 +6,7 @@ use super::{ use alloy_primitives::{utils::format_units, Address, TxHash, U256}; use ethers_core::types::transaction::eip2718::TypedTransaction; use ethers_providers::{JsonRpcClient, Middleware, Provider}; -use ethers_signers::{LocalWallet, Signer}; +use ethers_signers::Signer; use eyre::{bail, Context, ContextCompat, Result}; use forge::{inspectors::cheatcodes::BroadcastableTransactions, traces::CallTraceDecoder}; use foundry_cli::{ diff --git a/crates/forge/bin/cmd/script/cmd.rs b/crates/forge/bin/cmd/script/cmd.rs index f8bd73664db0..c1e0fefb0de3 100644 --- a/crates/forge/bin/cmd/script/cmd.rs +++ b/crates/forge/bin/cmd/script/cmd.rs @@ -19,7 +19,7 @@ use foundry_compilers::{ use foundry_debugger::Debugger; use foundry_evm::inspectors::cheatcodes::{BroadcastableTransaction, ScriptWallets}; use foundry_wallets::WalletSigner; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; /// Helper alias type for the collection of data changed due to the new sender. type NewSenderChanges = (CallTraceDecoder, Libraries, ArtifactContracts); diff --git a/crates/forge/bin/cmd/script/runner.rs b/crates/forge/bin/cmd/script/runner.rs index 93a838883b2d..96937bfdbc97 100644 --- a/crates/forge/bin/cmd/script/runner.rs +++ b/crates/forge/bin/cmd/script/runner.rs @@ -7,7 +7,6 @@ use forge::{ revm::interpreter::{return_ok, InstructionResult}, traces::{TraceKind, Traces}, }; -use foundry_common::types::ToEthers; use foundry_config::Config; use yansi::Paint; From f1ff391fb039853566a83a1d8fb533fd5f6096e6 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Mon, 19 Feb 2024 23:54:14 +0400 Subject: [PATCH 18/18] fmt --- crates/forge/bin/cmd/script/executor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge/bin/cmd/script/executor.rs b/crates/forge/bin/cmd/script/executor.rs index 0193ae01436e..e0f8ed8ece1b 100644 --- a/crates/forge/bin/cmd/script/executor.rs +++ b/crates/forge/bin/cmd/script/executor.rs @@ -14,8 +14,8 @@ use forge::{ }; use foundry_cli::utils::{ensure_clean_constructor, needs_setup}; use foundry_common::{get_contract_name, provider::ethers::RpcUrl, shell, ContractsByArtifact}; -use foundry_evm::inspectors::cheatcodes::ScriptWallets; use foundry_compilers::artifacts::ContractBytecodeSome; +use foundry_evm::inspectors::cheatcodes::ScriptWallets; use futures::future::join_all; use parking_lot::RwLock; use std::{