Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cast wallet list) issue #6958: Include HW wallets in cast wallet ls #7123

Merged
merged 7 commits into from
Feb 22, 2024

Conversation

grandizzy
Copy link
Collaborator

@grandizzy grandizzy commented Feb 14, 2024

  • added new options for list command, default list local keystore
Options:
      --dir [<DIR>]                List all the accounts in the keystore directory. Default keystore directory is used if no path provided
  -l, --ledger                     List accounts from a Ledger hardware wallet
  -t, --trezor                     List accounts from a Trezor hardware wallet
  -m, --max-senders <MAX_SENDERS>  Max number of addresses to display from hardware wallets [default: 3]
      --aws                        List accounts from AWS KMS
      --all                        List all configured accounts
  -h, --help                       Print help
  • for ledger display 3 addresses for each ledger live and legacy HD

  • for trezor display 3 addresses for trezor live HD path

  • for AWS display all keys configured

  • for local keystore list files in dir

  • unit tests

Note: I don't have Trezor / AWS configured

Things to discuss:

  • chain id is hardcoded to 0 and not determined from rpc url, should it be?
  • number of accounts to display are hardcoded to 2, should this be configurable?
  • for local accounts - rn it creates the keystore directory if it doesn't exist, I'd argue this shouldn't be part of list command?

Motivation

Solution

@grandizzy grandizzy changed the title issue #6958: Include HW wallets in cast wallet ls feat(cast wallet list) issue #6958: Include HW wallets in cast wallet ls Feb 14, 2024
Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is great.

I want to make this more like a utility function that this command then consumes

crates/cast/bin/cmd/wallet/mod.rs Outdated Show resolved Hide resolved
crates/cast/bin/cmd/wallet/mod.rs Outdated Show resolved Hide resolved
crates/cast/bin/cmd/wallet/mod.rs Outdated Show resolved Hide resolved
Some(Ok(path))
} else {
None
WalletSubcommands::List { dir, ledger, trezor, aws, all } => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be useful in the wallet crate @klkvr

like a struct that looks up all configured sources and returns the results

so I think we want to do this a bit differently,
the helper struct should gather all the info and then we print them here in the command

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, happy to accommodate such after things moved to new crate or leave up to @klkvr lmk what you guys decide

Copy link
Member

@klkvr klkvr Feb 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@grandizzy we should merge #7141 soon which will change structure of foundry-wallets, so I think for now this PR is somewhat blocked, but feel free to take a look at the code structure and entities in #7141 as those are unlikely to change much

once its merged I believe we can extend WalletSigner enum from there with async fn available_senders(&self, max: usize) -> Vec<Address> and then use it for the list command

you should be able to obtain WalletSigners either by calling utils functions directly or by constructing MultiWalletOpts with hw wallets opts + keystore paths, and then calling get_multi_wallet on it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, checked and makes sense (I might need to derive builder for MultiWalletOpts), will prepare changes and make a PR once #7141 in master

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I redid PR with new wallet structure, pretty tidy now imo, please review

Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

few more nits.

pending @klkvr and @DaniPopes

@@ -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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm personally okay with this

any objections @DaniPopes ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fine

Comment on lines 48 to 56
.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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think these are required

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

per builder doc The build method returns Result<T, E>, where T is the struct you started with and E is a generated builder error type. It returns Err if you didn’t initialize all fields and no default values were provided. I removed the code and made builder defaults as annotations

@@ -57,6 +57,37 @@ impl WalletSigner {
Ok(Self::Local(wallet))
}

pub async fn available_senders(&self, max: usize) -> Result<Vec<ethers_core::types::Address>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add one line doc

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. Also updated impl to handle Local signer too and to return Ledger addresses for legacy derivation path as well

Comment on lines 103 to 106
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."))?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be if let Some else instead

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I simplified handle logic for creating default dir, need the unwrap_or_default as this code handles the cast wallet ls command where dir is None

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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, this will always be true if any leder,trezor,aws argument is missing

should this be || (!self.ledger && !self.trezor && !self.aws) instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, changed

- 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
Copy link
Member

@klkvr klkvr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you, lgtm! just a nit

Comment on lines 53 to 88
// 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)
}
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice if we could reduce duplication here via macro/fn

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, good point, changed in c4ce461

aws: bool,

/// List all configured accounts.
#[clap(long, help_heading = "List all accounts")]
Copy link
Member

@DaniPopes DaniPopes Feb 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove help_headings, these don't need sections for each option

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed in 38fc356

dunce::canonicalize(keystore_path)?
};

match std::fs::read_dir(keystore_dir) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove match, use ? as it already exists

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed in 38fc356

Ok(())
}

async fn list_local_senders(&self) -> Result<()> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't need async

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated in 38fc356

.expect("build multi wallet");

// max number of senders to be shown for ledger and trezor signers
let max_senders = 3;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be an argument?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, made this a wallet list argument, grouped with hardware wallets and all option 38fc356

match self {
WalletSigner::Local(local) => {
senders.push(local.address());
Ok(senders)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move Ok(senders) to the bottom outside of the match

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 38fc356

/// - 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<Vec<ethers_core::types::Address>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can't fail

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed list_senders fn to reflect this in 38fc356


async fn list_senders(
&self,
signers: Vec<WalletSigner>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
signers: Vec<WalletSigner>,
signers: &[WalletSigner],

Copy link
Collaborator Author

@grandizzy grandizzy Feb 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure exactly what to change here, can you pls elaborate? changed in 4ecb726 but not needed anymore as moved list_senders logic in macro 6474ca3

@@ -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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fine

- 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
Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

last style nit

otherwise lgtm
pending @DaniPopes

Comment on lines 76 to 86
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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");


// macro to print senders for a list of signers
macro_rules! list_senders {
($signers:ident, $label: ident) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
($signers:ident, $label: ident) => {
($signers:expr, $label:literal) => {

@grandizzy
Copy link
Collaborator Author

last style nit

otherwise lgtm pending @DaniPopes

yep, nicer :) added in a9cd6ad

Copy link
Member

@DaniPopes DaniPopes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@DaniPopes DaniPopes merged commit 57815e0 into foundry-rs:master Feb 22, 2024
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants