Skip to content

Commit

Permalink
feat: avm executor - layout and groundwork (AztecProtocol#3928)
Browse files Browse the repository at this point in the history
  • Loading branch information
Maddiaa0 authored Jan 16, 2024
1 parent 8a5023d commit ed369dc
Show file tree
Hide file tree
Showing 28 changed files with 1,042 additions and 120 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ cmake-build-debug

# Local Netlify folder
.netlify

.graphite*
3 changes: 3 additions & 0 deletions yarn-project/acir-simulator/src/avm/avm_context.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
describe('Avm', () => {
it('Executes a simple call', () => {});
});
44 changes: 44 additions & 0 deletions yarn-project/acir-simulator/src/avm/avm_context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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';

/**
* Avm Executor manages the execution of the AVM
*
* It stores a state manager
*/
export class AvmContext {
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[]): 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');

const instructions: Instruction[] = interpretBytecode(bytecode);

const context = new AvmMachineState(calldata);
const interpreter = new AvmInterpreter(context, this.stateManager, instructions);

return interpreter.run();
}
}
79 changes: 79 additions & 0 deletions yarn-project/acir-simulator/src/avm/avm_machine_state.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
34 changes: 34 additions & 0 deletions yarn-project/acir-simulator/src/avm/avm_message_call_result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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);
}
}
44 changes: 44 additions & 0 deletions yarn-project/acir-simulator/src/avm/avm_state_manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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, journal: AvmJournal) {
this.blockHeader = blockHeader;
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);
}
}
1 change: 1 addition & 0 deletions yarn-project/acir-simulator/src/avm/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Place large AVM text fixtures in here
3 changes: 3 additions & 0 deletions yarn-project/acir-simulator/src/avm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './avm_machine_state.js';
export * from './avm_context.js';
export * from './avm_state_manager.js';
1 change: 1 addition & 0 deletions yarn-project/acir-simulator/src/avm/interpreter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './interpreter.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Fr } from '@aztec/foundation/fields';

import { mock } from 'jest-mock-extended';

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 { Instruction } from '../opcodes/instruction.js';
import { CallDataCopy } from '../opcodes/memory.js';
import { AvmInterpreter } from './interpreter.js';

describe('interpreter', () => {
it('Should execute a series of instructions', () => {
const calldata: Fr[] = [new Fr(1), new Fr(2)];
const stateManager = mock<AvmStateManager>();

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
new Add(0, 1, 2), // 1 + 2
// Return the result
new Return(2, 1), // [3]
];

const context = new AvmMachineState(calldata);
const interpreter = new AvmInterpreter(context, stateManager, instructions);
const avmReturnData = interpreter.run();

expect(avmReturnData.reverted).toBe(false);

const returnData = avmReturnData.output;
expect(returnData.length).toBe(1);
expect(returnData).toEqual([new Fr(3)]);
});
});
54 changes: 54 additions & 0 deletions yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// import { AvmContext } from "../avm_machineState.js";
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';

/**
* Avm Interpreter
*
* Executes an Avm context
*/
export class AvmInterpreter {
private instructions: Instruction[] = [];
private machineState: AvmMachineState;
private stateManager: AvmStateManager;

constructor(machineState: AvmMachineState, stateManager: AvmStateManager, bytecode: Instruction[]) {
this.machineState = machineState;
this.stateManager = stateManager;
this.instructions = bytecode;
}

/**
* Run the avm
* @returns bool - successful execution will return true
* - reverted execution will return false
* - any other panic will throw
*/
run(): AvmMessageCallResult {
try {
for (const instruction of this.instructions) {
instruction.execute(this.machineState, this.stateManager);
}

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
const revertData = this.machineState.getReturnData();
return AvmMessageCallResult.revert(revertData);
}
}

/**
* 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.machineState.getReturnData();
}
}
18 changes: 18 additions & 0 deletions yarn-project/acir-simulator/src/avm/journal/host_storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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;
}
}
2 changes: 2 additions & 0 deletions yarn-project/acir-simulator/src/avm/journal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './host_storage.js';
export * from './journal.js';
7 changes: 7 additions & 0 deletions yarn-project/acir-simulator/src/avm/journal/journal.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
describe('journal', () => {
it('Should write to storage', () => {});

it('Should read from storage', () => {});

it('Should merge two journals together', () => {});
});
Loading

0 comments on commit ed369dc

Please sign in to comment.