diff --git a/console/network/src/lib.rs b/console/network/src/lib.rs index 5ef9335a71..c5569c8c6d 100644 --- a/console/network/src/lib.rs +++ b/console/network/src/lib.rs @@ -128,6 +128,11 @@ pub trait Network: /// The maximum number of entries in a struct. const MAX_STRUCT_ENTRIES: usize = Self::MAX_DATA_ENTRIES; + /// The minimum number of entries in an array. + const MIN_ARRAY_ENTRIES: usize = 1; // This ensures the array is not empty. + /// The maximum number of entries in an array. + const MAX_ARRAY_ENTRIES: usize = Self::MAX_DATA_ENTRIES; + /// The minimum number of entries in a record. const MIN_RECORD_ENTRIES: usize = 1; // This accounts for 'record.owner'. /// The maximum number of entries in a record. diff --git a/console/program/src/data_types/array_type/bytes.rs b/console/program/src/data_types/array_type/bytes.rs new file mode 100644 index 0000000000..35549bc418 --- /dev/null +++ b/console/program/src/data_types/array_type/bytes.rs @@ -0,0 +1,43 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; + +impl FromBytes for ArrayType { + fn read_le(mut reader: R) -> IoResult { + let variant = u8::read_le(&mut reader)?; + match variant { + 0 => Ok(Self::new_literal(LiteralType::read_le(&mut reader)?, U32::read_le(&mut reader)?).map_err(error)?), + 1 => Ok(Self::new_struct(Identifier::read_le(&mut reader)?, U32::read_le(&mut reader)?).map_err(error)?), + 2.. => Err(error(format!("Failed to deserialize annotation variant {variant}"))), + } + } +} + +impl ToBytes for ArrayType { + fn write_le(&self, mut writer: W) -> IoResult<()> { + match self { + Self::Literal(literal_type, length) => { + 0u8.write_le(&mut writer)?; + literal_type.write_le(&mut writer)?; + length.write_le(&mut writer) + } + Self::Struct(identifier, length) => { + 1u8.write_le(&mut writer)?; + identifier.write_le(&mut writer)?; + length.write_le(&mut writer) + } + } + } +} diff --git a/console/program/src/data_types/array_type/mod.rs b/console/program/src/data_types/array_type/mod.rs new file mode 100644 index 0000000000..cfb5d13381 --- /dev/null +++ b/console/program/src/data_types/array_type/mod.rs @@ -0,0 +1,133 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod bytes; +mod parse; +mod serialize; + +use crate::{Identifier, LiteralType, PlaintextType, U32}; +use snarkvm_console_network::prelude::*; + +use core::fmt::{Debug, Display}; + +/// An `ArrayType` defines the type and size of an array. +#[derive(Clone, PartialEq, Eq, Hash)] +pub enum ArrayType { + /// An array of literals. + Literal(LiteralType, U32), + /// An array of structs. + Struct(Identifier, U32), +} + +impl ArrayType { + /// Initializes a new array type composed of literals. + pub fn new_literal(literal_type: LiteralType, length: U32) -> Result { + ensure!(*length as usize >= N::MIN_ARRAY_ENTRIES, "An array must have {} element", N::MIN_ARRAY_ENTRIES); + ensure!(*length as usize <= N::MAX_ARRAY_ENTRIES, "An array can contain {} elements", N::MAX_ARRAY_ENTRIES); + Ok(Self::Literal(literal_type, length)) + } + + /// Initializes a new array type composed of structs. + pub fn new_struct(struct_: Identifier, length: U32) -> Result { + ensure!(*length as usize >= N::MIN_ARRAY_ENTRIES, "An array must have {} element", N::MIN_ARRAY_ENTRIES); + ensure!(*length as usize <= N::MAX_ARRAY_ENTRIES, "An array can contain {} elements", N::MAX_ARRAY_ENTRIES); + Ok(Self::Struct(struct_, length)) + } +} + +impl ArrayType { + /// Returns the element type. + pub const fn element_type(&self) -> PlaintextType { + match &self { + ArrayType::Literal(literal_type, ..) => PlaintextType::Literal(*literal_type), + ArrayType::Struct(identifier, ..) => PlaintextType::Struct(*identifier), + } + } + + /// Returns `true` if the array is empty. + pub fn is_empty(&self) -> bool { + match &self { + ArrayType::Literal(_, length) => **length == 0, + ArrayType::Struct(_, length) => **length == 0, + } + } + + /// Returns the length of the array. + pub const fn length(&self) -> &U32 { + match &self { + ArrayType::Literal(_, length) => length, + ArrayType::Struct(_, length) => length, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use snarkvm_console_network::Testnet3; + + use core::str::FromStr; + + type CurrentNetwork = Testnet3; + + #[test] + fn test_array_type() -> Result<()> { + // Test literal array types. + let array = ArrayType::::from_str("[field; 4]")?; + assert_eq!(array, ArrayType::::Literal(LiteralType::Field, U32::new(4))); + assert_eq!( + array.to_bytes_le()?, + ArrayType::::from_bytes_le(&array.to_bytes_le()?)?.to_bytes_le()? + ); + assert_eq!(array.element_type(), PlaintextType::Literal(LiteralType::Field)); + assert_eq!(array.length(), &U32::new(4)); + assert!(!array.is_empty()); + + // Test struct array types. + let array = ArrayType::::from_str("[foo; 1]")?; + assert_eq!(array, ArrayType::::Struct(Identifier::from_str("foo")?, U32::new(1))); + assert_eq!( + array.to_bytes_le()?, + ArrayType::::from_bytes_le(&array.to_bytes_le()?)?.to_bytes_le()? + ); + assert_eq!(array.element_type(), PlaintextType::Struct(Identifier::from_str("foo")?)); + assert_eq!(array.length(), &U32::new(1)); + assert!(!array.is_empty()); + + // Test array type with maximum length. + let array = ArrayType::::from_str("[scalar; 32]")?; + assert_eq!(array, ArrayType::::Literal(LiteralType::Scalar, U32::new(32))); + assert_eq!( + array.to_bytes_le()?, + ArrayType::::from_bytes_le(&array.to_bytes_le()?)?.to_bytes_le()? + ); + assert_eq!(array.element_type(), PlaintextType::Literal(LiteralType::Scalar)); + assert_eq!(array.length(), &U32::new(32)); + assert!(!array.is_empty()); + + Ok(()) + } + + #[test] + fn test_array_type_fails() { + let type_ = ArrayType::::from_str("[field; 0]"); + assert!(type_.is_err()); + + let type_ = ArrayType::::from_str("[field; 4294967296]"); + assert!(type_.is_err()); + + let type_ = ArrayType::::from_str("[foo; -1]"); + assert!(type_.is_err()); + } +} diff --git a/console/program/src/data_types/array_type/parse.rs b/console/program/src/data_types/array_type/parse.rs new file mode 100644 index 0000000000..46f0a2e519 --- /dev/null +++ b/console/program/src/data_types/array_type/parse.rs @@ -0,0 +1,88 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; + +impl Parser for ArrayType { + /// Parses a string into a literal type. + #[inline] + fn parse(string: &str) -> ParserResult { + // Parse the opening bracket. + let (string, _) = tag("[")(string)?; + // Parse the whitespaces from the string. + let (string, _) = Sanitizer::parse_whitespaces(string)?; + + // A helper function to parse the semicolon, length, and closing bracket. + fn parse_length(string: &str) -> ParserResult { + // Parse the whitespaces from the string. + let (string, _) = Sanitizer::parse_whitespaces(string)?; + // Parse the semicolon. + let (string, _) = tag(";")(string)?; + // Parse the whitespaces from the string. + let (string, _) = Sanitizer::parse_whitespaces(string)?; + // Parse the length from the string. + let (string, length) = + map_res(recognize(many1(one_of("0123456789"))), |digits: &str| digits.parse::())(string)?; + // Parse the whitespaces from the string. + let (string, _) = Sanitizer::parse_whitespaces(string)?; + // Parse the closing bracket. + let (string, _) = tag("]")(string)?; + Ok((string, length)) + } + + // Parse the element type, followed by the length. + alt(( + map_res(pair(LiteralType::parse, parse_length), |(element_type, length)| { + ArrayType::new_literal(element_type, U32::new(length)) + }), + map_res(pair(Identifier::parse, parse_length), |(element_type, length)| { + ArrayType::new_struct(element_type, U32::new(length)) + }), + ))(string) + } +} + +impl FromStr for ArrayType { + type Err = Error; + + /// Returns an array type from a string literal. + fn from_str(string: &str) -> Result { + match Self::parse(string) { + Ok((remainder, object)) => { + // Ensure the remainder is empty. + ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\""); + // Return the object. + Ok(object) + } + Err(error) => bail!("Failed to parse string. {error}"), + } + } +} + +impl Debug for ArrayType { + /// Prints the array type as a string. + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl Display for ArrayType { + /// Prints the array type as a string. + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Literal(literal_type, length) => write!(f, "[{}; {}]", literal_type, **length), + Self::Struct(identifier, length) => write!(f, "[{}; {}]", identifier, **length), + } + } +} diff --git a/console/program/src/data_types/array_type/serialize.rs b/console/program/src/data_types/array_type/serialize.rs new file mode 100644 index 0000000000..e8a8845750 --- /dev/null +++ b/console/program/src/data_types/array_type/serialize.rs @@ -0,0 +1,88 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; + +impl Serialize for ArrayType { + /// Serializes the array type into string or bytes. + fn serialize(&self, serializer: S) -> Result { + match serializer.is_human_readable() { + true => serializer.collect_str(self), + false => ToBytesSerializer::serialize_with_size_encoding(self, serializer), + } + } +} + +impl<'de, N: Network> Deserialize<'de> for ArrayType { + /// Deserializes the array type from a string or bytes. + fn deserialize>(deserializer: D) -> Result { + match deserializer.is_human_readable() { + true => FromStr::from_str(&String::deserialize(deserializer)?).map_err(de::Error::custom), + false => FromBytesDeserializer::::deserialize_with_size_encoding(deserializer, "array type"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use snarkvm_console_network::Testnet3; + + /// Add test cases here to be checked for serialization. + const TEST_CASES: &[&str] = &["[u8; 1]", "[foo; 4]", "[boolean; 31]", "[field; 32]"]; + + fn check_serde_json< + T: Serialize + for<'a> Deserialize<'a> + Debug + Display + PartialEq + Eq + FromStr + ToBytes + FromBytes, + >( + expected: T, + ) { + // Serialize + let expected_string = &expected.to_string(); + let candidate_string = serde_json::to_string(&expected).unwrap(); + assert_eq!(expected_string, serde_json::Value::from_str(&candidate_string).unwrap().as_str().unwrap()); + + // Deserialize + assert_eq!(expected, T::from_str(expected_string).unwrap_or_else(|_| panic!("FromStr: {expected_string}"))); + assert_eq!(expected, serde_json::from_str(&candidate_string).unwrap()); + } + + fn check_bincode< + T: Serialize + for<'a> Deserialize<'a> + Debug + Display + PartialEq + Eq + FromStr + ToBytes + FromBytes, + >( + expected: T, + ) { + // Serialize + let expected_bytes = expected.to_bytes_le().unwrap(); + let expected_bytes_with_size_encoding = bincode::serialize(&expected).unwrap(); + assert_eq!(&expected_bytes[..], &expected_bytes_with_size_encoding[8..]); + + // Deserialize + assert_eq!(expected, T::read_le(&expected_bytes[..]).unwrap()); + assert_eq!(expected, bincode::deserialize(&expected_bytes_with_size_encoding[..]).unwrap()); + } + + #[test] + fn test_serde_json() { + for case in TEST_CASES.iter() { + check_serde_json(ArrayType::::from_str(case).unwrap()); + } + } + + #[test] + fn test_bincode() { + for case in TEST_CASES.iter() { + check_bincode(ArrayType::::from_str(case).unwrap()); + } + } +} diff --git a/console/program/src/data_types/mod.rs b/console/program/src/data_types/mod.rs index 184415d4a8..ee089ffa10 100644 --- a/console/program/src/data_types/mod.rs +++ b/console/program/src/data_types/mod.rs @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod array_type; +pub use array_type::ArrayType; + mod literal_type; pub use literal_type::LiteralType; diff --git a/console/program/src/data_types/plaintext_type/mod.rs b/console/program/src/data_types/plaintext_type/mod.rs index 32add8fe9f..4c58848c12 100644 --- a/console/program/src/data_types/plaintext_type/mod.rs +++ b/console/program/src/data_types/plaintext_type/mod.rs @@ -19,7 +19,7 @@ mod serialize; use crate::{Identifier, LiteralType}; use snarkvm_console_network::prelude::*; -/// A `ValueType` defines the type parameter for an entry in an `Struct`. +/// A `PlaintextType` defines the type parameter for an entry in an `Struct`. #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum PlaintextType { /// A literal type contains its type name. diff --git a/utilities/src/lib.rs b/utilities/src/lib.rs index af6666b105..a6c5fd85d3 100644 --- a/utilities/src/lib.rs +++ b/utilities/src/lib.rs @@ -80,6 +80,6 @@ pub fn error(_msg: &'static str) -> io::Error { } #[cfg(feature = "std")] -pub fn error>(msg: S) -> io::Error { - io::Error::new(io::ErrorKind::Other, msg.into()) +pub fn error(msg: S) -> io::Error { + io::Error::new(io::ErrorKind::Other, msg.to_string()) }