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