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