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 #[arg(long)]
62 pub wasm: PathBuf,
63}
64
65impl Args {
66 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 pub fn len(&self) -> Result<u64, Error> {
78 len(&self.wasm)
79 }
80
81 pub fn is_empty(&self) -> Result<bool, Error> {
84 self.len().map(|len| len == 0)
85 }
86
87 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
114pub 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}