stellar_registry_cli/commands/
deploy.rs

1#![allow(dead_code)]
2use std::{ffi::OsString, path::PathBuf};
3
4use clap::Parser;
5use soroban_rpc as rpc;
6pub use soroban_spec_tools::contract as contract_spec;
7use stellar_cli::{
8    assembled::simulate_and_assemble_transaction,
9    commands::contract::invoke,
10    config, fee,
11    utils::rpc::get_remote_wasm_from_hash,
12    xdr::{self, AccountId, InvokeContractArgs, ScSpecEntry, ScString, ScVal, Uint256},
13};
14
15use crate::contract::NetworkContract;
16
17mod util;
18
19#[derive(Parser, Debug, Clone)]
20pub struct Cmd {
21    /// Name of contract to be deployed
22    #[arg(long, visible_alias = "deploy-as")]
23    pub contract_name: String,
24    /// Name of published contract to deploy from
25    #[arg(long)]
26    pub wasm_name: String,
27    /// Arguments for constructor
28    #[arg(last = true, id = "CONSTRUCTOR_ARGS")]
29    pub slop: Vec<OsString>,
30    /// Version of the wasm to deploy
31    #[arg(long)]
32    pub version: Option<String>,
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    Install(#[from] super::create_alias::Error),
47    #[error(transparent)]
48    Rpc(#[from] rpc::Error),
49    #[error(transparent)]
50    SpecTools(#[from] soroban_spec_tools::Error),
51    #[error(transparent)]
52    Config(#[from] config::Error),
53    #[error(transparent)]
54    Xdr(#[from] xdr::Error),
55    #[error("Cannot parse contract spec")]
56    CannotParseContractSpec,
57    #[error("argument count ({current}) surpasses maximum allowed count ({maximum})")]
58    MaxNumberOfArgumentsReached { current: usize, maximum: usize },
59    #[error("function {0} was not found in the contract")]
60    FunctionNotFoundInContractSpec(String),
61    #[error("parsing argument {arg}: {error}")]
62    CannotParseArg {
63        arg: String,
64        error: stellar_cli::commands::contract::arg_parsing::Error,
65    },
66    #[error("function name {0} is too long")]
67    FunctionNameTooLong(String),
68    #[error("Missing file argument {0:#?}")]
69    MissingFileArg(PathBuf),
70    #[error("Missing argument {0}")]
71    MissingArgument(String),
72    #[error("Constructor help message: {0}")]
73    ConstructorHelpMessage(String),
74    #[error("{0}")]
75    InvalidReturnValue(String),
76}
77
78impl Cmd {
79    pub async fn run(&self) -> Result<(), Error> {
80        match self.invoke().await {
81            Ok(contract_id) => {
82                println!(
83                    "Contract {} deployed successfully to {contract_id}",
84                    self.contract_name
85                );
86                Ok(())
87            }
88            Err(Error::ConstructorHelpMessage(help)) => {
89                println!("Constructor help message:\n{help}");
90                Ok(())
91            }
92            Err(e) => Err(e),
93        }
94    }
95
96    pub async fn hash(&self) -> Result<xdr::Hash, Error> {
97        let res = self
98            .config
99            .view_registry(&["fetch_hash", "--wasm_name", &self.wasm_name])
100            .await?;
101        let res = res.trim_matches('"');
102        Ok(res.parse().unwrap())
103    }
104
105    pub async fn wasm(&self) -> Result<Vec<u8>, Error> {
106        Ok(get_remote_wasm_from_hash(&self.config.rpc_client()?, &self.hash().await?).await?)
107    }
108
109    pub async fn spec_entries(&self) -> Result<Vec<ScSpecEntry>, Error> {
110        Ok(contract_spec::Spec::new(&self.wasm().await?)
111            .map_err(|_| Error::CannotParseContractSpec)?
112            .spec)
113    }
114
115    async fn invoke(&self) -> Result<stellar_strkey::Contract, Error> {
116        let client = self.config.rpc_client()?;
117        let key = self.config.key_pair()?;
118        let config = &self.config;
119
120        let contract_address = self.config.contract_sc_address()?;
121        let contract_id = &self.config.contract_id()?;
122        let spec_entries = self.spec_entries().await?;
123        let (args, signers) =
124            util::find_args_and_signers(contract_id, self.slop.clone(), &spec_entries)?;
125
126        let invoke_contract_args = InvokeContractArgs {
127            contract_address: contract_address.clone(),
128            function_name: "deploy".try_into().unwrap(),
129            args: [
130                ScVal::String(ScString(self.wasm_name.clone().try_into().unwrap())),
131                self.version.clone().map_or(ScVal::Void, |s| {
132                    ScVal::String(ScString(s.try_into().unwrap()))
133                }),
134                ScVal::String(ScString(self.contract_name.clone().try_into().unwrap())),
135                ScVal::Address(xdr::ScAddress::Account(AccountId(
136                    xdr::PublicKey::PublicKeyTypeEd25519(Uint256(key.verifying_key().to_bytes())),
137                ))),
138                args,
139            ]
140            .try_into()
141            .unwrap(),
142        };
143
144        // Get the account sequence number
145        let public_strkey =
146            stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string();
147        let account_details = client.get_account(&public_strkey).await?;
148        let sequence: i64 = account_details.seq_num.into();
149        let tx =
150            util::build_invoke_contract_tx(invoke_contract_args, sequence + 1, self.fee.fee, &key)?;
151        let assembled = simulate_and_assemble_transaction(&client, &tx).await?;
152        let mut txn = assembled.transaction().clone();
153        txn = config
154            .sign_soroban_authorizations(&txn, &signers)
155            .await?
156            .unwrap_or(txn);
157        let return_value = client
158            .send_transaction_polling(&config.sign(txn).await?)
159            .await?
160            .return_value()?;
161        match return_value {
162            ScVal::Address(xdr::ScAddress::Contract(xdr::ContractId(hash))) => {
163                Ok(stellar_strkey::Contract(hash.0))
164            }
165            _ => Err(Error::InvalidReturnValue(
166                "{return_value:#?} is not a contract address".to_string(),
167            )),
168        }
169    }
170}