use std::path::PathBuf;
use crate::{
commands::contract::info::shared::Error::InvalidWasmHash,
config::{
self, locator,
network::{self, Network},
},
print::Print,
utils::rpc::get_remote_wasm_from_hash,
wasm::{self, Error::ContractIsStellarAsset},
xdr,
};
use stellar_xdr::curr::ContractId;
#[derive(Debug, clap::Args, Clone, Default)]
#[command(group(
clap::ArgGroup::new("Source")
.required(true)
.args(& ["wasm", "wasm_hash", "contract_id"]),
))]
#[group(skip)]
pub struct Args {
#[arg(
long,
group = "Source",
conflicts_with = "contract_id",
conflicts_with = "wasm_hash"
)]
pub wasm: Option<PathBuf>,
#[arg(
long = "wasm-hash",
group = "Source",
conflicts_with = "contract_id",
conflicts_with = "wasm"
)]
pub wasm_hash: Option<String>,
#[arg(
long,
env = "STELLAR_CONTRACT_ID",
group = "Source",
visible_alias = "id",
conflicts_with = "wasm",
conflicts_with = "wasm_hash"
)]
pub contract_id: Option<config::UnresolvedContract>,
#[command(flatten)]
pub network: network::Args,
#[command(flatten)]
pub locator: locator::Args,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum, Default)]
pub enum MetasInfoOutput {
#[default]
Text,
XdrBase64,
Json,
JsonFormatted,
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Network(#[from] network::Error),
#[error(transparent)]
Wasm(#[from] wasm::Error),
#[error("provided wasm hash is invalid {0:?}")]
InvalidWasmHash(String),
#[error("must provide one of --wasm, --wasm-hash, or --contract-id")]
MissingArg,
#[error(transparent)]
Rpc(#[from] soroban_rpc::Error),
#[error(transparent)]
Locator(#[from] locator::Error),
}
pub struct Fetched {
pub contract: Contract,
pub source: Source,
}
pub enum Contract {
Wasm { wasm_bytes: Vec<u8> },
StellarAssetContract,
}
pub enum Source {
File {
path: PathBuf,
},
Wasm {
hash: String,
network: Network,
},
Contract {
resolved_address: String,
network: Network,
},
}
impl Source {
pub fn network(&self) -> Option<&Network> {
match self {
Source::File { .. } => None,
Source::Wasm { ref network, .. } | Source::Contract { ref network, .. } => {
Some(network)
}
}
}
}
pub async fn fetch(args: &Args, print: &Print) -> Result<Fetched, Error> {
if let Some(path) = &args.wasm {
print.infoln("Loading contract spec from file...");
let wasm_bytes = wasm::Args { wasm: path.clone() }.read()?;
return Ok(Fetched {
contract: Contract::Wasm { wasm_bytes },
source: Source::File { path: path.clone() },
});
}
let network = &args.network.get(&args.locator)?;
print.infoln(format!("Network: {}", network.network_passphrase));
if let Some(wasm_hash) = &args.wasm_hash {
let hash = hex::decode(wasm_hash)
.map_err(|_| InvalidWasmHash(wasm_hash.clone()))?
.try_into()
.map_err(|_| InvalidWasmHash(wasm_hash.clone()))?;
let hash = xdr::Hash(hash);
let client = network.rpc_client()?;
client
.verify_network_passphrase(Some(&network.network_passphrase))
.await?;
print.globeln(format!(
"Downloading contract spec for wasm hash: {wasm_hash}"
));
let wasm_bytes = get_remote_wasm_from_hash(&client, &hash).await?;
Ok(Fetched {
contract: Contract::Wasm { wasm_bytes },
source: Source::Wasm {
hash: wasm_hash.clone(),
network: network.clone(),
},
})
} else if let Some(contract_id) = &args.contract_id {
let contract_id =
contract_id.resolve_contract_id(&args.locator, &network.network_passphrase)?;
let derived_address =
xdr::ScAddress::Contract(ContractId(xdr::Hash(contract_id.0))).to_string();
print.globeln(format!("Downloading contract spec: {derived_address}"));
let res = wasm::fetch_from_contract(&contract_id, network).await;
if let Some(ContractIsStellarAsset) = res.as_ref().err() {
return Ok(Fetched {
contract: Contract::StellarAssetContract,
source: Source::Contract {
resolved_address: derived_address,
network: network.clone(),
},
});
}
Ok(Fetched {
contract: Contract::Wasm { wasm_bytes: res? },
source: Source::Contract {
resolved_address: derived_address,
network: network.clone(),
},
})
} else {
Err(Error::MissingArg)
}
}