Skip to main content

snarkvm_ledger_block/transaction/deployment/
mod.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#![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    /// The edition.
34    edition: u16,
35    /// The program.
36    program: Program<N>,
37    /// The mapping of function (and optionally record names) to their verifying key and certificate.
38    verifying_keys: Vec<(Identifier<N>, (VerifyingKey<N>, Certificate<N>))>,
39    /// An optional checksum for the program.
40    /// This field creates a backwards-compatible implicit versioning mechanism for deployments.
41    /// Before the migration height where this feature is enabled, the checksum will **not** be allowed.
42    /// After the migration height where this feature is enabled, the checksum will be required.
43    program_checksum: Option<[U8<N>; 32]>,
44    /// An optional owner for the program.
45    /// This field creates a backwards-compatible implicit versioning mechanism for deployments.
46    /// Before the migration height where this feature is enabled, the owner will **not** be allowed.
47    /// After the migration height where this feature is enabled, the owner will be required, except for V3 (amendment) deployments.
48    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    /// Initializes a new deployment.
65    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        // Construct the deployment.
73        let deployment = Self { edition, program, verifying_keys, program_checksum, program_owner };
74        // Ensure the deployment is ordered.
75        deployment.check_is_ordered()?;
76        // Return the deployment.
77        Ok(deployment)
78    }
79
80    /// Checks that the deployment is ordered.
81    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        // Ensure that the appropriate optional fields are present.
87        // The call to `Deployment::version` implicitly performs this check.
88        self.version()?;
89
90        // Validate the deployment based on the program checksum.
91        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 the program contains functions.
98        ensure!(
99            !self.program.functions().is_empty(),
100            "No functions present in the deployment for program '{program_id}'"
101        );
102        // Ensure the number of functions does not exceed the maximum.
103        ensure!(
104            num_functions <= N::MAX_FUNCTIONS,
105            "Deployment has too many functions (maximum is '{}')",
106            N::MAX_FUNCTIONS
107        );
108        // Ensure the number of records does not exceed the maximum.
109        ensure!(num_records <= N::MAX_RECORDS, "Deployment has too many records (maximum is '{}')", N::MAX_RECORDS);
110
111        // Ensure the deployment contains verifying keys.
112        ensure!(
113            !self.verifying_keys.is_empty(),
114            "No verifying keys present in the deployment for program '{program_id}'"
115        );
116        // Ensure the number of verifying keys is either num_functions or num_functions + num_records.
117        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        // Ensure the function verifying keys correspond to the program functions.
122        for ((function_name, function), (name, _)) in
123            self.program.functions().iter().zip_eq(&self.verifying_keys[..num_functions])
124        {
125            // Ensure the function name is correct.
126            if function_name != function.name() {
127                bail!("The function key is '{function_name}', but the function name is '{}'", function.name())
128            }
129            // Ensure the function name with the verifying key is correct.
130            if name != function.name() {
131                bail!("The verifier key is '{name}', but the function name is '{}'", function.name())
132            }
133        }
134        // Ensure there are no duplicate verifying keys.
135        ensure!(
136            !has_duplicates(self.verifying_keys.iter().map(|(name, ..)| name)),
137            "A duplicate verifying key name was found"
138        );
139
140        // If record verifying keys are present, ensure they are well-formed.
141        if self.verifying_keys.len() > num_functions {
142            let record_keys = &self.verifying_keys[num_functions..];
143            // Ensure the number of records matches the number of record verifying keys.
144            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            // Ensure the records and record verifying keys correspond.
151            for ((record_name, record), (name, _)) in self.program.records().iter().zip_eq(record_keys) {
152                // Ensure the record name is correct.
153                if record_name != record.name() {
154                    bail!("The record key is '{record_name}', but the record name is '{}'", record.name())
155                }
156                // Ensure the record name with the record verifying key is correct.
157                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    /// Returns the size in bytes.
167    pub fn size_in_bytes(&self) -> Result<u64> {
168        Ok(u64::try_from(self.to_bytes_le()?.len())?)
169    }
170
171    /// Returns the number of program functions in the deployment.
172    pub fn num_functions(&self) -> usize {
173        self.program.functions().len()
174    }
175
176    /// Returns the edition.
177    pub const fn edition(&self) -> u16 {
178        self.edition
179    }
180
181    /// Returns the program.
182    pub const fn program(&self) -> &Program<N> {
183        &self.program
184    }
185
186    /// Returns the program checksum, if it was stored.
187    pub const fn program_checksum(&self) -> Option<[U8<N>; 32]> {
188        self.program_checksum
189    }
190
191    /// Returns the program owner, if it was stored.
192    pub const fn program_owner(&self) -> Option<Address<N>> {
193        self.program_owner
194    }
195
196    /// Returns the program.
197    pub const fn program_id(&self) -> &ProgramID<N> {
198        self.program.id()
199    }
200
201    /// Returns the verifying keys.
202    /// The function keys are ordered first, followed by the optional record keys.
203    pub const fn verifying_keys(&self) -> &Vec<(Identifier<N>, (VerifyingKey<N>, Certificate<N>))> {
204        &self.verifying_keys
205    }
206
207    /// Returns the function verifying keys.
208    pub fn function_verifying_keys(&self) -> &[(Identifier<N>, (VerifyingKey<N>, Certificate<N>))] {
209        &self.verifying_keys[..self.program.functions().len()]
210    }
211
212    /// Returns the record translation verifying keys, if any are present.
213    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    /// Returns the sum of the variable counts in this deployment.
219    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    /// Returns the sum of the variable counts for all functions in this deployment.
226    pub fn num_combined_function_variables(&self) -> Result<u64> {
227        // Initialize the accumulator.
228        let mut num_combined_variables = 0u64;
229        // Iterate over the function verifying keys.
230        for (_, (vk, _)) in self.function_verifying_keys() {
231            // Add the number of variables.
232            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        // Return the number of combined variables.
237        Ok(num_combined_variables)
238    }
239
240    /// Returns the sum of the variable counts for all record translations in this deployment.
241    pub fn num_combined_translation_variables(&self) -> Result<u64> {
242        // Initialize the accumulator.
243        let mut num_combined_variables = 0u64;
244        // Iterate over the record verifying keys, if any.
245        if let Some(record_vks) = self.translation_verifying_keys() {
246            for (_, (vk, _)) in record_vks {
247                // Add the number of variables.
248                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        // Return the number of combined variables.
254        Ok(num_combined_variables)
255    }
256
257    /// Returns the sum of the constraint counts in this deployment.
258    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    /// Returns the sum of the constraint counts for all functions in this deployment.
265    pub fn num_combined_function_constraints(&self) -> Result<u64> {
266        // Initialize the accumulator.
267        let mut num_combined_constraints = 0u64;
268        // Iterate over the function verifying keys.
269        for (_, (vk, _)) in self.function_verifying_keys() {
270            // Add the number of constraints.
271            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        // Return the number of combined constraints.
276        Ok(num_combined_constraints)
277    }
278
279    /// Returns the sum of the constraint counts for all record translations in this deployment.
280    pub fn num_combined_translation_constraints(&self) -> Result<u64> {
281        // Initialize the accumulator.
282        let mut num_combined_constraints = 0u64;
283        // Iterate over the record verifying keys, if any.
284        if let Some(record_vks) = self.translation_verifying_keys() {
285            for (_, (vk, _)) in record_vks {
286                // Add the number of constraints.
287                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        // Return the number of combined constraints.
293        Ok(num_combined_constraints)
294    }
295
296    /// Returns the deployment ID.
297    pub fn to_deployment_id(&self) -> Result<Field<N>> {
298        Ok(*Transaction::deployment_tree(self)?.root())
299    }
300}
301
302impl<N: Network> Deployment<N> {
303    /// Sets the edition.
304    pub fn set_edition_raw(&mut self, edition: u16) {
305        self.edition = edition;
306    }
307
308    /// Sets the program checksum.
309    pub fn set_program_checksum_raw(&mut self, program_checksum: Option<[U8<N>; 32]>) {
310        self.program_checksum = program_checksum;
311    }
312
313    /// Sets the program owner.
314    pub fn set_program_owner_raw(&mut self, program_owner: Option<Address<N>>) {
315        self.program_owner = program_owner;
316    }
317
318    /// Removes the verifying key entry with the given name.
319    /// Note: This method is intended to be used by the synthesizer **only**, and should not be called by the user.
320    #[doc(hidden)]
321    pub fn remove_verifying_key(&mut self, name: &Identifier<N>) {
322        self.verifying_keys.retain(|(n, _)| n != name);
323    }
324
325    /// Removes all verifying key entries whose names are in the given set.
326    /// Note: This method is intended to be used by the synthesizer **only**, and should not be called by the user.
327    #[doc(hidden)]
328    pub fn remove_verifying_keys(&mut self, names: &[Identifier<N>]) {
329        self.verifying_keys.retain(|(n, _)| !names.contains(n));
330    }
331
332    /// Returns the implicit deployment version.
333    /// This function implicitly checks that the deployment checksum and owner is well-formed.
334    pub fn version(&self) -> Result<DeploymentVersion> {
335        match (self.program_checksum.is_some(), self.program_owner.is_some()) {
336            // V1: No checksum, no owner.
337            (false, false) => Ok(DeploymentVersion::V1),
338            // V2: Checksum + owner.
339            (true, true) => Ok(DeploymentVersion::V2),
340            // V3: Checksum, no owner (amendment).
341            (true, false) => Ok(DeploymentVersion::V3),
342            // Invalid: Owner without checksum.
343            (false, true) => {
344                bail!("The program owner is present, but the program checksum is absent.")
345            }
346        }
347    }
348}
349
350/// The deployment version.
351#[derive(Copy, Clone, Eq, PartialEq)]
352pub enum DeploymentVersion {
353    /// A deployment without a program checksum or program owner (V1).
354    /// Inactive after consensus version >= V9.
355    V1 = 1,
356    /// A deployment with both a program checksum and program owner (V2).
357    /// Active after consensus version >= V9.
358    V2 = 2,
359    /// A deployment with a program checksum but without a program owner (V3).
360    /// Active after consensus version >= V14. This is used for amendments.
361    V3 = 3,
362}
363
364#[cfg(test)]
365pub mod test_helpers {
366    use super::*;
367    use console::network::MainnetV0;
368    use snarkvm_synthesizer_process::Process;
369
370    use std::sync::OnceLock;
371
372    type CurrentNetwork = MainnetV0;
373    type CurrentAleo = snarkvm_circuit::network::AleoV0;
374
375    pub(crate) fn sample_deployment_v1(edition: u16, rng: &mut TestRng) -> Deployment<CurrentNetwork> {
376        static INSTANCE: OnceLock<Deployment<CurrentNetwork>> = OnceLock::new();
377        let deployment = INSTANCE
378            .get_or_init(|| {
379                // Initialize a new program.
380                let (string, program) = Program::<CurrentNetwork>::parse(
381                    r"
382program testing_three.aleo;
383
384mapping store:
385    key as u32.public;
386    value as u32.public;
387
388function compute:
389    input r0 as u32.private;
390    add r0 r0 into r1;
391    output r1 as u32.public;",
392                )
393                .unwrap();
394                assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
395                // Construct the process.
396                let process = Process::load().unwrap();
397                // Compute the deployment.
398                let deployment = process.deploy::<CurrentAleo, _>(&program, rng).unwrap();
399                // Return the deployment.
400                // Note: This is a testing-only hack to adhere to Rust's dependency cycle rules.
401                Deployment::from_str(&deployment.to_string()).unwrap()
402            })
403            .clone();
404        // Create a new deployment with the desired edition.
405        // Note the only valid editions for V1 deployments are 0 and 1.
406        Deployment::<CurrentNetwork>::new(
407            edition % 2,
408            deployment.program().clone(),
409            deployment.verifying_keys().clone(),
410            None,
411            None,
412        )
413        .unwrap()
414    }
415
416    pub(crate) fn sample_deployment_v2_without_translation_keys(
417        edition: u16,
418        rng: &mut TestRng,
419    ) -> Deployment<CurrentNetwork> {
420        static INSTANCE: OnceLock<Deployment<CurrentNetwork>> = OnceLock::new();
421        let deployment = INSTANCE
422            .get_or_init(|| {
423                // Initialize a new program.
424                let (string, program) = Program::<CurrentNetwork>::parse(
425                    r"
426program testing_four.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                // Construct the process.
440                let process = Process::load().unwrap();
441                // Compute the deployment.
442                let deployment = process.deploy::<CurrentAleo, _>(&program, rng).unwrap();
443                // Return the deployment.
444                // Note: This is a testing-only hack to adhere to Rust's dependency cycle rules.
445                Deployment::from_str(&deployment.to_string()).unwrap()
446            })
447            .clone();
448        // Create a new deployment with the desired edition.
449        Deployment::<CurrentNetwork>::new(
450            edition,
451            deployment.program().clone(),
452            deployment.verifying_keys().clone(),
453            deployment.program_checksum(),
454            Some(Address::rand(rng)),
455        )
456        .unwrap()
457    }
458
459    pub(crate) fn sample_deployment_v2_with_translation_keys(
460        edition: u16,
461        rng: &mut TestRng,
462    ) -> Deployment<CurrentNetwork> {
463        static INSTANCE: OnceLock<Deployment<CurrentNetwork>> = OnceLock::new();
464        let deployment = INSTANCE
465            .get_or_init(|| {
466                // Initialize a new program with records (required for translation VKs).
467                let (string, program) = Program::<CurrentNetwork>::parse(
468                    r"
469program testing_five.aleo;
470
471record data:
472    owner as address.private;
473    one as field.private;
474    two as group.public;
475
476mapping store:
477    key as u32.public;
478    value as u32.public;
479
480function compute:
481    input r0 as u32.private;
482    add r0 r0 into r1;
483    output r1 as u32.public;",
484                )
485                .unwrap();
486                assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
487                // Construct the process.
488                let process = Process::load().unwrap();
489                // Compute the deployment.
490                let deployment = process.deploy::<CurrentAleo, _>(&program, rng).unwrap();
491                // Return the deployment.
492                // Note: This is a testing-only hack to adhere to Rust's dependency cycle rules.
493                Deployment::from_str(&deployment.to_string()).unwrap()
494            })
495            .clone();
496        // Create a new deployment with the desired edition.
497        Deployment::<CurrentNetwork>::new(
498            edition,
499            deployment.program().clone(),
500            deployment.verifying_keys().clone(),
501            deployment.program_checksum(),
502            Some(Address::rand(rng)),
503        )
504        .unwrap()
505    }
506
507    /// Samples a V3 deployment (amendment) for the same program as V2.
508    /// V3 = checksum + no owner.
509    pub(crate) fn sample_deployment_v3(edition: u16, rng: &mut TestRng) -> Deployment<CurrentNetwork> {
510        // Sample a V2 deployment with translation keys, then remove the owner.
511        let mut deployment = sample_deployment_v2_with_translation_keys(edition, rng);
512        deployment.set_program_owner_raw(None);
513        deployment
514    }
515}