From eaba3ecb0a15540301d2b45ca14265f23154d153 Mon Sep 17 00:00:00 2001 From: Flux <175354924+0xiamflux@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:14:52 -0500 Subject: [PATCH 1/6] feat: add initial version of predeploys and l2-contracts-manager --- specs/protocol/xfork/l2-contracts-manager.md | 156 +++++++++++++++++ specs/protocol/xfork/predeploys.md | 174 +++++++++++++++++++ 2 files changed, 330 insertions(+) create mode 100644 specs/protocol/xfork/l2-contracts-manager.md create mode 100644 specs/protocol/xfork/predeploys.md diff --git a/specs/protocol/xfork/l2-contracts-manager.md b/specs/protocol/xfork/l2-contracts-manager.md new file mode 100644 index 000000000..2ccd5638d --- /dev/null +++ b/specs/protocol/xfork/l2-contracts-manager.md @@ -0,0 +1,156 @@ +# L2ContractsManager + + + + +- [Summary](#summary) +- [Upgrade Flow](#upgrade-flow) +- [Definitions](#definitions) + - [NUT Bundle](#nut-bundle) + - [Hard Fork Activation Block](#hard-fork-activation-block) + - [Predeploys Config](#predeploys-config) +- [Assumptions](#assumptions) + - [A-01: Addresses for implementations are properly calculated](#a-01-addresses-for-implementations-are-properly-calculated) + - [A-02: Execution of the `upgrade` function is performed in `L2ProxyAdmin's` context](#a-02-execution-of-the-upgrade-function-is-performed-in-l2proxyadmins-context) + - [A-03: Sufficient gas is available for the upgrade process](#a-03-sufficient-gas-is-available-for-the-upgrade-process) + - [A-04: All predeploys use ERC1967 proxy pattern](#a-04-all-predeploys-use-erc1967-proxy-pattern) +- [Invariants](#invariants) +- [Functions](#functions) + - [`upgrade`](#upgrade) + - [Behavior](#behavior) +- [Security Considerations](#security-considerations) + + + +## Summary + +A contract responsible for orchestrating the upgrade of all the supported predeploys for a given hard fork. Contains the logic necessary to perform the upgrades and any additional work they might need for the correct setup. The base upgrade logic lives in an abstract contract that MUST be inherited. Given that each hard fork may require different upgrade logic or different configuration handling, a new `L2ContractsManager` is deployed for each hard fork. The manager is deployed as part of the NUT bundle for the target hard fork, ensuring that the upgrade logic is versioned and tied to what the specific hard fork requires. + +The `upgrade()` function is the only function in the public interface of the manager. The manager itself is not a privileged contract and the process relies on the `L2ProxyAdmin` properly calling the `upgrade()` function via a `delegatecall`. + +Implementation addresses for predeploys are determined by the deterministic deployment process using the `ConditionalDeployer` contract. These implementations are deployed in the NUT bundle before the `L2ContractsManager` is invoked. If a contract’s bytecode is unchanged, its implementation address will be unchanged, ensuring deterministic behavior across upgrades. + +This contract supports dev feature flags and MUST upgrade predeploys to the correct implementation based on the enabled feature flags when executed in alphanets or testing environments. + +## Upgrade Flow + +```mermaid +graph TD + A[NUT Bundle Transactions] -->|1. Deploy implementations| B[ConditionalDeployer] + A -->|2. Deploy| C[L2ContractsManager] + A -->|3. Call| D[DEPOSITOR_ACCOUNT] + D -->|calls upgradePredeploys| E[L2ProxyAdmin] + E -->|delegatecall| F[L2ContractsManager.upgrade] + F -->|gather_config| G[Existing Predeploys] + F -->|upgradeTo/upgradeToAndCall| H[All Predeploys] + B -.->|Predeploy addresses
passed to L2CM| F +``` + +1. Implementation contracts are deployed via the `ConditionalDeployer` contract using the NUT bundle, ensuring deterministic addresses. +2. The `L2ContractsManager` is deployed using the NUT bundle. +3. The `DEPOSITOR_ACCOUNT` calls `L2ProxyAdmin.upgradePredeploys(_l2ContractsManager)` during the hard fork activation block. +4. `L2ProxyAdmin` performs a `delegatecall` to `L2ContractsManager.upgrade()`. +5. `L2ContractsManager.upgrade()` gathers network-specific configuration from existing predeploys. +6. `L2ContractsManager.upgrade()` upgrades all supported predeploys using either `upgradeTo()` or `upgradeToAndCall()` as appropriate, using the deterministically deployed implementation addresses. + +## Definitions + +### NUT Bundle + +A collection of Network Upgrade Transactions stored in JSON format and executed in a specific order. The bundle includes transactions for deploying implementation contracts via `ConditionalDeployer`, deploying the `L2ContractsManager` for the target hard fork, and calling `L2ProxyAdmin.upgradePredeploys()`. The bundle is client-agnostic and can be consumed by any implementation: + +```json +{ + "version": "", + "createdAt": 0, + "transactions": [ + // Example of a contract deployment transaction + { + "data": "0x608060405234801561001057600080fd5b50610b...", + "gas": 375000, + "mint": 0, + "sourceHash": "0x877a6077205782ea15a6dc8699fa5ebcec5e0f4389f09cb8eda09488231346f8", + "to": "0x0000000000000000000000000000000000000000", + "value": 0 + }, + // Example of a function execution transaction + { + "data": "0x7c36f37e000000000000000000000000bb2cfb2907d198451a12e58a6afee0339f3bbd33", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gas": 18446744073709551615, + "mint": 0, + "sourceHash": "0x0d638f060aea832124ac02c40cddc81f6d4800b51451ac9ba34a92d14f1426d2", + "to": "0x4200000000000000000000000000000000000018", + "value": 0, + "contractMethod": { + "inputs": [ + { + "internalType": "address", + "name": "_target", + "type": "address" + } + ], + "name": "upgradePredeploys", + "payable": true + }, + "contractInputsValues": { + "_target": "0xbb2cfb2907d198451a12e58a6afee0339f3bbd33" + } + } + ] +} +``` + +### Hard Fork Activation Block + +The specific L2 block at which a hard fork becomes active. At this block, the NUT bundle transactions are executed, including the deployment of new implementations and the upgrade of all predeploys. + +### Predeploys Config + +Network-specific configuration values gathered from existing predeploys before **performing upgrades**. This includes values such as L1 cross-domain messenger addresses, bridge configurations, and other network-specific parameters that must be preserved during upgrades. + +## Assumptions + +### A-01: Addresses for implementations are properly calculated + +The contract assumes that all implementation addresses point to valid, properly deployed contracts with the expected interface. + +### A-02: Execution of the `upgrade` function is performed in `L2ProxyAdmin's` context + +The contract assumes it has sufficient permissions to perform upgrades on the predeploys; therefore, it assumes the `upgrade` function is being called by the `L2ProxyAdmin` via `delegatecall`. + +### A-03: Sufficient gas is available for the upgrade process + +The contract assumes that sufficient gas is available for the entire upgrade process, including configuration gathering and all predeploy upgrades. The system provides additional upgrade gas beyond the normal `systemTxMaxGas` limit to ensure upgrades can complete successfully. + +### A-04: All predeploys use ERC1967 proxy pattern + +The contract assumes that all predeploys being upgraded use the ERC1967 proxy pattern and support the `upgradeTo()` and `upgradeToAndCall()` methods. + +## Invariants + +TBD + +## Functions + +### `upgrade` + +This function is responsible for orchestrating the upgrades of all the supported predeploys for the target hard fork. The manager has no special privileges and relies on the `L2ProxyAdmin` calling `upgrade()` via a `delegatecall`. + +```solidity +function upgrade() external; +``` + +#### Behavior + +- MUST always succeed when called via `delegatecall` by `L2ProxyAdmin`. +- MUST gather network-specific configuration values from existing predeploys before performing upgrades. These values are read from the current predeploy implementations and used to initialize or configure the upgraded implementations. +- MUST upgrade ALL predeploys that are supported by the target hard fork, including: + - Predeploys with unchanged implementations (they will be upgraded to the same implementation address). + - Predeploys with feature-flagged implementations when executed in alphanets or testing environments. +- For each predeploy being upgraded: + - MUST call `Proxy.upgradeToAndCall()` if the contract has initializer arguments that need to be set, passing the gathered configuration values. + - MUST call `Proxy.upgradeTo()` if the contract does not have initializer arguments. +- MUST use the implementation addresses that were deterministically deployed via the `ConditionalDeployer` contract in the NUT bundle. + +## Security Considerations diff --git a/specs/protocol/xfork/predeploys.md b/specs/protocol/xfork/predeploys.md new file mode 100644 index 000000000..0499fca8f --- /dev/null +++ b/specs/protocol/xfork/predeploys.md @@ -0,0 +1,174 @@ +# Predeploys + + + + +- [Overview](#overview) +- [L2ProxyAdmin](#l2proxyadmin) +- [Invariants](#invariants) + - [I-01: upgradePredeploys Access Control](#i-01-upgradepredeploys-access-control) + - [Impact](#impact) + - [I-02: Depositor Account Execution Guarantee](#i-02-depositor-account-execution-guarantee) + - [Impact](#impact-1) +- [Functions](#functions) + - [`upgradePredeploys`](#upgradepredeploys) + - [`setProxyType`](#setproxytype) + - [`setImplementationName`](#setimplementationname) + - [`setAddressManager`](#setaddressmanager) + - [`setAddress`](#setaddress) + - [`setUpgrading`](#setupgrading) + - [`isUpgrading`](#isupgrading) + - [`getProxyImplementation`](#getproxyimplementation) + - [`getProxyAdmin`](#getproxyadmin) + - [`changeProxyAdmin`](#changeproxyadmin) + - [`upgrade`](#upgrade) + - [`upgradeAndCall`](#upgradeandcall) +- [Security Considerations](#security-considerations) + + + +## Overview + +## L2ProxyAdmin + +The `ProxyAdmin` predeploy at `0x4200000000000000000000000000000000000018` is upgraded with a new L2-specific implementation, `L2ProxyAdmin`, that supports predeploy upgrades during hard forks. The `L2ProxyAdmin` contract inherits from the universal `ProxyAdmin` contract and adds the `upgradePredeploys()` function, which is called during a hard fork activation block by the `DEPOSITOR_ACCOUNT` to upgrade all predeploys in a single transaction. + +The `L2ProxyAdmin` implementation also removes unused logic from the universal `ProxyAdmin` contract. Previously, the L2 ProxyAdmin used the same "universal" implementation deployed on L1, which supports multiple proxy types (ERC1967, ChugSplash, and ResolvedDelegate) and includes proxy type validation logic. Since all L2 predeploys use ERC1967 proxies exclusively, `L2ProxyAdmin` overrides functions related to legacy proxy types to remove their support while keeping the same public interface for backward compatibility. Setters for configuration values regarding proxy types are overridden to result in no-ops, and getters are overridden to either return default values or values that are relevant in the ERC1967 context only. + +## Invariants + +### I-01: upgradePredeploys Access Control + +The `upgradePredeploys` function MUST only be callable by the contract owner or the `DEPOSITOR_ACCOUNT` (`0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001`). + +#### Impact + +Violation of this invariant would allow an attacker to trigger predeploy upgrades via `delegatecall` to arbitrary contracts. + +### I-02: Depositor Account Execution Guarantee + +When called by the `DEPOSITOR_ACCOUNT`, the `upgradePredeploys` function MUST NOT revert, provided that the `_l2ContractsManager` parameter is a valid contract address. + +#### Impact + +Violation of this invariant will allow upgrades to fail, potentially leading to network stalling. + +## Functions + +### `upgradePredeploys` + +This is a permissioned function that performs a `delegatecall` to a previously deployed `L2ContractsManager`. + +```solidity +function upgradePredeploys(address _l2ContractsManager) external; +``` + +- MUST revert if not called by either the `DEPOSITOR_ACCOUNT` or the owner of the `L2ProxyAdmin` +- MUST perform a single `delegatecall` to `_l2ContractsManager` +- MUST never revert + +### `setProxyType` + +```solidity +function setProxyType(address _address, ProxyType _type) external; +``` + +- MUST NOT revert +- MUST NOT modify any state + +### `setImplementationName` + +```solidity +function setImplementationName(address _address, string memory _name) external; +``` + +- MUST NOT revert +- MUST NOT modify any state + +### `setAddressManager` + +```solidity +function setAddressManager(IAddressManager _address) external; +``` + +- MUST NOT revert +- MUST NOT modify any state + +### `setAddress` + +```solidity +function setAddress(string memory _name, address _address) external; +``` + +- MUST NOT revert +- MUST NOT modify any state + +### `setUpgrading` + +```solidity +function setUpgrading(bool _upgrading) external; +``` + +- MUST NOT revert +- MUST NOT modify any state + +### `isUpgrading` + +```solidity +function isUpgrading() external view returns (bool); +``` + +- MUST return `false` + +### `getProxyImplementation` + +```solidity +function getProxyImplementation(address _proxy) external view returns (address); +``` + +- MUST return the implementation address by calling `implementation()` on the ERC1967 proxy +- MUST NOT query proxy type configuration + +### `getProxyAdmin` + +```solidity +function getProxyAdmin(address payable _proxy) external view returns (address); +``` + +- MUST return the admin address by calling `admin()` on the ERC1967 proxy +- MUST NOT query proxy type configuration + +### `changeProxyAdmin` + +```solidity +function changeProxyAdmin(address payable _proxy, address _newAdmin) external; +``` + +- MUST revert if caller is not the owner +- MUST call `changeAdmin(_newAdmin)` on the ERC1967 proxy + +### `upgrade` + +```solidity +function upgrade(address payable _proxy, address _implementation) public; +``` + +- MUST revert if caller is not the owner +- MUST call `upgradeTo(_implementation)` on the ERC1967 proxy + +### `upgradeAndCall` + +```solidity +function upgradeAndCall( + address payable _proxy, + address _implementation, + bytes memory _data +) external payable; +``` + +- MUST revert if caller is not the owner +- MUST call `upgradeToAndCall(_implementation, _data)` on the ERC1967 proxy with forwarded `msg.value` + +## Security Considerations + +- Performing a `delegatecall` from the `ProxyAdmin` which manages **ALL** the predeploys must be implemented with extreme caution. We must ensure that no accounts other than the owner or the `DEPOSITOR_ACCOUNT` can call the `upgradePredeploys` function. From 85abbfb3df8496632a5a91e671df9a1a330faba0 Mon Sep 17 00:00:00 2001 From: Flux <175354924+0xiamflux@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:38:55 -0500 Subject: [PATCH 2/6] chore: moves files to experimental --- specs/experimental/conditional-deployer.md | 1 + specs/{protocol/xfork => experimental}/l2-contracts-manager.md | 0 specs/{protocol/xfork => experimental}/predeploys.md | 0 specs/experimental/transaction-generation-script.md | 1 + 4 files changed, 2 insertions(+) create mode 100644 specs/experimental/conditional-deployer.md rename specs/{protocol/xfork => experimental}/l2-contracts-manager.md (100%) rename specs/{protocol/xfork => experimental}/predeploys.md (100%) create mode 100644 specs/experimental/transaction-generation-script.md diff --git a/specs/experimental/conditional-deployer.md b/specs/experimental/conditional-deployer.md new file mode 100644 index 000000000..544beb8bc --- /dev/null +++ b/specs/experimental/conditional-deployer.md @@ -0,0 +1 @@ +# Conditional Deployer diff --git a/specs/protocol/xfork/l2-contracts-manager.md b/specs/experimental/l2-contracts-manager.md similarity index 100% rename from specs/protocol/xfork/l2-contracts-manager.md rename to specs/experimental/l2-contracts-manager.md diff --git a/specs/protocol/xfork/predeploys.md b/specs/experimental/predeploys.md similarity index 100% rename from specs/protocol/xfork/predeploys.md rename to specs/experimental/predeploys.md diff --git a/specs/experimental/transaction-generation-script.md b/specs/experimental/transaction-generation-script.md new file mode 100644 index 000000000..f97db839c --- /dev/null +++ b/specs/experimental/transaction-generation-script.md @@ -0,0 +1 @@ +# Transaction Generation Script From 64f97116a31e5aeedcd0071082509168d19ff39f Mon Sep 17 00:00:00 2001 From: Flux <175354924+0xiamflux@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:51:59 -0500 Subject: [PATCH 3/6] chore: remove unrelated functions --- specs/experimental/predeploys.md | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/specs/experimental/predeploys.md b/specs/experimental/predeploys.md index 0499fca8f..c970f7c4d 100644 --- a/specs/experimental/predeploys.md +++ b/specs/experimental/predeploys.md @@ -12,10 +12,7 @@ - [Impact](#impact-1) - [Functions](#functions) - [`upgradePredeploys`](#upgradepredeploys) - - [`setProxyType`](#setproxytype) - [`setImplementationName`](#setimplementationname) - - [`setAddressManager`](#setaddressmanager) - - [`setAddress`](#setaddress) - [`setUpgrading`](#setupgrading) - [`isUpgrading`](#isupgrading) - [`getProxyImplementation`](#getproxyimplementation) @@ -31,7 +28,7 @@ ## L2ProxyAdmin -The `ProxyAdmin` predeploy at `0x4200000000000000000000000000000000000018` is upgraded with a new L2-specific implementation, `L2ProxyAdmin`, that supports predeploy upgrades during hard forks. The `L2ProxyAdmin` contract inherits from the universal `ProxyAdmin` contract and adds the `upgradePredeploys()` function, which is called during a hard fork activation block by the `DEPOSITOR_ACCOUNT` to upgrade all predeploys in a single transaction. +The `ProxyAdmin` predeploy at `0x4200000000000000000000000000000000000018` is upgraded with a new L2-specific implementation, `L2ProxyAdmin`, that supports predeploy upgrades during hard forks. The `L2ProxyAdmin` contract adds the `upgradePredeploys()` function, which is called during a hard fork activation block by the `DEPOSITOR_ACCOUNT` to upgrade all predeploys in a single transaction. The `L2ProxyAdmin` implementation also removes unused logic from the universal `ProxyAdmin` contract. Previously, the L2 ProxyAdmin used the same "universal" implementation deployed on L1, which supports multiple proxy types (ERC1967, ChugSplash, and ResolvedDelegate) and includes proxy type validation logic. Since all L2 predeploys use ERC1967 proxies exclusively, `L2ProxyAdmin` overrides functions related to legacy proxy types to remove their support while keeping the same public interface for backward compatibility. Setters for configuration values regarding proxy types are overridden to result in no-ops, and getters are overridden to either return default values or values that are relevant in the ERC1967 context only. @@ -67,15 +64,6 @@ function upgradePredeploys(address _l2ContractsManager) external; - MUST perform a single `delegatecall` to `_l2ContractsManager` - MUST never revert -### `setProxyType` - -```solidity -function setProxyType(address _address, ProxyType _type) external; -``` - -- MUST NOT revert -- MUST NOT modify any state - ### `setImplementationName` ```solidity @@ -85,24 +73,6 @@ function setImplementationName(address _address, string memory _name) external; - MUST NOT revert - MUST NOT modify any state -### `setAddressManager` - -```solidity -function setAddressManager(IAddressManager _address) external; -``` - -- MUST NOT revert -- MUST NOT modify any state - -### `setAddress` - -```solidity -function setAddress(string memory _name, address _address) external; -``` - -- MUST NOT revert -- MUST NOT modify any state - ### `setUpgrading` ```solidity From 2afddd33cc568a95afe827b16252037df5befabc Mon Sep 17 00:00:00 2001 From: Flux <175354924+0xiamflux@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:53:51 -0500 Subject: [PATCH 4/6] chore: make clear all network config is gathered --- specs/experimental/l2-contracts-manager.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/experimental/l2-contracts-manager.md b/specs/experimental/l2-contracts-manager.md index 2ccd5638d..27410bb28 100644 --- a/specs/experimental/l2-contracts-manager.md +++ b/specs/experimental/l2-contracts-manager.md @@ -144,7 +144,7 @@ function upgrade() external; #### Behavior - MUST always succeed when called via `delegatecall` by `L2ProxyAdmin`. -- MUST gather network-specific configuration values from existing predeploys before performing upgrades. These values are read from the current predeploy implementations and used to initialize or configure the upgraded implementations. +- MUST gather all network-specific configuration values from existing predeploys before performing upgrades. These values are read from the current predeploy implementations and used to initialize or configure the upgraded implementations. - MUST upgrade ALL predeploys that are supported by the target hard fork, including: - Predeploys with unchanged implementations (they will be upgraded to the same implementation address). - Predeploys with feature-flagged implementations when executed in alphanets or testing environments. From 0556a1eb295c5b86f12661bbc2ec0fa39cc774e6 Mon Sep 17 00:00:00 2001 From: Flux <175354924+0xiamflux@users.noreply.github.com> Date: Thu, 13 Nov 2025 16:13:08 -0500 Subject: [PATCH 5/6] feat: clarify use of gather config --- specs/experimental/l2-contracts-manager.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/experimental/l2-contracts-manager.md b/specs/experimental/l2-contracts-manager.md index 27410bb28..4eda1d3c7 100644 --- a/specs/experimental/l2-contracts-manager.md +++ b/specs/experimental/l2-contracts-manager.md @@ -144,7 +144,7 @@ function upgrade() external; #### Behavior - MUST always succeed when called via `delegatecall` by `L2ProxyAdmin`. -- MUST gather all network-specific configuration values from existing predeploys before performing upgrades. These values are read from the current predeploy implementations and used to initialize or configure the upgraded implementations. +- MUST gather all network-specific configuration values from existing predeploys before performing upgrades by calling `_gatherPredeploysConfig()`. These values are read from the current predeploy implementations and used to initialize or configure the upgraded implementations. - MUST upgrade ALL predeploys that are supported by the target hard fork, including: - Predeploys with unchanged implementations (they will be upgraded to the same implementation address). - Predeploys with feature-flagged implementations when executed in alphanets or testing environments. From 46c8fde2392af5de3334a4a25d72a19ea0126b6f Mon Sep 17 00:00:00 2001 From: Flux <175354924+0xiamflux@users.noreply.github.com> Date: Thu, 13 Nov 2025 16:20:58 -0500 Subject: [PATCH 6/6] fix: update flow diagram --- specs/experimental/l2-contracts-manager.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/experimental/l2-contracts-manager.md b/specs/experimental/l2-contracts-manager.md index 4eda1d3c7..00f003867 100644 --- a/specs/experimental/l2-contracts-manager.md +++ b/specs/experimental/l2-contracts-manager.md @@ -43,7 +43,6 @@ graph TD E -->|delegatecall| F[L2ContractsManager.upgrade] F -->|gather_config| G[Existing Predeploys] F -->|upgradeTo/upgradeToAndCall| H[All Predeploys] - B -.->|Predeploy addresses
passed to L2CM| F ``` 1. Implementation contracts are deployed via the `ConditionalDeployer` contract using the NUT bundle, ensuring deterministic addresses.