diff --git a/yarn-project/acir-simulator/src/avm/avm_context.test.ts b/yarn-project/acir-simulator/src/avm/avm_context.test.ts index 05e9cf10f3e..6f198fa0bb8 100644 --- a/yarn-project/acir-simulator/src/avm/avm_context.test.ts +++ b/yarn-project/acir-simulator/src/avm/avm_context.test.ts @@ -1,3 +1,18 @@ -describe('Avm', () => { - it('Executes a simple call', () => {}); +// import { AztecAddress, Fr } from '@aztec/circuits.js'; +// import { initContext } from './fixtures/index.js'; + +describe('Avm Context', () => { + it('New call should fork context correctly', () => { + // const context = initContext(); + // const newAddress = AztecAddress.random(); + // const newCalldata = [new Fr(1), new Fr(2)]; + // const newContext = context.createNestedContractCallContext(newAddress, newCalldata); + }); + + it('New static call should fork context correctly', () => { + // const context = initContext(); + // const newAddress = AztecAddress.random(); + // const newCalldata = [new Fr(1), new Fr(2)]; + // const newContext = context.createNestedContractStaticCallContext(newAddress, newCalldata); + }); }); diff --git a/yarn-project/acir-simulator/src/avm/avm_context.ts b/yarn-project/acir-simulator/src/avm/avm_context.ts index b6226c818e9..851f4bfc536 100644 --- a/yarn-project/acir-simulator/src/avm/avm_context.ts +++ b/yarn-project/acir-simulator/src/avm/avm_context.ts @@ -1,140 +1,63 @@ -import { AztecAddress, FunctionSelector } from '@aztec/circuits.js'; +import { AztecAddress } from '@aztec/circuits.js'; import { Fr } from '@aztec/foundation/fields'; import { AvmExecutionEnvironment } from './avm_execution_environment.js'; import { AvmMachineState } from './avm_machine_state.js'; -import { AvmMessageCallResult } from './avm_message_call_result.js'; -import { AvmInterpreterError, executeAvm } from './interpreter/index.js'; -import { AvmJournal } from './journal/journal.js'; -import { Instruction } from './opcodes/instruction.js'; -import { decodeFromBytecode } from './serialization/bytecode_serialization.js'; - -// FIXME: dependency cycle. +import { AvmWorldStateJournal } from './journal/journal.js'; /** - * Avm Executor manages the execution of the AVM - * - * It stores a state manager + * An execution context includes the information necessary to initiate AVM + * execution along with all state maintained by the AVM throughout execution. */ export class AvmContext { - /** Contains constant variables provided by the kernel */ - private executionEnvironment: AvmExecutionEnvironment; - /** Manages mutable state during execution - (caching, fetching) */ - private journal: AvmJournal; - - constructor(executionEnvironment: AvmExecutionEnvironment, journal: AvmJournal) { - this.executionEnvironment = executionEnvironment; - this.journal = journal; - } - - /** - * Call a contract with the given calldata - * - * - We get the contract from storage - * - We interpret the bytecode - * - We run the interpreter - * - */ - async call(): Promise { - // NOTE: the following is mocked as getPublicBytecode does not exist yet - const selector = new FunctionSelector(0); - const bytecode = await this.journal.hostStorage.contractsDb.getBytecode( - this.executionEnvironment.address, - selector, - ); - - // This assumes that we will not be able to send messages to accounts without code - // Pending classes and instances impl details - if (!bytecode) { - throw new NoBytecodeFoundInterpreterError(this.executionEnvironment.address); - } - - const instructions: Instruction[] = decodeFromBytecode(bytecode); - - const machineState = new AvmMachineState(this.executionEnvironment); - return executeAvm(machineState, this.journal, instructions); - } - - /** - * Create a new forked avm context - for internal calls - */ - public newWithForkedState(): AvmContext { - const forkedState = AvmJournal.branchParent(this.journal); - return new AvmContext(this.executionEnvironment, forkedState); - } - /** - * Create a new forked avm context - for external calls + * Create a new AVM context + * @param worldState - Manages mutable state during execution - (caching, fetching) + * @param environment - Contains constant variables provided by the kernel + * @param machineState - VM state that is modified on an instruction-by-instruction basis + * @returns new AvmContext instance */ - public static newWithForkedState(executionEnvironment: AvmExecutionEnvironment, journal: AvmJournal): AvmContext { - const forkedState = AvmJournal.branchParent(journal); - return new AvmContext(executionEnvironment, forkedState); - } + constructor( + public worldState: AvmWorldStateJournal, + public environment: AvmExecutionEnvironment, + public machineState: AvmMachineState, + ) {} /** - * Prepare a new AVM context that will be ready for an external call - * - It will fork the journal - * - It will set the correct execution Environment Variables for a call - * - Alter both address and storageAddress + * Prepare a new AVM context that will be ready for an external/nested call + * - Fork the world state journal + * - Derive a machine state from the current state + * - E.g., gas metering is preserved but pc is reset + * - Derive an execution environment from the caller/parent + * - Alter both address and storageAddress * - * @param address - The contract to call - * @param executionEnvironment - The current execution environment - * @param journal - The current journal + * @param address - The contract instance to initialize a context for + * @param calldata - Data/arguments for nested call * @returns new AvmContext instance */ - public static prepExternalCallContext( - address: AztecAddress, - calldata: Fr[], - executionEnvironment: AvmExecutionEnvironment, - journal: AvmJournal, - ): AvmContext { - const newExecutionEnvironment = executionEnvironment.newCall(address, calldata); - const forkedState = AvmJournal.branchParent(journal); - return new AvmContext(newExecutionEnvironment, forkedState); + public createNestedContractCallContext(address: AztecAddress, calldata: Fr[]): AvmContext { + const newExecutionEnvironment = this.environment.deriveEnvironmentForNestedCall(address, calldata); + const forkedWorldState = this.worldState.fork(); + const machineState = AvmMachineState.fromState(this.machineState); + return new AvmContext(forkedWorldState, newExecutionEnvironment, machineState); } /** - * Prepare a new AVM context that will be ready for an external static call - * - It will fork the journal - * - It will set the correct execution Environment Variables for a call - * - Alter both address and storageAddress + * Prepare a new AVM context that will be ready for an external/nested static call + * - Fork the world state journal + * - Derive a machine state from the current state + * - E.g., gas metering is preserved but pc is reset + * - Derive an execution environment from the caller/parent + * - Alter both address and storageAddress * - * @param address - The contract to call - * @param executionEnvironment - The current execution environment - * @param journal - The current journal + * @param address - The contract instance to initialize a context for + * @param calldata - Data/arguments for nested call * @returns new AvmContext instance */ - public static prepExternalStaticCallContext( - address: AztecAddress, - calldata: Fr[], - executionEnvironment: AvmExecutionEnvironment, - journal: AvmJournal, - ): AvmContext { - const newExecutionEnvironment = executionEnvironment.newStaticCall(address, calldata); - const forkedState = AvmJournal.branchParent(journal); - return new AvmContext(newExecutionEnvironment, forkedState); - } - - /** - * Merge the journal of this call with it's parent - * NOTE: this should never be called on a root context - only from within a nested call - */ - public mergeJournalSuccess() { - this.journal.mergeSuccessWithParent(); - } - - /** - * Merge the journal of this call with it's parent - * For when the child call fails ( we still must track state accesses ) - */ - public mergeJournalFailure() { - this.journal.mergeFailureWithParent(); - } -} - -class NoBytecodeFoundInterpreterError extends AvmInterpreterError { - constructor(contractAddress: AztecAddress) { - super(`No bytecode found at: ${contractAddress}`); - this.name = 'NoBytecodeFoundInterpreterError'; + public createNestedContractStaticCallContext(address: AztecAddress, calldata: Fr[]): AvmContext { + const newExecutionEnvironment = this.environment.deriveEnvironmentForNestedStaticCall(address, calldata); + const forkedWorldState = this.worldState.fork(); + const machineState = AvmMachineState.fromState(this.machineState); + return new AvmContext(forkedWorldState, newExecutionEnvironment, machineState); } } diff --git a/yarn-project/acir-simulator/src/avm/avm_execution_environment.test.ts b/yarn-project/acir-simulator/src/avm/avm_execution_environment.test.ts index bf792862c8d..27043eb3660 100644 --- a/yarn-project/acir-simulator/src/avm/avm_execution_environment.test.ts +++ b/yarn-project/acir-simulator/src/avm/avm_execution_environment.test.ts @@ -8,7 +8,7 @@ describe('Execution Environment', () => { it('New call should fork execution environment correctly', () => { const executionEnvironment = initExecutionEnvironment(); - const newExecutionEnvironment = executionEnvironment.newCall(newAddress, calldata); + const newExecutionEnvironment = executionEnvironment.deriveEnvironmentForNestedCall(newAddress, calldata); allTheSameExcept(executionEnvironment, newExecutionEnvironment, { address: newAddress, @@ -30,7 +30,7 @@ describe('Execution Environment', () => { it('New static call call should fork execution environment correctly', () => { const executionEnvironment = initExecutionEnvironment(); - const newExecutionEnvironment = executionEnvironment.newStaticCall(newAddress, calldata); + const newExecutionEnvironment = executionEnvironment.deriveEnvironmentForNestedStaticCall(newAddress, calldata); allTheSameExcept(executionEnvironment, newExecutionEnvironment, { address: newAddress, diff --git a/yarn-project/acir-simulator/src/avm/avm_execution_environment.ts b/yarn-project/acir-simulator/src/avm/avm_execution_environment.ts index 6a87f62d87b..aa3bbb4672c 100644 --- a/yarn-project/acir-simulator/src/avm/avm_execution_environment.ts +++ b/yarn-project/acir-simulator/src/avm/avm_execution_environment.ts @@ -37,7 +37,7 @@ export class AvmExecutionEnvironment { public readonly calldata: Fr[], ) {} - public newCall(address: AztecAddress, calldata: Fr[]): AvmExecutionEnvironment { + public deriveEnvironmentForNestedCall(address: AztecAddress, calldata: Fr[]): AvmExecutionEnvironment { return new AvmExecutionEnvironment( /*address=*/ address, /*storageAddress=*/ address, @@ -55,7 +55,7 @@ export class AvmExecutionEnvironment { ); } - public newStaticCall(address: AztecAddress, calldata: Fr[]): AvmExecutionEnvironment { + public deriveEnvironmentForNestedStaticCall(address: AztecAddress, calldata: Fr[]): AvmExecutionEnvironment { return new AvmExecutionEnvironment( /*address=*/ address, /*storageAddress=*/ address, diff --git a/yarn-project/acir-simulator/src/avm/avm_machine_state.ts b/yarn-project/acir-simulator/src/avm/avm_machine_state.ts index 75da070f6b5..6d4dd4e8cb1 100644 --- a/yarn-project/acir-simulator/src/avm/avm_machine_state.ts +++ b/yarn-project/acir-simulator/src/avm/avm_machine_state.ts @@ -1,69 +1,93 @@ -import { Fr } from '@aztec/foundation/fields'; +import { Fr } from '@aztec/circuits.js'; -import { AvmExecutionEnvironment } from './avm_execution_environment.js'; import { TaggedMemory } from './avm_memory_types.js'; +import { AvmContractCallResults } from './avm_message_call_result.js'; /** - * Store's data for an Avm execution frame + * A few fields of machine state are initialized from AVM session inputs or call instruction arguments + */ +export type InitialAvmMachineState = { + l1GasLeft: number; + l2GasLeft: number; + daGasLeft: number; +}; + +/** + * Avm state modified on an instruction-per-instruction basis. */ export class AvmMachineState { + public l1GasLeft: number; + /** gas remaining of the gas allocated for a contract call */ + public l2GasLeft: number; + public daGasLeft: number; + /** program counter */ + public pc: number = 0; + /** - * Execution environment contains hard coded information that is received from the kernel - * Items like, the block header and global variables fall within this category + * On INTERNALCALL, internal call stack is pushed to with the current pc + 1 + * On INTERNALRETURN, value is popped from the internal call stack and assigned to the pc. */ - public readonly executionEnvironment: AvmExecutionEnvironment; + public internalCallStack: number[] = []; - private returnData: Fr[]; - - public readonly memory: TaggedMemory; + /** Memory accessible to user code */ + public readonly memory: TaggedMemory = new TaggedMemory(); /** - * When an internal_call is invoked, the internal call stack is added to with the current pc + 1 - * When internal_return is invoked, the latest value is popped from the internal call stack and set to the pc. - */ - public internalCallStack: number[]; + * Signals that execution should end. + * AvmContext execution continues executing instructions until the machine state signals "halted" + * */ + public halted: boolean = false; + /** Signals that execution has reverted normally (this does not cover exceptional halts) */ + private reverted: boolean = false; + /** Output data must NOT be modified once it is set */ + private output: Fr[] = []; - public pc: number; + constructor(l1GasLeft: number, l2GasLeft: number, daGasLeft: number) { + this.l1GasLeft = l1GasLeft; + this.l2GasLeft = l2GasLeft; + this.daGasLeft = daGasLeft; + } - public callStack: number[]; + public static fromState(state: InitialAvmMachineState): AvmMachineState { + return new AvmMachineState(state.l1GasLeft, state.l2GasLeft, state.daGasLeft); + } /** - * If an instruction triggers a halt, then it ends execution of the VM + * Most instructions just increment PC before they complete */ - public halted: boolean; - /** - * Signifies if the execution has reverted ( due to a revert instruction ) - */ - public reverted: boolean; + public incrementPc() { + this.pc++; + } /** - * Create a new avm context - * @param executionEnvironment - Machine context that is passed to the avm + * Halt as successful + * Output data must NOT be modified once it is set + * @param output */ - constructor(executionEnvironment: AvmExecutionEnvironment) { - this.returnData = []; - this.memory = new TaggedMemory(); - this.internalCallStack = []; - - this.pc = 0; - this.callStack = []; - - this.halted = false; - this.reverted = false; - - this.executionEnvironment = executionEnvironment; + public return(output: Fr[]) { + this.halted = true; + this.output = output; } /** - * Return data must NOT be modified once it is set - * @param returnData - + * Halt as reverted + * Output data must NOT be modified once it is set + * @param output */ - public setReturnData(returnData: Fr[]) { - this.returnData = returnData; - Object.freeze(returnData); + public revert(output: Fr[]) { + this.halted = true; + this.reverted = true; + this.output = output; } - public getReturnData(): Fr[] { - return this.returnData; + /** + * Get a summary of execution results for a halted machine state + * @returns summary of execution results + */ + public getResults(): AvmContractCallResults { + if (!this.halted) { + throw new Error('Execution results are not ready! Execution is ongoing.'); + } + return new AvmContractCallResults(this.reverted, this.output); } } diff --git a/yarn-project/acir-simulator/src/avm/avm_memory_types.ts b/yarn-project/acir-simulator/src/avm/avm_memory_types.ts index 40c02b65463..9515ae59ce0 100644 --- a/yarn-project/acir-simulator/src/avm/avm_memory_types.ts +++ b/yarn-project/acir-simulator/src/avm/avm_memory_types.ts @@ -2,6 +2,8 @@ import { Fr } from '@aztec/foundation/fields'; import { strict as assert } from 'assert'; +import { TagCheckError } from './errors.js'; + export abstract class MemoryValue { public abstract add(rhs: MemoryValue): MemoryValue; public abstract sub(rhs: MemoryValue): MemoryValue; @@ -268,6 +270,33 @@ export class TaggedMemory { return TaggedMemory.getTag(this._mem[offset]); } + /** + * Check that the memory at the given offset matches the specified tag. + */ + public checkTag(tag: TypeTag, offset: number) { + if (this.getTag(offset) !== tag) { + throw new TagCheckError(offset, TypeTag[this.getTag(offset)], TypeTag[tag]); + } + } + + /** + * Check tags for memory at all of the specified offsets. + */ + public checkTags(tag: TypeTag, ...offsets: number[]) { + for (const offset of offsets) { + this.checkTag(tag, offset); + } + } + + /** + * Check tags for all memory in the specified range. + */ + public checkTagsRange(tag: TypeTag, startOffset: number, size: number) { + for (let offset = startOffset; offset < startOffset + size; offset++) { + this.checkTag(tag, offset); + } + } + // TODO: this might be slow, but I don't want to have the types know of their tags. // It might be possible to have a map. public static getTag(v: MemoryValue | undefined): TypeTag { diff --git a/yarn-project/acir-simulator/src/avm/avm_message_call_result.ts b/yarn-project/acir-simulator/src/avm/avm_message_call_result.ts index 713a306b3e9..3b7b4e87a0f 100644 --- a/yarn-project/acir-simulator/src/avm/avm_message_call_result.ts +++ b/yarn-project/acir-simulator/src/avm/avm_message_call_result.ts @@ -1,37 +1,29 @@ import { Fr } from '@aztec/foundation/fields'; /** - * AVM message call result. + * Results of an contract call's execution in the AVM. */ -export class AvmMessageCallResult { +export class AvmContractCallResults { public readonly reverted: boolean; + public readonly output: Fr[]; + /** For exceptional halts */ public readonly revertReason: Error | undefined; - /** .- */ - public readonly output: Fr[]; - private constructor(reverted: boolean, output: Fr[], revertReason?: Error) { + constructor(reverted: boolean, output: Fr[], revertReason?: Error) { this.reverted = reverted; this.output = output; this.revertReason = revertReason; } /** - * Terminate a call as a success - * @param output - Return data - * @returns instance of AvmMessageCallResult - */ - public static success(output: Fr[]): AvmMessageCallResult { - return new AvmMessageCallResult(false, output); - } - - /** - * Terminate a call as a revert - * @param output - Return data ( revert message ) - * @param reason - Optional reason for revert - * @returns instance of AvmMessageCallResult + * Generate a string representation of call results. */ - public static revert(output: Fr[], reason?: Error): AvmMessageCallResult { - return new AvmMessageCallResult(true, output, reason); + toString(): string { + let resultsStr = `reverted: ${this.reverted}, output: ${this.output}`; + if (this.revertReason) { + resultsStr += `, revertReason: ${this.revertReason}`; + } + return resultsStr; } } diff --git a/yarn-project/acir-simulator/src/avm/index.test.ts b/yarn-project/acir-simulator/src/avm/avm_simulator.test.ts similarity index 52% rename from yarn-project/acir-simulator/src/avm/index.test.ts rename to yarn-project/acir-simulator/src/avm/avm_simulator.test.ts index a9e05a97ea2..62a2c1ab56d 100644 --- a/yarn-project/acir-simulator/src/avm/index.test.ts +++ b/yarn-project/acir-simulator/src/avm/avm_simulator.test.ts @@ -1,20 +1,17 @@ import { Fr } from '@aztec/foundation/fields'; import { AvmTestContractArtifact } from '@aztec/noir-contracts'; -import { mock } from 'jest-mock-extended'; +import { jest } from '@jest/globals'; -import { AvmMachineState } from './avm_machine_state.js'; import { TypeTag } from './avm_memory_types.js'; -import { initExecutionEnvironment } from './fixtures/index.js'; -import { executeAvm } from './interpreter/interpreter.js'; -import { AvmJournal } from './journal/journal.js'; +import { AvmSimulator } from './avm_simulator.js'; +import { initContext, initExecutionEnvironment } from './fixtures/index.js'; import { Add, CalldataCopy, Return } from './opcodes/index.js'; -import { decodeFromBytecode, encodeToBytecode } from './serialization/bytecode_serialization.js'; +import { encodeToBytecode } from './serialization/bytecode_serialization.js'; describe('avm', () => { it('Should execute bytecode that performs basic addition', async () => { const calldata: Fr[] = [new Fr(1), new Fr(2)]; - const journal = mock(); // Construct bytecode const bytecode = encodeToBytecode([ @@ -23,16 +20,14 @@ describe('avm', () => { new Return(/*indirect=*/ 0, /*returnOffset=*/ 2, /*copySize=*/ 1), ]); - // Decode bytecode into instructions - const instructions = decodeFromBytecode(bytecode); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + jest.spyOn(context.worldState.hostStorage.contractsDb, 'getBytecode').mockReturnValue(Promise.resolve(bytecode)); - // Execute instructions - const context = new AvmMachineState(initExecutionEnvironment({ calldata })); - const avmReturnData = await executeAvm(context, journal, instructions); + const results = await new AvmSimulator(context).execute(); - expect(avmReturnData.reverted).toBe(false); + expect(results.reverted).toBe(false); - const returnData = avmReturnData.output; + const returnData = results.output; expect(returnData.length).toBe(1); expect(returnData).toEqual([new Fr(3)]); }); @@ -41,22 +36,21 @@ describe('avm', () => { // TODO(https://github.com/AztecProtocol/aztec-packages/issues/4361): sync wire format w/transpiler. it('Should execute contract function that performs addition', async () => { const calldata: Fr[] = [new Fr(1), new Fr(2)]; - const journal = mock(); // Get contract function artifact const addArtifact = AvmTestContractArtifact.functions.find(f => f.name === 'avm_addArgsReturn')!; // Decode bytecode into instructions - const instructionsBytecode = Buffer.from(addArtifact.bytecode, 'base64'); - const instructions = decodeFromBytecode(instructionsBytecode); + const bytecode = Buffer.from(addArtifact.bytecode, 'base64'); - // Execute instructions - const context = new AvmMachineState(initExecutionEnvironment({ calldata })); - const avmReturnData = await executeAvm(context, journal, instructions); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + jest.spyOn(context.worldState.hostStorage.contractsDb, 'getBytecode').mockReturnValue(Promise.resolve(bytecode)); - expect(avmReturnData.reverted).toBe(false); + const results = await new AvmSimulator(context).execute(); - const returnData = avmReturnData.output; + expect(results.reverted).toBe(false); + + const returnData = results.output; expect(returnData.length).toBe(1); expect(returnData).toEqual([new Fr(3)]); }); diff --git a/yarn-project/acir-simulator/src/avm/avm_simulator.ts b/yarn-project/acir-simulator/src/avm/avm_simulator.ts new file mode 100644 index 00000000000..a3c35012d00 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/avm_simulator.ts @@ -0,0 +1,89 @@ +import { FunctionSelector } from '@aztec/circuits.js'; +import { DebugLogger, createDebugLogger } from '@aztec/foundation/log'; + +import { strict as assert } from 'assert'; + +import type { AvmContext } from './avm_context.js'; +import { AvmContractCallResults } from './avm_message_call_result.js'; +import { AvmExecutionError, InvalidProgramCounterError, NoBytecodeForContractError } from './errors.js'; +import type { Instruction } from './opcodes/index.js'; +import { decodeFromBytecode } from './serialization/bytecode_serialization.js'; + +export class AvmSimulator { + private log: DebugLogger = createDebugLogger('aztec:avm_simulator'); + + constructor(private context: AvmContext) {} + + /** + * Fetch the bytecode and execute it in the current context. + */ + public async execute(): Promise { + const instructions = await this.fetchAndDecodeBytecode(); + return this.executeInstructions(instructions); + } + + /** + * Executes the provided instructions in the current context. + * This method is useful for testing and debugging. + */ + public async executeInstructions(instructions: Instruction[]): Promise { + assert(instructions.length > 0); + + try { + // Execute instruction pointed to by the current program counter + // continuing until the machine state signifies a halt + while (!this.context.machineState.halted) { + const instruction = instructions[this.context.machineState.pc]; + assert(!!instruction); // This should never happen + + this.log(`Executing PC=${this.context.machineState.pc}: ${instruction.toString()}`); + // Execute the instruction. + // Normal returns and reverts will return normally here. + // "Exceptional halts" will throw. + await instruction.execute(this.context); + + if (this.context.machineState.pc >= instructions.length) { + this.log('Passed end of program!'); + throw new InvalidProgramCounterError(this.context.machineState.pc, /*max=*/ instructions.length); + } + } + + // Return results for processing by calling context + const results = this.context.machineState.getResults(); + this.log(`Context execution results: ${results.toString()}`); + return results; + } catch (e) { + this.log('Exceptional halt'); + if (!(e instanceof AvmExecutionError)) { + this.log(`Unknown error thrown by avm: ${e}`); + throw e; + } + + // Return results for processing by calling context + // Note: "exceptional halts" cannot return data + const results = new AvmContractCallResults(/*reverted=*/ true, /*output=*/ [], /*revertReason=*/ e); + this.log(`Context execution results: ${results.toString()}`); + return results; + } + } + + /** + * Fetch contract bytecode from world state and decode into executable instructions. + */ + private async fetchAndDecodeBytecode(): Promise { + // NOTE: the following is mocked as getPublicBytecode does not exist yet + const selector = new FunctionSelector(0); + const bytecode = await this.context.worldState.hostStorage.contractsDb.getBytecode( + this.context.environment.address, + selector, + ); + + // This assumes that we will not be able to send messages to accounts without code + // Pending classes and instances impl details + if (!bytecode) { + throw new NoBytecodeForContractError(this.context.environment.address); + } + + return decodeFromBytecode(bytecode); + } +} diff --git a/yarn-project/acir-simulator/src/avm/errors.ts b/yarn-project/acir-simulator/src/avm/errors.ts new file mode 100644 index 00000000000..735abb33df5 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/errors.ts @@ -0,0 +1,49 @@ +import { AztecAddress } from '@aztec/circuits.js'; + +/** + * Avm-specific errors should derive from this + */ +export abstract class AvmExecutionError extends Error { + constructor(message: string, ...rest: any[]) { + super(message, ...rest); + this.name = 'AvmInterpreterError'; + } +} + +export class NoBytecodeForContractError extends AvmExecutionError { + constructor(contractAddress: AztecAddress) { + super(`No bytecode found at: ${contractAddress}`); + this.name = 'NoBytecodeFoundInterpreterError'; + } +} + +/** + * Error is thrown when the program counter goes to an invalid location. + * There is no instruction at the provided pc + */ +export class InvalidProgramCounterError extends AvmExecutionError { + constructor(pc: number, max: number) { + super(`Invalid program counter ${pc}, max is ${max}`); + this.name = 'InvalidProgramCounterError'; + } +} + +/** + * Error thrown during an instruction's execution (during its execute()). + */ +export class InstructionExecutionError extends AvmExecutionError { + constructor(message: string) { + super(message); + this.name = 'InstructionExecutionError'; + } +} + +/** + * Error thrown on failed AVM memory tag check. + */ +export class TagCheckError extends AvmExecutionError { + constructor(offset: number, gotTag: string, expectedTag: string) { + super(`Memory offset ${offset} has tag ${gotTag}, expected ${expectedTag}`); + this.name = 'TagCheckError'; + } +} diff --git a/yarn-project/acir-simulator/src/avm/fixtures/index.ts b/yarn-project/acir-simulator/src/avm/fixtures/index.ts index 4c0d889063c..bdbf5bed4bc 100644 --- a/yarn-project/acir-simulator/src/avm/fixtures/index.ts +++ b/yarn-project/acir-simulator/src/avm/fixtures/index.ts @@ -1,13 +1,40 @@ -// Place large AVM text fixtures in here import { GlobalVariables } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; +import { mock } from 'jest-mock-extended'; + +import { CommitmentsDB, PublicContractsDB, PublicStateDB } from '../../index.js'; +import { AvmContext } from '../avm_context.js'; import { AvmExecutionEnvironment } from '../avm_execution_environment.js'; +import { AvmMachineState } from '../avm_machine_state.js'; +import { HostStorage } from '../journal/host_storage.js'; +import { AvmWorldStateJournal } from '../journal/journal.js'; + +/** + * Create a new AVM context with default values. + */ +export function initContext(overrides?: { + worldState?: AvmWorldStateJournal; + env?: AvmExecutionEnvironment; + machineState?: AvmMachineState; +}): AvmContext { + return new AvmContext( + overrides?.worldState || initMockWorldStateJournal(), + overrides?.env || initExecutionEnvironment(), + overrides?.machineState || initMachineState(), + ); +} + +/** Creates an empty world state with mocked storage. */ +export function initMockWorldStateJournal(): AvmWorldStateJournal { + const hostStorage = new HostStorage(mock(), mock(), mock()); + return new AvmWorldStateJournal(hostStorage); +} /** - * Create an empty instance of the Execution Environment where all values are zero, unless overriden in the overrides object + * Create an empty instance of the Execution Environment where all values are zero, unless overridden in the overrides object */ export function initExecutionEnvironment(overrides?: Partial): AvmExecutionEnvironment { return new AvmExecutionEnvironment( @@ -28,7 +55,7 @@ export function initExecutionEnvironment(overrides?: Partial): GlobalVariables { return new GlobalVariables( @@ -38,3 +65,14 @@ export function initGlobalVariables(overrides?: Partial): Globa overrides?.timestamp ?? Fr.zero(), ); } + +/** + * Create an empty instance of the Machine State where all values are zero, unless overridden in the overrides object + */ +export function initMachineState(overrides?: Partial): AvmMachineState { + return AvmMachineState.fromState({ + l1GasLeft: overrides?.l1GasLeft ?? 0, + l2GasLeft: overrides?.l2GasLeft ?? 0, + daGasLeft: overrides?.daGasLeft ?? 0, + }); +} diff --git a/yarn-project/acir-simulator/src/avm/interpreter/index.ts b/yarn-project/acir-simulator/src/avm/interpreter/index.ts deleted file mode 100644 index d5189852b6e..00000000000 --- a/yarn-project/acir-simulator/src/avm/interpreter/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './interpreter.js'; diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts deleted file mode 100644 index c0111f067ac..00000000000 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Fr } from '@aztec/foundation/fields'; - -import { MockProxy, mock } from 'jest-mock-extended'; - -import { AvmMachineState } from '../avm_machine_state.js'; -import { TypeTag } from '../avm_memory_types.js'; -import { initExecutionEnvironment } from '../fixtures/index.js'; -import { AvmJournal } from '../journal/journal.js'; -import { Add } from '../opcodes/arithmetic.js'; -import { Jump, Return } from '../opcodes/control_flow.js'; -import { Instruction } from '../opcodes/instruction.js'; -import { CalldataCopy } from '../opcodes/memory.js'; -import { InvalidProgramCounterError, executeAvm } from './interpreter.js'; - -describe('interpreter', () => { - let journal: MockProxy; - - beforeEach(() => { - journal = mock(); - }); - - it('Should execute a series of instructions', async () => { - const calldata: Fr[] = [new Fr(1), new Fr(2)]; - - const instructions: Instruction[] = [ - new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 0, /*copySize=*/ 2, /*dstOffset=*/ 0), - new Add(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2), - new Return(/*indirect=*/ 0, /*returnOffset=*/ 2, /*copySize=*/ 1), - ]; - - const machineState = new AvmMachineState(initExecutionEnvironment({ calldata })); - const avmReturnData = await executeAvm(machineState, journal, instructions); - - expect(avmReturnData.reverted).toBe(false); - expect(avmReturnData.revertReason).toBeUndefined(); - expect(avmReturnData.output).toEqual([new Fr(3)]); - }); - - it('Should revert with an invalid jump', async () => { - const calldata: Fr[] = []; - - const invalidJumpDestination = 22; - - const instructions: Instruction[] = [new Jump(invalidJumpDestination)]; - - const machineState = new AvmMachineState(initExecutionEnvironment({ calldata })); - const avmReturnData = await executeAvm(machineState, journal, instructions); - - expect(avmReturnData.reverted).toBe(true); - expect(avmReturnData.revertReason).toBeInstanceOf(InvalidProgramCounterError); - expect(avmReturnData.output).toHaveLength(0); - }); -}); diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts deleted file mode 100644 index fb23425f8b8..00000000000 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { strict as assert } from 'assert'; - -import { AvmMachineState } from '../avm_machine_state.js'; -import { AvmMessageCallResult } from '../avm_message_call_result.js'; -import { AvmJournal } from '../journal/index.js'; -import { Instruction, InstructionExecutionError } from '../opcodes/instruction.js'; - -/** - * Run the avm - * @returns bool - successful execution will return true - * - reverted execution will return false - * - any other panic will throw - */ -export async function executeAvm( - machineState: AvmMachineState, - journal: AvmJournal, - instructions: Instruction[] = [], -): Promise { - assert(instructions.length > 0); - - try { - while (!machineState.halted) { - const instruction = instructions[machineState.pc]; - assert(!!instruction); // This should never happen - - await instruction.execute(machineState, journal); - - if (machineState.pc >= instructions.length) { - throw new InvalidProgramCounterError(machineState.pc, /*max=*/ instructions.length); - } - } - - const returnData = machineState.getReturnData(); - if (machineState.reverted) { - return AvmMessageCallResult.revert(returnData); - } - - return AvmMessageCallResult.success(returnData); - } catch (e) { - if (!(e instanceof AvmInterpreterError || e instanceof InstructionExecutionError)) { - throw e; - } - - const revertData = machineState.getReturnData(); - return AvmMessageCallResult.revert(revertData, /*revertReason=*/ e); - } -} - -/** - * Avm-specific errors should derive from this - */ -export abstract class AvmInterpreterError extends Error { - constructor(message: string, ...rest: any[]) { - super(message, ...rest); - this.name = 'AvmInterpreterError'; - } -} - -/** - * Error is thrown when the program counter goes to an invalid location. - * There is no instruction at the provided pc - */ -export class InvalidProgramCounterError extends AvmInterpreterError { - constructor(pc: number, max: number) { - super(`Invalid program counter ${pc}, max is ${max}`); - this.name = 'InvalidProgramCounterError'; - } -} diff --git a/yarn-project/acir-simulator/src/avm/journal/errors.ts b/yarn-project/acir-simulator/src/avm/journal/errors.ts deleted file mode 100644 index 17696b1de68..00000000000 --- a/yarn-project/acir-simulator/src/avm/journal/errors.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Error thrown when a base journal is attempted to be merged. - */ -export class RootJournalCannotBeMerged extends Error { - constructor() { - super('Root journal cannot be merged'); - } -} diff --git a/yarn-project/acir-simulator/src/avm/journal/journal.test.ts b/yarn-project/acir-simulator/src/avm/journal/journal.test.ts index 2a3aafd6da6..0a2b8c4b1b3 100644 --- a/yarn-project/acir-simulator/src/avm/journal/journal.test.ts +++ b/yarn-project/acir-simulator/src/avm/journal/journal.test.ts @@ -3,13 +3,12 @@ import { Fr } from '@aztec/foundation/fields'; import { MockProxy, mock } from 'jest-mock-extended'; import { CommitmentsDB, PublicContractsDB, PublicStateDB } from '../../index.js'; -import { RootJournalCannotBeMerged } from './errors.js'; import { HostStorage } from './host_storage.js'; -import { AvmJournal, JournalData } from './journal.js'; +import { AvmWorldStateJournal, JournalData } from './journal.js'; describe('journal', () => { let publicDb: MockProxy; - let journal: AvmJournal; + let journal: AvmWorldStateJournal; beforeEach(() => { publicDb = mock(); @@ -17,7 +16,7 @@ describe('journal', () => { const contractsDb = mock(); const hostStorage = new HostStorage(publicDb, contractsDb, commitmentsDb); - journal = new AvmJournal(hostStorage); + journal = new AvmWorldStateJournal(hostStorage); }); describe('Public Storage', () => { @@ -43,7 +42,7 @@ describe('journal', () => { publicDb.storageRead.mockResolvedValue(Promise.resolve(storedValue)); - const childJournal = new AvmJournal(journal.hostStorage, journal); + const childJournal = new AvmWorldStateJournal(journal.hostStorage, journal); // Get the cache miss const cacheMissResult = await childJournal.readStorage(contractAddress, key); @@ -144,15 +143,15 @@ describe('journal', () => { journal.writeL1Message(logs); journal.writeNullifier(commitment); - const journal1 = new AvmJournal(journal.hostStorage, journal); - journal1.writeStorage(contractAddress, key, valueT1); - await journal1.readStorage(contractAddress, key); - journal1.writeNoteHash(commitmentT1); - journal1.writeLog(logsT1); - journal1.writeL1Message(logsT1); - journal1.writeNullifier(commitmentT1); + const childJournal = new AvmWorldStateJournal(journal.hostStorage, journal); + childJournal.writeStorage(contractAddress, key, valueT1); + await childJournal.readStorage(contractAddress, key); + childJournal.writeNoteHash(commitmentT1); + childJournal.writeLog(logsT1); + childJournal.writeL1Message(logsT1); + childJournal.writeNullifier(commitmentT1); - journal1.mergeSuccessWithParent(); + journal.acceptNestedWorldState(childJournal); const result = await journal.readStorage(contractAddress, key); expect(result).toEqual(valueT1); @@ -204,15 +203,15 @@ describe('journal', () => { journal.writeL1Message(logs); journal.writeNullifier(commitment); - const journal1 = new AvmJournal(journal.hostStorage, journal); - journal1.writeStorage(contractAddress, key, valueT1); - await journal1.readStorage(contractAddress, key); - journal1.writeNoteHash(commitmentT1); - journal1.writeLog(logsT1); - journal1.writeL1Message(logsT1); - journal1.writeNullifier(commitmentT1); + const childJournal = new AvmWorldStateJournal(journal.hostStorage, journal); + childJournal.writeStorage(contractAddress, key, valueT1); + await childJournal.readStorage(contractAddress, key); + childJournal.writeNoteHash(commitmentT1); + childJournal.writeLog(logsT1); + childJournal.writeL1Message(logsT1); + childJournal.writeNullifier(commitmentT1); - journal1.mergeFailureWithParent(); + journal.rejectNestedWorldState(childJournal); // Check that the storage is reverted by reading from the journal const result = await journal.readStorage(contractAddress, key); @@ -239,14 +238,11 @@ describe('journal', () => { expect(journalUpdates.newNullifiers).toEqual([commitment]); }); - it('Cannot merge a root journal, but can merge a child journal', () => { - const rootJournal = AvmJournal.rootJournal(journal.hostStorage); - const childJournal = AvmJournal.branchParent(rootJournal); + it('Can fork and merge journals', () => { + const rootJournal = new AvmWorldStateJournal(journal.hostStorage); + const childJournal = rootJournal.fork(); - expect(() => rootJournal.mergeSuccessWithParent()).toThrow(RootJournalCannotBeMerged); - expect(() => rootJournal.mergeFailureWithParent()).toThrow(RootJournalCannotBeMerged); - - expect(() => childJournal.mergeSuccessWithParent()); - expect(() => childJournal.mergeFailureWithParent()); + expect(() => rootJournal.acceptNestedWorldState(childJournal)); + expect(() => rootJournal.rejectNestedWorldState(childJournal)); }); }); diff --git a/yarn-project/acir-simulator/src/avm/journal/journal.ts b/yarn-project/acir-simulator/src/avm/journal/journal.ts index 3b2445e7dc6..cdeb4b4ff92 100644 --- a/yarn-project/acir-simulator/src/avm/journal/journal.ts +++ b/yarn-project/acir-simulator/src/avm/journal/journal.ts @@ -1,6 +1,5 @@ import { Fr } from '@aztec/foundation/fields'; -import { RootJournalCannotBeMerged } from './errors.js'; import { HostStorage } from './host_storage.js'; /** @@ -25,11 +24,11 @@ export type JournalData = { * A cache of the current state of the AVM * The interpreter should make any state queries through this object * - * When a sub context's call succeeds, it's journal is merge into the parent - * When a a call fails, it's journal is discarded and the parent is used from this point forward + * When a nested context succeeds, it's journal is merge into the parent + * When a call fails, it's journal is discarded and the parent is used from this point forward * When a call succeeds's we can merge a child into its parent */ -export class AvmJournal { +export class AvmWorldStateJournal { /** Reference to node storage */ public readonly hostStorage: HostStorage; @@ -49,27 +48,18 @@ export class AvmJournal { // contract address -> key -> value private currentStorageValue: Map> = new Map(); - private parentJournal: AvmJournal | undefined; + private parentJournal: AvmWorldStateJournal | undefined; - constructor(hostStorage: HostStorage, parentJournal?: AvmJournal) { + constructor(hostStorage: HostStorage, parentJournal?: AvmWorldStateJournal) { this.hostStorage = hostStorage; this.parentJournal = parentJournal; } /** - * Create a new root journal, without a parent - * @param hostStorage - + * Create a new world state journal forked from this one */ - public static rootJournal(hostStorage: HostStorage) { - return new AvmJournal(hostStorage); - } - - /** - * Create a new journal from a parent - * @param parentJournal - - */ - public static branchParent(parentJournal: AvmJournal) { - return new AvmJournal(parentJournal.hostStorage, parentJournal); + public fork() { + return new AvmWorldStateJournal(this.hostStorage, this); } /** @@ -162,44 +152,36 @@ export class AvmJournal { } /** - * Merge Journal from successful call into parent + * Accept nested world state, merging in its journal, and accepting its state modifications * - Utxo objects are concatenated * - Public state changes are merged, with the value in the incoming journal taking precedent * - Public state journals (r/w logs), with the accessing being appended in chronological order */ - public mergeSuccessWithParent() { - if (!this.parentJournal) { - throw new RootJournalCannotBeMerged(); - } - + public acceptNestedWorldState(nestedJournal: AvmWorldStateJournal) { // Merge UTXOs - this.parentJournal.newNoteHashes = this.parentJournal.newNoteHashes.concat(this.newNoteHashes); - this.parentJournal.newL1Messages = this.parentJournal.newL1Messages.concat(this.newL1Messages); - this.parentJournal.newNullifiers = this.parentJournal.newNullifiers.concat(this.newNullifiers); - this.parentJournal.newLogs = this.parentJournal.newLogs.concat(this.newLogs); + this.newNoteHashes = this.newNoteHashes.concat(nestedJournal.newNoteHashes); + this.newL1Messages = this.newL1Messages.concat(nestedJournal.newL1Messages); + this.newNullifiers = this.newNullifiers.concat(nestedJournal.newNullifiers); + this.newLogs = this.newLogs.concat(nestedJournal.newLogs); // Merge Public State - mergeCurrentValueMaps(this.parentJournal.currentStorageValue, this.currentStorageValue); + mergeCurrentValueMaps(this.currentStorageValue, nestedJournal.currentStorageValue); // Merge storage read and write journals - mergeContractJournalMaps(this.parentJournal.storageReads, this.storageReads); - mergeContractJournalMaps(this.parentJournal.storageWrites, this.storageWrites); + mergeContractJournalMaps(this.storageReads, nestedJournal.storageReads); + mergeContractJournalMaps(this.storageWrites, nestedJournal.storageWrites); } /** - * Merge Journal for failed call into parent + * Reject nested world state, merging in its journal, but not accepting its state modifications * - Utxo objects are concatenated * - Public state changes are dropped * - Public state journals (r/w logs) are maintained, with the accessing being appended in chronological order */ - public mergeFailureWithParent() { - if (!this.parentJournal) { - throw new RootJournalCannotBeMerged(); - } - + public rejectNestedWorldState(nestedJournal: AvmWorldStateJournal) { // Merge storage read and write journals - mergeContractJournalMaps(this.parentJournal.storageReads, this.storageReads); - mergeContractJournalMaps(this.parentJournal.storageWrites, this.storageWrites); + mergeContractJournalMaps(this.storageReads, nestedJournal.storageReads); + mergeContractJournalMaps(this.storageWrites, nestedJournal.storageWrites); } /** diff --git a/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.test.ts index 0163b8bea21..83996e1e3b7 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.test.ts @@ -1,21 +1,14 @@ -import { mock } from 'jest-mock-extended'; - -import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmContext } from '../avm_context.js'; import { Field } from '../avm_memory_types.js'; -import { initExecutionEnvironment } from '../fixtures/index.js'; -import { HostStorage } from '../journal/host_storage.js'; -import { AvmJournal } from '../journal/journal.js'; +import { initContext, initExecutionEnvironment } from '../fixtures/index.js'; import { EmitNoteHash, EmitNullifier, EmitUnencryptedLog, SendL2ToL1Message } from './accrued_substate.js'; import { StaticCallStorageAlterError } from './storage.js'; describe('Accrued Substate', () => { - let journal: AvmJournal; - let machineState: AvmMachineState; + let context: AvmContext; beforeEach(() => { - const hostStorage = mock(); - journal = new AvmJournal(hostStorage); - machineState = new AvmMachineState(initExecutionEnvironment()); + context = initContext(); }); describe('EmitNoteHash', () => { @@ -33,11 +26,11 @@ describe('Accrued Substate', () => { it('Should append a new note hash correctly', async () => { const value = new Field(69n); - machineState.memory.set(0, value); + context.machineState.memory.set(0, value); - await new EmitNoteHash(/*indirect=*/ 0, /*offset=*/ 0).execute(machineState, journal); + await new EmitNoteHash(/*indirect=*/ 0, /*offset=*/ 0).execute(context); - const journalState = journal.flush(); + const journalState = context.worldState.flush(); const expected = [value.toFr()]; expect(journalState.newNoteHashes).toEqual(expected); }); @@ -58,11 +51,11 @@ describe('Accrued Substate', () => { it('Should append a new nullifier correctly', async () => { const value = new Field(69n); - machineState.memory.set(0, value); + context.machineState.memory.set(0, value); - await new EmitNullifier(/*indirect=*/ 0, /*offset=*/ 0).execute(machineState, journal); + await new EmitNullifier(/*indirect=*/ 0, /*offset=*/ 0).execute(context); - const journalState = journal.flush(); + const journalState = context.worldState.flush(); const expected = [value.toFr()]; expect(journalState.newNullifiers).toEqual(expected); }); @@ -86,13 +79,13 @@ describe('Accrued Substate', () => { const startOffset = 0; const values = [new Field(69n), new Field(420n), new Field(Field.MODULUS - 1n)]; - machineState.memory.setSlice(0, values); + context.machineState.memory.setSlice(0, values); const length = values.length; - await new EmitUnencryptedLog(/*indirect=*/ 0, /*offset=*/ startOffset, length).execute(machineState, journal); + await new EmitUnencryptedLog(/*indirect=*/ 0, /*offset=*/ startOffset, length).execute(context); - const journalState = journal.flush(); + const journalState = context.worldState.flush(); const expected = values.map(v => v.toFr()); expect(journalState.newLogs).toEqual([expected]); }); @@ -116,21 +109,20 @@ describe('Accrued Substate', () => { const startOffset = 0; const values = [new Field(69n), new Field(420n), new Field(Field.MODULUS - 1n)]; - machineState.memory.setSlice(0, values); + context.machineState.memory.setSlice(0, values); const length = values.length; - await new SendL2ToL1Message(/*indirect=*/ 0, /*offset=*/ startOffset, length).execute(machineState, journal); + await new SendL2ToL1Message(/*indirect=*/ 0, /*offset=*/ startOffset, length).execute(context); - const journalState = journal.flush(); + const journalState = context.worldState.flush(); const expected = values.map(v => v.toFr()); expect(journalState.newL1Messages).toEqual([expected]); }); }); it('All substate instructions should fail within a static call', async () => { - const executionEnvironment = initExecutionEnvironment({ isStaticCall: true }); - machineState = new AvmMachineState(executionEnvironment); + context = initContext({ env: initExecutionEnvironment({ isStaticCall: true }) }); const instructions = [ new EmitNoteHash(/*indirect=*/ 0, /*offset=*/ 0), @@ -140,7 +132,7 @@ describe('Accrued Substate', () => { ]; for (const instruction of instructions) { - await expect(instruction.execute(machineState, journal)).rejects.toThrow(StaticCallStorageAlterError); + await expect(instruction.execute(context)).rejects.toThrow(StaticCallStorageAlterError); } }); }); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.ts b/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.ts index 1e85dde5d20..a59da301337 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.ts @@ -1,5 +1,4 @@ -import { AvmMachineState } from '../avm_machine_state.js'; -import { AvmJournal } from '../journal/journal.js'; +import type { AvmContext } from '../avm_context.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; import { Instruction } from './instruction.js'; import { StaticCallStorageAlterError } from './storage.js'; @@ -14,15 +13,15 @@ export class EmitNoteHash extends Instruction { super(); } - async execute(machineState: AvmMachineState, journal: AvmJournal): Promise { - if (machineState.executionEnvironment.isStaticCall) { + async execute(context: AvmContext): Promise { + if (context.environment.isStaticCall) { throw new StaticCallStorageAlterError(); } - const noteHash = machineState.memory.get(this.noteHashOffset).toFr(); - journal.writeNoteHash(noteHash); + const noteHash = context.machineState.memory.get(this.noteHashOffset).toFr(); + context.worldState.writeNoteHash(noteHash); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -36,15 +35,15 @@ export class EmitNullifier extends Instruction { super(); } - async execute(machineState: AvmMachineState, journal: AvmJournal): Promise { - if (machineState.executionEnvironment.isStaticCall) { + async execute(context: AvmContext): Promise { + if (context.environment.isStaticCall) { throw new StaticCallStorageAlterError(); } - const nullifier = machineState.memory.get(this.nullifierOffset).toFr(); - journal.writeNullifier(nullifier); + const nullifier = context.machineState.memory.get(this.nullifierOffset).toFr(); + context.worldState.writeNullifier(nullifier); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -58,15 +57,15 @@ export class EmitUnencryptedLog extends Instruction { super(); } - async execute(machineState: AvmMachineState, journal: AvmJournal): Promise { - if (machineState.executionEnvironment.isStaticCall) { + async execute(context: AvmContext): Promise { + if (context.environment.isStaticCall) { throw new StaticCallStorageAlterError(); } - const log = machineState.memory.getSlice(this.logOffset, this.logSize).map(f => f.toFr()); - journal.writeLog(log); + const log = context.machineState.memory.getSlice(this.logOffset, this.logSize).map(f => f.toFr()); + context.worldState.writeLog(log); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -80,14 +79,14 @@ export class SendL2ToL1Message extends Instruction { super(); } - async execute(machineState: AvmMachineState, journal: AvmJournal): Promise { - if (machineState.executionEnvironment.isStaticCall) { + async execute(context: AvmContext): Promise { + if (context.environment.isStaticCall) { throw new StaticCallStorageAlterError(); } - const msg = machineState.memory.getSlice(this.msgOffset, this.msgSize).map(f => f.toFr()); - journal.writeL1Message(msg); + const msg = context.machineState.memory.getSlice(this.msgOffset, this.msgSize).map(f => f.toFr()); + context.worldState.writeL1Message(msg); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.test.ts index f54db2d82a7..a66201a9808 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.test.ts @@ -1,18 +1,13 @@ -import { MockProxy, mock } from 'jest-mock-extended'; - -import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmContext } from '../avm_context.js'; import { Field, TypeTag } from '../avm_memory_types.js'; -import { initExecutionEnvironment } from '../fixtures/index.js'; -import { AvmJournal } from '../journal/journal.js'; +import { initContext } from '../fixtures/index.js'; import { Add, Div, Mul, Sub } from './arithmetic.js'; describe('Arithmetic Instructions', () => { - let machineState: AvmMachineState; - let journal: MockProxy; + let context: AvmContext; - beforeEach(async () => { - machineState = new AvmMachineState(initExecutionEnvironment()); - journal = mock(); + beforeEach(() => { + context = initContext(); }); describe('Add', () => { @@ -41,8 +36,8 @@ describe('Arithmetic Instructions', () => { const a = new Field(1n); const b = new Field(2n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); await new Add( /*indirect=*/ 0, @@ -50,10 +45,10 @@ describe('Arithmetic Instructions', () => { /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2, - ).execute(machineState, journal); + ).execute(context); const expected = new Field(3n); - const actual = machineState.memory.get(2); + const actual = context.machineState.memory.get(2); expect(actual).toEqual(expected); }); @@ -61,8 +56,8 @@ describe('Arithmetic Instructions', () => { const a = new Field(1n); const b = new Field(Field.MODULUS - 1n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); await new Add( /*indirect=*/ 0, @@ -70,10 +65,10 @@ describe('Arithmetic Instructions', () => { /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2, - ).execute(machineState, journal); + ).execute(context); const expected = new Field(0n); - const actual = machineState.memory.get(2); + const actual = context.machineState.memory.get(2); expect(actual).toEqual(expected); }); }); @@ -104,8 +99,8 @@ describe('Arithmetic Instructions', () => { const a = new Field(1n); const b = new Field(2n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); await new Sub( /*indirect=*/ 0, @@ -113,10 +108,10 @@ describe('Arithmetic Instructions', () => { /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2, - ).execute(machineState, journal); + ).execute(context); const expected = new Field(Field.MODULUS - 1n); - const actual = machineState.memory.get(2); + const actual = context.machineState.memory.get(2); expect(actual).toEqual(expected); }); }); @@ -147,8 +142,8 @@ describe('Arithmetic Instructions', () => { const a = new Field(2n); const b = new Field(3n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); await new Mul( /*indirect=*/ 0, @@ -156,10 +151,10 @@ describe('Arithmetic Instructions', () => { /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2, - ).execute(machineState, journal); + ).execute(context); const expected = new Field(6n); - const actual = machineState.memory.get(2); + const actual = context.machineState.memory.get(2); expect(actual).toEqual(expected); }); @@ -167,8 +162,8 @@ describe('Arithmetic Instructions', () => { const a = new Field(2n); const b = new Field(Field.MODULUS / 2n - 1n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); await new Mul( /*indirect=*/ 0, @@ -176,10 +171,10 @@ describe('Arithmetic Instructions', () => { /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2, - ).execute(machineState, journal); + ).execute(context); const expected = new Field(Field.MODULUS - 3n); - const actual = machineState.memory.get(2); + const actual = context.machineState.memory.get(2); expect(actual).toEqual(expected); }); }); @@ -210,8 +205,8 @@ describe('Arithmetic Instructions', () => { const a = new Field(2n); const b = new Field(3n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); await new Div( /*indirect=*/ 0, @@ -219,9 +214,9 @@ describe('Arithmetic Instructions', () => { /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2, - ).execute(machineState, journal); + ).execute(context); - const actual = machineState.memory.get(2); + const actual = context.machineState.memory.get(2); const recovered = actual.mul(b); expect(recovered).toEqual(a); }); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts index 71a571b45dd..91c5a44a6dc 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts @@ -1,5 +1,4 @@ -import { AvmMachineState } from '../avm_machine_state.js'; -import { AvmJournal } from '../journal/index.js'; +import type { AvmContext } from '../avm_context.js'; import { Opcode } from '../serialization/instruction_serialization.js'; import { ThreeOperandInstruction } from './instruction_impl.js'; @@ -11,14 +10,14 @@ export class Add extends ThreeOperandInstruction { super(indirect, inTag, aOffset, bOffset, dstOffset); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const a = machineState.memory.get(this.aOffset); - const b = machineState.memory.get(this.bOffset); + async execute(context: AvmContext): Promise { + const a = context.machineState.memory.get(this.aOffset); + const b = context.machineState.memory.get(this.bOffset); const dest = a.add(b); - machineState.memory.set(this.dstOffset, dest); + context.machineState.memory.set(this.dstOffset, dest); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -30,14 +29,14 @@ export class Sub extends ThreeOperandInstruction { super(indirect, inTag, aOffset, bOffset, dstOffset); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const a = machineState.memory.get(this.aOffset); - const b = machineState.memory.get(this.bOffset); + async execute(context: AvmContext): Promise { + const a = context.machineState.memory.get(this.aOffset); + const b = context.machineState.memory.get(this.bOffset); const dest = a.sub(b); - machineState.memory.set(this.dstOffset, dest); + context.machineState.memory.set(this.dstOffset, dest); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -49,14 +48,14 @@ export class Mul extends ThreeOperandInstruction { super(indirect, inTag, aOffset, bOffset, dstOffset); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const a = machineState.memory.get(this.aOffset); - const b = machineState.memory.get(this.bOffset); + async execute(context: AvmContext): Promise { + const a = context.machineState.memory.get(this.aOffset); + const b = context.machineState.memory.get(this.bOffset); const dest = a.mul(b); - machineState.memory.set(this.dstOffset, dest); + context.machineState.memory.set(this.dstOffset, dest); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -68,13 +67,13 @@ export class Div extends ThreeOperandInstruction { super(indirect, inTag, aOffset, bOffset, dstOffset); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const a = machineState.memory.get(this.aOffset); - const b = machineState.memory.get(this.bOffset); + async execute(context: AvmContext): Promise { + const a = context.machineState.memory.get(this.aOffset); + const b = context.machineState.memory.get(this.bOffset); const dest = a.div(b); - machineState.memory.set(this.dstOffset, dest); + context.machineState.memory.set(this.dstOffset, dest); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.test.ts index d41c34611e0..8a16f215f03 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.test.ts @@ -1,18 +1,13 @@ -import { MockProxy, mock } from 'jest-mock-extended'; - -import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmContext } from '../avm_context.js'; import { TypeTag, Uint16, Uint32 } from '../avm_memory_types.js'; -import { initExecutionEnvironment } from '../fixtures/index.js'; -import { AvmJournal } from '../journal/journal.js'; +import { initContext } from '../fixtures/index.js'; import { And, Not, Or, Shl, Shr, Xor } from './bitwise.js'; describe('Bitwise instructions', () => { - let machineState: AvmMachineState; - let journal: MockProxy; + let context: AvmContext; - beforeEach(async () => { - machineState = new AvmMachineState(initExecutionEnvironment()); - journal = mock(); + beforeEach(() => { + context = initContext(); }); describe('AND', () => { @@ -38,8 +33,8 @@ describe('Bitwise instructions', () => { }); it('Should AND correctly over integral types', async () => { - machineState.memory.set(0, new Uint32(0b11111110010011100100n)); - machineState.memory.set(1, new Uint32(0b11100100111001001111n)); + context.machineState.memory.set(0, new Uint32(0b11111110010011100100n)); + context.machineState.memory.set(1, new Uint32(0b11100100111001001111n)); await new And( /*indirect=*/ 0, @@ -47,9 +42,9 @@ describe('Bitwise instructions', () => { /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2, - ).execute(machineState, journal); + ).execute(context); - const actual = machineState.memory.get(2); + const actual = context.machineState.memory.get(2); expect(actual).toEqual(new Uint32(0b11100100010001000100n)); }); }); @@ -80,8 +75,8 @@ describe('Bitwise instructions', () => { const a = new Uint32(0b11111110010011100100n); const b = new Uint32(0b11100100111001001111n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); await new Or( /*indirect=*/ 0, @@ -89,10 +84,10 @@ describe('Bitwise instructions', () => { /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2, - ).execute(machineState, journal); + ).execute(context); const expected = new Uint32(0b11111110111011101111n); - const actual = machineState.memory.get(2); + const actual = context.machineState.memory.get(2); expect(actual).toEqual(expected); }); }); @@ -123,8 +118,8 @@ describe('Bitwise instructions', () => { const a = new Uint32(0b11111110010011100100n); const b = new Uint32(0b11100100111001001111n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); await new Xor( /*indirect=*/ 0, @@ -132,10 +127,10 @@ describe('Bitwise instructions', () => { /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2, - ).execute(machineState, journal); + ).execute(context); const expected = new Uint32(0b00011010101010101011n); - const actual = machineState.memory.get(2); + const actual = context.machineState.memory.get(2); expect(actual).toEqual(expected); }); }); @@ -166,8 +161,8 @@ describe('Bitwise instructions', () => { const a = new Uint32(0b11111110010011100100n); const b = new Uint32(0n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); await new Shr( /*indirect=*/ 0, @@ -175,10 +170,10 @@ describe('Bitwise instructions', () => { /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2, - ).execute(machineState, journal); + ).execute(context); const expected = a; - const actual = machineState.memory.get(2); + const actual = context.machineState.memory.get(2); expect(actual).toEqual(expected); }); @@ -186,8 +181,8 @@ describe('Bitwise instructions', () => { const a = new Uint32(0b11111110010011100100n); const b = new Uint32(2n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); await new Shr( /*indirect=*/ 0, @@ -195,10 +190,10 @@ describe('Bitwise instructions', () => { /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2, - ).execute(machineState, journal); + ).execute(context); const expected = new Uint32(0b00111111100100111001n); - const actual = machineState.memory.get(2); + const actual = context.machineState.memory.get(2); expect(actual).toEqual(expected); }); @@ -206,8 +201,8 @@ describe('Bitwise instructions', () => { const a = new Uint32(0b11111110010011100100n); const b = new Uint32(19n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); await new Shr( /*indirect=*/ 0, @@ -215,10 +210,10 @@ describe('Bitwise instructions', () => { /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2, - ).execute(machineState, journal); + ).execute(context); const expected = new Uint32(0b01n); - const actual = machineState.memory.get(2); + const actual = context.machineState.memory.get(2); expect(actual).toEqual(expected); }); }); @@ -249,8 +244,8 @@ describe('Bitwise instructions', () => { const a = new Uint32(0b11111110010011100100n); const b = new Uint32(0n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); await new Shl( /*indirect=*/ 0, @@ -258,10 +253,10 @@ describe('Bitwise instructions', () => { /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2, - ).execute(machineState, journal); + ).execute(context); const expected = a; - const actual = machineState.memory.get(2); + const actual = context.machineState.memory.get(2); expect(actual).toEqual(expected); }); @@ -269,8 +264,8 @@ describe('Bitwise instructions', () => { const a = new Uint32(0b11111110010011100100n); const b = new Uint32(2n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); await new Shl( /*indirect=*/ 0, @@ -278,10 +273,10 @@ describe('Bitwise instructions', () => { /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2, - ).execute(machineState, journal); + ).execute(context); const expected = new Uint32(0b1111111001001110010000n); - const actual = machineState.memory.get(2); + const actual = context.machineState.memory.get(2); expect(actual).toEqual(expected); }); @@ -289,8 +284,8 @@ describe('Bitwise instructions', () => { const a = new Uint16(0b1110010011100111n); const b = new Uint16(17n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); await new Shl( /*indirect=*/ 0, @@ -298,10 +293,10 @@ describe('Bitwise instructions', () => { /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2, - ).execute(machineState, journal); + ).execute(context); const expected = new Uint16(0n); - const actual = machineState.memory.get(2); + const actual = context.machineState.memory.get(2); expect(actual).toEqual(expected); }); @@ -309,8 +304,8 @@ describe('Bitwise instructions', () => { const a = new Uint16(0b1110010011100111n); const b = new Uint16(2n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); await new Shl( /*indirect=*/ 0, @@ -318,10 +313,10 @@ describe('Bitwise instructions', () => { /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2, - ).execute(machineState, journal); + ).execute(context); const expected = new Uint16(0b1001001110011100n); - const actual = machineState.memory.get(2); + const actual = context.machineState.memory.get(2); expect(actual).toEqual(expected); }); }); @@ -349,15 +344,12 @@ describe('Bitwise instructions', () => { it('Should NOT correctly over integral types', async () => { const a = new Uint16(0b0110010011100100n); - machineState.memory.set(0, a); + context.machineState.memory.set(0, a); - await new Not(/*indirect=*/ 0, /*inTag=*/ TypeTag.UINT16, /*aOffset=*/ 0, /*dstOffset=*/ 1).execute( - machineState, - journal, - ); + await new Not(/*indirect=*/ 0, /*inTag=*/ TypeTag.UINT16, /*aOffset=*/ 0, /*dstOffset=*/ 1).execute(context); const expected = new Uint16(0b1001101100011011n); // high bits! - const actual = machineState.memory.get(1); + const actual = context.machineState.memory.get(1); expect(actual).toEqual(expected); }); }); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts index f30be9b5053..9161c8bb22c 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts @@ -1,8 +1,6 @@ -import { AvmMachineState } from '../avm_machine_state.js'; +import type { AvmContext } from '../avm_context.js'; import { IntegralValue } from '../avm_memory_types.js'; -import { AvmJournal } from '../journal/index.js'; import { Opcode } from '../serialization/instruction_serialization.js'; -import { Instruction } from './instruction.js'; import { ThreeOperandInstruction, TwoOperandInstruction } from './instruction_impl.js'; export class And extends ThreeOperandInstruction { @@ -13,16 +11,16 @@ export class And extends ThreeOperandInstruction { super(indirect, inTag, aOffset, bOffset, dstOffset); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - Instruction.checkTags(machineState, this.inTag, this.aOffset, this.bOffset); + async execute(context: AvmContext): Promise { + context.machineState.memory.checkTags(this.inTag, this.aOffset, this.bOffset); - const a = machineState.memory.getAs(this.aOffset); - const b = machineState.memory.getAs(this.bOffset); + const a = context.machineState.memory.getAs(this.aOffset); + const b = context.machineState.memory.getAs(this.bOffset); const res = a.and(b); - machineState.memory.set(this.dstOffset, res); + context.machineState.memory.set(this.dstOffset, res); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -34,16 +32,16 @@ export class Or extends ThreeOperandInstruction { super(indirect, inTag, aOffset, bOffset, dstOffset); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - Instruction.checkTags(machineState, this.inTag, this.aOffset, this.bOffset); + async execute(context: AvmContext): Promise { + context.machineState.memory.checkTags(this.inTag, this.aOffset, this.bOffset); - const a = machineState.memory.getAs(this.aOffset); - const b = machineState.memory.getAs(this.bOffset); + const a = context.machineState.memory.getAs(this.aOffset); + const b = context.machineState.memory.getAs(this.bOffset); const res = a.or(b); - machineState.memory.set(this.dstOffset, res); + context.machineState.memory.set(this.dstOffset, res); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -55,16 +53,16 @@ export class Xor extends ThreeOperandInstruction { super(indirect, inTag, aOffset, bOffset, dstOffset); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - Instruction.checkTags(machineState, this.inTag, this.aOffset, this.bOffset); + async execute(context: AvmContext): Promise { + context.machineState.memory.checkTags(this.inTag, this.aOffset, this.bOffset); - const a = machineState.memory.getAs(this.aOffset); - const b = machineState.memory.getAs(this.bOffset); + const a = context.machineState.memory.getAs(this.aOffset); + const b = context.machineState.memory.getAs(this.bOffset); const res = a.xor(b); - machineState.memory.set(this.dstOffset, res); + context.machineState.memory.set(this.dstOffset, res); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -76,15 +74,15 @@ export class Not extends TwoOperandInstruction { super(indirect, inTag, aOffset, dstOffset); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - Instruction.checkTags(machineState, this.inTag, this.aOffset); + async execute(context: AvmContext): Promise { + context.machineState.memory.checkTags(this.inTag, this.aOffset); - const a = machineState.memory.getAs(this.aOffset); + const a = context.machineState.memory.getAs(this.aOffset); const res = a.not(); - machineState.memory.set(this.dstOffset, res); + context.machineState.memory.set(this.dstOffset, res); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -96,16 +94,16 @@ export class Shl extends ThreeOperandInstruction { super(indirect, inTag, aOffset, bOffset, dstOffset); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - Instruction.checkTags(machineState, this.inTag, this.aOffset, this.bOffset); + async execute(context: AvmContext): Promise { + context.machineState.memory.checkTags(this.inTag, this.aOffset, this.bOffset); - const a = machineState.memory.getAs(this.aOffset); - const b = machineState.memory.getAs(this.bOffset); + const a = context.machineState.memory.getAs(this.aOffset); + const b = context.machineState.memory.getAs(this.bOffset); const res = a.shl(b); - machineState.memory.set(this.dstOffset, res); + context.machineState.memory.set(this.dstOffset, res); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -117,15 +115,15 @@ export class Shr extends ThreeOperandInstruction { super(indirect, inTag, aOffset, bOffset, dstOffset); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - Instruction.checkTags(machineState, this.inTag, this.aOffset, this.bOffset); + async execute(context: AvmContext): Promise { + context.machineState.memory.checkTags(this.inTag, this.aOffset, this.bOffset); - const a = machineState.memory.getAs(this.aOffset); - const b = machineState.memory.getAs(this.bOffset); + const a = context.machineState.memory.getAs(this.aOffset); + const b = context.machineState.memory.getAs(this.bOffset); const res = a.shr(b); - machineState.memory.set(this.dstOffset, res); + context.machineState.memory.set(this.dstOffset, res); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/comparators.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/comparators.test.ts index b707b138aa7..c8001c7759f 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/comparators.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/comparators.test.ts @@ -1,19 +1,14 @@ -import { MockProxy, mock } from 'jest-mock-extended'; - -import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmContext } from '../avm_context.js'; import { Field, TypeTag, Uint16, Uint32 } from '../avm_memory_types.js'; -import { initExecutionEnvironment } from '../fixtures/index.js'; -import { AvmJournal } from '../journal/journal.js'; +import { TagCheckError } from '../errors.js'; +import { initContext } from '../fixtures/index.js'; import { Eq, Lt, Lte } from './comparators.js'; -import { InstructionExecutionError } from './instruction.js'; describe('Comparators', () => { - let machineState: AvmMachineState; - let journal: MockProxy; + let context: AvmContext; - beforeEach(async () => { - machineState = new AvmMachineState(initExecutionEnvironment()); - journal = mock(); + beforeEach(() => { + context = initContext(); }); describe('Eq', () => { @@ -39,33 +34,33 @@ describe('Comparators', () => { }); it('Works on integral types', async () => { - machineState.memory.setSlice(0, [new Uint32(1), new Uint32(2), new Uint32(3), new Uint32(1)]); + context.machineState.memory.setSlice(0, [new Uint32(1), new Uint32(2), new Uint32(3), new Uint32(1)]); [ new Eq(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10), new Eq(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 11), new Eq(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 3, /*dstOffset=*/ 12), - ].forEach(i => i.execute(machineState, journal)); + ].forEach(i => i.execute(context)); - const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); + const actual = context.machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); expect(actual).toEqual([new Uint32(0), new Uint32(0), new Uint32(1)]); }); it('Works on field elements', async () => { - machineState.memory.setSlice(0, [new Field(1), new Field(2), new Field(3), new Field(1)]); + context.machineState.memory.setSlice(0, [new Field(1), new Field(2), new Field(3), new Field(1)]); [ new Eq(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10), new Eq(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 11), new Eq(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 3, /*dstOffset=*/ 12), - ].forEach(i => i.execute(machineState, journal)); + ].forEach(i => i.execute(context)); - const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); + const actual = context.machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); expect(actual).toEqual([new Field(0), new Field(0), new Field(1)]); }); it('InTag is checked', async () => { - machineState.memory.setSlice(0, [new Field(1), new Uint32(2), new Uint16(3)]); + context.machineState.memory.setSlice(0, [new Field(1), new Uint32(2), new Uint16(3)]); const ops = [ new Eq(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10), @@ -75,7 +70,7 @@ describe('Comparators', () => { ]; for (const o of ops) { - await expect(() => o.execute(machineState, journal)).rejects.toThrow(InstructionExecutionError); + await expect(() => o.execute(context)).rejects.toThrow(TagCheckError); } }); }); @@ -103,33 +98,33 @@ describe('Comparators', () => { }); it('Works on integral types', async () => { - machineState.memory.setSlice(0, [new Uint32(1), new Uint32(2), new Uint32(0)]); + context.machineState.memory.setSlice(0, [new Uint32(1), new Uint32(2), new Uint32(0)]); [ new Lt(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 10), new Lt(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 11), new Lt(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 12), - ].forEach(i => i.execute(machineState, journal)); + ].forEach(i => i.execute(context)); - const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); + const actual = context.machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); expect(actual).toEqual([new Uint32(0), new Uint32(1), new Uint32(0)]); }); it('Works on field elements', async () => { - machineState.memory.setSlice(0, [new Field(1), new Field(2), new Field(0)]); + context.machineState.memory.setSlice(0, [new Field(1), new Field(2), new Field(0)]); [ new Lt(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 10), new Lt(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 11), new Lt(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 12), - ].forEach(i => i.execute(machineState, journal)); + ].forEach(i => i.execute(context)); - const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); + const actual = context.machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); expect(actual).toEqual([new Field(0), new Field(1), new Field(0)]); }); it('InTag is checked', async () => { - machineState.memory.setSlice(0, [new Field(1), new Uint32(2), new Uint16(3)]); + context.machineState.memory.setSlice(0, [new Field(1), new Uint32(2), new Uint16(3)]); const ops = [ new Lt(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10), @@ -139,7 +134,7 @@ describe('Comparators', () => { ]; for (const o of ops) { - await expect(() => o.execute(machineState, journal)).rejects.toThrow(InstructionExecutionError); + await expect(() => o.execute(context)).rejects.toThrow(TagCheckError); } }); }); @@ -167,33 +162,33 @@ describe('Comparators', () => { }); it('Works on integral types', async () => { - machineState.memory.setSlice(0, [new Uint32(1), new Uint32(2), new Uint32(0)]); + context.machineState.memory.setSlice(0, [new Uint32(1), new Uint32(2), new Uint32(0)]); [ new Lte(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 10), new Lte(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 11), new Lte(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 12), - ].forEach(i => i.execute(machineState, journal)); + ].forEach(i => i.execute(context)); - const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); + const actual = context.machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); expect(actual).toEqual([new Uint32(1), new Uint32(1), new Uint32(0)]); }); it('Works on field elements', async () => { - machineState.memory.setSlice(0, [new Field(1), new Field(2), new Field(0)]); + context.machineState.memory.setSlice(0, [new Field(1), new Field(2), new Field(0)]); [ new Lte(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 10), new Lte(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 11), new Lte(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 12), - ].forEach(i => i.execute(machineState, journal)); + ].forEach(i => i.execute(context)); - const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); + const actual = context.machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); expect(actual).toEqual([new Field(1), new Field(1), new Field(0)]); }); it('InTag is checked', async () => { - machineState.memory.setSlice(0, [new Field(1), new Uint32(2), new Uint16(3)]); + context.machineState.memory.setSlice(0, [new Field(1), new Uint32(2), new Uint16(3)]); const ops = [ new Lte(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10), @@ -203,7 +198,7 @@ describe('Comparators', () => { ]; for (const o of ops) { - await expect(() => o.execute(machineState, journal)).rejects.toThrow(InstructionExecutionError); + await expect(() => o.execute(context)).rejects.toThrow(TagCheckError); } }); }); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts index a4cffa19d4a..3f896248738 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts @@ -1,7 +1,5 @@ -import { AvmMachineState } from '../avm_machine_state.js'; -import { AvmJournal } from '../journal/index.js'; +import type { AvmContext } from '../avm_context.js'; import { Opcode } from '../serialization/instruction_serialization.js'; -import { Instruction } from './instruction.js'; import { ThreeOperandInstruction } from './instruction_impl.js'; export class Eq extends ThreeOperandInstruction { @@ -12,17 +10,17 @@ export class Eq extends ThreeOperandInstruction { super(indirect, inTag, aOffset, bOffset, dstOffset); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - Instruction.checkTags(machineState, this.inTag, this.aOffset, this.bOffset); + async execute(context: AvmContext): Promise { + context.machineState.memory.checkTags(this.inTag, this.aOffset, this.bOffset); - const a = machineState.memory.get(this.aOffset); - const b = machineState.memory.get(this.bOffset); + const a = context.machineState.memory.get(this.aOffset); + const b = context.machineState.memory.get(this.bOffset); // Result will be of the same type as 'a'. const dest = a.build(a.equals(b) ? 1n : 0n); - machineState.memory.set(this.dstOffset, dest); + context.machineState.memory.set(this.dstOffset, dest); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -34,17 +32,17 @@ export class Lt extends ThreeOperandInstruction { super(indirect, inTag, aOffset, bOffset, dstOffset); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - Instruction.checkTags(machineState, this.inTag, this.aOffset, this.bOffset); + async execute(context: AvmContext): Promise { + context.machineState.memory.checkTags(this.inTag, this.aOffset, this.bOffset); - const a = machineState.memory.get(this.aOffset); - const b = machineState.memory.get(this.bOffset); + const a = context.machineState.memory.get(this.aOffset); + const b = context.machineState.memory.get(this.bOffset); // Result will be of the same type as 'a'. const dest = a.build(a.lt(b) ? 1n : 0n); - machineState.memory.set(this.dstOffset, dest); + context.machineState.memory.set(this.dstOffset, dest); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -56,16 +54,16 @@ export class Lte extends ThreeOperandInstruction { super(indirect, inTag, aOffset, bOffset, dstOffset); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - Instruction.checkTags(machineState, this.inTag, this.aOffset, this.bOffset); + async execute(context: AvmContext): Promise { + context.machineState.memory.checkTags(this.inTag, this.aOffset, this.bOffset); - const a = machineState.memory.get(this.aOffset); - const b = machineState.memory.get(this.bOffset); + const a = context.machineState.memory.get(this.aOffset); + const b = context.machineState.memory.get(this.bOffset); // Result will be of the same type as 'a'. const dest = a.build(a.equals(b) || a.lt(b) ? 1n : 0n); - machineState.memory.set(this.dstOffset, dest); + context.machineState.memory.set(this.dstOffset, dest); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts index 82879c619b9..fee81ec3402 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts @@ -1,21 +1,16 @@ import { Fr } from '@aztec/foundation/fields'; -import { MockProxy, mock } from 'jest-mock-extended'; - -import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmContext } from '../avm_context.js'; import { Field, Uint16 } from '../avm_memory_types.js'; -import { initExecutionEnvironment } from '../fixtures/index.js'; -import { AvmJournal } from '../journal/journal.js'; +import { InstructionExecutionError } from '../errors.js'; +import { initContext } from '../fixtures/index.js'; import { InternalCall, InternalReturn, Jump, JumpI, Return, Revert } from './control_flow.js'; -import { InstructionExecutionError } from './instruction.js'; describe('Control Flow Opcodes', () => { - let journal: MockProxy; - let machineState: AvmMachineState; + let context: AvmContext; beforeEach(() => { - journal = mock(); - machineState = new AvmMachineState(initExecutionEnvironment()); + context = initContext(); }); describe('JUMP', () => { @@ -33,11 +28,11 @@ describe('Control Flow Opcodes', () => { it('Should implement JUMP', async () => { const jumpLocation = 22; - expect(machineState.pc).toBe(0); + expect(context.machineState.pc).toBe(0); const instruction = new Jump(jumpLocation); - await instruction.execute(machineState, journal); - expect(machineState.pc).toBe(jumpLocation); + await instruction.execute(context); + expect(context.machineState.pc).toBe(jumpLocation); }); }); @@ -59,31 +54,31 @@ describe('Control Flow Opcodes', () => { const jumpLocation = 22; const jumpLocation1 = 69; - expect(machineState.pc).toBe(0); + expect(context.machineState.pc).toBe(0); - machineState.memory.set(0, new Uint16(1n)); - machineState.memory.set(1, new Uint16(2n)); + context.machineState.memory.set(0, new Uint16(1n)); + context.machineState.memory.set(1, new Uint16(2n)); const instruction = new JumpI(/*indirect=*/ 0, jumpLocation, /*condOffset=*/ 0); - await instruction.execute(machineState, journal); - expect(machineState.pc).toBe(jumpLocation); + await instruction.execute(context); + expect(context.machineState.pc).toBe(jumpLocation); // Truthy can be greater than 1 const instruction1 = new JumpI(/*indirect=*/ 0, jumpLocation1, /*condOffset=*/ 1); - await instruction1.execute(machineState, journal); - expect(machineState.pc).toBe(jumpLocation1); + await instruction1.execute(context); + expect(context.machineState.pc).toBe(jumpLocation1); }); it('Should implement JUMPI - falsy', async () => { const jumpLocation = 22; - expect(machineState.pc).toBe(0); + expect(context.machineState.pc).toBe(0); - machineState.memory.set(0, new Uint16(0n)); + context.machineState.memory.set(0, new Uint16(0n)); const instruction = new JumpI(/*indirect=*/ 0, jumpLocation, /*condOffset=*/ 0); - await instruction.execute(machineState, journal); - expect(machineState.pc).toBe(1); + await instruction.execute(context); + expect(context.machineState.pc).toBe(1); }); }); @@ -102,20 +97,20 @@ describe('Control Flow Opcodes', () => { it('Should implement Internal Call and Return', async () => { const jumpLocation = 22; - expect(machineState.pc).toBe(0); + expect(context.machineState.pc).toBe(0); const instruction = new InternalCall(jumpLocation); const returnInstruction = new InternalReturn(); - await instruction.execute(machineState, journal); - expect(machineState.pc).toBe(jumpLocation); + await instruction.execute(context); + expect(context.machineState.pc).toBe(jumpLocation); - await returnInstruction.execute(machineState, journal); - expect(machineState.pc).toBe(1); + await returnInstruction.execute(context); + expect(context.machineState.pc).toBe(1); }); it('Should error if Internal Return is called without a corresponding Internal Call', async () => { - const returnInstruction = () => new InternalReturn().execute(machineState, journal); + const returnInstruction = () => new InternalReturn().execute(context); await expect(returnInstruction()).rejects.toThrow(InstructionExecutionError); }); }); @@ -151,8 +146,8 @@ describe('Control Flow Opcodes', () => { ]; for (let i = 0; i < instructions.length; i++) { - await instructions[i].execute(machineState, journal); - expect(machineState.pc).toBe(expectedPcs[i]); + await instructions[i].execute(context); + expect(context.machineState.pc).toBe(expectedPcs[i]); } }); }); @@ -174,16 +169,18 @@ describe('Control Flow Opcodes', () => { it('Should return data from the return opcode', async () => { const returnData = [new Fr(1n), new Fr(2n), new Fr(3n)]; - machineState.memory.set(0, new Field(1n)); - machineState.memory.set(1, new Field(2n)); - machineState.memory.set(2, new Field(3n)); + context.machineState.memory.set(0, new Field(1n)); + context.machineState.memory.set(1, new Field(2n)); + context.machineState.memory.set(2, new Field(3n)); const instruction = new Return(/*indirect=*/ 0, /*returnOffset=*/ 0, returnData.length); - await instruction.execute(machineState, journal); + await instruction.execute(context); - expect(machineState.getReturnData()).toEqual(returnData); - expect(machineState.halted).toBe(true); - expect(machineState.reverted).toBe(false); + expect(context.machineState.halted).toBe(true); + expect(context.machineState.getResults()).toEqual({ + reverted: false, + output: returnData, + }); }); }); @@ -204,16 +201,18 @@ describe('Control Flow Opcodes', () => { it('Should return data and revert from the revert opcode', async () => { const returnData = [new Fr(1n), new Fr(2n), new Fr(3n)]; - machineState.memory.set(0, new Field(1n)); - machineState.memory.set(1, new Field(2n)); - machineState.memory.set(2, new Field(3n)); + context.machineState.memory.set(0, new Field(1n)); + context.machineState.memory.set(1, new Field(2n)); + context.machineState.memory.set(2, new Field(3n)); const instruction = new Revert(/*indirect=*/ 0, /*returnOffset=*/ 0, returnData.length); - await instruction.execute(machineState, journal); + await instruction.execute(context); - expect(machineState.getReturnData()).toEqual(returnData); - expect(machineState.halted).toBe(true); - expect(machineState.reverted).toBe(true); + expect(context.machineState.halted).toBe(true); + expect(context.machineState.getResults()).toEqual({ + reverted: true, + output: returnData, + }); }); }); }); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts index 1527a677116..a3a8faaf619 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts @@ -1,8 +1,8 @@ -import { AvmMachineState } from '../avm_machine_state.js'; +import type { AvmContext } from '../avm_context.js'; import { IntegralValue } from '../avm_memory_types.js'; -import { AvmJournal } from '../journal/journal.js'; +import { InstructionExecutionError } from '../errors.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; -import { Instruction, InstructionExecutionError } from './instruction.js'; +import { Instruction } from './instruction.js'; export class Return extends Instruction { static type: string = 'RETURN'; @@ -19,12 +19,10 @@ export class Return extends Instruction { super(); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const returnData = machineState.memory.getSlice(this.returnOffset, this.copySize).map(word => word.toFr()); + async execute(context: AvmContext): Promise { + const output = context.machineState.memory.getSlice(this.returnOffset, this.copySize).map(word => word.toFr()); - machineState.setReturnData(returnData); - - this.halt(machineState); + context.machineState.return(output); } } @@ -43,13 +41,12 @@ export class Revert extends Instruction { super(); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const returnData = machineState.memory + async execute(context: AvmContext): Promise { + const output = context.machineState.memory .getSlice(this.returnOffset, this.returnOffset + this.retSize) .map(word => word.toFr()); - machineState.setReturnData(returnData); - this.revert(machineState); + context.machineState.revert(output); } } @@ -63,8 +60,8 @@ export class Jump extends Instruction { super(); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - machineState.pc = this.jumpOffset; + async execute(context: AvmContext): Promise { + context.machineState.pc = this.jumpOffset; } } @@ -84,14 +81,14 @@ export class JumpI extends Instruction { super(); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const condition = machineState.memory.getAs(this.condOffset); + async execute(context: AvmContext): Promise { + const condition = context.machineState.memory.getAs(this.condOffset); // TODO: reconsider this casting if (condition.toBigInt() == 0n) { - this.incrementPc(machineState); + context.machineState.incrementPc(); } else { - machineState.pc = this.loc; + context.machineState.pc = this.loc; } } } @@ -106,9 +103,9 @@ export class InternalCall extends Instruction { super(); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - machineState.internalCallStack.push(machineState.pc + 1); - machineState.pc = this.loc; + async execute(context: AvmContext): Promise { + context.machineState.internalCallStack.push(context.machineState.pc + 1); + context.machineState.pc = this.loc; } } @@ -122,11 +119,11 @@ export class InternalReturn extends Instruction { super(); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const jumpOffset = machineState.internalCallStack.pop(); + async execute(context: AvmContext): Promise { + const jumpOffset = context.machineState.internalCallStack.pop(); if (jumpOffset === undefined) { throw new InstructionExecutionError('Internal call empty!'); } - machineState.pc = jumpOffset; + context.machineState.pc = jumpOffset; } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/environment_getters.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/environment_getters.test.ts index 2acf4729546..0af20744a53 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/environment_getters.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/environment_getters.test.ts @@ -1,10 +1,6 @@ import { Fr } from '@aztec/foundation/fields'; -import { MockProxy, mock } from 'jest-mock-extended'; - -import { AvmMachineState } from '../avm_machine_state.js'; -import { initExecutionEnvironment, initGlobalVariables } from '../fixtures/index.js'; -import { AvmJournal } from '../journal/journal.js'; +import { initContext, initExecutionEnvironment, initGlobalVariables } from '../fixtures/index.js'; import { Address, BlockNumber, @@ -21,19 +17,12 @@ import { } from './environment_getters.js'; describe('Environment getters instructions', () => { - let machineState: AvmMachineState; - let journal: MockProxy; - - beforeEach(async () => { - journal = mock(); - }); - type EnvInstruction = Portal | FeePerL1Gas | FeePerL2Gas | FeePerDAGas | Origin | Sender | StorageAddress | Address; const envGetterTest = async (key: string, value: Fr, instruction: EnvInstruction) => { - machineState = new AvmMachineState(initExecutionEnvironment({ [key]: value })); + const context = initContext({ env: initExecutionEnvironment({ [key]: value }) }); - await instruction.execute(machineState, journal); - const actual = machineState.memory.get(0).toFr(); + await instruction.execute(context); + const actual = context.machineState.memory.get(0).toFr(); expect(actual).toEqual(value); }; @@ -193,10 +182,10 @@ describe('Environment getters instructions', () => { type GlobalsInstruction = ChainId | Version | BlockNumber | Timestamp; const readGlobalVariableTest = async (key: string, value: Fr, instruction: GlobalsInstruction) => { const globals = initGlobalVariables({ [key]: value }); - machineState = new AvmMachineState(initExecutionEnvironment({ globals })); + const context = initContext({ env: initExecutionEnvironment({ globals }) }); - await instruction.execute(machineState, journal); - const actual = machineState.memory.get(0).toFr(); + await instruction.execute(context); + const actual = context.machineState.memory.get(0).toFr(); expect(actual).toEqual(value); }; diff --git a/yarn-project/acir-simulator/src/avm/opcodes/environment_getters.ts b/yarn-project/acir-simulator/src/avm/opcodes/environment_getters.ts index 8dc59330e94..35e2e29c4d9 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/environment_getters.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/environment_getters.ts @@ -1,7 +1,6 @@ -import { AvmExecutionEnvironment } from '../avm_execution_environment.js'; -import { AvmMachineState } from '../avm_machine_state.js'; +import type { AvmContext } from '../avm_context.js'; +import type { AvmExecutionEnvironment } from '../avm_execution_environment.js'; import { Field } from '../avm_memory_types.js'; -import { AvmJournal } from '../journal/journal.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; import { Instruction } from './instruction.js'; @@ -13,10 +12,10 @@ abstract class GetterInstruction extends Instruction { super(); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const res = new Field(this.getIt(machineState.executionEnvironment)); - machineState.memory.set(this.dstOffset, res); - this.incrementPc(machineState); + async execute(context: AvmContext): Promise { + const res = new Field(this.getIt(context.environment)); + context.machineState.memory.set(this.dstOffset, res); + context.machineState.incrementPc(); } protected abstract getIt(env: AvmExecutionEnvironment): any; diff --git a/yarn-project/acir-simulator/src/avm/opcodes/external_calls.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/external_calls.test.ts index 2395cc32479..a7b0bab2e69 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/external_calls.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/external_calls.test.ts @@ -1,14 +1,14 @@ import { Fr } from '@aztec/foundation/fields'; import { jest } from '@jest/globals'; -import { MockProxy, mock } from 'jest-mock-extended'; +import { mock } from 'jest-mock-extended'; import { CommitmentsDB, PublicContractsDB, PublicStateDB } from '../../index.js'; -import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmContext } from '../avm_context.js'; import { Field } from '../avm_memory_types.js'; -import { initExecutionEnvironment } from '../fixtures/index.js'; +import { initContext } from '../fixtures/index.js'; import { HostStorage } from '../journal/host_storage.js'; -import { AvmJournal } from '../journal/journal.js'; +import { AvmWorldStateJournal } from '../journal/journal.js'; import { encodeToBytecode } from '../serialization/bytecode_serialization.js'; import { Return } from './control_flow.js'; import { Call, StaticCall } from './external_calls.js'; @@ -17,20 +17,15 @@ import { CalldataCopy } from './memory.js'; import { SStore } from './storage.js'; describe('External Calls', () => { - let machineState: AvmMachineState; - let journal: AvmJournal; - - let contractsDb: MockProxy; + let context: AvmContext; beforeEach(() => { - machineState = new AvmMachineState(initExecutionEnvironment()); - - contractsDb = mock(); - + const contractsDb = mock(); const commitmentsDb = mock(); const publicStateDb = mock(); const hostStorage = new HostStorage(publicStateDb, contractsDb, commitmentsDb); - journal = new AvmJournal(hostStorage); + const journal = new AvmWorldStateJournal(hostStorage); + context = initContext({ worldState: journal }); }); describe('Call', () => { @@ -79,11 +74,11 @@ describe('External Calls', () => { new Return(/*indirect=*/ 0, /*retOffset=*/ 0, /*size=*/ 2), ]); - machineState.memory.set(0, new Field(gas)); - machineState.memory.set(1, new Field(addr)); - machineState.memory.setSlice(2, args); + context.machineState.memory.set(0, new Field(gas)); + context.machineState.memory.set(1, new Field(addr)); + context.machineState.memory.setSlice(2, args); jest - .spyOn(journal.hostStorage.contractsDb, 'getBytecode') + .spyOn(context.worldState.hostStorage.contractsDb, 'getBytecode') .mockReturnValue(Promise.resolve(otherContextInstructionsBytecode)); const instruction = new Call( @@ -96,16 +91,16 @@ describe('External Calls', () => { retSize, successOffset, ); - await instruction.execute(machineState, journal); + await instruction.execute(context); - const successValue = machineState.memory.get(successOffset); + const successValue = context.machineState.memory.get(successOffset); expect(successValue).toEqual(new Field(1n)); - const retValue = machineState.memory.getSlice(retOffset, retSize); + const retValue = context.machineState.memory.getSlice(retOffset, retSize); expect(retValue).toEqual([new Field(1n), new Field(2n)]); // Check that the storage call has been merged into the parent journal - const { currentStorageValue } = journal.flush(); + const { currentStorageValue } = context.worldState.flush(); expect(currentStorageValue.size).toEqual(1); const nestedContractWrites = currentStorageValue.get(addr.toBigInt()); @@ -158,9 +153,9 @@ describe('External Calls', () => { const retSize = 2; const successOffset = 7; - machineState.memory.set(0, gas); - machineState.memory.set(1, addr); - machineState.memory.setSlice(2, args); + context.machineState.memory.set(0, gas); + context.machineState.memory.set(1, addr); + context.machineState.memory.setSlice(2, args); const otherContextInstructions: Instruction[] = [ new CalldataCopy(/*indirect=*/ 0, /*csOffset=*/ 0, /*copySize=*/ argsSize, /*dstOffset=*/ 0), @@ -170,7 +165,7 @@ describe('External Calls', () => { const otherContextInstructionsBytecode = encodeToBytecode(otherContextInstructions); jest - .spyOn(journal.hostStorage.contractsDb, 'getBytecode') + .spyOn(context.worldState.hostStorage.contractsDb, 'getBytecode') .mockReturnValue(Promise.resolve(otherContextInstructionsBytecode)); const instruction = new StaticCall( @@ -183,10 +178,10 @@ describe('External Calls', () => { retSize, successOffset, ); - await instruction.execute(machineState, journal); + await instruction.execute(context); // No revert has occurred, but the nested execution has failed - const successValue = machineState.memory.get(successOffset); + const successValue = context.machineState.memory.get(successOffset); expect(successValue).toEqual(new Field(0n)); }); }); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/external_calls.ts b/yarn-project/acir-simulator/src/avm/opcodes/external_calls.ts index 79b96891f72..8ff20f6b24a 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/external_calls.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/external_calls.ts @@ -1,9 +1,8 @@ import { Fr } from '@aztec/foundation/fields'; -import { AvmContext } from '../avm_context.js'; -import { AvmMachineState } from '../avm_machine_state.js'; +import type { AvmContext } from '../avm_context.js'; import { Field } from '../avm_memory_types.js'; -import { AvmJournal } from '../journal/journal.js'; +import { AvmSimulator } from '../avm_simulator.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; import { Instruction } from './instruction.js'; @@ -37,34 +36,30 @@ export class Call extends Instruction { } // TODO(https://github.com/AztecProtocol/aztec-packages/issues/3992): there is no concept of remaining / available gas at this moment - async execute(machineState: AvmMachineState, journal: AvmJournal): Promise { - const callAddress = machineState.memory.getAs(this.addrOffset); - const calldata = machineState.memory.getSlice(this.argsOffset, this.argsSize).map(f => new Fr(f.toBigInt())); + async execute(context: AvmContext): Promise { + const callAddress = context.machineState.memory.getAs(this.addrOffset); + const calldata = context.machineState.memory.getSlice(this.argsOffset, this.argsSize).map(f => f.toFr()); - const avmContext = AvmContext.prepExternalCallContext( - new Fr(callAddress.toBigInt()), - calldata, - machineState.executionEnvironment, - journal, - ); + const nestedContext = context.createNestedContractCallContext(callAddress.toFr(), calldata); - const returnObject = await avmContext.call(); - const success = !returnObject.reverted; + const nestedCallResults = await new AvmSimulator(nestedContext).execute(); + const success = !nestedCallResults.reverted; // We only take as much data as was specified in the return size -> TODO: should we be reverting here - const returnData = returnObject.output.slice(0, this.retSize); + const returnData = nestedCallResults.output.slice(0, this.retSize); const convertedReturnData = returnData.map(f => new Field(f)); // Write our return data into memory - machineState.memory.set(this.successOffset, new Field(success ? 1 : 0)); - machineState.memory.setSlice(this.retOffset, convertedReturnData); + context.machineState.memory.set(this.successOffset, new Field(success ? 1 : 0)); + context.machineState.memory.setSlice(this.retOffset, convertedReturnData); if (success) { - avmContext.mergeJournalSuccess(); + context.worldState.acceptNestedWorldState(nestedContext.worldState); } else { - avmContext.mergeJournalFailure(); + context.worldState.rejectNestedWorldState(nestedContext.worldState); } - this.incrementPc(machineState); + + context.machineState.incrementPc(); } } @@ -97,34 +92,31 @@ export class StaticCall extends Instruction { super(); } - async execute(machineState: AvmMachineState, journal: AvmJournal): Promise { - const callAddress = machineState.memory.get(this.addrOffset); - const calldata = machineState.memory.getSlice(this.argsOffset, this.argsSize).map(f => new Fr(f.toBigInt())); + async execute(context: AvmContext): Promise { + const callAddress = context.machineState.memory.get(this.addrOffset); + const calldata = context.machineState.memory + .getSlice(this.argsOffset, this.argsSize) + .map(f => new Fr(f.toBigInt())); - const avmContext = AvmContext.prepExternalStaticCallContext( - new Fr(callAddress.toBigInt()), - calldata, - machineState.executionEnvironment, - journal, - ); + const nestedContext = context.createNestedContractStaticCallContext(callAddress.toFr(), calldata); - const returnObject = await avmContext.call(); - const success = !returnObject.reverted; + const nestedCallResults = await new AvmSimulator(nestedContext).execute(); + const success = !nestedCallResults.reverted; // We only take as much data as was specified in the return size -> TODO: should we be reverting here - const returnData = returnObject.output.slice(0, this.retSize); + const returnData = nestedCallResults.output.slice(0, this.retSize); const convertedReturnData = returnData.map(f => new Field(f)); // Write our return data into memory - machineState.memory.set(this.successOffset, new Field(success ? 1 : 0)); - machineState.memory.setSlice(this.retOffset, convertedReturnData); + context.machineState.memory.set(this.successOffset, new Field(success ? 1 : 0)); + context.machineState.memory.setSlice(this.retOffset, convertedReturnData); if (success) { - avmContext.mergeJournalSuccess(); + context.worldState.acceptNestedWorldState(nestedContext.worldState); } else { - avmContext.mergeJournalFailure(); + context.worldState.rejectNestedWorldState(nestedContext.worldState); } - this.incrementPc(machineState); + context.machineState.incrementPc(); } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/index.ts b/yarn-project/acir-simulator/src/avm/opcodes/index.ts index 2fa19f7b04a..d8dc52a8cf5 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/index.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/index.ts @@ -5,7 +5,6 @@ export * from './instruction.js'; export * from './comparators.js'; export * from './memory.js'; export * from './storage.js'; -// TODO(https://github.com/AztecProtocol/aztec-packages/issues/4359): dependency cycle -// export * from './external_calls.js'; +export * from './external_calls.js'; export * from './environment_getters.js'; export * from './accrued_substate.js'; diff --git a/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts b/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts index ff5fdd5e96f..3f4cf27f7de 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts @@ -1,41 +1,46 @@ import { assert } from 'console'; -import { AvmMachineState } from '../avm_machine_state.js'; -import { TypeTag } from '../avm_memory_types.js'; -import { AvmJournal } from '../journal/index.js'; +import type { AvmContext } from '../avm_context.js'; import { BufferCursor } from '../serialization/buffer_cursor.js'; import { OperandType, deserialize, serialize } from '../serialization/instruction_serialization.js'; /** * Parent class for all AVM instructions. - * It's most important aspects are execution and (de)serialization. + * It's most important aspects are execute and (de)serialize. */ export abstract class Instruction { - public abstract execute(machineState: AvmMachineState, journal: AvmJournal): Promise; - - incrementPc(machineState: AvmMachineState): void { - machineState.pc++; - } - - halt(machineState: AvmMachineState): void { - machineState.halted = true; - } - - revert(machineState: AvmMachineState): void { - machineState.halted = true; - machineState.reverted = true; - } + /** + * Execute the instruction. + * Instruction sub-classes must implement this. + * As an AvmContext executes its contract code, it calls this function for + * each instruction until the machine state signals "halted". + * @param context - The AvmContext in which the instruction executes. + */ + public abstract execute(context: AvmContext): Promise; - static checkTags(machineState: AvmMachineState, tag: TypeTag, ...offsets: number[]) { - for (const offset of offsets) { - checkTag(machineState, tag, offset); + /** + * Generate a string representation of the instruction including + * the instruction sub-class name all of its flags and operands. + * @returns Thee string representation. + */ + public toString(): string { + let instructionStr = this.constructor.name + ': '; + // assumes that all properties are flags or operands + for (const prop of Object.getOwnPropertyNames(this) as (keyof Instruction)[]) { + instructionStr += `${prop}:${this[prop].toString()}, `; } + return instructionStr; } - static checkTagsRange(machineState: AvmMachineState, tag: TypeTag, startOffset: number, size: number) { - for (let offset = startOffset; offset < startOffset + size; offset++) { - checkTag(machineState, tag, offset); - } + /** + * Serialize the instruction to a Buffer according to its wire format specified in its subclass. + * If you want to use this, your subclass should specify a {@code static wireFormat: OperandType[]}. + * @param this - The instruction to serialize. + * @returns The serialized instruction. + */ + public serialize(this: any): Buffer { + assert(this instanceof Instruction); + return serialize(this.constructor.wireFormat, this); } /** @@ -53,26 +58,4 @@ export abstract class Instruction { const args = res.slice(1) as ConstructorParameters; // Remove opcode. return new this(...args); } - - public serialize(this: any): Buffer { - assert(this instanceof Instruction); - return serialize(this.constructor.wireFormat, this); - } -} - -/** - * Checks that the memory at the given offset has the given tag. - */ -function checkTag(machineState: AvmMachineState, tag: TypeTag, offset: number) { - if (machineState.memory.getTag(offset) !== tag) { - const error = `Offset ${offset} has tag ${TypeTag[machineState.memory.getTag(offset)]}, expected ${TypeTag[tag]}`; - throw new InstructionExecutionError(error); - } -} - -export class InstructionExecutionError extends Error { - constructor(message: string) { - super(message); - this.name = 'InstructionExecutionError'; - } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/memory.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/memory.test.ts index 438e7d9eadb..4889bca0a99 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/memory.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/memory.test.ts @@ -1,21 +1,16 @@ import { Fr } from '@aztec/foundation/fields'; -import { MockProxy, mock } from 'jest-mock-extended'; - -import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmContext } from '../avm_context.js'; import { Field, TypeTag, Uint8, Uint16, Uint32, Uint64, Uint128 } from '../avm_memory_types.js'; -import { initExecutionEnvironment } from '../fixtures/index.js'; -import { AvmJournal } from '../journal/journal.js'; -import { InstructionExecutionError } from './instruction.js'; +import { InstructionExecutionError } from '../errors.js'; +import { initContext, initExecutionEnvironment } from '../fixtures/index.js'; import { CMov, CalldataCopy, Cast, Mov, Set } from './memory.js'; describe('Memory instructions', () => { - let machineState: AvmMachineState; - let journal: MockProxy; + let context: AvmContext; - beforeEach(async () => { - machineState = new AvmMachineState(initExecutionEnvironment()); - journal = mock(); + beforeEach(() => { + context = initContext(); }); describe('SET', () => { @@ -39,41 +34,32 @@ describe('Memory instructions', () => { }); it('should correctly set value and tag (uninitialized)', async () => { - await new Set(/*indirect=*/ 0, /*inTag=*/ TypeTag.UINT16, /*value=*/ 1234n, /*offset=*/ 1).execute( - machineState, - journal, - ); + await new Set(/*indirect=*/ 0, /*inTag=*/ TypeTag.UINT16, /*value=*/ 1234n, /*offset=*/ 1).execute(context); - const actual = machineState.memory.get(1); - const tag = machineState.memory.getTag(1); + const actual = context.machineState.memory.get(1); + const tag = context.machineState.memory.getTag(1); expect(actual).toEqual(new Uint16(1234n)); expect(tag).toEqual(TypeTag.UINT16); }); it('should correctly set value and tag (overwriting)', async () => { - machineState.memory.set(1, new Field(27)); + context.machineState.memory.set(1, new Field(27)); - await new Set(/*indirect=*/ 0, /*inTag=*/ TypeTag.UINT32, /*value=*/ 1234n, /*offset=*/ 1).execute( - machineState, - journal, - ); + await new Set(/*indirect=*/ 0, /*inTag=*/ TypeTag.UINT32, /*value=*/ 1234n, /*offset=*/ 1).execute(context); - const actual = machineState.memory.get(1); - const tag = machineState.memory.getTag(1); + const actual = context.machineState.memory.get(1); + const tag = context.machineState.memory.getTag(1); expect(actual).toEqual(new Uint32(1234n)); expect(tag).toEqual(TypeTag.UINT32); }); it('should correctly set value and tag (truncating)', async () => { - await new Set(/*indirect=*/ 0, /*inTag=*/ TypeTag.UINT16, /*value=*/ 0x12345678n, /*offset=*/ 1).execute( - machineState, - journal, - ); + await new Set(/*indirect=*/ 0, /*inTag=*/ TypeTag.UINT16, /*value=*/ 0x12345678n, /*offset=*/ 1).execute(context); - const actual = machineState.memory.get(1); - const tag = machineState.memory.getTag(1); + const actual = context.machineState.memory.get(1); + const tag = context.machineState.memory.getTag(1); expect(actual).toEqual(new Uint16(0x5678)); expect(tag).toEqual(TypeTag.UINT16); @@ -82,7 +68,7 @@ describe('Memory instructions', () => { it('should throw if tag is FIELD, UNINITIALIZED, INVALID', async () => { for (const tag of [TypeTag.FIELD, TypeTag.UNINITIALIZED, TypeTag.INVALID]) { await expect( - new Set(/*indirect=*/ 0, /*inTag=*/ tag, /*value=*/ 1234n, /*offset=*/ 1).execute(machineState, journal), + new Set(/*indirect=*/ 0, /*inTag=*/ tag, /*value=*/ 1234n, /*offset=*/ 1).execute(context), ).rejects.toThrow(InstructionExecutionError); } }); @@ -109,11 +95,11 @@ describe('Memory instructions', () => { }); it('Should upcast between integral types', () => { - machineState.memory.set(0, new Uint8(20n)); - machineState.memory.set(1, new Uint16(65000n)); - machineState.memory.set(2, new Uint32(1n << 30n)); - machineState.memory.set(3, new Uint64(1n << 50n)); - machineState.memory.set(4, new Uint128(1n << 100n)); + context.machineState.memory.set(0, new Uint8(20n)); + context.machineState.memory.set(1, new Uint16(65000n)); + context.machineState.memory.set(2, new Uint32(1n << 30n)); + context.machineState.memory.set(3, new Uint64(1n << 50n)); + context.machineState.memory.set(4, new Uint128(1n << 100n)); [ new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT16, /*aOffset=*/ 0, /*dstOffset=*/ 10), @@ -121,9 +107,9 @@ describe('Memory instructions', () => { new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT64, /*aOffset=*/ 2, /*dstOffset=*/ 12), new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT128, /*aOffset=*/ 3, /*dstOffset=*/ 13), new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT128, /*aOffset=*/ 4, /*dstOffset=*/ 14), - ].forEach(i => i.execute(machineState, journal)); + ].forEach(i => i.execute(context)); - const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 5); + const actual = context.machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 5); expect(actual).toEqual([ new Uint16(20n), new Uint32(65000n), @@ -131,16 +117,16 @@ describe('Memory instructions', () => { new Uint128(1n << 50n), new Uint128(1n << 100n), ]); - const tags = machineState.memory.getSliceTags(/*offset=*/ 10, /*size=*/ 5); + const tags = context.machineState.memory.getSliceTags(/*offset=*/ 10, /*size=*/ 5); expect(tags).toEqual([TypeTag.UINT16, TypeTag.UINT32, TypeTag.UINT64, TypeTag.UINT128, TypeTag.UINT128]); }); it('Should downcast (truncating) between integral types', () => { - machineState.memory.set(0, new Uint8(20n)); - machineState.memory.set(1, new Uint16(65000n)); - machineState.memory.set(2, new Uint32((1n << 30n) - 1n)); - machineState.memory.set(3, new Uint64((1n << 50n) - 1n)); - machineState.memory.set(4, new Uint128((1n << 100n) - 1n)); + context.machineState.memory.set(0, new Uint8(20n)); + context.machineState.memory.set(1, new Uint16(65000n)); + context.machineState.memory.set(2, new Uint32((1n << 30n) - 1n)); + context.machineState.memory.set(3, new Uint64((1n << 50n) - 1n)); + context.machineState.memory.set(4, new Uint128((1n << 100n) - 1n)); [ new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT8, /*aOffset=*/ 0, /*dstOffset=*/ 10), @@ -148,9 +134,9 @@ describe('Memory instructions', () => { new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT16, /*aOffset=*/ 2, /*dstOffset=*/ 12), new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT32, /*aOffset=*/ 3, /*dstOffset=*/ 13), new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT64, /*aOffset=*/ 4, /*dstOffset=*/ 14), - ].forEach(i => i.execute(machineState, journal)); + ].forEach(i => i.execute(context)); - const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 5); + const actual = context.machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 5); expect(actual).toEqual([ new Uint8(20n), new Uint8(232), @@ -158,26 +144,25 @@ describe('Memory instructions', () => { new Uint32((1n << 32n) - 1n), new Uint64((1n << 64n) - 1n), ]); - const tags = machineState.memory.getSliceTags(/*offset=*/ 10, /*size=*/ 5); + const tags = context.machineState.memory.getSliceTags(/*offset=*/ 10, /*size=*/ 5); expect(tags).toEqual([TypeTag.UINT8, TypeTag.UINT8, TypeTag.UINT16, TypeTag.UINT32, TypeTag.UINT64]); }); it('Should upcast from integral types to field', () => { - machineState.memory.set(0, new Uint8(20n)); - machineState.memory.set(1, new Uint16(65000n)); - machineState.memory.set(2, new Uint32(1n << 30n)); - machineState.memory.set(3, new Uint64(1n << 50n)); - machineState.memory.set(4, new Uint128(1n << 100n)); - + context.machineState.memory.set(0, new Uint8(20n)); + context.machineState.memory.set(1, new Uint16(65000n)); + context.machineState.memory.set(2, new Uint32(1n << 30n)); + context.machineState.memory.set(3, new Uint64(1n << 50n)); + context.machineState.memory.set(4, new Uint128(1n << 100n)); [ new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.FIELD, /*aOffset=*/ 0, /*dstOffset=*/ 10), new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.FIELD, /*aOffset=*/ 1, /*dstOffset=*/ 11), new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.FIELD, /*aOffset=*/ 2, /*dstOffset=*/ 12), new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.FIELD, /*aOffset=*/ 3, /*dstOffset=*/ 13), new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.FIELD, /*aOffset=*/ 4, /*dstOffset=*/ 14), - ].forEach(i => i.execute(machineState, journal)); + ].forEach(i => i.execute(context)); - const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 5); + const actual = context.machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 5); expect(actual).toEqual([ new Field(20n), new Field(65000n), @@ -185,16 +170,16 @@ describe('Memory instructions', () => { new Field(1n << 50n), new Field(1n << 100n), ]); - const tags = machineState.memory.getSliceTags(/*offset=*/ 10, /*size=*/ 5); + const tags = context.machineState.memory.getSliceTags(/*offset=*/ 10, /*size=*/ 5); expect(tags).toEqual([TypeTag.FIELD, TypeTag.FIELD, TypeTag.FIELD, TypeTag.FIELD, TypeTag.FIELD]); }); it('Should downcast (truncating) from field to integral types', () => { - machineState.memory.set(0, new Field((1n << 200n) - 1n)); - machineState.memory.set(1, new Field((1n << 200n) - 1n)); - machineState.memory.set(2, new Field((1n << 200n) - 1n)); - machineState.memory.set(3, new Field((1n << 200n) - 1n)); - machineState.memory.set(4, new Field((1n << 200n) - 1n)); + context.machineState.memory.set(0, new Field((1n << 200n) - 1n)); + context.machineState.memory.set(1, new Field((1n << 200n) - 1n)); + context.machineState.memory.set(2, new Field((1n << 200n) - 1n)); + context.machineState.memory.set(3, new Field((1n << 200n) - 1n)); + context.machineState.memory.set(4, new Field((1n << 200n) - 1n)); [ new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT8, /*aOffset=*/ 0, /*dstOffset=*/ 10), @@ -202,9 +187,9 @@ describe('Memory instructions', () => { new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT32, /*aOffset=*/ 2, /*dstOffset=*/ 12), new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT64, /*aOffset=*/ 3, /*dstOffset=*/ 13), new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT128, /*aOffset=*/ 4, /*dstOffset=*/ 14), - ].forEach(i => i.execute(machineState, journal)); + ].forEach(i => i.execute(context)); - const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 5); + const actual = context.machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 5); expect(actual).toEqual([ new Uint8((1n << 8n) - 1n), new Uint16((1n << 16n) - 1n), @@ -212,21 +197,18 @@ describe('Memory instructions', () => { new Uint64((1n << 64n) - 1n), new Uint128((1n << 128n) - 1n), ]); - const tags = machineState.memory.getSliceTags(/*offset=*/ 10, /*size=*/ 5); + const tags = context.machineState.memory.getSliceTags(/*offset=*/ 10, /*size=*/ 5); expect(tags).toEqual([TypeTag.UINT8, TypeTag.UINT16, TypeTag.UINT32, TypeTag.UINT64, TypeTag.UINT128]); }); it('Should cast between field elements', async () => { - machineState.memory.set(0, new Field(12345678n)); + context.machineState.memory.set(0, new Field(12345678n)); - await new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.FIELD, /*aOffset=*/ 0, /*dstOffset=*/ 1).execute( - machineState, - journal, - ); + await new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.FIELD, /*aOffset=*/ 0, /*dstOffset=*/ 1).execute(context); - const actual = machineState.memory.get(1); + const actual = context.machineState.memory.get(1); expect(actual).toEqual(new Field(12345678n)); - const tags = machineState.memory.getTag(1); + const tags = context.machineState.memory.getTag(1); expect(tags).toEqual(TypeTag.FIELD); }); }); @@ -246,22 +228,22 @@ describe('Memory instructions', () => { }); it('Should move integrals on different memory cells', async () => { - machineState.memory.set(0, new Uint16(27)); - await new Mov(/*indirect=*/ 0, /*srcOffset=*/ 0, /*dstOffset=*/ 1).execute(machineState, journal); + context.machineState.memory.set(0, new Uint16(27)); + await new Mov(/*indirect=*/ 0, /*srcOffset=*/ 0, /*dstOffset=*/ 1).execute(context); - const actual = machineState.memory.get(1); - const tag = machineState.memory.getTag(1); + const actual = context.machineState.memory.get(1); + const tag = context.machineState.memory.getTag(1); expect(actual).toEqual(new Uint16(27n)); expect(tag).toEqual(TypeTag.UINT16); }); it('Should move field elements on different memory cells', async () => { - machineState.memory.set(0, new Field(27)); - await new Mov(/*indirect=*/ 0, /*srcOffset=*/ 0, /*dstOffset=*/ 1).execute(machineState, journal); + context.machineState.memory.set(0, new Field(27)); + await new Mov(/*indirect=*/ 0, /*srcOffset=*/ 0, /*dstOffset=*/ 1).execute(context); - const actual = machineState.memory.get(1); - const tag = machineState.memory.getTag(1); + const actual = context.machineState.memory.get(1); + const tag = context.machineState.memory.getTag(1); expect(actual).toEqual(new Field(27n)); expect(tag).toEqual(TypeTag.FIELD); @@ -291,65 +273,61 @@ describe('Memory instructions', () => { }); it('Should move A if COND is true, on different memory cells (integral condition)', async () => { - machineState.memory.set(0, new Uint32(123)); // A - machineState.memory.set(1, new Uint16(456)); // B - machineState.memory.set(2, new Uint8(2)); // Condition + context.machineState.memory.set(0, new Uint32(123)); // A + context.machineState.memory.set(1, new Uint16(456)); // B + context.machineState.memory.set(2, new Uint8(2)); // Condition await new CMov(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*condOffset=*/ 2, /*dstOffset=*/ 3).execute( - machineState, - journal, + context, ); - const actual = machineState.memory.get(3); - const tag = machineState.memory.getTag(3); + const actual = context.machineState.memory.get(3); + const tag = context.machineState.memory.getTag(3); expect(actual).toEqual(new Uint32(123)); expect(tag).toEqual(TypeTag.UINT32); }); it('Should move B if COND is false, on different memory cells (integral condition)', async () => { - machineState.memory.set(0, new Uint32(123)); // A - machineState.memory.set(1, new Uint16(456)); // B - machineState.memory.set(2, new Uint8(0)); // Condition + context.machineState.memory.set(0, new Uint32(123)); // A + context.machineState.memory.set(1, new Uint16(456)); // B + context.machineState.memory.set(2, new Uint8(0)); // Condition await new CMov(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*condOffset=*/ 2, /*dstOffset=*/ 3).execute( - machineState, - journal, + context, ); - const actual = machineState.memory.get(3); - const tag = machineState.memory.getTag(3); + const actual = context.machineState.memory.get(3); + const tag = context.machineState.memory.getTag(3); expect(actual).toEqual(new Uint16(456)); expect(tag).toEqual(TypeTag.UINT16); }); it('Should move A if COND is true, on different memory cells (field condition)', async () => { - machineState.memory.set(0, new Uint32(123)); // A - machineState.memory.set(1, new Uint16(456)); // B - machineState.memory.set(2, new Field(1)); // Condition + context.machineState.memory.set(0, new Uint32(123)); // A + context.machineState.memory.set(1, new Uint16(456)); // B + context.machineState.memory.set(2, new Field(1)); // Condition await new CMov(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*condOffset=*/ 2, /*dstOffset=*/ 3).execute( - machineState, - journal, + context, ); - const actual = machineState.memory.get(3); - const tag = machineState.memory.getTag(3); + const actual = context.machineState.memory.get(3); + const tag = context.machineState.memory.getTag(3); expect(actual).toEqual(new Uint32(123)); expect(tag).toEqual(TypeTag.UINT32); }); it('Should move B if COND is false, on different memory cells (integral condition)', async () => { - machineState.memory.set(0, new Uint32(123)); // A - machineState.memory.set(1, new Uint16(456)); // B - machineState.memory.set(2, new Field(0)); // Condition + context.machineState.memory.set(0, new Uint32(123)); // A + context.machineState.memory.set(1, new Uint16(456)); // B + context.machineState.memory.set(2, new Field(0)); // Condition await new CMov(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*condOffset=*/ 2, /*dstOffset=*/ 3).execute( - machineState, - journal, + context, ); - const actual = machineState.memory.get(3); - const tag = machineState.memory.getTag(3); + const actual = context.machineState.memory.get(3); + const tag = context.machineState.memory.getTag(3); expect(actual).toEqual(new Uint16(456)); expect(tag).toEqual(TypeTag.UINT16); }); @@ -377,43 +355,34 @@ describe('Memory instructions', () => { it('Writes nothing if size is 0', async () => { const calldata = [new Fr(1n), new Fr(2n), new Fr(3n)]; - machineState = new AvmMachineState(initExecutionEnvironment({ calldata })); - machineState.memory.set(0, new Uint16(12)); // Some previous data to be overwritten + context = initContext({ env: initExecutionEnvironment({ calldata }) }); + context.machineState.memory.set(0, new Uint16(12)); // Some previous data to be overwritten - await new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 0, /*copySize=*/ 0, /*dstOffset=*/ 0).execute( - machineState, - journal, - ); + await new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 0, /*copySize=*/ 0, /*dstOffset=*/ 0).execute(context); - const actual = machineState.memory.get(0); + const actual = context.machineState.memory.get(0); expect(actual).toEqual(new Uint16(12)); }); it('Copies all calldata', async () => { const calldata = [new Fr(1n), new Fr(2n), new Fr(3n)]; - machineState = new AvmMachineState(initExecutionEnvironment({ calldata })); - machineState.memory.set(0, new Uint16(12)); // Some previous data to be overwritten + context = initContext({ env: initExecutionEnvironment({ calldata }) }); + context.machineState.memory.set(0, new Uint16(12)); // Some previous data to be overwritten - await new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 0, /*copySize=*/ 3, /*dstOffset=*/ 0).execute( - machineState, - journal, - ); + await new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 0, /*copySize=*/ 3, /*dstOffset=*/ 0).execute(context); - const actual = machineState.memory.getSlice(/*offset=*/ 0, /*size=*/ 3); + const actual = context.machineState.memory.getSlice(/*offset=*/ 0, /*size=*/ 3); expect(actual).toEqual([new Field(1), new Field(2), new Field(3)]); }); it('Copies slice of calldata', async () => { const calldata = [new Fr(1n), new Fr(2n), new Fr(3n)]; - machineState = new AvmMachineState(initExecutionEnvironment({ calldata })); - machineState.memory.set(0, new Uint16(12)); // Some previous data to be overwritten + context = initContext({ env: initExecutionEnvironment({ calldata }) }); + context.machineState.memory.set(0, new Uint16(12)); // Some previous data to be overwritten - await new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 1, /*copySize=*/ 2, /*dstOffset=*/ 0).execute( - machineState, - journal, - ); + await new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 1, /*copySize=*/ 2, /*dstOffset=*/ 0).execute(context); - const actual = machineState.memory.getSlice(/*offset=*/ 0, /*size=*/ 2); + const actual = context.machineState.memory.getSlice(/*offset=*/ 0, /*size=*/ 2); expect(actual).toEqual([new Field(2), new Field(3)]); }); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts index fd26f92060c..4e43edc6868 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts @@ -1,8 +1,8 @@ -import { AvmMachineState } from '../avm_machine_state.js'; +import type { AvmContext } from '../avm_context.js'; import { Field, TaggedMemory, TypeTag } from '../avm_memory_types.js'; -import { AvmJournal } from '../journal/index.js'; +import { InstructionExecutionError } from '../errors.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; -import { Instruction, InstructionExecutionError } from './instruction.js'; +import { Instruction } from './instruction.js'; import { TwoOperandInstruction } from './instruction_impl.js'; export class Set extends Instruction { @@ -21,16 +21,16 @@ export class Set extends Instruction { super(); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { + async execute(context: AvmContext): Promise { // Per the YP, the tag cannot be a field. if ([TypeTag.FIELD, TypeTag.UNINITIALIZED, TypeTag.INVALID].includes(this.inTag)) { throw new InstructionExecutionError(`Invalid tag ${TypeTag[this.inTag]} for SET.`); } const res = TaggedMemory.integralFromTag(this.value, this.inTag); - machineState.memory.set(this.dstOffset, res); + context.machineState.memory.set(this.dstOffset, res); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -57,15 +57,15 @@ export class CMov extends Instruction { super(); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const a = machineState.memory.get(this.aOffset); - const b = machineState.memory.get(this.bOffset); - const cond = machineState.memory.get(this.condOffset); + async execute(context: AvmContext): Promise { + const a = context.machineState.memory.get(this.aOffset); + const b = context.machineState.memory.get(this.bOffset); + const cond = context.machineState.memory.get(this.condOffset); // TODO: reconsider toBigInt() here - machineState.memory.set(this.dstOffset, cond.toBigInt() > 0 ? a : b); + context.machineState.memory.set(this.dstOffset, cond.toBigInt() > 0 ? a : b); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -77,16 +77,16 @@ export class Cast extends TwoOperandInstruction { super(indirect, dstTag, aOffset, dstOffset); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const a = machineState.memory.get(this.aOffset); + async execute(context: AvmContext): Promise { + const a = context.machineState.memory.get(this.aOffset); // TODO: consider not using toBigInt() const casted = this.inTag == TypeTag.FIELD ? new Field(a.toBigInt()) : TaggedMemory.integralFromTag(a.toBigInt(), this.inTag); - machineState.memory.set(this.dstOffset, casted); + context.machineState.memory.set(this.dstOffset, casted); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -105,12 +105,12 @@ export class Mov extends Instruction { super(); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const a = machineState.memory.get(this.srcOffset); + async execute(context: AvmContext): Promise { + const a = context.machineState.memory.get(this.srcOffset); - machineState.memory.set(this.dstOffset, a); + context.machineState.memory.set(this.dstOffset, a); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -130,12 +130,12 @@ export class CalldataCopy extends Instruction { super(); } - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const transformedData = machineState.executionEnvironment.calldata + async execute(context: AvmContext): Promise { + const transformedData = context.environment.calldata .slice(this.cdOffset, this.cdOffset + this.copySize) .map(f => new Field(f)); - machineState.memory.setSlice(this.dstOffset, transformedData); + context.machineState.memory.setSlice(this.dstOffset, transformedData); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/storage.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/storage.test.ts index 8f704b036a3..dde14ba639c 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/storage.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/storage.test.ts @@ -3,22 +3,20 @@ import { Fr } from '@aztec/foundation/fields'; import { MockProxy, mock } from 'jest-mock-extended'; -import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmContext } from '../avm_context.js'; import { Field } from '../avm_memory_types.js'; -import { initExecutionEnvironment } from '../fixtures/index.js'; -import { AvmJournal } from '../journal/journal.js'; +import { initContext, initExecutionEnvironment } from '../fixtures/index.js'; +import { AvmWorldStateJournal } from '../journal/journal.js'; import { SLoad, SStore, StaticCallStorageAlterError } from './storage.js'; describe('Storage Instructions', () => { - let journal: MockProxy; - let machineState: AvmMachineState; + let context: AvmContext; + let journal: MockProxy; const address = AztecAddress.random(); - beforeEach(() => { - journal = mock(); - - const executionEnvironment = initExecutionEnvironment({ address, storageAddress: address }); - machineState = new AvmMachineState(executionEnvironment); + beforeEach(async () => { + journal = mock(); + context = initContext({ worldState: journal, env: initExecutionEnvironment({ address, storageAddress: address }) }); }); describe('SSTORE', () => { @@ -39,26 +37,27 @@ describe('Storage Instructions', () => { const a = new Field(1n); const b = new Field(2n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); - await new SStore(/*indirect=*/ 0, /*srcOffset=*/ 0, /*slotOffset=*/ 1).execute(machineState, journal); + await new SStore(/*indirect=*/ 0, /*srcOffset=*/ 0, /*slotOffset=*/ 1).execute(context); expect(journal.writeStorage).toHaveBeenCalledWith(address, new Fr(a.toBigInt()), new Fr(b.toBigInt())); }); it('Should not be able to write to storage in a static call', async () => { - const executionEnvironment = initExecutionEnvironment({ isStaticCall: true }); - machineState = new AvmMachineState(executionEnvironment); + context = initContext({ + worldState: journal, + env: initExecutionEnvironment({ address, storageAddress: address, isStaticCall: true }), + }); const a = new Field(1n); const b = new Field(2n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); - const instruction = () => - new SStore(/*indirect=*/ 0, /*srcOffset=*/ 0, /*slotOffset=*/ 1).execute(machineState, journal); + const instruction = () => new SStore(/*indirect=*/ 0, /*srcOffset=*/ 0, /*slotOffset=*/ 1).execute(context); await expect(instruction()).rejects.toThrow(StaticCallStorageAlterError); }); }); @@ -85,14 +84,14 @@ describe('Storage Instructions', () => { const a = new Field(1n); const b = new Field(2n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + context.machineState.memory.set(0, a); + context.machineState.memory.set(1, b); - await new SLoad(/*indirect=*/ 0, /*slotOffset=*/ 0, /*dstOffset=*/ 1).execute(machineState, journal); + await new SLoad(/*indirect=*/ 0, /*slotOffset=*/ 0, /*dstOffset=*/ 1).execute(context); expect(journal.readStorage).toHaveBeenCalledWith(address, new Fr(a.toBigInt())); - const actual = machineState.memory.get(1); + const actual = context.machineState.memory.get(1); expect(actual).toEqual(new Field(expectedResult)); }); }); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/storage.ts b/yarn-project/acir-simulator/src/avm/opcodes/storage.ts index 5cd9bee9ceb..3d59c5c57c6 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/storage.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/storage.ts @@ -1,10 +1,10 @@ import { Fr } from '@aztec/foundation/fields'; -import { AvmMachineState } from '../avm_machine_state.js'; +import type { AvmContext } from '../avm_context.js'; import { Field } from '../avm_memory_types.js'; -import { AvmJournal } from '../journal/journal.js'; +import { InstructionExecutionError } from '../errors.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; -import { Instruction, InstructionExecutionError } from './instruction.js'; +import { Instruction } from './instruction.js'; abstract class BaseStorageInstruction extends Instruction { // Informs (de)serialization. See Instruction.deserialize. @@ -28,21 +28,21 @@ export class SStore extends BaseStorageInstruction { super(indirect, srcOffset, slotOffset); } - async execute(machineState: AvmMachineState, journal: AvmJournal): Promise { - if (machineState.executionEnvironment.isStaticCall) { + async execute(context: AvmContext): Promise { + if (context.environment.isStaticCall) { throw new StaticCallStorageAlterError(); } - const slot = machineState.memory.get(this.aOffset); - const data = machineState.memory.get(this.bOffset); + const slot = context.machineState.memory.get(this.aOffset); + const data = context.machineState.memory.get(this.bOffset); - journal.writeStorage( - machineState.executionEnvironment.storageAddress, + context.worldState.writeStorage( + context.environment.storageAddress, new Fr(slot.toBigInt()), new Fr(data.toBigInt()), ); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } @@ -54,17 +54,14 @@ export class SLoad extends BaseStorageInstruction { super(indirect, slotOffset, dstOffset); } - async execute(machineState: AvmMachineState, journal: AvmJournal): Promise { - const slot = machineState.memory.get(this.aOffset); + async execute(context: AvmContext): Promise { + const slot = context.machineState.memory.get(this.aOffset); - const data: Fr = await journal.readStorage( - machineState.executionEnvironment.storageAddress, - new Fr(slot.toBigInt()), - ); + const data: Fr = await context.worldState.readStorage(context.environment.storageAddress, new Fr(slot.toBigInt())); - machineState.memory.set(this.bOffset, new Field(data)); + context.machineState.memory.set(this.bOffset, new Field(data)); - this.incrementPc(machineState); + context.machineState.incrementPc(); } } diff --git a/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.test.ts b/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.test.ts index adca61ef609..26e75171b65 100644 --- a/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.test.ts +++ b/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.test.ts @@ -1,6 +1,6 @@ import { strict as assert } from 'assert'; -import { Add, Address, Sub } from '../opcodes/index.js'; +import { Add, Address, Call, StaticCall, Sub } from '../opcodes/index.js'; import { BufferCursor } from './buffer_cursor.js'; import { InstructionSet, decodeFromBytecode, encodeToBytecode } from './bytecode_serialization.js'; import { Opcode } from './instruction_serialization.js'; @@ -75,6 +75,26 @@ describe('Bytecode Serialization', () => { new Add(/*indirect=*/ 0, /*inTag=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2), new Sub(/*indirect=*/ 0, /*inTag=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2), new Address(/*indirect=*/ 0, /*dstOffset=*/ 1), + new Call( + /*indirect=*/ 0x01, + /*gasOffset=*/ 0x12345678, + /*addrOffset=*/ 0xa2345678, + /*argsOffset=*/ 0xb2345678, + /*argsSize=*/ 0xc2345678, + /*retOffset=*/ 0xd2345678, + /*retSize=*/ 0xe2345678, + /*successOffset=*/ 0xf2345678, + ), + new StaticCall( + /*indirect=*/ 0x01, + /*gasOffset=*/ 0x12345678, + /*addrOffset=*/ 0xa2345678, + /*argsOffset=*/ 0xb2345678, + /*argsSize=*/ 0xc2345678, + /*retOffset=*/ 0xd2345678, + /*retSize=*/ 0xe2345678, + /*successOffset=*/ 0xf2345678, + ), ]; const bytecode = Buffer.concat(instructions.map(i => i.serialize())); @@ -88,6 +108,26 @@ describe('Bytecode Serialization', () => { new Add(/*indirect=*/ 0, /*inTag=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2), new Sub(/*indirect=*/ 0, /*inTag=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2), new Address(/*indirect=*/ 0, /*dstOffset=*/ 1), + new Call( + /*indirect=*/ 0x01, + /*gasOffset=*/ 0x12345678, + /*addrOffset=*/ 0xa2345678, + /*argsOffset=*/ 0xb2345678, + /*argsSize=*/ 0xc2345678, + /*retOffset=*/ 0xd2345678, + /*retSize=*/ 0xe2345678, + /*successOffset=*/ 0xf2345678, + ), + new StaticCall( + /*indirect=*/ 0x01, + /*gasOffset=*/ 0x12345678, + /*addrOffset=*/ 0xa2345678, + /*argsOffset=*/ 0xb2345678, + /*argsSize=*/ 0xc2345678, + /*retOffset=*/ 0xd2345678, + /*retSize=*/ 0xe2345678, + /*successOffset=*/ 0xf2345678, + ), ]; const actual = encodeToBytecode(instructions); diff --git a/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.ts b/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.ts index 8b200e5800c..1033cb5f785 100644 --- a/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.ts +++ b/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.ts @@ -3,7 +3,8 @@ import { Address, And, BlockNumber, - CMov, // Call, + CMov, + Call, CalldataCopy, Cast, ChainId, @@ -35,14 +36,15 @@ import { Sender, Set, Shl, - Shr, // StaticCall, + Shr, + StaticCall, StorageAddress, Sub, Timestamp, Version, Xor, } from '../opcodes/index.js'; -import { Instruction } from '../opcodes/instruction.js'; +import type { Instruction } from '../opcodes/index.js'; import { BufferCursor } from './buffer_cursor.js'; import { Opcode } from './instruction_serialization.js'; @@ -52,8 +54,10 @@ interface DeserializableInstruction { } export type InstructionSet = Map; -const INSTRUCTION_SET: InstructionSet = new Map( - [ +// TODO(4359): This is a function so that Call and StaticCall can be lazily resolved. +// This is a temporary solution until we solve the dependency cycle. +const INSTRUCTION_SET = () => + new Map([ [Add.opcode, Add], [Sub.opcode, Sub], [Mul.opcode, Mul], @@ -116,16 +120,15 @@ const INSTRUCTION_SET: InstructionSet = new Map