Skip to content

Commit

Permalink
feat(avm): revert instruction (AztecProtocol#4206)
Browse files Browse the repository at this point in the history
  • Loading branch information
Maddiaa0 authored Jan 30, 2024
1 parent 60d2377 commit bd6e797
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 158 deletions.
5 changes: 5 additions & 0 deletions yarn-project/acir-simulator/src/avm/avm_machine_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export class AvmMachineState {
* If an instruction triggers a halt, then it ends execution of the VM
*/
public halted: boolean;
/**
* Signifies if the execution has reverted ( due to a revert instruction )
*/
public reverted: boolean;

/**
* Create a new avm context
Expand All @@ -45,6 +49,7 @@ export class AvmMachineState {
this.callStack = [];

this.halted = false;
this.reverted = false;

this.executionEnvironment = executionEnvironment;
}
Expand Down
47 changes: 3 additions & 44 deletions yarn-project/acir-simulator/src/avm/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,10 @@ import { Fr } from '@aztec/foundation/fields';

import { AvmExecutionEnvironment } from '../avm_execution_environment.js';

/**
* An interface that allows to override the default values of the AvmExecutionEnvironment
*/
export interface AvmExecutionEnvironmentOverrides {
address?: AztecAddress;

storageAddress?: AztecAddress;

origin?: AztecAddress;

sender?: AztecAddress;

portal?: EthAddress;

feePerL1Gas?: Fr;

feePerL2Gas?: Fr;

feePerDaGas?: Fr;

contractCallDepth?: Fr;

globals?: GlobalVariables;

isStaticCall?: boolean;

isDelegateCall?: boolean;

calldata?: Fr[];
}

/**
* Create an empty instance of the Execution Environment where all values are zero, unless overriden in the overrides object
*/
export function initExecutionEnvironment(overrides?: AvmExecutionEnvironmentOverrides): AvmExecutionEnvironment {
export function initExecutionEnvironment(overrides?: Partial<AvmExecutionEnvironment>): AvmExecutionEnvironment {
return new AvmExecutionEnvironment(
overrides?.address ?? AztecAddress.zero(),
overrides?.storageAddress ?? AztecAddress.zero(),
Expand All @@ -59,19 +28,9 @@ export function initExecutionEnvironment(overrides?: AvmExecutionEnvironmentOver
}

/**
* 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
* Create an empty instance of the Execution Environment where all values are zero, unless overriden in the overrides object
*/
export function initGlobalVariables(overrides?: GlobalVariablesOverrides): GlobalVariables {
export function initGlobalVariables(overrides?: Partial<GlobalVariables>): GlobalVariables {
return new GlobalVariables(
overrides?.chainId ?? Fr.zero(),
overrides?.version ?? Fr.zero(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export class AvmInterpreter {
}

const returnData = this.machineState.getReturnData();
if (this.machineState.reverted) {
return AvmMessageCallResult.revert(returnData);
}

return AvmMessageCallResult.success(returnData);
} catch (_e) {
if (!(_e instanceof AvmInterpreterError)) {
Expand Down
254 changes: 145 additions & 109 deletions yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Fr } from '@aztec/foundation/fields';

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

import { AvmMachineState } from '../avm_machine_state.js';
import { TypeTag, Uint16 } from '../avm_memory_types.js';
import { Field, TypeTag, Uint16 } from '../avm_memory_types.js';
import { initExecutionEnvironment } from '../fixtures/index.js';
import { AvmJournal } from '../journal/journal.js';
import { Add, Mul, Sub } from './arithmetic.js';
import { And, Not, Or, Shl, Shr, Xor } from './bitwise.js';
import { Eq, Lt, Lte } from './comparators.js';
import { InternalCall, InternalReturn, Jump, JumpI } from './control_flow.js';
import { InternalCall, InternalReturn, Jump, JumpI, Return, Revert } from './control_flow.js';
import { InstructionExecutionError } from './instruction.js';
import { CMov, CalldataCopy, Cast, Mov, Set } from './memory.js';

Expand All @@ -20,132 +22,166 @@ describe('Control Flow Opcodes', () => {
machineState = new AvmMachineState(initExecutionEnvironment());
});

it('Should implement JUMP', async () => {
const jumpLocation = 22;
describe('Jumps', () => {
it('Should implement JUMP', async () => {
const jumpLocation = 22;

expect(machineState.pc).toBe(0);
expect(machineState.pc).toBe(0);

const instruction = new Jump(jumpLocation);
await instruction.execute(machineState, journal);
expect(machineState.pc).toBe(jumpLocation);
});
const instruction = new Jump(jumpLocation);
await instruction.execute(machineState, journal);
expect(machineState.pc).toBe(jumpLocation);
});

it('Should implement JUMPI - truthy', async () => {
const jumpLocation = 22;
const jumpLocation1 = 69;
it('Should implement JUMPI - truthy', async () => {
const jumpLocation = 22;
const jumpLocation1 = 69;

expect(machineState.pc).toBe(0);
expect(machineState.pc).toBe(0);

machineState.memory.set(0, new Uint16(1n));
machineState.memory.set(1, new Uint16(2n));
machineState.memory.set(0, new Uint16(1n));
machineState.memory.set(1, new Uint16(2n));

const instruction = new JumpI(jumpLocation, 0);
await instruction.execute(machineState, journal);
expect(machineState.pc).toBe(jumpLocation);
const instruction = new JumpI(jumpLocation, 0);
await instruction.execute(machineState, journal);
expect(machineState.pc).toBe(jumpLocation);

// Truthy can be greater than 1
const instruction1 = new JumpI(jumpLocation1, 1);
await instruction1.execute(machineState, journal);
expect(machineState.pc).toBe(jumpLocation1);
});
// Truthy can be greater than 1
const instruction1 = new JumpI(jumpLocation1, 1);
await instruction1.execute(machineState, journal);
expect(machineState.pc).toBe(jumpLocation1);
});

it('Should implement JUMPI - falsy', async () => {
const jumpLocation = 22;

expect(machineState.pc).toBe(0);

machineState.memory.set(0, new Uint16(0n));

it('Should implement JUMPI - falsy', async () => {
const jumpLocation = 22;
const instruction = new JumpI(jumpLocation, 0);
await instruction.execute(machineState, journal);
expect(machineState.pc).toBe(1);
});

expect(machineState.pc).toBe(0);
it('Should implement Internal Call and Return', async () => {
const jumpLocation = 22;

machineState.memory.set(0, new Uint16(0n));
expect(machineState.pc).toBe(0);

const instruction = new JumpI(jumpLocation, 0);
await instruction.execute(machineState, journal);
expect(machineState.pc).toBe(1);
const instruction = new InternalCall(jumpLocation);
const returnInstruction = new InternalReturn();

await instruction.execute(machineState, journal);
expect(machineState.pc).toBe(jumpLocation);

await returnInstruction.execute(machineState, journal);
expect(machineState.pc).toBe(1);
});

it('Should chain series of control flow instructions', async () => {
const jumpLocation0 = 22;
const jumpLocation1 = 69;
const jumpLocation2 = 1337;

const aloneJumpLocation = 420;

const instructions = [
// pc | internal call stack
new InternalCall(jumpLocation0), // 22 | [1]
new InternalCall(jumpLocation1), // 69 | [1, 23]
new InternalReturn(), // 23 | [1]
new Jump(aloneJumpLocation), // 420 | [1]
new InternalCall(jumpLocation2), // 1337| [1, 421]
new InternalReturn(), // 421 | [1]
new InternalReturn(), // 1 | []
];

// The expected program counter after each instruction is invoked
const expectedPcs = [
jumpLocation0,
jumpLocation1,
jumpLocation0 + 1,
aloneJumpLocation,
jumpLocation2,
aloneJumpLocation + 1,
1,
];

for (let i = 0; i < instructions.length; i++) {
await instructions[i].execute(machineState, journal);
expect(machineState.pc).toBe(expectedPcs[i]);
}
});

it('Should error if Internal Return is called without a corresponding Internal Call', async () => {
const returnInstruction = () => new InternalReturn().execute(machineState, journal);
await expect(returnInstruction()).rejects.toThrow(InstructionExecutionError);
});

it('Should increment PC on All other Instructions', async () => {
const instructions = [
new Add(0, 1, 2),
new Sub(0, 1, 2),
new Mul(0, 1, 2),
new Lt(TypeTag.UINT16, 0, 1, 2),
new Lte(TypeTag.UINT16, 0, 1, 2),
new Eq(TypeTag.UINT16, 0, 1, 2),
new Xor(TypeTag.UINT16, 0, 1, 2),
new And(TypeTag.UINT16, 0, 1, 2),
new Or(TypeTag.UINT16, 0, 1, 2),
new Shl(TypeTag.UINT16, 0, 1, 2),
new Shr(TypeTag.UINT16, 0, 1, 2),
new Not(TypeTag.UINT16, 0, 2),
new CalldataCopy(0, 1, 2),
new Set(TypeTag.UINT16, 0n, 1),
new Mov(0, 1),
new CMov(0, 1, 2, 3),
new Cast(TypeTag.UINT16, 0, 1),
];

for (const instruction of instructions) {
// Use a fresh machine state each run
const innerMachineState = new AvmMachineState(initExecutionEnvironment());
innerMachineState.memory.set(0, new Uint16(4n));
innerMachineState.memory.set(1, new Uint16(8n));
innerMachineState.memory.set(2, new Uint16(12n));
expect(innerMachineState.pc).toBe(0);

await instruction.execute(innerMachineState, journal);
}
});
});

it('Should implement Internal Call and Return', async () => {
const jumpLocation = 22;
describe('Halting Opcodes', () => {
it('Should return data from the return opcode', async () => {
const returnData = [new Fr(1n), new Fr(2n), new Fr(3n)];

expect(machineState.pc).toBe(0);
machineState.memory.set(0, new Field(1n));
machineState.memory.set(1, new Field(2n));
machineState.memory.set(2, new Field(3n));

const instruction = new InternalCall(jumpLocation);
const returnInstruction = new InternalReturn();
const instruction = new Return(0, returnData.length);
await instruction.execute(machineState, journal);

await instruction.execute(machineState, journal);
expect(machineState.pc).toBe(jumpLocation);
expect(machineState.getReturnData()).toEqual(returnData);
expect(machineState.halted).toBe(true);
expect(machineState.reverted).toBe(false);
});

await returnInstruction.execute(machineState, journal);
expect(machineState.pc).toBe(1);
});
it('Should return data and revert from the revert opcode', async () => {
const returnData = [new Fr(1n), new Fr(2n), new Fr(3n)];

it('Should chain series of control flow instructions', async () => {
const jumpLocation0 = 22;
const jumpLocation1 = 69;
const jumpLocation2 = 1337;

const aloneJumpLocation = 420;

const instructions = [
// pc | internal call stack
new InternalCall(jumpLocation0), // 22 | [1]
new InternalCall(jumpLocation1), // 69 | [1, 23]
new InternalReturn(), // 23 | [1]
new Jump(aloneJumpLocation), // 420 | [1]
new InternalCall(jumpLocation2), // 1337| [1, 421]
new InternalReturn(), // 421 | [1]
new InternalReturn(), // 1 | []
];

// The expected program counter after each instruction is invoked
const expectedPcs = [
jumpLocation0,
jumpLocation1,
jumpLocation0 + 1,
aloneJumpLocation,
jumpLocation2,
aloneJumpLocation + 1,
1,
];

for (let i = 0; i < instructions.length; i++) {
await instructions[i].execute(machineState, journal);
expect(machineState.pc).toBe(expectedPcs[i]);
}
});
machineState.memory.set(0, new Field(1n));
machineState.memory.set(1, new Field(2n));
machineState.memory.set(2, new Field(3n));

it('Should error if Internal Return is called without a corresponding Internal Call', async () => {
const returnInstruction = () => new InternalReturn().execute(machineState, journal);
await expect(returnInstruction()).rejects.toThrow(InstructionExecutionError);
});
const instruction = new Revert(0, returnData.length);
await instruction.execute(machineState, journal);

it('Should increment PC on All other Instructions', async () => {
const instructions = [
new Add(0, 1, 2),
new Sub(0, 1, 2),
new Mul(0, 1, 2),
new Lt(TypeTag.UINT16, 0, 1, 2),
new Lte(TypeTag.UINT16, 0, 1, 2),
new Eq(TypeTag.UINT16, 0, 1, 2),
new Xor(TypeTag.UINT16, 0, 1, 2),
new And(TypeTag.UINT16, 0, 1, 2),
new Or(TypeTag.UINT16, 0, 1, 2),
new Shl(TypeTag.UINT16, 0, 1, 2),
new Shr(TypeTag.UINT16, 0, 1, 2),
new Not(TypeTag.UINT16, 0, 2),
new CalldataCopy(0, 1, 2),
new Set(TypeTag.UINT16, 0n, 1),
new Mov(0, 1),
new CMov(0, 1, 2, 3),
new Cast(TypeTag.UINT16, 0, 1),
];

for (const instruction of instructions) {
// Use a fresh machine state each run
const innerMachineState = new AvmMachineState(initExecutionEnvironment());
innerMachineState.memory.set(0, new Uint16(4n));
innerMachineState.memory.set(1, new Uint16(8n));
innerMachineState.memory.set(2, new Uint16(12n));
expect(machineState.pc).toBe(0);
await instruction.execute(innerMachineState, journal);
expect(innerMachineState.pc).toBe(1);
}
expect(machineState.getReturnData()).toEqual(returnData);
expect(machineState.halted).toBe(true);
expect(machineState.reverted).toBe(true);
});
});
});
Loading

0 comments on commit bd6e797

Please sign in to comment.