diff --git a/specs/SUMMARY.md b/specs/SUMMARY.md
index 182e9a37a..69b2d33e7 100644
--- a/specs/SUMMARY.md
+++ b/specs/SUMMARY.md
@@ -55,8 +55,13 @@
- [Execution Engine](./protocol/holocene/exec-engine.md)
- [System Config](./protocol/holocene/system-config.md)
- [Isthmus](./protocol/isthmus/overview.md)
+ - [Configurability](./protocol/isthmus/configurability.md)
+ - [Derivation](./protocol/isthmus/derivation.md)
- [Execution Engine](./protocol/isthmus/exec-engine.md)
+ - [L1 Attributes](./protocol/isthmus/l1-attributes.md)
+ - [Predeploys](./protocol/isthmus/predeploys.md)
- [Superchain Config](./protocol/isthmus/superchain-config.md)
+ - [System Config](./protocol/isthmus/system-config.md)
- [Governance]()
- [Governance Token](./governance/gov-token.md)
- [Experimental]()
diff --git a/specs/protocol/configurability.md b/specs/protocol/configurability.md
index 267cb3521..9ba1a1d4a 100644
--- a/specs/protocol/configurability.md
+++ b/specs/protocol/configurability.md
@@ -30,6 +30,8 @@
- [Start block](#start-block)
- [Superchain target](#superchain-target)
- [Governance Token](#governance-token)
+ - [Operator Fee Scalar](#operator-fee-scalar)
+ - [Operator Fee Constant](#operator-fee-constant)
- [Resource Config](#resource-config)
- [Policy Parameters](#policy-parameters)
- [Data Availability Type](#data-availability-type)
@@ -47,6 +49,7 @@
- [Guardian address](#guardian-address)
- [Proposer address](#proposer-address)
- [Sequencer P2P / Unsafe head signer](#sequencer-p2p--unsafe-head-signer)
+ - [Operator Fee Manager](#operator-fee-manager)
@@ -283,6 +286,21 @@ contracts deployed on layer 1.
**Requirement:** Disabled
**Notes:** Simple clear restriction.
+### [Operator Fee Scalar](exec-engine.md#operator-fees)
+
+**Description:** Operator fee scalar -- used to calculate the operator fee
+**Administrator:** [Operator Fee Manager](#operator-fee-manager)
+**Requirement:** 0
+
+### [Operator Fee Constant](exec-engine.md#operator-fees)
+
+**Description:** Operator fee constant -- used to calculate the operator fee
+**Administrator:** [Operator Fee Manager](#operator-fee-manager)
+**Requirement:** 0
+
+Note that the operator fee scalar and constant are primarily used for non-standard configurations,
+like op-succinct, so their standard values are 0.
+
[^chain-id-uniqueness]: The chain ID must be globally unique among all EVM chains.
### Resource Config
@@ -441,4 +459,11 @@ configuration of the permissioned dispute game.
**Requirement:** No requirement
**Notes:**
+### Operator Fee Manager
+
+**Description:** Account authorized to modify the operator fee scalar.
+**Administrator:** [System Config Owner](#admin-roles)
+**Requirement:** `address(0)`
+**Notes:** For standard configurations, the operator fee manager is not used, so it is set to the null address.
+
[^of-gnosis-safe-l1]: 5 of 7 GnosisSafe controlled by Optimism Foundation (OF). Mainnet and Sepolia addresses can be found at [privileged roles](https://docs.optimism.io/chain/security/privileged-roles).
diff --git a/specs/protocol/isthmus/configurability.md b/specs/protocol/isthmus/configurability.md
index ed16365e9..63e56e39f 100644
--- a/specs/protocol/isthmus/configurability.md
+++ b/specs/protocol/isthmus/configurability.md
@@ -10,11 +10,17 @@
- [`SystemConfig`](#systemconfig)
- [`ConfigUpdate`](#configupdate)
- [Initialization](#initialization)
+ - [Modifying Operator Fee Parameters](#modifying-operator-fee-parameters)
- [Interface](#interface)
+ - [Operator fee parameters](#operator-fee-parameters)
+ - [`operatorFeeScalar`](#operatorfeescalar)
+ - [`operatorFeeConstant`](#operatorfeeconstant)
+ - [`setOperatorFeeScalars`](#setoperatorfeescalars)
- [Fee Vault Config](#fee-vault-config)
- [`setBaseFeeVaultConfig`](#setbasefeevaultconfig)
- [`setL1FeeVaultConfig`](#setl1feevaultconfig)
- [`setSequencerFeeVaultConfig`](#setsequencerfeevaultconfig)
+ - [`setOperatorFeeVaultConfig`](#setoperatorfeevaultconfig)
- [`OptimismPortal`](#optimismportal)
- [Interface](#interface-1)
- [`setConfig`](#setconfig)
@@ -25,7 +31,7 @@
## Overview
The `SystemConfig` and `OptimismPortal` are updated with a new flow for chain
-configurability.
+configurability. A new service role `OperatorFeeManager` is added to manage the operator fee collection.
## Constants
@@ -39,10 +45,11 @@ The `ConfigType` enum represents configuration that can be modified.
| `BASE_FEE_VAULT_CONFIG` | `uint8(1)` | Sets the Fee Vault Config for the `BaseFeeVault` |
| `L1_FEE_VAULT_CONFIG` | `uint8(2)` | Sets the Fee Vault Config for the `L1FeeVault` |
| `SEQUENCER_FEE_VAULT_CONFIG` | `uint8(3)` | Sets the Fee Vault Config for the `SequencerFeeVault` |
-| `L1_CROSS_DOMAIN_MESSENGER_ADDRESS` | `uint8(4)` | Sets the `L1CrossDomainMessenger` address |
-| `L1_ERC_721_BRIDGE_ADDRESS` | `uint8(5)` | Sets the `L1ERC721Bridge` address |
-| `L1_STANDARD_BRIDGE_ADDRESS` | `uint8(6)` | Sets the `L1StandardBridge` address |
-| `REMOTE_CHAIN_ID` | `uint8(7)` | Sets the chain id of the base chain |
+| `OPERATOR_FEE_VAULT_CONFIG` | `uint8(4)` | Sets the Fee Vault Config for the `OperatorFeeVault` |
+| `L1_CROSS_DOMAIN_MESSENGER_ADDRESS` | `uint8(5)` | Sets the `L1CrossDomainMessenger` address |
+| `L1_ERC_721_BRIDGE_ADDRESS` | `uint8(6)` | Sets the `L1ERC721Bridge` address |
+| `L1_STANDARD_BRIDGE_ADDRESS` | `uint8(7)` | Sets the `L1StandardBridge` address |
+| `REMOTE_CHAIN_ID` | `uint8(8)` | Sets the chain id of the base chain |
## `SystemConfig`
@@ -57,6 +64,7 @@ The following `ConfigUpdate` event is defined where the `CONFIG_VERSION` is `uin
| `GAS_LIMIT` | `uint8(2)` | `abi.encode(uint64 _gasLimit)` | Modifies the L2 gas limit |
| `UNSAFE_BLOCK_SIGNER` | `uint8(3)` | `abi.encode(address)` | Modifies the account that is authorized to progress the unsafe chain |
| `EIP_1559_PARAMS` | `uint8(4)` | `uint256(uint64(uint32(_denominator))) << 32 \| uint64(uint32(_elasticity))` | Modifies the EIP-1559 denominator and elasticity |
+| `OPERATOR_FEE_PARAMS` | `uint8(5)` | `uint256(_operatorFeeScalar) << 64 \| _operatorFeeConstant` | Modifies the operator fee parameters |
### Initialization
@@ -67,10 +75,12 @@ The following actions should happen during the initialization of the `SystemConf
- `emit ConfigUpdate.GAS_LIMIT`
- `emit ConfigUpdate.UNSAFE_BLOCK_SIGNER`
- `emit ConfigUpdate.EIP_1559_PARAMS`
+- `emit ConfigUpdate.OPERATOR_FEE_PARAMS`
- `setConfig(SET_GAS_PAYING_TOKEN)`
- `setConfig(SET_BASE_FEE_VAULT_CONFIG)`
- `setConfig(SET_L1_FEE_VAULT_CONFIG)`
- `setConfig(SET_SEQUENCER_FEE_VAULT_CONFIG)`
+- `setConfig(SET_OPERATOR_FEE_VAULT_CONFIG)`
- `setConfig(SET_L1_CROSS_DOMAIN_MESSENGER_ADDRESS)`
- `setConfig(SET_L1_ERC_721_BRIDGE_ADDRESS)`
- `setConfig(SET_L1_STANDARD_BRIDGE_ADDRESS)`
@@ -78,8 +88,46 @@ The following actions should happen during the initialization of the `SystemConf
These actions MAY only be triggered if there is a diff to the value.
+Since the `OperatorFeeVault` is new in Isthmus, the `setConfig(SET_OPERATOR_FEE_VAULT_CONFIG)` MUST be emitted.
+
+`ConfigUpdate.OPERATOR_FEE_PARAMS` MAY be emitted. If it is not emitted, the `operatorFeeScalar` and
+`operatorFeeConstant` are set to 0 by default.
+
+### Modifying Operator Fee Parameters
+
+A new `SystemConfig` `UpdateType` is introduced that enables the modification of
+the `operatorFeeScalar` and `operatorFeeConstant` by the [`OperatorFeeManager`](../configurability.md#operator-fee-manager).
+
### Interface
+#### Operator fee parameters
+
+##### `operatorFeeScalar`
+
+This function returns the currently configured operator fee scalar.
+
+```solidity
+function operatorFeeScalar()(uint32)
+```
+
+##### `operatorFeeConstant`
+
+This function returns the currently configured operator fee constant.
+
+```solidity
+function operatorFeeConstant()(uint64)
+```
+
+##### `setOperatorFeeScalars`
+
+This function sets the `operatorFeeScalar` and `operatorFeeConstant`.
+
+This function MUST only be callable by the [`OperatorFeeManager`](../configurability.md#operator-fee-manager).
+
+```solidity
+function setOperatorFeeScalar(uint32 _operatorFeeScalar, uint64 _operatorFeeConstant)
+```
+
#### Fee Vault Config
For each `FeeVault`, there is a setter for its config. The arguments to the setter include
@@ -106,6 +154,12 @@ function setL1FeeVaultConfig(address,uint256,WithdrawalNetwork)
function setSequencerFeeVaultConfig(address,uint256,WithdrawalNetwork)
```
+##### `setOperatorFeeVaultConfig`
+
+```solidity
+function setOperatorFeeVaultConfig(address,uint256,WithdrawalNetwork)
+```
+
## `OptimismPortal`
The `OptimismPortal` is updated to emit a special system `TransactionDeposited` event.
diff --git a/specs/protocol/isthmus/derivation.md b/specs/protocol/isthmus/derivation.md
new file mode 100644
index 000000000..d5e8c0c01
--- /dev/null
+++ b/specs/protocol/isthmus/derivation.md
@@ -0,0 +1,25 @@
+# Isthmus L2 Chain Derivation Changes
+
+
+
+**Table of Contents**
+
+- [Network upgrade automation transactions](#network-upgrade-automation-transactions)
+
+
+
+# Network upgrade automation transactions
+
+The Isthmus hardfork activation block contains the following transactions, in this order:
+
+- L1 Attributes Transaction
+- User deposits from L1
+- Network Upgrade Transactions
+ - L1Block deployment
+ - Update L1Block Proxy ERC-1967 Implementation
+ - L1Block Enable Isthmus
+ - GasPriceOracle deployment
+ - Update GasPriceOracle Proxy ERC-1967 Implementation
+ - GasPriceOracle Enable Isthmus
+ - OptimismMintableERC20Factory deployment
+ - Update OptimismMintableERC20Factory Proxy ERC-1967 Implementation
diff --git a/specs/protocol/isthmus/exec-engine.md b/specs/protocol/isthmus/exec-engine.md
index e1eaf905a..0e99a74fc 100644
--- a/specs/protocol/isthmus/exec-engine.md
+++ b/specs/protocol/isthmus/exec-engine.md
@@ -21,6 +21,10 @@
- [Engine API Updates](#engine-api-updates)
- [Update to `ExecutableData`](#update-to-executabledata)
- [`engine_newPayloadV3` API](#engine_newpayloadv3-api)
+- [Fees](#fees)
+ - [Operator Fee](#operator-fee)
+ - [Configuring Parameters](#configuring-parameters)
+ - [Fee Vaults](#fee-vaults)
@@ -141,3 +145,42 @@ Withdrawals list in the block body is encoded as an empty RLP list.
Post Isthmus, `engine_newPayloadV3` will be used with the additional `ExecutionPayload` attribute. This attribute
is omitted prior to Isthmus.
+
+## Fees
+
+New OP stack variants have different resource consumption patterns, and thus require a more flexible
+pricing model. To enable more customizable fee structures, Isthmus adds a new component to the fee
+calculation: the `operatorFee`, which is parameterized by two scalars: the `operatorFeeScalar`
+and the `operatorFeeConstant`.
+
+### Operator Fee
+
+The operator fee, is set as follows:
+
+`operatorFee = (gasUsed * operatorFeeScalar / 1e6) + operatorFeeConstant`
+
+Where:
+
+- `gasUsed` is amount of gas used by the transaction.
+- `operatorFeeScalar` is a `uint32` scalar set by the chain operator, scaled by `1e6`.
+- `operatorFeeConstant` is a `uint64` scalar set by the chain operator.
+
+#### Configuring Parameters
+
+`operatorFeeScalar` and `operatorFeeConstant` are loaded in a similar way to the `baseFeeScalar` and
+`blobBaseFeeScalar` used in the [`L1Fee`](../../protocol/exec-engine.md#ecotone-l1-cost-fee-changes-eip-4844-da).
+calculation. In more detail, these parameters can be accessed in two interchangable ways.
+
+- read from the deposited L1 attributes (`operatorFeeScalar` and `operatorFeeConstant`) of the current L2 block
+- read from the L1 Block Info contract (`0x4200000000000000000000000000000000000015`)
+ - using the respective solidity getter functions (`operatorFeeScalar`, `operatorFeeConstant`)
+ - using direct storage-reads:
+ - Operator fee scalar as big-endian `uint32` in slot `8` at offset `0`.
+ - Operator fee constant as big-endian `uint64` in slot `8` at offset `4`.
+
+### Fee Vaults
+
+These collected fees are sent to a new vault for the `operatorFee`: the [`OperatorFeeVault`](predeploys.md#operatorfeevault).
+
+Like the existing vaults, this is a hardcoded address, pointing at a pre-deployed proxy contract.
+The proxy is backed by a vault contract deployment, based on `FeeVault`, to route vault funds to L1 securely.
diff --git a/specs/protocol/isthmus/l1-attributes.md b/specs/protocol/isthmus/l1-attributes.md
new file mode 100644
index 000000000..1f1e0e559
--- /dev/null
+++ b/specs/protocol/isthmus/l1-attributes.md
@@ -0,0 +1,40 @@
+# L1 Block Attributes
+
+
+
+**Table of Contents**
+
+- [Overview](#overview)
+
+
+
+## Overview
+
+The L1 block attributes transaction is updated to include the operator fee parameters.
+
+| Input arg | Type | Calldata bytes | Segment |
+| ----------------- | ------- | -------------- | ------- |
+| {0x098999be} | | 0-3 | n/a |
+| baseFeeScalar | uint32 | 4-7 | 1 |
+| blobBaseFeeScalar | uint32 | 8-11 | |
+| sequenceNumber | uint64 | 12-19 | |
+| l1BlockTimestamp | uint64 | 20-27 | |
+| l1BlockNumber | uint64 | 28-35 | |
+| basefee | uint256 | 36-67 | 2 |
+| blobBaseFee | uint256 | 68-99 | 3 |
+| l1BlockHash | bytes32 | 100-131 | 4 |
+| batcherHash | bytes32 | 132-163 | 5 |
+| operatorFeeScalar | uint32 | 164-167 | 6 |
+| operatorFeeConstant | uint64 | 168-175 | |
+
+Note that the first input argument, in the same pattern as previous versions of the L1 attributes transaction,
+is the function selector: the first four bytes of `keccak256("setL1BlockValuesIsthmus()")`.
+
+In the first L2 block after the Isthmus activation block, the Isthmus L1 attributes are first used.
+
+The pre-Isthmus values are migrated over 1:1.
+Blocks after the Isthmus activation block contain all pre-Isthmus values 1:1,
+and also set the following new attributes:
+
+- The `operatorFeeScalar` is set to `0`.
+- The `operatorFeeConstant` is set to `0`.
diff --git a/specs/protocol/isthmus/overview.md b/specs/protocol/isthmus/overview.md
index 90c286eb2..776747ab1 100644
--- a/specs/protocol/isthmus/overview.md
+++ b/specs/protocol/isthmus/overview.md
@@ -15,9 +15,16 @@ This document is not finalized and should be considered experimental.
## Execution Layer
- [L2ToL1MessagePasser Storage Root in Header](./exec-engine.md##l2tol1messagepasser-storage-root-in-header)
+- [Operator Fee](./exec-engine.md#operator-fee)
## Consensus Layer
+- [Isthmus Derivation](./derivation.md#isthmus-derivation)
+- [Configurability](./configurability.md)
+
## Smart Contracts
- [SuperchainConfig](./superchain-config.md)
+- [Predeploys](./predeploys.md)
+- [L1 Block Attributes](./l1-attributes.md)
+- [System Config](./system-config.md)
diff --git a/specs/protocol/isthmus/predeploys.md b/specs/protocol/isthmus/predeploys.md
index 51cd6ed58..81930df04 100644
--- a/specs/protocol/isthmus/predeploys.md
+++ b/specs/protocol/isthmus/predeploys.md
@@ -17,12 +17,17 @@
- [FeeVault](#feevault)
- [Interface](#interface-1)
- [`config`](#config)
+ - [OperatorFeeVault](#operatorfeevault)
- [L2CrossDomainMessenger](#l2crossdomainmessenger)
- [Interface](#interface-2)
- [L2ERC721Bridge](#l2erc721bridge)
- [Interface](#interface-3)
- [L2StandardBridge](#l2standardbridge)
- [Interface](#interface-4)
+ - [GasPriceOracle](#gaspriceoracle)
+ - [Interface](#interface-5)
+ - [`setIsthmus`](#setisthmus-1)
+ - [`getOperatorFee`](#getoperatorfee)
- [OptimismMintableERC721Factory](#optimismmintableerc721factory)
- [Security Considerations](#security-considerations)
- [GovernanceToken](#governancetoken)
@@ -46,6 +51,7 @@ of the `SystemConfig`.
| `BASE_FEE_VAULT_CONFIG` | `bytes32(uint256(keccak256("opstack.basefeevaultconfig")) - 1)` | The Fee Vault Config for the `BaseFeeVault` |
| `L1_FEE_VAULT_CONFIG` | `bytes32(uint256(keccak256("opstack.l1feevaultconfig")) - 1)` | The Fee Vault Config for the `L1FeeVault` |
| `SEQUENCER_FEE_VAULT_CONFIG` | `bytes32(uint256(keccak256("opstack.sequencerfeevaultconfig")) - 1)` | The Fee Vault Config for the `SequencerFeeVault` |
+| `OPERATOR_FEE_VAULT_CONFIG` | `bytes32(uint256(keccak256("opstack.operatorfeevaultconfig")) - 1)` | The Fee Vault Config for the `OperatorFeeVault` |
| `L1_CROSS_DOMAIN_MESSENGER_ADDRESS` | `bytes32(uint256(keccak256("opstack.l1crossdomainmessengeraddress")) - 1)` | `abi.encode(address(L1CrossDomainMessengerProxy))` |
| `L1_ERC_721_BRIDGE_ADDRESS` | `bytes32(uint256(keccak256("opstack.l1erc721bridgeaddress")) - 1)` | `abi.encode(address(L1ERC721BridgeProxy))` |
| `L1_STANDARD_BRIDGE_ADDRESS` | `bytes32(uint256(keccak256("opstack.l1standardbridgeaddress")) - 1)` | `abi.encode(address(L1StandardBridgeProxy))` |
@@ -67,6 +73,7 @@ graph LR
BaseFeeVault -- "getConfig(ConfigType.GAS_PAYING_TOKEN)(address,uint256,uint8)" --> L1Block
SequencerFeeVault -- "getConfig(ConfigType.SEQUENCER_FEE_VAULT_CONFIG)(address,uint256,uint8)" --> L1Block
L1FeeVault -- "getConfig(ConfigType.L1_FEE_VAULT_CONFIG)(address,uint256,uint8)" --> L1Block
+ OperatorFeeVault -- "getConfig(ConfigType.OPERATOR_FEE_VAULT_CONFIG)(address,uint256,uint8)" --> L1Block
L2CrossDomainMessenger -- "getConfig(ConfigType.L1_CROSS_DOMAIN_MESSENGER_ADDRESS)(address)" --> L1Block
L2StandardBridge -- "getConfig(ConfigType.L1_STANDARD_BRIDGE_ADDRESS)(address)" --> L1Block
L2ERC721Bridge -- "getConfig(ConfigType.L1_ERC721_BRIDGE_ADDRESS)(address)" --> L1Block
@@ -98,6 +105,7 @@ The following storage slots are defined:
- `BASE_FEE_VAULT_CONFIG`
- `L1_FEE_VAULT_CONFIG`
- `SEQUENCER_FEE_VAULT_CONFIG`
+- `OPERATOR_FEE_VAULT_CONFIG`
- `L1_CROSS_DOMAIN_MESSENGER_ADDRESS`
- `L1_ERC_721_BRIDGE_ADDRESS`
- `L1_STANDARD_BRIDGE_ADDRESS`
@@ -140,7 +148,8 @@ The caller needs to ABI decode the data into the desired type.
### FeeVault
-The following changes apply to each of the `BaseFeeVault`, the `L1FeeVault` and the `SequencerFeeVault`.
+The following changes apply to each of the `BaseFeeVault`, the `L1FeeVault` the `SequencerFeeVault`, and the new
+`OperatorFeeVault`.
#### Interface
@@ -156,6 +165,7 @@ The following functions are updated to read from the `L1Block` contract:
| `BaseFeeVault` | `L1Block.getConfig(ConfigType.BASE_FEE_VAULT_CONFIG)` |
| `SequencerFeeVault` | `L1Block.getConfig(ConfigType.SEQUENCER_FEE_VAULT_CONFIG)` |
| `L1FeeVault` | `L1Block.getConfig(ConfigType.L1_FEE_VAULT_CONFIG)` |
+| `OperatorFeeVault` | `L1Block.getConfig(ConfigType.OPERATOR_FEE_VAULT_CONFIG)` |
##### `config`
@@ -165,6 +175,13 @@ A new function is added to fetch the full Fee Vault Config.
function config()(address,uint256,WithdrawalNetwork)
```
+### OperatorFeeVault
+
+This vault implements `FeeVault`, like `BaseFeeVault`, `SequencerFeeVault`, and `L1FeeVault`. No special logic is
+needed in order to insert or withdraw funds.
+
+Its address will be `0x420000000000000000000000000000000000001b`.
+
### L2CrossDomainMessenger
#### Interface
@@ -192,6 +209,40 @@ The following functions are updated to read from the `L1Block` contract by calli
- `otherBridge()(address)`
- `OTHER_BRIDGE()(address)`
+### GasPriceOracle
+
+In order to maintain accurate offchain fee estimation, the `GasPriceOracle` must be updated to allow users
+to estimate the operator fee. We also add a new boolean `isIsthmus` to help with evaluating the operator fee.
+
+#### Interface
+
+##### `setIsthmus`
+
+This function is meant to be called once on the activation block of the isthmus network upgrade.
+It MUST only be callable by the `DEPOSITOR_ACCOUNT` once. When it is called, it MUST call
+call each getter for the network specific config and set the returndata into storage.
+
+```solidity
+function setIsthmus() external;
+```
+
+##### `getOperatorFee`
+
+This function calculates the operator fee based on the expected amount of gas used for a certain transaction.
+
+It uses the following values:
+
+- `operatorFeeScalar`
+- `operatorFeeConstant`
+- `isIsthmus`
+
+`operatorFeeScalar` and `operatorFeeConstant` are read from the `L1Block` contract, and `isIsthmus`
+is read directly from storage. If `isIsthmus` is false, then this function MUST return `0`.
+
+```solidity
+function getOperatorFee(uint256 gasUsed)(uint256)
+```
+
### OptimismMintableERC721Factory
The chain id is no longer read from storage but instead is read from the `L1Block` contract by calling
diff --git a/specs/protocol/isthmus/system-config.md b/specs/protocol/isthmus/system-config.md
new file mode 100644
index 000000000..000e85b23
--- /dev/null
+++ b/specs/protocol/isthmus/system-config.md
@@ -0,0 +1,29 @@
+# System Config
+
+
+
+**Table of Contents**
+
+- [Overview](#overview)
+ - [`Roles`](#roles)
+
+
+
+## Overview
+
+The `SystemConfig` is updated to set a new role: the `OPERATOR_FEE_MANAGER`. This role is set once upon
+initialization and can only be set again with another call to `initialize`.
+
+### `Roles`
+
+The `Roles` struct is updated to include the new `OPERATOR_FEE_MANAGER` role.
+
+```solidity
+struct Roles {
+ address owner;
+ address feeAdmin;
+ address operatorFeeManager; // new role
+}
+```
+
+The operator may only change the `operatorFeeManager` role with a call to `initialize`.