diff --git a/avm-transpiler/src/instructions.rs b/avm-transpiler/src/instructions.rs index b49753c6357..4a51dad5570 100644 --- a/avm-transpiler/src/instructions.rs +++ b/avm-transpiler/src/instructions.rs @@ -8,7 +8,6 @@ pub const ALL_DIRECT: u8 = 0b00000000; pub const ZEROTH_OPERAND_INDIRECT: u8 = 0b00000001; pub const FIRST_OPERAND_INDIRECT: u8 = 0b00000010; pub const SECOND_OPERAND_INDIRECT: u8 = 0b00000100; -pub const ZEROTH_FIRST_OPERANDS_INDIRECT: u8 = ZEROTH_OPERAND_INDIRECT | FIRST_OPERAND_INDIRECT; /// A simple representation of an AVM instruction for the purpose /// of generating an AVM bytecode from Brillig. diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index 1b97449d81c..c3f7fae20ea 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 f7b732df61d..537dfb34bb3 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 317d500e4c9..474e6f05a72 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; @@ -174,6 +176,60 @@ contract AvmTest { dep::std::hash::pedersen_hash_with_separator(data, 20) } + /************************************************************************ + * ACVM interoperability + ************************************************************************/ + #[aztec(public)] + fn constant_field_acvm() -> pub Field { + 123456 + } + + #[aztec(public-vm)] + fn constant_field_avm() -> pub Field { + 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( + context.this_address(), + FunctionSelector::from_signature("constant_field_acvm()"), + [] + ); + data_to_return[0] + } + + #[aztec(public)] + fn call_avm_from_acvm() -> pub Field { + let data_to_return: [Field; RETURN_VALUES_LENGTH] = context.call_public_function( + context.this_address(), + FunctionSelector::from_signature("constant_field_avm()"), + [] + ); + 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/src/e2e_avm_simulator.test.ts b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts index 6d1729f0292..a40731b019a 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', () => { diff --git a/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts b/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts index da083c15b97..fa1cbebee4c 100644 --- a/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts +++ b/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts @@ -230,8 +230,8 @@ export abstract class AbstractPhaseManager { while (executionStack.length) { const current = executionStack.pop()!; const isExecutionRequest = !isPublicExecutionResult(current); - const sideEffectCounter = lastSideEffectCounter(tx) + 1; + const result = isExecutionRequest ? await this.publicExecutor.simulate(current, this.globalVariables, sideEffectCounter) : current; diff --git a/yarn-project/simulator/src/avm/avm_execution_environment.ts b/yarn-project/simulator/src/avm/avm_execution_environment.ts index 379444053e0..c21fb948a98 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 b5247881e7e..52e2372a02e 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -10,6 +10,7 @@ import { AvmTestContractArtifact } from '@aztec/noir-contracts.js'; import { jest } from '@jest/globals'; import { strict as assert } from 'assert'; +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 +24,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[]; diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index d7274dec92b..79ab833dc53 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -44,18 +44,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; diff --git a/yarn-project/simulator/src/avm/opcodes/external_calls.ts b/yarn-project/simulator/src/avm/opcodes/external_calls.ts index 3037225f545..76b05dc51cc 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 { + adjustAvmContextFromPublicExecutionResult, + convertPublicExecutionResult, + createPublicExecutionContext, +} 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); + adjustAvmContextFromPublicExecutionResult(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/avm/temporary_executor_migration.ts b/yarn-project/simulator/src/avm/temporary_executor_migration.ts deleted file mode 100644 index 41ee7981a85..00000000000 --- a/yarn-project/simulator/src/avm/temporary_executor_migration.ts +++ /dev/null @@ -1,139 +0,0 @@ -// All code in this file needs to die once the public executor is phased out. -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 { createSimulationError } from '../common/errors.js'; -import { type PublicExecution, type PublicExecutionResult } from '../public/execution.js'; -import { AvmExecutionEnvironment } from './avm_execution_environment.js'; -import { type AvmContractCallResults } from './avm_message_call_result.js'; -import { type JournalData } from './journal/journal.js'; -import { Mov } from './opcodes/memory.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); -} diff --git a/yarn-project/simulator/src/public/executor.ts b/yarn-project/simulator/src/public/executor.ts index 51d125df336..8f2528568cb 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,35 +19,92 @@ 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 { convertAvmResults, createAvmExecutionEnvironment, isAvmBytecode } from './transitional_adaptors.js'; /** * Execute a public function and return the execution result. */ export async function executePublicFunction( + context: PublicExecutionContext, + nested: boolean, +): Promise { + const bytecode = await context.contractsDb.getBytecode( + context.execution.contractAddress, + context.execution.functionData.selector, + ); + if (!bytecode) { + throw new Error( + `Bytecode not found for ${context.execution.contractAddress}:${context.execution.functionData.selector}`, + ); + } + + if (isAvmBytecode(bytecode)) { + return await executePublicFunctionAvm(context); + } else { + return await executePublicFunctionAcvm(context, bytecode, nested); + } +} + +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( + executionContext.stateDb, + executionContext.contractsDb, + executionContext.commitmentsDb, + ); + const worldStateJournal = new AvmPersistableStateManager(hostStorage); + + const executionEnv = createAvmExecutionEnvironment( + executionContext.execution, + executionContext.header, + executionContext.globalVariables, + ); + + // TODO(@spalladino) Load initial gas from the public execution request + 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 convertAvmResults(executionContext.execution, newWorldState, result); +} + +async function executePublicFunctionAcvm( context: PublicExecutionContext, acir: Buffer, nested: boolean, - log = createDebugLogger('aztec:simulator:public_execution'), ): Promise { 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, { @@ -73,7 +125,9 @@ export async function executePublicFunction( revertReason: createSimulationError(ee), }; } - }); + } + })(); + if (reverted) { if (!revertReason) { throw new Error('Reverted but no revert reason'); @@ -179,36 +233,6 @@ export class PublicExecutor { 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 { - const selector = execution.functionData.selector; - const acir = await this.contractsDb.getBytecode(execution.contractAddress, selector); - if (!acir) { - throw new Error(`Bytecode not found for ${execution.contractAddress}:${selector}`); - } - // Functions can request to pack arguments before calling other functions. // We use this cache to hold the packed arguments. const packedArgs = PackedArgsCache.create([]); @@ -224,7 +248,7 @@ export class PublicExecutor { this.commitmentsDb, ); - const executionResult = await executePublicFunction(context, acir, false /** nested */); + const executionResult = await executePublicFunction(context, /*nested=*/ false); if (executionResult.execution.callContext.isStaticCall) { checkValidStaticCall( @@ -239,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 b999d27ad13..82d2ac393ed 100644 --- a/yarn-project/simulator/src/public/public_execution_context.ts +++ b/yarn-project/simulator/src/public/public_execution_context.ts @@ -36,13 +36,13 @@ export class PublicExecutionContext extends TypedOracle { * Data for this execution. */ public readonly execution: PublicExecution, - private readonly header: Header, - private readonly globalVariables: GlobalVariables, + public readonly header: Header, + public readonly globalVariables: GlobalVariables, private readonly packedArgsCache: PackedArgsCache, private readonly sideEffectCounter: SideEffectCounter, - private readonly stateDb: PublicStateDB, - private readonly contractsDb: PublicContractsDB, - private readonly commitmentsDb: CommitmentsDB, + public readonly stateDb: PublicStateDB, + public readonly contractsDb: PublicContractsDB, + public readonly commitmentsDb: CommitmentsDB, private log = createDebugLogger('aztec:simulator:public_execution_context'), ) { super(); @@ -190,14 +190,7 @@ export class PublicExecutionContext extends TypedOracle { this.log(`Public function call: addr=${targetContractAddress} selector=${functionSelector} args=${args.join(',')}`); const portalAddress = (await this.contractsDb.getPortalContractAddress(targetContractAddress)) ?? EthAddress.ZERO; - - const acir = await this.contractsDb.getBytecode(targetContractAddress, functionSelector); - if (!acir) { - throw new Error(`Bytecode not found for ${targetContractAddress}:${functionSelector}`); - } - - const functionData = new FunctionData(functionSelector, false); - + const functionData = new FunctionData(functionSelector, /*isPrivate=*/ false); const callContext = CallContext.from({ msgSender: isDelegateCall ? this.execution.callContext.msgSender : this.execution.contractAddress, storageContractAddress: isDelegateCall ? this.execution.contractAddress : targetContractAddress, @@ -227,7 +220,7 @@ export class PublicExecutionContext extends TypedOracle { this.log, ); - const childExecutionResult = await executePublicFunction(context, acir, true /** nested */); + const childExecutionResult = await executePublicFunction(context, /*nested=*/ true); if (isStaticCall) { checkValidStaticCall( 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 00000000000..758b6dabc0e --- /dev/null +++ b/yarn-project/simulator/src/public/transitional_adaptors.ts @@ -0,0 +1,217 @@ +// 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, + 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 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: 1, // FIXME + }); + 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(0), + 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[] = []; + 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 newNoteHashes = newWorldState.newNoteHashes.map(noteHash => new SideEffect(noteHash, Fr.zero())); + const nullifierReadRequests: ReadRequest[] = newWorldState.nullifierChecks + .filter(nullifier => nullifier.exists) + .map(nullifier => new ReadRequest(nullifier.nullifier, nullifier.counter.toNumber())); + const nullifierNonExistentReadRequests: ReadRequest[] = newWorldState.nullifierChecks + .filter(nullifier => !nullifier.exists) + .map(nullifier => new ReadRequest(nullifier.nullifier, nullifier.counter.toNumber())); + const newNullifiers: SideEffectLinkedToNoteHash[] = newWorldState.newNullifiers.map( + (nullifier, i) => new SideEffectLinkedToNoteHash(nullifier, Fr.zero(), new Fr(i + 1)), + ); + 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 adjustAvmContextFromPublicExecutionResult(ctx: AvmContext, result: PublicExecutionResult): void { + for (const readRequest of result.contractStorageReads) { + ctx.persistableState.trace.tracePublicStorageRead( + ctx.environment.address, + readRequest.storageSlot, + readRequest.currentValue, + ); + } + + for (const updateRequest of result.contractStorageUpdateRequests) { + ctx.persistableState.trace.tracePublicStorageWrite( + ctx.environment.address, + updateRequest.storageSlot, + updateRequest.newValue, + ); + } + + for (const nullifier of result.newNullifiers) { + ctx.persistableState.trace.traceNewNullifier(nullifier.noteHash, nullifier.counter); + } + + for (const noteHash of result.newNoteHashes) { + ctx.persistableState.trace.traceNewNoteHash(ctx.environment.address, noteHash.value); + } + + 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)); + } +} + +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); +}