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

refactor(avm): Introduce fixed gas cost instruction abstraction #5514

Closed
wants to merge 3 commits into from
Closed
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
4 changes: 2 additions & 2 deletions noir-projects/aztec-nr/aztec/src/context/avm_context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ impl PublicContextInterface for AvmContext {
temporary_function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
) -> [Field; RETURN_VALUES_LENGTH] {
let gas = [/*l1_gas*/42, /*l2_gas*/24, /*da_gas*/420];
let gas = [/*l1_gas*/10000, /*l2_gas*/10000, /*da_gas*/10000];

let results = call(
gas,
Expand All @@ -144,7 +144,7 @@ impl PublicContextInterface for AvmContext {
temporary_function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
) -> [Field; RETURN_VALUES_LENGTH] {
let gas = [/*l1_gas*/42, /*l2_gas*/24, /*da_gas*/420];
let gas = [/*l1_gas*/10000, /*l2_gas*/10000, /*da_gas*/10000];

let (data_to_return, success): ([Field; RETURN_VALUES_LENGTH], u8) = call_static(
gas,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ contract AvmTest {
#[aztec(public-vm)]
fn raw_nested_call_to_add(arg_a: Field, arg_b: Field) -> pub Field {
let selector = FunctionSelector::from_signature("add_args_return(Field,Field)").to_field();
let gas = [/*l1_gas*/42, /*l2_gas*/24, /*da_gas*/420];
let gas = [/*l1_gas*/10000, /*l2_gas*/10000, /*da_gas*/10000];

// Nested call
let results = context.call_public_function_raw(gas, context.this_address(), selector, [arg_a, arg_b]);
Expand Down Expand Up @@ -348,7 +348,7 @@ contract AvmTest {
#[aztec(public-vm)]
fn raw_nested_static_call_to_add(arg_a: Field, arg_b: Field) -> pub (Field, u8) {
let selector = FunctionSelector::from_signature("add_args_return(Field,Field)").to_field();
let gas = [/*l1_gas*/42, /*l2_gas*/24, /*da_gas*/420];
let gas = [/*l1_gas*/10000, /*l2_gas*/10000, /*da_gas*/10000];

let (result_data, success): ([Field; 1], u8) = context.static_call_public_function_raw(gas, context.this_address(), selector, [arg_a, arg_b]);

Expand All @@ -359,7 +359,7 @@ contract AvmTest {
#[aztec(public-vm)]
fn raw_nested_static_call_to_set_storage() -> pub u8 {
let selector = FunctionSelector::from_signature("set_storage_single(Field)").to_field();
let gas = [/*l1_gas*/42, /*l2_gas*/24, /*da_gas*/420];
let gas = [/*l1_gas*/10000, /*l2_gas*/10000, /*da_gas*/10000];
let calldata: [Field; 1] = [20];

let (_data_to_return, success): ([Field; 0], u8) = context.static_call_public_function_raw(gas, context.this_address(), selector, calldata);
Expand Down
12 changes: 10 additions & 2 deletions yarn-project/simulator/src/avm/avm_context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ describe('Avm Context', () => {

const newAddress = AztecAddress.random();
const newCalldata = [new Fr(1), new Fr(2)];
const newContext = context.createNestedContractCallContext(newAddress, newCalldata);
const allocatedGas = { l1Gas: 1, l2Gas: 2, daGas: 3 }; // How much of the current call gas we pass to the nested call
const newContext = context.createNestedContractCallContext(newAddress, newCalldata, allocatedGas, 'CALL');

expect(newContext.environment).toEqual(
allSameExcept(context.environment, {
Expand All @@ -23,6 +24,9 @@ describe('Avm Context', () => {
expect(newContext.machineState).toEqual(
allSameExcept(context.machineState, {
pc: 0,
l1GasLeft: 1,
l2GasLeft: 2,
daGasLeft: 3,
}),
);

Expand All @@ -36,7 +40,8 @@ describe('Avm Context', () => {

const newAddress = AztecAddress.random();
const newCalldata = [new Fr(1), new Fr(2)];
const newContext = context.createNestedContractStaticCallContext(newAddress, newCalldata);
const allocatedGas = { l1Gas: 1, l2Gas: 2, daGas: 3 };
const newContext = context.createNestedContractCallContext(newAddress, newCalldata, allocatedGas, 'STATICCALL');

expect(newContext.environment).toEqual(
allSameExcept(context.environment, {
Expand All @@ -50,6 +55,9 @@ describe('Avm Context', () => {
expect(newContext.machineState).toEqual(
allSameExcept(context.machineState, {
pc: 0,
l1GasLeft: 1,
l2GasLeft: 2,
daGasLeft: 3,
}),
);

Expand Down
44 changes: 11 additions & 33 deletions yarn-project/simulator/src/avm/avm_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AztecAddress, FunctionSelector } from '@aztec/circuits.js';
import { Fr } from '@aztec/foundation/fields';

import { AvmExecutionEnvironment } from './avm_execution_environment.js';
import { Gas, gasToGasLeft } from './avm_gas.js';
import { AvmMachineState } from './avm_machine_state.js';
import { AvmPersistableStateManager } from './journal/journal.js';

Expand Down Expand Up @@ -33,47 +34,24 @@ export class AvmContext {
*
* @param address - The contract instance to initialize a context for
* @param calldata - Data/arguments for nested call
* @param allocatedGas - Gas allocated for the nested call
* @param callType - Type of call (CALL or STATICCALL)
* @returns new AvmContext instance
*/
public createNestedContractCallContext(
address: AztecAddress,
calldata: Fr[],
allocatedGas: Gas,
callType: 'CALL' | 'STATICCALL',
temporaryFunctionSelector: FunctionSelector = FunctionSelector.empty(),
): AvmContext {
const newExecutionEnvironment = this.environment.deriveEnvironmentForNestedCall(
address,
calldata,
temporaryFunctionSelector,
);
const deriveFn =
callType === 'CALL'
? this.environment.deriveEnvironmentForNestedCall
: this.environment.deriveEnvironmentForNestedStaticCall;
const newExecutionEnvironment = deriveFn.call(this.environment, address, calldata, temporaryFunctionSelector);
const forkedWorldState = this.persistableState.fork();
const machineState = AvmMachineState.fromState(this.machineState);
return new AvmContext(forkedWorldState, newExecutionEnvironment, machineState);
}

/**
* Prepare a new AVM context that will be ready for an external/nested static call
* - Fork the world state journal
* - Derive a machine state from the current state
* - E.g., gas metering is preserved but pc is reset
* - Derive an execution environment from the caller/parent
* - Alter both address and storageAddress
*
* @param address - The contract instance to initialize a context for
* @param calldata - Data/arguments for nested call
* @returns new AvmContext instance
*/
public createNestedContractStaticCallContext(
address: AztecAddress,
calldata: Fr[],
temporaryFunctionSelector: FunctionSelector = FunctionSelector.empty(),
): AvmContext {
const newExecutionEnvironment = this.environment.deriveEnvironmentForNestedStaticCall(
address,
calldata,
temporaryFunctionSelector,
);
const forkedWorldState = this.persistableState.fork();
const machineState = AvmMachineState.fromState(this.machineState);
const machineState = AvmMachineState.fromState(gasToGasLeft(allocatedGas));
return new AvmContext(forkedWorldState, newExecutionEnvironment, machineState);
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
import { TypeTag } from './avm_memory_types.js';
import { InstructionExecutionError } from './errors.js';
import { Addressing, AddressingMode } from './opcodes/addressing_mode.js';
import { Opcode } from './serialization/instruction_serialization.js';

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

/** Maps a Gas struct to gasLeft properties. */
export function gasToGasLeft(gas: Gas) {
return { l1GasLeft: gas.l1Gas, l2GasLeft: gas.l2Gas, daGasLeft: gas.daGas };
}

/** Maps gasLeft properties to a gas struct. */
export function gasLeftToGas(gasLeft: { l1GasLeft: number; l2GasLeft: number; daGasLeft: number }) {
return { l1Gas: gasLeft.l1GasLeft, l2Gas: gasLeft.l2GasLeft, daGas: gasLeft.daGasLeft };
}

/** Creates a new instance with all values set to zero except the ones set. */
export function makeGasCost(gasCost: Partial<GasCost>) {
return { ...EmptyGasCost, ...gasCost };
export function makeGasCost(gasCost: Partial<Gas>) {
return { ...EmptyGas, ...gasCost };
}

/** Sums together multiple instances of Gas. */
export function sumGas(...gases: Partial<Gas>[]) {
return gases.reduce(
(acc: Gas, gas) => ({
l1Gas: acc.l1Gas + (gas.l1Gas ?? 0),
l2Gas: acc.l2Gas + (gas.l2Gas ?? 0),
daGas: acc.daGas + (gas.daGas ?? 0),
}),
EmptyGas,
);
}

/** Gas cost of zero across all gas dimensions. */
export const EmptyGasCost = {
/** Zero gas across all gas dimensions. */
export const EmptyGas: Gas = {
l1Gas: 0,
l2Gas: 0,
daGas: 0,
Expand Down Expand Up @@ -102,12 +126,29 @@ export const GasCosts = {
[Opcode.PEDERSEN]: TemporaryDefaultGasCost, // temp - may be removed, but alot of contracts rely on i: TemporaryDefaultGasCost,t
} as const;

/** Returns the fixed gas cost for a given opcode, or throws if set to dynamic. */
export function getFixedGasCost(opcode: Opcode): Gas {
const cost = GasCosts[opcode];
if (cost === DynamicGasCost) {
throw new Error(`Opcode ${Opcode[opcode]} has dynamic gas cost`);
}
return cost;
}

/** Returns the additional cost from indirect accesses to memory. */
export function getCostFromIndirectAccess(indirect: number): Partial<Gas> {
const indirectCount = Addressing.fromWire(indirect).modePerOperand.filter(
mode => mode === AddressingMode.INDIRECT,
).length;
return { l2Gas: indirectCount * GasCostConstants.COST_PER_INDIRECT_ACCESS };
}

/** Constants used in base cost calculations. */
export const GasCostConstants = {
SET_COST_PER_BYTE: 100,
CALLDATACOPY_COST_PER_BYTE: 10,
ARITHMETIC_COST_PER_BYTE: 10,
ARITHMETIC_COST_PER_INDIRECT_ACCESS: 5,
COST_PER_INDIRECT_ACCESS: 5,
};

/** Returns a multiplier based on the size of the type represented by the tag. Throws on uninitialized or invalid. */
Expand All @@ -127,6 +168,6 @@ export function getGasCostMultiplierFromTypeTag(tag: TypeTag) {
return 32;
case TypeTag.INVALID:
case TypeTag.UNINITIALIZED:
throw new Error(`Invalid tag type for gas cost multiplier: ${TypeTag[tag]}`);
throw new InstructionExecutionError(`Invalid tag type for gas cost multiplier: ${TypeTag[tag]}`);
}
}
11 changes: 9 additions & 2 deletions yarn-project/simulator/src/avm/avm_machine_state.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Fr } from '@aztec/circuits.js';

import { GasCost, GasDimensions } from './avm_gas_cost.js';
import { Gas, GasDimensions } from './avm_gas.js';
import { TaggedMemory } from './avm_memory_types.js';
import { AvmContractCallResults } from './avm_message_call_result.js';
import { OutOfGasError } from './errors.js';
Expand Down Expand Up @@ -59,7 +59,7 @@ export class AvmMachineState {
* 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>) {
public consumeGas(gasCost: Partial<Gas>) {
// Assert there is enough gas on every dimension.
const outOfGasDimensions = GasDimensions.filter(
dimension => this[`${dimension}Left`] - (gasCost[dimension] ?? 0) < 0,
Expand All @@ -76,6 +76,13 @@ export class AvmMachineState {
}
}

/** Increases the gas left by the amounts specified. */
public refundGas(gasRefund: Partial<Gas>) {
for (const dimension of GasDimensions) {
this[`${dimension}Left`] += gasRefund[dimension] ?? 0;
}
}

/**
* Most instructions just increment PC before they complete
*/
Expand Down
5 changes: 5 additions & 0 deletions yarn-project/simulator/src/avm/avm_memory_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export abstract class MemoryValue {
return new Fr(this.toBigInt());
}

// To number. Throws if exceeds max safe int.
public toNumber(): number {
return this.toFr().toNumber();
}

public toString(): string {
return `${this.constructor.name}(0x${this.toBigInt().toString(16)})`;
}
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/simulator/src/avm/avm_simulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class AvmSimulator {
// Execute the instruction.
// Normal returns and reverts will return normally here.
// "Exceptional halts" will throw.
await instruction.run(this.context);
await instruction.execute(this.context);

if (this.context.machineState.pc >= instructions.length) {
this.log('Passed end of program!');
Expand Down
Loading
Loading