Skip to main content

snarkvm_synthesizer_program/finalize/
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::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 identifier type in its inputs or commands.
99    pub fn contains_identifier_type(&self) -> Result<bool> {
100        for input_type in self.input_types() {
101            if let FinalizeType::Plaintext(plaintext_type) = input_type {
102                if plaintext_type.contains_identifier_type()? {
103                    return Ok(true);
104                }
105            }
106        }
107        // Check commands for identifier types in cast destinations.
108        for command in &self.commands {
109            if command.contains_identifier_type()? {
110                return Ok(true);
111            }
112        }
113        Ok(false)
114    }
115
116    /// Returns `true` if the finalize scope contains an array type with a size that exceeds the given maximum.
117    pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
118        self.input_types().iter().any(|input_type| {
119            matches!(input_type, FinalizeType::Plaintext(plaintext_type) if plaintext_type.exceeds_max_array_size(max_array_size))
120        }) || self.commands.iter().any(|command| {
121            command.exceeds_max_array_size(max_array_size)
122        })
123    }
124}
125
126impl<N: Network> FinalizeCore<N> {
127    /// Adds the input statement to finalize.
128    ///
129    /// # Errors
130    /// This method will halt if a command was previously added.
131    /// This method will halt if the maximum number of inputs has been reached.
132    /// This method will halt if the input statement was previously added.
133    #[inline]
134    fn add_input(&mut self, input: Input<N>) -> Result<()> {
135        // Ensure there are no commands in memory.
136        ensure!(self.commands.is_empty(), "Cannot add inputs after commands have been added");
137
138        // Ensure the maximum number of inputs has not been exceeded.
139        ensure!(self.inputs.len() < N::MAX_INPUTS, "Cannot add more than {} inputs", N::MAX_INPUTS);
140        // Ensure the input statement was not previously added.
141        ensure!(!self.inputs.contains(&input), "Cannot add duplicate input statement");
142
143        // Ensure the input register is a locator.
144        ensure!(matches!(input.register(), Register::Locator(..)), "Input register must be a locator");
145
146        // Insert the input statement.
147        self.inputs.insert(input);
148        Ok(())
149    }
150
151    /// Adds the given command to finalize.
152    ///
153    /// # Errors
154    /// This method will halt if the maximum number of commands has been reached.
155    #[inline]
156    pub fn add_command(&mut self, command: Command<N>) -> Result<()> {
157        // Ensure the maximum number of commands has not been exceeded.
158        ensure!(self.commands.len() < N::MAX_COMMANDS, "Cannot add more than {} commands", N::MAX_COMMANDS);
159        // Ensure the number of write commands has not been exceeded.
160        if command.is_write() {
161            ensure!(
162                self.num_writes < N::LATEST_MAX_WRITES(),
163                "Cannot add more than {} 'set' & 'remove' commands",
164                N::LATEST_MAX_WRITES()
165            );
166        }
167
168        // Ensure the command is not an async instruction.
169        ensure!(!command.is_async(), "Forbidden operation: Finalize cannot invoke an 'async' instruction");
170        // Ensure the command is not a call instruction.
171        ensure!(!command.is_call(), "Forbidden operation: Finalize cannot invoke a 'call'");
172        // Ensure the command is not a cast to record instruction.
173        ensure!(!command.is_cast_to_record(), "Forbidden operation: Finalize cannot cast to a record");
174
175        // Check the destination registers.
176        for register in command.destinations() {
177            // Ensure the destination register is a locator.
178            ensure!(matches!(register, Register::Locator(..)), "Destination register must be a locator");
179        }
180
181        // Check if the command is a branch command.
182        if let Some(position) = command.branch_to() {
183            // Ensure the branch target does not reference an earlier position.
184            ensure!(!self.positions.contains_key(position), "Cannot branch to an earlier position '{position}'");
185        }
186
187        // Check if the command is a position command.
188        if let Some(position) = command.position() {
189            // Ensure the position is not yet defined.
190            ensure!(!self.positions.contains_key(position), "Cannot redefine position '{position}'");
191            // Ensure that there are less than `u8::MAX` positions.
192            ensure!(self.positions.len() < N::MAX_POSITIONS, "Cannot add more than {} positions", N::MAX_POSITIONS);
193            // Insert the position.
194            self.positions.insert(*position, self.commands.len());
195        }
196
197        // Check if the command is a write command.
198        if command.is_write() {
199            // Increment the number of write commands.
200            self.num_writes += 1;
201        }
202
203        // Insert the command.
204        self.commands.push(command);
205        Ok(())
206    }
207}
208
209impl<N: Network> TypeName for FinalizeCore<N> {
210    /// Returns the type name as a string.
211    #[inline]
212    fn type_name() -> &'static str {
213        "finalize"
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    use crate::{Command, Finalize};
222
223    type CurrentNetwork = console::network::MainnetV0;
224
225    #[test]
226    fn test_add_input() {
227        // Initialize a new finalize instance.
228        let name = Identifier::from_str("finalize_core_test").unwrap();
229        let mut finalize = Finalize::<CurrentNetwork>::new(name);
230
231        // Ensure that an input can be added.
232        let input = Input::<CurrentNetwork>::from_str("input r0 as field.public;").unwrap();
233        assert!(finalize.add_input(input.clone()).is_ok());
234
235        // Ensure that adding a duplicate input will fail.
236        assert!(finalize.add_input(input).is_err());
237
238        // Ensure that adding more than the maximum number of inputs will fail.
239        for i in 1..CurrentNetwork::MAX_INPUTS * 2 {
240            let input = Input::<CurrentNetwork>::from_str(&format!("input r{i} as field.public;")).unwrap();
241
242            match finalize.inputs.len() < CurrentNetwork::MAX_INPUTS {
243                true => assert!(finalize.add_input(input).is_ok()),
244                false => assert!(finalize.add_input(input).is_err()),
245            }
246        }
247    }
248
249    #[test]
250    fn test_add_command() {
251        // Initialize a new finalize instance.
252        let name = Identifier::from_str("finalize_core_test").unwrap();
253        let mut finalize = Finalize::<CurrentNetwork>::new(name);
254
255        // Ensure that a command can be added.
256        let command = Command::<CurrentNetwork>::from_str("add r0 r1 into r2;").unwrap();
257        assert!(finalize.add_command(command).is_ok());
258
259        // Ensure that adding more than the maximum number of commands will fail.
260        for i in 3..CurrentNetwork::MAX_COMMANDS * 2 {
261            let command = Command::<CurrentNetwork>::from_str(&format!("add r0 r1 into r{i};")).unwrap();
262
263            match finalize.commands.len() < CurrentNetwork::MAX_COMMANDS {
264                true => assert!(finalize.add_command(command).is_ok()),
265                false => assert!(finalize.add_command(command).is_err()),
266            }
267        }
268
269        // Ensure that adding more than the maximum number of writes will fail.
270
271        // Initialize a new finalize instance.
272        let name = Identifier::from_str("finalize_core_test").unwrap();
273        let mut finalize = Finalize::<CurrentNetwork>::new(name);
274
275        for _ in 0..CurrentNetwork::LATEST_MAX_WRITES() * 2 {
276            let command = Command::<CurrentNetwork>::from_str("remove object[r0];").unwrap();
277
278            match finalize.commands.len() < CurrentNetwork::LATEST_MAX_WRITES() as usize {
279                true => assert!(finalize.add_command(command).is_ok()),
280                false => assert!(finalize.add_command(command).is_err()),
281            }
282        }
283    }
284
285    #[test]
286    fn test_add_command_duplicate_positions() {
287        // Initialize a new finalize instance.
288        let name = Identifier::from_str("finalize_core_test").unwrap();
289        let mut finalize = Finalize::<CurrentNetwork>::new(name);
290
291        // Ensure that a command can be added.
292        let command = Command::<CurrentNetwork>::from_str("position start;").unwrap();
293        assert!(finalize.add_command(command.clone()).is_ok());
294
295        // Ensure that adding a duplicate position will fail.
296        assert!(finalize.add_command(command).is_err());
297
298        // Helper method to convert a number to a unique string.
299        #[allow(clippy::cast_possible_truncation)]
300        fn to_unique_string(mut n: usize) -> String {
301            let mut s = String::new();
302            while n > 0 {
303                s.push((b'A' + (n % 26) as u8) as char);
304                n /= 26;
305            }
306            s.chars().rev().collect::<String>()
307        }
308
309        // Ensure that adding more than the maximum number of positions will fail.
310        for i in 1..u8::MAX as usize * 2 {
311            let position = to_unique_string(i);
312            // println!("position: {position}");
313            let command = Command::<CurrentNetwork>::from_str(&format!("position {position};")).unwrap();
314
315            match finalize.commands.len() < u8::MAX as usize {
316                true => assert!(finalize.add_command(command).is_ok()),
317                false => assert!(finalize.add_command(command).is_err()),
318            }
319        }
320    }
321}