From cb65c193c5ef43420ce96711079af82b37fadf8d Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 24 Sep 2024 10:12:36 -0300 Subject: [PATCH 01/14] Generate public_entrypoint dispatch function --- .../aztec/src/macros/entrypoint/mod.nr | 144 ++++++++++++++++++ .../aztec-nr/aztec/src/macros/mod.nr | 7 +- .../contracts/avm_test_contract/src/main.nr | 12 ++ 3 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 noir-projects/aztec-nr/aztec/src/macros/entrypoint/mod.nr diff --git a/noir-projects/aztec-nr/aztec/src/macros/entrypoint/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/entrypoint/mod.nr new file mode 100644 index 00000000000..d4a0489cd8f --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/macros/entrypoint/mod.nr @@ -0,0 +1,144 @@ +use protocol_types::abis::function_selector::FunctionSelector; +use std::meta::unquote; +use crate::context::inputs::public_context_inputs::PublicContextInputs; + +/// Returns an `fn public_entrypoint(...)` function for the given module that's assumed to be an Aztec contract. +pub comptime fn generate_public_entrypoint(m: Module) -> Quoted { + let functions = m.functions(); + let functions = functions.filter(|function: FunctionDefinition| function.has_named_attribute("public")); + + let public_context_inputs = get_type::(); + let unit = get_type::<()>(); + + let ifs = functions.map( + |function: FunctionDefinition| { + let name = function.name(); + let parameters = function.parameters(); + let return_type = function.return_type(); + + let signature = function_signature(name, parameters).as_quoted_str!(); + let selector: FunctionSelector = unquote!(quote { FunctionSelector::from_signature($signature) }); + + let mut parameter_index = 0; + + let reads = parameters.map(|param: (Quoted, Type)| { + let param_type = param.1; + let param_size = size_in_fields(param_type); + let param_name = f"arg{parameter_index}".quoted_contents(); + let read = quote { let $param_name = dep::aztec::protocol_types::traits::Deserialize::deserialize(dep::aztec::context::public_context::calldata_copy(offset, $param_size)); }; + let increment = quote { offset += $param_size; }; + let read = if parameter_index == 0 { + quote { $increment } + } else { + quote { $read $increment } + }; + parameter_index += 1; + read + }); + let read = reads.join(quote { }); + + let mut args = &[]; + for parameter_index in 0..parameters.len() { + let param_name = f"arg{parameter_index}".quoted_contents(); + args = args.push_back(quote { $param_name }); + } + + let args = args.join(quote { , }); + let call = quote { $name($args) }; + + let return_code = if return_type == unit { + quote { $call } + } else { + quote { + let return_value = dep::aztec::protocol_types::traits::Serialize::serialize($call); + dep::aztec::context::public_context::avm_return(return_value); + } + }; + + let if_ = quote { + if selector == $selector { + $read + $return_code + } + }; + if_ + } + ); + + let ifs = ifs.push_back(quote { { panic(f"Unknown selector") } }); + let dispatch = ifs.join(quote { else }); + + let from_field_fn = FunctionSelector::from_field; + + quote { + pub fn public_entrypoint(arg0: $public_context_inputs, selector: Field) { + let selector = $from_field_fn(selector); + let mut offset = 0; + $dispatch + } + } +} + +comptime fn function_signature(name: Quoted, parameters: [(Quoted, Type)]) -> CtString { + let types = parameters.map( + |param: (Quoted, Type)| { + let typ = param.1; + f"{typ}".as_ctstring() + } + ); + let types = types.join(", ".as_ctstring()); + + f"{name}({types})".as_ctstring() +} + +comptime fn size_in_fields(typ: Type) -> u32 { + if typ.as_slice().is_some() { + panic(f"Can't determine size in fields of Slice type") + } else { + let size = array_size_in_fields(typ); + let size = size.or_else(|| struct_size_in_fields(typ)); + let size = size.or_else(|| tuple_size_in_fields(typ)); + size.unwrap_or(1) + } +} + +comptime fn array_size_in_fields(typ: Type) -> Option { + typ.as_array().and_then( + |typ: (Type, Type)| { + let (typ, element_size) = typ; + element_size.as_constant().map(|x: u32| { + x * size_in_fields(typ) + }) + } + ) +} + +comptime fn struct_size_in_fields(typ: Type) -> Option { + typ.as_struct().map( + |typ: (StructDefinition, [Type])| { + let struct_type = typ.0; + let mut size = 0; + for field in struct_type.fields() { + size += size_in_fields(field.1); + } + size + } + ) +} + +comptime fn tuple_size_in_fields(typ: Type) -> Option { + typ.as_tuple().map( + |types: [Type]| { + let mut size = 0; + for typ in types { + size += size_in_fields(typ); + } + size + } + ) +} + +comptime fn get_type() -> Type { + let t: T = std::mem::zeroed(); + std::meta::type_of(t) +} diff --git a/noir-projects/aztec-nr/aztec/src/macros/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/mod.nr index 23da22f2d1d..60af6a52032 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/mod.nr @@ -1,3 +1,4 @@ +mod entrypoint; mod functions; mod utils; mod notes; @@ -10,26 +11,26 @@ use notes::{NOTES, generate_note_export}; use functions::transform_unconstrained; use utils::module_has_storage; +use entrypoint::generate_public_entrypoint; /// Marks a contract as an Aztec contract, generating the interfaces for its functions and notes, as well as injecting /// the `compute_note_hash_and_optionally_a_nullifier` function PXE requires in order to validate notes. pub comptime fn aztec(m: Module) -> Quoted { let interface = generate_contract_interface(m); - let unconstrained_functions = m.functions().filter( | f: FunctionDefinition | f.is_unconstrained() & !f.has_named_attribute("test") & !f.has_named_attribute("public") ); for f in unconstrained_functions { transform_unconstrained(f); } - let compute_note_hash_and_optionally_a_nullifier = generate_compute_note_hash_and_optionally_a_nullifier(); let note_exports = generate_note_exports(); - + let public_entrypoint = generate_public_entrypoint(m); quote { $note_exports $interface $compute_note_hash_and_optionally_a_nullifier + $public_entrypoint } } diff --git a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr index e6861d4cc58..a54e246b7fa 100644 --- a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr @@ -567,3 +567,15 @@ contract AvmTest { let _ = nested_static_call_to_add(inputs, 1, 2); } } + +mod tests { + use super::AvmTest; + use aztec::context::inputs::public_context_inputs::PublicContextInputs; + + #[test] + fn contract_has_public_entrypoint() { + let selector = 0; + let inputs = PublicContextInputs::empty(); + AvmTest::public_entrypoint(inputs, selector); + } +} From 695fdc9afdcb218d63a69dabffa568795620e780 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 24 Sep 2024 10:18:55 -0300 Subject: [PATCH 02/14] Serialize and Deserialize for arrays of integers --- .../crates/types/src/type_serialization.nr | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/type_serialization.nr b/noir-projects/noir-protocol-circuits/crates/types/src/type_serialization.nr index ac113fe2f79..7554d2ce070 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/type_serialization.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/type_serialization.nr @@ -78,3 +78,99 @@ impl Deserialize for Field { fields[0] } } + +impl Serialize for [u8; N] { + fn serialize(self) -> [Field; N] { + self.map(|value| value as Field) + } +} + +impl Deserialize for [u8; N] { + fn deserialize(fields: [Field; N]) -> Self { + fields.map(|value| value as u8) + } +} + +impl Serialize for [u16; N] { + fn serialize(self) -> [Field; N] { + self.map(|value| value as Field) + } +} + +impl Deserialize for [u16; N] { + fn deserialize(fields: [Field; N]) -> Self { + fields.map(|value| value as u16) + } +} + +impl Serialize for [u32; N] { + fn serialize(self) -> [Field; N] { + self.map(|value| value as Field) + } +} + +impl Deserialize for [u32; N] { + fn deserialize(fields: [Field; N]) -> Self { + fields.map(|value| value as u32) + } +} + +impl Serialize for [u64; N] { + fn serialize(self) -> [Field; N] { + self.map(|value| value as Field) + } +} + +impl Deserialize for [u64; N] { + fn deserialize(fields: [Field; N]) -> Self { + fields.map(|value| value as u64) + } +} + +impl Serialize for [i8; N] { + fn serialize(self) -> [Field; N] { + self.map(|value| value as Field) + } +} + +impl Deserialize for [i8; N] { + fn deserialize(fields: [Field; N]) -> Self { + fields.map(|value| value as i8) + } +} + +impl Serialize for [i16; N] { + fn serialize(self) -> [Field; N] { + self.map(|value| value as Field) + } +} + +impl Deserialize for [i16; N] { + fn deserialize(fields: [Field; N]) -> Self { + fields.map(|value| value as i16) + } +} + +impl Serialize for [i32; N] { + fn serialize(self) -> [Field; N] { + self.map(|value| value as Field) + } +} + +impl Deserialize for [i32; N] { + fn deserialize(fields: [Field; N]) -> Self { + fields.map(|value| value as i32) + } +} + +impl Serialize for [i64; N] { + fn serialize(self) -> [Field; N] { + self.map(|value| value as Field) + } +} + +impl Deserialize for [i64; N] { + fn deserialize(fields: [Field; N]) -> Self { + fields.map(|value| value as i64) + } +} From 93c4d69813f7061da5c4ce4864d200c7c0a55d66 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 24 Sep 2024 12:24:05 -0300 Subject: [PATCH 03/14] Reuse `compute_fn_selector` function --- .../aztec/src/macros/entrypoint/mod.nr | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/macros/entrypoint/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/entrypoint/mod.nr index d4a0489cd8f..461c008ffaa 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/entrypoint/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/entrypoint/mod.nr @@ -1,6 +1,6 @@ use protocol_types::abis::function_selector::FunctionSelector; -use std::meta::unquote; use crate::context::inputs::public_context_inputs::PublicContextInputs; +use super::utils::compute_fn_selector; /// Returns an `fn public_entrypoint(...)` function for the given module that's assumed to be an Aztec contract. pub comptime fn generate_public_entrypoint(m: Module) -> Quoted { @@ -16,8 +16,7 @@ pub comptime fn generate_public_entrypoint(m: Module) -> Quoted { let parameters = function.parameters(); let return_type = function.return_type(); - let signature = function_signature(name, parameters).as_quoted_str!(); - let selector: FunctionSelector = unquote!(quote { FunctionSelector::from_signature($signature) }); + let selector: Field = compute_fn_selector(function); let mut parameter_index = 0; @@ -68,27 +67,16 @@ pub comptime fn generate_public_entrypoint(m: Module) -> Quoted { let ifs = ifs.push_back(quote { { panic(f"Unknown selector") } }); let dispatch = ifs.join(quote { else }); - let from_field_fn = FunctionSelector::from_field; - - quote { + let body = quote { pub fn public_entrypoint(arg0: $public_context_inputs, selector: Field) { - let selector = $from_field_fn(selector); let mut offset = 0; $dispatch } - } -} + }; -comptime fn function_signature(name: Quoted, parameters: [(Quoted, Type)]) -> CtString { - let types = parameters.map( - |param: (Quoted, Type)| { - let typ = param.1; - f"{typ}".as_ctstring() - } - ); - let types = types.join(", ".as_ctstring()); + // println(body); - f"{name}({types})".as_ctstring() + body } comptime fn size_in_fields(typ: Type) -> u32 { From 8240f70aad3cca97f50e3a8063155310b6dbc2ba Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 24 Sep 2024 12:48:29 -0300 Subject: [PATCH 04/14] public_entrypoint -> public_dispatch --- .../aztec/src/macros/{entrypoint => dispatch}/mod.nr | 6 +++--- noir-projects/aztec-nr/aztec/src/macros/mod.nr | 8 ++++---- .../contracts/avm_test_contract/src/main.nr | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) rename noir-projects/aztec-nr/aztec/src/macros/{entrypoint => dispatch}/mod.nr (93%) diff --git a/noir-projects/aztec-nr/aztec/src/macros/entrypoint/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr similarity index 93% rename from noir-projects/aztec-nr/aztec/src/macros/entrypoint/mod.nr rename to noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr index 461c008ffaa..4c277a84948 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/entrypoint/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr @@ -2,8 +2,8 @@ use protocol_types::abis::function_selector::FunctionSelector; use crate::context::inputs::public_context_inputs::PublicContextInputs; use super::utils::compute_fn_selector; -/// Returns an `fn public_entrypoint(...)` function for the given module that's assumed to be an Aztec contract. -pub comptime fn generate_public_entrypoint(m: Module) -> Quoted { +/// Returns an `fn public_dispatch(...)` function for the given module that's assumed to be an Aztec contract. +pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let functions = m.functions(); let functions = functions.filter(|function: FunctionDefinition| function.has_named_attribute("public")); @@ -68,7 +68,7 @@ pub comptime fn generate_public_entrypoint(m: Module) -> Quoted { let dispatch = ifs.join(quote { else }); let body = quote { - pub fn public_entrypoint(arg0: $public_context_inputs, selector: Field) { + pub fn public_dispatch(arg0: $public_context_inputs, selector: Field) { let mut offset = 0; $dispatch } diff --git a/noir-projects/aztec-nr/aztec/src/macros/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/mod.nr index 60af6a52032..df5a1530f12 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/mod.nr @@ -1,4 +1,4 @@ -mod entrypoint; +mod dispatch; mod functions; mod utils; mod notes; @@ -11,7 +11,7 @@ use notes::{NOTES, generate_note_export}; use functions::transform_unconstrained; use utils::module_has_storage; -use entrypoint::generate_public_entrypoint; +use dispatch::generate_public_dispatch; /// Marks a contract as an Aztec contract, generating the interfaces for its functions and notes, as well as injecting /// the `compute_note_hash_and_optionally_a_nullifier` function PXE requires in order to validate notes. @@ -25,12 +25,12 @@ pub comptime fn aztec(m: Module) -> Quoted { } let compute_note_hash_and_optionally_a_nullifier = generate_compute_note_hash_and_optionally_a_nullifier(); let note_exports = generate_note_exports(); - let public_entrypoint = generate_public_entrypoint(m); + let public_dispatch = generate_public_dispatch(m); quote { $note_exports $interface $compute_note_hash_and_optionally_a_nullifier - $public_entrypoint + $public_dispatch } } diff --git a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr index a54e246b7fa..a07d7b47c47 100644 --- a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr @@ -573,9 +573,9 @@ mod tests { use aztec::context::inputs::public_context_inputs::PublicContextInputs; #[test] - fn contract_has_public_entrypoint() { + fn contract_has_public_dispatch() { let selector = 0; let inputs = PublicContextInputs::empty(); - AvmTest::public_entrypoint(inputs, selector); + AvmTest::public_dispatch(inputs, selector); } } From dae2f2cb3e059348f423e8026f69dd62ffe62f84 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 24 Sep 2024 12:54:03 -0300 Subject: [PATCH 05/14] Adjust initial offset --- noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr index 4c277a84948..e09ce50008b 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr @@ -9,6 +9,7 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let public_context_inputs = get_type::(); let unit = get_type::<()>(); + let initial_offset = size_in_fields(public_context_inputs) + 1; // +1 for the Field selector let ifs = functions.map( |function: FunctionDefinition| { @@ -27,7 +28,7 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let read = quote { let $param_name = dep::aztec::protocol_types::traits::Deserialize::deserialize(dep::aztec::context::public_context::calldata_copy(offset, $param_size)); }; let increment = quote { offset += $param_size; }; let read = if parameter_index == 0 { - quote { $increment } + quote {} } else { quote { $read $increment } }; @@ -69,7 +70,7 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let body = quote { pub fn public_dispatch(arg0: $public_context_inputs, selector: Field) { - let mut offset = 0; + let mut offset = $initial_offset; $dispatch } }; From 4d980eacd818ddf29313419049963d69e0af649f Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 24 Sep 2024 12:55:52 -0300 Subject: [PATCH 06/14] Reorganize --- .../aztec-nr/aztec/src/macros/dispatch/mod.nr | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr index e09ce50008b..a4ad98fb0ca 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr @@ -22,14 +22,15 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let mut parameter_index = 0; let reads = parameters.map(|param: (Quoted, Type)| { - let param_type = param.1; - let param_size = size_in_fields(param_type); - let param_name = f"arg{parameter_index}".quoted_contents(); - let read = quote { let $param_name = dep::aztec::protocol_types::traits::Deserialize::deserialize(dep::aztec::context::public_context::calldata_copy(offset, $param_size)); }; - let increment = quote { offset += $param_size; }; + // Skip the `PublicContextInputs` argument as we already have that let read = if parameter_index == 0 { quote {} } else { + let param_type = param.1; + let param_size = size_in_fields(param_type); + let param_name = f"arg{parameter_index}".quoted_contents(); + let read = quote { let $param_name = dep::aztec::protocol_types::traits::Deserialize::deserialize(dep::aztec::context::public_context::calldata_copy(offset, $param_size)); }; + let increment = quote { offset += $param_size; }; quote { $read $increment } }; parameter_index += 1; From bd917475204fe3c61c88a317222866bc487b7b8e Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 24 Sep 2024 13:00:50 -0300 Subject: [PATCH 07/14] Keep track of offset at compile-time --- .../aztec-nr/aztec/src/macros/dispatch/mod.nr | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr index a4ad98fb0ca..a4b19dab070 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr @@ -20,6 +20,7 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let selector: Field = compute_fn_selector(function); let mut parameter_index = 0; + let mut offset = initial_offset; let reads = parameters.map(|param: (Quoted, Type)| { // Skip the `PublicContextInputs` argument as we already have that @@ -29,9 +30,9 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let param_type = param.1; let param_size = size_in_fields(param_type); let param_name = f"arg{parameter_index}".quoted_contents(); - let read = quote { let $param_name = dep::aztec::protocol_types::traits::Deserialize::deserialize(dep::aztec::context::public_context::calldata_copy(offset, $param_size)); }; - let increment = quote { offset += $param_size; }; - quote { $read $increment } + let read = quote { let $param_name = dep::aztec::protocol_types::traits::Deserialize::deserialize(dep::aztec::context::public_context::calldata_copy($offset, $param_size)); }; + offset += param_size; + quote { $read } }; parameter_index += 1; read @@ -71,12 +72,11 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let body = quote { pub fn public_dispatch(arg0: $public_context_inputs, selector: Field) { - let mut offset = $initial_offset; $dispatch } }; - // println(body); + println(body); body } From 075c7d6043f25f1177a0d8596d712806c7f4986f Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Wed, 25 Sep 2024 10:01:16 -0300 Subject: [PATCH 08/14] No need for PublicContextInputs, and avoid generating dispatch if there are no public functions --- .../aztec-nr/aztec/src/macros/dispatch/mod.nr | 44 ++++++++----------- .../contracts/avm_test_contract/src/main.nr | 14 +----- 2 files changed, 19 insertions(+), 39 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr index a4b19dab070..43b32066432 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr @@ -1,5 +1,3 @@ -use protocol_types::abis::function_selector::FunctionSelector; -use crate::context::inputs::public_context_inputs::PublicContextInputs; use super::utils::compute_fn_selector; /// Returns an `fn public_dispatch(...)` function for the given module that's assumed to be an Aztec contract. @@ -7,9 +5,7 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let functions = m.functions(); let functions = functions.filter(|function: FunctionDefinition| function.has_named_attribute("public")); - let public_context_inputs = get_type::(); let unit = get_type::<()>(); - let initial_offset = size_in_fields(public_context_inputs) + 1; // +1 for the Field selector let ifs = functions.map( |function: FunctionDefinition| { @@ -20,22 +16,17 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let selector: Field = compute_fn_selector(function); let mut parameter_index = 0; - let mut offset = initial_offset; + let mut offset = 1; // 1 for the Field selector let reads = parameters.map(|param: (Quoted, Type)| { // Skip the `PublicContextInputs` argument as we already have that - let read = if parameter_index == 0 { - quote {} - } else { - let param_type = param.1; - let param_size = size_in_fields(param_type); - let param_name = f"arg{parameter_index}".quoted_contents(); - let read = quote { let $param_name = dep::aztec::protocol_types::traits::Deserialize::deserialize(dep::aztec::context::public_context::calldata_copy($offset, $param_size)); }; - offset += param_size; - quote { $read } - }; + let param_type = param.1; + let param_size = size_in_fields(param_type); + let param_name = f"arg{parameter_index}".quoted_contents(); + let read = quote { let $param_name = dep::aztec::protocol_types::traits::Deserialize::deserialize(dep::aztec::context::public_context::calldata_copy($offset, $param_size)); }; + offset += param_size; parameter_index += 1; - read + quote { $read } }); let read = reads.join(quote { }); @@ -67,18 +58,19 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { } ); - let ifs = ifs.push_back(quote { { panic(f"Unknown selector") } }); - let dispatch = ifs.join(quote { else }); + if ifs.len() == 0 { + // No dispatch function if there are no public functions + quote {} + } else { + let ifs = ifs.push_back(quote { { panic(f"Unknown selector") } }); + let dispatch = ifs.join(quote { else }); - let body = quote { - pub fn public_dispatch(arg0: $public_context_inputs, selector: Field) { - $dispatch + quote { + pub fn public_dispatch(selector: Field) { + $dispatch + } } - }; - - println(body); - - body + } } comptime fn size_in_fields(typ: Type) -> u32 { diff --git a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr index 4557796a822..c04373f9cc3 100644 --- a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr @@ -564,16 +564,4 @@ contract AvmTest { dep::aztec::oracle::debug_log::debug_log("nested_static_call_to_add"); let _ = nested_static_call_to_add(1, 2); } -} - -mod tests { - use super::AvmTest; - use aztec::context::inputs::public_context_inputs::PublicContextInputs; - - #[test] - fn contract_has_public_dispatch() { - let selector = 0; - let inputs = PublicContextInputs::empty(); - AvmTest::public_dispatch(inputs, selector); - } -} +} \ No newline at end of file From 4be2c498e53fc8f00870e2eaa1d78c2731fcfad8 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Wed, 25 Sep 2024 10:02:28 -0300 Subject: [PATCH 09/14] Make function #[public] --- noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr | 1 + 1 file changed, 1 insertion(+) diff --git a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr index 43b32066432..df18a05469b 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr @@ -66,6 +66,7 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let dispatch = ifs.join(quote { else }); quote { + #[public] pub fn public_dispatch(selector: Field) { $dispatch } From b29ab8b6b4d726555512f4bea7a8e1f9d7a76082 Mon Sep 17 00:00:00 2001 From: fcarreiro Date: Wed, 25 Sep 2024 15:02:44 +0000 Subject: [PATCH 10/14] add 2 avm simulator tests --- .../simulator/src/avm/avm_simulator.test.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index a5dac0b3035..4427267a8c8 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -90,6 +90,33 @@ describe('AVM simulator: injected bytecode', () => { }); describe('AVM simulator: transpiled Noir contracts', () => { + it('addition via dispatch', async () => { + const calldata: Fr[] = [ + FunctionSelector.fromSignature('add_args_return(Field,Field)').toField(), + new Fr(1), + new Fr(2), + ]; + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + + const bytecode = getAvmTestContractBytecode('public_dispatch'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); + + expect(results.reverted).toBe(false); + expect(results.output).toEqual([new Fr(3)]); + }); + + it('get_args_hash via dispatch', async () => { + const calldata = [new Fr(1), new Fr(2), new Fr(3)]; + const dispatchCalldata = [FunctionSelector.fromSignature('get_args_hash(u8,[Field;3])').toField(), ...calldata]; + + const context = initContext({ env: initExecutionEnvironment({ calldata: dispatchCalldata }) }); + const bytecode = getAvmTestContractBytecode('public_dispatch'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); + + expect(results.reverted).toBe(false); + expect(results.output).toEqual([computeVarArgsHash(calldata)]); + }); + it('addition', async () => { const calldata: Fr[] = [new Fr(1), new Fr(2)]; const context = initContext({ env: initExecutionEnvironment({ calldata }) }); From a92c01f1b5788f9a72d0c89b5cae7c35ab57a64a Mon Sep 17 00:00:00 2001 From: fcarreiro Date: Wed, 25 Sep 2024 15:32:59 +0000 Subject: [PATCH 11/14] no need for #[public] --- avm-transpiler/src/transpile_contract.rs | 4 +++- noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr | 7 +++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/avm-transpiler/src/transpile_contract.rs b/avm-transpiler/src/transpile_contract.rs index 542baef9cc7..12f5e5ce089 100644 --- a/avm-transpiler/src/transpile_contract.rs +++ b/avm-transpiler/src/transpile_contract.rs @@ -92,7 +92,9 @@ impl From for TranspiledContractArtifact { for function in contract.functions { // TODO(4269): once functions are tagged for transpilation to AVM, check tag - if function.custom_attributes.contains(&"public".to_string()) { + if function.custom_attributes.contains(&"public".to_string()) + || function.name == "public_dispatch" + { info!("Transpiling AVM function {} on contract {}", function.name, contract.name); // Extract Brillig Opcodes from acir let acir_program = function.bytecode; diff --git a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr index df18a05469b..ca86673b049 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr @@ -42,13 +42,13 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let return_code = if return_type == unit { quote { $call } } else { - quote { + quote { let return_value = dep::aztec::protocol_types::traits::Serialize::serialize($call); dep::aztec::context::public_context::avm_return(return_value); } }; - let if_ = quote { + let if_ = quote { if selector == $selector { $read $return_code @@ -66,8 +66,7 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let dispatch = ifs.join(quote { else }); quote { - #[public] - pub fn public_dispatch(selector: Field) { + unconstrained pub fn public_dispatch(selector: Field) { $dispatch } } From 3a17afb23c3d405c737c6a236d6e9237339a0fd0 Mon Sep 17 00:00:00 2001 From: fcarreiro Date: Wed, 25 Sep 2024 15:57:47 +0000 Subject: [PATCH 12/14] fix test --- yarn-project/simulator/src/avm/avm_simulator.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 4427267a8c8..7261fcd56a7 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -106,7 +106,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { }); it('get_args_hash via dispatch', async () => { - const calldata = [new Fr(1), new Fr(2), new Fr(3)]; + const calldata = [new Fr(8), new Fr(1), new Fr(2), new Fr(3)]; const dispatchCalldata = [FunctionSelector.fromSignature('get_args_hash(u8,[Field;3])').toField(), ...calldata]; const context = initContext({ env: initExecutionEnvironment({ calldata: dispatchCalldata }) }); From 3058e5987d9f636a92eafafc1ff4b6e6de4adc8e Mon Sep 17 00:00:00 2001 From: fcarreiro Date: Wed, 25 Sep 2024 16:44:49 +0000 Subject: [PATCH 13/14] disable get_args_hash test --- yarn-project/simulator/src/avm/avm_simulator.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 7261fcd56a7..c816a56a855 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -105,7 +105,8 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(results.output).toEqual([new Fr(3)]); }); - it('get_args_hash via dispatch', async () => { + // This will not work with the current impl of args hash. + it.skip('get_args_hash via dispatch', async () => { const calldata = [new Fr(8), new Fr(1), new Fr(2), new Fr(3)]; const dispatchCalldata = [FunctionSelector.fromSignature('get_args_hash(u8,[Field;3])').toField(), ...calldata]; @@ -114,7 +115,9 @@ describe('AVM simulator: transpiled Noir contracts', () => { const results = await new AvmSimulator(context).executeBytecode(bytecode); expect(results.reverted).toBe(false); - expect(results.output).toEqual([computeVarArgsHash(calldata)]); + // It is expected that the output is the hash of the DISPATCH calldata. + // This is ok, especially for authwit. + expect(results.output).toEqual([computeVarArgsHash(dispatchCalldata)]); }); it('addition', async () => { From d2dc8a9fb9c0c0fa6ac897bac168ce9863ba4b45 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Wed, 25 Sep 2024 16:51:58 -0300 Subject: [PATCH 14/14] Use FromCalldata --- .../aztec-nr/aztec/src/macros/dispatch/mod.nr | 40 +++- .../crates/types/src/address/aztec_address.nr | 5 +- .../crates/types/src/address/eth_address.nr | 3 +- .../crates/types/src/traits.nr | 198 ++++++++++++++++++ 4 files changed, 234 insertions(+), 12 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr index ca86673b049..157a18d883d 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr @@ -15,15 +15,32 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let selector: Field = compute_fn_selector(function); - let mut parameter_index = 0; - let mut offset = 1; // 1 for the Field selector + let param_sizes = parameters.map(|param: (Quoted, Type)| { + size_in_fields(param.1) + }); + + let mut parameters_size = 0; + for param_size in param_sizes { + parameters_size += param_size; + } + + let initial_read = if parameters.len() == 0 { + quote {} + } else { + // The initial calldata_copy offset is 1 to skip the Field selector + quote { + let input_calldata = dep::aztec::context::public_context::calldata_copy(1, $parameters_size); + } + }; - let reads = parameters.map(|param: (Quoted, Type)| { - // Skip the `PublicContextInputs` argument as we already have that - let param_type = param.1; - let param_size = size_in_fields(param_type); + let mut parameter_index = 0; + let mut offset = 0; + let reads = parameters.map(|_: (Quoted, Type)| { + let param_size = param_sizes[parameter_index]; let param_name = f"arg{parameter_index}".quoted_contents(); - let read = quote { let $param_name = dep::aztec::protocol_types::traits::Deserialize::deserialize(dep::aztec::context::public_context::calldata_copy($offset, $param_size)); }; + let read = quote { + let ($param_name, _) = dep::aztec::protocol_types::traits::FromCalldata::from_calldata(input_calldata, $offset); + }; offset += param_size; parameter_index += 1; quote { $read } @@ -50,6 +67,7 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let if_ = quote { if selector == $selector { + $initial_read $read $return_code } @@ -65,11 +83,15 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted { let ifs = ifs.push_back(quote { { panic(f"Unknown selector") } }); let dispatch = ifs.join(quote { else }); - quote { + let body = quote { unconstrained pub fn public_dispatch(selector: Field) { $dispatch } - } + }; + + println(body); + + body } } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/address/aztec_address.nr b/noir-projects/noir-protocol-circuits/crates/types/src/address/aztec_address.nr index 06605d2c553..dffc2b2d7f0 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/address/aztec_address.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/address/aztec_address.nr @@ -1,11 +1,12 @@ use crate::{ crate::address::{partial_address::PartialAddress, public_keys_hash::PublicKeysHash}, constants::{AZTEC_ADDRESS_LENGTH, GENERATOR_INDEX__CONTRACT_ADDRESS_V1}, - hash::poseidon2_hash_with_separator, traits::{Empty, FromField, ToField, Serialize, Deserialize}, - utils + hash::poseidon2_hash_with_separator, + traits::{Empty, FromField, ToField, Serialize, Deserialize, FromCalldata}, utils }; // Aztec address +#[derive(FromCalldata)] pub struct AztecAddress { inner : Field } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/address/eth_address.nr b/noir-projects/noir-protocol-circuits/crates/types/src/address/eth_address.nr index 79e90bd2c00..26e309d95a2 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/address/eth_address.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/address/eth_address.nr @@ -1,5 +1,6 @@ -use crate::{constants::ETH_ADDRESS_LENGTH, traits::{Empty, ToField, Serialize, Deserialize}, utils}; +use crate::{constants::ETH_ADDRESS_LENGTH, traits::{Empty, ToField, Serialize, Deserialize, FromCalldata}, utils}; +#[derive(FromCalldata)] pub struct EthAddress{ inner : Field } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr b/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr index 8b287d4177a..c3a58b87d69 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr @@ -184,3 +184,201 @@ impl Deserialize for [Field; N] { fields } } + +/// A trait for types that can be deserialized from a calldata field array +#[derive_via(derive_from_calldata)] +pub trait FromCalldata { + /// Deserializes Self by reading it from calldata at the given index, also returning the next index to read from + fn from_calldata(calldata: [Field; N], index: Field) -> (Self, Field); +} + +pub comptime fn derive_from_calldata(s: StructDefinition) -> Quoted { + let typ = s.as_type(); + + let impl_generics = s.generics().map(|g| quote { $g }); + let impl_generics = impl_generics.push_front(quote { let N: u32 }); + let impl_generics = impl_generics.join(quote {,}); + + let where_clause = s.generics().map(|name| quote { $name: FromCalldata }).join(quote {,}); + + let names = s.fields().map(|f: (Quoted, Type)| f.0); + if names.len() == 0 { + quote { + impl FromCalldata for $typ { + fn from_calldata(_calldata: [Field; N], index: Field) -> (Self, Field) { + (Self {}, index) + } + } + } + } else { + let statements = names.map( + |name: Quoted| { + quote { + let ($name, index) = FromCalldata::from_calldata(calldata, index); + } + } + ); + let self_names = names.join(quote { , }); + let statements = statements.join(quote { }); + quote { + impl <$impl_generics> FromCalldata for $typ where $where_clause { + fn from_calldata(calldata: [Field; N], index: Field) -> (Self, Field) { + $statements + (Self { $self_names }, index) + } + } + } + } +} + +impl FromCalldata for Field { + fn from_calldata(calldata: [Field; N], index: Field) -> (Self, Field) { + let value = calldata[index]; + (value, index + 1) + } +} + +impl FromCalldata for bool { + fn from_calldata(calldata: [Field; N], index: Field) -> (Self, Field) { + let value = calldata[index] as bool; + (value, index + 1) + } +} + +impl FromCalldata for u1 { + fn from_calldata(calldata: [Field; N], index: Field) -> (Self, Field) { + let value = calldata[index] as u1; + (value, index + 1) + } +} + +impl FromCalldata for u8 { + fn from_calldata(calldata: [Field; N], index: Field) -> (Self, Field) { + let value = calldata[index] as u8; + (value, index + 1) + } +} + +impl FromCalldata for u16 { + fn from_calldata(calldata: [Field; N], index: Field) -> (Self, Field) { + let value = calldata[index] as u16; + (value, index + 1) + } +} + +impl FromCalldata for u32 { + fn from_calldata(calldata: [Field; N], index: Field) -> (Self, Field) { + let value = calldata[index] as u32; + (value, index + 1) + } +} + +impl FromCalldata for u64 { + fn from_calldata(calldata: [Field; N], index: Field) -> (Self, Field) { + let value = calldata[index] as u64; + (value, index + 1) + } +} + +impl FromCalldata for i8 { + fn from_calldata(calldata: [Field; N], index: Field) -> (Self, Field) { + let value = calldata[index] as i8; + (value, index + 1) + } +} + +impl FromCalldata for i16 { + fn from_calldata(calldata: [Field; N], index: Field) -> (Self, Field) { + let value = calldata[index] as i16; + (value, index + 1) + } +} + +impl FromCalldata for i32 { + fn from_calldata(calldata: [Field; N], index: Field) -> (Self, Field) { + let value = calldata[index] as i32; + (value, index + 1) + } +} + +impl FromCalldata for i64 { + fn from_calldata(calldata: [Field; N], index: Field) -> (Self, Field) { + let value = calldata[index] as i64; + (value, index + 1) + } +} + +impl FromCalldata for U128 { + fn from_calldata(calldata: [Field; N], index: Field) -> (Self, Field) { + let input = [calldata[index]]; + (U128::deserialize(input), index + 1) + } +} + +impl FromCalldata for [T; M] where T: FromCalldata { + fn from_calldata(calldata: [Field; N], mut index: Field) -> (Self, Field) { + let mut values = [std::mem::zeroed(); M]; + for loop_index in 0..M { + let (value, new_index) = FromCalldata::from_calldata(calldata, index); + values[loop_index] = value; + index = new_index; + } + (values, index) + } +} + +mod tests { + use super::FromCalldata; + + #[derive(FromCalldata)] + struct Point { + x: T, + y: T, + } + + #[test] + fn test_field_from_call_data() { + let calldata = [1]; + let (value, index): (Field, Field) = FromCalldata::from_calldata(calldata, 0); + assert_eq(value, 1); + assert_eq(index, 1); + } + + #[test] + fn test_bool_from_call_data() { + let calldata = [1]; + let (value, index): (bool, Field) = FromCalldata::from_calldata(calldata, 0); + assert_eq(value, true); + assert_eq(index, 1); + + let calldata = [0]; + let (value, index): (bool, Field) = FromCalldata::from_calldata(calldata, 0); + assert_eq(value, false); + assert_eq(index, 1); + } + + #[test] + fn test_u8_from_call_data() { + let calldata = [1]; + let (value, index): (u8, Field) = FromCalldata::from_calldata(calldata, 0); + assert_eq(value, 1); + assert_eq(index, 1); + } + + #[test] + fn test_struct_from_call_data() { + let calldata = [1, 2]; + let (value, index): (Point, Field) = FromCalldata::from_calldata(calldata, 0); + assert_eq(value.x, 1); + assert_eq(value.y, 2); + assert_eq(index, 2); + } + + #[test] + fn test_array_from_call_data() { + let calldata = [1, 2, 3]; + let (value, index): ([Field; 3], Field) = FromCalldata::from_calldata(calldata, 0); + assert_eq(value, [1, 2, 3]); + assert_eq(index, 3); + } +}