I wrote lots of related ideas in this ‘discussion’ hackmd which we chatted about last month.
https://hackmd.io/gs1Q_fOSRpyJWG3Sa_cwDQ#Escrow-the-max-fee-then-refund-unused-gas
Here’s the detail pasted (some of the thinking around paying L1 gas separately is slightly outdated).
It includes a discussion on refunds, and a discussion on private refunds, by ‘completing’ token commitments.
# Gas limits and gas prices.
A proposal
Here’s a simple proposal for spcifying gas info, which copies the Ethereum model:
Each tx that a user submits will contain the following fields:
{
aztec_gas_limit: 50000,
aztec_gas_price: 10, // Currency dictated by price_currency_id
eth_gas_limit: 100000,
eth_gas_price: 10, // NOT NECESSARILY MEASURED IN GWEI!!!
// Currency dictated by price_currency_id
// These allow kernel circuits to handle different choices:
pay_fee_from_private_l2: true,
pay_fee_from_public_l2: false,
pay_fee_from_l1: false,
signed_fee_tx: {
contract_address,
function_signature,
arguments,
// other stuff
signature,
}
}
- The user specifies a separate
aztec_gas_limit
and eth_gas_limit
, since eth_gas
and aztec_gas
measure different kinds of computation.
- The user specifies a corresponding
aztec_gas_price
and eth_gas_price
, as a way of specifying the amount they’re willing to pay for each unit of gas.
- NOTICE: the
eth_gas_price
is not measured in traditional gwei:
- Since we want users to be able to pay fees in any currency, we allow the user to specify the price in any currency.
- The
signed_fee_tx
contains all the information the Sequencer needs to infer the currency in which they’ll be paid per unit of gas.
- The Sequencer can check current exchange rates to determine whether these parameters will cover their cost of compute - both on Ethereum and inside the rollup.
- The Kernel & Rollup circuits will be able read the specified
aztec_gas_limit
values to determine whether the user hasn’t specified a high enough gas limit.
- The Kernel & Rollup circuits will be able to measure the actual aztec-gas used, by the tx, which can be used to deduct an exact amount (of whichever currency the user is paying in) from the user’s balance. I.e. we’ll need to deduct
aztec_gas_used * aztec_gas_price
from the user’s balance (by calling the relevant fee-paying contract).
But giving some Kernel (or Rollup) circuit permission to deduct value from someone’s balance within a custom token contract is a hard problem to solve…
Subtracting gas_used from someone’s balance
In Ethereum, the EVM has special permission to decrement a user’s ETH balance based on the eth_gas * eth_gas_price
(measured in gwei) that their tx consumes. ETH is special in this regard, since such decrementation permissions (restricted to the party producing the block) are baked into the protocol. The protocol does the following:
- Check the user’s ETH balances is
>= eth_gas_price * eth_gas_limit
.
- Execute the tx.
- Subtract
eth_gas_price * eth_gas_used
from the user’s ETH balance.
- Transfer some of that ETH to the validator after burning some ETH in line with EIP-1559.
For us to support something similar for any asset type is more complicated. Different L2 token contracts might have different interfaces, depending on whether they’re for public or private tokens, and especially in the early days of the network when standard interfaces for tokens won’t yet have been agreed. In particular, a standard which requires token contracts to somehow give special permissions to the current sequencer’s address, so that they may decrement others’ balances under certain conditions sounds pretty sketchy.
Escrow the max fee, then refund unused gas
Instead of permitting a sequencer to decrement balances, we could require users to lock up a max fee in escrow (up to their chosen gas limit) before sending their txs. Sequencers could then refund any unspent gas after the amount of actual gas used has been measured.
We could do this with some standardised “Special Sequencer Escrow Contracts” - one escrow contract for each fungible token standard that exists on the network.
- The user would send
aztec_max_fee = aztec_gas_limit * aztec_gas_price
to the appropriate escrow contract, which would be stored against an identifier for that tx, and an identifier for the currency being paid (i.e. the token contract’s address).
- The sequencer would measure the
aztec_gas_used
of each tx in their rollup and collate this info. The info would be linked to a public input of the root rollup proof. - The sequencer would then be forced to append a special set of txs to the end of the rollup, which would call each escrow contract (there might be many such contracts if there are multiple fungible token standards on the network) to refund users.
- The escrow contract would refund the users for any unused gas; an amount
(aztec_gas_limit - aztec_gas_used) * aztec_gas_price
. (See below for how to do this with private tokens).
- The escrow contract would then allow the sequencer to withdraw all remaining tokens in the escrow contract.
Refunding private tokens, without learning user IDs
- If fees are paid in Private L2 tokens, we don’t want the Sequencer to know who the user is, and so can’t as easily refund a particular address. Here’s what we could do:
- The user executes a fee-paying tx which creates a token commitment of value
aztec_gas_limit * aztec_gas_price
- i.e. the maximum they’re willing to pay for their tx.
- The user also submits a “partial commitment” (commitments would have to be additively homomorphic, like Pedersen), commiting to their address and a salt.
- Once the Sequencer has executed the user’s tx and the
aztec_gas_used
is known, the protocol could force the Sequencer to pay a gas refund through a special escrow contract, by ‘completing’ the user’s partial commitment, by ‘adding’ a value equal to: (aztec_gas_limit - aztec_gas_used) * aztec_gas_price
.
I.e.:
uint32_t max_fee = gas_limit * gas_price;
// Might need more info in practice, but this is just illustrative.
// A, B, C, D are elliptic curve points.
Point max_fee_commitment = (contract_address * A) + (max_fee * B) +
(owner * C) + (salt_1 * D);
Point refund_partial_commitment = (contract_address * A) +
(owner * C) + (salt_2 * D);
uint32_t refund_amount = (gas_limit - gas_used) * gas_price;
Point refund_commitment = (refund_amount * B) + refund_partial_commitment;
A single fee-paying token
A lot of the above headaches around standardising token interfaces - so that a Sequencer may be constrained to subtract-from or refund value back to users in a consistent way - disappear if we have a single token through which aztec-gas fees must be paid.
Imagine all aztec-gas must be paid through a single token; let’s call it the “Aztec Token”. Then this Aztec Token’s contract could be designed in such a way that the Sequencer could indeed charge exact aztec-gas fees to users. The Kernel and Rollup circuits could be designed based on that one interface.
But would such an Aztec Token be public or private? Indeed, we know such an Aztec Token might be needed as part of the sequencer-selection protocol, regardless of whether there’s a canonical fee-paying token…
(I’ll address some answers to topics raised by messages above, in a separate message in this thread)