soroban_cli/commands/contract/deploy/
asset.rs1use crate::config::locator;
2use crate::print::Print;
3use crate::xdr::{
4 Asset, ContractDataDurability, ContractExecutable, ContractIdPreimage, CreateContractArgs,
5 Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, LedgerKey::ContractData,
6 LedgerKeyContractData, Limits, Memo, MuxedAccount, Operation, OperationBody, Preconditions,
7 ScAddress, ScVal, SequenceNumber, Transaction, TransactionExt, VecM, WriteXdr,
8};
9use clap::{arg, command, Parser};
10use std::convert::Infallible;
11use std::{array::TryFromSliceError, fmt::Debug, num::ParseIntError};
12
13use crate::{
14 assembled::simulate_and_assemble_transaction,
15 commands::{
16 global,
17 txn_result::{TxnEnvelopeResult, TxnResult},
18 NetworkRunnable,
19 },
20 config::{self, data, network},
21 rpc::Error as SorobanRpcError,
22 tx::builder,
23 utils::contract_id_hash_from_asset,
24};
25
26use crate::commands::contract::deploy::utils::alias_validator;
27
28#[derive(thiserror::Error, Debug)]
29pub enum Error {
30 #[error("error parsing int: {0}")]
31 ParseIntError(#[from] ParseIntError),
32 #[error(transparent)]
33 Client(#[from] SorobanRpcError),
34 #[error("internal conversion error: {0}")]
35 TryFromSliceError(#[from] TryFromSliceError),
36 #[error("xdr processing error: {0}")]
37 Xdr(#[from] XdrError),
38 #[error(transparent)]
39 Config(#[from] config::Error),
40 #[error(transparent)]
41 Data(#[from] data::Error),
42 #[error(transparent)]
43 Network(#[from] network::Error),
44 #[error(transparent)]
45 Builder(#[from] builder::Error),
46 #[error(transparent)]
47 Asset(#[from] builder::asset::Error),
48 #[error(transparent)]
49 Locator(#[from] locator::Error),
50}
51
52impl From<Infallible> for Error {
53 fn from(_: Infallible) -> Self {
54 unreachable!()
55 }
56}
57
58#[derive(Parser, Debug, Clone)]
59#[group(skip)]
60pub struct Cmd {
61 #[arg(long)]
63 pub asset: builder::Asset,
64
65 #[command(flatten)]
66 pub config: config::Args,
67
68 #[command(flatten)]
69 pub fee: crate::fee::Args,
70
71 #[arg(long, value_parser = clap::builder::ValueParser::new(alias_validator))]
75 pub alias: Option<String>,
76}
77
78impl Cmd {
79 pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
80 let res = self.run_against_rpc_server(None, None).await?.to_envelope();
81 match res {
82 TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?),
83 TxnEnvelopeResult::Res(contract) => {
84 let network = self.config.get_network()?;
85
86 if let Some(alias) = self.alias.clone() {
87 if let Some(existing_contract) = self
88 .config
89 .locator
90 .get_contract_id(&alias, &network.network_passphrase)?
91 {
92 let print = Print::new(global_args.quiet);
93 print.warnln(format!(
94 "Overwriting existing contract id: {existing_contract}"
95 ));
96 }
97
98 self.config.locator.save_contract_id(
99 &network.network_passphrase,
100 &contract,
101 &alias,
102 )?;
103 }
104
105 println!("{contract}");
106 }
107 }
108 Ok(())
109 }
110}
111
112#[async_trait::async_trait]
113impl NetworkRunnable for Cmd {
114 type Error = Error;
115 type Result = TxnResult<stellar_strkey::Contract>;
116
117 async fn run_against_rpc_server(
118 &self,
119 args: Option<&global::Args>,
120 config: Option<&config::Args>,
121 ) -> Result<Self::Result, Error> {
122 let config = config.unwrap_or(&self.config);
123 let asset = self.asset.resolve(&config.locator)?;
125
126 let network = config.get_network()?;
127 let client = network.rpc_client()?;
128 client
129 .verify_network_passphrase(Some(&network.network_passphrase))
130 .await?;
131
132 let source_account = config.source_account().await?;
133
134 let account_details = client
137 .get_account(&source_account.clone().to_string())
138 .await?;
139 let sequence: i64 = account_details.seq_num.into();
140 let network_passphrase = &network.network_passphrase;
141 let contract_id = contract_id_hash_from_asset(&asset, network_passphrase);
142 let tx = build_wrap_token_tx(
143 asset,
144 &contract_id,
145 sequence + 1,
146 self.fee.fee,
147 network_passphrase,
148 source_account,
149 )?;
150 if self.fee.build_only {
151 return Ok(TxnResult::Txn(Box::new(tx)));
152 }
153 let txn = simulate_and_assemble_transaction(&client, &tx).await?;
154 let txn = self.fee.apply_to_assembled_txn(txn).transaction().clone();
155 #[cfg(feature = "version_lt_23")]
156 if self.fee.sim_only {
157 return Ok(TxnResult::Txn(Box::new(txn)));
158 }
159 let get_txn_resp = client
160 .send_transaction_polling(&self.config.sign(txn).await?)
161 .await?
162 .try_into()?;
163 if args.is_none_or(|a| !a.no_cache) {
164 data::write(get_txn_resp, &network.rpc_uri()?)?;
165 }
166
167 Ok(TxnResult::Res(stellar_strkey::Contract(contract_id.0)))
168 }
169}
170
171fn build_wrap_token_tx(
172 asset: impl Into<Asset>,
173 contract_id: &stellar_strkey::Contract,
174 sequence: i64,
175 fee: u32,
176 _network_passphrase: &str,
177 source_account: MuxedAccount,
178) -> Result<Transaction, Error> {
179 let contract = ScAddress::Contract(stellar_xdr::curr::ContractId(Hash(contract_id.0)));
180 let mut read_write = vec![
181 ContractData(LedgerKeyContractData {
182 contract: contract.clone(),
183 key: ScVal::LedgerKeyContractInstance,
184 durability: ContractDataDurability::Persistent,
185 }),
186 ContractData(LedgerKeyContractData {
187 contract: contract.clone(),
188 key: ScVal::Vec(Some(
189 vec![ScVal::Symbol("Metadata".try_into().unwrap())].try_into()?,
190 )),
191 durability: ContractDataDurability::Persistent,
192 }),
193 ];
194 let asset = asset.into();
195 if asset != Asset::Native {
196 read_write.push(ContractData(LedgerKeyContractData {
197 contract,
198 key: ScVal::Vec(Some(
199 vec![ScVal::Symbol("Admin".try_into().unwrap())].try_into()?,
200 )),
201 durability: ContractDataDurability::Persistent,
202 }));
203 }
204
205 let op = Operation {
206 source_account: None,
207 body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
208 host_function: HostFunction::CreateContract(CreateContractArgs {
209 contract_id_preimage: ContractIdPreimage::Asset(asset),
210 executable: ContractExecutable::StellarAsset,
211 }),
212 auth: VecM::default(),
213 }),
214 };
215
216 Ok(Transaction {
217 source_account,
218 fee,
219 seq_num: SequenceNumber(sequence),
220 cond: Preconditions::None,
221 memo: Memo::None,
222 operations: vec![op].try_into()?,
223 ext: TransactionExt::V0,
224 })
225}