diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index 1b97449d81c2..c3f7fae20ea7 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -84,9 +84,6 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec { BinaryIntOp::Xor => AvmOpcode::XOR, BinaryIntOp::Shl => AvmOpcode::SHL, BinaryIntOp::Shr => AvmOpcode::SHR, - _ => panic!( - "Transpiler doesn't know how to process {:?}", brillig_instr - ), }; avm_instrs.push(AvmInstruction { opcode: avm_opcode, diff --git a/avm-transpiler/src/transpile_contract.rs b/avm-transpiler/src/transpile_contract.rs index f7b732df61db..537dfb34bb3d 100644 --- a/avm-transpiler/src/transpile_contract.rs +++ b/avm-transpiler/src/transpile_contract.rs @@ -1,6 +1,5 @@ use base64::Engine; use log::info; -use regex::Regex; use serde::{Deserialize, Serialize}; use acvm::acir::circuit::Program; diff --git a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr index bc34fa26b3b1..474e6f05a723 100644 --- a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr @@ -26,9 +26,11 @@ contract AvmTest { // Libs use dep::aztec::prelude::Map; use dep::aztec::state_vars::PublicMutable; + use dep::aztec::history::nullifier_inclusion::prove_nullifier_inclusion; use dep::aztec::protocol_types::{ address::{AztecAddress, EthAddress}, constants::L1_TO_L2_MESSAGE_LENGTH, - contract_instance::ContractInstance + contract_instance::ContractInstance, + hash::silo_nullifier, }; use dep::aztec::oracle::get_contract_instance::{get_contract_instance_avm, get_contract_instance_internal_avm}; use dep::aztec::protocol_types::abis::function_selector::FunctionSelector; @@ -187,6 +189,18 @@ contract AvmTest { 123456 } + #[aztec(public)] + fn new_nullifier_acvm(nullifier: Field) -> pub Field { + context.push_new_nullifier(nullifier, 0); + } + + #[aztec(public)] + fn assert_unsiloed_nullifier_acvm(nullifier: Field) { + // ACVM requires siloed nullifier. + let siloed_nullifier = silo_nullifier(context.this_address(), nullifier); + prove_nullifier_inclusion(siloed_nullifier, context); + } + #[aztec(public-vm)] fn call_acvm_from_avm() -> pub Field { let data_to_return: [Field; RETURN_VALUES_LENGTH] = context.call_public_function( @@ -207,6 +221,15 @@ contract AvmTest { data_to_return[0] } + #[aztec(public-vm)] + fn avm_to_acvm_call(selector: FunctionSelector, args: Field) { + context.call_public_function( + context.this_address(), + selector, + [args] + ); + } + /************************************************************************ * Contract instance ************************************************************************/ diff --git a/yarn-project/end-to-end/package.json b/yarn-project/end-to-end/package.json index 27df3c3cada6..570c954e7586 100644 --- a/yarn-project/end-to-end/package.json +++ b/yarn-project/end-to-end/package.json @@ -15,7 +15,8 @@ "clean": "rm -rf ./dest .tsbuildinfo", "formatting": "run -T prettier --check ./src \"!src/web/main.js\" && run -T eslint ./src", "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src", - "test": "DEBUG='aztec:*' NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --runInBand --testTimeout=60000 --forceExit", + "test:all-logs": "DEBUG='aztec:*' NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --runInBand --testTimeout=60000 --forceExit", + "test": "DEBUG='aztec:*,-*avm_simulator:memory,-*avm_simulator:core*' NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --runInBand --testTimeout=60000 --forceExit", "test:integration": "concurrently -k -s first -c reset,dim -n test,anvil \"yarn test:integration:run\" \"anvil\"", "test:integration:run": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --no-cache --runInBand --config jest.integration.config.json" }, diff --git a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts index f21c466d630b..a40731b019ac 100644 --- a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts +++ b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts @@ -1,4 +1,4 @@ -import { AztecAddress, Fr, TxStatus, type Wallet } from '@aztec/aztec.js'; +import { AztecAddress, Fr, FunctionSelector, TxStatus, type Wallet } from '@aztec/aztec.js'; import { AvmInitializerTestContract, AvmTestContract } from '@aztec/noir-contracts.js'; import { jest } from '@jest/globals'; @@ -64,6 +64,40 @@ describe('e2e_avm_simulator', () => { expect(tx.status).toEqual(TxStatus.MINED); }); }); + + describe('ACVM interoperability', () => { + it('Can execute ACVM function among AVM functions', async () => { + expect(await avmContract.methods.constant_field_acvm().simulate()).toEqual([123456n, 0n, 0n, 0n]); + }); + + it('Can call AVM function from ACVM', async () => { + expect(await avmContract.methods.call_avm_from_acvm().simulate()).toEqual([123456n, 0n, 0n, 0n]); + }); + + it('Can call ACVM function from AVM', async () => { + expect(await avmContract.methods.call_acvm_from_avm().simulate()).toEqual([123456n, 0n, 0n, 0n]); + }); + + // Cannot work because ACVM does not support pending nullifiers. + // it('AVM->ACVM nullifiers work (pending)', async () => { + // await avmContract.methods.avm_to_acvm_nullifier().send().wait(); + // }); + + it('AVM sees settled nullifiers by ACVM', async () => { + const nullifier = new Fr(123456); + await avmContract.methods.new_nullifier(nullifier).send().wait(); + await avmContract.methods.assert_unsiloed_nullifier_acvm(nullifier).send().wait(); + }); + + it('AVM nested call to ACVM sees settled nullifiers', async () => { + const nullifier = new Fr(123456); + await avmContract.methods.new_nullifier(nullifier).send().wait(); + await avmContract.methods + .avm_to_acvm_call(FunctionSelector.fromSignature('assert_unsiloed_nullifier_acvm(Field)'), nullifier) + .send() + .wait(); + }); + }); }); describe('AvmInitializerTestContract', () => { @@ -79,10 +113,4 @@ describe('e2e_avm_simulator', () => { }); }); }); - - describe('ACVM interoperability', () => { - it('Can execute ACVM function among AVM functions', async () => { - expect(await avmContact.methods.constant_field_acvm().simulate()).toEqual(123456n); - }); - }); }); diff --git a/yarn-project/simulator/src/avm/avm_execution_environment.ts b/yarn-project/simulator/src/avm/avm_execution_environment.ts index 379444053e09..c21fb948a980 100644 --- a/yarn-project/simulator/src/avm/avm_execution_environment.ts +++ b/yarn-project/simulator/src/avm/avm_execution_environment.ts @@ -48,15 +48,15 @@ export class AvmExecutionEnvironment { } public deriveEnvironmentForNestedCall( - address: AztecAddress, + targetAddress: AztecAddress, calldata: Fr[], temporaryFunctionSelector: FunctionSelector = FunctionSelector.empty(), ): AvmExecutionEnvironment { return new AvmExecutionEnvironment( - address, - /*storageAddress=*/ address, + targetAddress, + /*storageAddress=*/ targetAddress, this.origin, - this.sender, + this.address, this.portal, this.feePerL1Gas, this.feePerL2Gas, diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index b5247881e7e8..9aeea5052960 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -9,7 +9,9 @@ import { AvmTestContractArtifact } from '@aztec/noir-contracts.js'; import { jest } from '@jest/globals'; import { strict as assert } from 'assert'; +import { exists } from 'fs'; +import { isAvmBytecode } from '../public/transitional_adaptors.js'; import { AvmMachineState } from './avm_machine_state.js'; import { TypeTag } from './avm_memory_types.js'; import { AvmSimulator } from './avm_simulator.js'; @@ -23,7 +25,6 @@ import { } from './fixtures/index.js'; import { Add, CalldataCopy, Return } from './opcodes/index.js'; import { encodeToBytecode } from './serialization/bytecode_serialization.js'; -import { isAvmBytecode } from './temporary_executor_migration.js'; describe('AVM simulator: injected bytecode', () => { let calldata: Fr[]; @@ -337,7 +338,12 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(results.reverted).toBe(false); - expect(context.persistableState.flush().newNoteHashes).toEqual([utxo]); + expect(context.persistableState.flush().newNoteHashes).toEqual([ + expect.objectContaining({ + storageAddress: context.environment.storageAddress, + noteHash: utxo, + }), + ]); }); it(`Emit nullifier (should be traced)`, async () => { @@ -350,7 +356,12 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(results.reverted).toBe(false); - expect(context.persistableState.flush().newNullifiers).toEqual([utxo]); + expect(context.persistableState.flush().newNullifiers).toEqual([ + expect.objectContaining({ + storageAddress: context.environment.storageAddress, + nullifier: utxo, + }), + ]); }); it(`Nullifier exists (it does not)`, async () => { @@ -366,8 +377,16 @@ describe('AVM simulator: transpiled Noir contracts', () => { // Nullifier existence check should be in trace const trace = context.persistableState.flush(); - expect(trace.nullifierChecks.length).toEqual(1); - expect(trace.nullifierChecks[0].exists).toEqual(false); + expect(trace.nullifierChecks).toEqual([ + expect.objectContaining({ + storageAddress: context.environment.storageAddress, + nullifier: utxo, + exists: false, + counter: expect.any(Fr), + isPending: false, + leafIndex: expect.any(Fr), + }), + ]); }); it(`Nullifier exists (it does)`, async () => { @@ -387,8 +406,16 @@ describe('AVM simulator: transpiled Noir contracts', () => { // Nullifier existence check should be in trace const trace = context.persistableState.flush(); - expect(trace.nullifierChecks.length).toEqual(1); - expect(trace.nullifierChecks[0].exists).toEqual(true); + expect(trace.nullifierChecks).toEqual([ + expect.objectContaining({ + storageAddress: context.environment.storageAddress, + nullifier: utxo, + exists: true, + counter: expect.any(Fr), + isPending: false, + leafIndex: expect.any(Fr), + }), + ]); }); it(`Emits a nullifier and checks its existence`, async () => { @@ -402,9 +429,22 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(results.reverted).toBe(false); // Nullifier existence check should be in trace const trace = context.persistableState.flush(); - expect(trace.newNullifiers).toEqual([utxo]); - expect(trace.nullifierChecks.length).toEqual(1); - expect(trace.nullifierChecks[0].exists).toEqual(true); + expect(trace.newNullifiers).toEqual([ + expect.objectContaining({ + storageAddress: context.environment.storageAddress, + nullifier: utxo, + }), + ]); + expect(trace.nullifierChecks).toEqual([ + expect.objectContaining({ + storageAddress: context.environment.storageAddress, + nullifier: utxo, + exists: true, + counter: expect.any(Fr), + isPending: true, + leafIndex: expect.any(Fr), + }), + ]); }); it(`Emits same nullifier twice (should fail)`, async () => { @@ -417,7 +457,12 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(results.reverted).toBe(true); // Only the first nullifier should be in the trace, second one failed to add - expect(context.persistableState.flush().newNullifiers).toEqual([utxo]); + expect(context.persistableState.flush().newNullifiers).toEqual([ + expect.objectContaining({ + storageAddress: context.environment.storageAddress, + nullifier: utxo, + }), + ]); }); }); @@ -468,7 +513,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { const context = initContext({ env: initExecutionEnvironment({ calldata }) }); jest .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') - .mockReturnValueOnce(Promise.resolve(addBytecode)); + .mockReturnValue(Promise.resolve(addBytecode)); const results = await new AvmSimulator(context).executeBytecode(callBytecode); @@ -484,7 +529,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { const context = initContext({ env: initExecutionEnvironment({ calldata }) }); jest .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') - .mockReturnValueOnce(Promise.resolve(addBytecode)); + .mockReturnValue(Promise.resolve(addBytecode)); const results = await new AvmSimulator(context).executeBytecode(callBytecode); @@ -499,7 +544,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { const context = initContext({ env: initExecutionEnvironment({ calldata }) }); jest .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') - .mockReturnValueOnce(Promise.resolve(addBytecode)); + .mockReturnValue(Promise.resolve(addBytecode)); const results = await new AvmSimulator(context).executeBytecode(callBytecode); @@ -513,7 +558,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { const context = initContext(); jest .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') - .mockReturnValueOnce(Promise.resolve(nestedBytecode)); + .mockReturnValue(Promise.resolve(nestedBytecode)); const results = await new AvmSimulator(context).executeBytecode(callBytecode); @@ -528,7 +573,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { const context = initContext({ env: initExecutionEnvironment({ calldata }) }); jest .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') - .mockReturnValueOnce(Promise.resolve(addBytecode)); + .mockReturnValue(Promise.resolve(addBytecode)); const results = await new AvmSimulator(context).executeBytecode(callBytecode); @@ -542,11 +587,13 @@ describe('AVM simulator: transpiled Noir contracts', () => { const context = initContext(); jest .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') - .mockReturnValueOnce(Promise.resolve(nestedBytecode)); + .mockReturnValue(Promise.resolve(nestedBytecode)); const results = await new AvmSimulator(context).executeBytecode(callBytecode); expect(results.reverted).toBe(true); // The outer call should revert. + // TODO(fcarreiro): revertReason lost in translation between results. + // expect(results.revertReason).toEqual(/StaticCallStorageAlterError/); }); }); @@ -572,9 +619,13 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(adminSlotValue).toEqual(value); // Tracing - const storageTrace = worldState.storageWrites.get(address.toBigInt())!; - const slotTrace = storageTrace.get(slot); - expect(slotTrace).toEqual([value]); + expect(worldState.storageWrites).toEqual([ + expect.objectContaining({ + storageAddress: address, + slot: new Fr(slot), + value: value, + }), + ]); }); it('Should read value in storage (single)', async () => { @@ -598,9 +649,14 @@ describe('AVM simulator: transpiled Noir contracts', () => { // Tracing const worldState = context.persistableState.flush(); - const storageTrace = worldState.storageReads.get(address.toBigInt())!; - const slotTrace = storageTrace.get(slot); - expect(slotTrace).toEqual([value]); + expect(worldState.storageReads).toEqual([ + expect.objectContaining({ + storageAddress: address, + slot: new Fr(slot), + value: value, + exists: true, + }), + ]); }); it('Should set and read a value from storage (single)', async () => { @@ -620,14 +676,22 @@ describe('AVM simulator: transpiled Noir contracts', () => { // Test read trace const worldState = context.persistableState.flush(); - const storageReadTrace = worldState.storageReads.get(address.toBigInt())!; - const slotReadTrace = storageReadTrace.get(slot); - expect(slotReadTrace).toEqual([value]); + expect(worldState.storageWrites).toEqual([ + expect.objectContaining({ + storageAddress: address, + slot: new Fr(slot), + value: value, + }), + ]); // Test write trace - const storageWriteTrace = worldState.storageWrites.get(address.toBigInt())!; - const slotWriteTrace = storageWriteTrace.get(slot); - expect(slotWriteTrace).toEqual([value]); + expect(worldState.storageWrites).toEqual([ + expect.objectContaining({ + storageAddress: address, + slot: new Fr(slot), + value: value, + }), + ]); }); it('Should set a value in storage (list)', async () => { @@ -650,9 +714,18 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(storageSlot.get(slot + 1n)).toEqual(calldata[1]); // Tracing - const storageTrace = worldState.storageWrites.get(address.toBigInt())!; - expect(storageTrace.get(slot)).toEqual([calldata[0]]); - expect(storageTrace.get(slot + 1n)).toEqual([calldata[1]]); + expect(worldState.storageWrites).toEqual([ + expect.objectContaining({ + storageAddress: address, + slot: new Fr(slot), + value: calldata[0], + }), + expect.objectContaining({ + storageAddress: address, + slot: new Fr(slot + 1n), + value: calldata[1], + }), + ]); }); it('Should read a value in storage (list)', async () => { @@ -678,9 +751,20 @@ describe('AVM simulator: transpiled Noir contracts', () => { // Tracing const worldState = context.persistableState.flush(); - const storageTrace = worldState.storageReads.get(address.toBigInt())!; - expect(storageTrace.get(slot)).toEqual([values[0]]); - expect(storageTrace.get(slot + 1n)).toEqual([values[1]]); + expect(worldState.storageReads).toEqual([ + expect.objectContaining({ + storageAddress: address, + slot: new Fr(slot), + value: values[0], + exists: true, + }), + expect.objectContaining({ + storageAddress: address, + slot: new Fr(slot + 1n), + value: values[1], + exists: true, + }), + ]); }); it('Should set a value in storage (map)', async () => { @@ -703,8 +787,13 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(storageSlot.get(slotNumber)).toEqual(value); // Tracing - const storageTrace = worldState.storageWrites.get(address.toBigInt())!; - expect(storageTrace.get(slotNumber)).toEqual([value]); + expect(worldState.storageWrites).toEqual([ + expect.objectContaining({ + storageAddress: address, + slot: new Fr(slotNumber), + value: value, + }), + ]); }); it('Should read-add-set a value in storage (map)', async () => { @@ -727,10 +816,21 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(storageSlot.get(slotNumber)).toEqual(value); // Tracing - const storageReadTrace = worldState.storageReads.get(address.toBigInt())!; - expect(storageReadTrace.get(slotNumber)).toEqual([new Fr(0)]); - const storageWriteTrace = worldState.storageWrites.get(address.toBigInt())!; - expect(storageWriteTrace.get(slotNumber)).toEqual([value]); + expect(worldState.storageReads).toEqual([ + expect.objectContaining({ + storageAddress: address, + slot: new Fr(slotNumber), + value: Fr.ZERO, + exists: false, + }), + ]); + expect(worldState.storageWrites).toEqual([ + expect.objectContaining({ + storageAddress: address, + slot: new Fr(slotNumber), + value: value, + }), + ]); }); it('Should read value in storage (map)', async () => { @@ -753,8 +853,14 @@ describe('AVM simulator: transpiled Noir contracts', () => { // Tracing const worldState = context.persistableState.flush(); - const storageTrace = worldState.storageReads.get(address.toBigInt())!; - expect([...storageTrace.values()]).toEqual([[value]]); + expect(worldState.storageReads).toEqual([ + expect.objectContaining({ + storageAddress: address, + // slot depends on pedersen hash of key, etc. + value: value, + exists: true, + }), + ]); }); }); diff --git a/yarn-project/simulator/src/avm/journal/journal.test.ts b/yarn-project/simulator/src/avm/journal/journal.test.ts index 302a403d4e5e..400d70669215 100644 --- a/yarn-project/simulator/src/avm/journal/journal.test.ts +++ b/yarn-project/simulator/src/avm/journal/journal.test.ts @@ -47,23 +47,40 @@ describe('journal', () => { // We expect the journal to store the access in [storedVal, cachedVal] - [time0, time1] const { storageReads, storageWrites }: JournalData = journal.flush(); - const contractReads = storageReads.get(contractAddress.toBigInt()); - const keyReads = contractReads?.get(key.toBigInt()); - expect(keyReads).toEqual([storedValue, cachedValue]); - - const contractWrites = storageWrites.get(contractAddress.toBigInt()); - const keyWrites = contractWrites?.get(key.toBigInt()); - expect(keyWrites).toEqual([cachedValue]); + expect(storageReads).toEqual([ + expect.objectContaining({ + storageAddress: contractAddress, + exists: true, + slot: key, + value: storedValue, + }), + expect.objectContaining({ + storageAddress: contractAddress, + exists: true, + slot: key, + value: cachedValue, + }), + ]); + expect(storageWrites).toEqual([ + expect.objectContaining({ + storageAddress: contractAddress, + slot: key, + value: cachedValue, + }), + ]); }); }); describe('UTXOs & messages', () => { it('Should maintain commitments', () => { const utxo = new Fr(1); - journal.writeNoteHash(utxo); + const address = new Fr(1234); + journal.writeNoteHash(address, utxo); const journalUpdates = journal.flush(); - expect(journalUpdates.newNoteHashes).toEqual([utxo]); + expect(journalUpdates.newNoteHashes).toEqual([ + expect.objectContaining({ noteHash: utxo, storageAddress: address }), + ]); }); it('checkNullifierExists works for missing nullifiers', async () => { const contractAddress = new Fr(1); @@ -92,7 +109,9 @@ describe('journal', () => { await journal.writeNullifier(contractAddress, utxo); const journalUpdates = journal.flush(); - expect(journalUpdates.newNullifiers).toEqual([utxo]); + expect(journalUpdates.newNullifiers).toEqual([ + expect.objectContaining({ storageAddress: contractAddress, nullifier: utxo }), + ]); }); it('checkL1ToL2MessageExists works for missing message', async () => { const utxo = new Fr(2); @@ -125,7 +144,9 @@ describe('journal', () => { await journal.writeNullifier(contractAddress, utxo); const journalUpdates = journal.flush(); - expect(journalUpdates.newNullifiers).toEqual([utxo]); + expect(journalUpdates.newNullifiers).toEqual([ + expect.objectContaining({ storageAddress: contractAddress, nullifier: utxo }), + ]); }); it('Should maintain l1 messages', () => { const recipient = EthAddress.fromField(new Fr(1)); @@ -159,7 +180,7 @@ describe('journal', () => { journal.writeStorage(contractAddress, key, value); await journal.readStorage(contractAddress, key); - journal.writeNoteHash(commitment); + journal.writeNoteHash(contractAddress, commitment); journal.writeLog(new Fr(log.address), new Fr(log.selector), log.data); journal.writeL1Message(recipient, commitment); await journal.writeNullifier(contractAddress, commitment); @@ -169,7 +190,7 @@ describe('journal', () => { const childJournal = new AvmPersistableStateManager(journal.hostStorage, journal); childJournal.writeStorage(contractAddress, key, valueT1); await childJournal.readStorage(contractAddress, key); - childJournal.writeNoteHash(commitmentT1); + childJournal.writeNoteHash(contractAddress, commitmentT1); childJournal.writeLog(new Fr(logT1.address), new Fr(logT1.selector), logT1.data); childJournal.writeL1Message(recipient, commitmentT1); await childJournal.writeNullifier(contractAddress, commitmentT1); @@ -187,16 +208,46 @@ describe('journal', () => { // Check storage reads order is preserved upon merge // We first read value from t0, then value from t1 - const contractReads = journalUpdates.storageReads.get(contractAddress.toBigInt()); - const slotReads = contractReads?.get(key.toBigInt()); - expect(slotReads).toEqual([value, valueT1, valueT1]); // Read a third time to check storage + expect(journalUpdates.storageReads).toEqual([ + expect.objectContaining({ + storageAddress: contractAddress, + exists: true, + slot: key, + value: value, + }), + expect.objectContaining({ + storageAddress: contractAddress, + exists: true, + slot: key, + value: valueT1, + }), + // Read a third time to check storage + expect.objectContaining({ + storageAddress: contractAddress, + exists: true, + slot: key, + value: valueT1, + }), + ]); // We first write value from t0, then value from t1 - const contractWrites = journalUpdates.storageWrites.get(contractAddress.toBigInt()); - const slotWrites = contractWrites?.get(key.toBigInt()); - expect(slotWrites).toEqual([value, valueT1]); + expect(journalUpdates.storageWrites).toEqual([ + expect.objectContaining({ + storageAddress: contractAddress, + slot: key, + value: value, + }), + expect.objectContaining({ + storageAddress: contractAddress, + slot: key, + value: valueT1, + }), + ]); - expect(journalUpdates.newNoteHashes).toEqual([commitment, commitmentT1]); + expect(journalUpdates.newNoteHashes).toEqual([ + expect.objectContaining({ noteHash: commitment, storageAddress: contractAddress }), + expect.objectContaining({ noteHash: commitmentT1, storageAddress: contractAddress }), + ]); expect(journalUpdates.newLogs).toEqual([ new UnencryptedL2Log( AztecAddress.fromBigInt(log.address), @@ -217,7 +268,16 @@ describe('journal', () => { expect.objectContaining({ nullifier: commitment, exists: true }), expect.objectContaining({ nullifier: commitmentT1, exists: true }), ]); - expect(journalUpdates.newNullifiers).toEqual([commitment, commitmentT1]); + expect(journalUpdates.newNullifiers).toEqual([ + expect.objectContaining({ + storageAddress: contractAddress, + nullifier: commitment, + }), + expect.objectContaining({ + storageAddress: contractAddress, + nullifier: commitmentT1, + }), + ]); expect(journalUpdates.l1ToL2MessageChecks).toEqual([ expect.objectContaining({ leafIndex: index, msgHash: commitment, exists: false }), expect.objectContaining({ leafIndex: indexT1, msgHash: commitmentT1, exists: false }), @@ -248,7 +308,7 @@ describe('journal', () => { journal.writeStorage(contractAddress, key, value); await journal.readStorage(contractAddress, key); - journal.writeNoteHash(commitment); + journal.writeNoteHash(contractAddress, commitment); await journal.writeNullifier(contractAddress, commitment); await journal.checkNullifierExists(contractAddress, commitment); await journal.checkL1ToL2MessageExists(commitment, index); @@ -258,7 +318,7 @@ describe('journal', () => { const childJournal = new AvmPersistableStateManager(journal.hostStorage, journal); childJournal.writeStorage(contractAddress, key, valueT1); await childJournal.readStorage(contractAddress, key); - childJournal.writeNoteHash(commitmentT1); + childJournal.writeNoteHash(contractAddress, commitmentT1); await childJournal.writeNullifier(contractAddress, commitmentT1); await childJournal.checkNullifierExists(contractAddress, commitmentT1); await journal.checkL1ToL2MessageExists(commitmentT1, indexT1); @@ -276,22 +336,61 @@ describe('journal', () => { // Reads and writes should be preserved // Check storage reads order is preserved upon merge // We first read value from t0, then value from t1 - const contractReads = journalUpdates.storageReads.get(contractAddress.toBigInt()); - const slotReads = contractReads?.get(key.toBigInt()); - expect(slotReads).toEqual([value, valueT1, value]); // Read a third time to check storage above + expect(journalUpdates.storageReads).toEqual([ + expect.objectContaining({ + storageAddress: contractAddress, + exists: true, + slot: key, + value: value, + }), + expect.objectContaining({ + storageAddress: contractAddress, + exists: true, + slot: key, + value: valueT1, + }), + // Read a third time to check storage + expect.objectContaining({ + storageAddress: contractAddress, + exists: true, + slot: key, + value: value, + }), + ]); // We first write value from t0, then value from t1 - const contractWrites = journalUpdates.storageWrites.get(contractAddress.toBigInt()); - const slotWrites = contractWrites?.get(key.toBigInt()); - expect(slotWrites).toEqual([value, valueT1]); + expect(journalUpdates.storageWrites).toEqual([ + expect.objectContaining({ + storageAddress: contractAddress, + slot: key, + value: value, + }), + expect.objectContaining({ + storageAddress: contractAddress, + slot: key, + value: valueT1, + }), + ]); // Check that the world state _traces_ are merged even on rejection - expect(journalUpdates.newNoteHashes).toEqual([commitment, commitmentT1]); + expect(journalUpdates.newNoteHashes).toEqual([ + expect.objectContaining({ noteHash: commitment, storageAddress: contractAddress }), + expect.objectContaining({ noteHash: commitmentT1, storageAddress: contractAddress }), + ]); expect(journalUpdates.nullifierChecks).toEqual([ expect.objectContaining({ nullifier: commitment, exists: true }), expect.objectContaining({ nullifier: commitmentT1, exists: true }), ]); - expect(journalUpdates.newNullifiers).toEqual([commitment, commitmentT1]); + expect(journalUpdates.newNullifiers).toEqual([ + expect.objectContaining({ + storageAddress: contractAddress, + nullifier: commitment, + }), + expect.objectContaining({ + storageAddress: contractAddress, + nullifier: commitmentT1, + }), + ]); expect(journalUpdates.l1ToL2MessageChecks).toEqual([ expect.objectContaining({ leafIndex: index, msgHash: commitment, exists: false }), expect.objectContaining({ leafIndex: indexT1, msgHash: commitmentT1, exists: false }), diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index d7274dec92b9..c662a5ecc8c9 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -7,16 +7,27 @@ import { type HostStorage } from './host_storage.js'; import { Nullifiers } from './nullifiers.js'; import { PublicStorage } from './public_storage.js'; import { WorldStateAccessTrace } from './trace.js'; -import { type TracedL1toL2MessageCheck, type TracedNoteHashCheck, type TracedNullifierCheck } from './trace_types.js'; +import { + type TracedL1toL2MessageCheck, + type TracedNoteHash, + type TracedNoteHashCheck, + type TracedNullifier, + type TracedNullifierCheck, + type TracedPublicStorageRead, + type TracedPublicStorageWrite, +} from './trace_types.js'; /** * Data held within the journal */ export type JournalData = { + storageWrites: TracedPublicStorageWrite[]; + storageReads: TracedPublicStorageRead[]; + noteHashChecks: TracedNoteHashCheck[]; - newNoteHashes: Fr[]; + newNoteHashes: TracedNoteHash[]; nullifierChecks: TracedNullifierCheck[]; - newNullifiers: Fr[]; + newNullifiers: TracedNullifier[]; l1ToL2MessageChecks: TracedL1toL2MessageCheck[]; newL1Messages: L2ToL1Message[]; @@ -24,11 +35,6 @@ export type JournalData = { /** contract address -\> key -\> value */ currentStorageValue: Map>; - - /** contract address -\> key -\> value[] (stored in order of access) */ - storageWrites: Map>; - /** contract address -\> key -\> value[] (stored in order of access) */ - storageReads: Map>; }; /** @@ -44,18 +50,19 @@ export class AvmPersistableStateManager { /** Reference to node storage */ public readonly hostStorage: HostStorage; + // TODO: make members private once this is not used in transitional_adaptors.ts. /** World State */ /** Public storage, including cached writes */ - private publicStorage: PublicStorage; + public publicStorage: PublicStorage; /** Nullifier set, including cached/recently-emitted nullifiers */ - private nullifiers: Nullifiers; + public nullifiers: Nullifiers; /** World State Access Trace */ - private trace: WorldStateAccessTrace; + public trace: WorldStateAccessTrace; /** Accrued Substate **/ - private newL1Messages: L2ToL1Message[] = []; - private newLogs: UnencryptedL2Log[] = []; + public newL1Messages: L2ToL1Message[] = []; + public newLogs: UnencryptedL2Log[] = []; constructor(hostStorage: HostStorage, parent?: AvmPersistableStateManager) { this.hostStorage = hostStorage; @@ -93,9 +100,9 @@ export class AvmPersistableStateManager { * @returns the latest value written to slot, or 0 if never written to before */ public async readStorage(storageAddress: Fr, slot: Fr): Promise { - const [_exists, value] = await this.publicStorage.read(storageAddress, slot); + const [exists, value] = await this.publicStorage.read(storageAddress, slot); // We want to keep track of all performed reads (even reverted ones) - this.trace.tracePublicStorageRead(storageAddress, slot, value); + this.trace.tracePublicStorageRead(storageAddress, slot, value, exists); return Promise.resolve(value); } @@ -119,8 +126,8 @@ export class AvmPersistableStateManager { * Write a note hash, trace the write. * @param noteHash - the unsiloed note hash to write */ - public writeNoteHash(noteHash: Fr) { - this.trace.traceNewNoteHash(/*storageAddress*/ Fr.ZERO, noteHash); + public writeNoteHash(storageAddress: Fr, noteHash: Fr) { + this.trace.traceNewNoteHash(storageAddress, noteHash); } /** diff --git a/yarn-project/simulator/src/avm/journal/trace.test.ts b/yarn-project/simulator/src/avm/journal/trace.test.ts index 93231b4b7270..e19c14dc7069 100644 --- a/yarn-project/simulator/src/avm/journal/trace.test.ts +++ b/yarn-project/simulator/src/avm/journal/trace.test.ts @@ -1,7 +1,7 @@ import { Fr } from '@aztec/foundation/fields'; import { WorldStateAccessTrace } from './trace.js'; -import { type TracedL1toL2MessageCheck, type TracedNullifierCheck } from './trace_types.js'; +import { type TracedL1toL2MessageCheck, type TracedNullifier, type TracedNullifierCheck } from './trace_types.js'; describe('world state access trace', () => { let trace: WorldStateAccessTrace; @@ -21,12 +21,12 @@ describe('world state access trace', () => { expect(trace.noteHashChecks).toEqual([ { - callPointer: expect.any(Fr), + // callPointer: expect.any(Fr), storageAddress: contractAddress, noteHash: noteHash, exists: exists, counter: Fr.ZERO, // 0th access - endLifetime: expect.any(Fr), + // endLifetime: expect.any(Fr), leafIndex: leafIndex, }, ]); @@ -35,8 +35,12 @@ describe('world state access trace', () => { it('Should trace note hashes', () => { const contractAddress = new Fr(1); const utxo = new Fr(2); + trace.traceNewNoteHash(contractAddress, utxo); - expect(trace.newNoteHashes).toEqual([utxo]); + + expect(trace.newNoteHashes).toEqual([ + expect.objectContaining({ storageAddress: contractAddress, noteHash: utxo }), + ]); expect(trace.getAccessCounter()).toEqual(1); }); it('Should trace nullifier checks', () => { @@ -47,12 +51,12 @@ describe('world state access trace', () => { const leafIndex = new Fr(42); trace.traceNullifierCheck(contractAddress, utxo, exists, isPending, leafIndex); const expectedCheck: TracedNullifierCheck = { - callPointer: Fr.ZERO, + // callPointer: Fr.ZERO, storageAddress: contractAddress, nullifier: utxo, exists: exists, counter: Fr.ZERO, // 0th access - endLifetime: Fr.ZERO, + // endLifetime: Fr.ZERO, isPending: isPending, leafIndex: leafIndex, }; @@ -63,7 +67,14 @@ describe('world state access trace', () => { const contractAddress = new Fr(1); const utxo = new Fr(2); trace.traceNewNullifier(contractAddress, utxo); - expect(trace.newNullifiers).toEqual([utxo]); + const expectedNullifier: TracedNullifier = { + // callPointer: Fr.ZERO, + storageAddress: contractAddress, + nullifier: utxo, + counter: new Fr(0), + // endLifetime: Fr.ZERO, + }; + expect(trace.newNullifiers).toEqual([expectedNullifier]); expect(trace.getAccessCounter()).toEqual(1); }); it('Should trace L1ToL2 Message checks', () => { @@ -99,7 +110,7 @@ describe('world state access trace', () => { let counter = 0; trace.tracePublicStorageWrite(contractAddress, slot, value); counter++; - trace.tracePublicStorageRead(contractAddress, slot, value); + trace.tracePublicStorageRead(contractAddress, slot, value, /*exists=*/ true); counter++; trace.traceNoteHashCheck(contractAddress, noteHash, noteHashExists, noteHashLeafIndex); counter++; @@ -113,7 +124,7 @@ describe('world state access trace', () => { counter++; trace.tracePublicStorageWrite(contractAddress, slot, value); counter++; - trace.tracePublicStorageRead(contractAddress, slot, value); + trace.tracePublicStorageRead(contractAddress, slot, value, /*exists=*/ true); counter++; trace.traceNewNoteHash(contractAddress, noteHash); counter++; @@ -167,7 +178,7 @@ describe('world state access trace', () => { }; trace.tracePublicStorageWrite(contractAddress, slot, value); - trace.tracePublicStorageRead(contractAddress, slot, value); + trace.tracePublicStorageRead(contractAddress, slot, value, /*exists=*/ true); trace.traceNoteHashCheck(contractAddress, noteHash, noteHashExists, noteHashLeafIndex); trace.traceNewNoteHash(contractAddress, noteHash); trace.traceNullifierCheck(contractAddress, nullifier, nullifierExists, nullifierIsPending, nullifierLeafIndex); @@ -176,7 +187,7 @@ describe('world state access trace', () => { const childTrace = new WorldStateAccessTrace(trace); childTrace.tracePublicStorageWrite(contractAddress, slot, valueT1); - childTrace.tracePublicStorageRead(contractAddress, slot, valueT1); + childTrace.tracePublicStorageRead(contractAddress, slot, valueT1, /*exists=*/ true); childTrace.traceNoteHashCheck(contractAddress, noteHashT1, noteHashExistsT1, noteHashLeafIndexT1); childTrace.traceNewNoteHash(contractAddress, nullifierT1); childTrace.traceNullifierCheck( @@ -193,12 +204,34 @@ describe('world state access trace', () => { trace.acceptAndMerge(childTrace); expect(trace.getAccessCounter()).toEqual(childCounterBeforeMerge); - const slotReads = trace.publicStorageReads?.get(contractAddress.toBigInt())?.get(slot.toBigInt()); - const slotWrites = trace.publicStorageWrites?.get(contractAddress.toBigInt())?.get(slot.toBigInt()); - expect(slotReads).toEqual([value, valueT1]); - expect(slotWrites).toEqual([value, valueT1]); - expect(trace.newNoteHashes).toEqual([nullifier, nullifierT1]); - expect(trace.newNullifiers).toEqual([nullifier, nullifierT1]); + expect(trace.publicStorageReads).toEqual([ + expect.objectContaining({ storageAddress: contractAddress, slot: slot, value: value, exists: true }), + expect.objectContaining({ storageAddress: contractAddress, slot: slot, value: valueT1, exists: true }), + ]); + expect(trace.publicStorageWrites).toEqual([ + expect.objectContaining({ storageAddress: contractAddress, slot: slot, value: value }), + expect.objectContaining({ storageAddress: contractAddress, slot: slot, value: valueT1 }), + ]); + expect(trace.newNoteHashes).toEqual([ + expect.objectContaining({ + storageAddress: contractAddress, + noteHash: nullifier, + }), + expect.objectContaining({ + storageAddress: contractAddress, + noteHash: nullifierT1, + }), + ]); + expect(trace.newNullifiers).toEqual([ + expect.objectContaining({ + storageAddress: contractAddress, + nullifier: nullifier, + }), + expect.objectContaining({ + storageAddress: contractAddress, + nullifier: nullifierT1, + }), + ]); expect(trace.nullifierChecks).toEqual([ expect.objectContaining({ nullifier: nullifier, diff --git a/yarn-project/simulator/src/avm/journal/trace.ts b/yarn-project/simulator/src/avm/journal/trace.ts index 48e4df34dc75..aa88454f9b7f 100644 --- a/yarn-project/simulator/src/avm/journal/trace.ts +++ b/yarn-project/simulator/src/avm/journal/trace.ts @@ -1,23 +1,28 @@ import { Fr } from '@aztec/foundation/fields'; -import { type TracedL1toL2MessageCheck, type TracedNoteHashCheck, type TracedNullifierCheck } from './trace_types.js'; +import { + type TracedL1toL2MessageCheck, + type TracedNoteHash, + type TracedNoteHashCheck, + type TracedNullifier, + type TracedNullifierCheck, + type TracedPublicStorageRead, + type TracedPublicStorageWrite, +} from './trace_types.js'; export class WorldStateAccessTrace { public accessCounter: number; - //public contractCalls: Array = []; - //public publicStorageReads: Array = []; - public publicStorageReads: Map> = new Map(); - //public publicStorageWrites: Array = []; - public publicStorageWrites: Map> = new Map(); + public publicStorageReads: TracedPublicStorageRead[] = []; + public publicStorageWrites: TracedPublicStorageWrite[] = []; public noteHashChecks: TracedNoteHashCheck[] = []; - //public newNoteHashes: TracedNoteHash[] = []; - public newNoteHashes: Fr[] = []; + public newNoteHashes: TracedNoteHash[] = []; public nullifierChecks: TracedNullifierCheck[] = []; - //public newNullifiers: TracedNullifier[] = []; - public newNullifiers: Fr[] = []; + public newNullifiers: TracedNullifier[] = []; public l1ToL2MessageChecks: TracedL1toL2MessageCheck[] = []; + + //public contractCalls: TracedContractCall[] = []; //public archiveChecks: TracedArchiveLeafCheck[] = []; constructor(parentTrace?: WorldStateAccessTrace) { @@ -28,76 +33,73 @@ export class WorldStateAccessTrace { return this.accessCounter; } - public tracePublicStorageRead(storageAddress: Fr, slot: Fr, value: Fr /*, _exists: boolean*/) { + public tracePublicStorageRead(storageAddress: Fr, slot: Fr, value: Fr, exists: boolean) { // TODO(4805): check if some threshold is reached for max storage reads // (need access to parent length, or trace needs to be initialized with parent's contents) - //const traced: TracedPublicStorageRead = { - // callPointer: Fr.ZERO, - // storageAddress, - // slot, - // value, - // exists, - // counter: new Fr(this.accessCounter), - // endLifetime: Fr.ZERO, - //}; - //this.publicStorageReads.push(traced); - this.journalRead(storageAddress, slot, value); + const traced: TracedPublicStorageRead = { + // callPointer: Fr.ZERO, + storageAddress, + slot, + value, + exists, + counter: new Fr(this.accessCounter), + // endLifetime: Fr.ZERO, + }; + this.publicStorageReads.push(traced); this.incrementAccessCounter(); } public tracePublicStorageWrite(storageAddress: Fr, slot: Fr, value: Fr) { // TODO(4805): check if some threshold is reached for max storage writes // (need access to parent length, or trace needs to be initialized with parent's contents) - //const traced: TracedPublicStorageWrite = { - // callPointer: Fr.ZERO, - // storageAddress, - // slot, - // value, - // counter: new Fr(this.accessCounter), - // endLifetime: Fr.ZERO, - //}; - //this.publicStorageWrites.push(traced); - this.journalWrite(storageAddress, slot, value); + const traced: TracedPublicStorageWrite = { + // callPointer: Fr.ZERO, + storageAddress, + slot, + value, + counter: new Fr(this.accessCounter), + // endLifetime: Fr.ZERO, + }; + this.publicStorageWrites.push(traced); this.incrementAccessCounter(); } public traceNoteHashCheck(storageAddress: Fr, noteHash: Fr, exists: boolean, leafIndex: Fr) { const traced: TracedNoteHashCheck = { - callPointer: Fr.ZERO, // FIXME + // callPointer: Fr.ZERO, storageAddress, noteHash, exists, counter: new Fr(this.accessCounter), - endLifetime: Fr.ZERO, + // endLifetime: Fr.ZERO, leafIndex, }; this.noteHashChecks.push(traced); this.incrementAccessCounter(); } - public traceNewNoteHash(_storageAddress: Fr, noteHash: Fr) { + public traceNewNoteHash(storageAddress: Fr, noteHash: Fr) { // TODO(4805): check if some threshold is reached for max new note hash - //const traced: TracedNoteHash = { - // callPointer: Fr.ZERO, - // storageAddress, - // noteHash, - // counter: new Fr(this.accessCounter), - // endLifetime: Fr.ZERO, - //}; - //this.newNoteHashes.push(traced); - this.newNoteHashes.push(noteHash); + const traced: TracedNoteHash = { + // callPointer: Fr.ZERO, + storageAddress, + noteHash, + counter: new Fr(this.accessCounter), + // endLifetime: Fr.ZERO, + }; + this.newNoteHashes.push(traced); this.incrementAccessCounter(); } public traceNullifierCheck(storageAddress: Fr, nullifier: Fr, exists: boolean, isPending: boolean, leafIndex: Fr) { // TODO(4805): check if some threshold is reached for max new nullifier const traced: TracedNullifierCheck = { - callPointer: Fr.ZERO, // FIXME + // callPointer: Fr.ZERO, storageAddress, nullifier, exists, counter: new Fr(this.accessCounter), - endLifetime: Fr.ZERO, + // endLifetime: Fr.ZERO, isPending, leafIndex, }; @@ -105,17 +107,16 @@ export class WorldStateAccessTrace { this.incrementAccessCounter(); } - public traceNewNullifier(_storageAddress: Fr, nullifier: Fr) { + public traceNewNullifier(storageAddress: Fr, nullifier: Fr) { // TODO(4805): check if some threshold is reached for max new nullifier - //const traced: TracedNullifier = { - // callPointer: Fr.ZERO, - // storageAddress, - // nullifier, - // counter: new Fr(this.accessCounter), - // endLifetime: Fr.ZERO, - //}; - //this.newNullifiers.push(traced); - this.newNullifiers.push(nullifier); + const tracedNullifier: TracedNullifier = { + // callPointer: Fr.ZERO, + storageAddress, + nullifier, + counter: new Fr(this.accessCounter), + // endLifetime: Fr.ZERO, + }; + this.newNullifiers.push(tracedNullifier); this.incrementAccessCounter(); } @@ -146,8 +147,8 @@ export class WorldStateAccessTrace { */ public acceptAndMerge(incomingTrace: WorldStateAccessTrace) { // Merge storage read and write journals - mergeContractJournalMaps(this.publicStorageReads, incomingTrace.publicStorageReads); - mergeContractJournalMaps(this.publicStorageWrites, incomingTrace.publicStorageWrites); + this.publicStorageReads = this.publicStorageReads.concat(incomingTrace.publicStorageReads); + this.publicStorageWrites = this.publicStorageWrites.concat(incomingTrace.publicStorageWrites); // Merge new note hashes and nullifiers this.noteHashChecks = this.noteHashChecks.concat(incomingTrace.noteHashChecks); this.newNoteHashes = this.newNoteHashes.concat(incomingTrace.newNoteHashes); @@ -157,67 +158,4 @@ export class WorldStateAccessTrace { // it is assumed that the incoming trace was initialized with this as parent, so accept counter this.accessCounter = incomingTrace.accessCounter; } - - /** - * We want to keep track of all performed reads in the journal - * This information is hinted to the avm circuit - - * @param contractAddress - - * @param key - - * @param value - - */ - journalUpdate(map: Map>, contractAddress: Fr, key: Fr, value: Fr): void { - let contractMap = map.get(contractAddress.toBigInt()); - if (!contractMap) { - contractMap = new Map>(); - map.set(contractAddress.toBigInt(), contractMap); - } - - let accessArray = contractMap.get(key.toBigInt()); - if (!accessArray) { - accessArray = new Array(); - contractMap.set(key.toBigInt(), accessArray); - } - accessArray.push(value); - } - - // Create an instance of journalUpdate that appends to the read array - private journalRead = this.journalUpdate.bind(this, this.publicStorageReads); - // Create an instance of journalUpdate that appends to the writes array - private journalWrite = this.journalUpdate.bind(this, this.publicStorageWrites); -} - -/** - * Merges two contract journalling maps together - * For read maps, we just append the childMap arrays into the host map arrays, as the order is important - * - * @param hostMap - The map to be merged into - * @param childMap - The map to be merged from - */ -function mergeContractJournalMaps(hostMap: Map>, childMap: Map>) { - for (const [key, value] of childMap) { - const map1Value = hostMap.get(key); - if (!map1Value) { - hostMap.set(key, value); - } else { - mergeStorageJournalMaps(map1Value, value); - } - } -} - -/** - * Merge two storage journalling maps together (for a particular contract). - * - * @param hostMap - The map to be merge into - * @param childMap - The map to be merged from - */ -function mergeStorageJournalMaps(hostMap: Map, childMap: Map) { - for (const [key, value] of childMap) { - const readArr = hostMap.get(key); - if (!readArr) { - hostMap.set(key, value); - } else { - hostMap.set(key, readArr?.concat(...value)); - } - } } diff --git a/yarn-project/simulator/src/avm/journal/trace_types.ts b/yarn-project/simulator/src/avm/journal/trace_types.ts index f86b51e57e56..81de24729e73 100644 --- a/yarn-project/simulator/src/avm/journal/trace_types.ts +++ b/yarn-project/simulator/src/avm/journal/trace_types.ts @@ -6,64 +6,64 @@ import { type Fr } from '@aztec/foundation/fields'; // storageAddress: Fr; // endLifetime: Fr; //}; -// -//export type TracedPublicStorageRead = { -// callPointer: Fr; -// storageAddress: Fr; -// exists: boolean; -// slot: Fr; -// value: Fr; -// counter: Fr; -// endLifetime: Fr; -//}; -// -//export type TracedPublicStorageWrite = { -// callPointer: Fr; -// storageAddress: Fr; -// slot: Fr; -// value: Fr; -// counter: Fr; -// endLifetime: Fr; -//}; -// + +export type TracedPublicStorageRead = { + // callPointer: Fr; + storageAddress: Fr; + exists: boolean; + slot: Fr; + value: Fr; + counter: Fr; + // endLifetime: Fr; +}; + +export type TracedPublicStorageWrite = { + // callPointer: Fr; + storageAddress: Fr; + slot: Fr; + value: Fr; + counter: Fr; + // endLifetime: Fr; +}; + export type TracedNoteHashCheck = { - callPointer: Fr; + // callPointer: Fr; storageAddress: Fr; leafIndex: Fr; noteHash: Fr; exists: boolean; counter: Fr; - endLifetime: Fr; + // endLifetime: Fr; +}; + +export type TracedNoteHash = { + // callPointer: Fr; + storageAddress: Fr; + noteHash: Fr; + counter: Fr; + // endLifetime: Fr; }; -// -//export type TracedNoteHash = { -// callPointer: Fr; -// storageAddress: Fr; -// noteHash: Fr; -// counter: Fr; -// endLifetime: Fr; -//}; export type TracedNullifierCheck = { - callPointer: Fr; + // callPointer: Fr; storageAddress: Fr; nullifier: Fr; exists: boolean; counter: Fr; - endLifetime: Fr; + // endLifetime: Fr; // the fields below are relevant only to the public kernel // and are therefore omitted from VM inputs isPending: boolean; leafIndex: Fr; }; -//export type TracedNullifier = { -// callPointer: Fr; -// storageAddress: Fr; -// nullifier: Fr; -// counter: Fr; -// endLifetime: Fr; -//}; +export type TracedNullifier = { + // callPointer: Fr; + storageAddress: Fr; + nullifier: Fr; + counter: Fr; + // endLifetime: Fr; +}; export type TracedL1toL2MessageCheck = { //callPointer: Fr; diff --git a/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts b/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts index 9b064a20827c..1c1a8023f899 100644 --- a/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts @@ -24,6 +24,7 @@ import { NullifierExists, SendL2ToL1Message, } from './accrued_substate.js'; +import { StorageAddress } from './environment_getters.js'; import { StaticCallStorageAlterError } from './storage.js'; describe('Accrued Substate', () => { @@ -152,8 +153,12 @@ describe('Accrued Substate', () => { await new EmitNoteHash(/*indirect=*/ 0, /*offset=*/ 0).execute(context); const journalState = context.persistableState.flush(); - const expected = [value.toFr()]; - expect(journalState.newNoteHashes).toEqual(expected); + expect(journalState.newNoteHashes).toEqual([ + expect.objectContaining({ + storageAddress: context.environment.storageAddress, + noteHash: value.toFr(), + }), + ]); }); }); @@ -243,8 +248,12 @@ describe('Accrued Substate', () => { await new EmitNullifier(/*indirect=*/ 0, /*offset=*/ 0).execute(context); const journalState = context.persistableState.flush(); - const expected = [value.toFr()]; - expect(journalState.newNullifiers).toEqual(expected); + expect(journalState.newNullifiers).toEqual([ + expect.objectContaining({ + storageAddress: context.environment.storageAddress.toField(), + nullifier: value.toFr(), + }), + ]); }); it('Nullifier collision reverts (same nullifier emitted twice)', async () => { diff --git a/yarn-project/simulator/src/avm/opcodes/accrued_substate.ts b/yarn-project/simulator/src/avm/opcodes/accrued_substate.ts index b633dd5f55f1..c98d65ded62d 100644 --- a/yarn-project/simulator/src/avm/opcodes/accrued_substate.ts +++ b/yarn-project/simulator/src/avm/opcodes/accrued_substate.ts @@ -69,7 +69,7 @@ export class EmitNoteHash extends Instruction { } const noteHash = memory.get(this.noteHashOffset).toFr(); - context.persistableState.writeNoteHash(noteHash); + context.persistableState.writeNoteHash(context.environment.storageAddress, noteHash); memory.assert(memoryOperations); context.machineState.incrementPc(); diff --git a/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts b/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts index 4ba9e6b1fecb..d7429e31f7f0 100644 --- a/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts @@ -4,6 +4,7 @@ import { jest } from '@jest/globals'; import { mock } from 'jest-mock-extended'; import { type CommitmentsDB, type PublicContractsDB, type PublicStateDB } from '../../index.js'; +import { markBytecodeAsAvm } from '../../public/transitional_adaptors.js'; import { type AvmContext } from '../avm_context.js'; import { Field, Uint8 } from '../avm_memory_types.js'; import { adjustCalldataIndex, initContext } from '../fixtures/index.js'; @@ -57,7 +58,7 @@ describe('External Calls', () => { expect(inst.serialize()).toEqual(buf); }); - it('Should execute a call correctly', async () => { + it.only('Should execute a call correctly', async () => { const gasOffset = 0; const l1Gas = 1e6; const l2Gas = 2e6; @@ -72,16 +73,18 @@ describe('External Calls', () => { const successOffset = 7; const otherContextInstructionsL2GasCost = 780; // Includes the cost of the call itself - const otherContextInstructionsBytecode = encodeToBytecode([ - new CalldataCopy( - /*indirect=*/ 0, - /*csOffset=*/ adjustCalldataIndex(0), - /*copySize=*/ argsSize, - /*dstOffset=*/ 0, - ), - new SStore(/*indirect=*/ 0, /*srcOffset=*/ 0, /*size=*/ 1, /*slotOffset=*/ 0), - new Return(/*indirect=*/ 0, /*retOffset=*/ 0, /*size=*/ 2), - ]); + const otherContextInstructionsBytecode = markBytecodeAsAvm( + encodeToBytecode([ + new CalldataCopy( + /*indirect=*/ 0, + /*csOffset=*/ adjustCalldataIndex(0), + /*copySize=*/ argsSize, + /*dstOffset=*/ 0, + ), + new SStore(/*indirect=*/ 0, /*srcOffset=*/ 0, /*size=*/ 1, /*slotOffset=*/ 0), + new Return(/*indirect=*/ 0, /*retOffset=*/ 0, /*size=*/ 2), + ]), + ); const { l1GasLeft: initialL1Gas, l2GasLeft: initialL2Gas, daGasLeft: initialDaGas } = context.machineState; @@ -227,7 +230,7 @@ describe('External Calls', () => { new SStore(/*indirect=*/ 0, /*srcOffset=*/ 1, /*size=*/ 1, /*slotOffset=*/ 0), ]; - const otherContextInstructionsBytecode = encodeToBytecode(otherContextInstructions); + const otherContextInstructionsBytecode = markBytecodeAsAvm(encodeToBytecode(otherContextInstructions)); jest .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') diff --git a/yarn-project/simulator/src/avm/opcodes/external_calls.ts b/yarn-project/simulator/src/avm/opcodes/external_calls.ts index 3037225f545e..87aa3197d520 100644 --- a/yarn-project/simulator/src/avm/opcodes/external_calls.ts +++ b/yarn-project/simulator/src/avm/opcodes/external_calls.ts @@ -1,10 +1,16 @@ import { FunctionSelector } from '@aztec/circuits.js'; import { padArrayEnd } from '@aztec/foundation/collection'; +import { executePublicFunction } from '../../public/executor.js'; +import { + convertPublicExecutionResult, + createPublicExecutionContext, + updateAvmContextFromPublicExecutionResult, +} from '../../public/transitional_adaptors.js'; import type { AvmContext } from '../avm_context.js'; import { gasLeftToGas, sumGas } from '../avm_gas.js'; import { Field, Uint8 } from '../avm_memory_types.js'; -import { AvmSimulator } from '../avm_simulator.js'; +import { type AvmContractCallResults } from '../avm_message_call_result.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; import { Addressing } from './addressing_mode.js'; import { Instruction } from './instruction.js'; @@ -61,6 +67,7 @@ abstract class ExternalCall extends Instruction { const totalGas = sumGas(this.gasCost(memoryOperations), allocatedGas); context.machineState.consumeGas(totalGas); + // TRANSITIONAL: This should be removed once the AVM is fully operational and the public executor is gone. const nestedContext = context.createNestedContractCallContext( callAddress.toFr(), calldata, @@ -68,8 +75,19 @@ abstract class ExternalCall extends Instruction { this.type, FunctionSelector.fromField(functionSelector), ); + const pxContext = createPublicExecutionContext(nestedContext, calldata); + const pxResults = await executePublicFunction(pxContext, /*nested=*/ true); + const nestedCallResults: AvmContractCallResults = convertPublicExecutionResult(pxResults); + updateAvmContextFromPublicExecutionResult(nestedContext, pxResults); + const nestedPersistableState = nestedContext.persistableState; + // const nestedContext = context.createNestedContractCallContext( + // callAddress.toFr(), + // calldata, + // FunctionSelector.fromField(functionSelector), + // ); + // const nestedCallResults: AvmContractCallResults = await new AvmSimulator(nestedContext).execute(); + // const nestedPersistableState = nestedContext.persistableState; - const nestedCallResults = await new AvmSimulator(nestedContext).execute(); const success = !nestedCallResults.reverted; // We only take as much data as was specified in the return size and pad with zeroes if the return data is smaller @@ -90,9 +108,9 @@ abstract class ExternalCall extends Instruction { // TODO: Should we merge the changes from a nested call in the case of a STATIC call? if (success) { - context.persistableState.acceptNestedCallState(nestedContext.persistableState); + context.persistableState.acceptNestedCallState(nestedPersistableState); } else { - context.persistableState.rejectNestedCallState(nestedContext.persistableState); + context.persistableState.rejectNestedCallState(nestedPersistableState); } memory.assert(memoryOperations); diff --git a/yarn-project/simulator/src/public/executor.ts b/yarn-project/simulator/src/public/executor.ts index 649e7642f788..8f2528568cb6 100644 --- a/yarn-project/simulator/src/public/executor.ts +++ b/yarn-project/simulator/src/public/executor.ts @@ -12,11 +12,6 @@ import { AvmMachineState } from '../avm/avm_machine_state.js'; import { AvmSimulator } from '../avm/avm_simulator.js'; import { HostStorage } from '../avm/journal/host_storage.js'; import { AvmPersistableStateManager } from '../avm/journal/index.js'; -import { - isAvmBytecode, - temporaryConvertAvmResults, - temporaryCreateAvmExecutionEnvironment, -} from '../avm/temporary_executor_migration.js'; import { AcirSimulator } from '../client/simulator.js'; import { ExecutionError, createSimulationError } from '../common/errors.js'; import { SideEffectCounter } from '../common/index.js'; @@ -24,11 +19,7 @@ import { PackedArgsCache } from '../common/packed_args_cache.js'; import { type CommitmentsDB, type PublicContractsDB, type PublicStateDB } from './db.js'; import { type PublicExecution, type PublicExecutionResult, checkValidStaticCall } from './execution.js'; import { PublicExecutionContext } from './public_execution_context.js'; -import { - isAvmBytecode, - temporaryConvertAvmResults, - temporaryCreateAvmExecutionEnvironment, -} from './transitional_migration.js'; +import { convertAvmResults, createAvmExecutionEnvironment, isAvmBytecode } from './transitional_adaptors.js'; /** * Execute a public function and return the execution result. @@ -55,6 +46,11 @@ export async function executePublicFunction( } async function executePublicFunctionAvm(executionContext: PublicExecutionContext): Promise { + const address = executionContext.execution.contractAddress; + const selector = executionContext.execution.functionData.selector; + const log = createDebugLogger('aztec:simulator:public_execution'); + log(`[AVM] Executing public external function ${address.toString()}:${selector}.`); + // Temporary code to construct the AVM context // These data structures will permeate across the simulator when the public executor is phased out const hostStorage = new HostStorage( @@ -63,20 +59,26 @@ async function executePublicFunctionAvm(executionContext: PublicExecutionContext executionContext.commitmentsDb, ); const worldStateJournal = new AvmPersistableStateManager(hostStorage); - const executionEnv = temporaryCreateAvmExecutionEnvironment( + + const executionEnv = createAvmExecutionEnvironment( executionContext.execution, + executionContext.header, executionContext.globalVariables, ); + // TODO(@spalladino) Load initial gas from the public execution request - const machineState = new AvmMachineState(100_000, 100_000, 100_000); + const machineState = new AvmMachineState(1e7, 1e7, 1e7); const context = new AvmContext(worldStateJournal, executionEnv, machineState); const simulator = new AvmSimulator(context); const result = await simulator.execute(); const newWorldState = context.persistableState.flush(); + + log(`[AVM] ${address.toString()}:${selector} returned, reverted: ${result.reverted}.`); + // TODO(@spalladino) Read gas left from machineState and return it - return temporaryConvertAvmResults(executionContext.execution, newWorldState, result); + return convertAvmResults(executionContext.execution, newWorldState, result); } async function executePublicFunctionAcvm( @@ -84,26 +86,25 @@ async function executePublicFunctionAcvm( acir: Buffer, nested: boolean, ): Promise { - const log = createDebugLogger('aztec:simulator:public_execution'); const execution = context.execution; const { contractAddress, functionData } = execution; const selector = functionData.selector; - log(`Executing public external function ${contractAddress.toString()}:${selector}`); + const log = createDebugLogger('aztec:simulator:public_execution'); + log(`[ACVM] Executing public external function ${contractAddress.toString()}:${selector}.`); const initialWitness = context.getInitialWitness(); const acvmCallback = new Oracle(context); - const { partialWitness, reverted, revertReason } = await acvm( - await AcirSimulator.getSolver(), - acir, - initialWitness, - acvmCallback, - ) - .then(result => ({ - partialWitness: result.partialWitness, - reverted: false, - revertReason: undefined, - })) - .catch((err: Error) => { + + const { partialWitness, reverted, revertReason } = await (async () => { + try { + const result = await acvm(await AcirSimulator.getSolver(), acir, initialWitness, acvmCallback); + return { + partialWitness: result.partialWitness, + reverted: false, + revertReason: undefined, + }; + } catch (err_) { + const err = err_ as Error; const ee = new ExecutionError( err.message, { @@ -124,7 +125,9 @@ async function executePublicFunctionAcvm( revertReason: createSimulationError(ee), }; } - }); + } + })(); + if (reverted) { if (!revertReason) { throw new Error('Reverted but no revert reason'); @@ -229,30 +232,6 @@ export class PublicExecutor { execution: PublicExecution, globalVariables: GlobalVariables, sideEffectCounter: number = 0, - ): Promise { - const selector = execution.functionData.selector; - const bytecode = await this.contractsDb.getBytecode(execution.contractAddress, selector); - if (!bytecode) { - throw new Error(`Bytecode not found for ${execution.contractAddress}:${selector}`); - } - - if (isAvmBytecode(bytecode)) { - return await this.simulateAvm(execution, globalVariables, sideEffectCounter); - } else { - return await this.simulateAcvm(execution, globalVariables, sideEffectCounter); - } - } - - /** - * Executes a public execution request with the ACVM. - * @param execution - The execution to run. - * @param globalVariables - The global variables to use. - * @returns The result of the run plus all nested runs. - */ - private async simulateAcvm( - execution: PublicExecution, - globalVariables: GlobalVariables, - sideEffectCounter: number = 0, ): Promise { // Functions can request to pack arguments before calling other functions. // We use this cache to hold the packed arguments. @@ -284,35 +263,6 @@ export class PublicExecutor { return executionResult; } - /** - * Executes a public execution request in the AVM. - * @param execution - The execution to run. - * @param globalVariables - The global variables to use. - * @returns The result of the run plus all nested runs. - */ - private async simulateAvm( - execution: PublicExecution, - globalVariables: GlobalVariables, - _sideEffectCounter = 0, - ): Promise { - // Temporary code to construct the AVM context - // These data structures will permeate across the simulator when the public executor is phased out - const hostStorage = new HostStorage(this.stateDb, this.contractsDb, this.commitmentsDb); - const worldStateJournal = new AvmPersistableStateManager(hostStorage); - const executionEnv = temporaryCreateAvmExecutionEnvironment(execution, this.header, globalVariables); - // TODO(@spalladino) Load initial gas from the public execution request - const machineState = new AvmMachineState(1e10, 1e10, 1e10); - - const context = new AvmContext(worldStateJournal, executionEnv, machineState); - const simulator = new AvmSimulator(context); - - const result = await simulator.execute(); - const newWorldState = context.persistableState.flush(); - - // TODO(@spalladino) Read gas left from machineState and return it - return temporaryConvertAvmResults(execution, newWorldState, result); - } - /** * These functions are currently housed in the temporary executor as it relies on access to * oracles like the contractsDB and this is the least intrusive way to achieve this. diff --git a/yarn-project/simulator/src/public/public_execution_context.ts b/yarn-project/simulator/src/public/public_execution_context.ts index 88e4fbe927bb..82d2ac393ede 100644 --- a/yarn-project/simulator/src/public/public_execution_context.ts +++ b/yarn-project/simulator/src/public/public_execution_context.ts @@ -36,7 +36,7 @@ export class PublicExecutionContext extends TypedOracle { * Data for this execution. */ public readonly execution: PublicExecution, - private readonly header: Header, + public readonly header: Header, public readonly globalVariables: GlobalVariables, private readonly packedArgsCache: PackedArgsCache, private readonly sideEffectCounter: SideEffectCounter, diff --git a/yarn-project/simulator/src/public/transitional_adaptors.ts b/yarn-project/simulator/src/public/transitional_adaptors.ts new file mode 100644 index 000000000000..e67f2298e442 --- /dev/null +++ b/yarn-project/simulator/src/public/transitional_adaptors.ts @@ -0,0 +1,225 @@ +// All code in this file needs to die once the public executor is phased out in favor of the AVM. +import { UnencryptedFunctionL2Logs, UnencryptedL2Log } from '@aztec/circuit-types'; +import { + CallContext, + ContractStorageRead, + ContractStorageUpdateRequest, + FunctionData, + type GlobalVariables, + type Header, + L2ToL1Message, + ReadRequest, + SideEffect, + SideEffectLinkedToNoteHash, +} from '@aztec/circuits.js'; +import { Fr } from '@aztec/foundation/fields'; + +import { type AvmContext } from '../avm/avm_context.js'; +import { AvmExecutionEnvironment } from '../avm/avm_execution_environment.js'; +import { AvmContractCallResults } from '../avm/avm_message_call_result.js'; +import { type JournalData } from '../avm/journal/journal.js'; +import { Mov } from '../avm/opcodes/memory.js'; +import { createSimulationError } from '../common/errors.js'; +import { PackedArgsCache, SideEffectCounter } from '../index.js'; +import { type PublicExecution, type PublicExecutionResult } from './execution.js'; +import { PublicExecutionContext } from './public_execution_context.js'; + +/** + * Convert a PublicExecution(Environment) object to an AvmExecutionEnvironment + * + * @param current + * @param globalVariables + * @returns + */ +export function createAvmExecutionEnvironment( + current: PublicExecution, + header: Header, + globalVariables: GlobalVariables, +): AvmExecutionEnvironment { + return new AvmExecutionEnvironment( + current.contractAddress, + current.callContext.storageContractAddress, + current.callContext.msgSender, // TODO: origin is not available + current.callContext.msgSender, + current.callContext.portalContractAddress, + /*feePerL1Gas=*/ Fr.zero(), + /*feePerL2Gas=*/ Fr.zero(), + /*feePerDaGas=*/ Fr.zero(), + /*contractCallDepth=*/ Fr.zero(), + header, + globalVariables, + current.callContext.isStaticCall, + current.callContext.isDelegateCall, + current.args, + current.functionData.selector, + ); +} + +export function createPublicExecutionContext(avmContext: AvmContext, calldata: Fr[]): PublicExecutionContext { + const sideEffectCounter = avmContext.persistableState.trace.accessCounter; + const callContext = CallContext.from({ + msgSender: avmContext.environment.sender, + storageContractAddress: avmContext.environment.storageAddress, + portalContractAddress: avmContext.environment.portal, + functionSelector: avmContext.environment.temporaryFunctionSelector, + isDelegateCall: avmContext.environment.isDelegateCall, + isStaticCall: avmContext.environment.isStaticCall, + sideEffectCounter: sideEffectCounter, + }); + const functionData = new FunctionData(avmContext.environment.temporaryFunctionSelector, /*isPrivate=*/ false); + const execution: PublicExecution = { + contractAddress: avmContext.environment.address, + callContext, + args: calldata, + functionData, + }; + const packedArgs = PackedArgsCache.create([]); + + const context = new PublicExecutionContext( + execution, + avmContext.environment.header, + avmContext.environment.globals, + packedArgs, + new SideEffectCounter(sideEffectCounter), + avmContext.persistableState.hostStorage.publicStateDb, + avmContext.persistableState.hostStorage.contractsDb, + avmContext.persistableState.hostStorage.commitmentsDb, + ); + + return context; +} + +/** + * Convert the result of an AVM contract call to a PublicExecutionResult for the public kernel + * + * @param execution + * @param newWorldState + * @param result + * @returns + */ +export function convertAvmResults( + execution: PublicExecution, + newWorldState: JournalData, + result: AvmContractCallResults, +): PublicExecutionResult { + const contractStorageReads: ContractStorageRead[] = newWorldState.storageReads.map( + read => new ContractStorageRead(read.slot, read.value, read.counter.toNumber()), + ); + const contractStorageUpdateRequests: ContractStorageUpdateRequest[] = newWorldState.storageWrites.map( + write => new ContractStorageUpdateRequest(write.slot, write.value, write.counter.toNumber()), + ); + const newNoteHashes = newWorldState.newNoteHashes.map( + noteHash => new SideEffect(noteHash.noteHash, noteHash.counter), + ); + const nullifierReadRequests: ReadRequest[] = newWorldState.nullifierChecks + .filter(nullifierCheck => nullifierCheck.exists) + .map(nullifierCheck => new ReadRequest(nullifierCheck.nullifier, nullifierCheck.counter.toNumber())); + const nullifierNonExistentReadRequests: ReadRequest[] = newWorldState.nullifierChecks + .filter(nullifierCheck => !nullifierCheck.exists) + .map(nullifierCheck => new ReadRequest(nullifierCheck.nullifier, nullifierCheck.counter.toNumber())); + const newNullifiers: SideEffectLinkedToNoteHash[] = newWorldState.newNullifiers.map( + tracedNullifier => + new SideEffectLinkedToNoteHash( + /*value=*/ tracedNullifier.nullifier, + /*noteHash=*/ Fr.ZERO, // NEEDED? + tracedNullifier.counter, + ), + ); + const unencryptedLogs: UnencryptedFunctionL2Logs = new UnencryptedFunctionL2Logs( + newWorldState.newLogs.map(log => new UnencryptedL2Log(log.contractAddress, log.selector, log.data)), + ); + const newL2ToL1Messages = newWorldState.newL1Messages.map(m => new L2ToL1Message(m.recipient, m.content)); + + const returnValues = result.output; + + // TODO: Support nested executions. + const nestedExecutions: PublicExecutionResult[] = []; + // TODO keep track of side effect counters + const startSideEffectCounter = Fr.ZERO; + const endSideEffectCounter = Fr.ZERO; + + return { + execution, + nullifierReadRequests, + nullifierNonExistentReadRequests, + newNoteHashes, + newL2ToL1Messages, + startSideEffectCounter, + endSideEffectCounter, + newNullifiers, + contractStorageReads, + contractStorageUpdateRequests, + returnValues, + nestedExecutions, + unencryptedLogs, + reverted: result.reverted, + revertReason: result.revertReason ? createSimulationError(result.revertReason) : undefined, + }; +} + +export function convertPublicExecutionResult(res: PublicExecutionResult): AvmContractCallResults { + return new AvmContractCallResults(res.reverted, res.returnValues, res.revertReason); +} + +export function updateAvmContextFromPublicExecutionResult(ctx: AvmContext, result: PublicExecutionResult): void { + // We have to push these manually and not use the trace* functions + // so that we respect the side effect counters. + for (const readRequest of result.contractStorageReads) { + ctx.persistableState.trace.publicStorageReads.push({ + storageAddress: ctx.environment.storageAddress, + exists: true, // FIXME + slot: readRequest.storageSlot, + value: readRequest.currentValue, + counter: new Fr(readRequest.sideEffectCounter ?? Fr.ZERO), + }); + } + + for (const updateRequest of result.contractStorageUpdateRequests) { + ctx.persistableState.trace.publicStorageWrites.push({ + storageAddress: ctx.environment.storageAddress, + slot: updateRequest.storageSlot, + value: updateRequest.newValue, + counter: new Fr(updateRequest.sideEffectCounter ?? Fr.ZERO), + }); + } + + for (const nullifier of result.newNullifiers) { + ctx.persistableState.trace.newNullifiers.push({ + storageAddress: ctx.environment.storageAddress, + nullifier: nullifier.value, + counter: nullifier.counter, + }); + } + + for (const noteHash of result.newNoteHashes) { + ctx.persistableState.trace.newNoteHashes.push({ + storageAddress: ctx.environment.storageAddress, + noteHash: noteHash.value, + counter: noteHash.counter, + }); + } + + for (const message of result.newL2ToL1Messages) { + ctx.persistableState.newL1Messages.push(message); + } + + for (const log of result.unencryptedLogs.logs) { + ctx.persistableState.newLogs.push(new UnencryptedL2Log(log.contractAddress, log.selector, log.data)); + } +} + +const AVM_MAGIC_SUFFIX = Buffer.from([ + Mov.opcode, // opcode + 0x00, // indirect + ...Buffer.from('000018ca', 'hex'), // srcOffset + ...Buffer.from('000018ca', 'hex'), // dstOffset +]); + +export function markBytecodeAsAvm(bytecode: Buffer): Buffer { + return Buffer.concat([bytecode, AVM_MAGIC_SUFFIX]); +} + +export function isAvmBytecode(bytecode: Buffer): boolean { + const magicSize = AVM_MAGIC_SUFFIX.length; + return bytecode.subarray(-magicSize).equals(AVM_MAGIC_SUFFIX); +} diff --git a/yarn-project/simulator/src/public/transitional_migration.ts b/yarn-project/simulator/src/public/transitional_migration.ts deleted file mode 100644 index 66e463f41892..000000000000 --- a/yarn-project/simulator/src/public/transitional_migration.ts +++ /dev/null @@ -1,139 +0,0 @@ -// 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 { - ContractStorageRead, - ContractStorageUpdateRequest, - type GlobalVariables, - type Header, - L2ToL1Message, - type ReadRequest, - SideEffect, - SideEffectLinkedToNoteHash, -} from '@aztec/circuits.js'; -import { Fr } from '@aztec/foundation/fields'; - -import { AvmExecutionEnvironment } from '../avm/avm_execution_environment.js'; -import { type AvmContractCallResults } from '../avm/avm_message_call_result.js'; -import { type JournalData } from '../avm/journal/journal.js'; -import { Mov } from '../avm/opcodes/memory.js'; -import { createSimulationError } from '../common/errors.js'; -import { type PublicExecution, type PublicExecutionResult } from './execution.js'; - -/** Temporary Method - * - * Convert a PublicExecution(Environment) object to an AvmExecutionEnvironment - * - * @param current - * @param globalVariables - * @returns - */ -export function temporaryCreateAvmExecutionEnvironment( - current: PublicExecution, - header: Header, - globalVariables: GlobalVariables, -): AvmExecutionEnvironment { - // Function selector is included temporarily until noir codegens public contract bytecode in a single blob - return new AvmExecutionEnvironment( - current.contractAddress, - current.callContext.storageContractAddress, - current.callContext.msgSender, // TODO: origin is not available - current.callContext.msgSender, - current.callContext.portalContractAddress, - /*feePerL1Gas=*/ Fr.zero(), - /*feePerL2Gas=*/ Fr.zero(), - /*feePerDaGas=*/ Fr.zero(), - /*contractCallDepth=*/ Fr.zero(), - header, - globalVariables, - current.callContext.isStaticCall, - current.callContext.isDelegateCall, - current.args, - current.functionData.selector, - ); -} - -/** Temporary Method - * - * Convert the result of an AVM contract call to a PublicExecutionResult for the public kernel - * - * @param execution - * @param newWorldState - * @param result - * @returns - */ -export function temporaryConvertAvmResults( - execution: PublicExecution, - newWorldState: JournalData, - result: AvmContractCallResults, -): PublicExecutionResult { - const newNoteHashes = newWorldState.newNoteHashes.map(noteHash => new SideEffect(noteHash, Fr.zero())); - - const contractStorageReads: ContractStorageRead[] = []; - const reduceStorageReadRequests = (contractAddress: bigint, storageReads: Map) => { - return storageReads.forEach((innerArray, key) => { - innerArray.forEach(value => { - contractStorageReads.push(new ContractStorageRead(new Fr(key), new Fr(value), 0)); - }); - }); - }; - newWorldState.storageReads.forEach((storageMap: Map, address: bigint) => - reduceStorageReadRequests(address, storageMap), - ); - - const contractStorageUpdateRequests: ContractStorageUpdateRequest[] = []; - const reduceStorageUpdateRequests = (contractAddress: bigint, storageUpdateRequests: Map) => { - return storageUpdateRequests.forEach((innerArray, key) => { - innerArray.forEach(value => { - contractStorageUpdateRequests.push(new ContractStorageUpdateRequest(new Fr(key), new Fr(value), 0)); - }); - }); - }; - newWorldState.storageWrites.forEach((storageMap: Map, address: bigint) => - reduceStorageUpdateRequests(address, storageMap), - ); - - const returnValues = result.output; - - // TODO(follow up in pr tree): NOT SUPPORTED YET, make sure hashing and log resolution is done correctly - // Disabled. - const nestedExecutions: PublicExecutionResult[] = []; - const nullifierReadRequests: ReadRequest[] = []; - const nullifierNonExistentReadRequests: ReadRequest[] = []; - const newNullifiers: SideEffectLinkedToNoteHash[] = newWorldState.newNullifiers.map( - (nullifier, i) => new SideEffectLinkedToNoteHash(nullifier.toField(), Fr.zero(), new Fr(i + 1)), - ); - const unencryptedLogs = UnencryptedFunctionL2Logs.empty(); - const newL2ToL1Messages = newWorldState.newL1Messages.map(() => L2ToL1Message.empty()); - // TODO keep track of side effect counters - const startSideEffectCounter = Fr.ZERO; - const endSideEffectCounter = Fr.ZERO; - - return { - execution, - nullifierReadRequests, - nullifierNonExistentReadRequests, - newNoteHashes, - newL2ToL1Messages, - startSideEffectCounter, - endSideEffectCounter, - newNullifiers, - contractStorageReads, - contractStorageUpdateRequests, - returnValues, - nestedExecutions, - unencryptedLogs, - reverted: result.reverted, - revertReason: result.revertReason ? createSimulationError(result.revertReason) : undefined, - }; -} - -export function isAvmBytecode(bytecode: Buffer): boolean { - const magicBuf = Buffer.from([ - Mov.opcode, // opcode - 0x00, // indirect - ...Buffer.from('000018ca', 'hex'), // srcOffset - ...Buffer.from('000018ca', 'hex'), // dstOffset - ]); - const magicSize = magicBuf.length; - return bytecode.subarray(-magicSize).equals(magicBuf); -}