|
| 1 | +# Purpose |
| 2 | + |
| 3 | +<!-- This section is also sometimes called “Motivations” or “Goals”. --> |
| 4 | + |
| 5 | +<!-- It is fine to remove this section from the final document, |
| 6 | +but understanding the purpose of the doc when writing is very helpful. --> |
| 7 | + |
| 8 | +# Summary |
| 9 | + |
| 10 | +<!-- Most (if not all) documents should have a summary. |
| 11 | +While the length will likely be proportional to the length of the full document, |
| 12 | +the summary should be as succinct as possible. --> |
| 13 | + |
| 14 | +The L2 predeploys are refactored in a way such that the network specific configuration |
| 15 | +is all sourced from a single location where it is ultimately set from L1 deposit transactions. |
| 16 | +Any initializable logic in the L2 predeploys is also removed, to make the deposit transaction |
| 17 | +based upgrade scheme simple to reason about. |
| 18 | + |
| 19 | +This will accelerate the ability to ship secure software, as we will be able to get chains |
| 20 | +on the same versions of the software and know which versions work very well together. |
| 21 | + |
| 22 | +# Problem Statement + Context |
| 23 | + |
| 24 | +<!-- Describe the specific problem that the document is seeking to address as well |
| 25 | +as information needed to understand the problem and design space. |
| 26 | +If more information is needed on the costs of the problem, |
| 27 | +this is a good place to that information. --> |
| 28 | + |
| 29 | +There is currently no good way to do releases of L2 predeploys. Historically, chains |
| 30 | +have launched with an arbitrary commit for their L2 genesis, making the block history integrity |
| 31 | +checks that are part of the superchain registry very difficult. We tell chains that are |
| 32 | +trying to launch to use governance approved L1 contracts, the same should apply for L2. |
| 33 | + |
| 34 | +Given all of the work for making releases and upgrades nice for the L1 contracts and the client software, |
| 35 | +it is all a waste if we cannot also have good releases of the L2 predeploys. |
| 36 | + |
| 37 | +Right now, OP Mainnet is running contracts at various versions of the software. It is actually very difficult |
| 38 | +to reproduce the exact OP Mainnet set of contracts being used. It would require cherry picking bytecode from many |
| 39 | +different commits. We run no tests against this particular combination of contracts. We believe it is safe |
| 40 | +given our development practices, but every time that we do want to do a release it results in a lot of time |
| 41 | +being spent trying to make sure that we are upgrading to a compatible set of contracts. |
| 42 | + |
| 43 | +# Proposed Solution |
| 44 | + |
| 45 | +<!-- A high level overview of the proposed solution. |
| 46 | +When there are multiple alternatives there should be an explanation |
| 47 | +of why one solution was picked over other solutions. |
| 48 | +As a rule of thumb, including code snippets (except for defining an external API) |
| 49 | +is likely too low level. --> |
| 50 | + |
| 51 | +A WIP implementation can be found [here](https://github.com/ethereum-optimism/optimism/pull/12057). |
| 52 | +The specs can be found [here](https://github.com/ethereum-optimism/specs/tree/17ef36cdc3bb9893b206a93464122d56730d30fb/specs/protocol/holocene). |
| 53 | + |
| 54 | +Similar to the L1 MCP project, we move all of the network specific configuration out of the |
| 55 | +individual contracts themselves and instead place all of it in a single place. Rather than using |
| 56 | +`sload` to read the values, the contracts will make a `CALL` to the L1Block contract. These values |
| 57 | +will be sourced from L1 via deposit transactions that come from the `SystemConfig.initialize` call. |
| 58 | +We need to make sure that the max deposit gas limit is at least able to fullfill these deposit |
| 59 | +transactions. |
| 60 | + |
| 61 | +The general flow is as follows: |
| 62 | + |
| 63 | +```mermaid |
| 64 | +graph LR |
| 65 | + subgraph L1 |
| 66 | + SystemConfig -- "setConfig(uint8,bytes)" --> OptimismPortal |
| 67 | + end |
| 68 | + subgraph L2 |
| 69 | + L1Block |
| 70 | + BaseFeeVault -- "baseFeeVaultConfig()(address,uint256,uint8)" --> L1Block |
| 71 | + SequencerFeeVault -- "sequencerFeeVaultConfig()(address,uint256,uint8)" --> L1Block |
| 72 | + L1FeeVault -- "l1FeeVaultConfig()(address,uint256,uint8)" --> L1Block |
| 73 | + L2CrossDomainMessenger -- "l1CrossDomainMessenger()(address)" --> L1Block |
| 74 | + L2StandardBridge -- "l1StandardBridge()(address)" --> L1Block |
| 75 | + L2ERC721Bridge -- "l1ERC721Bridge()(address)" --> L1Block |
| 76 | + OptimismMintableERC721Factory -- "remoteChainId()(uint256)" --> L1Block |
| 77 | + end |
| 78 | + OptimismPortal -- "setConfig(uint8,bytes)" --> L1Block |
| 79 | +``` |
| 80 | + |
| 81 | +This is taken from the [specs](https://github.com/ethereum-optimism/specs/blob/17ef36cdc3bb9893b206a93464122d56730d30fb/specs/protocol/holocene/predeploys.md) and misses the `L2ProxyAdmin`. The `L2ProxyAdmin` must also be deterministic |
| 82 | +and is explored in the following issue: https://github.com/ethereum-optimism/specs/issues/388. There is general |
| 83 | +consensus on using the `DEPOSITOR_ACCOUNT` as the owner. |
| 84 | + |
| 85 | +When we do a contract release, we commit to that bytecode as part of consensus. That is the bytecode used |
| 86 | +with deposit transactions doing upgrades to the network. In the L2 genesis creation script, we could have |
| 87 | +a library for each release of the predeploys. The genesis script would take the bytecode from the library if |
| 88 | +configured for a specific hardfork at genesis, otherwise it would use the compiled source code. This gives |
| 89 | +us a lot of flexibility and simplicity when it comes to being able to recreate an L2 genesis deterministically. |
| 90 | + |
| 91 | +The block history integrity check becomes as simple as observing that a 32 byte state root in the genesis |
| 92 | +block matches the expected value. |
| 93 | + |
| 94 | +### Rationale Behind Certain Changes |
| 95 | + |
| 96 | +#### SuperchainConfig "Upgrader" Role |
| 97 | + |
| 98 | +The `upgrader` role can call the `OptimismPortal.upgrade(bytes memory data, uint32 gasLimit)` function |
| 99 | +and it emits a deposit tx from the `DEPOSITOR_ACCOUNT` that calls the `L2ProxyAdmin`. Sourcing the auth |
| 100 | +from the `SuperchainConfig` allows for simple management of this very important role, given that it impacts |
| 101 | +stage 1 status. This is meant to simplify operations by removing the aliased L1 `ProxyAdmin` owner |
| 102 | +being set as the L2 `ProxyAdmin`. |
| 103 | + |
| 104 | +Since the the L1 and L2 `ProxyAdmin` contracts are intended to have the same owner, an additional |
| 105 | +improvement (which may be excluded to limit scope creep), would be to remove the `Ownable` |
| 106 | +dependency on the L1 `ProxyAdmin` contract, and instead have it read the |
| 107 | +`SuperchainConfig.upgrader()` role to authorize upgrades. This would also be in alignment with the |
| 108 | +Superchain strategy, as the Security Council should not manage the pause of chains which they are |
| 109 | +not also responsible for upgrading, and participation in the pause is a benefit that chains get when |
| 110 | +they join the Superchain ecosystem.Regardless, in order to preserve the existing auth model we MUST |
| 111 | +ensure that the `upgrader` is the same account as the current L1 ProxyAdmin owner. |
| 112 | + |
| 113 | +The `data` and `gasLimit` are allowed to be specified since we don't fully know what sorts of calls we may have to do. |
| 114 | +We may only need to do simple `upgradeTo` calls, but we may also need to do `upgradeToAndCall`. To support the |
| 115 | +[liquidity migration](https://github.com/ethereum-optimism/design-docs/blob/4b62eb12eceb8e4867ac101134730102c0f5a989/protocol/superchainerc20/liquidity-migration.md), we need to backport storage slots into the `OptimismMintableERC20Factory` |
| 116 | +contract. |
| 117 | + |
| 118 | +#### FeeAdmin role |
| 119 | + |
| 120 | +The entity which is authorized to modify the various `FeeVault` configs must be able to vary from chain |
| 121 | + |
| 122 | +to chain. Therefore a new `feeAdmin` role will be added to the `SystemConfig` contract. This role |
| 123 | +can call a new `SystemConfig.setFeeConfig()` function which forwards config updates to |
| 124 | +`OptimismPortal.setConfig()` with the appropriate `ConfigType`. |
| 125 | + |
| 126 | +This role will be set in `SystemConfig.initialize()`, meaning that it can only be updated by an upgrade. |
| 127 | + |
| 128 | +> [!NOTE] |
| 129 | +> We need to guarantee 100% backwards compatibility in the roles during the upgrade, so for example |
| 130 | +> the same 2/2 multisig that owns the L2 ProxyAdmin on base should be the FeeAdmin in base's |
| 131 | +> SystemConfig. |
| 132 | +
|
| 133 | +In summary: |
| 134 | + |
| 135 | +1. The `FeeAdmin` can update the `FeeConfig`. |
| 136 | +2. The Upgrade Controller (aka [L1 ProxyAdmin Owner](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/stage-1.md#configuration-of-safes)) Safe cand update the `FeeAdmin`. |
| 137 | + |
| 138 | +#### L2ProxyAdmin |
| 139 | + |
| 140 | +A new contract exists called the `L2ProxyAdmin`, it simply inherits from the `ProxyAdmin` and overrides the |
| 141 | +`owner()(address)` function to return `DEPOSITOR_ACCOUNT`. |
| 142 | + |
| 143 | +Ideally we can remove the need for legacy proxy types since they don't exist on L2 eventually, but |
| 144 | +that is considered a bonus when we do get around to it. |
| 145 | + |
| 146 | +#### SystemConfig |
| 147 | + |
| 148 | +The `SystemConfig`'s `initialize()` function will be updated to: |
| 149 | + |
| 150 | +- Accept a new `Roles` struct, composed of the existing `owner` address, and the new |
| 151 | + `feeAdmin` role. |
| 152 | +- Makes multiple calls to the OptimismPortal's `setConfig()` function to set the config values. |
| 153 | + |
| 154 | +> [!IMPORTANT] |
| 155 | +> We should consider if there is a risk associated with 'resetting' these values. Similar to the OptimismPortal's |
| 156 | +> `DEFAULT_L2_SENDER` [reinit issue](https://github.com/ethereum-optimism/optimism/pull/8864). |
| 157 | +> I do not believe so as they are only modifiable in the `initializer` and so cannot be |
| 158 | +> changed in normal operation. However the current design will require that all |
| 159 | +> `SystemConfig` upgrades do not unintentionally modify the existing values. |
| 160 | +
|
| 161 | +The `SystemConfig` will also get the following new external methods which are only callable by the |
| 162 | +`feeAdmin`: |
| 163 | + |
| 164 | +```solidity |
| 165 | +function setBaseFeeVaultConfig(address _recipient, uint256 _min, Types.WithdrawalNetwork _network) external; |
| 166 | +function setL1FeeVaultConfig(address _recipient, uint256 _min, Types.WithdrawalNetwork _network) external; |
| 167 | +function setSequencerFeeVaultConfig(address _recipient, uint256 _min, Types.WithdrawalNetwork _network) external; |
| 168 | +``` |
| 169 | + |
| 170 | +#### Initializable Predeploys Removed |
| 171 | + |
| 172 | +All predeploys are no longer initializable. This allows for upgrades issued by deposit transactions to be very smooth. |
| 173 | +This impacts the following contracts: |
| 174 | + |
| 175 | +- `CrossDomainMessenger` |
| 176 | +- `StandardBridge` |
| 177 | +- `ERC721Bridge` |
| 178 | + |
| 179 | +#### CrossDomainMessenger |
| 180 | + |
| 181 | +Since the `CrossDomainMessenger` is no longer `initializable` we need to slightly modify the semantics around |
| 182 | +the `xDomainMsgSender`. There is actually no need to set the value in storage during `initialize`, we could modify |
| 183 | +the semantics such that if its `address(0)` in storage, then return the default value, otherwise return the |
| 184 | +actual sender value. This should be safe since there is no way to be a sender from `address(0)`. |
| 185 | + |
| 186 | +Given this insight and the fact that there is reentrancy check on `relayMessage`, it should be safe to use transient |
| 187 | +storage without a call depth context. There is an [open PR](https://github.com/ethereum-optimism/optimism/pull/12356) to migrate to solc `0.8.25`. |
| 188 | + |
| 189 | +## Resource Usage |
| 190 | + |
| 191 | +<!-- What is the resource usage of the proposed solution? |
| 192 | +Does it consume a large amount of computational resources or time? --> |
| 193 | + |
| 194 | +The additional deposit gas is the only additional resource usage and its covered in the risks section |
| 195 | +at the bottom of this document. |
| 196 | + |
| 197 | +This approach expands the ABI of the `L1Block` contract, meaning that automatically generated solidity dispatcher |
| 198 | +will binary search over the possible function selectors, consuming a bit more gas. |
| 199 | + |
| 200 | +## Implications for the Predeploy Releases Process |
| 201 | + |
| 202 | +- TODO: get clarity about how to package up L2 contracts releases. How will alternative clients consume the L2 Genesis? |
| 203 | + |
| 204 | +### L2Genesis Generation |
| 205 | + |
| 206 | +When a new predeploy release is created, the bytcode from each predeploy should be placed into a |
| 207 | +an new autogenerated library which resembles the following: |
| 208 | + |
| 209 | +```solidity |
| 210 | +library HolocenePredeploys { |
| 211 | + bytes constant L1Block = hex"..."; |
| 212 | + ... |
| 213 | +} |
| 214 | +``` |
| 215 | + |
| 216 | +The `L2Genesis.s.sol` solidity script will have additional functionality so that it can |
| 217 | +optionally generate the L2 state from the current commit as it currently does using |
| 218 | +`vm.getDeployedCode()`, or retrieve the code from the specified library. |
| 219 | + |
| 220 | +### Upgrade Process |
| 221 | + |
| 222 | +Note that this design modifies how L2 upgrade auth is managed (moving that management into a single |
| 223 | +storage slot on L1), but does not change how upgrades to predeploy contracts are performed. |
| 224 | +Predeploy upgrades can still be done either via a `TransactionDeposited()` event, or a [network |
| 225 | +upgrade automation |
| 226 | +transaction](https://github.com/ethereum-optimism/specs/blob/9f7226be064be0c87f90cbc6be6b0a4b4f58656a/specs/protocol/derivation.md#network-upgrade-automation-transactions). |
| 227 | + |
| 228 | +Routine hardforks should continue to prefer using network upgrade automation transactions, with |
| 229 | +deposit transaction upgrades being reserved for incident response or other extenuating circumstances. |
| 230 | + |
| 231 | +# Alternatives Considered |
| 232 | + |
| 233 | +<!-- List out a short summary of each possible solution that was considered. |
| 234 | +Comparing the effort of each solution --> |
| 235 | + |
| 236 | +There is a long history of alternatives here. |
| 237 | + |
| 238 | +Another option would be to embed these config values directly into the client software's config and have the client |
| 239 | +software create these deposit txs rather than the smart contracts. This is less flexible but comes with the tradeoff |
| 240 | +of additional required rollup config and doesn't solve the problem for existing chains. Existing chains would need a way |
| 241 | +to source this config, it would likely need to be hardcoded in the binary and that isn't super scalable. |
| 242 | + |
| 243 | +# Risks & Uncertainties |
| 244 | + |
| 245 | +<!-- An overview of what could go wrong. |
| 246 | +Also any open questions that need more work to resolve. --> |
| 247 | + |
| 248 | +## Sequenced transactions on a fresh chain |
| 249 | + |
| 250 | +There is a concern that the sequencer can include transactions before these values are set on L2. |
| 251 | +If we define the `SystemConfig.startBlock` as the [first block to start derivation in](https://github.com/ethereum-optimism/optimism/blob/d05fb505809717282d5cee7264a09d26002a4ddd/op-node/cmd/genesis/cmd.go#L174C30-L174C40), |
| 252 | +which is set on `SystemConfig.initialize` and also the deposit transactions that set these values are |
| 253 | +sent in the same block, then we should have the guarantee that no user transactions are included before |
| 254 | +the deposit transactions. |
| 255 | + |
| 256 | +## Max Resource Limit on Deposit Transactions |
| 257 | + |
| 258 | +There is a concern around the max deposit gas limit being too small so that the `SystemConfig` cannot |
| 259 | +deposit all of these values, we should have logic that reverts if the deposit gas limit is too small |
| 260 | +in the `setResourceConfig()` function's [sanity checks](https://github.com/ethereum-optimism/optimism/blob/feat/holocene-contracts/packages/contracts-bedrock/src/L1/SystemConfig.sol#L538-L556). Since the `ResourceConfig` can be modified during |
| 261 | +`initialize`, it should be sufficient to include testing to identify when the total cost of |
| 262 | +`setConfig()` calls will exceed the resource limit in a single block. |
| 263 | + |
| 264 | +A related concern is that the `useGas()` |
| 265 | +[function](https://github.com/ethereum-optimism/optimism/blob/f99424ded3917ddc0c4ef14355d61e50a38d4d0d/packages/contracts-bedrock/src/L1/ResourceMetering.sol#L156) |
| 266 | +which is called in the `upgrade()` and `setConfig()` functions does not check that the max resource limit is not exceeded by |
| 267 | +the additional `prevBoughtGas`. This is in contrast with |
| 268 | +[`_metered()`](https://github.com/ethereum-optimism/optimism/blob/f99424ded3917ddc0c4ef14355d61e50a38d4d0d/packages/contracts-bedrock/src/L1/ResourceMetering.sol#L128C1-L132C10) which does check `prevBoughtGas`. This requires investigation. |
0 commit comments