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 process = package.get_process()?;
152
153 let mut deployment =
155 package.deploy::<A>(&process, None).with_context(|| "Failed to generate the deployment")?;
156
157 let consensus_version =
159 N::CONSENSUS_VERSION(query.current_block_height().with_context(|| "Failed to query consensus height")?)?;
160
161 if consensus_version < ConsensusVersion::V9 {
164 deployment.set_program_checksum_raw(None);
165 deployment.set_program_owner_raw(None);
166 } else {
167 deployment.set_program_checksum_raw(Some(package.program().to_checksum()));
168 deployment.set_program_owner_raw(Some(Address::try_from(&private_key)?));
169 };
170
171 let deployment_id = deployment.to_deployment_id().with_context(|| "Failed to compute deployment ID")?;
173
174 let (minimum_deployment_cost, (_, _, _, _)) = deployment_cost(&process, &deployment, consensus_version)
176 .with_context(|| "Failed to compute the minimum deployment cost")?;
177
178 let transaction = {
180 let rng = &mut rand::thread_rng();
182
183 let storage_mode = match &self.storage_path {
185 Some(path) => StorageMode::Custom(path.clone()),
186 None => StorageMode::Production,
187 };
188 let store = ConsensusStore::<N, ConsensusMemory<N>>::open(storage_mode)
189 .with_context(|| "Failed to open the consensus store")?;
190
191 let vm = VM::from(store).with_context(|| "Failed to initialize the virtual machine")?;
193
194 let fee = match &self.record {
196 Some(record) => {
197 let fee_record =
198 Developer::parse_record(&private_key, record).with_context(|| "Failed to parse record")?;
199 let fee_authorization = vm.authorize_fee_private(
200 &private_key,
201 fee_record,
202 minimum_deployment_cost,
203 self.priority_fee,
204 deployment_id,
205 rng,
206 )?;
207 vm.execute_fee_authorization(fee_authorization, Some(&query), rng)
208 .with_context(|| "Failed to execute fee authorization")?
209 }
210 None => {
211 let fee_authorization = vm.authorize_fee_public(
212 &private_key,
213 minimum_deployment_cost,
214 self.priority_fee,
215 deployment_id,
216 rng,
217 )?;
218 vm.execute_fee_authorization(fee_authorization, Some(&query), rng)
219 .with_context(|| "Failed to execute fee authorization")?
220 }
221 };
222 let owner = ProgramOwner::new(&private_key, deployment_id, rng)
224 .with_context(|| "Failed to construct program owner")?;
225
226 Transaction::from_deployment(owner, deployment, fee).with_context(|| "Failed to crate transaction")?
228 };
229 println!("✅ Created deployment transaction for '{}'", program_id.to_string().bold());
230
231 Developer::handle_transaction(
233 &endpoint,
234 &self.broadcast,
235 self.dry_run,
236 &self.store,
237 self.store_format,
238 self.wait,
239 self.timeout,
240 transaction,
241 program_id.to_string(),
242 )
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use crate::commands::{CLI, Command, DeveloperCommand};
250
251 use anyhow::bail;
252
253 #[test]
254 fn clap_snarkos_deploy_missing_mode() {
255 let arg_vec = &[
256 "snarkos",
257 "developer",
258 "deploy",
259 "--private-key=PRIVATE_KEY",
260 "--endpoint=ENDPOINT",
261 "--priority-fee=77",
262 "--record=RECORD",
263 "hello.aleo",
264 ];
265
266 let err = CLI::try_parse_from(arg_vec).unwrap_err();
268 assert_eq!(err.kind(), clap::error::ErrorKind::MissingRequiredArgument);
269 }
270
271 #[test]
272 fn clap_snarkos_deploy() -> Result<()> {
273 let arg_vec = &[
274 "snarkos",
275 "developer",
276 "deploy",
277 "--private-key=PRIVATE_KEY",
278 "--endpoint=ENDPOINT",
279 "--priority-fee=77",
280 "--dry-run",
281 "--record=RECORD",
282 "hello.aleo",
283 ];
284 let cli = CLI::try_parse_from(arg_vec)?;
286
287 let Command::Developer(developer) = cli.command else {
288 bail!("Unexpected result of clap parsing!");
289 };
290 let DeveloperCommand::Deploy(deploy) = developer.command else {
291 bail!("Unexpected result of clap parsing!");
292 };
293
294 assert_eq!(developer.network, 0);
295 assert_eq!(deploy.program_id, "hello.aleo");
296 assert_eq!(deploy.private_key, Some("PRIVATE_KEY".to_string()));
297 assert_eq!(deploy.private_key_file, None);
298 assert_eq!(deploy.endpoint, "ENDPOINT");
299 assert!(deploy.dry_run);
300 assert!(deploy.broadcast.is_none());
301 assert_eq!(deploy.store, None);
302 assert_eq!(deploy.priority_fee, 77);
303 assert_eq!(deploy.record, Some("RECORD".to_string()));
304
305 Ok(())
306 }
307
308 #[test]
309 fn clap_snarkos_deploy_broadcast() -> Result<()> {
310 let arg_vec = &[
311 "snarkos",
312 "developer",
313 "deploy",
314 "--private-key=PRIVATE_KEY",
315 "--endpoint=ENDPOINT",
316 "--priority-fee=77",
317 "--broadcast=ENDPOINT2",
318 "--record=RECORD",
319 "hello.aleo",
320 ];
321 let cli = CLI::try_parse_from(arg_vec)?;
323
324 let Command::Developer(developer) = cli.command else {
325 bail!("Unexpected result of clap parsing!");
326 };
327 let DeveloperCommand::Deploy(deploy) = developer.command else {
328 bail!("Unexpected result of clap parsing!");
329 };
330
331 assert_eq!(developer.network, 0);
332 assert_eq!(deploy.program_id, "hello.aleo");
333 assert_eq!(deploy.private_key, Some("PRIVATE_KEY".to_string()));
334 assert_eq!(deploy.private_key_file, None);
335 assert_eq!(deploy.endpoint, "ENDPOINT");
336 assert!(!deploy.dry_run);
337 assert_eq!(Some(Some(Uri::try_from("ENDPOINT2").unwrap())), deploy.broadcast);
339 assert_eq!(deploy.store, None);
340 assert_eq!(deploy.priority_fee, 77);
341 assert_eq!(deploy.record, Some("RECORD".to_string()));
342
343 Ok(())
344 }
345}