Skip to main content

soroban_cli/
get_spec.rs

1use crate::xdr;
2
3use crate::xdr::{ContractDataEntry, ContractExecutable, ScContractInstance, ScSpecEntry, ScVal};
4
5use soroban_spec::read::FromWasmError;
6pub use soroban_spec_tools::contract as contract_spec;
7
8use crate::commands::global;
9use crate::config::{self, data, locator, network};
10use crate::rpc;
11use crate::utils::rpc::{get_remote_wasm_from_hash, resolve_external_ref_wasm_hash};
12
13#[derive(thiserror::Error, Debug)]
14pub enum Error {
15    #[error("parsing contract spec: {0}")]
16    CannotParseContractSpec(FromWasmError),
17    #[error(transparent)]
18    Rpc(#[from] rpc::Error),
19    #[error("missing result")]
20    MissingResult,
21    #[error(transparent)]
22    Data(#[from] data::Error),
23    #[error(transparent)]
24    Xdr(#[from] xdr::Error),
25    #[error(transparent)]
26    Network(#[from] network::Error),
27    #[error(transparent)]
28    Config(#[from] config::Error),
29    #[error(transparent)]
30    ContractSpec(#[from] contract_spec::Error),
31}
32
33///
34/// # Errors
35pub async fn get_remote_contract_spec(
36    contract_id: &[u8; 32],
37    locator: &locator::Args,
38    network: &network::Args,
39    global_args: Option<&global::Args>,
40    config: Option<&config::Args>,
41) -> Result<Vec<ScSpecEntry>, Error> {
42    let network = config.map_or_else(
43        || network.get(locator).map_err(Error::from),
44        |c| c.get_network().map_err(Error::from),
45    )?;
46    tracing::trace!(?network);
47    let client = network.rpc_client()?;
48    // Get contract data
49    let r = client.get_contract_data(contract_id).await?;
50    tracing::trace!("{r:?}");
51
52    let ContractDataEntry {
53        val: ScVal::ContractInstance(ScContractInstance { executable, .. }),
54        ..
55    } = r
56    else {
57        return Err(Error::MissingResult);
58    };
59
60    // Get the contract spec entries based on the executable type. Both a
61    // direct Wasm executable and a CAP-85 externally managed executable
62    // resolve to a Wasm hash whose spec is fetched (and cached) the same way.
63    Ok(match executable {
64        ContractExecutable::StellarAsset => {
65            soroban_spec::read::parse_raw(stellar_asset_spec::xdr())?
66        }
67        ContractExecutable::Wasm(hash) => {
68            get_spec_for_wasm_hash(&client, &hash, global_args).await?
69        }
70        ContractExecutable::ExternalRef(external_ref) => {
71            let hash = resolve_external_ref_wasm_hash(&client, &external_ref).await?;
72            get_spec_for_wasm_hash(&client, &hash, global_args).await?
73        }
74    })
75}
76
77async fn get_spec_for_wasm_hash(
78    client: &rpc::Client,
79    hash: &xdr::Hash,
80    global_args: Option<&global::Args>,
81) -> Result<Vec<ScSpecEntry>, Error> {
82    let hash_str = hash.to_string();
83    if let Ok(entries) = data::read_spec(&hash_str) {
84        return Ok(entries);
85    }
86    let raw_wasm = get_remote_wasm_from_hash(client, hash).await?;
87    let res = contract_spec::Spec::new(&raw_wasm)?.spec;
88    if global_args.is_none_or(|a| !a.no_cache) {
89        data::write_spec(&hash_str, &res)?;
90    }
91    Ok(res)
92}