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