1use super::*;
18
19impl<N: Network> ProgramManager<N> {
20 pub fn deploy_program(
22 &mut self,
23 program_id: impl TryInto<ProgramID<N>>,
24 priority_fee: u64,
25 fee_record: Option<Record<N, Plaintext<N>>>,
26 password: Option<&str>,
27 ) -> Result<String> {
28 ensure!(
30 self.api_client.is_some(),
31 "❌ Network client not set, network config must be set before deployment in order to send transactions to the Aleo network"
32 );
33
34 let program_id = program_id.try_into().map_err(|_| anyhow!("Invalid program ID"))?;
36
37 ensure!(
39 self.api_client()?.get_program(program_id).is_err(),
40 "❌ Program {:?} already deployed on chain, cancelling deployment",
41 program_id
42 );
43
44 println!("Loading program {program_id:?}..");
46 let program = if let Ok(program) = self.get_program(program_id) {
47 println!("Program {:?} already exists in program manager, using existing program", program_id);
48 program
49 } else if let Some(dir) = self.local_program_directory.as_ref() {
50 let program = self.find_program_on_disk(&program_id);
51 if program.is_err() {
52 bail!(
53 "❌ Program {program_id:?} could not be found at {dir:?} or in the program manager, please ensure the program is in the correct directory before continuing with deployment"
54 );
55 }
56 program?
57 } else {
58 bail!(
59 "❌ Program {:?} not found in program manager and no local program directory was configured",
60 program_id
61 );
62 };
63
64 program.imports().keys().try_for_each(|program_id| {
67 let imported_program = if self.contains_program(program_id)? {
68 self.get_program(program_id)
70 } else {
71 self.find_program(program_id)
73 }.map_err(|_| anyhow!("❌ Imported program {program_id:?} could not be found locally or on the Aleo Network"))?;
74
75 let imported_program_id = imported_program.id();
77 match self.on_chain_program_state(&imported_program)? {
78 OnChainProgramState::NotDeployed => {
79 bail!("❌ Imported program {imported_program_id:?} could not be found on the Aleo Network, please deploy this imported program first before continuing with deployment of {program_id:?}");
81 }
82 OnChainProgramState::Different => {
83 bail!("❌ Imported program {imported_program_id:?} is already deployed on chain and did not match local import");
85 }
86 OnChainProgramState::Same => (),
87 };
88
89 Ok::<_, Error>(())
90 })?;
91
92 let private_key = self.get_private_key(password)?;
94
95 println!("Building transaction..");
97 let query = self.api_client.as_ref().unwrap().base_url();
98 let transaction = Self::create_deploy_transaction(
99 &program,
100 &private_key,
101 priority_fee,
102 fee_record,
103 query.to_string(),
104 self.api_client()?,
105 &self.vm,
106 )?;
107
108 println!(
109 "Attempting to broadcast a deploy transaction for program {:?} to node {:?}",
110 program_id,
111 self.api_client().unwrap().base_url()
112 );
113
114 let result = self.broadcast_transaction(transaction);
115
116 if result.is_ok() {
118 println!("✅ Deployment transaction for {program_id:?} broadcast successfully");
119 } else {
120 println!("❌ Deployment transaction for {program_id:?} failed to broadcast");
121 };
122
123 result
124 }
125
126 pub fn create_deploy_transaction(
128 program: &Program<N>,
129 private_key: &PrivateKey<N>,
130 priority_fee: u64,
131 fee_record: Option<Record<N, Plaintext<N>>>,
132 node_url: String,
133 api_client: &AleoAPIClient<N>,
134 vm: &Option<VM<N, ConsensusMemory<N>>>,
135 ) -> Result<Transaction<N>> {
136 let rng = &mut rand::thread_rng();
138 let query = Query::from(node_url);
139
140 if let Some(vm) = vm {
141 vm.deploy(private_key, program, fee_record, priority_fee, Some(query), rng)
143 } else {
144 let vm = Self::initialize_vm(api_client, program, false)?;
146
147 vm.deploy(private_key, program, fee_record, priority_fee, Some(query), rng)
149 }
150 }
151
152 pub fn estimate_deployment_fee<A: Aleo<Network = N>>(&self, program: &Program<N>) -> Result<(u64, (u64, u64))> {
157 let vm = Self::initialize_vm(self.api_client()?, program, false)?;
158 let rng = &mut rand::thread_rng();
159 let private_key = PrivateKey::<N>::new(rng)?;
160 let deployment = vm.deploy(&private_key, program, None, 0u64, None, rng)?;
161 let (minimum_deployment_cost, (storage_cost, namespace_cost)) =
162 deployment_cost::<N>(deployment.deployment().ok_or(anyhow!("Deployment failed"))?)?;
163 Ok((minimum_deployment_cost, (storage_cost, namespace_cost)))
164 }
165
166 pub fn estimate_namespace_fee(program_id: impl TryInto<ProgramID<N>>) -> Result<u64> {
172 let program_id = program_id.try_into().map_err(|_| anyhow!("❌ Invalid program ID"))?;
173 let num_characters = program_id.to_string().chars().count() as u32;
174 let namespace_cost = 10u64
175 .checked_pow(10u32.saturating_sub(num_characters))
176 .ok_or(anyhow!("The namespace cost computation overflowed for a deployment"))?
177 .saturating_mul(1_000_000); Ok(namespace_cost)
179 }
180}
181
182#[cfg(test)]
183#[cfg(not(feature = "wasm"))]
184mod tests {
185 use super::*;
186 use crate::{
187 test_utils::{
188 random_program,
189 random_program_id,
190 setup_directory,
191 transfer_to_test_account,
192 CREDITS_IMPORT_TEST_PROGRAM,
193 HELLO_PROGRAM,
194 MULTIPLY_IMPORT_PROGRAM,
195 MULTIPLY_PROGRAM,
196 RECORD_2000000001_MICROCREDITS,
197 RECORD_5_MICROCREDITS,
198 },
199 AleoAPIClient,
200 RecordFinder,
201 };
202 use snarkvm_console::network::Testnet3;
203
204 use std::{ops::Add, str::FromStr, thread};
205
206 #[test]
207 #[ignore]
208 fn test_deploy() {
209 let recipient_private_key = PrivateKey::<Testnet3>::from_str(RECIPIENT_PRIVATE_KEY).unwrap();
210 let finalize_program = Program::<Testnet3>::from_str(FINALIZE_TEST_PROGRAM).unwrap();
211 let multiply_program = Program::<Testnet3>::from_str(MULTIPLY_PROGRAM).unwrap();
212 let multiply_import_program = Program::<Testnet3>::from_str(MULTIPLY_IMPORT_PROGRAM).unwrap();
213
214 thread::sleep(std::time::Duration::from_secs(5));
216 transfer_to_test_account(2000000001, 14, recipient_private_key, "3033").unwrap();
217 let api_client = AleoAPIClient::<Testnet3>::local_testnet3("3033");
218 let record_finder = RecordFinder::<Testnet3>::new(api_client.clone());
219 let temp_dir = setup_directory("aleo_test_deploy", CREDITS_IMPORT_TEST_PROGRAM, vec![]).unwrap();
220
221 let mut program_manager =
223 ProgramManager::<Testnet3>::new(Some(recipient_private_key), None, Some(api_client), Some(temp_dir), false)
224 .unwrap();
225
226 thread::sleep(std::time::Duration::from_secs(30));
228 let deployment_fee = 200_000_001;
229 let fee_record = record_finder.find_one_record(&recipient_private_key, deployment_fee, None).unwrap();
230 program_manager.deploy_program("credits_import_test.aleo", deployment_fee, Some(fee_record), None).unwrap();
231
232 thread::sleep(std::time::Duration::from_secs(45));
234 for _ in 0..4 {
235 let deployed_program = program_manager.api_client().unwrap().get_program("credits_import_test.aleo");
236
237 if deployed_program.is_ok() {
238 assert_eq!(deployed_program.unwrap(), Program::from_str(CREDITS_IMPORT_TEST_PROGRAM).unwrap());
239 break;
240 }
241 println!("Program has not yet appeared on chain, waiting another 15 seconds");
242 thread::sleep(std::time::Duration::from_secs(15));
243 }
244
245 program_manager.add_program(&finalize_program).unwrap();
247
248 let fee_record = record_finder.find_one_record(&recipient_private_key, deployment_fee, None).unwrap();
249 program_manager.deploy_program("finalize_test.aleo", deployment_fee, Some(fee_record), None).unwrap();
250
251 thread::sleep(std::time::Duration::from_secs(45));
253 for _ in 0..4 {
254 let deployed_program = program_manager.api_client().unwrap().get_program("finalize_test.aleo");
255
256 if deployed_program.is_ok() {
257 assert_eq!(deployed_program.unwrap(), Program::from_str(FINALIZE_TEST_PROGRAM).unwrap());
258 break;
259 }
260 println!("Program has not yet appeared on chain, waiting another 15 seconds");
261 thread::sleep(std::time::Duration::from_secs(15));
262 }
263
264 program_manager.add_program(&multiply_program).unwrap();
266
267 let fee_record = record_finder.find_one_record(&recipient_private_key, deployment_fee, None).unwrap();
268 program_manager.deploy_program("multiply_test.aleo", deployment_fee, Some(fee_record), None).unwrap();
269
270 thread::sleep(std::time::Duration::from_secs(45));
272 for _ in 0..4 {
273 let deployed_program = program_manager.api_client().unwrap().get_program("multiply_test.aleo");
274
275 if deployed_program.is_ok() {
276 assert_eq!(deployed_program.unwrap(), Program::from_str(MULTIPLY_PROGRAM).unwrap());
277 break;
278 }
279 println!("Program has not yet appeared on chain, waiting another 15 seconds");
280 thread::sleep(std::time::Duration::from_secs(15));
281 }
282
283 program_manager.add_program(&multiply_import_program).unwrap();
285
286 let fee_record = record_finder.find_one_record(&recipient_private_key, deployment_fee, None).unwrap();
287 program_manager.deploy_program("double_test.aleo", deployment_fee, Some(fee_record), None).unwrap();
288
289 thread::sleep(std::time::Duration::from_secs(45));
291 for _ in 0..4 {
292 let deployed_program = program_manager.api_client().unwrap().get_program("double_test.aleo");
293
294 if deployed_program.is_ok() {
295 assert_eq!(deployed_program.unwrap(), Program::from_str(MULTIPLY_IMPORT_PROGRAM).unwrap());
296 break;
297 }
298 println!("Program has not yet appeared on chain, waiting another 15 seconds");
299 thread::sleep(std::time::Duration::from_secs(15));
300 }
301 }
302
303 #[test]
304 fn test_deploy_failure_conditions() {
305 let rng = &mut rand::thread_rng();
306 let recipient_private_key = PrivateKey::<Testnet3>::new(rng).unwrap();
307 let record_5_microcredits = Record::<Testnet3, Plaintext<Testnet3>>::from_str(RECORD_5_MICROCREDITS).unwrap();
308 let record_2000000001_microcredits =
309 Record::<Testnet3, Plaintext<Testnet3>>::from_str(RECORD_2000000001_MICROCREDITS).unwrap();
310 let api_client = AleoAPIClient::<Testnet3>::local_testnet3("3033");
311 let randomized_program = random_program();
312 let randomized_program_id = randomized_program.id().to_string();
313 let randomized_program_string = randomized_program.to_string();
314 let temp_dir = setup_directory("aleo_unit_test_fees", &randomized_program.to_string(), vec![]).unwrap();
315
316 let mut program_manager = ProgramManager::<Testnet3>::new(
318 Some(recipient_private_key),
319 None,
320 Some(api_client.clone()),
321 Some(temp_dir),
322 false,
323 )
324 .unwrap();
325
326 let deployment_fee = 200000001;
327 let deployment =
329 program_manager.deploy_program(&randomized_program_id, 0, Some(record_5_microcredits.clone()), None);
330 assert!(deployment.is_err());
331
332 let deployment =
334 program_manager.deploy_program(&randomized_program_id, 2, Some(record_5_microcredits.clone()), None);
335 assert!(deployment.is_err());
336
337 let deployment =
339 program_manager.deploy_program(&randomized_program_id, deployment_fee, Some(record_5_microcredits), None);
340 assert!(deployment.is_err());
341
342 let deployment = program_manager.deploy_program(
344 "hello.aleo",
345 deployment_fee,
346 Some(record_2000000001_microcredits.clone()),
347 None,
348 );
349 assert!(deployment.is_err());
350
351 let missing_import_program_string =
353 format!("import {};\n", random_program_id(16)).add(&randomized_program_string);
354 let temp_dir_2 = setup_directory("aleo_unit_test_imports", &missing_import_program_string, vec![
355 ("hello.aleo", HELLO_PROGRAM),
356 (&randomized_program_id, &missing_import_program_string),
357 ])
358 .unwrap();
359 let mut program_manager = ProgramManager::<Testnet3>::new(
360 Some(recipient_private_key),
361 None,
362 Some(api_client),
363 Some(temp_dir_2),
364 false,
365 )
366 .unwrap();
367
368 let deployment = program_manager.deploy_program(
369 &randomized_program_id,
370 deployment_fee,
371 Some(record_2000000001_microcredits),
372 None,
373 );
374 assert!(deployment.is_err());
375 }
376}