1use super::{DEFAULT_ENDPOINT, Developer};
17use crate::{
18 commands::StoreFormat,
19 helpers::args::{parse_private_key, prepare_endpoint},
20};
21
22use snarkvm::{
23 circuit::{Aleo, AleoCanaryV0, AleoTestnetV0, AleoV0},
24 console::{
25 network::{CanaryV0, MainnetV0, Network, TestnetV0},
26 program::ProgramOwner,
27 },
28 ledger::store::helpers::memory::BlockMemory,
29 prelude::{
30 ProgramID,
31 VM,
32 block::Transaction,
33 deployment_cost,
34 query::{Query, QueryTrait},
35 store::{ConsensusStore, helpers::memory::ConsensusMemory},
36 },
37};
38
39use aleo_std::StorageMode;
40use anyhow::Result;
41use clap::{Parser, builder::NonEmptyStringValueParser};
42use colored::Colorize;
43use snarkvm::prelude::{Address, ConsensusVersion};
44use std::{path::PathBuf, str::FromStr};
45use ureq::http::Uri;
46use zeroize::Zeroize;
47
48use anyhow::Context;
49
50#[derive(Debug, Parser)]
52#[command(
53 group(clap::ArgGroup::new("mode").required(true).multiple(false)),
54 group(clap::ArgGroup::new("key").required(true).multiple(false))
55)]
56pub struct Deploy {
57 program_id: String,
59 #[clap(long)]
61 path: Option<String>,
62 #[clap(short = 'p', long, group = "key", value_parser=NonEmptyStringValueParser::default())]
64 private_key: Option<String>,
65 #[clap(long, group = "key")]
67 dev_key: Option<u16>,
68 #[clap(long, group = "key", value_parser=NonEmptyStringValueParser::default())]
70 private_key_file: Option<String>,
71 #[clap(short, long, alias="query", default_value=DEFAULT_ENDPOINT, verbatim_doc_comment)]
78 endpoint: Uri,
79 #[clap(long, default_value_t = 0)]
81 priority_fee: u64,
82 #[clap(short, long)]
84 record: Option<String>,
85 #[clap(short, long, group = "mode", verbatim_doc_comment)]
89 broadcast: Option<Option<Uri>>,
90 #[clap(short, long, group = "mode")]
92 dry_run: bool,
93 #[clap(long, group = "mode")]
95 store: Option<String>,
96 #[clap(long, value_enum, default_value_t = StoreFormat::Bytes, requires="store")]
99 store_format: StoreFormat,
100 #[clap(long, requires = "broadcast")]
102 wait: bool,
103 #[clap(long, default_value_t = 60, requires = "wait")]
105 timeout: u64,
106 #[clap(long = "storage_path")]
108 storage_path: Option<PathBuf>,
109}
110
111impl Drop for Deploy {
112 fn drop(&mut self) {
114 self.private_key.zeroize();
115 }
116}
117
118impl Deploy {
119 pub fn parse<N: Network>(self) -> Result<String> {
121 match N::ID {
123 MainnetV0::ID => self.construct_deployment::<MainnetV0, AleoV0>(),
124 TestnetV0::ID => self.construct_deployment::<TestnetV0, AleoTestnetV0>(),
125 CanaryV0::ID => self.construct_deployment::<CanaryV0, AleoCanaryV0>(),
126 _ => unreachable!(),
127 }
128 .with_context(|| "Deployment failed")
129 }
130
131 fn construct_deployment<N: Network, A: Aleo<Network = N, BaseField = N::Field>>(self) -> Result<String> {
133 let endpoint = prepare_endpoint(self.endpoint.clone())?;
134
135 let query = Query::<N, BlockMemory<N>>::from(endpoint.clone());
137
138 let private_key = parse_private_key(self.private_key.clone(), self.private_key_file.clone(), self.dev_key)?;
140
141 let program_id = ProgramID::from_str(&self.program_id).with_context(|| "Failed to parse program ID")?;
143
144 let package =
146 Developer::parse_package(program_id, &self.path).with_context(|| "Failed to parse program package")?;
147
148 println!("📦 Creating deployment transaction for '{}'...\n", &program_id.to_string().bold());
149
150 let mut deployment = package.deploy::<A>(None).with_context(|| "Failed to generate the deployment")?;
152
153 let consensus_version =
155 N::CONSENSUS_VERSION(query.current_block_height().with_context(|| "Failed to query consensus height")?)?;
156
157 if consensus_version < ConsensusVersion::V9 {
160 deployment.set_program_checksum_raw(None);
161 deployment.set_program_owner_raw(None);
162 } else {
163 deployment.set_program_checksum_raw(Some(package.program().to_checksum()));
164 deployment.set_program_owner_raw(Some(Address::try_from(&private_key)?));
165 };
166
167 let deployment_id = deployment.to_deployment_id().with_context(|| "Failed to compute deployment ID")?;
169
170 let transaction = {
172 let rng = &mut rand::thread_rng();
174
175 let storage_mode = match &self.storage_path {
177 Some(path) => StorageMode::Custom(path.clone()),
178 None => StorageMode::Production,
179 };
180 let store = ConsensusStore::<N, ConsensusMemory<N>>::open(storage_mode)?;
181
182 let vm = VM::from(store).with_context(|| "Failed to initialize the virtual machine")?;
184
185 let (minimum_deployment_cost, (_, _, _, _)) =
187 deployment_cost(&vm.process().read(), &deployment, consensus_version)?;
188
189 let fee = match &self.record {
191 Some(record) => {
192 let fee_record =
193 Developer::parse_record(&private_key, record).with_context(|| "Failed to parse record")?;
194 let fee_authorization = vm.authorize_fee_private(
195 &private_key,
196 fee_record,
197 minimum_deployment_cost,
198 self.priority_fee,
199 deployment_id,
200 rng,
201 )?;
202 vm.execute_fee_authorization(fee_authorization, Some(&query), rng)
203 .with_context(|| "Failed to execute fee authorization")?
204 }
205 None => {
206 let fee_authorization = vm.authorize_fee_public(
207 &private_key,
208 minimum_deployment_cost,
209 self.priority_fee,
210 deployment_id,
211 rng,
212 )?;
213 vm.execute_fee_authorization(fee_authorization, Some(&query), rng)
214 .with_context(|| "Failed to execute fee authorization")?
215 }
216 };
217 let owner = ProgramOwner::new(&private_key, deployment_id, rng)
219 .with_context(|| "Failed to construct program owner")?;
220
221 Transaction::from_deployment(owner, deployment, fee).with_context(|| "Failed to crate transaction")?
223 };
224 println!("✅ Created deployment transaction for '{}'", program_id.to_string().bold());
225
226 Developer::handle_transaction(
228 &endpoint,
229 &self.broadcast,
230 self.dry_run,
231 &self.store,
232 self.store_format,
233 self.wait,
234 self.timeout,
235 transaction,
236 program_id.to_string(),
237 )
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244 use crate::commands::{CLI, Command, DeveloperCommand};
245
246 use anyhow::bail;
247
248 #[test]
249 fn clap_snarkos_deploy_missing_mode() {
250 let arg_vec = &[
251 "snarkos",
252 "developer",
253 "deploy",
254 "--private-key=PRIVATE_KEY",
255 "--endpoint=ENDPOINT",
256 "--priority-fee=77",
257 "--record=RECORD",
258 "hello.aleo",
259 ];
260
261 let err = CLI::try_parse_from(arg_vec).unwrap_err();
263 assert_eq!(err.kind(), clap::error::ErrorKind::MissingRequiredArgument);
264 }
265
266 #[test]
267 fn clap_snarkos_deploy() -> Result<()> {
268 let arg_vec = &[
269 "snarkos",
270 "developer",
271 "deploy",
272 "--private-key=PRIVATE_KEY",
273 "--endpoint=ENDPOINT",
274 "--priority-fee=77",
275 "--dry-run",
276 "--record=RECORD",
277 "hello.aleo",
278 ];
279 let cli = CLI::try_parse_from(arg_vec)?;
281
282 let Command::Developer(developer) = cli.command else {
283 bail!("Unexpected result of clap parsing!");
284 };
285 let DeveloperCommand::Deploy(deploy) = developer.command else {
286 bail!("Unexpected result of clap parsing!");
287 };
288
289 assert_eq!(developer.network, 0);
290 assert_eq!(deploy.program_id, "hello.aleo");
291 assert_eq!(deploy.private_key, Some("PRIVATE_KEY".to_string()));
292 assert_eq!(deploy.private_key_file, None);
293 assert_eq!(deploy.endpoint, "ENDPOINT");
294 assert!(deploy.dry_run);
295 assert!(deploy.broadcast.is_none());
296 assert_eq!(deploy.store, None);
297 assert_eq!(deploy.priority_fee, 77);
298 assert_eq!(deploy.record, Some("RECORD".to_string()));
299
300 Ok(())
301 }
302
303 #[test]
304 fn clap_snarkos_deploy_broadcast() -> Result<()> {
305 let arg_vec = &[
306 "snarkos",
307 "developer",
308 "deploy",
309 "--private-key=PRIVATE_KEY",
310 "--endpoint=ENDPOINT",
311 "--priority-fee=77",
312 "--broadcast=ENDPOINT2",
313 "--record=RECORD",
314 "hello.aleo",
315 ];
316 let cli = CLI::try_parse_from(arg_vec)?;
318
319 let Command::Developer(developer) = cli.command else {
320 bail!("Unexpected result of clap parsing!");
321 };
322 let DeveloperCommand::Deploy(deploy) = developer.command else {
323 bail!("Unexpected result of clap parsing!");
324 };
325
326 assert_eq!(developer.network, 0);
327 assert_eq!(deploy.program_id, "hello.aleo");
328 assert_eq!(deploy.private_key, Some("PRIVATE_KEY".to_string()));
329 assert_eq!(deploy.private_key_file, None);
330 assert_eq!(deploy.endpoint, "ENDPOINT");
331 assert!(!deploy.dry_run);
332 assert_eq!(Some(Some(Uri::try_from("ENDPOINT2").unwrap())), deploy.broadcast);
334 assert_eq!(deploy.store, None);
335 assert_eq!(deploy.priority_fee, 77);
336 assert_eq!(deploy.record, Some("RECORD".to_string()));
337
338 Ok(())
339 }
340}