soroban_cli/commands/contract/info/
shared.rs

1use std::path::PathBuf;
2
3use clap::arg;
4
5use crate::{
6    commands::contract::info::shared::Error::InvalidWasmHash,
7    config::{
8        self, locator,
9        network::{self, Network},
10    },
11    print::Print,
12    utils::rpc::get_remote_wasm_from_hash,
13    wasm::{self, Error::ContractIsStellarAsset},
14    xdr,
15};
16
17use stellar_xdr::curr::ContractId;
18
19#[derive(Debug, clap::Args, Clone, Default)]
20#[command(group(
21    clap::ArgGroup::new("Source")
22    .required(true)
23    .args(& ["wasm", "wasm_hash", "contract_id"]),
24))]
25#[group(skip)]
26pub struct Args {
27    /// Wasm file path on local filesystem. Provide this OR `--wasm-hash` OR `--contract-id`.
28    #[arg(
29        long,
30        group = "Source",
31        conflicts_with = "contract_id",
32        conflicts_with = "wasm_hash"
33    )]
34    pub wasm: Option<PathBuf>,
35    /// Hash of Wasm blob on a network. Provide this OR `--wasm` OR `--contract-id`.
36    #[arg(
37        long = "wasm-hash",
38        group = "Source",
39        conflicts_with = "contract_id",
40        conflicts_with = "wasm"
41    )]
42    pub wasm_hash: Option<String>,
43    /// Contract ID/alias on a network. Provide this OR `--wasm-hash` OR `--wasm`.
44    #[arg(
45        long,
46        env = "STELLAR_CONTRACT_ID",
47        group = "Source",
48        visible_alias = "id",
49        conflicts_with = "wasm",
50        conflicts_with = "wasm_hash"
51    )]
52    pub contract_id: Option<config::UnresolvedContract>,
53    #[command(flatten)]
54    pub network: network::Args,
55    #[command(flatten)]
56    pub locator: locator::Args,
57}
58
59#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum, Default)]
60pub enum MetasInfoOutput {
61    /// Text output of the meta info entry
62    #[default]
63    Text,
64    /// XDR output of the info entry
65    XdrBase64,
66    /// JSON output of the info entry (one line, not formatted)
67    Json,
68    /// Formatted (multiline) JSON output of the info entry
69    JsonFormatted,
70}
71
72#[derive(thiserror::Error, Debug)]
73pub enum Error {
74    #[error(transparent)]
75    Network(#[from] network::Error),
76    #[error(transparent)]
77    Wasm(#[from] wasm::Error),
78    #[error("provided wasm hash is invalid {0:?}")]
79    InvalidWasmHash(String),
80    #[error("must provide one of --wasm, --wasm-hash, or --contract-id")]
81    MissingArg,
82    #[error(transparent)]
83    Rpc(#[from] soroban_rpc::Error),
84    #[error(transparent)]
85    Locator(#[from] locator::Error),
86}
87
88pub struct Fetched {
89    pub contract: Contract,
90    pub source: Source,
91}
92
93pub enum Contract {
94    Wasm { wasm_bytes: Vec<u8> },
95    StellarAssetContract,
96}
97
98pub enum Source {
99    File {
100        path: PathBuf,
101    },
102    Wasm {
103        hash: String,
104        network: Network,
105    },
106    Contract {
107        resolved_address: String,
108        network: Network,
109    },
110}
111
112impl Source {
113    pub fn network(&self) -> Option<&Network> {
114        match self {
115            Source::File { .. } => None,
116            Source::Wasm { ref network, .. } | Source::Contract { ref network, .. } => {
117                Some(network)
118            }
119        }
120    }
121}
122
123pub async fn fetch(args: &Args, print: &Print) -> Result<Fetched, Error> {
124    // Check if a local WASM file path is provided
125    if let Some(path) = &args.wasm {
126        // Read the WASM file and return its contents
127        print.infoln("Loading contract spec from file...");
128        let wasm_bytes = wasm::Args { wasm: path.clone() }.read()?;
129        return Ok(Fetched {
130            contract: Contract::Wasm { wasm_bytes },
131            source: Source::File { path: path.clone() },
132        });
133    }
134
135    // If no local wasm, then check for wasm_hash and fetch from the network
136    let network = &args.network.get(&args.locator)?;
137    print.infoln(format!("Network: {}", network.network_passphrase));
138
139    if let Some(wasm_hash) = &args.wasm_hash {
140        let hash = hex::decode(wasm_hash)
141            .map_err(|_| InvalidWasmHash(wasm_hash.clone()))?
142            .try_into()
143            .map_err(|_| InvalidWasmHash(wasm_hash.clone()))?;
144
145        let hash = xdr::Hash(hash);
146
147        let client = network.rpc_client()?;
148
149        client
150            .verify_network_passphrase(Some(&network.network_passphrase))
151            .await?;
152
153        print.globeln(format!(
154            "Downloading contract spec for wasm hash: {wasm_hash}"
155        ));
156        let wasm_bytes = get_remote_wasm_from_hash(&client, &hash).await?;
157        Ok(Fetched {
158            contract: Contract::Wasm { wasm_bytes },
159            source: Source::Wasm {
160                hash: wasm_hash.clone(),
161                network: network.clone(),
162            },
163        })
164    } else if let Some(contract_id) = &args.contract_id {
165        let contract_id =
166            contract_id.resolve_contract_id(&args.locator, &network.network_passphrase)?;
167        let derived_address =
168            xdr::ScAddress::Contract(ContractId(xdr::Hash(contract_id.0))).to_string();
169        print.globeln(format!("Downloading contract spec: {derived_address}"));
170        let res = wasm::fetch_from_contract(&contract_id, network).await;
171        if let Some(ContractIsStellarAsset) = res.as_ref().err() {
172            return Ok(Fetched {
173                contract: Contract::StellarAssetContract,
174                source: Source::Contract {
175                    resolved_address: derived_address,
176                    network: network.clone(),
177                },
178            });
179        }
180        Ok(Fetched {
181            contract: Contract::Wasm { wasm_bytes: res? },
182            source: Source::Contract {
183                resolved_address: derived_address,
184                network: network.clone(),
185            },
186        })
187    } else {
188        Err(Error::MissingArg)
189    }
190}