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