mod.rs
  1  // Copyright (c) 2019-2025 Alpha-Delta Network Inc.
  2  // This file is part of the deltavm 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  
 16  use crate::Command;
 17  
 18  mod input;
 19  use input::*;
 20  
 21  mod bytes;
 22  mod parse;
 23  
 24  use console::{
 25      network::prelude::*,
 26      program::{FinalizeType, Identifier, Register},
 27  };
 28  
 29  use indexmap::IndexSet;
 30  use std::collections::HashMap;
 31  
 32  #[derive(Clone, PartialEq, Eq)]
 33  pub 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  
 47  impl<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  
102  impl<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  
185  impl<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)]
194  mod 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  }