soroban_cli/commands/contract/
fetch.rs1use std::convert::Infallible;
2
3use std::io::Write;
4use std::path::{Path, PathBuf};
5use std::str::FromStr;
6use std::{fmt::Debug, fs, io};
7
8use clap::Parser;
9
10use crate::{
11 config::{
12 self, locator,
13 network::{self, Network},
14 },
15 wasm, xdr, Pwd,
16};
17
18#[derive(Parser, Debug, Default, Clone)]
19#[allow(clippy::struct_excessive_bools)]
20#[group(skip)]
21pub struct Cmd {
22 #[arg(long = "id", env = "STELLAR_CONTRACT_ID")]
24 pub contract_id: Option<config::UnresolvedContract>,
25 #[arg(long = "wasm-hash", conflicts_with = "contract_id")]
27 pub wasm_hash: Option<String>,
28 #[arg(long, short = 'o')]
30 pub out_file: Option<std::path::PathBuf>,
31 #[command(flatten)]
32 pub locator: locator::Args,
33 #[command(flatten)]
34 pub network: network::Args,
35}
36
37impl FromStr for Cmd {
38 type Err = clap::error::Error;
39
40 fn from_str(s: &str) -> Result<Self, Self::Err> {
41 use clap::{CommandFactory, FromArgMatches};
42 Self::from_arg_matches_mut(&mut Self::command().get_matches_from(s.split_whitespace()))
43 }
44}
45
46impl Pwd for Cmd {
47 fn set_pwd(&mut self, pwd: &Path) {
48 self.locator.set_pwd(pwd);
49 }
50}
51
52#[derive(thiserror::Error, Debug)]
53pub enum Error {
54 #[error(transparent)]
55 Config(#[from] config::Error),
56 #[error(transparent)]
57 Locator(#[from] locator::Error),
58 #[error(transparent)]
59 Io(#[from] std::io::Error),
60 #[error("reading file {0:?}: {1}")]
61 CannotWriteContractFile(PathBuf, io::Error),
62 #[error(transparent)]
63 Network(#[from] network::Error),
64 #[error("cannot create contract directory for {0:?}")]
65 CannotCreateContractDir(PathBuf),
66 #[error(transparent)]
67 Wasm(#[from] wasm::Error),
68 #[error("wasm hash is invalid {0:?}")]
69 InvalidWasmHash(String),
70 #[error("must provide one of --wasm-hash, or --id")]
71 MissingArg,
72}
73
74impl From<Infallible> for Error {
75 fn from(_: Infallible) -> Self {
76 unreachable!()
77 }
78}
79
80impl Cmd {
81 pub async fn run(&self) -> Result<(), Error> {
82 let bytes = self.get_bytes().await?;
83 if let Some(out_file) = &self.out_file {
84 if let Some(parent) = out_file.parent() {
85 if !parent.exists() {
86 fs::create_dir_all(parent)
87 .map_err(|_| Error::CannotCreateContractDir(out_file.clone()))?;
88 }
89 }
90 fs::write(out_file, bytes)
91 .map_err(|io| Error::CannotWriteContractFile(out_file.clone(), io))
92 } else {
93 let stdout = std::io::stdout();
94 let mut handle = stdout.lock();
95 handle.write_all(&bytes)?;
96 handle.flush()?;
97 Ok(())
98 }
99 }
100
101 pub async fn get_bytes(&self) -> Result<Vec<u8>, Error> {
102 self.execute(&config::Args {
103 locator: self.locator.clone(),
104 network: self.network.clone(),
105 source_account: config::UnresolvedMuxedAccount::default(),
106 sign_with: config::sign_with::Args::default(),
107 fee: None,
108 inclusion_fee: None,
109 })
110 .await
111 }
112
113 pub fn network(&self) -> Result<Network, Error> {
114 Ok(self.network.get(&self.locator)?)
115 }
116
117 pub async fn execute(&self, config: &config::Args) -> Result<Vec<u8>, Error> {
118 let network = config.get_network()?;
119 if let Some(contract_id) = &self.contract_id {
120 Ok(wasm::fetch_from_contract(
121 &contract_id.resolve_contract_id(&self.locator, &network.network_passphrase)?,
122 &network,
123 )
124 .await?)
125 } else if let Some(wasm_hash) = &self.wasm_hash {
126 let hash = hex::decode(wasm_hash)
127 .map_err(|_| Error::InvalidWasmHash(wasm_hash.clone()))?
128 .try_into()
129 .map_err(|_| Error::InvalidWasmHash(wasm_hash.clone()))?;
130 let hash = xdr::Hash(hash);
131 Ok(wasm::fetch_from_wasm_hash(hash, &network).await?)
132 } else {
133 Err(Error::MissingArg)
134 }
135 }
136}