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