Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: accrued substate instructions #4197

Merged
merged 10 commits into from
Jan 29, 2024
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
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Re: lines 141 to 141]

Please fix comment.

See this comment inline on Graphite.

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
Loading