Skip to main content

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