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): Track gas usage in AVM simulator #5438

Merged
merged 1 commit into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions yarn-project/simulator/src/avm/avm_gas_cost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Opcode } from './serialization/instruction_serialization.js';

/** Gas cost in L1, L2, and DA for a given instruction. */
export type GasCost = {
l1Gas: number;
l2Gas: number;
daGas: number;
};

/** Gas cost of zero across all gas dimensions. */
export const EmptyGasCost = {
l1Gas: 0,
l2Gas: 0,
daGas: 0,
};

/** Dimensions of gas usage: L1, L2, and DA */
export const GasDimensions = ['l1Gas', 'l2Gas', 'daGas'] as const;

/** Temporary default gas cost. We should eventually remove all usage of this variable in favor of actual gas for each opcode. */
const TemporaryDefaultGasCost = { l1Gas: 0, l2Gas: 10, daGas: 0 };

/** Gas costs for each instruction. */
export const GasCosts: Record<Opcode, GasCost> = {
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought you wanted to do it per instruction and variable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I realized most instructions will have a fixed cost, and it's easier to see them in a single place. Instructions with a variable cost can just override gasCost. I'll work on one in a future PR to act as an example!

[Opcode.ADD]: TemporaryDefaultGasCost,
[Opcode.SUB]: TemporaryDefaultGasCost,
[Opcode.MUL]: TemporaryDefaultGasCost,
[Opcode.DIV]: TemporaryDefaultGasCost,
[Opcode.FDIV]: TemporaryDefaultGasCost,
[Opcode.EQ]: TemporaryDefaultGasCost,
[Opcode.LT]: TemporaryDefaultGasCost,
[Opcode.LTE]: TemporaryDefaultGasCost,
[Opcode.AND]: TemporaryDefaultGasCost,
[Opcode.OR]: TemporaryDefaultGasCost,
[Opcode.XOR]: TemporaryDefaultGasCost,
[Opcode.NOT]: TemporaryDefaultGasCost,
[Opcode.SHL]: TemporaryDefaultGasCost,
[Opcode.SHR]: TemporaryDefaultGasCost,
[Opcode.CAST]: TemporaryDefaultGasCost,
// Execution environment
[Opcode.ADDRESS]: TemporaryDefaultGasCost,
[Opcode.STORAGEADDRESS]: TemporaryDefaultGasCost,
[Opcode.ORIGIN]: TemporaryDefaultGasCost,
[Opcode.SENDER]: TemporaryDefaultGasCost,
[Opcode.PORTAL]: TemporaryDefaultGasCost,
[Opcode.FEEPERL1GAS]: TemporaryDefaultGasCost,
[Opcode.FEEPERL2GAS]: TemporaryDefaultGasCost,
[Opcode.FEEPERDAGAS]: TemporaryDefaultGasCost,
[Opcode.CONTRACTCALLDEPTH]: TemporaryDefaultGasCost,
[Opcode.CHAINID]: TemporaryDefaultGasCost,
[Opcode.VERSION]: TemporaryDefaultGasCost,
[Opcode.BLOCKNUMBER]: TemporaryDefaultGasCost,
[Opcode.TIMESTAMP]: TemporaryDefaultGasCost,
[Opcode.COINBASE]: TemporaryDefaultGasCost,
[Opcode.BLOCKL1GASLIMIT]: TemporaryDefaultGasCost,
[Opcode.BLOCKL2GASLIMIT]: TemporaryDefaultGasCost,
[Opcode.BLOCKDAGASLIMIT]: TemporaryDefaultGasCost,
[Opcode.CALLDATACOPY]: TemporaryDefaultGasCost,
// Gas
[Opcode.L1GASLEFT]: TemporaryDefaultGasCost,
[Opcode.L2GASLEFT]: TemporaryDefaultGasCost,
[Opcode.DAGASLEFT]: TemporaryDefaultGasCost,
// Control flow
[Opcode.JUMP]: TemporaryDefaultGasCost,
[Opcode.JUMPI]: TemporaryDefaultGasCost,
[Opcode.INTERNALCALL]: TemporaryDefaultGasCost,
[Opcode.INTERNALRETURN]: TemporaryDefaultGasCost,
// Memory
[Opcode.SET]: TemporaryDefaultGasCost,
[Opcode.MOV]: TemporaryDefaultGasCost,
[Opcode.CMOV]: TemporaryDefaultGasCost,
// World state
[Opcode.SLOAD]: TemporaryDefaultGasCost,
[Opcode.SSTORE]: TemporaryDefaultGasCost,
[Opcode.NOTEHASHEXISTS]: TemporaryDefaultGasCost,
[Opcode.EMITNOTEHASH]: TemporaryDefaultGasCost,
[Opcode.NULLIFIEREXISTS]: TemporaryDefaultGasCost,
[Opcode.EMITNULLIFIER]: TemporaryDefaultGasCost,
[Opcode.L1TOL2MSGEXISTS]: TemporaryDefaultGasCost,
[Opcode.HEADERMEMBER]: TemporaryDefaultGasCost,
[Opcode.EMITUNENCRYPTEDLOG]: TemporaryDefaultGasCost,
[Opcode.SENDL2TOL1MSG]: TemporaryDefaultGasCost,
// External calls
[Opcode.CALL]: TemporaryDefaultGasCost,
[Opcode.STATICCALL]: TemporaryDefaultGasCost,
[Opcode.DELEGATECALL]: TemporaryDefaultGasCost,
[Opcode.RETURN]: TemporaryDefaultGasCost,
[Opcode.REVERT]: TemporaryDefaultGasCost,
// Gadgets
[Opcode.KECCAK]: TemporaryDefaultGasCost,
[Opcode.POSEIDON]: TemporaryDefaultGasCost,
[Opcode.SHA256]: TemporaryDefaultGasCost, // temp - may be removed, but alot of contracts rely on i: TemporaryDefaultGasCost,
[Opcode.PEDERSEN]: TemporaryDefaultGasCost, // temp - may be removed, but alot of contracts rely on i: TemporaryDefaultGasCost,t
};
35 changes: 34 additions & 1 deletion yarn-project/simulator/src/avm/avm_machine_state.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Fr } from '@aztec/circuits.js';

