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 num_calls: u16,
45 positions: HashMap<Identifier<N>, usize>,
47}
48
49impl<N: Network> FinalizeCore<N> {
50 pub fn new(name: Identifier<N>) -> Self {
52 Self {
53 name,
54 inputs: IndexSet::new(),
55 commands: Vec::new(),
56 num_writes: 0,
57 num_calls: 0,
58 positions: HashMap::new(),
59 }
60 }
61
62 pub const fn name(&self) -> &Identifier<N> {
64 &self.name
65 }
66
67 pub const fn inputs(&self) -> &IndexSet<Input<N>> {
69 &self.inputs
70 }
71
72 pub fn input_types(&self) -> Vec<FinalizeType<N>> {
74 self.inputs.iter().map(|input| input.finalize_type()).cloned().collect()
75 }
76
77 pub fn commands(&self) -> &[Command<N>] {
79 &self.commands
80 }
81
82 pub const fn num_writes(&self) -> u16 {
84 self.num_writes
85 }
86
87 pub const fn positions(&self) -> &HashMap<Identifier<N>, usize> {
89 &self.positions
90 }
91
92 pub fn contains_external_struct(&self) -> bool {
93 self.commands
94 .iter()
95 .any(|command| matches!(command, Command::Instruction(inst) if inst.contains_external_struct()))
96 }
97
98 pub fn contains_string_type(&self) -> bool {
100 self.input_types().iter().any(|input_type| {
101 matches!(input_type, FinalizeType::Plaintext(plaintext_type) if plaintext_type.contains_string_type())
102 }) || self.commands.iter().any(|command| {
103 command.contains_string_type()
104 })
105 }
106
107 pub fn contains_identifier_type(&self) -> Result<bool> {
109 for input_type in self.input_types() {
110 if let FinalizeType::Plaintext(plaintext_type) = input_type {
111 if plaintext_type.contains_identifier_type()? {
112 return Ok(true);
113 }
114 }
115 }
116 for command in &self.commands {
118 if command.contains_identifier_type()? {
119 return Ok(true);
120 }
121 }
122 Ok(false)
123 }
124
125 pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
127 self.input_types().iter().any(|input_type| {
128 matches!(input_type, FinalizeType::Plaintext(plaintext_type) if plaintext_type.exceeds_max_array_size(max_array_size))
129 }) || self.commands.iter().any(|command| {
130 command.exceeds_max_array_size(max_array_size)
131 })
132 }
133}
134
135impl<N: Network> FinalizeCore<N> {
136 #[inline]
143 fn add_input(&mut self, input: Input<N>) -> Result<()> {
144 ensure!(self.commands.is_empty(), "Cannot add inputs after commands have been added");
146
147 ensure!(self.inputs.len() < N::MAX_INPUTS, "Cannot add more than {} inputs", N::MAX_INPUTS);
149 ensure!(!self.inputs.contains(&input), "Cannot add duplicate input statement");
151
152 ensure!(matches!(input.register(), Register::Locator(..)), "Input register must be a locator");
154
155 self.inputs.insert(input);
157 Ok(())
158 }
159
160 #[inline]
165 pub fn add_command(&mut self, command: Command<N>) -> Result<()> {
166 ensure!(self.commands.len() < N::MAX_COMMANDS, "Cannot add more than {} commands", N::MAX_COMMANDS);
168 if command.is_write() {
170 ensure!(
171 self.num_writes < N::LATEST_MAX_WRITES(),
172 "Cannot add more than {} 'set' & 'remove' commands",
173 N::LATEST_MAX_WRITES()
174 );
175 }
176
177 ensure!(!command.is_async(), "Forbidden operation: Finalize cannot invoke an 'async' instruction");
179 ensure!(!command.is_dynamic_call(), "Forbidden operation: Finalize cannot invoke a 'call.dynamic'");
183 if command.is_call() {
189 ensure!(
190 (self.num_calls as usize) < N::MAX_CALLS,
191 "Cannot add more than {} 'call' commands in a finalize body",
192 N::MAX_CALLS
193 );
194 }
195 ensure!(!command.is_instruction_for_record(), "Forbidden operation: Finalize cannot operate on records");
197
198 for register in command.destinations() {
200 ensure!(matches!(register, Register::Locator(..)), "Destination register must be a locator");
202 }
203
204 if let Some(position) = command.branch_to() {
206 ensure!(!self.positions.contains_key(position), "Cannot branch to an earlier position '{position}'");
208 }
209
210 if let Some(position) = command.position() {
212 ensure!(!self.positions.contains_key(position), "Cannot redefine position '{position}'");
214 ensure!(self.positions.len() < N::MAX_POSITIONS, "Cannot add more than {} positions", N::MAX_POSITIONS);
216 self.positions.insert(*position, self.commands.len());
218 }
219
220 if command.is_write() {
222 self.num_writes += 1;
224 }
225 if command.is_call() {
228 self.num_calls += 1;
229 }
230
231 self.commands.push(command);
233 Ok(())
234 }
235}
236
237impl<N: Network> TypeName for FinalizeCore<N> {
238 #[inline]
240 fn type_name() -> &'static str {
241 "finalize"
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 use crate::{Command, Finalize};
250
251 type CurrentNetwork = console::network::MainnetV0;
252
253 #[test]
254 fn test_add_input() {
255 let name = Identifier::from_str("finalize_core_test").unwrap();
257 let mut finalize = Finalize::<CurrentNetwork>::new(name);
258
259 let input = Input::<CurrentNetwork>::from_str("input r0 as field.public;").unwrap();
261 assert!(finalize.add_input(input.clone()).is_ok());
262
263 assert!(finalize.add_input(input).is_err());
265
266 for i in 1..CurrentNetwork::MAX_INPUTS * 2 {
268 let input = Input::<CurrentNetwork>::from_str(&format!("input r{i} as field.public;")).unwrap();
269
270 match finalize.inputs.len() < CurrentNetwork::MAX_INPUTS {
271 true => assert!(finalize.add_input(input).is_ok()),
272 false => assert!(finalize.add_input(input).is_err()),
273 }
274 }
275 }
276
277 #[test]
278 fn test_add_command() {
279 let name = Identifier::from_str("finalize_core_test").unwrap();
281 let mut finalize = Finalize::<CurrentNetwork>::new(name);
282
283 let command = Command::<CurrentNetwork>::from_str("add r0 r1 into r2;").unwrap();
285 assert!(finalize.add_command(command).is_ok());
286
287 for i in 3..CurrentNetwork::MAX_COMMANDS * 2 {
289 let command = Command::<CurrentNetwork>::from_str(&format!("add r0 r1 into r{i};")).unwrap();
290
291 match finalize.commands.len() < CurrentNetwork::MAX_COMMANDS {
292 true => assert!(finalize.add_command(command).is_ok()),
293 false => assert!(finalize.add_command(command).is_err()),
294 }
295 }
296
297 let name = Identifier::from_str("finalize_core_test").unwrap();
301 let mut finalize = Finalize::<CurrentNetwork>::new(name);
302
303 for _ in 0..CurrentNetwork::LATEST_MAX_WRITES() * 2 {
304 let command = Command::<CurrentNetwork>::from_str("remove object[r0];").unwrap();
305
306 match finalize.commands.len() < CurrentNetwork::LATEST_MAX_WRITES() as usize {
307 true => assert!(finalize.add_command(command).is_ok()),
308 false => assert!(finalize.add_command(command).is_err()),
309 }
310 }
311 }
312
313 #[test]
314 fn test_add_command_duplicate_positions() {
315 let name = Identifier::from_str("finalize_core_test").unwrap();
317 let mut finalize = Finalize::<CurrentNetwork>::new(name);
318
319 let command = Command::<CurrentNetwork>::from_str("position start;").unwrap();
321 assert!(finalize.add_command(command.clone()).is_ok());
322
323 assert!(finalize.add_command(command).is_err());
325
326 #[allow(clippy::cast_possible_truncation)]
328 fn to_unique_string(mut n: usize) -> String {
329 let mut s = String::new();
330 while n > 0 {
331 s.push((b'A' + (n % 26) as u8) as char);
332 n /= 26;
333 }
334 s.chars().rev().collect::<String>()
335 }
336
337 for i in 1..u8::MAX as usize * 2 {
339 let position = to_unique_string(i);
340 let command = Command::<CurrentNetwork>::from_str(&format!("position {position};")).unwrap();
342
343 match finalize.commands.len() < u8::MAX as usize {
344 true => assert!(finalize.add_command(command).is_ok()),
345 false => assert!(finalize.add_command(command).is_err()),
346 }
347 }
348 }
349
350 #[test]
351 fn test_reject_cast_to_dynamic_record_in_finalize() {
352 let name = Identifier::from_str("finalize_core_test").unwrap();
353 let mut finalize = Finalize::<CurrentNetwork>::new(name);
354
355 let cmd = Command::<CurrentNetwork>::from_str("cast r0 into r1 as dynamic.record;").unwrap();
356 let err = finalize.add_command(cmd).unwrap_err();
357 assert!(err.to_string().contains("operate on records"));
358 }
359
360 #[test]
361 fn test_reject_get_record_dynamic_in_finalize() {
362 let name = Identifier::from_str("finalize_core_test").unwrap();
363 let mut finalize = Finalize::<CurrentNetwork>::new(name);
364
365 let cmd = Command::<CurrentNetwork>::from_str("get.record.dynamic r0.x into r1 as bool;").unwrap();
366 let err = finalize.add_command(cmd).unwrap_err();
367 assert!(err.to_string().contains("operate on records"));
368 }
369}