From 689887e67e22bd6d0d38c7c19e5ed3c2760566f9 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Mon, 3 Jun 2024 17:04:50 -0700 Subject: [PATCH 01/19] Optimistically save contract id alias name to local soroban directory. --- .../src/commands/contract/deploy/wasm.rs | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 0b944c78a..74beff9d2 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -1,6 +1,8 @@ -use std::array::TryFromSliceError; use std::fmt::Debug; +use std::io::Write; use std::num::ParseIntError; +use std::path::Path; +use std::{array::TryFromSliceError, fs::OpenOptions}; use clap::{arg, command, Parser}; use rand::Rng; @@ -54,6 +56,12 @@ pub struct Cmd { #[arg(long, short = 'i', default_value = "false")] /// Whether to ignore safety checks when deploying contracts pub ignore_checks: bool, + /// The alias that will be used to save the contract's id. + #[arg(long)] + pub alias: Option, + /// Force saving the contract id file even if it already exists. + #[arg(long, short = 'f', default_value = "false")] + pub force: bool, } #[derive(thiserror::Error, Debug)] @@ -100,19 +108,61 @@ pub enum Error { Network(#[from] network::Error), #[error(transparent)] Wasm(#[from] wasm::Error), + #[error("alias \"{alias}\" already exist. Use --force to override it.")] + AliasAlreadyExist { alias: String }, } impl Cmd { pub async fn run(&self) -> Result<(), Error> { + self.validate_alias()?; + let res = self.run_against_rpc_server(None, None).await?.to_envelope(); match res { TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?), TxnEnvelopeResult::Res(contract) => { + self.save_contract_id(&contract); println!("{contract}"); } } Ok(()) } + + fn validate_alias(&self) -> Result<(), Error> { + let alias = self.alias(); + + if alias.is_empty() { + return Ok(()); + } + + let path = self.alias_path(); + let to = Path::new(&path); + + if to.exists() && !self.force { + Err(Error::AliasAlreadyExist { alias }) + } else { + Ok(()) + } + } + + fn alias(&self) -> String { + match &self.alias { + Some(n) => n.to_string(), + None => String::new(), + } + } + + fn alias_path(&self) -> String { + format!("./.soroban/contract-ids/{}.txt", self.alias()) + } + + fn save_contract_id(&self, contract: &String) { + let mut to_file = OpenOptions::new() + .create(true) + .write(true) + .open(self.alias_path()) + .unwrap(); + let _ = to_file.write_all(contract.as_bytes()); + } } #[async_trait::async_trait] From 0ae1aebd01741d3bea08d80232494c81983aa9b5 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Tue, 4 Jun 2024 11:33:13 -0700 Subject: [PATCH 02/19] Update docs. --- FULL_HELP_DOCS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index c23eaf76c..3849dbccf 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -452,6 +452,13 @@ Deploy a wasm contract Possible values: `true`, `false` +* `--alias ` — The alias that will be used to save the contract's id +* `-f`, `--force` — Force saving the contract id file even if it already exists + + Default value: `false` + + Possible values: `true`, `false` + From da28bdff0d4c0c1ff5e07af98d57b787cf91879f Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Tue, 4 Jun 2024 14:06:07 -0700 Subject: [PATCH 03/19] Use config_dir() instead. --- .../src/commands/contract/deploy/wasm.rs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 74beff9d2..a30db60de 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -1,7 +1,8 @@ use std::fmt::Debug; +use std::fs::create_dir_all; use std::io::Write; use std::num::ParseIntError; -use std::path::Path; +use std::path::PathBuf; use std::{array::TryFromSliceError, fs::OpenOptions}; use clap::{arg, command, Parser}; @@ -135,9 +136,8 @@ impl Cmd { } let path = self.alias_path(); - let to = Path::new(&path); - if to.exists() && !self.force { + if path.exists() && !self.force { Err(Error::AliasAlreadyExist { alias }) } else { Ok(()) @@ -151,15 +151,23 @@ impl Cmd { } } - fn alias_path(&self) -> String { - format!("./.soroban/contract-ids/{}.txt", self.alias()) + fn alias_path(&self) -> PathBuf { + let config_dir = self.config.config_dir().unwrap(); + let name = format!("{}.txt", self.alias()); + + config_dir.join("contract-ids").join(name) } fn save_contract_id(&self, contract: &String) { + let file_path = self.alias_path(); + let dir = file_path.parent().unwrap(); + + let _ = create_dir_all(dir); + let mut to_file = OpenOptions::new() .create(true) .write(true) - .open(self.alias_path()) + .open(file_path) .unwrap(); let _ = to_file.write_all(contract.as_bytes()); } From 1c21fc5babbccf46e6c416da2965b66e501e1956 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Tue, 4 Jun 2024 16:38:03 -0700 Subject: [PATCH 04/19] Read contract id from alias. --- .../src/commands/contract/invoke.rs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 37b06514d..5952705e6 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -305,8 +305,33 @@ impl Cmd { impl Cmd { fn contract_id(&self) -> Result<[u8; 32], Error> { - soroban_spec_tools::utils::contract_id_from_str(&self.contract_id) - .map_err(|e| Error::CannotParseContractId(self.contract_id.clone(), e)) + let contract_id = match self.load_contract_id() { + Some(id) => id, + None => self.contract_id.clone(), + }; + + soroban_spec_tools::utils::contract_id_from_str(&contract_id) + .map_err(|e| Error::CannotParseContractId(contract_id.clone(), e)) + } + + fn load_contract_id(&self) -> Option { + let file_name = format!("{}.txt", self.contract_id); + + let path = self + .config + .config_dir() + .unwrap() + .join("contract-ids") + .join(file_name); + + if path.exists() { + match fs::read_to_string(path) { + Ok(content) => Some(content), + _ => None, + } + } else { + None + } } } From cce8aca8147d9eb117cad920d9c755b488ad457e Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Wed, 5 Jun 2024 09:53:37 -0700 Subject: [PATCH 05/19] Update cmd/soroban-cli/src/commands/contract/deploy/wasm.rs Co-authored-by: Willem Wyndham --- cmd/soroban-cli/src/commands/contract/deploy/wasm.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index a30db60de..b7f914fba 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -145,10 +145,7 @@ impl Cmd { } fn alias(&self) -> String { - match &self.alias { - Some(n) => n.to_string(), - None => String::new(), - } + self.alias.as_ref().map(Clone::clone).unwrap_or_default() } fn alias_path(&self) -> PathBuf { From 1e678fd3ea95e0d2347bfd06aca5cef73be1e02f Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Wed, 5 Jun 2024 11:25:54 -0700 Subject: [PATCH 06/19] Apply pr feedback. --- .../src/commands/contract/deploy/wasm.rs | 39 +++++++++++++------ .../src/commands/contract/invoke.rs | 35 ++++++++--------- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index b7f914fba..cb9387612 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -111,6 +111,10 @@ pub enum Error { Wasm(#[from] wasm::Error), #[error("alias \"{alias}\" already exist. Use --force to override it.")] AliasAlreadyExist { alias: String }, + #[error("cannot access config dir for alias file")] + CannotAccessConfigDir, + #[error("cannot create alias file")] + CannotCreateAliasFile, } impl Cmd { @@ -121,7 +125,7 @@ impl Cmd { match res { TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?), TxnEnvelopeResult::Res(contract) => { - self.save_contract_id(&contract); + self.save_contract_id(&contract)?; println!("{contract}"); } } @@ -135,7 +139,7 @@ impl Cmd { return Ok(()); } - let path = self.alias_path(); + let path = self.alias_path()?.expect("should not be empty"); if path.exists() && !self.force { Err(Error::AliasAlreadyExist { alias }) @@ -148,25 +152,36 @@ impl Cmd { self.alias.as_ref().map(Clone::clone).unwrap_or_default() } - fn alias_path(&self) -> PathBuf { - let config_dir = self.config.config_dir().unwrap(); - let name = format!("{}.txt", self.alias()); + fn alias_path(&self) -> Result, Error> { + let config_dir = self.config.config_dir()?; - config_dir.join("contract-ids").join(name) + Ok(self + .alias + .as_ref() + .map(|alias| config_dir.join("contract-ids").join(alias))) } - fn save_contract_id(&self, contract: &String) { - let file_path = self.alias_path(); - let dir = file_path.parent().unwrap(); + fn save_contract_id(&self, contract: &String) -> Result<(), Error> { + let file_path = self.alias_path()?.expect("must be set"); + let Some(dir) = file_path.parent() else { + return Err(Error::CannotAccessConfigDir); + }; - let _ = create_dir_all(dir); + match create_dir_all(dir) { + Ok(()) => {} + _ => return Err(Error::CannotAccessConfigDir), + }; let mut to_file = OpenOptions::new() .create(true) .write(true) .open(file_path) - .unwrap(); - let _ = to_file.write_all(contract.as_bytes()); + .expect("cannot open file"); + + match to_file.write_all(contract.as_bytes()) { + Ok(()) => Ok(()), + Err(_) => Err(Error::CannotCreateAliasFile), + } } } diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 5952705e6..5ba372838 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -152,6 +152,10 @@ pub enum Error { Network(#[from] network::Error), #[error(transparent)] GetSpecError(#[from] get_spec::Error), + #[error("unable to read alias file")] + UnableToReadAliasFile, + #[error("alias file not found")] + NoAliasFileFound, } impl From for Error { @@ -306,32 +310,27 @@ impl Cmd { impl Cmd { fn contract_id(&self) -> Result<[u8; 32], Error> { let contract_id = match self.load_contract_id() { - Some(id) => id, - None => self.contract_id.clone(), + Ok(Some(id)) => id, + _ => self.contract_id.clone(), }; soroban_spec_tools::utils::contract_id_from_str(&contract_id) .map_err(|e| Error::CannotParseContractId(contract_id.clone(), e)) } - fn load_contract_id(&self) -> Option { - let file_name = format!("{}.txt", self.contract_id); + fn alias_path(&self) -> Result { + let config_dir = self.config.config_dir()?; - let path = self - .config - .config_dir() - .unwrap() - .join("contract-ids") - .join(file_name); + Ok(config_dir.join("contract-ids").join(&self.contract_id)) + } - if path.exists() { - match fs::read_to_string(path) { - Ok(content) => Some(content), - _ => None, - } - } else { - None - } + fn load_contract_id(&self) -> Result, Error> { + let file_path = self.alias_path()?; + + Ok(file_path + .exists() + .then(|| fs::read_to_string(file_path)) + .transpose()?) } } From c25ed7ee66817bb73ed05cfa855d99a7ccc48f05 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Wed, 5 Jun 2024 12:29:20 -0700 Subject: [PATCH 07/19] Do not make alias_path() return an optional. --- cmd/soroban-cli/src/commands/contract/deploy/wasm.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index cb9387612..7e766fe7e 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -139,7 +139,7 @@ impl Cmd { return Ok(()); } - let path = self.alias_path()?.expect("should not be empty"); + let path = self.alias_path()?; if path.exists() && !self.force { Err(Error::AliasAlreadyExist { alias }) @@ -152,17 +152,19 @@ impl Cmd { self.alias.as_ref().map(Clone::clone).unwrap_or_default() } - fn alias_path(&self) -> Result, Error> { + fn alias_path(&self) -> Result { let config_dir = self.config.config_dir()?; + let network = self.config.network.network.clone().expect("must be set"); Ok(self .alias .as_ref() - .map(|alias| config_dir.join("contract-ids").join(alias))) + .map(|alias| config_dir.join("contract-ids").join(network).join(alias)) + .expect("must be set")) } fn save_contract_id(&self, contract: &String) -> Result<(), Error> { - let file_path = self.alias_path()?.expect("must be set"); + let file_path = self.alias_path()?; let Some(dir) = file_path.parent() else { return Err(Error::CannotAccessConfigDir); }; From 93b8b610b6edc77f3d9d4aa831e6b3142a23065d Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Wed, 5 Jun 2024 12:35:56 -0700 Subject: [PATCH 08/19] Add network name as part of the alias file path. --- cmd/soroban-cli/src/commands/contract/deploy/wasm.rs | 1 + cmd/soroban-cli/src/commands/contract/invoke.rs | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 7e766fe7e..d345d04fb 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -165,6 +165,7 @@ impl Cmd { fn save_contract_id(&self, contract: &String) -> Result<(), Error> { let file_path = self.alias_path()?; + let Some(dir) = file_path.parent() else { return Err(Error::CannotAccessConfigDir); }; diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 5ba372838..d0fec5dd9 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -320,8 +320,12 @@ impl Cmd { fn alias_path(&self) -> Result { let config_dir = self.config.config_dir()?; + let network = self.config.network.network.clone().expect("must be set"); - Ok(config_dir.join("contract-ids").join(&self.contract_id)) + Ok(config_dir + .join("contract-ids") + .join(network) + .join(&self.contract_id)) } fn load_contract_id(&self) -> Result, Error> { From 983b51408b2e9867d07c7e69ea3e4e546cbc1f3e Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Wed, 5 Jun 2024 14:13:20 -0700 Subject: [PATCH 09/19] Validate alias format. --- FULL_HELP_DOCS.md | 6 ------ .../src/commands/contract/deploy/wasm.rs | 18 +++++++++--------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index e856bd5be..316fca209 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -453,12 +453,6 @@ Deploy a wasm contract Possible values: `true`, `false` * `--alias ` — The alias that will be used to save the contract's id -* `-f`, `--force` — Force saving the contract id file even if it already exists - - Default value: `false` - - Possible values: `true`, `false` - diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index d345d04fb..7b5c5b1a5 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -7,6 +7,7 @@ use std::{array::TryFromSliceError, fs::OpenOptions}; use clap::{arg, command, Parser}; use rand::Rng; +use regex::Regex; use soroban_env_host::{ xdr::{ AccountId, ContractExecutable, ContractIdPreimage, ContractIdPreimageFromAddress, @@ -60,9 +61,6 @@ pub struct Cmd { /// The alias that will be used to save the contract's id. #[arg(long)] pub alias: Option, - /// Force saving the contract id file even if it already exists. - #[arg(long, short = 'f', default_value = "false")] - pub force: bool, } #[derive(thiserror::Error, Debug)] @@ -109,12 +107,14 @@ pub enum Error { Network(#[from] network::Error), #[error(transparent)] Wasm(#[from] wasm::Error), - #[error("alias \"{alias}\" already exist. Use --force to override it.")] - AliasAlreadyExist { alias: String }, #[error("cannot access config dir for alias file")] CannotAccessConfigDir, #[error("cannot create alias file")] CannotCreateAliasFile, + #[error( + "alias must be 1-30 chars long, and have only letters, numbers, underscores and dashes" + )] + InvalidAliasFormat { alias: String }, } impl Cmd { @@ -139,12 +139,12 @@ impl Cmd { return Ok(()); } - let path = self.alias_path()?; + let regex = Regex::new(r"^[a-zA-Z0-9_-]{1,30}$").unwrap(); - if path.exists() && !self.force { - Err(Error::AliasAlreadyExist { alias }) - } else { + if regex.is_match(&alias) { Ok(()) + } else { + Err(Error::InvalidAliasFormat { alias }) } } From c659ac6f8ab3f822eadc83c155f0c7c3a52af94b Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Wed, 5 Jun 2024 15:53:54 -0700 Subject: [PATCH 10/19] Use json to store the contract id alias. --- .../src/commands/contract/deploy/wasm.rs | 31 ++++++++++++------- .../src/commands/contract/invoke.rs | 20 +++++++++--- cmd/soroban-cli/src/commands/contract/mod.rs | 7 +++++ 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 7b5c5b1a5..5950ecd5f 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -18,6 +18,7 @@ use soroban_env_host::{ HostError, }; +use crate::commands::contract::AliasData; use crate::commands::{ config::data, contract::{self, id::wasm::get_contract_id}, @@ -109,12 +110,14 @@ pub enum Error { Wasm(#[from] wasm::Error), #[error("cannot access config dir for alias file")] CannotAccessConfigDir, - #[error("cannot create alias file")] - CannotCreateAliasFile, #[error( "alias must be 1-30 chars long, and have only letters, numbers, underscores and dashes" )] InvalidAliasFormat { alias: String }, + #[error(transparent)] + JSONSerialization(#[from] serde_json::Error), + #[error(transparent)] + IO(#[from] std::io::Error), } impl Cmd { @@ -155,12 +158,13 @@ impl Cmd { fn alias_path(&self) -> Result { let config_dir = self.config.config_dir()?; let network = self.config.network.network.clone().expect("must be set"); + let alias = self.alias(); + let file_name = format!("{alias}.json"); - Ok(self - .alias - .as_ref() - .map(|alias| config_dir.join("contract-ids").join(network).join(alias)) - .expect("must be set")) + Ok(config_dir + .join("contract-ids") + .join(network) + .join(file_name)) } fn save_contract_id(&self, contract: &String) -> Result<(), Error> { @@ -179,12 +183,15 @@ impl Cmd { .create(true) .write(true) .open(file_path) - .expect("cannot open file"); + .map_err(Error::IO)?; - match to_file.write_all(contract.as_bytes()) { - Ok(()) => Ok(()), - Err(_) => Err(Error::CannotCreateAliasFile), - } + let payload = AliasData { + id: contract.into(), + }; + + let content = serde_json::to_string(&payload).map_err(Error::JSONSerialization)?; + + to_file.write_all(content.as_bytes()).map_err(Error::IO) } } diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index d0fec5dd9..408f56887 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -31,6 +31,7 @@ use super::super::{ config::{self, locator}, events, }; +use super::AliasData; use crate::commands::txn_result::{TxnEnvelopeResult, TxnResult}; use crate::commands::NetworkRunnable; use crate::get_spec::{self, get_remote_contract_spec}; @@ -156,6 +157,8 @@ pub enum Error { UnableToReadAliasFile, #[error("alias file not found")] NoAliasFileFound, + #[error(transparent)] + JSONDeserialization(#[from] serde_json::Error), } impl From for Error { @@ -321,20 +324,27 @@ impl Cmd { fn alias_path(&self) -> Result { let config_dir = self.config.config_dir()?; let network = self.config.network.network.clone().expect("must be set"); + let file_name = format!("{}.json", self.contract_id); Ok(config_dir .join("contract-ids") .join(network) - .join(&self.contract_id)) + .join(file_name)) } fn load_contract_id(&self) -> Result, Error> { let file_path = self.alias_path()?; - Ok(file_path - .exists() - .then(|| fs::read_to_string(file_path)) - .transpose()?) + if !file_path.exists() { + return Ok(None); + } + + let content = fs::read_to_string(file_path).map_err(Error::Io)?; + + let data: AliasData = + serde_json::from_str(content.as_str()).map_err(Error::JSONDeserialization)?; + + Ok(Some(data.id)) } } diff --git a/cmd/soroban-cli/src/commands/contract/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs index 66541a546..f423a9de6 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -13,6 +13,8 @@ pub mod optimize; pub mod read; pub mod restore; +use serde::{Deserialize, Serialize}; + use crate::commands::global; #[derive(Debug, clap::Subcommand)] @@ -165,3 +167,8 @@ pub enum SpecOutput { /// Pretty print of contract spec entries Docs, } + +#[derive(Serialize, Deserialize)] +pub struct AliasData { + id: String, +} From a1664c549d862060894d97247e53bb6a9c96d620 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Thu, 6 Jun 2024 09:37:47 -0700 Subject: [PATCH 11/19] Rename due to language semantics. --- cmd/soroban-cli/src/commands/contract/deploy/wasm.rs | 10 +++++----- cmd/soroban-cli/src/commands/contract/invoke.rs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 5950ecd5f..8da3f71ff 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -115,9 +115,9 @@ pub enum Error { )] InvalidAliasFormat { alias: String }, #[error(transparent)] - JSONSerialization(#[from] serde_json::Error), + JsonSerialization(#[from] serde_json::Error), #[error(transparent)] - IO(#[from] std::io::Error), + Io(#[from] std::io::Error), } impl Cmd { @@ -183,15 +183,15 @@ impl Cmd { .create(true) .write(true) .open(file_path) - .map_err(Error::IO)?; + .map_err(Error::Io)?; let payload = AliasData { id: contract.into(), }; - let content = serde_json::to_string(&payload).map_err(Error::JSONSerialization)?; + let content = serde_json::to_string(&payload).map_err(Error::JsonSerialization)?; - to_file.write_all(content.as_bytes()).map_err(Error::IO) + to_file.write_all(content.as_bytes()).map_err(Error::Io) } } diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 408f56887..51e4f477b 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -158,7 +158,7 @@ pub enum Error { #[error("alias file not found")] NoAliasFileFound, #[error(transparent)] - JSONDeserialization(#[from] serde_json::Error), + JsonDeserialization(#[from] serde_json::Error), } impl From for Error { @@ -342,7 +342,7 @@ impl Cmd { let content = fs::read_to_string(file_path).map_err(Error::Io)?; let data: AliasData = - serde_json::from_str(content.as_str()).map_err(Error::JSONDeserialization)?; + serde_json::from_str(content.as_str()).map_err(Error::JsonDeserialization)?; Ok(Some(data.id)) } From f1dca793d887e138752a8c387a7593a9c8e8c91b Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Thu, 6 Jun 2024 09:45:53 -0700 Subject: [PATCH 12/19] Update cmd/soroban-cli/src/commands/contract/deploy/wasm.rs Co-authored-by: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> --- cmd/soroban-cli/src/commands/contract/deploy/wasm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 8da3f71ff..859ffa358 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -152,7 +152,7 @@ impl Cmd { } fn alias(&self) -> String { - self.alias.as_ref().map(Clone::clone).unwrap_or_default() + self.alias.clone().unwrap_or_default() } fn alias_path(&self) -> Result { From 8506896bbc2d087f80985c2949c9027ac4778b93 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Thu, 6 Jun 2024 09:57:43 -0700 Subject: [PATCH 13/19] Update cmd/soroban-cli/src/commands/contract/deploy/wasm.rs Co-authored-by: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> --- cmd/soroban-cli/src/commands/contract/deploy/wasm.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 859ffa358..ddd3b2c74 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -181,6 +181,7 @@ impl Cmd { let mut to_file = OpenOptions::new() .create(true) + .truncate(true) .write(true) .open(file_path) .map_err(Error::Io)?; From faac4afff15770b59e843f50f95a70ff35d359fe Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Thu, 6 Jun 2024 10:56:14 -0700 Subject: [PATCH 14/19] Remove map_err in favor of macro implementation. --- cmd/soroban-cli/src/commands/contract/deploy/wasm.rs | 4 ++-- cmd/soroban-cli/src/commands/contract/invoke.rs | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index ddd3b2c74..e0fabdbac 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -190,9 +190,9 @@ impl Cmd { id: contract.into(), }; - let content = serde_json::to_string(&payload).map_err(Error::JsonSerialization)?; + let content = serde_json::to_string(&payload)?; - to_file.write_all(content.as_bytes()).map_err(Error::Io) + Ok(to_file.write_all(content.as_bytes())?) } } diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 51e4f477b..8dcebe9bf 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -339,10 +339,8 @@ impl Cmd { return Ok(None); } - let content = fs::read_to_string(file_path).map_err(Error::Io)?; - - let data: AliasData = - serde_json::from_str(content.as_str()).map_err(Error::JsonDeserialization)?; + let content = fs::read_to_string(file_path)?; + let data: AliasData = serde_json::from_str(content.as_str())?; Ok(Some(data.id)) } From 010853091c31d483c74a5188cfdb320b6c5e05ac Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Thu, 6 Jun 2024 14:15:30 -0700 Subject: [PATCH 15/19] Early return when no alias has been provided. --- cmd/soroban-cli/src/commands/contract/deploy/wasm.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index e0fabdbac..8ead5cb2d 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -168,6 +168,10 @@ impl Cmd { } fn save_contract_id(&self, contract: &String) -> Result<(), Error> { + if self.alias().is_empty() { + return Ok(()); + } + let file_path = self.alias_path()?; let Some(dir) = file_path.parent() else { From 80fa1f274577a49f22bf8c521730156cf7b31d8f Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Fri, 7 Jun 2024 11:00:55 -0700 Subject: [PATCH 16/19] Make use of Option, instead of checking for the string's content. --- .../src/commands/contract/deploy/wasm.rs | 76 +++++++++---------- .../src/commands/contract/invoke.rs | 2 +- 2 files changed, 37 insertions(+), 41 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 8ead5cb2d..5be744073 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -136,29 +136,24 @@ impl Cmd { } fn validate_alias(&self) -> Result<(), Error> { - let alias = self.alias(); - - if alias.is_empty() { - return Ok(()); - } - - let regex = Regex::new(r"^[a-zA-Z0-9_-]{1,30}$").unwrap(); - - if regex.is_match(&alias) { - Ok(()) - } else { - Err(Error::InvalidAliasFormat { alias }) + match self.alias.clone() { + Some(alias) => { + let regex = Regex::new(r"^[a-zA-Z0-9_-]{1,30}$").unwrap(); + + if regex.is_match(&alias) { + Ok(()) + } else { + Err(Error::InvalidAliasFormat { alias }) + } + } + None => Ok(()), } } - fn alias(&self) -> String { - self.alias.clone().unwrap_or_default() - } - fn alias_path(&self) -> Result { let config_dir = self.config.config_dir()?; let network = self.config.network.network.clone().expect("must be set"); - let alias = self.alias(); + let alias = self.alias.clone().expect("must be set"); let file_name = format!("{alias}.json"); Ok(config_dir @@ -168,35 +163,36 @@ impl Cmd { } fn save_contract_id(&self, contract: &String) -> Result<(), Error> { - if self.alias().is_empty() { - return Ok(()); - } + match &self.alias { + Some(_alias) => { + let file_path = self.alias_path()?; - let file_path = self.alias_path()?; - - let Some(dir) = file_path.parent() else { - return Err(Error::CannotAccessConfigDir); - }; + let Some(dir) = file_path.parent() else { + return Err(Error::CannotAccessConfigDir); + }; - match create_dir_all(dir) { - Ok(()) => {} - _ => return Err(Error::CannotAccessConfigDir), - }; + match create_dir_all(dir) { + Ok(()) => {} + _ => return Err(Error::CannotAccessConfigDir), + }; - let mut to_file = OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(file_path) - .map_err(Error::Io)?; + let mut to_file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(file_path) + .map_err(Error::Io)?; - let payload = AliasData { - id: contract.into(), - }; + let payload = AliasData { + id: contract.into(), + }; - let content = serde_json::to_string(&payload)?; + let content = serde_json::to_string(&payload)?; - Ok(to_file.write_all(content.as_bytes())?) + Ok(to_file.write_all(content.as_bytes())?) + } + _ => Ok(()), + } } } diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 8dcebe9bf..11d9130b2 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -340,7 +340,7 @@ impl Cmd { } let content = fs::read_to_string(file_path)?; - let data: AliasData = serde_json::from_str(content.as_str())?; + let data: AliasData = serde_json::from_str(&content)?; Ok(Some(data.id)) } From cda271353ecb3a96878fea84e4cf9a4e7df7c3f1 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Fri, 7 Jun 2024 11:11:12 -0700 Subject: [PATCH 17/19] Use other rust idioms. --- .../src/commands/contract/deploy/wasm.rs | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 5be744073..324898172 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -150,10 +150,9 @@ impl Cmd { } } - fn alias_path(&self) -> Result { + fn alias_path_for(&self, alias: &str) -> Result { let config_dir = self.config.config_dir()?; let network = self.config.network.network.clone().expect("must be set"); - let alias = self.alias.clone().expect("must be set"); let file_name = format!("{alias}.json"); Ok(config_dir @@ -163,36 +162,28 @@ impl Cmd { } fn save_contract_id(&self, contract: &String) -> Result<(), Error> { - match &self.alias { - Some(_alias) => { - let file_path = self.alias_path()?; + let Some(alias) = &self.alias else { + return Ok(()); + }; - let Some(dir) = file_path.parent() else { - return Err(Error::CannotAccessConfigDir); - }; + let file_path = self.alias_path_for(alias)?; + let dir = file_path.parent().ok_or(Error::CannotAccessConfigDir)?; - match create_dir_all(dir) { - Ok(()) => {} - _ => return Err(Error::CannotAccessConfigDir), - }; + create_dir_all(dir).map_err(|_| Error::CannotAccessConfigDir)?; - let mut to_file = OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(file_path) - .map_err(Error::Io)?; + let mut to_file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(file_path)?; - let payload = AliasData { - id: contract.into(), - }; + let payload = AliasData { + id: contract.into(), + }; - let content = serde_json::to_string(&payload)?; + let content = serde_json::to_string(&payload)?; - Ok(to_file.write_all(content.as_bytes())?) - } - _ => Ok(()), - } + Ok(to_file.write_all(content.as_bytes())?) } } From e18e96edc632ac8b31d9d69fe605c07c0624fcd8 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Fri, 7 Jun 2024 11:56:11 -0700 Subject: [PATCH 18/19] Ensure network name is set. --- cmd/soroban-cli/src/commands/contract/invoke.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 11d9130b2..fa67f93d1 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -312,8 +312,11 @@ impl Cmd { impl Cmd { fn contract_id(&self) -> Result<[u8; 32], Error> { - let contract_id = match self.load_contract_id() { - Ok(Some(id)) => id, + let contract_id = match &self.config.network.network { + Some(network) => match self.load_contract_id(network) { + Ok(Some(id)) => id, + _ => self.contract_id.clone(), + }, _ => self.contract_id.clone(), }; @@ -321,9 +324,8 @@ impl Cmd { .map_err(|e| Error::CannotParseContractId(contract_id.clone(), e)) } - fn alias_path(&self) -> Result { + fn alias_path_for(&self, network: &str) -> Result { let config_dir = self.config.config_dir()?; - let network = self.config.network.network.clone().expect("must be set"); let file_name = format!("{}.json", self.contract_id); Ok(config_dir @@ -332,8 +334,8 @@ impl Cmd { .join(file_name)) } - fn load_contract_id(&self) -> Result, Error> { - let file_path = self.alias_path()?; + fn load_contract_id(&self, network: &str) -> Result, Error> { + let file_path = self.alias_path_for(network)?; if !file_path.exists() { return Ok(None); From ea24a9a596c5589c312d270a67a856e0f0fc3f6f Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Mon, 10 Jun 2024 11:29:20 -0700 Subject: [PATCH 19/19] Scope aliases by network passphrases. --- .../src/commands/contract/deploy/wasm.rs | 20 ++++++++-------- .../src/commands/contract/invoke.rs | 24 +++++++++---------- cmd/soroban-cli/src/commands/contract/mod.rs | 6 +++-- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 324898172..d50fa6e31 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -1,5 +1,5 @@ use std::fmt::Debug; -use std::fs::create_dir_all; +use std::fs::{self, create_dir_all}; use std::io::Write; use std::num::ParseIntError; use std::path::PathBuf; @@ -152,13 +152,9 @@ impl Cmd { fn alias_path_for(&self, alias: &str) -> Result { let config_dir = self.config.config_dir()?; - let network = self.config.network.network.clone().expect("must be set"); let file_name = format!("{alias}.json"); - Ok(config_dir - .join("contract-ids") - .join(network) - .join(file_name)) + Ok(config_dir.join("contract-ids").join(file_name)) } fn save_contract_id(&self, contract: &String) -> Result<(), Error> { @@ -171,17 +167,21 @@ impl Cmd { create_dir_all(dir).map_err(|_| Error::CannotAccessConfigDir)?; + let content = fs::read_to_string(&file_path).unwrap_or_default(); + let mut data: AliasData = serde_json::from_str(&content).unwrap_or_default(); + let mut to_file = OpenOptions::new() .create(true) .truncate(true) .write(true) .open(file_path)?; - let payload = AliasData { - id: contract.into(), - }; + data.ids.insert( + self.config.get_network()?.network_passphrase, + contract.into(), + ); - let content = serde_json::to_string(&payload)?; + let content = serde_json::to_string(&data)?; Ok(to_file.write_all(content.as_bytes())?) } diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index fa67f93d1..712c73c51 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -312,11 +312,8 @@ impl Cmd { impl Cmd { fn contract_id(&self) -> Result<[u8; 32], Error> { - let contract_id = match &self.config.network.network { - Some(network) => match self.load_contract_id(network) { - Ok(Some(id)) => id, - _ => self.contract_id.clone(), - }, + let contract_id: String = match self.load_contract_id() { + Ok(Some(id)) => id.to_string(), _ => self.contract_id.clone(), }; @@ -324,18 +321,16 @@ impl Cmd { .map_err(|e| Error::CannotParseContractId(contract_id.clone(), e)) } - fn alias_path_for(&self, network: &str) -> Result { + fn alias_path(&self) -> Result { let config_dir = self.config.config_dir()?; let file_name = format!("{}.json", self.contract_id); - Ok(config_dir - .join("contract-ids") - .join(network) - .join(file_name)) + Ok(config_dir.join("contract-ids").join(file_name)) } - fn load_contract_id(&self, network: &str) -> Result, Error> { - let file_path = self.alias_path_for(network)?; + fn load_contract_id(&self) -> Result, Error> { + let network = &self.config.get_network()?.network_passphrase; + let file_path = self.alias_path()?; if !file_path.exists() { return Ok(None); @@ -344,7 +339,10 @@ impl Cmd { let content = fs::read_to_string(file_path)?; let data: AliasData = serde_json::from_str(&content)?; - Ok(Some(data.id)) + match data.ids.get(network) { + Some(id) => Ok(Some(id.into())), + _ => Ok(None), + } } } diff --git a/cmd/soroban-cli/src/commands/contract/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs index f423a9de6..9da55714c 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -13,6 +13,8 @@ pub mod optimize; pub mod read; pub mod restore; +use std::collections::HashMap; + use serde::{Deserialize, Serialize}; use crate::commands::global; @@ -168,7 +170,7 @@ pub enum SpecOutput { Docs, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Default)] pub struct AliasData { - id: String, + ids: HashMap, }