Skip to main content

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