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