stellar_registry_cli/commands/
deploy.rs

1#![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    /// Name of contract to be deployed
25    #[arg(long, visible_alias = "deploy-as")]
26    pub contract_name: String,
27    /// Name of published contract to deploy from
28    #[arg(long)]
29    pub wasm_name: String,
30    /// Arguments for constructor
31    #[arg(last = true, id = "CONSTRUCTOR_ARGS")]
32    pub slop: Vec<OsString>,
33
34    /// Version of the wasm to deploy
35    #[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        // Get the account sequence number
109        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}