Predeploys
Table of Contents
- Overview
- FeeVault
- Fee Vaults (SequencerFeeVault, L1FeeVault, BaseFeeVault, OperatorFeeVault)
- FeeSplitter
- Security Considerations
Overview
| Name | Address | Deprecated | Proxied |
|---|---|---|---|
| FeeSplitter | 0x420000000000000000000000000000000000002B | No | Yes |
The FeeSplitter predeploy manages the distribution of all L2 fees. Fee vault contracts (OperatorFeeVault,
BaseFeeVault, L1FeeVault, and SequencerFeeVault) update their configuration via setter functions for
minimum withdrawal amounts, withdrawal networks, and recipients without requiring new deployments.
Using the FeeSplitter requires vaults to use WithdrawalNetwork.L2 and set the FeeSplitter as their
fee recipient. Chain operators may opt-in at any time or they can continue using other solutions.
Disburse Fees Flow
sequenceDiagram
participant OperatorFeeVault
participant L1FeeVault
participant BaseFeeVault
participant SequencerFeeVault
participant FeeSplitter
actor Caller
participant ISharesCalculator
participant Recipient
Caller ->> FeeSplitter: 1) disburseFees()
Note over FeeSplitter: Check interval and vault config <br> (WithdrawalNetwork=L2, Recipient=FeeSplitter). <br/>Only withdraw if enough fees collected.
FeeSplitter ->> SequencerFeeVault: 2) withdraw()
SequencerFeeVault -->> FeeSplitter:
FeeSplitter ->> BaseFeeVault: 3) withdraw()
BaseFeeVault -->> FeeSplitter:
FeeSplitter ->> L1FeeVault: 4) withdraw()
L1FeeVault -->> FeeSplitter:
FeeSplitter ->> OperatorFeeVault: 5) withdraw()
OperatorFeeVault -->> FeeSplitter:
Note over FeeSplitter: If any fees were collected, calculate the share and <br> remaining amounts to transfer based on the rates.
FeeSplitter ->> ISharesCalculator: 6) getRecipientsAndAmounts(per-vault revenue)
ISharesCalculator -->> FeeSplitter: ShareInfo[]
loop For each ShareInfo
FeeSplitter ->> Recipient: 7) send(shareInfo.amount)
end
FeeVault
Legacy uppercase getters (e.g., MIN_WITHDRAWAL_AMOUNT(), RECIPIENT(), WITHDRAWAL_NETWORK()) are preserved for
backward compatibility but simply return the values from the storage variables.
The withdraw function returns the value that was withdrawn from the vault at the time of the function call.
Functions
setMinWithdrawalAmount
Updates the minimum amount of funds the vault contract must hold before they can be withdrawn.
function setMinWithdrawalAmount(uint256 _newMinWithdrawalAmount) external
- MUST only be callable by
ProxyAdmin.owner() - MUST emit the
MinWithdrawalAmountUpdatedevent - MUST update the
_minWithdrawalAmountstorage variable
setRecipient
Updates the recipient of sequencer fees when they are withdrawn from the vault.
function setRecipient(address _newRecipient) external
- MUST only be callable by
ProxyAdmin.owner() - MUST emit the
RecipientUpdatedevent - MUST update the
_recipientstorage variable
setWithdrawalNetwork
Updates the network to which the vault collected fees will be withdrawn.
This can be either WithdrawalNetwork.L1 to withdraw them to an address on L1 by using the L2ToL1MessagePasser
predeploy, or WithdrawalNetwork.L2 to withdraw them to an address on the same chain.
function setWithdrawalNetwork(WithdrawalNetwork _newWithdrawalNetwork) external
- MUST only be callable by
ProxyAdmin.owner() - MUST emit the
WithdrawalNetworkUpdatedevent - MUST update the
_withdrawalNetworkstorage variable
recipient
Returns the current recipient address from storage.
function recipient() external view returns (address)
- MUST return the recipient address from storage.
minWithdrawalAmount
Returns the current minimum withdrawal amount from storage.
function minWithdrawalAmount() external view returns (uint256)
- MUST return the minimum withdrawal amount from storage.
withdrawalNetwork
Returns the current withdrawal network from storage.
function withdrawalNetwork() external view returns (WithdrawalNetwork)
- MUST return the withdrawal network from storage.
withdraw
Withdraws the funds stored in the vault to the recipient. If the recipient is an address on L2, a SafeCall.send
is performed; if it's an address on L1, IL2ToL1MessagePasser.initiateWithdrawal is used.
Returns the value of the withdrawal.
function withdraw() external returns(uint256)
- MUST revert if the vault's balance is below
minWithdrawalAmount(). - MUST increment
totalProcessedby the balance the vault had at the time of the function call. - MUST emit the
Withdrawal(uint256 value, address to, address from)event. - MUST emit the
Withdrawal(uint256 value, address to, address from, WithdrawalNetwork withdrawalNetwork)event. - MUST send the vault's total balance to the appropriate recipient.
Events
MinWithdrawalAmountUpdated
Emitted when the minimum withdrawal amount for the vault is updated.
event MinWithdrawalAmountUpdated(uint256 oldWithdrawalAmount, uint256 newWithdrawalAmount)
RecipientUpdated
Emitted when the fee recipient for this vault is updated.
event RecipientUpdated(address oldRecipient, address newRecipient)
WithdrawalNetworkUpdated
Emitted when the withdrawal network for this vault is updated.
event WithdrawalNetworkUpdated(WithdrawalNetwork oldWithdrawalNetwork, WithdrawalNetwork newWithdrawalNetwork)
Invariants
- Only
ProxyAdmin.owner()is allowed to call the setter functions. - If using the
FeeSplitter, the withdrawal network MUST be set toWithdrawalNetwork.L2and the recipient MUST be set to theFeeSplitterpredeploy address. - The balance of the vault MUST be preserved between implementation upgrades.
- On successful
withdraw()execution, it MUST withdraw the entire balance.
Fee Vaults (SequencerFeeVault, L1FeeVault, BaseFeeVault, OperatorFeeVault)
These contracts inherit the changes made to the FeeVault contract, meaning that they have storage
variables and setters instead of constants for the configuration values, and they are initializable.
Their configuration includes the withdrawal network and the recipient to which the fees are sent:
- WithdrawalNetwork.L1: Funds are withdrawn to an L1 address (default behavior)
- WithdrawalNetwork.L2: Funds are withdrawn to an L2 address
For those chains that choose to use the FeeSplitter predeploy, WithdrawalNetwork.L2 as the withdrawal
network, and the FeeSplitter as the recipient MUST be set using the setter functions.
FeeSplitter
This contract splits the funds it receives from the vaults using a configured ISharesCalculator compatible
revenue shares calculator to determine which addresses should receive funds and in what amounts
by querying ISharesCalculator.getRecipientsAndAmounts:
struct ShareInfo {
address payable recipient;
uint256 amount;
}
function getRecipientsAndAmounts(
uint256 _sequencerFeeVaultBalance,
uint256 _baseFeeVaultBalance,
uint256 _operatorFeeVaultBalance,
uint256 _l1FeeVaultBalance)
external
view
returns (ShareInfo[] memory shareInfo);
The ShareInfo[] array returned represents pairs: a recipient address to receive the funds and an amount of funds
it should receive; a default SuperchainRevSharesCalculator
implementation of this interface is provided.
The FeeSplitter integrates with the fee vault system by configuring each Fee Vault to use WithdrawalNetwork.L2
and setting this predeploy as the recipient in every fee vault.
The FeeSplitter MUST be proxied and initializable only by the ProxyAdmin.owner().
Functions
initialize
Initializes the contract with the shares calculator and sets disbursement interval to 1 days as default.
function initialize(
ISharesCalculator _sharesCalculator
) external
- MUST only be callable once.
- MUST set
sharesCalculatorto_sharesCalculator. - MUST set
feeDisbursementIntervalto1 days. - MUST set
lastDisbursementTimetoblock.timestamp. - Emits an
Initialized(uint8 version)event via OpenZeppelin'sinitializermodifier.
disburseFees
Initiates the routing flow by withdrawing the fees that each of the fee vaults has collected and sends the shares to the appropriate addresses according to the amounts returned by the set shares calculator.
When attempting to withdraw from the vaults, it checks that the withdrawal network is set to WithdrawalNetwork.L2,
and that the recipient of the vault is the FeeSplitter. It MUST revert if any of these conditions is not met.
It MUST only withdraw if the vault balance is greater than or equal to its minimum withdrawal amount.
In addition, it follows a nonReentrant pattern using TSTOREd flags, to avoid receiving balance back
once the fees are being disbursed.
function disburseFees() external
- MUST revert if not enough time has passed since the last successful execution.
- MUST revert if any vault has a recipient different from this contract.
- MUST revert if any vault has a withdrawal network different from
WithdrawalNetwork.L2. - MUST revert if total fees collected are 0.
- It MUST set the
lastDisbursementTimeto the current block timestamp. - MUST emit
FeesDisbursedevent if the funds were disbursed.
receive
Receives funds from any of the FeeVaults if and only if the disbursing process is in progress, and reverts
otherwise. This is enforced using transient storage flags.
receive() external payable
- MUST revert if the disbursing process is not in progress.
- MUST accept funds from the
BaseFeeVault,L1FeeVault,SequencerFeeVaultandOperatorFeeVaultonly. - MUST emit a
FeesReceivedevent upon successful execution.
setSharesCalculator
Sets the address of the calculator used to partition the fees.
function setSharesCalculator(ISharesCalculator _newSharesCalculator) external
- MUST only be callable by
ProxyAdmin.owner() - MUST emit a
SharesCalculatorUpdatedevent upon successful execution. - MUST update the
sharesCalculatorstorage variable.
setFeeDisbursementInterval
Sets the minimum time, in seconds, that must pass between consecutive calls to disburseFees.
function setFeeDisbursementInterval(uint128 _newInterval) external
- MUST only be callable by
ProxyAdmin.owner() - MUST emit a
FeeDisbursementIntervalUpdatedevent upon successful execution. - MUST update the
feeDisbursementIntervalstorage variable.
Events
FeesDisbursed
Emitted when fees are successfully withdrawn from fee vaults and distributed to recipients.
event FeesDisbursed(ShareInfo[] shareInfo, uint256 grossRevenue)
FeesReceived
Emitted when the contract receives funds.
event FeesReceived(address indexed sender, uint256 amount)
FeeDisbursementIntervalUpdated
Emitted when the minimum time interval between consecutive fee disbursements is successfully updated.
event FeeDisbursementIntervalUpdated(uint128 oldFeeDisbursementInterval, uint128 newFeeDisbursementInterval)
SharesCalculatorUpdated
Emitted when the shares calculator is updated.
event SharesCalculatorUpdated(address oldSharesCalculator, address newSharesCalculator)
Security Considerations
- Given that vault recipients can now be updated, it's important to ensure that this can only be done by the
appropriate address, namely
ProxyAdmin.owner().