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 #[arg(long)]
59 pub wasm: PathBuf,
60}
61
62impl Args {
63 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 pub fn len(&self) -> Result<u64, Error> {
75 len(&self.wasm)
76 }
77
78 pub fn is_empty(&self) -> Result<bool, Error> {
81 self.len().map(|len| len == 0)
82 }
83
84 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
111pub 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}