diff --git a/cspell.json b/cspell.json index 3ed9bc44cf4..0002201c734 100644 --- a/cspell.json +++ b/cspell.json @@ -175,6 +175,7 @@ "noirc", "noirup", "nullifer", + "Nullifiable", "offchain", "onchain", "opentelemetry", diff --git a/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr index 5d8aa114b09..61353de3916 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr @@ -19,19 +19,19 @@ comptime global NOTE_HEADER_TYPE: Type = type_of(NoteHeader::empty()); pub comptime mut global NOTES: UHashMap> = UHashMap::default(); -/// Computes a note type id by hashing a note name (e.g. `TokenNote`), getting the first 4 bytes of the hash -/// and returning it as a `Field`. -comptime fn compute_note_type_id(name: Quoted) -> Field { - let (name_as_str_quote, _) = name.as_str_quote(); +pub comptime mut global NOTE_TYPE_ID_COUNTER: u32 = 0; + +/// The note type id is set by enumerating the note types. +comptime fn get_next_note_type_id() -> Field { + // We assert that the note type id fits within 7 bits + assert( + NOTE_TYPE_ID_COUNTER < 128 as u32, + "A contract can contain at most 128 different note types", + ); - unquote!( - quote { - let bytes = $name_as_str_quote.as_bytes(); - let hash = protocol_types::hash::poseidon2_hash_bytes(bytes); - let hash_bytes = hash.to_be_bytes::<4>(); - protocol_types::utils::field::field_from_bytes(hash_bytes, true) - }, - ) + let note_type_id = NOTE_TYPE_ID_COUNTER as Field; + NOTE_TYPE_ID_COUNTER += 1; + note_type_id } /// Generates default `NoteInterface` implementation for a given note struct `s` and returns it as quote along with @@ -123,6 +123,7 @@ comptime fn generate_note_interface( let mut buffer: [u8; $content_len * 32 + 64] = [0; $content_len * 32 + 64]; let storage_slot_bytes: [u8; 32] = storage_slot.to_be_bytes(); + // TODO(#10952): The following can be reduced to 7 bits let note_type_id_bytes: [u8; 32] = $name::get_note_type_id().to_be_bytes(); for i in 0..32 { @@ -265,7 +266,7 @@ pub(crate) comptime fn generate_note_export( let mut hasher = Poseidon2Hasher::default(); s.as_type().hash(&mut hasher); let hash = hasher.finish() as u32; - let global_export_name = f"{name}_{hash}_EXPORTS".quoted_contents(); + let global_export_name = f"{name}_EXPORTS_{hash}".quoted_contents(); let note_fields_name = f"{name}Fields_{hash}".quoted_contents(); let (note_name_as_str, _) = name.as_str_quote(); let note_name_str_len = unquote!(quote { $note_name_as_str.as_bytes().len() }); @@ -871,7 +872,7 @@ pub comptime fn partial_note(s: StructDefinition, nullable_fields: [Quoted]) -> inject_note_header(s); let note_properties = generate_note_properties(s); - let note_type_id = compute_note_type_id(s.name()); + let note_type_id = get_next_note_type_id(); let (setup_payload_impl, setup_payload_name) = generate_setup_payload(s, indexed_fixed_fields, indexed_nullable_fields); let (finalization_payload_impl, finalization_payload_name) = @@ -914,7 +915,7 @@ pub comptime fn note(s: StructDefinition) -> Quoted { inject_note_header(s); let note_properties = generate_note_properties(s); - let note_type_id = compute_note_type_id(s.name()); + let note_type_id = get_next_note_type_id(); let (note_interface_impl, note_serialized_len) = generate_note_interface( s, note_type_id, @@ -944,7 +945,7 @@ pub comptime fn note_custom_interface(s: StructDefinition) -> Quoted { inject_note_header(s); let note_properties = generate_note_properties(s); - let note_type_id = compute_note_type_id(s.name()); + let note_type_id = get_next_note_type_id(); let serialized_len_type = fresh_type_variable(); let note_interface_impl = s.as_type().get_trait_impl( quote { crate::note::note_interface::NoteInterface<$serialized_len_type> } diff --git a/noir-projects/aztec-nr/aztec/src/macros/storage/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/storage/mod.nr index 77abd9e2517..40c22dae0a5 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/storage/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/storage/mod.nr @@ -3,6 +3,8 @@ use std::{collections::umap::UHashMap, hash::{BuildHasherDefault, poseidon2::Pos use super::utils::get_serialized_size; use super::utils::is_note; +/// Stores a map from a module to the name of the struct that describes its storage layout. +/// This is then used when generating a `storage_layout()` getter on the contract struct. pub comptime mut global STORAGE_LAYOUT_NAME: UHashMap> = UHashMap::default(); diff --git a/noir-projects/aztec-nr/aztec/src/note/note_type_id.nr b/noir-projects/aztec-nr/aztec/src/note/note_type_id.nr deleted file mode 100644 index 8b137891791..00000000000 --- a/noir-projects/aztec-nr/aztec/src/note/note_type_id.nr +++ /dev/null @@ -1 +0,0 @@ - diff --git a/noir-projects/noir-contracts/contracts/test_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/test_contract/Nargo.toml index 8ddc423b451..3a38c679cbb 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/Nargo.toml +++ b/noir-projects/noir-contracts/contracts/test_contract/Nargo.toml @@ -7,4 +7,5 @@ type = "contract" [dependencies] aztec = { path = "../../../aztec-nr/aztec" } value_note = { path = "../../../aztec-nr/value-note" } +uint_note = { path = "../../../aztec-nr/uint-note" } token_portal_content_hash_lib = { path = "../token_portal_content_hash_lib" } diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr index ed3918d1f68..2b9cd89fca9 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr @@ -1,3 +1,4 @@ +mod test; mod test_note; // A contract used for testing a random hodgepodge of small features from simulator and end-to-end tests. @@ -5,6 +6,8 @@ use dep::aztec::macros::aztec; #[aztec] contract Test { + // Note: If you import a new kind of note you will most likely need to update the test_note_type_id test + // as the note type ids of current notes might have changed. use dep::aztec::encrypted_logs::encrypted_event_emission::encode_and_encrypt_event_unconstrained; use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note; @@ -42,6 +45,10 @@ contract Test { use std::meta::derive; use crate::test_note::TestNote; + + // Imported just to test note type ids + use dep::uint_note::uint_note::UintNote; + #[derive(Serialize)] #[event] struct ExampleEvent { diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/test.nr b/noir-projects/noir-contracts/contracts/test_contract/src/test.nr new file mode 100644 index 00000000000..19d2139799a --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test_contract/src/test.nr @@ -0,0 +1,12 @@ +use crate::test_note::TestNote; +use dep::uint_note::uint_note::UintNote; +use dep::value_note::value_note::ValueNote; + +#[test] +unconstrained fn test_note_type_id() { + // I don't really know why the ids are assigned in this way, but the important thing is that they are sequential + // and start from 0. + assert_eq(UintNote::get_note_type_id(), 0, "UintNote type id should be 0"); + assert_eq(ValueNote::get_note_type_id(), 1, "ValueNote type id should be 1"); + assert_eq(TestNote::get_note_type_id(), 2, "TestNote type id should be 2"); +} diff --git a/yarn-project/circuit-types/src/interfaces/pxe.ts b/yarn-project/circuit-types/src/interfaces/pxe.ts index d70216ef15c..13fd3dbe8b1 100644 --- a/yarn-project/circuit-types/src/interfaces/pxe.ts +++ b/yarn-project/circuit-types/src/interfaces/pxe.ts @@ -388,7 +388,7 @@ export interface PXE { isContractInitialized(address: AztecAddress): Promise; /** - * Returns the enctypred events given search parameters. + * Returns the encrypted events given search parameters. * @param eventMetadata - Metadata of the event. This should be the class generated from the contract. e.g. Contract.events.Event * @param from - The block number to search from. * @param limit - The amount of blocks to search. diff --git a/yarn-project/foundation/src/abi/note_selector.ts b/yarn-project/foundation/src/abi/note_selector.ts index 10f784620f1..d02e0601a8d 100644 --- a/yarn-project/foundation/src/abi/note_selector.ts +++ b/yarn-project/foundation/src/abi/note_selector.ts @@ -1,6 +1,6 @@ import { toBigIntBE } from '../bigint-buffer/index.js'; -import { randomBytes } from '../crypto/index.js'; -import { type Fr } from '../fields/fields.js'; +import { randomInt } from '../crypto/index.js'; +import { Fr } from '../fields/fields.js'; import { hexSchemaFor } from '../schemas/utils.js'; import { BufferReader } from '../serialize/buffer_reader.js'; import { TypeRegistry } from '../serialize/type_registry.js'; @@ -14,7 +14,10 @@ export interface NoteSelector { _branding: 'NoteSelector'; } -/** A note selector is the first 4 bytes of the hash of a note signature. */ +/** + * A note selector is a 7 bit long value that identifies a note type within a contract. + * TODO(#10952): Encoding of note type id can be reduced to 7 bits. + */ export class NoteSelector extends Selector { /** * Deserializes from a buffer or reader, corresponding to a write in cpp. @@ -24,6 +27,9 @@ export class NoteSelector extends Selector { static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); const value = Number(toBigIntBE(reader.readBytes(Selector.SIZE))); + if (value >= 1 << 7) { + throw new Error('Invalid note selector'); + } return new NoteSelector(value); } @@ -55,7 +61,8 @@ export class NoteSelector extends Selector { * @returns A random selector. */ static random() { - return NoteSelector.fromBuffer(randomBytes(Selector.SIZE)); + const value = randomInt(1 << 7); + return NoteSelector.fromField(new Fr(value)); } toJSON() {