import { GasCost, GasDimensions } from './avm_gas_cost.js';
import { TaggedMemory } from './avm_memory_types.js';
import { AvmContractCallResults } from './avm_message_call_result.js';
import { OutOfGasError } from './errors.js';

/**
* A few fields of machine state are initialized from AVM session inputs or call instruction arguments
Expand Down Expand Up @@ -35,7 +37,7 @@ export class AvmMachineState {
/**
* Signals that execution should end.
* AvmContext execution continues executing instructions until the machine state signals "halted"
* */
*/
public halted: boolean = false;
/** Signals that execution has reverted normally (this does not cover exceptional halts) */
private reverted: boolean = false;
Expand All @@ -52,6 +54,28 @@ export class AvmMachineState {
return new AvmMachineState(state.l1GasLeft, state.l2GasLeft, state.daGasLeft);
}

/**
* Consumes the given gas.
* Should any of the gas dimensions get depleted, it sets all gas left to zero and triggers
* an exceptional halt by throwing an OutOfGasError.
*/
public consumeGas(gasCost: Partial<GasCost>) {
// Assert there is enough gas on every dimension.
const outOfGasDimensions = GasDimensions.filter(
dimension => this[`${dimension}Left`] - (gasCost[dimension] ?? 0) < 0,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the YP says that it has to be strictly bigger than 0? It might be a mistake (I don't see why 0 would be wrong if you don't need to spend anything). Would you mind updating that in the YP? (I don't mind if you do it later).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hah, good catch!

);
// If not, trigger an exceptional halt.
// See https://yp-aztec.netlify.app/docs/public-vm/execution#gas-checks-and-tracking
if (outOfGasDimensions.length > 0) {
this.exceptionalHalt();
Copy link
Contributor

Choose a reason for hiding this comment

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

You can use this.revert([]) ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Revert didn't set gas to zero, that's why I set up a different function. Still, I wonder whether this is needed at all, since the state seems to be discarded once an exception is thrown.

throw new OutOfGasError(outOfGasDimensions);
}
// Otherwise, charge the corresponding gas
for (const dimension of GasDimensions) {
this[`${dimension}Left`] -= gasCost[dimension] ?? 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we losing any static type checking by using a string here or is TS smart enough?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

TS is smart enough! GasDimensions is defined as const so its type is not string but the actual values, and string literal types do the rest.

}
}

/**
* Most instructions just increment PC before they complete
*/
Expand Down Expand Up @@ -80,6 +104,15 @@ export class AvmMachineState {
this.output = output;
}

/**
* Flag an exceptional halt. Clears gas left and sets the reverted flag. No output data.
*/
protected exceptionalHalt() {
GasDimensions.forEach(dimension => (this[`${dimension}Left`] = 0));
Copy link
Contributor

Choose a reason for hiding this comment

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

@fcarreiro @spalladino
What is the plan on how we're going to prove to the public kernel that I threw an exception due to "out of gas"?
In my mind, it would need to be something like "I had state S and was about to execute X". So it would provide S (the state before the operation) and X. In this case, we wouldn't want to zero out gas.

Copy link
Contributor

Choose a reason for hiding this comment

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

@Maddiaa0 on this one.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Isn't this handled by the public VM? i.e. you don't need to prove it to the kernel, the VM says so.

Copy link
Contributor

Choose a reason for hiding this comment

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

Got it. Even still, I'm not positive on what the outputs should be. Suppose I specify an l2 gas limit of 10, and i try to run two opcodes, one that consumes 1, and another that would have consumed 100. Do we charge the user 1 gas, or 10?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yep, I understand this is "just" the simulation side of things, the proof is produced by the circuit. And this clearing of gas is specified in the yellow paper.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Do we charge the user 1 gas, or 10?

I understand we always charge them all of their gas (in all dimensions!) if there is an out of gas.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think we would charge them the gas from all dimensions would we? e.g. I wouldn't lose all my L2 gas if I emitted a note hash right at the start of public execution that breached my DA gas limit.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agree, but setting all gas dimensions to zero in the event of an OOG as specified in the YP seems to point at that. Who should we talk to if we wanted to change that?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the YP should be taken as "malleable" in most things gas-related. It doesn't really change much about what the VM actually has to do, so I would defer to protocol experts (i.e., you). However, it might be worth checking if having to calculate the actual gas, etc, has any impact on the actual VM circuit. Maybe resetting all was chosen because of that? I'll bring Sean's attention to this.

Copy link
Member

@Maddiaa0 Maddiaa0 Mar 26, 2024

Choose a reason for hiding this comment

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

  • Yep, the vm circuit can catch an underflow on the gas left column and set a reverted flag.
  • Agree that it is malleable on these things. Our first pass was that reverting will consume all gas, but if we would like to be granular, we can

this.reverted = true;
this.halted = true;
}

/**
* Get a summary of execution results for a halted machine state
* @returns summary of execution results
Expand Down
35 changes: 29 additions & 6 deletions yarn-project/simulator/src/avm/avm_simulator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { AvmTestContractArtifact } from '@aztec/noir-contracts.js';
import { jest } from '@jest/globals';
import { strict as assert } from 'assert';

import { AvmMachineState } from './avm_machine_state.js';
import { TypeTag } from './avm_memory_types.js';
import { AvmSimulator } from './avm_simulator.js';
import {
Expand All @@ -17,26 +18,48 @@ import {
initExecutionEnvironment,
initGlobalVariables,
initL1ToL2MessageOracleInput,
initMachineState,
} from './fixtures/index.js';
import { Add, CalldataCopy, Return } from './opcodes/index.js';
import { Add, CalldataCopy, Instruction, Return } from './opcodes/index.js';
import { encodeToBytecode } from './serialization/bytecode_serialization.js';

describe('AVM simulator: injected bytecode', () => {
it('Should execute bytecode that performs basic addition', async () => {
const calldata: Fr[] = [new Fr(1), new Fr(2)];
let calldata: Fr[];
let ops: Instruction[];
let bytecode: Buffer;

// Construct bytecode
const bytecode = encodeToBytecode([
beforeAll(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you plan to add more tests here? How detailed do we want to be in checking that gas costs are set properly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm planning on adding more tests for covering the more "dynamic" gas cost functions, for checking reverts, and for testing the different type of gases.

calldata = [new Fr(1), new Fr(2)];
ops = [
new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ adjustCalldataIndex(0), /*copySize=*/ 2, /*dstOffset=*/ 0),
new Add(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2),
new Return(/*indirect=*/ 0, /*returnOffset=*/ 2, /*copySize=*/ 1),
]);
];
bytecode = encodeToBytecode(ops);
});

