snarkvm_synthesizer_program/finalize/
mod.rs1use 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 name: Identifier<N>,
36 inputs: IndexSet<Input<N>>,
39 commands: Vec<Command<N>>,
41 num_writes: u16,
43 positions: HashMap<Identifier<N>, usize>,
45}
46
47impl<N: Network> FinalizeCore<N> {
48 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 pub const fn name(&self) -> &Identifier<N> {
55 &self.name
56 }
57
58 pub const fn inputs(&self) -> &IndexSet<Input<N>> {
60 &self.inputs
61 }
62
63 pub fn input_types(&self) -> Vec<FinalizeType<N>> {
65 self.inputs.iter().map(|input| input.finalize_type()).cloned().collect()
66 }
67
68 pub fn commands(&self) -> &[Command<N>] {
70 &self.commands
71 }
72
73 pub const fn num_writes(&self) -> u16 {
75 self.num_writes
76 }
77
78 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 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 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 for command in &self.commands {
109 if command.contains_identifier_type()? {
110 return Ok(true);
111 }
112 }
113 Ok(false)
114 }
115
116 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 #[inline]
134 fn add_input(&mut self, input: Input<N>) -> Result<()> {
135 ensure!(self.commands.is_empty(), "Cannot add inputs after commands have been added");
137
138 ensure!(self.inputs.len() < N::MAX_INPUTS, "Cannot add more than {} inputs", N::MAX_INPUTS);
140 ensure!(!self.inputs.contains(&input), "Cannot add duplicate input statement");
142
143 ensure!(matches!(input.register(), Register::Locator(..)), "Input register must be a locator");
145
146 self.inputs.insert(input);
148 Ok(())
149 }
150
151 #[inline]
156 pub fn add_command(&mut self, command: Command<N>) -> Result<()> {
157 ensure!(self.commands.len() < N::MAX_COMMANDS, "Cannot add more than {} commands", N::MAX_COMMANDS);
159 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!(!command.is_async(), "Forbidden operation: Finalize cannot invoke an 'async' instruction");
170 ensure!(!command.is_call(), "Forbidden operation: Finalize cannot invoke a 'call'");
172 ensure!(!command.is_cast_to_record(), "Forbidden operation: Finalize cannot cast to a record");
174
175 for register in command.destinations() {
177 ensure!(matches!(register, Register::Locator(..)), "Destination register must be a locator");
179 }
180
181 if let Some(position) = command.branch_to() {
183 ensure!(!self.positions.contains_key(position), "Cannot branch to an earlier position '{position}'");
185 }
186
187 if let Some(position) = command.position() {
189 ensure!(!self.positions.contains_key(position), "Cannot redefine position '{position}'");
191 ensure!(self.positions.len() < N::MAX_POSITIONS, "Cannot add more than {} positions", N::MAX_POSITIONS);
193 self.positions.insert(*position, self.commands.len());
195 }
196
197 if command.is_write() {
199 self.num_writes += 1;
201 }
202
203 self.commands.push(command);
205 Ok(())
206 }
207}
208
209impl<N: Network> TypeName for FinalizeCore<N> {
210 #[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 let name = Identifier::from_str("finalize_core_test").unwrap();
229 let mut finalize = Finalize::<CurrentNetwork>::new(name);
230
231 let input = Input::<CurrentNetwork>::from_str("input r0 as field.public;").unwrap();
233 assert!(finalize.add_input(input.clone()).is_ok());
234
235 assert!(finalize.add_input(input).is_err());
237
238 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 let name = Identifier::from_str("finalize_core_test").unwrap();
253 let mut finalize = Finalize::<CurrentNetwork>::new(name);
254
255 let command = Command::<CurrentNetwork>::from_str("add r0 r1 into r2;").unwrap();
257 assert!(finalize.add_command(command).is_ok());
258
259 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 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 let name = Identifier::from_str("finalize_core_test").unwrap();
289 let mut finalize = Finalize::<CurrentNetwork>::new(name);
290
291 let command = Command::<CurrentNetwork>::from_str("position start;").unwrap();
293 assert!(finalize.add_command(command.clone()).is_ok());
294
295 assert!(finalize.add_command(command).is_err());
297
298 #[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 for i in 1..u8::MAX as usize * 2 {
311 let position = to_unique_string(i);
312 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}