Skip to main content

snarkvm_synthesizer_program/closure/
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
16use crate::Instruction;
17
18mod input;
19use input::*;
20
21mod output;
22use output::*;
23
24mod bytes;
25mod parse;
26
27use console::{
28    network::{error, prelude::*},
29    program::{Identifier, Register, RegisterType},
30};
31
32use indexmap::IndexSet;
33
34#[derive(Clone, PartialEq, Eq)]
35pub struct ClosureCore<N: Network> {
36    /// The name of the closure.
37    name: Identifier<N>,
38    /// The input statements, added in order of the input registers.
39    /// Input assignments are ensured to match the ordering of the input statements.
40    inputs: IndexSet<Input<N>>,
41    /// The instructions, in order of execution.
42    instructions: Vec<Instruction<N>>,
43    /// The output statements, in order of the desired output.
44    outputs: IndexSet<Output<N>>,
45}
46
47impl<N: Network> ClosureCore<N> {
48    /// Initializes a new closure with the given name.
49    pub fn new(name: Identifier<N>) -> Self {
50        Self { name, inputs: IndexSet::new(), instructions: Vec::new(), outputs: IndexSet::new() }
51    }
52
53    /// Returns the name of the closure.
54    pub const fn name(&self) -> &Identifier<N> {
55        &self.name
56    }
57
58    /// Returns the checksum of the closure.
59    ///
60    /// The checksum is a 32-byte hash of the closure's source code in string format.
61    /// This ensures a strict definition of closure equivalence, useful for program upgradability.
62    pub fn to_checksum(&self) -> [console::types::U8<N>; 32] {
63        crate::to_checksum::source_code_checksum(&self.to_string())
64    }
65
66    /// Returns the closure inputs.
67    pub const fn inputs(&self) -> &IndexSet<Input<N>> {
68        &self.inputs
69    }
70
71    /// Returns the closure instructions.
72    pub fn instructions(&self) -> &[Instruction<N>] {
73        &self.instructions
74    }
75
76    /// Returns the closure outputs.
77    pub const fn outputs(&self) -> &IndexSet<Output<N>> {
78        &self.outputs
79    }
80
81    /// Returns the closure output types.
82    pub fn output_types(&self) -> Vec<RegisterType<N>> {
83        self.outputs.iter().map(|output| output.register_type()).cloned().collect()
84    }
85
86    /// Returns whether the closure refers to an external struct.
87    pub fn contains_external_struct(&self) -> bool {
88        self.inputs.iter().any(|input| input.register_type().contains_external_struct())
89            || self.outputs.iter().any(|output| output.register_type().contains_external_struct())
90            || self.instructions.iter().any(|instruction| instruction.contains_external_struct())
91    }
92
93    /// Returns `true` if the closure instructions contain a string type.
94    /// Note that input and output types don't have to be checked if we are sure the broader function doesn't contain a string type.
95    pub fn contains_string_type(&self) -> bool {
96        self.instructions.iter().any(|instruction| instruction.contains_string_type())
97    }
98
99    /// Returns `true` if the closure contains an identifier type in its inputs, outputs, or instructions.
100    pub fn contains_identifier_type(&self) -> Result<bool> {
101        for input in &self.inputs {
102            if input.register_type().contains_identifier_type()? {
103                return Ok(true);
104            }
105        }
106        for output in &self.outputs {
107            if output.register_type().contains_identifier_type()? {
108                return Ok(true);
109            }
110        }
111        // Check instruction-level types (e.g., cast destination types).
112        for instruction in &self.instructions {
113            if instruction.contains_identifier_type()? {
114                return Ok(true);
115            }
116        }
117        Ok(false)
118    }
119
120    /// Returns `true` if the closure contains an array type with a size that exceeds the given maximum.
121    pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
122        self.inputs.iter().any(|input| input.register_type().exceeds_max_array_size(max_array_size))
123            || self.outputs.iter().any(|output| output.register_type().exceeds_max_array_size(max_array_size))
124            || self.instructions.iter().any(|instruction| instruction.exceeds_max_array_size(max_array_size))
125    }
126}
127
128impl<N: Network> ClosureCore<N> {
129    /// Adds the input statement to the closure.
130    ///
131    /// # Errors
132    /// This method will halt if there are instructions or output statements already.
133    /// This method will halt if the maximum number of inputs has been reached.
134    /// This method will halt if the input statement was previously added.
135    #[inline]
136    fn add_input(&mut self, input: Input<N>) -> Result<()> {
137        // Ensure there are no instructions or output statements in memory.
138        ensure!(self.instructions.is_empty(), "Cannot add inputs after instructions have been added");
139        ensure!(self.outputs.is_empty(), "Cannot add inputs after outputs have been added");
140
141        // Ensure the maximum number of inputs has not been exceeded.
142        ensure!(self.inputs.len() < N::MAX_INPUTS, "Cannot add more than {} inputs", N::MAX_INPUTS);
143        // Ensure the input statement was not previously added.
144        ensure!(!self.inputs.contains(&input), "Cannot add duplicate input statement");
145
146        // Ensure the input register is a locator.
147        ensure!(matches!(input.register(), Register::Locator(..)), "Input register must be a locator");
148
149        // Insert the input statement.
150        self.inputs.insert(input);
151        Ok(())
152    }
153
154    /// Adds the given instruction to the closure.
155    ///
156    /// # Errors
157    /// This method will halt if there are output statements already.
158    /// This method will halt if the maximum number of instructions has been reached.
159    #[inline]
160    pub fn add_instruction(&mut self, instruction: Instruction<N>) -> Result<()> {
161        // Ensure that there are no outputs in memory.
162        ensure!(self.outputs.is_empty(), "Cannot add instructions after outputs have been added");
163
164        // Ensure the maximum number of instructions has not been exceeded.
165        ensure!(
166            self.instructions.len() < N::MAX_INSTRUCTIONS,
167            "Cannot add more than {} instructions",
168            N::MAX_INSTRUCTIONS
169        );
170
171        // Ensure the destination register is a locator.
172        for register in instruction.destinations() {
173            ensure!(matches!(register, Register::Locator(..)), "Destination register must be a locator");
174        }
175
176        // Insert the instruction.
177        self.instructions.push(instruction);
178        Ok(())
179    }
180
181    /// Adds the output statement to the closure.
182    ///
183    /// # Errors
184    /// This method will halt if the maximum number of outputs has been reached.
185    #[inline]
186    fn add_output(&mut self, output: Output<N>) -> Result<()> {
187        // Ensure the maximum number of outputs has not been exceeded.
188        ensure!(self.outputs.len() < N::MAX_OUTPUTS, "Cannot add more than {} outputs", N::MAX_OUTPUTS);
189
190        // Ensure the closure output register is not a static record.
191        // ExternalRecord and DynamicRecord are checked at V15+ deployment time (see `VM::check_transaction`).
192        ensure!(!matches!(output.register_type(), RegisterType::Record(..)), "Closure outputs do not support records");
193
194        // Insert the output statement.
195        self.outputs.insert(output);
196        Ok(())
197    }
198}
199
200impl<N: Network> TypeName for ClosureCore<N> {
201    /// Returns the type name as a string.
202    #[inline]
203    fn type_name() -> &'static str {
204        "closure"
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    use crate::{Closure, Instruction};
213
214    type CurrentNetwork = console::network::MainnetV0;
215
216    #[test]
217    fn test_add_input() {
218        // Initialize a new closure instance.
219        let name = Identifier::from_str("closure_core_test").unwrap();
220        let mut closure = Closure::<CurrentNetwork>::new(name);
221
222        // Ensure that an input can be added.
223        let input = Input::<CurrentNetwork>::from_str("input r0 as field;").unwrap();
224        assert!(closure.add_input(input.clone()).is_ok());
225
226        // Ensure that adding a duplicate input will fail.
227        assert!(closure.add_input(input).is_err());
228
229        // Ensure that adding more than the maximum number of inputs will fail.
230        for i in 1..CurrentNetwork::MAX_INPUTS * 2 {
231            let input = Input::<CurrentNetwork>::from_str(&format!("input r{i} as field;")).unwrap();
232
233            match closure.inputs.len() < CurrentNetwork::MAX_INPUTS {
234                true => assert!(closure.add_input(input).is_ok()),
235                false => assert!(closure.add_input(input).is_err()),
236            }
237        }
238    }
239
240    #[test]
241    fn test_add_instruction() {
242        // Initialize a new closure instance.
243        let name = Identifier::from_str("closure_core_test").unwrap();
244        let mut closure = Closure::<CurrentNetwork>::new(name);
245
246        // Ensure that an instruction can be added.
247        let instruction = Instruction::<CurrentNetwork>::from_str("add r0 r1 into r2;").unwrap();
248        assert!(closure.add_instruction(instruction).is_ok());
249
250        // Ensure that adding more than the maximum number of instructions will fail.
251        for i in 3..CurrentNetwork::MAX_INSTRUCTIONS * 2 {
252            let instruction = Instruction::<CurrentNetwork>::from_str(&format!("add r0 r1 into r{i};")).unwrap();
253
254            match closure.instructions.len() < CurrentNetwork::MAX_INSTRUCTIONS {
255                true => assert!(closure.add_instruction(instruction).is_ok()),
256                false => assert!(closure.add_instruction(instruction).is_err()),
257            }
258        }
259    }
260
261    #[test]
262    fn test_add_output() {
263        // Initialize a new closure instance.
264        let name = Identifier::from_str("closure_core_test").unwrap();
265        let mut closure = Closure::<CurrentNetwork>::new(name);
266
267        // Ensure that an output can be added.
268        let output = Output::<CurrentNetwork>::from_str("output r0 as field;").unwrap();
269        assert!(closure.add_output(output).is_ok());
270
271        // Ensure that adding more than the maximum number of outputs will fail.
272        for i in 1..CurrentNetwork::MAX_OUTPUTS * 2 {
273            let output = Output::<CurrentNetwork>::from_str(&format!("output r{i} as field;")).unwrap();
274
275            match closure.outputs.len() < CurrentNetwork::MAX_OUTPUTS {
276                true => assert!(closure.add_output(output).is_ok()),
277                false => assert!(closure.add_output(output).is_err()),
278            }
279        }
280    }
281
282    #[test]
283    fn test_add_output_record_types_rejected() {
284        let name = Identifier::from_str("closure_core_test").unwrap();
285
286        // DynamicRecord output is allowed at parse time (gated at V15+ deployment time).
287        let mut closure = Closure::<CurrentNetwork>::new(name);
288        let output = Output::<CurrentNetwork>::from_str("output r0 as dynamic.record;").unwrap();
289        assert!(closure.add_output(output).is_ok());
290
291        // ExternalRecord output is allowed at parse time (gated at V15+ deployment time).
292        let mut closure = Closure::<CurrentNetwork>::new(name);
293        let output = Output::<CurrentNetwork>::from_str("output r0 as test_prog.aleo/token.record;").unwrap();
294        assert!(closure.add_output(output).is_ok());
295
296        // Static Record output is rejected at parse time.
297        let mut closure = Closure::<CurrentNetwork>::new(name);
298        let output = Output::<CurrentNetwork>::from_str("output r0 as token.record;").unwrap();
299        let result = closure.add_output(output);
300        assert!(result.is_err());
301        assert!(result.unwrap_err().to_string().contains("Closure outputs do not support records"));
302    }
303}