diff --git a/.semgrep/rules/sol-rules.yaml b/.semgrep/rules/sol-rules.yaml index 81dcd5b7a0a..720eb699abf 100644 --- a/.semgrep/rules/sol-rules.yaml +++ b/.semgrep/rules/sol-rules.yaml @@ -330,7 +330,6 @@ rules: - packages/contracts-bedrock/src/L2/FeeVault.sol - packages/contracts-bedrock/src/L2/OptimismMintableERC721.sol - packages/contracts-bedrock/src/L2/OptimismMintableERC721Factory.sol - - packages/contracts-bedrock/src/L2/XForkL2ContractsManager.sol - packages/contracts-bedrock/src/L2/L2ContractsManager.sol - packages/contracts-bedrock/src/cannon/MIPS64.sol - packages/contracts-bedrock/src/cannon/PreimageOracle.sol diff --git a/packages/contracts-bedrock/interfaces/L2/IL2ContractsManager.sol b/packages/contracts-bedrock/interfaces/L2/IL2ContractsManager.sol index ee3ecb61381..6c8229fe426 100644 --- a/packages/contracts-bedrock/interfaces/L2/IL2ContractsManager.sol +++ b/packages/contracts-bedrock/interfaces/L2/IL2ContractsManager.sol @@ -28,4 +28,91 @@ interface IL2ContractsManager is ISemver { /// @param _implementations The implementation struct containing the new implementation addresses for the L2 /// predeploys. function __constructor__(L2ContractsManagerTypes.Implementations memory _implementations) external; + + /// @notice Returns the implementation address of the StorageSetter contract. + function storageSetterImpl() external view returns (address); + + /// @notice Returns the implementation address of the GasPriceOracle contract. + function gasPriceOracleImpl() external view returns (address); + + /// @notice Returns the implementation address of the L2CrossDomainMessenger contract. + function l2CrossDomainMessengerImpl() external view returns (address); + + /// @notice Returns the implementation address of the L2StandardBridge contract. + function l2StandardBridgeImpl() external view returns (address); + + /// @notice Returns the implementation address of the SequencerFeeWallet contract. + function sequencerFeeWalletImpl() external view returns (address); + + /// @notice Returns the implementation address of the OptimismMintableERC20Factory contract. + function optimismMintableERC20FactoryImpl() external view returns (address); + + /// @notice Returns the implementation address of the L2ERC721Bridge contract. + function l2ERC721BridgeImpl() external view returns (address); + + /// @notice Returns the implementation address of the L1Block contract. + function l1BlockImpl() external view returns (address); + + /// @notice Returns the implementation address of the L1Block contract for custom gas token networks. + function l1BlockCGTImpl() external view returns (address); + + /// @notice Returns the implementation address of the L2ToL1MessagePasser contract. + function l2ToL1MessagePasserImpl() external view returns (address); + + /// @notice Returns the implementation address of the L2ToL1MessagePasser contract for custom gas token networks. + function l2ToL1MessagePasserCGTImpl() external view returns (address); + + /// @notice Returns the implementation address of the OptimismMintableERC721Factory contract. + function optimismMintableERC721FactoryImpl() external view returns (address); + + /// @notice Returns the implementation address of the ProxyAdmin contract. + function proxyAdminImpl() external view returns (address); + + /// @notice Returns the implementation address of the BaseFeeVault contract. + function baseFeeVaultImpl() external view returns (address); + + /// @notice Returns the implementation address of the L1FeeVault contract. + function l1FeeVaultImpl() external view returns (address); + + /// @notice Returns the implementation address of the OperatorFeeVault contract. + function operatorFeeVaultImpl() external view returns (address); + + /// @notice Returns the implementation address of the SchemaRegistry contract. + function schemaRegistryImpl() external view returns (address); + + /// @notice Returns the implementation address of the EAS contract. + function easImpl() external view returns (address); + + /// @notice Returns the implementation address of the CrossL2Inbox contract. + function crossL2InboxImpl() external view returns (address); + + /// @notice Returns the implementation address of the L2ToL2CrossDomainMessenger contract. + function l2ToL2CrossDomainMessengerImpl() external view returns (address); + + /// @notice Returns the implementation address of the SuperchainETHBridge contract. + function superchainETHBridgeImpl() external view returns (address); + + /// @notice Returns the implementation address of the ETHLiquidity contract. + function ethLiquidityImpl() external view returns (address); + + /// @notice Returns the implementation address of the OptimismSuperchainERC20Factory contract. + function optimismSuperchainERC20FactoryImpl() external view returns (address); + + /// @notice Returns the implementation address of the OptimismSuperchainERC20Beacon contract. + function optimismSuperchainERC20BeaconImpl() external view returns (address); + + /// @notice Returns the implementation address of the SuperchainTokenBridge contract. + function superchainTokenBridgeImpl() external view returns (address); + + /// @notice Returns the implementation address of the NativeAssetLiquidity contract. + function nativeAssetLiquidityImpl() external view returns (address); + + /// @notice Returns the implementation address of the LiquidityController contract. + function liquidityControllerImpl() external view returns (address); + + /// @notice Returns the implementation address of the FeeSplitter contract. + function feeSplitterImpl() external view returns (address); + + /// @notice Returns the implementation address of the ConditionalDeployer contract. + function conditionalDeployerImpl() external view returns (address); } diff --git a/packages/contracts-bedrock/snapshots/abi/L2ContractsManager.json b/packages/contracts-bedrock/snapshots/abi/L2ContractsManager.json index 2e92830542a..1f243719572 100644 --- a/packages/contracts-bedrock/snapshots/abi/L2ContractsManager.json +++ b/packages/contracts-bedrock/snapshots/abi/L2ContractsManager.json @@ -157,6 +157,383 @@ "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [], + "name": "baseFeeVaultImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "conditionalDeployerImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "crossL2InboxImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "easImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ethLiquidityImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeSplitterImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gasPriceOracleImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1BlockCGTImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1BlockImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1FeeVaultImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l2CrossDomainMessengerImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l2ERC721BridgeImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l2StandardBridgeImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l2ToL1MessagePasserCGTImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l2ToL1MessagePasserImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l2ToL2CrossDomainMessengerImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "liquidityControllerImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nativeAssetLiquidityImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "operatorFeeVaultImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "optimismMintableERC20FactoryImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "optimismMintableERC721FactoryImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "optimismSuperchainERC20BeaconImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "optimismSuperchainERC20FactoryImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proxyAdminImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "schemaRegistryImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sequencerFeeWalletImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "storageSetterImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "superchainETHBridgeImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "superchainTokenBridgeImpl", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "upgrade", diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index cac78c637fc..fd6eb820ac7 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -96,8 +96,8 @@ "sourceCodeHash": "0x7e438cbbe9a8248887b8c21f68c811f90a5cae4902cbbf7b0a1f6cd644dc42d9" }, "src/L2/L2ContractsManager.sol:L2ContractsManager": { - "initCodeHash": "0x2f41a11c7ed9bddbccebe9486243af5bc2693d1d98dfd77afb48f4154280c2e5", - "sourceCodeHash": "0x977efd878ca60d12f5a9348ad9c0502fe9cdae31d5579bd580fc76c3c0992a3a" + "initCodeHash": "0xf93ed1ade549d0a29b09171b67911e39bf0347bbdd2f29aecda6acb2f7e17d74", + "sourceCodeHash": "0x5f69f14ab2a7a542892372bedfd775840f20a61dd9b78febc23237201c6a4942" }, "src/L2/L2CrossDomainMessenger.sol:L2CrossDomainMessenger": { "initCodeHash": "0xe160be403df12709c371c33195d1b9c3b5e9499e902e86bdabc8eed749c3fd61", diff --git a/packages/contracts-bedrock/src/L2/L2ContractsManager.sol b/packages/contracts-bedrock/src/L2/L2ContractsManager.sol index 50a99af39d9..f0ca4145374 100644 --- a/packages/contracts-bedrock/src/L2/L2ContractsManager.sol +++ b/packages/contracts-bedrock/src/L2/L2ContractsManager.sol @@ -163,7 +163,7 @@ contract L2ContractsManager is ISemver { // We need our upgrades be able to determine if the network is a custom gas token network so that we can // apply the appropriate configuration to the LiquidityController predeploy. In networks without custom gas // tokens, the LiquidityController predeploy is not used and points to address(0). - bool isCustomGasToken = IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken(); + fullConfig_.isCustomGasToken = IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken(); // L2CrossDomainMessenger fullConfig_.crossDomainMessenger = L2ContractsManagerTypes.CrossDomainMessengerConfig({ @@ -198,7 +198,7 @@ contract L2ContractsManager is ISemver { fullConfig_.operatorFeeVault = L2ContractsManagerUtils.readFeeVaultConfig(Predeploys.OPERATOR_FEE_VAULT); // LiquidityController - if (isCustomGasToken) { + if (fullConfig_.isCustomGasToken) { ILiquidityController liquidityController = ILiquidityController(Predeploys.LIQUIDITY_CONTROLLER); fullConfig_.liquidityController = L2ContractsManagerTypes.LiquidityControllerConfig({ owner: liquidityController.owner(), @@ -211,8 +211,6 @@ contract L2ContractsManager is ISemver { fullConfig_.feeSplitter = L2ContractsManagerTypes.FeeSplitterConfig({ sharesCalculator: IFeeSplitter(payable(Predeploys.FEE_SPLITTER)).sharesCalculator() }); - - fullConfig_.isCustomGasToken = isCustomGasToken; } /// @notice Upgrades each of the predeploys to its corresponding new implementation. Applies the appropriate @@ -394,4 +392,149 @@ contract L2ContractsManager is ISemver { L2ContractsManagerUtils.upgradeTo(Predeploys.EAS, EAS_IMPL); L2ContractsManagerUtils.upgradeTo(Predeploys.CONDITIONAL_DEPLOYER, CONDITIONAL_DEPLOYER_IMPL); } + + /// @notice Returns the implementation address of the StorageSetter contract. + function storageSetterImpl() public view returns (address) { + return STORAGE_SETTER_IMPL; + } + + /// @notice Returns the implementation address of the GasPriceOracle contract. + function gasPriceOracleImpl() public view returns (address) { + return GAS_PRICE_ORACLE_IMPL; + } + + /// @notice Returns the implementation address of the L2CrossDomainMessenger contract. + function l2CrossDomainMessengerImpl() public view returns (address) { + return L2_CROSS_DOMAIN_MESSENGER_IMPL; + } + + /// @notice Returns the implementation address of the L2StandardBridge contract. + function l2StandardBridgeImpl() public view returns (address) { + return L2_STANDARD_BRIDGE_IMPL; + } + + /// @notice Returns the implementation address of the SequencerFeeWallet contract. + function sequencerFeeWalletImpl() public view returns (address) { + return SEQUENCER_FEE_WALLET_IMPL; + } + + /// @notice Returns the implementation address of the OptimismMintableERC20Factory contract. + function optimismMintableERC20FactoryImpl() public view returns (address) { + return OPTIMISM_MINTABLE_ERC20_FACTORY_IMPL; + } + + /// @notice Returns the implementation address of the L2ERC721Bridge contract. + function l2ERC721BridgeImpl() public view returns (address) { + return L2_ERC721_BRIDGE_IMPL; + } + + /// @notice Returns the implementation address of the L1Block contract. + function l1BlockImpl() public view returns (address) { + return L1_BLOCK_IMPL; + } + + /// @notice Returns the implementation address of the L1Block contract for custom gas token networks. + function l1BlockCGTImpl() public view returns (address) { + return L1_BLOCK_CGT_IMPL; + } + + /// @notice Returns the implementation address of the L2ToL1MessagePasser contract. + function l2ToL1MessagePasserImpl() public view returns (address) { + return L2_TO_L1_MESSAGE_PASSER_IMPL; + } + + /// @notice Returns the implementation address of the L2ToL1MessagePasser contract for custom gas token networks. + function l2ToL1MessagePasserCGTImpl() public view returns (address) { + return L2_TO_L1_MESSAGE_PASSER_CGT_IMPL; + } + + /// @notice Returns the implementation address of the OptimismMintableERC721Factory contract. + function optimismMintableERC721FactoryImpl() public view returns (address) { + return OPTIMISM_MINTABLE_ERC721_FACTORY_IMPL; + } + + /// @notice Returns the implementation address of the ProxyAdmin contract. + function proxyAdminImpl() public view returns (address) { + return PROXY_ADMIN_IMPL; + } + + /// @notice Returns the implementation address of the BaseFeeVault contract. + function baseFeeVaultImpl() public view returns (address) { + return BASE_FEE_VAULT_IMPL; + } + + /// @notice Returns the implementation address of the L1FeeVault contract. + function l1FeeVaultImpl() public view returns (address) { + return L1_FEE_VAULT_IMPL; + } + + /// @notice Returns the implementation address of the OperatorFeeVault contract. + function operatorFeeVaultImpl() public view returns (address) { + return OPERATOR_FEE_VAULT_IMPL; + } + + /// @notice Returns the implementation address of the SchemaRegistry contract. + function schemaRegistryImpl() public view returns (address) { + return SCHEMA_REGISTRY_IMPL; + } + + /// @notice Returns the implementation address of the EAS contract. + function easImpl() public view returns (address) { + return EAS_IMPL; + } + + /// @notice Returns the implementation address of the CrossL2Inbox contract. + function crossL2InboxImpl() public view returns (address) { + return CROSS_L2_INBOX_IMPL; + } + + /// @notice Returns the implementation address of the L2ToL2CrossDomainMessenger contract. + function l2ToL2CrossDomainMessengerImpl() public view returns (address) { + return L2_TO_L2_CROSS_DOMAIN_MESSENGER_IMPL; + } + + /// @notice Returns the implementation address of the SuperchainETHBridge contract. + function superchainETHBridgeImpl() public view returns (address) { + return SUPERCHAIN_ETH_BRIDGE_IMPL; + } + + /// @notice Returns the implementation address of the ETHLiquidity contract. + function ethLiquidityImpl() public view returns (address) { + return ETH_LIQUIDITY_IMPL; + } + + /// @notice Returns the implementation address of the OptimismSuperchainERC20Factory contract. + function optimismSuperchainERC20FactoryImpl() public view returns (address) { + return OPTIMISM_SUPERCHAIN_ERC20_FACTORY_IMPL; + } + + /// @notice Returns the implementation address of the OptimismSuperchainERC20Beacon contract. + function optimismSuperchainERC20BeaconImpl() public view returns (address) { + return OPTIMISM_SUPERCHAIN_ERC20_BEACON_IMPL; + } + + /// @notice Returns the implementation address of the SuperchainTokenBridge contract. + function superchainTokenBridgeImpl() public view returns (address) { + return SUPERCHAIN_TOKEN_BRIDGE_IMPL; + } + + /// @notice Returns the implementation address of the NativeAssetLiquidity contract. + function nativeAssetLiquidityImpl() public view returns (address) { + return NATIVE_ASSET_LIQUIDITY_IMPL; + } + + /// @notice Returns the implementation address of the LiquidityController contract. + function liquidityControllerImpl() public view returns (address) { + return LIQUIDITY_CONTROLLER_IMPL; + } + + /// @notice Returns the implementation address of the FeeSplitter contract. + function feeSplitterImpl() public view returns (address) { + return FEE_SPLITTER_IMPL; + } + + /// @notice Returns the implementation address of the ConditionalDeployer contract. + function conditionalDeployerImpl() public view returns (address) { + return CONDITIONAL_DEPLOYER_IMPL; + } } diff --git a/packages/contracts-bedrock/src/libraries/L2ContractsManagerUtils.sol b/packages/contracts-bedrock/src/libraries/L2ContractsManagerUtils.sol index dd4917de094..9b83393d1c9 100644 --- a/packages/contracts-bedrock/src/libraries/L2ContractsManagerUtils.sol +++ b/packages/contracts-bedrock/src/libraries/L2ContractsManagerUtils.sol @@ -4,9 +4,11 @@ pragma solidity ^0.8.0; // Libraries import { L2ContractsManagerTypes } from "src/libraries/L2ContractsManagerTypes.sol"; import { SemverComp } from "src/libraries/SemverComp.sol"; -import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; +// Contracts +import { L2ProxyAdmin } from "src/L2/L2ProxyAdmin.sol"; + // Interfaces import { IStorageSetter } from "interfaces/universal/IStorageSetter.sol"; import { IFeeVault } from "interfaces/L2/IFeeVault.sol"; @@ -31,11 +33,14 @@ library L2ContractsManagerUtils { // Skip if the predeploy is not upgradeable (e.g., not deployed on this chain). if (!Predeploys.isUpgradeable(_proxy)) return; + // We skip checking the version for those predeploys that have no code. This would be the case for newly added + // predeploys that are being introduced on this particular upgrade. + address implementation = L2ProxyAdmin(Predeploys.PROXY_ADMIN).getProxyImplementation(_proxy); + // We avoid downgrading Predeploys if ( // TODO(#19195): Remove this code skipping the ProxyAdmin once version is implemented. - _proxy != Predeploys.PROXY_ADMIN - && ProxyAdmin(Predeploys.PROXY_ADMIN).getProxyImplementation(_proxy) != address(0) + _proxy != Predeploys.PROXY_ADMIN && implementation.code.length != 0 && SemverComp.gt(ISemver(_proxy).version(), ISemver(_implementation).version()) ) { revert L2ContractsManager_DowngradeNotAllowed(address(_proxy)); @@ -86,11 +91,14 @@ library L2ContractsManagerUtils { // Skip if the predeploy is not upgradeable (e.g., not deployed on this chain). if (!Predeploys.isUpgradeable(_proxy)) return; + // We skip checking the version for those predeploys that have no code. This would be the case for newly added + // predeploys that are being introduced on this particular upgrade. + address implementation = L2ProxyAdmin(Predeploys.PROXY_ADMIN).getProxyImplementation(_proxy); + if ( // TODO(#19195): Remove this code skipping the ProxyAdmin once version is implemented. // This should never be the case, if you're trying to initialize the ProxyAdmin, it's probably a mistake. - _proxy != Predeploys.PROXY_ADMIN - && ProxyAdmin(Predeploys.PROXY_ADMIN).getProxyImplementation(_proxy) != address(0) + _proxy != Predeploys.PROXY_ADMIN && implementation.code.length != 0 && SemverComp.gt(ISemver(_proxy).version(), ISemver(_implementation).version()) ) { revert L2ContractsManager_DowngradeNotAllowed(address(_proxy)); diff --git a/packages/contracts-bedrock/src/libraries/Predeploys.sol b/packages/contracts-bedrock/src/libraries/Predeploys.sol index 72bf45bbbcf..dab948384f1 100644 --- a/packages/contracts-bedrock/src/libraries/Predeploys.sol +++ b/packages/contracts-bedrock/src/libraries/Predeploys.sol @@ -4,9 +4,6 @@ pragma solidity ^0.8.0; // Libraries import { Fork } from "scripts/libraries/Config.sol"; -// Interfaces -import { IStaticERC1967Proxy } from "interfaces/universal/IStaticERC1967Proxy.sol"; - /// @title Predeploys /// @notice Contains constant addresses for protocol contracts that are pre-deployed to the L2 system. // This excludes the preinstalls (non-protocol contracts). @@ -217,13 +214,11 @@ library Predeploys { } /// @notice Returns true if the predeploy is upgradeable. In this context, upgradeable means that the predeploy - /// is in the predeploy namespace, is proxied, and has an implementation contract with code. + /// is in the predeploy namespace and it is proxied. /// @param _proxy The address of the predeploy. /// @return isUpgradeable_ True if the predeploy is upgradeable, false otherwise. - function isUpgradeable(address _proxy) internal view returns (bool isUpgradeable_) { - address implementation = IStaticERC1967Proxy(_proxy).implementation(); - - isUpgradeable_ = isPredeployNamespace(_proxy) && !notProxied(_proxy) && implementation.code.length > 0; + function isUpgradeable(address _proxy) internal pure returns (bool isUpgradeable_) { + isUpgradeable_ = isPredeployNamespace(_proxy) && !notProxied(_proxy); } /// @notice Returns all proxied predeploys that should be upgraded by L2CM. diff --git a/packages/contracts-bedrock/test/L2/L2ContractsManager.t.sol b/packages/contracts-bedrock/test/L2/L2ContractsManager.t.sol index 31807ca8b62..ac93f27b852 100644 --- a/packages/contracts-bedrock/test/L2/L2ContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L2/L2ContractsManager.t.sol @@ -6,6 +6,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { DevFeatures } from "src/libraries/DevFeatures.sol"; import { L2ContractsManager } from "src/L2/L2ContractsManager.sol"; import { L2ContractsManagerTypes } from "src/libraries/L2ContractsManagerTypes.sol"; +import { L2ContractsManagerUtils } from "src/libraries/L2ContractsManagerUtils.sol"; import { CommonTest } from "test/setup/CommonTest.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { StorageSetter } from "src/universal/StorageSetter.sol"; @@ -23,6 +24,7 @@ import { IFeeSplitter } from "interfaces/L2/IFeeSplitter.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { ILiquidityController } from "interfaces/L2/ILiquidityController.sol"; import { IProxy } from "interfaces/universal/IProxy.sol"; +import { ISemver } from "interfaces/universal/ISemver.sol"; // Contracts import { GasPriceOracle } from "src/L2/GasPriceOracle.sol"; @@ -666,6 +668,60 @@ contract L2ContractsManager_Upgrade_CGT_Test is L2ContractsManager_Upgrade_Test } } +/// @title L2ContractsManager_Upgrade_DowngradePrevention_Test +/// @notice Test contract that verifies L2CM prevents downgrading predeploy implementations. +contract L2ContractsManager_Upgrade_DowngradePrevention_Test is L2ContractsManager_Upgrade_Test { + /// @notice Tests that upgrade reverts when a non-initializable predeploy has a higher version than the new + /// implementation. + function test_upgrade_whenDowngradingNonInitializablePredeploy_reverts() public { + // Mock GasPriceOracle to report a version higher than the new implementation + string memory higherVersion = "999.0.0"; + vm.mockCall(Predeploys.GAS_PRICE_ORACLE, abi.encodeCall(ISemver.version, ()), abi.encode(higherVersion)); + + vm.expectRevert( + abi.encodeWithSelector( + L2ContractsManagerUtils.L2ContractsManager_DowngradeNotAllowed.selector, Predeploys.GAS_PRICE_ORACLE + ) + ); + _executeUpgrade(); + } + + /// @notice Tests that upgrade reverts when an initializable predeploy has a higher version than the new + /// implementation. + function test_upgrade_whenDowngradingInitializablePredeploy_reverts() public { + // Mock L2CrossDomainMessenger to report a version higher than the new implementation + string memory higherVersion = "999.0.0"; + vm.mockCall( + Predeploys.L2_CROSS_DOMAIN_MESSENGER, abi.encodeCall(ISemver.version, ()), abi.encode(higherVersion) + ); + + vm.expectRevert( + abi.encodeWithSelector( + L2ContractsManagerUtils.L2ContractsManager_DowngradeNotAllowed.selector, + Predeploys.L2_CROSS_DOMAIN_MESSENGER + ) + ); + _executeUpgrade(); + } + + /// @notice Tests that upgrade succeeds when the predeploy has the same version as the new implementation + /// (not a downgrade). + function test_upgrade_whenSameVersion_succeeds() public { + // Mock GasPriceOracle to report the same version as the new implementation + string memory implVersion = ISemver(implementations.gasPriceOracleImpl).version(); + vm.mockCall(Predeploys.GAS_PRICE_ORACLE, abi.encodeCall(ISemver.version, ()), abi.encode(implVersion)); + + _executeUpgrade(); + + // Verify the upgrade went through + assertEq( + EIP1967Helper.getImplementation(Predeploys.GAS_PRICE_ORACLE), + implementations.gasPriceOracleImpl, + "GasPriceOracle should be upgraded" + ); + } +} + /// @title L2ContractsManager_Upgrade_Coverage_Test /// @notice Test that verifies all predeploys receive upgrade calls during L2CM upgrade. /// Uses Predeploys.sol as the source of truth for which predeploys should be upgraded. diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol index 299804295e0..cc61a0991f8 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol @@ -57,9 +57,7 @@ abstract contract OptimismSuperchainERC20_TestInit is Test { address _impl = Predeploys.predeployToCodeNamespace(_addr); vm.etch( _impl, - vm.getDeployedCode( - "forge-artifacts/OptimismSuperchainERC20Beacon.sol/OptimismSuperchainERC20Beacon.json" - ) + vm.getDeployedCode("forge-artifacts/OptimismSuperchainERC20Beacon.sol/OptimismSuperchainERC20Beacon.json") ); // Deploy the ERC1967Proxy contract at the Predeploy