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