From e8a8bf51be6fd22cb836d08fb1f91f0df5c40c49 Mon Sep 17 00:00:00 2001 From: grandizzy Date: Tue, 20 Feb 2024 12:08:08 +0200 Subject: [PATCH 1/7] issue #6958: Include HW wallets in cast wallet ls --- Cargo.lock | 73 +++++++++++++ crates/cast/bin/cmd/wallet/list.rs | 158 ++++++++++++++++++++++++++++ crates/cast/bin/cmd/wallet/mod.rs | 42 ++------ crates/cast/tests/cli/main.rs | 23 +++- crates/wallets/Cargo.toml | 1 + crates/wallets/src/multi_wallet.rs | 3 +- crates/wallets/src/wallet_signer.rs | 31 ++++++ 7 files changed, 293 insertions(+), 38 deletions(-) create mode 100644 crates/cast/bin/cmd/wallet/list.rs diff --git a/Cargo.lock b/Cargo.lock index 422ebd05ae4f..ad5908e54e7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1894,6 +1894,41 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "darling" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c376d08ea6aa96aafe61237c7200d1241cb177b7d3a542d791f2d118e9cbb955" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33043dcd19068b8192064c704b3f83eb464f91f1ff527b44a4e2b08d9cdb8855" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 2.0.49", +] + +[[package]] +name = "darling_macro" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a91391accf613803c2a9bf9abccdbaa07c54b4244a5b64883f9c3c137c86be" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.49", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -1955,6 +1990,37 @@ dependencies = [ "syn 2.0.49", ] +[[package]] +name = "derive_builder" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.49", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" +dependencies = [ + "derive_builder_core", + "syn 2.0.49", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -3349,6 +3415,7 @@ dependencies = [ "async-trait", "clap", "const-hex", + "derive_builder", "ethers-core", "ethers-providers", "ethers-signers", @@ -4138,6 +4205,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" diff --git a/crates/cast/bin/cmd/wallet/list.rs b/crates/cast/bin/cmd/wallet/list.rs new file mode 100644 index 000000000000..4c92b633ad18 --- /dev/null +++ b/crates/cast/bin/cmd/wallet/list.rs @@ -0,0 +1,158 @@ +use clap::Parser; +use eyre::Result; + +use foundry_common::{fs, types::ToAlloy}; +use foundry_config::Config; +use foundry_wallets::{multi_wallet::MultiWalletOptsBuilder, WalletSigner}; + +/// CLI arguments for `cast wallet list`. +#[derive(Clone, Debug, Parser)] +pub struct ListArgs { + /// List all the accounts in the keystore directory. + /// Default keystore directory is used if no path provided. + #[clap(long, default_missing_value = "", num_args(0..=1), help_heading = "List local accounts")] + dir: Option, + + /// List accounts from a Ledger hardware wallet. + #[clap(long, short, help_heading = "List Ledger hardware wallet accounts")] + ledger: bool, + + /// List accounts from a Trezor hardware wallet. + #[clap(long, short, help_heading = "List Trezor hardware wallet accounts")] + trezor: bool, + + /// List accounts from AWS KMS. + #[clap(long, help_heading = "List AWS KMS account")] + aws: bool, + + /// List all configured accounts. + #[clap(long, help_heading = "List all accounts")] + all: bool, +} + +impl ListArgs { + pub async fn run(self) -> Result<()> { + // list local accounts as files in keystore dir, no need to unlock / provide password + if self.dir.is_some() || self.all || !self.ledger && !self.trezor && !self.aws { + self.list_local_senders().await?; + } + + // Create options for multi wallet - ledger, trezor and AWS + let list_opts = MultiWalletOptsBuilder::default() + .ledger(self.ledger || self.all) + .mnemonic_indexes(Some(vec![0])) + .trezor(self.trezor || self.all) + .aws(self.aws || self.all) + .froms(None) + .interactives(0) + .private_keys(None) + .private_key(None) + .mnemonics(None) + .mnemonic_passphrases(None) + .hd_paths(None) + .keystore_paths(None) + .keystore_account_names(None) + .keystore_passwords(None) + .keystore_password_files(None) + .build() + .expect("build multi wallet"); + + // max number of senders to be shown for ledger and trezor signers + let max_senders = 3; + + // List ledger accounts + match list_opts.ledgers().await { + Ok(signers) => { + self.list_senders(signers.unwrap_or_default(), max_senders, "Ledger").await? + } + Err(e) => { + if !self.all { + println!("{}", e) + } + } + } + + // List Trezor accounts + match list_opts.trezors().await { + Ok(signers) => { + self.list_senders(signers.unwrap_or_default(), max_senders, "Trezor").await? + } + Err(e) => { + if !self.all { + println!("{}", e) + } + } + } + + // List AWS accounts + match list_opts.aws_signers().await { + Ok(signers) => { + self.list_senders(signers.unwrap_or_default(), max_senders, "AWS").await? + } + Err(e) => { + if !self.all { + println!("{}", e) + } + } + } + + Ok(()) + } + + async fn list_local_senders(&self) -> Result<()> { + let keystore_path = self.dir.clone().unwrap_or_default(); + let keystore_dir = if keystore_path.is_empty() { + let default_dir = Config::foundry_keystores_dir() + .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; + // Create the keystore directory if it doesn't exist + fs::create_dir_all(&default_dir)?; + default_dir + } else { + dunce::canonicalize(keystore_path)? + }; + + match std::fs::read_dir(keystore_dir) { + Ok(entries) => { + entries.flatten().for_each(|entry| { + let path = entry.path(); + if path.is_file() && path.extension().is_none() { + if let Some(file_name) = path.file_name() { + if let Some(name) = file_name.to_str() { + println!("{} (Local)", name); + } + } + } + }); + } + Err(e) => { + if !self.all { + println!("{}", e) + } + } + } + + Ok(()) + } + + async fn list_senders( + &self, + signers: Vec, + max_senders: usize, + label: &str, + ) -> eyre::Result<()> { + for signer in signers.iter() { + match signer.available_senders(max_senders).await { + Ok(senders) => { + senders.iter().for_each(|sender| println!("{} ({})", sender.to_alloy(), label)); + } + Err(e) => { + if !self.all { + println!("{}", e) + } + } + } + } + + Ok(()) + } +} diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index 5850c523a368..240134ef933b 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -18,6 +18,9 @@ use yansi::Paint; pub mod vanity; use vanity::VanityArgs; +pub mod list; +use list::ListArgs; + /// CLI arguments for `cast wallet`. #[derive(Debug, Parser)] pub enum WalletSubcommands { @@ -137,7 +140,7 @@ pub enum WalletSubcommands { }, /// List all the accounts in the keystore default directory #[clap(visible_alias = "ls")] - List, + List(ListArgs), /// Derives private key from mnemonic #[clap(name = "derive-private-key", visible_aliases = &["--derive-private-key"])] @@ -331,41 +334,8 @@ flag to set your key via: ); println!("{}", Paint::green(success_message)); } - WalletSubcommands::List => { - let default_keystore_dir = Config::foundry_keystores_dir() - .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; - // Create the keystore directory if it doesn't exist - fs::create_dir_all(&default_keystore_dir)?; - // List all files in keystore directory - let keystore_files: Result, eyre::Report> = - std::fs::read_dir(&default_keystore_dir) - .wrap_err("Failed to read the directory")? - .filter_map(|entry| match entry { - Ok(entry) => { - let path = entry.path(); - if path.is_file() && path.extension().is_none() { - Some(Ok(path)) - } else { - None - } - } - Err(e) => Some(Err(e.into())), - }) - .collect::, eyre::Report>>(); - // Print the names of the keystore files - match keystore_files { - Ok(files) => { - // Print the names of the keystore files - for file in files { - if let Some(file_name) = file.file_name() { - if let Some(name) = file_name.to_str() { - println!("{}", name); - } - } - } - } - Err(e) => return Err(e), - } + WalletSubcommands::List(cmd) => { + cmd.run().await?; } WalletSubcommands::DerivePrivateKey { mnemonic, mnemonic_index } => { let phrase = Mnemonic::::new_from_phrase(mnemonic.as_str())?.to_phrase(); diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index e4009aa4af9f..d566a7bbeae1 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -2,7 +2,7 @@ use foundry_common::rpc::{next_http_rpc_endpoint, next_ws_rpc_endpoint}; use foundry_test_utils::{casttest, util::OutputExt}; -use std::{io::Write, path::Path}; +use std::{fs, io::Write, path::Path}; // tests `--help` is printed to std out casttest!(print_help, |_prj, cmd| { @@ -131,6 +131,27 @@ casttest!(wallet_sign_typed_data_file, |_prj, cmd| { assert_eq!(output.trim(), "0x06c18bdc8163219fddc9afaf5a0550e381326474bb757c86dc32317040cf384e07a2c72ce66c1a0626b6750ca9b6c035bf6f03e7ed67ae2d1134171e9085c0b51b"); }); +// tests that `cast wallet list` outputs the local accounts +casttest!(wallet_list_local_accounts, |prj, cmd| { + let keystore_path = prj.root().join("keystore"); + fs::create_dir_all(keystore_path).unwrap(); + cmd.set_current_dir(prj.root()); + + // empty results + cmd.cast_fuse().args(["wallet", "list", "--dir", "keystore"]); + let list_output = cmd.stdout_lossy(); + assert!(list_output.is_empty()); + + // create 10 wallets + cmd.cast_fuse().args(["wallet", "new", "keystore", "-n", "10", "--unsafe-password", "test"]); + cmd.stdout_lossy(); + + // test list new wallet + cmd.cast_fuse().args(["wallet", "list", "--dir", "keystore"]); + let list_output = cmd.stdout_lossy(); + assert_eq!(list_output.matches('\n').count(), 10); +}); + // tests that `cast estimate` is working correctly. casttest!(estimate_function_gas, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); diff --git a/crates/wallets/Cargo.toml b/crates/wallets/Cargo.toml index 501d0777f0fb..1ef26972dbd4 100644 --- a/crates/wallets/Cargo.toml +++ b/crates/wallets/Cargo.toml @@ -24,6 +24,7 @@ foundry-common.workspace = true async-trait = "0.1" clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } +derive_builder = "0.20.0" eyre.workspace = true hex = { workspace = true, features = ["serde"] } itertools.workspace = true diff --git a/crates/wallets/src/multi_wallet.rs b/crates/wallets/src/multi_wallet.rs index 21692eb04794..a818c2c5e5c9 100644 --- a/crates/wallets/src/multi_wallet.rs +++ b/crates/wallets/src/multi_wallet.rs @@ -4,6 +4,7 @@ use crate::{ }; use alloy_primitives::Address; use clap::Parser; +use derive_builder::Builder; use ethers_signers::Signer; use eyre::Result; use foundry_common::types::ToAlloy; @@ -87,7 +88,7 @@ macro_rules! create_hw_wallets { /// 5. Private Keys (cleartext in CLI) /// 6. Private Keys (interactively via secure prompt) /// 7. AWS KMS -#[derive(Clone, Debug, Default, Serialize, Parser)] +#[derive(Builder, Clone, Debug, Default, Serialize, Parser)] #[clap(next_help_heading = "Wallet options", about = None, long_about = None)] pub struct MultiWalletOpts { /// The sender accounts. diff --git a/crates/wallets/src/wallet_signer.rs b/crates/wallets/src/wallet_signer.rs index ed7b99a0c576..de3fff1cb8ca 100644 --- a/crates/wallets/src/wallet_signer.rs +++ b/crates/wallets/src/wallet_signer.rs @@ -57,6 +57,37 @@ impl WalletSigner { Ok(Self::Local(wallet)) } + pub async fn available_senders(&self, max: usize) -> Result> { + let mut senders = Vec::new(); + match self { + WalletSigner::Ledger(ledger) => { + for i in 0..max { + if let Ok(address) = + ledger.get_address_with_path(&LedgerHDPath::LedgerLive(i)).await + { + senders.push(address); + } + } + Ok(senders) + } + WalletSigner::Trezor(trezor) => { + for i in 0..max { + if let Ok(address) = + trezor.get_address_with_path(&TrezorHDPath::TrezorLive(i)).await + { + senders.push(address); + } + } + Ok(senders) + } + WalletSigner::Aws(aws) => { + senders.push(aws.address()); + Ok(senders) + } + _ => Ok(senders), + } + } + pub fn from_mnemonic( mnemonic: &str, passphrase: Option<&str>, From 981b9665c529f8d82c3bcead0d0a34afdbed6e90 Mon Sep 17 00:00:00 2001 From: grandizzy Date: Wed, 21 Feb 2024 08:27:31 +0200 Subject: [PATCH 2/7] Changes after review: - use annotations for builder defaults - handle Local signer in available_senders, return Ledger addresses for legacy derivation, add doc - fix condition to list files in keystore dir - simplify creation of keystore default directory --- crates/cast/bin/cmd/wallet/list.rs | 17 +++-------------- crates/wallets/src/multi_wallet.rs | 10 ++++++++++ crates/wallets/src/wallet_signer.rs | 18 +++++++++++++++++- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/crates/cast/bin/cmd/wallet/list.rs b/crates/cast/bin/cmd/wallet/list.rs index 4c92b633ad18..2bb90b79fc07 100644 --- a/crates/cast/bin/cmd/wallet/list.rs +++ b/crates/cast/bin/cmd/wallet/list.rs @@ -33,7 +33,7 @@ pub struct ListArgs { impl ListArgs { pub async fn run(self) -> Result<()> { // list local accounts as files in keystore dir, no need to unlock / provide password - if self.dir.is_some() || self.all || !self.ledger && !self.trezor && !self.aws { + if self.dir.is_some() || self.all || (!self.ledger && !self.trezor && !self.aws) { self.list_local_senders().await?; } @@ -43,17 +43,7 @@ impl ListArgs { .mnemonic_indexes(Some(vec![0])) .trezor(self.trezor || self.all) .aws(self.aws || self.all) - .froms(None) .interactives(0) - .private_keys(None) - .private_key(None) - .mnemonics(None) - .mnemonic_passphrases(None) - .hd_paths(None) - .keystore_paths(None) - .keystore_account_names(None) - .keystore_passwords(None) - .keystore_password_files(None) .build() .expect("build multi wallet"); @@ -102,9 +92,8 @@ impl ListArgs { async fn list_local_senders(&self) -> Result<()> { let keystore_path = self.dir.clone().unwrap_or_default(); let keystore_dir = if keystore_path.is_empty() { - let default_dir = Config::foundry_keystores_dir() - .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; - // Create the keystore directory if it doesn't exist + // Create the keystore default directory if it doesn't exist + let default_dir = Config::foundry_keystores_dir().unwrap(); fs::create_dir_all(&default_dir)?; default_dir } else { diff --git a/crates/wallets/src/multi_wallet.rs b/crates/wallets/src/multi_wallet.rs index a818c2c5e5c9..c70e3615255c 100644 --- a/crates/wallets/src/multi_wallet.rs +++ b/crates/wallets/src/multi_wallet.rs @@ -100,6 +100,7 @@ pub struct MultiWalletOpts { env = "ETH_FROM", num_args(0..), )] + #[builder(default = "None")] pub froms: Option>, /// Open an interactive prompt to enter your private key. @@ -116,6 +117,7 @@ pub struct MultiWalletOpts { /// Use the provided private keys. #[clap(long, help_heading = "Wallet options - raw", value_name = "RAW_PRIVATE_KEYS")] + #[builder(default = "None")] pub private_keys: Option>, /// Use the provided private key. @@ -125,14 +127,17 @@ pub struct MultiWalletOpts { conflicts_with = "private_keys", value_name = "RAW_PRIVATE_KEY" )] + #[builder(default = "None")] pub private_key: Option, /// Use the mnemonic phrases of mnemonic files at the specified paths. #[clap(long, alias = "mnemonic-paths", help_heading = "Wallet options - raw")] + #[builder(default = "None")] pub mnemonics: Option>, /// Use a BIP39 passphrases for the mnemonic. #[clap(long, help_heading = "Wallet options - raw", value_name = "PASSPHRASE")] + #[builder(default = "None")] pub mnemonic_passphrases: Option>, /// The wallet derivation path. @@ -144,6 +149,7 @@ pub struct MultiWalletOpts { help_heading = "Wallet options - raw", value_name = "PATH" )] + #[builder(default = "None")] pub hd_paths: Option>, /// Use the private key from the given mnemonic index. @@ -166,6 +172,7 @@ pub struct MultiWalletOpts { value_name = "PATHS", env = "ETH_KEYSTORE" )] + #[builder(default = "None")] pub keystore_paths: Option>, /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename @@ -177,6 +184,7 @@ pub struct MultiWalletOpts { env = "ETH_KEYSTORE_ACCOUNT", conflicts_with = "keystore_paths" )] + #[builder(default = "None")] pub keystore_account_names: Option>, /// The keystore password. @@ -188,6 +196,7 @@ pub struct MultiWalletOpts { requires = "keystore_paths", value_name = "PASSWORDS" )] + #[builder(default = "None")] pub keystore_passwords: Option>, /// The keystore password file path. @@ -200,6 +209,7 @@ pub struct MultiWalletOpts { value_name = "PATHS", env = "ETH_PASSWORD" )] + #[builder(default = "None")] pub keystore_password_files: Option>, /// Use a Ledger hardware wallet. diff --git a/crates/wallets/src/wallet_signer.rs b/crates/wallets/src/wallet_signer.rs index de3fff1cb8ca..240dbdf09cc6 100644 --- a/crates/wallets/src/wallet_signer.rs +++ b/crates/wallets/src/wallet_signer.rs @@ -57,9 +57,19 @@ impl WalletSigner { Ok(Self::Local(wallet)) } + /// Returns a list of addresses available to use with current signer + /// + /// - for Ledger and Trezor signers the number of addresses to retrieve is specified as argument + /// - the result for Ledger signers includes addresses available for both LedgerLive and Legacy + /// derivation paths + /// - for Local and AWS signers the result contains a single address pub async fn available_senders(&self, max: usize) -> Result> { let mut senders = Vec::new(); match self { + WalletSigner::Local(local) => { + senders.push(local.address()); + Ok(senders) + } WalletSigner::Ledger(ledger) => { for i in 0..max { if let Ok(address) = @@ -68,6 +78,13 @@ impl WalletSigner { senders.push(address); } } + for i in 0..max { + if let Ok(address) = + ledger.get_address_with_path(&LedgerHDPath::Legacy(i)).await + { + senders.push(address); + } + } Ok(senders) } WalletSigner::Trezor(trezor) => { @@ -84,7 +101,6 @@ impl WalletSigner { senders.push(aws.address()); Ok(senders) } - _ => Ok(senders), } } From c4ce461125b7da4357f10c0a10e8b252039b6222 Mon Sep 17 00:00:00 2001 From: grandizzy Date: Wed, 21 Feb 2024 17:25:43 +0200 Subject: [PATCH 3/7] Changes after review: use list_signers macro --- crates/cast/bin/cmd/wallet/list.rs | 53 +++++++++++++----------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/crates/cast/bin/cmd/wallet/list.rs b/crates/cast/bin/cmd/wallet/list.rs index 2bb90b79fc07..761977a6d09a 100644 --- a/crates/cast/bin/cmd/wallet/list.rs +++ b/crates/cast/bin/cmd/wallet/list.rs @@ -50,41 +50,32 @@ impl ListArgs { // max number of senders to be shown for ledger and trezor signers let max_senders = 3; - // List ledger accounts - match list_opts.ledgers().await { - Ok(signers) => { - self.list_senders(signers.unwrap_or_default(), max_senders, "Ledger").await? - } - Err(e) => { - if !self.all { - println!("{}", e) + macro_rules! list_signers { + ($signers:ident, $label: ident) => { + match $signers.await { + Ok(signers) => { + self.list_senders(signers.unwrap_or_default(), max_senders, $label).await? + } + Err(e) => { + if !self.all { + println!("{}", e) + } + } } - } + }; } - // List Trezor accounts - match list_opts.trezors().await { - Ok(signers) => { - self.list_senders(signers.unwrap_or_default(), max_senders, "Trezor").await? - } - Err(e) => { - if !self.all { - println!("{}", e) - } - } - } + let label = "Ledger"; + let signers = list_opts.ledgers(); + list_signers!(signers, label); - // List AWS accounts - match list_opts.aws_signers().await { - Ok(signers) => { - self.list_senders(signers.unwrap_or_default(), max_senders, "AWS").await? - } - Err(e) => { - if !self.all { - println!("{}", e) - } - } - } + let label = "Trezor"; + let signers = list_opts.trezors(); + list_signers!(signers, label); + + let label = "AWS"; + let signers = list_opts.aws_signers(); + list_signers!(signers, label); Ok(()) } From 38fc35692436fa46b8c1cf68577c913bcb339adc Mon Sep 17 00:00:00 2001 From: grandizzy Date: Wed, 21 Feb 2024 19:48:20 +0200 Subject: [PATCH 4/7] Changes after review: - remove help_headings - remove match and use ? as dir already exists - remove async from list_local_senders fn - move Ok(senders) at the bottom of available_senders fn - list_senders doesn't need match as available_senders cannot fail - make max_senders arg for ls command , default 3 --- crates/cast/bin/cmd/wallet/list.rs | 67 +++++++++++++---------------- crates/wallets/src/wallet_signer.rs | 5 +-- 2 files changed, 31 insertions(+), 41 deletions(-) diff --git a/crates/cast/bin/cmd/wallet/list.rs b/crates/cast/bin/cmd/wallet/list.rs index 761977a6d09a..0da7d9efbaaf 100644 --- a/crates/cast/bin/cmd/wallet/list.rs +++ b/crates/cast/bin/cmd/wallet/list.rs @@ -10,31 +10,35 @@ use foundry_wallets::{multi_wallet::MultiWalletOptsBuilder, WalletSigner}; pub struct ListArgs { /// List all the accounts in the keystore directory. /// Default keystore directory is used if no path provided. - #[clap(long, default_missing_value = "", num_args(0..=1), help_heading = "List local accounts")] + #[clap(long, default_missing_value = "", num_args(0..=1))] dir: Option, /// List accounts from a Ledger hardware wallet. - #[clap(long, short, help_heading = "List Ledger hardware wallet accounts")] + #[clap(long, short, group = "hw-wallets")] ledger: bool, /// List accounts from a Trezor hardware wallet. - #[clap(long, short, help_heading = "List Trezor hardware wallet accounts")] + #[clap(long, short, group = "hw-wallets")] trezor: bool, /// List accounts from AWS KMS. - #[clap(long, help_heading = "List AWS KMS account")] + #[clap(long)] aws: bool, /// List all configured accounts. - #[clap(long, help_heading = "List all accounts")] + #[clap(long, group = "hw-wallets")] all: bool, + + /// Max number of addresses to display from hardware wallets. + #[clap(long, short, default_value = "3", requires = "hw-wallets")] + max_senders: Option, } impl ListArgs { pub async fn run(self) -> Result<()> { // list local accounts as files in keystore dir, no need to unlock / provide password if self.dir.is_some() || self.all || (!self.ledger && !self.trezor && !self.aws) { - self.list_local_senders().await?; + let _ = self.list_local_senders(); } // Create options for multi wallet - ledger, trezor and AWS @@ -47,14 +51,16 @@ impl ListArgs { .build() .expect("build multi wallet"); - // max number of senders to be shown for ledger and trezor signers - let max_senders = 3; - macro_rules! list_signers { ($signers:ident, $label: ident) => { match $signers.await { Ok(signers) => { - self.list_senders(signers.unwrap_or_default(), max_senders, $label).await? + self.list_senders( + signers.unwrap_or_default(), + self.max_senders.unwrap(), + $label, + ) + .await? } Err(e) => { if !self.all { @@ -80,7 +86,7 @@ impl ListArgs { Ok(()) } - async fn list_local_senders(&self) -> Result<()> { + fn list_local_senders(&self) -> Result<()> { let keystore_path = self.dir.clone().unwrap_or_default(); let keystore_dir = if keystore_path.is_empty() { // Create the keystore default directory if it doesn't exist @@ -91,25 +97,17 @@ impl ListArgs { dunce::canonicalize(keystore_path)? }; - match std::fs::read_dir(keystore_dir) { - Ok(entries) => { - entries.flatten().for_each(|entry| { - let path = entry.path(); - if path.is_file() && path.extension().is_none() { - if let Some(file_name) = path.file_name() { - if let Some(name) = file_name.to_str() { - println!("{} (Local)", name); - } - } + // list files within keystore dir + std::fs::read_dir(keystore_dir)?.flatten().for_each(|entry| { + let path = entry.path(); + if path.is_file() && path.extension().is_none() { + if let Some(file_name) = path.file_name() { + if let Some(name) = file_name.to_str() { + println!("{} (Local)", name); } - }); - } - Err(e) => { - if !self.all { - println!("{}", e) } } - } + }); Ok(()) } @@ -121,16 +119,11 @@ impl ListArgs { label: &str, ) -> eyre::Result<()> { for signer in signers.iter() { - match signer.available_senders(max_senders).await { - Ok(senders) => { - senders.iter().for_each(|sender| println!("{} ({})", sender.to_alloy(), label)); - } - Err(e) => { - if !self.all { - println!("{}", e) - } - } - } + signer + .available_senders(max_senders) + .await? + .iter() + .for_each(|sender| println!("{} ({})", sender.to_alloy(), label)); } Ok(()) diff --git a/crates/wallets/src/wallet_signer.rs b/crates/wallets/src/wallet_signer.rs index 240dbdf09cc6..d71fbe1af7ff 100644 --- a/crates/wallets/src/wallet_signer.rs +++ b/crates/wallets/src/wallet_signer.rs @@ -68,7 +68,6 @@ impl WalletSigner { match self { WalletSigner::Local(local) => { senders.push(local.address()); - Ok(senders) } WalletSigner::Ledger(ledger) => { for i in 0..max { @@ -85,7 +84,6 @@ impl WalletSigner { senders.push(address); } } - Ok(senders) } WalletSigner::Trezor(trezor) => { for i in 0..max { @@ -95,13 +93,12 @@ impl WalletSigner { senders.push(address); } } - Ok(senders) } WalletSigner::Aws(aws) => { senders.push(aws.address()); - Ok(senders) } } + Ok(senders) } pub fn from_mnemonic( From 4ecb726acc06d60296891c7a87718afa8c4d481f Mon Sep 17 00:00:00 2001 From: grandizzy Date: Thu, 22 Feb 2024 08:01:20 +0200 Subject: [PATCH 5/7] Nit --- crates/cast/bin/cmd/wallet/list.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/cast/bin/cmd/wallet/list.rs b/crates/cast/bin/cmd/wallet/list.rs index 0da7d9efbaaf..7e280bcb511c 100644 --- a/crates/cast/bin/cmd/wallet/list.rs +++ b/crates/cast/bin/cmd/wallet/list.rs @@ -56,7 +56,7 @@ impl ListArgs { match $signers.await { Ok(signers) => { self.list_senders( - signers.unwrap_or_default(), + &signers.unwrap_or_default(), self.max_senders.unwrap(), $label, ) @@ -114,10 +114,10 @@ impl ListArgs { async fn list_senders( &self, - signers: Vec, + signers: &[WalletSigner], max_senders: usize, label: &str, - ) -> eyre::Result<()> { + ) -> Result<()> { for signer in signers.iter() { signer .available_senders(max_senders) From 6474ca3cf93d7e52dd52ed9d73acb7c081cc2d1b Mon Sep 17 00:00:00 2001 From: grandizzy Date: Thu, 22 Feb 2024 08:33:12 +0200 Subject: [PATCH 6/7] Remove list_senders fn, move logic in macro --- crates/cast/bin/cmd/wallet/list.rs | 41 ++++++++++-------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/crates/cast/bin/cmd/wallet/list.rs b/crates/cast/bin/cmd/wallet/list.rs index 7e280bcb511c..c808234320b6 100644 --- a/crates/cast/bin/cmd/wallet/list.rs +++ b/crates/cast/bin/cmd/wallet/list.rs @@ -3,7 +3,7 @@ use eyre::Result; use foundry_common::{fs, types::ToAlloy}; use foundry_config::Config; -use foundry_wallets::{multi_wallet::MultiWalletOptsBuilder, WalletSigner}; +use foundry_wallets::multi_wallet::MultiWalletOptsBuilder; /// CLI arguments for `cast wallet list`. #[derive(Clone, Debug, Parser)] @@ -51,16 +51,18 @@ impl ListArgs { .build() .expect("build multi wallet"); - macro_rules! list_signers { + // macro to print senders for a list of signers + macro_rules! list_senders { ($signers:ident, $label: ident) => { match $signers.await { Ok(signers) => { - self.list_senders( - &signers.unwrap_or_default(), - self.max_senders.unwrap(), - $label, - ) - .await? + for signer in signers.unwrap_or_default().iter() { + signer + .available_senders(self.max_senders.unwrap()) + .await? + .iter() + .for_each(|sender| println!("{} ({})", sender.to_alloy(), $label)); + } } Err(e) => { if !self.all { @@ -73,15 +75,15 @@ impl ListArgs { let label = "Ledger"; let signers = list_opts.ledgers(); - list_signers!(signers, label); + list_senders!(signers, label); let label = "Trezor"; let signers = list_opts.trezors(); - list_signers!(signers, label); + list_senders!(signers, label); let label = "AWS"; let signers = list_opts.aws_signers(); - list_signers!(signers, label); + list_senders!(signers, label); Ok(()) } @@ -111,21 +113,4 @@ impl ListArgs { Ok(()) } - - async fn list_senders( - &self, - signers: &[WalletSigner], - max_senders: usize, - label: &str, - ) -> Result<()> { - for signer in signers.iter() { - signer - .available_senders(max_senders) - .await? - .iter() - .for_each(|sender| println!("{} ({})", sender.to_alloy(), label)); - } - - Ok(()) - } } From a9cd6addda685c7deef5e4e72c43fcd5c5ae84c4 Mon Sep 17 00:00:00 2001 From: grandizzy Date: Thu, 22 Feb 2024 13:50:58 +0200 Subject: [PATCH 7/7] Nit macro --- crates/cast/bin/cmd/wallet/list.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/crates/cast/bin/cmd/wallet/list.rs b/crates/cast/bin/cmd/wallet/list.rs index c808234320b6..b0984d4ba861 100644 --- a/crates/cast/bin/cmd/wallet/list.rs +++ b/crates/cast/bin/cmd/wallet/list.rs @@ -53,7 +53,7 @@ impl ListArgs { // macro to print senders for a list of signers macro_rules! list_senders { - ($signers:ident, $label: ident) => { + ($signers:expr, $label:literal) => { match $signers.await { Ok(signers) => { for signer in signers.unwrap_or_default().iter() { @@ -73,17 +73,9 @@ impl ListArgs { }; } - let label = "Ledger"; - let signers = list_opts.ledgers(); - list_senders!(signers, label); - - let label = "Trezor"; - let signers = list_opts.trezors(); - list_senders!(signers, label); - - let label = "AWS"; - let signers = list_opts.aws_signers(); - list_senders!(signers, label); + list_senders!(list_opts.ledgers(), "Ledger"); + list_senders!(list_opts.trezors(), "Trezor"); + list_senders!(list_opts.aws_signers(), "AWS"); Ok(()) }