diff --git a/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs b/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs index 5317c81f3..1da64eaa5 100644 --- a/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs +++ b/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs @@ -1,12 +1,12 @@ use std::{ffi::OsString, fmt::Debug, path::PathBuf}; use clap::{command, Parser}; -use soroban_spec_tools::contract as contract_spec; +use soroban_spec_tools::contract as spec_tools; use soroban_spec_typescript::boilerplate::Project; use crate::print::Print; use crate::{ - commands::{contract::info::shared as wasm_or_contract, global, NetworkRunnable}, + commands::{contract::info::shared as contract_spec, global, NetworkRunnable}, config, }; use soroban_spec_tools::contract::Spec; @@ -15,7 +15,7 @@ use soroban_spec_tools::contract::Spec; #[group(skip)] pub struct Cmd { #[command(flatten)] - pub wasm_or_hash_or_contract_id: wasm_or_contract::Args, + pub wasm_or_hash_or_contract_id: contract_spec::Args, /// Where to place generated project #[arg(long)] pub output_dir: PathBuf, @@ -39,11 +39,11 @@ pub enum Error { NotUtf8(OsString), #[error(transparent)] - Spec(#[from] contract_spec::Error), + Spec(#[from] spec_tools::Error), #[error("Failed to get file name from path: {0:?}")] FailedToGetFileName(PathBuf), #[error(transparent)] - WasmOrContract(#[from] wasm_or_contract::Error), + WasmOrContract(#[from] contract_spec::Error), #[error(transparent)] Xdr(#[from] crate::xdr::Error), } @@ -60,13 +60,14 @@ impl NetworkRunnable for Cmd { ) -> Result<(), Error> { let print = Print::new(global_args.is_some_and(|a| a.quiet)); - let (spec, contract_address, network) = - wasm_or_contract::fetch_wasm(&self.wasm_or_hash_or_contract_id, &print).await?; + let contract_spec::Fetched { contract, source } = + contract_spec::fetch(&self.wasm_or_hash_or_contract_id, &print).await?; - let spec = if let Some(spec) = spec { - Spec::new(&spec)?.spec - } else { - soroban_spec::read::parse_raw(&soroban_sdk::token::StellarAssetSpec::spec_xdr())? + let spec = match contract { + contract_spec::Contract::Wasm { wasm_bytes } => Spec::new(&wasm_bytes)?.spec, + contract_spec::Contract::StellarAssetContract => { + soroban_spec::read::parse_raw(&soroban_sdk::token::StellarAssetSpec::spec_xdr())? + } }; if self.output_dir.is_file() { @@ -88,12 +89,20 @@ impl NetworkRunnable for Cmd { let contract_name = &file_name .to_str() .ok_or_else(|| Error::NotUtf8(file_name.to_os_string()))?; - if let Some(contract_address) = contract_address.clone() { - print.infoln(format!("Embedding contract address: {contract_address}")); - } + let (resolved_address, network) = match source { + contract_spec::Source::Contract { + resolved_address, + network, + } => { + print.infoln(format!("Embedding contract address: {resolved_address}")); + (Some(resolved_address), Some(network)) + } + contract_spec::Source::Wasm { network, .. } => (None, Some(network)), + contract_spec::Source::File { .. } => (None, None), + }; p.init( contract_name, - contract_address.as_deref(), + resolved_address.as_deref(), network.as_ref().map(|n| n.rpc_url.as_ref()), network.as_ref().map(|n| n.network_passphrase.as_ref()), &spec, diff --git a/cmd/soroban-cli/src/commands/contract/info/env_meta.rs b/cmd/soroban-cli/src/commands/contract/info/env_meta.rs index f882d2737..02da6f439 100644 --- a/cmd/soroban-cli/src/commands/contract/info/env_meta.rs +++ b/cmd/soroban-cli/src/commands/contract/info/env_meta.rs @@ -9,7 +9,7 @@ use crate::{ commands::{ contract::info::{ env_meta::Error::{NoEnvMetaPresent, NoSACEnvMeta}, - shared::{self, fetch_wasm, MetasInfoOutput}, + shared::{self, fetch, Fetched, MetasInfoOutput}, }, global, }, @@ -43,12 +43,12 @@ pub enum Error { impl Cmd { pub async fn run(&self, global_args: &global::Args) -> Result { let print = Print::new(global_args.quiet); - let (bytes, ..) = fetch_wasm(&self.common, &print).await?; + let Fetched { contract, .. } = fetch(&self.common, &print).await?; - let Some(bytes) = bytes else { - return Err(NoSACEnvMeta()); + let spec = match contract { + shared::Contract::Wasm { wasm_bytes } => Spec::new(&wasm_bytes)?, + shared::Contract::StellarAssetContract => return Err(NoSACEnvMeta()), }; - let spec = Spec::new(&bytes)?; let Some(env_meta_base64) = spec.env_meta_base64 else { return Err(NoEnvMetaPresent()); diff --git a/cmd/soroban-cli/src/commands/contract/info/interface.rs b/cmd/soroban-cli/src/commands/contract/info/interface.rs index dd71150fa..0c896bc3d 100644 --- a/cmd/soroban-cli/src/commands/contract/info/interface.rs +++ b/cmd/soroban-cli/src/commands/contract/info/interface.rs @@ -1,8 +1,7 @@ use std::fmt::Debug; use crate::commands::contract::info::interface::Error::NoInterfacePresent; -use crate::commands::contract::info::shared; -use crate::commands::contract::info::shared::fetch_wasm; +use crate::commands::contract::info::shared::{self, fetch, Fetched}; use crate::commands::global; use crate::print::Print; use clap::{command, Parser}; @@ -47,18 +46,21 @@ pub enum Error { impl Cmd { pub async fn run(&self, global_args: &global::Args) -> Result { let print = Print::new(global_args.quiet); - let (bytes, ..) = fetch_wasm(&self.common, &print).await?; + let Fetched { contract, .. } = fetch(&self.common, &print).await?; - let (base64, spec) = if bytes.is_none() { - Spec::spec_to_base64(&soroban_sdk::token::StellarAssetSpec::spec_xdr())? - } else { - let spec = Spec::new(&bytes.unwrap())?; + let (base64, spec) = match contract { + shared::Contract::Wasm { wasm_bytes } => { + let spec = Spec::new(&wasm_bytes)?; - if spec.env_meta_base64.is_none() { - return Err(NoInterfacePresent()); - } + if spec.env_meta_base64.is_none() { + return Err(NoInterfacePresent()); + } - (spec.spec_base64.unwrap(), spec.spec) + (spec.spec_base64.unwrap(), spec.spec) + } + shared::Contract::StellarAssetContract => { + Spec::spec_to_base64(&soroban_sdk::token::StellarAssetSpec::spec_xdr())? + } }; let res = match self.output { diff --git a/cmd/soroban-cli/src/commands/contract/info/meta.rs b/cmd/soroban-cli/src/commands/contract/info/meta.rs index 4b6fdee3e..736e8f432 100644 --- a/cmd/soroban-cli/src/commands/contract/info/meta.rs +++ b/cmd/soroban-cli/src/commands/contract/info/meta.rs @@ -1,8 +1,7 @@ use std::fmt::Debug; use crate::commands::contract::info::meta::Error::{NoMetaPresent, NoSACMeta}; -use crate::commands::contract::info::shared; -use crate::commands::contract::info::shared::{fetch_wasm, MetasInfoOutput}; +use crate::commands::contract::info::shared::{self, fetch, Fetched, MetasInfoOutput}; use crate::commands::global; use crate::print::Print; use clap::{command, Parser}; @@ -36,12 +35,12 @@ pub enum Error { impl Cmd { pub async fn run(&self, global_args: &global::Args) -> Result { let print = Print::new(global_args.quiet); - let (bytes, ..) = fetch_wasm(&self.common, &print).await?; + let Fetched { contract, .. } = fetch(&self.common, &print).await?; - let Some(bytes) = bytes else { - return Err(NoSACMeta()); + let spec = match contract { + shared::Contract::Wasm { wasm_bytes } => Spec::new(&wasm_bytes)?, + shared::Contract::StellarAssetContract => return Err(NoSACMeta()), }; - let spec = Spec::new(&bytes)?; let Some(meta_base64) = spec.meta_base64 else { return Err(NoMetaPresent()); diff --git a/cmd/soroban-cli/src/commands/contract/info/shared.rs b/cmd/soroban-cli/src/commands/contract/info/shared.rs index 2ee5d018d..6023b03cf 100644 --- a/cmd/soroban-cli/src/commands/contract/info/shared.rs +++ b/cmd/soroban-cli/src/commands/contract/info/shared.rs @@ -76,23 +76,58 @@ pub enum Error { #[error("provided wasm hash is invalid {0:?}")] InvalidWasmHash(String), #[error("must provide one of --wasm, --wasm-hash, or --contract-id")] - MalformedWasmOrWasmHashOrContractId, + MissingArg, #[error(transparent)] Rpc(#[from] soroban_rpc::Error), #[error(transparent)] Locator(#[from] locator::Error), } -pub async fn fetch_wasm( - args: &Args, - print: &Print, -) -> Result<(Option>, Option, Option), Error> { +pub struct Fetched { + pub contract: Contract, + pub source: Source, +} + +pub enum Contract { + Wasm { wasm_bytes: Vec }, + StellarAssetContract, +} + +pub enum Source { + File { + path: PathBuf, + }, + Wasm { + hash: String, + network: Network, + }, + Contract { + resolved_address: String, + network: Network, + }, +} + +impl Source { + pub fn network(&self) -> Option<&Network> { + match self { + Source::File { .. } => None, + Source::Wasm { ref network, .. } | Source::Contract { ref network, .. } => { + Some(network) + } + } + } +} + +pub async fn fetch(args: &Args, print: &Print) -> Result { // Check if a local WASM file path is provided if let Some(path) = &args.wasm { // Read the WASM file and return its contents print.infoln("Loading contract spec from file..."); let wasm_bytes = wasm::Args { wasm: path.clone() }.read()?; - return Ok((Some(wasm_bytes), None, None)); + return Ok(Fetched { + contract: Contract::Wasm { wasm_bytes }, + source: Source::File { path: path.clone() }, + }); } // If no local wasm, then check for wasm_hash and fetch from the network @@ -116,22 +151,37 @@ pub async fn fetch_wasm( print.globeln(format!( "Downloading contract spec for wasm hash: {wasm_hash}" )); - Ok(( - Some(get_remote_wasm_from_hash(&client, &hash).await?), - None, - Some(network.clone()), - )) + let wasm_bytes = get_remote_wasm_from_hash(&client, &hash).await?; + Ok(Fetched { + contract: Contract::Wasm { wasm_bytes }, + source: Source::Wasm { + hash: wasm_hash.clone(), + network: network.clone(), + }, + }) } else if let Some(contract_id) = &args.contract_id { let contract_id = contract_id.resolve_contract_id(&args.locator, &network.network_passphrase)?; - let contract_address = xdr::ScAddress::Contract(xdr::Hash(contract_id.0)).to_string(); - print.globeln(format!("Downloading contract spec: {contract_address}")); + let derived_address = xdr::ScAddress::Contract(xdr::Hash(contract_id.0)).to_string(); + print.globeln(format!("Downloading contract spec: {derived_address}")); let res = wasm::fetch_from_contract(&contract_id, network).await; if let Some(ContractIsStellarAsset) = res.as_ref().err() { - return Ok((None, Some(contract_address), Some(network.clone()))); + return Ok(Fetched { + contract: Contract::StellarAssetContract, + source: Source::Contract { + resolved_address: derived_address, + network: network.clone(), + }, + }); } - Ok((Some(res?), Some(contract_address), Some(network.clone()))) + Ok(Fetched { + contract: Contract::Wasm { wasm_bytes: res? }, + source: Source::Contract { + resolved_address: derived_address, + network: network.clone(), + }, + }) } else { - return Err(Error::MalformedWasmOrWasmHashOrContractId); + return Err(Error::MissingArg); } }