soroban_cli/commands/contract/info/
shared.rs1use 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 #[arg(
29 long,
30 group = "Source",
31 conflicts_with = "contract_id",
32 conflicts_with = "wasm_hash"
33 )]
34 pub wasm: Option<PathBuf>,
35 #[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 #[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 #[default]
63 Text,
64 XdrBase64,
66 Json,
68 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 if let Some(path) = &args.wasm {
126 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 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}