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 1d5d0d08671f..58b0b3345b90 100644 --- a/yarn-project/acir-simulator/src/avm/journal/journal.test.ts +++ b/yarn-project/acir-simulator/src/avm/journal/journal.test.ts @@ -86,14 +86,14 @@ describe('journal', () => { describe('UTXOs', () => { it('Should maintain commitments', () => { const utxo = new Fr(1); - journal.writeCommitment(utxo); + journal.writeNoteHash(utxo); const journalUpdates = journal.flush(); - expect(journalUpdates.newCommitments).toEqual([utxo]); + expect(journalUpdates.newNoteHashes).toEqual([utxo]); }); it('Should maintain l1 messages', () => { - const utxo = new Fr(1); + const utxo = [new Fr(1)]; journal.writeL1Message(utxo); const journalUpdates = journal.flush(); @@ -123,16 +123,20 @@ describe('journal', () => { const valueT1 = new Fr(2); const commitment = new Fr(10); const commitmentT1 = new Fr(20); + const logs = [new Fr(1), new Fr(2)]; + const logsT1 = [new Fr(3), new Fr(4)]; journal.writeStorage(contractAddress, key, value); - journal.writeCommitment(commitment); - journal.writeL1Message(commitment); + journal.writeNoteHash(commitment); + journal.writeLog(logs); + journal.writeL1Message(logs); journal.writeNullifier(commitment); const journal1 = new AvmJournal(journal.hostStorage, journal); journal.writeStorage(contractAddress, key, valueT1); - journal.writeCommitment(commitmentT1); - journal.writeL1Message(commitmentT1); + journal.writeNoteHash(commitmentT1); + journal.writeLog(logsT1); + journal.writeL1Message(logsT1); journal.writeNullifier(commitmentT1); journal1.mergeWithParent(); @@ -143,8 +147,9 @@ describe('journal', () => { // 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.newNoteHashes).toEqual([commitment, commitmentT1]); + expect(journalUpdates.newLogs).toEqual([logs, logsT1]); + expect(journalUpdates.newL1Messages).toEqual([logs, logsT1]); expect(journalUpdates.newNullifiers).toEqual([commitment, commitmentT1]); }); diff --git a/yarn-project/acir-simulator/src/avm/journal/journal.ts b/yarn-project/acir-simulator/src/avm/journal/journal.ts index 4d5b92fec6b2..da503f2b88f1 100644 --- a/yarn-project/acir-simulator/src/avm/journal/journal.ts +++ b/yarn-project/acir-simulator/src/avm/journal/journal.ts @@ -7,11 +7,10 @@ import { HostStorage } from './host_storage.js'; * Data held within the journal */ export type JournalData = { - newCommitments: Fr[]; - - newL1Messages: Fr[]; - + newNoteHashes: Fr[]; newNullifiers: Fr[]; + newL1Messages: Fr[][]; + newLogs: Fr[][]; /** contract address -\> key -\> value */ storageWrites: Map>; }; @@ -34,11 +33,11 @@ export class AvmJournal { private storageReads: Map> = new Map(); // New written state - private newCommitments: Fr[] = []; + private newNoteHashes: Fr[] = []; private newNullifiers: Fr[] = []; - private newL1Message: Fr[] = []; - // New Substrate + // New Substate + private newL1Message: Fr[][] = []; private newLogs: Fr[][] = []; // contract address -> key -> value @@ -102,27 +101,22 @@ export class AvmJournal { return this.hostStorage.publicStateDb.storageRead(contractAddress, key); } - /** - - * @param commitment - - */ - public writeCommitment(commitment: Fr) { - this.newCommitments.push(commitment); + public writeNoteHash(noteHash: Fr) { + this.newNoteHashes.push(noteHash); } - /** - - * @param message - - */ - public writeL1Message(message: Fr) { + public writeL1Message(message: Fr[]) { this.newL1Message.push(message); } - /** - - * @param nullifier - - */ public writeNullifier(nullifier: Fr) { this.newNullifiers.push(nullifier); } + public writeLog(log: Fr[]) { + this.newLogs.push(log); + } + /** * Merge Journal into parent * - Utxo objects are concatenated @@ -136,7 +130,7 @@ export class AvmJournal { const incomingFlush = this.flush(); // Merge UTXOs - this.parentJournal.newCommitments = this.parentJournal.newCommitments.concat(incomingFlush.newCommitments); + this.parentJournal.newNoteHashes = this.parentJournal.newNoteHashes.concat(incomingFlush.newNoteHashes); this.parentJournal.newL1Message = this.parentJournal.newL1Message.concat(incomingFlush.newL1Messages); this.parentJournal.newNullifiers = this.parentJournal.newNullifiers.concat(incomingFlush.newNullifiers); @@ -150,9 +144,10 @@ export class AvmJournal { */ public flush(): JournalData { return { - newCommitments: this.newCommitments, - newL1Messages: this.newL1Message, + newNoteHashes: this.newNoteHashes, newNullifiers: this.newNullifiers, + newL1Messages: this.newL1Message, + newLogs: this.newLogs, storageWrites: this.storageWrites, }; } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.test.ts new file mode 100644 index 000000000000..c12bf1e75709 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.test.ts @@ -0,0 +1,84 @@ +import { Fr } from '@aztec/foundation/fields'; + +import { mock } from 'jest-mock-extended'; + +import { AvmMachineState } from '../avm_machine_state.js'; +import { initExecutionEnvironment } from '../fixtures/index.js'; +import { HostStorage } from '../journal/host_storage.js'; +import { AvmJournal } from '../journal/journal.js'; +import { EmitNoteHash, EmitNullifier, EmitUnencryptedLog, SendL2ToL1Message } from './accrued_substate.js'; +import { StaticCallStorageAlterError } from './storage.js'; + +describe('Accrued Substate', () => { + let journal: AvmJournal; + let machineState: AvmMachineState; + + beforeEach(async () => { + const hostStorage = mock(); + journal = new AvmJournal(hostStorage); + machineState = new AvmMachineState(initExecutionEnvironment()); + }); + + it('Should append a new note hash correctly', async () => { + const value = new Fr(69n); + machineState.writeMemory(0, value); + + await new EmitNoteHash(0).execute(machineState, journal); + + const journalState = journal.flush(); + expect(journalState.newNoteHashes).toEqual([value]); + }); + + it('Should append a new nullifier correctly', async () => { + const value = new Fr(69n); + machineState.writeMemory(0, value); + + await new EmitNullifier(0).execute(machineState, journal); + + const journalState = journal.flush(); + expect(journalState.newNullifiers).toEqual([value]); + }); + + it('Should append unencrypted logs correctly', async () => { + const startOffset = 0; + const length = 2; + + const values = [new Fr(69n), new Fr(420n)]; + machineState.writeMemoryChunk(0, values); + + await new EmitUnencryptedLog(startOffset, length).execute(machineState, journal); + + const journalState = journal.flush(); + expect(journalState.newLogs).toEqual([values]); + }); + + it('Should append l1 to l2 messages correctly', async () => { + const startOffset = 0; + const length = 2; + + const values = [new Fr(69n), new Fr(420n)]; + machineState.writeMemoryChunk(0, values); + + await new SendL2ToL1Message(startOffset, length).execute(machineState, journal); + + const journalState = journal.flush(); + expect(journalState.newLogs).toEqual([values]); + }); + + it('All substate instructions should fail within a static call', async () => { + const executionEnvironment = initExecutionEnvironment({ isStaticCall: true }); + machineState = new AvmMachineState(executionEnvironment); + + const instructions = [ + new EmitNoteHash(0), + new EmitNullifier(0), + new EmitUnencryptedLog(0, 1), + new SendL2ToL1Message(0, 1), + ]; + + for (const instruction of instructions) { + const inst = () => instruction.execute(machineState, journal); + await expect(inst()).rejects.toThrowError(StaticCallStorageAlterError); + } + }); +}); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.ts b/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.ts index e69de29bb2d1..71d2e41b5990 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.ts @@ -0,0 +1,88 @@ +import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmJournal } from '../journal/journal.js'; +import { Instruction } from './instruction.js'; +import { StaticCallStorageAlterError } from './storage.js'; + +export class EmitNoteHash extends Instruction { + static type: string = 'EMITNOTEHASH'; + static numberOfOperands = 1; + + constructor(private noteHashOffset: number) { + super(); + } + + async execute(machineState: AvmMachineState, journal: AvmJournal): Promise { + if (machineState.executionEnvironment.isStaticCall) { + throw new StaticCallStorageAlterError(); + } + + const noteHash = machineState.readMemory(this.noteHashOffset); + + journal.writeNoteHash(noteHash); + + this.incrementPc(machineState); + } +} + +export class EmitNullifier extends Instruction { + static type: string = 'EMITNULLIFIER'; + static numberOfOperands = 1; + + constructor(private nullifierOffset: number) { + super(); + } + + async execute(machineState: AvmMachineState, journal: AvmJournal): Promise { + if (machineState.executionEnvironment.isStaticCall) { + throw new StaticCallStorageAlterError(); + } + + const nullifier = machineState.readMemory(this.nullifierOffset); + + journal.writeNullifier(nullifier); + + this.incrementPc(machineState); + } +} + +export class EmitUnencryptedLog extends Instruction { + static type: string = 'EMITUNENCRYPTEDLOG'; + static numberOfOperands = 2; + + constructor(private logOffset: number, private logSize: number) { + super(); + } + + async execute(machineState: AvmMachineState, journal: AvmJournal): Promise { + if (machineState.executionEnvironment.isStaticCall) { + throw new StaticCallStorageAlterError(); + } + + const log = machineState.readMemoryChunk(this.logOffset, this.logSize); + + journal.writeLog(log); + + this.incrementPc(machineState); + } +} + +export class SendL2ToL1Message extends Instruction { + static type: string = 'EMITUNENCRYPTEDLOG'; + static numberOfOperands = 2; + + constructor(private msgOffset: number, private msgSize: number) { + super(); + } + + async execute(machineState: AvmMachineState, journal: AvmJournal): Promise { + if (machineState.executionEnvironment.isStaticCall) { + throw new StaticCallStorageAlterError(); + } + + const msg = machineState.readMemoryChunk(this.msgOffset, this.msgSize); + + journal.writeLog(msg); + + this.incrementPc(machineState); + } +}