From 61f2881f689daed83fa6d54fc4a4ae560eb11283 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Wed, 5 Jun 2024 15:20:59 +0000 Subject: [PATCH] feat(avm): get contract instance now works e2e with avm proving --- .../barretenberg/vm/avm_trace/avm_common.hpp | 12 +- .../barretenberg/vm/avm_trace/avm_trace.cpp | 1 + .../vm/tests/avm_execution.test.cpp | 6 +- .../bb-prover/src/avm_proving.test.ts | 36 +++++- .../circuits.js/src/structs/avm/avm.ts | 118 ++++++++++++++++-- .../circuits.js/src/tests/factories.ts | 21 +++- .../simulator/src/avm/fixtures/index.ts | 13 ++ .../simulator/src/avm/journal/journal.test.ts | 31 ++++- .../simulator/src/avm/journal/journal.ts | 15 +++ .../simulator/src/avm/journal/trace.test.ts | 16 +++ .../simulator/src/avm/journal/trace.ts | 8 ++ .../simulator/src/avm/journal/trace_types.ts | 3 + .../src/avm/opcodes/contract.test.ts | 21 ++-- .../simulator/src/avm/opcodes/contract.ts | 34 ++--- .../src/public/transitional_adaptors.ts | 13 ++ .../types/src/contracts/contract_instance.ts | 25 ++-- 16 files changed, 311 insertions(+), 62 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_common.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_common.hpp index b83abf04c0f4..51fa5360ebd9 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_common.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_common.hpp @@ -61,6 +61,7 @@ inline void read(uint8_t const*& it, ExternalCallHint& hint) } struct ContractInstanceHint { + FF address; FF instance_found_in_address; FF salt; FF deployer_addr; @@ -73,6 +74,7 @@ struct ContractInstanceHint { inline void read(uint8_t const*& it, ContractInstanceHint& hint) { using serialize::read; + read(it, hint.address); read(it, hint.instance_found_in_address); read(it, hint.salt); read(it, hint.deployer_addr); @@ -87,7 +89,6 @@ struct ExecutionHints { std::vector> nullifier_exists_hints; std::vector> l1_to_l2_message_exists_hints; std::vector externalcall_hints; - // TODO(dbanks): not read yet. std::map contract_instance_hints; ExecutionHints() = default; @@ -150,8 +151,6 @@ struct ExecutionHints { std::vector> note_hash_exists_hints; std::vector> nullifier_exists_hints; std::vector> l1_to_l2_message_exists_hints; - // TODO(dbanks): not read yet. - std::map contract_instance_hints; using serialize::read; const auto* it = data.data(); @@ -163,6 +162,13 @@ struct ExecutionHints { std::vector externalcall_hints; read(it, externalcall_hints); + std::vector contract_instance_hints_vec; + read(it, contract_instance_hints_vec); + std::map contract_instance_hints; + for (const auto& instance : contract_instance_hints_vec) { + contract_instance_hints[instance.address] = instance; + } + return { std::move(storage_value_hints), std::move(note_hash_exists_hints), std::move(nullifier_exists_hints), std::move(l1_to_l2_message_exists_hints), std::move(externalcall_hints), std::move(contract_instance_hints) }; diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp index daaea2106caf..a2a9cff6953e 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp @@ -2556,6 +2556,7 @@ void AvmTraceBuilder::op_get_contract_instance(uint8_t indirect, uint32_t addres // Read the contract instance ContractInstanceHint contract_instance = execution_hints.contract_instance_hints.at(read_address.val); + // NOTE: we don't write the first entry (the contract instance's address/key) to memory std::vector contract_instance_vec = { contract_instance.instance_found_in_address, contract_instance.salt, contract_instance.deployer_addr, diff --git a/barretenberg/cpp/src/barretenberg/vm/tests/avm_execution.test.cpp b/barretenberg/cpp/src/barretenberg/vm/tests/avm_execution.test.cpp index 98be9b067f4b..d2a66d0dff81 100644 --- a/barretenberg/cpp/src/barretenberg/vm/tests/avm_execution.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/tests/avm_execution.test.cpp @@ -2000,10 +2000,12 @@ TEST_F(AvmExecutionTests, opGetContractInstanceOpcodes) std::vector returndata = {}; // Generate Hint for call operation - auto execution_hints = ExecutionHints().with_contract_instance_hints({ { address, { 1, 1, 2, 3, 4, 5 } } }); + // Note: opcode does not write 'address' into memory + auto execution_hints = + ExecutionHints().with_contract_instance_hints({ { address, { address, 1, 2, 3, 4, 5, 6 } } }); auto trace = Execution::gen_trace(instructions, returndata, calldata, public_inputs_vec, execution_hints); - EXPECT_EQ(returndata, std::vector({ 1, 1, 2, 3, 4, 5 })); // The first one represents true + EXPECT_EQ(returndata, std::vector({ 1, 2, 3, 4, 5, 6 })); // The first one represents true validate_trace(std::move(trace)); } diff --git a/yarn-project/bb-prover/src/avm_proving.test.ts b/yarn-project/bb-prover/src/avm_proving.test.ts index 40ebc18d6e94..266582ad6237 100644 --- a/yarn-project/bb-prover/src/avm_proving.test.ts +++ b/yarn-project/bb-prover/src/avm_proving.test.ts @@ -29,17 +29,25 @@ import { computeVarArgsHash } from '@aztec/circuits.js/hash'; import { padArrayEnd } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; -import { AvmSimulator, type PublicExecutionResult } from '@aztec/simulator'; -import { getAvmTestContractBytecode, initContext, initExecutionEnvironment } from '@aztec/simulator/avm/fixtures'; +import { AvmSimulator, type PublicContractsDB, type PublicExecutionResult } from '@aztec/simulator'; +import { + getAvmTestContractBytecode, + initContext, + initExecutionEnvironment, + initHostStorage, +} from '@aztec/simulator/avm/fixtures'; +import { mock } from 'jest-mock-extended'; import fs from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'path'; +import { AvmPersistableStateManager } from '../../simulator/src/avm/journal/journal.js'; import { convertAvmResultsToPxResult, createPublicExecution, } from '../../simulator/src/public/transitional_adaptors.js'; +import { SerializableContractInstance } from '../../types/src/contracts/contract_instance.js'; import { type BBSuccess, BB_RESULT, generateAvmProof, verifyAvmProof } from './bb/execute.js'; import { extractVkData } from './verification_key/verification_key_data.js'; @@ -70,6 +78,14 @@ describe('AVM WitGen, proof generation and verification', () => { TIMEOUT, ); + it( + 'Should prove get contract instance opcode with hints', + async () => { + await proveAndVerifyAvmTestContract('test_get_contract_instance'); + }, + TIMEOUT, + ); + it( 'Should prove new note hash', async () => { @@ -130,7 +146,21 @@ describe('AVM WitGen, proof generation and verification', () => { const proveAndVerifyAvmTestContract = async (functionName: string, calldata: Fr[] = []) => { const startSideEffectCounter = 0; const environment = initExecutionEnvironment({ calldata }); - const context = initContext({ env: environment }); + + const contractsDb = mock(); + const contractInstance = new SerializableContractInstance({ + version: 1, + salt: new Fr(0x123), + deployer: new Fr(0x456), + contractClassId: new Fr(0x789), + initializationHash: new Fr(0x101112), + publicKeysHash: new Fr(0x161718), + }).withAddress(environment.address); + contractsDb.getContractInstance.mockResolvedValue(Promise.resolve(contractInstance)); + + const hostStorage = initHostStorage({ contractsDb }); + const persistableState = new AvmPersistableStateManager(hostStorage); + const context = initContext({ env: environment, persistableState }); const startGas = new Gas(context.machineState.gasLeft.daGas, context.machineState.gasLeft.l2Gas); const oldPublicExecution = createPublicExecution(startSideEffectCounter, environment, calldata); diff --git a/yarn-project/circuits.js/src/structs/avm/avm.ts b/yarn-project/circuits.js/src/structs/avm/avm.ts index 772aaf215d76..19a55c38667b 100644 --- a/yarn-project/circuits.js/src/structs/avm/avm.ts +++ b/yarn-project/circuits.js/src/structs/avm/avm.ts @@ -57,7 +57,7 @@ export class AvmKeyValueHint { * @param buffer - Buffer or reader to read from. * @returns The deserialized instance. */ - static fromBuffer(buff: Buffer | BufferReader): AvmKeyValueHint { + static fromBuffer(buff: Buffer | BufferReader) { const reader = BufferReader.asReader(buff); return new AvmKeyValueHint(Fr.fromBuffer(reader), Fr.fromBuffer(reader)); } @@ -147,12 +147,109 @@ export class AvmExternalCallHint { } } +export class AvmContractInstanceHint { + constructor( + public readonly address: Fr, + public readonly exists: Fr, + public readonly salt: Fr, + public readonly deployer: Fr, + public readonly contractClassId: Fr, + public readonly initializationHash: Fr, + public readonly publicKeysHash: Fr, + ) {} + /** + * Serializes the inputs to a buffer. + * @returns - The inputs serialized to a buffer. + */ + toBuffer() { + return serializeToBuffer(...AvmContractInstanceHint.getFields(this)); + } + + /** + * Serializes the inputs to a hex string. + * @returns The instance serialized to a hex string. + */ + toString() { + return this.toBuffer().toString('hex'); + } + + /** + * Is the struct empty? + * @returns whether all members are empty. + */ + isEmpty(): boolean { + return ( + this.address.isZero() && + this.exists.isZero() && + this.salt.isZero() && + this.deployer.isZero() && + this.contractClassId.isZero() && + this.initializationHash.isZero() && + this.publicKeysHash.isZero() + ); + } + + /** + * Creates a new instance from fields. + * @param fields - Fields to create the instance from. + * @returns A new AvmHint instance. + */ + static from(fields: FieldsOf): AvmContractInstanceHint { + return new AvmContractInstanceHint(...AvmContractInstanceHint.getFields(fields)); + } + + /** + * Extracts fields from an instance. + * @param fields - Fields to create the instance from. + * @returns An array of fields. + */ + static getFields(fields: FieldsOf) { + return [ + fields.address, + fields.exists, + fields.salt, + fields.deployer, + fields.contractClassId, + fields.initializationHash, + fields.publicKeysHash, + ] as const; + } + + /** + * Deserializes from a buffer or reader. + * @param buffer - Buffer or reader to read from. + * @returns The deserialized instance. + */ + static fromBuffer(buff: Buffer | BufferReader): AvmContractInstanceHint { + const reader = BufferReader.asReader(buff); + return new AvmContractInstanceHint( + Fr.fromBuffer(reader), + Fr.fromBuffer(reader), + Fr.fromBuffer(reader), + Fr.fromBuffer(reader), + Fr.fromBuffer(reader), + Fr.fromBuffer(reader), + Fr.fromBuffer(reader), + ); + } + + /** + * Deserializes from a hex string. + * @param str - Hex string to read from. + * @returns The deserialized instance. + */ + static fromString(str: string): AvmContractInstanceHint { + return AvmContractInstanceHint.fromBuffer(Buffer.from(str, 'hex')); + } +} + export class AvmExecutionHints { public readonly storageValues: Vector; public readonly noteHashExists: Vector; public readonly nullifierExists: Vector; public readonly l1ToL2MessageExists: Vector; public readonly externalCalls: Vector; + public readonly contractInstances: Vector; constructor( storageValues: AvmKeyValueHint[], @@ -160,12 +257,14 @@ export class AvmExecutionHints { nullifierExists: AvmKeyValueHint[], l1ToL2MessageExists: AvmKeyValueHint[], externalCalls: AvmExternalCallHint[], + contractInstances: AvmContractInstanceHint[], ) { this.storageValues = new Vector(storageValues); this.noteHashExists = new Vector(noteHashExists); this.nullifierExists = new Vector(nullifierExists); this.l1ToL2MessageExists = new Vector(l1ToL2MessageExists); this.externalCalls = new Vector(externalCalls); + this.contractInstances = new Vector(contractInstances); } /** @@ -194,7 +293,8 @@ export class AvmExecutionHints { this.noteHashExists.items.length == 0 && this.nullifierExists.items.length == 0 && this.l1ToL2MessageExists.items.length == 0 && - this.externalCalls.items.length == 0 + this.externalCalls.items.length == 0 && + this.contractInstances.items.length == 0 ); } @@ -210,6 +310,7 @@ export class AvmExecutionHints { fields.nullifierExists.items, fields.l1ToL2MessageExists.items, fields.externalCalls.items, + fields.contractInstances.items, ); } @@ -225,18 +326,10 @@ export class AvmExecutionHints { fields.nullifierExists, fields.l1ToL2MessageExists, fields.externalCalls, + fields.contractInstances, ] as const; } - flat() { - return [ - ...this.storageValues.items, - ...this.noteHashExists.items, - ...this.nullifierExists.items, - ...this.l1ToL2MessageExists.items, - ]; - } - /** * Deserializes from a buffer or reader. * @param buffer - Buffer or reader to read from. @@ -250,6 +343,7 @@ export class AvmExecutionHints { reader.readVector(AvmKeyValueHint), reader.readVector(AvmKeyValueHint), reader.readVector(AvmExternalCallHint), + reader.readVector(AvmContractInstanceHint), ); } @@ -267,7 +361,7 @@ export class AvmExecutionHints { * @returns The empty instance. */ static empty() { - return new AvmExecutionHints([], [], [], [], []); + return new AvmExecutionHints([], [], [], [], [], []); } } diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index cc0993b36bc4..e47c222d4e20 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -17,6 +17,7 @@ import { ARGS_LENGTH, AppendOnlyTreeSnapshot, AvmCircuitInputs, + AvmContractInstanceHint, AvmExecutionHints, AvmExternalCallHint, AvmKeyValueHint, @@ -1277,7 +1278,7 @@ export function makeAvmKeyValueHint(seed = 0): AvmKeyValueHint { /** * Makes arbitrary AvmExternalCallHint. * @param seed - The seed to use for generating the state reference. - * @returns AvmKeyValueHint. + * @returns AvmExternalCallHint. */ export function makeAvmExternalCallHint(seed = 0): AvmExternalCallHint { return new AvmExternalCallHint( @@ -1287,6 +1288,23 @@ export function makeAvmExternalCallHint(seed = 0): AvmExternalCallHint { ); } +/** + * Makes arbitrary AvmContractInstanceHint. + * @param seed - The seed to use for generating the state reference. + * @returns AvmContractInstanceHint. + */ +export function makeAvmContractInstanceHint(seed = 0): AvmContractInstanceHint { + return new AvmContractInstanceHint( + new Fr(seed), + new Fr(seed + 0x1), + new Fr(seed + 0x2), + new Fr(seed + 0x3), + new Fr(seed + 0x4), + new Fr(seed + 0x5), + new Fr(seed + 0x6), + ); +} + /** * Creates arbitrary AvmExecutionHints. * @param seed - The seed to use for generating the hints. @@ -1306,6 +1324,7 @@ export function makeAvmExecutionHints( nullifierExists: makeVector(baseLength + 2, makeAvmKeyValueHint, seed + 0x4400), l1ToL2MessageExists: makeVector(baseLength + 3, makeAvmKeyValueHint, seed + 0x4500), externalCalls: makeVector(baseLength + 4, makeAvmExternalCallHint, seed + 0x4600), + contractInstances: makeVector(baseLength + 5, makeAvmContractInstanceHint, seed + 0x4700), ...overrides, }); } diff --git a/yarn-project/simulator/src/avm/fixtures/index.ts b/yarn-project/simulator/src/avm/fixtures/index.ts index d67242435e30..fae76eceb2c3 100644 --- a/yarn-project/simulator/src/avm/fixtures/index.ts +++ b/yarn-project/simulator/src/avm/fixtures/index.ts @@ -4,6 +4,7 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; import { AvmNestedCallsTestContractArtifact, AvmTestContractArtifact } from '@aztec/noir-contracts.js'; +import { SerializableContractInstance } from '@aztec/types/contracts'; import { strict as assert } from 'assert'; import { mock } from 'jest-mock-extended'; @@ -16,6 +17,7 @@ import { AvmMachineState } from '../avm_machine_state.js'; import { Field, Uint8 } from '../avm_memory_types.js'; import { HostStorage } from '../journal/host_storage.js'; import { AvmPersistableStateManager } from '../journal/journal.js'; +import { type TracedContractInstance } from '../journal/trace_types.js'; /** * Create a new AVM context with default values. @@ -145,3 +147,14 @@ export function getAvmNestedCallsTestContractBytecode(functionName: string): Buf ); return artifact.bytecode; } + +export function randomTracedContractInstance(): TracedContractInstance { + const instance = SerializableContractInstance.random(); + const address = AztecAddress.random(); + return { exists: true, ...instance, address }; +} + +export function emptyTracedContractInstance(withAddress?: AztecAddress): TracedContractInstance { + const instance = SerializableContractInstance.empty().withAddress(withAddress ?? AztecAddress.zero()); + return { exists: false, ...instance }; +} diff --git a/yarn-project/simulator/src/avm/journal/journal.test.ts b/yarn-project/simulator/src/avm/journal/journal.test.ts index ea08c9a63871..77b7b3732b68 100644 --- a/yarn-project/simulator/src/avm/journal/journal.test.ts +++ b/yarn-project/simulator/src/avm/journal/journal.test.ts @@ -6,18 +6,20 @@ import { Fr } from '@aztec/foundation/fields'; import { type MockProxy, mock } from 'jest-mock-extended'; import { type CommitmentsDB, type PublicContractsDB, type PublicStateDB } from '../../index.js'; +import { emptyTracedContractInstance, randomTracedContractInstance } from '../fixtures/index.js'; import { HostStorage } from './host_storage.js'; import { AvmPersistableStateManager, type JournalData } from './journal.js'; describe('journal', () => { let publicDb: MockProxy; + let contractsDb: MockProxy; let commitmentsDb: MockProxy; let journal: AvmPersistableStateManager; beforeEach(() => { publicDb = mock(); commitmentsDb = mock(); - const contractsDb = mock(); + contractsDb = mock(); const hostStorage = new HostStorage(publicDb, contractsDb, commitmentsDb); journal = new AvmPersistableStateManager(hostStorage); @@ -155,6 +157,23 @@ describe('journal', () => { const journalUpdates = journal.flush(); expect(journalUpdates.newL1Messages).toEqual([expect.objectContaining({ recipient, content: msgHash })]); }); + + describe('Getting contract instances', () => { + it('Should get contract instance', async () => { + const contractAddress = AztecAddress.fromField(new Fr(2)); + const instance = randomTracedContractInstance(); + instance.exists = true; + contractsDb.getContractInstance.mockResolvedValue(Promise.resolve(instance)); + await journal.getContractInstance(contractAddress); + expect(journal.trace.gotContractInstances).toEqual([instance]); + }); + it('Can get undefined contract instance', async () => { + const contractAddress = AztecAddress.fromField(new Fr(2)); + await journal.getContractInstance(contractAddress); + const emptyInstance = emptyTracedContractInstance(AztecAddress.fromField(contractAddress)); + expect(journal.trace.gotContractInstances).toEqual([emptyInstance]); + }); + }); }); it('Should merge two successful journals together', async () => { @@ -166,6 +185,7 @@ describe('journal', () => { // t2 -> journal0 -> read | 2 const contractAddress = new Fr(1); + const aztecContractAddress = AztecAddress.fromField(contractAddress); const key = new Fr(2); const value = new Fr(1); const valueT1 = new Fr(2); @@ -176,6 +196,7 @@ describe('journal', () => { const logT1 = { address: 20n, selector: 8, data: [new Fr(7), new Fr(8)] }; const index = new Fr(42); const indexT1 = new Fr(24); + const instance = emptyTracedContractInstance(aztecContractAddress); journal.writeStorage(contractAddress, key, value); await journal.readStorage(contractAddress, key); @@ -185,6 +206,7 @@ describe('journal', () => { await journal.writeNullifier(contractAddress, commitment); await journal.checkNullifierExists(contractAddress, commitment); await journal.checkL1ToL2MessageExists(commitment, index); + await journal.getContractInstance(aztecContractAddress); const childJournal = new AvmPersistableStateManager(journal.hostStorage, journal); childJournal.writeStorage(contractAddress, key, valueT1); @@ -195,6 +217,7 @@ describe('journal', () => { await childJournal.writeNullifier(contractAddress, commitmentT1); await childJournal.checkNullifierExists(contractAddress, commitmentT1); await childJournal.checkL1ToL2MessageExists(commitmentT1, indexT1); + await childJournal.getContractInstance(aztecContractAddress); journal.acceptNestedCallState(childJournal); @@ -281,6 +304,7 @@ describe('journal', () => { expect.objectContaining({ leafIndex: index, msgHash: commitment, exists: false }), expect.objectContaining({ leafIndex: indexT1, msgHash: commitmentT1, exists: false }), ]); + expect(journal.trace.gotContractInstances).toEqual([instance, instance]); }); it('Should merge failed journals together', async () => { @@ -294,6 +318,7 @@ describe('journal', () => { // t2 -> journal0 -> read | 1 const contractAddress = new Fr(1); + const aztecContractAddress = AztecAddress.fromField(contractAddress); const key = new Fr(2); const value = new Fr(1); const valueT1 = new Fr(2); @@ -304,6 +329,7 @@ describe('journal', () => { const logT1 = { address: 20n, selector: 8, data: [new Fr(7), new Fr(8)] }; const index = new Fr(42); const indexT1 = new Fr(24); + const instance = emptyTracedContractInstance(aztecContractAddress); journal.writeStorage(contractAddress, key, value); await journal.readStorage(contractAddress, key); @@ -313,6 +339,7 @@ describe('journal', () => { await journal.checkL1ToL2MessageExists(commitment, index); journal.writeLog(new Fr(log.address), new Fr(log.selector), log.data); journal.writeL1Message(recipient, commitment); + await journal.getContractInstance(aztecContractAddress); const childJournal = new AvmPersistableStateManager(journal.hostStorage, journal); childJournal.writeStorage(contractAddress, key, valueT1); @@ -323,6 +350,7 @@ describe('journal', () => { await journal.checkL1ToL2MessageExists(commitmentT1, indexT1); childJournal.writeLog(new Fr(logT1.address), new Fr(logT1.selector), logT1.data); childJournal.writeL1Message(recipient, commitmentT1); + await childJournal.getContractInstance(aztecContractAddress); journal.rejectNestedCallState(childJournal); @@ -404,6 +432,7 @@ describe('journal', () => { ), ]); expect(journalUpdates.newL1Messages).toEqual([expect.objectContaining({ recipient, content: commitment })]); + expect(journal.trace.gotContractInstances).toEqual([instance, instance]); }); it('Can fork and merge journals', () => { diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index 06126bc13592..6db4376f6d57 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -14,6 +14,7 @@ import { import { EventSelector } from '@aztec/foundation/abi'; import { Fr } from '@aztec/foundation/fields'; import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; +import { SerializableContractInstance } from '@aztec/types/contracts'; import { type PublicExecutionResult } from '../../index.js'; import { type HostStorage } from './host_storage.js'; @@ -21,6 +22,7 @@ import { Nullifiers } from './nullifiers.js'; import { PublicStorage } from './public_storage.js'; import { WorldStateAccessTrace } from './trace.js'; import { + type TracedContractInstance, type TracedL1toL2MessageCheck, type TracedNoteHash, type TracedNoteHashCheck, @@ -329,6 +331,19 @@ export class AvmPersistableStateManager { this.trace.traceNewLog(logHash); } + public async getContractInstance(contractAddress: Fr): Promise { + let exists = true; + const aztecAddress = AztecAddress.fromField(contractAddress); + let instance = await this.hostStorage.contractsDb.getContractInstance(aztecAddress); + if (instance === undefined) { + instance = SerializableContractInstance.empty().withAddress(aztecAddress); + exists = false; + } + const tracedInstance = { ...instance, exists }; + this.trace.traceGetContractInstance(tracedInstance); + return Promise.resolve(tracedInstance); + } + /** * Accept nested world state modifications, merging in its trace and accrued substate */ diff --git a/yarn-project/simulator/src/avm/journal/trace.test.ts b/yarn-project/simulator/src/avm/journal/trace.test.ts index adb1b164936a..a143ce4e3be4 100644 --- a/yarn-project/simulator/src/avm/journal/trace.test.ts +++ b/yarn-project/simulator/src/avm/journal/trace.test.ts @@ -1,5 +1,6 @@ import { Fr } from '@aztec/foundation/fields'; +import { randomTracedContractInstance } from '../fixtures/index.js'; import { WorldStateAccessTrace } from './trace.js'; import { type TracedL1toL2MessageCheck, type TracedNullifier, type TracedNullifierCheck } from './trace_types.js'; @@ -91,6 +92,12 @@ describe('world state access trace', () => { expect(trace.l1ToL2MessageChecks).toEqual([expectedCheck]); expect(trace.getAccessCounter()).toEqual(1); }); + it('Should trace get contract instance', () => { + const instance = randomTracedContractInstance(); + trace.traceGetContractInstance(instance); + expect(trace.gotContractInstances).toEqual([instance]); + expect(trace.getAccessCounter()).toEqual(1); + }); }); it('Access counter should properly count accesses', () => { @@ -107,6 +114,7 @@ describe('world state access trace', () => { const msgExists = false; const msgLeafIndex = Fr.ZERO; const msgHash = new Fr(10); + const instance = randomTracedContractInstance(); let counter = 0; trace.tracePublicStorageWrite(contractAddress, slot, value); @@ -135,6 +143,8 @@ describe('world state access trace', () => { counter++; trace.traceL1ToL2MessageCheck(msgHash, msgLeafIndex, msgExists); counter++; + trace.traceGetContractInstance(instance); + counter++; expect(trace.getAccessCounter()).toEqual(counter); }); @@ -167,6 +177,9 @@ describe('world state access trace', () => { const msgExistsT1 = true; const msgLeafIndexT1 = new Fr(42); + const instance = randomTracedContractInstance(); + const instanceT1 = randomTracedContractInstance(); + const expectedMessageCheck = { leafIndex: msgLeafIndex, msgHash: msgHash, @@ -185,6 +198,7 @@ describe('world state access trace', () => { trace.traceNullifierCheck(contractAddress, nullifier, nullifierExists, nullifierIsPending, nullifierLeafIndex); trace.traceNewNullifier(contractAddress, nullifier); trace.traceL1ToL2MessageCheck(msgHash, msgLeafIndex, msgExists); + trace.traceGetContractInstance(instance); const childTrace = new WorldStateAccessTrace(trace); childTrace.tracePublicStorageWrite(contractAddress, slot, valueT1); @@ -200,6 +214,7 @@ describe('world state access trace', () => { ); childTrace.traceNewNullifier(contractAddress, nullifierT1); childTrace.traceL1ToL2MessageCheck(msgHashT1, msgLeafIndexT1, msgExistsT1); + childTrace.traceGetContractInstance(instanceT1); const childCounterBeforeMerge = childTrace.getAccessCounter(); trace.acceptAndMerge(childTrace); @@ -274,5 +289,6 @@ describe('world state access trace', () => { expect.objectContaining({ leafIndex: msgLeafIndex, msgHash: msgHash, exists: msgExists }), expect.objectContaining({ leafIndex: msgLeafIndexT1, msgHash: msgHashT1, exists: msgExistsT1 }), ]); + expect(trace.gotContractInstances).toEqual([instance, instanceT1]); }); }); diff --git a/yarn-project/simulator/src/avm/journal/trace.ts b/yarn-project/simulator/src/avm/journal/trace.ts index de8d4e0d00cf..608f738ccc34 100644 --- a/yarn-project/simulator/src/avm/journal/trace.ts +++ b/yarn-project/simulator/src/avm/journal/trace.ts @@ -1,6 +1,7 @@ import { Fr } from '@aztec/foundation/fields'; import { + type TracedContractInstance, type TracedL1toL2MessageCheck, type TracedNoteHash, type TracedNoteHashCheck, @@ -23,6 +24,7 @@ export class WorldStateAccessTrace { public newNullifiers: TracedNullifier[] = []; public l1ToL2MessageChecks: TracedL1toL2MessageCheck[] = []; public newLogsHashes: TracedUnencryptedL2Log[] = []; + public gotContractInstances: TracedContractInstance[] = []; //public contractCalls: TracedContractCall[] = []; //public archiveChecks: TracedArchiveLeafCheck[] = []; @@ -147,6 +149,11 @@ export class WorldStateAccessTrace { this.incrementAccessCounter(); } + public traceGetContractInstance(instance: TracedContractInstance) { + this.gotContractInstances.push(instance); + this.incrementAccessCounter(); + } + private incrementAccessCounter() { this.accessCounter++; } @@ -167,6 +174,7 @@ export class WorldStateAccessTrace { this.newNullifiers.push(...incomingTrace.newNullifiers); this.l1ToL2MessageChecks.push(...incomingTrace.l1ToL2MessageChecks); this.newLogsHashes.push(...incomingTrace.newLogsHashes); + this.gotContractInstances.push(...incomingTrace.gotContractInstances); // it is assumed that the incoming trace was initialized with this as parent, so accept counter this.accessCounter = incomingTrace.accessCounter; } diff --git a/yarn-project/simulator/src/avm/journal/trace_types.ts b/yarn-project/simulator/src/avm/journal/trace_types.ts index 24c88a789bc3..db57e53998ba 100644 --- a/yarn-project/simulator/src/avm/journal/trace_types.ts +++ b/yarn-project/simulator/src/avm/journal/trace_types.ts @@ -1,4 +1,5 @@ import { type Fr } from '@aztec/foundation/fields'; +import { type ContractInstanceWithAddress } from '@aztec/types/contracts'; //export type TracedContractCall = { // callPointer: Fr; @@ -86,3 +87,5 @@ export type TracedUnencryptedL2Log = { // leafIndex: Fr; // leaf: Fr; //}; + +export type TracedContractInstance = { exists: boolean } & ContractInstanceWithAddress; diff --git a/yarn-project/simulator/src/avm/opcodes/contract.test.ts b/yarn-project/simulator/src/avm/opcodes/contract.test.ts index b993a84eb47a..105d9ef579c5 100644 --- a/yarn-project/simulator/src/avm/opcodes/contract.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/contract.test.ts @@ -1,23 +1,21 @@ import { AztecAddress, Fr } from '@aztec/circuits.js'; +import { type ContractInstanceWithAddress } from '@aztec/types/contracts'; -import { type DeepMockProxy, mockDeep } from 'jest-mock-extended'; +import { mock } from 'jest-mock-extended'; +import { type PublicContractsDB } from '../../public/db_interfaces.js'; import { type AvmContext } from '../avm_context.js'; import { Field } from '../avm_memory_types.js'; -import { initContext } from '../fixtures/index.js'; -import { type AvmPersistableStateManager } from '../journal/journal.js'; +import { initContext, initHostStorage } from '../fixtures/index.js'; +import { AvmPersistableStateManager } from '../journal/journal.js'; import { GetContractInstance } from './contract.js'; describe('Contract opcodes', () => { let context: AvmContext; - let journal: DeepMockProxy; const address = AztecAddress.random(); beforeEach(async () => { - journal = mockDeep(); - context = initContext({ - persistableState: journal, - }); + context = initContext(); }); describe('GETCONTRACTINSTANCE', () => { @@ -49,9 +47,11 @@ describe('Contract opcodes', () => { initializationHash: new Fr(40), publicKeysHash: new Fr(50), deployer: AztecAddress.random(), - }; + } as ContractInstanceWithAddress; - journal.hostStorage.contractsDb.getContractInstance.mockReturnValue(Promise.resolve(contractInstance)); + const contractsDb = mock(); + contractsDb.getContractInstance.mockResolvedValue(Promise.resolve(contractInstance)); + context.persistableState = new AvmPersistableStateManager(initHostStorage({ contractsDb })); await new GetContractInstance(/*indirect=*/ 0, /*addressOffset=*/ 0, /*dstOffset=*/ 1).execute(context); @@ -68,7 +68,6 @@ describe('Contract opcodes', () => { it('should return zeroes if not found', async () => { context.machineState.memory.set(0, new Field(address.toField())); - journal.hostStorage.contractsDb.getContractInstance.mockReturnValue(Promise.resolve(undefined)); await new GetContractInstance(/*indirect=*/ 0, /*addressOffset=*/ 0, /*dstOffset=*/ 1).execute(context); diff --git a/yarn-project/simulator/src/avm/opcodes/contract.ts b/yarn-project/simulator/src/avm/opcodes/contract.ts index a0b41705bfd9..7b48bd527e85 100644 --- a/yarn-project/simulator/src/avm/opcodes/contract.ts +++ b/yarn-project/simulator/src/avm/opcodes/contract.ts @@ -1,4 +1,4 @@ -import { AztecAddress, Fr } from '@aztec/circuits.js'; +import { Fr } from '@aztec/circuits.js'; import type { AvmContext } from '../avm_context.js'; import { Field } from '../avm_memory_types.js'; @@ -27,27 +27,17 @@ export class GetContractInstance extends Instruction { context.machineState.memory, ); - const address = AztecAddress.fromField(context.machineState.memory.get(addressOffset).toFr()); - const instance = await context.persistableState.hostStorage.contractsDb.getContractInstance(address); - - const data = - instance === undefined - ? [ - new Field(0), // not found - new Field(0), - new Field(0), - new Field(0), - new Field(0), - new Field(0), - ] - : [ - new Fr(1), // found - instance.salt, - instance.deployer.toField(), - instance.contractClassId, - instance.initializationHash, - instance.publicKeysHash, - ].map(f => new Field(f)); + const address = context.machineState.memory.get(addressOffset).toFr(); + const instance = await context.persistableState.getContractInstance(address); + + const data = [ + new Fr(instance.exists), + instance.salt, + instance.deployer.toField(), + instance.contractClassId, + instance.initializationHash, + instance.publicKeysHash, + ].map(f => new Field(f)); context.machineState.memory.setSlice(dstOffset, data); diff --git a/yarn-project/simulator/src/public/transitional_adaptors.ts b/yarn-project/simulator/src/public/transitional_adaptors.ts index 8045c0bf0160..21d26e0810ce 100644 --- a/yarn-project/simulator/src/public/transitional_adaptors.ts +++ b/yarn-project/simulator/src/public/transitional_adaptors.ts @@ -1,6 +1,7 @@ // All code in this file needs to die once the public executor is phased out in favor of the AVM. import { UnencryptedFunctionL2Logs } from '@aztec/circuit-types'; import { + AvmContractInstanceHint, AvmExecutionHints, AvmExternalCallHint, AvmKeyValueHint, @@ -91,6 +92,18 @@ function computeHints(trace: WorldStateAccessTrace, executionResult: PartialPubl ); return new AvmExternalCallHint(/*success=*/ new Fr(nested.reverted ? 0 : 1), nested.returnValues, gasUsed); }), + trace.gotContractInstances.map( + instance => + new AvmContractInstanceHint( + instance.address, + new Fr(instance.exists ? 1 : 0), + instance.salt, + instance.deployer, + instance.contractClassId, + instance.initializationHash, + instance.publicKeysHash, + ), + ), ); } diff --git a/yarn-project/types/src/contracts/contract_instance.ts b/yarn-project/types/src/contracts/contract_instance.ts index 1a46d2027af9..aa12e8aff2ec 100644 --- a/yarn-project/types/src/contracts/contract_instance.ts +++ b/yarn-project/types/src/contracts/contract_instance.ts @@ -11,14 +11,14 @@ export interface ContractInstance { version: typeof VERSION; /** User-generated pseudorandom value for uniqueness. */ salt: Fr; + /** Optional deployer address or zero if this was a universal deploy. */ + deployer: AztecAddress; /** Identifier of the contract class for this instance. */ contractClassId: Fr; /** Hash of the selector and arguments to the constructor. */ initializationHash: Fr; /** Optional hash of the struct of public keys used for encryption and nullifying by this contract. */ publicKeysHash: Fr; - /** Optional deployer address or zero if this was a universal deploy. */ - deployer: AztecAddress; } export type ContractInstanceWithAddress = ContractInstance & { address: AztecAddress }; @@ -26,30 +26,30 @@ export type ContractInstanceWithAddress = ContractInstance & { address: AztecAdd export class SerializableContractInstance { public readonly version = VERSION; public readonly salt: Fr; + public readonly deployer: AztecAddress; public readonly contractClassId: Fr; public readonly initializationHash: Fr; public readonly publicKeysHash: Fr; - public readonly deployer: AztecAddress; constructor(instance: ContractInstance) { if (instance.version !== VERSION) { throw new Error(`Unexpected contract class version ${instance.version}`); } this.salt = instance.salt; + this.deployer = instance.deployer; this.contractClassId = instance.contractClassId; this.initializationHash = instance.initializationHash; this.publicKeysHash = instance.publicKeysHash; - this.deployer = instance.deployer; } public toBuffer() { return serializeToBuffer( numToUInt8(this.version), this.salt, + this.deployer, this.contractClassId, this.initializationHash, this.publicKeysHash, - this.deployer, ); } @@ -63,10 +63,10 @@ export class SerializableContractInstance { return new SerializableContractInstance({ version: reader.readUInt8() as typeof VERSION, salt: reader.readObject(Fr), + deployer: reader.readObject(AztecAddress), contractClassId: reader.readObject(Fr), initializationHash: reader.readObject(Fr), publicKeysHash: reader.readObject(Fr), - deployer: reader.readObject(AztecAddress), }); } @@ -74,11 +74,22 @@ export class SerializableContractInstance { return new SerializableContractInstance({ version: VERSION, salt: Fr.random(), + deployer: AztecAddress.random(), contractClassId: Fr.random(), initializationHash: Fr.random(), publicKeysHash: Fr.random(), - deployer: AztecAddress.random(), ...opts, }); } + + static empty() { + return new SerializableContractInstance({ + version: VERSION, + salt: Fr.zero(), + deployer: AztecAddress.zero(), + contractClassId: Fr.zero(), + initializationHash: Fr.zero(), + publicKeysHash: Fr.zero(), + }); + } }