From 52dce61a1a6744ebc12d0201717e0ce68653c172 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Fri, 14 Jun 2024 13:30:07 -0700 Subject: [PATCH] Extract alias logic into its own implementation. (#1369) * Extract alias logic into its own implementation. * Apply pr feedback. * Name the argument explicitly. * Do not use an Option on Data::load(). * Move functions to config instead. * Remove guard from function and bring it to the caller. --- cmd/soroban-cli/src/commands/config/alias.rs | 103 ++++++++++++++++++ cmd/soroban-cli/src/commands/config/mod.rs | 1 + .../src/commands/contract/deploy/wasm.rs | 55 ++-------- .../src/commands/contract/invoke.rs | 51 +-------- cmd/soroban-cli/src/commands/contract/mod.rs | 9 -- 5 files changed, 116 insertions(+), 103 deletions(-) create mode 100644 cmd/soroban-cli/src/commands/config/alias.rs diff --git a/cmd/soroban-cli/src/commands/config/alias.rs b/cmd/soroban-cli/src/commands/config/alias.rs new file mode 100644 index 000000000..ca17c411e --- /dev/null +++ b/cmd/soroban-cli/src/commands/config/alias.rs @@ -0,0 +1,103 @@ +use std::{ + collections::HashMap, + fs::{self, create_dir_all, OpenOptions}, + io::Write, + path::PathBuf, +}; + +use serde::{Deserialize, Serialize}; +use stellar_strkey::DecodeError; + +use crate::commands::config; + +use super::Args; + +#[derive(Serialize, Deserialize, Default)] +pub struct Data { + ids: HashMap, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Json(#[from] serde_json::Error), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("cannot access config dir for alias file")] + CannotAccessConfigDir, + #[error("cannot parse contract ID {0}: {1}")] + CannotParseContractId(String, DecodeError), + #[error(transparent)] + Config(#[from] config::Error), +} + +impl Args { + fn load(&self, alias: &str) -> Result, Error> { + let path = self.alias_path(alias)?; + + if !path.exists() { + return Ok(None); + } + + let content = fs::read_to_string(path)?; + let data: Data = serde_json::from_str(&content).unwrap_or_default(); + + Ok(Some(data)) + } + + fn alias_path(&self, alias: &str) -> Result { + let file_name = format!("{alias}.json"); + let config_dir = self.config_dir()?; + Ok(config_dir.join("contract-ids").join(file_name)) + } + + pub fn save_contract_id(&self, contract_id: &str, alias: &str) -> Result<(), Error> { + let path = self.alias_path(alias)?; + let dir = path.parent().ok_or(Error::CannotAccessConfigDir)?; + + create_dir_all(dir).map_err(|_| Error::CannotAccessConfigDir)?; + + let content = fs::read_to_string(&path).unwrap_or_default(); + let mut data: Data = serde_json::from_str(&content).unwrap_or_default(); + + let mut to_file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(path)?; + + let network = self.get_network()?; + + data.ids + .insert(network.network_passphrase, contract_id.into()); + + let content = serde_json::to_string(&data)?; + + Ok(to_file.write_all(content.as_bytes())?) + } + + pub fn get_contract_id( + &self, + alias: &str, + network_passphrase: &str, + ) -> Result, Error> { + let Some(alias_data) = self.load(alias)? else { + return Ok(None); + }; + + Ok(alias_data.ids.get(network_passphrase).cloned()) + } + + pub fn resolve_contract_id( + &self, + alias_or_contract_id: &str, + network_passphrase: &str, + ) -> Result<[u8; 32], Error> { + let contract_id = self + .get_contract_id(alias_or_contract_id, network_passphrase)? + .unwrap_or_else(|| alias_or_contract_id.to_string()); + + soroban_spec_tools::utils::contract_id_from_str(&contract_id) + .map_err(|e| Error::CannotParseContractId(contract_id.clone(), e)) + } +} diff --git a/cmd/soroban-cli/src/commands/config/mod.rs b/cmd/soroban-cli/src/commands/config/mod.rs index f0c388ecc..99b15e230 100644 --- a/cmd/soroban-cli/src/commands/config/mod.rs +++ b/cmd/soroban-cli/src/commands/config/mod.rs @@ -15,6 +15,7 @@ use self::{network::Network, secret::Secret}; use super::{keys, network}; +pub mod alias; pub mod data; pub mod locator; pub mod secret; diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 571e95bd4..59325539d 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -1,9 +1,6 @@ +use std::array::TryFromSliceError; use std::fmt::Debug; -use std::fs::{self, create_dir_all}; -use std::io::Write; use std::num::ParseIntError; -use std::path::PathBuf; -use std::{array::TryFromSliceError, fs::OpenOptions}; use clap::{arg, command, Parser}; use rand::Rng; @@ -18,9 +15,8 @@ use soroban_env_host::{ HostError, }; -use crate::commands::contract::AliasData; use crate::commands::{ - config::data, + config::{alias, data}, contract::{self, id::wasm::get_contract_id}, global, network, txn_result::{TxnEnvelopeResult, TxnResult}, @@ -108,16 +104,12 @@ pub enum Error { Network(#[from] network::Error), #[error(transparent)] Wasm(#[from] wasm::Error), - #[error("cannot access config dir for alias file")] - CannotAccessConfigDir, #[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), + Alias(#[from] alias::Error), } impl Cmd { @@ -128,7 +120,10 @@ impl Cmd { match res { TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?), TxnEnvelopeResult::Res(contract) => { - self.save_contract_id(&contract)?; + if let Some(alias) = self.alias.clone() { + self.config.save_contract_id(&contract, &alias)?; + } + println!("{contract}"); } } @@ -149,42 +144,6 @@ impl Cmd { None => Ok(()), } } - - fn alias_path_for(&self, alias: &str) -> Result { - let config_dir = self.config.config_dir()?; - let file_name = format!("{alias}.json"); - - Ok(config_dir.join("contract-ids").join(file_name)) - } - - fn save_contract_id(&self, contract: &String) -> Result<(), Error> { - let Some(alias) = &self.alias else { - return Ok(()); - }; - - let file_path = self.alias_path_for(alias)?; - let dir = file_path.parent().ok_or(Error::CannotAccessConfigDir)?; - - 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)?; - - data.ids.insert( - self.config.get_network()?.network_passphrase, - contract.into(), - ); - - let content = serde_json::to_string(&data)?; - - Ok(to_file.write_all(content.as_bytes())?) - } } #[async_trait::async_trait] diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 52df9fed9..0690f121c 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -22,13 +22,12 @@ use soroban_env_host::{ }; use soroban_spec::read::FromWasmError; -use stellar_strkey::DecodeError; use super::super::{ config::{self, locator}, events, }; -use super::AliasData; +use crate::commands::config::alias; use crate::commands::txn_result::{TxnEnvelopeResult, TxnResult}; use crate::commands::NetworkRunnable; use crate::get_spec::{self, get_remote_contract_spec}; @@ -95,8 +94,6 @@ pub enum Error { filepath: std::path::PathBuf, error: events::Error, }, - #[error("cannot parse contract ID {0}: {1}")] - CannotParseContractId(String, DecodeError), #[error("function {0} was not found in the contract")] FunctionNotFoundInContractSpec(String), #[error("parsing contract spec: {0}")] @@ -150,12 +147,8 @@ 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, #[error(transparent)] - JsonDeserialization(#[from] serde_json::Error), + Alias(#[from] alias::Error), } impl From for Error { @@ -307,42 +300,6 @@ impl Cmd { } } -impl Cmd { - fn contract_id(&self) -> Result<[u8; 32], Error> { - let contract_id: String = match self.load_contract_id() { - Ok(Some(id)) => id.to_string(), - _ => self.contract_id.clone(), - }; - - soroban_spec_tools::utils::contract_id_from_str(&contract_id) - .map_err(|e| Error::CannotParseContractId(contract_id.clone(), e)) - } - - 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(file_name)) - } - - 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); - } - - let content = fs::read_to_string(file_path)?; - let data: AliasData = serde_json::from_str(&content)?; - - match data.ids.get(network) { - Some(id) => Ok(Some(id.into())), - _ => Ok(None), - } - } -} - #[async_trait::async_trait] impl NetworkRunnable for Cmd { type Error = Error; @@ -356,7 +313,9 @@ impl NetworkRunnable for Cmd { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); - let contract_id = self.contract_id()?; + let contract_id = self + .config + .resolve_contract_id(&self.contract_id, &network.network_passphrase)?; let spec_entries = self.spec_entries()?; if let Some(spec_entries) = &spec_entries { // For testing wasm arg parsing diff --git a/cmd/soroban-cli/src/commands/contract/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs index 9da55714c..66541a546 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -13,10 +13,6 @@ pub mod optimize; pub mod read; pub mod restore; -use std::collections::HashMap; - -use serde::{Deserialize, Serialize}; - use crate::commands::global; #[derive(Debug, clap::Subcommand)] @@ -169,8 +165,3 @@ pub enum SpecOutput { /// Pretty print of contract spec entries Docs, } - -#[derive(Serialize, Deserialize, Default)] -pub struct AliasData { - ids: HashMap, -}