diff --git a/avm-transpiler/src/transpile_contract.rs b/avm-transpiler/src/transpile_contract.rs index 542baef9cc72..10b9367a4abd 100644 --- a/avm-transpiler/src/transpile_contract.rs +++ b/avm-transpiler/src/transpile_contract.rs @@ -91,7 +91,6 @@ impl From for TranspiledContractArtifact { let mut functions: Vec = Vec::new(); 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()) { info!("Transpiling AVM function {} on contract {}", function.name, contract.name); // Extract Brillig Opcodes from acir diff --git a/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr new file mode 100644 index 000000000000..f7e962241306 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/macros/dispatch/mod.nr @@ -0,0 +1,154 @@ +use super::utils::compute_fn_selector; + +/// 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")); + + 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 selector: Field = compute_fn_selector(function); + + let mut parameters_size = 0; + for param in parameters { + parameters_size += size_in_fields(param.1); + } + + let initial_read = if parameters.len() == 0 { + quote {} + } else { + // The initial calldata_copy offset is 1 to skip the Field selector + // The expected calldata is the serialization of + // - FunctionSelector: the selector of the function intended to dispatch + // - Parameters: the parameters of the function intended to dispatch + // That is, exactly what is expected for a call to the target function, + // but with a selector added at the beginning. + quote { + let input_calldata: [Field; $parameters_size] = dep::aztec::context::public_context::calldata_copy(1, $parameters_size); + let mut reader = dep::aztec::protocol_types::utils::reader::Reader::new(input_calldata); + } + }; + + let mut parameter_index = 0; + let reads = parameters.map(|param: (Quoted, Type)| { + let param_name = f"arg{parameter_index}".quoted_contents(); + let param_type = param.1; + let read = quote { + let $param_name: $param_type = reader.read_struct(dep::aztec::protocol_types::traits::Deserialize::deserialize); + }; + parameter_index += 1; + quote { $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; + // Force early return. + dep::aztec::context::public_context::avm_return([]); + } + } 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 { + $initial_read + $read + $return_code + } + }; + if_ + } + ); + + 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 { }); + + let body = quote { + // We mark this as public because our whole system depends on public + // functions having this attribute. However, the public MACRO will + // handle the public_dispatch function specially and do nothing. + #[public] + unconstrained pub fn public_dispatch(selector: Field) { + $dispatch + } + }; + + body + } +} + +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/functions/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr index 2b5768278819..1fc42fbab76b 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr @@ -225,6 +225,15 @@ pub comptime fn private(f: FunctionDefinition) -> Quoted { /// Public functions are executed sequencer-side and do not preserve privacy, similar to the EVM. pub comptime fn public(f: FunctionDefinition) -> Quoted { + // We don't want to transform the public_dispatch function. + if f.name() == quote { public_dispatch } { + quote {} + } else { + transform_public(f) + } +} + +comptime fn transform_public(f: FunctionDefinition) -> Quoted { let fn_abi = create_fn_abi_export(f); let fn_stub = stub_fn(f); register_stub(f.module(), fn_stub); diff --git a/noir-projects/aztec-nr/aztec/src/macros/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/mod.nr index 23da22f2d1d5..df5a1530f12f 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 dispatch; 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 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. 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_dispatch = generate_public_dispatch(m); quote { $note_exports $interface $compute_note_hash_and_optionally_a_nullifier + $public_dispatch } } 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 8b287d4177ab..60a3c6031b30 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr @@ -155,12 +155,6 @@ pub trait Serialize { } // docs:end:serialize -impl Serialize for [Field; N] { - fn serialize(self) -> [Field; N] { - self - } -} - impl Serialize for str { fn serialize(self) -> [Field; N] { let bytes = self.as_bytes(); @@ -179,8 +173,8 @@ pub trait Deserialize { } // docs:end:deserialize -impl Deserialize for [Field; N] { +impl Deserialize for str { fn deserialize(fields: [Field; N]) -> Self { - fields + str::from(fields.map(|value| value as u8)) } } 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 ac113fe2f794..59d9fad56280 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 @@ -20,7 +20,7 @@ impl Deserialize for bool { } impl Serialize for u8 { - fn serialize(self) -> [Field; U32_SERIALIZED_LEN] { + fn serialize(self) -> [Field; U8_SERIALIZED_LEN] { [self as Field] } } @@ -56,7 +56,7 @@ impl Deserialize for u64 { } impl Serialize for U128 { - fn serialize(self) -> [Field; 1] { + fn serialize(self) -> [Field; U128_SERIALIZED_LEN] { [self.to_integer()] } } @@ -68,7 +68,7 @@ impl Deserialize for U128 { } impl Serialize for Field { - fn serialize(self) -> [Field; U32_SERIALIZED_LEN] { + fn serialize(self) -> [Field; FIELD_SERIALIZED_LEN] { [self] } } @@ -78,3 +78,25 @@ impl Deserialize for Field { fields[0] } } + +impl Serialize for [T; N] where T: Serialize { + fn serialize(self) -> [Field; N * M] { + let mut result: [Field; N * M] = std::mem::zeroed(); + let mut serialized: [Field; M] = std::mem::zeroed(); + for i in 0..N { + serialized = self[i].serialize(); + for j in 0..M { + result[i * M + j] = serialized[j]; + } + } + result + } +} + +impl Deserialize for [T; N] where T: Deserialize { + fn deserialize(fields: [Field; N * M]) -> Self { + let mut reader = crate::utils::reader::Reader::new(fields); + let mut result: [T; N] = std::mem::zeroed(); + reader.read_struct_array::(Deserialize::deserialize, result) + } +}