it('Should execute bytecode that performs basic addition', async () => {
const context = initContext({ env: initExecutionEnvironment({ calldata }) });
const { l2GasLeft: initialL2GasLeft } = AvmMachineState.fromState(context.machineState);
const results = await new AvmSimulator(context).executeBytecode(bytecode);
const expectedL2GasUsed = ops.reduce((sum, op) => sum + op.gasCost().l2Gas, 0);

expect(results.reverted).toBe(false);
expect(results.output).toEqual([new Fr(3)]);
expect(expectedL2GasUsed).toBeGreaterThan(0);
Comment on lines +45 to +49
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd rather have hardcoded numbers for these. Having code compute the expected outputs IMO should be avoided when possible.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I didn't want the tests to break when we updated gas costs for those instructions, but agree on the sentiment of hardcoding. I'll do the change!

expect(context.machineState.l2GasLeft).toEqual(initialL2GasLeft - expectedL2GasUsed);
});

it('Should halt if runs out of gas', async () => {
const context = initContext({
env: initExecutionEnvironment({ calldata }),
machineState: initMachineState({ l2GasLeft: 5 }),
});

const results = await new AvmSimulator(context).executeBytecode(bytecode);
expect(results.reverted).toBe(true);
expect(results.output).toEqual([]);
expect(results.revertReason?.name).toEqual('OutOfGasError');
});
});

