snarkvm_synthesizer_process/stack/helpers/check_upgrade.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
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 /// |-------------------|--------|--------------|-------|
38 ///
39 #[inline]
40 pub fn check_upgrade_is_valid(old_program: &Program<N>, new_program: &Program<N>) -> Result<()> {
41 // Get the new program ID.
42 let program_id = new_program.id();
43 // Ensure the program is not `credits.aleo`.
44 ensure!(program_id != &ProgramID::from_str("credits.aleo")?, "Cannot upgrade 'credits.aleo'");
45 // Ensure the program ID matches.
46 ensure!(old_program.id() == new_program.id(), "Cannot upgrade '{program_id}' with different program ID");
47 // Ensure that all of the imports in the old program exist in the new program.
48 for old_import in old_program.imports().keys() {
49 if !new_program.contains_import(old_import) {
50 bail!("Cannot upgrade '{program_id}' because it is missing the original import '{old_import}'");
51 }
52 }
53 // Ensure that the constructors in both programs are exactly the same.
54 // Note: Programs without constructors are not allowed to be upgraded.
55 match (old_program.constructor(), new_program.constructor()) {
56 (_, None) => bail!("A program cannot be upgraded to a program without a constructor"),
57 (None, _) => bail!("A program without a constructor cannot be upgraded"),
58 (Some(old_constructor), Some(new_constructor)) => {
59 ensure!(
60 old_constructor == new_constructor,
61 "Cannot upgrade '{program_id}' because the constructor does not match"
62 );
63 }
64 }
65 // Ensure that all of the mappings in the old program exist in the new program.
66 for (old_mapping_id, old_mapping_type) in old_program.mappings() {
67 let new_mapping_type = new_program.get_mapping(old_mapping_id)?;
68 ensure!(
69 *old_mapping_type == new_mapping_type,
70 "Cannot upgrade '{program_id}' because the mapping '{old_mapping_id}' does not match"
71 );
72 }
73 // Ensure that all of the structs in the old program exist in the new program.
74 for (old_struct_id, old_struct_type) in old_program.structs() {
75 let new_struct_type = new_program.get_struct(old_struct_id)?;
76 ensure!(
77 old_struct_type == new_struct_type,
78 "Cannot upgrade '{program_id}' because the struct '{old_struct_id}' does not match"
79 );
80 }
81 // Ensure that all of the records in the old program exist in the new program.
82 for (old_record_id, old_record_type) in old_program.records() {
83 let new_record_type = new_program.get_record(old_record_id)?;
84 ensure!(
85 old_record_type == new_record_type,
86 "Cannot upgrade '{program_id}' because the record '{old_record_id}' does not match"
87 );
88 }
89 // Ensure that the old program closures exist in the new program, with the exact same definition.
90 for old_closure in old_program.closures().values() {
91 let old_closure_name = old_closure.name();
92 let new_closure = new_program.get_closure(old_closure_name)?;
93 ensure!(
94 old_closure == &new_closure,
95 "Cannot upgrade '{program_id}' because the closure '{old_closure_name}' does not match"
96 );
97 }
98 // Ensure that the old program functions exist in the new program, with the same input and output types.
99 // If the function has an associated `finalize` block, then ensure that the finalize block exists in the new program.
100 for old_function in old_program.functions().values() {
101 let old_function_name = old_function.name();
102 let new_function = new_program.get_function_ref(old_function_name)?;
103 ensure!(
104 old_function.input_types() == new_function.input_types(),
105 "Cannot upgrade '{program_id}' because the input types to the function '{old_function_name}' do not match"
106 );
107 ensure!(
108 old_function.output_types() == new_function.output_types(),
109 "Cannot upgrade '{program_id}' because the output types of the function '{old_function_name}' do not match"
110 );
111 match (old_function.finalize_logic(), new_function.finalize_logic()) {
112 (None, None) => {} // Do nothing
113 (None, Some(_)) => bail!(
114 "Cannot upgrade '{program_id}' because the function '{old_function_name}' should not have a finalize block"
115 ),
116 (Some(_), None) => bail!(
117 "Cannot upgrade '{program_id}' because the function '{old_function_name}' should have a finalize block"
118 ),
119 (Some(old_finalize), Some(new_finalize)) => {
120 ensure!(
121 old_finalize.input_types() == new_finalize.input_types(),
122 "Cannot upgrade '{program_id}' because the finalize inputs to the function '{old_function_name}' do not match"
123 );
124 }
125 }
126 }
127 Ok(())
128 }
129}