Skip to content

Commit

Permalink
feat: accrued substate instructions (AztecProtocol#4197)
Browse files Browse the repository at this point in the history
  • Loading branch information
Maddiaa0 authored Jan 29, 2024
1 parent 12e520c commit d97ed01
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 44 deletions.
10 changes: 10 additions & 0 deletions yarn-project/acir-simulator/src/avm/avm_memory_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ export abstract class MemoryValue {

// Use sparingly.
public abstract toBigInt(): bigint;

// To field
public toFr(): Fr {
return new Fr(this.toBigInt());
}
}

export abstract class IntegralValue extends MemoryValue {
Expand Down Expand Up @@ -237,6 +242,11 @@ export class TaggedMemory {
return this._mem.slice(offset, offset + size);
}

public getSliceAs<T>(offset: number, size: number): T[] {
assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
return this._mem.slice(offset, offset + size) as T[];
}

public getSliceTags(offset: number, size: number): TypeTag[] {
assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
return this._mem.slice(offset, offset + size).map(TaggedMemory.getTag);
Expand Down
23 changes: 14 additions & 9 deletions yarn-project/acir-simulator/src/avm/journal/journal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -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]);
});

Expand Down
54 changes: 24 additions & 30 deletions yarn-project/acir-simulator/src/avm/journal/journal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<bigint, Map<bigint, Fr>>;
};
Expand All @@ -34,11 +33,11 @@ export class AvmJournal {
private storageReads: Map<bigint, Map<bigint, Fr>> = new Map();

// New written state
private newCommitments: Fr[] = [];
private newNoteHashes: Fr[] = [];
private newNullifiers: Fr[] = [];
private newL1Message: Fr[] = [];

// New Substrate
// New Substate
private newL1Messages: Fr[][] = [];
private newLogs: Fr[][] = [];

// contract address -> key -> value
Expand Down Expand Up @@ -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) {
this.newL1Message.push(message);
public writeL1Message(message: Fr[]) {
this.newL1Messages.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
Expand All @@ -133,26 +127,26 @@ export class AvmJournal {
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);
this.parentJournal.newNoteHashes = this.parentJournal.newNoteHashes.concat(this.newNoteHashes);
this.parentJournal.newL1Messages = this.parentJournal.newL1Messages.concat(this.newL1Messages);
this.parentJournal.newNullifiers = this.parentJournal.newNullifiers.concat(this.newNullifiers);

// Merge Public State
mergeContractMaps(this.parentJournal.storageWrites, incomingFlush.storageWrites);
mergeContractMaps(this.parentJournal.storageWrites, this.storageWrites);
}

/** Access the current state of the journal
/**
* Access the current state of the journal
*
* @returns a JournalData object that can be used to write to the storage
* @returns a JournalData object
*/
public flush(): JournalData {
return {
newCommitments: this.newCommitments,
newL1Messages: this.newL1Message,
newNoteHashes: this.newNoteHashes,
newNullifiers: this.newNullifiers,
newL1Messages: this.newL1Messages,
newLogs: this.newLogs,
storageWrites: this.storageWrites,
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { mock } from 'jest-mock-extended';

import { AvmMachineState } from '../avm_machine_state.js';
import { Field } from '../avm_memory_types.js';
import { initExecutionEnvironment } from '../fixtures/index.js';
import { HostStorage } from '../journal/host_storage.js';
import { AvmJournal } from '../journal/journal.js';
import { EmitNoteHash, EmitNullifier, EmitUnencryptedLog, SendL2ToL1Message } from './accrued_substate.js';
import { StaticCallStorageAlterError } from './storage.js';

describe('Accrued Substate', () => {
let journal: AvmJournal;
let machineState: AvmMachineState;

beforeEach(() => {
const hostStorage = mock<HostStorage>();
journal = new AvmJournal(hostStorage);
machineState = new AvmMachineState(initExecutionEnvironment());
});

it('Should append a new note hash correctly', async () => {
const value = new Field(69n);
machineState.memory.set(0, value);

await new EmitNoteHash(0).execute(machineState, journal);

const journalState = journal.flush();
const expected = [value.toFr()];
expect(journalState.newNoteHashes).toEqual(expected);
});

it('Should append a new nullifier correctly', async () => {
const value = new Field(69n);
machineState.memory.set(0, value);

await new EmitNullifier(0).execute(machineState, journal);

const journalState = journal.flush();
const expected = [value.toFr()];
expect(journalState.newNullifiers).toEqual(expected);
});

it('Should append unencrypted logs correctly', async () => {
const startOffset = 0;

const values = [new Field(69n), new Field(420n), new Field(Field.MODULUS - 1n)];
machineState.memory.setSlice(0, values);

const length = values.length;

await new EmitUnencryptedLog(startOffset, length).execute(machineState, journal);

const journalState = journal.flush();
const expected = values.map(v => v.toFr());
expect(journalState.newLogs).toEqual([expected]);
});

it('Should append l1 to l2 messages correctly', async () => {
const startOffset = 0;

const values = [new Field(69n), new Field(420n), new Field(Field.MODULUS - 1n)];
machineState.memory.setSlice(0, values);

const length = values.length;

await new SendL2ToL1Message(startOffset, length).execute(machineState, journal);

const journalState = journal.flush();
const expected = values.map(v => v.toFr());
expect(journalState.newLogs).toEqual([expected]);
});

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);
}
});
});
84 changes: 84 additions & 0 deletions yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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<void> {
if (machineState.executionEnvironment.isStaticCall) {
throw new StaticCallStorageAlterError();
}

const noteHash = machineState.memory.get(this.noteHashOffset).toFr();
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<void> {
if (machineState.executionEnvironment.isStaticCall) {
throw new StaticCallStorageAlterError();
}

const nullifier = machineState.memory.get(this.nullifierOffset).toFr();
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<void> {
if (machineState.executionEnvironment.isStaticCall) {
throw new StaticCallStorageAlterError();
}

const log = machineState.memory.getSlice(this.logOffset, this.logSize).map(f => f.toFr());
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<void> {
if (machineState.executionEnvironment.isStaticCall) {
throw new StaticCallStorageAlterError();
}

const msg = machineState.memory.getSlice(this.msgOffset, this.msgSize).map(f => f.toFr());
journal.writeLog(msg);

this.incrementPc(machineState);
}
}
23 changes: 18 additions & 5 deletions yarn-project/acir-simulator/src/avm/opcodes/instruction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,26 @@ export abstract class Instruction {
}

static checkTags(machineState: AvmMachineState, tag: TypeTag, ...offsets: number[]) {
for (const off of offsets) {
if (machineState.memory.getTag(off) !== tag) {
const error = `Offset ${off} has tag ${TypeTag[machineState.memory.getTag(off)]}, expected ${TypeTag[tag]}`;
throw new InstructionExecutionError(error);
}
for (const offset of offsets) {
checkTag(machineState, tag, offset);
}
}

static checkTagsRange(machineState: AvmMachineState, tag: TypeTag, startOffset: number, size: number) {
for (let offset = startOffset; offset < startOffset + size; offset++) {
checkTag(machineState, tag, offset);
}
}
}

/**
* Checks that the memory at the given offset has the given tag.
*/
function checkTag(machineState: AvmMachineState, tag: TypeTag, offset: number) {
if (machineState.memory.getTag(offset) !== tag) {
const error = `Offset ${offset} has tag ${TypeTag[machineState.memory.getTag(offset)]}, expected ${TypeTag[tag]}`;
throw new InstructionExecutionError(error);
}
}

export class InstructionExecutionError extends Error {
Expand Down

0 comments on commit d97ed01

Please sign in to comment.