Skip to main content

snarkvm_synthesizer_process/stack/helpers/
check_upgrade.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
16use super::*;
17
18impl<N: Network> Stack<N> {
19    /// Checks that the new program is a valid upgrade.
20    /// At a high-level, an upgrade must preserve the existing interfaces of the original program.
21    /// An upgrade may add new components, except for constructors, and modify logic **only** in functions and finalize scopes.
22    /// An upgrade may also be exactly the same as the original program.
23    ///
24    /// The order of the components in the new program may be modified, as long as the interfaces remain the same.
25    ///
26    /// An detailed overview of what an upgrade can and cannot do is given below:
27    ///  | Program Component | Delete |    Modify    |  Add  |
28    ///  |-------------------|--------|--------------|-------|
29    ///  | import            |   ❌   |      ❌      |  ✅   |
30    ///  | constructor       |   ❌   |      ❌      |  ❌   |
31    ///  | mapping           |   ❌   |      ❌      |  ✅   |
32    ///  | struct            |   ❌   |      ❌      |  ✅   |
33    ///  | record            |   ❌   |      ❌      |  ✅   |
34    ///  | closure           |   ❌   |      ❌      |  ✅   |
35    ///  | function          |   ❌   | ✅ (logic)   |  ✅   |
36    ///  | finalize          |   ❌   | ✅ (logic)   |  ✅   |
37    ///  | view              |   ❌   | ✅ (logic)   |  ✅   |
38    ///  |-------------------|--------|--------------|-------|
39    ///
40    /// There is one important caveat in that output register indices **MUST** remain the same.
41    /// For example, changing `output r10 as <NAME>.record` into `output r11 as <NAME>.record` would not be a valid upgrade.
42    /// This restriction is necessary because the output register index is instantiated as a constant in the caller circuit.
43    /// This check is enforced in `check_transaction` in `synthesizer/src/vm/verify.rs`.
44    #[inline]
45    pub fn check_upgrade_is_valid(old_program: &Program<N>, new_program: &Program<N>) -> Result<()> {
46        // Get the new program ID.
47        let program_id = new_program.id();
48        // Ensure the program is not `credits.aleo`.
49        ensure!(program_id != &ProgramID::from_str("credits.aleo")?, "Cannot upgrade 'credits.aleo'");
50        // Ensure the program ID matches.
51        ensure!(old_program.id() == new_program.id(), "Cannot upgrade '{program_id}' with different program ID");
52        // Ensure that all of the imports in the old program exist in the new program.
53        for old_import in old_program.imports().keys() {
54            if !new_program.contains_import(old_import) {
55                bail!("Cannot upgrade '{program_id}' because it is missing the original import '{old_import}'");
56            }
57        }
58        // Ensure that the constructors in both programs are exactly the same.
59        // Note: Programs without constructors are not allowed to be upgraded.
60        match (old_program.constructor(), new_program.constructor()) {
61            (_, None) => bail!("A program cannot be upgraded to a program without a constructor"),
62            (None, _) => bail!("A program without a constructor cannot be upgraded"),
63            (Some(old_constructor), Some(new_constructor)) => {
64                ensure!(
65                    old_constructor == new_constructor,
66                    "Cannot upgrade '{program_id}' because the constructor does not match"
67                );
68            }
69        }
70        // Ensure that all of the mappings in the old program exist in the new program.
71        for (old_mapping_id, old_mapping_type) in old_program.mappings() {
72            let new_mapping_type = new_program.get_mapping(old_mapping_id)?;
73            ensure!(
74                *old_mapping_type == new_mapping_type,
75                "Cannot upgrade '{program_id}' because the mapping '{old_mapping_id}' does not match"
76            );
77        }
78        // Ensure that all of the structs in the old program exist in the new program.
79        for (old_struct_id, old_struct_type) in old_program.structs() {
80            let new_struct_type = new_program.get_struct(old_struct_id)?;
81            ensure!(
82                old_struct_type == new_struct_type,
83                "Cannot upgrade '{program_id}' because the struct '{old_struct_id}' does not match"
84            );
85        }
86        // Ensure that all of the records in the old program exist in the new program.
87        for (old_record_id, old_record_type) in old_program.records() {
88            let new_record_type = new_program.get_record(old_record_id)?;
89            ensure!(
90                old_record_type == new_record_type,
91                "Cannot upgrade '{program_id}' because the record '{old_record_id}' does not match"
92            );
93        }
94        // Ensure that the old program closures exist in the new program, with the exact same definition.
95        for old_closure in old_program.closures().values() {
96            let old_closure_name = old_closure.name();
97            let new_closure = new_program.get_closure(old_closure_name)?;
98            ensure!(
99                old_closure == &new_closure,
100                "Cannot upgrade '{program_id}' because the closure '{old_closure_name}' does not match"
101            );
102        }
103        // Ensure that the old program functions exist in the new program, with the same input and output types.
104        // If the function has an associated `finalize` block, then ensure that the finalize block exists in the new program.
105        for old_function in old_program.functions().values() {
106            let old_function_name = old_function.name();
107            let new_function = new_program.get_function_ref(old_function_name)?;
108            ensure!(
109                old_function.input_types() == new_function.input_types(),
110                "Cannot upgrade '{program_id}' because the input types to the function '{old_function_name}' do not match"
111            );
112            ensure!(
113                old_function.output_types() == new_function.output_types(),
114                "Cannot upgrade '{program_id}' because the output types of the function '{old_function_name}' do not match"
115            );
116            match (old_function.finalize_logic(), new_function.finalize_logic()) {
117                (None, None) => {} // Do nothing
118                (None, Some(_)) => bail!(
119                    "Cannot upgrade '{program_id}' because the function '{old_function_name}' should not have a finalize block"
120                ),
121                (Some(_), None) => bail!(
122                    "Cannot upgrade '{program_id}' because the function '{old_function_name}' should have a finalize block"
123                ),
124                (Some(old_finalize), Some(new_finalize)) => {
125                    ensure!(
126                        old_finalize.input_types() == new_finalize.input_types(),
127                        "Cannot upgrade '{program_id}' because the finalize inputs to the function '{old_function_name}' do not match"
128                    );
129                }
130            }
131        }
132        // Ensure that each old view exists in the new program with the same interface. View
133        // bodies are mutable (same policy as function/finalize logic) — only the externally
134        // visible input and output types must remain stable for downstream callers.
135        for old_view in old_program.views().values() {
136            let old_view_name = old_view.name();
137            let new_view = new_program.get_view_ref(old_view_name)?;
138            ensure!(
139                old_view.input_types() == new_view.input_types(),
140                "Cannot upgrade '{program_id}' because the input types of the view '{old_view_name}' do not match"
141            );
142            ensure!(
143                old_view.output_types() == new_view.output_types(),
144                "Cannot upgrade '{program_id}' because the output types of the view '{old_view_name}' do not match"
145            );
146        }
147        Ok(())
148    }
149}