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}