use crate::Command;
mod input;
pub use input::*;
mod output;
pub use output::*;
mod bytes;
mod parse;
use console::{
network::{error, prelude::*},
program::{FinalizeType, Identifier, Register},
};
use indexmap::IndexSet;
use std::collections::HashMap;
#[derive(Clone, PartialEq, Eq)]
pub struct ViewCore<N: Network> {
name: Identifier<N>,
inputs: IndexSet<Input<N>>,
commands: Vec<Command<N>>,
outputs: IndexSet<Output<N>>,
positions: HashMap<Identifier<N>, usize>,
}
impl<N: Network> ViewCore<N> {
pub fn new(name: Identifier<N>) -> Self {
Self {
name,
inputs: IndexSet::new(),
commands: Vec::new(),
outputs: IndexSet::new(),
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 outputs(&self) -> &IndexSet<Output<N>> {
&self.outputs
}
pub fn output_types(&self) -> Vec<FinalizeType<N>> {
self.outputs.iter().map(|output| output.finalize_type()).cloned().collect()
}
pub const fn positions(&self) -> &HashMap<Identifier<N>, usize> {
&self.positions
}
pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
self.inputs.iter().any(|input| {
matches!(input.finalize_type(), FinalizeType::Plaintext(p) if p.exceeds_max_array_size(max_array_size))
}) || self.commands.iter().any(|command| command.exceeds_max_array_size(max_array_size))
|| self.outputs.iter().any(|output| {
matches!(output.finalize_type(), FinalizeType::Plaintext(p) if p.exceeds_max_array_size(max_array_size))
})
}
pub fn contains_external_struct(&self) -> bool {
self.inputs
.iter()
.any(|input| matches!(input.finalize_type(), FinalizeType::Plaintext(p) if p.contains_external_struct()))
|| self
.commands
.iter()
.any(|command| matches!(command, Command::Instruction(inst) if inst.contains_external_struct()))
|| self.outputs.iter().any(
|output| matches!(output.finalize_type(), FinalizeType::Plaintext(p) if p.contains_external_struct()),
)
}
pub fn contains_string_type(&self) -> bool {
self.inputs
.iter()
.any(|input| matches!(input.finalize_type(), FinalizeType::Plaintext(p) if p.contains_string_type()))
|| self.commands.iter().any(|command| command.contains_string_type())
|| self
.outputs
.iter()
.any(|output| matches!(output.finalize_type(), FinalizeType::Plaintext(p) if p.contains_string_type()))
}
}
impl<N: Network> ViewCore<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.outputs.is_empty(), "Cannot add inputs after outputs 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.finalize_type(), FinalizeType::Plaintext(..)),
"View inputs must be plaintext (futures are forbidden)"
);
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.outputs.is_empty(), "Cannot add commands after outputs have been added");
ensure!(self.commands.len() < N::MAX_COMMANDS, "Cannot add more than {} commands", N::MAX_COMMANDS);
ensure!(!command.is_write(), "Forbidden operation: view functions cannot use 'set' or 'remove'");
ensure!(!command.is_async(), "Forbidden operation: view functions cannot invoke an 'async' instruction");
ensure!(!command.is_await(), "Forbidden operation: view functions cannot 'await' a future");
ensure!(!command.is_call(), "Forbidden operation: view functions cannot 'call' another function");
ensure!(!command.is_instruction_for_record(), "Forbidden operation: view functions cannot operate on records");
ensure!(!command.is_rand_chacha(), "Forbidden operation: view functions cannot use 'rand.chacha'");
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());
}
self.commands.push(command);
Ok(())
}
#[inline]
fn add_output(&mut self, output: Output<N>) -> Result<()> {
ensure!(self.outputs.len() < N::MAX_OUTPUTS, "Cannot add more than {} outputs", N::MAX_OUTPUTS);
ensure!(
matches!(output.finalize_type(), FinalizeType::Plaintext(..)),
"View outputs must be plaintext (futures are forbidden)"
);
self.outputs.insert(output);
Ok(())
}
}
impl<N: Network> TypeName for ViewCore<N> {
#[inline]
fn type_name() -> &'static str {
"view"
}
}
#[cfg(test)]
mod tests {
use super::*;
type CurrentNetwork = console::network::MainnetV0;
#[test]
fn test_add_input() {
let name = Identifier::from_str("view_core_test").unwrap();
let mut view = ViewCore::<CurrentNetwork>::new(name);
let input = Input::<CurrentNetwork>::from_str("input r0 as field.public;").unwrap();
assert!(view.add_input(input.clone()).is_ok());
assert!(view.add_input(input).is_err());
}
#[test]
fn test_reject_set_command() {
let name = Identifier::from_str("view_core_test").unwrap();
let mut view = ViewCore::<CurrentNetwork>::new(name);
let cmd = Command::<CurrentNetwork>::from_str("set 1u64 into balances[0u64];").unwrap();
let err = view.add_command(cmd).unwrap_err();
assert!(err.to_string().contains("'set' or 'remove'"));
}
#[test]
fn test_reject_remove_command() {
let name = Identifier::from_str("view_core_test").unwrap();
let mut view = ViewCore::<CurrentNetwork>::new(name);
let cmd = Command::<CurrentNetwork>::from_str("remove balances[0u64];").unwrap();
let err = view.add_command(cmd).unwrap_err();
assert!(err.to_string().contains("'set' or 'remove'"));
}
#[test]
fn test_reject_rand_chacha() {
let name = Identifier::from_str("view_core_test").unwrap();
let mut view = ViewCore::<CurrentNetwork>::new(name);
let cmd = Command::<CurrentNetwork>::from_str("rand.chacha into r0 as u64;").unwrap();
let err = view.add_command(cmd).unwrap_err();
assert!(err.to_string().contains("rand.chacha"));
}
#[test]
fn test_reject_await_command() {
let name = Identifier::from_str("view_core_test").unwrap();
let mut view = ViewCore::<CurrentNetwork>::new(name);
let cmd = Command::<CurrentNetwork>::from_str("await r0;").unwrap();
let err = view.add_command(cmd).unwrap_err();
assert!(err.to_string().contains("'await'"));
}
#[test]
fn test_reject_call_instruction() {
let name = Identifier::from_str("view_core_test").unwrap();
let mut view = ViewCore::<CurrentNetwork>::new(name);
let cmd = Command::<CurrentNetwork>::from_str("call foo r0 into r1;").unwrap();
let err = view.add_command(cmd).unwrap_err();
assert!(err.to_string().contains("'call'"));
}
#[test]
fn test_reject_cast_to_record() {
let name = Identifier::from_str("view_core_test").unwrap();
let mut view = ViewCore::<CurrentNetwork>::new(name);
let cmd =
Command::<CurrentNetwork>::from_str("cast r0.owner r0.token_amount into r1 as token.record;").unwrap();
let err = view.add_command(cmd).unwrap_err();
assert!(err.to_string().contains("operate on records"));
}
#[test]
fn test_reject_cast_to_dynamic_record() {
let name = Identifier::from_str("view_core_test").unwrap();
let mut view = ViewCore::<CurrentNetwork>::new(name);
let cmd = Command::<CurrentNetwork>::from_str("cast r0 into r1 as dynamic.record;").unwrap();
let err = view.add_command(cmd).unwrap_err();
assert!(err.to_string().contains("operate on records"));
}
#[test]
fn test_reject_get_record_dynamic() {
let name = Identifier::from_str("view_core_test").unwrap();
let mut view = ViewCore::<CurrentNetwork>::new(name);
let cmd = Command::<CurrentNetwork>::from_str("get.record.dynamic r0.x into r1 as bool;").unwrap();
let err = view.add_command(cmd).unwrap_err();
assert!(err.to_string().contains("operate on records"));
}
#[test]
fn test_reject_async_instruction() {
let name = Identifier::from_str("view_core_test").unwrap();
let mut view = ViewCore::<CurrentNetwork>::new(name);
let cmd = Command::<CurrentNetwork>::from_str("async foo r0 r1 into r3;").unwrap();
let err = view.add_command(cmd).unwrap_err();
assert!(err.to_string().contains("'async'"));
}
}