diff --git a/Cargo.lock b/Cargo.lock index 9bb3cc694..7a54f07a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4417,9 +4417,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.8.3" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e73139bc5ec2d45e6c5fd85be5a46949c1c39a4c18e56915f5eb4c12f975e377" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" dependencies = [ "base64 0.22.1", "chrono", @@ -4435,9 +4435,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.8.3" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b80d3d6b56b64335c0180e5ffde23b3c5e08c14c585b51a15bd0e95393f46703" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" dependencies = [ "darling", "proc-macro2", diff --git a/cmd/soroban-cli/src/commands/contract/fetch.rs b/cmd/soroban-cli/src/commands/contract/fetch.rs index 9fc66e84e..830db5cd4 100644 --- a/cmd/soroban-cli/src/commands/contract/fetch.rs +++ b/cmd/soroban-cli/src/commands/contract/fetch.rs @@ -6,12 +6,15 @@ use std::str::FromStr; use std::{fmt::Debug, fs, io}; use clap::{arg, command, Parser}; +use stellar_xdr::curr::{ContractDataEntry, ContractExecutable, ScVal}; +use crate::commands::contract::fetch::Error::{ContractIsStellarAsset, UnexpectedContractToken}; use crate::commands::{global, NetworkRunnable}; use crate::config::{ self, locator, network::{self, Network}, }; +use crate::utils::rpc::get_remote_wasm_from_hash; use crate::{ rpc::{self, Client}, Pwd, @@ -64,6 +67,13 @@ pub enum Error { Network(#[from] network::Error), #[error("cannot create contract directory for {0:?}")] CannotCreateContractDir(PathBuf), + #[error("unexpected contract data {0:?}")] + UnexpectedContractToken(ContractDataEntry), + #[error( + "cannot fetch wasm for contract because the contract is \ + a network built-in asset contract that does not have a downloadable code binary" + )] + ContractIsStellarAsset(), } impl From for Error { @@ -126,6 +136,15 @@ impl NetworkRunnable for Cmd { client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; - Ok(client.get_remote_wasm(&contract_id).await?) + let data_entry = client.get_contract_data(&contract_id).await?; + if let ScVal::ContractInstance(contract) = &data_entry.val { + return match &contract.executable { + ContractExecutable::Wasm(hash) => { + Ok(get_remote_wasm_from_hash(&client, hash).await?) + } + ContractExecutable::StellarAsset => Err(ContractIsStellarAsset()), + }; + } + return Err(UnexpectedContractToken(data_entry)); } } diff --git a/cmd/soroban-cli/src/get_spec.rs b/cmd/soroban-cli/src/get_spec.rs index cd9477de2..125fa984c 100644 --- a/cmd/soroban-cli/src/get_spec.rs +++ b/cmd/soroban-cli/src/get_spec.rs @@ -10,6 +10,7 @@ pub use soroban_spec_tools::contract as contract_spec; use crate::commands::global; use crate::config::{self, data, locator, network}; use crate::rpc; +use crate::utils::rpc::get_remote_wasm_from_hash; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -65,7 +66,7 @@ pub async fn get_remote_contract_spec( if let Ok(entries) = data::read_spec(&hash_str) { entries } else { - let raw_wasm = client.get_remote_wasm_from_hash(hash).await?; + let raw_wasm = get_remote_wasm_from_hash(&client, &hash).await?; let res = contract_spec::Spec::new(&raw_wasm)?; let res = res.spec; if global_args.map_or(true, |a| !a.no_cache) { diff --git a/cmd/soroban-cli/src/utils.rs b/cmd/soroban-cli/src/utils.rs index e74222c2c..88daa9a80 100644 --- a/cmd/soroban-cli/src/utils.rs +++ b/cmd/soroban-cli/src/utils.rs @@ -129,6 +129,29 @@ pub fn contract_id_hash_from_asset( Ok(Hash(Sha256::digest(preimage_xdr).into())) } +pub mod rpc { + use soroban_env_host::xdr; + use soroban_rpc::{Client, Error}; + use stellar_xdr::curr::{Hash, LedgerEntryData, LedgerKey, Limits, ReadXdr}; + + pub async fn get_remote_wasm_from_hash(client: &Client, hash: &Hash) -> Result, Error> { + let code_key = LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() }); + let contract_data = client.get_ledger_entries(&[code_key]).await?; + let entries = contract_data.entries.unwrap_or_default(); + if entries.is_empty() { + return Err(Error::NotFound( + "Contract Code".to_string(), + hex::encode(hash), + )); + } + let contract_data_entry = &entries[0]; + match LedgerEntryData::from_xdr_base64(&contract_data_entry.xdr, Limits::none())? { + LedgerEntryData::ContractCode(xdr::ContractCodeEntry { code, .. }) => Ok(code.into()), + scval => Err(Error::UnexpectedContractCodeDataType(scval)), + } + } +} + pub mod parsing { use regex::Regex;