stellar_registry_cli/commands/
deploy.rs1#![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::{self, UnresolvedMuxedAccount},
11 utils::rpc::get_remote_wasm_from_hash,
12 xdr::{self, AccountId, InvokeContractArgs, ScSpecEntry, ScString, ScVal, Uint256},
13};
14use stellar_registry_build::{named_registry::PrefixedName, registry::Registry};
15
16use crate::commands::global;
17
18pub mod util;
19
20#[derive(Parser, Debug, Clone)]
21pub struct Cmd {
22 #[arg(long, visible_alias = "deploy-as")]
25 pub contract_name: PrefixedName,
26 #[arg(long)]
29 pub wasm_name: PrefixedName,
30 #[arg(last = true, id = "CONSTRUCTOR_ARGS")]
32 pub slop: Vec<OsString>,
33 #[arg(long)]
35 pub version: Option<String>,
36 #[arg(long)]
38 pub deployer: Option<UnresolvedMuxedAccount>,
39 #[command(flatten)]
40 pub config: global::Args,
41}
42
43#[derive(thiserror::Error, Debug)]
44pub enum Error {
45 #[error(transparent)]
46 Invoke(#[from] invoke::Error),
47 #[error(transparent)]
48 Io(#[from] std::io::Error),
49 #[error(transparent)]
50 Install(#[from] super::create_alias::Error),
51 #[error(transparent)]
52 Rpc(#[from] rpc::Error),
53 #[error(transparent)]
54 SpecTools(#[from] soroban_spec_tools::Error),
55 #[error(transparent)]
56 Config(#[from] config::Error),
57 #[error(transparent)]
58 ConfigAddress(#[from] config::address::Error),
59 #[error(transparent)]
60 Xdr(#[from] xdr::Error),
61 #[error("Cannot parse contract spec")]
62 CannotParseContractSpec,
63 #[error("argument count ({current}) surpasses maximum allowed count ({maximum})")]
64 MaxNumberOfArgumentsReached { current: usize, maximum: usize },
65 #[error("function {0} was not found in the contract")]
66 FunctionNotFoundInContractSpec(String),
67 #[error("parsing argument {arg}: {error}")]
68 CannotParseArg {
69 arg: String,
70 error: stellar_cli::commands::contract::arg_parsing::Error,
71 },
72 #[error("function name {0} is too long")]
73 FunctionNameTooLong(String),
74 #[error("Missing file argument {0:#?}")]
75 MissingFileArg(PathBuf),
76 #[error("Missing argument {0}")]
77 MissingArgument(String),
78 #[error("Constructor help message: {0}")]
79 ConstructorHelpMessage(String),
80 #[error("{0}")]
81 InvalidReturnValue(String),
82 #[error(transparent)]
83 Registry(#[from] stellar_registry_build::Error),
84}
85
86impl Cmd {
87 pub async fn run(&self) -> Result<(), Error> {
88 match self.invoke().await {
89 Ok(contract_id) => {
90 println!(
91 "Contract {} deployed successfully to {contract_id}",
92 self.contract_name.name
93 );
94 Ok(())
95 }
96 Err(Error::ConstructorHelpMessage(help)) => {
97 println!("Constructor help message:\n{help}");
98 Ok(())
99 }
100 Err(e) => Err(e),
101 }
102 }
103
104 pub async fn hash(&self, registry: &Registry) -> Result<xdr::Hash, Error> {
105 let res = registry
106 .as_contract()
107 .invoke_with_result(&["fetch_hash", "--wasm_name", &self.wasm_name.name], true)
108 .await?;
109 let res = res.trim_matches('"');
110 Ok(res.parse().unwrap())
111 }
112
113 pub async fn wasm(&self, registry: &Registry) -> Result<Vec<u8>, Error> {
114 Ok(
115 get_remote_wasm_from_hash(&self.config.rpc_client()?, &self.hash(registry).await?)
116 .await?,
117 )
118 }
119
120 pub async fn spec_entries(&self, registry: &Registry) -> Result<Vec<ScSpecEntry>, Error> {
121 Ok(contract_spec::Spec::new(&self.wasm(registry).await?)
122 .map_err(|_| Error::CannotParseContractSpec)?
123 .spec)
124 }
125
126 async fn invoke(&self) -> Result<stellar_strkey::Contract, Error> {
127 let registry = self.contract_name.registry(&self.config).await?;
128 let client = self.config.rpc_client()?;
129 let key = self.config.key_pair()?;
130 let config = &self.config;
131
132 let contract_address = registry.as_contract().sc_address();
133 let contract_id = ®istry.as_contract().id();
134 let spec_entries = self.spec_entries(®istry).await?;
135 let (args, signers) =
136 util::find_args_and_signers(contract_id, self.slop.clone(), &spec_entries).await?;
137 let deployer = if let Some(deployer) = &self.deployer {
138 Some(
139 deployer
140 .resolve_muxed_account(&self.config.locator, None)
141 .await?,
142 )
143 } else {
144 None
145 };
146 let invoke_contract_args = InvokeContractArgs {
147 contract_address: contract_address.clone(),
148 function_name: "deploy".try_into().unwrap(),
149 args: [
150 ScVal::String(ScString(self.wasm_name.name.clone().try_into().unwrap())),
151 self.version.clone().map_or(ScVal::Void, |s| {
152 ScVal::String(ScString(s.try_into().unwrap()))
153 }),
154 ScVal::String(ScString(
155 self.contract_name.name.clone().try_into().unwrap(),
156 )),
157 ScVal::Address(xdr::ScAddress::Account(AccountId(
158 xdr::PublicKey::PublicKeyTypeEd25519(Uint256(key.verifying_key().to_bytes())),
159 ))),
160 args,
161 deployer.map_or(ScVal::Void, |muxed_account| {
162 ScVal::Address(xdr::ScAddress::Account(muxed_account.account_id()))
163 }),
164 ]
165 .try_into()
166 .unwrap(),
167 };
168
169 let public_strkey =
171 stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string();
172 let account_details = client.get_account(&public_strkey).await?;
173 let sequence: i64 = account_details.seq_num.into();
174 let tx = util::build_invoke_contract_tx(invoke_contract_args, sequence + 1, 100, &key)?;
175 let assembled = simulate_and_assemble_transaction(&client, &tx, None, None).await?;
176 let mut txn = assembled.transaction().clone();
177 txn = config
178 .sign_soroban_authorizations(&txn, &signers)
179 .await?
180 .unwrap_or(txn);
181 let return_value = client
182 .send_transaction_polling(&config.sign(txn, false).await?)
183 .await?
184 .return_value()?;
185 match return_value {
186 ScVal::Address(xdr::ScAddress::Contract(xdr::ContractId(hash))) => {
187 Ok(stellar_strkey::Contract(hash.0))
188 }
189 _ => Err(Error::InvalidReturnValue(
190 "{return_value:#?} is not a contract address".to_string(),
191 )),
192 }
193 }
194}