Skip to content

Commit

Permalink
feat!: extend storage read oracle to receive address and block number (
Browse files Browse the repository at this point in the history
…AztecProtocol#7243)

Follow up to AztecProtocol#7237, closes
AztecProtocol#7230. I only
changed the oracles and not the PXE interface to keep this change as
small as possible.

I did change the node interface, but made it so you can still do it the
old way by passing `'latest'`, which I had to do in a couple places.

Finally, I added getters for `UnconstrainedContext`, mirroring the work
in AztecProtocol#7320, which I imagine are the ones we'll use in the vast majority of
cases.
  • Loading branch information
nventuro authored Jun 29, 2024
1 parent 99ce26f commit 153b201
Show file tree
Hide file tree
Showing 18 changed files with 110 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use dep::protocol_types::address::AztecAddress;
use dep::protocol_types::{address::AztecAddress, traits::Deserialize};
use crate::oracle::storage::{raw_storage_read, storage_read};

struct UnconstrainedContext {
block_number: u32,
Expand Down Expand Up @@ -35,6 +36,14 @@ impl UnconstrainedContext {
fn chain_id(self) -> Field {
self.chain_id
}

unconstrained fn raw_storage_read<N>(self: Self, storage_slot: Field) -> [Field; N] {
storage_read(self.this_address(), storage_slot, self.block_number())
}

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

#[oracle(getContractAddress)]
Expand Down
42 changes: 29 additions & 13 deletions noir-projects/aztec-nr/aztec/src/oracle/storage.nr
Original file line number Diff line number Diff line change
@@ -1,42 +1,58 @@
use dep::protocol_types::traits::Deserialize;
use dep::protocol_types::{address::AztecAddress, traits::Deserialize};

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

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

unconstrained pub fn raw_storage_read<N>(
address: AztecAddress,
storage_slot: Field,
block_number: u32
) -> [Field; N] {
storage_read_oracle(address.to_field(), storage_slot, block_number as Field, N)
}

unconstrained pub fn storage_read<T, N>(storage_slot: Field) -> T where T: Deserialize<N> {
T::deserialize(raw_storage_read(storage_slot))
unconstrained pub fn storage_read<T, N>(
address: AztecAddress,
storage_slot: Field,
block_number: u32
) -> T where T: Deserialize<N> {
T::deserialize(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 std::test::OracleMock;
use crate::test::mocks::mock_struct::MockStruct;

global address = AztecAddress::from_field(29);
global slot = 7;
global block_number = 17;

#[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 _ = OracleMock::mock("storageRead").returns(written.serialize());

let read: [Field; 2] = raw_storage_read(slot);
let read: [Field; 2] = raw_storage_read(address, slot, block_number);
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 _ = OracleMock::mock("storageRead").returns(written.serialize());

let read: MockStruct = storage_read(slot);
let read: MockStruct = storage_read(address, slot, block_number);
assert_eq(read.a, 13);
assert_eq(read.b, 42);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,6 @@ impl <T> PublicImmutable<T, &mut PublicContext> {

impl<T> PublicImmutable<T, UnconstrainedContext> {
unconstrained pub fn read<T_SERIALIZED_LEN>(self) -> T where T: Deserialize<T_SERIALIZED_LEN> {
storage_read(self.storage_slot)
self.context.storage_read(self.storage_slot)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ impl<T> PublicMutable<T, &mut PublicContext> {

impl<T> PublicMutable<T, UnconstrainedContext> {
unconstrained pub fn read<T_SERIALIZED_LEN>(self) -> T where T: Deserialize<T_SERIALIZED_LEN> {
storage_read(self.storage_slot)
self.context.storage_read(self.storage_slot)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl<T> SharedImmutable<T, &mut PublicContext> {

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,11 @@ pub fn setup_and_mint(with_account_contracts: bool) -> (&mut TestEnvironment, Az
pub fn check_public_balance(token_contract_address: AztecAddress, address: AztecAddress, address_amount: Field) {
let current_contract_address = cheatcodes::get_contract_address();
cheatcodes::set_contract_address(token_contract_address);
let block_number = cheatcodes::get_block_number();

let balances_slot = Token::storage().public_balances.slot;
let address_slot = derive_storage_slot_in_map(balances_slot, address);
let amount: U128 = storage_read(address_slot);
let amount: U128 = storage_read(token_contract_address, address_slot, block_number);
assert(amount.to_field() == address_amount, "Public balance is not correct");
cheatcodes::set_contract_address(current_contract_address);
}
Expand Down
5 changes: 3 additions & 2 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -704,10 +704,11 @@ export class AztecNodeService implements AztecNode {
*
* @param contract - Address of the contract to query.
* @param slot - Slot to query.
* @param blockNumber - The block number at which to get the data or 'latest'.
* @returns Storage value at the given contract slot.
*/
public async getPublicStorageAt(contract: AztecAddress, slot: Fr): Promise<Fr> {
const committedDb = await this.#getWorldState('latest');
public async getPublicStorageAt(contract: AztecAddress, slot: Fr, blockNumber: L2BlockNumber): Promise<Fr> {
const committedDb = await this.#getWorldState(blockNumber);
const leafSlot = computePublicDataTreeLeafSlot(contract, slot);

const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
Expand Down
3 changes: 2 additions & 1 deletion yarn-project/circuit-types/src/interfaces/aztec-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,10 @@ export interface AztecNode {
*
* @param contract - Address of the contract to query.
* @param slot - Slot to query.
* @param blockNumber - The block number at which to get the data or 'latest'.
* @returns Storage value at the given contract slot.
*/
getPublicStorageAt(contract: AztecAddress, slot: Fr): Promise<Fr>;
getPublicStorageAt(contract: AztecAddress, slot: Fr, blockNumber: L2BlockNumber): Promise<Fr>;

/**
* Returns the currently committed block header.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe('benchmarks/process_history', () => {
const node = await AztecNodeService.createAndSync(nodeConfig);
// call getPublicStorageAt (which calls #getWorldState, which calls #syncWorldState) to force a sync with
// world state to ensure the node has caught up
await node.getPublicStorageAt(AztecAddress.random(), Fr.random());
await node.getPublicStorageAt(AztecAddress.random(), Fr.random(), 'latest');
return node;
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('benchmarks/publish_rollup', () => {
// world state to ensure the node has caught up
context.logger.info(`Starting new aztec node`);
const node = await AztecNodeService.createAndSync({ ...context.config, disableSequencer: true });
await node.getPublicStorageAt(AztecAddress.random(), Fr.random());
await node.getPublicStorageAt(AztecAddress.random(), Fr.random(), 'latest');

// Spin up a new pxe and sync it, we'll use it to test sync times of new accounts for the last block
context.logger.info(`Starting new pxe`);
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/noir-contracts.js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,4 @@
"engines": {
"node": ">=18"
}
}
}
2 changes: 1 addition & 1 deletion yarn-project/pxe/src/pxe_service/pxe_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ export class PXEService implements PXE {
if (!(await this.getContractInstance(contract))) {
throw new Error(`Contract ${contract.toString()} is not deployed`);
}
return await this.node.getPublicStorageAt(contract, slot);
return await this.node.getPublicStorageAt(contract, slot, 'latest');
}

public async getIncomingNotes(filter: IncomingNotesFilter): Promise<ExtendedNote[]> {
Expand Down
14 changes: 12 additions & 2 deletions yarn-project/simulator/src/acvm/oracle/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,18 @@ export class Oracle {
return message.toFields().map(toACVMField);
}

async storageRead([startStorageSlot]: ACVMField[], [numberOfElements]: ACVMField[]): Promise<ACVMField[]> {
const values = await this.typedOracle.storageRead(fromACVMField(startStorageSlot), +numberOfElements);
async storageRead(
[contractAddress]: ACVMField[],
[startStorageSlot]: ACVMField[],
[blockNumber]: ACVMField[],
[numberOfElements]: ACVMField[],
): Promise<ACVMField[]> {
const values = await this.typedOracle.storageRead(
fromACVMField(contractAddress),
fromACVMField(startStorageSlot),
+blockNumber,
+numberOfElements,
);
return values.map(toACVMField);
}

Expand Down
7 changes: 6 additions & 1 deletion yarn-project/simulator/src/acvm/oracle/typed_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,12 @@ export abstract class TypedOracle {
throw new OracleMethodNotAvailableError('getL1ToL2MembershipWitness');
}

storageRead(_startStorageSlot: Fr, _numberOfElements: number): Promise<Fr[]> {
storageRead(
_contractAddress: Fr,
_startStorageSlot: Fr,
_blockNumber: number,
_numberOfElements: number,
): Promise<Fr[]> {
throw new OracleMethodNotAvailableError('storageRead');
}

Expand Down
15 changes: 12 additions & 3 deletions yarn-project/simulator/src/client/client_execution_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,16 +678,25 @@ export class ClientExecutionContext extends ViewDataOracle {

/**
* Read the public storage data.
* @param contractAddress - The address to read storage from.
* @param startStorageSlot - The starting storage slot.
* @param blockNumber - The block number to read storage at.
* @param numberOfElements - Number of elements to read from the starting storage slot.
*/
public override async storageRead(startStorageSlot: Fr, numberOfElements: number): Promise<Fr[]> {
public override async storageRead(
contractAddress: Fr,
startStorageSlot: Fr,
blockNumber: number,
numberOfElements: number,
): Promise<Fr[]> {
const values = [];
for (let i = 0n; i < numberOfElements; i++) {
const storageSlot = new Fr(startStorageSlot.value + i);

const value = await this.aztecNode.getPublicStorageAt(this.callContext.storageContractAddress, storageSlot);
this.log.debug(`Oracle storage read: slot=${storageSlot.toString()} value=${value}`);
const value = await this.aztecNode.getPublicStorageAt(contractAddress, storageSlot, blockNumber);
this.log.debug(
`Oracle storage read: slot=${storageSlot.toString()} address-${contractAddress.toString()} value=${value}`,
);

values.push(value);
}
Expand Down
15 changes: 12 additions & 3 deletions yarn-project/simulator/src/client/view_data_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,16 +260,25 @@ export class ViewDataOracle extends TypedOracle {

/**
* Read the public storage data.
* @param contractAddress - The address to read storage from.
* @param startStorageSlot - The starting storage slot.
* @param blockNumber - The block number to read storage at.
* @param numberOfElements - Number of elements to read from the starting storage slot.
*/
public override async storageRead(startStorageSlot: Fr, numberOfElements: number) {
public override async storageRead(
contractAddress: Fr,
startStorageSlot: Fr,
blockNumber: number,
numberOfElements: number,
) {
const values = [];
for (let i = 0n; i < numberOfElements; i++) {
const storageSlot = new Fr(startStorageSlot.value + i);
const value = await this.aztecNode.getPublicStorageAt(this.contractAddress, storageSlot);
const value = await this.aztecNode.getPublicStorageAt(contractAddress, storageSlot, blockNumber);

this.log.debug(`Oracle storage read: slot=${storageSlot.toString()} value=${value}`);
this.log.debug(
`Oracle storage read: slot=${storageSlot.toString()} address-${contractAddress.toString()} value=${value}`,
);
values.push(value);
}
return values;
Expand Down
9 changes: 7 additions & 2 deletions yarn-project/txe/src/oracle/txe_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,13 +434,18 @@ export class TXE implements TypedOracle {
throw new Error('Method not implemented.');
}

async storageRead(startStorageSlot: Fr, numberOfElements: number): Promise<Fr[]> {
async storageRead(
contractAddress: Fr,
startStorageSlot: Fr,
blockNumber: number, // TODO(#7230): use block number
numberOfElements: number,
): Promise<Fr[]> {
const db = this.trees.asLatest();

const values = [];
for (let i = 0n; i < numberOfElements; i++) {
const storageSlot = startStorageSlot.add(new Fr(i));
const leafSlot = computePublicDataTreeLeafSlot(this.contractAddress, storageSlot).toBigInt();
const leafSlot = computePublicDataTreeLeafSlot(contractAddress, storageSlot).toBigInt();

const lowLeafResult = await db.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot);

Expand Down
9 changes: 8 additions & 1 deletion yarn-project/txe/src/txe_service/txe_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,16 @@ export class TXEService {
return toForeignCallResult([]);
}

async storageRead(startStorageSlot: ForeignCallSingle, numberOfElements: ForeignCallSingle) {
async storageRead(
contractAddress: ForeignCallSingle,
startStorageSlot: ForeignCallSingle,
blockNumber: ForeignCallSingle,
numberOfElements: ForeignCallSingle,
) {
const values = await this.typedOracle.storageRead(
fromSingle(contractAddress),
fromSingle(startStorageSlot),
fromSingle(blockNumber).toNumber(),
fromSingle(numberOfElements).toNumber(),
);
return toForeignCallResult([toArray(values)]);
Expand Down

0 comments on commit 153b201

Please sign in to comment.