snarkvm_ledger_block/transaction/deployment/
mod.rs1#![allow(clippy::type_complexity)]
17
18mod bytes;
19mod serialize;
20mod string;
21
22use crate::Transaction;
23use console::{
24 network::prelude::*,
25 program::{Address, Identifier, ProgramID},
26 types::{Field, U8},
27};
28use snarkvm_synthesizer_program::Program;
29use snarkvm_synthesizer_snark::{Certificate, VerifyingKey};
30
31#[derive(Clone)]
32pub struct Deployment<N: Network> {
33 edition: u16,
35 program: Program<N>,
37 verifying_keys: Vec<(Identifier<N>, (VerifyingKey<N>, Certificate<N>))>,
39 program_checksum: Option<[U8<N>; 32]>,
44 program_owner: Option<Address<N>>,
49}
50
51impl<N: Network> PartialEq for Deployment<N> {
52 fn eq(&self, other: &Self) -> bool {
53 self.edition == other.edition
54 && self.program_checksum == other.program_checksum
55 && self.program_owner == other.program_owner
56 && self.verifying_keys == other.verifying_keys
57 && self.program == other.program
58 }
59}
60
61impl<N: Network> Eq for Deployment<N> {}
62
63impl<N: Network> Deployment<N> {
64 pub fn new(
66 edition: u16,
67 program: Program<N>,
68 verifying_keys: Vec<(Identifier<N>, (VerifyingKey<N>, Certificate<N>))>,
69 program_checksum: Option<[U8<N>; 32]>,
70 program_owner: Option<Address<N>>,
71 ) -> Result<Self> {
72 let deployment = Self { edition, program, verifying_keys, program_checksum, program_owner };
74 deployment.check_is_ordered()?;
76 Ok(deployment)
78 }
79
80 pub fn check_is_ordered(&self) -> Result<()> {
82 let program_id = self.program.id();
83 let num_functions = self.program.functions().len();
84 let num_records = self.program.records().len();
85
86 self.version()?;
89
90 if let Some(program_checksum) = self.program_checksum {
92 ensure!(
93 program_checksum == self.program.to_checksum(),
94 "The program checksum in the deployment does not match the computed checksum for '{program_id}'"
95 );
96 }
97 ensure!(
99 !self.program.functions().is_empty(),
100 "No functions present in the deployment for program '{program_id}'"
101 );
102 ensure!(
104 num_functions <= N::MAX_FUNCTIONS,
105 "Deployment has too many functions (maximum is '{}')",
106 N::MAX_FUNCTIONS
107 );
108 ensure!(num_records <= N::MAX_RECORDS, "Deployment has too many records (maximum is '{}')", N::MAX_RECORDS);
110
111 ensure!(
113 !self.verifying_keys.is_empty(),
114 "No verifying keys present in the deployment for program '{program_id}'"
115 );
116 ensure!(
118 self.verifying_keys.len() == num_functions || self.verifying_keys.len() == num_functions + num_records,
119 "Deployment has an incorrect number of verifying keys, according to the program."
120 );
121 for ((function_name, function), (name, _)) in
123 self.program.functions().iter().zip_eq(&self.verifying_keys[..num_functions])
124 {
125 if function_name != function.name() {
127 bail!("The function key is '{function_name}', but the function name is '{}'", function.name())
128 }
129 if name != function.name() {
131 bail!("The verifier key is '{name}', but the function name is '{}'", function.name())
132 }
133 }
134 ensure!(
136 !has_duplicates(self.verifying_keys.iter().map(|(name, ..)| name)),
137 "A duplicate verifying key name was found"
138 );
139
140 if self.verifying_keys.len() > num_functions {
142 let record_keys = &self.verifying_keys[num_functions..];
143 ensure!(
145 num_records == record_keys.len(),
146 "Expected {} records, but {} record verifying keys were provided.",
147 num_records,
148 record_keys.len()
149 );
150 for ((record_name, record), (name, _)) in self.program.records().iter().zip_eq(record_keys) {
152 if record_name != record.name() {
154 bail!("The record key is '{record_name}', but the record name is '{}'", record.name())
155 }
156 if name != record.name() {
158 bail!("The record verifying key is '{name}', but the record name is '{}'", record.name())
159 }
160 }
161 }
162
163 Ok(())
164 }
165
166 pub fn size_in_bytes(&self) -> Result<u64> {
168 Ok(u64::try_from(self.to_bytes_le()?.len())?)
169 }
170
171 pub fn num_functions(&self) -> usize {
173 self.program.functions().len()
174 }
175
176 pub const fn edition(&self) -> u16 {
178 self.edition
179 }
180
181 pub const fn program(&self) -> &Program<N> {
183 &self.program
184 }
185
186 pub const fn program_checksum(&self) -> Option<[U8<N>; 32]> {
188 self.program_checksum
189 }
190
191 pub const fn program_owner(&self) -> Option<Address<N>> {
193 self.program_owner
194 }
195
196 pub const fn program_id(&self) -> &ProgramID<N> {
198 self.program.id()
199 }
200
201 pub const fn verifying_keys(&self) -> &Vec<(Identifier<N>, (VerifyingKey<N>, Certificate<N>))> {
204 &self.verifying_keys
205 }
206
207 pub fn function_verifying_keys(&self) -> &[(Identifier<N>, (VerifyingKey<N>, Certificate<N>))] {
209 &self.verifying_keys[..self.program.functions().len()]
210 }
211
212 pub fn translation_verifying_keys(&self) -> Option<&[(Identifier<N>, (VerifyingKey<N>, Certificate<N>))]> {
214 let num_functions = self.program.functions().len();
215 if self.verifying_keys.len() > num_functions { Some(&self.verifying_keys[num_functions..]) } else { None }
216 }
217
218 pub fn num_combined_variables(&self) -> Result<u64> {
220 self.num_combined_function_variables()?
221 .checked_add(self.num_combined_translation_variables()?)
222 .ok_or_else(|| anyhow!("Overflow when counting total variables for '{}'", self.program_id()))
223 }
224
225 pub fn num_combined_function_variables(&self) -> Result<u64> {
227 let mut num_combined_variables = 0u64;
229 for (_, (vk, _)) in self.function_verifying_keys() {
231 num_combined_variables = num_combined_variables
233 .checked_add(vk.num_variables())
234 .ok_or_else(|| anyhow!("Overflow when counting variables for '{}'", self.program_id()))?;
235 }
236 Ok(num_combined_variables)
238 }
239
240 pub fn num_combined_translation_variables(&self) -> Result<u64> {
242 let mut num_combined_variables = 0u64;
244 if let Some(record_vks) = self.translation_verifying_keys() {
246 for (_, (vk, _)) in record_vks {
247 num_combined_variables = num_combined_variables
249 .checked_add(vk.num_variables())
250 .ok_or_else(|| anyhow!("Overflow when counting variables for '{}'", self.program_id()))?;
251 }
252 }
253 Ok(num_combined_variables)
255 }
256
257 pub fn num_combined_constraints(&self) -> Result<u64> {
259 self.num_combined_function_constraints()?
260 .checked_add(self.num_combined_translation_constraints()?)
261 .ok_or_else(|| anyhow!("Overflow when counting total constraints for '{}'", self.program_id()))
262 }
263
264 pub fn num_combined_function_constraints(&self) -> Result<u64> {
266 let mut num_combined_constraints = 0u64;
268 for (_, (vk, _)) in self.function_verifying_keys() {
270 num_combined_constraints = num_combined_constraints
272 .checked_add(vk.circuit_info.num_constraints as u64)
273 .ok_or_else(|| anyhow!("Overflow when counting constraints for '{}'", self.program_id()))?;
274 }
275 Ok(num_combined_constraints)
277 }
278
279 pub fn num_combined_translation_constraints(&self) -> Result<u64> {
281 let mut num_combined_constraints = 0u64;
283 if let Some(record_vks) = self.translation_verifying_keys() {
285 for (_, (vk, _)) in record_vks {
286 num_combined_constraints = num_combined_constraints
288 .checked_add(vk.circuit_info.num_constraints as u64)
289 .ok_or_else(|| anyhow!("Overflow when counting constraints for '{}'", self.program_id()))?;
290 }
291 }
292 Ok(num_combined_constraints)
294 }
295
296 pub fn combined_density(&self) -> u64 {
298 self.combined_function_density().saturating_add(self.combined_translation_density())
299 }
300
301 pub fn combined_function_density(&self) -> u64 {
303 let mut combined_density = 0u64;
305
306 for (_, (vk, _)) in self.function_verifying_keys() {
308 let info = vk.circuit_info;
309
310 let function_density = (info.num_non_zero_a as u64)
311 .saturating_add(info.num_non_zero_b as u64)
312 .saturating_add(info.num_non_zero_c as u64);
313
314 combined_density = combined_density.saturating_add(function_density);
315 }
316
317 combined_density
318 }
319
320 pub fn combined_translation_density(&self) -> u64 {
322 let mut combined_density = 0u64;
324
325 if let Some(record_vks) = self.translation_verifying_keys() {
327 for (_, (vk, _)) in record_vks {
328 let info = vk.circuit_info;
329 let circuit_density = (info.num_non_zero_a as u64)
330 .saturating_add(info.num_non_zero_b as u64)
331 .saturating_add(info.num_non_zero_c as u64);
332
333 combined_density = combined_density.saturating_add(circuit_density);
334 }
335 }
336
337 combined_density
338 }
339
340 pub fn to_deployment_id(&self) -> Result<Field<N>> {
342 Ok(*Transaction::deployment_tree(self)?.root())
343 }
344}
345
346impl<N: Network> Deployment<N> {
347 pub fn set_edition_raw(&mut self, edition: u16) {
349 self.edition = edition;
350 }
351
352 pub fn set_program_checksum_raw(&mut self, program_checksum: Option<[U8<N>; 32]>) {
354 self.program_checksum = program_checksum;
355 }
356
357 pub fn set_program_owner_raw(&mut self, program_owner: Option<Address<N>>) {
359 self.program_owner = program_owner;
360 }
361
362 #[doc(hidden)]
365 pub fn remove_verifying_key(&mut self, name: &Identifier<N>) {
366 self.verifying_keys.retain(|(n, _)| n != name);
367 }
368
369 #[doc(hidden)]
372 pub fn remove_verifying_keys(&mut self, names: &[Identifier<N>]) {
373 self.verifying_keys.retain(|(n, _)| !names.contains(n));
374 }
375
376 pub fn version(&self) -> Result<DeploymentVersion> {
379 match (self.program_checksum.is_some(), self.program_owner.is_some()) {
380 (false, false) => Ok(DeploymentVersion::V1),
382 (true, true) => Ok(DeploymentVersion::V2),
384 (true, false) => Ok(DeploymentVersion::V3),
386 (false, true) => {
388 bail!("The program owner is present, but the program checksum is absent.")
389 }
390 }
391 }
392}
393
394#[derive(Copy, Clone, Eq, PartialEq)]
396pub enum DeploymentVersion {
397 V1 = 1,
400 V2 = 2,
403 V3 = 3,
406}
407
408#[cfg(test)]
409pub mod test_helpers {
410 use super::*;
411 use console::network::MainnetV0;
412 use snarkvm_synthesizer_process::Process;
413
414 use std::sync::OnceLock;
415
416 type CurrentNetwork = MainnetV0;
417 type CurrentAleo = snarkvm_circuit::network::AleoV0;
418
419 pub(crate) fn sample_deployment_v1(edition: u16, rng: &mut TestRng) -> Deployment<CurrentNetwork> {
420 static INSTANCE: OnceLock<Deployment<CurrentNetwork>> = OnceLock::new();
421 let deployment = INSTANCE
422 .get_or_init(|| {
423 let (string, program) = Program::<CurrentNetwork>::parse(
425 r"
426program testing_three.aleo;
427
428mapping store:
429 key as u32.public;
430 value as u32.public;
431
432function compute:
433 input r0 as u32.private;
434 add r0 r0 into r1;
435 output r1 as u32.public;",
436 )
437 .unwrap();
438 assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
439 let process = Process::load().unwrap();
441 let deployment = process.deploy::<CurrentAleo, _>(&program, rng).unwrap();
443 Deployment::from_str(&deployment.to_string()).unwrap()
446 })
447 .clone();
448 Deployment::<CurrentNetwork>::new(
451 edition % 2,
452 deployment.program().clone(),
453 deployment.verifying_keys().clone(),
454 None,
455 None,
456 )
457 .unwrap()
458 }
459
460 pub(crate) fn sample_deployment_v2_without_translation_keys(
461 edition: u16,
462 rng: &mut TestRng,
463 ) -> Deployment<CurrentNetwork> {
464 static INSTANCE: OnceLock<Deployment<CurrentNetwork>> = OnceLock::new();
465 let deployment = INSTANCE
466 .get_or_init(|| {
467 let (string, program) = Program::<CurrentNetwork>::parse(
469 r"
470program testing_four.aleo;
471
472mapping store:
473 key as u32.public;
474 value as u32.public;
475
476function compute:
477 input r0 as u32.private;
478 add r0 r0 into r1;
479 output r1 as u32.public;",
480 )
481 .unwrap();
482 assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
483 let process = Process::load().unwrap();
485 let deployment = process.deploy::<CurrentAleo, _>(&program, rng).unwrap();
487 Deployment::from_str(&deployment.to_string()).unwrap()
490 })
491 .clone();
492 Deployment::<CurrentNetwork>::new(
494 edition,
495 deployment.program().clone(),
496 deployment.verifying_keys().clone(),
497 deployment.program_checksum(),
498 Some(Address::rand(rng)),
499 )
500 .unwrap()
501 }
502
503 pub(crate) fn sample_deployment_v2_with_translation_keys(
504 edition: u16,
505 rng: &mut TestRng,
506 ) -> Deployment<CurrentNetwork> {
507 static INSTANCE: OnceLock<Deployment<CurrentNetwork>> = OnceLock::new();
508 let deployment = INSTANCE
509 .get_or_init(|| {
510 let (string, program) = Program::<CurrentNetwork>::parse(
512 r"
513program testing_five.aleo;
514
515record data:
516 owner as address.private;
517 one as field.private;
518 two as group.public;
519
520mapping store:
521 key as u32.public;
522 value as u32.public;
523
524function compute:
525 input r0 as u32.private;
526 add r0 r0 into r1;
527 output r1 as u32.public;",
528 )
529 .unwrap();
530 assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
531 let process = Process::load().unwrap();
533 let deployment = process.deploy::<CurrentAleo, _>(&program, rng).unwrap();
535 Deployment::from_str(&deployment.to_string()).unwrap()
538 })
539 .clone();
540 Deployment::<CurrentNetwork>::new(
542 edition,
543 deployment.program().clone(),
544 deployment.verifying_keys().clone(),
545 deployment.program_checksum(),
546 Some(Address::rand(rng)),
547 )
548 .unwrap()
549 }
550
551 pub(crate) fn sample_deployment_v3(edition: u16, rng: &mut TestRng) -> Deployment<CurrentNetwork> {
554 let mut deployment = sample_deployment_v2_with_translation_keys(edition, rng);
556 deployment.set_program_owner_raw(None);
557 deployment
558 }
559}