soroban_cli/commands/contract/deploy/
asset.rs1use crate::config::locator;
2use crate::print::Print;
3use crate::tx::sim_sign_and_send_tx;
4use crate::utils;
5use crate::xdr::{
6 Asset, ContractDataDurability, ContractExecutable, ContractIdPreimage, CreateContractArgs,
7 Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, LedgerKey::ContractData,
8 LedgerKeyContractData, Limits, Memo, MuxedAccount, Operation, OperationBody, Preconditions,
9 ScAddress, ScVal, SequenceNumber, Transaction, TransactionExt, VecM, WriteXdr,
10};
11use clap::Parser;
12use std::convert::Infallible;
13use std::{array::TryFromSliceError, fmt::Debug, num::ParseIntError};
14
15use crate::commands::tx::fetch;
16use crate::{
17 commands::{
18 global,
19 txn_result::{TxnEnvelopeResult, TxnResult},
20 HEADING_TRANSACTION,
21 },
22 config::{self, data, network, token::UnresolvedToken},
23 rpc::Error as SorobanRpcError,
24 tx::builder,
25};
26
27use crate::config::address::AliasName;
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 Locator(#[from] locator::Error),
57
58 #[error(transparent)]
59 Token(#[from] config::token::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 resources: crate::resources::Args,
86
87 #[arg(long)]
91 pub alias: Option<AliasName>,
92
93 #[arg(long, help_heading = HEADING_TRANSACTION)]
95 pub build_only: bool,
96}
97
98impl Cmd {
99 pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
100 if let Some(alias) = &self.alias {
103 crate::config::alias::validate_reserved_aliases(alias)?;
104 }
105
106 let res = self
107 .execute(&self.config, global_args.quiet, global_args.no_cache)
108 .await?
109 .to_envelope();
110 match res {
111 TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?),
112 TxnEnvelopeResult::Res(contract) => {
113 let network = self.config.get_network()?;
114
115 if let Some(alias) = self.alias.clone() {
116 if let Some(existing_contract) = self
117 .config
118 .locator
119 .get_contract_id(&alias, &network.network_passphrase)?
120 {
121 let print = Print::new(global_args.quiet);
122 print.warnln(format!(
123 "Overwriting existing contract id: {existing_contract}"
124 ));
125 }
126
127 self.config.locator.save_contract_id(
128 &network.network_passphrase,
129 &contract,
130 &alias,
131 )?;
132 }
133
134 println!("{contract}");
135 }
136 }
137 Ok(())
138 }
139
140 pub async fn execute(
141 &self,
142 config: &config::Args,
143 quiet: bool,
144 no_cache: bool,
145 ) -> Result<TxnResult<stellar_strkey::Contract>, Error> {
146 let print = Print::new(quiet);
147
148 let network = config.get_network()?;
149 let token = UnresolvedToken::Asset(self.asset.clone())
152 .resolve(&config.locator, &network.network_passphrase)?;
153 let contract_id = token.contract_id;
154 let asset = token
155 .asset()
156 .expect("an asset reference resolves to a SAC")
157 .clone();
158
159 let client = network.rpc_client()?;
160 client
161 .verify_network_passphrase(Some(&network.network_passphrase))
162 .await?;
163
164 let source_account = config.source_account()?;
165
166 let account_details = client
169 .get_account(&source_account.clone().to_string())
170 .await?;
171 let sequence: i64 = account_details.seq_num.into();
172 let network_passphrase = &network.network_passphrase;
173 let tx = build_wrap_token_tx(
174 asset,
175 &contract_id,
176 sequence + 1,
177 config.get_inclusion_fee()?,
178 network_passphrase,
179 source_account,
180 )?;
181
182 if self.build_only {
183 return Ok(TxnResult::Txn(Box::new(tx)));
184 }
185
186 sim_sign_and_send_tx::<Error>(
187 &client,
188 &tx,
189 config,
190 &self.resources,
191 &[],
192 None,
194 quiet,
195 no_cache,
196 )
197 .await?;
198
199 if let Some(url) = utils::lab_url_for_contract(&network, &contract_id) {
200 print.linkln(url);
201 }
202 print.checkln("Deployed!");
203
204 Ok(TxnResult::Res(stellar_strkey::Contract(contract_id.0)))
205 }
206}
207
208fn build_wrap_token_tx(
209 asset: impl Into<Asset>,
210 contract_id: &stellar_strkey::Contract,
211 sequence: i64,
212 fee: u32,
213 _network_passphrase: &str,
214 source_account: MuxedAccount,
215) -> Result<Transaction, Error> {
216 let contract = ScAddress::Contract(stellar_xdr::ContractId(Hash(contract_id.0)));
217 let mut read_write = vec![
218 ContractData(LedgerKeyContractData {
219 contract: contract.clone(),
220 key: ScVal::LedgerKeyContractInstance,
221 durability: ContractDataDurability::Persistent,
222 }),
223 ContractData(LedgerKeyContractData {
224 contract: contract.clone(),
225 key: ScVal::Vec(Some(
226 vec![ScVal::Symbol("Metadata".try_into().unwrap())].try_into()?,
227 )),
228 durability: ContractDataDurability::Persistent,
229 }),
230 ];
231 let asset = asset.into();
232 if asset != Asset::Native {
233 read_write.push(ContractData(LedgerKeyContractData {
234 contract,
235 key: ScVal::Vec(Some(
236 vec![ScVal::Symbol("Admin".try_into().unwrap())].try_into()?,
237 )),
238 durability: ContractDataDurability::Persistent,
239 }));
240 }
241
242 let op = Operation {
243 source_account: None,
244 body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
245 host_function: HostFunction::CreateContract(CreateContractArgs {
246 contract_id_preimage: ContractIdPreimage::Asset(asset),
247 executable: ContractExecutable::StellarAsset,
248 }),
249 auth: VecM::default(),
250 }),
251 };
252
253 Ok(Transaction {
254 source_account,
255 fee,
256 seq_num: SequenceNumber(sequence),
257 cond: Preconditions::None,
258 memo: Memo::None,
259 operations: vec![op].try_into()?,
260 ext: TransactionExt::V0,
261 })
262}