Skip to content

Commit

Permalink
feat: Allow arrays of arbitrary types in the program ABI (#1651)
Browse files Browse the repository at this point in the history
* chore: refactor `InputValue` to accept arrays of `InputValues`

* chore: generalize `TomlTypes` and `JsonTypes` to have a single array type

* chore: add test for arrays of structs in program ABI

* chore: use `vecmap` over plain iterators

* chore: improve functions to convert to `InputValue`

* chore: remove restrictions on array element types

* chore: clippy

* chore: remove stale comment

* chore: move new test case to be run on experimental ssa
  • Loading branch information
TomAFrench authored Jul 11, 2023
1 parent 0d85752 commit 811ede1
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 101 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
authors = [""]
compiler_version = "0.6.0"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[[foos]]
bar = 0
baz = 0

[[foos]]
bar = 0
baz = 0

[[foos]]
bar = 1
baz = 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
struct Foo {
bar: Field,
baz: Field,
}

fn main(foos: [Foo; 3]) -> pub Field {
foos[2].bar + foos[2].baz
}
53 changes: 10 additions & 43 deletions crates/noirc_abi/src/input_parser/json.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::{parse_str_to_field, InputValue};
use crate::{errors::InputParserError, Abi, AbiType, MAIN_RETURN_NAME};
use acvm::FieldElement;
use iter_extended::{try_btree_map, try_vecmap, vecmap};
use iter_extended::{try_btree_map, try_vecmap};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

Expand Down Expand Up @@ -71,12 +71,8 @@ enum JsonTypes {
Integer(u64),
// Simple boolean flag
Bool(bool),
// Array of regular integers
ArrayNum(Vec<u64>),
// Array of hexadecimal integers
ArrayString(Vec<String>),
// Array of booleans
ArrayBool(Vec<bool>),
// Array of JsonTypes
Array(Vec<JsonTypes>),
// Struct of JsonTypes
Table(BTreeMap<String, JsonTypes>),
}
Expand All @@ -93,17 +89,11 @@ impl JsonTypes {
}
(InputValue::Field(f), AbiType::Boolean) => JsonTypes::Bool(f.is_one()),

(InputValue::Vec(v), AbiType::Array { typ, .. }) => match typ.as_ref() {
AbiType::Field | AbiType::Integer { .. } => {
let array = v.iter().map(|i| format!("0x{}", i.to_hex())).collect();
JsonTypes::ArrayString(array)
}
AbiType::Boolean => {
let array = v.iter().map(|i| i.is_one()).collect();
JsonTypes::ArrayBool(array)
}
_ => return Err(InputParserError::AbiTypeMismatch(abi_type.clone())),
},
(InputValue::Vec(vector), AbiType::Array { typ, .. }) => {
let array =
try_vecmap(vector, |value| JsonTypes::try_from_input_value(value, typ))?;
JsonTypes::Array(array)
}

(InputValue::String(s), AbiType::String { .. }) => JsonTypes::String(s.to_string()),

Expand Down Expand Up @@ -145,32 +135,9 @@ impl InputValue {

(JsonTypes::Bool(boolean), AbiType::Boolean) => InputValue::Field(boolean.into()),

(JsonTypes::ArrayNum(arr_num), AbiType::Array { typ, .. })
if matches!(
typ.as_ref(),
AbiType::Field | AbiType::Integer { .. } | AbiType::Boolean
) =>
{
(JsonTypes::Array(array), AbiType::Array { typ, .. }) => {
let array_elements =
vecmap(arr_num, |elem_num| FieldElement::from(i128::from(elem_num)));

InputValue::Vec(array_elements)
}
(JsonTypes::ArrayString(arr_str), AbiType::Array { typ, .. })
if matches!(
typ.as_ref(),
AbiType::Field | AbiType::Integer { .. } | AbiType::Boolean
) =>
{
let array_elements = try_vecmap(arr_str, |elem_str| parse_str_to_field(&elem_str))?;

InputValue::Vec(array_elements)
}
(JsonTypes::ArrayBool(arr_bool), AbiType::Array { typ, .. })
if matches!(typ.as_ref(), AbiType::Boolean) =>
{
let array_elements = vecmap(arr_bool, FieldElement::from);

try_vecmap(array, |value| InputValue::try_from_json(value, typ, arg_name))?;
InputValue::Vec(array_elements)
}

Expand Down
18 changes: 11 additions & 7 deletions crates/noirc_abi/src/input_parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ use crate::{Abi, AbiType};
#[derive(Debug, Clone, Serialize, PartialEq)]
pub enum InputValue {
Field(FieldElement),
Vec(Vec<FieldElement>),
String(String),
Vec(Vec<InputValue>),
Struct(BTreeMap<String, InputValue>),
}

Expand All @@ -32,14 +32,12 @@ impl InputValue {
field_element.is_one() || field_element.is_zero()
}

(InputValue::Vec(field_elements), AbiType::Array { length, typ, .. }) => {
if field_elements.len() != *length as usize {
(InputValue::Vec(array_elements), AbiType::Array { length, typ, .. }) => {
if array_elements.len() != *length as usize {
return false;
}
// Check that all of the array's elements' values match the ABI as well.
field_elements
.iter()
.all(|field_element| Self::Field(*field_element).matches_abi(typ))
array_elements.iter().all(|input_value| input_value.matches_abi(typ))
}

(InputValue::String(string), AbiType::String { length }) => {
Expand Down Expand Up @@ -164,7 +162,13 @@ mod serialization_tests {
"bar".into(),
InputValue::Struct(BTreeMap::from([
("field1".into(), InputValue::Field(255u128.into())),
("field2".into(), InputValue::Vec(vec![true.into(), false.into()])),
(
"field2".into(),
InputValue::Vec(vec![
InputValue::Field(true.into()),
InputValue::Field(false.into()),
]),
),
])),
),
(MAIN_RETURN_NAME.into(), InputValue::String("hello".to_owned())),
Expand Down
53 changes: 10 additions & 43 deletions crates/noirc_abi/src/input_parser/toml.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::{parse_str_to_field, InputValue};
use crate::{errors::InputParserError, Abi, AbiType, MAIN_RETURN_NAME};
use acvm::FieldElement;
use iter_extended::{try_btree_map, try_vecmap, vecmap};
use iter_extended::{try_btree_map, try_vecmap};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

Expand Down Expand Up @@ -68,12 +68,8 @@ enum TomlTypes {
Integer(u64),
// Simple boolean flag
Bool(bool),
// Array of regular integers
ArrayNum(Vec<u64>),
// Array of hexadecimal integers
ArrayString(Vec<String>),
// Array of booleans
ArrayBool(Vec<bool>),
// Array of TomlTypes
Array(Vec<TomlTypes>),
// Struct of TomlTypes
Table(BTreeMap<String, TomlTypes>),
}
Expand All @@ -90,17 +86,11 @@ impl TomlTypes {
}
(InputValue::Field(f), AbiType::Boolean) => TomlTypes::Bool(f.is_one()),

(InputValue::Vec(v), AbiType::Array { typ, .. }) => match typ.as_ref() {
AbiType::Field | AbiType::Integer { .. } => {
let array = v.iter().map(|i| format!("0x{}", i.to_hex())).collect();
TomlTypes::ArrayString(array)
}
AbiType::Boolean => {
let array = v.iter().map(|i| i.is_one()).collect();
TomlTypes::ArrayBool(array)
}
_ => return Err(InputParserError::AbiTypeMismatch(abi_type.clone())),
},
(InputValue::Vec(vector), AbiType::Array { typ, .. }) => {
let array =
try_vecmap(vector, |value| TomlTypes::try_from_input_value(value, typ))?;
TomlTypes::Array(array)
}

(InputValue::String(s), AbiType::String { .. }) => TomlTypes::String(s.to_string()),

Expand Down Expand Up @@ -142,32 +132,9 @@ impl InputValue {

(TomlTypes::Bool(boolean), AbiType::Boolean) => InputValue::Field(boolean.into()),

(TomlTypes::ArrayNum(arr_num), AbiType::Array { typ, .. })
if matches!(
typ.as_ref(),
AbiType::Field | AbiType::Integer { .. } | AbiType::Boolean
) =>
{
(TomlTypes::Array(array), AbiType::Array { typ, .. }) => {
let array_elements =
vecmap(arr_num, |elem_num| FieldElement::from(i128::from(elem_num)));

InputValue::Vec(array_elements)
}
(TomlTypes::ArrayString(arr_str), AbiType::Array { typ, .. })
if matches!(
typ.as_ref(),
AbiType::Field | AbiType::Integer { .. } | AbiType::Boolean
) =>
{
let array_elements = try_vecmap(arr_str, |elem_str| parse_str_to_field(&elem_str))?;

InputValue::Vec(array_elements)
}
(TomlTypes::ArrayBool(arr_bool), AbiType::Array { typ, .. })
if matches!(typ.as_ref(), AbiType::Boolean) =>
{
let array_elements = vecmap(arr_bool, FieldElement::from);

try_vecmap(array, |value| InputValue::try_from_toml(value, typ, arg_name))?;
InputValue::Vec(array_elements)
}

Expand Down
25 changes: 19 additions & 6 deletions crates/noirc_abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,11 @@ impl Abi {
match (value, abi_type) {
(InputValue::Field(elem), _) => encoded_value.push(elem),

(InputValue::Vec(vec_elem), _) => encoded_value.extend(vec_elem),
(InputValue::Vec(vec_elements), AbiType::Array { typ, .. }) => {
for elem in vec_elements {
encoded_value.extend(Self::encode_value(elem, typ)?);
}
}

(InputValue::String(string), _) => {
let str_as_fields =
Expand Down Expand Up @@ -384,11 +388,14 @@ impl Abi {

InputValue::Field(field_element)
}
AbiType::Array { length, .. } => {
let field_elements: Vec<FieldElement> =
field_iterator.take(*length as usize).collect();
AbiType::Array { length, typ } => {
let length = *length as usize;
let mut array_elements = Vec::with_capacity(length);
for _ in 0..length {
array_elements.push(Self::decode_value(field_iterator, typ)?);
}

InputValue::Vec(field_elements)
InputValue::Vec(array_elements)
}
AbiType::String { length } => {
let field_elements: Vec<FieldElement> =
Expand Down Expand Up @@ -459,7 +466,13 @@ mod test {

// Note we omit return value from inputs
let inputs: InputMap = BTreeMap::from([
("thing1".to_string(), InputValue::Vec(vec![FieldElement::one(), FieldElement::one()])),
(
"thing1".to_string(),
InputValue::Vec(vec![
InputValue::Field(FieldElement::one()),
InputValue::Field(FieldElement::one()),
]),
),
("thing2".to_string(), InputValue::Field(FieldElement::zero())),
]);

Expand Down

0 comments on commit 811ede1

Please sign in to comment.