From 2b2c97f2397f74cb67dd7776337733b2f64ece16 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Tue, 9 Jan 2024 18:40:14 +0000 Subject: [PATCH 01/20] init --- .../acir-simulator/src/avm/avm.test.ts | 401 +++++++++++++ yarn-project/acir-simulator/src/avm/avm.ts | 541 ++++++++++++++++++ .../src/avm/fixtures/avm_examples.ts | 84 +++ yarn-project/acir-simulator/src/avm/index.ts | 1 + .../src/avm/interpreter/index.ts | 0 .../acir-simulator/src/avm/opcodes.ts | 124 ++++ .../src/avm/opcodes/arithmetic.ts | 0 .../acir-simulator/src/avm/opcodes/call.ts | 0 .../src/avm/opcodes/control_flow.ts | 0 .../acir-simulator/src/avm/opcodes/index.ts | 3 + 10 files changed, 1154 insertions(+) create mode 100644 yarn-project/acir-simulator/src/avm/avm.test.ts create mode 100644 yarn-project/acir-simulator/src/avm/avm.ts create mode 100644 yarn-project/acir-simulator/src/avm/fixtures/avm_examples.ts create mode 100644 yarn-project/acir-simulator/src/avm/index.ts create mode 100644 yarn-project/acir-simulator/src/avm/interpreter/index.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/call.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/index.ts diff --git a/yarn-project/acir-simulator/src/avm/avm.test.ts b/yarn-project/acir-simulator/src/avm/avm.test.ts new file mode 100644 index 000000000000..991b6297f496 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/avm.test.ts @@ -0,0 +1,401 @@ +import { + CallContext, + CircuitsWasm, + FunctionData, +} from '@aztec/circuits.js'; +import { pedersenPlookupCommitWithHashIndexPoint } from '@aztec/circuits.js/barretenberg'; +import { FunctionSelector } from '@aztec/foundation/abi'; +import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { EthAddress } from '@aztec/foundation/eth-address'; +import { Fr } from '@aztec/foundation/fields'; +import { + AvmTestContractArtifact, +} from '@aztec/noir-contracts/artifacts'; +import { createDebugLogger } from '@aztec/foundation/log'; + +import { MockProxy, mock } from 'jest-mock-extended'; + +import { PublicContractsDB, PublicStateDB } from '../public/db.js'; +import { PublicCall, PublicExecutionResult } from '../public/execution.js'; + +import { AVMExecutor } from './avm.js'; +import { + addBytecode, + storageBytecode, + nestedCallBytecode, +} from './fixtures/avm_examples.js'; + + +describe('ACIR public execution simulator', () => { + const log = createDebugLogger('aztec:acir-simulator:vm.test.ts'); + let publicState: MockProxy; + let publicContracts: MockProxy; + //let commitmentsDb: MockProxy; + //let blockData: HistoricBlockData; + let executor: AVMExecutor; // TODO: replace with AVMSimulator + let msgSender: AztecAddress; + + beforeEach(() => { + publicState = mock(); + publicContracts = mock(); + //commitmentsDb = mock(); + //blockData = HistoricBlockData.empty(); + executor = new AVMExecutor(publicState, publicContracts); + + msgSender = AztecAddress.random(); + }, 10000); + + async function simulateAndCheck( + calldata: Fr[], + expectReturn: Fr[], + bytecode: Buffer, + bytecodesForNestedCalls: Buffer[] = [], + ): Promise { + const contractAddress = AztecAddress.random(); + const functionSelector = new FunctionSelector(0x1234); + const functionData = new FunctionData(functionSelector, false, false, false); + const callContext = CallContext.from({ + msgSender, + storageContractAddress: contractAddress, + portalContractAddress: EthAddress.random(), + functionSelector: FunctionSelector.empty(), + isContractDeployment: false, + isDelegateCall: false, + isStaticCall: false, + }); + + publicContracts.getBytecode.mockResolvedValueOnce(bytecode); + for (let i = 0; i < bytecodesForNestedCalls.length; i++) { + publicContracts.getBytecode.mockResolvedValueOnce(bytecodesForNestedCalls[i]); + publicContracts.getPortalContractAddress.mockResolvedValueOnce(EthAddress.random()); + publicContracts.getIsInternal.mockResolvedValueOnce(false); + } + + const publicCall: PublicCall = { contractAddress, functionData, calldata, callContext }; + const result = await executor.simulate(publicCall); //, GlobalVariables.empty()); + + expect(result.returnValues.length).toEqual(expectReturn.length); + for (let i = 0; i < expectReturn.length; i++) { + expect(result.returnValues[i]).toEqual(new Fr(expectReturn[i])); + } + + return result; + } + + describe('Token contract', () => { + describe('AVM tests using bytecode constructed manually in-test', () => { + it('AVM can add arguments and return result', async () => { + // ADD 42 + 25 + // => 67 + const addArg0 = 42n; + const addArg1 = 25n; + const calldata = [addArg0, addArg1].map(arg => new Fr(arg)); + const returndata = [addArg0 + addArg1].map(arg => new Fr(arg)); + await simulateAndCheck(calldata, returndata, addBytecode); + }); + it('AVM storage operations work', async () => { + // ADD 42 + S[61] + // ADD 42 + 96 + // => 138 + const addArg0 = 42n; + const slotArg = 61n; + const startValueAtSlot = 96n; + const calldata = [addArg0, slotArg].map(arg => new Fr(arg)); + const ret = addArg0 + startValueAtSlot; + const returndata = [ret].map(arg => new Fr(arg)); + + //publicContracts.getBytecode.mockResolvedValue(storageBytecode); + + publicState.storageRead + .mockResolvedValueOnce(new Fr(startValueAtSlot)) // before sstore + .mockResolvedValueOnce(new Fr(ret)); // after sstore + + const result = await simulateAndCheck(calldata, returndata, storageBytecode); + + // VALIDATE STORAGE ACTION TRACE + // SLOAD is performed before SSTORE and after + expect(result.contractStorageReads).toEqual([ + { + storageSlot: new Fr(slotArg), + currentValue: new Fr(startValueAtSlot), + sideEffectCounter: 0, + }, + { + storageSlot: new Fr(slotArg), + currentValue: new Fr(ret), + sideEffectCounter: 2, + }, + ]); + // Confirm that ADD result was SSTOREd + expect(result.contractStorageUpdateRequests).toEqual([ + { + storageSlot: new Fr(slotArg), + oldValue: new Fr(startValueAtSlot), + newValue: new Fr(ret), + sideEffectCounter: 1, + }, + ]); + }); + it('AVM can perform nested calls', async () => { + // ADD 42 + 25 + // => 67 + const nestedCallAddress = 5678n; + const addArg0 = 42n; + const addArg1 = 25n; + const calldata = [nestedCallAddress, addArg0, addArg1].map(arg => new Fr(arg)); + const returndata = [addArg0 + addArg1].map(arg => new Fr(arg)); + + // top-level call (nestedCallBytecode) just makes a CALL to addBytecode + // which performs an ADD and returns the result + // pseudocode: + // fn foo(addr, a, b) { + // return addr::bar(a, b); + // } + // fn bar(a, b) { + // return a + b; + // } + await simulateAndCheck(calldata, returndata, nestedCallBytecode, [addBytecode]); + }); + }); + + describe('AVM tests that transpile Brillig bytecode to AVM before executing', () => { + it('Noir function that just does a basic add', async () => { + const addExampleArtifact = AvmTestContractArtifact.functions.find( + f => f.name === 'addExample', + )!; + const bytecode = Buffer.from(addExampleArtifact.bytecode, 'base64'); + + // ADD 42 + 25 + // => 67 + const addArg0 = 42n; + const addArg1 = 25n; + const calldata = [addArg0, addArg1].map(arg => new Fr(arg)); + const returndata = [addArg0 + addArg1].map(arg => new Fr(arg)); + await simulateAndCheck(calldata, returndata, bytecode); + }); + it('Noir function with slightly more complex arithmetic', async () => { + // ADD 42 + 25 + // => 67 + // ADD 67 + 67 + // => 137 + // SUB 137 - 30 + // => 107 + const arithmeticExample = AvmTestContractArtifact.functions.find( + f => f.name === 'arithmeticExample', + )!; + const bytecode = Buffer.from(arithmeticExample.bytecode, 'base64'); + + const addArg0 = 42n; + const addArg1 = 25n; + const subArg0 = 30n; + const calldata = [addArg0, addArg1, subArg0].map(arg => new Fr(arg)); + const returndata = [(addArg0 + addArg1) + (addArg0 + addArg1) - subArg0].map(arg => new Fr(arg)); + await simulateAndCheck(calldata, returndata, bytecode); + }); + + it('Noir function with storage actions', async () => { + const storageExample = AvmTestContractArtifact.functions.find( + f => f.name === 'storageExample', + )!; + const bytecode = Buffer.from(storageExample.bytecode, 'base64'); + + // ADD 42 + S[61] + // ADD 42 + 96 + // => 138 + const addArg0 = 42n; + const slotArg = 61n; + const startValueAtSlot = 96n; + const calldata = [addArg0, slotArg].map(arg => new Fr(arg)); + const ret = addArg0 + startValueAtSlot; + const returndata = [ret].map(arg => new Fr(arg)); + + publicState.storageRead + .mockResolvedValueOnce(new Fr(startValueAtSlot)) // before sstore + .mockResolvedValueOnce(new Fr(ret)); // after sstore + + const result = await simulateAndCheck(calldata, returndata, bytecode); + + // VALIDATE STORAGE ACTION TRACE + // SLOAD is performed before SSTORE and after + expect(result.contractStorageReads).toEqual([ + { + storageSlot: new Fr(slotArg), + currentValue: new Fr(startValueAtSlot), + sideEffectCounter: 0, + }, + { + storageSlot: new Fr(slotArg), + currentValue: new Fr(ret), + sideEffectCounter: 2, + }, + ]); + // Confirm that ADD result was SSTOREd + expect(result.contractStorageUpdateRequests).toEqual([ + { + storageSlot: new Fr(slotArg), + oldValue: new Fr(startValueAtSlot), + newValue: new Fr(ret), + sideEffectCounter: 1, + }, + ]); + }); + it('Noir function with a nested contract call', async () => { + const nestedCallExample = AvmTestContractArtifact.functions.find( + f => f.name === 'nestedCallExample', + )!; + const bytecode = Buffer.from(nestedCallExample.bytecode, 'base64'); + // ADD 42 + 25 + // => 67 + const nestedCallAddress = 5678n; + const addArg0 = 42n; + const addArg1 = 25n; + const calldata = [nestedCallAddress, addArg0, addArg1].map(arg => new Fr(arg)); + const returndata = [addArg0 + addArg1].map(arg => new Fr(arg)); + + // top-level call (nestedCallBytecode) just makes a CALL to addBytecode + // which performs an ADD and returns the result + // pseudocode: + // fn foo(addr, a, b) { + // return addr::bar(a, b); + // } + // fn bar(a, b) { + // return a + b; + // } + await simulateAndCheck(calldata, returndata, bytecode, [addBytecode]); + }); + it('Noir function with storage maps', async () => { + const balanceOfPublic = AvmTestContractArtifact.functions.find( + f => f.name === 'balance_of_public', + )!; + const bytecode = Buffer.from(balanceOfPublic.bytecode, 'base64'); + + const userAddress = AztecAddress.random(); + const slot = 6n; // slot used in Storage struct in Noir for public balances map + const startValueAtSlot = 96n; + const calldata = [userAddress.toField()].map(arg => new Fr(arg)); + const returndata = [startValueAtSlot].map(arg => new Fr(arg)); + + publicState.storageRead + .mockResolvedValueOnce(new Fr(startValueAtSlot)) // before sstore + + const result = await simulateAndCheck(calldata, returndata, bytecode); + + // TODO: test that the actual slot read from is correct! + // Must be sure that AVM's slot-computation is correct for storage maps. + const wasm = await CircuitsWasm.get(); + const hashIndex = 0; + const inputs = [new Fr(slot).toBuffer(), userAddress.toBuffer()]; + const outputBufs = pedersenPlookupCommitWithHashIndexPoint(wasm, inputs, hashIndex); + const mapKeySlot = Fr.fromBuffer(outputBufs[0]); + + // VALIDATE STORAGE ACTION TRACE + expect(result.contractStorageReads).toEqual([ + { + storageSlot: mapKeySlot, + currentValue: new Fr(startValueAtSlot), + sideEffectCounter: 0, + }, + ]); + }); + it('Noir function that accesses context variables/constants', async () => { + const contextVarsExample = AvmTestContractArtifact.functions.find( + f => f.name === 'context_vars_example', + )!; + const bytecode = Buffer.from(contextVarsExample.bytecode, 'base64'); + + const calldata: Fr[] = []; + const returndata = [msgSender.toField()]; + await simulateAndCheck(calldata, returndata, bytecode); + }); + it('Noir function to mint_public', async () => { + const mintPublic = AvmTestContractArtifact.functions.find( + f => f.name === 'mint_public', + )!; + const bytecode = Buffer.from(mintPublic.bytecode, 'base64'); + + const receiverAddress = AztecAddress.random(); + + // slots used in Storage struct in Noir + const mintersSlot = 2n; + const totalSupplySlot = 4n; + const publicBalancesSlot = 6n; + + const mintAmount = 42n; + const receiverStartBal = 96n; + const startTotalSupply = 123n; + const receiverEndBal = receiverStartBal + mintAmount; + const endTotalSupply = startTotalSupply + mintAmount; + + const calldata = [receiverAddress.toField(), new Fr(mintAmount)]; + const returndata = [new Fr(1)].map(arg => new Fr(arg)); // returns 1 for success + + publicState.storageRead + .mockResolvedValueOnce(new Fr(1)) // assert sender in minters map + .mockResolvedValueOnce(new Fr(receiverStartBal)) // receiver's original balance + .mockResolvedValueOnce(new Fr(startTotalSupply)); + + const result = await simulateAndCheck(calldata, returndata, bytecode); + + // TODO: test that the actual slot read from is correct! + // Must be sure that AVM's slot-computation is correct for storage maps. + const wasm = await CircuitsWasm.get(); + const hashIndex = 0; + // Compute minters slot for map key (msgSender address) + let inputs = [new Fr(mintersSlot).toBuffer(), msgSender.toBuffer()]; + let outputBufs = pedersenPlookupCommitWithHashIndexPoint(wasm, inputs, hashIndex); + const mintersKeySlot = Fr.fromBuffer(outputBufs[0]); + // Compute public balances slot for map key (receiver address) + inputs = [new Fr(publicBalancesSlot).toBuffer(), receiverAddress.toBuffer()]; + outputBufs = pedersenPlookupCommitWithHashIndexPoint(wasm, inputs, hashIndex); + const publicBalancesKeySlot = Fr.fromBuffer(outputBufs[0]); + + log(`expected/computed mintersKeySlot: ${mintersKeySlot}`); + log(`expected/computed publicBalancesKeySlot: ${publicBalancesKeySlot}`); + + // VALIDATE STORAGE ACTION TRACE + expect(result.contractStorageReads).toEqual([ + { + storageSlot: mintersKeySlot, + currentValue: new Fr(1), + sideEffectCounter: 0, + }, + { + storageSlot: publicBalancesKeySlot, + currentValue: new Fr(receiverStartBal), + sideEffectCounter: 1, + }, + { + storageSlot: new Fr(totalSupplySlot), + currentValue: new Fr(startTotalSupply), + sideEffectCounter: 2, + }, + ]); + // Confirm that balance and total supply were written properly + expect(result.contractStorageUpdateRequests).toEqual([ + { + storageSlot: publicBalancesKeySlot, + oldValue: new Fr(receiverStartBal), + newValue: new Fr(receiverEndBal), + sideEffectCounter: 3, + }, + { + storageSlot: new Fr(totalSupplySlot), + oldValue: new Fr(startTotalSupply), + newValue: new Fr(endTotalSupply), + sideEffectCounter: 4, + }, + ]); + }); + }); + + //describe('AVM tests including calls to C++ witness generation and/or proving', () => { + // it('should prove the public vm', async () => { + // //... + // const outAsmPath = await executor.bytecodeToPowdr(execution); + // await executor.generateWitness(outAsmPath); + // await executor.prove(); + // }, 1_000_000); + //}); + }); + // TODO: test field addition that could overflow (should just wrap around) +}); diff --git a/yarn-project/acir-simulator/src/avm/avm.ts b/yarn-project/acir-simulator/src/avm/avm.ts new file mode 100644 index 000000000000..b4be8d8a4bc4 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/avm.ts @@ -0,0 +1,541 @@ +import { AztecAddress, CallContext, CircuitsWasm, ContractStorageRead, ContractStorageUpdateRequest, EthAddress, Fr, FunctionData, FunctionSelector, GlobalVariables, HistoricBlockData } from '@aztec/circuits.js'; +import { AVMInstruction, Opcode, PC_MODIFIERS } from './opcodes.js'; +import { createDebugLogger } from '@aztec/foundation/log'; +import { PublicCall, PublicExecution, PublicExecutionResult } from '../public/execution.js'; +import { CommitmentsDB, PublicContractsDB, PublicStateDB } from '../public/db.js'; +import { FunctionL2Logs, PackedArguments } from '@aztec/types'; +import { ContractStorageActionsCollector } from '../public/state_actions.js'; +import { SideEffectCounter } from '../common/side_effect_counter.js'; +import { pedersenPlookupCommitWithHashIndexPoint } from '@aztec/circuits.js/barretenberg'; +import { keccak } from '@aztec/foundation/crypto'; +import { PublicExecutor } from '../index.js'; + +// TODO: figure out what info needs to go to witgen and prover, and what info +// is really just for the TS code to keep track of the entire TX/callstack + +/** + * VM object with top-level/tx-level state + * - VM can execute a TX which in turns executes each call + * VM call/context object with call-level state + * - call can execute + * + * There are a few different levels of state during execution: + * 1. Call-level state that is modified by each instruction + * 2. TX-level state that is modified by each call + * 3. Top-level state that is modified by each TX + * 4. Block-level state that remains constant for all TXs in a block + * + * Within call-level state, there are values that will remain constant + * for the duration of the call, and values that will be modified per-instruction + * + * CallContext is constant for a call. It is the context in which the + * call was triggered, but is not modified by the call. + */ + + +// Decisions +// - We want the avm context to + + +class AvmContext { + static readonly MEM_REGION_WORDS = 4096; // 2**22 ? + static readonly RETURN_BUFFER_WORDS = 128; + + private readonly calldata: Fr[]; + + public pc: number = 0; // TODO: should be u32 + public error: boolean = false; + public returned: boolean = false; + public call_stack: number[] = []; // TODO: should be u32[] + + //public l1GasUsed: number = 0; // or left? + //public l2GasUsed: number = 0; // or left? + /** Field memory region for this call */ + public fieldMemory: Fr[] = new Array(AVMCallState.MEM_REGION_WORDS).fill(Fr.ZERO); + /** Buffer to store returnData from nested calls */ + public returnBuffer: Fr[] = new Array(AVMCallState.RETURN_BUFFER_WORDS).fill(Fr.ZERO); + + constructor(calldata: Fr[]) { + this.calldata = calldata; + this.pc = 0; + } + + static public function new(bytecode: Fr[], calldata: Fr[]): AvmContext { + return new AvmContext(calldata); + } +} + + +class AVM { + private log = createDebugLogger('aztec:simulator:avm'); + + private state = new AVMCallState(); + private instructions: AVMInstruction[] = []; + + // Components of the partial witness (inputs to witness generation) + // collapsedStorageActions is used to retrieve latest value per slot + private collapsedStorageActions: ContractStorageActionsCollector; + private allStorageReads: ContractStorageRead[] = []; + private allStorageUpdates: ContractStorageUpdateRequest[] = []; + private nestedExecutions: PublicExecutionResult[] = []; + //private unencryptedLogs: UnencryptedL2Log[] = []; + // ^ these are computed gradually as instructions execute + + constructor( + private context: PublicCall, + private readonly sideEffectCounter: SideEffectCounter, + private readonly stateDb: PublicStateDB, + private readonly contractsDb: PublicContractsDB, + ) { + this.collapsedStorageActions = new ContractStorageActionsCollector(stateDb, context.contractAddress); + } + /** + * Must be called before AVM can be executed or simulated. + */ + async init(){ + this.instructions = await this.fetchAndDecodeBytecode(); + } + + /** + * Execute this call. + * Generate a partial witness. + */ + public async simulate(): Promise { + this.log(`Simulating the Aztec Public VM`); + + const returnValues = await this.simulateInternal(); + // Note: not collecting storage actions because we actually want the full uncollapsed lists of actions + //const [contractStorageReads, contractStorageUpdateRequests] = this.collapsedStorageActions.collect(); + + // just rename args to calldata... + const execution = { + contractAddress: this.context.contractAddress, + functionData: this.context.functionData, + args: this.context.calldata, // rename + callContext: this.context.callContext + }; + return { + execution, + newCommitments: [], + newL2ToL1Messages: [], + newNullifiers: [], + contractStorageReads: this.allStorageReads, + contractStorageUpdateRequests: this.allStorageUpdates, + returnValues: returnValues, + nestedExecutions: this.nestedExecutions, + unencryptedLogs: FunctionL2Logs.empty(), + }; + } + + /** + * Execute each instruction based on the program counter. + * End execution when the call errors or returns. + */ + private async simulateInternal(): Promise { + while(this.state.pc < this.instructions.length && !this.state.error && !this.state.returned) { + const returnData = await this.simulateNextInstruction(); + if (this.state.returned) { + return returnData; + } + if (this.state.error) { + throw new Error("Reverting is not yet supported in the AVM"); + } + } + throw new Error("Reached end of bytecode without RETURN or REVERT"); + } + + /** + * Execute the instruction at the current program counter. + */ + private async simulateNextInstruction(): Promise { + // TODO: check memory out of bounds + const instr = this.instructions[this.state.pc]; + this.log(`Executing instruction (pc:${this.state.pc}): ${Opcode[instr.opcode]}`); + switch (instr.opcode) { + ///////////////////////////////////////////////////////////////////////// + // Arithmetic + ///////////////////////////////////////////////////////////////////////// + case Opcode.ADD: { + // TODO: consider having a single case for all arithmetic operations and then applying the corresponding function to the args + // TODO: use actual field math + this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() + this.state.fieldMemory[instr.s1].toBigInt()) % Fr.MODULUS); + this.log(`ADD: M[${instr.d0}] = M[${instr.s0}] + M[${instr.s1}] % Fr.MODULUS`); + this.log(`ADD: M[${instr.d0}] = ${this.state.fieldMemory[instr.s0].toBigInt()} + ${this.state.fieldMemory[instr.s1].toBigInt()} = ${this.state.fieldMemory[instr.d0]}`); + break; + } + case Opcode.SUB: { + // TODO: use actual field math + this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() - this.state.fieldMemory[instr.s1].toBigInt()) % Fr.MODULUS); + break; + } + //TODO: case Opcode.MUL + case Opcode.DIV: { + // TODO: use actual field math + this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() / this.state.fieldMemory[instr.s1].toBigInt())); + break; + } + case Opcode.EQ: { + // TODO: use actual field math + this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() == this.state.fieldMemory[instr.s1].toBigInt())); + this.log(`EQ: M[${instr.d0}] = M[${instr.s0}] == M[${instr.s1}]`); + this.log(`EQ: M[${instr.d0}] = ${this.state.fieldMemory[instr.s0].toBigInt()} == ${this.state.fieldMemory[instr.s1].toBigInt()} = ${this.state.fieldMemory[instr.d0]}`); + break; + } + case Opcode.LT: { + // TODO: use actual field math + this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() < this.state.fieldMemory[instr.s1].toBigInt())); + break; + } + case Opcode.LTE: { + // TODO: use actual field math + this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() <= this.state.fieldMemory[instr.s1].toBigInt())); + break; + } + case Opcode.AND: { + // TODO: use actual field math + this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() & this.state.fieldMemory[instr.s1].toBigInt())); + break; + } + case Opcode.OR: { + // TODO: use actual field math + this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() | this.state.fieldMemory[instr.s1].toBigInt())); + break; + } + case Opcode.XOR: { + // TODO: use actual field math + this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() ^ this.state.fieldMemory[instr.s1].toBigInt())); + break; + } + case Opcode.NOT: { + // TODO: use actual field math + this.state.fieldMemory[instr.d0] = new Fr((~this.state.fieldMemory[instr.s0].toBigInt())); + break; + } + case Opcode.SHL: { + // TODO: use actual field math + this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() << this.state.fieldMemory[instr.s1].toBigInt())); + break; + } + case Opcode.SHR: { + // TODO: use actual field math + this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() >> this.state.fieldMemory[instr.s1].toBigInt())); + break; + } + ///////////////////////////////////////////////////////////////////////// + // Memory + ///////////////////////////////////////////////////////////////////////// + case Opcode.SET: { + // TODO: allow instr.s0 to be larger since SET accepts an immediate value + this.log(`SET: M[${instr.d0}] = ${instr.s0}`); + this.state.fieldMemory[instr.d0] = new Fr(instr.s0); + break; + } + case Opcode.MOV: { + // TODO: use u32 memory for addresses here + const srcAddr = instr.s0Indirect ? Number(this.state.fieldMemory[instr.s0].toBigInt()) : instr.s0; + const dstAddr = instr.d0Indirect ? Number(this.state.fieldMemory[instr.d0].toBigInt()) : instr.d0; + if (instr.s0Indirect) { + this.log(`MOV: source is indirect, so srcAddr is M[s0] = M[${instr.s0}] = ${srcAddr}`); + } + if (instr.d0Indirect) { + this.log(`MOV: destination is indirect, so dstAddr is M[d0] = M[${instr.d0}] = ${dstAddr}`); + } + this.log(`MOV: M[${dstAddr}] = M[${srcAddr}]`); + this.log(`MOV: M[${dstAddr}] = ${this.state.fieldMemory[srcAddr]}`); + this.state.fieldMemory[dstAddr] = this.state.fieldMemory[srcAddr]; + break; + } + // TODO: RETURNDATASIZE and RETURNDATACOPY + case Opcode.CALLDATASIZE: { + // TODO: dest should be u32 + this.log(`CALLDATASIZE: M[${instr.d0}] = ${this.context.calldata.length}`); + this.state.fieldMemory[instr.d0] = new Fr(this.context.calldata.length); + break; + } + case Opcode.CALLDATACOPY: { + /** + * Might be best if this opcode doesn't truly accept dynamic length (lookups will be a pain) + * Opcode can have a max copy size and compiler can break larger copies into smaller components + */ + // TODO: srcOffset and copySize should be u32s + const copySize = Number(this.state.fieldMemory[instr.s1].toBigInt()); + this.log(`CALLDATACOPY: M[${instr.d0}:${instr.d0+copySize}] = calldata[${instr.s0}:${instr.s0+copySize}]`); + //assert instr.s0 + copySize <= context.calldata.length; + //assert instr.d0 + copySize <= context.fieldMemory.length; + for (let i = 0; i < copySize; i++) { + this.log(`Copying calldata[${instr.s0+i}] (${this.context.calldata[instr.s0+i]}) to fieldMemory[${instr.d0+i}]`); + this.state.fieldMemory[instr.d0+i] = this.context.calldata[instr.s0+i]; + } + break; + } + ///////////////////////////////////////////////////////////////////////// + // Control flow + ///////////////////////////////////////////////////////////////////////// + case Opcode.JUMP: { + this.log(`JUMP: to pc:${instr.s0} from pc:${this.state.pc}`) + this.state.pc = instr.s0; + break; + } + case Opcode.JUMPI: { + this.log(`JUMPI: if (M[${instr.sd}]:${this.state.fieldMemory[instr.sd]}) to pc:${instr.s0} from pc:${this.state.pc}`) + this.state.pc = !this.state.fieldMemory[instr.sd].isZero() ? instr.s0 : this.state.pc + 1; + break; + } + case Opcode.INTERNALCALL: { + this.state.call_stack.push(this.state.pc + 1); + this.state.pc = instr.s0; + this.log(`INTERNALCALL: pushed pc:${this.state.call_stack.at(-1)} to call_stack and jumped to pc:${this.state.pc}`); + break; + } + case Opcode.INTERNALRETURN: { + if (this.state.call_stack.length === 0) { + throw new Error("INTERNALRETURN: call_stack is empty - nowhere to return to"); + } + this.state.pc = this.state.call_stack.pop()!; + break; + } + ///////////////////////////////////////////////////////////////////////// + // Storage + ///////////////////////////////////////////////////////////////////////// + case Opcode.SLOAD: { + // TODO: use u32 memory for storage slot + this.log(`SLOAD: M[${instr.d0}] = S[M[${instr.s0}]]`) + const storageSlot = this.state.fieldMemory[instr.s0]; + this.state.fieldMemory[instr.d0] = await this.sload(storageSlot); + this.log(`SLOAD value: ${this.state.fieldMemory[instr.d0]} (S[${storageSlot}])`) + break; + } + case Opcode.SSTORE: { + // TODO: use u32 memory for storage slot + this.log(`SSTORE: S[M[${instr.d0}]] = M[${instr.s0}]`) + const storageSlot = this.state.fieldMemory[instr.d0]; + const value = this.state.fieldMemory[instr.s0]; + this.log(`SSTORE: S[${storageSlot}] = ${value}`) + await this.sstore(storageSlot, value); + break; + } + case Opcode.RETURN: { + const retSizeFr = this.state.fieldMemory[instr.s1]; + const retSize = Number(retSizeFr.toBigInt()); + this.log(`RETURN: M[${instr.s0}:${instr.s0 + retSize}] (size: ${retSize})`); + //assert instr.s0 + retSize <= context.fieldMemory.length; + this.state.returned = true; + return this.state.fieldMemory.slice(instr.s0, instr.s0 + retSize); + } + case Opcode.REVERT: { + const retSizeFr = this.state.fieldMemory[instr.s1]; + const retSize = Number(retSizeFr.toBigInt()); + this.log.error(`REVERT M[${instr.s0}:${instr.s0 + retSize}] (size: ${retSize})`); + //assert instr.s0 + retSize <= context.fieldMemory.length; + this.state.error = true; + this.state.returned = true; + throw new Error(`AVM reverted execution in contract:${this.context.contractAddress}, function:${this.context.callContext.functionSelector}`); + // TODO: return data and allow caller to handle revert + //return this.state.fieldMemory.slice(instr.s0, instr.s0 + retSize); + } + ///////////////////////////////////////////////////////////////////////// + // Contract call control flow + ///////////////////////////////////////////////////////////////////////// + case Opcode.CALL: { + const gas = this.state.fieldMemory[instr.s0]; + const addrFr = this.state.fieldMemory[instr.s1]; + const targetContractAddress = AztecAddress.fromBigInt(addrFr.toBigInt()); + this.log(`CALL was allocated ${gas} gas to call contract:${targetContractAddress.toString()}`); + + // argsAndRetOffset = sd + // size of argsAndRetOffset is 4: + // - argsOffset: M[sd] + // - argsSize: M[sd + 1] + // - retOffset: M[sd + 2] + // - retSize: M[sd + 3] + // TODO: use u32 memory for offsets and sizes + this.log(`CALL: sd=${instr.sd}, M[sd]=${this.state.fieldMemory[instr.sd]}`); + const argsAndRetOffset = Number(this.state.fieldMemory[instr.sd].toBigInt()); + const argsOffset = Number(this.state.fieldMemory[argsAndRetOffset].toBigInt()); + const argsSize = Number(this.state.fieldMemory[argsAndRetOffset + 1].toBigInt()); + //const argsSize = argsAndRetOffset + 1; + //const argsSize = 2; + const retOffset = Number(this.state.fieldMemory[argsAndRetOffset + 2].toBigInt()); + const retSize = Number(this.state.fieldMemory[argsAndRetOffset + 3].toBigInt()); + //const argsOffset = Number(this.state.fieldMemory[argsAndRetOffset].toBigInt()); + //const argsSize = Number(this.state.fieldMemory[argsAndRetOffset + 1].toBigInt()); + //const retOffset = Number(this.state.fieldMemory[argsAndRetOffset + 2].toBigInt()); + //const retSize = Number(this.state.fieldMemory[argsAndRetOffset + 3].toBigInt()); + //const argsOffset = Number(this.state.fieldMemory[instr.sd].toBigInt()); + //const argsSize = Number(this.state.fieldMemory[instr.sd + 1].toBigInt()); + //const retOffset = Number(this.state.fieldMemory[instr.sd + 2].toBigInt()); + //const retSize = Number(this.state.fieldMemory[instr.sd + 3].toBigInt()); + this.log(`CALL: argsOffset=${argsOffset} argsSize=${argsSize} retOffset=${retOffset} retSize=${retSize}`); + + const calldata = this.state.fieldMemory.slice(argsOffset, argsOffset + argsSize); + // For now, extract functionSelector here. + // TODO: eventually functionSelector can become a use-case of calldata as in EVM. + // FIXME: calldata[0] could be larger than 4-byte function selector! + // FIXME this is not function selector in vm.test.ts examples! + const functionSelector = new FunctionSelector(Number(calldata[0].toBigInt())); + this.log(`Nested call in AVM: addr=${targetContractAddress} selector=${functionSelector}`); + for (let i = 0; i < calldata.length; i++) { + this.log(`\tInitializing nested calldata[${i}] = ${calldata[i]}`) + } + + const portalAddress = (await this.contractsDb.getPortalContractAddress(targetContractAddress)) ?? EthAddress.ZERO; + const isInternal = await this.contractsDb.getIsInternal(targetContractAddress, functionSelector); + if (isInternal === undefined) { + throw new Error(`ERR: Method not found - ${targetContractAddress.toString()}:${functionSelector.toString()}`); + } + + const functionData = new FunctionData(functionSelector, isInternal, false, false); + const nestedCallContext = CallContext.from({ + msgSender: this.context.contractAddress, + portalContractAddress: portalAddress, + storageContractAddress: targetContractAddress, + functionSelector, + isContractDeployment: false, + isDelegateCall: false, + isStaticCall: false, + }); + const nestedContext = { + contractAddress:targetContractAddress, + functionData, + calldata: calldata, + callContext: nestedCallContext, + }; + const nestedAVM = new AVM( + nestedContext, + this.sideEffectCounter, + this.stateDb, + this.contractsDb, + ); + await nestedAVM.init(); + const childExecutionResult = await nestedAVM.simulate(); + this.nestedExecutions.push(childExecutionResult); + this.log(`Returning from nested call: ret=${childExecutionResult.returnValues.join(', ')}`); + + // When retSize is provided (known at time of CALL), write return values to memory at provided offset. + // Otherwise, write return values to start of returnBuffer. + if (retSize > 0) { + this.state.fieldMemory.splice(retOffset, retSize, ...childExecutionResult.returnValues); + } else { + this.state.returnBuffer.splice(0, retSize, ...childExecutionResult.returnValues); + } + break; + } + ///////////////////////////////////////////////////////////////////////// + // Call context operations + ///////////////////////////////////////////////////////////////////////// + case Opcode.SENDER: { + this.state.fieldMemory[instr.d0] = this.context.callContext.msgSender.toField(); + break; + } + case Opcode.ADDRESS: { + this.state.fieldMemory[instr.d0] = this.context.callContext.storageContractAddress.toField(); + break; + } + case Opcode.SELECTOR: { + this.state.fieldMemory[instr.d0] = this.context.callContext.functionSelector.toField(); + break; + } + case Opcode.ARGSHASH: { + const wasm = await CircuitsWasm.get(); + const hash = (await PackedArguments.fromArgs(this.context.calldata, wasm)).hash.value; + this.state.fieldMemory[instr.d0] = new Fr(hash); + break; + } + ///////////////////////////////////////////////////////////////////////// + // Blackbox/nasty operations + ///////////////////////////////////////////////////////////////////////// + case Opcode.PEDERSEN: { + const wasm = await CircuitsWasm.get(); + + const domainSeparator = this.state.fieldMemory[instr.s0]; + const argsOffset = Number(this.state.fieldMemory[instr.s1].toBigInt()); + const argsSize = Number(this.state.fieldMemory[instr.sd].toBigInt()); + const retOffset = Number(this.state.fieldMemory[instr.d0].toBigInt()); + const retSize = 2; + this.log(`PEDERSEN: argsOffset=${argsOffset} argsSize=${argsSize} retOffset=${retOffset} retSize=${retSize}`); + + const hashIndex = Number(domainSeparator.toBigInt()); + const inputs = this.state.fieldMemory.slice(argsOffset, argsOffset + argsSize); + const inputBufs = inputs.map(field => field.toBuffer()); + this.log(`PEDERSEN: inputs=[${inputs.join(', ')}], hash_index=${hashIndex}`); + const outputBufs = pedersenPlookupCommitWithHashIndexPoint(wasm, inputBufs, hashIndex); + const output = outputBufs.map(buf => Fr.fromBuffer(buf)); + this.log(`PEDERSEN: output=[${output.join(', ')}]`); + + this.state.fieldMemory.splice(retOffset, retSize, ...output); + break; + } + case Opcode.KECCAK256: { + // FIXME: not working!!!! this is unfinished + // FIXME: need to properly handle byte-array inputs and outputs! + const argsOffset = Number(this.state.fieldMemory[instr.s1].toBigInt()); + const argsSize = Number(this.state.fieldMemory[instr.sd].toBigInt()); + const retOffset = Number(this.state.fieldMemory[instr.d0].toBigInt()); + const retSize = 1; + this.log(`KECCAK256: argsOffset=${argsOffset} argsSize=${argsSize} retOffset=${retOffset} retSize=${retSize}`); + + const inputs = this.state.fieldMemory.slice(argsOffset, argsOffset + argsSize); + const inputBufs = inputs.map(field => field.toBuffer()); + this.log(`KECCAK256: inputs=[${inputs.join(', ')}]`); + const inputCattedBuf = Buffer.concat(inputBufs); + const outputBuf = keccak(inputCattedBuf); + const output = Fr.fromBuffer(outputBuf); + //this.log(`KECCAK256: output=[${output.join(', ')}]`); + this.log(`KECCAK256: output=${output}`); + + //this.state.fieldMemory.splice(retOffset, retSize, ...output); + this.state.fieldMemory[retOffset] = output; + break; + } + default: throw new Error(`AVM does not know how to process opcode ${Opcode[instr.opcode]} (aka ${instr.opcode}) at pc: ${this.state.pc}`); + } + if (!PC_MODIFIERS.includes(instr.opcode)) { + this.state.pc++; + } + return []; + } + + /** + * Read a public storage word. + * @param storageSlot - The starting storage slot. + * @returns value - The value read from the storage slot. + */ + private async sload(storageSlot: Fr): Promise { + const sideEffectCounter = this.sideEffectCounter.count(); + const value = await this.collapsedStorageActions.read(storageSlot, sideEffectCounter); + this.allStorageReads.push(new ContractStorageRead(storageSlot, value, sideEffectCounter)); + this.log(`Oracle storage read: slot=${storageSlot.toString()} value=${value.toString()}`); + return value; + } + /** + * Write a word to public storage. + * @param storageSlot - The storage slot. + * @param value - The value to be written. + */ + private async sstore(storageSlot: Fr, value: Fr) { + const sideEffectCounter = this.sideEffectCounter.count(); + const oldValue = await this.collapsedStorageActions.peek(storageSlot); + await this.collapsedStorageActions.write(storageSlot, value, sideEffectCounter); + this.allStorageUpdates.push(new ContractStorageUpdateRequest(storageSlot, oldValue, value, sideEffectCounter)); + await this.stateDb.storageWrite(this.context.contractAddress, storageSlot, value); + this.log(`Oracle storage write: slot=${storageSlot.toString()} value=${value.toString()}`); + } + + private async fetchAndDecodeBytecode(): Promise { + this.log(`Fetching and decoding bytecode for ${this.context.contractAddress}:${this.context.functionData.selector}`); + const bytecode = await this.contractsDb.getBytecode(this.context.contractAddress, this.context.functionData.selector); + if (!bytecode) throw new Error(`Bytecode not found for ${this.context.contractAddress}:${this.context.functionData.selector}`); + // TODO: consider decoding instructions individually as they are simulated + return AVMInstruction.fromBytecodeBuffer(bytecode); + } +} + +// public async generateWitness(outAsmPath: string) { +// await tryExec(`cd ../../barretenberg/cpp/ && ${POWDR_BINDIR}/powdr pil ${outAsmPath} --field bn254 --force`); +// } +// public async prove() { +// const log = createDebugLogger('aztec:simulator:public_vm_prove'); +// log(`Proving public vm`); +// +// await tryExec('cd ../../barretenberg/cpp/build/ && ./bin/publicvm_cli dummy-path'); +// } \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/fixtures/avm_examples.ts b/yarn-project/acir-simulator/src/avm/fixtures/avm_examples.ts new file mode 100644 index 000000000000..bdc152fb9824 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/fixtures/avm_examples.ts @@ -0,0 +1,84 @@ +import { AVMInstruction, Opcode } from '../opcodes.js'; + + +// function addExample(addArg0, addArg1) { +// return addArg0 + addArg1; +//} +const addExample = [ + // Get calldata size and store at M[0] + new AVMInstruction(Opcode.CALLDATASIZE, 0, 0, 0, 0), // SET M[0] = CD.length + // Copy calldata to memory starting at M[1] + new AVMInstruction(Opcode.CALLDATACOPY, 1, 0, 0, 0), // M[1:1+M[0]] = calldata[0+M[0]]); + // Add args and store at M[10] + new AVMInstruction(Opcode.ADD, 10, 0, 1, 2), // M[10] = M[1] + M[2] + // set return size to 1 + new AVMInstruction(Opcode.SET, 20, 0, 1, 0), // SET M[20] = 1 + new AVMInstruction(Opcode.RETURN, 0, 0, 10, 20), // return M[10] +]; +export const addBytecode = AVMInstruction.toBytecode(addExample); + +// function storageExample(addArg0, slotArg) { +// S[slotArg] = addArg0 + S[slotArg]; +// return S[slotArg]; +//} +const storageExample = [ + // Get calldata size and store at M[0] + new AVMInstruction(Opcode.CALLDATASIZE, 0, 0, 0, 0), // SET M[0] = CD.length + // Copy calldata to memory starting at M[1] + new AVMInstruction(Opcode.CALLDATACOPY, 1, 0, 0, 0), // M[1:1+M[0]] = calldata[0+M[0]]); + // Arg1 species storage slot to load from (S[M[2]]) + // load it into M[3] + new AVMInstruction(Opcode.SLOAD, 3, 0, 2, 0), // M[3] = S[M[2]] + // Add arg0 to value loaded from storage. Store result to memory at M[10]. + new AVMInstruction(Opcode.ADD, 10, 0, 1, 3), // M[10] = M[1] + M[2] + // store results of ADD to the same storage slot S[M[2]] + new AVMInstruction(Opcode.SSTORE, 2, 0, 10, 0), // S[M[2]] = M[10] + // load the same word from storage (S[M[2]]) that was just written. + // store word to memory (M[4]) + // (should now have value stored above) + new AVMInstruction(Opcode.SLOAD, 4, 0, 2, 0), // M[4] = S[M[2]] + // set return size to 1 + new AVMInstruction(Opcode.SET, 20, 0, 1, 0), // SET M[20] = 1 + // return the word loaded from storage (should match ADD output) + new AVMInstruction(Opcode.RETURN, 0, 0, 4, 20), // return M[4] +]; +export const storageBytecode = AVMInstruction.toBytecode(storageExample); + +// Make nested call to the specified address with some args +// return nested call results +//function nestedCallExample(targetAddr, [nestedCallArgs]) { +// gas = 1234 +// return CALL(gas, targetAddr, nestedCallArgs) +//} +const nestedCallExample = [ + // Get calldata size and store at M[0] + new AVMInstruction(Opcode.CALLDATASIZE, 0, 0, 0, 0), // SET M[0] = CD.length + // Copy calldata to memory starting at M[1] + new AVMInstruction(Opcode.CALLDATACOPY, 1, 0, 0, 0), // M[1:1+M[0]] = calldata[0+M[0]]); + // gas limit for CALL + new AVMInstruction(Opcode.SET, 100, 0, 1234, 0), // SET M[100] = 1234 + // Populate M[10,11,12,13] with the argsOffset, argsSize, retOffset, retSize for CALL + // argsOffset for CALL: M[2] because M[0] is calldatasize and M[1] is targetAddress (arg0 to top call) + new AVMInstruction(Opcode.SET, 10, 0, 2, 0), // SET M[10] = 2 (points to second arg) + // const 1 for subtraction below + new AVMInstruction(Opcode.SET, 20, 0, 1, 0), // SET M[10] = 1 + // argsSize for CALL: CALLDATASIZE - 1 + // - args/calldata for nested call is pretty much the same as this call's calldata + // but we don't forward the nested call address + new AVMInstruction(Opcode.SUB, 11, 0, 0, 20), // SET M[11] = M[0] - 1 + // ReturnData will be one word and will be placed at M[200] + // retOffset for CALL: where will returnData go + new AVMInstruction(Opcode.SET, 12, 0, 200, 0), // SET M[12] = M[200] + // retSize for CALL: just one return field + new AVMInstruction(Opcode.SET, 13, 0, 1, 0), // SET M[13] = 1 + // register 14 will contain the argsAndRetOffset (points to address 10 where M[10,11,12,13] contain argsOffset, argsSize, retOffset, retSize) + new AVMInstruction(Opcode.SET, 14, 0, 10, 0), // SET M[14] = 10 + // Make a nested CALL with: + // - gas: M[100] (1234) + // - targetAddress: M[1] + // - argsAndRetOffset: M[14] (10 which points to M[10,11,12,13] containing argsOffset, argsSize, retOffset, retSize + new AVMInstruction(Opcode.CALL, 0, 14, 100, 1), + // TODO: add support for RETURNDATASIZE/COPY + new AVMInstruction(Opcode.RETURN, 0, 0, 200, 13), // return M[200] (size 1 from M[13]) +]; +export const nestedCallBytecode = AVMInstruction.toBytecode(nestedCallExample); diff --git a/yarn-project/acir-simulator/src/avm/index.ts b/yarn-project/acir-simulator/src/avm/index.ts new file mode 100644 index 000000000000..749aaa14cbd1 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/index.ts @@ -0,0 +1 @@ +export { AVMExecutor } from './avm.js'; \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/interpreter/index.ts b/yarn-project/acir-simulator/src/avm/interpreter/index.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/yarn-project/acir-simulator/src/avm/opcodes.ts b/yarn-project/acir-simulator/src/avm/opcodes.ts new file mode 100644 index 000000000000..1970c0a31ab0 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes.ts @@ -0,0 +1,124 @@ +import { createDebugLogger } from "@aztec/foundation/log"; + +export enum Opcode { + // Arithmetic + ADD, + SUB, + MUL, + DIV, + EQ, + LT, + LTE, + AND, + OR, + XOR, + NOT, + SHL, + SHR, + // Memory + SET, + MOV, + CALLDATASIZE, + CALLDATACOPY, + // Control flow + JUMP, + JUMPI, + INTERNALCALL, + INTERNALRETURN, + // Storage + SLOAD, + SSTORE, + // Contract call control flow + RETURN, + REVERT, + CALL, + // Call context + SENDER, + ADDRESS, + SELECTOR, + ARGSHASH, + // Blackbox ops + PEDERSEN, + KECCAK256, +} + +export const PC_MODIFIERS = [ Opcode.JUMP, Opcode.JUMPI, Opcode.INTERNALCALL, Opcode.INTERNALRETURN ]; + +export class AVMInstruction { + /** Size of an instruction */ + public static readonly BYTELEN = 1+4+4+4+4+1+1; + + constructor( + public opcode: Opcode, + public d0: number, + public sd: number, + public s0: number, + public s1: number, + public d0Indirect: boolean = false, + public s0Indirect: boolean = false, + ) {} + + //public toBuffer(offset: number = 0): Buffer { + // const buf = Buffer.alloc(AVMInstruction.BYTELEN); + // this.intoBuffer(buf, offset); + // return buf + //} + + private intoBuffer(buf: Buffer, offset: number = 0) { + buf.writeUInt8(this.opcode, offset); + offset += 1; + buf.writeUInt32BE(this.d0, offset); + offset += 4; + buf.writeUInt32BE(this.sd, offset); + offset += 4; + buf.writeUInt32BE(this.s0, offset); + offset += 4; + buf.writeUInt32BE(this.s1, offset); + offset += 4; + buf.writeUInt8(Number(this.d0Indirect), offset); + offset += 1; + buf.writeUInt8(Number(this.s0Indirect), offset); + offset += 1; + } + + public static fromBuffer(buf: Buffer, offset: number = 0): AVMInstruction { + const log = createDebugLogger('aztec:simulator:avm_instructions'); + const opcode = buf.readUInt8(offset); + offset += 1; + const d0 = buf.readUInt32BE(offset); // d0 + offset += 4; + const sd = buf.readUInt32BE(offset); // sd + offset += 4; + const s0 = buf.readUInt32BE(offset); // s0 + offset += 4; + const s1 = buf.readUInt32BE(offset); // s1 + offset += 4; + const d0Indirect = Boolean(buf.readUInt8(offset)); + offset += 1; + const s0Indirect = Boolean(buf.readUInt8(offset)); + offset += 1; + log(`Instruction from buffer: opcode:${opcode} d0:${d0} sd:${sd} s0:${s0} s1:${s1}`); + return new AVMInstruction(opcode, d0, sd, s0, s1, d0Indirect, s0Indirect); + } + + public static fromBytecodeBuffer(buf: Buffer): AVMInstruction[] { + const log = createDebugLogger('aztec:simulator:avm_instructions'); + if (buf.length % AVMInstruction.BYTELEN !== 0) throw new Error(`Invalid bytecode length`); + const numInstructions = buf.length / AVMInstruction.BYTELEN; + const instructions: AVMInstruction[] = []; + for (let pc = 0; pc < numInstructions; pc++) { + const instr = AVMInstruction.fromBuffer(buf, pc * AVMInstruction.BYTELEN) + log(`Decoded instruction (pc:${pc}): ${Opcode[instr.opcode]}`); + instructions.push(instr); + } + return instructions; + } + + public static toBytecode(instructions: AVMInstruction[]): Buffer { + const buf = Buffer.alloc(AVMInstruction.BYTELEN * instructions.length); + for (let i = 0; i < instructions.length; i++) { + instructions[i].intoBuffer(buf, i * AVMInstruction.BYTELEN); + } + return buf; + } +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/yarn-project/acir-simulator/src/avm/opcodes/call.ts b/yarn-project/acir-simulator/src/avm/opcodes/call.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/yarn-project/acir-simulator/src/avm/opcodes/index.ts b/yarn-project/acir-simulator/src/avm/opcodes/index.ts new file mode 100644 index 000000000000..d96e3a472e5f --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/index.ts @@ -0,0 +1,3 @@ +export * from "./arithmetic.js"; +export * from "./control_flow.js"; +export * from "./call.js"; \ No newline at end of file From d2e8fabcb8791b8e232bf948423e88d4ef2f56db Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:50:31 +0000 Subject: [PATCH 02/20] fix: update missing mul instruction --- .../docs/public-vm/gen/_InstructionSet.mdx | 204 +++++++++--------- .../InstructionSet/InstructionSet.js | 40 ++-- 2 files changed, 124 insertions(+), 120 deletions(-) diff --git a/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx b/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx index 60047658fa79..c362c88b8406 100644 --- a/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx +++ b/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx @@ -30,7 +30,15 @@ Click on an instruction name to jump to its section. } - 0x02 [`DIV`](#isa-section-div) + 0x02 [`MUL`](#isa-section-mul) + Subtraction (a - b) + 128 + { + `M[dstOffset] = M[aOffset] * M[bOffset] mod 2^k` + } + + + 0x03 [`DIV`](#isa-section-div) Unsigned division (a / b) 128 { @@ -38,7 +46,7 @@ Click on an instruction name to jump to its section. } - 0x03 [`EQ`](#isa-section-eq) + 0x04 [`EQ`](#isa-section-eq) Equality check (a == b) 128 { @@ -46,7 +54,7 @@ Click on an instruction name to jump to its section. } - 0x04 [`LT`](#isa-section-lt) + 0x05 [`LT`](#isa-section-lt) Less-than check (a < b) 128 { @@ -54,7 +62,7 @@ Click on an instruction name to jump to its section. } - 0x05 [`LTE`](#isa-section-lte) + 0x06 [`LTE`](#isa-section-lte) Less-than-or-equals check (a <= b) 128 { @@ -62,7 +70,7 @@ Click on an instruction name to jump to its section. } - 0x06 [`AND`](#isa-section-and) + 0x07 [`AND`](#isa-section-and) Bitwise AND (a & b) 128 { @@ -70,7 +78,7 @@ Click on an instruction name to jump to its section. } - 0x07 [`OR`](#isa-section-or) + 0x08 [`OR`](#isa-section-or) Bitwise OR (a | b) 128 { @@ -78,7 +86,7 @@ Click on an instruction name to jump to its section. } - 0x08 [`XOR`](#isa-section-xor) + 0x09 [`XOR`](#isa-section-xor) Bitwise XOR (a ^ b) 128 { @@ -86,7 +94,7 @@ Click on an instruction name to jump to its section. } - 0x09 [`NOT`](#isa-section-not) + 0x0a [`NOT`](#isa-section-not) Bitwise NOT (inversion) 96 { @@ -94,7 +102,7 @@ Click on an instruction name to jump to its section. } - 0x0a [`SHL`](#isa-section-shl) + 0x0b [`SHL`](#isa-section-shl) Bitwise leftward shift (a << b) 128 { @@ -102,7 +110,7 @@ Click on an instruction name to jump to its section. } - 0x0b [`SHR`](#isa-section-shr) + 0x0c [`SHR`](#isa-section-shr) Bitwise rightward shift (a >> b) 128 { @@ -110,7 +118,7 @@ Click on an instruction name to jump to its section. } - 0x0c [`CAST`](#isa-section-cast) + 0x0d [`CAST`](#isa-section-cast) Type cast 96 { @@ -118,7 +126,7 @@ Click on an instruction name to jump to its section. } - 0x0d [`SET`](#isa-section-set) + 0x0e [`SET`](#isa-section-set) Set a memory word from a constant in the bytecode. 64+N { @@ -126,7 +134,7 @@ Click on an instruction name to jump to its section. } - 0x0e [`MOV`](#isa-section-mov) + 0x0f [`MOV`](#isa-section-mov) Move a word from source memory location to destination`. 88 { @@ -134,7 +142,7 @@ Click on an instruction name to jump to its section. } - 0x0f [`CMOV`](#isa-section-cmov) + 0x10 [`CMOV`](#isa-section-cmov) Move a word (conditionally chosen) from one memory location to another (`d = cond > 0 ? a : b`). 152 { @@ -142,7 +150,7 @@ Click on an instruction name to jump to its section. } - 0x10 [`CALLDATACOPY`](#isa-section-calldatacopy) + 0x11 [`CALLDATACOPY`](#isa-section-calldatacopy) Copy calldata into memory. 120 { @@ -150,7 +158,7 @@ Click on an instruction name to jump to its section. } - 0x11 [`SLOAD`](#isa-section-sload) + 0x12 [`SLOAD`](#isa-section-sload) Load a word from storage. 88 { @@ -158,7 +166,7 @@ Click on an instruction name to jump to its section. } - 0x12 [`SSTORE`](#isa-section-sstore) + 0x13 [`SSTORE`](#isa-section-sstore) Write a word to storage. 88 { @@ -166,25 +174,25 @@ Click on an instruction name to jump to its section. } - 0x13 [`EMITNOTEHASH`](#isa-section-emitnotehash) + 0x14 [`EMITNOTEHASH`](#isa-section-emitnotehash) Emit a new note hash to be inserted into the notes tree 56 emitNoteHash(M[contentOffset]) - 0x14 [`EMITNULLIFIER`](#isa-section-emitnullifier) + 0x15 [`EMITNULLIFIER`](#isa-section-emitnullifier) Emit a new nullifier to be inserted into the nullifier tree 56 emitNullifier(M[nullifierOffset]) - 0x15 [`SENDL2TOL1MSG`](#isa-section-sendl2tol1msg) + 0x16 [`SENDL2TOL1MSG`](#isa-section-sendl2tol1msg) Send an L2-to-L1 message 56 sendL2ToL1Message(M[contentOffset]) - 0x16 [`JUMP`](#isa-section-jump) + 0x17 [`JUMP`](#isa-section-jump) Jump to a location in the bytecode. 48 { @@ -192,7 +200,7 @@ Click on an instruction name to jump to its section. } - 0x17 [`JUMPI`](#isa-section-jumpi) + 0x18 [`JUMPI`](#isa-section-jumpi) Conditionally jump to a location in the bytecode. 88 { @@ -200,7 +208,7 @@ Click on an instruction name to jump to its section. } - 0x18 [`RETURN`](#isa-section-return) + 0x19 [`RETURN`](#isa-section-return) Halt execution with `success`, optionally returning some data. 88 { @@ -208,7 +216,7 @@ Click on an instruction name to jump to its section. } - 0x19 [`REVERT`](#isa-section-revert) + 0x1a [`REVERT`](#isa-section-revert) Halt execution with `failure`, reverting state changes and optionally returning some data. 88 { @@ -216,7 +224,7 @@ Click on an instruction name to jump to its section. } - 0x1a [`CALL`](#isa-section-call) + 0x1b [`CALL`](#isa-section-call) Call into another contract. 248 @@ -227,7 +235,7 @@ Click on an instruction name to jump to its section. - 0x1b [`STATICCALL`](#isa-section-staticcall) + 0x1c [`STATICCALL`](#isa-section-staticcall) Call into another contract, disallowing persistent state modifications. 248 @@ -238,7 +246,7 @@ Click on an instruction name to jump to its section. - 0x1c [`ULOG`](#isa-section-ulog) + 0x1d [`ULOG`](#isa-section-ulog) Emit an unencrypted log with data from the `field` memory page 88 { @@ -246,7 +254,7 @@ Click on an instruction name to jump to its section. } - 0x1d [`CHAINID`](#isa-section-chainid) + 0x1e [`CHAINID`](#isa-section-chainid) Get this rollup's L1 chain ID 56 { @@ -254,7 +262,7 @@ Click on an instruction name to jump to its section. } - 0x1e [`VERSION`](#isa-section-version) + 0x1f [`VERSION`](#isa-section-version) Get this rollup's L2 version ID 56 { @@ -262,7 +270,7 @@ Click on an instruction name to jump to its section. } - 0x1f [`BLOCKNUMBER`](#isa-section-blocknumber) + 0x20 [`BLOCKNUMBER`](#isa-section-blocknumber) Get this block's number 56 { @@ -270,7 +278,7 @@ Click on an instruction name to jump to its section. } - 0x20 [`TIMESTAMP`](#isa-section-timestamp) + 0x21 [`TIMESTAMP`](#isa-section-timestamp) Get this L2 block's timestamp 56 { @@ -278,7 +286,7 @@ Click on an instruction name to jump to its section. } - 0x21 [`COINBASE`](#isa-section-coinbase) + 0x22 [`COINBASE`](#isa-section-coinbase) Get the block's beneficiary address 56 { @@ -286,7 +294,7 @@ Click on an instruction name to jump to its section. } - 0x22 [`BLOCKL1GASLIMIT`](#isa-section-blockl1gaslimit) + 0x23 [`BLOCKL1GASLIMIT`](#isa-section-blockl1gaslimit) Total amount of "L1 gas" that a block can consume 56 { @@ -294,7 +302,7 @@ Click on an instruction name to jump to its section. } - 0x23 [`BLOCKL2GASLIMIT`](#isa-section-blockl2gaslimit) + 0x24 [`BLOCKL2GASLIMIT`](#isa-section-blockl2gaslimit) Total amount of "L2 gas" that a block can consume 56 { @@ -302,7 +310,7 @@ Click on an instruction name to jump to its section. } - 0x24 [`NOTESROOT`](#isa-section-notesroot) + 0x25 [`NOTESROOT`](#isa-section-notesroot) Get the historical note-hash tree root as of the specified block number. 88 { @@ -310,7 +318,7 @@ Click on an instruction name to jump to its section. } - 0x25 [`NULLIFIERSROOT`](#isa-section-nullroot) + 0x26 [`NULLIFIERSROOT`](#isa-section-nullroot) Get the historical nullifier tree root as of the specified block number. 88 { @@ -318,7 +326,7 @@ Click on an instruction name to jump to its section. } - 0x26 [`CONTRACTSROOT`](#isa-section-contractsroot) + 0x27 [`CONTRACTSROOT`](#isa-section-contractsroot) Get the historical contracts tree root as of the specified block number. 88 { @@ -326,21 +334,13 @@ Click on an instruction name to jump to its section. } - 0x27 [`MSGSROOT`](#isa-section-msgsroot) + 0x28 [`MSGSROOT`](#isa-section-msgsroot) Get the historical l1-to-l2 message tree root as of the specified block number. 88 { `M[dstOffset] = HistoricalBlockData[M[blockNumOffset]].l1_to_l2_message_tree_root` } - - 0x28 [`NOTESROOT`](#isa-section-notesroot) - Get the historical note-hash tree root as of the specified block number. - 88 - { - `M[dstOffset] = HistoricalBlockData[M[blockNumOffset]].note_hash_tree_root` - } - 0x29 [`PUBLICDATAROOT`](#isa-section-publicdataroot) Get the historical public data tree root as of the specified block number. @@ -498,7 +498,26 @@ Subtraction (a - b) [![](./images/bit-formats/SUB.png)](./images/bit-formats/SUB.png) -### `DIV` (0x02) +### `MUL` (0x02) +Subtraction (a - b) + +[See in table.](#isa-table-mul) + +- **Category**: arithmetic +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. 0th bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **in-tag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. +- **Args**: + - **aOffset**: memory offset of the operation's left input + - **bOffset**: memory offset of the operation's right input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = M[aOffset] * M[bOffset] mod 2^k` +- **Tag checks**: `T[aOffset] == T[bOffset] == in-tag` +- **Tag updates**: `T[dstOffset] = in-tag` +- **Bit-size**: 128 + + +### `DIV` (0x03) Unsigned division (a / b) [See in table.](#isa-table-div) @@ -518,7 +537,7 @@ Unsigned division (a / b) [![](./images/bit-formats/DIV.png)](./images/bit-formats/DIV.png) -### `EQ` (0x03) +### `EQ` (0x04) Equality check (a == b) [See in table.](#isa-table-eq) @@ -538,7 +557,7 @@ Equality check (a == b) [![](./images/bit-formats/EQ.png)](./images/bit-formats/EQ.png) -### `LT` (0x04) +### `LT` (0x05) Less-than check (a < b) [See in table.](#isa-table-lt) @@ -558,7 +577,7 @@ Less-than check (a < b) [![](./images/bit-formats/LT.png)](./images/bit-formats/LT.png) -### `LTE` (0x05) +### `LTE` (0x06) Less-than-or-equals check (a <= b) [See in table.](#isa-table-lte) @@ -578,7 +597,7 @@ Less-than-or-equals check (a <= b) [![](./images/bit-formats/LTE.png)](./images/bit-formats/LTE.png) -### `AND` (0x06) +### `AND` (0x07) Bitwise AND (a & b) [See in table.](#isa-table-and) @@ -598,7 +617,7 @@ Bitwise AND (a & b) [![](./images/bit-formats/AND.png)](./images/bit-formats/AND.png) -### `OR` (0x07) +### `OR` (0x08) Bitwise OR (a | b) [See in table.](#isa-table-or) @@ -618,7 +637,7 @@ Bitwise OR (a | b) [![](./images/bit-formats/OR.png)](./images/bit-formats/OR.png) -### `XOR` (0x08) +### `XOR` (0x09) Bitwise XOR (a ^ b) [See in table.](#isa-table-xor) @@ -638,7 +657,7 @@ Bitwise XOR (a ^ b) [![](./images/bit-formats/XOR.png)](./images/bit-formats/XOR.png) -### `NOT` (0x09) +### `NOT` (0x0a) Bitwise NOT (inversion) [See in table.](#isa-table-not) @@ -657,7 +676,7 @@ Bitwise NOT (inversion) [![](./images/bit-formats/NOT.png)](./images/bit-formats/NOT.png) -### `SHL` (0x0a) +### `SHL` (0x0b) Bitwise leftward shift (a << b) [See in table.](#isa-table-shl) @@ -677,7 +696,7 @@ Bitwise leftward shift (a << b) [![](./images/bit-formats/SHL.png)](./images/bit-formats/SHL.png) -### `SHR` (0x0b) +### `SHR` (0x0c) Bitwise rightward shift (a >> b) [See in table.](#isa-table-shr) @@ -697,7 +716,7 @@ Bitwise rightward shift (a >> b) [![](./images/bit-formats/SHR.png)](./images/bit-formats/SHR.png) -### `CAST` (0x0c) +### `CAST` (0x0d) Type cast [See in table.](#isa-table-cast) @@ -716,7 +735,7 @@ Type cast [![](./images/bit-formats/CAST.png)](./images/bit-formats/CAST.png) -### `SET` (0x0d) +### `SET` (0x0e) Set a memory word from a constant in the bytecode. [See in table.](#isa-table-set) @@ -735,7 +754,7 @@ Set a memory word from a constant in the bytecode. [![](./images/bit-formats/SET.png)](./images/bit-formats/SET.png) -### `MOV` (0x0e) +### `MOV` (0x0f) Move a word from source memory location to destination`. [See in table.](#isa-table-mov) @@ -752,7 +771,7 @@ Move a word from source memory location to destination`. [![](./images/bit-formats/MOV.png)](./images/bit-formats/MOV.png) -### `CMOV` (0x0f) +### `CMOV` (0x10) Move a word (conditionally chosen) from one memory location to another (`d = cond > 0 ? a : b`). [See in table.](#isa-table-cmov) @@ -772,7 +791,7 @@ Move a word (conditionally chosen) from one memory location to another (`d = con [![](./images/bit-formats/CMOV.png)](./images/bit-formats/CMOV.png) -### `CALLDATACOPY` (0x10) +### `CALLDATACOPY` (0x11) Copy calldata into memory. [See in table.](#isa-table-calldatacopy) @@ -791,7 +810,7 @@ Copy calldata into memory. [![](./images/bit-formats/CALLDATACOPY.png)](./images/bit-formats/CALLDATACOPY.png) -### `SLOAD` (0x11) +### `SLOAD` (0x12) Load a word from storage. [See in table.](#isa-table-sload) @@ -809,7 +828,7 @@ Load a word from storage. [![](./images/bit-formats/SLOAD.png)](./images/bit-formats/SLOAD.png) -### `SSTORE` (0x12) +### `SSTORE` (0x13) Write a word to storage. [See in table.](#isa-table-sstore) @@ -826,7 +845,7 @@ Write a word to storage. [![](./images/bit-formats/SSTORE.png)](./images/bit-formats/SSTORE.png) -### `EMITNOTEHASH` (0x13) +### `EMITNOTEHASH` (0x14) Emit a new note hash to be inserted into the notes tree [See in table.](#isa-table-emitnotehash) @@ -841,7 +860,7 @@ Emit a new note hash to be inserted into the notes tree [![](./images/bit-formats/EMITNOTEHASH.png)](./images/bit-formats/EMITNOTEHASH.png) -### `EMITNULLIFIER` (0x14) +### `EMITNULLIFIER` (0x15) Emit a new nullifier to be inserted into the nullifier tree [See in table.](#isa-table-emitnullifier) @@ -856,7 +875,7 @@ Emit a new nullifier to be inserted into the nullifier tree [![](./images/bit-formats/EMITNULLIFIER.png)](./images/bit-formats/EMITNULLIFIER.png) -### `SENDL2TOL1MSG` (0x15) +### `SENDL2TOL1MSG` (0x16) Send an L2-to-L1 message [See in table.](#isa-table-sendl2tol1msg) @@ -871,7 +890,7 @@ Send an L2-to-L1 message [![](./images/bit-formats/SENDL2TOL1MSG.png)](./images/bit-formats/SENDL2TOL1MSG.png) -### `JUMP` (0x16) +### `JUMP` (0x17) Jump to a location in the bytecode. [See in table.](#isa-table-jump) @@ -885,7 +904,7 @@ Jump to a location in the bytecode. [![](./images/bit-formats/JUMP.png)](./images/bit-formats/JUMP.png) -### `JUMPI` (0x17) +### `JUMPI` (0x18) Conditionally jump to a location in the bytecode. [See in table.](#isa-table-jumpi) @@ -902,7 +921,7 @@ Conditionally jump to a location in the bytecode. [![](./images/bit-formats/JUMPI.png)](./images/bit-formats/JUMPI.png) -### `RETURN` (0x18) +### `RETURN` (0x19) Halt execution with `success`, optionally returning some data. [See in table.](#isa-table-return) @@ -919,7 +938,7 @@ Halt execution with `success`, optionally returning some data. [![](./images/bit-formats/RETURN.png)](./images/bit-formats/RETURN.png) -### `REVERT` (0x19) +### `REVERT` (0x1a) Halt execution with `failure`, reverting state changes and optionally returning some data. [See in table.](#isa-table-revert) @@ -936,7 +955,7 @@ Halt execution with `failure`, reverting state changes and optionally returning [![](./images/bit-formats/REVERT.png)](./images/bit-formats/REVERT.png) -### `CALL` (0x1a) +### `CALL` (0x1b) Call into another contract. [See in table.](#isa-table-call) @@ -972,7 +991,7 @@ T[retOffset:retOffset+retSize] = field`} [![](./images/bit-formats/CALL.png)](./images/bit-formats/CALL.png) -### `STATICCALL` (0x1b) +### `STATICCALL` (0x1c) Call into another contract, disallowing persistent state modifications. [See in table.](#isa-table-staticcall) @@ -1006,7 +1025,7 @@ T[retOffset:retOffset+retSize] = field`} [![](./images/bit-formats/STATICCALL.png)](./images/bit-formats/STATICCALL.png) -### `ULOG` (0x1c) +### `ULOG` (0x1d) Emit an unencrypted log with data from the `field` memory page [See in table.](#isa-table-ulog) @@ -1022,7 +1041,7 @@ Emit an unencrypted log with data from the `field` memory page [![](./images/bit-formats/ULOG.png)](./images/bit-formats/ULOG.png) -### `CHAINID` (0x1d) +### `CHAINID` (0x1e) Get this rollup's L1 chain ID [See in table.](#isa-table-chainid) @@ -1038,7 +1057,7 @@ Get this rollup's L1 chain ID [![](./images/bit-formats/CHAINID.png)](./images/bit-formats/CHAINID.png) -### `VERSION` (0x1e) +### `VERSION` (0x1f) Get this rollup's L2 version ID [See in table.](#isa-table-version) @@ -1054,7 +1073,7 @@ Get this rollup's L2 version ID [![](./images/bit-formats/VERSION.png)](./images/bit-formats/VERSION.png) -### `BLOCKNUMBER` (0x1f) +### `BLOCKNUMBER` (0x20) Get this block's number [See in table.](#isa-table-blocknumber) @@ -1070,7 +1089,7 @@ Get this block's number [![](./images/bit-formats/BLOCKNUMBER.png)](./images/bit-formats/BLOCKNUMBER.png) -### `TIMESTAMP` (0x20) +### `TIMESTAMP` (0x21) Get this L2 block's timestamp [See in table.](#isa-table-timestamp) @@ -1086,7 +1105,7 @@ Get this L2 block's timestamp [![](./images/bit-formats/TIMESTAMP.png)](./images/bit-formats/TIMESTAMP.png) -### `COINBASE` (0x21) +### `COINBASE` (0x22) Get the block's beneficiary address [See in table.](#isa-table-coinbase) @@ -1102,7 +1121,7 @@ Get the block's beneficiary address [![](./images/bit-formats/COINBASE.png)](./images/bit-formats/COINBASE.png) -### `BLOCKL1GASLIMIT` (0x22) +### `BLOCKL1GASLIMIT` (0x23) Total amount of "L1 gas" that a block can consume [See in table.](#isa-table-blockl1gaslimit) @@ -1118,7 +1137,7 @@ Total amount of "L1 gas" that a block can consume [![](./images/bit-formats/BLOCKL1GASLIMIT.png)](./images/bit-formats/BLOCKL1GASLIMIT.png) -### `BLOCKL2GASLIMIT` (0x23) +### `BLOCKL2GASLIMIT` (0x24) Total amount of "L2 gas" that a block can consume [See in table.](#isa-table-blockl2gaslimit) @@ -1134,7 +1153,7 @@ Total amount of "L2 gas" that a block can consume [![](./images/bit-formats/BLOCKL2GASLIMIT.png)](./images/bit-formats/BLOCKL2GASLIMIT.png) -### `NOTESROOT` (0x24) +### `NOTESROOT` (0x25) Get the historical note-hash tree root as of the specified block number. [See in table.](#isa-table-notesroot) @@ -1151,7 +1170,7 @@ Get the historical note-hash tree root as of the specified block number. [![](./images/bit-formats/NOTESROOT.png)](./images/bit-formats/NOTESROOT.png) -### `NULLIFIERSROOT` (0x25) +### `NULLIFIERSROOT` (0x26) Get the historical nullifier tree root as of the specified block number. [See in table.](#isa-table-nullroot) @@ -1168,7 +1187,7 @@ Get the historical nullifier tree root as of the specified block number. [![](./images/bit-formats/NULLIFIERSROOT.png)](./images/bit-formats/NULLIFIERSROOT.png) -### `CONTRACTSROOT` (0x26) +### `CONTRACTSROOT` (0x27) Get the historical contracts tree root as of the specified block number. [See in table.](#isa-table-contractsroot) @@ -1185,7 +1204,7 @@ Get the historical contracts tree root as of the specified block number. [![](./images/bit-formats/CONTRACTSROOT.png)](./images/bit-formats/CONTRACTSROOT.png) -### `MSGSROOT` (0x27) +### `MSGSROOT` (0x28) Get the historical l1-to-l2 message tree root as of the specified block number. [See in table.](#isa-table-msgsroot) @@ -1202,23 +1221,6 @@ Get the historical l1-to-l2 message tree root as of the specified block number. [![](./images/bit-formats/MSGSROOT.png)](./images/bit-formats/MSGSROOT.png) -### `NOTESROOT` (0x28) -Get the historical note-hash tree root as of the specified block number. - -[See in table.](#isa-table-notesroot) - -- **Category**: historical access -- **Flags**: - - **indirect**: Toggles whether each memory-offset argument is an indirect offset. 0th bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. -- **Args**: - - **blockNumOffset**: memory offset of the block number input - - **dstOffset**: memory offset specifying where to store operation's result -- **Expression**: `M[dstOffset] = HistoricalBlockData[M[blockNumOffset]].note_hash_tree_root` -- **Tag updates**: `T[dstOffset] = field` -- **Bit-size**: 88 - -[![](./images/bit-formats/NOTESROOT.png)](./images/bit-formats/NOTESROOT.png) - ### `PUBLICDATAROOT` (0x29) Get the historical public data tree root as of the specified block number. diff --git a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js index 4dfd2ed34a66..f734022e4473 100644 --- a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js +++ b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js @@ -54,6 +54,27 @@ const INSTRUCTION_SET_RAW = [ "Tag checks": "`T[aOffset] == T[bOffset] == in-tag`", "Tag updates": "`T[dstOffset] = in-tag`", }, + { + "id": "mul", + "Name": "`MUL`", + "Category": "arithmetic", + "Flags": [ + {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, + {"name": "in-tag", "description": IN_TAG_DESCRIPTION}, + ], + "#memreads": "2", + "#memwrites": "1", + "Args": [ + {"name": "aOffset", "description": "memory offset of the operation's left input"}, + {"name": "bOffset", "description": "memory offset of the operation's right input"}, + {"name": "dstOffset", "description": "memory offset specifying where to store operation's result"}, + ], + "Expression": "`M[dstOffset] = M[aOffset] * M[bOffset] mod 2^k`", + "Summary": "Subtraction (a - b)", + "Details": "", + "Tag checks": "`T[aOffset] == T[bOffset] == in-tag`", + "Tag updates": "`T[dstOffset] = in-tag`", + }, { "id": "div", "Name": "`DIV`", @@ -815,25 +836,6 @@ T[retOffset:retOffset+retSize] = field "Tag checks": "", "Tag updates": "`T[dstOffset] = field`", }, - { - "id": "notesroot", - "Name": "`NOTESROOT`", - "Category": "historical access", - "Flags": [ - {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, - ], - "#memreads": "1", - "#memwrites": "1", - "Args": [ - {"name": "blockNumOffset", "description": "memory offset of the block number input"}, - {"name": "dstOffset", "description": "memory offset specifying where to store operation's result"}, - ], - "Expression": "`M[dstOffset] = HistoricalBlockData[M[blockNumOffset]].note_hash_tree_root`", - "Summary": "Get the historical note-hash tree root as of the specified block number.", - "Details": "", - "Tag checks": "", - "Tag updates": "`T[dstOffset] = field`", - }, { "id": "publicdataroot", "Name": "`PUBLICDATAROOT`", From feef22437beca9c8bb1f2419b2760aba6af751aa Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:52:53 +0000 Subject: [PATCH 03/20] feat: initial avm scaffold --- .../acir-simulator/src/avm/avm.legacy.test.ts | 401 +++++++++++++ .../acir-simulator/src/avm/avm.test.ts | 401 +------------ yarn-project/acir-simulator/src/avm/avm.ts | 556 +----------------- .../acir-simulator/src/avm/avm_context.ts | 35 ++ .../src/avm/fixtures/avm_examples.ts | 160 ++--- yarn-project/acir-simulator/src/avm/index.ts | 1 - .../src/avm/interpreter/index.ts | 1 + .../src/avm/interpreter/interpreter.test.ts | 9 + .../src/avm/interpreter/interpreter.ts | 38 ++ .../acir-simulator/src/avm/opcodes.ts | 124 ---- .../src/avm/opcodes/arithmetic.ts | 142 +++++ .../acir-simulator/src/avm/opcodes/bitwise.ts | 66 +++ .../src/avm/opcodes/from_bytecode.test.ts | 40 ++ .../src/avm/opcodes/from_bytecode.ts | 56 ++ .../acir-simulator/src/avm/opcodes/index.ts | 3 +- .../acir-simulator/src/avm/opcodes/memory.ts | 55 ++ .../acir-simulator/src/avm/opcodes/opcode.ts | 9 + .../acir-simulator/src/avm/opcodes/opcodes.ts | 78 +++ 18 files changed, 1039 insertions(+), 1136 deletions(-) create mode 100644 yarn-project/acir-simulator/src/avm/avm.legacy.test.ts create mode 100644 yarn-project/acir-simulator/src/avm/avm_context.ts create mode 100644 yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts create mode 100644 yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts delete mode 100644 yarn-project/acir-simulator/src/avm/opcodes.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.test.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/memory.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/opcode.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts diff --git a/yarn-project/acir-simulator/src/avm/avm.legacy.test.ts b/yarn-project/acir-simulator/src/avm/avm.legacy.test.ts new file mode 100644 index 000000000000..c4a83ffd739c --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/avm.legacy.test.ts @@ -0,0 +1,401 @@ +// import { +// CallContext, +// CircuitsWasm, +// FunctionData, +// } from '@aztec/circuits.js'; +// import { pedersenPlookupCommitWithHashIndexPoint } from '@aztec/circuits.js/barretenberg'; +// import { FunctionSelector } from '@aztec/foundation/abi'; +// import { AztecAddress } from '@aztec/foundation/aztec-address'; +// import { EthAddress } from '@aztec/foundation/eth-address'; +// import { Fr } from '@aztec/foundation/fields'; +// import { +// AvmTestContractArtifact, +// } from '@aztec/noir-contracts/artifacts'; +// import { createDebugLogger } from '@aztec/foundation/log'; + +// import { MockProxy, mock } from 'jest-mock-extended'; + +// import { PublicContractsDB, PublicStateDB } from '../public/db.js'; +// import { PublicCall, PublicExecutionResult } from '../public/execution.js'; + +// import { AVMExecutor } from './avm.js'; +// import { +// addBytecode, +// storageBytecode, +// nestedCallBytecode, +// } from './fixtures/avm_examples.js'; + + +// describe('ACIR public execution simulator', () => { +// const log = createDebugLogger('aztec:acir-simulator:vm.test.ts'); +// let publicState: MockProxy; +// let publicContracts: MockProxy; +// //let commitmentsDb: MockProxy; +// //let blockData: HistoricBlockData; +// let executor: AVMExecutor; // TODO: replace with AVMSimulator +// let msgSender: AztecAddress; + +// beforeEach(() => { +// publicState = mock(); +// publicContracts = mock(); +// //commitmentsDb = mock(); +// //blockData = HistoricBlockData.empty(); +// executor = new AVMExecutor(publicState, publicContracts); + +// msgSender = AztecAddress.random(); +// }, 10000); + +// async function simulateAndCheck( +// calldata: Fr[], +// expectReturn: Fr[], +// bytecode: Buffer, +// bytecodesForNestedCalls: Buffer[] = [], +// ): Promise { +// const contractAddress = AztecAddress.random(); +// const functionSelector = new FunctionSelector(0x1234); +// const functionData = new FunctionData(functionSelector, false, false, false); +// const callContext = CallContext.from({ +// msgSender, +// storageContractAddress: contractAddress, +// portalContractAddress: EthAddress.random(), +// functionSelector: FunctionSelector.empty(), +// isContractDeployment: false, +// isDelegateCall: false, +// isStaticCall: false, +// }); + +// publicContracts.getBytecode.mockResolvedValueOnce(bytecode); +// for (let i = 0; i < bytecodesForNestedCalls.length; i++) { +// publicContracts.getBytecode.mockResolvedValueOnce(bytecodesForNestedCalls[i]); +// publicContracts.getPortalContractAddress.mockResolvedValueOnce(EthAddress.random()); +// publicContracts.getIsInternal.mockResolvedValueOnce(false); +// } + +// const publicCall: PublicCall = { contractAddress, functionData, calldata, callContext }; +// const result = await executor.simulate(publicCall); //, GlobalVariables.empty()); + +// expect(result.returnValues.length).toEqual(expectReturn.length); +// for (let i = 0; i < expectReturn.length; i++) { +// expect(result.returnValues[i]).toEqual(new Fr(expectReturn[i])); +// } + +// return result; +// } + +// describe('Token contract', () => { +// describe('AVM tests using bytecode constructed manually in-test', () => { +// it('AVM can add arguments and return result', async () => { +// // ADD 42 + 25 +// // => 67 +// const addArg0 = 42n; +// const addArg1 = 25n; +// const calldata = [addArg0, addArg1].map(arg => new Fr(arg)); +// const returndata = [addArg0 + addArg1].map(arg => new Fr(arg)); +// await simulateAndCheck(calldata, returndata, addBytecode); +// }); +// it('AVM storage operations work', async () => { +// // ADD 42 + S[61] +// // ADD 42 + 96 +// // => 138 +// const addArg0 = 42n; +// const slotArg = 61n; +// const startValueAtSlot = 96n; +// const calldata = [addArg0, slotArg].map(arg => new Fr(arg)); +// const ret = addArg0 + startValueAtSlot; +// const returndata = [ret].map(arg => new Fr(arg)); + +// //publicContracts.getBytecode.mockResolvedValue(storageBytecode); + +// publicState.storageRead +// .mockResolvedValueOnce(new Fr(startValueAtSlot)) // before sstore +// .mockResolvedValueOnce(new Fr(ret)); // after sstore + +// const result = await simulateAndCheck(calldata, returndata, storageBytecode); + +// // VALIDATE STORAGE ACTION TRACE +// // SLOAD is performed before SSTORE and after +// expect(result.contractStorageReads).toEqual([ +// { +// storageSlot: new Fr(slotArg), +// currentValue: new Fr(startValueAtSlot), +// sideEffectCounter: 0, +// }, +// { +// storageSlot: new Fr(slotArg), +// currentValue: new Fr(ret), +// sideEffectCounter: 2, +// }, +// ]); +// // Confirm that ADD result was SSTOREd +// expect(result.contractStorageUpdateRequests).toEqual([ +// { +// storageSlot: new Fr(slotArg), +// oldValue: new Fr(startValueAtSlot), +// newValue: new Fr(ret), +// sideEffectCounter: 1, +// }, +// ]); +// }); +// it('AVM can perform nested calls', async () => { +// // ADD 42 + 25 +// // => 67 +// const nestedCallAddress = 5678n; +// const addArg0 = 42n; +// const addArg1 = 25n; +// const calldata = [nestedCallAddress, addArg0, addArg1].map(arg => new Fr(arg)); +// const returndata = [addArg0 + addArg1].map(arg => new Fr(arg)); + +// // top-level call (nestedCallBytecode) just makes a CALL to addBytecode +// // which performs an ADD and returns the result +// // pseudocode: +// // fn foo(addr, a, b) { +// // return addr::bar(a, b); +// // } +// // fn bar(a, b) { +// // return a + b; +// // } +// await simulateAndCheck(calldata, returndata, nestedCallBytecode, [addBytecode]); +// }); +// }); + +// describe('AVM tests that transpile Brillig bytecode to AVM before executing', () => { +// it('Noir function that just does a basic add', async () => { +// const addExampleArtifact = AvmTestContractArtifact.functions.find( +// f => f.name === 'addExample', +// )!; +// const bytecode = Buffer.from(addExampleArtifact.bytecode, 'base64'); + +// // ADD 42 + 25 +// // => 67 +// const addArg0 = 42n; +// const addArg1 = 25n; +// const calldata = [addArg0, addArg1].map(arg => new Fr(arg)); +// const returndata = [addArg0 + addArg1].map(arg => new Fr(arg)); +// await simulateAndCheck(calldata, returndata, bytecode); +// }); +// it('Noir function with slightly more complex arithmetic', async () => { +// // ADD 42 + 25 +// // => 67 +// // ADD 67 + 67 +// // => 137 +// // SUB 137 - 30 +// // => 107 +// const arithmeticExample = AvmTestContractArtifact.functions.find( +// f => f.name === 'arithmeticExample', +// )!; +// const bytecode = Buffer.from(arithmeticExample.bytecode, 'base64'); + +// const addArg0 = 42n; +// const addArg1 = 25n; +// const subArg0 = 30n; +// const calldata = [addArg0, addArg1, subArg0].map(arg => new Fr(arg)); +// const returndata = [(addArg0 + addArg1) + (addArg0 + addArg1) - subArg0].map(arg => new Fr(arg)); +// await simulateAndCheck(calldata, returndata, bytecode); +// }); + +// it('Noir function with storage actions', async () => { +// const storageExample = AvmTestContractArtifact.functions.find( +// f => f.name === 'storageExample', +// )!; +// const bytecode = Buffer.from(storageExample.bytecode, 'base64'); + +// // ADD 42 + S[61] +// // ADD 42 + 96 +// // => 138 +// const addArg0 = 42n; +// const slotArg = 61n; +// const startValueAtSlot = 96n; +// const calldata = [addArg0, slotArg].map(arg => new Fr(arg)); +// const ret = addArg0 + startValueAtSlot; +// const returndata = [ret].map(arg => new Fr(arg)); + +// publicState.storageRead +// .mockResolvedValueOnce(new Fr(startValueAtSlot)) // before sstore +// .mockResolvedValueOnce(new Fr(ret)); // after sstore + +// const result = await simulateAndCheck(calldata, returndata, bytecode); + +// // VALIDATE STORAGE ACTION TRACE +// // SLOAD is performed before SSTORE and after +// expect(result.contractStorageReads).toEqual([ +// { +// storageSlot: new Fr(slotArg), +// currentValue: new Fr(startValueAtSlot), +// sideEffectCounter: 0, +// }, +// { +// storageSlot: new Fr(slotArg), +// currentValue: new Fr(ret), +// sideEffectCounter: 2, +// }, +// ]); +// // Confirm that ADD result was SSTOREd +// expect(result.contractStorageUpdateRequests).toEqual([ +// { +// storageSlot: new Fr(slotArg), +// oldValue: new Fr(startValueAtSlot), +// newValue: new Fr(ret), +// sideEffectCounter: 1, +// }, +// ]); +// }); +// it('Noir function with a nested contract call', async () => { +// const nestedCallExample = AvmTestContractArtifact.functions.find( +// f => f.name === 'nestedCallExample', +// )!; +// const bytecode = Buffer.from(nestedCallExample.bytecode, 'base64'); +// // ADD 42 + 25 +// // => 67 +// const nestedCallAddress = 5678n; +// const addArg0 = 42n; +// const addArg1 = 25n; +// const calldata = [nestedCallAddress, addArg0, addArg1].map(arg => new Fr(arg)); +// const returndata = [addArg0 + addArg1].map(arg => new Fr(arg)); + +// // top-level call (nestedCallBytecode) just makes a CALL to addBytecode +// // which performs an ADD and returns the result +// // pseudocode: +// // fn foo(addr, a, b) { +// // return addr::bar(a, b); +// // } +// // fn bar(a, b) { +// // return a + b; +// // } +// await simulateAndCheck(calldata, returndata, bytecode, [addBytecode]); +// }); +// it('Noir function with storage maps', async () => { +// const balanceOfPublic = AvmTestContractArtifact.functions.find( +// f => f.name === 'balance_of_public', +// )!; +// const bytecode = Buffer.from(balanceOfPublic.bytecode, 'base64'); + +// const userAddress = AztecAddress.random(); +// const slot = 6n; // slot used in Storage struct in Noir for public balances map +// const startValueAtSlot = 96n; +// const calldata = [userAddress.toField()].map(arg => new Fr(arg)); +// const returndata = [startValueAtSlot].map(arg => new Fr(arg)); + +// publicState.storageRead +// .mockResolvedValueOnce(new Fr(startValueAtSlot)) // before sstore + +// const result = await simulateAndCheck(calldata, returndata, bytecode); + +// // TODO: test that the actual slot read from is correct! +// // Must be sure that AVM's slot-computation is correct for storage maps. +// const wasm = await CircuitsWasm.get(); +// const hashIndex = 0; +// const inputs = [new Fr(slot).toBuffer(), userAddress.toBuffer()]; +// const outputBufs = pedersenPlookupCommitWithHashIndexPoint(wasm, inputs, hashIndex); +// const mapKeySlot = Fr.fromBuffer(outputBufs[0]); + +// // VALIDATE STORAGE ACTION TRACE +// expect(result.contractStorageReads).toEqual([ +// { +// storageSlot: mapKeySlot, +// currentValue: new Fr(startValueAtSlot), +// sideEffectCounter: 0, +// }, +// ]); +// }); +// it('Noir function that accesses context variables/constants', async () => { +// const contextVarsExample = AvmTestContractArtifact.functions.find( +// f => f.name === 'context_vars_example', +// )!; +// const bytecode = Buffer.from(contextVarsExample.bytecode, 'base64'); + +// const calldata: Fr[] = []; +// const returndata = [msgSender.toField()]; +// await simulateAndCheck(calldata, returndata, bytecode); +// }); +// it('Noir function to mint_public', async () => { +// const mintPublic = AvmTestContractArtifact.functions.find( +// f => f.name === 'mint_public', +// )!; +// const bytecode = Buffer.from(mintPublic.bytecode, 'base64'); + +// const receiverAddress = AztecAddress.random(); + +// // slots used in Storage struct in Noir +// const mintersSlot = 2n; +// const totalSupplySlot = 4n; +// const publicBalancesSlot = 6n; + +// const mintAmount = 42n; +// const receiverStartBal = 96n; +// const startTotalSupply = 123n; +// const receiverEndBal = receiverStartBal + mintAmount; +// const endTotalSupply = startTotalSupply + mintAmount; + +// const calldata = [receiverAddress.toField(), new Fr(mintAmount)]; +// const returndata = [new Fr(1)].map(arg => new Fr(arg)); // returns 1 for success + +// publicState.storageRead +// .mockResolvedValueOnce(new Fr(1)) // assert sender in minters map +// .mockResolvedValueOnce(new Fr(receiverStartBal)) // receiver's original balance +// .mockResolvedValueOnce(new Fr(startTotalSupply)); + +// const result = await simulateAndCheck(calldata, returndata, bytecode); + +// // TODO: test that the actual slot read from is correct! +// // Must be sure that AVM's slot-computation is correct for storage maps. +// const wasm = await CircuitsWasm.get(); +// const hashIndex = 0; +// // Compute minters slot for map key (msgSender address) +// let inputs = [new Fr(mintersSlot).toBuffer(), msgSender.toBuffer()]; +// let outputBufs = pedersenPlookupCommitWithHashIndexPoint(wasm, inputs, hashIndex); +// const mintersKeySlot = Fr.fromBuffer(outputBufs[0]); +// // Compute public balances slot for map key (receiver address) +// inputs = [new Fr(publicBalancesSlot).toBuffer(), receiverAddress.toBuffer()]; +// outputBufs = pedersenPlookupCommitWithHashIndexPoint(wasm, inputs, hashIndex); +// const publicBalancesKeySlot = Fr.fromBuffer(outputBufs[0]); + +// log(`expected/computed mintersKeySlot: ${mintersKeySlot}`); +// log(`expected/computed publicBalancesKeySlot: ${publicBalancesKeySlot}`); + +// // VALIDATE STORAGE ACTION TRACE +// expect(result.contractStorageReads).toEqual([ +// { +// storageSlot: mintersKeySlot, +// currentValue: new Fr(1), +// sideEffectCounter: 0, +// }, +// { +// storageSlot: publicBalancesKeySlot, +// currentValue: new Fr(receiverStartBal), +// sideEffectCounter: 1, +// }, +// { +// storageSlot: new Fr(totalSupplySlot), +// currentValue: new Fr(startTotalSupply), +// sideEffectCounter: 2, +// }, +// ]); +// // Confirm that balance and total supply were written properly +// expect(result.contractStorageUpdateRequests).toEqual([ +// { +// storageSlot: publicBalancesKeySlot, +// oldValue: new Fr(receiverStartBal), +// newValue: new Fr(receiverEndBal), +// sideEffectCounter: 3, +// }, +// { +// storageSlot: new Fr(totalSupplySlot), +// oldValue: new Fr(startTotalSupply), +// newValue: new Fr(endTotalSupply), +// sideEffectCounter: 4, +// }, +// ]); +// }); +// }); + +// //describe('AVM tests including calls to C++ witness generation and/or proving', () => { +// // it('should prove the public vm', async () => { +// // //... +// // const outAsmPath = await executor.bytecodeToPowdr(execution); +// // await executor.generateWitness(outAsmPath); +// // await executor.prove(); +// // }, 1_000_000); +// //}); +// }); +// // TODO: test field addition that could overflow (should just wrap around) +// }); diff --git a/yarn-project/acir-simulator/src/avm/avm.test.ts b/yarn-project/acir-simulator/src/avm/avm.test.ts index 991b6297f496..df8a538581c4 100644 --- a/yarn-project/acir-simulator/src/avm/avm.test.ts +++ b/yarn-project/acir-simulator/src/avm/avm.test.ts @@ -1,401 +1,6 @@ -import { - CallContext, - CircuitsWasm, - FunctionData, -} from '@aztec/circuits.js'; -import { pedersenPlookupCommitWithHashIndexPoint } from '@aztec/circuits.js/barretenberg'; -import { FunctionSelector } from '@aztec/foundation/abi'; -import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { EthAddress } from '@aztec/foundation/eth-address'; -import { Fr } from '@aztec/foundation/fields'; -import { - AvmTestContractArtifact, -} from '@aztec/noir-contracts/artifacts'; -import { createDebugLogger } from '@aztec/foundation/log'; -import { MockProxy, mock } from 'jest-mock-extended'; -import { PublicContractsDB, PublicStateDB } from '../public/db.js'; -import { PublicCall, PublicExecutionResult } from '../public/execution.js'; -import { AVMExecutor } from './avm.js'; -import { - addBytecode, - storageBytecode, - nestedCallBytecode, -} from './fixtures/avm_examples.js'; - - -describe('ACIR public execution simulator', () => { - const log = createDebugLogger('aztec:acir-simulator:vm.test.ts'); - let publicState: MockProxy; - let publicContracts: MockProxy; - //let commitmentsDb: MockProxy; - //let blockData: HistoricBlockData; - let executor: AVMExecutor; // TODO: replace with AVMSimulator - let msgSender: AztecAddress; - - beforeEach(() => { - publicState = mock(); - publicContracts = mock(); - //commitmentsDb = mock(); - //blockData = HistoricBlockData.empty(); - executor = new AVMExecutor(publicState, publicContracts); - - msgSender = AztecAddress.random(); - }, 10000); - - async function simulateAndCheck( - calldata: Fr[], - expectReturn: Fr[], - bytecode: Buffer, - bytecodesForNestedCalls: Buffer[] = [], - ): Promise { - const contractAddress = AztecAddress.random(); - const functionSelector = new FunctionSelector(0x1234); - const functionData = new FunctionData(functionSelector, false, false, false); - const callContext = CallContext.from({ - msgSender, - storageContractAddress: contractAddress, - portalContractAddress: EthAddress.random(), - functionSelector: FunctionSelector.empty(), - isContractDeployment: false, - isDelegateCall: false, - isStaticCall: false, - }); - - publicContracts.getBytecode.mockResolvedValueOnce(bytecode); - for (let i = 0; i < bytecodesForNestedCalls.length; i++) { - publicContracts.getBytecode.mockResolvedValueOnce(bytecodesForNestedCalls[i]); - publicContracts.getPortalContractAddress.mockResolvedValueOnce(EthAddress.random()); - publicContracts.getIsInternal.mockResolvedValueOnce(false); - } - - const publicCall: PublicCall = { contractAddress, functionData, calldata, callContext }; - const result = await executor.simulate(publicCall); //, GlobalVariables.empty()); - - expect(result.returnValues.length).toEqual(expectReturn.length); - for (let i = 0; i < expectReturn.length; i++) { - expect(result.returnValues[i]).toEqual(new Fr(expectReturn[i])); - } - - return result; - } - - describe('Token contract', () => { - describe('AVM tests using bytecode constructed manually in-test', () => { - it('AVM can add arguments and return result', async () => { - // ADD 42 + 25 - // => 67 - const addArg0 = 42n; - const addArg1 = 25n; - const calldata = [addArg0, addArg1].map(arg => new Fr(arg)); - const returndata = [addArg0 + addArg1].map(arg => new Fr(arg)); - await simulateAndCheck(calldata, returndata, addBytecode); - }); - it('AVM storage operations work', async () => { - // ADD 42 + S[61] - // ADD 42 + 96 - // => 138 - const addArg0 = 42n; - const slotArg = 61n; - const startValueAtSlot = 96n; - const calldata = [addArg0, slotArg].map(arg => new Fr(arg)); - const ret = addArg0 + startValueAtSlot; - const returndata = [ret].map(arg => new Fr(arg)); - - //publicContracts.getBytecode.mockResolvedValue(storageBytecode); - - publicState.storageRead - .mockResolvedValueOnce(new Fr(startValueAtSlot)) // before sstore - .mockResolvedValueOnce(new Fr(ret)); // after sstore - - const result = await simulateAndCheck(calldata, returndata, storageBytecode); - - // VALIDATE STORAGE ACTION TRACE - // SLOAD is performed before SSTORE and after - expect(result.contractStorageReads).toEqual([ - { - storageSlot: new Fr(slotArg), - currentValue: new Fr(startValueAtSlot), - sideEffectCounter: 0, - }, - { - storageSlot: new Fr(slotArg), - currentValue: new Fr(ret), - sideEffectCounter: 2, - }, - ]); - // Confirm that ADD result was SSTOREd - expect(result.contractStorageUpdateRequests).toEqual([ - { - storageSlot: new Fr(slotArg), - oldValue: new Fr(startValueAtSlot), - newValue: new Fr(ret), - sideEffectCounter: 1, - }, - ]); - }); - it('AVM can perform nested calls', async () => { - // ADD 42 + 25 - // => 67 - const nestedCallAddress = 5678n; - const addArg0 = 42n; - const addArg1 = 25n; - const calldata = [nestedCallAddress, addArg0, addArg1].map(arg => new Fr(arg)); - const returndata = [addArg0 + addArg1].map(arg => new Fr(arg)); - - // top-level call (nestedCallBytecode) just makes a CALL to addBytecode - // which performs an ADD and returns the result - // pseudocode: - // fn foo(addr, a, b) { - // return addr::bar(a, b); - // } - // fn bar(a, b) { - // return a + b; - // } - await simulateAndCheck(calldata, returndata, nestedCallBytecode, [addBytecode]); - }); - }); - - describe('AVM tests that transpile Brillig bytecode to AVM before executing', () => { - it('Noir function that just does a basic add', async () => { - const addExampleArtifact = AvmTestContractArtifact.functions.find( - f => f.name === 'addExample', - )!; - const bytecode = Buffer.from(addExampleArtifact.bytecode, 'base64'); - - // ADD 42 + 25 - // => 67 - const addArg0 = 42n; - const addArg1 = 25n; - const calldata = [addArg0, addArg1].map(arg => new Fr(arg)); - const returndata = [addArg0 + addArg1].map(arg => new Fr(arg)); - await simulateAndCheck(calldata, returndata, bytecode); - }); - it('Noir function with slightly more complex arithmetic', async () => { - // ADD 42 + 25 - // => 67 - // ADD 67 + 67 - // => 137 - // SUB 137 - 30 - // => 107 - const arithmeticExample = AvmTestContractArtifact.functions.find( - f => f.name === 'arithmeticExample', - )!; - const bytecode = Buffer.from(arithmeticExample.bytecode, 'base64'); - - const addArg0 = 42n; - const addArg1 = 25n; - const subArg0 = 30n; - const calldata = [addArg0, addArg1, subArg0].map(arg => new Fr(arg)); - const returndata = [(addArg0 + addArg1) + (addArg0 + addArg1) - subArg0].map(arg => new Fr(arg)); - await simulateAndCheck(calldata, returndata, bytecode); - }); - - it('Noir function with storage actions', async () => { - const storageExample = AvmTestContractArtifact.functions.find( - f => f.name === 'storageExample', - )!; - const bytecode = Buffer.from(storageExample.bytecode, 'base64'); - - // ADD 42 + S[61] - // ADD 42 + 96 - // => 138 - const addArg0 = 42n; - const slotArg = 61n; - const startValueAtSlot = 96n; - const calldata = [addArg0, slotArg].map(arg => new Fr(arg)); - const ret = addArg0 + startValueAtSlot; - const returndata = [ret].map(arg => new Fr(arg)); - - publicState.storageRead - .mockResolvedValueOnce(new Fr(startValueAtSlot)) // before sstore - .mockResolvedValueOnce(new Fr(ret)); // after sstore - - const result = await simulateAndCheck(calldata, returndata, bytecode); - - // VALIDATE STORAGE ACTION TRACE - // SLOAD is performed before SSTORE and after - expect(result.contractStorageReads).toEqual([ - { - storageSlot: new Fr(slotArg), - currentValue: new Fr(startValueAtSlot), - sideEffectCounter: 0, - }, - { - storageSlot: new Fr(slotArg), - currentValue: new Fr(ret), - sideEffectCounter: 2, - }, - ]); - // Confirm that ADD result was SSTOREd - expect(result.contractStorageUpdateRequests).toEqual([ - { - storageSlot: new Fr(slotArg), - oldValue: new Fr(startValueAtSlot), - newValue: new Fr(ret), - sideEffectCounter: 1, - }, - ]); - }); - it('Noir function with a nested contract call', async () => { - const nestedCallExample = AvmTestContractArtifact.functions.find( - f => f.name === 'nestedCallExample', - )!; - const bytecode = Buffer.from(nestedCallExample.bytecode, 'base64'); - // ADD 42 + 25 - // => 67 - const nestedCallAddress = 5678n; - const addArg0 = 42n; - const addArg1 = 25n; - const calldata = [nestedCallAddress, addArg0, addArg1].map(arg => new Fr(arg)); - const returndata = [addArg0 + addArg1].map(arg => new Fr(arg)); - - // top-level call (nestedCallBytecode) just makes a CALL to addBytecode - // which performs an ADD and returns the result - // pseudocode: - // fn foo(addr, a, b) { - // return addr::bar(a, b); - // } - // fn bar(a, b) { - // return a + b; - // } - await simulateAndCheck(calldata, returndata, bytecode, [addBytecode]); - }); - it('Noir function with storage maps', async () => { - const balanceOfPublic = AvmTestContractArtifact.functions.find( - f => f.name === 'balance_of_public', - )!; - const bytecode = Buffer.from(balanceOfPublic.bytecode, 'base64'); - - const userAddress = AztecAddress.random(); - const slot = 6n; // slot used in Storage struct in Noir for public balances map - const startValueAtSlot = 96n; - const calldata = [userAddress.toField()].map(arg => new Fr(arg)); - const returndata = [startValueAtSlot].map(arg => new Fr(arg)); - - publicState.storageRead - .mockResolvedValueOnce(new Fr(startValueAtSlot)) // before sstore - - const result = await simulateAndCheck(calldata, returndata, bytecode); - - // TODO: test that the actual slot read from is correct! - // Must be sure that AVM's slot-computation is correct for storage maps. - const wasm = await CircuitsWasm.get(); - const hashIndex = 0; - const inputs = [new Fr(slot).toBuffer(), userAddress.toBuffer()]; - const outputBufs = pedersenPlookupCommitWithHashIndexPoint(wasm, inputs, hashIndex); - const mapKeySlot = Fr.fromBuffer(outputBufs[0]); - - // VALIDATE STORAGE ACTION TRACE - expect(result.contractStorageReads).toEqual([ - { - storageSlot: mapKeySlot, - currentValue: new Fr(startValueAtSlot), - sideEffectCounter: 0, - }, - ]); - }); - it('Noir function that accesses context variables/constants', async () => { - const contextVarsExample = AvmTestContractArtifact.functions.find( - f => f.name === 'context_vars_example', - )!; - const bytecode = Buffer.from(contextVarsExample.bytecode, 'base64'); - - const calldata: Fr[] = []; - const returndata = [msgSender.toField()]; - await simulateAndCheck(calldata, returndata, bytecode); - }); - it('Noir function to mint_public', async () => { - const mintPublic = AvmTestContractArtifact.functions.find( - f => f.name === 'mint_public', - )!; - const bytecode = Buffer.from(mintPublic.bytecode, 'base64'); - - const receiverAddress = AztecAddress.random(); - - // slots used in Storage struct in Noir - const mintersSlot = 2n; - const totalSupplySlot = 4n; - const publicBalancesSlot = 6n; - - const mintAmount = 42n; - const receiverStartBal = 96n; - const startTotalSupply = 123n; - const receiverEndBal = receiverStartBal + mintAmount; - const endTotalSupply = startTotalSupply + mintAmount; - - const calldata = [receiverAddress.toField(), new Fr(mintAmount)]; - const returndata = [new Fr(1)].map(arg => new Fr(arg)); // returns 1 for success - - publicState.storageRead - .mockResolvedValueOnce(new Fr(1)) // assert sender in minters map - .mockResolvedValueOnce(new Fr(receiverStartBal)) // receiver's original balance - .mockResolvedValueOnce(new Fr(startTotalSupply)); - - const result = await simulateAndCheck(calldata, returndata, bytecode); - - // TODO: test that the actual slot read from is correct! - // Must be sure that AVM's slot-computation is correct for storage maps. - const wasm = await CircuitsWasm.get(); - const hashIndex = 0; - // Compute minters slot for map key (msgSender address) - let inputs = [new Fr(mintersSlot).toBuffer(), msgSender.toBuffer()]; - let outputBufs = pedersenPlookupCommitWithHashIndexPoint(wasm, inputs, hashIndex); - const mintersKeySlot = Fr.fromBuffer(outputBufs[0]); - // Compute public balances slot for map key (receiver address) - inputs = [new Fr(publicBalancesSlot).toBuffer(), receiverAddress.toBuffer()]; - outputBufs = pedersenPlookupCommitWithHashIndexPoint(wasm, inputs, hashIndex); - const publicBalancesKeySlot = Fr.fromBuffer(outputBufs[0]); - - log(`expected/computed mintersKeySlot: ${mintersKeySlot}`); - log(`expected/computed publicBalancesKeySlot: ${publicBalancesKeySlot}`); - - // VALIDATE STORAGE ACTION TRACE - expect(result.contractStorageReads).toEqual([ - { - storageSlot: mintersKeySlot, - currentValue: new Fr(1), - sideEffectCounter: 0, - }, - { - storageSlot: publicBalancesKeySlot, - currentValue: new Fr(receiverStartBal), - sideEffectCounter: 1, - }, - { - storageSlot: new Fr(totalSupplySlot), - currentValue: new Fr(startTotalSupply), - sideEffectCounter: 2, - }, - ]); - // Confirm that balance and total supply were written properly - expect(result.contractStorageUpdateRequests).toEqual([ - { - storageSlot: publicBalancesKeySlot, - oldValue: new Fr(receiverStartBal), - newValue: new Fr(receiverEndBal), - sideEffectCounter: 3, - }, - { - storageSlot: new Fr(totalSupplySlot), - oldValue: new Fr(startTotalSupply), - newValue: new Fr(endTotalSupply), - sideEffectCounter: 4, - }, - ]); - }); - }); - - //describe('AVM tests including calls to C++ witness generation and/or proving', () => { - // it('should prove the public vm', async () => { - // //... - // const outAsmPath = await executor.bytecodeToPowdr(execution); - // await executor.generateWitness(outAsmPath); - // await executor.prove(); - // }, 1_000_000); - //}); - }); - // TODO: test field addition that could overflow (should just wrap around) -}); +describe("Avm", () => { + +}) \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/avm.ts b/yarn-project/acir-simulator/src/avm/avm.ts index b4be8d8a4bc4..bd3a2643f610 100644 --- a/yarn-project/acir-simulator/src/avm/avm.ts +++ b/yarn-project/acir-simulator/src/avm/avm.ts @@ -1,541 +1,33 @@ -import { AztecAddress, CallContext, CircuitsWasm, ContractStorageRead, ContractStorageUpdateRequest, EthAddress, Fr, FunctionData, FunctionSelector, GlobalVariables, HistoricBlockData } from '@aztec/circuits.js'; -import { AVMInstruction, Opcode, PC_MODIFIERS } from './opcodes.js'; -import { createDebugLogger } from '@aztec/foundation/log'; -import { PublicCall, PublicExecution, PublicExecutionResult } from '../public/execution.js'; -import { CommitmentsDB, PublicContractsDB, PublicStateDB } from '../public/db.js'; -import { FunctionL2Logs, PackedArguments } from '@aztec/types'; -import { ContractStorageActionsCollector } from '../public/state_actions.js'; -import { SideEffectCounter } from '../common/side_effect_counter.js'; -import { pedersenPlookupCommitWithHashIndexPoint } from '@aztec/circuits.js/barretenberg'; -import { keccak } from '@aztec/foundation/crypto'; -import { PublicExecutor } from '../index.js'; -// TODO: figure out what info needs to go to witgen and prover, and what info -// is really just for the TS code to keep track of the entire TX/callstack +// Decisions: +// 1. At this current time the avm should not handle getting state itself or anything like that +// - it will purely run as an interpreter and the code / starting state will be given to it on init +// 2. Anything that is a constant +// - e.g. will go in its own relevant file -> there is no need to have it in the interpreter file -/** - * VM object with top-level/tx-level state - * - VM can execute a TX which in turns executes each call - * VM call/context object with call-level state - * - call can execute - * - * There are a few different levels of state during execution: - * 1. Call-level state that is modified by each instruction - * 2. TX-level state that is modified by each call - * 3. Top-level state that is modified by each TX - * 4. Block-level state that remains constant for all TXs in a block - * - * Within call-level state, there are values that will remain constant - * for the duration of the call, and values that will be modified per-instruction - * - * CallContext is constant for a call. It is the context in which the - * call was triggered, but is not modified by the call. - */ +// import { AvmContext } from "./avm_context.js"; +// import { Add, Mul, Opcode, Sub } from "./opcodes/index.js"; -// Decisions -// - We want the avm context to +// // First steps +// // 1. Implement the basic opcodes and do it without state +// // AVM +// // The avm itself is responsible for +// // - Triggering a call +// // - Creating an interpreter to handle the call +// // - Getting the journal of state changes from the interpreter +// // - Applying the state changes through the state manager interface +// class Avm { -class AvmContext { - static readonly MEM_REGION_WORDS = 4096; // 2**22 ? - static readonly RETURN_BUFFER_WORDS = 128; +// // The interpreter is responsible for executing +// private interpreter: AvmInterpreter; +// private stateManager: AvmStateManager; + +// // TODO: should this buffer go inside the avm or else where +// constructor(private context: AvmContext) { - private readonly calldata: Fr[]; +// } +// } - public pc: number = 0; // TODO: should be u32 - public error: boolean = false; - public returned: boolean = false; - public call_stack: number[] = []; // TODO: should be u32[] - //public l1GasUsed: number = 0; // or left? - //public l2GasUsed: number = 0; // or left? - /** Field memory region for this call */ - public fieldMemory: Fr[] = new Array(AVMCallState.MEM_REGION_WORDS).fill(Fr.ZERO); - /** Buffer to store returnData from nested calls */ - public returnBuffer: Fr[] = new Array(AVMCallState.RETURN_BUFFER_WORDS).fill(Fr.ZERO); - - constructor(calldata: Fr[]) { - this.calldata = calldata; - this.pc = 0; - } - - static public function new(bytecode: Fr[], calldata: Fr[]): AvmContext { - return new AvmContext(calldata); - } -} - - -class AVM { - private log = createDebugLogger('aztec:simulator:avm'); - - private state = new AVMCallState(); - private instructions: AVMInstruction[] = []; - - // Components of the partial witness (inputs to witness generation) - // collapsedStorageActions is used to retrieve latest value per slot - private collapsedStorageActions: ContractStorageActionsCollector; - private allStorageReads: ContractStorageRead[] = []; - private allStorageUpdates: ContractStorageUpdateRequest[] = []; - private nestedExecutions: PublicExecutionResult[] = []; - //private unencryptedLogs: UnencryptedL2Log[] = []; - // ^ these are computed gradually as instructions execute - - constructor( - private context: PublicCall, - private readonly sideEffectCounter: SideEffectCounter, - private readonly stateDb: PublicStateDB, - private readonly contractsDb: PublicContractsDB, - ) { - this.collapsedStorageActions = new ContractStorageActionsCollector(stateDb, context.contractAddress); - } - /** - * Must be called before AVM can be executed or simulated. - */ - async init(){ - this.instructions = await this.fetchAndDecodeBytecode(); - } - - /** - * Execute this call. - * Generate a partial witness. - */ - public async simulate(): Promise { - this.log(`Simulating the Aztec Public VM`); - - const returnValues = await this.simulateInternal(); - // Note: not collecting storage actions because we actually want the full uncollapsed lists of actions - //const [contractStorageReads, contractStorageUpdateRequests] = this.collapsedStorageActions.collect(); - - // just rename args to calldata... - const execution = { - contractAddress: this.context.contractAddress, - functionData: this.context.functionData, - args: this.context.calldata, // rename - callContext: this.context.callContext - }; - return { - execution, - newCommitments: [], - newL2ToL1Messages: [], - newNullifiers: [], - contractStorageReads: this.allStorageReads, - contractStorageUpdateRequests: this.allStorageUpdates, - returnValues: returnValues, - nestedExecutions: this.nestedExecutions, - unencryptedLogs: FunctionL2Logs.empty(), - }; - } - - /** - * Execute each instruction based on the program counter. - * End execution when the call errors or returns. - */ - private async simulateInternal(): Promise { - while(this.state.pc < this.instructions.length && !this.state.error && !this.state.returned) { - const returnData = await this.simulateNextInstruction(); - if (this.state.returned) { - return returnData; - } - if (this.state.error) { - throw new Error("Reverting is not yet supported in the AVM"); - } - } - throw new Error("Reached end of bytecode without RETURN or REVERT"); - } - - /** - * Execute the instruction at the current program counter. - */ - private async simulateNextInstruction(): Promise { - // TODO: check memory out of bounds - const instr = this.instructions[this.state.pc]; - this.log(`Executing instruction (pc:${this.state.pc}): ${Opcode[instr.opcode]}`); - switch (instr.opcode) { - ///////////////////////////////////////////////////////////////////////// - // Arithmetic - ///////////////////////////////////////////////////////////////////////// - case Opcode.ADD: { - // TODO: consider having a single case for all arithmetic operations and then applying the corresponding function to the args - // TODO: use actual field math - this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() + this.state.fieldMemory[instr.s1].toBigInt()) % Fr.MODULUS); - this.log(`ADD: M[${instr.d0}] = M[${instr.s0}] + M[${instr.s1}] % Fr.MODULUS`); - this.log(`ADD: M[${instr.d0}] = ${this.state.fieldMemory[instr.s0].toBigInt()} + ${this.state.fieldMemory[instr.s1].toBigInt()} = ${this.state.fieldMemory[instr.d0]}`); - break; - } - case Opcode.SUB: { - // TODO: use actual field math - this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() - this.state.fieldMemory[instr.s1].toBigInt()) % Fr.MODULUS); - break; - } - //TODO: case Opcode.MUL - case Opcode.DIV: { - // TODO: use actual field math - this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() / this.state.fieldMemory[instr.s1].toBigInt())); - break; - } - case Opcode.EQ: { - // TODO: use actual field math - this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() == this.state.fieldMemory[instr.s1].toBigInt())); - this.log(`EQ: M[${instr.d0}] = M[${instr.s0}] == M[${instr.s1}]`); - this.log(`EQ: M[${instr.d0}] = ${this.state.fieldMemory[instr.s0].toBigInt()} == ${this.state.fieldMemory[instr.s1].toBigInt()} = ${this.state.fieldMemory[instr.d0]}`); - break; - } - case Opcode.LT: { - // TODO: use actual field math - this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() < this.state.fieldMemory[instr.s1].toBigInt())); - break; - } - case Opcode.LTE: { - // TODO: use actual field math - this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() <= this.state.fieldMemory[instr.s1].toBigInt())); - break; - } - case Opcode.AND: { - // TODO: use actual field math - this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() & this.state.fieldMemory[instr.s1].toBigInt())); - break; - } - case Opcode.OR: { - // TODO: use actual field math - this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() | this.state.fieldMemory[instr.s1].toBigInt())); - break; - } - case Opcode.XOR: { - // TODO: use actual field math - this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() ^ this.state.fieldMemory[instr.s1].toBigInt())); - break; - } - case Opcode.NOT: { - // TODO: use actual field math - this.state.fieldMemory[instr.d0] = new Fr((~this.state.fieldMemory[instr.s0].toBigInt())); - break; - } - case Opcode.SHL: { - // TODO: use actual field math - this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() << this.state.fieldMemory[instr.s1].toBigInt())); - break; - } - case Opcode.SHR: { - // TODO: use actual field math - this.state.fieldMemory[instr.d0] = new Fr((this.state.fieldMemory[instr.s0].toBigInt() >> this.state.fieldMemory[instr.s1].toBigInt())); - break; - } - ///////////////////////////////////////////////////////////////////////// - // Memory - ///////////////////////////////////////////////////////////////////////// - case Opcode.SET: { - // TODO: allow instr.s0 to be larger since SET accepts an immediate value - this.log(`SET: M[${instr.d0}] = ${instr.s0}`); - this.state.fieldMemory[instr.d0] = new Fr(instr.s0); - break; - } - case Opcode.MOV: { - // TODO: use u32 memory for addresses here - const srcAddr = instr.s0Indirect ? Number(this.state.fieldMemory[instr.s0].toBigInt()) : instr.s0; - const dstAddr = instr.d0Indirect ? Number(this.state.fieldMemory[instr.d0].toBigInt()) : instr.d0; - if (instr.s0Indirect) { - this.log(`MOV: source is indirect, so srcAddr is M[s0] = M[${instr.s0}] = ${srcAddr}`); - } - if (instr.d0Indirect) { - this.log(`MOV: destination is indirect, so dstAddr is M[d0] = M[${instr.d0}] = ${dstAddr}`); - } - this.log(`MOV: M[${dstAddr}] = M[${srcAddr}]`); - this.log(`MOV: M[${dstAddr}] = ${this.state.fieldMemory[srcAddr]}`); - this.state.fieldMemory[dstAddr] = this.state.fieldMemory[srcAddr]; - break; - } - // TODO: RETURNDATASIZE and RETURNDATACOPY - case Opcode.CALLDATASIZE: { - // TODO: dest should be u32 - this.log(`CALLDATASIZE: M[${instr.d0}] = ${this.context.calldata.length}`); - this.state.fieldMemory[instr.d0] = new Fr(this.context.calldata.length); - break; - } - case Opcode.CALLDATACOPY: { - /** - * Might be best if this opcode doesn't truly accept dynamic length (lookups will be a pain) - * Opcode can have a max copy size and compiler can break larger copies into smaller components - */ - // TODO: srcOffset and copySize should be u32s - const copySize = Number(this.state.fieldMemory[instr.s1].toBigInt()); - this.log(`CALLDATACOPY: M[${instr.d0}:${instr.d0+copySize}] = calldata[${instr.s0}:${instr.s0+copySize}]`); - //assert instr.s0 + copySize <= context.calldata.length; - //assert instr.d0 + copySize <= context.fieldMemory.length; - for (let i = 0; i < copySize; i++) { - this.log(`Copying calldata[${instr.s0+i}] (${this.context.calldata[instr.s0+i]}) to fieldMemory[${instr.d0+i}]`); - this.state.fieldMemory[instr.d0+i] = this.context.calldata[instr.s0+i]; - } - break; - } - ///////////////////////////////////////////////////////////////////////// - // Control flow - ///////////////////////////////////////////////////////////////////////// - case Opcode.JUMP: { - this.log(`JUMP: to pc:${instr.s0} from pc:${this.state.pc}`) - this.state.pc = instr.s0; - break; - } - case Opcode.JUMPI: { - this.log(`JUMPI: if (M[${instr.sd}]:${this.state.fieldMemory[instr.sd]}) to pc:${instr.s0} from pc:${this.state.pc}`) - this.state.pc = !this.state.fieldMemory[instr.sd].isZero() ? instr.s0 : this.state.pc + 1; - break; - } - case Opcode.INTERNALCALL: { - this.state.call_stack.push(this.state.pc + 1); - this.state.pc = instr.s0; - this.log(`INTERNALCALL: pushed pc:${this.state.call_stack.at(-1)} to call_stack and jumped to pc:${this.state.pc}`); - break; - } - case Opcode.INTERNALRETURN: { - if (this.state.call_stack.length === 0) { - throw new Error("INTERNALRETURN: call_stack is empty - nowhere to return to"); - } - this.state.pc = this.state.call_stack.pop()!; - break; - } - ///////////////////////////////////////////////////////////////////////// - // Storage - ///////////////////////////////////////////////////////////////////////// - case Opcode.SLOAD: { - // TODO: use u32 memory for storage slot - this.log(`SLOAD: M[${instr.d0}] = S[M[${instr.s0}]]`) - const storageSlot = this.state.fieldMemory[instr.s0]; - this.state.fieldMemory[instr.d0] = await this.sload(storageSlot); - this.log(`SLOAD value: ${this.state.fieldMemory[instr.d0]} (S[${storageSlot}])`) - break; - } - case Opcode.SSTORE: { - // TODO: use u32 memory for storage slot - this.log(`SSTORE: S[M[${instr.d0}]] = M[${instr.s0}]`) - const storageSlot = this.state.fieldMemory[instr.d0]; - const value = this.state.fieldMemory[instr.s0]; - this.log(`SSTORE: S[${storageSlot}] = ${value}`) - await this.sstore(storageSlot, value); - break; - } - case Opcode.RETURN: { - const retSizeFr = this.state.fieldMemory[instr.s1]; - const retSize = Number(retSizeFr.toBigInt()); - this.log(`RETURN: M[${instr.s0}:${instr.s0 + retSize}] (size: ${retSize})`); - //assert instr.s0 + retSize <= context.fieldMemory.length; - this.state.returned = true; - return this.state.fieldMemory.slice(instr.s0, instr.s0 + retSize); - } - case Opcode.REVERT: { - const retSizeFr = this.state.fieldMemory[instr.s1]; - const retSize = Number(retSizeFr.toBigInt()); - this.log.error(`REVERT M[${instr.s0}:${instr.s0 + retSize}] (size: ${retSize})`); - //assert instr.s0 + retSize <= context.fieldMemory.length; - this.state.error = true; - this.state.returned = true; - throw new Error(`AVM reverted execution in contract:${this.context.contractAddress}, function:${this.context.callContext.functionSelector}`); - // TODO: return data and allow caller to handle revert - //return this.state.fieldMemory.slice(instr.s0, instr.s0 + retSize); - } - ///////////////////////////////////////////////////////////////////////// - // Contract call control flow - ///////////////////////////////////////////////////////////////////////// - case Opcode.CALL: { - const gas = this.state.fieldMemory[instr.s0]; - const addrFr = this.state.fieldMemory[instr.s1]; - const targetContractAddress = AztecAddress.fromBigInt(addrFr.toBigInt()); - this.log(`CALL was allocated ${gas} gas to call contract:${targetContractAddress.toString()}`); - - // argsAndRetOffset = sd - // size of argsAndRetOffset is 4: - // - argsOffset: M[sd] - // - argsSize: M[sd + 1] - // - retOffset: M[sd + 2] - // - retSize: M[sd + 3] - // TODO: use u32 memory for offsets and sizes - this.log(`CALL: sd=${instr.sd}, M[sd]=${this.state.fieldMemory[instr.sd]}`); - const argsAndRetOffset = Number(this.state.fieldMemory[instr.sd].toBigInt()); - const argsOffset = Number(this.state.fieldMemory[argsAndRetOffset].toBigInt()); - const argsSize = Number(this.state.fieldMemory[argsAndRetOffset + 1].toBigInt()); - //const argsSize = argsAndRetOffset + 1; - //const argsSize = 2; - const retOffset = Number(this.state.fieldMemory[argsAndRetOffset + 2].toBigInt()); - const retSize = Number(this.state.fieldMemory[argsAndRetOffset + 3].toBigInt()); - //const argsOffset = Number(this.state.fieldMemory[argsAndRetOffset].toBigInt()); - //const argsSize = Number(this.state.fieldMemory[argsAndRetOffset + 1].toBigInt()); - //const retOffset = Number(this.state.fieldMemory[argsAndRetOffset + 2].toBigInt()); - //const retSize = Number(this.state.fieldMemory[argsAndRetOffset + 3].toBigInt()); - //const argsOffset = Number(this.state.fieldMemory[instr.sd].toBigInt()); - //const argsSize = Number(this.state.fieldMemory[instr.sd + 1].toBigInt()); - //const retOffset = Number(this.state.fieldMemory[instr.sd + 2].toBigInt()); - //const retSize = Number(this.state.fieldMemory[instr.sd + 3].toBigInt()); - this.log(`CALL: argsOffset=${argsOffset} argsSize=${argsSize} retOffset=${retOffset} retSize=${retSize}`); - - const calldata = this.state.fieldMemory.slice(argsOffset, argsOffset + argsSize); - // For now, extract functionSelector here. - // TODO: eventually functionSelector can become a use-case of calldata as in EVM. - // FIXME: calldata[0] could be larger than 4-byte function selector! - // FIXME this is not function selector in vm.test.ts examples! - const functionSelector = new FunctionSelector(Number(calldata[0].toBigInt())); - this.log(`Nested call in AVM: addr=${targetContractAddress} selector=${functionSelector}`); - for (let i = 0; i < calldata.length; i++) { - this.log(`\tInitializing nested calldata[${i}] = ${calldata[i]}`) - } - - const portalAddress = (await this.contractsDb.getPortalContractAddress(targetContractAddress)) ?? EthAddress.ZERO; - const isInternal = await this.contractsDb.getIsInternal(targetContractAddress, functionSelector); - if (isInternal === undefined) { - throw new Error(`ERR: Method not found - ${targetContractAddress.toString()}:${functionSelector.toString()}`); - } - - const functionData = new FunctionData(functionSelector, isInternal, false, false); - const nestedCallContext = CallContext.from({ - msgSender: this.context.contractAddress, - portalContractAddress: portalAddress, - storageContractAddress: targetContractAddress, - functionSelector, - isContractDeployment: false, - isDelegateCall: false, - isStaticCall: false, - }); - const nestedContext = { - contractAddress:targetContractAddress, - functionData, - calldata: calldata, - callContext: nestedCallContext, - }; - const nestedAVM = new AVM( - nestedContext, - this.sideEffectCounter, - this.stateDb, - this.contractsDb, - ); - await nestedAVM.init(); - const childExecutionResult = await nestedAVM.simulate(); - this.nestedExecutions.push(childExecutionResult); - this.log(`Returning from nested call: ret=${childExecutionResult.returnValues.join(', ')}`); - - // When retSize is provided (known at time of CALL), write return values to memory at provided offset. - // Otherwise, write return values to start of returnBuffer. - if (retSize > 0) { - this.state.fieldMemory.splice(retOffset, retSize, ...childExecutionResult.returnValues); - } else { - this.state.returnBuffer.splice(0, retSize, ...childExecutionResult.returnValues); - } - break; - } - ///////////////////////////////////////////////////////////////////////// - // Call context operations - ///////////////////////////////////////////////////////////////////////// - case Opcode.SENDER: { - this.state.fieldMemory[instr.d0] = this.context.callContext.msgSender.toField(); - break; - } - case Opcode.ADDRESS: { - this.state.fieldMemory[instr.d0] = this.context.callContext.storageContractAddress.toField(); - break; - } - case Opcode.SELECTOR: { - this.state.fieldMemory[instr.d0] = this.context.callContext.functionSelector.toField(); - break; - } - case Opcode.ARGSHASH: { - const wasm = await CircuitsWasm.get(); - const hash = (await PackedArguments.fromArgs(this.context.calldata, wasm)).hash.value; - this.state.fieldMemory[instr.d0] = new Fr(hash); - break; - } - ///////////////////////////////////////////////////////////////////////// - // Blackbox/nasty operations - ///////////////////////////////////////////////////////////////////////// - case Opcode.PEDERSEN: { - const wasm = await CircuitsWasm.get(); - - const domainSeparator = this.state.fieldMemory[instr.s0]; - const argsOffset = Number(this.state.fieldMemory[instr.s1].toBigInt()); - const argsSize = Number(this.state.fieldMemory[instr.sd].toBigInt()); - const retOffset = Number(this.state.fieldMemory[instr.d0].toBigInt()); - const retSize = 2; - this.log(`PEDERSEN: argsOffset=${argsOffset} argsSize=${argsSize} retOffset=${retOffset} retSize=${retSize}`); - - const hashIndex = Number(domainSeparator.toBigInt()); - const inputs = this.state.fieldMemory.slice(argsOffset, argsOffset + argsSize); - const inputBufs = inputs.map(field => field.toBuffer()); - this.log(`PEDERSEN: inputs=[${inputs.join(', ')}], hash_index=${hashIndex}`); - const outputBufs = pedersenPlookupCommitWithHashIndexPoint(wasm, inputBufs, hashIndex); - const output = outputBufs.map(buf => Fr.fromBuffer(buf)); - this.log(`PEDERSEN: output=[${output.join(', ')}]`); - - this.state.fieldMemory.splice(retOffset, retSize, ...output); - break; - } - case Opcode.KECCAK256: { - // FIXME: not working!!!! this is unfinished - // FIXME: need to properly handle byte-array inputs and outputs! - const argsOffset = Number(this.state.fieldMemory[instr.s1].toBigInt()); - const argsSize = Number(this.state.fieldMemory[instr.sd].toBigInt()); - const retOffset = Number(this.state.fieldMemory[instr.d0].toBigInt()); - const retSize = 1; - this.log(`KECCAK256: argsOffset=${argsOffset} argsSize=${argsSize} retOffset=${retOffset} retSize=${retSize}`); - - const inputs = this.state.fieldMemory.slice(argsOffset, argsOffset + argsSize); - const inputBufs = inputs.map(field => field.toBuffer()); - this.log(`KECCAK256: inputs=[${inputs.join(', ')}]`); - const inputCattedBuf = Buffer.concat(inputBufs); - const outputBuf = keccak(inputCattedBuf); - const output = Fr.fromBuffer(outputBuf); - //this.log(`KECCAK256: output=[${output.join(', ')}]`); - this.log(`KECCAK256: output=${output}`); - - //this.state.fieldMemory.splice(retOffset, retSize, ...output); - this.state.fieldMemory[retOffset] = output; - break; - } - default: throw new Error(`AVM does not know how to process opcode ${Opcode[instr.opcode]} (aka ${instr.opcode}) at pc: ${this.state.pc}`); - } - if (!PC_MODIFIERS.includes(instr.opcode)) { - this.state.pc++; - } - return []; - } - - /** - * Read a public storage word. - * @param storageSlot - The starting storage slot. - * @returns value - The value read from the storage slot. - */ - private async sload(storageSlot: Fr): Promise { - const sideEffectCounter = this.sideEffectCounter.count(); - const value = await this.collapsedStorageActions.read(storageSlot, sideEffectCounter); - this.allStorageReads.push(new ContractStorageRead(storageSlot, value, sideEffectCounter)); - this.log(`Oracle storage read: slot=${storageSlot.toString()} value=${value.toString()}`); - return value; - } - /** - * Write a word to public storage. - * @param storageSlot - The storage slot. - * @param value - The value to be written. - */ - private async sstore(storageSlot: Fr, value: Fr) { - const sideEffectCounter = this.sideEffectCounter.count(); - const oldValue = await this.collapsedStorageActions.peek(storageSlot); - await this.collapsedStorageActions.write(storageSlot, value, sideEffectCounter); - this.allStorageUpdates.push(new ContractStorageUpdateRequest(storageSlot, oldValue, value, sideEffectCounter)); - await this.stateDb.storageWrite(this.context.contractAddress, storageSlot, value); - this.log(`Oracle storage write: slot=${storageSlot.toString()} value=${value.toString()}`); - } - - private async fetchAndDecodeBytecode(): Promise { - this.log(`Fetching and decoding bytecode for ${this.context.contractAddress}:${this.context.functionData.selector}`); - const bytecode = await this.contractsDb.getBytecode(this.context.contractAddress, this.context.functionData.selector); - if (!bytecode) throw new Error(`Bytecode not found for ${this.context.contractAddress}:${this.context.functionData.selector}`); - // TODO: consider decoding instructions individually as they are simulated - return AVMInstruction.fromBytecodeBuffer(bytecode); - } -} - -// public async generateWitness(outAsmPath: string) { -// await tryExec(`cd ../../barretenberg/cpp/ && ${POWDR_BINDIR}/powdr pil ${outAsmPath} --field bn254 --force`); -// } -// public async prove() { -// const log = createDebugLogger('aztec:simulator:public_vm_prove'); -// log(`Proving public vm`); -// -// await tryExec('cd ../../barretenberg/cpp/build/ && ./bin/publicvm_cli dummy-path'); -// } \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/avm_context.ts b/yarn-project/acir-simulator/src/avm/avm_context.ts new file mode 100644 index 000000000000..a0a8849b7915 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/avm_context.ts @@ -0,0 +1,35 @@ +import { Fr } from "@aztec/foundation/fields"; + + +export class AvmContext { + + public readonly calldata: Fr[]; + + // TODO: implement tagged memory + public memory: Fr[]; + + public pc: number; + public callStack: number[]; + + constructor( calldata: Fr[]) { + this.calldata = calldata; + this.memory = []; + + this.pc = 0; + this.callStack = []; + } + + + public readMemory(offset: number): Fr { + // TODO: check offset is within bounds + return this.memory[offset] ?? Fr.ZERO; + } + + public writeMemory(offset: number, value: Fr): void { + this.memory[offset] = value; + } + + public writeMemoryChunk(offset: number, values: Fr[]): void { + this.memory.splice(offset, values.length, ...values); + } +} diff --git a/yarn-project/acir-simulator/src/avm/fixtures/avm_examples.ts b/yarn-project/acir-simulator/src/avm/fixtures/avm_examples.ts index bdc152fb9824..722fb3324fa0 100644 --- a/yarn-project/acir-simulator/src/avm/fixtures/avm_examples.ts +++ b/yarn-project/acir-simulator/src/avm/fixtures/avm_examples.ts @@ -1,84 +1,84 @@ -import { AVMInstruction, Opcode } from '../opcodes.js'; +// import { AVMInstruction, Opcode } from '../opcodes.js'; -// function addExample(addArg0, addArg1) { -// return addArg0 + addArg1; -//} -const addExample = [ - // Get calldata size and store at M[0] - new AVMInstruction(Opcode.CALLDATASIZE, 0, 0, 0, 0), // SET M[0] = CD.length - // Copy calldata to memory starting at M[1] - new AVMInstruction(Opcode.CALLDATACOPY, 1, 0, 0, 0), // M[1:1+M[0]] = calldata[0+M[0]]); - // Add args and store at M[10] - new AVMInstruction(Opcode.ADD, 10, 0, 1, 2), // M[10] = M[1] + M[2] - // set return size to 1 - new AVMInstruction(Opcode.SET, 20, 0, 1, 0), // SET M[20] = 1 - new AVMInstruction(Opcode.RETURN, 0, 0, 10, 20), // return M[10] -]; -export const addBytecode = AVMInstruction.toBytecode(addExample); +// // function addExample(addArg0, addArg1) { +// // return addArg0 + addArg1; +// //} +// const addExample = [ +// // Get calldata size and store at M[0] +// new AVMInstruction(Opcode.CALLDATASIZE, 0, 0, 0, 0), // SET M[0] = CD.length +// // Copy calldata to memory starting at M[1] +// new AVMInstruction(Opcode.CALLDATACOPY, 1, 0, 0, 0), // M[1:1+M[0]] = calldata[0+M[0]]); +// // Add args and store at M[10] +// new AVMInstruction(Opcode.ADD, 10, 0, 1, 2), // M[10] = M[1] + M[2] +// // set return size to 1 +// new AVMInstruction(Opcode.SET, 20, 0, 1, 0), // SET M[20] = 1 +// new AVMInstruction(Opcode.RETURN, 0, 0, 10, 20), // return M[10] +// ]; +// export const addBytecode = AVMInstruction.toBytecode(addExample); -// function storageExample(addArg0, slotArg) { -// S[slotArg] = addArg0 + S[slotArg]; -// return S[slotArg]; -//} -const storageExample = [ - // Get calldata size and store at M[0] - new AVMInstruction(Opcode.CALLDATASIZE, 0, 0, 0, 0), // SET M[0] = CD.length - // Copy calldata to memory starting at M[1] - new AVMInstruction(Opcode.CALLDATACOPY, 1, 0, 0, 0), // M[1:1+M[0]] = calldata[0+M[0]]); - // Arg1 species storage slot to load from (S[M[2]]) - // load it into M[3] - new AVMInstruction(Opcode.SLOAD, 3, 0, 2, 0), // M[3] = S[M[2]] - // Add arg0 to value loaded from storage. Store result to memory at M[10]. - new AVMInstruction(Opcode.ADD, 10, 0, 1, 3), // M[10] = M[1] + M[2] - // store results of ADD to the same storage slot S[M[2]] - new AVMInstruction(Opcode.SSTORE, 2, 0, 10, 0), // S[M[2]] = M[10] - // load the same word from storage (S[M[2]]) that was just written. - // store word to memory (M[4]) - // (should now have value stored above) - new AVMInstruction(Opcode.SLOAD, 4, 0, 2, 0), // M[4] = S[M[2]] - // set return size to 1 - new AVMInstruction(Opcode.SET, 20, 0, 1, 0), // SET M[20] = 1 - // return the word loaded from storage (should match ADD output) - new AVMInstruction(Opcode.RETURN, 0, 0, 4, 20), // return M[4] -]; -export const storageBytecode = AVMInstruction.toBytecode(storageExample); +// // function storageExample(addArg0, slotArg) { +// // S[slotArg] = addArg0 + S[slotArg]; +// // return S[slotArg]; +// //} +// const storageExample = [ +// // Get calldata size and store at M[0] +// new AVMInstruction(Opcode.CALLDATASIZE, 0, 0, 0, 0), // SET M[0] = CD.length +// // Copy calldata to memory starting at M[1] +// new AVMInstruction(Opcode.CALLDATACOPY, 1, 0, 0, 0), // M[1:1+M[0]] = calldata[0+M[0]]); +// // Arg1 species storage slot to load from (S[M[2]]) +// // load it into M[3] +// new AVMInstruction(Opcode.SLOAD, 3, 0, 2, 0), // M[3] = S[M[2]] +// // Add arg0 to value loaded from storage. Store result to memory at M[10]. +// new AVMInstruction(Opcode.ADD, 10, 0, 1, 3), // M[10] = M[1] + M[2] +// // store results of ADD to the same storage slot S[M[2]] +// new AVMInstruction(Opcode.SSTORE, 2, 0, 10, 0), // S[M[2]] = M[10] +// // load the same word from storage (S[M[2]]) that was just written. +// // store word to memory (M[4]) +// // (should now have value stored above) +// new AVMInstruction(Opcode.SLOAD, 4, 0, 2, 0), // M[4] = S[M[2]] +// // set return size to 1 +// new AVMInstruction(Opcode.SET, 20, 0, 1, 0), // SET M[20] = 1 +// // return the word loaded from storage (should match ADD output) +// new AVMInstruction(Opcode.RETURN, 0, 0, 4, 20), // return M[4] +// ]; +// export const storageBytecode = AVMInstruction.toBytecode(storageExample); -// Make nested call to the specified address with some args -// return nested call results -//function nestedCallExample(targetAddr, [nestedCallArgs]) { -// gas = 1234 -// return CALL(gas, targetAddr, nestedCallArgs) -//} -const nestedCallExample = [ - // Get calldata size and store at M[0] - new AVMInstruction(Opcode.CALLDATASIZE, 0, 0, 0, 0), // SET M[0] = CD.length - // Copy calldata to memory starting at M[1] - new AVMInstruction(Opcode.CALLDATACOPY, 1, 0, 0, 0), // M[1:1+M[0]] = calldata[0+M[0]]); - // gas limit for CALL - new AVMInstruction(Opcode.SET, 100, 0, 1234, 0), // SET M[100] = 1234 - // Populate M[10,11,12,13] with the argsOffset, argsSize, retOffset, retSize for CALL - // argsOffset for CALL: M[2] because M[0] is calldatasize and M[1] is targetAddress (arg0 to top call) - new AVMInstruction(Opcode.SET, 10, 0, 2, 0), // SET M[10] = 2 (points to second arg) - // const 1 for subtraction below - new AVMInstruction(Opcode.SET, 20, 0, 1, 0), // SET M[10] = 1 - // argsSize for CALL: CALLDATASIZE - 1 - // - args/calldata for nested call is pretty much the same as this call's calldata - // but we don't forward the nested call address - new AVMInstruction(Opcode.SUB, 11, 0, 0, 20), // SET M[11] = M[0] - 1 - // ReturnData will be one word and will be placed at M[200] - // retOffset for CALL: where will returnData go - new AVMInstruction(Opcode.SET, 12, 0, 200, 0), // SET M[12] = M[200] - // retSize for CALL: just one return field - new AVMInstruction(Opcode.SET, 13, 0, 1, 0), // SET M[13] = 1 - // register 14 will contain the argsAndRetOffset (points to address 10 where M[10,11,12,13] contain argsOffset, argsSize, retOffset, retSize) - new AVMInstruction(Opcode.SET, 14, 0, 10, 0), // SET M[14] = 10 - // Make a nested CALL with: - // - gas: M[100] (1234) - // - targetAddress: M[1] - // - argsAndRetOffset: M[14] (10 which points to M[10,11,12,13] containing argsOffset, argsSize, retOffset, retSize - new AVMInstruction(Opcode.CALL, 0, 14, 100, 1), - // TODO: add support for RETURNDATASIZE/COPY - new AVMInstruction(Opcode.RETURN, 0, 0, 200, 13), // return M[200] (size 1 from M[13]) -]; -export const nestedCallBytecode = AVMInstruction.toBytecode(nestedCallExample); +// // Make nested call to the specified address with some args +// // return nested call results +// //function nestedCallExample(targetAddr, [nestedCallArgs]) { +// // gas = 1234 +// // return CALL(gas, targetAddr, nestedCallArgs) +// //} +// const nestedCallExample = [ +// // Get calldata size and store at M[0] +// new AVMInstruction(Opcode.CALLDATASIZE, 0, 0, 0, 0), // SET M[0] = CD.length +// // Copy calldata to memory starting at M[1] +// new AVMInstruction(Opcode.CALLDATACOPY, 1, 0, 0, 0), // M[1:1+M[0]] = calldata[0+M[0]]); +// // gas limit for CALL +// new AVMInstruction(Opcode.SET, 100, 0, 1234, 0), // SET M[100] = 1234 +// // Populate M[10,11,12,13] with the argsOffset, argsSize, retOffset, retSize for CALL +// // argsOffset for CALL: M[2] because M[0] is calldatasize and M[1] is targetAddress (arg0 to top call) +// new AVMInstruction(Opcode.SET, 10, 0, 2, 0), // SET M[10] = 2 (points to second arg) +// // const 1 for subtraction below +// new AVMInstruction(Opcode.SET, 20, 0, 1, 0), // SET M[10] = 1 +// // argsSize for CALL: CALLDATASIZE - 1 +// // - args/calldata for nested call is pretty much the same as this call's calldata +// // but we don't forward the nested call address +// new AVMInstruction(Opcode.SUB, 11, 0, 0, 20), // SET M[11] = M[0] - 1 +// // ReturnData will be one word and will be placed at M[200] +// // retOffset for CALL: where will returnData go +// new AVMInstruction(Opcode.SET, 12, 0, 200, 0), // SET M[12] = M[200] +// // retSize for CALL: just one return field +// new AVMInstruction(Opcode.SET, 13, 0, 1, 0), // SET M[13] = 1 +// // register 14 will contain the argsAndRetOffset (points to address 10 where M[10,11,12,13] contain argsOffset, argsSize, retOffset, retSize) +// new AVMInstruction(Opcode.SET, 14, 0, 10, 0), // SET M[14] = 10 +// // Make a nested CALL with: +// // - gas: M[100] (1234) +// // - targetAddress: M[1] +// // - argsAndRetOffset: M[14] (10 which points to M[10,11,12,13] containing argsOffset, argsSize, retOffset, retSize +// new AVMInstruction(Opcode.CALL, 0, 14, 100, 1), +// // TODO: add support for RETURNDATASIZE/COPY +// new AVMInstruction(Opcode.RETURN, 0, 0, 200, 13), // return M[200] (size 1 from M[13]) +// ]; +// export const nestedCallBytecode = AVMInstruction.toBytecode(nestedCallExample); diff --git a/yarn-project/acir-simulator/src/avm/index.ts b/yarn-project/acir-simulator/src/avm/index.ts index 749aaa14cbd1..e69de29bb2d1 100644 --- a/yarn-project/acir-simulator/src/avm/index.ts +++ b/yarn-project/acir-simulator/src/avm/index.ts @@ -1 +0,0 @@ -export { AVMExecutor } from './avm.js'; \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/interpreter/index.ts b/yarn-project/acir-simulator/src/avm/interpreter/index.ts index e69de29bb2d1..8a75bf842c38 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/index.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/index.ts @@ -0,0 +1 @@ +export * from "./interpreter.js" \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts new file mode 100644 index 000000000000..055d660e39ff --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts @@ -0,0 +1,9 @@ + + + +describe("interpreter", () => { + + it("Should execute a series of opcodes", () => { + + }); +}); \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts new file mode 100644 index 000000000000..0633421c2443 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -0,0 +1,38 @@ + + +// import { AvmContext } from "../avm_context.js"; +import { AvmContext } from "../avm_context.js"; +import { Opcode } from "../opcodes/index.js"; + +// Function that will take in the opcode the interpreter state and the world state - then execute it + +class AvmInterpreter { + + private opcodes: Opcode[] = []; + private context: AvmContext; + // private journal: ContractStorageActionsCollector; + + constructor(context: AvmContext, bytecode: Opcode[]) { + this.context = context; + this.opcodes = bytecode; + } + + public static new(context: AvmContext, bytecode: Opcode[]) { + return new AvmInterpreter(context, bytecode); + } + + public static fromOpcodes(opcodes: Opcode[]) { + // No calldata here -> decide which order around this should go + const context = new AvmContext([]); + const interpreter = new AvmInterpreter(context, opcodes); + interpreter.opcodes = opcodes; + return interpreter; + } + + run() { + for (const opcode of this.opcodes) { + opcode.execute(this.context); + } + } +} + diff --git a/yarn-project/acir-simulator/src/avm/opcodes.ts b/yarn-project/acir-simulator/src/avm/opcodes.ts deleted file mode 100644 index 1970c0a31ab0..000000000000 --- a/yarn-project/acir-simulator/src/avm/opcodes.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { createDebugLogger } from "@aztec/foundation/log"; - -export enum Opcode { - // Arithmetic - ADD, - SUB, - MUL, - DIV, - EQ, - LT, - LTE, - AND, - OR, - XOR, - NOT, - SHL, - SHR, - // Memory - SET, - MOV, - CALLDATASIZE, - CALLDATACOPY, - // Control flow - JUMP, - JUMPI, - INTERNALCALL, - INTERNALRETURN, - // Storage - SLOAD, - SSTORE, - // Contract call control flow - RETURN, - REVERT, - CALL, - // Call context - SENDER, - ADDRESS, - SELECTOR, - ARGSHASH, - // Blackbox ops - PEDERSEN, - KECCAK256, -} - -export const PC_MODIFIERS = [ Opcode.JUMP, Opcode.JUMPI, Opcode.INTERNALCALL, Opcode.INTERNALRETURN ]; - -export class AVMInstruction { - /** Size of an instruction */ - public static readonly BYTELEN = 1+4+4+4+4+1+1; - - constructor( - public opcode: Opcode, - public d0: number, - public sd: number, - public s0: number, - public s1: number, - public d0Indirect: boolean = false, - public s0Indirect: boolean = false, - ) {} - - //public toBuffer(offset: number = 0): Buffer { - // const buf = Buffer.alloc(AVMInstruction.BYTELEN); - // this.intoBuffer(buf, offset); - // return buf - //} - - private intoBuffer(buf: Buffer, offset: number = 0) { - buf.writeUInt8(this.opcode, offset); - offset += 1; - buf.writeUInt32BE(this.d0, offset); - offset += 4; - buf.writeUInt32BE(this.sd, offset); - offset += 4; - buf.writeUInt32BE(this.s0, offset); - offset += 4; - buf.writeUInt32BE(this.s1, offset); - offset += 4; - buf.writeUInt8(Number(this.d0Indirect), offset); - offset += 1; - buf.writeUInt8(Number(this.s0Indirect), offset); - offset += 1; - } - - public static fromBuffer(buf: Buffer, offset: number = 0): AVMInstruction { - const log = createDebugLogger('aztec:simulator:avm_instructions'); - const opcode = buf.readUInt8(offset); - offset += 1; - const d0 = buf.readUInt32BE(offset); // d0 - offset += 4; - const sd = buf.readUInt32BE(offset); // sd - offset += 4; - const s0 = buf.readUInt32BE(offset); // s0 - offset += 4; - const s1 = buf.readUInt32BE(offset); // s1 - offset += 4; - const d0Indirect = Boolean(buf.readUInt8(offset)); - offset += 1; - const s0Indirect = Boolean(buf.readUInt8(offset)); - offset += 1; - log(`Instruction from buffer: opcode:${opcode} d0:${d0} sd:${sd} s0:${s0} s1:${s1}`); - return new AVMInstruction(opcode, d0, sd, s0, s1, d0Indirect, s0Indirect); - } - - public static fromBytecodeBuffer(buf: Buffer): AVMInstruction[] { - const log = createDebugLogger('aztec:simulator:avm_instructions'); - if (buf.length % AVMInstruction.BYTELEN !== 0) throw new Error(`Invalid bytecode length`); - const numInstructions = buf.length / AVMInstruction.BYTELEN; - const instructions: AVMInstruction[] = []; - for (let pc = 0; pc < numInstructions; pc++) { - const instr = AVMInstruction.fromBuffer(buf, pc * AVMInstruction.BYTELEN) - log(`Decoded instruction (pc:${pc}): ${Opcode[instr.opcode]}`); - instructions.push(instr); - } - return instructions; - } - - public static toBytecode(instructions: AVMInstruction[]): Buffer { - const buf = Buffer.alloc(AVMInstruction.BYTELEN * instructions.length); - for (let i = 0; i < instructions.length; i++) { - instructions[i].intoBuffer(buf, i * AVMInstruction.BYTELEN); - } - return buf; - } -} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts index e69de29bb2d1..9dd308f2ac67 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts @@ -0,0 +1,142 @@ +import { Fr } from "@aztec/foundation/fields"; +import { AvmContext } from "../avm_context.js"; +import { Opcode } from "./opcode.js"; + +export class Add implements Opcode { + static type: string = "ADD"; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext): void { + const a = context.readMemory(this.aOffset); + const b = context.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() + b.toBigInt() % Fr.MODULUS); + context.writeMemory(this.destOffset, dest); + } +} + +export class Sub implements Opcode { + static type: string = "SUB"; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext): void { + const a = context.readMemory(this.aOffset); + const b = context.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() - b.toBigInt() % Fr.MODULUS); + context.writeMemory(this.destOffset, dest); + } +} + +export class Mul implements Opcode { + static type: string = "MUL"; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() * b.toBigInt() % Fr.MODULUS); + context.writeMemory(this.destOffset, dest); + } +} + +export class Div implements Opcode { + static type: string = "DIV"; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: proper field division + const dest = new Fr(a.toBigInt() / b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } +} +export class Eq implements Opcode { + static type: string = "EQ"; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(a.toBigInt() == b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } +} +export class Lt implements Opcode { + static type: string = "Lt"; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(a.toBigInt() < b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } +} + +export class Lte implements Opcode { + static type: string = "LTE"; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(a.toBigInt() < b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } +} + +export class Shl implements Opcode { + static type: string = "SHL"; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(a.toBigInt() << b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } +} + +export class Shr implements Opcode { + static type: string = "SHR"; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(a.toBigInt() >> b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts new file mode 100644 index 000000000000..67fd253225be --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts @@ -0,0 +1,66 @@ +import { Fr } from "@aztec/foundation/fields"; +import { AvmContext } from "../avm_context.js"; +import { Opcode } from "./opcode.js"; + +export class And implements Opcode { + static type: string = "AND"; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(a.toBigInt() & b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } +} + +export class Or implements Opcode { + static type: string = "OR"; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(a.toBigInt() | b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } +} + +export class Xor implements Opcode { + static type: string = "XOR"; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(a.toBigInt() ^ b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } +} + +export class Not implements Opcode { + static type: string = "NOT"; + static numberOfOperands = 2; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext): void { + const a: Fr = context.readMemory(this.aOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(~a.toBigInt()); + context.writeMemory(this.destOffset, dest); + } +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.test.ts new file mode 100644 index 000000000000..10bac448f0eb --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.test.ts @@ -0,0 +1,40 @@ +import { Add, Sub } from "./arithmetic.js"; +import { OPCODE_BYTE_LENGTH, OPERAND_BTYE_LENGTH, interpretBytecode } from "./from_bytecode.js"; +import { Opcode } from "./opcode.js"; + +describe("Avm Interpreter", () => { + + const toByte = (num: number): Buffer => { + const buf = Buffer.alloc(OPCODE_BYTE_LENGTH); + buf.writeUInt8(num); + return buf; + } + const to4Byte = (num: number): Buffer => { + const buf = Buffer.alloc(OPERAND_BTYE_LENGTH); + buf.writeUInt32BE(num); + return buf; + } + + it("Should read bytecode string into a list of opcodes", () => { + const opcode = 1; + const opcode2 = 2; + const a = 1; + const b = 2; + const c = 3; + + const ops = toByte(opcode); + const ops2 = toByte(opcode2); + const as = to4Byte(a); + const bs = to4Byte(b); + const cs = to4Byte(c); + const bytecode = Buffer.concat([ops, as, bs, cs, ops2, as, bs, cs]); + + const expectedOpcodes: Opcode[] = [ + new Add(a,b,c), + new Sub(a,b,c) + ]; + + const opcodes = interpretBytecode(bytecode); + expect(opcodes).toEqual(expectedOpcodes); + }); +}); \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts new file mode 100644 index 000000000000..880c99f5332e --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts @@ -0,0 +1,56 @@ +import { Opcode } from "./opcode.js"; +import { Add, Sub, Mul } from "./arithmetic.js"; + +export const OPERAND_BIT_LENGTH = 32; +export const OPERAND_BTYE_LENGTH = 4; +export const OPCODE_BIT_LENGTH = 8; +export const OPCODE_BYTE_LENGTH = 1; + + +const OPERANDS_LOOKUP: { [key: number]: number } = { + 0x1: Add.numberOfOperands, + 0x2: Sub.numberOfOperands, + 0x3: Mul.numberOfOperands, +} + +function opcodeLookup(opcode: number, operands: number[]): Opcode { + switch (opcode) { + case 0x1: + return new Add(operands[0], operands[1], operands[2]); + case 0x2: + return new Sub(operands[0], operands[1], operands[2]); + case 0x3: + return new Mul(operands[0], operands[1], operands[2]); + default: + throw new Error(`Opcode ${opcode} not found`); + } +} + +/** + * Convert a buffer of bytecode into an array of opcodes + * @param bytecode - Buffer of bytecode + * @returns Bytecode interpreted into an ordered array of Opcodes + */ +export function interpretBytecode(bytecode: Buffer) : Opcode[] { + let readPtr = 0; + const bytecodeLength = bytecode.length; + + const opcodes: Opcode[] = []; + + while (readPtr < bytecodeLength) { + const opcode = bytecode[readPtr]; + readPtr += 1; + + const numberOfOperands = OPERANDS_LOOKUP[opcode]; + const operands: number[] = []; + for (let i = 0; i < numberOfOperands; i++) { + const operand = bytecode.readUInt32BE(readPtr); + readPtr += OPERAND_BTYE_LENGTH; + operands.push(operand); + } + + opcodes.push(opcodeLookup(opcode, operands)); + } + + return opcodes; +} \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/opcodes/index.ts b/yarn-project/acir-simulator/src/avm/opcodes/index.ts index d96e3a472e5f..3a836e7aa895 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/index.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/index.ts @@ -1,3 +1,4 @@ export * from "./arithmetic.js"; export * from "./control_flow.js"; -export * from "./call.js"; \ No newline at end of file +export * from "./call.js"; +export * from "./opcode.js" \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts new file mode 100644 index 000000000000..7864c39f1369 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts @@ -0,0 +1,55 @@ + +import { Fr } from "@aztec/foundation/fields"; +import { AvmContext } from "../avm_context.js"; +import { Opcode } from "./opcode.js"; + +export class Set implements Opcode { + static type: string = "SET"; + static numberOfOperands = 2; + + constructor(private constt: bigint, private destOffset: number) {} + + execute(context: AvmContext): void { + const dest = new Fr(this.constt); + context.writeMemory(this.destOffset, dest); + } +} + +// TODO: tags are not implemented yet - this will behave as a mov +export class Cast implements Opcode { + static type: string = "CAST"; + static numberOfOperands = 2; + + constructor(private aOffset: number, private destOffset: number) {} + + execute(context: AvmContext): void { + const a = context.readMemory(this.aOffset); + + context.writeMemory(this.destOffset, a); + } +} + +export class Mov implements Opcode { + static type: string = "MOV"; + static numberOfOperands = 2; + + constructor(private aOffset: number, private destOffset: number) {} + + execute(context: AvmContext): void { + const a = context.readMemory(this.aOffset); + + context.writeMemory(this.destOffset, a); + } +} + +export class CallDataCopy implements Opcode { + static type: string = "CALLDATACOPY"; + static numberOfOperands = 3; + + constructor(private cdOffset: number, private copySize: number, private destOffset: number) {} + + execute(context: AvmContext): void { + const calldata = context.calldata.slice(this.cdOffset, this.cdOffset + this.copySize); + context.writeMemoryChunk(this.destOffset, calldata); + } +} \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/opcodes/opcode.ts b/yarn-project/acir-simulator/src/avm/opcodes/opcode.ts new file mode 100644 index 000000000000..7936e83c7854 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/opcode.ts @@ -0,0 +1,9 @@ +import { AvmContext } from "../avm_context.js"; + +/** + * Opcode base class + */ +export abstract class Opcode { + + abstract execute(context: AvmContext): void; +} \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts b/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts new file mode 100644 index 000000000000..23b6aeb43ba3 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts @@ -0,0 +1,78 @@ + +/** + * All avm opcodes + */ +export enum Opcodes { + // Arithmetic + ADD, + SUB, + MUL, + DIV, + EQ, + LT, + LTE, + AND, + OR, + XOR, + NOT, + SHL, + SHR, + CAST, + // Memory + SET, + MOV, + CMOV, + CALLDATACOPY, + + // Logs + EMITNOTEHASH, + EMITNULLIFIER, + SENDL2TOL1MSG, + ULOG, + // Control flow + JUMP, + JUMPI, + INTERNALCALL, + INTERNALRETURN, + + // Storage + SLOAD, + SSTORE, + // Contract call control flow + RETURN, + REVERT, + CALL, + STATICCALL, + + CHAINID, + VERSION, + BLOCKNUMBER, + TIMESTAMP, + COINBASE, + BLOCKL1GASLIMIT, + BLOCKL2GASLIMIT, + NULLIFIERSOOT, + CONTRACTSROOT, + MSGSROOT, + NOTESROOT, + PUBLICDATAROOT, + GLOBALSHASH, + BLOCKSROOT, // TODO: are these the same + GRANDROOT, + + // Call context + ORIGIN, + REFUNDEE, + FEEPERL1GAS, + FEEPERL2GAS, + CALLER, + ADDRESS, + PORTAL, + CALLDEPTH, + l1GAS, + L2GAS, + + // Black box + KECCAK, + POSEIDON +} \ No newline at end of file From dc8cd7668b9fac023b91e476d4db834485ddb91e Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 10 Jan 2024 18:57:07 +0000 Subject: [PATCH 04/20] chore: interpreter test --- .../acir-simulator/src/avm/avm_context.ts | 18 ++++ .../src/avm/fixtures/avm_examples.ts | 84 ------------------- .../acir-simulator/src/avm/fixtures/index.ts | 1 + .../src/avm/interpreter/interpreter.test.ts | 28 ++++++- .../src/avm/interpreter/interpreter.ts | 33 ++++++-- .../src/avm/opcodes/control_flow.ts | 14 ++++ 6 files changed, 86 insertions(+), 92 deletions(-) delete mode 100644 yarn-project/acir-simulator/src/avm/fixtures/avm_examples.ts create mode 100644 yarn-project/acir-simulator/src/avm/fixtures/index.ts diff --git a/yarn-project/acir-simulator/src/avm/avm_context.ts b/yarn-project/acir-simulator/src/avm/avm_context.ts index a0a8849b7915..53a9ae87b973 100644 --- a/yarn-project/acir-simulator/src/avm/avm_context.ts +++ b/yarn-project/acir-simulator/src/avm/avm_context.ts @@ -4,6 +4,7 @@ import { Fr } from "@aztec/foundation/fields"; export class AvmContext { public readonly calldata: Fr[]; + private returnData: Fr[]; // TODO: implement tagged memory public memory: Fr[]; @@ -13,18 +14,35 @@ export class AvmContext { constructor( calldata: Fr[]) { this.calldata = calldata; + this.returnData = []; this.memory = []; this.pc = 0; this.callStack = []; } + /** + * Return data must NOT be modified once it is set + */ + public setReturnData(returnData: Fr[]) { + this.returnData = returnData; + Object.freeze(returnData); + } + + public getReturnData(): Fr[] { + return this.returnData; + } public readMemory(offset: number): Fr { // TODO: check offset is within bounds return this.memory[offset] ?? Fr.ZERO; } + public readMemoryChunk(offset: number, size: number): Fr[] { + // TODO: bounds -> initialise to 0 + return this.memory.slice(offset, offset + size); + } + public writeMemory(offset: number, value: Fr): void { this.memory[offset] = value; } diff --git a/yarn-project/acir-simulator/src/avm/fixtures/avm_examples.ts b/yarn-project/acir-simulator/src/avm/fixtures/avm_examples.ts deleted file mode 100644 index 722fb3324fa0..000000000000 --- a/yarn-project/acir-simulator/src/avm/fixtures/avm_examples.ts +++ /dev/null @@ -1,84 +0,0 @@ -// import { AVMInstruction, Opcode } from '../opcodes.js'; - - -// // function addExample(addArg0, addArg1) { -// // return addArg0 + addArg1; -// //} -// const addExample = [ -// // Get calldata size and store at M[0] -// new AVMInstruction(Opcode.CALLDATASIZE, 0, 0, 0, 0), // SET M[0] = CD.length -// // Copy calldata to memory starting at M[1] -// new AVMInstruction(Opcode.CALLDATACOPY, 1, 0, 0, 0), // M[1:1+M[0]] = calldata[0+M[0]]); -// // Add args and store at M[10] -// new AVMInstruction(Opcode.ADD, 10, 0, 1, 2), // M[10] = M[1] + M[2] -// // set return size to 1 -// new AVMInstruction(Opcode.SET, 20, 0, 1, 0), // SET M[20] = 1 -// new AVMInstruction(Opcode.RETURN, 0, 0, 10, 20), // return M[10] -// ]; -// export const addBytecode = AVMInstruction.toBytecode(addExample); - -// // function storageExample(addArg0, slotArg) { -// // S[slotArg] = addArg0 + S[slotArg]; -// // return S[slotArg]; -// //} -// const storageExample = [ -// // Get calldata size and store at M[0] -// new AVMInstruction(Opcode.CALLDATASIZE, 0, 0, 0, 0), // SET M[0] = CD.length -// // Copy calldata to memory starting at M[1] -// new AVMInstruction(Opcode.CALLDATACOPY, 1, 0, 0, 0), // M[1:1+M[0]] = calldata[0+M[0]]); -// // Arg1 species storage slot to load from (S[M[2]]) -// // load it into M[3] -// new AVMInstruction(Opcode.SLOAD, 3, 0, 2, 0), // M[3] = S[M[2]] -// // Add arg0 to value loaded from storage. Store result to memory at M[10]. -// new AVMInstruction(Opcode.ADD, 10, 0, 1, 3), // M[10] = M[1] + M[2] -// // store results of ADD to the same storage slot S[M[2]] -// new AVMInstruction(Opcode.SSTORE, 2, 0, 10, 0), // S[M[2]] = M[10] -// // load the same word from storage (S[M[2]]) that was just written. -// // store word to memory (M[4]) -// // (should now have value stored above) -// new AVMInstruction(Opcode.SLOAD, 4, 0, 2, 0), // M[4] = S[M[2]] -// // set return size to 1 -// new AVMInstruction(Opcode.SET, 20, 0, 1, 0), // SET M[20] = 1 -// // return the word loaded from storage (should match ADD output) -// new AVMInstruction(Opcode.RETURN, 0, 0, 4, 20), // return M[4] -// ]; -// export const storageBytecode = AVMInstruction.toBytecode(storageExample); - -// // Make nested call to the specified address with some args -// // return nested call results -// //function nestedCallExample(targetAddr, [nestedCallArgs]) { -// // gas = 1234 -// // return CALL(gas, targetAddr, nestedCallArgs) -// //} -// const nestedCallExample = [ -// // Get calldata size and store at M[0] -// new AVMInstruction(Opcode.CALLDATASIZE, 0, 0, 0, 0), // SET M[0] = CD.length -// // Copy calldata to memory starting at M[1] -// new AVMInstruction(Opcode.CALLDATACOPY, 1, 0, 0, 0), // M[1:1+M[0]] = calldata[0+M[0]]); -// // gas limit for CALL -// new AVMInstruction(Opcode.SET, 100, 0, 1234, 0), // SET M[100] = 1234 -// // Populate M[10,11,12,13] with the argsOffset, argsSize, retOffset, retSize for CALL -// // argsOffset for CALL: M[2] because M[0] is calldatasize and M[1] is targetAddress (arg0 to top call) -// new AVMInstruction(Opcode.SET, 10, 0, 2, 0), // SET M[10] = 2 (points to second arg) -// // const 1 for subtraction below -// new AVMInstruction(Opcode.SET, 20, 0, 1, 0), // SET M[10] = 1 -// // argsSize for CALL: CALLDATASIZE - 1 -// // - args/calldata for nested call is pretty much the same as this call's calldata -// // but we don't forward the nested call address -// new AVMInstruction(Opcode.SUB, 11, 0, 0, 20), // SET M[11] = M[0] - 1 -// // ReturnData will be one word and will be placed at M[200] -// // retOffset for CALL: where will returnData go -// new AVMInstruction(Opcode.SET, 12, 0, 200, 0), // SET M[12] = M[200] -// // retSize for CALL: just one return field -// new AVMInstruction(Opcode.SET, 13, 0, 1, 0), // SET M[13] = 1 -// // register 14 will contain the argsAndRetOffset (points to address 10 where M[10,11,12,13] contain argsOffset, argsSize, retOffset, retSize) -// new AVMInstruction(Opcode.SET, 14, 0, 10, 0), // SET M[14] = 10 -// // Make a nested CALL with: -// // - gas: M[100] (1234) -// // - targetAddress: M[1] -// // - argsAndRetOffset: M[14] (10 which points to M[10,11,12,13] containing argsOffset, argsSize, retOffset, retSize -// new AVMInstruction(Opcode.CALL, 0, 14, 100, 1), -// // TODO: add support for RETURNDATASIZE/COPY -// new AVMInstruction(Opcode.RETURN, 0, 0, 200, 13), // return M[200] (size 1 from M[13]) -// ]; -// export const nestedCallBytecode = AVMInstruction.toBytecode(nestedCallExample); diff --git a/yarn-project/acir-simulator/src/avm/fixtures/index.ts b/yarn-project/acir-simulator/src/avm/fixtures/index.ts new file mode 100644 index 000000000000..34539cf2b6e4 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/fixtures/index.ts @@ -0,0 +1 @@ +// Place large AVM text fixtures in here \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts index 055d660e39ff..9b8195b43065 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts @@ -1,9 +1,33 @@ - - +import {Fr} from "@aztec/foundation/fields"; +import { Add } from "../opcodes/arithmetic.js"; +import { CallDataCopy } from "../opcodes/memory.js"; +import { Opcode } from "../opcodes/opcode.js"; +import { AvmInterpreter } from "./interpreter.js"; +import { AvmContext } from "../avm_context.js"; +import { Return } from "../opcodes/control_flow.js"; describe("interpreter", () => { it("Should execute a series of opcodes", () => { + const calldata: Fr[] = [new Fr(1), new Fr(2)] + + const opcodes: Opcode[] = [ + // Copy the first two elements of the calldata to memory regions 0 and 1 + new CallDataCopy(0, 2, 0), + // Add the two together and store the result in memory region 2 + new Add(0, 1, 2), // 1 + 2 + // Return the result + new Return(2, 1) // [3] + ] + + const context = new AvmContext(calldata); + const interpreter = new AvmInterpreter(context, opcodes); + const success = interpreter.run(); + + expect(success).toBe(true); + const returnData = interpreter.returnData(); + expect(returnData.length).toBe(1); + expect(returnData).toEqual([new Fr(3)]); }); }); \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts index 0633421c2443..1869ecfdcfd9 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -1,12 +1,11 @@ - - // import { AvmContext } from "../avm_context.js"; +import { Fr } from "@aztec/foundation/fields"; import { AvmContext } from "../avm_context.js"; import { Opcode } from "../opcodes/index.js"; // Function that will take in the opcode the interpreter state and the world state - then execute it -class AvmInterpreter { +export class AvmInterpreter { private opcodes: Opcode[] = []; private context: AvmContext; @@ -29,10 +28,32 @@ class AvmInterpreter { return interpreter; } - run() { - for (const opcode of this.opcodes) { - opcode.execute(this.context); + /** + * Run the avm + * @returns bool - successful execution will return true + * - reverted execution will return false + * - any other panic will throw + */ + run(): boolean { + try { + for (const opcode of this.opcodes) { + opcode.execute(this.context); + } + + return true; + } catch (e) { + // TODO: This should only accept AVM defined errors, anything else SHOULD be thrown upstream + return false; } } + + /** + * Get the return data from avm execution + * TODO: this should fail if the code has not been executed + * - maybe move the return in run into a variable and track it + */ + returnData(): Fr[] { + return this.context.getReturnData(); + } } 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 e69de29bb2d1..f888791c52da 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts @@ -0,0 +1,14 @@ +import { AvmContext } from "../avm_context.js"; +import { Opcode } from "./opcode.js"; + +export class Return implements Opcode { + static type: string = "RETURN"; + static numberOfOperands = 2; + + constructor(private returnOffset: number, private copySize: number) {} + + execute(context: AvmContext): void { + const returnData = context.readMemoryChunk(this.returnOffset, this.returnOffset + this.copySize); + context.setReturnData(returnData); + } +} \ No newline at end of file From d5629ab56c4037c0e5a0ac35fe3b3ceaa062287d Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 10 Jan 2024 19:52:13 +0000 Subject: [PATCH 05/20] feat: Journal interface --- .../src/avm/journal/host_storage.ts | 13 ++++ .../acir-simulator/src/avm/journal/index.ts | 2 + .../src/avm/journal/journal.test.ts | 17 +++++ .../acir-simulator/src/avm/journal/journal.ts | 68 +++++++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 yarn-project/acir-simulator/src/avm/journal/host_storage.ts create mode 100644 yarn-project/acir-simulator/src/avm/journal/index.ts create mode 100644 yarn-project/acir-simulator/src/avm/journal/journal.test.ts create mode 100644 yarn-project/acir-simulator/src/avm/journal/journal.ts diff --git a/yarn-project/acir-simulator/src/avm/journal/host_storage.ts b/yarn-project/acir-simulator/src/avm/journal/host_storage.ts new file mode 100644 index 000000000000..95983b79e225 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/journal/host_storage.ts @@ -0,0 +1,13 @@ +import { CommitmentsDB, PublicContractsDB, PublicStateDB } from "../../index.js"; + +export class HostStorage { + public readonly stateDb: PublicStateDB; + public readonly contractsDb: PublicContractsDB; + public readonly commitmentsDb: CommitmentsDB; + + constructor(stateDb: PublicStateDB, contractsDb: PublicContractsDB, commitmentsDb: CommitmentsDB) { + this.stateDb = stateDb; + this.contractsDb = contractsDb; + this.commitmentsDb = commitmentsDb; + } +} \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/journal/index.ts b/yarn-project/acir-simulator/src/avm/journal/index.ts new file mode 100644 index 000000000000..68e0517ca06d --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/journal/index.ts @@ -0,0 +1,2 @@ +export * from "./host_storage.js"; +export * from "./journal.js"; \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/journal/journal.test.ts b/yarn-project/acir-simulator/src/avm/journal/journal.test.ts new file mode 100644 index 000000000000..e33071921426 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/journal/journal.test.ts @@ -0,0 +1,17 @@ + +describe("journal", () => { + + it("Should write to storage", () => { + + }); + + + it("Should read from storage", () => { + + }); + + it("Should merge two journals together", () => { + + }); + +}); \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/journal/journal.ts b/yarn-project/acir-simulator/src/avm/journal/journal.ts new file mode 100644 index 000000000000..ad06e9d7f360 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/journal/journal.ts @@ -0,0 +1,68 @@ +import { Fr } from "@aztec/foundation/fields"; +import { HostStorage } from "./host_storage.js"; + +// TODO: all of the data that comes out of the avm ready for write should be in this format +export type JournalData = { + newCommitments: Fr[], + newL1Message: Fr[], + storageWrites: {[key: string]: {[key: string]: Fr}}, +} + +// This persists for an entire block +// Each transaction should have its own journal that gets appended to this one upon success +export class AvmJournal { + private hostStorage: HostStorage; + + // We need to keep track of the following + // - State reads + // - State updates + // - New Commitments + // - Commitment reads + + private newCommitments: Fr[] = []; + private newL1Message: Fr[] = []; + + // TODO: type this structure -> contract address -> key -> value + private storageWrites: {[key: string]: {[key: string]: Fr}} = {}; + + constructor(hostStorage: HostStorage) { + this.hostStorage = hostStorage; + } + + // TODO: work on the typing + public writeStorage(contractAddress: Fr, key: Fr, value: Fr) { + // TODO: do we want this map to be ordered -> is there performance upside to this? + this.storageWrites[contractAddress.toString()][key.toString()] = value; + } + + public readStorage(contractAddress: Fr, key: Fr) { + const cachedValue = this.storageWrites[contractAddress.toString()][key.toString()]; + if (cachedValue) { + return cachedValue; + } + return this.hostStorage.stateDb.storageRead(contractAddress, key); + } + + public writeCommitment(commitment: Fr) { + this.newCommitments.push(commitment); + } + + public writeL1Message(message: Fr) { + this.newL1Message.push(message); + } + + // TODO: This function will merge two journals together -> the new head of the chain + public mergeJournal(journal: AvmJournal) { + // TODO: This function will + void(journal); + } + + public flush(): JournalData { + return { + newCommitments: this.newCommitments, + newL1Message: this.newL1Message, + storageWrites: this.storageWrites + } + } +} + From 34bfbb14b3552a6266d4282b94c2337b87991386 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 10 Jan 2024 21:29:45 +0000 Subject: [PATCH 06/20] feat: AvmExecutor and state manager interfaces --- yarn-project/acir-simulator/src/avm/avm.ts | 33 --------------- .../avm/{avm.test.ts => avm_executor.test.ts} | 0 .../acir-simulator/src/avm/avm_executor.ts | 40 +++++++++++++++++++ .../src/avm/avm_state_manager.ts | 19 +++++++++ .../src/avm/interpreter/interpreter.test.ts | 5 ++- .../src/avm/interpreter/interpreter.ts | 23 ++++------- .../acir-simulator/src/avm/journal/journal.ts | 3 +- .../src/avm/opcodes/arithmetic.ts | 19 ++++----- .../acir-simulator/src/avm/opcodes/bitwise.ts | 9 +++-- .../src/avm/opcodes/control_flow.ts | 3 +- .../acir-simulator/src/avm/opcodes/memory.ts | 9 +++-- .../acir-simulator/src/avm/opcodes/opcode.ts | 3 +- 12 files changed, 97 insertions(+), 69 deletions(-) delete mode 100644 yarn-project/acir-simulator/src/avm/avm.ts rename yarn-project/acir-simulator/src/avm/{avm.test.ts => avm_executor.test.ts} (100%) create mode 100644 yarn-project/acir-simulator/src/avm/avm_executor.ts create mode 100644 yarn-project/acir-simulator/src/avm/avm_state_manager.ts diff --git a/yarn-project/acir-simulator/src/avm/avm.ts b/yarn-project/acir-simulator/src/avm/avm.ts deleted file mode 100644 index bd3a2643f610..000000000000 --- a/yarn-project/acir-simulator/src/avm/avm.ts +++ /dev/null @@ -1,33 +0,0 @@ - -// Decisions: -// 1. At this current time the avm should not handle getting state itself or anything like that -// - it will purely run as an interpreter and the code / starting state will be given to it on init -// 2. Anything that is a constant -// - e.g. will go in its own relevant file -> there is no need to have it in the interpreter file - -// import { AvmContext } from "./avm_context.js"; -// import { Add, Mul, Opcode, Sub } from "./opcodes/index.js"; - - -// // First steps -// // 1. Implement the basic opcodes and do it without state - -// // AVM -// // The avm itself is responsible for -// // - Triggering a call -// // - Creating an interpreter to handle the call -// // - Getting the journal of state changes from the interpreter -// // - Applying the state changes through the state manager interface -// class Avm { - -// // The interpreter is responsible for executing -// private interpreter: AvmInterpreter; -// private stateManager: AvmStateManager; - -// // TODO: should this buffer go inside the avm or else where -// constructor(private context: AvmContext) { - -// } -// } - - diff --git a/yarn-project/acir-simulator/src/avm/avm.test.ts b/yarn-project/acir-simulator/src/avm/avm_executor.test.ts similarity index 100% rename from yarn-project/acir-simulator/src/avm/avm.test.ts rename to yarn-project/acir-simulator/src/avm/avm_executor.test.ts diff --git a/yarn-project/acir-simulator/src/avm/avm_executor.ts b/yarn-project/acir-simulator/src/avm/avm_executor.ts new file mode 100644 index 000000000000..84d0ba36d6f8 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/avm_executor.ts @@ -0,0 +1,40 @@ + +// Decisions: +// 1. At this current time the avm should not handle getting state itself or anything like that +// - it will purely run as an interpreter and the code / starting state will be given to it on init +// 2. Anything that is a constant +// - e.g. will go in its own relevant file -> there is no need to have it in the interpreter file + +import { AvmInterpreter } from "./interpreter/index.js"; +import { AvmContext } from "./avm_context.js"; + +import {Fr} from "@aztec/foundation/fields"; +import { interpretBytecode } from "./opcodes/from_bytecode.js"; +import { Opcode } from "./opcodes/opcode.js"; +import { AvmStateManager } from "./avm_state_manager.js"; + +export class AvmExecutor { + private stateManager: AvmStateManager; + + constructor(stateManager: AvmStateManager) { + this.stateManager = stateManager; + } + + public call(contractAddress: Fr, calldata: Fr[]): Fr[] { + // NOTE: the following is mocked as getPublicBytecode does not exist yet + // const bytecode = stateManager.journal.hostStorage.contractsDb.getBytecode(contractAddress); + const bytecode = Buffer.from("0x01000100020003"); + + const opcodes: Opcode[] = interpretBytecode(bytecode); + + const context = new AvmContext(calldata); + const interpreter = new AvmInterpreter(context, this.stateManager, opcodes); + + // TODO: combine the two? + interpreter.run(); + const returnData = interpreter.returnData(); + + // TODO: Do something with state hand off of a successful call + return returnData; + } +} \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/avm_state_manager.ts b/yarn-project/acir-simulator/src/avm/avm_state_manager.ts new file mode 100644 index 000000000000..e37c2b18ccd9 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/avm_state_manager.ts @@ -0,0 +1,19 @@ + +// The state manager is a wrapper around the node state +// It has access to all of the data bases +// It is responsible for tracking the state during a function call + +// It is also responsible for populating the journal as time goes on + +import { BlockHeader } from "@aztec/circuits.js"; +import { AvmJournal, HostStorage } from "./journal/index.js"; + +export class AvmStateManager { + public readonly blockHeader: BlockHeader; + public readonly journal: AvmJournal; + + constructor(blockHeader: BlockHeader, hostStorage: HostStorage) { + this.blockHeader = blockHeader; + this.journal = new AvmJournal(hostStorage); + } +} \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts index 9b8195b43065..eac041e1673a 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts @@ -5,11 +5,14 @@ import { Opcode } from "../opcodes/opcode.js"; import { AvmInterpreter } from "./interpreter.js"; import { AvmContext } from "../avm_context.js"; import { Return } from "../opcodes/control_flow.js"; +import { AvmStateManager } from "../avm_state_manager.js"; +import { mock } from "jest-mock-extended"; describe("interpreter", () => { it("Should execute a series of opcodes", () => { const calldata: Fr[] = [new Fr(1), new Fr(2)] + const stateManager = mock(); const opcodes: Opcode[] = [ // Copy the first two elements of the calldata to memory regions 0 and 1 @@ -21,7 +24,7 @@ describe("interpreter", () => { ] const context = new AvmContext(calldata); - const interpreter = new AvmInterpreter(context, opcodes); + const interpreter = new AvmInterpreter(context, stateManager, opcodes); const success = interpreter.run(); expect(success).toBe(true); diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts index 1869ecfdcfd9..2ca7697476d3 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -2,32 +2,25 @@ import { Fr } from "@aztec/foundation/fields"; import { AvmContext } from "../avm_context.js"; import { Opcode } from "../opcodes/index.js"; +import { AvmStateManager } from "../avm_state_manager.js"; // Function that will take in the opcode the interpreter state and the world state - then execute it + + +// TO DEFINE DOES THIS ONLY Interpret a SINGLE CALL FRAME OF THE AVM export class AvmInterpreter { private opcodes: Opcode[] = []; private context: AvmContext; - // private journal: ContractStorageActionsCollector; + private stateManager: AvmStateManager; - constructor(context: AvmContext, bytecode: Opcode[]) { + constructor(context: AvmContext, stateManager: AvmStateManager, bytecode: Opcode[]) { this.context = context; + this.stateManager = stateManager; this.opcodes = bytecode; } - public static new(context: AvmContext, bytecode: Opcode[]) { - return new AvmInterpreter(context, bytecode); - } - - public static fromOpcodes(opcodes: Opcode[]) { - // No calldata here -> decide which order around this should go - const context = new AvmContext([]); - const interpreter = new AvmInterpreter(context, opcodes); - interpreter.opcodes = opcodes; - return interpreter; - } - /** * Run the avm * @returns bool - successful execution will return true @@ -37,7 +30,7 @@ export class AvmInterpreter { run(): boolean { try { for (const opcode of this.opcodes) { - opcode.execute(this.context); + opcode.execute(this.context, this.stateManager); } return true; diff --git a/yarn-project/acir-simulator/src/avm/journal/journal.ts b/yarn-project/acir-simulator/src/avm/journal/journal.ts index ad06e9d7f360..67f880e75889 100644 --- a/yarn-project/acir-simulator/src/avm/journal/journal.ts +++ b/yarn-project/acir-simulator/src/avm/journal/journal.ts @@ -11,7 +11,8 @@ export type JournalData = { // This persists for an entire block // Each transaction should have its own journal that gets appended to this one upon success export class AvmJournal { - private hostStorage: HostStorage; + // TODO: should we make private? + public readonly hostStorage: HostStorage; // We need to keep track of the following // - State reads diff --git a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts index 9dd308f2ac67..fc7b29337edd 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts @@ -1,6 +1,7 @@ import { Fr } from "@aztec/foundation/fields"; import { AvmContext } from "../avm_context.js"; import { Opcode } from "./opcode.js"; +import { AvmStateManager } from "../avm_state_manager.js"; export class Add implements Opcode { static type: string = "ADD"; @@ -8,7 +9,7 @@ export class Add implements Opcode { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const a = context.readMemory(this.aOffset); const b = context.readMemory(this.bOffset); @@ -23,7 +24,7 @@ export class Sub implements Opcode { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const a = context.readMemory(this.aOffset); const b = context.readMemory(this.bOffset); @@ -38,7 +39,7 @@ export class Mul implements Opcode { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); @@ -53,7 +54,7 @@ export class Div implements Opcode { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); @@ -68,7 +69,7 @@ export class Eq implements Opcode { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); @@ -83,7 +84,7 @@ export class Lt implements Opcode { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); @@ -99,7 +100,7 @@ export class Lte implements Opcode { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); @@ -115,7 +116,7 @@ export class Shl implements Opcode { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); @@ -131,7 +132,7 @@ export class Shr implements Opcode { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts index 67fd253225be..bee49a061bb2 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts @@ -1,6 +1,7 @@ import { Fr } from "@aztec/foundation/fields"; import { AvmContext } from "../avm_context.js"; import { Opcode } from "./opcode.js"; +import { AvmStateManager } from "../avm_state_manager.js"; export class And implements Opcode { static type: string = "AND"; @@ -8,7 +9,7 @@ export class And implements Opcode { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); @@ -24,7 +25,7 @@ export class Or implements Opcode { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); @@ -40,7 +41,7 @@ export class Xor implements Opcode { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); @@ -56,7 +57,7 @@ export class Not implements Opcode { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const a: Fr = context.readMemory(this.aOffset); // TODO: floor div? - this will not perform field division 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 f888791c52da..0d3fa7294627 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts @@ -1,4 +1,5 @@ import { AvmContext } from "../avm_context.js"; +import { AvmStateManager } from "../avm_state_manager.js"; import { Opcode } from "./opcode.js"; export class Return implements Opcode { @@ -7,7 +8,7 @@ export class Return implements Opcode { constructor(private returnOffset: number, private copySize: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const returnData = context.readMemoryChunk(this.returnOffset, this.returnOffset + this.copySize); context.setReturnData(returnData); } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts index 7864c39f1369..6803052e935b 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts @@ -2,6 +2,7 @@ import { Fr } from "@aztec/foundation/fields"; import { AvmContext } from "../avm_context.js"; import { Opcode } from "./opcode.js"; +import { AvmStateManager } from "../avm_state_manager.js"; export class Set implements Opcode { static type: string = "SET"; @@ -9,7 +10,7 @@ export class Set implements Opcode { constructor(private constt: bigint, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const dest = new Fr(this.constt); context.writeMemory(this.destOffset, dest); } @@ -22,7 +23,7 @@ export class Cast implements Opcode { constructor(private aOffset: number, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const a = context.readMemory(this.aOffset); context.writeMemory(this.destOffset, a); @@ -35,7 +36,7 @@ export class Mov implements Opcode { constructor(private aOffset: number, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const a = context.readMemory(this.aOffset); context.writeMemory(this.destOffset, a); @@ -48,7 +49,7 @@ export class CallDataCopy implements Opcode { constructor(private cdOffset: number, private copySize: number, private destOffset: number) {} - execute(context: AvmContext): void { + execute(context: AvmContext, _stateManager: AvmStateManager): void { const calldata = context.calldata.slice(this.cdOffset, this.cdOffset + this.copySize); context.writeMemoryChunk(this.destOffset, calldata); } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/opcode.ts b/yarn-project/acir-simulator/src/avm/opcodes/opcode.ts index 7936e83c7854..fbabc99b25e8 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/opcode.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/opcode.ts @@ -1,9 +1,10 @@ import { AvmContext } from "../avm_context.js"; +import { AvmStateManager } from "../avm_state_manager.js"; /** * Opcode base class */ export abstract class Opcode { - abstract execute(context: AvmContext): void; + abstract execute(context: AvmContext, stateManager: AvmStateManager): void; } \ No newline at end of file From 483644c4d0f043670eb77fc18e1c1a2f5fb9eda6 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 10 Jan 2024 21:30:07 +0000 Subject: [PATCH 07/20] chore: prettier --- .../acir-simulator/src/avm/avm.legacy.test.ts | 1 - .../acir-simulator/src/avm/avm_context.ts | 10 +- .../src/avm/avm_executor.test.ts | 7 +- .../acir-simulator/src/avm/avm_executor.ts | 66 +++-- .../src/avm/avm_state_manager.ts | 20 +- .../acir-simulator/src/avm/fixtures/index.ts | 2 +- .../src/avm/interpreter/index.ts | 2 +- .../src/avm/interpreter/interpreter.test.ts | 61 ++--- .../src/avm/interpreter/interpreter.ts | 19 +- .../src/avm/journal/host_storage.ts | 20 +- .../acir-simulator/src/avm/journal/index.ts | 4 +- .../src/avm/journal/journal.test.ts | 20 +- .../acir-simulator/src/avm/journal/journal.ts | 102 ++++---- .../src/avm/opcodes/arithmetic.ts | 237 +++++++++--------- .../acir-simulator/src/avm/opcodes/bitwise.ts | 111 ++++---- .../src/avm/opcodes/control_flow.ts | 24 +- .../src/avm/opcodes/from_bytecode.test.ts | 66 +++-- .../src/avm/opcodes/from_bytecode.ts | 13 +- .../acir-simulator/src/avm/opcodes/index.ts | 8 +- .../acir-simulator/src/avm/opcodes/memory.ts | 86 +++---- .../acir-simulator/src/avm/opcodes/opcode.ts | 9 +- .../acir-simulator/src/avm/opcodes/opcodes.ts | 9 +- 22 files changed, 434 insertions(+), 463 deletions(-) diff --git a/yarn-project/acir-simulator/src/avm/avm.legacy.test.ts b/yarn-project/acir-simulator/src/avm/avm.legacy.test.ts index c4a83ffd739c..fa2a67fcaf4d 100644 --- a/yarn-project/acir-simulator/src/avm/avm.legacy.test.ts +++ b/yarn-project/acir-simulator/src/avm/avm.legacy.test.ts @@ -25,7 +25,6 @@ // nestedCallBytecode, // } from './fixtures/avm_examples.js'; - // describe('ACIR public execution simulator', () => { // const log = createDebugLogger('aztec:acir-simulator:vm.test.ts'); // let publicState: MockProxy; diff --git a/yarn-project/acir-simulator/src/avm/avm_context.ts b/yarn-project/acir-simulator/src/avm/avm_context.ts index 53a9ae87b973..8cd75c4c2f4c 100644 --- a/yarn-project/acir-simulator/src/avm/avm_context.ts +++ b/yarn-project/acir-simulator/src/avm/avm_context.ts @@ -1,18 +1,16 @@ -import { Fr } from "@aztec/foundation/fields"; - +import { Fr } from '@aztec/foundation/fields'; export class AvmContext { - public readonly calldata: Fr[]; - private returnData: Fr[]; - + private returnData: Fr[]; + // TODO: implement tagged memory public memory: Fr[]; public pc: number; public callStack: number[]; - constructor( calldata: Fr[]) { + constructor(calldata: Fr[]) { this.calldata = calldata; this.returnData = []; this.memory = []; diff --git a/yarn-project/acir-simulator/src/avm/avm_executor.test.ts b/yarn-project/acir-simulator/src/avm/avm_executor.test.ts index df8a538581c4..db93559b733a 100644 --- a/yarn-project/acir-simulator/src/avm/avm_executor.test.ts +++ b/yarn-project/acir-simulator/src/avm/avm_executor.test.ts @@ -1,6 +1 @@ - - - -describe("Avm", () => { - -}) \ No newline at end of file +describe('Avm', () => {}); diff --git a/yarn-project/acir-simulator/src/avm/avm_executor.ts b/yarn-project/acir-simulator/src/avm/avm_executor.ts index 84d0ba36d6f8..ad906966648c 100644 --- a/yarn-project/acir-simulator/src/avm/avm_executor.ts +++ b/yarn-project/acir-simulator/src/avm/avm_executor.ts @@ -1,40 +1,38 @@ - // Decisions: -// 1. At this current time the avm should not handle getting state itself or anything like that +// 1. At this current time the avm should not handle getting state itself or anything like that // - it will purely run as an interpreter and the code / starting state will be given to it on init -// 2. Anything that is a constant +// 2. Anything that is a constant // - e.g. will go in its own relevant file -> there is no need to have it in the interpreter file +import { Fr } from '@aztec/foundation/fields'; -import { AvmInterpreter } from "./interpreter/index.js"; -import { AvmContext } from "./avm_context.js"; - -import {Fr} from "@aztec/foundation/fields"; -import { interpretBytecode } from "./opcodes/from_bytecode.js"; -import { Opcode } from "./opcodes/opcode.js"; -import { AvmStateManager } from "./avm_state_manager.js"; +import { AvmContext } from './avm_context.js'; +import { AvmStateManager } from './avm_state_manager.js'; +import { AvmInterpreter } from './interpreter/index.js'; +import { interpretBytecode } from './opcodes/from_bytecode.js'; +import { Opcode } from './opcodes/opcode.js'; export class AvmExecutor { - private stateManager: AvmStateManager; - - constructor(stateManager: AvmStateManager) { - this.stateManager = stateManager; - } - - public call(contractAddress: Fr, calldata: Fr[]): Fr[] { - // NOTE: the following is mocked as getPublicBytecode does not exist yet - // const bytecode = stateManager.journal.hostStorage.contractsDb.getBytecode(contractAddress); - const bytecode = Buffer.from("0x01000100020003"); - - const opcodes: Opcode[] = interpretBytecode(bytecode); - - const context = new AvmContext(calldata); - const interpreter = new AvmInterpreter(context, this.stateManager, opcodes); - - // TODO: combine the two? - interpreter.run(); - const returnData = interpreter.returnData(); - - // TODO: Do something with state hand off of a successful call - return returnData; - } -} \ No newline at end of file + private stateManager: AvmStateManager; + + constructor(stateManager: AvmStateManager) { + this.stateManager = stateManager; + } + + public call(contractAddress: Fr, calldata: Fr[]): Fr[] { + // NOTE: the following is mocked as getPublicBytecode does not exist yet + // const bytecode = stateManager.journal.hostStorage.contractsDb.getBytecode(contractAddress); + const bytecode = Buffer.from('0x01000100020003'); + + const opcodes: Opcode[] = interpretBytecode(bytecode); + + const context = new AvmContext(calldata); + const interpreter = new AvmInterpreter(context, this.stateManager, opcodes); + + // TODO: combine the two? + interpreter.run(); + const returnData = interpreter.returnData(); + + // TODO: Do something with state hand off of a successful call + return returnData; + } +} diff --git a/yarn-project/acir-simulator/src/avm/avm_state_manager.ts b/yarn-project/acir-simulator/src/avm/avm_state_manager.ts index e37c2b18ccd9..048d0d3f346c 100644 --- a/yarn-project/acir-simulator/src/avm/avm_state_manager.ts +++ b/yarn-project/acir-simulator/src/avm/avm_state_manager.ts @@ -1,19 +1,17 @@ - // The state manager is a wrapper around the node state // It has access to all of the data bases // It is responsible for tracking the state during a function call - // It is also responsible for populating the journal as time goes on +import { BlockHeader } from '@aztec/circuits.js'; -import { BlockHeader } from "@aztec/circuits.js"; -import { AvmJournal, HostStorage } from "./journal/index.js"; +import { AvmJournal, HostStorage } from './journal/index.js'; export class AvmStateManager { - public readonly blockHeader: BlockHeader; - public readonly journal: AvmJournal; + public readonly blockHeader: BlockHeader; + public readonly journal: AvmJournal; - constructor(blockHeader: BlockHeader, hostStorage: HostStorage) { - this.blockHeader = blockHeader; - this.journal = new AvmJournal(hostStorage); - } -} \ No newline at end of file + constructor(blockHeader: BlockHeader, hostStorage: HostStorage) { + this.blockHeader = blockHeader; + this.journal = new AvmJournal(hostStorage); + } +} diff --git a/yarn-project/acir-simulator/src/avm/fixtures/index.ts b/yarn-project/acir-simulator/src/avm/fixtures/index.ts index 34539cf2b6e4..cfab7bc47b8f 100644 --- a/yarn-project/acir-simulator/src/avm/fixtures/index.ts +++ b/yarn-project/acir-simulator/src/avm/fixtures/index.ts @@ -1 +1 @@ -// Place large AVM text fixtures in here \ No newline at end of file +// Place large AVM text fixtures in here diff --git a/yarn-project/acir-simulator/src/avm/interpreter/index.ts b/yarn-project/acir-simulator/src/avm/interpreter/index.ts index 8a75bf842c38..d5189852b6e2 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/index.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/index.ts @@ -1 +1 @@ -export * from "./interpreter.js" \ No newline at end of file +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 index eac041e1673a..92cd6e6f8bf9 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts @@ -1,36 +1,37 @@ -import {Fr} from "@aztec/foundation/fields"; -import { Add } from "../opcodes/arithmetic.js"; -import { CallDataCopy } from "../opcodes/memory.js"; -import { Opcode } from "../opcodes/opcode.js"; -import { AvmInterpreter } from "./interpreter.js"; -import { AvmContext } from "../avm_context.js"; -import { Return } from "../opcodes/control_flow.js"; -import { AvmStateManager } from "../avm_state_manager.js"; -import { mock } from "jest-mock-extended"; +import { Fr } from '@aztec/foundation/fields'; -describe("interpreter", () => { +import { mock } from 'jest-mock-extended'; - it("Should execute a series of opcodes", () => { - const calldata: Fr[] = [new Fr(1), new Fr(2)] - const stateManager = mock(); +import { AvmContext } from '../avm_context.js'; +import { AvmStateManager } from '../avm_state_manager.js'; +import { Add } from '../opcodes/arithmetic.js'; +import { Return } from '../opcodes/control_flow.js'; +import { CallDataCopy } from '../opcodes/memory.js'; +import { Opcode } from '../opcodes/opcode.js'; +import { AvmInterpreter } from './interpreter.js'; - const opcodes: Opcode[] = [ - // Copy the first two elements of the calldata to memory regions 0 and 1 - new CallDataCopy(0, 2, 0), - // Add the two together and store the result in memory region 2 - new Add(0, 1, 2), // 1 + 2 - // Return the result - new Return(2, 1) // [3] - ] +describe('interpreter', () => { + it('Should execute a series of opcodes', () => { + const calldata: Fr[] = [new Fr(1), new Fr(2)]; + const stateManager = mock(); - const context = new AvmContext(calldata); - const interpreter = new AvmInterpreter(context, stateManager, opcodes); - const success = interpreter.run(); + const opcodes: Opcode[] = [ + // Copy the first two elements of the calldata to memory regions 0 and 1 + new CallDataCopy(0, 2, 0), + // Add the two together and store the result in memory region 2 + new Add(0, 1, 2), // 1 + 2 + // Return the result + new Return(2, 1), // [3] + ]; - expect(success).toBe(true); + const context = new AvmContext(calldata); + const interpreter = new AvmInterpreter(context, stateManager, opcodes); + const success = interpreter.run(); - const returnData = interpreter.returnData(); - expect(returnData.length).toBe(1); - expect(returnData).toEqual([new Fr(3)]); - }); -}); \ No newline at end of file + expect(success).toBe(true); + + const returnData = interpreter.returnData(); + expect(returnData.length).toBe(1); + expect(returnData).toEqual([new Fr(3)]); + }); +}); diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts index 2ca7697476d3..69d1a3ca62bc 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -1,16 +1,14 @@ // import { AvmContext } from "../avm_context.js"; -import { Fr } from "@aztec/foundation/fields"; -import { AvmContext } from "../avm_context.js"; -import { Opcode } from "../opcodes/index.js"; -import { AvmStateManager } from "../avm_state_manager.js"; - -// Function that will take in the opcode the interpreter state and the world state - then execute it +import { Fr } from '@aztec/foundation/fields'; +import { AvmContext } from '../avm_context.js'; +import { AvmStateManager } from '../avm_state_manager.js'; +import { Opcode } from '../opcodes/index.js'; +// Function that will take in the opcode the interpreter state and the world state - then execute it // TO DEFINE DOES THIS ONLY Interpret a SINGLE CALL FRAME OF THE AVM export class AvmInterpreter { - private opcodes: Opcode[] = []; private context: AvmContext; private stateManager: AvmStateManager; @@ -30,9 +28,9 @@ export class AvmInterpreter { run(): boolean { try { for (const opcode of this.opcodes) { - opcode.execute(this.context, this.stateManager); + opcode.execute(this.context, this.stateManager); } - + return true; } catch (e) { // TODO: This should only accept AVM defined errors, anything else SHOULD be thrown upstream @@ -40,7 +38,7 @@ export class AvmInterpreter { } } - /** + /** * Get the return data from avm execution * TODO: this should fail if the code has not been executed * - maybe move the return in run into a variable and track it @@ -49,4 +47,3 @@ export class AvmInterpreter { return this.context.getReturnData(); } } - diff --git a/yarn-project/acir-simulator/src/avm/journal/host_storage.ts b/yarn-project/acir-simulator/src/avm/journal/host_storage.ts index 95983b79e225..6bf040789c15 100644 --- a/yarn-project/acir-simulator/src/avm/journal/host_storage.ts +++ b/yarn-project/acir-simulator/src/avm/journal/host_storage.ts @@ -1,13 +1,13 @@ -import { CommitmentsDB, PublicContractsDB, PublicStateDB } from "../../index.js"; +import { CommitmentsDB, PublicContractsDB, PublicStateDB } from '../../index.js'; export class HostStorage { - public readonly stateDb: PublicStateDB; - public readonly contractsDb: PublicContractsDB; - public readonly commitmentsDb: CommitmentsDB; + public readonly stateDb: PublicStateDB; + public readonly contractsDb: PublicContractsDB; + public readonly commitmentsDb: CommitmentsDB; - constructor(stateDb: PublicStateDB, contractsDb: PublicContractsDB, commitmentsDb: CommitmentsDB) { - this.stateDb = stateDb; - this.contractsDb = contractsDb; - this.commitmentsDb = commitmentsDb; - } -} \ No newline at end of file + constructor(stateDb: PublicStateDB, contractsDb: PublicContractsDB, commitmentsDb: CommitmentsDB) { + this.stateDb = stateDb; + this.contractsDb = contractsDb; + this.commitmentsDb = commitmentsDb; + } +} diff --git a/yarn-project/acir-simulator/src/avm/journal/index.ts b/yarn-project/acir-simulator/src/avm/journal/index.ts index 68e0517ca06d..86fe25115d8e 100644 --- a/yarn-project/acir-simulator/src/avm/journal/index.ts +++ b/yarn-project/acir-simulator/src/avm/journal/index.ts @@ -1,2 +1,2 @@ -export * from "./host_storage.js"; -export * from "./journal.js"; \ No newline at end of file +export * from './host_storage.js'; +export * from './journal.js'; 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 e33071921426..c356db723862 100644 --- a/yarn-project/acir-simulator/src/avm/journal/journal.test.ts +++ b/yarn-project/acir-simulator/src/avm/journal/journal.test.ts @@ -1,17 +1,7 @@ +describe('journal', () => { + it('Should write to storage', () => {}); -describe("journal", () => { + it('Should read from storage', () => {}); - it("Should write to storage", () => { - - }); - - - it("Should read from storage", () => { - - }); - - it("Should merge two journals together", () => { - - }); - -}); \ No newline at end of file + it('Should merge two journals together', () => {}); +}); diff --git a/yarn-project/acir-simulator/src/avm/journal/journal.ts b/yarn-project/acir-simulator/src/avm/journal/journal.ts index 67f880e75889..db059c4ed02c 100644 --- a/yarn-project/acir-simulator/src/avm/journal/journal.ts +++ b/yarn-project/acir-simulator/src/avm/journal/journal.ts @@ -1,69 +1,69 @@ -import { Fr } from "@aztec/foundation/fields"; -import { HostStorage } from "./host_storage.js"; +import { Fr } from '@aztec/foundation/fields'; + +import { HostStorage } from './host_storage.js'; // TODO: all of the data that comes out of the avm ready for write should be in this format export type JournalData = { - newCommitments: Fr[], - newL1Message: Fr[], - storageWrites: {[key: string]: {[key: string]: Fr}}, -} + newCommitments: Fr[]; + newL1Message: Fr[]; + storageWrites: { [key: string]: { [key: string]: Fr } }; +}; // This persists for an entire block // Each transaction should have its own journal that gets appended to this one upon success export class AvmJournal { - // TODO: should we make private? - public readonly hostStorage: HostStorage; + // TODO: should we make private? + public readonly hostStorage: HostStorage; - // We need to keep track of the following - // - State reads - // - State updates - // - New Commitments - // - Commitment reads + // We need to keep track of the following + // - State reads + // - State updates + // - New Commitments + // - Commitment reads - private newCommitments: Fr[] = []; - private newL1Message: Fr[] = []; - - // TODO: type this structure -> contract address -> key -> value - private storageWrites: {[key: string]: {[key: string]: Fr}} = {}; + private newCommitments: Fr[] = []; + private newL1Message: Fr[] = []; - constructor(hostStorage: HostStorage) { - this.hostStorage = hostStorage; - } + // TODO: type this structure -> contract address -> key -> value + private storageWrites: { [key: string]: { [key: string]: Fr } } = {}; - // TODO: work on the typing - public writeStorage(contractAddress: Fr, key: Fr, value: Fr) { - // TODO: do we want this map to be ordered -> is there performance upside to this? - this.storageWrites[contractAddress.toString()][key.toString()] = value; - } + constructor(hostStorage: HostStorage) { + this.hostStorage = hostStorage; + } - public readStorage(contractAddress: Fr, key: Fr) { - const cachedValue = this.storageWrites[contractAddress.toString()][key.toString()]; - if (cachedValue) { - return cachedValue; - } - return this.hostStorage.stateDb.storageRead(contractAddress, key); - } + // TODO: work on the typing + public writeStorage(contractAddress: Fr, key: Fr, value: Fr) { + // TODO: do we want this map to be ordered -> is there performance upside to this? + this.storageWrites[contractAddress.toString()][key.toString()] = value; + } - public writeCommitment(commitment: Fr) { - this.newCommitments.push(commitment); + public readStorage(contractAddress: Fr, key: Fr) { + const cachedValue = this.storageWrites[contractAddress.toString()][key.toString()]; + if (cachedValue) { + return cachedValue; } + return this.hostStorage.stateDb.storageRead(contractAddress, key); + } - public writeL1Message(message: Fr) { - this.newL1Message.push(message); - } + public writeCommitment(commitment: Fr) { + this.newCommitments.push(commitment); + } - // TODO: This function will merge two journals together -> the new head of the chain - public mergeJournal(journal: AvmJournal) { - // TODO: This function will - void(journal); - } + public writeL1Message(message: Fr) { + this.newL1Message.push(message); + } - public flush(): JournalData { - return { - newCommitments: this.newCommitments, - newL1Message: this.newL1Message, - storageWrites: this.storageWrites - } - } -} + // TODO: This function will merge two journals together -> the new head of the chain + public mergeJournal(journal: AvmJournal) { + // TODO: This function will + void journal; + } + public flush(): JournalData { + return { + newCommitments: this.newCommitments, + newL1Message: this.newL1Message, + storageWrites: this.storageWrites, + }; + } +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts index fc7b29337edd..a405aafdce61 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts @@ -1,143 +1,144 @@ -import { Fr } from "@aztec/foundation/fields"; -import { AvmContext } from "../avm_context.js"; -import { Opcode } from "./opcode.js"; -import { AvmStateManager } from "../avm_state_manager.js"; +import { Fr } from '@aztec/foundation/fields'; + +import { AvmContext } from '../avm_context.js'; +import { AvmStateManager } from '../avm_state_manager.js'; +import { Opcode } from './opcode.js'; export class Add implements Opcode { - static type: string = "ADD"; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a = context.readMemory(this.aOffset); - const b = context.readMemory(this.bOffset); - - const dest = new Fr(a.toBigInt() + b.toBigInt() % Fr.MODULUS); - context.writeMemory(this.destOffset, dest); - } + static type: string = 'ADD'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a = context.readMemory(this.aOffset); + const b = context.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() + (b.toBigInt() % Fr.MODULUS)); + context.writeMemory(this.destOffset, dest); + } } export class Sub implements Opcode { - static type: string = "SUB"; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a = context.readMemory(this.aOffset); - const b = context.readMemory(this.bOffset); - - const dest = new Fr(a.toBigInt() - b.toBigInt() % Fr.MODULUS); - context.writeMemory(this.destOffset, dest); - } + static type: string = 'SUB'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a = context.readMemory(this.aOffset); + const b = context.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() - (b.toBigInt() % Fr.MODULUS)); + context.writeMemory(this.destOffset, dest); + } } export class Mul implements Opcode { - static type: string = "MUL"; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); - - const dest = new Fr(a.toBigInt() * b.toBigInt() % Fr.MODULUS); - context.writeMemory(this.destOffset, dest); - } + static type: string = 'MUL'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + const dest = new Fr((a.toBigInt() * b.toBigInt()) % Fr.MODULUS); + context.writeMemory(this.destOffset, dest); + } } export class Div implements Opcode { - static type: string = "DIV"; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); - - // TODO: proper field division - const dest = new Fr(a.toBigInt() / b.toBigInt()); - context.writeMemory(this.destOffset, dest); - } + static type: string = 'DIV'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: proper field division + const dest = new Fr(a.toBigInt() / b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } } export class Eq implements Opcode { - static type: string = "EQ"; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); - - // TODO: floor div? - this will not perform field division - const dest = new Fr(a.toBigInt() == b.toBigInt()); - context.writeMemory(this.destOffset, dest); - } + static type: string = 'EQ'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(a.toBigInt() == b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } } export class Lt implements Opcode { - static type: string = "Lt"; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); - - // TODO: floor div? - this will not perform field division - const dest = new Fr(a.toBigInt() < b.toBigInt()); - context.writeMemory(this.destOffset, dest); - } + static type: string = 'Lt'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(a.toBigInt() < b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } } export class Lte implements Opcode { - static type: string = "LTE"; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); - - // TODO: floor div? - this will not perform field division - const dest = new Fr(a.toBigInt() < b.toBigInt()); - context.writeMemory(this.destOffset, dest); - } + static type: string = 'LTE'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(a.toBigInt() < b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } } export class Shl implements Opcode { - static type: string = "SHL"; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); - - // TODO: floor div? - this will not perform field division - const dest = new Fr(a.toBigInt() << b.toBigInt()); - context.writeMemory(this.destOffset, dest); - } + static type: string = 'SHL'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(a.toBigInt() << b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } } export class Shr implements Opcode { - static type: string = "SHR"; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); - - // TODO: floor div? - this will not perform field division - const dest = new Fr(a.toBigInt() >> b.toBigInt()); - context.writeMemory(this.destOffset, dest); - } + static type: string = 'SHR'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(a.toBigInt() >> b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts index bee49a061bb2..5ca7b80e3f6d 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts @@ -1,67 +1,68 @@ -import { Fr } from "@aztec/foundation/fields"; -import { AvmContext } from "../avm_context.js"; -import { Opcode } from "./opcode.js"; -import { AvmStateManager } from "../avm_state_manager.js"; +import { Fr } from '@aztec/foundation/fields'; + +import { AvmContext } from '../avm_context.js'; +import { AvmStateManager } from '../avm_state_manager.js'; +import { Opcode } from './opcode.js'; export class And implements Opcode { - static type: string = "AND"; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); - - // TODO: floor div? - this will not perform field division - const dest = new Fr(a.toBigInt() & b.toBigInt()); - context.writeMemory(this.destOffset, dest); - } + static type: string = 'AND'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(a.toBigInt() & b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } } export class Or implements Opcode { - static type: string = "OR"; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); - - // TODO: floor div? - this will not perform field division - const dest = new Fr(a.toBigInt() | b.toBigInt()); - context.writeMemory(this.destOffset, dest); - } + static type: string = 'OR'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(a.toBigInt() | b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } } export class Xor implements Opcode { - static type: string = "XOR"; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); - - // TODO: floor div? - this will not perform field division - const dest = new Fr(a.toBigInt() ^ b.toBigInt()); - context.writeMemory(this.destOffset, dest); - } + static type: string = 'XOR'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(a.toBigInt() ^ b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } } export class Not implements Opcode { - static type: string = "NOT"; - static numberOfOperands = 2; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - - // TODO: floor div? - this will not perform field division - const dest = new Fr(~a.toBigInt()); - context.writeMemory(this.destOffset, dest); - } + static type: string = 'NOT'; + static numberOfOperands = 2; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a: Fr = context.readMemory(this.aOffset); + + // TODO: floor div? - this will not perform field division + const dest = new Fr(~a.toBigInt()); + context.writeMemory(this.destOffset, dest); + } } 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 0d3fa7294627..09057279a66a 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts @@ -1,15 +1,15 @@ -import { AvmContext } from "../avm_context.js"; -import { AvmStateManager } from "../avm_state_manager.js"; -import { Opcode } from "./opcode.js"; +import { AvmContext } from '../avm_context.js'; +import { AvmStateManager } from '../avm_state_manager.js'; +import { Opcode } from './opcode.js'; export class Return implements Opcode { - static type: string = "RETURN"; - static numberOfOperands = 2; - - constructor(private returnOffset: number, private copySize: number) {} + static type: string = 'RETURN'; + static numberOfOperands = 2; - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const returnData = context.readMemoryChunk(this.returnOffset, this.returnOffset + this.copySize); - context.setReturnData(returnData); - } -} \ No newline at end of file + constructor(private returnOffset: number, private copySize: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const returnData = context.readMemoryChunk(this.returnOffset, this.returnOffset + this.copySize); + context.setReturnData(returnData); + } +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.test.ts index 10bac448f0eb..c97fc51338ce 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.test.ts @@ -1,40 +1,36 @@ -import { Add, Sub } from "./arithmetic.js"; -import { OPCODE_BYTE_LENGTH, OPERAND_BTYE_LENGTH, interpretBytecode } from "./from_bytecode.js"; -import { Opcode } from "./opcode.js"; +import { Add, Sub } from './arithmetic.js'; +import { OPCODE_BYTE_LENGTH, OPERAND_BTYE_LENGTH, interpretBytecode } from './from_bytecode.js'; +import { Opcode } from './opcode.js'; -describe("Avm Interpreter", () => { +describe('Avm Interpreter', () => { + const toByte = (num: number): Buffer => { + const buf = Buffer.alloc(OPCODE_BYTE_LENGTH); + buf.writeUInt8(num); + return buf; + }; + const to4Byte = (num: number): Buffer => { + const buf = Buffer.alloc(OPERAND_BTYE_LENGTH); + buf.writeUInt32BE(num); + return buf; + }; - const toByte = (num: number): Buffer => { - const buf = Buffer.alloc(OPCODE_BYTE_LENGTH); - buf.writeUInt8(num); - return buf; - } - const to4Byte = (num: number): Buffer => { - const buf = Buffer.alloc(OPERAND_BTYE_LENGTH); - buf.writeUInt32BE(num); - return buf; - } + it('Should read bytecode string into a list of opcodes', () => { + const opcode = 1; + const opcode2 = 2; + const a = 1; + const b = 2; + const c = 3; - it("Should read bytecode string into a list of opcodes", () => { - const opcode = 1; - const opcode2 = 2; - const a = 1; - const b = 2; - const c = 3; + const ops = toByte(opcode); + const ops2 = toByte(opcode2); + const as = to4Byte(a); + const bs = to4Byte(b); + const cs = to4Byte(c); + const bytecode = Buffer.concat([ops, as, bs, cs, ops2, as, bs, cs]); - const ops = toByte(opcode); - const ops2 = toByte(opcode2); - const as = to4Byte(a); - const bs = to4Byte(b); - const cs = to4Byte(c); - const bytecode = Buffer.concat([ops, as, bs, cs, ops2, as, bs, cs]); - - const expectedOpcodes: Opcode[] = [ - new Add(a,b,c), - new Sub(a,b,c) - ]; + const expectedOpcodes: Opcode[] = [new Add(a, b, c), new Sub(a, b, c)]; - const opcodes = interpretBytecode(bytecode); - expect(opcodes).toEqual(expectedOpcodes); - }); -}); \ No newline at end of file + const opcodes = interpretBytecode(bytecode); + expect(opcodes).toEqual(expectedOpcodes); + }); +}); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts index 880c99f5332e..c34a84e4dc5c 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts @@ -1,17 +1,16 @@ -import { Opcode } from "./opcode.js"; -import { Add, Sub, Mul } from "./arithmetic.js"; +import { Add, Mul, Sub } from './arithmetic.js'; +import { Opcode } from './opcode.js'; export const OPERAND_BIT_LENGTH = 32; export const OPERAND_BTYE_LENGTH = 4; export const OPCODE_BIT_LENGTH = 8; export const OPCODE_BYTE_LENGTH = 1; - const OPERANDS_LOOKUP: { [key: number]: number } = { 0x1: Add.numberOfOperands, 0x2: Sub.numberOfOperands, 0x3: Mul.numberOfOperands, -} +}; function opcodeLookup(opcode: number, operands: number[]): Opcode { switch (opcode) { @@ -28,10 +27,10 @@ function opcodeLookup(opcode: number, operands: number[]): Opcode { /** * Convert a buffer of bytecode into an array of opcodes - * @param bytecode - Buffer of bytecode + * @param bytecode - Buffer of bytecode * @returns Bytecode interpreted into an ordered array of Opcodes */ -export function interpretBytecode(bytecode: Buffer) : Opcode[] { +export function interpretBytecode(bytecode: Buffer): Opcode[] { let readPtr = 0; const bytecodeLength = bytecode.length; @@ -53,4 +52,4 @@ export function interpretBytecode(bytecode: Buffer) : Opcode[] { } return opcodes; -} \ No newline at end of file +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/index.ts b/yarn-project/acir-simulator/src/avm/opcodes/index.ts index 3a836e7aa895..ce2985bb9f28 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/index.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/index.ts @@ -1,4 +1,4 @@ -export * from "./arithmetic.js"; -export * from "./control_flow.js"; -export * from "./call.js"; -export * from "./opcode.js" \ No newline at end of file +export * from './arithmetic.js'; +export * from './control_flow.js'; +export * from './call.js'; +export * from './opcode.js'; diff --git a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts index 6803052e935b..7c1c5cbaa73f 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts @@ -1,56 +1,56 @@ +import { Fr } from '@aztec/foundation/fields'; -import { Fr } from "@aztec/foundation/fields"; -import { AvmContext } from "../avm_context.js"; -import { Opcode } from "./opcode.js"; -import { AvmStateManager } from "../avm_state_manager.js"; +import { AvmContext } from '../avm_context.js'; +import { AvmStateManager } from '../avm_state_manager.js'; +import { Opcode } from './opcode.js'; export class Set implements Opcode { - static type: string = "SET"; - static numberOfOperands = 2; - - constructor(private constt: bigint, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const dest = new Fr(this.constt); - context.writeMemory(this.destOffset, dest); - } + static type: string = 'SET'; + static numberOfOperands = 2; + + constructor(private constt: bigint, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const dest = new Fr(this.constt); + context.writeMemory(this.destOffset, dest); + } } // TODO: tags are not implemented yet - this will behave as a mov export class Cast implements Opcode { - static type: string = "CAST"; - static numberOfOperands = 2; - - constructor(private aOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a = context.readMemory(this.aOffset); - - context.writeMemory(this.destOffset, a); - } + static type: string = 'CAST'; + static numberOfOperands = 2; + + constructor(private aOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a = context.readMemory(this.aOffset); + + context.writeMemory(this.destOffset, a); + } } export class Mov implements Opcode { - static type: string = "MOV"; - static numberOfOperands = 2; - - constructor(private aOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a = context.readMemory(this.aOffset); - - context.writeMemory(this.destOffset, a); - } + static type: string = 'MOV'; + static numberOfOperands = 2; + + constructor(private aOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a = context.readMemory(this.aOffset); + + context.writeMemory(this.destOffset, a); + } } export class CallDataCopy implements Opcode { - static type: string = "CALLDATACOPY"; - static numberOfOperands = 3; - - constructor(private cdOffset: number, private copySize: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const calldata = context.calldata.slice(this.cdOffset, this.cdOffset + this.copySize); - context.writeMemoryChunk(this.destOffset, calldata); - } -} \ No newline at end of file + static type: string = 'CALLDATACOPY'; + static numberOfOperands = 3; + + constructor(private cdOffset: number, private copySize: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const calldata = context.calldata.slice(this.cdOffset, this.cdOffset + this.copySize); + context.writeMemoryChunk(this.destOffset, calldata); + } +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/opcode.ts b/yarn-project/acir-simulator/src/avm/opcodes/opcode.ts index fbabc99b25e8..34710783005e 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/opcode.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/opcode.ts @@ -1,10 +1,9 @@ -import { AvmContext } from "../avm_context.js"; -import { AvmStateManager } from "../avm_state_manager.js"; +import { AvmContext } from '../avm_context.js'; +import { AvmStateManager } from '../avm_state_manager.js'; /** * Opcode base class */ export abstract class Opcode { - - abstract execute(context: AvmContext, stateManager: AvmStateManager): void; -} \ No newline at end of file + abstract execute(context: AvmContext, stateManager: AvmStateManager): void; +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts b/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts index 23b6aeb43ba3..fce28ac023dc 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts @@ -1,4 +1,3 @@ - /** * All avm opcodes */ @@ -23,7 +22,7 @@ export enum Opcodes { MOV, CMOV, CALLDATACOPY, - + // Logs EMITNOTEHASH, EMITNULLIFIER, @@ -58,7 +57,7 @@ export enum Opcodes { PUBLICDATAROOT, GLOBALSHASH, BLOCKSROOT, // TODO: are these the same - GRANDROOT, + GRANDROOT, // Call context ORIGIN, @@ -74,5 +73,5 @@ export enum Opcodes { // Black box KECCAK, - POSEIDON -} \ No newline at end of file + POSEIDON, +} From e812dd5c6959d0545b2bce7bb79d22dd92853f1c Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 10 Jan 2024 21:31:50 +0000 Subject: [PATCH 08/20] rm: legacy --- .../acir-simulator/src/avm/avm.legacy.test.ts | 400 ------------------ 1 file changed, 400 deletions(-) delete mode 100644 yarn-project/acir-simulator/src/avm/avm.legacy.test.ts diff --git a/yarn-project/acir-simulator/src/avm/avm.legacy.test.ts b/yarn-project/acir-simulator/src/avm/avm.legacy.test.ts deleted file mode 100644 index fa2a67fcaf4d..000000000000 --- a/yarn-project/acir-simulator/src/avm/avm.legacy.test.ts +++ /dev/null @@ -1,400 +0,0 @@ -// import { -// CallContext, -// CircuitsWasm, -// FunctionData, -// } from '@aztec/circuits.js'; -// import { pedersenPlookupCommitWithHashIndexPoint } from '@aztec/circuits.js/barretenberg'; -// import { FunctionSelector } from '@aztec/foundation/abi'; -// import { AztecAddress } from '@aztec/foundation/aztec-address'; -// import { EthAddress } from '@aztec/foundation/eth-address'; -// import { Fr } from '@aztec/foundation/fields'; -// import { -// AvmTestContractArtifact, -// } from '@aztec/noir-contracts/artifacts'; -// import { createDebugLogger } from '@aztec/foundation/log'; - -// import { MockProxy, mock } from 'jest-mock-extended'; - -// import { PublicContractsDB, PublicStateDB } from '../public/db.js'; -// import { PublicCall, PublicExecutionResult } from '../public/execution.js'; - -// import { AVMExecutor } from './avm.js'; -// import { -// addBytecode, -// storageBytecode, -// nestedCallBytecode, -// } from './fixtures/avm_examples.js'; - -// describe('ACIR public execution simulator', () => { -// const log = createDebugLogger('aztec:acir-simulator:vm.test.ts'); -// let publicState: MockProxy; -// let publicContracts: MockProxy; -// //let commitmentsDb: MockProxy; -// //let blockData: HistoricBlockData; -// let executor: AVMExecutor; // TODO: replace with AVMSimulator -// let msgSender: AztecAddress; - -// beforeEach(() => { -// publicState = mock(); -// publicContracts = mock(); -// //commitmentsDb = mock(); -// //blockData = HistoricBlockData.empty(); -// executor = new AVMExecutor(publicState, publicContracts); - -// msgSender = AztecAddress.random(); -// }, 10000); - -// async function simulateAndCheck( -// calldata: Fr[], -// expectReturn: Fr[], -// bytecode: Buffer, -// bytecodesForNestedCalls: Buffer[] = [], -// ): Promise { -// const contractAddress = AztecAddress.random(); -// const functionSelector = new FunctionSelector(0x1234); -// const functionData = new FunctionData(functionSelector, false, false, false); -// const callContext = CallContext.from({ -// msgSender, -// storageContractAddress: contractAddress, -// portalContractAddress: EthAddress.random(), -// functionSelector: FunctionSelector.empty(), -// isContractDeployment: false, -// isDelegateCall: false, -// isStaticCall: false, -// }); - -// publicContracts.getBytecode.mockResolvedValueOnce(bytecode); -// for (let i = 0; i < bytecodesForNestedCalls.length; i++) { -// publicContracts.getBytecode.mockResolvedValueOnce(bytecodesForNestedCalls[i]); -// publicContracts.getPortalContractAddress.mockResolvedValueOnce(EthAddress.random()); -// publicContracts.getIsInternal.mockResolvedValueOnce(false); -// } - -// const publicCall: PublicCall = { contractAddress, functionData, calldata, callContext }; -// const result = await executor.simulate(publicCall); //, GlobalVariables.empty()); - -// expect(result.returnValues.length).toEqual(expectReturn.length); -// for (let i = 0; i < expectReturn.length; i++) { -// expect(result.returnValues[i]).toEqual(new Fr(expectReturn[i])); -// } - -// return result; -// } - -// describe('Token contract', () => { -// describe('AVM tests using bytecode constructed manually in-test', () => { -// it('AVM can add arguments and return result', async () => { -// // ADD 42 + 25 -// // => 67 -// const addArg0 = 42n; -// const addArg1 = 25n; -// const calldata = [addArg0, addArg1].map(arg => new Fr(arg)); -// const returndata = [addArg0 + addArg1].map(arg => new Fr(arg)); -// await simulateAndCheck(calldata, returndata, addBytecode); -// }); -// it('AVM storage operations work', async () => { -// // ADD 42 + S[61] -// // ADD 42 + 96 -// // => 138 -// const addArg0 = 42n; -// const slotArg = 61n; -// const startValueAtSlot = 96n; -// const calldata = [addArg0, slotArg].map(arg => new Fr(arg)); -// const ret = addArg0 + startValueAtSlot; -// const returndata = [ret].map(arg => new Fr(arg)); - -// //publicContracts.getBytecode.mockResolvedValue(storageBytecode); - -// publicState.storageRead -// .mockResolvedValueOnce(new Fr(startValueAtSlot)) // before sstore -// .mockResolvedValueOnce(new Fr(ret)); // after sstore - -// const result = await simulateAndCheck(calldata, returndata, storageBytecode); - -// // VALIDATE STORAGE ACTION TRACE -// // SLOAD is performed before SSTORE and after -// expect(result.contractStorageReads).toEqual([ -// { -// storageSlot: new Fr(slotArg), -// currentValue: new Fr(startValueAtSlot), -// sideEffectCounter: 0, -// }, -// { -// storageSlot: new Fr(slotArg), -// currentValue: new Fr(ret), -// sideEffectCounter: 2, -// }, -// ]); -// // Confirm that ADD result was SSTOREd -// expect(result.contractStorageUpdateRequests).toEqual([ -// { -// storageSlot: new Fr(slotArg), -// oldValue: new Fr(startValueAtSlot), -// newValue: new Fr(ret), -// sideEffectCounter: 1, -// }, -// ]); -// }); -// it('AVM can perform nested calls', async () => { -// // ADD 42 + 25 -// // => 67 -// const nestedCallAddress = 5678n; -// const addArg0 = 42n; -// const addArg1 = 25n; -// const calldata = [nestedCallAddress, addArg0, addArg1].map(arg => new Fr(arg)); -// const returndata = [addArg0 + addArg1].map(arg => new Fr(arg)); - -// // top-level call (nestedCallBytecode) just makes a CALL to addBytecode -// // which performs an ADD and returns the result -// // pseudocode: -// // fn foo(addr, a, b) { -// // return addr::bar(a, b); -// // } -// // fn bar(a, b) { -// // return a + b; -// // } -// await simulateAndCheck(calldata, returndata, nestedCallBytecode, [addBytecode]); -// }); -// }); - -// describe('AVM tests that transpile Brillig bytecode to AVM before executing', () => { -// it('Noir function that just does a basic add', async () => { -// const addExampleArtifact = AvmTestContractArtifact.functions.find( -// f => f.name === 'addExample', -// )!; -// const bytecode = Buffer.from(addExampleArtifact.bytecode, 'base64'); - -// // ADD 42 + 25 -// // => 67 -// const addArg0 = 42n; -// const addArg1 = 25n; -// const calldata = [addArg0, addArg1].map(arg => new Fr(arg)); -// const returndata = [addArg0 + addArg1].map(arg => new Fr(arg)); -// await simulateAndCheck(calldata, returndata, bytecode); -// }); -// it('Noir function with slightly more complex arithmetic', async () => { -// // ADD 42 + 25 -// // => 67 -// // ADD 67 + 67 -// // => 137 -// // SUB 137 - 30 -// // => 107 -// const arithmeticExample = AvmTestContractArtifact.functions.find( -// f => f.name === 'arithmeticExample', -// )!; -// const bytecode = Buffer.from(arithmeticExample.bytecode, 'base64'); - -// const addArg0 = 42n; -// const addArg1 = 25n; -// const subArg0 = 30n; -// const calldata = [addArg0, addArg1, subArg0].map(arg => new Fr(arg)); -// const returndata = [(addArg0 + addArg1) + (addArg0 + addArg1) - subArg0].map(arg => new Fr(arg)); -// await simulateAndCheck(calldata, returndata, bytecode); -// }); - -// it('Noir function with storage actions', async () => { -// const storageExample = AvmTestContractArtifact.functions.find( -// f => f.name === 'storageExample', -// )!; -// const bytecode = Buffer.from(storageExample.bytecode, 'base64'); - -// // ADD 42 + S[61] -// // ADD 42 + 96 -// // => 138 -// const addArg0 = 42n; -// const slotArg = 61n; -// const startValueAtSlot = 96n; -// const calldata = [addArg0, slotArg].map(arg => new Fr(arg)); -// const ret = addArg0 + startValueAtSlot; -// const returndata = [ret].map(arg => new Fr(arg)); - -// publicState.storageRead -// .mockResolvedValueOnce(new Fr(startValueAtSlot)) // before sstore -// .mockResolvedValueOnce(new Fr(ret)); // after sstore - -// const result = await simulateAndCheck(calldata, returndata, bytecode); - -// // VALIDATE STORAGE ACTION TRACE -// // SLOAD is performed before SSTORE and after -// expect(result.contractStorageReads).toEqual([ -// { -// storageSlot: new Fr(slotArg), -// currentValue: new Fr(startValueAtSlot), -// sideEffectCounter: 0, -// }, -// { -// storageSlot: new Fr(slotArg), -// currentValue: new Fr(ret), -// sideEffectCounter: 2, -// }, -// ]); -// // Confirm that ADD result was SSTOREd -// expect(result.contractStorageUpdateRequests).toEqual([ -// { -// storageSlot: new Fr(slotArg), -// oldValue: new Fr(startValueAtSlot), -// newValue: new Fr(ret), -// sideEffectCounter: 1, -// }, -// ]); -// }); -// it('Noir function with a nested contract call', async () => { -// const nestedCallExample = AvmTestContractArtifact.functions.find( -// f => f.name === 'nestedCallExample', -// )!; -// const bytecode = Buffer.from(nestedCallExample.bytecode, 'base64'); -// // ADD 42 + 25 -// // => 67 -// const nestedCallAddress = 5678n; -// const addArg0 = 42n; -// const addArg1 = 25n; -// const calldata = [nestedCallAddress, addArg0, addArg1].map(arg => new Fr(arg)); -// const returndata = [addArg0 + addArg1].map(arg => new Fr(arg)); - -// // top-level call (nestedCallBytecode) just makes a CALL to addBytecode -// // which performs an ADD and returns the result -// // pseudocode: -// // fn foo(addr, a, b) { -// // return addr::bar(a, b); -// // } -// // fn bar(a, b) { -// // return a + b; -// // } -// await simulateAndCheck(calldata, returndata, bytecode, [addBytecode]); -// }); -// it('Noir function with storage maps', async () => { -// const balanceOfPublic = AvmTestContractArtifact.functions.find( -// f => f.name === 'balance_of_public', -// )!; -// const bytecode = Buffer.from(balanceOfPublic.bytecode, 'base64'); - -// const userAddress = AztecAddress.random(); -// const slot = 6n; // slot used in Storage struct in Noir for public balances map -// const startValueAtSlot = 96n; -// const calldata = [userAddress.toField()].map(arg => new Fr(arg)); -// const returndata = [startValueAtSlot].map(arg => new Fr(arg)); - -// publicState.storageRead -// .mockResolvedValueOnce(new Fr(startValueAtSlot)) // before sstore - -// const result = await simulateAndCheck(calldata, returndata, bytecode); - -// // TODO: test that the actual slot read from is correct! -// // Must be sure that AVM's slot-computation is correct for storage maps. -// const wasm = await CircuitsWasm.get(); -// const hashIndex = 0; -// const inputs = [new Fr(slot).toBuffer(), userAddress.toBuffer()]; -// const outputBufs = pedersenPlookupCommitWithHashIndexPoint(wasm, inputs, hashIndex); -// const mapKeySlot = Fr.fromBuffer(outputBufs[0]); - -// // VALIDATE STORAGE ACTION TRACE -// expect(result.contractStorageReads).toEqual([ -// { -// storageSlot: mapKeySlot, -// currentValue: new Fr(startValueAtSlot), -// sideEffectCounter: 0, -// }, -// ]); -// }); -// it('Noir function that accesses context variables/constants', async () => { -// const contextVarsExample = AvmTestContractArtifact.functions.find( -// f => f.name === 'context_vars_example', -// )!; -// const bytecode = Buffer.from(contextVarsExample.bytecode, 'base64'); - -// const calldata: Fr[] = []; -// const returndata = [msgSender.toField()]; -// await simulateAndCheck(calldata, returndata, bytecode); -// }); -// it('Noir function to mint_public', async () => { -// const mintPublic = AvmTestContractArtifact.functions.find( -// f => f.name === 'mint_public', -// )!; -// const bytecode = Buffer.from(mintPublic.bytecode, 'base64'); - -// const receiverAddress = AztecAddress.random(); - -// // slots used in Storage struct in Noir -// const mintersSlot = 2n; -// const totalSupplySlot = 4n; -// const publicBalancesSlot = 6n; - -// const mintAmount = 42n; -// const receiverStartBal = 96n; -// const startTotalSupply = 123n; -// const receiverEndBal = receiverStartBal + mintAmount; -// const endTotalSupply = startTotalSupply + mintAmount; - -// const calldata = [receiverAddress.toField(), new Fr(mintAmount)]; -// const returndata = [new Fr(1)].map(arg => new Fr(arg)); // returns 1 for success - -// publicState.storageRead -// .mockResolvedValueOnce(new Fr(1)) // assert sender in minters map -// .mockResolvedValueOnce(new Fr(receiverStartBal)) // receiver's original balance -// .mockResolvedValueOnce(new Fr(startTotalSupply)); - -// const result = await simulateAndCheck(calldata, returndata, bytecode); - -// // TODO: test that the actual slot read from is correct! -// // Must be sure that AVM's slot-computation is correct for storage maps. -// const wasm = await CircuitsWasm.get(); -// const hashIndex = 0; -// // Compute minters slot for map key (msgSender address) -// let inputs = [new Fr(mintersSlot).toBuffer(), msgSender.toBuffer()]; -// let outputBufs = pedersenPlookupCommitWithHashIndexPoint(wasm, inputs, hashIndex); -// const mintersKeySlot = Fr.fromBuffer(outputBufs[0]); -// // Compute public balances slot for map key (receiver address) -// inputs = [new Fr(publicBalancesSlot).toBuffer(), receiverAddress.toBuffer()]; -// outputBufs = pedersenPlookupCommitWithHashIndexPoint(wasm, inputs, hashIndex); -// const publicBalancesKeySlot = Fr.fromBuffer(outputBufs[0]); - -// log(`expected/computed mintersKeySlot: ${mintersKeySlot}`); -// log(`expected/computed publicBalancesKeySlot: ${publicBalancesKeySlot}`); - -// // VALIDATE STORAGE ACTION TRACE -// expect(result.contractStorageReads).toEqual([ -// { -// storageSlot: mintersKeySlot, -// currentValue: new Fr(1), -// sideEffectCounter: 0, -// }, -// { -// storageSlot: publicBalancesKeySlot, -// currentValue: new Fr(receiverStartBal), -// sideEffectCounter: 1, -// }, -// { -// storageSlot: new Fr(totalSupplySlot), -// currentValue: new Fr(startTotalSupply), -// sideEffectCounter: 2, -// }, -// ]); -// // Confirm that balance and total supply were written properly -// expect(result.contractStorageUpdateRequests).toEqual([ -// { -// storageSlot: publicBalancesKeySlot, -// oldValue: new Fr(receiverStartBal), -// newValue: new Fr(receiverEndBal), -// sideEffectCounter: 3, -// }, -// { -// storageSlot: new Fr(totalSupplySlot), -// oldValue: new Fr(startTotalSupply), -// newValue: new Fr(endTotalSupply), -// sideEffectCounter: 4, -// }, -// ]); -// }); -// }); - -// //describe('AVM tests including calls to C++ witness generation and/or proving', () => { -// // it('should prove the public vm', async () => { -// // //... -// // const outAsmPath = await executor.bytecodeToPowdr(execution); -// // await executor.generateWitness(outAsmPath); -// // await executor.prove(); -// // }, 1_000_000); -// //}); -// }); -// // TODO: test field addition that could overflow (should just wrap around) -// }); From df233c336386c312fbf8d4531fc0eb267ad16b62 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 11 Jan 2024 15:36:07 +0000 Subject: [PATCH 09/20] chore: document / silence linter --- .gitignore | 2 + .../acir-simulator/src/avm/avm_context.ts | 28 +++++++++++++ .../acir-simulator/src/avm/avm_executor.ts | 20 +++++++--- .../src/avm/avm_state_manager.ts | 39 ++++++++++++++++--- .../src/avm/interpreter/interpreter.ts | 8 ++-- .../src/avm/opcodes/arithmetic.ts | 10 +++++ .../acir-simulator/src/avm/opcodes/bitwise.ts | 4 ++ .../src/avm/opcodes/control_flow.ts | 1 + .../src/avm/opcodes/from_bytecode.ts | 7 ++++ .../acir-simulator/src/avm/opcodes/memory.ts | 4 ++ 10 files changed, 109 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 3ab25e14bb48..a7ebba4e7a3c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ cmake-build-debug # Local Netlify folder .netlify + +.graphite* \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/avm_context.ts b/yarn-project/acir-simulator/src/avm/avm_context.ts index 8cd75c4c2f4c..50ee021f69a0 100644 --- a/yarn-project/acir-simulator/src/avm/avm_context.ts +++ b/yarn-project/acir-simulator/src/avm/avm_context.ts @@ -1,15 +1,26 @@ import { Fr } from '@aztec/foundation/fields'; +/** + * Store's data for an Avm execution frame + */ export class AvmContext { + /** - */ public readonly calldata: Fr[]; private returnData: Fr[]; // TODO: implement tagged memory + /** - */ public memory: Fr[]; + /** - */ public pc: number; + /** - */ public callStack: number[]; + /** + * Create a new avm context + * @param calldata - + */ constructor(calldata: Fr[]) { this.calldata = calldata; this.returnData = []; @@ -21,30 +32,47 @@ export class AvmContext { /** * Return data must NOT be modified once it is set + * @param returnData - */ public setReturnData(returnData: Fr[]) { this.returnData = returnData; Object.freeze(returnData); } + /** - */ public getReturnData(): Fr[] { return this.returnData; } + /** - + * @param offset - + */ public readMemory(offset: number): Fr { // TODO: check offset is within bounds return this.memory[offset] ?? Fr.ZERO; } + /** - + * @param offset - + * @param size - + */ public readMemoryChunk(offset: number, size: number): Fr[] { // TODO: bounds -> initialise to 0 return this.memory.slice(offset, offset + size); } + /** - + * @param offset - + * @param value - + */ public writeMemory(offset: number, value: Fr): void { this.memory[offset] = value; } + /** - + * @param offset - + * @param values - + */ public writeMemoryChunk(offset: number, values: Fr[]): void { this.memory.splice(offset, values.length, ...values); } diff --git a/yarn-project/acir-simulator/src/avm/avm_executor.ts b/yarn-project/acir-simulator/src/avm/avm_executor.ts index ad906966648c..86d167f6230e 100644 --- a/yarn-project/acir-simulator/src/avm/avm_executor.ts +++ b/yarn-project/acir-simulator/src/avm/avm_executor.ts @@ -1,8 +1,3 @@ -// Decisions: -// 1. At this current time the avm should not handle getting state itself or anything like that -// - it will purely run as an interpreter and the code / starting state will be given to it on init -// 2. Anything that is a constant -// - e.g. will go in its own relevant file -> there is no need to have it in the interpreter file import { Fr } from '@aztec/foundation/fields'; import { AvmContext } from './avm_context.js'; @@ -11,6 +6,11 @@ import { AvmInterpreter } from './interpreter/index.js'; import { interpretBytecode } from './opcodes/from_bytecode.js'; import { Opcode } from './opcodes/opcode.js'; +/** + * Avm Executor manages the execution of the AVM + * + * It stores a state manager + */ export class AvmExecutor { private stateManager: AvmStateManager; @@ -18,6 +18,16 @@ export class AvmExecutor { this.stateManager = stateManager; } + /** + * Call a contract with the given calldata + * + * - We get the contract from storage + * - We interpret the bytecode + * - We run the interpreter + * + * @param contractAddress - + * @param calldata - + */ public call(contractAddress: Fr, calldata: Fr[]): Fr[] { // NOTE: the following is mocked as getPublicBytecode does not exist yet // const bytecode = stateManager.journal.hostStorage.contractsDb.getBytecode(contractAddress); diff --git a/yarn-project/acir-simulator/src/avm/avm_state_manager.ts b/yarn-project/acir-simulator/src/avm/avm_state_manager.ts index 048d0d3f346c..250a7d8f1225 100644 --- a/yarn-project/acir-simulator/src/avm/avm_state_manager.ts +++ b/yarn-project/acir-simulator/src/avm/avm_state_manager.ts @@ -1,17 +1,44 @@ -// The state manager is a wrapper around the node state -// It has access to all of the data bases -// It is responsible for tracking the state during a function call -// It is also responsible for populating the journal as time goes on import { BlockHeader } from '@aztec/circuits.js'; import { AvmJournal, HostStorage } from './journal/index.js'; +/** + * The Avm State Manager is the interpreter's interface to the node's state + * It creates revertible views into the node state and manages the current call's journal + */ export class AvmStateManager { + /** - */ public readonly blockHeader: BlockHeader; + + /** + * Journal keeps track of pending state changes + */ public readonly journal: AvmJournal; - constructor(blockHeader: BlockHeader, hostStorage: HostStorage) { + constructor(blockHeader: BlockHeader, journal: AvmJournal) { this.blockHeader = blockHeader; - this.journal = new AvmJournal(hostStorage); + this.journal = journal; + } + + /** + * Create a base state root manager + * - this should be created by the highest level item where the state + * can be reverted + * @param blockHeader - + * @param hostStorage - An immutable view into the node db + * @returns Avm State Manager + */ + public static rootStateManager(blockHeader: BlockHeader, hostStorage: HostStorage): AvmStateManager { + const journal = new AvmJournal(hostStorage); + return new AvmStateManager(blockHeader, journal); + } + + /** + * Avm State + * @param parent - Avm state manager with a forked journal + * @returns + */ + public static forkStateManager(parent: AvmStateManager): AvmStateManager { + return new AvmStateManager(parent.blockHeader, parent.journal); } } diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts index 69d1a3ca62bc..eecd309227d5 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -5,9 +5,11 @@ import { AvmContext } from '../avm_context.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Opcode } from '../opcodes/index.js'; -// Function that will take in the opcode the interpreter state and the world state - then execute it - -// TO DEFINE DOES THIS ONLY Interpret a SINGLE CALL FRAME OF THE AVM +/** + * Avm Interpreter + * + * Executes an Avm context + */ export class AvmInterpreter { private opcodes: Opcode[] = []; private context: AvmContext; diff --git a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts index a405aafdce61..29ba731c2440 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts @@ -4,6 +4,7 @@ import { AvmContext } from '../avm_context.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Opcode } from './opcode.js'; +/** -*/ export class Add implements Opcode { static type: string = 'ADD'; static numberOfOperands = 3; @@ -19,6 +20,7 @@ export class Add implements Opcode { } } +/** -*/ export class Sub implements Opcode { static type: string = 'SUB'; static numberOfOperands = 3; @@ -34,6 +36,7 @@ export class Sub implements Opcode { } } +/** -*/ export class Mul implements Opcode { static type: string = 'MUL'; static numberOfOperands = 3; @@ -49,6 +52,7 @@ export class Mul implements Opcode { } } +/** -*/ export class Div implements Opcode { static type: string = 'DIV'; static numberOfOperands = 3; @@ -64,6 +68,8 @@ export class Div implements Opcode { context.writeMemory(this.destOffset, dest); } } + +/** -*/ export class Eq implements Opcode { static type: string = 'EQ'; static numberOfOperands = 3; @@ -79,6 +85,7 @@ export class Eq implements Opcode { context.writeMemory(this.destOffset, dest); } } +/** -*/ export class Lt implements Opcode { static type: string = 'Lt'; static numberOfOperands = 3; @@ -95,6 +102,7 @@ export class Lt implements Opcode { } } +/** -*/ export class Lte implements Opcode { static type: string = 'LTE'; static numberOfOperands = 3; @@ -111,6 +119,7 @@ export class Lte implements Opcode { } } +/** -*/ export class Shl implements Opcode { static type: string = 'SHL'; static numberOfOperands = 3; @@ -127,6 +136,7 @@ export class Shl implements Opcode { } } +/** -*/ export class Shr implements Opcode { static type: string = 'SHR'; static numberOfOperands = 3; diff --git a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts index 5ca7b80e3f6d..440e383725e8 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts @@ -4,6 +4,7 @@ import { AvmContext } from '../avm_context.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Opcode } from './opcode.js'; +/** - */ export class And implements Opcode { static type: string = 'AND'; static numberOfOperands = 3; @@ -20,6 +21,7 @@ export class And implements Opcode { } } +/** - */ export class Or implements Opcode { static type: string = 'OR'; static numberOfOperands = 3; @@ -36,6 +38,7 @@ export class Or implements Opcode { } } +/** - */ export class Xor implements Opcode { static type: string = 'XOR'; static numberOfOperands = 3; @@ -52,6 +55,7 @@ export class Xor implements Opcode { } } +/** - */ export class Not implements Opcode { static type: string = 'NOT'; static numberOfOperands = 2; 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 09057279a66a..68b97d38f334 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts @@ -2,6 +2,7 @@ import { AvmContext } from '../avm_context.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Opcode } from './opcode.js'; +/** - */ export class Return implements Opcode { static type: string = 'RETURN'; static numberOfOperands = 2; diff --git a/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts index c34a84e4dc5c..77104f4b71a1 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts @@ -12,6 +12,13 @@ const OPERANDS_LOOKUP: { [key: number]: number } = { 0x3: Mul.numberOfOperands, }; +/** + * Given the opcode and operands that have been parsed by the interpreter + * We return a construction of the opcode + * + * @param opcode - Opcode value + * @param operands - Array of operands + */ function opcodeLookup(opcode: number, operands: number[]): Opcode { switch (opcode) { case 0x1: diff --git a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts index 7c1c5cbaa73f..428ec134de61 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts @@ -4,6 +4,7 @@ import { AvmContext } from '../avm_context.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Opcode } from './opcode.js'; +/** - */ export class Set implements Opcode { static type: string = 'SET'; static numberOfOperands = 2; @@ -17,6 +18,7 @@ export class Set implements Opcode { } // TODO: tags are not implemented yet - this will behave as a mov +/** - */ export class Cast implements Opcode { static type: string = 'CAST'; static numberOfOperands = 2; @@ -30,6 +32,7 @@ export class Cast implements Opcode { } } +/** - */ export class Mov implements Opcode { static type: string = 'MOV'; static numberOfOperands = 2; @@ -43,6 +46,7 @@ export class Mov implements Opcode { } } +/** - */ export class CallDataCopy implements Opcode { static type: string = 'CALLDATACOPY'; static numberOfOperands = 3; From 91025fa7265e7e80992ae7bc2ea0e38a85f009df Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 11 Jan 2024 16:02:20 +0000 Subject: [PATCH 10/20] fix: linter --- .../src/avm/avm_executor.test.ts | 4 ++- .../src/avm/journal/host_storage.ts | 5 +++ .../acir-simulator/src/avm/journal/journal.ts | 32 +++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/yarn-project/acir-simulator/src/avm/avm_executor.test.ts b/yarn-project/acir-simulator/src/avm/avm_executor.test.ts index db93559b733a..05e9cf10f3e5 100644 --- a/yarn-project/acir-simulator/src/avm/avm_executor.test.ts +++ b/yarn-project/acir-simulator/src/avm/avm_executor.test.ts @@ -1 +1,3 @@ -describe('Avm', () => {}); +describe('Avm', () => { + it('Executes a simple call', () => {}); +}); diff --git a/yarn-project/acir-simulator/src/avm/journal/host_storage.ts b/yarn-project/acir-simulator/src/avm/journal/host_storage.ts index 6bf040789c15..ccd318912053 100644 --- a/yarn-project/acir-simulator/src/avm/journal/host_storage.ts +++ b/yarn-project/acir-simulator/src/avm/journal/host_storage.ts @@ -1,8 +1,13 @@ import { CommitmentsDB, PublicContractsDB, PublicStateDB } from '../../index.js'; +/** - */ export class HostStorage { + /** - */ public readonly stateDb: PublicStateDB; + /** - */ public readonly contractsDb: PublicContractsDB; + + /** - */ public readonly commitmentsDb: CommitmentsDB; constructor(stateDb: PublicStateDB, contractsDb: PublicContractsDB, commitmentsDb: CommitmentsDB) { diff --git a/yarn-project/acir-simulator/src/avm/journal/journal.ts b/yarn-project/acir-simulator/src/avm/journal/journal.ts index db059c4ed02c..67563fe63704 100644 --- a/yarn-project/acir-simulator/src/avm/journal/journal.ts +++ b/yarn-project/acir-simulator/src/avm/journal/journal.ts @@ -3,16 +3,22 @@ import { Fr } from '@aztec/foundation/fields'; import { HostStorage } from './host_storage.js'; // TODO: all of the data that comes out of the avm ready for write should be in this format +/** - */ export type JournalData = { + /** - */ newCommitments: Fr[]; + /** - */ newL1Message: Fr[]; + /** - */ storageWrites: { [key: string]: { [key: string]: Fr } }; }; // This persists for an entire block // Each transaction should have its own journal that gets appended to this one upon success +/** - */ export class AvmJournal { // TODO: should we make private? + /** - */ public readonly hostStorage: HostStorage; // We need to keep track of the following @@ -32,11 +38,22 @@ export class AvmJournal { } // TODO: work on the typing + /** + * - + * @param contractAddress - + * @param key - + * @param value - + */ public writeStorage(contractAddress: Fr, key: Fr, value: Fr) { // TODO: do we want this map to be ordered -> is there performance upside to this? this.storageWrites[contractAddress.toString()][key.toString()] = value; } + /** + * - + * @param contractAddress - + * @param key - + */ public readStorage(contractAddress: Fr, key: Fr) { const cachedValue = this.storageWrites[contractAddress.toString()][key.toString()]; if (cachedValue) { @@ -45,20 +62,35 @@ export class AvmJournal { return this.hostStorage.stateDb.storageRead(contractAddress, key); } + /** + * - + * @param commitment - + */ public writeCommitment(commitment: Fr) { this.newCommitments.push(commitment); } + /** + * - + * @param message - + */ public writeL1Message(message: Fr) { this.newL1Message.push(message); } // TODO: This function will merge two journals together -> the new head of the chain + /** + * - + * @param journal - + */ public mergeJournal(journal: AvmJournal) { // TODO: This function will void journal; } + /** + * - + */ public flush(): JournalData { return { newCommitments: this.newCommitments, From 2a61f28a62e430c8d18a1f679fb66444dff4768f Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 12 Jan 2024 19:48:48 +0000 Subject: [PATCH 11/20] fix: give todos tags --- yarn-project/acir-simulator/src/avm/index.ts | 3 +++ yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts | 7 +------ yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts | 4 ---- .../acir-simulator/src/avm/opcodes/from_bytecode.ts | 2 ++ yarn-project/acir-simulator/src/avm/opcodes/memory.ts | 2 +- yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts | 2 +- 6 files changed, 8 insertions(+), 12 deletions(-) diff --git a/yarn-project/acir-simulator/src/avm/index.ts b/yarn-project/acir-simulator/src/avm/index.ts index e69de29bb2d1..65a3e8178dc3 100644 --- a/yarn-project/acir-simulator/src/avm/index.ts +++ b/yarn-project/acir-simulator/src/avm/index.ts @@ -0,0 +1,3 @@ +export * from './avm_context.js'; +export * from './avm_executor.js'; +export * from "./avm_state_manager.js" \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts index 29ba731c2440..3f0066819885 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts @@ -63,7 +63,7 @@ export class Div implements Opcode { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); - // TODO: proper field division + // TODO(https://github.com/AztecProtocol/aztec-packages/issues/3993): proper field division const dest = new Fr(a.toBigInt() / b.toBigInt()); context.writeMemory(this.destOffset, dest); } @@ -80,7 +80,6 @@ export class Eq implements Opcode { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); - // TODO: floor div? - this will not perform field division const dest = new Fr(a.toBigInt() == b.toBigInt()); context.writeMemory(this.destOffset, dest); } @@ -96,7 +95,6 @@ export class Lt implements Opcode { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); - // TODO: floor div? - this will not perform field division const dest = new Fr(a.toBigInt() < b.toBigInt()); context.writeMemory(this.destOffset, dest); } @@ -113,7 +111,6 @@ export class Lte implements Opcode { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); - // TODO: floor div? - this will not perform field division const dest = new Fr(a.toBigInt() < b.toBigInt()); context.writeMemory(this.destOffset, dest); } @@ -130,7 +127,6 @@ export class Shl implements Opcode { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); - // TODO: floor div? - this will not perform field division const dest = new Fr(a.toBigInt() << b.toBigInt()); context.writeMemory(this.destOffset, dest); } @@ -147,7 +143,6 @@ export class Shr implements Opcode { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); - // TODO: floor div? - this will not perform field division const dest = new Fr(a.toBigInt() >> b.toBigInt()); context.writeMemory(this.destOffset, dest); } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts index 440e383725e8..31bc8b71c8c5 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts @@ -15,7 +15,6 @@ export class And implements Opcode { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); - // TODO: floor div? - this will not perform field division const dest = new Fr(a.toBigInt() & b.toBigInt()); context.writeMemory(this.destOffset, dest); } @@ -32,7 +31,6 @@ export class Or implements Opcode { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); - // TODO: floor div? - this will not perform field division const dest = new Fr(a.toBigInt() | b.toBigInt()); context.writeMemory(this.destOffset, dest); } @@ -49,7 +47,6 @@ export class Xor implements Opcode { const a: Fr = context.readMemory(this.aOffset); const b: Fr = context.readMemory(this.bOffset); - // TODO: floor div? - this will not perform field division const dest = new Fr(a.toBigInt() ^ b.toBigInt()); context.writeMemory(this.destOffset, dest); } @@ -65,7 +62,6 @@ export class Not implements Opcode { execute(context: AvmContext, _stateManager: AvmStateManager): void { const a: Fr = context.readMemory(this.aOffset); - // TODO: floor div? - this will not perform field division const dest = new Fr(~a.toBigInt()); context.writeMemory(this.destOffset, dest); } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts index 77104f4b71a1..70b0e5188fde 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts @@ -6,6 +6,8 @@ export const OPERAND_BTYE_LENGTH = 4; export const OPCODE_BIT_LENGTH = 8; export const OPCODE_BYTE_LENGTH = 1; + + const OPERANDS_LOOKUP: { [key: number]: number } = { 0x1: Add.numberOfOperands, 0x2: Sub.numberOfOperands, diff --git a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts index 428ec134de61..f0bec669a936 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts @@ -17,7 +17,7 @@ export class Set implements Opcode { } } -// TODO: tags are not implemented yet - this will behave as a mov +// TODO(https://github.com/AztecProtocol/aztec-packages/issues/3987): tags are not implemented yet - this will behave as a mov /** - */ export class Cast implements Opcode { static type: string = 'CAST'; diff --git a/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts b/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts index fce28ac023dc..c277c0556281 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts @@ -56,7 +56,7 @@ export enum Opcodes { NOTESROOT, PUBLICDATAROOT, GLOBALSHASH, - BLOCKSROOT, // TODO: are these the same + BLOCKSROOT, GRANDROOT, // Call context From 7d255882f943a99d4e24a67b6d6ab5c58832578d Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 12 Jan 2024 19:51:26 +0000 Subject: [PATCH 12/20] fix: add comparators --- .../src/avm/opcodes/arithmetic.ts | 78 ------------------- .../src/avm/opcodes/comparators.ts | 51 ++++++++++++ .../acir-simulator/src/avm/opcodes/index.ts | 2 + 3 files changed, 53 insertions(+), 78 deletions(-) create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/comparators.ts diff --git a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts index 3f0066819885..2850ae2a568c 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts @@ -69,81 +69,3 @@ export class Div implements Opcode { } } -/** -*/ -export class Eq implements Opcode { - static type: string = 'EQ'; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); - - const dest = new Fr(a.toBigInt() == b.toBigInt()); - context.writeMemory(this.destOffset, dest); - } -} -/** -*/ -export class Lt implements Opcode { - static type: string = 'Lt'; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); - - const dest = new Fr(a.toBigInt() < b.toBigInt()); - context.writeMemory(this.destOffset, dest); - } -} - -/** -*/ -export class Lte implements Opcode { - static type: string = 'LTE'; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); - - const dest = new Fr(a.toBigInt() < b.toBigInt()); - context.writeMemory(this.destOffset, dest); - } -} - -/** -*/ -export class Shl implements Opcode { - static type: string = 'SHL'; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); - - const dest = new Fr(a.toBigInt() << b.toBigInt()); - context.writeMemory(this.destOffset, dest); - } -} - -/** -*/ -export class Shr implements Opcode { - static type: string = 'SHR'; - static numberOfOperands = 3; - - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); - - const dest = new Fr(a.toBigInt() >> b.toBigInt()); - context.writeMemory(this.destOffset, dest); - } -} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts new file mode 100644 index 000000000000..7d557a0f7006 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts @@ -0,0 +1,51 @@ +import { Fr } from "@aztec/foundation/fields"; +import { AvmContext } from "../avm_context.js"; +import { AvmStateManager } from "../avm_state_manager.js"; +import { Opcode } from "./opcode.js"; + +/** -*/ +export class Eq implements Opcode { + static type: string = 'EQ'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() == b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } +} +/** -*/ +export class Lt implements Opcode { + static type: string = 'Lt'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() < b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } +} + +/** -*/ +export class Lte implements Opcode { + static type: string = 'LTE'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() < b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/index.ts b/yarn-project/acir-simulator/src/avm/opcodes/index.ts index ce2985bb9f28..8ff0005f3745 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/index.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/index.ts @@ -2,3 +2,5 @@ export * from './arithmetic.js'; export * from './control_flow.js'; export * from './call.js'; export * from './opcode.js'; +export * from "./comparators.js"; +export * from "./memory.js"; \ No newline at end of file From 55dd8ce596e7facb7b80f3e8ac90e909022dfcd0 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 12 Jan 2024 19:51:42 +0000 Subject: [PATCH 13/20] fix: move shifts to bitwise --- .../acir-simulator/src/avm/opcodes/bitwise.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts index 31bc8b71c8c5..e9e9738930a4 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts @@ -4,6 +4,7 @@ import { AvmContext } from '../avm_context.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Opcode } from './opcode.js'; + /** - */ export class And implements Opcode { static type: string = 'AND'; @@ -66,3 +67,36 @@ export class Not implements Opcode { context.writeMemory(this.destOffset, dest); } } + + +/** -*/ +export class Shl implements Opcode { + static type: string = 'SHL'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() << b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } +} + +/** -*/ +export class Shr implements Opcode { + static type: string = 'SHR'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(context: AvmContext, _stateManager: AvmStateManager): void { + const a: Fr = context.readMemory(this.aOffset); + const b: Fr = context.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() >> b.toBigInt()); + context.writeMemory(this.destOffset, dest); + } +} \ No newline at end of file From 8eed7d38f8ce4eacfc42de1f813dfb3c818dd6c6 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 12 Jan 2024 19:55:52 +0000 Subject: [PATCH 14/20] fix: update opcodes -> instructions --- .../acir-simulator/src/avm/avm_executor.ts | 6 +++--- .../src/avm/interpreter/interpreter.test.ts | 8 ++++---- .../src/avm/interpreter/interpreter.ts | 12 ++++++------ .../src/avm/opcodes/arithmetic.ts | 10 +++++----- .../acir-simulator/src/avm/opcodes/bitwise.ts | 14 +++++++------- .../src/avm/opcodes/comparators.ts | 8 ++++---- .../src/avm/opcodes/control_flow.ts | 4 ++-- .../src/avm/opcodes/from_bytecode.test.ts | 8 ++++---- .../src/avm/opcodes/from_bytecode.ts | 18 ++++++++---------- .../acir-simulator/src/avm/opcodes/index.ts | 2 +- .../avm/opcodes/{opcode.ts => instruction.ts} | 2 +- .../acir-simulator/src/avm/opcodes/memory.ts | 10 +++++----- 12 files changed, 50 insertions(+), 52 deletions(-) rename yarn-project/acir-simulator/src/avm/opcodes/{opcode.ts => instruction.ts} (85%) diff --git a/yarn-project/acir-simulator/src/avm/avm_executor.ts b/yarn-project/acir-simulator/src/avm/avm_executor.ts index 86d167f6230e..30a6cfabd8e4 100644 --- a/yarn-project/acir-simulator/src/avm/avm_executor.ts +++ b/yarn-project/acir-simulator/src/avm/avm_executor.ts @@ -4,7 +4,7 @@ import { AvmContext } from './avm_context.js'; import { AvmStateManager } from './avm_state_manager.js'; import { AvmInterpreter } from './interpreter/index.js'; import { interpretBytecode } from './opcodes/from_bytecode.js'; -import { Opcode } from './opcodes/opcode.js'; +import { Instruction } from './opcodes/index.js'; /** * Avm Executor manages the execution of the AVM @@ -33,10 +33,10 @@ export class AvmExecutor { // const bytecode = stateManager.journal.hostStorage.contractsDb.getBytecode(contractAddress); const bytecode = Buffer.from('0x01000100020003'); - const opcodes: Opcode[] = interpretBytecode(bytecode); + const instructions: Instruction[] = interpretBytecode(bytecode); const context = new AvmContext(calldata); - const interpreter = new AvmInterpreter(context, this.stateManager, opcodes); + const interpreter = new AvmInterpreter(context, this.stateManager, instructions); // TODO: combine the two? interpreter.run(); diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts index 92cd6e6f8bf9..9bb9d8926325 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts @@ -7,15 +7,15 @@ import { AvmStateManager } from '../avm_state_manager.js'; import { Add } from '../opcodes/arithmetic.js'; import { Return } from '../opcodes/control_flow.js'; import { CallDataCopy } from '../opcodes/memory.js'; -import { Opcode } from '../opcodes/opcode.js'; +import { Instruction } from '../opcodes/instruction.js'; import { AvmInterpreter } from './interpreter.js'; describe('interpreter', () => { - it('Should execute a series of opcodes', () => { + it('Should execute a series of instructions', () => { const calldata: Fr[] = [new Fr(1), new Fr(2)]; const stateManager = mock(); - const opcodes: Opcode[] = [ + const instructions: Instruction[] = [ // Copy the first two elements of the calldata to memory regions 0 and 1 new CallDataCopy(0, 2, 0), // Add the two together and store the result in memory region 2 @@ -25,7 +25,7 @@ describe('interpreter', () => { ]; const context = new AvmContext(calldata); - const interpreter = new AvmInterpreter(context, stateManager, opcodes); + const interpreter = new AvmInterpreter(context, stateManager, instructions); const success = interpreter.run(); expect(success).toBe(true); diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts index eecd309227d5..1668f5411804 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -3,7 +3,7 @@ import { Fr } from '@aztec/foundation/fields'; import { AvmContext } from '../avm_context.js'; import { AvmStateManager } from '../avm_state_manager.js'; -import { Opcode } from '../opcodes/index.js'; +import { Instruction } from '../opcodes/index.js'; /** * Avm Interpreter @@ -11,14 +11,14 @@ import { Opcode } from '../opcodes/index.js'; * Executes an Avm context */ export class AvmInterpreter { - private opcodes: Opcode[] = []; + private instructions: Instruction[] = []; private context: AvmContext; private stateManager: AvmStateManager; - constructor(context: AvmContext, stateManager: AvmStateManager, bytecode: Opcode[]) { + constructor(context: AvmContext, stateManager: AvmStateManager, bytecode: Instruction[]) { this.context = context; this.stateManager = stateManager; - this.opcodes = bytecode; + this.instructions = bytecode; } /** @@ -29,8 +29,8 @@ export class AvmInterpreter { */ run(): boolean { try { - for (const opcode of this.opcodes) { - opcode.execute(this.context, this.stateManager); + for (const instruction of this.instructions) { + instruction.execute(this.context, this.stateManager); } return true; diff --git a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts index 2850ae2a568c..34c9569207bd 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts @@ -2,10 +2,10 @@ import { Fr } from '@aztec/foundation/fields'; import { AvmContext } from '../avm_context.js'; import { AvmStateManager } from '../avm_state_manager.js'; -import { Opcode } from './opcode.js'; +import { Instruction } from './instruction.js'; /** -*/ -export class Add implements Opcode { +export class Add implements Instruction { static type: string = 'ADD'; static numberOfOperands = 3; @@ -21,7 +21,7 @@ export class Add implements Opcode { } /** -*/ -export class Sub implements Opcode { +export class Sub implements Instruction { static type: string = 'SUB'; static numberOfOperands = 3; @@ -37,7 +37,7 @@ export class Sub implements Opcode { } /** -*/ -export class Mul implements Opcode { +export class Mul implements Instruction { static type: string = 'MUL'; static numberOfOperands = 3; @@ -53,7 +53,7 @@ export class Mul implements Opcode { } /** -*/ -export class Div implements Opcode { +export class Div implements Instruction { static type: string = 'DIV'; static numberOfOperands = 3; diff --git a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts index e9e9738930a4..7d35b3f1a207 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts @@ -2,11 +2,11 @@ import { Fr } from '@aztec/foundation/fields'; import { AvmContext } from '../avm_context.js'; import { AvmStateManager } from '../avm_state_manager.js'; -import { Opcode } from './opcode.js'; +import { Instruction } from './instruction.js'; /** - */ -export class And implements Opcode { +export class And implements Instruction { static type: string = 'AND'; static numberOfOperands = 3; @@ -22,7 +22,7 @@ export class And implements Opcode { } /** - */ -export class Or implements Opcode { +export class Or implements Instruction { static type: string = 'OR'; static numberOfOperands = 3; @@ -38,7 +38,7 @@ export class Or implements Opcode { } /** - */ -export class Xor implements Opcode { +export class Xor implements Instruction { static type: string = 'XOR'; static numberOfOperands = 3; @@ -54,7 +54,7 @@ export class Xor implements Opcode { } /** - */ -export class Not implements Opcode { +export class Not implements Instruction { static type: string = 'NOT'; static numberOfOperands = 2; @@ -70,7 +70,7 @@ export class Not implements Opcode { /** -*/ -export class Shl implements Opcode { +export class Shl implements Instruction { static type: string = 'SHL'; static numberOfOperands = 3; @@ -86,7 +86,7 @@ export class Shl implements Opcode { } /** -*/ -export class Shr implements Opcode { +export class Shr implements Instruction { static type: string = 'SHR'; static numberOfOperands = 3; diff --git a/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts index 7d557a0f7006..e6b4c82faada 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts @@ -1,10 +1,10 @@ import { Fr } from "@aztec/foundation/fields"; import { AvmContext } from "../avm_context.js"; import { AvmStateManager } from "../avm_state_manager.js"; -import { Opcode } from "./opcode.js"; +import { Instruction } from "./instruction.js"; /** -*/ -export class Eq implements Opcode { +export class Eq implements Instruction { static type: string = 'EQ'; static numberOfOperands = 3; @@ -19,7 +19,7 @@ export class Eq implements Opcode { } } /** -*/ -export class Lt implements Opcode { +export class Lt implements Instruction { static type: string = 'Lt'; static numberOfOperands = 3; @@ -35,7 +35,7 @@ export class Lt implements Opcode { } /** -*/ -export class Lte implements Opcode { +export class Lte implements Instruction { static type: string = 'LTE'; static numberOfOperands = 3; 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 68b97d38f334..f1257b1ef416 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts @@ -1,9 +1,9 @@ import { AvmContext } from '../avm_context.js'; import { AvmStateManager } from '../avm_state_manager.js'; -import { Opcode } from './opcode.js'; +import { Instruction } from './instruction.js'; /** - */ -export class Return implements Opcode { +export class Return implements Instruction { static type: string = 'RETURN'; static numberOfOperands = 2; diff --git a/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.test.ts index c97fc51338ce..e7f95b89b4f8 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.test.ts @@ -1,6 +1,6 @@ import { Add, Sub } from './arithmetic.js'; import { OPCODE_BYTE_LENGTH, OPERAND_BTYE_LENGTH, interpretBytecode } from './from_bytecode.js'; -import { Opcode } from './opcode.js'; +import { Instruction } from './instruction.js'; describe('Avm Interpreter', () => { const toByte = (num: number): Buffer => { @@ -28,9 +28,9 @@ describe('Avm Interpreter', () => { const cs = to4Byte(c); const bytecode = Buffer.concat([ops, as, bs, cs, ops2, as, bs, cs]); - const expectedOpcodes: Opcode[] = [new Add(a, b, c), new Sub(a, b, c)]; + const expectedInstructions: Instruction[] = [new Add(a, b, c), new Sub(a, b, c)]; - const opcodes = interpretBytecode(bytecode); - expect(opcodes).toEqual(expectedOpcodes); + const instructions = interpretBytecode(bytecode); + expect(instructions).toEqual(expectedInstructions); }); }); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts index 70b0e5188fde..697ff6be13bc 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts @@ -1,13 +1,11 @@ import { Add, Mul, Sub } from './arithmetic.js'; -import { Opcode } from './opcode.js'; +import { Instruction } from './instruction.js'; export const OPERAND_BIT_LENGTH = 32; export const OPERAND_BTYE_LENGTH = 4; export const OPCODE_BIT_LENGTH = 8; export const OPCODE_BYTE_LENGTH = 1; - - const OPERANDS_LOOKUP: { [key: number]: number } = { 0x1: Add.numberOfOperands, 0x2: Sub.numberOfOperands, @@ -21,7 +19,7 @@ const OPERANDS_LOOKUP: { [key: number]: number } = { * @param opcode - Opcode value * @param operands - Array of operands */ -function opcodeLookup(opcode: number, operands: number[]): Opcode { +function instructionLookup(opcode: number, operands: number[]): Instruction { switch (opcode) { case 0x1: return new Add(operands[0], operands[1], operands[2]); @@ -35,15 +33,15 @@ function opcodeLookup(opcode: number, operands: number[]): Opcode { } /** - * Convert a buffer of bytecode into an array of opcodes + * Convert a buffer of bytecode into an array of instructions * @param bytecode - Buffer of bytecode - * @returns Bytecode interpreted into an ordered array of Opcodes + * @returns Bytecode interpreted into an ordered array of Instructions */ -export function interpretBytecode(bytecode: Buffer): Opcode[] { +export function interpretBytecode(bytecode: Buffer): Instruction[] { let readPtr = 0; const bytecodeLength = bytecode.length; - const opcodes: Opcode[] = []; + const instructions: Instruction[] = []; while (readPtr < bytecodeLength) { const opcode = bytecode[readPtr]; @@ -57,8 +55,8 @@ export function interpretBytecode(bytecode: Buffer): Opcode[] { operands.push(operand); } - opcodes.push(opcodeLookup(opcode, operands)); + instructions.push(instructionLookup(opcode, operands)); } - return opcodes; + return instructions; } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/index.ts b/yarn-project/acir-simulator/src/avm/opcodes/index.ts index 8ff0005f3745..25e0f4764da3 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/index.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/index.ts @@ -1,6 +1,6 @@ export * from './arithmetic.js'; export * from './control_flow.js'; export * from './call.js'; -export * from './opcode.js'; +export * from './instruction.js'; export * from "./comparators.js"; export * from "./memory.js"; \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/opcodes/opcode.ts b/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts similarity index 85% rename from yarn-project/acir-simulator/src/avm/opcodes/opcode.ts rename to yarn-project/acir-simulator/src/avm/opcodes/instruction.ts index 34710783005e..611b1810566b 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/opcode.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts @@ -4,6 +4,6 @@ import { AvmStateManager } from '../avm_state_manager.js'; /** * Opcode base class */ -export abstract class Opcode { +export abstract class Instruction { abstract execute(context: AvmContext, stateManager: AvmStateManager): void; } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts index f0bec669a936..efa11b477374 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts @@ -2,10 +2,10 @@ import { Fr } from '@aztec/foundation/fields'; import { AvmContext } from '../avm_context.js'; import { AvmStateManager } from '../avm_state_manager.js'; -import { Opcode } from './opcode.js'; +import { Instruction } from './instruction.js'; /** - */ -export class Set implements Opcode { +export class Set implements Instruction { static type: string = 'SET'; static numberOfOperands = 2; @@ -19,7 +19,7 @@ export class Set implements Opcode { // TODO(https://github.com/AztecProtocol/aztec-packages/issues/3987): tags are not implemented yet - this will behave as a mov /** - */ -export class Cast implements Opcode { +export class Cast implements Instruction { static type: string = 'CAST'; static numberOfOperands = 2; @@ -33,7 +33,7 @@ export class Cast implements Opcode { } /** - */ -export class Mov implements Opcode { +export class Mov implements Instruction { static type: string = 'MOV'; static numberOfOperands = 2; @@ -47,7 +47,7 @@ export class Mov implements Opcode { } /** - */ -export class CallDataCopy implements Opcode { +export class CallDataCopy implements Instruction { static type: string = 'CALLDATACOPY'; static numberOfOperands = 3; From 63af79716410a2815a245cb83d06432e2f9e3ad1 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 12 Jan 2024 19:56:45 +0000 Subject: [PATCH 15/20] fix: update multiplication yp text --- yellow-paper/src/preprocess/InstructionSet/InstructionSet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js index f734022e4473..ff3fd87fb47e 100644 --- a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js +++ b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js @@ -70,7 +70,7 @@ const INSTRUCTION_SET_RAW = [ {"name": "dstOffset", "description": "memory offset specifying where to store operation's result"}, ], "Expression": "`M[dstOffset] = M[aOffset] * M[bOffset] mod 2^k`", - "Summary": "Subtraction (a - b)", + "Summary": "Multiplication (a * b)", "Details": "", "Tag checks": "`T[aOffset] == T[bOffset] == in-tag`", "Tag updates": "`T[dstOffset] = in-tag`", From 9c81e837f36cf49a8439063f7fc6121deeff14bb Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 12 Jan 2024 20:50:46 +0000 Subject: [PATCH 16/20] fix: align naming with yellow paper --- ...m_executor.test.ts => avm_context.test.ts} | 0 .../acir-simulator/src/avm/avm_context.ts | 93 ++++++------------- .../acir-simulator/src/avm/avm_executor.ts | 48 ---------- .../src/avm/avm_machine_state.ts | 79 ++++++++++++++++ .../src/avm/avm_message_call_result.ts | 35 +++++++ .../src/avm/avm_state_manager.ts | 1 - yarn-project/acir-simulator/src/avm/index.ts | 2 +- .../src/avm/interpreter/interpreter.test.ts | 4 +- .../src/avm/interpreter/interpreter.ts | 23 +++-- .../src/avm/opcodes/arithmetic.ts | 34 +++---- .../acir-simulator/src/avm/opcodes/bitwise.ts | 48 +++++----- .../src/avm/opcodes/comparators.ts | 26 +++--- .../src/avm/opcodes/control_flow.ts | 8 +- .../src/avm/opcodes/instruction.ts | 4 +- .../acir-simulator/src/avm/opcodes/memory.ts | 24 ++--- 15 files changed, 231 insertions(+), 198 deletions(-) rename yarn-project/acir-simulator/src/avm/{avm_executor.test.ts => avm_context.test.ts} (100%) delete mode 100644 yarn-project/acir-simulator/src/avm/avm_executor.ts create mode 100644 yarn-project/acir-simulator/src/avm/avm_machine_state.ts create mode 100644 yarn-project/acir-simulator/src/avm/avm_message_call_result.ts diff --git a/yarn-project/acir-simulator/src/avm/avm_executor.test.ts b/yarn-project/acir-simulator/src/avm/avm_context.test.ts similarity index 100% rename from yarn-project/acir-simulator/src/avm/avm_executor.test.ts rename to yarn-project/acir-simulator/src/avm/avm_context.test.ts diff --git a/yarn-project/acir-simulator/src/avm/avm_context.ts b/yarn-project/acir-simulator/src/avm/avm_context.ts index 50ee021f69a0..faead45da7db 100644 --- a/yarn-project/acir-simulator/src/avm/avm_context.ts +++ b/yarn-project/acir-simulator/src/avm/avm_context.ts @@ -1,79 +1,44 @@ import { Fr } from '@aztec/foundation/fields'; +import { AvmMachineState } from './avm_machine_state.js'; +import { AvmStateManager } from './avm_state_manager.js'; +import { AvmInterpreter } from './interpreter/index.js'; +import { interpretBytecode } from './opcodes/from_bytecode.js'; +import { Instruction } from './opcodes/index.js'; +import { AvmMessageCallResult } from './avm_message_call_result.js'; + /** - * Store's data for an Avm execution frame + * Avm Executor manages the execution of the AVM + * + * It stores a state manager */ export class AvmContext { - /** - */ - public readonly calldata: Fr[]; - private returnData: Fr[]; - - // TODO: implement tagged memory - /** - */ - public memory: Fr[]; - - /** - */ - public pc: number; - /** - */ - public callStack: number[]; - - /** - * Create a new avm context - * @param calldata - - */ - constructor(calldata: Fr[]) { - this.calldata = calldata; - this.returnData = []; - this.memory = []; + private stateManager: AvmStateManager; - this.pc = 0; - this.callStack = []; + constructor(stateManager: AvmStateManager) { + this.stateManager = stateManager; } /** - * Return data must NOT be modified once it is set - * @param returnData - - */ - public setReturnData(returnData: Fr[]) { - this.returnData = returnData; - Object.freeze(returnData); - } - - /** - */ - public getReturnData(): Fr[] { - return this.returnData; - } - - /** - - * @param offset - + * Call a contract with the given calldata + * + * - We get the contract from storage + * - We interpret the bytecode + * - We run the interpreter + * + * @param contractAddress - + * @param calldata - */ - public readMemory(offset: number): Fr { - // TODO: check offset is within bounds - return this.memory[offset] ?? Fr.ZERO; - } + public call(contractAddress: Fr, calldata: Fr[]): AvmMessageCallResult { + // NOTE: the following is mocked as getPublicBytecode does not exist yet + // const bytecode = stateManager.journal.hostStorage.contractsDb.getBytecode(contractAddress); + const bytecode = Buffer.from('0x01000100020003'); - /** - - * @param offset - - * @param size - - */ - public readMemoryChunk(offset: number, size: number): Fr[] { - // TODO: bounds -> initialise to 0 - return this.memory.slice(offset, offset + size); - } + const instructions: Instruction[] = interpretBytecode(bytecode); - /** - - * @param offset - - * @param value - - */ - public writeMemory(offset: number, value: Fr): void { - this.memory[offset] = value; - } + const context = new AvmMachineState(calldata); + const interpreter = new AvmInterpreter(context, this.stateManager, instructions); - /** - - * @param offset - - * @param values - - */ - public writeMemoryChunk(offset: number, values: Fr[]): void { - this.memory.splice(offset, values.length, ...values); + return interpreter.run(); } } diff --git a/yarn-project/acir-simulator/src/avm/avm_executor.ts b/yarn-project/acir-simulator/src/avm/avm_executor.ts deleted file mode 100644 index 30a6cfabd8e4..000000000000 --- a/yarn-project/acir-simulator/src/avm/avm_executor.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Fr } from '@aztec/foundation/fields'; - -import { AvmContext } from './avm_context.js'; -import { AvmStateManager } from './avm_state_manager.js'; -import { AvmInterpreter } from './interpreter/index.js'; -import { interpretBytecode } from './opcodes/from_bytecode.js'; -import { Instruction } from './opcodes/index.js'; - -/** - * Avm Executor manages the execution of the AVM - * - * It stores a state manager - */ -export class AvmExecutor { - private stateManager: AvmStateManager; - - constructor(stateManager: AvmStateManager) { - this.stateManager = stateManager; - } - - /** - * Call a contract with the given calldata - * - * - We get the contract from storage - * - We interpret the bytecode - * - We run the interpreter - * - * @param contractAddress - - * @param calldata - - */ - public call(contractAddress: Fr, calldata: Fr[]): Fr[] { - // NOTE: the following is mocked as getPublicBytecode does not exist yet - // const bytecode = stateManager.journal.hostStorage.contractsDb.getBytecode(contractAddress); - const bytecode = Buffer.from('0x01000100020003'); - - const instructions: Instruction[] = interpretBytecode(bytecode); - - const context = new AvmContext(calldata); - const interpreter = new AvmInterpreter(context, this.stateManager, instructions); - - // TODO: combine the two? - interpreter.run(); - const returnData = interpreter.returnData(); - - // TODO: Do something with state hand off of a successful call - return returnData; - } -} diff --git a/yarn-project/acir-simulator/src/avm/avm_machine_state.ts b/yarn-project/acir-simulator/src/avm/avm_machine_state.ts new file mode 100644 index 000000000000..aa0fc689b58d --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/avm_machine_state.ts @@ -0,0 +1,79 @@ +import { Fr } from '@aztec/foundation/fields'; + +/** + * Store's data for an Avm execution frame + */ +export class AvmMachineState { + /** - */ + public readonly calldata: Fr[]; + private returnData: Fr[]; + + // TODO: implement tagged memory + /** - */ + public memory: Fr[]; + + /** - */ + public pc: number; + /** - */ + public callStack: number[]; + + /** + * Create a new avm context + * @param calldata - + */ + constructor(calldata: Fr[]) { + this.calldata = calldata; + this.returnData = []; + this.memory = []; + + this.pc = 0; + this.callStack = []; + } + + /** + * Return data must NOT be modified once it is set + * @param returnData - + */ + public setReturnData(returnData: Fr[]) { + this.returnData = returnData; + Object.freeze(returnData); + } + + /** - */ + public getReturnData(): Fr[] { + return this.returnData; + } + + /** - + * @param offset - + */ + public readMemory(offset: number): Fr { + // TODO: check offset is within bounds + return this.memory[offset] ?? Fr.ZERO; + } + + /** - + * @param offset - + * @param size - + */ + public readMemoryChunk(offset: number, size: number): Fr[] { + // TODO: bounds -> initialise to 0 + return this.memory.slice(offset, offset + size); + } + + /** - + * @param offset - + * @param value - + */ + public writeMemory(offset: number, value: Fr): void { + this.memory[offset] = value; + } + + /** - + * @param offset - + * @param values - + */ + public writeMemoryChunk(offset: number, values: Fr[]): void { + this.memory.splice(offset, values.length, ...values); + } +} 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 new file mode 100644 index 000000000000..0790134e31f6 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/avm_message_call_result.ts @@ -0,0 +1,35 @@ +import { Fr } from "@aztec/foundation/fields"; + +/** + * AVM message call result. + */ +export class AvmMessageCallResult { + /** - */ + public readonly reverted: boolean; + /** .- */ + public readonly output: Fr[]; + + constructor(reverted: boolean, output: Fr[]) { + this.reverted = reverted; + this.output = output; + } + + /** + * 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 ) + * @returns instance of AvmMessageCallResult + */ + public static revert(output: Fr[]): AvmMessageCallResult { + return new AvmMessageCallResult(true, output); + } + +} \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/avm_state_manager.ts b/yarn-project/acir-simulator/src/avm/avm_state_manager.ts index 250a7d8f1225..9ce2f1453b68 100644 --- a/yarn-project/acir-simulator/src/avm/avm_state_manager.ts +++ b/yarn-project/acir-simulator/src/avm/avm_state_manager.ts @@ -1,5 +1,4 @@ import { BlockHeader } from '@aztec/circuits.js'; - import { AvmJournal, HostStorage } from './journal/index.js'; /** diff --git a/yarn-project/acir-simulator/src/avm/index.ts b/yarn-project/acir-simulator/src/avm/index.ts index 65a3e8178dc3..6c51cb5d18ba 100644 --- a/yarn-project/acir-simulator/src/avm/index.ts +++ b/yarn-project/acir-simulator/src/avm/index.ts @@ -1,3 +1,3 @@ +export * from './avm_machine_state.js'; export * from './avm_context.js'; -export * from './avm_executor.js'; export * from "./avm_state_manager.js" \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts index 9bb9d8926325..0c744c2baa31 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts @@ -2,7 +2,7 @@ import { Fr } from '@aztec/foundation/fields'; import { mock } from 'jest-mock-extended'; -import { AvmContext } from '../avm_context.js'; +import { AvmMachineState } from '../avm_machine_state.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Add } from '../opcodes/arithmetic.js'; import { Return } from '../opcodes/control_flow.js'; @@ -24,7 +24,7 @@ describe('interpreter', () => { new Return(2, 1), // [3] ]; - const context = new AvmContext(calldata); + const context = new AvmMachineState(calldata); const interpreter = new AvmInterpreter(context, stateManager, instructions); const success = interpreter.run(); diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts index 1668f5411804..87ee1cb5d7d8 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -1,9 +1,10 @@ -// import { AvmContext } from "../avm_context.js"; +// import { AvmContext } from "../avm_machineState.js"; import { Fr } from '@aztec/foundation/fields'; -import { AvmContext } from '../avm_context.js'; +import { AvmMachineState } from '../avm_machine_state.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Instruction } from '../opcodes/index.js'; +import { AvmMessageCallResult } from '../avm_message_call_result.js'; /** * Avm Interpreter @@ -12,11 +13,11 @@ import { Instruction } from '../opcodes/index.js'; */ export class AvmInterpreter { private instructions: Instruction[] = []; - private context: AvmContext; + private machineState: AvmMachineState; private stateManager: AvmStateManager; - constructor(context: AvmContext, stateManager: AvmStateManager, bytecode: Instruction[]) { - this.context = context; + constructor(machineState: AvmMachineState, stateManager: AvmStateManager, bytecode: Instruction[]) { + this.machineState = machineState; this.stateManager = stateManager; this.instructions = bytecode; } @@ -27,16 +28,18 @@ export class AvmInterpreter { * - reverted execution will return false * - any other panic will throw */ - run(): boolean { + run(): AvmMessageCallResult { try { for (const instruction of this.instructions) { - instruction.execute(this.context, this.stateManager); + instruction.execute(this.machineState, this.stateManager); } - return true; + const returnData = this.machineState.getReturnData(); + return AvmMessageCallResult.success(returnData); } catch (e) { // TODO: This should only accept AVM defined errors, anything else SHOULD be thrown upstream - return false; + const revertData = this.machineState.getReturnData(); + return AvmMessageCallResult.revert(revertData); } } @@ -46,6 +49,6 @@ export class AvmInterpreter { * - maybe move the return in run into a variable and track it */ returnData(): Fr[] { - return this.context.getReturnData(); + return this.machineState.getReturnData(); } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts index 34c9569207bd..4889ea8e4f95 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts @@ -1,6 +1,6 @@ import { Fr } from '@aztec/foundation/fields'; -import { AvmContext } from '../avm_context.js'; +import { AvmMachineState } from '../avm_machine_state.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Instruction } from './instruction.js'; @@ -11,12 +11,12 @@ export class Add implements Instruction { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a = context.readMemory(this.aOffset); - const b = context.readMemory(this.bOffset); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a = machineState.readMemory(this.aOffset); + const b = machineState.readMemory(this.bOffset); const dest = new Fr(a.toBigInt() + (b.toBigInt() % Fr.MODULUS)); - context.writeMemory(this.destOffset, dest); + machineState.writeMemory(this.destOffset, dest); } } @@ -27,12 +27,12 @@ export class Sub implements Instruction { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a = context.readMemory(this.aOffset); - const b = context.readMemory(this.bOffset); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a = machineState.readMemory(this.aOffset); + const b = machineState.readMemory(this.bOffset); const dest = new Fr(a.toBigInt() - (b.toBigInt() % Fr.MODULUS)); - context.writeMemory(this.destOffset, dest); + machineState.writeMemory(this.destOffset, dest); } } @@ -43,12 +43,12 @@ export class Mul implements Instruction { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); const dest = new Fr((a.toBigInt() * b.toBigInt()) % Fr.MODULUS); - context.writeMemory(this.destOffset, dest); + machineState.writeMemory(this.destOffset, dest); } } @@ -59,13 +59,13 @@ export class Div implements Instruction { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); // TODO(https://github.com/AztecProtocol/aztec-packages/issues/3993): proper field division const dest = new Fr(a.toBigInt() / b.toBigInt()); - context.writeMemory(this.destOffset, dest); + machineState.writeMemory(this.destOffset, dest); } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts index 7d35b3f1a207..3ba17d105fec 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts @@ -1,6 +1,6 @@ import { Fr } from '@aztec/foundation/fields'; -import { AvmContext } from '../avm_context.js'; +import { AvmMachineState } from '../avm_machine_state.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Instruction } from './instruction.js'; @@ -12,12 +12,12 @@ export class And implements Instruction { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); const dest = new Fr(a.toBigInt() & b.toBigInt()); - context.writeMemory(this.destOffset, dest); + machineState.writeMemory(this.destOffset, dest); } } @@ -28,12 +28,12 @@ export class Or implements Instruction { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); const dest = new Fr(a.toBigInt() | b.toBigInt()); - context.writeMemory(this.destOffset, dest); + machineState.writeMemory(this.destOffset, dest); } } @@ -44,12 +44,12 @@ export class Xor implements Instruction { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); const dest = new Fr(a.toBigInt() ^ b.toBigInt()); - context.writeMemory(this.destOffset, dest); + machineState.writeMemory(this.destOffset, dest); } } @@ -60,11 +60,11 @@ export class Not implements Instruction { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); const dest = new Fr(~a.toBigInt()); - context.writeMemory(this.destOffset, dest); + machineState.writeMemory(this.destOffset, dest); } } @@ -76,12 +76,12 @@ export class Shl implements Instruction { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); const dest = new Fr(a.toBigInt() << b.toBigInt()); - context.writeMemory(this.destOffset, dest); + machineState.writeMemory(this.destOffset, dest); } } @@ -92,11 +92,11 @@ export class Shr implements Instruction { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); const dest = new Fr(a.toBigInt() >> b.toBigInt()); - context.writeMemory(this.destOffset, dest); + machineState.writeMemory(this.destOffset, dest); } } \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts index e6b4c82faada..46e737c56c76 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts @@ -1,5 +1,5 @@ import { Fr } from "@aztec/foundation/fields"; -import { AvmContext } from "../avm_context.js"; +import { AvmMachineState } from "../avm_machine_state.js"; import { AvmStateManager } from "../avm_state_manager.js"; import { Instruction } from "./instruction.js"; @@ -10,12 +10,12 @@ export class Eq implements Instruction { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); const dest = new Fr(a.toBigInt() == b.toBigInt()); - context.writeMemory(this.destOffset, dest); + machineState.writeMemory(this.destOffset, dest); } } /** -*/ @@ -25,12 +25,12 @@ export class Lt implements Instruction { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); const dest = new Fr(a.toBigInt() < b.toBigInt()); - context.writeMemory(this.destOffset, dest); + machineState.writeMemory(this.destOffset, dest); } } @@ -41,11 +41,11 @@ export class Lte implements Instruction { constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a: Fr = context.readMemory(this.aOffset); - const b: Fr = context.readMemory(this.bOffset); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); const dest = new Fr(a.toBigInt() < b.toBigInt()); - context.writeMemory(this.destOffset, dest); + machineState.writeMemory(this.destOffset, dest); } } 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 f1257b1ef416..31336e397be2 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts @@ -1,4 +1,4 @@ -import { AvmContext } from '../avm_context.js'; +import { AvmMachineState } from '../avm_machine_state.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Instruction } from './instruction.js'; @@ -9,8 +9,8 @@ export class Return implements Instruction { constructor(private returnOffset: number, private copySize: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const returnData = context.readMemoryChunk(this.returnOffset, this.returnOffset + this.copySize); - context.setReturnData(returnData); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const returnData = machineState.readMemoryChunk(this.returnOffset, this.returnOffset + this.copySize); + machineState.setReturnData(returnData); } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts b/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts index 611b1810566b..0411251b7053 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts @@ -1,9 +1,9 @@ -import { AvmContext } from '../avm_context.js'; +import { AvmMachineState } from '../avm_machine_state.js'; import { AvmStateManager } from '../avm_state_manager.js'; /** * Opcode base class */ export abstract class Instruction { - abstract execute(context: AvmContext, stateManager: AvmStateManager): void; + abstract execute(machineState: AvmMachineState, stateManager: AvmStateManager): void; } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts index efa11b477374..38fa560a3e6a 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts @@ -1,6 +1,6 @@ import { Fr } from '@aztec/foundation/fields'; -import { AvmContext } from '../avm_context.js'; +import { AvmMachineState } from '../avm_machine_state.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Instruction } from './instruction.js'; @@ -11,9 +11,9 @@ export class Set implements Instruction { constructor(private constt: bigint, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const dest = new Fr(this.constt); - context.writeMemory(this.destOffset, dest); + machineState.writeMemory(this.destOffset, dest); } } @@ -25,10 +25,10 @@ export class Cast implements Instruction { constructor(private aOffset: number, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a = context.readMemory(this.aOffset); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a = machineState.readMemory(this.aOffset); - context.writeMemory(this.destOffset, a); + machineState.writeMemory(this.destOffset, a); } } @@ -39,10 +39,10 @@ export class Mov implements Instruction { constructor(private aOffset: number, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const a = context.readMemory(this.aOffset); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a = machineState.readMemory(this.aOffset); - context.writeMemory(this.destOffset, a); + machineState.writeMemory(this.destOffset, a); } } @@ -53,8 +53,8 @@ export class CallDataCopy implements Instruction { constructor(private cdOffset: number, private copySize: number, private destOffset: number) {} - execute(context: AvmContext, _stateManager: AvmStateManager): void { - const calldata = context.calldata.slice(this.cdOffset, this.cdOffset + this.copySize); - context.writeMemoryChunk(this.destOffset, calldata); + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const calldata = machineState.calldata.slice(this.cdOffset, this.cdOffset + this.copySize); + machineState.writeMemoryChunk(this.destOffset, calldata); } } From 5a9e61ff6df6cb5571ac6635bbc510a229b91ce6 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 12 Jan 2024 20:52:49 +0000 Subject: [PATCH 17/20] fix: update interpreter test --- .../acir-simulator/src/avm/interpreter/interpreter.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts index 0c744c2baa31..1a568103b0be 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts @@ -26,11 +26,11 @@ describe('interpreter', () => { const context = new AvmMachineState(calldata); const interpreter = new AvmInterpreter(context, stateManager, instructions); - const success = interpreter.run(); + const avmReturnData = interpreter.run(); - expect(success).toBe(true); + expect(avmReturnData.reverted).toBe(false); - const returnData = interpreter.returnData(); + const returnData = avmReturnData.output; expect(returnData.length).toBe(1); expect(returnData).toEqual([new Fr(3)]); }); From 2c5cce3723b8d53522bd33f22a3e7466fff906c4 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 12 Jan 2024 20:53:18 +0000 Subject: [PATCH 18/20] chore: formatting fix --- .../acir-simulator/src/avm/avm_context.ts | 2 +- .../src/avm/avm_message_call_result.ts | 55 +++++++++---------- .../src/avm/avm_state_manager.ts | 1 + yarn-project/acir-simulator/src/avm/index.ts | 2 +- .../src/avm/interpreter/interpreter.test.ts | 2 +- .../src/avm/interpreter/interpreter.ts | 2 +- .../src/avm/opcodes/arithmetic.ts | 1 - .../acir-simulator/src/avm/opcodes/bitwise.ts | 4 +- .../src/avm/opcodes/comparators.ts | 9 +-- .../acir-simulator/src/avm/opcodes/index.ts | 4 +- 10 files changed, 40 insertions(+), 42 deletions(-) diff --git a/yarn-project/acir-simulator/src/avm/avm_context.ts b/yarn-project/acir-simulator/src/avm/avm_context.ts index faead45da7db..9d52f869acfa 100644 --- a/yarn-project/acir-simulator/src/avm/avm_context.ts +++ b/yarn-project/acir-simulator/src/avm/avm_context.ts @@ -1,11 +1,11 @@ import { Fr } from '@aztec/foundation/fields'; import { AvmMachineState } from './avm_machine_state.js'; +import { AvmMessageCallResult } from './avm_message_call_result.js'; import { AvmStateManager } from './avm_state_manager.js'; import { AvmInterpreter } from './interpreter/index.js'; import { interpretBytecode } from './opcodes/from_bytecode.js'; import { Instruction } from './opcodes/index.js'; -import { AvmMessageCallResult } from './avm_message_call_result.js'; /** * Avm Executor manages the execution of the AVM 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 0790134e31f6..ec258dff1126 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,35 +1,34 @@ -import { Fr } from "@aztec/foundation/fields"; +import { Fr } from '@aztec/foundation/fields'; /** * AVM message call result. */ -export class AvmMessageCallResult { - /** - */ - public readonly reverted: boolean; - /** .- */ - public readonly output: Fr[]; +export class AvmMessageCallResult { + /** - */ + public readonly reverted: boolean; + /** .- */ + public readonly output: Fr[]; - constructor(reverted: boolean, output: Fr[]) { - this.reverted = reverted; - this.output = output; - } + constructor(reverted: boolean, output: Fr[]) { + this.reverted = reverted; + this.output = output; + } - /** - * 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 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 ) - * @returns instance of AvmMessageCallResult - */ - public static revert(output: Fr[]): AvmMessageCallResult { - return new AvmMessageCallResult(true, output); - } - -} \ No newline at end of file + /** + * Terminate a call as a revert + * @param output - Return data ( revert message ) + * @returns instance of AvmMessageCallResult + */ + public static revert(output: Fr[]): AvmMessageCallResult { + return new AvmMessageCallResult(true, output); + } +} diff --git a/yarn-project/acir-simulator/src/avm/avm_state_manager.ts b/yarn-project/acir-simulator/src/avm/avm_state_manager.ts index 9ce2f1453b68..250a7d8f1225 100644 --- a/yarn-project/acir-simulator/src/avm/avm_state_manager.ts +++ b/yarn-project/acir-simulator/src/avm/avm_state_manager.ts @@ -1,4 +1,5 @@ import { BlockHeader } from '@aztec/circuits.js'; + import { AvmJournal, HostStorage } from './journal/index.js'; /** diff --git a/yarn-project/acir-simulator/src/avm/index.ts b/yarn-project/acir-simulator/src/avm/index.ts index 6c51cb5d18ba..6a102a6cd57e 100644 --- a/yarn-project/acir-simulator/src/avm/index.ts +++ b/yarn-project/acir-simulator/src/avm/index.ts @@ -1,3 +1,3 @@ export * from './avm_machine_state.js'; export * from './avm_context.js'; -export * from "./avm_state_manager.js" \ No newline at end of file +export * from './avm_state_manager.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 index 1a568103b0be..3b86414ab639 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts @@ -6,8 +6,8 @@ import { AvmMachineState } from '../avm_machine_state.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Add } from '../opcodes/arithmetic.js'; import { Return } from '../opcodes/control_flow.js'; -import { CallDataCopy } from '../opcodes/memory.js'; import { Instruction } from '../opcodes/instruction.js'; +import { CallDataCopy } from '../opcodes/memory.js'; import { AvmInterpreter } from './interpreter.js'; describe('interpreter', () => { diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts index 87ee1cb5d7d8..47fb9c3771d9 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -2,9 +2,9 @@ import { Fr } from '@aztec/foundation/fields'; import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmMessageCallResult } from '../avm_message_call_result.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Instruction } from '../opcodes/index.js'; -import { AvmMessageCallResult } from '../avm_message_call_result.js'; /** * Avm Interpreter diff --git a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts index 4889ea8e4f95..ab6465debf4c 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts @@ -68,4 +68,3 @@ export class Div implements Instruction { machineState.writeMemory(this.destOffset, dest); } } - diff --git a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts index 3ba17d105fec..62376269d8eb 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts @@ -4,7 +4,6 @@ import { AvmMachineState } from '../avm_machine_state.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Instruction } from './instruction.js'; - /** - */ export class And implements Instruction { static type: string = 'AND'; @@ -68,7 +67,6 @@ export class Not implements Instruction { } } - /** -*/ export class Shl implements Instruction { static type: string = 'SHL'; @@ -99,4 +97,4 @@ export class Shr implements Instruction { const dest = new Fr(a.toBigInt() >> b.toBigInt()); machineState.writeMemory(this.destOffset, dest); } -} \ No newline at end of file +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts index 46e737c56c76..ddd3c9d1de99 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts @@ -1,7 +1,8 @@ -import { Fr } from "@aztec/foundation/fields"; -import { AvmMachineState } from "../avm_machine_state.js"; -import { AvmStateManager } from "../avm_state_manager.js"; -import { Instruction } from "./instruction.js"; +import { Fr } from '@aztec/foundation/fields'; + +import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmStateManager } from '../avm_state_manager.js'; +import { Instruction } from './instruction.js'; /** -*/ export class Eq implements Instruction { diff --git a/yarn-project/acir-simulator/src/avm/opcodes/index.ts b/yarn-project/acir-simulator/src/avm/opcodes/index.ts index 25e0f4764da3..f1f479bab466 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/index.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/index.ts @@ -2,5 +2,5 @@ export * from './arithmetic.js'; export * from './control_flow.js'; export * from './call.js'; export * from './instruction.js'; -export * from "./comparators.js"; -export * from "./memory.js"; \ No newline at end of file +export * from './comparators.js'; +export * from './memory.js'; From c7c751b463e38d61e0bf61aa40f4e7f0a6404b90 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 11 Jan 2024 12:57:05 +0000 Subject: [PATCH 19/20] test: journal tests feat: journal revert + merge feat: change branching semantics fix: fmt test: journal tests fix: update state manger to use root journal construction fix: formatting fix: typo --- .../src/avm/avm_state_manager.ts | 5 +- .../acir-simulator/src/avm/journal/errors.ts | 8 + .../src/avm/journal/host_storage.ts | 13 +- .../src/avm/journal/journal.test.ts | 157 ++++++++++++++++- .../acir-simulator/src/avm/journal/journal.ts | 160 +++++++++++++----- 5 files changed, 293 insertions(+), 50 deletions(-) create mode 100644 yarn-project/acir-simulator/src/avm/journal/errors.ts diff --git a/yarn-project/acir-simulator/src/avm/avm_state_manager.ts b/yarn-project/acir-simulator/src/avm/avm_state_manager.ts index 250a7d8f1225..b5396accc7a9 100644 --- a/yarn-project/acir-simulator/src/avm/avm_state_manager.ts +++ b/yarn-project/acir-simulator/src/avm/avm_state_manager.ts @@ -29,7 +29,7 @@ export class AvmStateManager { * @returns Avm State Manager */ public static rootStateManager(blockHeader: BlockHeader, hostStorage: HostStorage): AvmStateManager { - const journal = new AvmJournal(hostStorage); + const journal = AvmJournal.rootJournal(hostStorage); return new AvmStateManager(blockHeader, journal); } @@ -39,6 +39,7 @@ export class AvmStateManager { * @returns */ public static forkStateManager(parent: AvmStateManager): AvmStateManager { - return new AvmStateManager(parent.blockHeader, parent.journal); + const journal = AvmJournal.branchParent(parent.journal); + return new AvmStateManager(parent.blockHeader, journal); } } diff --git a/yarn-project/acir-simulator/src/avm/journal/errors.ts b/yarn-project/acir-simulator/src/avm/journal/errors.ts new file mode 100644 index 000000000000..17696b1de687 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/journal/errors.ts @@ -0,0 +1,8 @@ +/** + * 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/host_storage.ts b/yarn-project/acir-simulator/src/avm/journal/host_storage.ts index ccd318912053..fcd9d27f7227 100644 --- a/yarn-project/acir-simulator/src/avm/journal/host_storage.ts +++ b/yarn-project/acir-simulator/src/avm/journal/host_storage.ts @@ -1,17 +1,20 @@ import { CommitmentsDB, PublicContractsDB, PublicStateDB } from '../../index.js'; -/** - */ +/** + * Host storage + * + * A wrapper around the node dbs + */ export class HostStorage { /** - */ - public readonly stateDb: PublicStateDB; + public readonly publicStateDb: PublicStateDB; /** - */ public readonly contractsDb: PublicContractsDB; - /** - */ public readonly commitmentsDb: CommitmentsDB; - constructor(stateDb: PublicStateDB, contractsDb: PublicContractsDB, commitmentsDb: CommitmentsDB) { - this.stateDb = stateDb; + constructor(publicStateDb: PublicStateDB, contractsDb: PublicContractsDB, commitmentsDb: CommitmentsDB) { + this.publicStateDb = publicStateDb; this.contractsDb = contractsDb; this.commitmentsDb = commitmentsDb; } 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 c356db723862..a11a3b38743c 100644 --- a/yarn-project/acir-simulator/src/avm/journal/journal.test.ts +++ b/yarn-project/acir-simulator/src/avm/journal/journal.test.ts @@ -1,7 +1,158 @@ +import { Fr } from '@aztec/foundation/fields'; + +import { MockProxy, mock } from 'jest-mock-extended'; + +import { CommitmentsDB, PublicContractsDB, PublicStateDB } from '../../index.js'; +import { HostStorage } from './host_storage.js'; +import { AvmJournal, JournalData } from './journal.js'; + describe('journal', () => { - it('Should write to storage', () => {}); + let publicDb: MockProxy; + let journal: AvmJournal; + + beforeEach(() => { + publicDb = mock(); + const commitmentsDb = mock(); + const contractsDb = mock(); + + const hostStorage = new HostStorage(publicDb, contractsDb, commitmentsDb); + journal = new AvmJournal(hostStorage); + }); + + describe('Public Storage', () => { + it('Should cache write to storage', () => { + // When writing to storage we should write to the storage writes map + const contractAddress = new Fr(1); + const key = new Fr(2); + const value = new Fr(3); + + journal.writeStorage(contractAddress, key, value); + + const journalUpdates: JournalData = journal.flush(); + expect(journalUpdates.storageWrites.get(contractAddress)?.get(key)).toEqual(value); + }); + + it('When reading from storage, should check the parent first', async () => { + // Store a different value in storage vs the cache, and make sure the cache is returned + const contractAddress = new Fr(1); + const key = new Fr(2); + const storedValue = new Fr(420); + const parentValue = new Fr(69); + const cachedValue = new Fr(1337); + + publicDb.storageRead.mockResolvedValue(Promise.resolve(storedValue)); + + const childJournal = new AvmJournal(journal.hostStorage, journal); + + // Get the cache miss + const cacheMissResult = await childJournal.readStorage(contractAddress, key); + expect(cacheMissResult).toEqual(storedValue); + + // Write to storage + journal.writeStorage(contractAddress, key, parentValue); + const parentResult = await childJournal.readStorage(contractAddress, key); + expect(parentResult).toEqual(parentValue); + + // Get the parent value + childJournal.writeStorage(contractAddress, key, cachedValue); + + // Get the storage value + const cachedResult = await childJournal.readStorage(contractAddress, key); + expect(cachedResult).toEqual(cachedValue); + }); + + it('When reading from storage, should check the cache first', async () => { + // Store a different value in storage vs the cache, and make sure the cache is returned + const contractAddress = new Fr(1); + const key = new Fr(2); + const storedValue = new Fr(420); + const cachedValue = new Fr(69); + + publicDb.storageRead.mockResolvedValue(Promise.resolve(storedValue)); + + // Get the cache first + const cacheMissResult = await journal.readStorage(contractAddress, key); + expect(cacheMissResult).toEqual(storedValue); + + // Write to storage + journal.writeStorage(contractAddress, key, cachedValue); + + // Get the storage value + const cachedResult = await journal.readStorage(contractAddress, key); + expect(cachedResult).toEqual(cachedValue); + }); + }); + + describe('UTXOs', () => { + it('Should maintain commitments', () => { + const utxo = new Fr(1); + journal.writeCommitment(utxo); + + const journalUpdates = journal.flush(); + expect(journalUpdates.newCommitments).toEqual([utxo]); + }); + + it('Should maintain l1 messages', () => { + const utxo = new Fr(1); + journal.writeL1Message(utxo); + + const journalUpdates = journal.flush(); + expect(journalUpdates.newL1Messages).toEqual([utxo]); + }); + + it('Should maintain nullifiers', () => { + const utxo = new Fr(1); + journal.writeNullifier(utxo); + + const journalUpdates = journal.flush(); + expect(journalUpdates.newNullifiers).toEqual([utxo]); + }); + }); + + it('Should merge two journals together', async () => { + // Fundamentally checking that insert ordering of public storage is preserved upon journal merge + // time | journal | op | value + // t0 -> journal0 -> write | 1 + // t1 -> journal1 -> write | 2 + // merge journals + // t2 -> journal0 -> read | 2 + + const contractAddress = new Fr(1); + const key = new Fr(2); + const value = new Fr(1); + const valueT1 = new Fr(2); + const commitment = new Fr(10); + const commitmentT1 = new Fr(20); + + journal.writeStorage(contractAddress, key, value); + journal.writeCommitment(commitment); + journal.writeL1Message(commitment); + journal.writeNullifier(commitment); + + const journal1 = new AvmJournal(journal.hostStorage, journal); + journal.writeStorage(contractAddress, key, valueT1); + journal.writeCommitment(commitmentT1); + journal.writeL1Message(commitmentT1); + journal.writeNullifier(commitmentT1); + + journal1.mergeWithParent(); + + // Check that the storage is merged by reading from the journal + const result = await journal.readStorage(contractAddress, key); + expect(result).toEqual(valueT1); + + // Check that the UTXOs are merged + const journalUpdates: JournalData = journal.flush(); + expect(journalUpdates.newCommitments).toEqual([commitment, commitmentT1]); + expect(journalUpdates.newL1Messages).toEqual([commitment, commitmentT1]); + expect(journalUpdates.newNullifiers).toEqual([commitment, commitmentT1]); + }); - it('Should read from storage', () => {}); + it('Cannot merge a root journal, but can merge a child journal', () => { + const rootJournal = AvmJournal.rootJournal(journal.hostStorage); + const childJournal = AvmJournal.branchParent(rootJournal); - it('Should merge two journals together', () => {}); + expect(() => rootJournal.mergeWithParent()).toThrow(); + expect(() => childJournal.mergeWithParent()); + }); }); diff --git a/yarn-project/acir-simulator/src/avm/journal/journal.ts b/yarn-project/acir-simulator/src/avm/journal/journal.ts index 67563fe63704..5c0fe85e0e2d 100644 --- a/yarn-project/acir-simulator/src/avm/journal/journal.ts +++ b/yarn-project/acir-simulator/src/avm/journal/journal.ts @@ -1,101 +1,181 @@ import { Fr } from '@aztec/foundation/fields'; +import { RootJournalCannotBeMerged } from './errors.js'; import { HostStorage } from './host_storage.js'; -// TODO: all of the data that comes out of the avm ready for write should be in this format -/** - */ +/** + * Data held within the journal + */ export type JournalData = { /** - */ newCommitments: Fr[]; /** - */ - newL1Message: Fr[]; + newL1Messages: Fr[]; /** - */ - storageWrites: { [key: string]: { [key: string]: Fr } }; + newNullifiers: Fr[]; + /** contract address -\> key -\> value */ + storageWrites: Map>; }; -// This persists for an entire block -// Each transaction should have its own journal that gets appended to this one upon success -/** - */ +/** + * 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 call succeeds's we can merge a child into its parent + */ export class AvmJournal { - // TODO: should we make private? - /** - */ + /** Reference to node storage */ public readonly hostStorage: HostStorage; - // We need to keep track of the following - // - State reads - // - State updates - // - New Commitments - // - Commitment reads - private newCommitments: Fr[] = []; + private newNullifiers: Fr[] = []; private newL1Message: Fr[] = []; + private parentJournal: AvmJournal | undefined; - // TODO: type this structure -> contract address -> key -> value - private storageWrites: { [key: string]: { [key: string]: Fr } } = {}; + // contract address -> key -> value + private storageWrites: Map> = new Map(); - constructor(hostStorage: HostStorage) { + constructor(hostStorage: HostStorage, parentJournal?: AvmJournal) { this.hostStorage = hostStorage; + this.parentJournal = parentJournal; + } + + /** + * Create a new root journal, without a parent + * @param hostStorage - + */ + public static rootJournal(hostStorage: HostStorage) { + return new AvmJournal(hostStorage); } - // TODO: work on the typing /** - * - + * Create a new journal from a parent + * @param parentJournal - + */ + public static branchParent(parentJournal: AvmJournal) { + return new AvmJournal(parentJournal.hostStorage, parentJournal); + } + + /** + * Write storage into journal + * * @param contractAddress - * @param key - * @param value - */ public writeStorage(contractAddress: Fr, key: Fr, value: Fr) { - // TODO: do we want this map to be ordered -> is there performance upside to this? - this.storageWrites[contractAddress.toString()][key.toString()] = value; + let contractMap = this.storageWrites.get(contractAddress); + if (!contractMap) { + contractMap = new Map(); + this.storageWrites.set(contractAddress, contractMap); + } + contractMap.set(key, value); } /** - * - + * Read storage from journal + * Read from host storage on cache miss + * * @param contractAddress - * @param key - + * @returns current value */ - public readStorage(contractAddress: Fr, key: Fr) { - const cachedValue = this.storageWrites[contractAddress.toString()][key.toString()]; + public readStorage(contractAddress: Fr, key: Fr): Promise { + const cachedValue = this.storageWrites.get(contractAddress)?.get(key); if (cachedValue) { - return cachedValue; + return Promise.resolve(cachedValue); + } + if (this.parentJournal) { + return this.parentJournal?.readStorage(contractAddress, key); } - return this.hostStorage.stateDb.storageRead(contractAddress, key); + return this.hostStorage.publicStateDb.storageRead(contractAddress, key); } - /** - * - + /** - * @param commitment - */ public writeCommitment(commitment: Fr) { this.newCommitments.push(commitment); } - /** - * - + /** - * @param message - */ public writeL1Message(message: Fr) { this.newL1Message.push(message); } - // TODO: This function will merge two journals together -> the new head of the chain - /** - * - - * @param journal - + /** - + * @param nullifier - */ - public mergeJournal(journal: AvmJournal) { - // TODO: This function will - void journal; + public writeNullifier(nullifier: Fr) { + this.newNullifiers.push(nullifier); } /** - * - + * Merge Journal into parent + * - Utxo objects are concatenated + * - Public state is merged, with the value in the incoming journal taking precedent + */ + public mergeWithParent() { + if (!this.parentJournal) { + throw new RootJournalCannotBeMerged(); + } + + const incomingFlush = this.flush(); + + // Merge UTXOs + this.parentJournal.newCommitments = this.parentJournal.newCommitments.concat(incomingFlush.newCommitments); + this.parentJournal.newL1Message = this.parentJournal.newL1Message.concat(incomingFlush.newL1Messages); + this.parentJournal.newNullifiers = this.parentJournal.newNullifiers.concat(incomingFlush.newNullifiers); + + // Merge Public State + mergeContractMaps(this.parentJournal.storageWrites, incomingFlush.storageWrites); + } + + /** Access the current state of the journal + * + * @returns a JournalData object that can be used to write to the storage */ public flush(): JournalData { return { newCommitments: this.newCommitments, - newL1Message: this.newL1Message, + newL1Messages: this.newL1Message, + newNullifiers: this.newNullifiers, storageWrites: this.storageWrites, }; } } + +/** + * Merges two contract maps together + * Where childMap keys will take precedent over the hostMap + * The assumption being that the child map is created at a later time + * And thus contains more up to date information + * + * @param hostMap - The map to be merged into + * @param childMap - The map to be merged from + */ +function mergeContractMaps(hostMap: Map>, childMap: Map>) { + for (const [key, value] of childMap) { + const map1Value = hostMap.get(key); + if (!map1Value) { + hostMap.set(key, value); + } else { + mergeStorageMaps(map1Value, value); + } + } +} + +/** + * + * @param hostMap - The map to be merge into + * @param childMap - The map to be merged from + */ +function mergeStorageMaps(hostMap: Map, childMap: Map) { + for (const [key, value] of childMap) { + hostMap.set(key, value); + } +} From 17b53cac55821804775c792e8abb357872d895d4 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 12 Jan 2024 21:18:08 +0000 Subject: [PATCH 20/20] =?UTF-8?q?=F0=9F=A7=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acir-simulator/src/avm/journal/journal.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/yarn-project/acir-simulator/src/avm/journal/journal.ts b/yarn-project/acir-simulator/src/avm/journal/journal.ts index 5c0fe85e0e2d..cb69b3c33ef6 100644 --- a/yarn-project/acir-simulator/src/avm/journal/journal.ts +++ b/yarn-project/acir-simulator/src/avm/journal/journal.ts @@ -29,14 +29,24 @@ export class AvmJournal { /** Reference to node storage */ public readonly hostStorage: HostStorage; + // Reading state - must be tracked for vm execution + // contract address -> key -> value + // TODO(https://github.com/AztecProtocol/aztec-packages/issues/3999) + private storageReads: Map> = new Map(); + + // New written state private newCommitments: Fr[] = []; private newNullifiers: Fr[] = []; private newL1Message: Fr[] = []; - private parentJournal: AvmJournal | undefined; + + // New Substrate + private newLogs: Fr[][] = []; // contract address -> key -> value private storageWrites: Map> = new Map(); + private parentJournal: AvmJournal | undefined; + constructor(hostStorage: HostStorage, parentJournal?: AvmJournal) { this.hostStorage = hostStorage; this.parentJournal = parentJournal;