soroban_cli/
wasm.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use crate::xdr::{self, Hash, LedgerKey, LedgerKeyContractCode};
use clap::arg;
use sha2::{Digest, Sha256};
use soroban_spec_tools::contract::{self, Spec};
use std::{
    fs, io,
    path::{Path, PathBuf},
};
use stellar_xdr::curr::{ContractDataEntry, ContractExecutable, ScVal};

use crate::{
    config::{
        locator,
        network::{Error as NetworkError, Network},
    },
    utils::{self, rpc::get_remote_wasm_from_hash},
    wasm::Error::{ContractIsStellarAsset, UnexpectedContractToken},
};

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("reading file {filepath}: {error}")]
    CannotReadContractFile {
        filepath: std::path::PathBuf,
        error: io::Error,
    },
    #[error("cannot parse wasm file {file}: {error}")]
    CannotParseWasm {
        file: std::path::PathBuf,
        error: wasmparser::BinaryReaderError,
    },
    #[error("xdr processing error: {0}")]
    Xdr(#[from] xdr::Error),

    #[error(transparent)]
    Parser(#[from] wasmparser::BinaryReaderError),
    #[error(transparent)]
    ContractSpec(#[from] contract::Error),

    #[error(transparent)]
    Locator(#[from] locator::Error),
    #[error(transparent)]
    Rpc(#[from] soroban_rpc::Error),
    #[error("unexpected contract data {0:?}")]
    UnexpectedContractToken(Box<ContractDataEntry>),
    #[error(
        "cannot fetch wasm for contract because the contract is \
    a network built-in asset contract that does not have a downloadable code binary"
    )]
    ContractIsStellarAsset,
    #[error(transparent)]
    Network(#[from] NetworkError),
}

#[derive(Debug, clap::Args, Clone)]
#[group(skip)]
pub struct Args {
    /// Path to wasm binary
    #[arg(long)]
    pub wasm: PathBuf,
}

impl Args {
    /// # Errors
    /// May fail to read wasm file
    pub fn read(&self) -> Result<Vec<u8>, Error> {
        fs::read(&self.wasm).map_err(|e| Error::CannotReadContractFile {
            filepath: self.wasm.clone(),
            error: e,
        })
    }

    /// # Errors
    /// May fail to read wasm file
    pub fn len(&self) -> Result<u64, Error> {
        len(&self.wasm)
    }

    /// # Errors
    /// May fail to read wasm file
    pub fn is_empty(&self) -> Result<bool, Error> {
        self.len().map(|len| len == 0)
    }

    /// # Errors
    /// May fail to read wasm file or parse xdr section
    pub fn parse(&self) -> Result<Spec, Error> {
        let contents = self.read()?;
        Ok(Spec::new(&contents)?)
    }

    pub fn hash(&self) -> Result<Hash, Error> {
        Ok(Hash(Sha256::digest(self.read()?).into()))
    }
}

impl From<&PathBuf> for Args {
    fn from(wasm: &PathBuf) -> Self {
        Self { wasm: wasm.clone() }
    }
}

impl TryInto<LedgerKey> for Args {
    type Error = Error;
    fn try_into(self) -> Result<LedgerKey, Self::Error> {
        Ok(LedgerKey::ContractCode(LedgerKeyContractCode {
            hash: utils::contract_hash(&self.read()?)?,
        }))
    }
}

/// # Errors
/// May fail to read wasm file
pub fn len(p: &Path) -> Result<u64, Error> {
    Ok(std::fs::metadata(p)
        .map_err(|e| Error::CannotReadContractFile {
            filepath: p.to_path_buf(),
            error: e,
        })?
        .len())
}

pub async fn fetch_from_contract(
    stellar_strkey::Contract(contract_id): &stellar_strkey::Contract,
    network: &Network,
) -> Result<Vec<u8>, Error> {
    tracing::trace!(?network);
    let client = network.rpc_client()?;
    client
        .verify_network_passphrase(Some(&network.network_passphrase))
        .await?;
    let data_entry = client.get_contract_data(contract_id).await?;
    if let ScVal::ContractInstance(contract) = &data_entry.val {
        return match &contract.executable {
            ContractExecutable::Wasm(hash) => Ok(get_remote_wasm_from_hash(&client, hash).await?),
            ContractExecutable::StellarAsset => Err(ContractIsStellarAsset),
        };
    }
    Err(UnexpectedContractToken(Box::new(data_entry)))
}