diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index 22e5cbcd8ca3..80610006ec0a 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -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 => { @@ -926,7 +926,7 @@ fn handle_storage_write( inputs: &Vec, ) { 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 { @@ -992,8 +992,8 @@ fn handle_storage_read( inputs: &Vec, ) { // 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 { diff --git a/noir-projects/aztec-nr/aztec/src/context/public_context.nr b/noir-projects/aztec-nr/aztec/src/context/public_context.nr index 57d2380e6cf4..7c056a43506d 100644 --- a/noir-projects/aztec-nr/aztec/src/context/public_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/public_context.nr @@ -166,6 +166,22 @@ impl PublicContext { fn da_gas_left(self) -> Field { da_gas_left() } + + fn raw_storage_read(_self: Self, storage_slot: Field) -> [Field; N] { + storage_read(storage_slot) + } + + fn storage_read(self, storage_slot: Field) -> T where T: Deserialize { + T::deserialize(self.raw_storage_read(storage_slot)) + } + + fn raw_storage_write(_self: Self, storage_slot: Field, values: [Field; N]) { + storage_write(storage_slot, values); + } + + fn storage_write(self, storage_slot: Field, value: T) where T: Serialize { + self.raw_storage_write(storage_slot, value.serialize()); + } } // Helper functions @@ -258,6 +274,14 @@ unconstrained fn call_static( call_static_opcode(gas, address, args, function_selector) } +unconstrained fn storage_read(storage_slot: Field) -> [Field; N] { + storage_read_opcode(storage_slot) +} + +unconstrained fn storage_write(storage_slot: Field, values: [Field; N]) { + storage_write_opcode(storage_slot, values); +} + impl Empty for PublicContext { fn empty() -> Self { PublicContext::new(PublicContextInputs::empty()) @@ -345,6 +369,12 @@ unconstrained fn call_static_opcode( ) -> ([Field; RET_SIZE], u8) {} // ^ return data ^ success +#[oracle(avmOpcodeStorageRead)] +unconstrained fn storage_read_opcode(storage_slot: Field) -> [Field; N] {} + +#[oracle(avmOpcodeStorageWrite)] +unconstrained fn storage_write_opcode(storage_slot: Field, values: [Field; N]) {} + struct FunctionReturns { values: [Field; N] } diff --git a/noir-projects/aztec-nr/aztec/src/lib.nr b/noir-projects/aztec-nr/aztec/src/lib.nr index e569b892e1ae..d4b1abf35e71 100644 --- a/noir-projects/aztec-nr/aztec/src/lib.nr +++ b/noir-projects/aztec-nr/aztec/src/lib.nr @@ -10,7 +10,6 @@ mod event; mod oracle; mod state_vars; mod prelude; -mod public_storage; mod encrypted_logs; mod unencrypted_logs; use dep::protocol_types; diff --git a/noir-projects/aztec-nr/aztec/src/oracle/storage.nr b/noir-projects/aztec-nr/aztec/src/oracle/storage.nr index b25e4a3b55c1..92925f889fad 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/storage.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/storage.nr @@ -1,19 +1,43 @@ -use dep::protocol_types::traits::{Deserialize, Serialize}; +use dep::protocol_types::traits::Deserialize; #[oracle(storageRead)] -unconstrained fn storage_read_oracle(_storage_slot: Field, _number_of_elements: Field) -> [Field; N] {} +unconstrained fn storage_read_oracle(storage_slot: Field, length: Field) -> [Field; N] {} -unconstrained fn storage_read_oracle_wrapper(_storage_slot: Field) -> [Field; N] { - storage_read_oracle(_storage_slot, N) +unconstrained pub fn raw_storage_read(storage_slot: Field) -> [Field; N] { + storage_read_oracle(storage_slot, N) } -pub fn storage_read(storage_slot: Field) -> [Field; N] { - storage_read_oracle_wrapper(storage_slot) +unconstrained pub fn storage_read(storage_slot: Field) -> T where T: Deserialize { + T::deserialize(raw_storage_read(storage_slot)) } -#[oracle(storageWrite)] -unconstrained fn storage_write_oracle(_storage_slot: Field, _values: [Field; N]) -> [Field; N] {} +mod tests { + use crate::oracle::storage::{raw_storage_read, storage_read}; -unconstrained pub fn storage_write(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); + } } diff --git a/noir-projects/aztec-nr/aztec/src/public_storage.nr b/noir-projects/aztec-nr/aztec/src/public_storage.nr deleted file mode 100644 index b9a34811babc..000000000000 --- a/noir-projects/aztec-nr/aztec/src/public_storage.nr +++ /dev/null @@ -1,56 +0,0 @@ -use dep::protocol_types::traits::{Deserialize, Serialize}; -use crate::oracle::storage::{storage_read, storage_write}; - -pub fn read(storage_slot: Field) -> T where T: Deserialize { - T::deserialize(storage_read(storage_slot)) -} - -pub fn write(storage_slot: Field, value: T) where T: Serialize { - storage_write(storage_slot, value.serialize()); -} - -mod tests { - use 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() { - let slot = 7; - let to_write = TestStruct { a: 13, b: 42 }; - - let mock = OracleMock::mock("storageWrite").returns([0; 2]); // The return value is unused - - public_storage::write(slot, to_write); - assert_eq(mock.get_last_params(), (slot, to_write.serialize())); - } -} diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr index b8f2c1d2dde6..32955df75934 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr @@ -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}}; @@ -37,32 +37,25 @@ impl PublicImmutable { // 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(self) -> T where T: Deserialize { - let fields = storage_read(self.storage_slot); - T::deserialize(fields) + self.context.storage_read(self.storage_slot) } // docs:end:public_immutable_struct_read } impl PublicImmutable { - pub fn read(self) -> T where T: Deserialize { - // 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(self) -> T where T: Deserialize { + storage_read(self.storage_slot) } } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/public_mutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/public_mutable.nr index 69a6a0f8a482..0d463051717b 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/public_mutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/public_mutable.nr @@ -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; @@ -29,25 +28,19 @@ impl PublicMutable { impl PublicMutable { // docs:start:public_mutable_struct_read pub fn read(self) -> T where T: Deserialize { - 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(self, value: T) where T: Serialize { - 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 PublicMutable { - pub fn read(self) -> T where T: Deserialize { - // 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(self) -> T where T: Deserialize { + storage_read(self.storage_slot) } } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_immutable.nr index 086c47aca56f..f71448809d49 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_immutable.nr @@ -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}}; @@ -33,26 +33,22 @@ impl SharedImmutable { // 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(self) -> T where T: Deserialize { - let fields = storage_read(self.storage_slot); - T::deserialize(fields) + self.context.storage_read(self.storage_slot) } } impl SharedImmutable { - pub fn read_public(self) -> T where T: Deserialize { - let fields = storage_read(self.storage_slot); - T::deserialize(fields) + unconstrained pub fn read_public(self) -> T where T: Deserialize { + storage_read(self.storage_slot) } } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr index 91c864a03b23..197d1402986f 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr @@ -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} @@ -98,19 +97,19 @@ impl SharedMutable { } fn read_value_change(self) -> ScheduledValueChange { - 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 { - 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) { - 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) { - public_storage::write(self.get_delay_change_storage_slot(), delay_change); + self.context.storage_write(self.get_delay_change_storage_slot(), delay_change); } } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable_private_getter.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable_private_getter.nr index 5a6ad9ad6fde..29ef525192c3 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable_private_getter.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable_private_getter.nr @@ -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} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr index 1801ddd72130..cb5b51339d6f 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr @@ -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); } diff --git a/yarn-project/end-to-end/src/e2e_state_vars.test.ts b/yarn-project/end-to-end/src/e2e_state_vars.test.ts index d306d976ffb7..400a25e9f361 100644 --- a/yarn-project/end-to-end/src/e2e_state_vars.test.ts +++ b/yarn-project/end-to-end/src/e2e_state_vars.test.ts @@ -94,7 +94,7 @@ describe('e2e_state_vars', () => { // Jest executes the tests sequentially and the first call to initialize_shared_immutable was executed // in the previous test, so the call below should fail. await expect(contract.methods.initialize_shared_immutable(1).prove()).rejects.toThrow( - "Assertion failed: SharedImmutable already initialized 'fields_read[0] == 0'", + 'Assertion failed: SharedImmutable already initialized', ); }); }); @@ -114,7 +114,7 @@ describe('e2e_state_vars', () => { // Jest executes the tests sequentially and the first call to initialize_public_immutable was executed // in the previous test, so the call below should fail. await expect(contract.methods.initialize_public_immutable(1).prove()).rejects.toThrow( - "Assertion failed: PublicImmutable already initialized 'fields_read[0] == 0'", + 'Assertion failed: PublicImmutable already initialized', ); }); });