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!: split storage access oracles #7237

Merged
merged 5 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
10 changes: 5 additions & 5 deletions avm-transpiler/src/transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,8 @@ fn handle_foreign_call(
"avmOpcodeGetContractInstance" => {
handle_get_contract_instance(avm_instrs, destinations, inputs);
}
"storageRead" => handle_storage_read(avm_instrs, destinations, inputs),
"storageWrite" => handle_storage_write(avm_instrs, destinations, inputs),
"avmOpcodeStorageRead" => handle_storage_read(avm_instrs, destinations, inputs),
"avmOpcodeStorageWrite" => handle_storage_write(avm_instrs, destinations, inputs),
"debugLog" => handle_debug_log(avm_instrs, destinations, inputs),
// Getters.
_ if inputs.is_empty() && destinations.len() == 1 => {
Expand Down Expand Up @@ -926,7 +926,7 @@ fn handle_storage_write(
inputs: &Vec<ValueOrArray>,
) {
assert!(inputs.len() == 2);
assert!(destinations.len() == 1);
assert!(destinations.len() == 0);

let slot_offset_maybe = inputs[0];
let slot_offset = match slot_offset_maybe {
Expand Down Expand Up @@ -992,8 +992,8 @@ fn handle_storage_read(
inputs: &Vec<ValueOrArray>,
) {
// For the foreign calls we want to handle, we do not want inputs, as they are getters
assert!(inputs.len() == 2); // output, len - but we dont use this len - its for the oracle
assert!(destinations.len() == 1);
assert!(inputs.len() == 1); // storage_slot
assert!(destinations.len() == 1); // return values

let slot_offset_maybe = inputs[0];
let slot_offset = match slot_offset_maybe {
Expand Down
30 changes: 30 additions & 0 deletions noir-projects/aztec-nr/aztec/src/context/public_context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,22 @@ impl PublicContext {
fn da_gas_left(self) -> Field {
da_gas_left()
}

fn raw_storage_read<N>(_self: Self, storage_slot: Field) -> [Field; N] {
storage_read(storage_slot)
}

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

fn raw_storage_write<N>(_self: Self, storage_slot: Field, values: [Field; N]) {
storage_write(storage_slot, values);
}

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

// Helper functions
Expand Down Expand Up @@ -259,6 +275,14 @@ unconstrained fn call_static<RET_SIZE>(
call_static_opcode(gas, address, args, function_selector)
}

unconstrained fn storage_read<N>(storage_slot: Field) -> [Field; N] {
storage_read_opcode(storage_slot)
}

unconstrained fn storage_write<N>(storage_slot: Field, values: [Field; N]) {
storage_write_opcode(storage_slot, values);
}

impl Empty for PublicContext {
fn empty() -> Self {
PublicContext::new(PublicContextInputs::empty())
Expand Down Expand Up @@ -346,6 +370,12 @@ unconstrained fn call_static_opcode<RET_SIZE>(
) -> ([Field; RET_SIZE], u8) {}
// ^ return data ^ success

#[oracle(avmOpcodeStorageRead)]
unconstrained fn storage_read_opcode<N>(storage_slot: Field) -> [Field; N] {}

#[oracle(avmOpcodeStorageWrite)]
unconstrained fn storage_write_opcode<N>(storage_slot: Field, values: [Field; N]) {}

struct FunctionReturns<N> {
values: [Field; N]
}
Expand Down
1 change: 0 additions & 1 deletion noir-projects/aztec-nr/aztec/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ mod event;
mod oracle;
mod state_vars;
mod prelude;
mod public_storage;
mod encrypted_logs;
use dep::protocol_types;
mod utils;
Expand Down
44 changes: 34 additions & 10 deletions noir-projects/aztec-nr/aztec/src/oracle/storage.nr
Original file line number Diff line number Diff line change
@@ -1,19 +1,43 @@
use dep::protocol_types::traits::{Deserialize, Serialize};
use dep::protocol_types::traits::Deserialize;

#[oracle(storageRead)]
unconstrained fn storage_read_oracle<N>(_storage_slot: Field, _number_of_elements: Field) -> [Field; N] {}
unconstrained fn storage_read_oracle<N>(storage_slot: Field, length: Field) -> [Field; N] {}

unconstrained fn storage_read_oracle_wrapper<N>(_storage_slot: Field) -> [Field; N] {
storage_read_oracle(_storage_slot, N)
unconstrained pub fn raw_storage_read<N>(storage_slot: Field) -> [Field; N] {
storage_read_oracle(storage_slot, N)
}

pub fn storage_read<N>(storage_slot: Field) -> [Field; N] {
storage_read_oracle_wrapper(storage_slot)
unconstrained pub fn storage_read<T, N>(storage_slot: Field) -> T where T: Deserialize<N> {
T::deserialize(raw_storage_read(storage_slot))
}

#[oracle(storageWrite)]
unconstrained fn storage_write_oracle<N>(_storage_slot: Field, _values: [Field; N]) -> [Field; N] {}
mod tests {
use crate::oracle::storage::{raw_storage_read, storage_read};

unconstrained pub fn storage_write<N>(storage_slot: Field, fields: [Field; N]) {
let _hash = storage_write_oracle(storage_slot, fields);
use std::test::OracleMock;
use crate::test::mocks::mock_struct::MockStruct;

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

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

let read: [Field; 2] = raw_storage_read(slot);
assert_eq(read[0], 13);
assert_eq(read[1], 42);
}

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

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

let read: MockStruct = storage_read(slot);
assert_eq(read.a, 13);
assert_eq(read.b, 42);
}
}
56 changes: 0 additions & 56 deletions noir-projects/aztec-nr/aztec/src/public_storage.nr

This file was deleted.

23 changes: 8 additions & 15 deletions noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
context::{PublicContext, UnconstrainedContext}, oracle::{storage::{storage_read, storage_write}},
context::{PublicContext, UnconstrainedContext}, oracle::storage::storage_read,
state_vars::storage::Storage
};
use dep::protocol_types::{constants::INITIALIZATION_SLOT_SEPARATOR, traits::{Deserialize, Serialize}};
Expand Down Expand Up @@ -37,32 +37,25 @@ impl <T> PublicImmutable<T, &mut PublicContext> {

// 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");
let init_field: Field = self.context.storage_read(initialization_slot);
assert(init_field == 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);
self.context.storage_write(initialization_slot, 0xdead);
self.context.storage_write(self.storage_slot, value);
}
// 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<T_SERIALIZED_LEN>(self) -> T where T: Deserialize<T_SERIALIZED_LEN> {
let fields = storage_read(self.storage_slot);
T::deserialize(fields)
self.context.storage_read(self.storage_slot)
}
// docs:end:public_immutable_struct_read
}

impl<T> PublicImmutable<T, UnconstrainedContext> {
pub fn read<T_SERIALIZED_LEN>(self) -> T where T: Deserialize<T_SERIALIZED_LEN> {
// This looks the same as the &mut PublicContext impl, but is actually very different. In public execution the
// storage read oracle gets transpiled to SLOAD opcodes, whereas in unconstrained execution the PXE returns
// historical data.
let fields = storage_read(self.storage_slot);
T::deserialize(fields)
unconstrained pub fn read<T_SERIALIZED_LEN>(self) -> T where T: Deserialize<T_SERIALIZED_LEN> {
storage_read(self.storage_slot)
}
}
15 changes: 4 additions & 11 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,5 @@
use crate::context::{PublicContext, UnconstrainedContext};
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;

Expand Down Expand Up @@ -29,25 +28,19 @@ impl<T, Context> PublicMutable<T, Context> {
impl<T> PublicMutable<T, &mut PublicContext> {
// docs:start:public_mutable_struct_read
pub fn read<T_SERIALIZED_LEN>(self) -> T where T: Deserialize<T_SERIALIZED_LEN> {
let fields = storage_read(self.storage_slot);
T::deserialize(fields)
self.context.storage_read(self.storage_slot)
}
// docs:end:public_mutable_struct_read

// docs:start:public_mutable_struct_write
pub fn write<T_SERIALIZED_LEN>(self, value: T) where T: Serialize<T_SERIALIZED_LEN> {
let fields = T::serialize(value);
storage_write(self.storage_slot, fields);
self.context.storage_write(self.storage_slot, value);
}
// docs:end:public_mutable_struct_write
}

impl<T> PublicMutable<T, UnconstrainedContext> {
pub fn read<T_SERIALIZED_LEN>(self) -> T where T: Deserialize<T_SERIALIZED_LEN> {
// This looks the same as the &mut PublicContext impl, but is actually very different. In public execution the
// storage read oracle gets transpiled to SLOAD opcodes, whereas in unconstrained execution the PXE returns
// historical data.
let fields = storage_read(self.storage_slot);
T::deserialize(fields)
unconstrained pub fn read<T_SERIALIZED_LEN>(self) -> T where T: Deserialize<T_SERIALIZED_LEN> {
storage_read(self.storage_slot)
}
}
22 changes: 9 additions & 13 deletions noir-projects/aztec-nr/aztec/src/state_vars/shared_immutable.nr
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
context::{PrivateContext, PublicContext, UnconstrainedContext},
oracle::{storage::{storage_read, storage_write}}, state_vars::storage::Storage
context::{PrivateContext, PublicContext, UnconstrainedContext}, oracle::storage::storage_read,
state_vars::storage::Storage
};
use dep::protocol_types::{constants::INITIALIZATION_SLOT_SEPARATOR, traits::{Deserialize, Serialize}};

Expand Down Expand Up @@ -33,26 +33,22 @@ impl<T> SharedImmutable<T, &mut PublicContext> {

// 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, "SharedImmutable already initialized");
let init_field: Field = self.context.storage_read(initialization_slot);
assert(init_field == 0, "SharedImmutable 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);
self.context.storage_write(initialization_slot, 0xdead);
self.context.storage_write(self.storage_slot, value);
}

pub fn read_public<T_SERIALIZED_LEN>(self) -> T where T: Deserialize<T_SERIALIZED_LEN> {
let fields = storage_read(self.storage_slot);
T::deserialize(fields)
self.context.storage_read(self.storage_slot)
}
}

impl<T> SharedImmutable<T, UnconstrainedContext> {
pub fn read_public<T_SERIALIZED_LEN>(self) -> T where T: Deserialize<T_SERIALIZED_LEN> {
let fields = storage_read(self.storage_slot);
T::deserialize(fields)
unconstrained pub fn read_public<T_SERIALIZED_LEN>(self) -> T where T: Deserialize<T_SERIALIZED_LEN> {
storage_read(self.storage_slot)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use dep::protocol_types::{hash::pedersen_hash, traits::FromField};

use crate::context::{PrivateContext, PublicContext};
use crate::public_storage;
use crate::state_vars::{
storage::Storage,
shared_mutable::{scheduled_value_change::ScheduledValueChange, scheduled_delay_change::ScheduledDelayChange}
Expand Down Expand Up @@ -98,19 +97,19 @@ impl<T, INITIAL_DELAY> SharedMutable<T, INITIAL_DELAY, &mut PublicContext> {
}

fn read_value_change(self) -> ScheduledValueChange<T> {
public_storage::read(self.get_value_change_storage_slot())
self.context.storage_read(self.get_value_change_storage_slot())
}

fn read_delay_change(self) -> ScheduledDelayChange<INITIAL_DELAY> {
public_storage::read(self.get_delay_change_storage_slot())
self.context.storage_read(self.get_delay_change_storage_slot())
}

fn write_value_change(self, value_change: ScheduledValueChange<T>) {
public_storage::write(self.get_value_change_storage_slot(), value_change);
self.context.storage_write(self.get_value_change_storage_slot(), value_change);
}

fn write_delay_change(self, delay_change: ScheduledDelayChange<INITIAL_DELAY>) {
public_storage::write(self.get_delay_change_storage_slot(), delay_change);
self.context.storage_write(self.get_delay_change_storage_slot(), delay_change);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use dep::protocol_types::{hash::pedersen_hash, traits::FromField, address::AztecAddress, header::Header};

use crate::context::PrivateContext;
use crate::public_storage;
use crate::state_vars::{
storage::Storage,
shared_mutable::{scheduled_delay_change::ScheduledDelayChange, scheduled_value_change::ScheduledValueChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ pub fn check_public_balance(token_contract_address: AztecAddress, address: Aztec

let balances_slot = Token::storage().public_balances.slot;
let address_slot = derive_storage_slot_in_map(balances_slot, address);
let fields = storage_read(address_slot);
assert(U128::deserialize(fields).to_field() == address_amount, "Public balance is not correct");
let amount: U128 = storage_read(address_slot);
assert(amount.to_field() == address_amount, "Public balance is not correct");
cheatcodes::set_contract_address(current_contract_address);
}

Expand Down
Loading