From b37da02841fa4c31c71ed4064bae1c312599d8b4 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 14 Nov 2024 19:31:16 -0500 Subject: [PATCH] fix: Add ContractAddress type to consolidate contract id parsing with aliases (#1692) * fix: resolve contract id * refactor: Refactor contract ID handling in Args module. --- Cargo.lock | 38 ++++++++-------- FULL_HELP_DOCS.md | 8 ++-- .../tests/it/integration/hello_world.rs | 2 +- .../src/commands/contract/alias/add.rs | 8 ++-- .../src/commands/contract/fetch.rs | 10 ++++- .../src/commands/contract/info/shared.rs | 14 +++--- .../src/commands/contract/invoke.rs | 7 ++- cmd/soroban-cli/src/commands/events.rs | 7 ++- cmd/soroban-cli/src/config/alias.rs | 43 ++++++++++++++++++- cmd/soroban-cli/src/config/locator.rs | 25 ++++++----- cmd/soroban-cli/src/config/mod.rs | 2 + cmd/soroban-cli/src/key.rs | 11 +++-- cmd/soroban-cli/src/wasm.rs | 8 +--- 13 files changed, 118 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4d3cc0c78..82b62d2e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1701,9 +1701,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -1711,9 +1711,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" @@ -1728,9 +1728,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" @@ -1762,9 +1762,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", @@ -1773,21 +1773,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -3013,11 +3013,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" dependencies = [ - "bitflags 2.6.0", + "bitflags 1.3.2", "cfg-if", "foreign-types", "libc", @@ -3045,9 +3045,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index c7cd9c56c..6bf321246 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -198,7 +198,7 @@ Add contract alias ###### **Arguments:** -* `` — The contract alias that will be removed +* `` — The contract alias that will be used ###### **Options:** @@ -522,7 +522,7 @@ Outputs no data when no data is present in the contract. * `--wasm ` — Wasm file to extract the data from * `--wasm-hash ` — Wasm hash to get the data for -* `--id ` — Contract id to get the data for +* `--id ` — Contract id or contract alias to get the data for * `--rpc-url ` — RPC server endpoint * `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server @@ -562,7 +562,7 @@ Outputs no data when no data is present in the contract. * `--wasm ` — Wasm file to extract the data from * `--wasm-hash ` — Wasm hash to get the data for -* `--id ` — Contract id to get the data for +* `--id ` — Contract id or contract alias to get the data for * `--rpc-url ` — RPC server endpoint * `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server @@ -602,7 +602,7 @@ Outputs no data when no data is present in the contract. * `--wasm ` — Wasm file to extract the data from * `--wasm-hash ` — Wasm hash to get the data for -* `--id ` — Contract id to get the data for +* `--id ` — Contract id or contract alias to get the data for * `--rpc-url ` — RPC server endpoint * `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server diff --git a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs index 12963b1f4..a6bd00c91 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -159,7 +159,7 @@ pub(crate) fn invoke_hello_world(sandbox: &TestEnv, id: &str) { fn hello_world_cmd(id: &str, arg: &str) -> contract::invoke::Cmd { contract::invoke::Cmd { - contract_id: id.to_string(), + contract_id: id.parse().unwrap(), slop: vec!["hello".into(), format!("--world={arg}").into()], ..Default::default() } diff --git a/cmd/soroban-cli/src/commands/contract/alias/add.rs b/cmd/soroban-cli/src/commands/contract/alias/add.rs index 5a7a17ddb..923ead5e9 100644 --- a/cmd/soroban-cli/src/commands/contract/alias/add.rs +++ b/cmd/soroban-cli/src/commands/contract/alias/add.rs @@ -13,9 +13,9 @@ pub struct Cmd { pub config_locator: locator::Args, #[command(flatten)] - network: network::Args, + pub network: network::Args, - /// The contract alias that will be removed. + /// The contract alias that will be used. pub alias: String, /// Overwrite the contract alias if it already exists. @@ -41,7 +41,7 @@ pub enum Error { AlreadyExist { alias: String, network_passphrase: String, - contract: String, + contract: stellar_strkey::Contract, }, } @@ -57,7 +57,7 @@ impl Cmd { .get_contract_id(&self.alias, network_passphrase)?; if let Some(contract) = contract { - if contract != self.contract_id.to_string() && !self.overwrite { + if contract != self.contract_id && !self.overwrite { return Err(Error::AlreadyExist { alias: alias.to_string(), network_passphrase: network_passphrase.to_string(), diff --git a/cmd/soroban-cli/src/commands/contract/fetch.rs b/cmd/soroban-cli/src/commands/contract/fetch.rs index 2714b1f07..31ed191ff 100644 --- a/cmd/soroban-cli/src/commands/contract/fetch.rs +++ b/cmd/soroban-cli/src/commands/contract/fetch.rs @@ -22,7 +22,7 @@ use crate::{ pub struct Cmd { /// Contract ID to fetch #[arg(long = "id", env = "STELLAR_CONTRACT_ID")] - pub contract_id: String, + pub contract_id: config::ContractAddress, /// Where to write output otherwise stdout is used #[arg(long, short = 'o')] pub out_file: Option, @@ -111,6 +111,12 @@ impl NetworkRunnable for Cmd { config: Option<&config::Args>, ) -> Result, Error> { let network = config.map_or_else(|| self.network(), |c| Ok(c.get_network()?))?; - return Ok(wasm::fetch_from_contract(&self.contract_id, &network, &self.locator).await?); + Ok(wasm::fetch_from_contract( + &self + .contract_id + .resolve_contract_id(&self.locator, &network.network_passphrase)?, + &network, + ) + .await?) } } diff --git a/cmd/soroban-cli/src/commands/contract/info/shared.rs b/cmd/soroban-cli/src/commands/contract/info/shared.rs index b34ec7da8..33a95b607 100644 --- a/cmd/soroban-cli/src/commands/contract/info/shared.rs +++ b/cmd/soroban-cli/src/commands/contract/info/shared.rs @@ -1,13 +1,13 @@ use std::path::PathBuf; -use crate::xdr; use clap::arg; use crate::{ commands::contract::info::shared::Error::InvalidWasmHash, - config::{locator, network}, + config::{self, locator, network}, utils::rpc::get_remote_wasm_from_hash, wasm::{self, Error::ContractIsStellarAsset}, + xdr, }; #[derive(Debug, clap::Args, Clone, Default)] @@ -24,9 +24,9 @@ pub struct Args { /// Wasm hash to get the data for #[arg(long = "wasm-hash", group = "Source")] pub wasm_hash: Option, - /// Contract id to get the data for + /// Contract id or contract alias to get the data for #[arg(long = "id", env = "STELLAR_CONTRACT_ID", group = "Source")] - pub contract_id: Option, + pub contract_id: Option, #[command(flatten)] pub network: network::Args, #[command(flatten)] @@ -56,6 +56,8 @@ pub enum Error { InvalidWasmHash(String), #[error(transparent)] Rpc(#[from] soroban_rpc::Error), + #[error(transparent)] + Locator(#[from] locator::Error), } pub async fn fetch_wasm(args: &Args) -> Result>, Error> { @@ -84,7 +86,9 @@ pub async fn fetch_wasm(args: &Args) -> Result>, Error> { get_remote_wasm_from_hash(&client, &hash).await? } else if let Some(contract_id) = &args.contract_id { - let res = wasm::fetch_from_contract(contract_id, network, &args.locator).await; + let contract_id = + contract_id.resolve_contract_id(&args.locator, &network.network_passphrase)?; + let res = wasm::fetch_from_contract(&contract_id, network).await; if let Some(ContractIsStellarAsset) = res.as_ref().err() { return Ok(None); } diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 47e2699fc..04eeaebd6 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -40,7 +40,7 @@ use soroban_spec_tools::contract; pub struct Cmd { /// Contract ID to invoke #[arg(long = "id", env = "STELLAR_CONTRACT_ID")] - pub contract_id: String, + pub contract_id: config::ContractAddress, // For testing only #[arg(skip)] pub wasm: Option, @@ -217,9 +217,8 @@ impl NetworkRunnable for Cmd { let network = config.get_network()?; tracing::trace!(?network); let contract_id = self - .config - .locator - .resolve_contract_id(&self.contract_id, &network.network_passphrase)?; + .contract_id + .resolve_contract_id(&config.locator, &network.network_passphrase)?; let spec_entries = self.spec_entries()?; if let Some(spec_entries) = &spec_entries { diff --git a/cmd/soroban-cli/src/commands/events.rs b/cmd/soroban-cli/src/commands/events.rs index a1f5de921..48d79c1b7 100644 --- a/cmd/soroban-cli/src/commands/events.rs +++ b/cmd/soroban-cli/src/commands/events.rs @@ -42,7 +42,7 @@ pub struct Cmd { num_args = 1..=6, help_heading = "FILTERS" )] - contract_ids: Vec, + contract_ids: Vec, /// A set of (up to 4) topic filters to filter event topics on. A single /// topic filter can contain 1-4 different segment filters, separated by /// commas, with an asterisk (`*` character) indicating a wildcard segment. @@ -218,9 +218,8 @@ impl NetworkRunnable for Cmd { .contract_ids .iter() .map(|id| { - Ok(self - .locator - .resolve_contract_id(id, &network.network_passphrase)? + Ok(id + .resolve_contract_id(&self.locator, &network.network_passphrase)? .to_string()) }) .collect::, Error>>()?; diff --git a/cmd/soroban-cli/src/config/alias.rs b/cmd/soroban-cli/src/config/alias.rs index 68ad556d6..9d1d8c11b 100644 --- a/cmd/soroban-cli/src/config/alias.rs +++ b/cmd/soroban-cli/src/config/alias.rs @@ -1,8 +1,49 @@ -use std::collections::HashMap; +use std::{collections::HashMap, convert::Infallible, str::FromStr}; use serde::{Deserialize, Serialize}; +use super::locator; + #[derive(Serialize, Deserialize, Default)] pub struct Data { pub ids: HashMap, } + +/// Address can be either a contract address, C.. or eventually an alias of a contract address. +#[derive(Clone, Debug)] +pub enum ContractAddress { + ContractId(stellar_strkey::Contract), + Alias(String), +} + +impl Default for ContractAddress { + fn default() -> Self { + ContractAddress::Alias(String::default()) + } +} + +impl FromStr for ContractAddress { + type Err = Infallible; + + fn from_str(value: &str) -> Result { + Ok(stellar_strkey::Contract::from_str(value).map_or_else( + |_| ContractAddress::Alias(value.to_string()), + ContractAddress::ContractId, + )) + } +} + +impl ContractAddress { + pub fn resolve_contract_id( + &self, + locator: &locator::Args, + network_passphrase: &str, + ) -> Result { + match self { + ContractAddress::ContractId(muxed_account) => Ok(*muxed_account), + ContractAddress::Alias(alias) => locator + .get_contract_id(alias, network_passphrase)? + .ok_or_else(|| locator::Error::ContractNotFound(alias.to_owned())), + } + } +} diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index 60b7856a6..8f51975a1 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -76,6 +76,8 @@ pub enum Error { CannotAccessAliasConfigFile, #[error("cannot parse contract ID {0}: {1}")] CannotParseContractId(String, DecodeError), + #[error("contract not found: {0}")] + ContractNotFound(String), #[error("Failed to read upgrade check file: {path}: {error}")] UpgradeCheckReadFailed { path: PathBuf, error: io::Error }, #[error("Failed to write upgrade check file: {path}: {error}")] @@ -332,12 +334,17 @@ impl Args { &self, alias: &str, network_passphrase: &str, - ) -> Result, Error> { + ) -> Result, Error> { let Some(alias_data) = self.load_contract_from_alias(alias)? else { return Ok(None); }; - Ok(alias_data.ids.get(network_passphrase).cloned()) + alias_data + .ids + .get(network_passphrase) + .map(|id| id.parse()) + .transpose() + .map_err(|e| Error::CannotParseContractId(alias.to_owned(), e)) } pub fn resolve_contract_id( @@ -345,14 +352,12 @@ impl Args { alias_or_contract_id: &str, network_passphrase: &str, ) -> Result { - let contract_id = self - .get_contract_id(alias_or_contract_id, network_passphrase)? - .unwrap_or_else(|| alias_or_contract_id.to_string()); - - Ok(Contract( - soroban_spec_tools::utils::contract_id_from_str(&contract_id) - .map_err(|e| Error::CannotParseContractId(contract_id.clone(), e))?, - )) + let Some(contract) = self.get_contract_id(alias_or_contract_id, network_passphrase)? else { + return alias_or_contract_id + .parse() + .map_err(|e| Error::CannotParseContractId(alias_or_contract_id.to_owned(), e)); + }; + Ok(contract) } } diff --git a/cmd/soroban-cli/src/config/mod.rs b/cmd/soroban-cli/src/config/mod.rs index 11eb179ee..6cd9b4d71 100644 --- a/cmd/soroban-cli/src/config/mod.rs +++ b/cmd/soroban-cli/src/config/mod.rs @@ -23,6 +23,8 @@ pub mod secret; pub mod sign_with; pub mod upgrade_check; +pub use alias::ContractAddress; + #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] diff --git a/cmd/soroban-cli/src/key.rs b/cmd/soroban-cli/src/key.rs index 3f4dcaf7c..b704541c4 100644 --- a/cmd/soroban-cli/src/key.rs +++ b/cmd/soroban-cli/src/key.rs @@ -4,7 +4,7 @@ use crate::xdr::{ }; use crate::{ commands::contract::Durability, - config::{locator, network::Network}, + config::{alias, locator, network::Network}, wasm, }; use clap::arg; @@ -34,7 +34,7 @@ pub struct Args { required_unless_present = "wasm", required_unless_present = "wasm_hash" )] - pub contract_id: Option, + pub contract_id: Option, /// Storage key (symbols only) #[arg(long = "key", conflicts_with = "key_xdr")] pub key: Option>, @@ -97,8 +97,11 @@ impl Args { } else { vec![ScVal::LedgerKeyContractInstance] }; - let contract = - locator.resolve_contract_id(self.contract_id.as_ref().unwrap(), network_passphrase)?; + let contract = self + .contract_id + .as_ref() + .unwrap() + .resolve_contract_id(locator, network_passphrase)?; Ok(keys .into_iter() diff --git a/cmd/soroban-cli/src/wasm.rs b/cmd/soroban-cli/src/wasm.rs index 5f0ed3b5c..a907e021a 100644 --- a/cmd/soroban-cli/src/wasm.rs +++ b/cmd/soroban-cli/src/wasm.rs @@ -121,16 +121,10 @@ pub fn len(p: &Path) -> Result { } pub async fn fetch_from_contract( - contract_id: &str, + stellar_strkey::Contract(contract_id): &stellar_strkey::Contract, network: &Network, - locator: &locator::Args, ) -> Result, Error> { tracing::trace!(?network); - - let contract_id = &locator - .resolve_contract_id(contract_id, &network.network_passphrase)? - .0; - let client = network.rpc_client()?; client .verify_network_passphrase(Some(&network.network_passphrase))