Expand Down
3 changes: 1 addition & 2 deletions yarn-project/simulator/src/avm/avm_simulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export class AvmSimulator {
*/
public async executeInstructions(instructions: Instruction[]): Promise<AvmContractCallResults> {
assert(instructions.length > 0);

try {
// Execute instruction pointed to by the current program counter
// continuing until the machine state signifies a halt
Expand All @@ -65,7 +64,7 @@ export class AvmSimulator {
// Execute the instruction.
// Normal returns and reverts will return normally here.
// "Exceptional halts" will throw.
await instruction.execute(this.context);
await instruction.run(this.context);

if (this.context.machineState.pc >= instructions.length) {
this.log('Passed end of program!');
Expand Down
8 changes: 8 additions & 0 deletions yarn-project/simulator/src/avm/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,11 @@ export class TagCheckError extends AvmExecutionError {
this.name = 'TagCheckError';
}
}

/** Error thrown when out of gas. */
export class OutOfGasError extends AvmExecutionError {
constructor(dimensions: string[]) {
super(`Not enough ${dimensions.map(d => d.toUpperCase()).join(', ')} gas left`);
this.name = 'OutOfGasError';
}
}
8 changes: 4 additions & 4 deletions yarn-project/simulator/src/avm/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ export function initGlobalVariables(overrides?: Partial<GlobalVariables>): Globa
}

/**
* Create an empty instance of the Machine State where all values are zero, unless overridden in the overrides object
* Create an empty instance of the Machine State where all values are set to a large enough amount, unless overridden in the overrides object
*/
export function initMachineState(overrides?: Partial<AvmMachineState>): AvmMachineState {
return AvmMachineState.fromState({
l1GasLeft: overrides?.l1GasLeft ?? 0,
l2GasLeft: overrides?.l2GasLeft ?? 0,
daGasLeft: overrides?.daGasLeft ?? 0,
l1GasLeft: overrides?.l1GasLeft ?? 1e6,
l2GasLeft: overrides?.l2GasLeft ?? 1e6,
daGasLeft: overrides?.daGasLeft ?? 1e6,
});
}

Expand Down
47 changes: 45 additions & 2 deletions yarn-project/simulator/src/avm/opcodes/instruction.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { strict as assert } from 'assert';

import type { AvmContext } from '../avm_context.js';
import { EmptyGasCost, GasCost, GasCosts } from '../avm_gas_cost.js';
import { BufferCursor } from '../serialization/buffer_cursor.js';
import { OperandType, deserialize, serialize } from '../serialization/instruction_serialization.js';
import { Opcode, OperandType, deserialize, serialize } from '../serialization/instruction_serialization.js';

type InstructionConstructor = {
new (...args: any[]): Instruction;
Expand All @@ -14,14 +15,32 @@ type InstructionConstructor = {
* It's most important aspects are execute and (de)serialize.
*/
export abstract class Instruction {
/**
* Consumes gas and executes the instruction.
* This is the main entry point for the instruction.
* @param context - The AvmContext in which the instruction executes.
*/
public run(context: AvmContext): Promise<void> {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm torn about this! But I guess it's ok and it gives us a place to put the PC changes as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Me too. I wanted an "external execute" that did the orchestration and an "internal execute" that had the logic, and let each instruction override either. Problem is I couldn't come up with good names.

context.machineState.consumeGas(this.gasCost());
return this.execute(context);
}

/**
* Loads default gas cost for the instruction from the GasCosts table.
* Instruction sub-classes can override this if their gas cost is not fixed.
*/
public gasCost(): GasCost {
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm will this work? E.g., if the gas cost depends on the inputs for the operation, you might not have enough info until you read from memory in execute (especially if you need to do indirections).

I don't know how fancy gas calculation needs to be, so I'll leave it up to you :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Is there an upper bound we could set for a naive implementation?

In a more sophisticated calculation, could it be two parts? First check that I have enough gas to read from memory, then check that I have enough to perform the op?

Copy link
Contributor

Choose a reason for hiding this comment

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

Is there an upper bound we could set for a naive implementation?
Depends on how precise you want to be. For ADD, for example, there are 3 operands, all of which could be indirect memory. So you'd have an upper bound for mem access as 2*3.

Another example is calldatacopy where you have mem reads but also a size (known at opcode construction time): there you probably want sth like 2*2 + copySize - cdOffset.

However, assume that you have a hashing opcode HASH that takes a mem offset for the start of the message to hash, and a mem offset (!) for the size. Then the size is not known at opcode construction time, it will be retrieved from memory (this is to enable hashes in noir over vectors whose size is not fixed at compile time). Then in this case (and probably only in this case, but you should look around) you cannot know how much you'll read.

In a more sophisticated calculation, could it be two parts?

That separation sounds too add hoc so I'd prefer not. Memory reading is one possible dimension, is there going to be a new one (storage?) and we'll start having 2*dimensions parts? :) If it's going to get too complicated, then maybe it just has to be done in execute().

More generally, should gas cost be possible to know without executing anything at all? Then we cannot have indirect memory addresses. Otherwise if some execution (and revertion) is ok then you could just do the gas calculation as you go.

(I don't have strong opinions on this beyond not having ad-hoc phases)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For the (few) cases where gas cost was intertwined with execution, my plan was to just override run (ie the "external execute") and deal with both simultaneously there. I'll mark gasCost as protected to ensure it's only used internally.

Copy link
Contributor

Choose a reason for hiding this comment

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

More generally, should gas cost be possible to know without executing anything at all?

Sounds like probably not then. I think I'd be fine with having the sequencer perform the work to determine the true amount required. Seems like very bounded risk.

(I don't have strong opinions on this beyond not having ad-hoc phases)

👍

return GasCosts[this.opcode] ?? EmptyGasCost;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we make this fail statically if GasCosts[this.opcode] is undefined?

Copy link
Contributor

Choose a reason for hiding this comment

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

Since GasCosts: Record<Opcode, GasCost>, it should fail statically if GasCosts is missing an Opcode. And since public get opcode(): Opcode, we should be able to safely remove the ?? EmptyGasGost.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yep, @just-mitch you're right!

}

/**
* Execute the instruction.
* Instruction sub-classes must implement this.
* As an AvmContext executes its contract code, it calls this function for
* each instruction until the machine state signals "halted".
* @param context - The AvmContext in which the instruction executes.
*/
public abstract execute(context: AvmContext): Promise<void>;
protected abstract execute(context: AvmContext): Promise<void>;

/**
* Generate a string representation of the instruction including
Expand Down Expand Up @@ -61,4 +80,28 @@ export abstract class Instruction {
const args = res.slice(1); // Remove opcode.
return new this(...args);
}

/**
* Returns the stringified type of the instruction.
* Instruction sub-classes should have a static `type` property.
*/
public get type(): string {
const type = 'type' in this.constructor && (this.constructor.type as string);
if (!type) {
throw new Error(`Instruction class ${this.constructor.name} does not have a static 'type' property defined.`);
}
return type;
}

/**
* Returns the opcode of the instruction.
* Instruction sub-classes should have a static `opcode` property.
*/
public get opcode(): Opcode {
const opcode = 'opcode' in this.constructor ? (this.constructor.opcode as Opcode) : undefined;
if (opcode === undefined || Opcode[opcode] === undefined) {
throw new Error(`Instruction class ${this.constructor.name} does not have a static 'opcode' property defined.`);
}
return opcode;
}
}
4 changes: 3 additions & 1 deletion yarn-project/simulator/src/public/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,15 @@ export class PublicExecutor {
const hostStorage = new HostStorage(this.stateDb, this.contractsDb, this.commitmentsDb);
const worldStateJournal = new AvmPersistableStateManager(hostStorage);
const executionEnv = temporaryCreateAvmExecutionEnvironment(execution, globalVariables);
const machineState = new AvmMachineState(0, 0, 0);
// TODO(@spalladino) Load initial gas from the public execution request
Copy link
Contributor

Choose a reason for hiding this comment

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

Just as an FYI, there are a few other places with hardcoded/unused gas

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks!

const machineState = new AvmMachineState(100_000, 100_000, 100_000);

const context = new AvmContext(worldStateJournal, executionEnv, machineState);
const simulator = new AvmSimulator(context);

const result = await simulator.execute();
const newWorldState = context.persistableState.flush();
// TODO(@spalladino) Read gas left from machineState and return it
return temporaryConvertAvmResults(execution, newWorldState, result);
}

Expand Down
Loading