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 new - enable default keystore #9201

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/cast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ foundry-test-utils.workspace = true

async-trait.workspace = true
divan.workspace = true
dirs = "5"

[features]
default = ["rustls", "jemalloc"]
Expand Down
133 changes: 74 additions & 59 deletions crates/cast/bin/cmd/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ pub enum WalletSubcommands {
/// Triggers a hidden password prompt for the JSON keystore.
///
/// Deprecated: prompting for a hidden password is now the default.
#[arg(long, short, requires = "path", conflicts_with = "unsafe_password")]
#[arg(long, short, conflicts_with = "unsafe_password")]
password: bool,

/// Password for the JSON keystore in cleartext.
///
/// This is UNSAFE to use and we recommend using the --password.
#[arg(long, requires = "path", env = "CAST_PASSWORD", value_name = "PASSWORD")]
#[arg(long, env = "CAST_PASSWORD", value_name = "PASSWORD")]
unsafe_password: Option<String>,

/// Number of wallets to generate.
Expand Down Expand Up @@ -211,78 +211,93 @@ pub enum WalletSubcommands {
impl WalletSubcommands {
pub async fn run(self) -> Result<()> {
match self {
Self::New { path, unsafe_password, number, .. } => {
Self::New { path, password, unsafe_password, number, .. } => {
let mut rng = thread_rng();

let mut json_values = if shell::is_json() { Some(vec![]) } else { None };
if let Some(path) = path {
let path = match dunce::canonicalize(path.clone()) {
Ok(path) => path,
// If the path doesn't exist, it will fail to be canonicalized,
// so we attach more context to the error message.

// Determine the path
let path = if let Some(path) = path {
match dunce::canonicalize(path.clone()) {
Ok(path) => {
if !path.is_dir() {
// we require path to be an existing directory
eyre::bail!("`{}` is not a directory", path.display());
}
Some(path)
}
Err(e) => {
eyre::bail!("If you specified a directory, please make sure it exists, or create it before running `cast wallet new <DIR>`.\n{path} is not a directory.\nError: {}", e);
}
};
if !path.is_dir() {
// we require path to be an existing directory
eyre::bail!("`{}` is not a directory", path.display());
}
} else if unsafe_password.is_some() || password {
let path = Config::foundry_keystores_dir().ok_or_else(|| {
eyre::eyre!("Could not find the default keystore directory.")
})?;
fs::create_dir_all(&path)?;
Some(path)
} else {
None
};

let password = if let Some(password) = unsafe_password {
password
} else {
// if no --unsafe-password was provided read via stdin
rpassword::prompt_password("Enter secret: ")?
};

for _ in 0..number {
let (wallet, uuid) = PrivateKeySigner::new_keystore(
&path,
&mut rng,
password.clone(),
None,
)?;

if let Some(json) = json_values.as_mut() {
json.push(json!({
"address": wallet.address().to_checksum(None),
"path": format!("{}", path.join(uuid).display()),
}
));
match path {
Some(path) => {
let password = if let Some(password) = unsafe_password {
password
} else {
sh_println!(
"Created new encrypted keystore file: {}",
path.join(uuid).display()
// if no --unsafe-password was provided read via stdin
rpassword::prompt_password("Enter secret: ")?
};

for _ in 0..number {
let (wallet, uuid) = PrivateKeySigner::new_keystore(
&path,
&mut rng,
password.clone(),
None,
)?;
sh_println!("Address: {}", wallet.address().to_checksum(None))?;

if let Some(json) = json_values.as_mut() {
json.push(json!({
"address": wallet.address().to_checksum(None),
"path": format!("{}", path.join(uuid).display()),
}
));
} else {
sh_println!(
"Created new encrypted keystore file: {}",
path.join(uuid).display()
)?;
sh_println!("Address: {}", wallet.address().to_checksum(None))?;
}
}
}

if let Some(json) = json_values.as_ref() {
sh_println!("{}", serde_json::to_string_pretty(json)?)?;
}
} else {
for _ in 0..number {
let wallet = PrivateKeySigner::random_with(&mut rng);

if let Some(json) = json_values.as_mut() {
json.push(json!({
"address": wallet.address().to_checksum(None),
"private_key": format!("0x{}", hex::encode(wallet.credential().to_bytes())),
}))
} else {
sh_println!("Successfully created new keypair.")?;
sh_println!("Address: {}", wallet.address().to_checksum(None))?;
sh_println!(
"Private key: 0x{}",
hex::encode(wallet.credential().to_bytes())
)?;
if let Some(json) = json_values.as_ref() {
sh_println!("{}", serde_json::to_string_pretty(json)?)?;
}
}
None => {
for _ in 0..number {
let wallet = PrivateKeySigner::random_with(&mut rng);

if let Some(json) = json_values.as_mut() {
json.push(json!({
"address": wallet.address().to_checksum(None),
"private_key": format!("0x{}", hex::encode(wallet.credential().to_bytes())),
}))
} else {
sh_println!("Successfully created new keypair.")?;
sh_println!("Address: {}", wallet.address().to_checksum(None))?;
sh_println!(
"Private key: 0x{}",
hex::encode(wallet.credential().to_bytes())
)?;
}
}

if let Some(json) = json_values.as_ref() {
sh_println!("{}", serde_json::to_string_pretty(json)?)?;
if let Some(json) = json_values.as_ref() {
sh_println!("{}", serde_json::to_string_pretty(json)?)?;
}
}
}
}
Expand Down
29 changes: 29 additions & 0 deletions crates/cast/tests/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,35 @@ Created new encrypted keystore file: [..]
]);
});

// tests that we can create a new wallet with default keystore location
casttest!(new_wallet_default_keystore, |_prj, cmd| {
cmd.args(["wallet", "new", "--unsafe-password", "test"]).assert_success().stdout_eq(str![[
r#"
Created new encrypted keystore file: [..]
[ADDRESS]

"#
]]);

// Verify the default keystore directory was created
let keystore_path = dirs::home_dir().unwrap().join(".foundry").join("keystores");
assert!(keystore_path.exists());
assert!(keystore_path.is_dir());
});

// tests that we can outputting multiple keys without a keystore path
casttest!(new_wallet_multiple_keys, |_prj, cmd| {
cmd.args(["wallet", "new", "-n", "2"]).assert_success().stdout_eq(str![[r#"
Successfully created new keypair.
Address: [..]
Private key: [..]
Successfully created new keypair.
Address: [..]
Private key: [..]

"#]]);
});

// tests that we can get the address of a keystore file
casttest!(wallet_address_keystore_with_password_file, |_prj, cmd| {
let keystore_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/keystore");
Expand Down
Loading