Skip to main content

soroban_cli/commands/contract/
fetch.rs

1use 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    /// Contract ID to fetch
23    #[arg(long = "id", env = "STELLAR_CONTRACT_ID")]
24    pub contract_id: Option<config::UnresolvedContract>,
25    /// Wasm to fetch
26    #[arg(long = "wasm-hash", conflicts_with = "contract_id")]
27    pub wasm_hash: Option<String>,
28    /// Where to write output otherwise stdout is used
29    #[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}