diff --git a/docs/docs/aztec/glossary/call_types.md b/docs/docs/aztec/glossary/call_types.md index 0ecb95dbd6c..715a2f2510f 100644 --- a/docs/docs/aztec/glossary/call_types.md +++ b/docs/docs/aztec/glossary/call_types.md @@ -140,9 +140,11 @@ This is the same function that was called by privately enqueuing a call to it! P ### Top-level Unconstrained -Contract functions with the `unconstrained` Noir keyword are a special type of function still under development, and their semantics will likely change in the near future. They are used to perform state queries from an off-chain client, and are never included in any transaction. No guarantees are made on the correctness of the result since they rely exclusively on unconstrained oracle calls. +Contract functions with the `unconstrained` Noir keyword are a special type of function still under development, and their semantics will likely change in the near future. They are used to perform state queries from an off-chain client (from both private and public state!), and are never included in any transaction. No guarantees are made on the correctness of the result since the entire execution is unconstrained and heavily reliant on oracle calls. -A reasonable mental model for them is that of a `view` Solidity function that is never called in any transaction, and is only ever invoked via `eth_call`. Note that in these the caller assumes that the node is acting honestly by exectuing the true contract bytecode with correct blockchain state, the same way the Aztec version assumes the oracles are returning legitimate data. +Any programming language could be used to construct these queries, since all they do is perform arbitrary computation on data that is either publicly available from any node, or locally available from the PXE. Top-level unconstrained functions exist because they let developers utilize the rest of the contract code directly by being part of the same Noir contract, and e.g. use the same libraries, structs, etc. instead of having to rely on manual computation of storage slots, struct layout and padding, and so on. + +A reasonable mental model for them is that of a Solidity `view` function that can never be called in any transaction, and is only ever invoked via `eth_call`. Note that in these the caller assumes that the node is acting honestly by executing the true contract bytecode with correct blockchain state, the same way the Aztec version assumes the oracles are returning legitimate data. ### aztec.js diff --git a/docs/docs/guides/smart_contracts/writing_contracts/common_patterns/index.md b/docs/docs/guides/smart_contracts/writing_contracts/common_patterns/index.md index 70490891197..d0d139c38e2 100644 --- a/docs/docs/guides/smart_contracts/writing_contracts/common_patterns/index.md +++ b/docs/docs/guides/smart_contracts/writing_contracts/common_patterns/index.md @@ -45,6 +45,7 @@ You can't read public storage in private domain. But nevertheless reading public 1. You pass the data as a parameter to your private method and later assert in public that the data is correct. E.g.: ```rust +#[aztec(storage)] struct Storage { token: PublicMutable, } diff --git a/docs/docs/migration_notes.md b/docs/docs/migration_notes.md index 2bdfb23618c..2186fdc0d4d 100644 --- a/docs/docs/migration_notes.md +++ b/docs/docs/migration_notes.md @@ -8,6 +8,57 @@ Aztec is in full-speed development. Literally every version breaks compatibility ## 0.41.0 + + +### [Aztec.nr] State variable rework + +Aztec.nr state variables have been reworked so that calling private functions in public and vice versa is detected as an error during compilation instead of at runtime. This affects users in a number of ways: + +#### New compile time errors + +It used to be that calling a state variable method only available in public from a private function resulted in obscure runtime errors in the form of a failed `_is_some` assertion. + +Incorrect usage of the state variable methods now results in compile time errors. For example, given the following function: + +```rust +#[aztec(public)] +fn get_decimals() -> pub u8 { + storage.decimals.read_private() +} +``` + +The compiler will now error out with +``` +Expected type SharedImmutable<_, &mut PrivateContext>, found type SharedImmutable +``` + +The key component is the second generic parameter: the compiler expects a `PrivateContext` (becuse `read_private` is only available during private execution), but a `PublicContext` is being used instead (because of the `#[aztec(public)]` attribute). + +#### Generic parameters in `Storage` + +The `Storage` struct (the one marked with `#[aztec(storage)]`) should now be generic over a `Context` type, which matches the new generic parameter of all Aztec.nr libraries. This parameter is always the last generic parameter. + +This means that, without any additional features, we'd end up with some extra boilerplate when declaring this struct: + +```diff +#[aztec(storage)] +- struct Storage { ++ struct Storage { +- nonce_for_burn_approval: PublicMutable, ++ nonce_for_burn_approval: PublicMutable, +- portal_address: SharedImmutable, ++ portal_address: SharedImmutable, +- approved_action: Map>, ++ approved_action: Map, Context>, +} +``` + +Because of this, the `#[aztec(storage)]` macro has been updated to **automatically inject** this `Context` generic parameter. The storage declaration does not require any changes. + +#### Removal of `Context` + +The `Context` type no longer exists. End users typically didn't use it, but if imported it needs to be deleted. + ### [Aztec.nr] View functions and interface navigation It is now possible to explicitly state a function doesn't perform any state alterations (including storage, logs, nullifiers and/or messages from L2 to L1) with the `#[aztec(view)]` attribute, similarly to solidity's `view` function modifier. diff --git a/docs/docs/reference/sandbox_reference/cheat_codes.md b/docs/docs/reference/sandbox_reference/cheat_codes.md index 493690c7466..00c408bda8d 100644 --- a/docs/docs/reference/sandbox_reference/cheat_codes.md +++ b/docs/docs/reference/sandbox_reference/cheat_codes.md @@ -528,7 +528,7 @@ Note: One Field element occupies a storage slot. Hence, structs with multiple fi #[aztec(storage)] struct Storage { ... - pending_shields: Set, + pending_shields: PrivateSet, } contract Token { diff --git a/docs/docs/reference/smart_contract_reference/storage/index.md b/docs/docs/reference/smart_contract_reference/storage/index.md index 8e8cfb0472d..3a9a3fee788 100644 --- a/docs/docs/reference/smart_contract_reference/storage/index.md +++ b/docs/docs/reference/smart_contract_reference/storage/index.md @@ -9,7 +9,7 @@ To learn more about storage slots, read [this explainer](/guides/smart_contracts You control this storage in Aztec using a struct annotated with `#[aztec(storage)]`. This struct serves as the housing unit for all your smart contract's state variables - the data it needs to keep track of and maintain. -These state variables come in two forms: public and private. Public variables are visible to anyone, and private variables remain hidden within the contract. +These state variables come in two forms: [public](./public_state.md) and [private](./private_state.md). Public variables are visible to anyone, and private variables remain hidden within the contract. A state variable with both public and private components is said to be [shared](./shared_state.md). Aztec.nr has a few abstractions to help define the type of data your contract holds. These include PrivateMutable, PrivateImmutable, PublicMutable, PrivateSet, and SharedImmutable. @@ -22,22 +22,32 @@ On this and the following pages in this section, you’ll learn: - Practical implications of Storage in real smart contracts In an Aztec.nr contract, storage is to be defined as a single struct, that contains both public and private state variables. -## Public and private state variables +## The `Context` parameter -Public state variables can be read by anyone, while private state variables can only be read by their owner (or people whom the owner has shared the decrypted data or note viewing key with). +Aztec contracts have three different modes of execution: [private](../../../aztec/glossary/call_types.md#private-execution), [public](../../../aztec/glossary/call_types.md#public-execution) and [top-level unconstrained](../../../aztec/glossary/call_types.md#top-level-unconstrained). How storage is accessed depends on the execution mode: for example, `PublicImmutable` can be read in all execution modes but only initialized in public, while `PrivateMutable` is entirely unavailable in public. -Public state follows the Ethereum style account model, where each contract has its own key-value datastore. Private state follows a UTXO model, where note contents (/aztec/aztec/concepts/state_model/index.md) and [private/public execution](/aztec/concepts/smart_contracts/communication/public_private_calls.md)) for more background. +Aztec.nr prevents developers from calling functions unavailable in the current execution mode via the `context` variable that is injected into all contract functions. Its type indicates the current execution mode: + - `&mut PrivateContext` for private execution + - `&mut PublicContext` for public execution + - `()` for unconstrained -## Storage struct +All state variables are generic over this `Context` type, and expose different methods in each execution mode. In the example above, `PublicImmutable`'s `initialize` function is only available with a public execution context, and so the following code results in a compilation error: ```rust #[aztec(storage)] struct Storage { - // public state variables - // private state variables + variable: PublicImmutable, +} + +#[aztec(private)] +fn some_private_function() { + storage.variable.initialize(0); + // ^ ERROR: Expected type PublicImmutable<_, &mut PublicContext>, found type PublicImmutable } ``` +The `Context` generic type parameter is not visible in the code above as it is automatically injected by the `#[aztec(storage)]` macro, in order to reduce boilerplate. Similarly, all state variables in that struct (e.g. `PublicImmutable`) similarly have that same type parameter automatically passed to them. + ## Map A `map` is a state variable that "maps" a key to a value. It can be used with private or public storage variables. diff --git a/noir-projects/aztec-nr/authwit/src/account.nr b/noir-projects/aztec-nr/authwit/src/account.nr index eb926315a65..33b519c7424 100644 --- a/noir-projects/aztec-nr/authwit/src/account.nr +++ b/noir-projects/aztec-nr/authwit/src/account.nr @@ -1,17 +1,17 @@ -use dep::aztec::context::{PrivateContext, PublicContext, Context}; +use dep::aztec::context::{PrivateContext, PublicContext}; use dep::aztec::state_vars::{Map, PublicMutable}; use dep::aztec::protocol_types::{address::AztecAddress, abis::function_selector::FunctionSelector, hash::pedersen_hash}; use crate::entrypoint::{app::AppPayload, fee::FeePayload}; use crate::auth::{IS_VALID_SELECTOR, compute_outer_authwit_hash}; -struct AccountActions { +struct AccountActions { context: Context, is_valid_impl: fn(&mut PrivateContext, Field) -> bool, - approved_action: Map>, + approved_action: Map, Context>, } -impl AccountActions { +impl AccountActions { pub fn init( context: Context, approved_action_storage_slot: Field, @@ -29,81 +29,58 @@ impl AccountActions { ) } } +} - pub fn private( - context: &mut PrivateContext, - approved_action_storage_slot: Field, - is_valid_impl: fn(&mut PrivateContext, Field) -> bool - ) -> Self { - AccountActions::init( - Context::private(context), - approved_action_storage_slot, - is_valid_impl - ) - } - - pub fn public( - context: &mut PublicContext, - approved_action_storage_slot: Field, - is_valid_impl: fn(&mut PrivateContext, Field) -> bool - ) -> Self { - AccountActions::init( - Context::public(context), - approved_action_storage_slot, - is_valid_impl - ) - } - +impl AccountActions<&mut PrivateContext> { // docs:start:entrypoint pub fn entrypoint(self, app_payload: AppPayload, fee_payload: FeePayload) { let valid_fn = self.is_valid_impl; - let mut private_context = self.context.private.unwrap(); let fee_hash = fee_payload.hash(); - assert(valid_fn(private_context, fee_hash)); - fee_payload.execute_calls(private_context); - private_context.end_setup(); + assert(valid_fn(self.context, fee_hash)); + fee_payload.execute_calls(self.context); + self.context.end_setup(); let app_hash = app_payload.hash(); - assert(valid_fn(private_context, app_hash)); - app_payload.execute_calls(private_context); + assert(valid_fn(self.context, app_hash)); + app_payload.execute_calls(self.context); } // docs:end:entrypoint // docs:start:spend_private_authwit pub fn spend_private_authwit(self, inner_hash: Field) -> Field { - let context = self.context.private.unwrap(); // The `inner_hash` is "siloed" with the `msg_sender` to ensure that only it can // consume the message. // This ensures that contracts cannot consume messages that are not intended for them. let message_hash = compute_outer_authwit_hash( - context.msg_sender(), - context.chain_id(), - context.version(), + self.context.msg_sender(), + self.context.chain_id(), + self.context.version(), inner_hash ); let valid_fn = self.is_valid_impl; - assert(valid_fn(context, message_hash) == true, "Message not authorized by account"); - context.push_new_nullifier(message_hash, 0); + assert(valid_fn(self.context, message_hash) == true, "Message not authorized by account"); + self.context.push_new_nullifier(message_hash, 0); IS_VALID_SELECTOR } // docs:end:spend_private_authwit +} +impl AccountActions<&mut PublicContext> { // docs:start:spend_public_authwit pub fn spend_public_authwit(self, inner_hash: Field) -> Field { - let context = self.context.public.unwrap(); // The `inner_hash` is "siloed" with the `msg_sender` to ensure that only it can // consume the message. // This ensures that contracts cannot consume messages that are not intended for them. let message_hash = compute_outer_authwit_hash( - context.msg_sender(), - context.chain_id(), - context.version(), + self.context.msg_sender(), + self.context.chain_id(), + self.context.version(), inner_hash ); let is_valid = self.approved_action.at(message_hash).read(); assert(is_valid == true, "Message not authorized by account"); - context.push_new_nullifier(message_hash, 0); + self.context.push_new_nullifier(message_hash, 0); IS_VALID_SELECTOR } // docs:end:spend_public_authwit diff --git a/noir-projects/aztec-nr/authwit/src/auth.nr b/noir-projects/aztec-nr/authwit/src/auth.nr index 55c15acec77..fa30e94a3fc 100644 --- a/noir-projects/aztec-nr/authwit/src/auth.nr +++ b/noir-projects/aztec-nr/authwit/src/auth.nr @@ -4,10 +4,7 @@ use dep::aztec::protocol_types::{ }; use dep::aztec::{ prelude::Deserialize, - context::{ - PrivateContext, PublicContext, Context, gas::GasOpts, - interface::{ContextInterface, PublicContextInterface} -}, + context::{PrivateContext, PublicContext, gas::GasOpts, interface::{ContextInterface, PublicContextInterface}}, hash::hash_args_array }; diff --git a/noir-projects/aztec-nr/aztec/src/context.nr b/noir-projects/aztec-nr/aztec/src/context.nr index 70992b0cea7..e194ce3643c 100644 --- a/noir-projects/aztec-nr/aztec/src/context.nr +++ b/noir-projects/aztec-nr/aztec/src/context.nr @@ -20,27 +20,3 @@ use private_context::PackedReturns; use public_context::PublicContext; use public_context::FunctionReturns; use avm_context::AvmContext; - -struct Context { - private: Option<&mut PrivateContext>, - public: Option<&mut PublicContext>, - avm: Option<&mut AvmContext>, -} - -impl Context { - pub fn private(context: &mut PrivateContext) -> Context { - Context { private: Option::some(context), public: Option::none(), avm: Option::none() } - } - - pub fn public(context: &mut PublicContext) -> Context { - Context { public: Option::some(context), private: Option::none(), avm: Option::none() } - } - - pub fn avm(context: &mut AvmContext) -> Context { - Context { avm: Option::some(context), public: Option::none(), private: Option::none() } - } - - pub fn none() -> Context { - Context { public: Option::none(), private: Option::none(), avm: Option::none() } - } -} diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/map.nr b/noir-projects/aztec-nr/aztec/src/state_vars/map.nr index c075578db3f..da09954a436 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/map.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/map.nr @@ -1,18 +1,17 @@ -use crate::context::{PrivateContext, PublicContext, Context}; use dep::protocol_types::{hash::pedersen_hash, traits::ToField}; use crate::state_vars::storage::Storage; // docs:start:map -struct Map { +struct Map { context: Context, storage_slot: Field, state_var_constructor: fn(Context, Field) -> V, } // docs:end:map -impl Storage for Map {} +impl Storage for Map {} -impl Map { +impl Map { // docs:start:new pub fn new( context: Context, diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr index 56c55a1384c..cbbb8d8a3b5 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr @@ -3,7 +3,7 @@ use dep::protocol_types::{ constants::GENERATOR_INDEX__INITIALIZATION_NULLIFIER, hash::pedersen_hash }; -use crate::context::{PrivateContext, Context}; +use crate::context::PrivateContext; use crate::note::{ lifecycle::create_note, note_getter::{get_note, view_notes}, note_interface::NoteInterface, note_viewer_options::NoteViewerOptions @@ -12,19 +12,19 @@ use crate::oracle::notes::check_nullifier_exists; use crate::state_vars::storage::Storage; // docs:start:struct -struct PrivateImmutable { - context: Option<&mut PrivateContext>, +struct PrivateImmutable { + context: Context, storage_slot: Field, } // docs:end:struct -impl Storage for PrivateImmutable {} +impl Storage for PrivateImmutable {} -impl PrivateImmutable { +impl PrivateImmutable { // docs:start:new pub fn new(context: Context, storage_slot: Field) -> Self { assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1."); - Self { context: context.private, storage_slot } + Self { context, storage_slot } } // docs:end:new @@ -40,14 +40,9 @@ impl PrivateImmutable { GENERATOR_INDEX__INITIALIZATION_NULLIFIER ) } +} - // docs:start:is_initialized - unconstrained pub fn is_initialized(self) -> bool { - let nullifier = self.compute_initialization_nullifier(); - check_nullifier_exists(nullifier) - } - // docs:end:is_initialized - +impl PrivateImmutable { // docs:start:initialize pub fn initialize( self, @@ -55,24 +50,31 @@ impl PrivateImmutable { broadcast: bool, ivpk_m: GrumpkinPoint ) where Note: NoteInterface { - let context = self.context.unwrap(); - // Nullify the storage slot. let nullifier = self.compute_initialization_nullifier(); - context.push_new_nullifier(nullifier, 0); + self.context.push_new_nullifier(nullifier, 0); - create_note(context, self.storage_slot, note, broadcast, ivpk_m); + create_note(self.context, self.storage_slot, note, broadcast, ivpk_m); } // docs:end:initialize // docs:start:get_note pub fn get_note(self) -> Note where Note: NoteInterface { - let context = self.context.unwrap(); let storage_slot = self.storage_slot; - get_note(context, storage_slot) + get_note(self.context, storage_slot) } // docs:end:get_note +} + +impl PrivateImmutable { + // docs:start:is_initialized + unconstrained pub fn is_initialized(self) -> bool { + let nullifier = self.compute_initialization_nullifier(); + check_nullifier_exists(nullifier) + } + // docs:end:is_initialized + // view_note does not actually use the context, but it calls oracles that are only available in private // docs:start:view_note unconstrained pub fn view_note(self) -> Note where Note: NoteInterface { let mut options = NoteViewerOptions::new(); diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr index a4908c37553..95d8a0badb0 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr @@ -3,7 +3,7 @@ use dep::protocol_types::{ grumpkin_point::GrumpkinPoint, hash::pedersen_hash }; -use crate::context::{PrivateContext, PublicContext, Context}; +use crate::context::PrivateContext; use crate::note::{ lifecycle::{create_note, destroy_note}, note_getter::{get_note, view_notes}, note_interface::NoteInterface, note_viewer_options::NoteViewerOptions @@ -12,22 +12,26 @@ use crate::oracle::notes::check_nullifier_exists; use crate::state_vars::storage::Storage; // docs:start:struct -struct PrivateMutable { - context: Option<&mut PrivateContext>, +struct PrivateMutable { + context: Context, storage_slot: Field } // docs:end:struct -impl Storage for PrivateMutable {} +impl Storage for PrivateMutable {} -impl PrivateMutable { +impl PrivateMutable { // docs:start:new pub fn new(context: Context, storage_slot: Field) -> Self { assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1."); - Self { context: context.private, storage_slot } + Self { context, storage_slot } } // docs:end:new + pub fn to_unconstrained(self) -> PrivateMutable { + PrivateMutable { context: (), storage_slot: self.storage_slot } + } + // The following computation is leaky, in that it doesn't hide the storage slot that has been initialized, nor does it hide the contract address of this contract. // When this initialization nullifier is emitted, an observer could do a dictionary or rainbow attack to learn the preimage of this nullifier to deduce the storage slot and contract address. // For some applications, leaking the details that a particular state variable of a particular contract has been initialized will be unacceptable. @@ -42,14 +46,9 @@ impl PrivateMutable { GENERATOR_INDEX__INITIALIZATION_NULLIFIER ) } +} - // docs:start:is_initialized - unconstrained pub fn is_initialized(self) -> bool { - let nullifier = self.compute_initialization_nullifier(); - check_nullifier_exists(nullifier) - } - // docs:end:is_initialized - +impl PrivateMutable { // docs:start:initialize pub fn initialize( self, @@ -57,13 +56,11 @@ impl PrivateMutable { broadcast: bool, ivpk_m: GrumpkinPoint ) where Note: NoteInterface { - let context = self.context.unwrap(); - // Nullify the storage slot. let nullifier = self.compute_initialization_nullifier(); - context.push_new_nullifier(nullifier, 0); + self.context.push_new_nullifier(nullifier, 0); - create_note(context, self.storage_slot, note, broadcast, ivpk_m); + create_note(self.context, self.storage_slot, note, broadcast, ivpk_m); } // docs:end:initialize @@ -74,14 +71,13 @@ impl PrivateMutable { broadcast: bool, ivpk_m: GrumpkinPoint ) where Note: NoteInterface { - let context = self.context.unwrap(); - let prev_note: Note = get_note(context, self.storage_slot); + let prev_note: Note = get_note(self.context, self.storage_slot); // Nullify previous note. - destroy_note(context, prev_note); + destroy_note(self.context, prev_note); // Add replacement note. - create_note(context, self.storage_slot, new_note, broadcast, ivpk_m); + create_note(self.context, self.storage_slot, new_note, broadcast, ivpk_m); } // docs:end:replace @@ -91,19 +87,25 @@ impl PrivateMutable { broadcast: bool, ivpk_m: GrumpkinPoint ) -> Note where Note: NoteInterface { - let context = self.context.unwrap(); - let mut note = get_note(context, self.storage_slot); + let mut note = get_note(self.context, self.storage_slot); // Nullify current note to make sure it's reading the latest note. - destroy_note(context, note); + destroy_note(self.context, note); // Add the same note again. // Because a nonce is added to every note in the kernel, its nullifier will be different. - create_note(context, self.storage_slot, &mut note, broadcast, ivpk_m); + create_note(self.context, self.storage_slot, &mut note, broadcast, ivpk_m); note } // docs:end:get_note +} + +impl PrivateMutable { + unconstrained pub fn is_initialized(self) -> bool { + let nullifier = self.compute_initialization_nullifier(); + check_nullifier_exists(nullifier) + } // docs:start:view_note unconstrained pub fn view_note(self) -> Note where Note: NoteInterface { diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr index 012facf8591..a976420cfd5 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr @@ -2,7 +2,7 @@ use dep::protocol_types::{ constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, abis::read_request::ReadRequest, grumpkin_point::GrumpkinPoint }; -use crate::context::{PrivateContext, PublicContext, Context}; +use crate::context::{PrivateContext, PublicContext}; use crate::note::{ constants::MAX_NOTES_PER_PAGE, lifecycle::{create_note, create_note_hash_from_public, destroy_note}, note_getter::{get_notes, view_notes}, note_getter_options::NoteGetterOptions, @@ -12,21 +12,32 @@ use crate::note::{ use crate::state_vars::storage::Storage; // docs:start:struct -struct PrivateSet { +struct PrivateSet { context: Context, storage_slot: Field, } // docs:end:struct -impl Storage for PrivateSet {} +impl Storage for PrivateSet {} -impl PrivateSet { +impl PrivateSet { // docs:start:new pub fn new(context: Context, storage_slot: Field) -> Self { assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1."); PrivateSet { context, storage_slot } } // docs:end:new +} + +impl PrivateSet { + // docs:start:insert_from_public + pub fn insert_from_public(self, note: &mut Note) where Note: NoteInterface { + create_note_hash_from_public(self.context, self.storage_slot, note); + } + // docs:end:insert_from_public +} + +impl PrivateSet { // docs:start:insert pub fn insert( self, @@ -34,22 +45,10 @@ impl PrivateSet { broadcast: bool, ivpk_m: GrumpkinPoint ) where Note: NoteInterface { - create_note( - self.context.private.unwrap(), - self.storage_slot, - note, - broadcast, - ivpk_m - ); + create_note(self.context, self.storage_slot, note, broadcast, ivpk_m); } // docs:end:insert - // docs:start:insert_from_public - pub fn insert_from_public(self, note: &mut Note) where Note: NoteInterface { - create_note_hash_from_public(self.context.public.unwrap(), self.storage_slot, note); - } - // docs:end:insert_from_public - // DEPRECATED fn assert_contains_and_remove(_self: Self, _note: &mut Note, _nonce: Field) { assert( @@ -66,12 +65,11 @@ impl PrivateSet { // docs:start:remove pub fn remove(self, note: Note) where Note: NoteInterface { - let context = self.context.private.unwrap(); let note_hash = compute_note_hash_for_read_request(note); - let has_been_read = context.note_hash_read_requests.any(|r: ReadRequest| r.value == note_hash); + let has_been_read = self.context.note_hash_read_requests.any(|r: ReadRequest| r.value == note_hash); assert(has_been_read, "Can only remove a note that has been read from the set."); - destroy_note(context, note); + destroy_note(self.context, note); } // docs:end:remove @@ -81,11 +79,13 @@ impl PrivateSet { options: NoteGetterOptions ) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where Note: NoteInterface { let storage_slot = self.storage_slot; - let opt_notes = get_notes(self.context.private.unwrap(), storage_slot, options); + let opt_notes = get_notes(self.context, storage_slot, options); opt_notes } // docs:end:get_notes +} +impl PrivateSet { // docs:start:view_notes unconstrained pub fn view_notes( self, diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr index f3fc0828bd6..f782be2ca3a 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr @@ -1,17 +1,20 @@ -use crate::{context::Context, oracle::{storage::{storage_read, storage_write}}, state_vars::storage::Storage}; +use crate::{ + context::{AvmContext, PublicContext}, oracle::{storage::{storage_read, storage_write}}, + state_vars::storage::Storage +}; use dep::protocol_types::{constants::INITIALIZATION_SLOT_SEPARATOR, traits::{Deserialize, Serialize}}; // Just like SharedImmutable but without the ability to read from private functions. // docs:start:public_immutable_struct -struct PublicImmutable { +struct PublicImmutable { context: Context, storage_slot: Field, } // docs:end:public_immutable_struct -impl Storage for PublicImmutable {} +impl Storage for PublicImmutable {} -impl PublicImmutable { +impl PublicImmutable { // docs:start:public_immutable_struct_new pub fn new( // Note: Passing the contexts to new(...) just to have an interface compatible with a Map. @@ -22,12 +25,53 @@ impl PublicImmutable { PublicImmutable { context, storage_slot } } // docs:end:public_immutable_struct_new +} + +impl PublicImmutable { + // docs:start:public_immutable_struct_write + pub fn initialize(self, value: T) where T: Serialize { + // TODO(#4738): Uncomment the following assert + // assert( + // self.context.public.unwrap_unchecked().is_deployment(), "PublicImmutable can only be initialized during contract deployment" + // ); + + // We check that the struct is not yet initialized by checking if the initialization slot is 0 + let initialization_slot = INITIALIZATION_SLOT_SEPARATOR + self.storage_slot; + let fields_read: [Field; 1] = storage_read(initialization_slot); + assert(fields_read[0] == 0, "PublicImmutable already initialized"); + + // We populate the initialization slot with a non-zero value to indicate that the struct is initialized + storage_write(initialization_slot, [0xdead]); + + let fields_write = T::serialize(value); + storage_write(self.storage_slot, fields_write); + } + // docs:end:public_immutable_struct_write + + // Note that we don't access the context, but we do call oracles that are only available in public + // docs:start:public_immutable_struct_read + pub fn read(self) -> T where T: Deserialize { + let fields = storage_read(self.storage_slot); + T::deserialize(fields) + } + // docs:end:public_immutable_struct_read +} + +impl PublicImmutable { + pub fn read(self) -> T where T: Deserialize { + // Note that this is the exact same implementation as for public execution, though it might change in the future + // since unconstrained execution might not rely on the same oracles as used for public execution (which + // transpile to AVM opcodes). + let fields = storage_read(self.storage_slot); + T::deserialize(fields) + } +} +// TODO (https://github.com/AztecProtocol/aztec-packages/issues/5818): remove this impl and leave the PublicContext impl +// once AvmContext becomes PublicContext. +impl PublicImmutable { // docs:start:public_immutable_struct_write pub fn initialize(self, value: T) where T: Serialize { - assert( - self.context.private.is_none(), "PublicImmutable can only be initialized from public functions" - ); // TODO(#4738): Uncomment the following assert // assert( // self.context.public.unwrap_unchecked().is_deployment(), "PublicImmutable can only be initialized during contract deployment" @@ -46,9 +90,9 @@ impl PublicImmutable { } // docs:end:public_immutable_struct_write + // Note that we don't access the context, but we do call oracles that are only available in public // docs:start:public_immutable_struct_read pub fn read(self) -> T where T: Deserialize { - assert(self.context.private.is_none(), "PublicImmutable reads only supported in public functions"); let fields = storage_read(self.storage_slot); T::deserialize(fields) } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/public_mutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/public_mutable.nr index 442a7d55656..128d8a4ce47 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/public_mutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/public_mutable.nr @@ -1,19 +1,19 @@ -use crate::context::Context; +use crate::context::{AvmContext, PublicContext}; use crate::oracle::storage::storage_read; use crate::oracle::storage::storage_write; use dep::protocol_types::traits::{Deserialize, Serialize}; use crate::state_vars::storage::Storage; // docs:start:public_mutable_struct -struct PublicMutable { +struct PublicMutable { context: Context, storage_slot: Field, } // docs:end:public_mutable_struct -impl Storage for PublicMutable {} +impl Storage for PublicMutable {} -impl PublicMutable { +impl PublicMutable { // docs:start:public_mutable_struct_new pub fn new( // Note: Passing the contexts to new(...) just to have an interface compatible with a Map. @@ -24,10 +24,39 @@ impl PublicMutable { PublicMutable { context, storage_slot } } // docs:end:public_mutable_struct_new +} + +impl PublicMutable { + // docs:start:public_mutable_struct_read + pub fn read(self) -> T where T: Deserialize { + let fields = storage_read(self.storage_slot); + T::deserialize(fields) + } + // docs:end:public_mutable_struct_read + + // docs:start:public_mutable_struct_write + pub fn write(self, value: T) where T: Serialize { + let fields = T::serialize(value); + storage_write(self.storage_slot, fields); + } + // docs:end:public_mutable_struct_write +} + +impl PublicMutable { + pub fn read(self) -> T where T: Deserialize { + // Note that this is the exact same implementation as for public execution, though it might change in the future + // since unconstrained execution might not rely on the same oracles as used for public execution (which + // transpile to AVM opcodes). + let fields = storage_read(self.storage_slot); + T::deserialize(fields) + } +} +// TODO (https://github.com/AztecProtocol/aztec-packages/issues/5818): remove this impl and leave the PublicContext impl +// once AvmContext becomes PublicContext. +impl PublicMutable { // docs:start:public_mutable_struct_read pub fn read(self) -> T where T: Deserialize { - assert(self.context.private.is_none(), "PublicMutable reads only supported in public functions"); let fields = storage_read(self.storage_slot); T::deserialize(fields) } @@ -35,7 +64,6 @@ impl PublicMutable { // docs:start:public_mutable_struct_write pub fn write(self, value: T) where T: Serialize { - assert(self.context.private.is_none(), "PublicMutable writes only supported in public functions"); let fields = T::serialize(value); storage_write(self.storage_slot, fields); } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_immutable.nr index a44a605c3fd..097f2d75867 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_immutable.nr @@ -1,18 +1,18 @@ use crate::{ - context::Context, history::public_storage::public_storage_historical_read, + context::{PrivateContext, PublicContext}, history::public_storage::public_storage_historical_read, oracle::{storage::{storage_read, storage_write}}, state_vars::storage::Storage }; use dep::protocol_types::{constants::INITIALIZATION_SLOT_SEPARATOR, traits::{Deserialize, Serialize}}; // Just like PublicImmutable but with the ability to read from private functions. -struct SharedImmutable{ +struct SharedImmutable{ context: Context, storage_slot: Field, } -impl Storage for SharedImmutable {} +impl Storage for SharedImmutable {} -impl SharedImmutable { +impl SharedImmutable { pub fn new( // Note: Passing the contexts to new(...) just to have an interface compatible with a Map. context: Context, @@ -21,12 +21,11 @@ impl SharedImmutable { assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1."); Self { context, storage_slot } } +} +impl SharedImmutable { // Intended to be only called once. pub fn initialize(self, value: T) where T: Serialize { - assert( - self.context.private.is_none(), "SharedImmutable can only be initialized from public functions" - ); // TODO(#4738): Uncomment the following assert // assert( // self.context.public.unwrap_unchecked().is_deployment(), "SharedImmutable can only be initialized during contract deployment" @@ -45,23 +44,28 @@ impl SharedImmutable { } pub fn read_public(self) -> T where T: Deserialize { - assert(self.context.private.is_none(), "Public read only supported in public functions"); let fields = storage_read(self.storage_slot); T::deserialize(fields) } +} - pub fn read_private(self) -> T where T: Deserialize { - assert(self.context.public.is_none(), "Private read only supported in private functions"); - let private_context = self.context.private.unwrap(); +impl SharedImmutable { + pub fn read_public(self) -> T where T: Deserialize { + let fields = storage_read(self.storage_slot); + T::deserialize(fields) + } +} +impl SharedImmutable { + pub fn read_private(self) -> T where T: Deserialize { let mut fields = [0; T_SERIALIZED_LEN]; for i in 0..fields.len() { fields[i] = public_storage_historical_read( - (*private_context), + (*self.context), self.storage_slot + i as Field, - (*private_context).this_address() + (*self.context).this_address() ); } T::deserialize(fields) diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr index a36bda7e6cb..0b8c91ef75c 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr @@ -1,6 +1,6 @@ use dep::protocol_types::{hash::pedersen_hash, traits::FromField}; -use crate::context::{PrivateContext, PublicContext, Context}; +use crate::context::{PrivateContext, PublicContext}; use crate::history::public_storage::public_storage_historical_read; use crate::public_storage; use crate::state_vars::{ @@ -8,7 +8,7 @@ use crate::state_vars::{ shared_mutable::{scheduled_value_change::ScheduledValueChange, scheduled_delay_change::ScheduledDelayChange} }; -struct SharedMutable { +struct SharedMutable { context: Context, storage_slot: Field, } @@ -20,7 +20,7 @@ struct SharedMutable { // // TODO https://github.com/AztecProtocol/aztec-packages/issues/5736: change the storage allocation scheme so that we // can actually use it here -impl Storage for SharedMutable {} +impl Storage for SharedMutable {} // SharedMutable stores a value of type T that is: // - publicly known (i.e. unencrypted) @@ -33,18 +33,31 @@ impl Storage for SharedMutable {} // future, so that they can guarantee the value will not have possibly changed by then (because of the delay). // The delay for changing a value is initially equal to INITIAL_DELAY, but can be changed by calling // `schedule_delay_change`. -impl SharedMutable { +impl SharedMutable { pub fn new(context: Context, storage_slot: Field) -> Self { assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1."); Self { context, storage_slot } } + // Since we can't rely on the native storage allocation scheme, we hash the storage slot to get a unique location in + // which we can safely store as much data as we need. + // See https://github.com/AztecProtocol/aztec-packages/issues/5492 and + // https://github.com/AztecProtocol/aztec-packages/issues/5736 + fn get_value_change_storage_slot(self) -> Field { + pedersen_hash([self.storage_slot, 0], 0) + } + + fn get_delay_change_storage_slot(self) -> Field { + pedersen_hash([self.storage_slot, 1], 0) + } +} + +impl SharedMutable { pub fn schedule_value_change(self, new_value: T) { - let context = self.context.public.unwrap(); let mut value_change = self.read_value_change(); let delay_change = self.read_delay_change(); - let block_number = context.block_number() as u32; + let block_number = self.context.block_number() as u32; let current_delay = delay_change.get_current(block_number); // TODO: make this configurable @@ -56,10 +69,9 @@ impl SharedMutable { } pub fn schedule_delay_change(self, new_delay: u32) { - let context = self.context.public.unwrap(); let mut delay_change = self.read_delay_change(); - let block_number = context.block_number() as u32; + let block_number = self.context.block_number() as u32; delay_change.schedule_change(new_delay, block_number); @@ -67,12 +79,12 @@ impl SharedMutable { } pub fn get_current_value_in_public(self) -> T { - let block_number = self.context.public.unwrap().block_number() as u32; + let block_number = self.context.block_number() as u32; self.read_value_change().get_current_at(block_number) } pub fn get_current_delay_in_public(self) -> u32 { - let block_number = self.context.public.unwrap().block_number() as u32; + let block_number = self.context.block_number() as u32; self.read_delay_change().get_current(block_number) } @@ -84,15 +96,31 @@ impl SharedMutable { self.read_delay_change().get_scheduled() } - pub fn get_current_value_in_private(self) -> T where T: FromField { - let mut context = self.context.private.unwrap(); + fn read_value_change(self) -> ScheduledValueChange { + public_storage::read(self.get_value_change_storage_slot()) + } + + fn read_delay_change(self) -> ScheduledDelayChange { + public_storage::read(self.get_delay_change_storage_slot()) + } + + fn write_value_change(self, value_change: ScheduledValueChange) { + public_storage::write(self.get_value_change_storage_slot(), value_change); + } + + fn write_delay_change(self, delay_change: ScheduledDelayChange) { + public_storage::write(self.get_delay_change_storage_slot(), delay_change); + } +} +impl SharedMutable { + pub fn get_current_value_in_private(self) -> T where T: FromField { // When reading the current value in private we construct a historical state proof for the public value. // However, since this value might change, we must constrain the maximum transaction block number as this proof // will only be valid for however many blocks we can ensure the value will not change, which will depend on the // current delay and any scheduled delay changes. - let (value_change, delay_change, historical_block_number) = self.historical_read_from_public_storage(*context); + let (value_change, delay_change, historical_block_number) = self.historical_read_from_public_storage(*self.context); // We use the effective minimum delay as opposed to the current delay at the historical block as this one also // takes into consideration any scheduled delay changes. @@ -106,7 +134,7 @@ impl SharedMutable { // We prevent this transaction from being included in any block after the block horizon, ensuring that the // historical public value matches the current one, since it can only change after the horizon. - context.set_tx_max_block_number(block_horizon); + self.context.set_tx_max_block_number(block_horizon); value_change.get_current_at(historical_block_number) } @@ -136,41 +164,13 @@ impl SharedMutable { (value_change, delay_change, historical_block_number) } - - fn read_value_change(self) -> ScheduledValueChange { - public_storage::read(self.get_value_change_storage_slot()) - } - - fn read_delay_change(self) -> ScheduledDelayChange { - public_storage::read(self.get_delay_change_storage_slot()) - } - - fn write_value_change(self, value_change: ScheduledValueChange) { - public_storage::write(self.get_value_change_storage_slot(), value_change); - } - - fn write_delay_change(self, delay_change: ScheduledDelayChange) { - public_storage::write(self.get_delay_change_storage_slot(), delay_change); - } - - // Since we can't rely on the native storage allocation scheme, we hash the storage slot to get a unique location in - // which we can safely store as much data as we need. - // See https://github.com/AztecProtocol/aztec-packages/issues/5492 and - // https://github.com/AztecProtocol/aztec-packages/issues/5736 - fn get_value_change_storage_slot(self) -> Field { - pedersen_hash([self.storage_slot, 0], 0) - } - - fn get_delay_change_storage_slot(self) -> Field { - pedersen_hash([self.storage_slot, 1], 0) - } } mod test { use dep::std::{merkle::compute_merkle_root, test::OracleMock}; use crate::{ - context::{PublicContext, PrivateContext, Context}, + context::{PublicContext, PrivateContext}, state_vars::shared_mutable::{ shared_mutable::SharedMutable, scheduled_value_change::ScheduledValueChange, scheduled_delay_change::ScheduledDelayChange @@ -193,30 +193,24 @@ mod test { global TEST_INITIAL_DELAY = 3; - fn setup(private: bool) -> (SharedMutable, Field) { + fn setup() -> (SharedMutable, Field) { let block_number = 40; - let context = create_context(block_number, private); + let context = create_context(block_number); let storage_slot = 57; - let state_var: SharedMutable = SharedMutable::new(context, storage_slot); + let state_var = SharedMutable::new(context, storage_slot); (state_var, block_number) } - fn create_context(block_number: Field, private: bool) -> Context { - if private { - let mut private_context = PrivateContext::empty(); - private_context.historical_header.global_variables.block_number = block_number; - Context::private(&mut private_context) - } else { - let mut public_context = PublicContext::empty(); - public_context.inputs.public_global_variables.block_number = block_number; - Context::public(&mut public_context) - } + fn create_context(block_number: Field) -> &mut PublicContext { + let mut public_context = PublicContext::empty(); + public_context.inputs.public_global_variables.block_number = block_number; + &mut public_context } fn mock_value_change_read( - state_var: SharedMutable, + state_var: SharedMutable, pre: Field, post: Field, block_of_change: Field @@ -228,7 +222,7 @@ mod test { } fn mock_delay_change_read( - state_var: SharedMutable, + state_var: SharedMutable, pre: Field, post: Field, block_of_change: Field @@ -244,7 +238,7 @@ mod test { let _ = OracleMock::mock("storageRead").with_params((delay_change_slot, 1)).returns(fields).times(1); } - fn mock_delay_change_read_uninitialized(state_var: SharedMutable) { + fn mock_delay_change_read_uninitialized(state_var: SharedMutable) { let delay_change_slot = state_var.get_delay_change_storage_slot(); let _ = OracleMock::mock("storageRead").with_params((delay_change_slot, 1)).returns([0]).times(1); } @@ -252,7 +246,7 @@ mod test { // Useful since change and delay values are always the global pre/post ones, so we typically only care about their // block of change. fn mock_value_and_delay_read( - state_var: SharedMutable, + state_var: SharedMutable, value_block_of_change: Field, delay_block_of_change: Field ) { @@ -269,7 +263,7 @@ mod test { } fn assert_value_change_write( - state_var: SharedMutable, + state_var: SharedMutable, mock: OracleMock, pre: Field, post: Field, @@ -280,7 +274,7 @@ mod test { } fn assert_delay_change_write( - state_var: SharedMutable, + state_var: SharedMutable, mock: OracleMock, pre: Field, post: Field, @@ -298,7 +292,7 @@ mod test { #[test] fn test_get_current_value_in_public() { - let (state_var, block_number) = setup(false); + let (state_var, block_number) = setup(); // Change in the future, current value is pre mock_value_change_read(state_var, pre_value, post_value, block_number + 1); @@ -315,7 +309,7 @@ mod test { #[test] fn test_get_scheduled_value_in_public() { - let (state_var, block_number) = setup(false); + let (state_var, block_number) = setup(); // Change in the future, scheduled is post (always is) mock_value_change_read(state_var, pre_value, post_value, block_number + 1); @@ -332,7 +326,7 @@ mod test { #[test] fn test_get_current_delay_in_public() { - let (state_var, block_number) = setup(false); + let (state_var, block_number) = setup(); // Uninitialized mock_delay_change_read_uninitialized(state_var); @@ -353,7 +347,7 @@ mod test { #[test] fn test_get_scheduled_delay_in_public_before_change() { - let (state_var, block_number) = setup(false); + let (state_var, block_number) = setup(); // Uninitialized mock_delay_change_read_uninitialized(state_var); @@ -374,7 +368,7 @@ mod test { #[test] fn test_schedule_value_change_no_delay() { - let (state_var, block_number) = setup(false); + let (state_var, block_number) = setup(); // Last value change was in the past mock_value_change_read(state_var, pre_value, post_value, 0); @@ -392,7 +386,7 @@ mod test { #[test] fn test_schedule_value_change_before_change_no_scheduled_delay() { - let (state_var, block_number) = setup(false); + let (state_var, block_number) = setup(); // Value change in the future, delay change in the past mock_value_and_delay_read(state_var, block_number + 1, block_number - 1); @@ -412,7 +406,7 @@ mod test { #[test] fn test_schedule_value_change_before_change_scheduled_delay() { - let (state_var, block_number) = setup(false); + let (state_var, block_number) = setup(); // Value change in the future, delay change in the future mock_value_and_delay_read(state_var, block_number + 1, block_number + 1); @@ -433,7 +427,7 @@ mod test { #[test] fn test_schedule_value_change_after_change_no_scheduled_delay() { - let (state_var, block_number) = setup(false); + let (state_var, block_number) = setup(); // Value change in the past, delay change in the past mock_value_and_delay_read(state_var, block_number - 1, block_number - 1); @@ -453,7 +447,7 @@ mod test { #[test] fn test_schedule_value_change_after_change_scheduled_delay() { - let (state_var, block_number) = setup(false); + let (state_var, block_number) = setup(); // Value change in the past, delay change in the future mock_value_and_delay_read(state_var, block_number - 1, block_number + 1); @@ -474,7 +468,7 @@ mod test { #[test] fn test_schedule_delay_increase_before_change() { - let (state_var, block_number) = setup(false); + let (state_var, block_number) = setup(); // Delay change in future, current delay is pre mock_delay_change_read(state_var, pre_delay, post_delay, block_number + 1); @@ -489,7 +483,7 @@ mod test { #[test] fn test_schedule_delay_reduction_before_change() { - let (state_var, block_number) = setup(false); + let (state_var, block_number) = setup(); // Delay change in future, current delay is pre mock_delay_change_read(state_var, pre_delay, post_delay, block_number + 1); @@ -510,7 +504,7 @@ mod test { #[test] fn test_schedule_delay_increase_after_change() { - let (state_var, block_number) = setup(false); + let (state_var, block_number) = setup(); // Delay change in the past, current delay is post mock_delay_change_read(state_var, pre_delay, post_delay, block_number - 1); @@ -525,7 +519,7 @@ mod test { #[test] fn test_schedule_delay_reduction_after_change() { - let (state_var, block_number) = setup(false); + let (state_var, block_number) = setup(); // Delay change in the past, current delay is post mock_delay_change_read(state_var, pre_delay, post_delay, block_number - 1); diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable_private_getter.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable_private_getter.nr index 7da8f1524fc..7e923806e73 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable_private_getter.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable_private_getter.nr @@ -1,6 +1,6 @@ use dep::protocol_types::{hash::pedersen_hash, traits::FromField, address::AztecAddress}; -use crate::context::{PrivateContext, Context}; +use crate::context::PrivateContext; use crate::history::public_storage::public_storage_historical_read; use crate::public_storage; use crate::state_vars::{ diff --git a/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr b/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr index af6a3609ea6..eeed3726be0 100644 --- a/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr +++ b/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr @@ -1,31 +1,30 @@ use dep::aztec::{ - protocol_types::{address::AztecAddress, grumpkin_point::GrumpkinPoint}, context::Context, + context::PrivateContext, protocol_types::{address::AztecAddress, grumpkin_point::GrumpkinPoint}, note::note_getter_options::NoteGetterOptions, state_vars::PrivateSet, keys::getters::{get_npk_m_hash, get_ivpk_m} }; use dep::value_note::{filter::filter_notes_min_sum, value_note::ValueNote}; -struct EasyPrivateUint { +struct EasyPrivateUint { context: Context, - set: PrivateSet, + set: PrivateSet, storage_slot: Field, } // Holds a note that can act similarly to an int. -impl EasyPrivateUint { +impl EasyPrivateUint { pub fn new(context: Context, storage_slot: Field) -> Self { assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1."); - let set = PrivateSet { context, storage_slot }; + let set = PrivateSet::new(context, storage_slot); EasyPrivateUint { context, set, storage_slot } } +} +impl EasyPrivateUint<&mut PrivateContext> { // Very similar to `value_note::utils::increment`. pub fn add(self, addend: u64, owner: AztecAddress) { - assert(self.context.public.is_none(), "EasyPrivateUint::add can be called from private only."); - let context = self.context.private.unwrap(); - - let owner_npk_m_hash = get_npk_m_hash(context, owner); - let owner_ivpk_m = get_ivpk_m(context, owner); + let owner_npk_m_hash = get_npk_m_hash(self.context, owner); + let owner_ivpk_m = get_ivpk_m(self.context, owner); // Creates new note for the owner. let mut addend_note = ValueNote::new(addend as Field, owner_npk_m_hash); @@ -37,11 +36,8 @@ impl EasyPrivateUint { // Very similar to `value_note::utils::decrement`. pub fn sub(self, subtrahend: u64, owner: AztecAddress) { - assert(self.context.public.is_none(), "EasyPrivateUint::sub can be called from private only."); - let context = self.context.private.unwrap(); - - let owner_npk_m_hash = get_npk_m_hash(context, owner); - let owner_ivpk_m = get_ivpk_m(context, owner); + let owner_npk_m_hash = get_npk_m_hash(self.context, owner); + let owner_ivpk_m = get_ivpk_m(self.context, owner); // docs:start:get_notes let options = NoteGetterOptions::with_filter(filter_notes_min_sum, subtrahend as Field); diff --git a/noir-projects/aztec-nr/value-note/src/balance_utils.nr b/noir-projects/aztec-nr/value-note/src/balance_utils.nr index 83029fe93e8..5cbe5a4d9d6 100644 --- a/noir-projects/aztec-nr/value-note/src/balance_utils.nr +++ b/noir-projects/aztec-nr/value-note/src/balance_utils.nr @@ -2,11 +2,11 @@ use dep::aztec::note::{note_getter::view_notes, note_viewer_options::NoteViewerO use dep::aztec::state_vars::PrivateSet; use crate::value_note::ValueNote; -unconstrained pub fn get_balance(set: PrivateSet) -> Field { +unconstrained pub fn get_balance(set: PrivateSet) -> Field { get_balance_with_offset(set, 0) } -unconstrained pub fn get_balance_with_offset(set: PrivateSet, offset: u32) -> Field { +unconstrained pub fn get_balance_with_offset(set: PrivateSet, offset: u32) -> Field { let mut balance = 0; // docs:start:view_notes let mut options = NoteViewerOptions::new(); diff --git a/noir-projects/aztec-nr/value-note/src/utils.nr b/noir-projects/aztec-nr/value-note/src/utils.nr index 466cf660d3f..659ecb38b8f 100644 --- a/noir-projects/aztec-nr/value-note/src/utils.nr +++ b/noir-projects/aztec-nr/value-note/src/utils.nr @@ -12,8 +12,12 @@ pub fn create_note_getter_options_for_decreasing_balance(amount: Field) -> NoteG // Creates a new note for the recipient. // Inserts it to the recipient's set of notes. -pub fn increment(balance: PrivateSet, amount: Field, recipient: AztecAddress) { - let context = balance.context.private.unwrap(); +pub fn increment( + balance: PrivateSet, + amount: Field, + recipient: AztecAddress +) { + let context = balance.context; let recipient_npk_m_hash = get_npk_m_hash(context, recipient); let recipient_ivpk_m = get_ivpk_m(context, recipient); @@ -26,7 +30,11 @@ pub fn increment(balance: PrivateSet, amount: Field, recipient: Aztec // Remove those notes. // If the value of the removed notes exceeds the requested `amount`, create a new note containing the excess value, so that exactly `amount` is removed. // Fail if the sum of the selected notes is less than the amount. -pub fn decrement(balance: PrivateSet, amount: Field, owner: AztecAddress) { +pub fn decrement( + balance: PrivateSet, + amount: Field, + owner: AztecAddress +) { let sum = decrement_by_at_most(balance, amount, owner); assert(sum == amount, "Balance too low"); } @@ -40,14 +48,14 @@ pub fn decrement(balance: PrivateSet, amount: Field, owner: AztecAddr // // It returns the decremented amount, which should be less than or equal to max_amount. pub fn decrement_by_at_most( - balance: PrivateSet, + balance: PrivateSet, max_amount: Field, owner: AztecAddress ) -> Field { let options = create_note_getter_options_for_decreasing_balance(max_amount); let opt_notes = balance.get_notes(options); - let owner_npk_m_hash = get_npk_m_hash(balance.context.private.unwrap(), owner); + let owner_npk_m_hash = get_npk_m_hash(balance.context, owner); let mut decremented = 0; for i in 0..opt_notes.len() { @@ -80,10 +88,14 @@ pub fn decrement_by_at_most( // Removes the note from the owner's set of notes. // Returns the value of the destroyed note. -pub fn destroy_note(balance: PrivateSet, owner: AztecAddress, note: ValueNote) -> Field { +pub fn destroy_note( + balance: PrivateSet, + owner: AztecAddress, + note: ValueNote +) -> Field { // Ensure the note is actually owned by the owner (to prevent user from generating a valid proof while // spending someone else's notes). - let owner_npk_m_hash = get_npk_m_hash(balance.context.private.unwrap(), owner); + let owner_npk_m_hash = get_npk_m_hash(balance.context, owner); // TODO (#6312): This will break with key rotation. Fix this. Will not be able to pass this after rotating keys. assert(note.npk_m_hash.eq(owner_npk_m_hash)); diff --git a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr index a232484e17d..bdb0d902c17 100644 --- a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr @@ -118,15 +118,19 @@ contract AppSubscription { let subscriber_ivpk_m = get_ivpk_m(&mut context, subscriber_address); let mut subscription_note = SubscriptionNote::new(subscriber_npk_m_hash, expiry_block_number, tx_count); - if (!is_initialized(subscriber_address)) { + + // is_initialized is an unconstrained function, so we must first convert the state variable to an unconstrained + // one to avoid having a mutable reference (to the private context) cross the constrained <> unconstrained + // boundary, which is not allowed. + let is_initialized = storage.subscriptions.at(subscriber_address).to_unconstrained().is_initialized(); + + if (!is_initialized) { storage.subscriptions.at(subscriber_address).initialize(&mut subscription_note, true, subscriber_ivpk_m); } else { storage.subscriptions.at(subscriber_address).replace(&mut subscription_note, true, subscriber_ivpk_m) } } - // Compiler bug workaround. You can't call an unconstrained function in another module, unless its from an - // unconstrained function in your module. unconstrained fn is_initialized(subscriber_address: AztecAddress) -> pub bool { storage.subscriptions.at(subscriber_address).is_initialized() } diff --git a/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr b/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr index a8b9f34d36b..8d79d608ff0 100644 --- a/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr @@ -7,7 +7,7 @@ contract Benchmarking { use dep::aztec::prelude::{AztecAddress, FunctionSelector, NoteHeader, NoteGetterOptions, Map, PublicMutable, PrivateSet}; use dep::value_note::{utils::{increment, decrement}, value_note::ValueNote}; - use dep::aztec::{context::{Context, gas::GasOpts}}; + use dep::aztec::context::gas::GasOpts; #[aztec(storage)] struct Storage { diff --git a/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr b/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr index 72f51f7fd57..a14d62cdf4b 100644 --- a/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr +++ b/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr @@ -5,8 +5,8 @@ use dep::aztec::{ traits::{ToField, Serialize, FromField}, grumpkin_point::GrumpkinPoint, constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL }, - keys::getters::{get_npk_m_hash, get_ivpk_m}, context::{PublicContext, Context}, - note::note_getter::view_notes, state_vars::PrivateSet, note::constants::MAX_NOTES_PER_PAGE + keys::getters::{get_npk_m_hash, get_ivpk_m}, note::note_getter::view_notes, state_vars::PrivateSet, + note::constants::MAX_NOTES_PER_PAGE }; use dep::std; use dep::std::{option::Option}; @@ -66,8 +66,8 @@ impl CardNote { } } -struct Deck { - set: PrivateSet, +struct Deck { + set: PrivateSet, } pub fn filter_cards( @@ -96,17 +96,17 @@ pub fn filter_cards( selected } -impl Deck { +impl Deck { pub fn new(context: Context, storage_slot: Field) -> Self { let set = PrivateSet { context, storage_slot }; Deck { set } } +} +impl Deck<&mut PrivateContext> { pub fn add_cards(&mut self, cards: [Card; N], owner: AztecAddress) -> [CardNote] { - let context = self.set.context.private.unwrap(); - - let owner_npk_m_hash = get_npk_m_hash(context, owner); - let owner_ivpk_m = get_ivpk_m(context, owner); + let owner_npk_m_hash = get_npk_m_hash(self.set.context, owner); + let owner_ivpk_m = get_ivpk_m(self.set.context, owner); let mut inserted_cards = &[]; for card in cards { @@ -119,7 +119,7 @@ impl Deck { } pub fn get_cards(&mut self, cards: [Card; N], owner: AztecAddress) -> [CardNote; N] { - let owner_npk_m_hash = get_npk_m_hash(self.set.context.private.unwrap(), owner); + let owner_npk_m_hash = get_npk_m_hash(self.set.context, owner); let options = NoteGetterOptions::with_filter(filter_cards, cards); let maybe_notes = self.set.get_notes(options); @@ -156,7 +156,9 @@ impl Deck { self.set.remove(card_note.note); } } +} +impl Deck<()> { unconstrained pub fn view_cards(self, offset: u32) -> [Option; MAX_NOTES_PER_PAGE] { let mut options = NoteViewerOptions::new(); let opt_notes = self.set.view_notes(options.set_offset(offset)); diff --git a/noir-projects/noir-contracts/contracts/card_game_contract/src/main.nr b/noir-projects/noir-contracts/contracts/card_game_contract/src/main.nr index fb37c121fcb..53ab64848df 100644 --- a/noir-projects/noir-contracts/contracts/card_game_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/card_game_contract/src/main.nr @@ -3,7 +3,7 @@ mod game; contract CardGame { use dep::aztec::protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress}; - use dep::aztec::{context::Context, hash::pedersen_hash, state_vars::{Map, PublicMutable}}; + use dep::aztec::{hash::pedersen_hash, state_vars::{Map, PublicMutable}}; use dep::value_note::{balance_utils, value_note::{ValueNote, VALUE_NOTE_LEN}}; diff --git a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr index cf75ab7031d..5c97d9a5c64 100644 --- a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr @@ -3,7 +3,7 @@ contract Child { use dep::aztec::prelude::{AztecAddress, FunctionSelector, PublicMutable, PrivateSet, PrivateContext, Deserialize}; use dep::aztec::{ - context::{PublicContext, Context, gas::GasOpts}, + context::gas::GasOpts, protocol_types::{abis::call_context::CallContext, grumpkin_point::GrumpkinPoint}, note::{note_getter_options::NoteGetterOptions, note_header::NoteHeader}, keys::getters::{get_npk_m_hash, get_ivpk_m} diff --git a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr index d863ecaaaeb..73c4cd0d3b4 100644 --- a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr @@ -1,7 +1,6 @@ contract Counter { // docs:start:imports use dep::aztec::prelude::{AztecAddress, Map}; - use dep::aztec::context::Context; use dep::value_note::{balance_utils, value_note::{ValueNote, VALUE_NOTE_LEN}}; use dep::easy_private_state::EasyPrivateUint; // docs:end:imports diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr index 7723b789a26..267c6affff9 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr @@ -18,10 +18,7 @@ contract DocsExample { PrivateContext, Map, PublicMutable, PublicImmutable, PrivateMutable, PrivateImmutable, PrivateSet, SharedImmutable, Deserialize }; - use dep::aztec::{ - note::note_getter_options::Comparator, context::{PublicContext, Context}, - keys::getters::{get_npk_m_hash, get_ivpk_m} - }; + use dep::aztec::{note::note_getter_options::Comparator, keys::getters::{get_npk_m_hash, get_ivpk_m}}; use dep::aztec::protocol_types::grumpkin_point::GrumpkinPoint; // how to import methods from other files/folders within your workspace use crate::options::create_account_card_getter_options; @@ -57,7 +54,7 @@ contract DocsExample { // Note: The following is no longer necessary to implement manually as our macros do this for us. It is left here // for documentation purposes only. - impl Storage { + impl Storage { fn init(context: Context) -> Self { Storage { // docs:start:storage-leader-init @@ -290,7 +287,8 @@ contract DocsExample { // docs:end:private_mutable_is_initialized // docs:start:get_note-private-immutable - unconstrained fn get_imm_card() -> pub CardNote { + #[aztec(private)] + fn get_imm_card() -> CardNote { storage.private_immutable.get_note() } // docs:end:get_note-private-immutable @@ -346,7 +344,7 @@ contract DocsExample { // docs:end:context-example-context // docs:start:storage-example-context - let mut storage = Storage::init(Context::private(&mut context)); + let mut storage = Storage::init(&mut context); // docs:end:storage-example-context // ************************************************************ diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr index 8664cbad9bb..169c20ae7ad 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr @@ -3,8 +3,8 @@ use dep::aztec::{ keys::getters::get_ivpk_m, note::{utils::compute_note_hash_for_consumption}, keys::getters::get_nsk_app, protocol_types::{ - traits::Empty, grumpkin_point::GrumpkinPoint, constants::GENERATOR_INDEX__NOTE_NULLIFIER, - hash::poseidon2_hash + traits::{Empty, Serialize}, grumpkin_point::GrumpkinPoint, + constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash } }; @@ -60,3 +60,9 @@ impl NoteInterface for CardNote { ); } } + +impl Serialize<3> for CardNote { + fn serialize(self) -> [Field; 3] { + [ self.points.to_field(), self.randomness, self.npk_m_hash.to_field() ] + } +} diff --git a/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr b/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr index ba4656e8fbb..bb19fa9505e 100644 --- a/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr @@ -4,7 +4,7 @@ contract EasyPrivateVoting { AztecAddress, FunctionSelector, NoteHeader, NoteInterface, NoteGetterOptions, PrivateContext, Map, PublicMutable }; - use dep::aztec::{context::Context, keys::getters::get_npk_m_hash}; + use dep::aztec::keys::getters::get_npk_m_hash; // docs:end:imports // docs:start:storage_struct #[aztec(storage)] diff --git a/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/main.nr index 0a967637e31..08400e06169 100644 --- a/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/main.nr @@ -9,7 +9,6 @@ contract EcdsaAccount { use dep::aztec::keys::getters::{get_npk_m_hash, get_ivpk_m}; use dep::std; - use dep::aztec::context::Context; use dep::authwit::{ entrypoint::{app::AppPayload, fee::FeePayload}, account::AccountActions, auth_witness::get_auth_witness @@ -39,20 +38,20 @@ contract EcdsaAccount { // Note: If you globally change the entrypoint signature don't forget to update default_entrypoint.ts #[aztec(private)] fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload) { - let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + let actions = AccountActions::init(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.entrypoint(app_payload, fee_payload); } #[aztec(private)] #[aztec(noinitcheck)] fn spend_private_authwit(inner_hash: Field) -> Field { - let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + let actions = AccountActions::init(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.spend_private_authwit(inner_hash) } #[aztec(public)] fn spend_public_authwit(inner_hash: Field) -> Field { - let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + let actions = AccountActions::init(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.spend_public_authwit(inner_hash) } @@ -65,14 +64,14 @@ contract EcdsaAccount { #[aztec(public)] #[aztec(internal)] fn approve_public_authwit(outer_hash: Field) { - let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + let actions = AccountActions::init(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.approve_public_authwit(outer_hash) } #[contract_library_method] fn is_valid_impl(context: &mut PrivateContext, outer_hash: Field) -> bool { // Load public key from storage - let storage = Storage::init(Context::private(context)); + let storage = Storage::init(context); let public_key = storage.public_key.get_note(); // Load auth witness diff --git a/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr b/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr index 7f04b26caed..550b32e09c1 100644 --- a/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr @@ -2,8 +2,6 @@ contract Escrow { use dep::aztec::prelude::{AztecAddress, EthAddress, FunctionSelector, NoteHeader, PrivateContext, PrivateImmutable}; - use dep::aztec::context::{PublicContext, Context}; - use dep::aztec::keys::getters::{get_npk_m_hash, get_ivpk_m}; use dep::address_note::address_note::AddressNote; diff --git a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr index 47cd407a002..d3fbbcf4b16 100644 --- a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr @@ -6,7 +6,7 @@ contract InclusionProofs { }; use dep::aztec::protocol_types::{grumpkin_point::GrumpkinPoint, contract_class_id::ContractClassId}; - use dep::aztec::{context::Context, note::note_getter_options::NoteStatus, keys::getters::{get_npk_m_hash, get_ivpk_m}}; + use dep::aztec::{note::note_getter_options::NoteStatus, keys::getters::{get_npk_m_hash, get_ivpk_m}}; // docs:start:imports use dep::aztec::history::{ contract_inclusion::{ diff --git a/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr b/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr index 1817bc8947d..79314a807e8 100644 --- a/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr @@ -11,7 +11,7 @@ mod helpers; // - Liquidations contract Lending { use dep::aztec::prelude::{FunctionSelector, AztecAddress, PrivateContext, Map, PublicMutable}; - use dep::aztec::context::{PublicContext, Context, gas::GasOpts}; + use dep::aztec::context::{PublicContext, gas::GasOpts}; use crate::asset::Asset; use crate::interest_math::compute_multiplier; diff --git a/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr b/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr index 213dbc91661..a21c94fa79e 100644 --- a/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr @@ -6,7 +6,6 @@ contract PendingNoteHashes { // Libs use dep::aztec::prelude::{AztecAddress, FunctionSelector, NoteHeader, NoteGetterOptions, PrivateContext, Map, PrivateSet}; use dep::value_note::{balance_utils, filter::filter_notes_min_sum, value_note::{VALUE_NOTE_LEN, ValueNote}}; - use dep::aztec::context::{PublicContext, Context}; use dep::aztec::keys::getters::{get_npk_m_hash, get_ivpk_m}; use dep::aztec::protocol_types::grumpkin_point::GrumpkinPoint; diff --git a/noir-projects/noir-contracts/contracts/price_feed_contract/src/main.nr b/noir-projects/noir-contracts/contracts/price_feed_contract/src/main.nr index 6aab33130b7..5b0c32b4ac7 100644 --- a/noir-projects/noir-contracts/contracts/price_feed_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/price_feed_contract/src/main.nr @@ -2,7 +2,6 @@ mod asset; contract PriceFeed { use dep::aztec::prelude::{AztecAddress, FunctionSelector, PrivateContext, Map, PublicMutable}; - use dep::aztec::{context::{PublicContext, Context}}; use crate::asset::Asset; // Storage structure, containing all storage, and specifying what slots they use. diff --git a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr index 6bff06003ce..c2b687b5311 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr @@ -7,7 +7,6 @@ contract SchnorrAccount { use dep::aztec::prelude::{AztecAddress, FunctionSelector, NoteHeader, PrivateContext, PrivateImmutable}; use dep::aztec::state_vars::{Map, PublicMutable}; - use dep::aztec::context::Context; use dep::authwit::{ entrypoint::{app::AppPayload, fee::FeePayload}, account::AccountActions, auth_witness::get_auth_witness @@ -44,7 +43,7 @@ contract SchnorrAccount { #[aztec(private)] #[aztec(noinitcheck)] fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload) { - let actions = AccountActions::private( + let actions = AccountActions::init( &mut context, storage.approved_actions.storage_slot, is_valid_impl @@ -55,7 +54,7 @@ contract SchnorrAccount { #[aztec(private)] #[aztec(noinitcheck)] fn spend_private_authwit(inner_hash: Field) -> Field { - let actions = AccountActions::private( + let actions = AccountActions::init( &mut context, storage.approved_actions.storage_slot, is_valid_impl @@ -66,7 +65,7 @@ contract SchnorrAccount { #[aztec(public)] #[aztec(noinitcheck)] fn spend_public_authwit(inner_hash: Field) -> Field { - let actions = AccountActions::public( + let actions = AccountActions::init( &mut context, storage.approved_actions.storage_slot, is_valid_impl @@ -84,7 +83,7 @@ contract SchnorrAccount { #[aztec(internal)] #[aztec(noinitcheck)] fn approve_public_authwit(outer_hash: Field) { - let actions = AccountActions::public( + let actions = AccountActions::init( &mut context, storage.approved_actions.storage_slot, is_valid_impl @@ -96,7 +95,7 @@ contract SchnorrAccount { fn is_valid_impl(context: &mut PrivateContext, outer_hash: Field) -> bool { // docs:start:entrypoint // Load public key from storage - let storage = Storage::init(Context::private(context)); + let storage = Storage::init(context); // docs:start:get_note let public_key = storage.signing_public_key.get_note(); // docs:end:get_note diff --git a/noir-projects/noir-contracts/contracts/schnorr_hardcoded_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/schnorr_hardcoded_account_contract/src/main.nr index c16e7adfd62..12c9ab3c7da 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_hardcoded_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_hardcoded_account_contract/src/main.nr @@ -17,19 +17,19 @@ contract SchnorrHardcodedAccount { // Note: If you globally change the entrypoint signature don't forget to update default_entrypoint.ts #[aztec(private)] fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload) { - let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + let actions = AccountActions::init(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.entrypoint(app_payload, fee_payload); } #[aztec(private)] fn spend_private_authwit(inner_hash: Field) -> Field { - let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + let actions = AccountActions::init(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.spend_private_authwit(inner_hash) } #[aztec(public)] fn spend_public_authwit(inner_hash: Field) -> Field { - let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + let actions = AccountActions::init(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.spend_public_authwit(inner_hash) } @@ -42,7 +42,7 @@ contract SchnorrHardcodedAccount { #[aztec(public)] #[aztec(internal)] fn approve_public_authwit(outer_hash: Field) { - let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + let actions = AccountActions::init(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.approve_public_authwit(outer_hash) } diff --git a/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/main.nr index 8d4a89d013d..808c1ebc451 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/main.nr @@ -13,19 +13,19 @@ contract SchnorrSingleKeyAccount { // Note: If you globally change the entrypoint signature don't forget to update default_entrypoint.ts #[aztec(private)] fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload) { - let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + let actions = AccountActions::init(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.entrypoint(app_payload, fee_payload); } #[aztec(private)] fn spend_private_authwit(inner_hash: Field) -> Field { - let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + let actions = AccountActions::init(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.spend_private_authwit(inner_hash) } #[aztec(public)] fn spend_public_authwit(inner_hash: Field) -> Field { - let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + let actions = AccountActions::init(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.spend_public_authwit(inner_hash) } @@ -38,7 +38,7 @@ contract SchnorrSingleKeyAccount { #[aztec(public)] #[aztec(internal)] fn approve_public_authwit(outer_hash: Field) { - let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); + let actions = AccountActions::init(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); actions.approve_public_authwit(outer_hash) } diff --git a/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr index db8efde4a25..6d3932038f8 100644 --- a/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr @@ -3,8 +3,7 @@ contract StatefulTest { use dep::aztec::prelude::{PrivateContext, NoteHeader, Map, PublicMutable, PrivateSet, AztecAddress, FunctionSelector}; use dep::value_note::{balance_utils, utils::{increment, decrement}, value_note::{VALUE_NOTE_LEN, ValueNote}}; use dep::aztec::{ - deploy::deploy_contract as aztec_deploy_contract, - context::{PublicContext, Context, gas::GasOpts}, + deploy::deploy_contract as aztec_deploy_contract, context::{PublicContext, gas::GasOpts}, oracle::get_contract_instance::get_contract_instance, initializer::assert_is_initialized_private }; diff --git a/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr b/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr index f6f7b7e79c7..9a8431fb252 100644 --- a/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr @@ -3,8 +3,7 @@ contract StaticChild { use dep::aztec::prelude::{AztecAddress, FunctionSelector, PublicMutable, PrivateSet, PrivateContext, Deserialize}; use dep::aztec::{ - context::{PublicContext, Context, gas::GasOpts}, - protocol_types::{abis::{call_context::CallContext}}, + context::{PublicContext, gas::GasOpts}, protocol_types::{abis::{call_context::CallContext}}, note::{note_getter_options::NoteGetterOptions, note_header::NoteHeader}, keys::getters::{get_npk_m_hash, get_ivpk_m} }; 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 95467448503..58656cc2646 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr @@ -25,7 +25,7 @@ contract Test { use dep::aztec::{ keys::getters::{get_npk_m, get_ivpk_m, get_npk_m_hash}, - context::{Context, inputs::private_context_inputs::PrivateContextInputs}, + context::inputs::private_context_inputs::PrivateContextInputs, hash::{pedersen_hash, compute_secret_hash, ArgsHasher}, note::{ lifecycle::{create_note, destroy_note}, note_getter::{get_notes, view_notes}, diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/balances_map.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/balances_map.nr index e405431cd3b..dd9c3acf344 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/balances_map.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/balances_map.nr @@ -3,18 +3,17 @@ use dep::aztec::prelude::{ PrivateSet, Map }; use dep::aztec::{ - context::{PublicContext, Context}, hash::pedersen_hash, - protocol_types::constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, + hash::pedersen_hash, protocol_types::constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, note::{note_getter::view_notes, note_getter_options::SortOrder}, keys::getters::{get_npk_m_hash, get_ivpk_m} }; use crate::types::token_note::{TokenNote, OwnedNote}; -struct BalancesMap { - map: Map> +struct BalancesMap { + map: Map, Context> } -impl BalancesMap { +impl BalancesMap { pub fn new(context: Context, storage_slot: Field) -> Self { assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1."); Self { @@ -25,7 +24,9 @@ impl BalancesMap { ) } } +} +impl BalancesMap { unconstrained pub fn balance_of( self: Self, owner: AztecAddress @@ -55,13 +56,15 @@ impl BalancesMap { balance } +} +impl BalancesMap { pub fn add( self: Self, owner: AztecAddress, addend: U128 ) where T: NoteInterface + OwnedNote { - let context = self.map.context.private.unwrap(); + let context = self.map.context; let owner_ivpk_m = get_ivpk_m(context, owner); // We fetch the nullifier public key hash from the registry / from our PXE diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index e488c5f4f43..09f69cca44e 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -76,6 +76,10 @@ contract Token { } // docs:end:set_admin + unconstrained fn un_get_name() -> pub [u8; 31] { + storage.name.read_public().to_bytes() + } + #[aztec(public)] fn public_get_name() -> pub FieldCompressedString { storage.name.read_public() @@ -86,8 +90,8 @@ contract Token { storage.name.read_private() } - unconstrained fn un_get_name() -> pub [u8; 31] { - storage.name.read_public().to_bytes() + unconstrained fn un_get_symbol() -> pub [u8; 31] { + storage.symbol.read_public().to_bytes() } #[aztec(public)] @@ -100,8 +104,8 @@ contract Token { storage.symbol.read_private() } - unconstrained fn un_get_symbol() -> pub [u8; 31] { - storage.symbol.read_public().to_bytes() + unconstrained fn un_get_decimals() -> pub u8 { + storage.decimals.read_public() } #[aztec(public)] @@ -118,10 +122,6 @@ contract Token { // docs:end:read_decimals_private } - unconstrained fn un_get_decimals() -> pub u8 { - storage.decimals.read_public() - } - // docs:start:set_minter #[aztec(public)] fn set_minter(minter: AztecAddress, approve: bool) { diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types/balances_map.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types/balances_map.nr index e405431cd3b..5f45d7279dc 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types/balances_map.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/types/balances_map.nr @@ -3,18 +3,17 @@ use dep::aztec::prelude::{ PrivateSet, Map }; use dep::aztec::{ - context::{PublicContext, Context}, hash::pedersen_hash, - protocol_types::constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, + hash::pedersen_hash, protocol_types::constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, note::{note_getter::view_notes, note_getter_options::SortOrder}, keys::getters::{get_npk_m_hash, get_ivpk_m} }; use crate::types::token_note::{TokenNote, OwnedNote}; -struct BalancesMap { - map: Map> +struct BalancesMap { + map: Map, Context> } -impl BalancesMap { +impl BalancesMap { pub fn new(context: Context, storage_slot: Field) -> Self { assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1."); Self { @@ -25,7 +24,9 @@ impl BalancesMap { ) } } +} +impl BalancesMap { unconstrained pub fn balance_of( self: Self, owner: AztecAddress @@ -55,13 +56,15 @@ impl BalancesMap { balance } +} +impl BalancesMap { pub fn add( self: Self, owner: AztecAddress, addend: U128 ) where T: NoteInterface + OwnedNote { - let context = self.map.context.private.unwrap(); + let context = self.map.context; let owner_ivpk_m = get_ivpk_m(context, owner); // We fetch the nullifier public key hash from the registry / from our PXE diff --git a/noir/noir-repo/aztec_macros/src/lib.rs b/noir/noir-repo/aztec_macros/src/lib.rs index c3638b7760d..5326920511b 100644 --- a/noir/noir-repo/aztec_macros/src/lib.rs +++ b/noir/noir-repo/aztec_macros/src/lib.rs @@ -14,7 +14,7 @@ use transforms::{ note_interface::{generate_note_interface_impl, inject_note_exports}, storage::{ assign_storage_slots, check_for_storage_definition, check_for_storage_implementation, - generate_storage_implementation, generate_storage_layout, + generate_storage_implementation, generate_storage_layout, inject_context_in_storage, }, }; @@ -102,6 +102,7 @@ fn transform_module( let storage_defined = maybe_storage_struct_name.is_some(); if let Some(ref storage_struct_name) = maybe_storage_struct_name { + inject_context_in_storage(module)?; if !check_for_storage_implementation(module, storage_struct_name) { generate_storage_implementation(module, storage_struct_name)?; } diff --git a/noir/noir-repo/aztec_macros/src/transforms/functions.rs b/noir/noir-repo/aztec_macros/src/transforms/functions.rs index 45bce6a681a..142e6ea1b3d 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/functions.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/functions.rs @@ -67,7 +67,7 @@ pub fn transform_function( // Add access to the storage struct if let Some(storage_struct_name) = storage_struct_name { - let storage_def = abstract_storage(storage_struct_name, &ty.to_lowercase(), false); + let storage_def = abstract_storage(storage_struct_name, false); func.def.body.statements.insert(0, storage_def); } @@ -221,10 +221,7 @@ pub fn export_fn_abi( /// /// This will allow developers to access their contract' storage struct in unconstrained functions pub fn transform_unconstrained(func: &mut NoirFunction, storage_struct_name: String) { - func.def - .body - .statements - .insert(0, abstract_storage(storage_struct_name, "Unconstrained", true)); + func.def.body.statements.insert(0, abstract_storage(storage_struct_name, true)); } /// Helper function that returns what the private context would look like in the ast @@ -597,7 +594,7 @@ fn abstract_return_values(func: &NoirFunction) -> Result>, /// ```noir /// #[aztec(private)] /// fn lol() { -/// let storage = Storage::init(Context::private(context)); +/// let storage = Storage::init(context); /// } /// ``` /// @@ -605,33 +602,28 @@ fn abstract_return_values(func: &NoirFunction) -> Result>, /// ```noir /// #[aztec(public)] /// fn lol() { -/// let storage = Storage::init(Context::public(context)); +/// let storage = Storage::init(context); /// } /// ``` /// /// For unconstrained functions: /// ```noir /// unconstrained fn lol() { -/// let storage = Storage::init(Context::none()); +/// let storage = Storage::init(()); /// } -fn abstract_storage(storage_struct_name: String, typ: &str, unconstrained: bool) -> Statement { - let init_context_call = if unconstrained { - call( - variable_path(chained_dep!("aztec", "context", "Context", "none")), // Path - vec![], // args - ) +fn abstract_storage(storage_struct_name: String, unconstrained: bool) -> Statement { + let context_expr = if unconstrained { + // Note that the literal unit type (i.e. '()') is not the same as a tuple with zero elements + expression(ExpressionKind::Literal(Literal::Unit)) } else { - call( - variable_path(chained_dep!("aztec", "context", "Context", typ)), // Path - vec![mutable_reference("context")], // args - ) + mutable_reference("context") }; assignment( "storage", // Assigned to call( variable_path(chained_path!(storage_struct_name.as_str(), "init")), // Path - vec![init_context_call], // args + vec![context_expr], // args ), ) } diff --git a/noir/noir-repo/aztec_macros/src/transforms/storage.rs b/noir/noir-repo/aztec_macros/src/transforms/storage.rs index 1e3cc011715..0a210934827 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/storage.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/storage.rs @@ -1,7 +1,7 @@ use noirc_errors::Span; use noirc_frontend::ast::{ BlockExpression, Expression, ExpressionKind, FunctionDefinition, Ident, Literal, NoirFunction, - NoirStruct, PathKind, Pattern, StatementKind, TypeImpl, UnresolvedType, UnresolvedTypeData, + NoirStruct, Pattern, StatementKind, TypeImpl, UnresolvedType, UnresolvedTypeData, }; use noirc_frontend::{ graph::CrateId, @@ -16,7 +16,7 @@ use noirc_frontend::{ }; use crate::{ - chained_dep, chained_path, + chained_path, utils::{ ast_utils::{ call, expression, ident, ident_path, is_custom_attribute, lambda, make_statement, @@ -48,7 +48,58 @@ pub fn check_for_storage_definition( Ok(result.iter().map(|&r#struct| r#struct.name.0.contents.clone()).next()) } -// Check to see if the user has defined a storage struct +// Injects the Context generic in each of the Storage struct fields to avoid boilerplate, +// taking maps into account (including nested maps) +fn inject_context_in_storage_field(field: &mut UnresolvedType) -> Result<(), AztecMacroError> { + match &mut field.typ { + UnresolvedTypeData::Named(path, generics, _) => { + generics.push(make_type(UnresolvedTypeData::Named( + ident_path("Context"), + vec![], + false, + ))); + match path.segments.last().unwrap().0.contents.as_str() { + "Map" => inject_context_in_storage_field(&mut generics[1]), + _ => Ok(()), + } + } + _ => Err(AztecMacroError::CouldNotInjectContextGenericInStorage { + secondary_message: Some(format!("Unsupported type: {:?}", field.typ)), + }), + } +} + +// Injects the Context generic in the storage struct to avoid boilerplate +// Transforms this: +// struct Storage { +// a_var: SomeStoragePrimitive, +// a_map: Map>, +// } +// +// Into this: +// +// struct Storage { +// a_var: SomeStoragePrimitive, +// a_map: Map, Context>, +// } +pub fn inject_context_in_storage(module: &mut SortedModule) -> Result<(), AztecMacroError> { + let storage_struct = module + .types + .iter_mut() + .find(|r#struct| { + r#struct.attributes.iter().any(|attr| is_custom_attribute(attr, "aztec(storage)")) + }) + .unwrap(); + storage_struct.generics.push(ident("Context")); + storage_struct + .fields + .iter_mut() + .map(|(_, field)| inject_context_in_storage_field(field)) + .collect::, _>>()?; + Ok(()) +} + +// Check to see if the user has defined an impl for the storage struct pub fn check_for_storage_implementation( module: &SortedModule, storage_struct_name: &String, @@ -79,22 +130,22 @@ pub fn generate_storage_field_constructor( variable("context"), slot, lambda( + // This lambda will be equivalent to the following + // | context, slot | { T::new(context, slot) } + // Since the `new` function has type bindings for its arguments, we don't specify the types + // of either context nor slot, and avoid that way having to deal with the generic context + // type. vec![ - ( - pattern("context"), - make_type(UnresolvedTypeData::Named( - chained_dep!("aztec", "context", "Context"), - vec![], - true, - )), - ), + (pattern("context"), make_type(UnresolvedTypeData::Unspecified)), ( Pattern::Identifier(ident("slot")), - make_type(UnresolvedTypeData::FieldElement), + make_type(UnresolvedTypeData::Unspecified), ), ], generate_storage_field_constructor( - &(type_ident.clone(), generics.iter().last().unwrap().clone()), + // Map is expected to have three generic parameters: key, value and context (i.e. + // Map. Here `get(1)` fetches the value type. + &(type_ident.clone(), generics.get(1).unwrap().clone()), variable("slot"), )?, ), @@ -113,15 +164,15 @@ pub fn generate_storage_field_constructor( // Generates the Storage implementation block from the Storage struct definition if it does not exist /// From: /// -/// struct Storage { -/// a_map: Map>, -/// a_nested_map: Map>>, -/// a_field: SomeStoragePrimitive, +/// struct Storage { +/// a_map: Map, Context>, +/// a_nested_map: Map, Context>, Context>, +/// a_field: SomeStoragePrimitive, /// } /// /// To: /// -/// impl Storage { +/// impl Storage { /// fn init(context: Context) -> Self { /// Storage { /// a_map: Map::new(context, 0, |context, slot| { @@ -167,17 +218,15 @@ pub fn generate_storage_implementation( ExpressionKind::constructor((chained_path!(storage_struct_name), field_constructors)), ))); + // This is the type over which the impl is generic. + let generic_context_ident = ident("Context"); + let generic_context_type = + make_type(UnresolvedTypeData::Named(ident_path("Context"), vec![], true)); + let init = NoirFunction::normal(FunctionDefinition::normal( &ident("init"), &vec![], - &[( - ident("context"), - make_type(UnresolvedTypeData::Named( - chained_dep!("aztec", "context", "Context"), - vec![], - true, - )), - )], + &[(ident("context"), generic_context_type.clone())], &BlockExpression { statements: vec![storage_constructor_statement] }, &[], &return_type(chained_path!("Self")), @@ -185,11 +234,16 @@ pub fn generate_storage_implementation( let storage_impl = TypeImpl { object_type: UnresolvedType { - typ: UnresolvedTypeData::Named(chained_path!(storage_struct_name), vec![], true), + typ: UnresolvedTypeData::Named( + chained_path!(storage_struct_name), + vec![generic_context_type.clone()], + true, + ), span: Some(Span::default()), }, type_span: Span::default(), - generics: vec![], + generics: vec![generic_context_ident], + methods: vec![(init, Span::default())], }; module.impls.push(storage_impl); @@ -341,7 +395,9 @@ pub fn assign_storage_slots( let mut storage_slot: u64 = 1; for (index, (_, expr_id)) in storage_constructor_expression.fields.iter().enumerate() { - let fields = storage_struct.borrow().get_fields(&[]); + let fields = storage_struct + .borrow() + .get_fields(&storage_constructor_expression.struct_generics); let (field_name, field_type) = fields.get(index).unwrap(); let new_call_expression = match context.def_interner.expression(expr_id) { HirExpression::Call(hir_call_expression) => Ok(hir_call_expression), diff --git a/noir/noir-repo/aztec_macros/src/utils/errors.rs b/noir/noir-repo/aztec_macros/src/utils/errors.rs index 8d1a19bfea9..51aea3d052f 100644 --- a/noir/noir-repo/aztec_macros/src/utils/errors.rs +++ b/noir/noir-repo/aztec_macros/src/utils/errors.rs @@ -16,6 +16,7 @@ pub enum AztecMacroError { CouldNotImplementNoteInterface { span: Option, secondary_message: Option }, MultipleStorageDefinitions { span: Option }, CouldNotExportStorageLayout { span: Option, secondary_message: Option }, + CouldNotInjectContextGenericInStorage { secondary_message: Option }, CouldNotExportFunctionAbi { span: Option, secondary_message: Option }, CouldNotGenerateContractInterface { secondary_message: Option }, EventError { span: Span, message: String }, @@ -76,6 +77,11 @@ impl From for MacroError { secondary_message, span, }, + AztecMacroError::CouldNotInjectContextGenericInStorage { secondary_message } => MacroError { + primary_message: "Could not inject context generic in storage".to_string(), + secondary_message, + span: None + }, AztecMacroError::CouldNotExportFunctionAbi { secondary_message, span } => MacroError { primary_message: "Could not generate and export function abi".to_string(), secondary_message,