Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Packable trait + using it for public storage #11136

Merged
merged 16 commits into from
Jan 22, 2025
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
Loading