Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion noir-projects/aztec-nr/aztec/src/macros/storage.nr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use std::meta::unquote;
/// generating a `storage_layout()` getter on the contract struct.
pub comptime mut global STORAGE_LAYOUT_NAME: CHashMap<Module, Quoted> = CHashMap::new();

/// This function
/// Declares the contract's storage.
///
/// This function:
/// - marks the contract as having storage, so that `macros::utils::module_has_storage` will return true,
/// - marks the struct `s` as the one describing the storage layout of a contract,
/// - generates an `impl` block for the storage struct with an `init` function (call to `init` is then injected at the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,34 @@ impl<T, let InitialDelay: u64> DelayedPublicMutable<T, InitialDelay, &mut Privat
where
T: Eq,
{
/// This function performs a historical public storage read (which is in the order of 4k gates), **regardless of
/// `T`'s packed length**. This is because [`DelayedPublicMutable::schedule_value_change`] stores not just the
/// value but also its hash: this function obtains the preimage from an oracle and proves that it matches the hash
/// from public storage.
///
/// Because of this reason, if two mutable values are often privately read together it can be convenient to group
/// them in a single type `T`. Note however that this will result in them sharing the mutation delay:
///
/// ```noir
/// // Bad: reading both `paused` and `fee` will require two historical public storage reads
/// #[storage]
/// struct Storage<C> {
/// paused: DelayedPublicMutable<bool, INITIAL_DELAY_S, C>,
/// fee: DelayedPublicMutable<Field, INITIAL_DELAY_S, C>,
/// }
///
/// // Good: both `paused` and `fee` are retrieved in a single historical public storage read
/// #[derive(Packable)]
/// struct Config {
/// paused: bool,
/// fee: Field,
/// }
///
/// #[storage]
/// struct Storage<Context> {
/// config: DelayedPublicMutable<Config, INITIAL_DELAY_S, Context>,
/// }
/// ```
pub fn get_current_value(self) -> T
where
T: Packable,
Expand Down
56 changes: 56 additions & 0 deletions noir-projects/aztec-nr/aztec/src/state_vars/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,62 @@
//! Due to Aztec contracts being able to store both public and private state, there are many more different types of
//! state variables, each with their nuance and use cases. Understanding these is key to understanding how a contract
//! works.
//!
//! ## Packing for Efficient Access
//!
//! Because all state variables are fully independent, when a contract reads or writes one of them all others are left
//! untouched. This is good for isolation, but in some cases users may want to group variables together for more
//! efficient access, for example to access multiple values in a single storage read or write.
//!
//! The pattern to follow is to **group related values in a `struct`**, just like in Solidity. How values are packed
//! inside this `struct` is governed by the [`crate::protocol::traits::Packable`] trait, which must be `#[derive]`'d or
//! manually implemented - see the [`Packable`](crate::protocol::traits::Packable)'s docs on how to do this.
//!
//! ```noir
//! // Inefficient reads and writes - each bool is assigned a distinct storage slot, so reading or writing both
//! requires
//! // executing `SLOAD` or `SSTORE` twice.
//! #[storage]
//! struct Storage<C> {
//! a: PublicMutable<bool, C>,
//! b: PublicMutable<bool, C>,
//! }
//!
//! // By storing the booleans in a single struct and implementing the Packable trait with tight packing, we can now
//! // read and write both values in a single `SLOAD` or `SSTORE` opcode.
//! struct TwoBooleans {
//! a: bool,
//! b: bool,
//! }
//!
//! impl aztec::protocol::traits::Packable for TwoBooleans {
//! let N: u32 = 1;
//!
//! fn pack(self) -> [Field; Self::N] {
//! [(self.a as Field) * 2.pow_32(1) + (self.b as Field)]
//! }
//!
//! fn unpack(packed: [Field; Self::N]) -> Self {
//! let b = (packed[0] as u1) != 0;
//! let a = (((packed[0] - b as Field) / 2.pow_32(1)) as u1) != 0;
//!
//! Self { a, b }
//! }
//! }
//!
//! #[storage]
//! struct Storage<C> {
//! a_and_b: PublicMutable<TwoBooleans, C>,
//! }
//! ```
//!
//! Note that private state variables and public ones that can be read from private (like
//! [`PublicImmutable`](crate::state_vars::PublicImmutable) and
//! [`DelayedPublicMutable`](crate::state_vars::DelayedPublicMutable)) benefit from packing multiple values in the same
//! `struct` even if there is no need for tight packing (e.g. if all values are `Field`s), since they often work by
//! reading the _hash_ of the entire value. Many values in the same `struct` will result in a single hash, and
//! therefore
//! a single read.

mod state_variable;
pub use state_variable::StateVariable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,11 +287,10 @@ impl<T> PublicImmutable<T, &mut PrivateContext> {
/// ## Cost
///
/// A nullifier existence request is pushed to the context, which will be verified by the kernel circuit.
/// Additionally, a historical public storage read at the anchor block is performed for a single storage slot,
/// **regardless of `T`'s packed length**. This is because [`PublicImmutable::initialize`] stores not just the
/// value
/// but also its hash: this function obtains the preimage from an oracle and proves that it matches the hash from
/// public storage.
/// Additionally, a historical public storage read at the anchor block (which is on the order of 4k gates) is
/// performed for a single storage slot, **regardless of `T`'s packed length**. This is because
/// [`PublicImmutable::initialize`] stores not just the value but also its hash: this function obtains the preimage
/// from an oracle and proves that it matches the hash from public storage.
///
/// Because of this reason it is convenient to group together all of a contract's public immutable values that are
/// read privately in a single type `T`:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -796,3 +796,31 @@ where
mod impls {
use crate::serialization::{Deserialize, Serialize};
}

#[test]
unconstrained fn bounded_vec_serialization() {
// Test empty BoundedVec
let empty_vec: BoundedVec<Field, 3> = BoundedVec::from_array([]);
let serialized = empty_vec.serialize();
let deserialized = BoundedVec::<Field, 3>::deserialize(serialized);
assert_eq(empty_vec, deserialized);
assert_eq(deserialized.len(), 0);

// Test partially filled BoundedVec
let partial_vec: BoundedVec<[u32; 2], 3> = BoundedVec::from_array([[1, 2]]);
let serialized = partial_vec.serialize();
let deserialized = BoundedVec::<[u32; 2], 3>::deserialize(serialized);
assert_eq(partial_vec, deserialized);
assert_eq(deserialized.len(), 1);
assert_eq(deserialized.get(0), [1, 2]);

// Test full BoundedVec
let full_vec: BoundedVec<[u32; 2], 3> = BoundedVec::from_array([[1, 2], [3, 4], [5, 6]]);
let serialized = full_vec.serialize();
let deserialized = BoundedVec::<[u32; 2], 3>::deserialize(serialized);
assert_eq(full_vec, deserialized);
assert_eq(deserialized.len(), 3);
assert_eq(deserialized.get(0), [1, 2]);
assert_eq(deserialized.get(1), [3, 4]);
assert_eq(deserialized.get(2), [5, 6]);
}
Loading
Loading