use crate::config::locator;
use crate::print::Print;
use crate::tx::sim_sign_and_send_tx;
use crate::utils;
use crate::xdr::{
Asset, ContractDataDurability, ContractExecutable, ContractIdPreimage, CreateContractArgs,
Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, LedgerKey::ContractData,
LedgerKeyContractData, Limits, Memo, MuxedAccount, Operation, OperationBody, Preconditions,
ScAddress, ScVal, SequenceNumber, Transaction, TransactionExt, VecM, WriteXdr,
};
use clap::Parser;
use std::convert::Infallible;
use std::{array::TryFromSliceError, fmt::Debug, num::ParseIntError};
use crate::commands::tx::fetch;
use crate::{
commands::{
global,
txn_result::{TxnEnvelopeResult, TxnResult},
},
config::{self, data, network},
rpc::Error as SorobanRpcError,
tx::builder,
utils::contract_id_hash_from_asset,
};
use crate::config::address::AliasName;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("error parsing int: {0}")]
ParseIntError(#[from] ParseIntError),
#[error(transparent)]
Client(#[from] SorobanRpcError),
#[error("internal conversion error: {0}")]
TryFromSliceError(#[from] TryFromSliceError),
#[error("xdr processing error: {0}")]
Xdr(#[from] XdrError),
#[error(transparent)]
Config(#[from] config::Error),
#[error(transparent)]
Data(#[from] data::Error),
#[error(transparent)]
Network(#[from] network::Error),
#[error(transparent)]
Builder(#[from] builder::Error),
#[error(transparent)]
Asset(#[from] builder::asset::Error),
#[error(transparent)]
Locator(#[from] locator::Error),
#[error(transparent)]
Fee(#[from] fetch::fee::Error),
#[error(transparent)]
Fetch(#[from] fetch::Error),
}
impl From<Infallible> for Error {
fn from(_: Infallible) -> Self {
unreachable!()
}
}
#[derive(Parser, Debug, Clone)]
#[group(skip)]
pub struct Cmd {
#[arg(long)]
pub asset: builder::Asset,
#[command(flatten)]
pub config: config::Args,
#[command(flatten)]
pub resources: crate::resources::Args,
#[arg(long)]
pub alias: Option<AliasName>,
#[arg(long)]
pub build_only: bool,
}
impl Cmd {
pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
let res = self
.execute(&self.config, global_args.quiet, global_args.no_cache)
.await?
.to_envelope();
match res {
TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?),
TxnEnvelopeResult::Res(contract) => {
let network = self.config.get_network()?;
if let Some(alias) = self.alias.clone() {
if let Some(existing_contract) = self
.config
.locator
.get_contract_id(&alias, &network.network_passphrase)?
{
let print = Print::new(global_args.quiet);
print.warnln(format!(
"Overwriting existing contract id: {existing_contract}"
));
}
self.config.locator.save_contract_id(
&network.network_passphrase,
&contract,
&alias,
)?;
}
println!("{contract}");
}
}
Ok(())
}
pub async fn execute(
&self,
config: &config::Args,
quiet: bool,
no_cache: bool,
) -> Result<TxnResult<stellar_strkey::Contract>, Error> {
let print = Print::new(quiet);
let asset = self.asset.resolve(&config.locator)?;
let network = config.get_network()?;
let client = network.rpc_client()?;
client
.verify_network_passphrase(Some(&network.network_passphrase))
.await?;
let source_account = config.source_account().await?;
let account_details = client
.get_account(&source_account.clone().to_string())
.await?;
let sequence: i64 = account_details.seq_num.into();
let network_passphrase = &network.network_passphrase;
let contract_id = contract_id_hash_from_asset(&asset, network_passphrase);
let tx = build_wrap_token_tx(
asset,
&contract_id,
sequence + 1,
config.get_inclusion_fee()?,
network_passphrase,
source_account,
)?;
if self.build_only {
return Ok(TxnResult::Txn(Box::new(tx)));
}
sim_sign_and_send_tx::<Error>(&client, &tx, config, &self.resources, &[], quiet, no_cache)
.await?;
if let Some(url) = utils::lab_url_for_contract(&network, &contract_id) {
print.linkln(url);
}
print.checkln("Deployed!");
Ok(TxnResult::Res(stellar_strkey::Contract(contract_id.0)))
}
}
fn build_wrap_token_tx(
asset: impl Into<Asset>,
contract_id: &stellar_strkey::Contract,
sequence: i64,
fee: u32,
_network_passphrase: &str,
source_account: MuxedAccount,
) -> Result<Transaction, Error> {
let contract = ScAddress::Contract(stellar_xdr::curr::ContractId(Hash(contract_id.0)));
let mut read_write = vec![
ContractData(LedgerKeyContractData {
contract: contract.clone(),
key: ScVal::LedgerKeyContractInstance,
durability: ContractDataDurability::Persistent,
}),
ContractData(LedgerKeyContractData {
contract: contract.clone(),
key: ScVal::Vec(Some(
vec![ScVal::Symbol("Metadata".try_into().unwrap())].try_into()?,
)),
durability: ContractDataDurability::Persistent,
}),
];
let asset = asset.into();
if asset != Asset::Native {
read_write.push(ContractData(LedgerKeyContractData {
contract,
key: ScVal::Vec(Some(
vec![ScVal::Symbol("Admin".try_into().unwrap())].try_into()?,
)),
durability: ContractDataDurability::Persistent,
}));
}
let op = Operation {
source_account: None,
body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
host_function: HostFunction::CreateContract(CreateContractArgs {
contract_id_preimage: ContractIdPreimage::Asset(asset),
executable: ContractExecutable::StellarAsset,
}),
auth: VecM::default(),
}),
};
Ok(Transaction {
source_account,
fee,
seq_num: SequenceNumber(sequence),
cond: Preconditions::None,
memo: Memo::None,
operations: vec![op].try_into()?,
ext: TransactionExt::V0,
})
}