From 0085e81e240022a03df44e9f34468fc6737331c7 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Tue, 11 Jun 2024 11:25:07 -0700 Subject: [PATCH] Extract alias logic into its own implementation. --- .../src/commands/contract/deploy/wasm.rs | 57 ++-------- .../src/commands/contract/invoke.rs | 53 ++------- cmd/soroban-cli/src/commands/contract/mod.rs | 106 +++++++++++++++++- 3 files changed, 122 insertions(+), 94 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index ff56c0e50..a5f58d437 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,7 +15,7 @@ use soroban_env_host::{ HostError, }; -use crate::commands::contract::AliasData; +use crate::commands::contract::{AliasData, AliasDataError}; use crate::commands::{ config::data, contract::{self, id::wasm::get_contract_id}, @@ -108,16 +105,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), + AliasData(#[from] AliasDataError), } impl Cmd { @@ -128,7 +121,13 @@ impl Cmd { match res { TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?), TxnEnvelopeResult::Res(contract) => { - self.save_contract_id(&contract)?; + AliasData::save_contract_id( + &self.config.config_dir()?, + &contract, + self.alias.as_ref(), + &self.config.get_network()?.network_passphrase, + )?; + println!("{contract}"); } } @@ -149,42 +148,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 712c73c51..e41374cc0 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -25,13 +25,12 @@ use soroban_env_host::xdr::{ AccountEntry, AccountEntryExt, AccountId, DiagnosticEvent, Thresholds, }; use soroban_spec::read::FromWasmError; -use stellar_strkey::DecodeError; use super::super::{ config::{self, locator}, events, }; -use super::AliasData; +use super::{AliasData, AliasDataError}; use crate::commands::txn_result::{TxnEnvelopeResult, TxnResult}; use crate::commands::NetworkRunnable; use crate::get_spec::{self, get_remote_contract_spec}; @@ -98,8 +97,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}")] @@ -153,12 +150,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), + AliasData(#[from] AliasDataError), } impl From for Error { @@ -310,42 +303,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; @@ -359,7 +316,11 @@ impl NetworkRunnable for Cmd { let unwrap_config = config.unwrap_or(&self.config); let network = unwrap_config.get_network()?; tracing::trace!(?network); - let contract_id = self.contract_id()?; + let contract_id = AliasData::load_contract_id_or_default( + &self.contract_id, + &self.config.config_dir()?, + &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..cb302a410 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -13,9 +13,15 @@ pub mod optimize; pub mod read; pub mod restore; -use std::collections::HashMap; +use std::{ + collections::HashMap, + fs::{self, create_dir_all, OpenOptions}, + io::Write, + path::{Path, PathBuf}, +}; use serde::{Deserialize, Serialize}; +use stellar_strkey::DecodeError; use crate::commands::global; @@ -174,3 +180,101 @@ pub enum SpecOutput { pub struct AliasData { ids: HashMap, } + +#[derive(thiserror::Error, Debug)] +pub enum AliasDataError { + #[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), +} + +impl AliasData { + pub fn load(config_dir: &Path, alias: Option<&str>) -> Result, AliasDataError> { + let Some(alias) = alias else { + return Ok(None); + }; + + let path = Self::alias_path(config_dir, alias); + + if !path.exists() { + return Ok(None); + } + + let content = fs::read_to_string(path)?; + let data: Self = serde_json::from_str(&content).unwrap_or_default(); + + Ok(Some(data)) + } + + pub fn alias_path(config_dir: &Path, alias: &str) -> PathBuf { + let file_name = format!("{alias}.json"); + config_dir.join("contract-ids").join(file_name) + } + + pub fn save_contract_id( + config_dir: &Path, + contract_id: &str, + alias: Option<&String>, + network_passphrase: &str, + ) -> Result<(), AliasDataError> { + let Some(alias) = alias else { + return Ok(()); + }; + + let path = Self::alias_path(config_dir, alias); + let dir = path.parent().ok_or(AliasDataError::CannotAccessConfigDir)?; + + create_dir_all(dir).map_err(|_| AliasDataError::CannotAccessConfigDir)?; + + let content = fs::read_to_string(&path).unwrap_or_default(); + let mut data: Self = serde_json::from_str(&content).unwrap_or_default(); + + let mut to_file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(path)?; + + data.ids + .insert(network_passphrase.into(), contract_id.into()); + + let content = serde_json::to_string(&data)?; + + Ok(to_file.write_all(content.as_bytes())?) + } + + pub fn get_contract_id( + alias: &str, + config_dir: &Path, + network_passphrase: &str, + ) -> Result, AliasDataError> { + let alias_data = Self::load(config_dir, Some(alias))?; + let Some(alias_data) = alias_data else { + return Ok(None); + }; + + match alias_data.ids.get(network_passphrase) { + Some(id) => Ok(Some(id.into())), + _ => Ok(None), + } + } + + pub fn load_contract_id_or_default( + alias: &str, + config_dir: &Path, + network_passphrase: &str, + ) -> Result<[u8; 32], AliasDataError> { + let contract_id = match Self::get_contract_id(alias, config_dir, network_passphrase)? { + None => alias.to_string(), + Some(id) => id, + }; + + soroban_spec_tools::utils::contract_id_from_str(&contract_id) + .map_err(|e| AliasDataError::CannotParseContractId(contract_id.clone(), e)) + } +}