snarkvm_ledger_block/transaction/deployment/
mod.rs

1// Copyright (c) 2019-2025 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 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.
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 && self.verifying_keys == other.verifying_keys && self.program == other.program
54    }
55}
56
57impl<N: Network> Eq for Deployment<N> {}
58
59impl<N: Network> Deployment<N> {
60    /// Initializes a new deployment.
61    pub fn new(
62        edition: u16,
63        program: Program<N>,
64        verifying_keys: Vec<(Identifier<N>, (VerifyingKey<N>, Certificate<N>))>,
65        program_checksum: Option<[U8<N>; 32]>,
66        program_owner: Option<Address<N>>,
67    ) -> Result<Self> {
68        // Construct the deployment.
69        let deployment = Self { edition, program, verifying_keys, program_checksum, program_owner };
70        // Ensure the deployment is ordered.
71        deployment.check_is_ordered()?;
72        // Return the deployment.
73        Ok(deployment)
74    }
75
76    /// Checks that the deployment is ordered.
77    pub fn check_is_ordered(&self) -> Result<()> {
78        let program_id = self.program.id();
79
80        // Ensure that either the both the program checksum and owner are present, or both are absent.
81        // The call to `Deployment::version` implicitly performs this check.
82        self.version()?;
83        // Validate the deployment based on the program checksum.
84        if let Some(program_checksum) = self.program_checksum {
85            ensure!(
86                program_checksum == self.program.to_checksum(),
87                "The program checksum in the deployment does not match the computed checksum for '{program_id}'"
88            );
89        }
90        // Ensure the program contains functions.
91        ensure!(
92            !self.program.functions().is_empty(),
93            "No functions present in the deployment for program '{program_id}'"
94        );
95        // Ensure the deployment contains verifying keys.
96        ensure!(
97            !self.verifying_keys.is_empty(),
98            "No verifying keys present in the deployment for program '{program_id}'"
99        );
100
101        // Ensure the number of functions matches the number of verifying keys.
102        if self.program.functions().len() != self.verifying_keys.len() {
103            bail!("Deployment has an incorrect number of verifying keys, according to the program.");
104        }
105
106        // Ensure the number of functions does not exceed the maximum.
107        ensure!(
108            self.program.functions().len() <= N::MAX_FUNCTIONS,
109            "Deployment has too many functions (maximum is '{}')",
110            N::MAX_FUNCTIONS
111        );
112
113        // Ensure the function and verifying keys correspond.
114        for ((function_name, function), (name, _)) in self.program.functions().iter().zip_eq(&self.verifying_keys) {
115            // Ensure the function name is correct.
116            if function_name != function.name() {
117                bail!("The function key is '{function_name}', but the function name is '{}'", function.name())
118            }
119            // Ensure the function name with the verifying key is correct.
120            if name != function.name() {
121                bail!("The verifier key is '{name}', but the function name is '{}'", function.name())
122            }
123        }
124
125        ensure!(
126            !has_duplicates(self.verifying_keys.iter().map(|(name, ..)| name)),
127            "A duplicate function name was found"
128        );
129
130        Ok(())
131    }
132
133    /// Returns the size in bytes.
134    pub fn size_in_bytes(&self) -> Result<u64> {
135        Ok(u64::try_from(self.to_bytes_le()?.len())?)
136    }
137
138    /// Returns the number of program functions in the deployment.
139    pub fn num_functions(&self) -> usize {
140        self.program.functions().len()
141    }
142
143    /// Returns the edition.
144    pub const fn edition(&self) -> u16 {
145        self.edition
146    }
147
148    /// Returns the program.
149    pub const fn program(&self) -> &Program<N> {
150        &self.program
151    }
152
153    /// Returns the program checksum, if it was stored.
154    pub const fn program_checksum(&self) -> Option<[U8<N>; 32]> {
155        self.program_checksum
156    }
157
158    /// Returns the program owner, if it was stored.
159    pub const fn program_owner(&self) -> Option<Address<N>> {
160        self.program_owner
161    }
162
163    /// Returns the program.
164    pub const fn program_id(&self) -> &ProgramID<N> {
165        self.program.id()
166    }
167
168    /// Returns the verifying keys.
169    pub const fn verifying_keys(&self) -> &Vec<(Identifier<N>, (VerifyingKey<N>, Certificate<N>))> {
170        &self.verifying_keys
171    }
172
173    /// Returns the sum of the variable counts for all functions in this deployment.
174    pub fn num_combined_variables(&self) -> Result<u64> {
175        // Initialize the accumulator.
176        let mut num_combined_variables = 0u64;
177        // Iterate over the functions.
178        for (_, (vk, _)) in &self.verifying_keys {
179            // Add the number of variables.
180            // Note: This method must be *checked* because the claimed variable count
181            // is from the user, not the synthesizer.
182            num_combined_variables = num_combined_variables
183                .checked_add(vk.num_variables())
184                .ok_or_else(|| anyhow!("Overflow when counting variables for '{}'", self.program_id()))?;
185        }
186        // Return the number of combined variables.
187        Ok(num_combined_variables)
188    }
189
190    /// Returns the sum of the constraint counts for all functions in this deployment.
191    pub fn num_combined_constraints(&self) -> Result<u64> {
192        // Initialize the accumulator.
193        let mut num_combined_constraints = 0u64;
194        // Iterate over the functions.
195        for (_, (vk, _)) in &self.verifying_keys {
196            // Add the number of constraints.
197            // Note: This method must be *checked* because the claimed constraint count
198            // is from the user, not the synthesizer.
199            num_combined_constraints = num_combined_constraints
200                .checked_add(vk.circuit_info.num_constraints as u64)
201                .ok_or_else(|| anyhow!("Overflow when counting constraints for '{}'", self.program_id()))?;
202        }
203        // Return the number of combined constraints.
204        Ok(num_combined_constraints)
205    }
206
207    /// Returns the deployment ID.
208    pub fn to_deployment_id(&self) -> Result<Field<N>> {
209        Ok(*Transaction::deployment_tree(self)?.root())
210    }
211}
212
213impl<N: Network> Deployment<N> {
214    /// Sets the program checksum.
215    /// Note: This method is intended to be used by the synthesizer **only**, and should not be called by the user.
216    #[doc(hidden)]
217    pub fn set_program_checksum_raw(&mut self, program_checksum: Option<[U8<N>; 32]>) {
218        self.program_checksum = program_checksum;
219    }
220
221    /// Sets the program owner.
222    /// Note: This method is intended to be used by the synthesizer **only**, and should not be called by the user.
223    #[doc(hidden)]
224    pub fn set_program_owner_raw(&mut self, program_owner: Option<Address<N>>) {
225        self.program_owner = program_owner;
226    }
227
228    /// An internal function to return the implicit deployment version.
229    /// This function implicitly checks that the deployment checksum and owner is well-formed.
230    #[doc(hidden)]
231    pub(super) fn version(&self) -> Result<DeploymentVersion> {
232        match (self.program_checksum.is_some(), self.program_owner.is_some()) {
233            (false, false) => Ok(DeploymentVersion::V1),
234            (true, true) => Ok(DeploymentVersion::V2),
235            (true, false) => {
236                bail!("The program checksum is present, but the program owner is absent.")
237            }
238            (false, true) => {
239                bail!("The program owner is present, but the program checksum is absent.")
240            }
241        }
242    }
243}
244
245// An internal enum to represent the deployment version.
246#[derive(Copy, Clone, Eq, PartialEq)]
247pub(super) enum DeploymentVersion {
248    V1 = 1,
249    V2 = 2,
250}
251
252#[cfg(test)]
253pub mod test_helpers {
254    use super::*;
255    use console::network::MainnetV0;
256    use snarkvm_synthesizer_process::Process;
257
258    use std::sync::OnceLock;
259
260    type CurrentNetwork = MainnetV0;
261    type CurrentAleo = snarkvm_circuit::network::AleoV0;
262
263    pub(crate) fn sample_deployment_v1(edition: u16, rng: &mut TestRng) -> Deployment<CurrentNetwork> {
264        static INSTANCE: OnceLock<Deployment<CurrentNetwork>> = OnceLock::new();
265        let deployment = INSTANCE
266            .get_or_init(|| {
267                // Initialize a new program.
268                let (string, program) = Program::<CurrentNetwork>::parse(
269                    r"
270program testing_three.aleo;
271
272mapping store:
273    key as u32.public;
274    value as u32.public;
275
276function compute:
277    input r0 as u32.private;
278    add r0 r0 into r1;
279    output r1 as u32.public;",
280                )
281                .unwrap();
282                assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
283                // Construct the process.
284                let process = Process::load().unwrap();
285                // Compute the deployment.
286                let mut deployment = process.deploy::<CurrentAleo, _>(&program, rng).unwrap();
287                // Unset the checksum.
288                deployment.set_program_checksum_raw(None);
289                // Unset the owner.
290                deployment.set_program_owner_raw(None);
291                // Return the deployment.
292                // Note: This is a testing-only hack to adhere to Rust's dependency cycle rules.
293                Deployment::from_str(&deployment.to_string()).unwrap()
294            })
295            .clone();
296        // Create a new deployment with the desired edition.
297        // Note the only valid editions for V1 deployments are 0 and 1.
298        Deployment::<CurrentNetwork>::new(
299            edition % 2,
300            deployment.program().clone(),
301            deployment.verifying_keys().clone(),
302            deployment.program_checksum(),
303            deployment.program_owner(),
304        )
305        .unwrap()
306    }
307
308    pub(crate) fn sample_deployment_v2(edition: u16, rng: &mut TestRng) -> Deployment<CurrentNetwork> {
309        static INSTANCE: OnceLock<Deployment<CurrentNetwork>> = OnceLock::new();
310        let deployment = INSTANCE
311            .get_or_init(|| {
312                // Initialize a new program.
313                let (string, program) = Program::<CurrentNetwork>::parse(
314                    r"
315program testing_four.aleo;
316
317mapping store:
318    key as u32.public;
319    value as u32.public;
320
321function compute:
322    input r0 as u32.private;
323    add r0 r0 into r1;
324    output r1 as u32.public;",
325                )
326                .unwrap();
327                assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
328                // Construct the process.
329                let process = Process::load().unwrap();
330                // Compute the deployment.
331                let mut deployment = process.deploy::<CurrentAleo, _>(&program, rng).unwrap();
332                // Set the program checksum.
333                deployment.set_program_checksum_raw(Some(deployment.program().to_checksum()));
334                // Set the program owner.
335                deployment.set_program_owner_raw(Some(Address::rand(rng)));
336                // Return the deployment.
337                // Note: This is a testing-only hack to adhere to Rust's dependency cycle rules.
338                Deployment::from_str(&deployment.to_string()).unwrap()
339            })
340            .clone();
341        // Create a new deployment with the desired edition.
342        Deployment::<CurrentNetwork>::new(
343            edition,
344            deployment.program().clone(),
345            deployment.verifying_keys().clone(),
346            deployment.program_checksum(),
347            deployment.program_owner(),
348        )
349        .unwrap()
350    }
351}