diff --git a/crates/dojo-world/src/metadata.rs b/crates/dojo-world/src/metadata.rs index 9726192215..a3c2c7d1ca 100644 --- a/crates/dojo-world/src/metadata.rs +++ b/crates/dojo-world/src/metadata.rs @@ -113,7 +113,6 @@ impl Environment { self.private_key.as_deref() } - #[allow(dead_code)] pub fn keystore_path(&self) -> Option<&str> { self.keystore_path.as_deref() } diff --git a/crates/sozo/src/commands/options/account.rs b/crates/sozo/src/commands/options/account.rs index f529e717d4..2afff4fddd 100644 --- a/crates/sozo/src/commands/options/account.rs +++ b/crates/sozo/src/commands/options/account.rs @@ -14,12 +14,16 @@ use super::{ #[derive(Debug, Args)] #[command(next_help_heading = "Account options")] +// INVARIANT: +// - For commandline: we can either specify `private_key` or `keystore_path` along with +// `keystore_password`. This is enforced by Clap. +// - For `Scarb.toml`: if both private_key and keystore are specified in `Scarb.toml` private_key +// will take priority pub struct AccountOptions { #[arg(long, env = DOJO_ACCOUNT_ADDRESS_ENV_VAR)] pub account_address: Option, #[arg(long, env = DOJO_PRIVATE_KEY_ENV_VAR)] - #[arg(requires = "account_address")] #[arg(conflicts_with = "keystore_path")] #[arg(help_heading = "Signer options - RAW")] #[arg(help = "The raw private key associated with the account contract.")] @@ -33,7 +37,6 @@ pub struct AccountOptions { #[arg(long = "password", env = DOJO_KEYSTORE_PASSWORD_ENV_VAR)] #[arg(value_name = "PASSWORD")] - #[arg(requires = "keystore_path")] #[arg(help_heading = "Signer options - KEYSTORE")] #[arg(help = "The keystore password. Used with --keystore.")] pub keystore_password: Option, @@ -72,7 +75,11 @@ impl AccountOptions { ))); } - if let Some(path) = &self.keystore_path { + if let Some(path) = &self + .keystore_path + .as_deref() + .or_else(|| env_metadata.and_then(|env| env.keystore_path())) + { if let Some(password) = self .keystore_password .as_deref() @@ -105,3 +112,214 @@ impl AccountOptions { } } } + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use clap::Parser; + use starknet::signers::{LocalWallet, Signer, SigningKey}; + use starknet_crypto::FieldElement; + + use super::{ + AccountOptions, DOJO_ACCOUNT_ADDRESS_ENV_VAR, DOJO_KEYSTORE_PASSWORD_ENV_VAR, + DOJO_PRIVATE_KEY_ENV_VAR, + }; + + #[derive(clap::Parser, Debug)] + struct Command { + #[clap(flatten)] + pub account: AccountOptions, + } + + #[test] + fn account_address_read_from_env_variable() { + std::env::set_var(DOJO_ACCOUNT_ADDRESS_ENV_VAR, "0x0"); + + let cmd = Command::parse_from([""]); + assert_eq!(cmd.account.account_address, Some(FieldElement::from_hex_be("0x0").unwrap())); + } + + #[test] + fn private_key_read_from_env_variable() { + std::env::set_var(DOJO_PRIVATE_KEY_ENV_VAR, "private_key"); + + let cmd = Command::parse_from(["sozo", "--account-address", "0x0"]); + assert_eq!(cmd.account.private_key, Some("private_key".to_owned())); + } + + #[test] + fn keystore_path_read_from_env_variable() { + std::env::set_var(DOJO_KEYSTORE_PASSWORD_ENV_VAR, "keystore_password"); + + let cmd = Command::parse_from(["sozo", "--keystore", "./some/path"]); + assert_eq!(cmd.account.keystore_password, Some("keystore_password".to_owned())); + } + + #[test] + fn account_address_from_args() { + let env_metadata = dojo_world::metadata::Environment::default(); + + let cmd = Command::parse_from(["sozo", "--account-address", "0x0"]); + assert_eq!( + cmd.account.account_address(Some(&env_metadata)).unwrap(), + FieldElement::from_hex_be("0x0").unwrap() + ); + } + + #[test] + fn account_address_from_env_metadata() { + let env_metadata = dojo_world::metadata::Environment { + account_address: Some("0x0".to_owned()), + ..Default::default() + }; + + let cmd = Command::parse_from([""]); + assert_eq!( + cmd.account.account_address(Some(&env_metadata)).unwrap(), + FieldElement::from_hex_be("0x0").unwrap() + ); + } + + #[test] + fn account_address_from_both() { + let env_metadata = dojo_world::metadata::Environment { + account_address: Some("0x0".to_owned()), + ..Default::default() + }; + + let cmd = Command::parse_from(["sozo", "--account-address", "0x1"]); + assert_eq!( + cmd.account.account_address(Some(&env_metadata)).unwrap(), + FieldElement::from_hex_be("0x1").unwrap() + ); + } + + #[test] + fn account_address_from_neither() { + let env_metadata = dojo_world::metadata::Environment::default(); + + let cmd = Command::parse_from([""]); + assert!(cmd.account.account_address(Some(&env_metadata)).is_err()); + } + + #[tokio::test] + async fn private_key_from_args() { + let env_metadata = dojo_world::metadata::Environment::default(); + let private_key = "0x1"; + + let cmd = + Command::parse_from(["sozo", "--account-address", "0x0", "--private-key", private_key]); + let result_wallet = cmd.account.signer(Some(&env_metadata)).unwrap(); + let expected_wallet = LocalWallet::from_signing_key(SigningKey::from_secret_scalar( + FieldElement::from_str(private_key).unwrap(), + )); + + let result_public_key = result_wallet.get_public_key().await.unwrap(); + let expected_public_key = expected_wallet.get_public_key().await.unwrap(); + assert!(result_public_key.scalar() == expected_public_key.scalar()); + } + + #[tokio::test] + async fn private_key_from_env_metadata() { + let private_key = "0x1"; + let env_metadata = dojo_world::metadata::Environment { + private_key: Some(private_key.to_owned()), + ..Default::default() + }; + + let cmd = Command::parse_from(["sozo", "--account-address", "0x0"]); + let result_wallet = cmd.account.signer(Some(&env_metadata)).unwrap(); + let expected_wallet = LocalWallet::from_signing_key(SigningKey::from_secret_scalar( + FieldElement::from_str(private_key).unwrap(), + )); + + let result_public_key = result_wallet.get_public_key().await.unwrap(); + let expected_public_key = expected_wallet.get_public_key().await.unwrap(); + assert!(result_public_key.scalar() == expected_public_key.scalar()); + } + + #[tokio::test] + async fn keystore_path_and_keystore_password_from_args() { + let keystore_path = "./tests/test_data/keystore/test.json"; + let keystore_password = "dojoftw"; + let private_key = "0x1"; + let env_metadata = dojo_world::metadata::Environment::default(); + + let cmd = Command::parse_from([ + "sozo", + "--keystore", + keystore_path, + "--password", + keystore_password, + ]); + let result_wallet = cmd.account.signer(Some(&env_metadata)).unwrap(); + let expected_wallet = LocalWallet::from_signing_key(SigningKey::from_secret_scalar( + FieldElement::from_str(private_key).unwrap(), + )); + + let result_public_key = result_wallet.get_public_key().await.unwrap(); + let expected_public_key = expected_wallet.get_public_key().await.unwrap(); + assert!(result_public_key.scalar() == expected_public_key.scalar()); + } + + #[tokio::test] + async fn keystore_path_from_env_metadata() { + let keystore_path = "./tests/test_data/keystore/test.json"; + let keystore_password = "dojoftw"; + + let private_key = "0x1"; + let env_metadata = dojo_world::metadata::Environment { + keystore_path: Some(keystore_path.to_owned()), + ..Default::default() + }; + + let cmd = Command::parse_from(["sozo", "--password", keystore_password]); + let result_wallet = cmd.account.signer(Some(&env_metadata)).unwrap(); + let expected_wallet = LocalWallet::from_signing_key(SigningKey::from_secret_scalar( + FieldElement::from_str(private_key).unwrap(), + )); + + let result_public_key = result_wallet.get_public_key().await.unwrap(); + let expected_public_key = expected_wallet.get_public_key().await.unwrap(); + assert!(result_public_key.scalar() == expected_public_key.scalar()); + } + + #[tokio::test] + async fn keystore_password_from_env_metadata() { + let keystore_path = "./tests/test_data/keystore/test.json"; + let keystore_password = "dojoftw"; + let private_key = "0x1"; + + let env_metadata = dojo_world::metadata::Environment { + keystore_password: Some(keystore_password.to_owned()), + ..Default::default() + }; + + let cmd = Command::parse_from(["sozo", "--keystore", keystore_path]); + let result_wallet = cmd.account.signer(Some(&env_metadata)).unwrap(); + let expected_wallet = LocalWallet::from_signing_key(SigningKey::from_secret_scalar( + FieldElement::from_str(private_key).unwrap(), + )); + + let result_public_key = result_wallet.get_public_key().await.unwrap(); + let expected_public_key = expected_wallet.get_public_key().await.unwrap(); + assert!(result_public_key.scalar() == expected_public_key.scalar()); + } + + #[test] + fn dont_allow_both_private_key_and_keystore() { + let keystore_path = "./tests/test_data/keystore/test.json"; + let private_key = "0x1"; + assert!( + Command::try_parse_from([ + "sozo", + "--keystore", + keystore_path, + "--private_key", + private_key, + ]) + .is_err() + ); + } +} diff --git a/crates/sozo/src/commands/options/starknet.rs b/crates/sozo/src/commands/options/starknet.rs index 93ba841dcc..011b04ae9e 100644 --- a/crates/sozo/src/commands/options/starknet.rs +++ b/crates/sozo/src/commands/options/starknet.rs @@ -54,7 +54,15 @@ mod tests { } #[test] - fn url_exist_in_env_metadata_but_env_doesnt() { + fn url_read_from_env_variable() { + std::env::set_var(STARKNET_RPC_URL_ENV_VAR, ENV_RPC); + + let cmd = Command::parse_from([""]); + assert_eq!(cmd.options.url(None).unwrap().as_str(), ENV_RPC); + } + + #[test] + fn url_exist_in_env_but_not_in_args() { let env_metadata = dojo_world::metadata::Environment { rpc_url: Some(METADATA_RPC.into()), ..Default::default() @@ -65,26 +73,26 @@ mod tests { } #[test] - fn url_doesnt_exist_in_env_metadata_but_env_does() { - std::env::set_var(STARKNET_RPC_URL_ENV_VAR, ENV_RPC); + fn url_doesnt_exist_in_env_but_exist_in_args() { let env_metadata = dojo_world::metadata::Environment::default(); - let cmd = Command::parse_from([""]); + let cmd = Command::parse_from(["sozo", "--rpc-url", ENV_RPC]); + assert_eq!(cmd.options.url(Some(&env_metadata)).unwrap().as_str(), ENV_RPC); } #[test] - fn exists_in_both() { - std::env::set_var(STARKNET_RPC_URL_ENV_VAR, ENV_RPC); + fn url_exists_in_both() { let env_metadata = dojo_world::metadata::Environment { rpc_url: Some(METADATA_RPC.into()), ..Default::default() }; - let cmd = Command::parse_from([""]); + + let cmd = Command::parse_from(["sozo", "--rpc-url", ENV_RPC]); assert_eq!(cmd.options.url(Some(&env_metadata)).unwrap().as_str(), ENV_RPC); } #[test] - fn exists_in_neither() { + fn url_exists_in_neither() { let env_metadata = dojo_world::metadata::Environment::default(); let cmd = Command::parse_from([""]); assert_eq!(cmd.options.url(Some(&env_metadata)).unwrap().as_str(), DEFAULT_RPC); diff --git a/crates/sozo/tests/test_data/keystore/test.json b/crates/sozo/tests/test_data/keystore/test.json new file mode 100644 index 0000000000..afcf956282 --- /dev/null +++ b/crates/sozo/tests/test_data/keystore/test.json @@ -0,0 +1 @@ +{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"86dcdc44db46801dd2df660e2242926c"},"ciphertext":"75c93de54b0d29c9d5ecce255478cfb52ce6c82752af0f4f1e353be91ab93f2a","kdf":"scrypt","kdfparams":{"dklen":32,"n":8192,"p":1,"r":8,"salt":"e9342c34144d65f40c5ecee338cea267f96049e9795fa52d1d9cb96923fce998"},"mac":"35252658d371ead5aa890b15a6a4cdfde427561208d2c1e84978d107217faa0b"},"id":"f10cdaf9-0f8f-44df-a906-4285f2ba798d","version":3} \ No newline at end of file