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::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}
107
108impl Drop for Deploy {
109 fn drop(&mut self) {
111 self.private_key.zeroize();
112 }
113}
114
115impl Deploy {
116 pub fn parse<N: Network>(self) -> Result<String> {
118 match N::ID {
120 MainnetV0::ID => self.construct_deployment::<MainnetV0, AleoV0>(),
121 TestnetV0::ID => self.construct_deployment::<TestnetV0, AleoTestnetV0>(),
122 CanaryV0::ID => self.construct_deployment::<CanaryV0, AleoCanaryV0>(),
123 _ => unreachable!(),
124 }
125 .with_context(|| "Deployment failed")
126 }
127
128 fn construct_deployment<N: Network, A: Aleo<Network = N, BaseField = N::Field>>(self) -> Result<String> {
130 let endpoint = prepare_endpoint(self.endpoint.clone())?;
131
132 let query = Query::<N, BlockMemory<N>>::from(endpoint.clone());
134
135 let private_key = parse_private_key(self.private_key.clone(), self.private_key_file.clone(), self.dev_key)?;
137
138 let program_id = ProgramID::from_str(&self.program_id).with_context(|| "Failed to parse program ID")?;
140
141 let package =
143 Developer::parse_package(program_id, &self.path).with_context(|| "Failed to parse program package")?;
144
145 println!("📦 Creating deployment transaction for '{}'...\n", &program_id.to_string().bold());
146
147 let process = package.get_process()?;
149
150 let mut deployment =
152 package.deploy::<A>(&process, None).with_context(|| "Failed to generate the deployment")?;
153
154 let consensus_version =
156 N::CONSENSUS_VERSION(query.current_block_height().with_context(|| "Failed to query consensus height")?)?;
157
158 if consensus_version < ConsensusVersion::V9 {
161 deployment.set_program_checksum_raw(None);
162 deployment.set_program_owner_raw(None);
163 } else {
164 deployment.set_program_checksum_raw(Some(package.program().to_checksum()));
165 deployment.set_program_owner_raw(Some(Address::try_from(&private_key)?));
166 };
167
168 let deployment_id = deployment.to_deployment_id().with_context(|| "Failed to compute deployment ID")?;
170
171 let (minimum_deployment_cost, (_, _, _, _)) = deployment_cost(&process, &deployment, consensus_version)
173 .with_context(|| "Failed to compute the minimum deployment cost")?;
174
175 let transaction = {
177 let rng = &mut rand::thread_rng();
179
180 let store = ConsensusStore::<N, ConsensusMemory<N>>::open(StorageMode::Production)
182 .with_context(|| "Failed to open the consensus store")?;
183
184 let vm = VM::from(store).with_context(|| "Failed to initialize the virtual machine")?;
186
187 let fee = match &self.record {
189 Some(record) => {
190 let fee_record =
191 Developer::parse_record(&private_key, record).with_context(|| "Failed to parse record")?;
192 let fee_authorization = vm.authorize_fee_private(
193 &private_key,
194 fee_record,
195 minimum_deployment_cost,
196 self.priority_fee,
197 deployment_id,
198 rng,
199 )?;
200 vm.execute_fee_authorization(fee_authorization, Some(&query), rng)
201 .with_context(|| "Failed to execute fee authorization")?
202 }
203 None => {
204 let fee_authorization = vm.authorize_fee_public(
205 &private_key,
206 minimum_deployment_cost,
207 self.priority_fee,
208 deployment_id,
209 rng,
210 )?;
211 vm.execute_fee_authorization(fee_authorization, Some(&query), rng)
212 .with_context(|| "Failed to execute fee authorization")?
213 }
214 };
215 let owner = ProgramOwner::new(&private_key, deployment_id, rng)
217 .with_context(|| "Failed to construct program owner")?;
218
219 Transaction::from_deployment(owner, deployment, fee).with_context(|| "Failed to crate transaction")?
221 };
222 println!("✅ Created deployment transaction for '{}'", program_id.to_string().bold());
223
224 Developer::handle_transaction(
226 &endpoint,
227 &self.broadcast,
228 self.dry_run,
229 &self.store,
230 self.store_format,
231 self.wait,
232 self.timeout,
233 transaction,
234 program_id.to_string(),
235 )
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use crate::commands::{CLI, Command, DeveloperCommand};
243
244 use anyhow::bail;
245
246 #[test]
247 fn clap_snarkos_deploy_missing_mode() {
248 let arg_vec = &[
249 "snarkos",
250 "developer",
251 "deploy",
252 "--private-key=PRIVATE_KEY",
253 "--endpoint=ENDPOINT",
254 "--priority-fee=77",
255 "--record=RECORD",
256 "hello.aleo",
257 ];
258
259 let err = CLI::try_parse_from(arg_vec).unwrap_err();
261 assert_eq!(err.kind(), clap::error::ErrorKind::MissingRequiredArgument);
262 }
263
264 #[test]
265 fn clap_snarkos_deploy() -> Result<()> {
266 let arg_vec = &[
267 "snarkos",
268 "developer",
269 "deploy",
270 "--private-key=PRIVATE_KEY",
271 "--endpoint=ENDPOINT",
272 "--priority-fee=77",
273 "--dry-run",
274 "--record=RECORD",
275 "hello.aleo",
276 ];
277 let cli = CLI::try_parse_from(arg_vec)?;
279
280 let Command::Developer(developer) = cli.command else {
281 bail!("Unexpected result of clap parsing!");
282 };
283 let DeveloperCommand::Deploy(deploy) = developer.command else {
284 bail!("Unexpected result of clap parsing!");
285 };
286
287 assert_eq!(developer.network, 0);
288 assert_eq!(deploy.program_id, "hello.aleo");
289 assert_eq!(deploy.private_key, Some("PRIVATE_KEY".to_string()));
290 assert_eq!(deploy.private_key_file, None);
291 assert_eq!(deploy.endpoint, "ENDPOINT");
292 assert!(deploy.dry_run);
293 assert!(deploy.broadcast.is_none());
294 assert_eq!(deploy.store, None);
295 assert_eq!(deploy.priority_fee, 77);
296 assert_eq!(deploy.record, Some("RECORD".to_string()));
297
298 Ok(())
299 }
300
301 #[test]
302 fn clap_snarkos_deploy_broadcast() -> Result<()> {
303 let arg_vec = &[
304 "snarkos",
305 "developer",
306 "deploy",
307 "--private-key=PRIVATE_KEY",
308 "--endpoint=ENDPOINT",
309 "--priority-fee=77",
310 "--broadcast=ENDPOINT2",
311 "--record=RECORD",
312 "hello.aleo",
313 ];
314 let cli = CLI::try_parse_from(arg_vec)?;
316
317 let Command::Developer(developer) = cli.command else {
318 bail!("Unexpected result of clap parsing!");
319 };
320 let DeveloperCommand::Deploy(deploy) = developer.command else {
321 bail!("Unexpected result of clap parsing!");
322 };
323
324 assert_eq!(developer.network, 0);
325 assert_eq!(deploy.program_id, "hello.aleo");
326 assert_eq!(deploy.private_key, Some("PRIVATE_KEY".to_string()));
327 assert_eq!(deploy.private_key_file, None);
328 assert_eq!(deploy.endpoint, "ENDPOINT");
329 assert!(!deploy.dry_run);
330 assert_eq!(Some(Some(Uri::try_from("ENDPOINT2").unwrap())), deploy.broadcast);
332 assert_eq!(deploy.store, None);
333 assert_eq!(deploy.priority_fee, 77);
334 assert_eq!(deploy.record, Some("RECORD".to_string()));
335
336 Ok(())
337 }
338}