Skip to content

Commit

Permalink
fix: Add ContractAddress type to consolidate contract id parsing with…
Browse files Browse the repository at this point in the history
… aliases (#1692)

* fix: resolve contract id

* refactor: Refactor contract ID handling in Args module.
  • Loading branch information
willemneal authored Nov 15, 2024
1 parent bc49f4c commit b37da02
Show file tree
Hide file tree
Showing 13 changed files with 118 additions and 65 deletions.
38 changes: 19 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions FULL_HELP_DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ Add contract alias

###### **Arguments:**

* `<ALIAS>` — The contract alias that will be removed
* `<ALIAS>` — The contract alias that will be used

###### **Options:**

Expand Down Expand Up @@ -522,7 +522,7 @@ Outputs no data when no data is present in the contract.

* `--wasm <WASM>` — Wasm file to extract the data from
* `--wasm-hash <WASM_HASH>` — Wasm hash to get the data for
* `--id <CONTRACT_ID>` — Contract id to get the data for
* `--id <CONTRACT_ID>` — Contract id or contract alias to get the data for
* `--rpc-url <RPC_URL>` — RPC server endpoint
* `--rpc-header <RPC_HEADERS>` — RPC Header(s) to include in requests to the RPC provider
* `--network-passphrase <NETWORK_PASSPHRASE>` — Network passphrase to sign the transaction sent to the rpc server
Expand Down Expand Up @@ -562,7 +562,7 @@ Outputs no data when no data is present in the contract.

* `--wasm <WASM>` — Wasm file to extract the data from
* `--wasm-hash <WASM_HASH>` — Wasm hash to get the data for
* `--id <CONTRACT_ID>` — Contract id to get the data for
* `--id <CONTRACT_ID>` — Contract id or contract alias to get the data for
* `--rpc-url <RPC_URL>` — RPC server endpoint
* `--rpc-header <RPC_HEADERS>` — RPC Header(s) to include in requests to the RPC provider
* `--network-passphrase <NETWORK_PASSPHRASE>` — Network passphrase to sign the transaction sent to the rpc server
Expand Down Expand Up @@ -602,7 +602,7 @@ Outputs no data when no data is present in the contract.

* `--wasm <WASM>` — Wasm file to extract the data from
* `--wasm-hash <WASM_HASH>` — Wasm hash to get the data for
* `--id <CONTRACT_ID>` — Contract id to get the data for
* `--id <CONTRACT_ID>` — Contract id or contract alias to get the data for
* `--rpc-url <RPC_URL>` — RPC server endpoint
* `--rpc-header <RPC_HEADERS>` — RPC Header(s) to include in requests to the RPC provider
* `--network-passphrase <NETWORK_PASSPHRASE>` — Network passphrase to sign the transaction sent to the rpc server
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
8 changes: 4 additions & 4 deletions cmd/soroban-cli/src/commands/contract/alias/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -41,7 +41,7 @@ pub enum Error {
AlreadyExist {
alias: String,
network_passphrase: String,
contract: String,
contract: stellar_strkey::Contract,
},
}

Expand All @@ -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(),
Expand Down
10 changes: 8 additions & 2 deletions cmd/soroban-cli/src/commands/contract/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::path::PathBuf>,
Expand Down Expand Up @@ -111,6 +111,12 @@ impl NetworkRunnable for Cmd {
config: Option<&config::Args>,
) -> Result<Vec<u8>, 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?)
}
}
14 changes: 9 additions & 5 deletions cmd/soroban-cli/src/commands/contract/info/shared.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -24,9 +24,9 @@ pub struct Args {
/// Wasm hash to get the data for
#[arg(long = "wasm-hash", group = "Source")]
pub wasm_hash: Option<String>,
/// 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<String>,
pub contract_id: Option<config::ContractAddress>,
#[command(flatten)]
pub network: network::Args,
#[command(flatten)]
Expand Down Expand Up @@ -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<Option<Vec<u8>>, Error> {
Expand Down Expand Up @@ -84,7 +86,9 @@ pub async fn fetch_wasm(args: &Args) -> Result<Option<Vec<u8>>, 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);
}
Expand Down
7 changes: 3 additions & 4 deletions cmd/soroban-cli/src/commands/contract/invoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::path::PathBuf>,
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 3 additions & 4 deletions cmd/soroban-cli/src/commands/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub struct Cmd {
num_args = 1..=6,
help_heading = "FILTERS"
)]
contract_ids: Vec<String>,
contract_ids: Vec<config::ContractAddress>,
/// 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.
Expand Down Expand Up @@ -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::<Result<Vec<_>, Error>>()?;
Expand Down
43 changes: 42 additions & 1 deletion cmd/soroban-cli/src/config/alias.rs
Original file line number Diff line number Diff line change
@@ -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<String, String>,
}

/// 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<Self, Self::Err> {
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<stellar_strkey::Contract, locator::Error> {
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())),
}
}
}
25 changes: 15 additions & 10 deletions cmd/soroban-cli/src/config/locator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}")]
Expand Down Expand Up @@ -332,27 +334,30 @@ impl Args {
&self,
alias: &str,
network_passphrase: &str,
) -> Result<Option<String>, Error> {
) -> Result<Option<Contract>, 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(
&self,
alias_or_contract_id: &str,
network_passphrase: &str,
) -> Result<Contract, Error> {
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)
}
}

Expand Down
Loading

0 comments on commit b37da02

Please sign in to comment.