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

fix: properly trace storage reads to slots never written before in AVM #10560

Merged
merged 5 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion yarn-project/end-to-end/src/e2e_avm_simulator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ describe('e2e_avm_simulator', () => {

describe('From private', () => {
it('Should enqueue a public function correctly', async () => {
await avmContract.methods.enqueue_public_from_private().simulate();
const request = await avmContract.methods.enqueue_public_from_private().create();
const simulation = await wallet.simulateTx(request, true);
expect(simulation.publicOutput!.revertReason).toBeUndefined();
});
});

Expand Down
26 changes: 17 additions & 9 deletions yarn-project/simulator/src/avm/journal/journal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,11 @@ export class AvmPersistableStateManager {
*/
public async writeStorage(contractAddress: AztecAddress, slot: Fr, value: Fr): Promise<void> {
this.log.debug(`Storage write (address=${contractAddress}, slot=${slot}): value=${value}`);
const leafSlot = computePublicDataTreeLeafSlot(contractAddress, slot);
this.log.debug(`leafSlot=${leafSlot}`);
// Cache storage writes for later reference/reads
this.publicStorage.write(contractAddress, slot, value);
const leafSlot = computePublicDataTreeLeafSlot(contractAddress, slot);

if (this.doMerkleOperations) {
const result = await this.merkleTrees.writePublicStorage(leafSlot, value);
assert(result !== undefined, 'Public data tree insertion error. You might want to disable doMerkleOperations.');
Expand All @@ -170,10 +172,13 @@ export class AvmPersistableStateManager {
const lowLeafPath = lowLeafInfo.siblingPath;

const newLeafPreimage = result.element as PublicDataTreeLeafPreimage;
let insertionPath;

let insertionPath: Fr[] | undefined;
if (!result.update) {
insertionPath = result.insertionPath;
assert(
newLeafPreimage.value.equals(value),
`Value mismatch when performing public data write (got value: ${value}, value in ephemeral tree: ${newLeafPreimage.value})`,
);
}

this.trace.tracePublicStorageWrite(
Expand Down Expand Up @@ -201,8 +206,8 @@ export class AvmPersistableStateManager {
public async readStorage(contractAddress: AztecAddress, slot: Fr): Promise<Fr> {
const { value, cached } = await this.publicStorage.read(contractAddress, slot);
this.log.debug(`Storage read (address=${contractAddress}, slot=${slot}): value=${value}, cached=${cached}`);

const leafSlot = computePublicDataTreeLeafSlot(contractAddress, slot);
this.log.debug(`leafSlot=${leafSlot}`);

if (this.doMerkleOperations) {
// Get leaf if present, low leaf if absent
Expand All @@ -217,22 +222,25 @@ export class AvmPersistableStateManager {
const leafPath = await this.merkleTrees.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, leafIndex);
const leafPreimage = preimage as PublicDataTreeLeafPreimage;

this.log.debug(`leafPreimage.slot: ${leafPreimage.slot}, leafPreimage.value: ${leafPreimage.value}`);
this.log.debug(
`leafPreimage.nextSlot: ${leafPreimage.nextSlot}, leafPreimage.nextIndex: ${Number(leafPreimage.nextIndex)}`,
);
this.log.debug(`leafPreimage.slot: ${leafPreimage.slot}, leafPreimage.value: ${leafPreimage.value}`);

if (!alreadyPresent) {
if (alreadyPresent) {
assert(
leafPreimage.value.equals(value),
`Value mismatch when performing public data read (got value: ${value}, value in ephemeral tree: ${leafPreimage.value})`,
);
} else {
this.log.debug(`Slot has never been written before!`);
// Sanity check that the leaf slot is skipped by low leaf when it doesn't exist
assert(
leafPreimage.slot.toBigInt() < leafSlot.toBigInt() &&
(leafPreimage.nextIndex === 0n || leafPreimage.nextSlot.toBigInt() > leafSlot.toBigInt()),
'Public data tree low leaf should skip the target leaf slot when the target leaf does not exist or is the max value.',
);
}
this.log.debug(
`Tracing storage leaf preimage slot=${slot}, leafSlot=${leafSlot}, value=${value}, nextKey=${leafPreimage.nextSlot}, nextIndex=${leafPreimage.nextIndex}`,
);
// On non-existence, AVM circuit will need to recognize that leafPreimage.slot != leafSlot,
// prove that this is a low leaf that skips leafSlot, and then prove membership of the leaf.
this.trace.tracePublicStorageRead(contractAddress, slot, value, leafPreimage, new Fr(leafIndex), leafPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import { Fr } from '@aztec/foundation/fields';
import { jsonStringify } from '@aztec/foundation/json-rpc';
import { createLogger } from '@aztec/foundation/log';

import { assert } from 'console';
import { strict as assert } from 'assert';

import { type AvmContractCallResult, type AvmFinalizedCallResult } from '../avm/avm_contract_call_result.js';
import { type AvmExecutionEnvironment } from '../avm/avm_execution_environment.js';
Expand Down Expand Up @@ -197,20 +197,17 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI
}

public tracePublicStorageRead(
_contractAddress: AztecAddress,
contractAddress: AztecAddress,
slot: Fr,
value: Fr,
leafPreimage: PublicDataTreeLeafPreimage = PublicDataTreeLeafPreimage.empty(),
leafIndex: Fr = Fr.zero(),
path: Fr[] = emptyPublicDataPath(),
) {
if (!leafIndex.equals(Fr.zero())) {
// if we have real merkle hint content, make sure the value matches the the provided preimage
assert(leafPreimage.value.equals(value), 'Value mismatch when tracing in public data write');
}

this.avmCircuitHints.publicDataReads.items.push(new AvmPublicDataReadTreeHint(leafPreimage, leafIndex, path));
this.log.debug(`SLOAD cnt: ${this.sideEffectCounter} val: ${value} slot: ${slot}`);
this.log.debug(
`Tracing storage read (address=${contractAddress}, slot=${slot}): value=${value} (counter=${this.sideEffectCounter})`,
);
this.incrementSideEffectCounter();
}

Expand All @@ -224,10 +221,6 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI
newLeafPreimage: PublicDataTreeLeafPreimage = PublicDataTreeLeafPreimage.empty(),
insertionPath: Fr[] = emptyPublicDataPath(),
) {
if (!lowLeafIndex.equals(Fr.zero())) {
// if we have real merkle hint content, make sure the value matches the the provided preimage
assert(newLeafPreimage.value.equals(value), 'Value mismatch when tracing in public data read');
}
if (
this.publicDataWrites.length + this.previousSideEffectArrayLengths.publicDataWrites >=
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX
Expand All @@ -248,7 +241,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI
);

this.log.debug(
`Traced public data write (address=${contractAddress}, slot=${slot}, leafSlot=${leafSlot}): value=${value} (counter=${this.sideEffectCounter})`,
`Traced public data write (address=${contractAddress}, slot=${slot}): value=${value} (counter=${this.sideEffectCounter})`,
);
this.incrementSideEffectCounter();
}
Expand Down
Loading