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