soroban_cli/
wasm.rs

1use crate::xdr::{self, Hash, LedgerKey, LedgerKeyContractCode};
2use clap::arg;
3use sha2::{Digest, Sha256};
4use soroban_spec_tools::contract::{self, Spec};
5use std::{
6    fs, io,
7    path::{Path, PathBuf},
8};
9use stellar_xdr::curr::{ContractDataEntry, ContractExecutable, ScVal};
10
11use crate::{
12    config::{
13        locator,
14        network::{Error as NetworkError, Network},
15    },
16    utils::{self, rpc::get_remote_wasm_from_hash},
17    wasm::Error::{ContractIsStellarAsset, UnexpectedContractToken},
18};
19
20#[derive(thiserror::Error, Debug)]
21pub enum Error {
22    #[error("reading file {filepath}: {error}")]
23    CannotReadContractFile {
24        filepath: std::path::PathBuf,
25        error: io::Error,
26    },
27    #[error("cannot parse wasm file {file}: {error}")]
28    CannotParseWasm {
29        file: std::path::PathBuf,
30        error: wasmparser::BinaryReaderError,
31    },
32    #[error("xdr processing error: {0}")]
33    Xdr(#[from] xdr::Error),
34
35    #[error(transparent)]
36    Parser(#[from] wasmparser::BinaryReaderError),
37    #[error(transparent)]
38    ContractSpec(#[from] contract::Error),
39
40    #[error(transparent)]
41    Locator(#[from] locator::Error),
42    #[error(transparent)]
43    Rpc(#[from] soroban_rpc::Error),
44    #[error("unexpected contract data {0:?}")]
45    UnexpectedContractToken(Box<ContractDataEntry>),
46    #[error(
47        "cannot fetch wasm for contract because the contract is \
48    a network built-in asset contract that does not have a downloadable code binary"
49    )]
50    ContractIsStellarAsset,
51    #[error(transparent)]
52    Network(#[from] NetworkError),
53}
54
55#[derive(Debug, clap::Args, Clone)]
56#[group(skip)]
57pub struct Args {
58    /// Path to wasm binary
59    #[arg(long)]
60    pub wasm: PathBuf,
61}
62
63impl Args {
64    /// # Errors
65    /// May fail to read wasm file
66    pub fn read(&self) -> Result<Vec<u8>, Error> {
67        fs::read(&self.wasm).map_err(|e| Error::CannotReadContractFile {
68            filepath: self.wasm.clone(),
69            error: e,
70        })
71    }
72
73    /// # Errors
74    /// May fail to read wasm file
75    pub fn len(&self) -> Result<u64, Error> {
76        len(&self.wasm)
77    }
78
79    /// # Errors
80    /// May fail to read wasm file
81    pub fn is_empty(&self) -> Result<bool, Error> {
82        self.len().map(|len| len == 0)
83    }
84
85    /// # Errors
86    /// May fail to read wasm file or parse xdr section
87    pub fn parse(&self) -> Result<Spec, Error> {
88        let contents = self.read()?;
89        Ok(Spec::new(&contents)?)
90    }
91
92    pub fn hash(&self) -> Result<Hash, Error> {
93        Ok(Hash(Sha256::digest(self.read()?).into()))
94    }
95}
96
97impl From<&PathBuf> for Args {
98    fn from(wasm: &PathBuf) -> Self {
99        Self { wasm: wasm.clone() }
100    }
101}
102
103impl TryInto<LedgerKey> for Args {
104    type Error = Error;
105    fn try_into(self) -> Result<LedgerKey, Self::Error> {
106        Ok(LedgerKey::ContractCode(LedgerKeyContractCode {
107            hash: utils::contract_hash(&self.read()?)?,
108        }))
109    }
110}
111
112/// # Errors
113/// May fail to read wasm file
114pub fn len(p: &Path) -> Result<u64, Error> {
115    Ok(std::fs::metadata(p)
116        .map_err(|e| Error::CannotReadContractFile {
117            filepath: p.to_path_buf(),
118            error: e,
119        })?
120        .len())
121}
122
123pub async fn fetch_from_contract(
124    stellar_strkey::Contract(contract_id): &stellar_strkey::Contract,
125    network: &Network,
126) -> Result<Vec<u8>, Error> {
127    tracing::trace!(?network);
128    let client = network.rpc_client()?;
129    client
130        .verify_network_passphrase(Some(&network.network_passphrase))
131        .await?;
132    let data_entry = client.get_contract_data(contract_id).await?;
133    if let ScVal::ContractInstance(contract) = &data_entry.val {
134        return match &contract.executable {
135            ContractExecutable::Wasm(hash) => Ok(get_remote_wasm_from_hash(&client, hash).await?),
136            ContractExecutable::StellarAsset => Err(ContractIsStellarAsset),
137        };
138    }
139    Err(UnexpectedContractToken(Box::new(data_entry)))
140}
141
142pub async fn fetch_from_wasm_hash(hash: Hash, network: &Network) -> Result<Vec<u8>, Error> {
143    tracing::trace!(?network);
144    let client = network.rpc_client()?;
145    Ok(get_remote_wasm_from_hash(&client, &hash).await?)
146}