Skip to content

Commit

Permalink
feat: shared mutable storage (#5490)
Browse files Browse the repository at this point in the history
(Large) part of
#4761.

This is an initial implementation of `SharedMutableStorage`, with some
limitations. I think those are best worked on in follow-up PRs, once we
have the bones working.

The bulk of the SharedMutable pattern is in `ScheduledValueChange`, a
pure Noir struct that has all of the block number related logic.
`SharedMutable` then makes a state variable out of that struct, adding
public storage access both in public and private (via historical reads -
see #5379), and using the new `request_max_block_number` function (from
#5251).

I made an effort to test as much as I could of these in Noir, with
partial success in the case of `SharedMutable` due to lack of certain
features, notably noir-lang/noir#4652. There
is also an end-to-end test that goes through two scheuled value changes,
showing that scheduled values do not affect the current one.

I added some inline docs but didn't include proper docsite pages yet so
that we can discuss the implementation, API, etc., and make e.g.
renamings less troublesome.

### Notable implementation details

I chose to make the delay a type parameter instead of a value mostly
because of two reasons:
- it lets us nicely serialize and deserialize `ScheduledValueChange`
without including this field (which we are not currently interested in
storing)
- it lets us declare a state variable of type `SharedMutable<T, DELAY>`
without having to change the signature of the `new` function, which is
automatically injected by the macro.

Overall I think this is fine, especially since we may later make the
delay mutable (see below), but still worth noting.

Additionally, I created a simple `public_storage` module to get slightly
nicer API and encapsulation. This highlighted a Noir issue
(noir-lang/noir#4633), which currently only
affects public historical reads but will also affect current reads once
we migrate to using the AVM opcodes.

### Future work

- #5491
- #5492 (this
takes care of padding during storage slot allocation)
- #5501
- #5493

---------

Co-authored-by: Jan Beneš <janbenes1234@gmail.com>
  • Loading branch information
nventuro and benesjan committed Apr 10, 2024
1 parent ec122c9 commit c4e41a9
Show file tree
Hide file tree
Showing 14 changed files with 825 additions and 7 deletions.
10 changes: 10 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,16 @@ jobs:
aztec_manifest_key: end-to-end
<<: *defaults_e2e_test

e2e-auth:
steps:
- *checkout
- *setup_env
- run:
name: "Test"
command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=e2e_auth_contract.test.ts
aztec_manifest_key: end-to-end
<<: *defaults_e2e_test

e2e-note-getter:
steps:
- *checkout
Expand Down
1 change: 1 addition & 0 deletions noir-projects/aztec-nr/aztec/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ mod note;
mod oracle;
mod state_vars;
mod prelude;
mod public_storage;
use dep::protocol_types;
68 changes: 68 additions & 0 deletions noir-projects/aztec-nr/aztec/src/public_storage.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use dep::protocol_types::traits::{Deserialize, Serialize};
use crate::oracle::storage::{storage_read, storage_write};

pub fn read<T, N>(storage_slot: Field) -> T where T: Deserialize<N> {
T::deserialize(storage_read(storage_slot))
}

pub fn write<T, N>(storage_slot: Field, value: T) where T: Serialize<N> {
storage_write(storage_slot, value.serialize());
}

// Ideally we'd do the following, but we cannot because of https://github.com/noir-lang/noir/issues/4633
// pub fn read_historical<T, N>(
// storage_slot: Field,
// context: PrivateContext
// ) -> T where T: Deserialize<N> {
// let mut fields = [0; N];
// for i in 0..N {
// fields[i] = public_storage_historical_read(
// context,
// storage_slot + i as Field,
// context.this_address()
// );
// }
// T::deserialize(fields)
// }

mod tests {
use dep::std::test::OracleMock;
use dep::protocol_types::traits::{Deserialize, Serialize};
use crate::public_storage;

struct TestStruct {
a: Field,
b: Field,
}

impl Deserialize<2> for TestStruct {
fn deserialize(fields: [Field; 2]) -> TestStruct {
TestStruct { a: fields[0], b: fields[1] }
}
}

impl Serialize<2> for TestStruct {
fn serialize(self) -> [Field; 2] {
[self.a, self.b]
}
}

#[test]
fn test_read() {
let slot = 7;
let written = TestStruct { a: 13, b: 42 };

OracleMock::mock("storageRead").with_params((slot, 2)).returns(written.serialize());

let read: TestStruct = public_storage::read(slot);
assert_eq(read.a, 13);
assert_eq(read.b, 42);
}

#[test]
fn test_write() {
// Here we'd want to test that what is written to storage is deserialized to the same struct, but the current
// oracle mocks lack these capabilities.
// TODO: implement this once https://github.com/noir-lang/noir/issues/4652 is closed
}
}
2 changes: 2 additions & 0 deletions noir-projects/aztec-nr/aztec/src/state_vars.nr
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod public_immutable;
mod public_mutable;
mod private_set;
mod shared_immutable;
mod shared_mutable;
mod storage;

use crate::state_vars::map::Map;
Expand All @@ -14,4 +15,5 @@ use crate::state_vars::public_immutable::PublicImmutable;
use crate::state_vars::public_mutable::PublicMutable;
use crate::state_vars::private_set::PrivateSet;
use crate::state_vars::shared_immutable::SharedImmutable;
use crate::state_vars::shared_mutable::SharedMutable;
use crate::state_vars::storage::Storage;
4 changes: 4 additions & 0 deletions noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod shared_mutable;
mod scheduled_value_change;

use shared_mutable::SharedMutable;
Loading

0 comments on commit c4e41a9

Please sign in to comment.