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