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 }