Skip to content

Commit

Permalink
feat: Inject fee payment update in base rollup (#6403)
Browse files Browse the repository at this point in the history
Implements the [fee payment update
spec](https://hackmd.io/HXPDMgHxRFGmt856JKRfPg) by charging the user's
fee in the base rollup circuit. Involves the following changes:

- Transactions now have a set of `protocol-public-data-writes`, which
are data writes triggered by the protocol and not the user. We populate
this in the base rollup circuit to charge the `fee_payer`. Note that if
no `fee_payer` is sent, the tx goes through without paying (this will
change once we enforce fees).
- The base rollup circuit now needs a public data read hint to be able
to read the `fee_payer`'s current balance.
- The `GasToken` contract now has no `pay_fee` function anymore. The
`pay_fee` functions in the FPC are now renamed to `pay_refund`
- The `GasToken` address is now a constant, since the base rollup
circuit needs to know it to derive the slot for users balances. Because
of this, the `portal_address` is removed from the `GasToken`
constructor, so the address of the GasToken does not depend on its L1
counterpart, which would have made deployments more cumbersome.
- Added an `ACVM_FORCE_WASM` flag to tests to force using acvm wasm
instead of binary, since the binary failed to report the assertion
failed messages.
- The sequencer now validates that the `fee_payer` (if set) has enough
balance to pay for the tx before picking it up, assuming it consumes all
its gas limits.

Coming up on future PRs:
- Reenable the native fee payment method and set up a non-bench test for
it
- Handle fee payments and gas token claims on the same tx
(#6562)
  • Loading branch information
spalladino authored May 22, 2024
1 parent f912ba3 commit 4991188
Show file tree
Hide file tree
Showing 73 changed files with 928 additions and 422 deletions.
2 changes: 1 addition & 1 deletion docs/docs/protocol-specs/constants.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ The statically-sized nature the kernel & rollup circuits will restrict the quant
| `MAX_NEW_NOTE_HASHES_PER_TX` | 128 |
| `MAX_NEW_NULLIFIERS_PER_TX` | 128 |
| `MAX_NEW_L2_TO_L1_MSGS_PER_TX` | 16 |
| `MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX` | 16 |
| `MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX` | 31 |
| `MAX_PUBLIC_DATA_READS_PER_TX` | 16 |
| `MAX_UNENCRYPTED_LOG_HASHES_PER_TX` | 128 |
| `MAX_ENCRYPTED_LOG_HASHES_PER_TX` | 128 |
Expand Down
10 changes: 7 additions & 3 deletions l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ library Constants {
uint256 internal constant MAX_NEW_NULLIFIERS_PER_TX = 64;
uint256 internal constant MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX = 8;
uint256 internal constant MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX = 32;
uint256 internal constant MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX = 32;
uint256 internal constant MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX = 31;
uint256 internal constant PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX = 1;
uint256 internal constant MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX = 32;
uint256 internal constant MAX_PUBLIC_DATA_READS_PER_TX = 32;
uint256 internal constant MAX_NEW_L2_TO_L1_MSGS_PER_TX = 2;
uint256 internal constant MAX_NOTE_HASH_READ_REQUESTS_PER_TX = 128;
Expand Down Expand Up @@ -89,8 +91,6 @@ library Constants {
0xe7af816635466f128568edb04c9fa024f6c87fb9010fdbffa68b3d99;
uint256 internal constant DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE =
0x85864497636cf755ae7bde03f267ce01a520981c21c3682aaf82a631;
uint256 internal constant DEPLOYER_CONTRACT_ADDRESS =
0x2e9c386f07e22a1d24e677ab70407b2dd0adbc7cafb9c822bf249685d6a2e4cc;
uint256 internal constant DEFAULT_GAS_LIMIT = 1_000_000_000;
uint256 internal constant DEFAULT_TEARDOWN_GAS_LIMIT = 100_000_000;
uint256 internal constant DEFAULT_MAX_FEE_PER_GAS = 10;
Expand All @@ -100,6 +100,10 @@ library Constants {
uint256 internal constant FIXED_DA_GAS = 512;
uint256 internal constant CANONICAL_KEY_REGISTRY_ADDRESS =
0x1585e564a60e6ec974bc151b62705292ebfc75c33341986a47fd9749cedb567e;
uint256 internal constant DEPLOYER_CONTRACT_ADDRESS =
0x2e9c386f07e22a1d24e677ab70407b2dd0adbc7cafb9c822bf249685d6a2e4cc;
uint256 internal constant GAS_TOKEN_ADDRESS =
0x283353eafd3802181f7c1461a4b68332afc6c04e95097bc51b5458d8a8abf4f9;
uint256 internal constant AZTEC_ADDRESS_LENGTH = 1;
uint256 internal constant GAS_FEES_LENGTH = 2;
uint256 internal constant GAS_LENGTH = 2;
Expand Down
8 changes: 3 additions & 5 deletions noir-projects/aztec-nr/aztec/src/keys/getters.nr
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
use dep::protocol_types::{
abis::key_validation_request::KeyValidationRequest, address::AztecAddress,
constants::CANONICAL_KEY_REGISTRY_ADDRESS, grumpkin_point::GrumpkinPoint
constants::CANONICAL_KEY_REGISTRY_ADDRESS, grumpkin_point::GrumpkinPoint,
storage::map::derive_storage_slot_in_map
};
use crate::{
context::PrivateContext,
oracle::{keys::get_public_keys_and_partial_address, key_validation_request::get_key_validation_request},
keys::{public_keys::PublicKeys, constants::{NULLIFIER_INDEX, INCOMING_INDEX}},
state_vars::{
map::derive_storage_slot_in_map,
shared_mutable::shared_mutable_private_getter::SharedMutablePrivateGetter
}
state_vars::{shared_mutable::shared_mutable_private_getter::SharedMutablePrivateGetter}
};

global DELAY = 5;
Expand Down
6 changes: 1 addition & 5 deletions noir-projects/aztec-nr/aztec/src/state_vars/map.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use dep::protocol_types::{hash::pedersen_hash, traits::ToField};
use dep::protocol_types::{hash::pedersen_hash, storage::map::derive_storage_slot_in_map, traits::ToField};
use crate::state_vars::storage::Storage;

// docs:start:map
Expand Down Expand Up @@ -33,7 +33,3 @@ impl<K, V, Context> Map<K, V, Context> {
}
// docs:end:at
}

pub fn derive_storage_slot_in_map<K>(storage_slot: Field, key: K) -> Field where K: ToField {
pedersen_hash([storage_slot, key.to_field()], 0)
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,9 @@ contract AppSubscription {
storage.subscriptions.at(user_address).replace(&mut note, true, subscriber_ivpk_m);

context.set_as_fee_payer();
let gas_limit = storage.gas_token_limit_per_tx.read_private();
context.set_public_teardown_function(
storage.gas_token_address.read_private(),
FunctionSelector::from_signature("pay_fee(Field)"),
[gas_limit]
);

// TODO(palla/gas) Assert gas_token_limit_per_tx is less than this tx gas_limit
let _gas_limit = storage.gas_token_limit_per_tx.read_private();

context.end_setup();

Expand Down

This file was deleted.

10 changes: 10 additions & 0 deletions noir-projects/noir-contracts/contracts/fpc_contract/src/lib.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use dep::aztec::context::interface::PublicContextInterface;

pub fn compute_rebate<TPublicContext>(
context: TPublicContext,
initial_amount: Field
) -> Field where TPublicContext: PublicContextInterface {
let actual_fee = context.transaction_fee();
assert(!initial_amount.lt(actual_fee), "Initial amount paid to the paymaster does not cover actual fee");
initial_amount - actual_fee
}
30 changes: 17 additions & 13 deletions noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
mod lib;

contract FPC {
use dep::aztec::protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress, traits::is_empty};
use dep::aztec::state_vars::SharedImmutable;
use dep::aztec::{
protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress, traits::is_empty},
state_vars::SharedImmutable, context::{interface::PublicContextInterface, gas::GasOpts}
};
use dep::token::Token;
use dep::gas_token::GasToken;
use dep::aztec::context::gas::GasOpts;
use crate::lib::compute_rebate;

#[aztec(storage)]
struct Storage {
Expand All @@ -24,10 +28,10 @@ contract FPC {
Token::at(asset).unshield(context.msg_sender(), context.this_address(), amount, nonce).call(&mut context);
context.set_as_fee_payer();
// Would like to get back to
// FPC::at(context.this_address()).pay_fee_with_shielded_rebate(amount, asset, secret_hash).set_public_teardown_function(&mut context);
// FPC::at(context.this_address()).pay_refund_with_shielded_rebate(amount, asset, secret_hash).set_public_teardown_function(&mut context);
context.set_public_teardown_function(
context.this_address(),
FunctionSelector::from_signature("pay_fee_with_shielded_rebate(Field,(Field),Field)"),
FunctionSelector::from_signature("pay_refund_with_shielded_rebate(Field,(Field),Field)"),
[amount, asset.to_field(), secret_hash]
);
}
Expand All @@ -37,34 +41,34 @@ contract FPC {
FPC::at(context.this_address()).prepare_fee(context.msg_sender(), amount, asset, nonce).enqueue(&mut context);
context.set_as_fee_payer();
// TODO(#6277) for improving interface:
// FPC::at(context.this_address()).pay_fee(context.msg_sender(), amount, asset).set_public_teardown_function(&mut context);
// FPC::at(context.this_address()).pay_refund(context.msg_sender(), amount, asset).set_public_teardown_function(&mut context);
context.set_public_teardown_function(
context.this_address(),
FunctionSelector::from_signature("pay_fee((Field),Field,(Field))"),
FunctionSelector::from_signature("pay_refund((Field),Field,(Field))"),
[context.msg_sender().to_field(), amount, asset.to_field()]
);
}

#[aztec(public)]
#[aztec(internal)]
fn prepare_fee(from: AztecAddress, amount: Field, asset: AztecAddress, nonce: Field) {
// docs:start:public_call
Token::at(asset).transfer_public(from, context.this_address(), amount, nonce).call(&mut context);
// docs:end:public_call
}

#[aztec(public)]
#[aztec(internal)]
fn pay_fee(refund_address: AztecAddress, amount: Field, asset: AztecAddress) {
// docs:start:public_call
let refund = GasToken::at(storage.gas_token_address.read_public()).pay_fee(amount).call(&mut context);
// docs:end:public_call
fn pay_refund(refund_address: AztecAddress, amount: Field, asset: AztecAddress) {
// Just do public refunds for the present
let refund = compute_rebate(context, amount);
Token::at(asset).transfer_public(context.this_address(), refund_address, refund, 0).call(&mut context);
}

#[aztec(public)]
#[aztec(internal)]
fn pay_fee_with_shielded_rebate(amount: Field, asset: AztecAddress, secret_hash: Field) {
let refund = GasToken::at(storage.gas_token_address.read_public()).pay_fee(amount).call(&mut context);
fn pay_refund_with_shielded_rebate(amount: Field, asset: AztecAddress, secret_hash: Field) {
let refund = compute_rebate(context, amount);
Token::at(asset).shield(context.this_address(), refund, secret_hash, 0).call(&mut context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,29 @@ contract GasToken {

#[aztec(storage)]
struct Storage {
// This map is accessed directly by protocol circuits to check balances for fee payment.
// Do not change this storage layout unless you also update the base rollup circuits.
balances: Map<AztecAddress, PublicMutable<U128>>,
portal_address: SharedImmutable<EthAddress>,
}

// We purposefully not set this function as an initializer so we do not bind
// the contract to a specific L1 portal address, since the gas token address
// is a hardcoded constant in the rollup circuits.
#[aztec(public)]
#[aztec(initializer)]
fn constructor(portal_address: EthAddress) {
fn set_portal(portal_address: EthAddress) {
assert(storage.portal_address.read_public().is_zero());
storage.portal_address.initialize(portal_address);
}

#[aztec(public)]
fn claim_public(to: AztecAddress, amount: Field, secret: Field, leaf_index: Field) {
let content_hash = get_bridge_gas_msg_hash(to, amount);
let portal_address = storage.portal_address.read_public();
assert(!portal_address.is_zero());

// Consume message and emit nullifier
context.consume_l1_to_l2_message(
content_hash,
secret,
storage.portal_address.read_public(),
leaf_index
);
context.consume_l1_to_l2_message(content_hash, secret, portal_address, leaf_index);

let new_balance = storage.balances.at(to).read() + U128::from_integer(amount);
storage.balances.at(to).write(new_balance);
Expand All @@ -51,24 +53,6 @@ contract GasToken {
assert(storage.balances.at(context.msg_sender()).read() >= fee_limit, "Balance too low");
}

#[aztec(public)]
fn pay_fee(fee_limit: Field) -> Field {
let fee_limit_u128 = U128::from_integer(fee_limit);
let fee = U128::from_integer(calculate_fee(context));
// TODO(6252): implement debug logging in AVM
//dep::aztec::oracle::debug_log::debug_log_format(
// "Gas token: paying fee {0} (limit {1})",
// [fee.to_field(), fee_limit]
//);
assert(fee <= fee_limit_u128, "Fee too high");

let sender_new_balance = storage.balances.at(context.msg_sender()).read() - fee;
storage.balances.at(context.msg_sender()).write(sender_new_balance);

let rebate = fee_limit_u128 - fee;
rebate.to_field()
}

// utility function for testing
unconstrained fn balance_of_public(owner: AztecAddress) -> pub Field {
storage.balances.at(owner).read().to_field()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ contract Test {
abis::private_circuit_public_inputs::PrivateCircuitPublicInputs,
constants::{MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, CANONICAL_KEY_REGISTRY_ADDRESS},
traits::{Serialize, ToField, FromField}, grumpkin_point::GrumpkinPoint,
grumpkin_private_key::GrumpkinPrivateKey
grumpkin_private_key::GrumpkinPrivateKey, storage::map::derive_storage_slot_in_map
};

use dep::aztec::encrypted_logs::header::EncryptedLogHeader;
Expand All @@ -21,7 +21,7 @@ contract Test {

use dep::aztec::note::constants::MAX_NOTES_PER_PAGE;

use dep::aztec::state_vars::{shared_mutable::SharedMutablePrivateGetter, map::derive_storage_slot_in_map};
use dep::aztec::state_vars::{shared_mutable::SharedMutablePrivateGetter};

use dep::aztec::{
keys::getters::{get_npk_m, get_ivpk_m, get_npk_m_hash},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use dep::types::{
hash::{silo_note_hash, silo_nullifier, compute_l2_to_l1_hash, accumulate_sha256},
utils::{arrays::{array_length, array_to_bounded_vec}}, traits::{is_empty, is_empty_array}
};
use crate::hash::{compute_public_data_tree_index, compute_public_data_tree_value};

// Validates inputs to the kernel circuit that are common to all invocation scenarios.
pub fn validate_inputs(public_call: PublicCallData) {
Expand Down Expand Up @@ -370,11 +369,7 @@ fn propagate_valid_public_data_update_requests(
for i in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL {
let update_request = update_requests[i];
if (!update_request.is_empty()) {
let public_data_update_request = PublicDataUpdateRequest {
leaf_slot: compute_public_data_tree_index(contract_address, update_request.storage_slot),
new_value: compute_public_data_tree_value(update_request.new_value)
};

let public_data_update_request = PublicDataUpdateRequest::from_contract_storage_update_request(contract_address, update_request);
public_data_update_requests.push(public_data_update_request);
}
}
Expand All @@ -393,10 +388,7 @@ fn propagate_valid_non_revertible_public_data_update_requests(
for i in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL {
let update_request = update_requests[i];
if (!update_request.is_empty()) {
let public_data_update_request = PublicDataUpdateRequest {
leaf_slot: compute_public_data_tree_index(contract_address, update_request.storage_slot),
new_value: compute_public_data_tree_value(update_request.new_value)
};
let public_data_update_request = PublicDataUpdateRequest::from_contract_storage_update_request(contract_address, update_request);

public_data_update_requests.push(public_data_update_request);
}
Expand All @@ -413,10 +405,7 @@ fn propagate_valid_public_data_reads(public_call: PublicCallData, circuit_output
for i in 0..MAX_PUBLIC_DATA_READS_PER_CALL {
let read_request: StorageRead = read_requests[i];
if !read_request.is_empty() {
let public_data_read = PublicDataRead {
leaf_slot: compute_public_data_tree_index(contract_address, read_request.storage_slot),
value: compute_public_data_tree_value(read_request.current_value)
};
let public_data_read = PublicDataRead::from_contract_storage_read(contract_address, read_request);
public_data_reads.push(public_data_read);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// TODO: rename to be precise as to what its common to (the public kernel circuits).
mod common;

mod hash;
mod utils;

mod public_kernel_setup;
Expand Down
Loading

0 comments on commit 4991188

Please sign in to comment.