Skip to content

Commit

Permalink
feat(avm): Set gas allowance in public calls (#5567)
Browse files Browse the repository at this point in the history
Allows user to set gas allowance in public calls, defaulting to the gas
left in the current frame.

- Adds the `(L1|L2|DA)GASLEFT` instructions.
- Adds a new `GasOpts` option to the
`(Public|Avm)Context.call_public_function` so the user can specify the
gas allocation for a nested public call (ignored in public context, only
used in avm context). Defaults gas to the result of gasleft, minus a
buffer for L2 compute.
- Updates the noir interface codegen to handle this new argument.
  • Loading branch information
spalladino authored Apr 9, 2024
1 parent 7b88bac commit ee23415
Show file tree
Hide file tree
Showing 36 changed files with 909 additions and 307 deletions.
3 changes: 3 additions & 0 deletions avm-transpiler/src/transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,9 @@ fn handle_getter_instruction(
"avmOpcodeVersion" => AvmOpcode::VERSION,
"avmOpcodeBlockNumber" => AvmOpcode::BLOCKNUMBER,
"avmOpcodeTimestamp" => AvmOpcode::TIMESTAMP,
"avmOpcodeL1GasLeft" => AvmOpcode::L1GASLEFT,
"avmOpcodeL2GasLeft" => AvmOpcode::L2GASLEFT,
"avmOpcodeDaGasLeft" => AvmOpcode::DAGASLEFT,
// "callStackDepth" => AvmOpcode::CallStackDepth,
_ => panic!(
"Transpiler doesn't know how to process ForeignCall function {:?}",
Expand Down
1 change: 1 addition & 0 deletions l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ library Constants {
uint256 internal constant INITIALIZATION_SLOT_SEPARATOR = 1000_000_000;
uint256 internal constant INITIAL_L2_BLOCK_NUM = 1;
uint256 internal constant BLOB_SIZE_IN_BYTES = 126976;
uint256 internal constant NESTED_CALL_L2_GAS_BUFFER = 20000;
uint256 internal constant MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 15000;
uint256 internal constant MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS = 3000;
uint256 internal constant MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS = 3000;
Expand Down
9 changes: 7 additions & 2 deletions noir-projects/aztec-nr/authwit/src/auth.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use dep::aztec::protocol_types::{
abis::function_selector::FunctionSelector, address::AztecAddress,
constants::{GENERATOR_INDEX__AUTHWIT_INNER, GENERATOR_INDEX__AUTHWIT_OUTER}, hash::pedersen_hash
};
use dep::aztec::{context::{PrivateContext, PublicContext, Context}, hash::hash_args_array};
use dep::aztec::{context::{PrivateContext, PublicContext, Context, gas::GasOpts}, hash::hash_args_array};

global IS_VALID_SELECTOR = 0xabf64ad4; // 4 first bytes of keccak256("IS_VALID()")

Expand All @@ -21,7 +21,12 @@ pub fn assert_current_call_valid_authwit(context: &mut PrivateContext, on_behalf
pub fn assert_current_call_valid_authwit_public(context: &mut PublicContext, on_behalf_of: AztecAddress) {
let function_selector = FunctionSelector::from_signature("spend_public_authwit(Field)");
let inner_hash = compute_inner_authwit_hash([context.msg_sender().to_field(), context.selector().to_field(), context.args_hash]);
let result = context.call_public_function(on_behalf_of, function_selector, [inner_hash])[0];
let result = context.call_public_function(
on_behalf_of,
function_selector,
[inner_hash],
GasOpts::default()
)[0];
assert(result == IS_VALID_SELECTOR, "Message not authorized by account");
}
// docs:end:assert_current_call_valid_authwit_public
Expand Down
1 change: 1 addition & 0 deletions noir-projects/aztec-nr/aztec/src/context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod private_context;
mod public_context;
mod avm_context;
mod interface;
mod gas;

use interface::ContextInterface;
use private_context::PrivateContext;
Expand Down
58 changes: 44 additions & 14 deletions noir-projects/aztec-nr/aztec/src/context/avm_context.nr
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use dep::protocol_types::{address::{AztecAddress, EthAddress}, constants::L1_TO_L2_MESSAGE_LENGTH, header::Header};
use dep::protocol_types::{
address::{AztecAddress, EthAddress},
constants::{L1_TO_L2_MESSAGE_LENGTH, NESTED_CALL_L2_GAS_BUFFER, RETURN_VALUES_LENGTH},
header::Header
};
use dep::protocol_types::traits::Serialize;
use dep::protocol_types::abis::function_selector::FunctionSelector;
use dep::protocol_types::abis::public_circuit_public_inputs::PublicCircuitPublicInputs;
use dep::protocol_types::constants::RETURN_VALUES_LENGTH;
use crate::context::inputs::avm_context_inputs::AvmContextInputs;
use crate::context::interface::ContextInterface;
use crate::context::interface::PublicContextInterface;
use crate::context::gas::GasOpts;

struct AvmContext {
inputs: AvmContextInputs,
Expand Down Expand Up @@ -54,22 +58,32 @@ impl AvmContext {

fn call_public_function_raw<ARGS_COUNT, RET_COUNT>(
self: &mut Self,
gas: [Field; 3],
gas: GasOpts,
contract_address: AztecAddress,
temporary_function_selector: Field,
args: [Field; ARGS_COUNT]
) -> ([Field; RET_COUNT], u8) {
call(gas, contract_address, args, temporary_function_selector)
call(
gas_for_call(gas),
contract_address,
args,
temporary_function_selector
)
}

fn static_call_public_function_raw<ARGS_COUNT, RET_COUNT>(
self: &mut Self,
gas: [Field; 3],
gas: GasOpts,
contract_address: AztecAddress,
temporary_function_selector: Field,
args: [Field; ARGS_COUNT]
) -> ([Field; RET_COUNT], u8) {
call_static(gas, contract_address, args, temporary_function_selector)
call_static(
gas_for_call(gas),
contract_address,
args,
temporary_function_selector
)
}
}

Expand Down Expand Up @@ -121,12 +135,11 @@ impl PublicContextInterface for AvmContext {
self: &mut Self,
contract_address: AztecAddress,
temporary_function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
args: [Field; ARGS_COUNT],
gas_opts: GasOpts
) -> [Field; RETURN_VALUES_LENGTH] {
let gas = [/*l1_gas*/10000, /*l2_gas*/10000, /*da_gas*/10000];

let results = call(
gas,
gas_for_call(gas_opts),
contract_address,
args,
temporary_function_selector.to_field()
Expand All @@ -142,12 +155,11 @@ impl PublicContextInterface for AvmContext {
self: &mut Self,
contract_address: AztecAddress,
temporary_function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
args: [Field; ARGS_COUNT],
gas_opts: GasOpts
) -> [Field; RETURN_VALUES_LENGTH] {
let gas = [/*l1_gas*/10000, /*l2_gas*/10000, /*da_gas*/10000];

let (data_to_return, success): ([Field; RETURN_VALUES_LENGTH], u8) = call_static(
gas,
gas_for_call(gas_opts),
contract_address,
args,
temporary_function_selector.to_field()
Expand Down Expand Up @@ -203,6 +215,15 @@ impl ContextInterface for AvmContext {
}
}

// Helper functions
fn gas_for_call(user_gas: GasOpts) -> [Field; 3] {
[
user_gas.l1_gas.unwrap_or_else(|| l1_gas_left()),
user_gas.l2_gas.unwrap_or_else(|| l2_gas_left() - NESTED_CALL_L2_GAS_BUFFER),
user_gas.da_gas.unwrap_or_else(|| da_gas_left())
]
}

// AVM oracles (opcodes) follow, do not use directly.
#[oracle(avmOpcodeAddress)]
fn address() -> AztecAddress {}
Expand Down Expand Up @@ -240,6 +261,15 @@ fn block_number() -> Field {}
#[oracle(avmOpcodeTimestamp)]
fn timestamp() -> u64 {}

#[oracle(avmOpcodeL1GasLeft)]
fn l1_gas_left() -> Field {}

#[oracle(avmOpcodeL2GasLeft)]
fn l2_gas_left() -> Field {}

#[oracle(avmOpcodeDaGasLeft)]
fn da_gas_left() -> Field {}

#[oracle(avmOpcodeNoteHashExists)]
fn note_hash_exists(note_hash: Field, leaf_index: Field) -> u8 {}

Expand Down
15 changes: 15 additions & 0 deletions noir-projects/aztec-nr/aztec/src/context/gas.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
struct GasOpts {
l1_gas: Option<Field>,
l2_gas: Option<Field>,
da_gas: Option<Field>,
}

impl GasOpts {
pub fn default() -> Self {
GasOpts { l1_gas: Option::none(), l2_gas: Option::none(), da_gas: Option::none() }
}

pub fn new(l1_gas: Field, l2_gas: Field, da_gas: Field) -> Self {
GasOpts { l1_gas: Option::some(l1_gas), l2_gas: Option::some(l2_gas), da_gas: Option::some(da_gas) }
}
}
8 changes: 6 additions & 2 deletions noir-projects/aztec-nr/aztec/src/context/interface.nr
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use dep::protocol_types::{
constants::RETURN_VALUES_LENGTH
};

use crate::context::gas::GasOpts;

trait ContextInterface {
fn push_new_note_hash(&mut self, note_hash: Field);
fn push_new_nullifier(&mut self, nullifier: Field, nullified_commitment: Field);
Expand Down Expand Up @@ -34,13 +36,15 @@ trait PublicContextInterface {
self: &mut Self,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
args: [Field; ARGS_COUNT],
gas_opts: GasOpts
) -> [Field; RETURN_VALUES_LENGTH];
fn static_call_public_function<ARGS_COUNT>(
self: &mut Self,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
args: [Field; ARGS_COUNT],
gas_opts: GasOpts
) -> [Field; RETURN_VALUES_LENGTH];
fn delegate_call_public_function<ARGS_COUNT>(
self: &mut Self,
Expand Down
11 changes: 8 additions & 3 deletions noir-projects/aztec-nr/aztec/src/context/public_context.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::{
context::{inputs::PublicContextInputs, interface::ContextInterface, interface::PublicContextInterface},
context::{
inputs::PublicContextInputs, interface::ContextInterface, interface::PublicContextInterface,
gas::GasOpts
},
messaging::process_l1_to_l2_message,
oracle::{arguments, public_call::call_public_function_internal}, hash::hash_args_array
};
Expand Down Expand Up @@ -268,7 +271,8 @@ impl PublicContextInterface for PublicContext {
self: &mut Self,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
args: [Field; ARGS_COUNT],
_gas: GasOpts
) -> [Field; RETURN_VALUES_LENGTH] {
let args_hash = hash_args_array(args);
assert(args_hash == arguments::pack_arguments(args));
Expand All @@ -279,7 +283,8 @@ impl PublicContextInterface for PublicContext {
self: &mut Self,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
args: [Field; ARGS_COUNT],
_gas: GasOpts
) -> [Field; RETURN_VALUES_LENGTH] {
let args_hash = hash_args_array(args);
assert(args_hash == arguments::pack_arguments(args));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ contract AvmTest {
address::{AztecAddress, EthAddress}, constants::L1_TO_L2_MESSAGE_LENGTH,
contract_instance::ContractInstance
};
use dep::aztec::context::gas::GasOpts;
use dep::aztec::oracle::get_contract_instance::{get_contract_instance_avm, get_contract_instance_internal_avm};
use dep::aztec::protocol_types::abis::function_selector::FunctionSelector;
use dep::aztec::protocol_types::traits::ToField;
Expand Down Expand Up @@ -340,12 +341,16 @@ 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*/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]);
let results = context.call_public_function_raw(
GasOpts::default(),
context.this_address(),
selector,
[arg_a, arg_b]
);
let data_to_return: [Field; 1] = results.0;
// this explicit size ^ is necessary to ensure that ret_size is compile-time
// this explicit size is necessary to ensure that ret_size is compile-time
// (ensure the data_to_return is in a HeapArray not a HeapVector)
let success: u8 = results.1;

Expand All @@ -355,14 +360,46 @@ contract AvmTest {
add_result
}

// Directly call the external call opcode to initiate a nested call to the add function with user-specified gas
#[aztec(public-vm)]
fn raw_nested_call_to_add_with_gas(
arg_a: Field,
arg_b: Field,
l1_gas: Field,
l2_gas: Field,
da_gas: Field
) -> pub Field {
let selector = FunctionSelector::from_signature("add_args_return(Field,Field)").to_field();

// Nested call
let results = context.call_public_function_raw(
GasOpts::new(l1_gas, l2_gas, da_gas),
context.this_address(),
selector,
[arg_a, arg_b]
);
let data_to_return: [Field; 1] = results.0;
// this explicit size is necessary to ensure that ret_size is compile-time
// (ensure the data_to_return is in a HeapArray not a HeapVector)
let success: u8 = results.1;

let add_result = data_to_return[0];
add_result
}

// Use the `call_public_function` wrapper to initiate a nested call to the add function
#[aztec(public-vm)]
fn nested_call_to_add(arg_a: Field, arg_b: Field) -> pub Field {
let selector = FunctionSelector::from_signature("add_args_return(Field,Field)");

// Nested call using standard context interface function
let data_to_return: [Field; RETURN_VALUES_LENGTH] = context.call_public_function(context.this_address(), selector, [arg_a, arg_b]);
// this explicit size ^ is necessary to ensure that ret_size is compile-time
let data_to_return: [Field; RETURN_VALUES_LENGTH] = context.call_public_function(
context.this_address(),
selector,
[arg_a, arg_b],
GasOpts::default()
);
// this explicit size in the return variable is necessary to ensure that ret_size is compile-time
// (ensure the data_to_return is in a HeapArray not a HeapVector)

let add_result = data_to_return[0];
Expand All @@ -373,9 +410,13 @@ 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*/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]);
let (result_data, success): ([Field; 1], u8) = context.static_call_public_function_raw(
GasOpts::default(),
context.this_address(),
selector,
[arg_a, arg_b]
);

(result_data[0], success)
}
Expand All @@ -384,10 +425,9 @@ 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*/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);
let (_data_to_return, success): ([Field; 0], u8) = context.static_call_public_function_raw(GasOpts::default(), context.this_address(), selector, calldata);

success
}
Expand All @@ -397,7 +437,12 @@ contract AvmTest {
fn nested_static_call_to_add(arg_a: Field, arg_b: Field) -> pub Field {
let selector = FunctionSelector::from_signature("add_args_return(Field,Field)");

let result_data: [Field; RETURN_VALUES_LENGTH] = context.static_call_public_function(context.this_address(), selector, [arg_a, arg_b]);
let result_data: [Field; RETURN_VALUES_LENGTH] = context.static_call_public_function(
context.this_address(),
selector,
[arg_a, arg_b],
GasOpts::default()
);

result_data[0]
}
Expand All @@ -408,6 +453,6 @@ contract AvmTest {
let selector = FunctionSelector::from_signature("set_storage_single(Field)");
let calldata: [Field; 1] = [20];

let _resultData: [Field; RETURN_VALUES_LENGTH] = context.static_call_public_function(context.this_address(), selector, calldata);
let _resultData: [Field; RETURN_VALUES_LENGTH] = context.static_call_public_function(context.this_address(), selector, calldata, GasOpts::default());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ contract Benchmarking {
};
use dep::value_note::{utils::{increment, decrement}, value_note::ValueNote};

use dep::aztec::{context::Context};
use dep::aztec::{context::{Context, gas::GasOpts}};

#[aztec(storage)]
struct Storage {
Expand Down Expand Up @@ -47,7 +47,8 @@ contract Benchmarking {
let _callStackItem1 = context.call_public_function(
context.this_address(),
FunctionSelector::from_signature("broadcast((Field))"),
[owner.to_field()]
[owner.to_field()],
GasOpts::default()
);
}

Expand Down
Loading

0 comments on commit ee23415

Please sign in to comment.