From 41ffd302e61e34a8f65490f48e65670f743dfd5b Mon Sep 17 00:00:00 2001 From: devnet0x Date: Mon, 23 Oct 2023 10:02:35 -0300 Subject: [PATCH] Add delete account feature to remove an account from the accounts file. (#743) Closes #498 ## Introduced changes Add account delete feature to remove an account from a network in the accounts file. ## Breaking changes N/A ## Checklist - [ X] Linked relevant issue - [ X] Updated relevant documentation - [ X] Added relevant tests - [ X] Performed self-review of the code - [ X] Added changes to `CHANGELOG.md` --------- Co-authored-by: Wojciech Szymczyk Co-authored-by: Kamil Jankowski --- CHANGELOG.md | 1 + Cargo.lock | 121 ++++++- crates/cast/Cargo.toml | 1 + crates/cast/src/helpers/response_structs.rs | 6 + crates/cast/src/main.rs | 25 +- .../src/starknet_commands/account/delete.rs | 114 ++++++ .../cast/src/starknet_commands/account/mod.rs | 3 + crates/cast/tests/e2e/account/delete.rs | 329 ++++++++++++++++++ crates/cast/tests/e2e/account/mod.rs | 1 + docs/src/SUMMARY.md | 1 + docs/src/appendix/cast.md | 1 + docs/src/appendix/cast/account/account.md | 1 + docs/src/appendix/cast/account/delete.md | 21 ++ docs/src/starknet/account.md | 24 ++ 14 files changed, 647 insertions(+), 2 deletions(-) create mode 100644 crates/cast/src/starknet_commands/account/delete.rs create mode 100644 crates/cast/tests/e2e/account/delete.rs create mode 100644 docs/src/appendix/cast/account/delete.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ec6885d26..24705eabeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### Added - `show-config` subcommand to display currently used configuration +- `account delete` command for removing accounts from the accounts file ## [0.8.3] - 2023-10-17 diff --git a/Cargo.lock b/Cargo.lock index 28c29d4ee8..30caaed6ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1072,6 +1072,7 @@ dependencies = [ "indoc", "primitive-types", "project-root", + "promptly", "rand", "reqwest", "rpassword", @@ -1202,6 +1203,17 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +[[package]] +name = "clipboard-win" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -1340,7 +1352,7 @@ dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", + "memoffset 0.9.0", "scopeguard", ] @@ -1615,6 +1627,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "equivalent" version = "1.0.1" @@ -1642,6 +1660,16 @@ dependencies = [ "libc", ] +[[package]] +name = "error-code" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" +dependencies = [ + "libc", + "str-buf", +] + [[package]] name = "eth-keystore" version = "0.5.0" @@ -1697,6 +1725,17 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +[[package]] +name = "fd-lock" +version = "3.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "fixed-hash" version = "0.8.0" @@ -2551,6 +2590,15 @@ version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "memoffset" version = "0.9.0" @@ -2648,6 +2696,28 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + [[package]] name = "nom" version = "7.1.3" @@ -3133,6 +3203,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bccbff07d5ed689c4087d20d7307a52ab6141edeedf487c3876a55b86cf63df" +[[package]] +name = "promptly" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9acbc6c5a5b029fe58342f58445acb00ccfe24624e538894bc2f04ce112980ba" +dependencies = [ + "rustyline", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -3154,6 +3233,16 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.8.5" @@ -3461,6 +3550,30 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "rustyline" +version = "9.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db7826789c0e25614b03e5a54a0717a86f9ff6e6e5247f92b369472869320039" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "clipboard-win", + "dirs-next", + "fd-lock", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "scopeguard", + "smallvec", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi", +] + [[package]] name = "ryu" version = "1.0.15" @@ -4113,6 +4226,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "str-buf" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" + [[package]] name = "string_cache" version = "0.8.7" diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index 1d93983055..674ab0701f 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -21,6 +21,7 @@ primitive-types = "0.12.1" shellexpand = "3.1.0" toml = "0.7.6" rpassword = "7.2.0" +promptly = "0.3.1" [dev-dependencies] ctor.workspace = true diff --git a/crates/cast/src/helpers/response_structs.rs b/crates/cast/src/helpers/response_structs.rs index 21ae204986..0202021c94 100644 --- a/crates/cast/src/helpers/response_structs.rs +++ b/crates/cast/src/helpers/response_structs.rs @@ -36,6 +36,12 @@ pub struct AccountAddResponse { pub add_profile: String, } +#[derive(Serialize)] +pub struct AccountDeleteResponse { + pub result: String, + pub scarb_result: String, +} + #[derive(Serialize)] pub struct MulticallNewResponse { pub path: Utf8PathBuf, diff --git a/crates/cast/src/main.rs b/crates/cast/src/main.rs index d9e3173167..06f79e6b65 100644 --- a/crates/cast/src/main.rs +++ b/crates/cast/src/main.rs @@ -8,7 +8,10 @@ use anyhow::{anyhow, Result}; use camino::Utf8PathBuf; use cast::helpers::constants::{DEFAULT_ACCOUNTS_FILE, DEFAULT_MULTICALL_CONTENTS}; use cast::helpers::scarb_utils::{parse_scarb_config, CastConfig}; -use cast::{get_account, get_block_id, get_chain_id, get_provider, print_command_result}; +use cast::{ + chain_id_to_network_name, get_account, get_block_id, get_chain_id, get_provider, + print_command_result, +}; use clap::{Parser, Subcommand}; mod starknet_commands; @@ -276,6 +279,26 @@ async fn main() -> Result<()> { print_command_result("account deploy", &mut result, cli.int_format, cli.json)?; Ok(()) } + account::Commands::Delete(delete) => { + config.account = delete + .name + .ok_or_else(|| anyhow!("required argument --name not provided"))?; + let network_name = match delete.network { + Some(network) => network, + None => chain_id_to_network_name(get_chain_id(&provider).await?), + }; + + let mut result = starknet_commands::account::delete::delete( + &config.account, + &config.accounts_file, + &cli.path_to_scarb_toml, + delete.delete_profile, + &network_name, + ); + + print_command_result("account delete", &mut result, cli.int_format, cli.json)?; + Ok(()) + } }, Commands::ShowConfig(_) => { let mut result = starknet_commands::show_config::show_config( diff --git a/crates/cast/src/starknet_commands/account/delete.rs b/crates/cast/src/starknet_commands/account/delete.rs new file mode 100644 index 0000000000..6e1c14a991 --- /dev/null +++ b/crates/cast/src/starknet_commands/account/delete.rs @@ -0,0 +1,114 @@ +use anyhow::{anyhow, bail, Context, Result}; +use camino::Utf8PathBuf; +use cast::helpers::response_structs::AccountDeleteResponse; +use cast::helpers::scarb_utils::get_scarb_manifest; +use clap::Args; +use promptly::prompt; +use serde_json::Map; +use std::fs::File; +use std::io::Read; +use std::io::Write; + +#[derive(Args, Debug)] +#[command(about = "Delete account information from the accounts file")] +pub struct Delete { + /// Name of the account to be deleted + #[clap(short, long)] + pub name: Option, + + /// If passed with false, Scarb profile won't be removed + #[clap(long, num_args = 1, default_value = "true")] + pub delete_profile: Option, + + /// Network where the account exists; defaults to network of rpc node + #[clap(long)] + pub network: Option, +} + +#[allow(clippy::too_many_arguments)] +pub fn delete( + name: &str, + path: &Utf8PathBuf, + path_to_scarb_toml: &Option, + delete_profile: Option, + network_name: &str, +) -> Result { + let contents = std::fs::read_to_string(path.clone()).context("Couldn't read accounts file")?; + let items: serde_json::Value = serde_json::from_str(&contents) + .map_err(|_| anyhow!("Failed to parse accounts file at {path}"))?; + + if items[&network_name].is_null() { + bail!("No accounts defined for network {}", network_name); + } + if items[&network_name][&name].is_null() { + bail!("Account with name {name} does not exist") + } + + let mut items: Map = + serde_json::from_str(&contents).expect("failed to read file { path }"); + + // Let's ask confirmation + let prompt_text = + format!("Do you want to remove the account {name} deployed to network {network_name} from local file {path}? (Y/n)"); + let input: String = prompt(prompt_text)?; + + if !input.starts_with('Y') { + bail!("Delete aborted"); + } + + // get to the nested object "nested" + let nested = items + .get_mut(network_name) + .expect("can't find network") + .as_object_mut() + .expect("invalid network format"); + + // now remove the child from there + nested.remove(name); + + std::fs::write(path.clone(), serde_json::to_string_pretty(&items).unwrap())?; + + let mut scarb_result = "Account not removed from Scarb.toml".to_string(); + // delete profile if delete_profile is true or not passed + if delete_profile == Some(true) { + let manifest_path = match path_to_scarb_toml.clone() { + Some(path) => path, + None => get_scarb_manifest().context("Failed to obtain manifest path from scarb")?, + }; + let mut toml_content = String::new(); + let mut file = File::open(manifest_path.clone()).expect("Failed to open file"); + file.read_to_string(&mut toml_content) + .expect("Failed to read file"); + + // Parse the TOML content + let mut parsed_toml: toml::Value = + toml::de::from_str(&toml_content).expect("Failed to parse TOML"); + + // Remove the nested section from the Value object. + if let Some(table) = parsed_toml + .get_mut("tool") + .and_then(toml::Value::as_table_mut) + { + if let Some(nested_table) = table.get_mut("sncast").and_then(toml::Value::as_table_mut) + { + if nested_table.remove(name).is_some() { + scarb_result = "Account removed from Scarb.toml".to_string(); + } + } + } + + // Serialize the modified TOML data back to a string + let modified_toml = toml::to_string(&parsed_toml).expect("Failed to serialize TOML"); + + // Write the modified content back to the file + let mut file = File::create(manifest_path).expect("Failed to create file"); + file.write_all(modified_toml.as_bytes()) + .expect("Failed to write to file"); + }; + + let result = "Account successfully removed".to_string(); + Ok(AccountDeleteResponse { + result, + scarb_result, + }) +} diff --git a/crates/cast/src/starknet_commands/account/mod.rs b/crates/cast/src/starknet_commands/account/mod.rs index 0942c119ab..d2d476581f 100644 --- a/crates/cast/src/starknet_commands/account/mod.rs +++ b/crates/cast/src/starknet_commands/account/mod.rs @@ -1,5 +1,6 @@ use crate::starknet_commands::account::add::Add; use crate::starknet_commands::account::create::Create; +use crate::starknet_commands::account::delete::Delete; use crate::starknet_commands::account::deploy::Deploy; use anyhow::{anyhow, bail, Context, Result}; use camino::Utf8PathBuf; @@ -17,6 +18,7 @@ use toml::Value; pub mod add; pub mod create; +pub mod delete; pub mod deploy; #[derive(Args)] @@ -31,6 +33,7 @@ pub enum Commands { Add(Add), Create(Create), Deploy(Deploy), + Delete(Delete), } pub fn prepare_account_json( diff --git a/crates/cast/tests/e2e/account/delete.rs b/crates/cast/tests/e2e/account/delete.rs new file mode 100644 index 0000000000..5a81564b43 --- /dev/null +++ b/crates/cast/tests/e2e/account/delete.rs @@ -0,0 +1,329 @@ +use crate::helpers::constants::URL; +use crate::helpers::fixtures::default_cli_args; +use crate::helpers::runner::runner; +use indoc::indoc; +use snapbox::cmd::{cargo_bin, Command}; +use tokio::fs::File; +use tokio::io::AsyncWriteExt; + +#[tokio::test] +pub async fn test_no_accounts_in_network() { + let mut args = default_cli_args(); + args.append(&mut vec![ + "account", + "delete", + "--name", + "user99", + "--network", + "goerli0-network", + ]); + + let snapbox = runner(&args); + + snapbox.assert().stderr_matches(indoc! {r#" + command: account delete + error: No accounts defined for network goerli0-network + "#}); +} + +#[tokio::test] +pub async fn test_account_does_not_exist() { + let mut args = default_cli_args(); + args.append(&mut vec!["account", "delete", "--name", "user99"]); + + let snapbox = runner(&args); + + snapbox.assert().stderr_matches(indoc! {r#" + command: account delete + error: Account with name user99 does not exist + "#}); +} + +#[tokio::test] +pub async fn test_delete_abort() { + // Creating dummy accounts test file + create_dummy_accounts_file("temp_accounts1.json").await; + create_dummy_scarb_file("temp_scarb1.toml").await; + + // Now delete dummy account + let args = vec![ + "--path-to-scarb-toml", + "temp_scarb1.toml", + "--url", + URL, + "--accounts-file", + "temp_accounts1.json", + "account", + "delete", + "--name", + "user3", + "--network", + "alpha-goerli2", + ]; + + // Run test with a negative user input + let snapbox = Command::new(cargo_bin!("sncast")).args(args).stdin("n"); + + snapbox.assert().stderr_matches(indoc! {r#" + command: account delete + error: Delete aborted + "#}); + + let _ = tokio::fs::remove_file("temp_accounts1.json").await; + let _ = tokio::fs::remove_file("temp_scarb1.toml").await; +} + +#[tokio::test] +pub async fn test_happy_case() { + // Creating dummy accounts test file + create_dummy_accounts_file("temp_accounts2.json").await; + create_dummy_scarb_file("temp_scarb2.toml").await; + + // Now delete dummy account + let args = vec![ + "--path-to-scarb-toml", + "temp_scarb2.toml", + "--url", + URL, + "--accounts-file", + "temp_accounts2.json", + "account", + "delete", + "--name", + "user3", + "--network", + "alpha-goerli2", + ]; + + // Run test with an affirmative user input + let snapbox = Command::new(cargo_bin!("sncast")).args(args).stdin("Y"); + let bdg = snapbox.assert(); + let out = bdg.get_output(); + let stdout_str = + std::str::from_utf8(&out.stdout).expect("failed to convert command output to string"); + + assert!(stdout_str.contains("Account successfully removed")); + + let _ = tokio::fs::remove_file("temp_accounts2.json").await; + let _ = tokio::fs::remove_file("temp_scarb2.toml").await; +} + +#[tokio::test] +pub async fn test_happy_case_with_explicit_remove_profile() { + // Creating dummy accounts and scarb.toml test file + create_dummy_accounts_file("temp_accounts3.json").await; + create_dummy_scarb_file("temp_scarb3.toml").await; + + // Now delete dummy account from accountd file and his profile from scarb + let args = vec![ + "--path-to-scarb-toml", + "temp_scarb3.toml", + "--url", + URL, + "--accounts-file", + "temp_accounts3.json", + "account", + "delete", + "--name", + "user0", + "--network", + "alpha-goerli", + "--delete-profile", + "true", + ]; + + // Run test with an affirmative user input + let snapbox = Command::new(cargo_bin!("sncast")).args(args).stdin("Y"); + let bdg = snapbox.assert(); + let out = bdg.get_output(); + let stdout_str = + std::str::from_utf8(&out.stdout).expect("failed to convert command output to string"); + + assert!(stdout_str.contains("Account removed from Scarb")); + + let _ = tokio::fs::remove_file("temp_accounts3.json").await; + let _ = tokio::fs::remove_file("temp_scarb3.toml").await; +} + +#[tokio::test] +pub async fn invalid_remove_profile_flag() { + // Creating dummy accounts and scarb.toml test file + create_dummy_accounts_file("temp_accounts4.json").await; + create_dummy_scarb_file("temp_scarb4.toml").await; + + // Now delete dummy account from accountd file and his profile from scarb + let args = vec![ + "--path-to-scarb-toml", + "temp_scarb4.toml", + "--url", + URL, + "--accounts-file", + "temp_accounts4.json", + "account", + "delete", + "--name", + "user0", + "--network", + "alpha-goerli", + "--delete-profile", + "no", + ]; + + // Run test with an affirmative user input + let snapbox = Command::new(cargo_bin!("sncast")).args(args).stdin("Y"); + + snapbox.assert().stderr_matches(indoc! {r#" + error: invalid value 'no' for '--delete-profile ' + [possible values: true, false] + + For more information, try '--help'. + "#}); + + let _ = tokio::fs::remove_file("temp_accounts4.json").await; + let _ = tokio::fs::remove_file("temp_scarb4.toml").await; +} + +#[tokio::test] +pub async fn test_happy_case_with_remove_profile_false() { + // Creating dummy accounts and scarb.toml test file + create_dummy_accounts_file("temp_accounts5.json").await; + create_dummy_scarb_file("temp_scarb5.toml").await; + + // Now delete dummy account from accountd file and his profile from scarb + let args = vec![ + "--path-to-scarb-toml", + "temp_scarb5.toml", + "--url", + URL, + "--accounts-file", + "temp_accounts5.json", + "account", + "delete", + "--name", + "user0", + "--network", + "alpha-goerli", + "--delete-profile", + "false", + ]; + + // Run test with an affirmative user input + let snapbox = Command::new(cargo_bin!("sncast")).args(args).stdin("Y"); + let bdg = snapbox.assert(); + let out = bdg.get_output(); + let stdout_str = + std::str::from_utf8(&out.stdout).expect("failed to convert command output to string"); + + assert!(stdout_str.contains("Account not removed from Scarb")); + + let _ = tokio::fs::remove_file("temp_accounts5.json").await; + let _ = tokio::fs::remove_file("temp_scarb5.toml").await; +} + +#[tokio::test] +pub async fn test_happy_case_without_network_args() { + // Creating dummy accounts test file + create_dummy_accounts_file("temp_accounts6.json").await; + create_dummy_scarb_file("temp_scarb6.toml").await; + + // Now delete dummy account + let args = vec![ + "--path-to-scarb-toml", + "temp_scarb6.toml", + "--url", + URL, + "--accounts-file", + "temp_accounts6.json", + "account", + "delete", + "--name", + "user0", + ]; + + // Run test with an affirmative user input + let snapbox = Command::new(cargo_bin!("sncast")).args(args).stdin("Y"); + let bdg = snapbox.assert(); + let out = bdg.get_output(); + let stdout_str = + std::str::from_utf8(&out.stdout).expect("failed to convert command output to string"); + + assert!(stdout_str.contains("Account successfully removed")); + + let _ = tokio::fs::remove_file("temp_accounts6.json").await; + let _ = tokio::fs::remove_file("temp_scarb6.toml").await; +} + +async fn create_dummy_accounts_file(file_name: &str) { + let json_data = indoc! {r#" + { + "alpha-goerli": { + "user0": { + "private_key": "0x1e9038bdc68ce1d27d54205256988e85", + "public_key": "0x2f91ed13f8f0f7d39b942c80bfcd3d0967809d99e0cc083606cbe59033d2b39", + "address": "0x4f5f24ceaae64434fa2bc2befd08976b51cf8f6a5d8257f7ec3616f61de263a" + } + }, + "alpha-goerli2": { + "user3": { + "private_key": "0xe3e70682c2094cac629f6fbed82c07cd", + "public_key": "0x7e52885445756b313ea16849145363ccb73fb4ab0440dbac333cf9d13de82b9", + "address": "0x7e00d496e324876bbc8531f2d9a82bf154d1a04a50218ee74cdd372f75a551a" + }, + "user4": { + "private_key": "0x73fbb3c1eff11167598455d0408f3932e42c678bd8f7fbc6028c716867cc01f", + "public_key": "0x43a74f86b7e204f1ba081636c9d4015e1f54f5bb03a4ae8741602a15ffbb182", + "salt": "0x54aa715a5cff30ccf7845ad4659eb1dac5b730c2541263c358c7e3a4c4a8064", + "address": "0x7ccdf182d27c7aaa2e733b94db4a3f7b28ff56336b34abf43c15e3a9edfbe91", + "deployed": true + } + } + } + "#}; + + let mut file = File::create(file_name) + .await + .expect("Could not create temporary accounts file!"); + file.write_all(json_data.as_bytes()) + .await + .expect("Could not write temporary testing accounts"); + let _ = file.flush().await; +} + +async fn create_dummy_scarb_file(file_name: &str) { + let json_data = indoc! {r#" + [dependencies] + starknet = "1.1.1" + + [dependencies.snforge_std] + git = "https://github.com/foundry-rs/starknet-foundry" + tag = "v0.6.0" + + [package] + name = "mypackage" + version = "0.1.0" + + [[target.starknet-contract]] + casm = true + + [tool.sncast] + url = "http://127.0.0.1:5050/rpc" + + [tool.sncast.user0] + account = "user0" + accounts-file = "./myfile.json" + url = "http://127.0.0.1:5050/rpc" + + [tool.sncast.user3] + account = "user3" + accounts-file = "./myfile.json" + url = "http://127.0.0.1:5050/rpc" + "#}; + + let mut file = File::create(file_name) + .await + .expect("Could not create temporary scarb file!"); + file.write_all(json_data.as_bytes()) + .await + .expect("Could not write temporary scarb accounts"); + let _ = file.flush().await; +} diff --git a/crates/cast/tests/e2e/account/mod.rs b/crates/cast/tests/e2e/account/mod.rs index 4b7a6a0d36..475ca87aa6 100644 --- a/crates/cast/tests/e2e/account/mod.rs +++ b/crates/cast/tests/e2e/account/mod.rs @@ -1,3 +1,4 @@ mod add; mod create; +mod delete; mod deploy; diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index e5213b0dad..a3979113f9 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -74,6 +74,7 @@ * [account](appendix/cast/account/account.md) * [create](appendix/cast/account/create.md) * [deploy](appendix/cast/account/deploy.md) + * [delete](appendix/cast/account/delete.md) * [declare](appendix/cast/declare.md) * [deploy](appendix/cast/deploy.md) * [invoke](appendix/cast/invoke.md) diff --git a/docs/src/appendix/cast.md b/docs/src/appendix/cast.md index c5af73c2f6..71427e3062 100644 --- a/docs/src/appendix/cast.md +++ b/docs/src/appendix/cast.md @@ -4,6 +4,7 @@ * [account](./cast/account/account.md) * [create](./cast/account/create.md) * [deploy](./cast/account/deploy.md) + * [delete](./cast/account/delete.md) * [declare](./cast/declare.md) * [deploy](./cast/deploy.md) * [invoke](./cast/invoke.md) diff --git a/docs/src/appendix/cast/account/account.md b/docs/src/appendix/cast/account/account.md index 08357917d3..709173ab4f 100644 --- a/docs/src/appendix/cast/account/account.md +++ b/docs/src/appendix/cast/account/account.md @@ -4,3 +4,4 @@ Provides a set of account management commands. It has the following subcommands: * [`create`](./create.md) * [`deploy`](./deploy.md) +* [`delete`](./delete.md) diff --git a/docs/src/appendix/cast/account/delete.md b/docs/src/appendix/cast/account/delete.md new file mode 100644 index 0000000000..4bdbc21ed3 --- /dev/null +++ b/docs/src/appendix/cast/account/delete.md @@ -0,0 +1,21 @@ +# `delete` +Delete an account from `accounts-file` and its associated Scarb profile. + +## Required common arguments - passed by CLI or specified in Scarb.toml + +* [`url`](../common.md#--url--u-rpc_url) + +## `--name, -n ` +Required. + +Account name which is going to be deleted. + +## `--delete-profile false` +Optional. + +If passed, the account's associated profile won't be removed from Scarb.toml. + +## `--network` +Optional. + +Network in `accounts-file` associated with the account. By default the network of rpc node. diff --git a/docs/src/starknet/account.md b/docs/src/starknet/account.md index 929be4d1b9..f95af2c054 100644 --- a/docs/src/starknet/account.md +++ b/docs/src/starknet/account.md @@ -6,6 +6,8 @@ entire account management flow with the `sncast account create` and `sncast acco Difference between those two commands is that the first one creates account information (private key, address and more) and the second one deploys it to the network. After deployment, account can be used to interact with Starknet. +To remove an account from the accounts file, you can use `sncast account delete`. Please note this only removes the account information stored locally - this will not remove the account from Starknet. + > 💡 **Info** > Currently, only OpenZeppelin account creation is supported. @@ -74,6 +76,28 @@ max_fee: 0x64a7168300 address: 0x7a949e83b243068d0cbedd8d5b8b32fafea66c54de23c40e68b126b5c845b61 ``` +### `account delete` + +Delete an account from `accounts-file` and its associated Scarb profile. + +```shell +$ sncast \ + --accounts-file my-account-file.json \ + account delete \ + --name some-name \ + --network alpha-goerli + +Do you want to remove account a4 from network alpha-goerli? (Y/n) +Y +Account removed from Scarb +command: account delete +result: Account successfully removed +``` +> 💡 **Info** +> Note that you can pass `--delete-profile false` argument to persist the associated profile in Scarb.toml. + +For a detailed CLI description, see [account delete command reference](../appendix/cast/account/delete.md). + ### Custom account contract By default, `sncast` creates/deploys an account using [openzeppelin contract's class hash](https://starkscan.co/class/0x058d97f7d76e78f44905cc30cb65b91ea49a4b908a76703c54197bca90f81773).