Skip to main content

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