stellar_registry_cli/commands/
deploy.rs1#![allow(dead_code)]
2use std::{ffi::OsString, path::PathBuf};
3
4use clap::Parser;
5use ed25519_dalek::SigningKey;
6
7use soroban_sdk::xdr::{
8 self, AccountId, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, Memo, MuxedAccount,
9 Operation, OperationBody, Preconditions, ScSpecEntry, ScString, ScVal, SequenceNumber,
10 Transaction, TransactionExt, Uint256, VecM,
11};
12use stellar_cli::{
13 assembled::simulate_and_assemble_transaction, commands::contract::invoke, config, fee,
14 utils::rpc::get_remote_wasm_from_hash,
15};
16
17use soroban_rpc as rpc;
18pub use soroban_spec_tools::contract as contract_spec;
19
20use crate::testnet::{self, contract_address, invoke_registry};
21
22#[derive(Parser, Debug, Clone)]
23pub struct Cmd {
24 #[arg(long, visible_alias = "deploy-as")]
26 pub contract_name: String,
27 #[arg(long)]
29 pub wasm_name: String,
30 #[arg(last = true, id = "CONSTRUCTOR_ARGS")]
32 pub slop: Vec<OsString>,
33
34 #[arg(long)]
36 pub version: Option<String>,
37
38 #[command(flatten)]
39 pub config: config::Args,
40 #[command(flatten)]
41 pub fee: fee::Args,
42}
43
44#[derive(thiserror::Error, Debug)]
45pub enum Error {
46 #[error(transparent)]
47 Invoke(#[from] invoke::Error),
48 #[error(transparent)]
49 Io(#[from] std::io::Error),
50 #[error(transparent)]
51 StellarBuild(#[from] stellar_build::Error),
52 #[error(transparent)]
53 Install(#[from] super::install::Error),
54 #[error(transparent)]
55 Rpc(#[from] rpc::Error),
56 #[error(transparent)]
57 SpecTools(#[from] soroban_spec_tools::Error),
58 #[error(transparent)]
59 Config(#[from] config::Error),
60 #[error(transparent)]
61 Xdr(#[from] xdr::Error),
62 #[error("Cannot parse contract spec")]
63 CannotParseContractSpec,
64 #[error("argument count ({current}) surpasses maximum allowed count ({maximum})")]
65 MaxNumberOfArgumentsReached { current: usize, maximum: usize },
66 #[error("function {0} was not found in the contract")]
67 FunctionNotFoundInContractSpec(String),
68 #[error("parsing argument {arg}: {error}")]
69 CannotParseArg {
70 arg: String,
71 error: stellar_cli::commands::contract::arg_parsing::Error,
72 },
73 #[error("function name {0} is too long")]
74 FunctionNameTooLong(String),
75 #[error("Missing file argument {0:#?}")]
76 MissingFileArg(PathBuf),
77 #[error("Missing argument {0}")]
78 MissingArgument(String),
79}
80
81impl Cmd {
82 pub async fn run(&self) -> Result<(), Error> {
83 self.invoke().await?;
84 Ok(())
85 }
86
87 pub async fn hash(&self) -> Result<xdr::Hash, Error> {
88 let res = invoke_registry(&["fetch_hash", "--wasm_name", &self.wasm_name]).await?;
89 let res = res.trim_matches('"');
90 Ok(res.parse().unwrap())
91 }
92
93 pub async fn wasm(&self) -> Result<Vec<u8>, Error> {
94 Ok(get_remote_wasm_from_hash(&testnet::client()?, &self.hash().await?).await?)
95 }
96
97 pub async fn spec_entries(&self) -> Result<Vec<ScSpecEntry>, Error> {
98 Ok(contract_spec::Spec::new(&self.wasm().await?)
99 .map_err(|_| Error::CannotParseContractSpec)?
100 .spec)
101 }
102
103 async fn invoke(&self) -> Result<(), Error> {
104 let client = testnet::client()?;
105 let key = self.config.key_pair()?;
106 let config = &self.config;
107
108 let public_strkey =
110 stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string();
111
112 let contract_address = contract_address();
113 let contract_id = &testnet::contract_id_strkey();
114 let spec_entries = self.spec_entries().await?;
115
116 let (args, signers) = if self.slop.is_empty() {
117 (ScVal::Void, vec![])
118 } else {
119 let res = stellar_cli::commands::contract::arg_parsing::build_host_function_parameters(
120 contract_id,
121 &self.slop,
122 &spec_entries,
123 &config::Args::default(),
124 );
125 match res {
126 Ok((_, _, host_function_params, signers)) => {
127 if host_function_params.function_name.len() > 64 {
128 return Err(Error::FunctionNameTooLong(
129 host_function_params.function_name.to_string(),
130 ));
131 }
132 let args = ScVal::Vec(Some(
133 vec![
134 ScVal::Symbol(host_function_params.function_name),
135 ScVal::Vec(Some(host_function_params.args.into())),
136 ]
137 .try_into()
138 .unwrap(),
139 ));
140 (args, signers)
141 }
142 Err(stellar_cli::commands::contract::arg_parsing::Error::HelpMessage(help)) => {
143 println!("{help}");
144 return Ok(());
145 }
146 Err(e) => {
147 return Err(Error::CannotParseArg {
148 arg: self
149 .slop
150 .iter()
151 .map(|s| s.to_string_lossy())
152 .collect::<Vec<_>>()
153 .join(" "),
154 error: e,
155 });
156 }
157 }
158 };
159
160 let invoke_contract_args = InvokeContractArgs {
161 contract_address: contract_address.clone(),
162 function_name: "deploy".try_into().unwrap(),
163 args: [
164 ScVal::String(ScString(self.wasm_name.clone().try_into().unwrap())),
165 ScVal::Void,
166 ScVal::String(ScString(self.contract_name.clone().try_into().unwrap())),
167 ScVal::Address(xdr::ScAddress::Account(AccountId(
168 xdr::PublicKey::PublicKeyTypeEd25519(Uint256(key.verifying_key().to_bytes())),
169 ))),
170 ScVal::Void,
171 args,
172 ]
173 .try_into()
174 .unwrap(),
175 };
176
177 let account_details = client.get_account(&public_strkey).await?;
178 let sequence: i64 = account_details.seq_num.into();
179 let tx = build_invoke_contract_tx(invoke_contract_args, sequence + 1, self.fee.fee, &key)?;
180 let assembled = simulate_and_assemble_transaction(&client, &tx).await?;
181 let mut txn = assembled.transaction().clone();
182 txn = config
183 .sign_soroban_authorizations(&txn, &signers)
184 .await?
185 .unwrap_or(txn);
186 let res = client
187 .send_transaction_polling(&config.sign_with_local_key(txn).await?)
188 .await?;
189
190 let return_value = res.return_value()?;
191 println!("{return_value:#?}");
192 Ok(())
193 }
194}
195
196fn build_invoke_contract_tx(
197 parameters: InvokeContractArgs,
198 sequence: i64,
199 fee: u32,
200 key: &SigningKey,
201) -> Result<Transaction, Error> {
202 let op = Operation {
203 source_account: None,
204 body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
205 host_function: HostFunction::InvokeContract(parameters),
206 auth: VecM::default(),
207 }),
208 };
209 Ok(Transaction {
210 source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())),
211 fee,
212 seq_num: SequenceNumber(sequence),
213 cond: Preconditions::None,
214 memo: Memo::None,
215 operations: vec![op].try_into()?,
216 ext: TransactionExt::V0,
217 })
218}