Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

L2 fee market

Table of Contents

Fee collection

The EXECUTE precompile exposes the coinbase address as an input parameter so that projects can decide by themselves how to collect priority fees on the L2.

While L1 burns the base fee, most L2s in production today decide to redirect it to a dedicated address. This is not possible to be supported by native rollups out of the box, and requires additional changes to the L1 protocol.

One proposal consists in exposing in the block_output the cumulative fee that is burned in the block, both by the effective_gas_fee and the blob_gas_fee. We highlight in green the two additional lines that need to be added to the BlockOutput class and to the process_transaction function to support this feature.

+++ vm/__init__.py
class BlockOutput:
    block_gas_used
    transactions_trie
    receipts_trie
    receipt_keys
    block_logs
    withdrawals_trie
    blob_gas_used
    requests
+   burned_fees
+++ fork.py
def process_transaction(​block_env: ethereum.osaka.vm.BlockEnvironment, ​​block_output: ethereum.osaka.vm.BlockOutput, ​​tx: Transaction, ​​index: Uint​) -> None:
    """
    Execute a transaction against the provided environment.
    This function processes the actions needed to execute a transaction.
    It decrements the sender's account after calculating the gas fee and
    refunds them the proper amount after execution. Calling contracts,
    deploying code, and incrementing nonces are all examples of actions that
    happen within this function or from a call made within this function.
    Accounts that are marked for deletion are processed and destroyed after
    execution.
    Parameters
    ----------
    block_env :
        Environment for the Ethereum Virtual Machine.
    block_output :
        The block output for the current block.
    tx :
        Transaction to execute.
    index:
        Index of the transaction in the block.
    """
    trie_set(
        block_output.transactions_trie,
        rlp.encode(index),
        encode_transaction(tx),
    )
    intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx)
    (
        sender,
        effective_gas_price,
        blob_versioned_hashes,
        tx_blob_gas_used,
    ) = check_transaction(
        block_env=block_env,
        block_output=block_output,
        tx=tx,
    )
    sender_account = get_account(block_env.state, sender)
    if isinstance(tx, BlobTransaction):
        blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx)
    else:
        blob_gas_fee = Uint(0)
    effective_gas_fee = tx.gas * effective_gas_price
    gas = tx.gas - intrinsic_gas
    increment_nonce(block_env.state, sender)
    sender_balance_after_gas_fee = (
        Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee
    )
    set_account_balance(
        block_env.state, sender, U256(sender_balance_after_gas_fee)
    )
    access_list_addresses = set()
    access_list_storage_keys = set()
    access_list_addresses.add(block_env.coinbase)
    if isinstance(
        tx,
        (
            AccessListTransaction,
            FeeMarketTransaction,
            BlobTransaction,
            SetCodeTransaction,
        ),
    ):
        for access in tx.access_list:
            access_list_addresses.add(access.account)
            for slot in access.slots:
                access_list_storage_keys.add((access.account, slot))
    authorizations: Tuple[Authorization, ...] = ()
    if isinstance(tx, SetCodeTransaction):
        authorizations = tx.authorizations
    tx_env = vm.TransactionEnvironment(
        origin=sender,
        gas_price=effective_gas_price,
        gas=gas,
        access_list_addresses=access_list_addresses,
        access_list_storage_keys=access_list_storage_keys,
        transient_storage=TransientStorage(),
        blob_versioned_hashes=blob_versioned_hashes,
        authorizations=authorizations,
        index_in_block=index,
        tx_hash=get_transaction_hash(encode_transaction(tx)),
    )
    message = prepare_message(block_env, tx_env, tx)
    tx_output = process_message_call(message)
    # For EIP-7623 we first calculate the execution_gas_used, which includes
    # the execution gas refund.
    tx_gas_used_before_refund = tx.gas - tx_output.gas_left
    tx_gas_refund = min(
        tx_gas_used_before_refund // Uint(5), Uint(tx_output.refund_counter)
    )
    tx_gas_used_after_refund = tx_gas_used_before_refund - tx_gas_refund
    # Transactions with less execution_gas_used than the floor pay at the
    # floor cost.
    tx_gas_used_after_refund = max(
        tx_gas_used_after_refund, calldata_floor_gas_cost
    )
    tx_gas_left = tx.gas - tx_gas_used_after_refund
    gas_refund_amount = tx_gas_left * effective_gas_price
    # For non-1559 transactions effective_gas_price == tx.gas_price
    priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas
    transaction_fee = tx_gas_used_after_refund * priority_fee_per_gas
    # refund gas
    sender_balance_after_refund = get_account(
        block_env.state, sender
    ).balance + U256(gas_refund_amount)
    set_account_balance(block_env.state, sender, sender_balance_after_refund)
    # transfer miner fees
    coinbase_balance_after_mining_fee = get_account(
        block_env.state, block_env.coinbase
    ).balance + U256(transaction_fee)
    if coinbase_balance_after_mining_fee != 0:
        set_account_balance(
            block_env.state,
            block_env.coinbase,
            coinbase_balance_after_mining_fee,
        )
    elif account_exists_and_is_empty(block_env.state, block_env.coinbase):
        destroy_account(block_env.state, block_env.coinbase)
    for address in tx_output.accounts_to_delete:
        destroy_account(block_env.state, address)
    block_output.block_gas_used += tx_gas_used_after_refund
    block_output.blob_gas_used += tx_blob_gas_used
+   block_output.burned_fees += 
+       effective_gas_fee - gas_refund_amount - transaction_fee + blob_gas_fee
    receipt = make_receipt(
        tx, tx_output.error, block_output.block_gas_used, tx_output.logs
    )
    receipt_key = rlp.encode(Uint(index))
    block_output.receipt_keys += (receipt_key,)
    trie_set(
        block_output.receipts_trie,
        receipt_key,
        receipt,
    )
    block_output.block_logs += tx_output.logs

This value can be exposed as an output of the EXECUTE precompile for projects to decide how to handle it. For example, for those projects whose gas token is bridged through L1, the L1 bridge can decide to credit the burned fees to a dedicated address.

Pricing

WIP.

To be discussed:

  • handling of DA costs in the L2 transactions fee market.