Skip to content

Commit

Permalink
feat: Packable trait + using it for public storage (#11136)
Browse files Browse the repository at this point in the history
  • Loading branch information
benesjan authored Jan 22, 2025
1 parent 887b8ff commit e74ce15
Show file tree
Hide file tree
Showing 36 changed files with 490 additions and 142 deletions.
25 changes: 25 additions & 0 deletions docs/docs/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,31 @@ keywords: [sandbox, aztec, notes, migration, updating, upgrading]

Aztec is in full-speed development. Literally every version breaks compatibility with the previous ones. This page attempts to target errors and difficulties you might encounter when upgrading, and how to resolve them.

## TBD

### [Aztec.nr] Introduction of `Packable` trait
We have introduced a `Packable` trait that allows types to be serialized and deserialized with a focus on minimizing the size of the resulting Field array.
This is in contrast to the `Serialize` and `Deserialize` traits, which follows Noir's intrinsic serialization format.
This is a breaking change because we now require `Packable` trait implementation for any type that is to be stored in contract storage.

Example implementation of Packable trait for `U128` type from `noir::std`:

```
use crate::traits::{Packable, ToField};
let U128_PACKED_LEN: u32 = 1;
impl Packable<U128_PACKED_LEN> for U128 {
fn pack(self) -> [Field; U128_PACKED_LEN] {
[self.to_field()]
}
fn unpack(fields: [Field; U128_PACKED_LEN]) -> Self {
U128::from_integer(fields[0])
}
}
```

## 0.72.0
### Some functions in `aztec.js` and `@aztec/accounts` are now async
In our efforts to make libraries more browser-friendly and providing with more bundling options for `bb.js` (like a non top-level-await version), some functions are being made async, in particular those that access our cryptographic functions.
Expand Down
10 changes: 5 additions & 5 deletions noir-projects/aztec-nr/aztec/src/context/public_context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::hash::{
use dep::protocol_types::abis::function_selector::FunctionSelector;
use dep::protocol_types::address::{AztecAddress, EthAddress};
use dep::protocol_types::constants::MAX_FIELD_VALUE;
use dep::protocol_types::traits::{Deserialize, Empty, Serialize};
use dep::protocol_types::traits::{Empty, Packable, Serialize};

pub struct PublicContext {
pub args_hash: Option<Field>,
Expand Down Expand Up @@ -219,9 +219,9 @@ impl PublicContext {

pub fn storage_read<T, let N: u32>(self, storage_slot: Field) -> T
where
T: Deserialize<N>,
T: Packable<N>,
{
T::deserialize(self.raw_storage_read(storage_slot))
T::unpack(self.raw_storage_read(storage_slot))
}

pub fn raw_storage_write<let N: u32>(_self: Self, storage_slot: Field, values: [Field; N]) {
Expand All @@ -233,9 +233,9 @@ impl PublicContext {

pub fn storage_write<T, let N: u32>(self, storage_slot: Field, value: T)
where
T: Serialize<N>,
T: Packable<N>,
{
self.raw_storage_write(storage_slot, value.serialize());
self.raw_storage_write(storage_slot, value.pack());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::oracle::{
execution::{get_block_number, get_chain_id, get_contract_address, get_version},
storage::storage_read,
};
use dep::protocol_types::{address::AztecAddress, traits::Deserialize};
use dep::protocol_types::{address::AztecAddress, traits::Packable};

pub struct UnconstrainedContext {
block_number: u32,
Expand Down Expand Up @@ -62,8 +62,8 @@ impl UnconstrainedContext {

pub unconstrained fn storage_read<T, let N: u32>(self, storage_slot: Field) -> T
where
T: Deserialize<N>,
T: Packable<N>,
{
T::deserialize(self.raw_storage_read(storage_slot))
T::unpack(self.raw_storage_read(storage_slot))
}
}
2 changes: 1 addition & 1 deletion noir-projects/aztec-nr/aztec/src/oracle/pxe_db.nr
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use protocol_types::{address::AztecAddress, traits::{Deserialize, Serialize}};

/// Stores arbitrary information in a per-contract non-volatile database, which can later be retrieved with `load`. If
/// data was already stored at this slot, it is overwrriten.
/// data was already stored at this slot, it is overwritten.
pub unconstrained fn store<T, let N: u32>(contract_address: AztecAddress, slot: Field, value: T)
where
T: Serialize<N>,
Expand Down
12 changes: 6 additions & 6 deletions noir-projects/aztec-nr/aztec/src/oracle/storage.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use dep::protocol_types::{address::AztecAddress, traits::Deserialize};
use dep::protocol_types::{address::AztecAddress, traits::Packable};

#[oracle(storageRead)]
unconstrained fn storage_read_oracle<let N: u32>(
Expand Down Expand Up @@ -27,14 +27,14 @@ pub unconstrained fn storage_read<T, let N: u32>(
block_number: u32,
) -> T
where
T: Deserialize<N>,
T: Packable<N>,
{
T::deserialize(raw_storage_read(address, storage_slot, block_number))
T::unpack(raw_storage_read(address, storage_slot, block_number))
}

mod tests {
use crate::oracle::storage::{raw_storage_read, storage_read};
use dep::protocol_types::address::AztecAddress;
use dep::protocol_types::{address::AztecAddress, traits::{FromField, Packable}};

use crate::test::mocks::mock_struct::MockStruct;
use std::test::OracleMock;
Expand All @@ -47,7 +47,7 @@ mod tests {
unconstrained fn test_raw_storage_read() {
let written = MockStruct { a: 13, b: 42 };

let _ = OracleMock::mock("storageRead").returns(written.serialize());
let _ = OracleMock::mock("storageRead").returns(written.pack());

let read: [Field; 2] = raw_storage_read(address, slot, block_number);
assert_eq(read[0], 13);
Expand All @@ -58,7 +58,7 @@ mod tests {
unconstrained fn test_storage_read() {
let written = MockStruct { a: 13, b: 42 };

let _ = OracleMock::mock("storageRead").returns(written.serialize());
let _ = OracleMock::mock("storageRead").returns(written.pack());

let read: MockStruct = storage_read(address, slot, block_number);
assert_eq(read.a, 13);
Expand Down
7 changes: 2 additions & 5 deletions noir-projects/aztec-nr/aztec/src/state_vars/map.nr
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
use crate::state_vars::storage::Storage;
use dep::protocol_types::{
storage::map::derive_storage_slot_in_map,
traits::{Deserialize, Serialize, ToField},
};
use dep::protocol_types::{storage::map::derive_storage_slot_in_map, traits::{Packable, ToField}};

// docs:start:map
pub struct Map<K, V, Context> {
Expand All @@ -14,7 +11,7 @@ pub struct Map<K, V, Context> {

impl<K, T, Context, let N: u32> Storage<T, N> for Map<K, T, Context>
where
T: Serialize<N> + Deserialize<N>,
T: Packable<N>,
{
fn get_storage_slot(self) -> Field {
self.storage_slot
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use dep::protocol_types::{
constants::GENERATOR_INDEX__INITIALIZATION_NULLIFIER,
hash::poseidon2_hash_with_separator,
traits::{Deserialize, Serialize},
constants::GENERATOR_INDEX__INITIALIZATION_NULLIFIER, hash::poseidon2_hash_with_separator,
traits::Packable,
};

use crate::context::{PrivateContext, UnconstrainedContext};
Expand All @@ -24,7 +23,7 @@ pub struct PrivateImmutable<Note, Context> {

impl<T, Context, let N: u32> Storage<T, N> for PrivateImmutable<T, Context>
where
T: Serialize<N> + Deserialize<N>,
T: Packable<N>,
{
fn get_storage_slot(self) -> Field {
self.storage_slot
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use dep::protocol_types::{
constants::GENERATOR_INDEX__INITIALIZATION_NULLIFIER,
hash::poseidon2_hash_with_separator,
traits::{Deserialize, Serialize},
constants::GENERATOR_INDEX__INITIALIZATION_NULLIFIER, hash::poseidon2_hash_with_separator,
traits::Packable,
};

use crate::context::{PrivateContext, UnconstrainedContext};
Expand All @@ -26,7 +25,7 @@ mod test;

impl<T, Context, let N: u32> Storage<T, N> for PrivateMutable<T, Context>
where
T: Serialize<N> + Deserialize<N>,
T: Packable<N>,
{
fn get_storage_slot(self) -> Field {
self.storage_slot
Expand Down
7 changes: 3 additions & 4 deletions noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ use crate::note::{
};
use crate::state_vars::storage::Storage;
use dep::protocol_types::{
abis::read_request::ReadRequest,
constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL,
traits::{Deserialize, Serialize},
abis::read_request::ReadRequest, constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL,
traits::Packable,
};

// docs:start:struct
Expand All @@ -25,7 +24,7 @@ pub struct PrivateSet<Note, Context> {

impl<T, Context, let N: u32> Storage<T, N> for PrivateSet<T, Context>
where
T: Serialize<N> + Deserialize<N>,
T: Packable<N>,
{
fn get_storage_slot(self) -> Field {
self.storage_slot
Expand Down
24 changes: 11 additions & 13 deletions noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use crate::{
context::{PrivateContext, PublicContext, UnconstrainedContext},
history::public_storage::PublicStorageHistoricalRead,
state_vars::storage::Storage,
};
use dep::protocol_types::{
constants::INITIALIZATION_SLOT_SEPARATOR,
traits::{Deserialize, Serialize},
};
use dep::protocol_types::{constants::INITIALIZATION_SLOT_SEPARATOR, traits::Packable};

/// Stores an immutable value in public state which can be read from public, private and unconstrained execution
/// contexts.
Expand All @@ -18,7 +16,7 @@ pub struct PublicImmutable<T, Context> {

impl<T, Context, let N: u32> Storage<T, N> for PublicImmutable<T, Context>
where
T: Serialize<N> + Deserialize<N>,
T: Packable<N>,
{
fn get_storage_slot(self) -> Field {
self.storage_slot
Expand All @@ -38,9 +36,9 @@ impl<T, Context> PublicImmutable<T, Context> {
// docs:end:public_immutable_struct_new
}

impl<T, let T_SERIALIZED_LEN: u32> PublicImmutable<T, &mut PublicContext>
impl<T, let T_PACKED_LEN: u32> PublicImmutable<T, &mut PublicContext>
where
T: Serialize<T_SERIALIZED_LEN> + Deserialize<T_SERIALIZED_LEN>,
T: Packable<T_PACKED_LEN>,
{
// docs:start:public_immutable_struct_write
pub fn initialize(self, value: T) {
Expand All @@ -63,29 +61,29 @@ where
// docs:end:public_immutable_struct_read
}

impl<T, let T_SERIALIZED_LEN: u32> PublicImmutable<T, UnconstrainedContext>
impl<T, let T_PACKED_LEN: u32> PublicImmutable<T, UnconstrainedContext>
where
T: Serialize<T_SERIALIZED_LEN> + Deserialize<T_SERIALIZED_LEN>,
T: Packable<T_PACKED_LEN>,
{
pub unconstrained fn read(self) -> T {
self.context.storage_read(self.storage_slot)
}
}

impl<T, let T_SERIALIZED_LEN: u32> PublicImmutable<T, &mut PrivateContext>
impl<T, let T_PACKED_LEN: u32> PublicImmutable<T, &mut PrivateContext>
where
T: Serialize<T_SERIALIZED_LEN> + Deserialize<T_SERIALIZED_LEN>,
T: Packable<T_PACKED_LEN>,
{
pub fn read(self) -> T {
let header = self.context.get_block_header();
let mut fields = [0; T_SERIALIZED_LEN];
let mut fields = [0; T_PACKED_LEN];

for i in 0..fields.len() {
fields[i] = header.public_storage_historical_read(
self.storage_slot + i as Field,
(*self.context).this_address(),
);
}
T::deserialize(fields)
T::unpack(fields)
}
}
12 changes: 6 additions & 6 deletions noir-projects/aztec-nr/aztec/src/state_vars/public_mutable.nr
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::context::{PublicContext, UnconstrainedContext};
use crate::state_vars::storage::Storage;
use dep::protocol_types::traits::{Deserialize, Serialize};
use dep::protocol_types::traits::Packable;

// docs:start:public_mutable_struct
pub struct PublicMutable<T, Context> {
Expand All @@ -11,7 +11,7 @@ pub struct PublicMutable<T, Context> {

impl<T, Context, let N: u32> Storage<T, N> for PublicMutable<T, Context>
where
T: Serialize<N> + Deserialize<N>,
T: Packable<N>,
{
fn get_storage_slot(self) -> Field {
self.storage_slot
Expand All @@ -31,9 +31,9 @@ impl<T, Context> PublicMutable<T, Context> {
// docs:end:public_mutable_struct_new
}

impl<T, let T_SERIALIZED_LEN: u32> PublicMutable<T, &mut PublicContext>
impl<T, let T_PACKED_LEN: u32> PublicMutable<T, &mut PublicContext>
where
T: Serialize<T_SERIALIZED_LEN> + Deserialize<T_SERIALIZED_LEN>,
T: Packable<T_PACKED_LEN>,
{
// docs:start:public_mutable_struct_read
pub fn read(self) -> T {
Expand All @@ -48,9 +48,9 @@ where
// docs:end:public_mutable_struct_write
}

impl<T, let T_SERIALIZED_LEN: u32> PublicMutable<T, UnconstrainedContext>
impl<T, let T_PACKED_LEN: u32> PublicMutable<T, UnconstrainedContext>
where
T: Deserialize<T_SERIALIZED_LEN>,
T: Packable<T_PACKED_LEN>,
{
pub unconstrained fn read(self) -> T {
self.context.storage_read(self.storage_slot)
Expand Down
11 changes: 5 additions & 6 deletions noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable.nr
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use dep::protocol_types::{
address::AztecAddress,
hash::{poseidon2_hash, poseidon2_hash_with_separator},
traits::{Deserialize, FromField, Serialize, ToField},
traits::{FromField, Packable, ToField},
utils::arrays::array_concat,
};

Expand Down Expand Up @@ -38,7 +38,7 @@ global HASH_SEPARATOR: u32 = 2;
// can actually use it here
impl<T, let INITIAL_DELAY: u32, Context, let N: u32> Storage<T, N> for SharedMutable<T, INITIAL_DELAY, Context>
where
T: Serialize<N> + Deserialize<N>,
T: Packable<N>,
{
fn get_storage_slot(self) -> Field {
self.storage_slot
Expand Down Expand Up @@ -225,12 +225,12 @@ where
// scheduled. Therefore, the hints must then correspond to uninitialized scheduled changes.
assert_eq(
value_change_hint,
ScheduledValueChange::deserialize(zeroed()),
ScheduledValueChange::unpack(zeroed()),
"Non-zero value change for zero hash",
);
assert_eq(
delay_change_hint,
ScheduledDelayChange::deserialize(zeroed()),
ScheduledDelayChange::unpack(zeroed()),
"Non-zero delay change for zero hash",
);
};
Expand All @@ -242,8 +242,7 @@ where
value_change: ScheduledValueChange<T>,
delay_change: ScheduledDelayChange<INITIAL_DELAY>,
) -> Field {
let concatenated: [Field; 4] =
array_concat(value_change.serialize(), delay_change.serialize());
let concatenated: [Field; 4] = array_concat(value_change.pack(), delay_change.pack());
poseidon2_hash(concatenated)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use dep::protocol_types::traits::{Deserialize, Serialize};
use dep::protocol_types::traits::Packable;
use std::cmp::min;

mod test;
Expand Down Expand Up @@ -125,8 +125,8 @@ impl<let INITIAL_DELAY: u32> ScheduledDelayChange<INITIAL_DELAY> {
}
}

impl<let INITIAL_DELAY: u32> Serialize<1> for ScheduledDelayChange<INITIAL_DELAY> {
fn serialize(self) -> [Field; 1] {
impl<let INITIAL_DELAY: u32> Packable<1> for ScheduledDelayChange<INITIAL_DELAY> {
fn pack(self) -> [Field; 1] {
// We pack all three u32 values into a single U128, which is made up of two u64 limbs.
// Low limb: [ pre_inner: u32 | post_inner: u32 ]
// High limb: [ empty | pre_is_some: u8 | post_is_some: u8 | block_of_change: u32 ]
Expand All @@ -141,10 +141,8 @@ impl<let INITIAL_DELAY: u32> Serialize<1> for ScheduledDelayChange<INITIAL_DELAY

[packed.to_integer()]
}
}

impl<let INITIAL_DELAY: u32> Deserialize<1> for ScheduledDelayChange<INITIAL_DELAY> {
fn deserialize(input: [Field; 1]) -> Self {
fn unpack(input: [Field; 1]) -> Self {
let packed = U128::from_integer(input[0]);

// We use division and modulo to clear the bits that correspond to other values when unpacking.
Expand Down
Loading

0 comments on commit e74ce15

Please sign in to comment.