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