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