use crate::Command;
mod input;
use input::*;
mod bytes;
mod parse;
use console::{
network::prelude::*,
program::{FinalizeType, Identifier, Register},
};
use indexmap::IndexSet;
use std::collections::HashMap;
#[derive(Clone, PartialEq, Eq)]
pub struct FinalizeCore<N: Network> {
name: Identifier<N>,
inputs: IndexSet<Input<N>>,
commands: Vec<Command<N>>,
num_writes: u16,
positions: HashMap<Identifier<N>, usize>,
}
impl<N: Network> FinalizeCore<N> {
pub fn new(name: Identifier<N>) -> Self {
Self { name, inputs: IndexSet::new(), commands: Vec::new(), num_writes: 0, positions: HashMap::new() }
}
pub const fn name(&self) -> &Identifier<N> {
&self.name
}
pub const fn inputs(&self) -> &IndexSet<Input<N>> {
&self.inputs
}
pub fn input_types(&self) -> Vec<FinalizeType<N>> {
self.inputs.iter().map(|input| input.finalize_type()).cloned().collect()
}
pub fn commands(&self) -> &[Command<N>] {
&self.commands
}
pub const fn num_writes(&self) -> u16 {
self.num_writes
}
pub const fn positions(&self) -> &HashMap<Identifier<N>, usize> {
&self.positions
}
pub fn contains_external_struct(&self) -> bool {
self.commands
.iter()
.any(|command| matches!(command, Command::Instruction(inst) if inst.contains_external_struct()))
}
pub fn contains_string_type(&self) -> bool {
self.input_types().iter().any(|input_type| {
matches!(input_type, FinalizeType::Plaintext(plaintext_type) if plaintext_type.contains_string_type())
}) || self.commands.iter().any(|command| {
command.contains_string_type()
})
}
pub fn contains_identifier_type(&self) -> Result<bool> {
for input_type in self.input_types() {
if let FinalizeType::Plaintext(plaintext_type) = input_type {
if plaintext_type.contains_identifier_type()? {
return Ok(true);
}
}
}
for command in &self.commands {
if command.contains_identifier_type()? {
return Ok(true);
}
}
Ok(false)
}
pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
self.input_types().iter().any(|input_type| {
matches!(input_type, FinalizeType::Plaintext(plaintext_type) if plaintext_type.exceeds_max_array_size(max_array_size))
}) || self.commands.iter().any(|command| {
command.exceeds_max_array_size(max_array_size)
})
}
}
impl<N: Network> FinalizeCore<N> {
#[inline]
fn add_input(&mut self, input: Input<N>) -> Result<()> {
ensure!(self.commands.is_empty(), "Cannot add inputs after commands have been added");
ensure!(self.inputs.len() < N::MAX_INPUTS, "Cannot add more than {} inputs", N::MAX_INPUTS);
ensure!(!self.inputs.contains(&input), "Cannot add duplicate input statement");
ensure!(matches!(input.register(), Register::Locator(..)), "Input register must be a locator");
self.inputs.insert(input);
Ok(())
}
#[inline]
pub fn add_command(&mut self, command: Command<N>) -> Result<()> {
ensure!(self.commands.len() < N::MAX_COMMANDS, "Cannot add more than {} commands", N::MAX_COMMANDS);
if command.is_write() {
ensure!(
self.num_writes < N::LATEST_MAX_WRITES(),
"Cannot add more than {} 'set' & 'remove' commands",
N::LATEST_MAX_WRITES()
);
}
ensure!(!command.is_async(), "Forbidden operation: Finalize cannot invoke an 'async' instruction");
ensure!(!command.is_call(), "Forbidden operation: Finalize cannot invoke a 'call'");
ensure!(!command.is_cast_to_record(), "Forbidden operation: Finalize cannot cast to a record");
for register in command.destinations() {
ensure!(matches!(register, Register::Locator(..)), "Destination register must be a locator");
}
if let Some(position) = command.branch_to() {
ensure!(!self.positions.contains_key(position), "Cannot branch to an earlier position '{position}'");
}
if let Some(position) = command.position() {
ensure!(!self.positions.contains_key(position), "Cannot redefine position '{position}'");
ensure!(self.positions.len() < N::MAX_POSITIONS, "Cannot add more than {} positions", N::MAX_POSITIONS);
self.positions.insert(*position, self.commands.len());
}
if command.is_write() {
self.num_writes += 1;
}
self.commands.push(command);
Ok(())
}
}
impl<N: Network> TypeName for FinalizeCore<N> {
#[inline]
fn type_name() -> &'static str {
"finalize"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Command, Finalize};
type CurrentNetwork = console::network::MainnetV0;
#[test]
fn test_add_input() {
let name = Identifier::from_str("finalize_core_test").unwrap();
let mut finalize = Finalize::<CurrentNetwork>::new(name);
let input = Input::<CurrentNetwork>::from_str("input r0 as field.public;").unwrap();
assert!(finalize.add_input(input.clone()).is_ok());
assert!(finalize.add_input(input).is_err());
for i in 1..CurrentNetwork::MAX_INPUTS * 2 {
let input = Input::<CurrentNetwork>::from_str(&format!("input r{i} as field.public;")).unwrap();
match finalize.inputs.len() < CurrentNetwork::MAX_INPUTS {
true => assert!(finalize.add_input(input).is_ok()),
false => assert!(finalize.add_input(input).is_err()),
}
}
}
#[test]
fn test_add_command() {
let name = Identifier::from_str("finalize_core_test").unwrap();
let mut finalize = Finalize::<CurrentNetwork>::new(name);
let command = Command::<CurrentNetwork>::from_str("add r0 r1 into r2;").unwrap();
assert!(finalize.add_command(command).is_ok());
for i in 3..CurrentNetwork::MAX_COMMANDS * 2 {
let command = Command::<CurrentNetwork>::from_str(&format!("add r0 r1 into r{i};")).unwrap();
match finalize.commands.len() < CurrentNetwork::MAX_COMMANDS {
true => assert!(finalize.add_command(command).is_ok()),
false => assert!(finalize.add_command(command).is_err()),
}
}
let name = Identifier::from_str("finalize_core_test").unwrap();
let mut finalize = Finalize::<CurrentNetwork>::new(name);
for _ in 0..CurrentNetwork::LATEST_MAX_WRITES() * 2 {
let command = Command::<CurrentNetwork>::from_str("remove object[r0];").unwrap();
match finalize.commands.len() < CurrentNetwork::LATEST_MAX_WRITES() as usize {
true => assert!(finalize.add_command(command).is_ok()),
false => assert!(finalize.add_command(command).is_err()),
}
}
}
#[test]
fn test_add_command_duplicate_positions() {
let name = Identifier::from_str("finalize_core_test").unwrap();
let mut finalize = Finalize::<CurrentNetwork>::new(name);
let command = Command::<CurrentNetwork>::from_str("position start;").unwrap();
assert!(finalize.add_command(command.clone()).is_ok());
assert!(finalize.add_command(command).is_err());
#[allow(clippy::cast_possible_truncation)]
fn to_unique_string(mut n: usize) -> String {
let mut s = String::new();
while n > 0 {
s.push((b'A' + (n % 26) as u8) as char);
n /= 26;
}
s.chars().rev().collect::<String>()
}
for i in 1..u8::MAX as usize * 2 {
let position = to_unique_string(i);
let command = Command::<CurrentNetwork>::from_str(&format!("position {position};")).unwrap();
match finalize.commands.len() < u8::MAX as usize {
true => assert!(finalize.add_command(command).is_ok()),
false => assert!(finalize.add_command(command).is_err()),
}
}
}
}