soroban_cli/
wasm.rs

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