Skip to content

Commit

Permalink
fix: properly trace storage reads to slots never written before in AVM (
Browse files Browse the repository at this point in the history
#10560)

For storage reads to slots never before written, we were incorrectly
comparing `leafPreimage.value` to the cached/db value of a storage slot
and asserting that they match. That doesn't work because in that case,
`leafPreimage` is the low leaf meant to skip the target slot.
  • Loading branch information
dbanks12 authored Dec 10, 2024
1 parent 93b8b1b commit 410c730
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 23 deletions.
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

0 comments on commit 410c730

Please sign in to comment.