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(avm): environment getters #4203

Merged
merged 14 commits into from
Jan 30, 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
22 changes: 22 additions & 0 deletions yarn-project/acir-simulator/src/avm/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,25 @@ export function initExecutionEnvironment(overrides?: AvmExecutionEnvironmentOver
overrides?.calldata ?? [],
);
}

/**
* An interface that allows to override the default values of the GlobalVariables
*/
export interface GlobalVariablesOverrides {
chainId?: Fr;
version?: Fr;
blockNumber?: Fr;
timestamp?: Fr;
}

/**
* Create an empty instance of the Global Variables where all values are zero, unless overriden in the overrides object
*/
export function initGlobalVariables(overrides?: GlobalVariablesOverrides): GlobalVariables {
return new GlobalVariables(
overrides?.chainId ?? Fr.zero(),
overrides?.version ?? Fr.zero(),
overrides?.blockNumber ?? Fr.zero(),
overrides?.timestamp ?? Fr.zero(),
);
}
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);
}
}
Loading
Loading