Skip to main content

snarkvm_synthesizer_program/finalize/
mod.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 crate::Command;
17
18mod input;
19use input::*;
20
21mod bytes;
22mod parse;
23
24use console::{
25    network::prelude::*,
26    program::{FinalizeType, Identifier, Register},
27};
28
29use indexmap::IndexSet;
30use std::collections::HashMap;
31
32#[derive(Clone, PartialEq, Eq)]
33pub struct FinalizeCore<N: Network> {
34    /// The name of the associated function.
35    name: Identifier<N>,
36    /// The input statements, added in order of the input registers.
37    /// Input assignments are ensured to match the ordering of the input statements.
38    inputs: IndexSet<Input<N>>,
39    /// The commands, in order of execution.
40    commands: Vec<Command<N>>,
41    /// The number of write commands.
42    num_writes: u16,
43    /// A mapping from `Position`s to their index in `commands`.
44    positions: HashMap<Identifier<N>, usize>,
45}
46
47impl<N: Network> FinalizeCore<N> {
48    /// Initializes a new finalize with the given name.
49    pub fn new(name: Identifier<N>) -> Self {
50        Self { name, inputs: IndexSet::new(), commands: Vec::new(), num_writes: 0, positions: HashMap::new() }
51    }
52
53    /// Returns the name of the associated function.
54    pub const fn name(&self) -> &Identifier<N> {
55        &self.name
56    }
57
58    /// Returns the finalize inputs.
59    pub const fn inputs(&self) -> &IndexSet<Input<N>> {
60        &self.inputs
61    }
62
63    /// Returns the finalize input types.
64    pub fn input_types(&self) -> Vec<FinalizeType<N>> {
65        self.inputs.iter().map(|input| input.finalize_type()).cloned().collect()
66    }
67
68    /// Returns the finalize commands.
69    pub fn commands(&self) -> &[Command<N>] {
70        &self.commands
71    }
72
73    /// Returns the number of write commands.
74    pub const fn num_writes(&self) -> u16 {
75        self.num_writes
76    }
77
78    /// Returns the mapping of `Position`s to their index in `commands`.
79    pub const fn positions(&self) -> &HashMap<Identifier<N>, usize> {
80        &self.positions
81    }
82
83    pub fn contains_external_struct(&self) -> bool {
84        self.commands
85            .iter()
86            .any(|command| matches!(command, Command::Instruction(inst) if inst.contains_external_struct()))
87    }
88
89    /// Returns `true` if the finalize scope contains a string type.
90    pub fn contains_string_type(&self) -> bool {
91        self.input_types().iter().any(|input_type| {
92            matches!(input_type, FinalizeType::Plaintext(plaintext_type) if plaintext_type.contains_string_type())
93        }) || self.commands.iter().any(|command| {
94            command.contains_string_type()
95        })
96    }
97
98    /// Returns `true` if the finalize scope contains an array type with a size that exceeds the given maximum.
99    pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
100        self.input_types().iter().any(|input_type| {
101            matches!(input_type, FinalizeType::Plaintext(plaintext_type) if plaintext_type.exceeds_max_array_size(max_array_size))
102        }) || self.commands.iter().any(|command| {
103            command.exceeds_max_array_size(max_array_size)
104        })
105    }
106}
107
108impl<N: Network> FinalizeCore<N> {
109    /// Adds the input statement to finalize.
110    ///
111    /// # Errors
112    /// This method will halt if a command was previously added.
113    /// This method will halt if the maximum number of inputs has been reached.
114    /// This method will halt if the input statement was previously added.
115    #[inline]
116    fn add_input(&mut self, input: Input<N>) -> Result<()> {
117        // Ensure there are no commands in memory.
118        ensure!(self.commands.is_empty(), "Cannot add inputs after commands have been added");
119
120        // Ensure the maximum number of inputs has not been exceeded.
121        ensure!(self.inputs.len() < N::MAX_INPUTS, "Cannot add more than {} inputs", N::MAX_INPUTS);
122        // Ensure the input statement was not previously added.
123        ensure!(!self.inputs.contains(&input), "Cannot add duplicate input statement");
124
125        // Ensure the input register is a locator.
126        ensure!(matches!(input.register(), Register::Locator(..)), "Input register must be a locator");
127
128        // Insert the input statement.
129        self.inputs.insert(input);
130        Ok(())
131    }
132
133    /// Adds the given command to finalize.
134    ///
135    /// # Errors
136    /// This method will halt if the maximum number of commands has been reached.
137    #[inline]
138    pub fn add_command(&mut self, command: Command<N>) -> Result<()> {
139        // Ensure the maximum number of commands has not been exceeded.
140        ensure!(self.commands.len() < N::MAX_COMMANDS, "Cannot add more than {} commands", N::MAX_COMMANDS);
141        // Ensure the number of write commands has not been exceeded.
142        if command.is_write() {
143            ensure!(
144                self.num_writes < N::MAX_WRITES,
145                "Cannot add more than {} 'set' & 'remove' commands",
146                N::MAX_WRITES
147            );
148        }
149
150        // Ensure the command is not an async instruction.
151        ensure!(!command.is_async(), "Forbidden operation: Finalize cannot invoke an 'async' instruction");
152        // Ensure the command is not a call instruction.
153        ensure!(!command.is_call(), "Forbidden operation: Finalize cannot invoke a 'call'");
154        // Ensure the command is not a cast to record instruction.
155        ensure!(!command.is_cast_to_record(), "Forbidden operation: Finalize cannot cast to a record");
156
157        // Check the destination registers.
158        for register in command.destinations() {
159            // Ensure the destination register is a locator.
160            ensure!(matches!(register, Register::Locator(..)), "Destination register must be a locator");
161        }
162
163        // Check if the command is a branch command.
164        if let Some(position) = command.branch_to() {
165            // Ensure the branch target does not reference an earlier position.
166            ensure!(!self.positions.contains_key(position), "Cannot branch to an earlier position '{position}'");
167        }
168
169        // Check if the command is a position command.
170        if let Some(position) = command.position() {
171            // Ensure the position is not yet defined.
172            ensure!(!self.positions.contains_key(position), "Cannot redefine position '{position}'");
173            // Ensure that there are less than `u8::MAX` positions.
174            ensure!(self.positions.len() < N::MAX_POSITIONS, "Cannot add more than {} positions", N::MAX_POSITIONS);
175            // Insert the position.
176            self.positions.insert(*position, self.commands.len());
177        }
178
179        // Check if the command is a write command.
180        if command.is_write() {
181            // Increment the number of write commands.
182            self.num_writes += 1;
183        }
184
185        // Insert the command.
186        self.commands.push(command);
187        Ok(())
188    }
189}
190
191impl<N: Network> TypeName for FinalizeCore<N> {
192    /// Returns the type name as a string.
193    #[inline]
194    fn type_name() -> &'static str {
195        "finalize"
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    use crate::{Command, Finalize};
204
205    type CurrentNetwork = console::network::MainnetV0;
206
207    #[test]
208    fn test_add_input() {
209        // Initialize a new finalize instance.
210        let name = Identifier::from_str("finalize_core_test").unwrap();
211        let mut finalize = Finalize::<CurrentNetwork>::new(name);
212
213        // Ensure that an input can be added.
214        let input = Input::<CurrentNetwork>::from_str("input r0 as field.public;").unwrap();
215        assert!(finalize.add_input(input.clone()).is_ok());
216
217        // Ensure that adding a duplicate input will fail.
218        assert!(finalize.add_input(input).is_err());
219
220        // Ensure that adding more than the maximum number of inputs will fail.
221        for i in 1..CurrentNetwork::MAX_INPUTS * 2 {
222            let input = Input::<CurrentNetwork>::from_str(&format!("input r{i} as field.public;")).unwrap();
223
224            match finalize.inputs.len() < CurrentNetwork::MAX_INPUTS {
225                true => assert!(finalize.add_input(input).is_ok()),
226                false => assert!(finalize.add_input(input).is_err()),
227            }
228        }
229    }
230
231    #[test]
232    fn test_add_command() {
233        // Initialize a new finalize instance.
234        let name = Identifier::from_str("finalize_core_test").unwrap();
235        let mut finalize = Finalize::<CurrentNetwork>::new(name);
236
237        // Ensure that a command can be added.
238        let command = Command::<CurrentNetwork>::from_str("add r0 r1 into r2;").unwrap();
239        assert!(finalize.add_command(command).is_ok());
240
241        // Ensure that adding more than the maximum number of commands will fail.
242        for i in 3..CurrentNetwork::MAX_COMMANDS * 2 {
243            let command = Command::<CurrentNetwork>::from_str(&format!("add r0 r1 into r{i};")).unwrap();
244
245            match finalize.commands.len() < CurrentNetwork::MAX_COMMANDS {
246                true => assert!(finalize.add_command(command).is_ok()),
247                false => assert!(finalize.add_command(command).is_err()),
248            }
249        }
250
251        // Ensure that adding more than the maximum number of writes will fail.
252
253        // Initialize a new finalize instance.
254        let name = Identifier::from_str("finalize_core_test").unwrap();
255        let mut finalize = Finalize::<CurrentNetwork>::new(name);
256
257        for _ in 0..CurrentNetwork::MAX_WRITES * 2 {
258            let command = Command::<CurrentNetwork>::from_str("remove object[r0];").unwrap();
259
260            match finalize.commands.len() < CurrentNetwork::MAX_WRITES as usize {
261                true => assert!(finalize.add_command(command).is_ok()),
262                false => assert!(finalize.add_command(command).is_err()),
263            }
264        }
265    }
266
267    #[test]
268    fn test_add_command_duplicate_positions() {
269        // Initialize a new finalize instance.
270        let name = Identifier::from_str("finalize_core_test").unwrap();
271        let mut finalize = Finalize::<CurrentNetwork>::new(name);
272
273        // Ensure that a command can be added.
274        let command = Command::<CurrentNetwork>::from_str("position start;").unwrap();
275        assert!(finalize.add_command(command.clone()).is_ok());
276
277        // Ensure that adding a duplicate position will fail.
278        assert!(finalize.add_command(command).is_err());
279
280        // Helper method to convert a number to a unique string.
281        #[allow(clippy::cast_possible_truncation)]
282        fn to_unique_string(mut n: usize) -> String {
283            let mut s = String::new();
284            while n > 0 {
285                s.push((b'A' + (n % 26) as u8) as char);
286                n /= 26;
287            }
288            s.chars().rev().collect::<String>()
289        }
290
291        // Ensure that adding more than the maximum number of positions will fail.
292        for i in 1..u8::MAX as usize * 2 {
293            let position = to_unique_string(i);
294            // println!("position: {position}");
295            let command = Command::<CurrentNetwork>::from_str(&format!("position {position};")).unwrap();
296
297            match finalize.commands.len() < u8::MAX as usize {
298                true => assert!(finalize.add_command(command).is_ok()),
299                false => assert!(finalize.add_command(command).is_err()),
300            }
301        }
302    }
303}