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 },
21 config::{self, data, network},
22 rpc::Error as SorobanRpcError,
23 tx::builder,
24 utils::contract_id_hash_from_asset,
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 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 resources: crate::resources::Args,
86
87 #[arg(long)]
91 pub alias: Option<AliasName>,
92
93 #[arg(long)]
95 pub build_only: bool,
96}
97
98impl Cmd {
99 pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
100 let res = self
101 .execute(&self.config, global_args.quiet, global_args.no_cache)
102 .await?
103 .to_envelope();
104 match res {
105 TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?),
106 TxnEnvelopeResult::Res(contract) => {
107 let network = self.config.get_network()?;
108
109 if let Some(alias) = self.alias.clone() {
110 if let Some(existing_contract) = self
111 .config
112 .locator
113 .get_contract_id(&alias, &network.network_passphrase)?
114 {
115 let print = Print::new(global_args.quiet);
116 print.warnln(format!(
117 "Overwriting existing contract id: {existing_contract}"
118 ));
119 }
120
121 self.config.locator.save_contract_id(
122 &network.network_passphrase,
123 &contract,
124 &alias,
125 )?;
126 }
127
128 println!("{contract}");
129 }
130 }
131 Ok(())
132 }
133
134 pub async fn execute(
135 &self,
136 config: &config::Args,
137 quiet: bool,
138 no_cache: bool,
139 ) -> Result<TxnResult<stellar_strkey::Contract>, Error> {
140 let print = Print::new(quiet);
142 let asset = self.asset.resolve(&config.locator)?;
143
144 let network = config.get_network()?;
145 let client = network.rpc_client()?;
146 client
147 .verify_network_passphrase(Some(&network.network_passphrase))
148 .await?;
149
150 let source_account = config.source_account().await?;
151
152 let account_details = client
155 .get_account(&source_account.clone().to_string())
156 .await?;
157 let sequence: i64 = account_details.seq_num.into();
158 let network_passphrase = &network.network_passphrase;
159 let contract_id = contract_id_hash_from_asset(&asset, network_passphrase);
160 let tx = build_wrap_token_tx(
161 asset,
162 &contract_id,
163 sequence + 1,
164 config.get_inclusion_fee()?,
165 network_passphrase,
166 source_account,
167 )?;
168
169 if self.build_only {
170 return Ok(TxnResult::Txn(Box::new(tx)));
171 }
172
173 sim_sign_and_send_tx::<Error>(&client, &tx, config, &self.resources, &[], quiet, no_cache)
174 .await?;
175
176 if let Some(url) = utils::lab_url_for_contract(&network, &contract_id) {
177 print.linkln(url);
178 }
179 print.checkln("Deployed!");
180
181 Ok(TxnResult::Res(stellar_strkey::Contract(contract_id.0)))
182 }
183}
184
185fn build_wrap_token_tx(
186 asset: impl Into<Asset>,
187 contract_id: &stellar_strkey::Contract,
188 sequence: i64,
189 fee: u32,
190 _network_passphrase: &str,
191 source_account: MuxedAccount,
192) -> Result<Transaction, Error> {
193 let contract = ScAddress::Contract(stellar_xdr::curr::ContractId(Hash(contract_id.0)));
194 let mut read_write = vec![
195 ContractData(LedgerKeyContractData {
196 contract: contract.clone(),
197 key: ScVal::LedgerKeyContractInstance,
198 durability: ContractDataDurability::Persistent,
199 }),
200 ContractData(LedgerKeyContractData {
201 contract: contract.clone(),
202 key: ScVal::Vec(Some(
203 vec![ScVal::Symbol("Metadata".try_into().unwrap())].try_into()?,
204 )),
205 durability: ContractDataDurability::Persistent,
206 }),
207 ];
208 let asset = asset.into();
209 if asset != Asset::Native {
210 read_write.push(ContractData(LedgerKeyContractData {
211 contract,
212 key: ScVal::Vec(Some(
213 vec![ScVal::Symbol("Admin".try_into().unwrap())].try_into()?,
214 )),
215 durability: ContractDataDurability::Persistent,
216 }));
217 }
218
219 let op = Operation {
220 source_account: None,
221 body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
222 host_function: HostFunction::CreateContract(CreateContractArgs {
223 contract_id_preimage: ContractIdPreimage::Asset(asset),
224 executable: ContractExecutable::StellarAsset,
225 }),
226 auth: VecM::default(),
227 }),
228 };
229
230 Ok(Transaction {
231 source_account,
232 fee,
233 seq_num: SequenceNumber(sequence),
234 cond: Preconditions::None,
235 memo: Memo::None,
236 operations: vec![op].try_into()?,
237 ext: TransactionExt::V0,
238 })
239}