Skip to content

Commit

Permalink
Merge pull request #1727 from AleoHQ/feat/array-type
Browse files Browse the repository at this point in the history
[Feature] Introduce `ArrayType`.
  • Loading branch information
howardwu authored Aug 28, 2023
2 parents 0b151b9 + 36afb4c commit fd0390c
Show file tree
Hide file tree
Showing 8 changed files with 363 additions and 3 deletions.
5 changes: 5 additions & 0 deletions console/network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
43 changes: 43 additions & 0 deletions console/program/src/data_types/array_type/bytes.rs
Original file line number Diff line number Diff line change
@@ -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<N: Network> FromBytes for ArrayType<N> {
fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
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<N: Network> ToBytes for ArrayType<N> {
fn write_le<W: Write>(&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)
}
}
}
}
133 changes: 133 additions & 0 deletions console/program/src/data_types/array_type/mod.rs
Original file line number Diff line number Diff line change
@@ -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<N: Network> {
/// An array of literals.
Literal(LiteralType, U32<N>),
/// An array of structs.
Struct(Identifier<N>, U32<N>),
}

impl<N: Network> ArrayType<N> {
/// Initializes a new array type composed of literals.
pub fn new_literal(literal_type: LiteralType, length: U32<N>) -> Result<Self> {
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<N>, length: U32<N>) -> Result<Self> {
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<N: Network> ArrayType<N> {
/// Returns the element type.
pub const fn element_type(&self) -> PlaintextType<N> {
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<N> {
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::<CurrentNetwork>::from_str("[field; 4]")?;
assert_eq!(array, ArrayType::<CurrentNetwork>::Literal(LiteralType::Field, U32::new(4)));
assert_eq!(
array.to_bytes_le()?,
ArrayType::<CurrentNetwork>::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::<CurrentNetwork>::from_str("[foo; 1]")?;
assert_eq!(array, ArrayType::<CurrentNetwork>::Struct(Identifier::from_str("foo")?, U32::new(1)));
assert_eq!(
array.to_bytes_le()?,
ArrayType::<CurrentNetwork>::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::<CurrentNetwork>::from_str("[scalar; 32]")?;
assert_eq!(array, ArrayType::<CurrentNetwork>::Literal(LiteralType::Scalar, U32::new(32)));
assert_eq!(
array.to_bytes_le()?,
ArrayType::<CurrentNetwork>::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::<CurrentNetwork>::from_str("[field; 0]");
assert!(type_.is_err());

let type_ = ArrayType::<CurrentNetwork>::from_str("[field; 4294967296]");
assert!(type_.is_err());

let type_ = ArrayType::<CurrentNetwork>::from_str("[foo; -1]");
assert!(type_.is_err());
}
}
88 changes: 88 additions & 0 deletions console/program/src/data_types/array_type/parse.rs
Original file line number Diff line number Diff line change
@@ -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<N: Network> Parser for ArrayType<N> {
/// Parses a string into a literal type.
#[inline]
fn parse(string: &str) -> ParserResult<Self> {
// 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<u32> {
// 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::<u32>())(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<N: Network> FromStr for ArrayType<N> {
type Err = Error;

/// Returns an array type from a string literal.
fn from_str(string: &str) -> Result<Self> {
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<N: Network> Debug for ArrayType<N> {
/// Prints the array type as a string.
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}

impl<N: Network> Display for ArrayType<N> {
/// 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),
}
}
}
88 changes: 88 additions & 0 deletions console/program/src/data_types/array_type/serialize.rs
Original file line number Diff line number Diff line change
@@ -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<N: Network> Serialize for ArrayType<N> {
/// Serializes the array type into string or bytes.
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<N> {
/// Deserializes the array type from a string or bytes.
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
match deserializer.is_human_readable() {
true => FromStr::from_str(&String::deserialize(deserializer)?).map_err(de::Error::custom),
false => FromBytesDeserializer::<Self>::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::<Testnet3>::from_str(case).unwrap());
}
}

#[test]
fn test_bincode() {
for case in TEST_CASES.iter() {
check_bincode(ArrayType::<Testnet3>::from_str(case).unwrap());
}
}
}
3 changes: 3 additions & 0 deletions console/program/src/data_types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion console/program/src/data_types/plaintext_type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<N: Network> {
/// A literal type contains its type name.
Expand Down
4 changes: 2 additions & 2 deletions utilities/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,6 @@ pub fn error(_msg: &'static str) -> io::Error {
}

#[cfg(feature = "std")]
pub fn error<S: Into<String>>(msg: S) -> io::Error {
io::Error::new(io::ErrorKind::Other, msg.into())
pub fn error<S: ToString>(msg: S) -> io::Error {
io::Error::new(io::ErrorKind::Other, msg.to_string())
}

1 comment on commit fd0390c

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'snarkVM Benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.50.

Benchmark suite Current: fd0390c Previous: a409ee5 Ratio
bls12_377: fq_double 12 ns/iter (± 0) 6 ns/iter (± 0) 2

This comment was automatically generated by workflow using github-action-benchmark.

CC: @raychu86

Please sign in to comment.