diff --git a/deploy-config/local.json b/deploy-config/local.json index ca6b79a70..4d80f4919 100644 --- a/deploy-config/local.json +++ b/deploy-config/local.json @@ -9,8 +9,6 @@ "fundDevAccounts": true, "gasPriceOracleBaseFeeScalar": 1368, "gasPriceOracleBlobBaseFeeScalar": 810949, - "guardian": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", - "incidentResponder": "0x0000000000000000000000000000000000000000", "l1ChainId": 900, "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", "l1FeeVaultRecipient": "0x71bE63f3384f5fb98995898A86B02Fb2426c5788", @@ -40,6 +38,8 @@ "sequencerFeeVaultRecipient": "0xfabb0ac9d68b0b445fb7357272ff202c5651694a", "sequencerFeeVaultWithdrawalNetwork": 0, "sp1Verifier": "0x0000000000000000000000000000000000000000", + "superchainConfigGuardian": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", + "superchainConfigIncidentResponder": "0x0000000000000000000000000000000000000000", "teeChallenger": "0x976EA74026E726554dB657fA54763abd0C3a0aa9", "teeImageHash": "0x0000000000000000000000000000000000000000000000000000000000000001", "teeProposer": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", diff --git a/interfaces/L1/IL1CrossDomainMessenger.sol b/interfaces/L1/IL1CrossDomainMessenger.sol index 321fd92ce..adb75a42b 100644 --- a/interfaces/L1/IL1CrossDomainMessenger.sol +++ b/interfaces/L1/IL1CrossDomainMessenger.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; interface IL1CrossDomainMessenger is ICrossDomainMessenger, IProxyAdminOwnedBase { @@ -15,6 +16,7 @@ interface IL1CrossDomainMessenger is ICrossDomainMessenger, IProxyAdminOwnedBase function portal() external view returns (IOptimismPortal); function systemConfig() external view returns (ISystemConfig); function version() external view returns (string memory); + function superchainConfig() external view returns (ISuperchainConfig); function __constructor__() external; } diff --git a/interfaces/L1/IL1ERC721Bridge.sol b/interfaces/L1/IL1ERC721Bridge.sol index fc8248415..a73a743da 100644 --- a/interfaces/L1/IL1ERC721Bridge.sol +++ b/interfaces/L1/IL1ERC721Bridge.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import { IERC721Bridge } from "interfaces/universal/IERC721Bridge.sol"; import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; interface IL1ERC721Bridge is IERC721Bridge, IProxyAdminOwnedBase { @@ -41,6 +42,7 @@ interface IL1ERC721Bridge is IERC721Bridge, IProxyAdminOwnedBase { function paused() external view returns (bool); function systemConfig() external view returns (ISystemConfig); function version() external view returns (string memory); + function superchainConfig() external view returns (ISuperchainConfig); function __constructor__() external; } diff --git a/interfaces/L1/IL1StandardBridge.sol b/interfaces/L1/IL1StandardBridge.sol index 240a717e7..bca0a79f5 100644 --- a/interfaces/L1/IL1StandardBridge.sol +++ b/interfaces/L1/IL1StandardBridge.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import { IStandardBridge } from "interfaces/universal/IStandardBridge.sol"; import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; interface IL1StandardBridge is IStandardBridge, IProxyAdminOwnedBase { @@ -69,6 +70,7 @@ interface IL1StandardBridge is IStandardBridge, IProxyAdminOwnedBase { function l2TokenBridge() external view returns (address); function systemConfig() external view returns (ISystemConfig); function version() external view returns (string memory); + function superchainConfig() external view returns (ISuperchainConfig); function __constructor__() external; } diff --git a/interfaces/L1/IOptimismPortal2.sol b/interfaces/L1/IOptimismPortal2.sol index 0eb60f549..a2c2ce989 100644 --- a/interfaces/L1/IOptimismPortal2.sol +++ b/interfaces/L1/IOptimismPortal2.sol @@ -6,6 +6,7 @@ import { GameType } from "src/libraries/bridge/LibUDT.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; @@ -59,6 +60,7 @@ interface IOptimismPortal2 is IProxyAdminOwnedBase { function disputeGameFactory() external view returns (IDisputeGameFactory); function disputeGameFinalityDelaySeconds() external view returns (uint256); function donateETH() external payable; + function superchainConfig() external view returns (ISuperchainConfig); function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external; function finalizeWithdrawalTransactionExternalProof( Types.WithdrawalTransaction memory _tx, diff --git a/interfaces/L1/ISuperchainConfig.sol b/interfaces/L1/ISuperchainConfig.sol new file mode 100644 index 000000000..8727be84f --- /dev/null +++ b/interfaces/L1/ISuperchainConfig.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; + +interface ISuperchainConfig is IProxyAdminOwnedBase { + event Paused(address identifier); + event Unpaused(address identifier); + event PauseExtended(address identifier); + + error SuperchainConfig_OnlyGuardian(); + error SuperchainConfig_OnlyGuardianOrIncidentResponder(); + error SuperchainConfig_AlreadyPaused(address identifier); + error SuperchainConfig_NotAlreadyPaused(address identifier); + + function guardian() external view returns (address); + function incidentResponder() external view returns (address); + function pause(address _identifier) external; + function unpause(address _identifier) external; + function pausable(address _identifier) external view returns (bool); + function paused() external view returns (bool); + function paused(address _identifier) external view returns (bool); + function expiration(address _identifier) external view returns (uint256); + function extend(address _identifier) external; + function version() external view returns (string memory); + function pauseTimestamps(address) external view returns (uint256); + function pauseExpiry() external view returns (uint256); + + function __constructor__(address _guardian, address _incidentResponder) external; +} diff --git a/interfaces/L1/ISystemConfig.sol b/interfaces/L1/ISystemConfig.sol index b1c90e011..837ca4e7c 100644 --- a/interfaces/L1/ISystemConfig.sol +++ b/interfaces/L1/ISystemConfig.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; interface ISystemConfig is IProxyAdminOwnedBase { @@ -27,22 +28,13 @@ interface ISystemConfig is IProxyAdminOwnedBase { error ReinitializableBase_ZeroInitVersion(); error SystemConfig_InvalidFeatureState(); - error SystemConfig_OnlyGuardian(); - error SystemConfig_OnlyGuardianOrIncidentResponder(); - error SystemConfig_AlreadyPaused(); - error SystemConfig_NotAlreadyPaused(); event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); event FeatureSet(bytes32 indexed feature, bool indexed enabled); event Initialized(uint8 version); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - event Paused(); - event Unpaused(); - event PauseExtended(); function BATCH_INBOX_SLOT() external view returns (bytes32); - function GUARDIAN() external view returns (address); - function INCIDENT_RESPONDER() external view returns (address); function L1_CROSS_DOMAIN_MESSENGER_SLOT() external view returns (bytes32); function L1_ERC_721_BRIDGE_SLOT() external view returns (bytes32); function L1_STANDARD_BRIDGE_SLOT() external view returns (bytes32); @@ -71,7 +63,8 @@ interface ISystemConfig is IProxyAdminOwnedBase { IResourceMetering.ResourceConfig memory _config, address _batchInbox, Addresses memory _addresses, - uint256 _l2ChainId + uint256 _l2ChainId, + ISuperchainConfig _superchainConfig ) external; function initVersion() external view returns (uint8); @@ -108,18 +101,11 @@ interface ISystemConfig is IProxyAdminOwnedBase { function unsafeBlockSigner() external view returns (address addr_); function version() external pure returns (string memory); function paused() external view returns (bool); + function superchainConfig() external view returns (ISuperchainConfig); function guardian() external view returns (address); - function incidentResponder() external view returns (address); - function pause() external; - function unpause() external; - function extend() external; - function pausable() external view returns (bool); - function expiration() external view returns (uint256); - function pauseTimestamp() external view returns (uint256); - function pauseExpiry() external view returns (uint256); function setFeature(bytes32 _feature, bool _enabled) external; function isFeatureEnabled(bytes32) external view returns (bool); function isCustomGasToken() external view returns (bool); - function __constructor__(address _guardian, address _incidentResponder) external; + function __constructor__() external; } diff --git a/interfaces/L1/proofs/IAnchorStateRegistry.sol b/interfaces/L1/proofs/IAnchorStateRegistry.sol index 3283a8f13..7823ac899 100644 --- a/interfaces/L1/proofs/IAnchorStateRegistry.sol +++ b/interfaces/L1/proofs/IAnchorStateRegistry.sol @@ -5,6 +5,7 @@ import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { GameType, Hash, Proposal } from "src/libraries/bridge/Types.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; interface IAnchorStateRegistry is IProxyAdminOwnedBase { @@ -50,6 +51,7 @@ interface IAnchorStateRegistry is IProxyAdminOwnedBase { function systemConfig() external view returns (ISystemConfig); function updateRetirementTimestamp() external; function version() external view returns (string memory); + function superchainConfig() external view returns (ISuperchainConfig); function __constructor__( uint256 _disputeGameFinalityDelaySeconds diff --git a/interfaces/L1/proofs/IDelayedWETH.sol b/interfaces/L1/proofs/IDelayedWETH.sol index dcf3d51c3..05b448e55 100644 --- a/interfaces/L1/proofs/IDelayedWETH.sol +++ b/interfaces/L1/proofs/IDelayedWETH.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; interface IDelayedWETH is IProxyAdminOwnedBase { @@ -58,6 +59,7 @@ interface IDelayedWETH is IProxyAdminOwnedBase { function transferFrom(address src, address dst, uint256 wad) external returns (bool); + function config() external view returns (ISuperchainConfig); function __constructor__(uint256 _delay) external; } diff --git a/interfaces/legacy/ILegacySuperchainConfig.sol b/interfaces/legacy/ILegacySuperchainConfig.sol deleted file mode 100644 index fbd92af3e..000000000 --- a/interfaces/legacy/ILegacySuperchainConfig.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface ILegacySuperchainConfig { - function pausable(address _identifier) external view returns (bool); -} - -interface ILegacySystemConfig { - function superchainConfig() external view returns (ILegacySuperchainConfig); -} diff --git a/scripts/deploy/DeployConfig.s.sol b/scripts/deploy/DeployConfig.s.sol index bc31b424d..6097a0e94 100644 --- a/scripts/deploy/DeployConfig.s.sol +++ b/scripts/deploy/DeployConfig.s.sol @@ -13,8 +13,6 @@ contract DeployConfig is Script { address public baseFeeVaultRecipient; address public batchSenderAddress; address public finalSystemOwner; - address public guardian; - address public incidentResponder; address public l1FeeVaultRecipient; address public nitroValidator; address public operatorFeeVaultRecipient; @@ -22,6 +20,8 @@ contract DeployConfig is Script { address public proxyAdminOwner; address public sequencerFeeVaultRecipient; address public sp1Verifier; + address public superchainConfigGuardian; + address public superchainConfigIncidentResponder; address public teeChallenger; address public teeProposer; @@ -80,8 +80,6 @@ contract DeployConfig is Script { baseFeeVaultRecipient = _json.readAddress("$.baseFeeVaultRecipient"); batchSenderAddress = _json.readAddress("$.batchSenderAddress"); finalSystemOwner = _json.readAddress("$.finalSystemOwner"); - guardian = _json.readAddress("$.guardian"); - incidentResponder = _json.readAddress("$.incidentResponder"); l1FeeVaultRecipient = _json.readAddress("$.l1FeeVaultRecipient"); nitroValidator = _json.readAddress("$.nitroValidator"); operatorFeeVaultRecipient = _json.readAddress("$.operatorFeeVaultRecipient"); @@ -89,6 +87,8 @@ contract DeployConfig is Script { proxyAdminOwner = _json.readAddress("$.proxyAdminOwner"); sequencerFeeVaultRecipient = _json.readAddress("$.sequencerFeeVaultRecipient"); sp1Verifier = _json.readAddress("$.sp1Verifier"); + superchainConfigGuardian = _json.readAddress("$.superchainConfigGuardian"); + superchainConfigIncidentResponder = _json.readAddress("$.superchainConfigIncidentResponder"); teeChallenger = _json.readAddress("$.teeChallenger"); teeProposer = _json.readAddress("$.teeProposer"); diff --git a/scripts/deploy/SystemDeploy.s.sol b/scripts/deploy/SystemDeploy.s.sol index b33caba8f..0923c411c 100644 --- a/scripts/deploy/SystemDeploy.s.sol +++ b/scripts/deploy/SystemDeploy.s.sol @@ -17,9 +17,9 @@ import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; -import { ILegacySuperchainConfig, ILegacySystemConfig } from "interfaces/legacy/ILegacySuperchainConfig.sol"; import { IL1ChugSplashProxy } from "interfaces/legacy/IL1ChugSplashProxy.sol"; import { IResolvedDelegateProxy } from "interfaces/legacy/IResolvedDelegateProxy.sol"; import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; @@ -40,6 +40,7 @@ import { TEEVerifier } from "src/L1/proofs/tee/TEEVerifier.sol"; import { ISP1Verifier } from "interfaces/L1/proofs/zk/ISP1Verifier.sol"; import { ZKVerifier } from "src/L1/proofs/zk/ZKVerifier.sol"; import { Constants } from "src/libraries/Constants.sol"; +import { SemverComp } from "src/libraries/SemverComp.sol"; import { GameType, GameTypes, Hash, Proposal } from "src/libraries/bridge/Types.sol"; import { Claim } from "src/libraries/bridge/LibUDT.sol"; @@ -55,6 +56,18 @@ contract SystemDeploy is Script { uint256 internal constant ETH_MAINNET_CHAIN_ID = 1; uint256 internal constant ETH_SEPOLIA_CHAIN_ID = 11155111; + struct SuperchainInput { + address guardian; + address incidentResponder; + address superchainProxyAdminOwner; + } + + struct SuperchainOutput { + ISuperchainConfig superchainConfigImpl; + ISuperchainConfig superchainConfigProxy; + IProxyAdmin superchainProxyAdmin; + } + struct ImplementationInput { uint256 withdrawalDelaySeconds; uint256 proofMaturityDelaySeconds; @@ -79,24 +92,29 @@ contract SystemDeploy is Script { struct DeployInput { bool saveArtifacts; + SuperchainInput superchainInput; + ISuperchainConfig superchainConfigProxy; ImplementationInput implementationsInput; Types.Implementations implementations; Types.DeployInput opChainInput; } struct DeployOutput { + SuperchainOutput superchain; Types.Implementations impls; Types.DeployOutput opChain; } struct UpgradeInput { bool saveArtifacts; + ISuperchainConfig superchainConfigProxy; Types.Implementations implementations; ISystemConfig systemConfigProxy; IProtocolVersions protocolVersionsProxy; } struct UpgradeOutput { + bool superchainConfigUpgraded; bool chainUpgraded; } @@ -132,8 +150,8 @@ contract SystemDeploy is Script { error InvalidChainId(); error InvalidRoleAddress(string role); error InvalidStartingAnchorRoot(); - error LegacySuperchainConfigPaused(); error MissingImplementations(); + error SuperchainConfigNeedsUpgrade(); /// @notice Sets up the shared deployment config and artifact registry. function setUp() public virtual { @@ -173,7 +191,7 @@ contract SystemDeploy is Script { /// @notice Deploys a fresh OP Stack from the active deploy config. function run() public { - console.log("Deploying a fresh OP Stack"); + console.log("Deploying a fresh OP Stack including SuperchainConfig"); _runConfigured(); } @@ -188,9 +206,15 @@ contract SystemDeploy is Script { _saveUpgradeArtifacts(output_); } + /// @notice Deploys the shared Superchain proxy admin and SuperchainConfig proxy. + function deploySuperchain(SuperchainInput memory _input) public returns (SuperchainOutput memory output_) { + output_ = _deploySuperchain(_input); + } + /// @notice Returns the latest implementation set saved by `deployImplementations` or `run`. function getImplementations() public view returns (Types.Implementations memory) { return Types.Implementations({ + superchainConfigImpl: artifacts.mustGetAddress("SuperchainConfigImpl"), l1ERC721BridgeImpl: artifacts.mustGetAddress("L1ERC721BridgeImpl"), optimismPortalImpl: artifacts.mustGetAddress("OptimismPortalImpl"), systemConfigImpl: artifacts.mustGetAddress("SystemConfigImpl"), @@ -211,7 +235,7 @@ contract SystemDeploy is Script { function _runConfigured() internal returns (DeployOutput memory output_) { output_ = deploy(_deployInput()); - vm.startPrank(ISystemConfig(artifacts.mustGetAddress("SystemConfigProxy")).guardian()); + vm.startPrank(ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")).guardian()); IAnchorStateRegistry(artifacts.mustGetAddress("AnchorStateRegistryProxy")) .setRespectedGameType(GameType.wrap(uint32(cfg.respectedGameType()))); vm.stopPrank(); @@ -223,6 +247,12 @@ contract SystemDeploy is Script { Types.Implementations memory emptyImpls; input_ = DeployInput({ saveArtifacts: true, + superchainInput: SuperchainInput({ + guardian: cfg.superchainConfigGuardian(), + incidentResponder: cfg.superchainConfigIncidentResponder(), + superchainProxyAdminOwner: cfg.finalSystemOwner() + }), + superchainConfigProxy: ISuperchainConfig(address(0)), implementationsInput: _configuredImplementationsInput(), implementations: emptyImpls, opChainInput: _configuredOPChainInput() @@ -248,8 +278,8 @@ contract SystemDeploy is Script { sp1Verifier: ISP1Verifier(cfg.sp1Verifier()), teeProposer: cfg.teeProposer(), teeChallenger: cfg.teeChallenger(), - guardian: cfg.guardian(), - incidentResponder: cfg.incidentResponder() + guardian: cfg.superchainConfigGuardian(), + incidentResponder: cfg.superchainConfigIncidentResponder() }); } @@ -274,7 +304,7 @@ contract SystemDeploy is Script { systemConfigOwner: cfg.finalSystemOwner(), batcher: cfg.batchSenderAddress(), unsafeBlockSigner: cfg.p2pSequencerAddress(), - incidentResponder: cfg.incidentResponder() + incidentResponder: cfg.superchainConfigIncidentResponder() }), basefeeScalar: cfg.basefeeScalar(), blobBasefeeScalar: cfg.blobbasefeeScalar(), @@ -294,17 +324,20 @@ contract SystemDeploy is Script { // script. _assertValidOPChainInput(_input.opChainInput); + output_.superchain = _deployOrLoadSuperchain(_input); if (_implementationsEmpty(_input.implementations)) { output_.impls = _deployImplementations(_input.implementationsInput); } else { _assertValidImplementations(_input.implementations); - _assertValidSystemConfigRoles(_input.implementations.systemConfigImpl, _input.implementationsInput); output_.impls = _input.implementations; } Types.Implementations memory implementations; (output_.opChain, implementations) = _deployOPChain({ - _input: _input.opChainInput, _impls: output_.impls, _implementationsInput: _input.implementationsInput + _input: _input.opChainInput, + _superchainConfig: output_.superchain.superchainConfigProxy, + _impls: output_.impls, + _implementationsInput: _input.implementationsInput }); output_.impls = implementations; @@ -318,10 +351,22 @@ contract SystemDeploy is Script { function upgrade(UpgradeInput memory _input) public returns (UpgradeOutput memory output_) { _assertValidImplementations(_input.implementations); + if (address(_input.superchainConfigProxy) != address(0)) { + output_.superchainConfigUpgraded = + _upgradeSuperchainConfigIfNeeded(_input.superchainConfigProxy, _input.implementations); + } + if (address(_input.systemConfigProxy) != address(0)) { ISystemConfig systemConfigProxy = _input.systemConfigProxy; DeployUtils.assertValidContractAddress(address(systemConfigProxy)); + ISuperchainConfig superchainConfig = systemConfigProxy.superchainConfig(); + if (SemverComp.lt( + superchainConfig.version(), ISuperchainConfig(_input.implementations.superchainConfigImpl).version() + )) { + revert SuperchainConfigNeedsUpgrade(); + } + IProtocolVersions protocolVersionsProxy = _input.protocolVersionsProxy; if (address(protocolVersionsProxy) == address(0) && address(artifacts).code.length != 0) { protocolVersionsProxy = IProtocolVersions(artifacts.getAddress("ProtocolVersionsProxy")); @@ -336,13 +381,99 @@ contract SystemDeploy is Script { } } + function _deployOrLoadSuperchain(DeployInput memory _input) internal returns (SuperchainOutput memory output_) { + if (address(_input.superchainConfigProxy) == address(0)) { + output_ = _deploySuperchain(_input.superchainInput); + } else { + DeployUtils.assertValidContractAddress(address(_input.superchainConfigProxy)); + output_.superchainConfigProxy = _input.superchainConfigProxy; + output_.superchainProxyAdmin = _input.superchainConfigProxy.proxyAdmin(); + } + } + + function _deploySuperchain(SuperchainInput memory _input) internal returns (SuperchainOutput memory output_) { + _assertValidSuperchainInput(_input); + + output_.superchainProxyAdmin = _deploySuperchainProxyAdmin(); + output_.superchainConfigImpl = _deploySuperchainConfigImpl(_input.guardian, _input.incidentResponder); + output_.superchainConfigProxy = + _deploySuperchainConfigProxy(output_.superchainProxyAdmin, output_.superchainConfigImpl); + + DeployUtils.assertValidContractAddress(address(output_.superchainProxyAdmin)); + vm.broadcast(msg.sender); + output_.superchainProxyAdmin.transferOwnership(_input.superchainProxyAdminOwner); + + _assertValidSuperchainOutput(_input, output_); + } + + function _deploySuperchainProxyAdmin() internal returns (IProxyAdmin proxyAdmin_) { + vm.broadcast(msg.sender); + proxyAdmin_ = IProxyAdmin( + DeployUtils.create1({ + _name: "ProxyAdmin", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxyAdmin.__constructor__, (msg.sender))) + }) + ); + vm.label(address(proxyAdmin_), "SuperchainProxyAdmin"); + } + + function _deploySuperchainConfigProxy( + IProxyAdmin _proxyAdmin, + ISuperchainConfig _impl + ) + internal + returns (ISuperchainConfig proxy_) + { + vm.startBroadcast(msg.sender); + proxy_ = ISuperchainConfig( + DeployUtils.create1({ + _name: "src/universal/Proxy.sol:Proxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (address(_proxyAdmin)))) + }) + ); + _proxyAdmin.upgrade(payable(address(proxy_)), address(_impl)); + vm.stopBroadcast(); + + vm.label(address(proxy_), "SuperchainConfigProxy"); + } + + function _assertValidSuperchainInput(SuperchainInput memory _input) internal pure { + if (_input.superchainProxyAdminOwner == address(0)) revert InvalidRoleAddress("superchainProxyAdminOwner"); + if (_input.guardian == address(0)) revert InvalidRoleAddress("guardian"); + } + + function _assertValidSuperchainOutput(SuperchainInput memory _input, SuperchainOutput memory _output) internal { + address[] memory addrs = new address[](3); + addrs[0] = address(_output.superchainProxyAdmin); + addrs[1] = address(_output.superchainConfigImpl); + addrs[2] = address(_output.superchainConfigProxy); + DeployUtils.assertValidContractAddresses(addrs); + + vm.startPrank(address(0)); + require( + IProxy(payable(address(_output.superchainConfigProxy))).implementation() + == address(_output.superchainConfigImpl), + "SUPCON-30" + ); + require( + IProxy(payable(address(_output.superchainConfigProxy))).admin() == address(_output.superchainProxyAdmin), + "SUPCON-40" + ); + vm.stopPrank(); + + require(_output.superchainProxyAdmin.owner() == _input.superchainProxyAdminOwner, "SPA-10"); + require(_output.superchainConfigProxy.guardian() == _input.guardian, "SUPCON-10"); + require(_output.superchainConfigImpl.guardian() == _input.guardian, "SUPCON-50"); + } + function _deployImplementations(ImplementationInput memory _input) internal returns (Types.Implementations memory output_) { _assertValidImplementationInput(_input); - output_.systemConfigImpl = address(_deploySystemConfigImpl(_input.guardian, _input.incidentResponder)); + output_.superchainConfigImpl = address(_deploySuperchainConfigImpl(_input.guardian, _input.incidentResponder)); + output_.systemConfigImpl = address(_deploySystemConfigImpl()); output_.l1CrossDomainMessengerImpl = address(_deployL1CrossDomainMessengerImpl()); output_.l1ERC721BridgeImpl = address(_deployL1ERC721BridgeImpl()); output_.l1StandardBridgeImpl = address(_deployL1StandardBridgeImpl()); @@ -356,6 +487,7 @@ contract SystemDeploy is Script { function _deployOPChain( Types.DeployInput memory _input, + ISuperchainConfig _superchainConfig, Types.Implementations memory _impls, ImplementationInput memory _implementationsInput ) @@ -422,7 +554,7 @@ contract SystemDeploy is Script { vm.broadcast(msg.sender); output_.opChainProxyAdmin.setImplementationName(address(output_.l1CrossDomainMessengerProxy), messengerName); - _initializeOPChain(_input, impls_, output_); + _initializeOPChain(_input, _superchainConfig, impls_, output_); _upgradeToAndCall( output_.opChainProxyAdmin, @@ -451,6 +583,7 @@ contract SystemDeploy is Script { function _initializeOPChain( Types.DeployInput memory _input, + ISuperchainConfig _superchainConfig, Types.Implementations memory _impls, Types.DeployOutput memory _output ) @@ -467,7 +600,7 @@ contract SystemDeploy is Script { _output.opChainProxyAdmin, address(_output.systemConfigProxy), _impls.systemConfigImpl, - _encodeSystemConfigInitializer(_input, _output) + _encodeSystemConfigInitializer(_input, _output, _superchainConfig) ); _upgradeToAndCall( @@ -528,6 +661,22 @@ contract SystemDeploy is Script { ); } + function _upgradeSuperchainConfigIfNeeded( + ISuperchainConfig _superchainConfig, + Types.Implementations memory _impls + ) + internal + returns (bool upgraded_) + { + if (SemverComp.gte(_superchainConfig.version(), ISuperchainConfig(_impls.superchainConfigImpl).version())) { + return false; + } + + IProxyAdmin superchainProxyAdmin = _superchainConfig.proxyAdmin(); + _upgradeTo(superchainProxyAdmin, address(_superchainConfig), _impls.superchainConfigImpl); + upgraded_ = true; + } + function _upgradeOPChain( ISystemConfig _systemConfigProxy, Types.Implementations memory _impls, @@ -538,8 +687,6 @@ contract SystemDeploy is Script { IProxyAdmin proxyAdmin = _systemConfigProxy.proxyAdmin(); uint256 l2ChainId = _systemConfigProxy.l2ChainId(); - _assertLegacySuperchainConfigNotPaused(_systemConfigProxy); - _upgradeTo(proxyAdmin, address(_systemConfigProxy), _impls.systemConfigImpl); IOptimismPortal optimismPortal = IOptimismPortal(payable(_systemConfigProxy.optimismPortal())); @@ -568,20 +715,6 @@ contract SystemDeploy is Script { emit Upgraded(l2ChainId, _systemConfigProxy, msg.sender); } - /// @notice Rejects a migration from SuperchainConfig until its global and chain-specific pause records are cleared. - /// @dev A pause submitted after this check and before the upgrade can be lost. Either the Guardian or the - /// Incident Responder can pause during that window, so coordinate migrations with both. - function _assertLegacySuperchainConfigNotPaused(ISystemConfig _systemConfigProxy) internal view { - (bool success, bytes memory returndata) = - address(_systemConfigProxy).staticcall(abi.encodeCall(ILegacySystemConfig.superchainConfig, ())); - if (!success || returndata.length != 32) return; - - ILegacySuperchainConfig superchainConfig = ILegacySuperchainConfig(abi.decode(returndata, (address))); - if (!superchainConfig.pausable(address(0)) || !superchainConfig.pausable(_systemConfigProxy.optimismPortal())) { - revert LegacySuperchainConfigPaused(); - } - } - function _upgradeMultiproofContracts( ISystemConfig _systemConfigProxy, IDisputeGameFactory _disputeGameFactory, @@ -615,7 +748,8 @@ contract SystemDeploy is Script { function _encodeSystemConfigInitializer( Types.DeployInput memory _input, - Types.DeployOutput memory _output + Types.DeployOutput memory _output, + ISuperchainConfig _superchainConfig ) internal pure @@ -642,7 +776,8 @@ contract SystemDeploy is Script { Constants.DEFAULT_RESOURCE_CONFIG(), Types.chainIdToBatchInboxAddress(_input.l2ChainId), opChainAddrs, - _input.l2ChainId + _input.l2ChainId, + _superchainConfig ) ); } @@ -743,18 +878,34 @@ contract SystemDeploy is Script { IAddressManager(_target).transferOwnership(_newOwner); } - function _deploySystemConfigImpl(address _guardian, address _incidentResponder) internal returns (ISystemConfig) { - return ISystemConfig( + function _deploySuperchainConfigImpl( + address _guardian, + address _incidentResponder + ) + internal + returns (ISuperchainConfig) + { + return ISuperchainConfig( DeployUtils.createDeterministic({ - _name: "SystemConfig", + _name: "SuperchainConfig", _args: DeployUtils.encodeConstructor( - abi.encodeCall(ISystemConfig.__constructor__, (_guardian, _incidentResponder)) + abi.encodeCall(ISuperchainConfig.__constructor__, (_guardian, _incidentResponder)) ), _salt: DeployUtils.DEFAULT_SALT }) ); } + function _deploySystemConfigImpl() internal returns (ISystemConfig) { + return ISystemConfig( + DeployUtils.createDeterministic({ + _name: "SystemConfig", + _args: DeployUtils.encodeConstructor(abi.encodeCall(ISystemConfig.__constructor__, ())), + _salt: DeployUtils.DEFAULT_SALT + }) + ); + } + function _deployL1CrossDomainMessengerImpl() internal returns (IL1CrossDomainMessenger) { return IL1CrossDomainMessenger( DeployUtils.createDeterministic({ @@ -971,7 +1122,6 @@ contract SystemDeploy is Script { require(_input.withdrawalDelaySeconds != 0, "SystemDeploy: withdrawalDelaySeconds not set"); require(_input.proofMaturityDelaySeconds != 0, "SystemDeploy: proofMaturityDelaySeconds not set"); require(_input.disputeGameFinalityDelaySeconds != 0, "SystemDeploy: finality delay not set"); - if (_input.guardian == address(0)) revert InvalidRoleAddress("guardian"); } function _multiproofEnabled(ImplementationInput memory _input) internal pure returns (bool) { @@ -1005,6 +1155,7 @@ contract SystemDeploy is Script { function _assertValidImplementations(Types.Implementations memory _impls) internal view { if (_implementationsEmpty(_impls)) revert MissingImplementations(); + DeployUtils.assertValidContractAddress(_impls.superchainConfigImpl); DeployUtils.assertValidContractAddress(_impls.l1ERC721BridgeImpl); DeployUtils.assertValidContractAddress(_impls.optimismPortalImpl); DeployUtils.assertValidContractAddress(_impls.systemConfigImpl); @@ -1022,27 +1173,17 @@ contract SystemDeploy is Script { } } - /// @notice Rejects a supplied SystemConfig implementation that does not carry the configured pause authorities. - /// @dev The Guardian and Incident Responder are constructor immutables, so an implementation that the script did - /// not build itself silently replaces the roles the deploy input asks for. - function _assertValidSystemConfigRoles(address _systemConfigImpl, ImplementationInput memory _input) internal view { - if (_input.guardian == address(0)) revert InvalidRoleAddress("guardian"); - - ISystemConfig systemConfigImpl = ISystemConfig(_systemConfigImpl); - require(systemConfigImpl.GUARDIAN() == _input.guardian, "SystemDeploy: SystemConfig guardian mismatch"); - require( - systemConfigImpl.INCIDENT_RESPONDER() == _input.incidentResponder, - "SystemDeploy: SystemConfig incident responder mismatch" - ); - } - function _implementationsEmpty(Types.Implementations memory _impls) internal pure returns (bool) { - return _impls.systemConfigImpl == address(0) && _impls.l1CrossDomainMessengerImpl == address(0); + return _impls.superchainConfigImpl == address(0) && _impls.systemConfigImpl == address(0) + && _impls.l1CrossDomainMessengerImpl == address(0); } function _saveDeployArtifacts(DeployOutput memory _output) internal { _saveUpgradeArtifacts(_output.impls); + artifacts.save("SuperchainProxyAdmin", address(_output.superchain.superchainProxyAdmin)); + artifacts.save("SuperchainConfigProxy", address(_output.superchain.superchainConfigProxy)); + Types.DeployOutput memory chain = _output.opChain; artifacts.save("ProxyAdmin", address(chain.opChainProxyAdmin)); artifacts.save("AddressManager", address(chain.addressManager)); @@ -1070,6 +1211,7 @@ contract SystemDeploy is Script { } function _saveUpgradeArtifacts(Types.Implementations memory _impls) internal { + artifacts.save("SuperchainConfigImpl", _impls.superchainConfigImpl); artifacts.save("L1ERC721BridgeImpl", _impls.l1ERC721BridgeImpl); artifacts.save("OptimismPortalImpl", _impls.optimismPortalImpl); artifacts.save("SystemConfigImpl", _impls.systemConfigImpl); diff --git a/scripts/libraries/Types.sol b/scripts/libraries/Types.sol index 658605ec5..4f5d43e00 100644 --- a/scripts/libraries/Types.sol +++ b/scripts/libraries/Types.sol @@ -73,6 +73,7 @@ library Types { /// @notice The latest implementation contracts for the OP Stack. struct Implementations { + address superchainConfigImpl; address l1ERC721BridgeImpl; address optimismPortalImpl; address systemConfigImpl; diff --git a/scripts/multiproof/mocks/MockAnchorStateRegistry.sol b/scripts/multiproof/mocks/MockAnchorStateRegistry.sol index 37a8f66b8..72979c6c0 100644 --- a/scripts/multiproof/mocks/MockAnchorStateRegistry.sol +++ b/scripts/multiproof/mocks/MockAnchorStateRegistry.sol @@ -9,7 +9,7 @@ import { GameType, Hash, Proposal } from "src/libraries/bridge/Types.sol"; /// @notice Minimal mock for testing - stores anchor state and factory reference. /// @dev We use a mock instead of the real AnchorStateRegistry because: /// 1. The real contract requires deploying the entire Optimism L1 stack -/// (SystemConfig, ProxyAdmin, Guardian roles, etc.) +/// (SystemConfig, SuperchainConfig, ProxyAdmin, Guardian roles, etc.) /// 2. The real contract has "stack too deep" compilation issues that require /// special compiler settings (via-ir) which significantly slow builds contract MockAnchorStateRegistry { diff --git a/snapshots/abi/AnchorStateRegistry.json b/snapshots/abi/AnchorStateRegistry.json index 69c4bb856..8957b66af 100644 --- a/snapshots/abi/AnchorStateRegistry.json +++ b/snapshots/abi/AnchorStateRegistry.json @@ -444,6 +444,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "superchainConfig", + "outputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "systemConfig", diff --git a/snapshots/abi/DelayedWETH.json b/snapshots/abi/DelayedWETH.json index 3f4128eaf..54b9f7f99 100644 --- a/snapshots/abi/DelayedWETH.json +++ b/snapshots/abi/DelayedWETH.json @@ -85,6 +85,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "config", + "outputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "decimals", diff --git a/snapshots/abi/L1CrossDomainMessenger.json b/snapshots/abi/L1CrossDomainMessenger.json index bab9eec87..bc0174b42 100644 --- a/snapshots/abi/L1CrossDomainMessenger.json +++ b/snapshots/abi/L1CrossDomainMessenger.json @@ -405,6 +405,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "superchainConfig", + "outputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "systemConfig", diff --git a/snapshots/abi/L1ERC721Bridge.json b/snapshots/abi/L1ERC721Bridge.json index cab371305..7b83c9246 100644 --- a/snapshots/abi/L1ERC721Bridge.json +++ b/snapshots/abi/L1ERC721Bridge.json @@ -264,6 +264,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "superchainConfig", + "outputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "systemConfig", diff --git a/snapshots/abi/L1StandardBridge.json b/snapshots/abi/L1StandardBridge.json index 295a685fc..78752f602 100644 --- a/snapshots/abi/L1StandardBridge.json +++ b/snapshots/abi/L1StandardBridge.json @@ -523,6 +523,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "superchainConfig", + "outputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "systemConfig", diff --git a/snapshots/abi/OptimismPortal2.json b/snapshots/abi/OptimismPortal2.json index 91839752f..bfa54a916 100644 --- a/snapshots/abi/OptimismPortal2.json +++ b/snapshots/abi/OptimismPortal2.json @@ -575,6 +575,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "superchainConfig", + "outputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "systemConfig", diff --git a/snapshots/abi/SuperchainConfig.json b/snapshots/abi/SuperchainConfig.json new file mode 100644 index 000000000..cfc27101f --- /dev/null +++ b/snapshots/abi/SuperchainConfig.json @@ -0,0 +1,351 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_guardian", + "type": "address" + }, + { + "internalType": "address", + "name": "_incidentResponder", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "GUARDIAN", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "INCIDENT_RESPONDER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_identifier", + "type": "address" + } + ], + "name": "expiration", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_identifier", + "type": "address" + } + ], + "name": "extend", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "guardian", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "incidentResponder", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_identifier", + "type": "address" + } + ], + "name": "pausable", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_identifier", + "type": "address" + } + ], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pauseExpiry", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "pauseTimestamps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_identifier", + "type": "address" + } + ], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proxyAdmin", + "outputs": [ + { + "internalType": "contract IProxyAdmin", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proxyAdminOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_identifier", + "type": "address" + } + ], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "identifier", + "type": "address" + } + ], + "name": "PauseExtended", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "identifier", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "identifier", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [], + "name": "ProxyAdminOwnedBase_NotProxyAdmin", + "type": "error" + }, + { + "inputs": [], + "name": "ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner", + "type": "error" + }, + { + "inputs": [], + "name": "ProxyAdminOwnedBase_NotProxyAdminOwner", + "type": "error" + }, + { + "inputs": [], + "name": "ProxyAdminOwnedBase_NotResolvedDelegateProxy", + "type": "error" + }, + { + "inputs": [], + "name": "ProxyAdminOwnedBase_NotSharedProxyAdminOwner", + "type": "error" + }, + { + "inputs": [], + "name": "ProxyAdminOwnedBase_ProxyAdminNotFound", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "identifier", + "type": "address" + } + ], + "name": "SuperchainConfig_AlreadyPaused", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "identifier", + "type": "address" + } + ], + "name": "SuperchainConfig_NotAlreadyPaused", + "type": "error" + }, + { + "inputs": [], + "name": "SuperchainConfig_OnlyGuardian", + "type": "error" + }, + { + "inputs": [], + "name": "SuperchainConfig_OnlyGuardianOrIncidentResponder", + "type": "error" + } +] \ No newline at end of file diff --git a/snapshots/abi/SystemConfig.json b/snapshots/abi/SystemConfig.json index 49db9ae02..826bfbcd9 100644 --- a/snapshots/abi/SystemConfig.json +++ b/snapshots/abi/SystemConfig.json @@ -1,17 +1,6 @@ [ { - "inputs": [ - { - "internalType": "address", - "name": "_guardian", - "type": "address" - }, - { - "internalType": "address", - "name": "_incidentResponder", - "type": "address" - } - ], + "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, @@ -41,32 +30,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "GUARDIAN", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "INCIDENT_RESPONDER", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "L1_CROSS_DOMAIN_MESSENGER_SLOT", @@ -288,26 +251,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "expiration", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "extend", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "gasLimit", @@ -379,19 +322,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "incidentResponder", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "initVersion", @@ -520,6 +450,11 @@ "internalType": "uint256", "name": "_l2ChainId", "type": "uint256" + }, + { + "internalType": "contract ISuperchainConfig", + "name": "_superchainConfig", + "type": "address" } ], "name": "initialize", @@ -728,52 +663,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "pausable", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "pauseExpiry", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "pauseTimestamp", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "paused", @@ -1059,6 +948,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "superchainConfig", + "outputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -1072,13 +974,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [], - "name": "unpause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "unsafeBlockSigner", @@ -1181,24 +1076,6 @@ "name": "OwnershipTransferred", "type": "event" }, - { - "anonymous": false, - "inputs": [], - "name": "PauseExtended", - "type": "event" - }, - { - "anonymous": false, - "inputs": [], - "name": "Paused", - "type": "event" - }, - { - "anonymous": false, - "inputs": [], - "name": "Unpaused", - "type": "event" - }, { "inputs": [], "name": "ProxyAdminOwnedBase_NotProxyAdmin", @@ -1234,29 +1111,9 @@ "name": "ReinitializableBase_ZeroInitVersion", "type": "error" }, - { - "inputs": [], - "name": "SystemConfig_AlreadyPaused", - "type": "error" - }, { "inputs": [], "name": "SystemConfig_InvalidFeatureState", "type": "error" - }, - { - "inputs": [], - "name": "SystemConfig_NotAlreadyPaused", - "type": "error" - }, - { - "inputs": [], - "name": "SystemConfig_OnlyGuardian", - "type": "error" - }, - { - "inputs": [], - "name": "SystemConfig_OnlyGuardianOrIncidentResponder", - "type": "error" } ] \ No newline at end of file diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 76fc7ef0f..3240f0755 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -1,39 +1,43 @@ { "src/L1/L1CrossDomainMessenger.sol:L1CrossDomainMessenger": { - "initCodeHash": "0x201a15eb9ae10168f6c2e467a050d7c8246d5fd8d1d3f2b9bf15b1f3f71820e8", - "sourceCodeHash": "0x1f3e2c1d357ca4ab1973be2e6a49c67ccac1a325f3ca618dceff54ef47d20ea8" + "initCodeHash": "0x3dc659aafb03bd357f92abfc6794af89ee0ddd5212364551637422bf8d0b00f9", + "sourceCodeHash": "0xb1467f925aaf0a834bc98cdfaffe8832bdf57d57a0e22087179d1c3b4bee2c53" }, "src/L1/L1ERC721Bridge.sol:L1ERC721Bridge": { - "initCodeHash": "0x4ab53d1f58ddf1d106f805ad4633cf76a493af324f62405d994e427f17165a53", - "sourceCodeHash": "0x052fa698c0526d026bc91ebc033d75160ce9f0e3aa395834c00707e2896a3c3c" + "initCodeHash": "0x373a859460bac6e05f3c31daa21ff44be50c52e1e2591dd33f255f5a5d5679a5", + "sourceCodeHash": "0xb20a9176e07c550466ccea8f131d74e98112133e1e833a72cbae5da93039392c" }, "src/L1/L1StandardBridge.sol:L1StandardBridge": { - "initCodeHash": "0xb19625cd26ea7d8f79a19e292e409ab505e730e5273f2179eba8b2c6008480e8", - "sourceCodeHash": "0x12df1f137e66f1c023d0496f6ecd300c3cf54926de7af4fd4720f1fc8ce82689" + "initCodeHash": "0xa83a0ca66ce82174573e0cb2a9dfa8198e5c5d945f6762b1d2fe6c63adc75cc0", + "sourceCodeHash": "0xd9f576a79e97bc541b3d7a2ee928f34223edaaecb074eeb9e6e2ecee857ce6a0" }, "src/L1/OptimismPortal2.sol:OptimismPortal2": { - "initCodeHash": "0xbf002905aef9f976be34670f737a71fa6292fe469ad3d1469ec815dd3712cd42", - "sourceCodeHash": "0xe7ec1b8d91402b825f30135afb1a1394a889313c016cb989e2646522db7dfecf" + "initCodeHash": "0xc7f081376014c9f7c1e62e20837440d9e3509763a2d3e4d9eeab3068c4fa9969", + "sourceCodeHash": "0x1260173d19eeffb1d5425e0545e661888bc2ab3887c2e5aa5d568bc88ca6f6f2" }, "src/L1/ProtocolVersions.sol:ProtocolVersions": { "initCodeHash": "0xbca4c0d69dcb7a196a9ebfcc8cb77162fa090377c5dda99831d61c6c68ba4352", "sourceCodeHash": "0x411f2590b43bf6fe9d70f393c8e0349192a1aa895ec0fbe0d7695831d3b6cb94" }, + "src/L1/SuperchainConfig.sol:SuperchainConfig": { + "initCodeHash": "0x9b1f3555b499709485d51d5d9665002c0eb1e5eb893be1fb978a30749e894858", + "sourceCodeHash": "0x79f0c771fc5f6d222d89b05addedc08ce40a7a42423fbd66f7cbb6cde3c2e74f" + }, "src/L1/SystemConfig.sol:SystemConfig": { - "initCodeHash": "0x4a83d1725257aee27f58f9874c543e55b89c3112e190c62ab4858e3c4dbf5bc3", - "sourceCodeHash": "0x04fee741d6628c4b74ec5315e252e5f5e62bdd820740e9ba7bd98525820043da" + "initCodeHash": "0x7d4e5da17f066ebcd6b0918c49182315fd3333a429fbe96a052375bafa04d51f", + "sourceCodeHash": "0x7d2d93cb800dceb69ab29c3787df525dab0e6ead9161d65101c1dc0225d5e786" }, "src/L1/proofs/AggregateVerifier.sol:AggregateVerifier": { "initCodeHash": "0x91a51808e37fa282a974cb5028e0ba517e04c903ce594b393cfda9dcc5408188", "sourceCodeHash": "0xdace3db84a70b9fd9bc4dd508184bc01af55568e78974381188fde5ddbb3509a" }, "src/L1/proofs/AnchorStateRegistry.sol:AnchorStateRegistry": { - "initCodeHash": "0xedf834940e0e600739722d1548955d6888e85144d165ad738353e136da000190", - "sourceCodeHash": "0x12468227fa10bb4b42a4691d5fb1d3bb93c1ef546602d2ba3c23265ecd5e450c" + "initCodeHash": "0x6f3afd2d0ef97a82ca3111976322b99343a270e54cd4a405028f2f29c75f7fb1", + "sourceCodeHash": "0x1094b0fcee1b6f22dfc3f2c0d22826a4273282a9b67dc604c1be518157e59e8a" }, "src/L1/proofs/DelayedWETH.sol:DelayedWETH": { - "initCodeHash": "0xae58f4b7b3d807db230f8029f57879ad0b055bce8fa60a1a7cde62c561892bb7", - "sourceCodeHash": "0x777858389ef4d25cb8780ed2f74ae1e32db18de694c47704a6a5104f3a70f3d8" + "initCodeHash": "0x592a6e22e1d02797eeb6fa622806e58f867bb6066944852e120b5871d3110825", + "sourceCodeHash": "0x7a1a6d240844c8e90e89a582d8d7b43e14c87ec22ec697cb3828682cfceb8470" }, "src/L1/proofs/DisputeGameFactory.sol:DisputeGameFactory": { "initCodeHash": "0x4b451a7de6befb16149d1de3e75c767d30a40f943d0fa0c775701947ea9cfd34", diff --git a/snapshots/storageLayout/SuperchainConfig.json b/snapshots/storageLayout/SuperchainConfig.json new file mode 100644 index 000000000..57708ae70 --- /dev/null +++ b/snapshots/storageLayout/SuperchainConfig.json @@ -0,0 +1,9 @@ +[ + { + "bytes": "32", + "label": "pauseTimestamps", + "offset": 0, + "slot": "0", + "type": "mapping(address => uint256)" + } +] \ No newline at end of file diff --git a/snapshots/storageLayout/SystemConfig.json b/snapshots/storageLayout/SystemConfig.json index 9fba35d9a..66f4c460a 100644 --- a/snapshots/storageLayout/SystemConfig.json +++ b/snapshots/storageLayout/SystemConfig.json @@ -127,10 +127,10 @@ }, { "bytes": "20", - "label": "spacer_108_0_20", + "label": "superchainConfig", "offset": 0, "slot": "108", - "type": "address" + "type": "contract ISuperchainConfig" }, { "bytes": "8", @@ -145,12 +145,5 @@ "offset": 0, "slot": "109", "type": "mapping(bytes32 => bool)" - }, - { - "bytes": "32", - "label": "pauseTimestamp", - "offset": 0, - "slot": "110", - "type": "uint256" } ] \ No newline at end of file diff --git a/src/L1/L1CrossDomainMessenger.sol b/src/L1/L1CrossDomainMessenger.sol index b78d2138f..3560f65ad 100644 --- a/src/L1/L1CrossDomainMessenger.sol +++ b/src/L1/L1CrossDomainMessenger.sol @@ -11,6 +11,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces import { ISemver } from "interfaces/universal/ISemver.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; @@ -35,8 +36,8 @@ contract L1CrossDomainMessenger is CrossDomainMessenger, ProxyAdminOwnedBase, Re address private spacer_253_0_20; /// @notice Semantic version. - /// @custom:semver 3.0.0 - string public constant version = "3.0.0"; + /// @custom:semver 2.11.0 + string public constant version = "2.11.0"; /// @notice Contract of the SystemConfig. ISystemConfig public systemConfig; @@ -64,6 +65,12 @@ contract L1CrossDomainMessenger is CrossDomainMessenger, ProxyAdminOwnedBase, Re return systemConfig.paused(); } + /// @notice Returns the SuperchainConfig contract. + /// @return ISuperchainConfig The SuperchainConfig contract. + function superchainConfig() public view returns (ISuperchainConfig) { + return systemConfig.superchainConfig(); + } + /// @notice Getter function for the OptimismPortal contract on this chain. /// Public getter is legacy and will be removed in the future. Use `portal()` instead. /// @return Contract of the OptimismPortal on this chain. diff --git a/src/L1/L1ERC721Bridge.sol b/src/L1/L1ERC721Bridge.sol index 32d8693a6..bb8d2b785 100644 --- a/src/L1/L1ERC721Bridge.sol +++ b/src/L1/L1ERC721Bridge.sol @@ -13,6 +13,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { IERC721 } from "lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IL2ERC721Bridge } from "interfaces/L2/IL2ERC721Bridge.sol"; @@ -32,8 +33,8 @@ contract L1ERC721Bridge is ERC721Bridge, ProxyAdminOwnedBase, ReinitializableBas address private spacer_50_0_20; /// @notice Semantic version. - /// @custom:semver 3.0.0 - string public constant version = "3.0.0"; + /// @custom:semver 2.9.0 + string public constant version = "2.9.0"; /// @notice Address of the SystemConfig contract. ISystemConfig public systemConfig; @@ -68,6 +69,12 @@ contract L1ERC721Bridge is ERC721Bridge, ProxyAdminOwnedBase, ReinitializableBas return systemConfig.paused(); } + /// @notice Returns the SuperchainConfig contract. + /// @return ISuperchainConfig The SuperchainConfig contract. + function superchainConfig() public view returns (ISuperchainConfig) { + return systemConfig.superchainConfig(); + } + /// @notice Completes an ERC721 bridge from the other domain and sends the ERC721 token to the /// recipient on this domain. /// @param _localToken Address of the ERC721 token on this domain. diff --git a/src/L1/L1StandardBridge.sol b/src/L1/L1StandardBridge.sol index 7b28060c4..9dc0a4e0f 100644 --- a/src/L1/L1StandardBridge.sol +++ b/src/L1/L1StandardBridge.sol @@ -13,6 +13,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; /// @custom:proxied true /// @title L1StandardBridge @@ -76,8 +77,8 @@ contract L1StandardBridge is StandardBridge, ProxyAdminOwnedBase, Reinitializabl ); /// @notice Semantic version. - /// @custom:semver 3.0.0 - string public constant version = "3.0.0"; + /// @custom:semver 2.8.0 + string public constant version = "2.8.0"; /// @custom:legacy /// @custom:spacer superchainConfig @@ -122,6 +123,12 @@ contract L1StandardBridge is StandardBridge, ProxyAdminOwnedBase, Reinitializabl return systemConfig.paused(); } + /// @notice Returns the SuperchainConfig contract. + /// @return ISuperchainConfig The SuperchainConfig contract. + function superchainConfig() public view returns (ISuperchainConfig) { + return systemConfig.superchainConfig(); + } + /// @notice Allows EOAs to bridge ETH by sending directly to the bridge. receive() external payable override onlyEOA { _initiateETHDeposit(msg.sender, msg.sender, RECEIVE_DEFAULT_GAS_LIMIT, bytes("")); diff --git a/src/L1/OptimismPortal2.sol b/src/L1/OptimismPortal2.sol index 8d193c75c..c8932beaa 100644 --- a/src/L1/OptimismPortal2.sol +++ b/src/L1/OptimismPortal2.sol @@ -25,6 +25,7 @@ import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; /// @custom:proxied true /// @title OptimismPortal2 @@ -205,9 +206,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ReinitializableBase error OptimismPortal_Unproven(); /// @notice Semantic version. - /// @custom:semver 7.0.1 + /// @custom:semver 6.0.1 function version() public pure virtual returns (string memory) { - return "7.0.1"; + return "6.0.1"; } /// @param _proofMaturityDelaySeconds The proof maturity delay in seconds. @@ -258,6 +259,12 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ReinitializableBase return anchorStateRegistry.disputeGameFactory(); } + /// @notice Returns the SuperchainConfig contract. + /// @return ISuperchainConfig The SuperchainConfig contract. + function superchainConfig() external view returns (ISuperchainConfig) { + return systemConfig.superchainConfig(); + } + /// @custom:legacy /// @notice Getter function for the address of the guardian. function guardian() external view returns (address) { diff --git a/src/L1/SuperchainConfig.sol b/src/L1/SuperchainConfig.sol new file mode 100644 index 000000000..b2fc85f32 --- /dev/null +++ b/src/L1/SuperchainConfig.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Contracts +import { ProxyAdminOwnedBase } from "src/universal/ProxyAdminOwnedBase.sol"; + +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; + +/// @custom:proxied true +/// @title SuperchainConfig +/// @notice The SuperchainConfig contract is used to manage configuration of global superchain values. +/// @dev WARNING: When upgrading this contract, any active pause states will be lost as the pause state +/// is stored in storage variables that are not preserved during upgrades. Therefore, this contract +/// should not be upgraded while the system is paused. +contract SuperchainConfig is ProxyAdminOwnedBase, ISemver { + /// @notice Thrown when a caller is not the guardian but tries to call a guardian-only function + error SuperchainConfig_OnlyGuardian(); + + /// @notice Thrown when a caller is not the guardian or incident responder but tries to pause + error SuperchainConfig_OnlyGuardianOrIncidentResponder(); + + /// @notice Thrown when attempting to pause an identifier that is already paused + error SuperchainConfig_AlreadyPaused(address identifier); + + /// @notice Thrown when attempting to extend a pause that is not already paused. + error SuperchainConfig_NotAlreadyPaused(address identifier); + + /// @notice The duration after which a pause expires. This value is set to exactly 3 months in + /// seconds. Any duration longer than this value is incompatible with Stage 1. + uint256 internal constant PAUSE_EXPIRY = 7_884_000; + + /// @notice The address of the guardian, which can pause withdrawals from the System. + /// This is an immutable variable set at construction time. + address public immutable GUARDIAN; + + /// @notice The address of the incident responder, which can pause the system. + /// This is an immutable variable set at construction time. + address public immutable INCIDENT_RESPONDER; + + /// @notice Mapping of pause identifiers to their pause timestamps + mapping(address => uint256) public pauseTimestamps; + + /// @notice Emitted when the pause is triggered. + /// @param identifier An address helping to identify provenance of the pause transaction. + event Paused(address identifier); + + /// @notice Emitted when the pause is lifted. + event Unpaused(address identifier); + + /// @notice Emitted when a pause is extended. + /// @param identifier An address helping to identify provenance of the pause transaction. + event PauseExtended(address identifier); + + /// @notice Semantic version. + /// @custom:semver 2.5.0 + string public constant version = "2.5.0"; + + /// @notice Constructs the SuperchainConfig contract. + /// @param _guardian The address of the guardian, which can pause and unpause the system. + /// @param _incidentResponder The address of the incident responder, which can pause the system. + constructor(address _guardian, address _incidentResponder) { + GUARDIAN = _guardian; + INCIDENT_RESPONDER = _incidentResponder; + } + + /// @notice Getter for the guardian address. + /// @return The guardian address. + function guardian() external view returns (address) { + return GUARDIAN; + } + + /// @notice Getter for the incident responder address. + /// @return The incident responder address. + function incidentResponder() external view returns (address) { + return INCIDENT_RESPONDER; + } + + /// @notice Returns the duration after which a pause expires. + /// @return The duration after which a pause expires. + function pauseExpiry() external pure returns (uint256) { + return PAUSE_EXPIRY; + } + + /// @notice Pauses the system for a specific superchain cluster identifier. + /// @param _identifier The address identifier for the pause. + function pause(address _identifier) external { + // Only the Guardian or Incident Responder can pause the system. + _assertOnlyGuardianOrIncidentResponder(); + + // Cannot pause if the identifier is already paused to prevent re-pausing without either + // unpausing, extending, or resetting the pause timestamp. Note that this check intentionally + // prevents re-pausing even after a pause has expired (when paused() returns false but the + // timestamp is still non-zero). This is a Stage 1 Decentralization requirement: the guardian + // must explicitly unpause before pausing again, ensuring deliberate action is taken. + if (pauseTimestamps[_identifier] != 0) { + revert SuperchainConfig_AlreadyPaused(_identifier); + } + + // Set the pause timestamp. + pauseTimestamps[_identifier] = block.timestamp; + emit Paused(_identifier); + } + + /// @notice Unpauses the system for a specific identifier. + /// @param _identifier The address identifier to unpause. + function unpause(address _identifier) external { + // Only the Guardian can unpause the system. + _assertOnlyGuardian(); + + // Unpause the system. + pauseTimestamps[_identifier] = 0; + emit Unpaused(_identifier); + } + + /// @notice Extends the pause for a specific identifier by resetting the pause timestamp. + /// @param _identifier The address identifier to extend. + function extend(address _identifier) external { + // Only the Guardian can extend the pause. + _assertOnlyGuardian(); + + // Cannot extend the pause if not already paused. + if (pauseTimestamps[_identifier] == 0) { + revert SuperchainConfig_NotAlreadyPaused(_identifier); + } + + // Reset the pause timestamp. + pauseTimestamps[_identifier] = block.timestamp; + emit PauseExtended(_identifier); + } + + /// @notice Checks if the system can be paused for a specific identifier. + /// @param _identifier The address identifier to check. + /// @return True if the system can be paused for this identifier. + function pausable(address _identifier) external view returns (bool) { + return pauseTimestamps[_identifier] == 0; + } + + /// @custom:legacy + /// @notice Checks if the global superchain system is paused. NOTE that this is a legacy + /// function that provides support for systems that still rely on the older interface. + /// Contracts should use paused(address) instead when possible. + /// @return True if the global superchain system is paused. + function paused() external view returns (bool) { + return paused(address(0)); + } + + /// @notice Checks if the system is currently paused for a specific identifier. + /// @param _identifier The address identifier to check. + /// @return True if the system is paused for this identifier and not expired. + function paused(address _identifier) public view returns (bool) { + uint256 timestamp = pauseTimestamps[_identifier]; + if (timestamp == 0) return false; + return block.timestamp < timestamp + PAUSE_EXPIRY; + } + + /// @notice Gets the expiration timestamp for a specific pause identifier. + /// @param _identifier The address identifier to check. + /// @return The timestamp when the pause expires, or 0 if not paused. + function expiration(address _identifier) external view returns (uint256) { + uint256 timestamp = pauseTimestamps[_identifier]; + if (timestamp == 0) return 0; + return timestamp + PAUSE_EXPIRY; + } + + /// @notice Asserts that the caller is the guardian. + function _assertOnlyGuardian() internal view { + if (msg.sender != GUARDIAN) { + revert SuperchainConfig_OnlyGuardian(); + } + } + + /// @notice Asserts that the caller is the guardian or incident responder. + function _assertOnlyGuardianOrIncidentResponder() internal view { + if (msg.sender != GUARDIAN && msg.sender != INCIDENT_RESPONDER) { + revert SuperchainConfig_OnlyGuardianOrIncidentResponder(); + } + } +} diff --git a/src/L1/SystemConfig.sol b/src/L1/SystemConfig.sol index 8b17024f7..4f2d0bf0e 100644 --- a/src/L1/SystemConfig.sol +++ b/src/L1/SystemConfig.sol @@ -14,15 +14,13 @@ import { Features } from "src/libraries/Features.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; /// @custom:proxied true /// @title SystemConfig /// @notice The SystemConfig contract is used to manage configuration of an Optimism network. /// All configuration is stored on L1 and picked up by L2 as part of the derviation of -/// the L2 chain. It also owns the pause state for the network, which the Guardian and -/// Incident Responder use to halt withdrawals and message relaying. -/// @dev Upgrading a pre-merge deployment moves pause management from the legacy SuperchainConfig. -/// Use SystemDeploy for that upgrade, which requires legacy pause records to be cleared. +/// the L2 chain. contract SystemConfig is ProxyAdminOwnedBase, OwnableUpgradeable, ReinitializableBase, ISemver { /// @notice Enum representing different types of updates. /// @custom:value BATCHER Represents an update to the batcher hash. @@ -101,18 +99,6 @@ contract SystemConfig is ProxyAdminOwnedBase, OwnableUpgradeable, Reinitializabl /// optimizations and improvements are made to the system at large. uint64 internal constant MAX_GAS_LIMIT = 500_000_000; - /// @notice The duration after which a pause expires. This value is set to exactly 3 months in - /// seconds. Any duration longer than this value is incompatible with Stage 1. - uint256 internal constant PAUSE_EXPIRY = 7_884_000; - - /// @notice The address of the guardian, which can pause and unpause withdrawals from the - /// System. This is an immutable variable set at construction time. - address public immutable GUARDIAN; - - /// @notice The address of the incident responder, which can pause the system. - /// This is an immutable variable set at construction time. - address public immutable INCIDENT_RESPONDER; - /// @notice Fixed L2 gas overhead. Used as part of the L2 fee calculation. /// Deprecated since the Ecotone network upgrade uint256 public overhead; @@ -159,10 +145,8 @@ contract SystemConfig is ProxyAdminOwnedBase, OwnableUpgradeable, Reinitializabl /// @notice The L2 chain ID that this SystemConfig configures. uint256 public l2ChainId; - /// @custom:legacy - /// @custom:spacer superchainConfig - /// @notice Spacer taking up the legacy `superchainConfig` slot. - address private spacer_108_0_20; + /// @notice The SuperchainConfig contract that manages the pause state. + ISuperchainConfig public superchainConfig; /// @notice The minimum base fee, in wei. uint64 public minBaseFee; @@ -170,9 +154,6 @@ contract SystemConfig is ProxyAdminOwnedBase, OwnableUpgradeable, Reinitializabl /// @notice Bytes32 feature flag name to boolean enabled value. mapping(bytes32 => bool) public isFeatureEnabled; - /// @notice Timestamp at which the current pause was started. - uint256 public pauseTimestamp; - /// @notice Emitted when configuration is updated. /// @param version SystemConfig version. /// @param updateType Type of update. @@ -184,45 +165,20 @@ contract SystemConfig is ProxyAdminOwnedBase, OwnableUpgradeable, Reinitializabl /// @param enabled Whether the feature is enabled. event FeatureSet(bytes32 indexed feature, bool indexed enabled); - /// @notice Emitted when the pause is triggered. - event Paused(); - - /// @notice Emitted when the pause is lifted. - event Unpaused(); - - /// @notice Emitted when a pause is extended. - event PauseExtended(); - /// @notice Thrown when attempting to enable/disable a feature when already enabled/disabled, /// respectively. error SystemConfig_InvalidFeatureState(); - /// @notice Thrown when a caller is not the guardian but tries to call a guardian-only function - error SystemConfig_OnlyGuardian(); - - /// @notice Thrown when a caller is not the guardian or incident responder but tries to pause - error SystemConfig_OnlyGuardianOrIncidentResponder(); - - /// @notice Thrown when attempting to pause an already paused system. - error SystemConfig_AlreadyPaused(); - - /// @notice Thrown when attempting to extend a pause that is not already paused. - error SystemConfig_NotAlreadyPaused(); - /// @notice Semantic version. - /// @custom:semver 4.0.0 + /// @custom:semver 3.14.0 function version() public pure virtual returns (string memory) { - return "4.0.0"; + return "3.14.0"; } /// @notice Constructs the SystemConfig contract. /// @dev START_BLOCK_SLOT is set to type(uint256).max here so that it will be a dead value /// in the singleton. - /// @param _guardian The address of the guardian, which can pause and unpause the system. - /// @param _incidentResponder The address of the incident responder, which can pause the system. - constructor(address _guardian, address _incidentResponder) ReinitializableBase(4) { - GUARDIAN = _guardian; - INCIDENT_RESPONDER = _incidentResponder; + constructor() ReinitializableBase(4) { Storage.setUint(START_BLOCK_SLOT, type(uint256).max); _disableInitializers(); } @@ -240,6 +196,7 @@ contract SystemConfig is ProxyAdminOwnedBase, OwnableUpgradeable, Reinitializabl /// canonical data. /// @param _addresses Set of L1 contract addresses. These should be the proxies. /// @param _l2ChainId The L2 chain ID that this SystemConfig configures. + /// @param _superchainConfig The SuperchainConfig contract address. function initialize( address _owner, uint32 _basefeeScalar, @@ -250,7 +207,8 @@ contract SystemConfig is ProxyAdminOwnedBase, OwnableUpgradeable, Reinitializabl IResourceMetering.ResourceConfig memory _config, address _batchInbox, SystemConfig.Addresses memory _addresses, - uint256 _l2ChainId + uint256 _l2ChainId, + ISuperchainConfig _superchainConfig ) public reinitializer(initVersion()) @@ -280,6 +238,7 @@ contract SystemConfig is ProxyAdminOwnedBase, OwnableUpgradeable, Reinitializabl _setResourceConfig(_config); l2ChainId = _l2ChainId; + superchainConfig = _superchainConfig; } /// @notice Returns the minimum L2 gas limit that can be safely set for the system to @@ -594,87 +553,16 @@ contract SystemConfig is ProxyAdminOwnedBase, OwnableUpgradeable, Reinitializabl emit FeatureSet(_feature, _enabled); } - /// @notice Pauses the system. - function pause() external { - // Only the Guardian or Incident Responder can pause the system. - if (msg.sender != GUARDIAN && msg.sender != INCIDENT_RESPONDER) { - revert SystemConfig_OnlyGuardianOrIncidentResponder(); - } - - // Cannot pause if the system is already paused to prevent re-pausing without either - // unpausing, extending, or resetting the pause timestamp. Note that this check intentionally - // prevents re-pausing even after a pause has expired (when paused() returns false but the - // timestamp is still non-zero). This is a Stage 1 Decentralization requirement: the guardian - // must explicitly unpause before pausing again, ensuring deliberate action is taken. - if (pauseTimestamp != 0) { - revert SystemConfig_AlreadyPaused(); - } - - // Set the pause timestamp. - pauseTimestamp = block.timestamp; - emit Paused(); - } - - /// @notice Unpauses the system. - function unpause() external { - // Only the Guardian can unpause the system. - if (msg.sender != GUARDIAN) revert SystemConfig_OnlyGuardian(); - - // Unpause the system. - pauseTimestamp = 0; - emit Unpaused(); - } - - /// @notice Extends the pause by resetting its timestamp. - function extend() external { - // Only the Guardian can extend the pause. - if (msg.sender != GUARDIAN) revert SystemConfig_OnlyGuardian(); - - // Cannot extend the pause if not already paused. - if (pauseTimestamp == 0) { - revert SystemConfig_NotAlreadyPaused(); - } - - // Reset the pause timestamp. - pauseTimestamp = block.timestamp; - emit PauseExtended(); - } - - /// @notice Getter for the incident responder address. - /// @return The incident responder address. - function incidentResponder() external view returns (address) { - return INCIDENT_RESPONDER; - } - - /// @notice Checks if the system can be paused. - /// @return True if the system can be paused. - function pausable() external view returns (bool) { - return pauseTimestamp == 0; - } - - /// @notice Gets the pause expiration timestamp. - /// @return The timestamp when the pause expires, or 0 if not paused. - function expiration() external view returns (uint256) { - if (pauseTimestamp == 0) return 0; - return pauseTimestamp + PAUSE_EXPIRY; - } - - /// @notice Returns the duration after which a pause expires. - /// @return The duration after which a pause expires. - function pauseExpiry() external pure returns (uint256) { - return PAUSE_EXPIRY; - } - /// @notice Returns the current pause state for this network. /// @return bool True if the system is paused, false otherwise. function paused() public view returns (bool) { - return pauseTimestamp != 0 && block.timestamp < pauseTimestamp + PAUSE_EXPIRY; + return superchainConfig.paused(address(0)) || superchainConfig.paused(optimismPortal()); } - /// @notice Getter for the guardian address. + /// @notice Returns the guardian address of the SuperchainConfig. /// @return address The guardian address. function guardian() public view returns (address) { - return GUARDIAN; + return superchainConfig.guardian(); } /// @custom:legacy diff --git a/src/L1/proofs/AnchorStateRegistry.sol b/src/L1/proofs/AnchorStateRegistry.sol index 98f08adcc..d42728ec6 100644 --- a/src/L1/proofs/AnchorStateRegistry.sol +++ b/src/L1/proofs/AnchorStateRegistry.sol @@ -15,6 +15,7 @@ import { ISemver } from "interfaces/universal/ISemver.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; /// @custom:proxied true /// @title AnchorStateRegistry @@ -24,8 +25,8 @@ import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; /// reduces the amount of required offchain computation. contract AnchorStateRegistry is ProxyAdminOwnedBase, Initializable, ReinitializableBase, ISemver { /// @notice Semantic version. - /// @custom:semver 4.0.0 - string public constant version = "4.0.0"; + /// @custom:semver 3.7.0 + string public constant version = "3.7.0"; /// @notice The dispute game finality delay in seconds. uint256 internal immutable DISPUTE_GAME_FINALITY_DELAY_SECONDS; @@ -119,6 +120,12 @@ contract AnchorStateRegistry is ProxyAdminOwnedBase, Initializable, Reinitializa return systemConfig.paused(); } + /// @notice Returns the SuperchainConfig contract. + /// @return ISuperchainConfig The SuperchainConfig contract. + function superchainConfig() public view returns (ISuperchainConfig) { + return systemConfig.superchainConfig(); + } + /// @notice Returns the dispute game finality delay in seconds. function disputeGameFinalityDelaySeconds() external view returns (uint256) { return DISPUTE_GAME_FINALITY_DELAY_SECONDS; diff --git a/src/L1/proofs/DelayedWETH.sol b/src/L1/proofs/DelayedWETH.sol index 8222689bb..9f0f3455f 100644 --- a/src/L1/proofs/DelayedWETH.sol +++ b/src/L1/proofs/DelayedWETH.sol @@ -10,6 +10,7 @@ import { ProxyAdminOwnedBase } from "src/universal/ProxyAdminOwnedBase.sol"; // Interfaces import { ISemver } from "interfaces/universal/ISemver.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; /// @custom:proxied true /// @title DelayedWETH @@ -29,8 +30,8 @@ contract DelayedWETH is Initializable, ProxyAdminOwnedBase, ReinitializableBase, } /// @notice Semantic version. - /// @custom:semver 2.0.0 - string public constant version = "2.0.0"; + /// @custom:semver 1.5.0 + string public constant version = "1.5.0"; /// @notice Returns a withdrawal request for the given address. mapping(address => mapping(address => WithdrawalRequest)) public withdrawals; @@ -63,6 +64,12 @@ contract DelayedWETH is Initializable, ProxyAdminOwnedBase, ReinitializableBase, return DELAY_SECONDS; } + /// @notice Returns the SuperchainConfig contract. + /// @return ISuperchainConfig The SuperchainConfig contract. + function config() public view returns (ISuperchainConfig) { + return systemConfig.superchainConfig(); + } + /// @notice Unlocks withdrawals for the sender's account, after a time delay. /// @param _guy Sub-account to unlock. /// @param _wad The amount of WETH to unlock. diff --git a/src/universal/CrossDomainMessenger.sol b/src/universal/CrossDomainMessenger.sol index 5802493fa..fdab22a20 100644 --- a/src/universal/CrossDomainMessenger.sol +++ b/src/universal/CrossDomainMessenger.sol @@ -432,7 +432,7 @@ abstract contract CrossDomainMessenger is function _isUnsafeTarget(address _target) internal view virtual returns (bool); /// @notice This function should return true if the contract is paused. - /// On L1 this function will check the SystemConfig for its paused status. + /// On L1 this function will check the SuperchainConfig for its paused status. /// On L2 this function should be a no-op. /// @return Whether or not the contract is paused. function paused() public view virtual returns (bool) { diff --git a/src/universal/ERC721Bridge.sol b/src/universal/ERC721Bridge.sol index 13769baca..d9f0cbf23 100644 --- a/src/universal/ERC721Bridge.sol +++ b/src/universal/ERC721Bridge.sol @@ -100,7 +100,7 @@ abstract contract ERC721Bridge is Initializable { } /// @notice This function should return true if the contract is paused. - /// On L1 this function will check the SystemConfig for its paused status. + /// On L1 this function will check the SuperchainConfig for its paused status. /// On L2 this function should be a no-op. /// @return Whether or not the contract is paused. function paused() public view virtual returns (bool) { diff --git a/src/universal/StandardBridge.sol b/src/universal/StandardBridge.sol index 7f372647d..fb7912551 100644 --- a/src/universal/StandardBridge.sol +++ b/src/universal/StandardBridge.sol @@ -151,7 +151,7 @@ abstract contract StandardBridge is Initializable { } /// @notice This function should return true if the contract is paused. - /// On L1 this function will check the SystemConfig for its paused status. + /// On L1 this function will check the SuperchainConfig for its paused status. /// On L2 this function should be a no-op. /// @return Whether or not the contract is paused. function paused() public view virtual returns (bool) { diff --git a/test/L1/L1CrossDomainMessenger.t.sol b/test/L1/L1CrossDomainMessenger.t.sol index 936a65562..29ac08d4c 100644 --- a/test/L1/L1CrossDomainMessenger.t.sol +++ b/test/L1/L1CrossDomainMessenger.t.sol @@ -125,24 +125,33 @@ contract L1CrossDomainMessenger_Initialize_Test is L1CrossDomainMessenger_TestIn /// @notice Tests for the `paused` functionality of the L1CrossDomainMessenger. contract L1CrossDomainMessenger_Paused_Test is L1CrossDomainMessenger_TestInit { /// @notice Tests that the superchain config is called by the messenger's paused function. - function test_pause_callsSystemConfig_succeeds() external { + function test_pause_callsSuperchainConfig_succeeds() external { // We use abi.encodeWithSignature because paused is overloaded. // nosemgrep: sol-style-use-abi-encodecall - vm.expectCall(address(systemConfig), abi.encodeWithSignature("paused()")); + vm.expectCall(address(superchainConfig), abi.encodeWithSignature("paused(address)", address(0))); l1CrossDomainMessenger.paused(); } /// @notice Tests that changing the superchain config paused status changes the return value /// of the messenger. - function test_pause_matchesSystemConfig_succeeds() external { + function test_pause_matchesSuperchainConfig_succeeds() external { assertFalse(l1CrossDomainMessenger.paused()); - assertEq(l1CrossDomainMessenger.paused(), systemConfig.paused()); + assertEq(l1CrossDomainMessenger.paused(), superchainConfig.paused(address(0))); - vm.prank(systemConfig.guardian()); - systemConfig.pause(); + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(address(0)); assertTrue(l1CrossDomainMessenger.paused()); - assertEq(l1CrossDomainMessenger.paused(), systemConfig.paused()); + assertEq(l1CrossDomainMessenger.paused(), superchainConfig.paused(address(0))); + } +} + +/// @title L1CrossDomainMessenger_SuperchainConfig_Test +/// @notice Tests for the `superchainConfig` function of the L1CrossDomainMessenger. +contract L1CrossDomainMessenger_SuperchainConfig_Test is L1CrossDomainMessenger_TestInit { + /// @notice Tests that `superchainConfig` returns the correct address. + function test_superchainConfig_succeeds() external view { + assertEq(address(l1CrossDomainMessenger.superchainConfig()), address(superchainConfig)); } } @@ -1026,8 +1035,8 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes /// @notice Tests that relayMessage reverts while the messenger is paused. function test_relayMessage_paused_reverts() external { - vm.prank(systemConfig.guardian()); - systemConfig.pause(); + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(address(0)); vm.expectRevert("CrossDomainMessenger: paused"); l1CrossDomainMessenger.relayMessage( diff --git a/test/L1/L1ERC721Bridge.t.sol b/test/L1/L1ERC721Bridge.t.sol index 98ef265bf..1a59d608a 100644 --- a/test/L1/L1ERC721Bridge.t.sol +++ b/test/L1/L1ERC721Bridge.t.sol @@ -163,6 +163,7 @@ contract L1ERC721Bridge_Initialize_Test is L1ERC721Bridge_TestInit { assertEq(address(l1ERC721Bridge.OTHER_BRIDGE()), Predeploys.L2_ERC721_BRIDGE); assertEq(address(l1ERC721Bridge.otherBridge()), Predeploys.L2_ERC721_BRIDGE); assertEq(address(l1ERC721Bridge.systemConfig()), address(systemConfig)); + assertEq(address(l1ERC721Bridge.superchainConfig()), address(systemConfig.superchainConfig())); } /// @notice Tests that the initializer value is correct. Trivial test for normal @@ -189,6 +190,15 @@ contract L1ERC721Bridge_Initialize_Test is L1ERC721Bridge_TestInit { } } +/// @title L1ERC721Bridge_SuperchainConfig_Test +/// @notice Test contract for L1ERC721Bridge `superchainConfig` function. +contract L1ERC721Bridge_SuperchainConfig_Test is L1ERC721Bridge_TestInit { + /// @notice Verifies superchainConfig returns the correct contract address. + function test_superchainConfig_succeeds() external view { + assertEq(address(l1ERC721Bridge.superchainConfig()), address(systemConfig.superchainConfig())); + } +} + /// @title L1ERC721Bridge_Version_Test /// @notice Test contract for L1ERC721Bridge `version` constant. contract L1ERC721Bridge_Version_Test is L1ERC721Bridge_TestInit { @@ -202,26 +212,26 @@ contract L1ERC721Bridge_Version_Test is L1ERC721Bridge_TestInit { /// @notice Test contract for L1ERC721Bridge `paused` functionality. contract L1ERC721Bridge_Paused_Test is L1ERC721Bridge_TestInit { /// @dev Verifies that the `paused` accessor returns the same value as the `paused` function of - /// the `systemConfig`. + /// the `superchainConfig`. function test_paused_succeeds() external view { assertEq(l1ERC721Bridge.paused(), systemConfig.paused()); } /// @dev Ensures that the `paused` function of the bridge contract actually calls the `paused` - /// function of the `systemConfig`. - function test_pause_callsSystemConfig_succeeds() external { - vm.expectCall(address(systemConfig), abi.encodeWithSignature("paused()")); + /// function of the `superchainConfig`. + function test_pause_callsSuperchainConfig_succeeds() external { + vm.expectCall(address(systemConfig), abi.encodeCall(ISystemConfig.paused, ())); l1ERC721Bridge.paused(); } /// @dev Checks that the `paused` state of the bridge matches the `paused` state of the - /// `systemConfig` after it's been changed. - function test_pause_matchesSystemConfig_succeeds() external { + /// `superchainConfig` after it's been changed. + function test_pause_matchesSuperchainConfig_succeeds() external { assertFalse(l1ERC721Bridge.paused()); assertEq(l1ERC721Bridge.paused(), systemConfig.paused()); - vm.prank(systemConfig.guardian()); - systemConfig.pause(); + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(address(0)); assertTrue(l1ERC721Bridge.paused()); assertEq(l1ERC721Bridge.paused(), systemConfig.paused()); @@ -320,8 +330,8 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_Bridge_TestI /// @notice Ensures that the `finalizeBridgeERC721` function reverts when the bridge is paused. function test_finalizeBridgeERC721_paused_reverts() external { - vm.prank(systemConfig.guardian()); - systemConfig.pause(); + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(address(0)); assertTrue(l1ERC721Bridge.paused()); _mockOtherBridge(); diff --git a/test/L1/L1StandardBridge.t.sol b/test/L1/L1StandardBridge.t.sol index e025c1829..c944b2d99 100644 --- a/test/L1/L1StandardBridge.t.sol +++ b/test/L1/L1StandardBridge.t.sol @@ -241,7 +241,7 @@ contract L1StandardBridge_Paused_Test is L1StandardBridge_TestInit { /// @notice Pauses the bridge and mocks the xDomainMessageSender to return the other bridge. function _pauseBridge() internal { vm.startPrank(systemConfig.guardian()); - systemConfig.pause(); + systemConfig.superchainConfig().pause(address(0)); vm.stopPrank(); assertTrue(l1StandardBridge.paused()); @@ -255,26 +255,26 @@ contract L1StandardBridge_Paused_Test is L1StandardBridge_TestInit { } /// @notice Verifies that the `paused` accessor returns the same value as the `paused` function - /// of the `systemConfig`. + /// of the `superchainConfig`. function test_paused_succeeds() external view { assertEq(l1StandardBridge.paused(), systemConfig.paused()); } /// @notice Ensures that the `paused` function of the bridge contract actually calls the - /// `paused` function of the `systemConfig`. - function test_paused_callsSystemConfig_succeeds() external { - vm.expectCall(address(systemConfig), abi.encodeWithSignature("paused()")); + /// `paused` function of the `superchainConfig`. + function test_paused_callsSuperchainConfig_succeeds() external { + vm.expectCall(address(systemConfig), abi.encodeCall(ISystemConfig.paused, ())); l1StandardBridge.paused(); } /// @notice Checks that the `paused` state of the bridge matches the `paused` state of the - /// `systemConfig` after it's been changed. - function test_paused_matchesSystemConfig_succeeds() external { + /// `superchainConfig` after it's been changed. + function test_paused_matchesSuperchainConfig_succeeds() external { assertFalse(l1StandardBridge.paused()); assertEq(l1StandardBridge.paused(), systemConfig.paused()); - vm.prank(systemConfig.guardian()); - systemConfig.pause(); + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(address(0)); assertTrue(l1StandardBridge.paused()); assertEq(l1StandardBridge.paused(), systemConfig.paused()); @@ -710,6 +710,7 @@ contract L1StandardBridge_Uncategorized_Test is L1StandardBridge_TestInit { assertEq(address(l1StandardBridge.messenger()), address(l1CrossDomainMessenger)); assertEq(address(l1StandardBridge.MESSENGER()), address(l1CrossDomainMessenger)); assertEq(address(l1StandardBridge.systemConfig()), address(systemConfig)); + assertEq(address(l1StandardBridge.superchainConfig()), address(systemConfig.superchainConfig())); } /// @notice Tests that bridging ETH succeeds. diff --git a/test/L1/OptimismPortal2.t.sol b/test/L1/OptimismPortal2.t.sol index 5628c1469..4e03629b8 100644 --- a/test/L1/OptimismPortal2.t.sol +++ b/test/L1/OptimismPortal2.t.sol @@ -276,6 +276,7 @@ contract OptimismPortal2_Initialize_Test is OptimismPortal2_TestInit { function test_initialize_succeeds() public view { assertEq(address(optimismPortal2.anchorStateRegistry()), address(anchorStateRegistry)); assertEq(address(optimismPortal2.disputeGameFactory()), address(disputeGameFactory)); + assertEq(address(optimismPortal2.superchainConfig()), address(superchainConfig)); assertEq(optimismPortal2.l2Sender(), Constants.DEFAULT_L2_SENDER); assertEq(optimismPortal2.paused(), false); assertEq(address(optimismPortal2.systemConfig()), address(systemConfig)); @@ -283,10 +284,10 @@ contract OptimismPortal2_Initialize_Test is OptimismPortal2_TestInit { returnIfForkTest( "OptimismPortal2_Initialize_Test: Do not check guardian and respectedGameType on forked networks" ); - address guardian = systemConfig.guardian(); + address guardian = superchainConfig.guardian(); // This check is not valid for forked tests, as the guardian is not the same as the one in local.json - assertEq(guardian, deploy.cfg().guardian()); + assertEq(guardian, deploy.cfg().superchainConfigGuardian()); // This check is not valid on forked tests as the respectedGameType varies between OP Chains. assertEq(optimismPortal2.respectedGameType().raw(), deploy.cfg().respectedGameType()); @@ -382,6 +383,15 @@ contract OptimismPortal2_DisputeGameFactory_Test is OptimismPortal2_TestInit { } } +/// @title OptimismPortal2_SuperchainConfig_Test +/// @notice Test contract for OptimismPortal2 `superchainConfig` function. +contract OptimismPortal2_SuperchainConfig_Test is OptimismPortal2_TestInit { + /// @notice Tests that `superchainConfig` returns the correct address. + function test_superchainConfig_succeeds() external view { + assertEq(address(optimismPortal2.superchainConfig()), address(superchainConfig)); + } +} + /// @title OptimismPortal2_Guardian_Test /// @notice Test contract for OptimismPortal2 `guardian` function. contract OptimismPortal2_Guardian_Test is OptimismPortal2_TestInit { @@ -549,7 +559,7 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test /// @notice Tests that `proveWithdrawalTransaction` reverts when paused. function test_proveWithdrawalTransaction_paused_reverts() external { vm.startPrank(optimismPortal2.guardian()); - systemConfig.pause(); + systemConfig.superchainConfig().pause(address(0)); vm.stopPrank(); vm.expectRevert(IOptimismPortal.OptimismPortal_CallPaused.selector); @@ -892,7 +902,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T /// @notice Tests that `finalizeWithdrawalTransaction` reverts if the contract is paused. function test_finalizeWithdrawalTransaction_paused_reverts() external { vm.prank(optimismPortal2.guardian()); - systemConfig.pause(); + superchainConfig.pause(address(0)); vm.expectRevert(IOptimismPortal.OptimismPortal_CallPaused.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); diff --git a/test/L1/ProtocolVersions.t.sol b/test/L1/ProtocolVersions.t.sol index 0fc3b11d5..21200cec3 100644 --- a/test/L1/ProtocolVersions.t.sol +++ b/test/L1/ProtocolVersions.t.sol @@ -70,7 +70,7 @@ contract ProtocolVersions_Initialize_Test is ProtocolVersions_TestInit { // The owner is inherited from the shared ProxyAdmin; initialize records the incident // responder from config and seeds the hash chain (scheduleId == the bytes32(0) seed). assertEq(protocolVersions.proxyAdminOwner(), proxyAdminOwner); - assertEq(protocolVersions.incidentResponder(), deploy.cfg().incidentResponder()); + assertEq(protocolVersions.incidentResponder(), deploy.cfg().superchainConfigIncidentResponder()); assertEq(protocolVersions.minimumProtocolVersion(), deploy.cfg().protocolVersionsInitialMinimumVersion()); assertEq(protocolVersions.scheduleId(), bytes32(0)); } diff --git a/test/L1/SuperchainConfig.t.sol b/test/L1/SuperchainConfig.t.sol new file mode 100644 index 000000000..8b6ec06d5 --- /dev/null +++ b/test/L1/SuperchainConfig.t.sol @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing +import { CommonTest } from "test/setup/CommonTest.sol"; + +// Interfaces +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; + +/// @title SuperchainConfig_TestInit +/// @notice Initialization contract for SuperchainConfig tests. +abstract contract SuperchainConfig_TestInit is CommonTest { + uint256 internal constant PAUSE_EXPIRY = 7_884_000; + + function setUp() public virtual override { + super.setUp(); + skipIfForkTest("SuperchainConfig_TestInit: cannot test initialization on forked network"); + } + + function _pauseAsGuardian(address _identifier) internal { + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(_identifier); + } +} + +/// @title SuperchainConfig_Constructor_Test +/// @notice Test contract for SuperchainConfig constructor values. +contract SuperchainConfig_Constructor_Test is SuperchainConfig_TestInit { + /// @notice Tests that constructor sets the correct values. + function test_constructor_unpaused_succeeds() external view { + assertFalse(superchainConfig.paused(address(this))); + assertEq(superchainConfig.guardian(), deploy.cfg().superchainConfigGuardian()); + } +} + +/// @title SuperchainConfig_PauseExpiry_Test +/// @notice Test contract for SuperchainConfig `pauseExpiry` function. +contract SuperchainConfig_PauseExpiry_Test is SuperchainConfig_TestInit { + /// @notice Tests that `pauseExpiry` returns the correct constant value. + function test_pauseExpiry_succeeds() external view { + assertEq(superchainConfig.pauseExpiry(), PAUSE_EXPIRY); + } +} + +/// @title SuperchainConfig_Paused_Test +/// @notice Test contract for SuperchainConfig `paused` function. +contract SuperchainConfig_Paused_Test is SuperchainConfig_TestInit { + /// @notice Tests that `paused` returns true when the specific identifier is paused. + /// @param _identifier The identifier to test. + /// @param _other The unpaused identifier to test. + function testFuzz_paused_specificIdentifier_succeeds(address _identifier, address _other) external { + vm.assume(_identifier != address(0)); + vm.assume(_other != _identifier); + + _pauseAsGuardian(_identifier); + assertTrue(superchainConfig.paused(_identifier)); + assertFalse(superchainConfig.paused(_other)); + } + + /// @notice Tests that `paused` returns true when the global superchain system is paused. + function test_paused_global_succeeds() external { + _pauseAsGuardian(address(0)); + + assertTrue(superchainConfig.paused()); + assertTrue(superchainConfig.paused(address(0))); + assertFalse(superchainConfig.paused(address(1))); + } + + /// @notice Tests that `paused` returns false after pause expires. + /// @param _identifier The identifier to test. + function testFuzz_paused_expired_succeeds(address _identifier) external { + _pauseAsGuardian(_identifier); + assertTrue(superchainConfig.paused(_identifier)); + + vm.warp(block.timestamp + PAUSE_EXPIRY + 1); + assertFalse(superchainConfig.paused(_identifier)); + } + + /// @notice Tests that `paused` returns true just before expiry. + /// @param _identifier The identifier to test. + function testFuzz_paused_beforeExpiry_succeeds(address _identifier) external { + _pauseAsGuardian(_identifier); + + vm.warp(block.timestamp + PAUSE_EXPIRY - 1); + assertTrue(superchainConfig.paused(_identifier)); + } +} + +/// @title SuperchainConfig_Pause_Test +/// @notice Test contract for SuperchainConfig `pause` function. +contract SuperchainConfig_Pause_Test is SuperchainConfig_TestInit { + /// @notice Tests that `pause` successfully pauses when called by the guardian. + /// @param _identifier The identifier to test. + function testFuzz_pause_succeeds(address _identifier) external { + assertFalse(superchainConfig.paused(_identifier)); + + vm.expectEmit(address(superchainConfig)); + emit Paused(_identifier); + + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(_identifier); + + assertTrue(superchainConfig.paused(_identifier)); + } + + /// @notice Tests that `pause` reverts when called by a non-guardian and non-incident-responder. + /// @param _caller The unauthorized caller to test. + function testFuzz_pause_notGuardianOrIncidentResponder_reverts(address _caller) external { + vm.assume(_caller != superchainConfig.guardian() && _caller != superchainConfig.incidentResponder()); + + vm.expectRevert(ISuperchainConfig.SuperchainConfig_OnlyGuardianOrIncidentResponder.selector); + vm.prank(_caller); + superchainConfig.pause(address(this)); + } + + /// @notice Tests that `pause` reverts when the identifier is already used. + /// @param _identifier The identifier to test. + function testFuzz_pause_alreadyUsed_reverts(address _identifier) external { + _pauseAsGuardian(_identifier); + + vm.prank(superchainConfig.guardian()); + vm.expectRevert(abi.encodeWithSelector(ISuperchainConfig.SuperchainConfig_AlreadyPaused.selector, _identifier)); + superchainConfig.pause(_identifier); + } +} + +/// @title SuperchainConfig_Unpause_Test +/// @notice Test contract for SuperchainConfig `unpause` function. +contract SuperchainConfig_Unpause_Test is SuperchainConfig_TestInit { + /// @notice Tests that `unpause` successfully unpauses when called by the guardian. + /// @param _identifier The identifier to test. + function testFuzz_unpause_succeeds(address _identifier) external { + _pauseAsGuardian(_identifier); + assertTrue(superchainConfig.paused(_identifier)); + + vm.expectEmit(address(superchainConfig)); + emit Unpaused(_identifier); + vm.prank(superchainConfig.guardian()); + superchainConfig.unpause(_identifier); + + assertFalse(superchainConfig.paused(_identifier)); + } + + /// @notice Tests that `unpause` reverts when called by a non-guardian. + /// @param _caller The non-guardian caller to test. + function testFuzz_unpause_notGuardian_reverts(address _caller) external { + vm.assume(_caller != superchainConfig.guardian()); + + _pauseAsGuardian(address(this)); + assertTrue(superchainConfig.paused(address(this))); + + vm.expectRevert(ISuperchainConfig.SuperchainConfig_OnlyGuardian.selector); + vm.prank(_caller); + superchainConfig.unpause(address(this)); + } +} + +/// @title SuperchainConfig_Extend_Test +/// @notice Test contract for SuperchainConfig `extend` function. +contract SuperchainConfig_Extend_Test is SuperchainConfig_TestInit { + /// @notice Tests that `extend` successfully resets and re-pauses an identifier. + /// @param _identifier The identifier to test. + function testFuzz_extend_succeeds(address _identifier) external { + _pauseAsGuardian(_identifier); + uint256 firstPauseTimestamp = block.timestamp; + + vm.warp(block.timestamp + 1); + + vm.expectEmit(address(superchainConfig)); + emit PauseExtended(_identifier); + vm.prank(superchainConfig.guardian()); + superchainConfig.extend(_identifier); + assertTrue(superchainConfig.pauseTimestamps(_identifier) > firstPauseTimestamp); + assertTrue(superchainConfig.paused(_identifier)); + } + + /// @notice Tests that `extend` reverts when called by a non-guardian. + /// @param _caller The non-guardian caller to test. + function testFuzz_extend_notGuardian_reverts(address _caller) external { + vm.assume(_caller != superchainConfig.guardian()); + + _pauseAsGuardian(address(this)); + + vm.expectRevert(ISuperchainConfig.SuperchainConfig_OnlyGuardian.selector); + vm.prank(_caller); + superchainConfig.extend(address(this)); + } + + /// @notice Tests that `extend` reverts when the identifier is not already paused. + /// @param _identifier The identifier to test. + function testFuzz_extend_notAlreadyPaused_reverts(address _identifier) external { + vm.prank(superchainConfig.guardian()); + vm.expectRevert( + abi.encodeWithSelector(ISuperchainConfig.SuperchainConfig_NotAlreadyPaused.selector, _identifier) + ); + superchainConfig.extend(_identifier); + } +} + +/// @title SuperchainConfig_Pausable_Test +/// @notice Test contract for SuperchainConfig `pausable` function. +contract SuperchainConfig_Pausable_Test is SuperchainConfig_TestInit { + /// @notice Tests that `pausable` returns true when the identifier is not paused. + /// @param _identifier The identifier to test. + function testFuzz_pausable_notPaused_succeeds(address _identifier) external view { + assertTrue(superchainConfig.pausable(_identifier)); + } + + /// @notice Tests that `pausable` returns false when the identifier is paused. + /// @param _identifier The identifier to test. + function testFuzz_pausable_paused_succeeds(address _identifier) external { + _pauseAsGuardian(_identifier); + assertFalse(superchainConfig.pausable(_identifier)); + } + + /// @notice Tests that `pausable` returns false even after pause expires. + /// @param _identifier The identifier to test. + function testFuzz_pausable_expired_succeeds(address _identifier) external { + _pauseAsGuardian(_identifier); + + vm.warp(block.timestamp + PAUSE_EXPIRY + 1); + + // Expired pauses remain unpausable because the timestamp stays set. + assertFalse(superchainConfig.pausable(_identifier)); + assertFalse(superchainConfig.paused(_identifier)); + } +} + +/// @title SuperchainConfig_Guardian_Test +/// @notice Test contract for SuperchainConfig `guardian` getter function. +contract SuperchainConfig_Guardian_Test is SuperchainConfig_TestInit { + /// @notice Tests that `guardian` returns the correct guardian address. + function test_guardian_succeeds() external view { + assertEq(superchainConfig.guardian(), deploy.cfg().superchainConfigGuardian()); + } +} + +/// @title SuperchainConfig_PauseTimestamps_Test +/// @notice Test contract for SuperchainConfig `pauseTimestamps` getter function. +contract SuperchainConfig_PauseTimestamps_Test is SuperchainConfig_TestInit { + /// @notice Tests that `pauseTimestamps` returns 0 for unpaused identifiers. + /// @param _identifier The identifier to test. + function testFuzz_pauseTimestamps_unpaused_succeeds(address _identifier) external view { + assertEq(superchainConfig.pauseTimestamps(_identifier), 0); + } + + /// @notice Tests that `pauseTimestamps` returns the correct timestamp for paused identifiers. + /// @param _identifier The identifier to test. + function testFuzz_pauseTimestamps_paused_succeeds(address _identifier) external { + _pauseAsGuardian(_identifier); + assertEq(superchainConfig.pauseTimestamps(_identifier), block.timestamp); + } + + /// @notice Tests that `pauseTimestamps` returns 0 after unpausing. + /// @param _identifier The identifier to test. + function testFuzz_pauseTimestamps_afterUnpause_succeeds(address _identifier) external { + _pauseAsGuardian(_identifier); + assertTrue(superchainConfig.pauseTimestamps(_identifier) != 0); + + vm.prank(superchainConfig.guardian()); + superchainConfig.unpause(_identifier); + assertEq(superchainConfig.pauseTimestamps(_identifier), 0); + } +} + +/// @title SuperchainConfig_Version_Test +/// @notice Test contract for SuperchainConfig `version` getter function. +contract SuperchainConfig_Version_Test is SuperchainConfig_TestInit { + /// @notice Tests that `version` returns a version string. + function test_version_succeeds() external view { + assert(bytes(superchainConfig.version()).length > 0); + } +} + +/// @title SuperchainConfig_Expiration_Test +/// @notice Test contract for SuperchainConfig `expiration` function. +contract SuperchainConfig_Expiration_Test is SuperchainConfig_TestInit { + /// @notice Tests that `expiration` returns 0 when the identifier is not paused. + function test_expiration_notPaused_succeeds() external view { + assertEq(superchainConfig.expiration(address(this)), 0); + } + + /// @notice Tests that `expiration` returns the correct timestamp when the identifier is + /// paused. + function test_expiration_paused_succeeds() external { + _pauseAsGuardian(address(this)); + assertEq(superchainConfig.expiration(address(this)), block.timestamp + PAUSE_EXPIRY); + } + + /// @notice Tests that `expiration` returns the updated timestamp after extending the pause. + function test_expiration_afterExtend_succeeds() external { + _pauseAsGuardian(address(this)); + vm.warp(block.timestamp + 100); + + vm.prank(superchainConfig.guardian()); + superchainConfig.extend(address(this)); + + assertEq(superchainConfig.expiration(address(this)), block.timestamp + PAUSE_EXPIRY); + } + + /// @notice Tests that `expiration` works correctly with fuzzed identifiers. + /// @param _identifier The identifier to test. + function testFuzz_expiration_succeeds(address _identifier) external { + assertEq(superchainConfig.expiration(_identifier), 0); + + _pauseAsGuardian(_identifier); + assertEq(superchainConfig.expiration(_identifier), block.timestamp + PAUSE_EXPIRY); + } +} diff --git a/test/L1/SystemConfig.t.sol b/test/L1/SystemConfig.t.sol index f7c69ec2b..30d8cbab1 100644 --- a/test/L1/SystemConfig.t.sol +++ b/test/L1/SystemConfig.t.sol @@ -5,7 +5,6 @@ pragma solidity 0.8.15; import { CommonTest } from "test/setup/CommonTest.sol"; // Scripts -import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; // Libraries import { Constants } from "src/libraries/Constants.sol"; @@ -14,6 +13,7 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; // Interfaces import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; /// @title SystemConfig Test Init @@ -23,7 +23,6 @@ abstract contract SystemConfig_TestInit is CommonTest { bytes32 public constant EXAMPLE_FEATURE = "EXAMPLE_FEATURE"; bytes32 internal constant INITIALIZED_SLOT = bytes32(0); - uint256 internal constant PAUSE_EXPIRY = 7_884_000; address owner; bytes32 batcherHash; @@ -55,11 +54,6 @@ abstract contract SystemConfig_TestInit is CommonTest { return uint8(uint256(slotVal) & 0xFF); } - function _pauseAsGuardian() internal { - vm.prank(systemConfig.guardian()); - systemConfig.pause(); - } - function _emptyAddresses() internal pure returns (ISystemConfig.Addresses memory) { return ISystemConfig.Addresses({ l1CrossDomainMessenger: address(0), @@ -82,7 +76,8 @@ abstract contract SystemConfig_TestInit is CommonTest { _config: _config, _batchInbox: address(0), _addresses: _emptyAddresses(), - _l2ChainId: 1234 + _l2ChainId: 1234, + _superchainConfig: ISuperchainConfig(address(0)) }); } } @@ -539,7 +534,8 @@ contract SystemConfig_SetResourceConfig_Test is SystemConfig_TestInit { _config: config, _batchInbox: address(0), _addresses: _emptyAddresses(), - _l2ChainId: 1234 + _l2ChainId: 1234, + _superchainConfig: ISuperchainConfig(address(0)) }); } } @@ -547,26 +543,66 @@ contract SystemConfig_SetResourceConfig_Test is SystemConfig_TestInit { /// @title SystemConfig_Paused_Test /// @notice Test contract for SystemConfig `paused` function. contract SystemConfig_Paused_Test is SystemConfig_TestInit { - /// @notice Tests that `paused()` returns false when no pause is active. - function test_paused_noPause_succeeds() external view { + /// @notice Tests that `paused()` returns false when no pauses are active. + function test_paused_noPauses_succeeds() external view { + assertFalse(systemConfig.paused()); + } + + /// @notice Tests that `paused()` returns true when global pause is active. + function test_paused_globalPause_succeeds() external { + // Initially not paused + assertFalse(systemConfig.paused()); + + // Pause the system globally + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(address(0)); + + // Verify paused state + assertTrue(systemConfig.paused()); + } + + /// @notice Tests that `paused()` returns true when the OptimismPortal identifier is paused. + function test_paused_optimismPortalIdentifier_succeeds() external { + // Initially not paused assertFalse(systemConfig.paused()); + + // Pause the system with OptimismPortal identifier + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(address(optimismPortal2)); + + // Verify paused state + assertTrue(systemConfig.paused()); } - /// @notice Tests that `paused()` returns true after the system is paused. - function test_paused_succeeds() external { - _pauseAsGuardian(); + /// @notice Tests that `paused()` returns true when both pauses are active. + function test_paused_bothPausesActive_succeeds() external { + assertFalse(systemConfig.paused()); + + // Pause both globally and with identifier + vm.startPrank(superchainConfig.guardian()); + superchainConfig.pause(address(0)); + superchainConfig.pause(address(optimismPortal2)); + vm.stopPrank(); + // Verify paused state assertTrue(systemConfig.paused()); } - /// @notice Tests that `paused()` returns false after the pause expires. - function test_paused_expired_succeeds() external { - _pauseAsGuardian(); + /// @notice Tests that `paused()` returns false when any other address is paused. + /// @param _address The address to pause. + function testFuzz_paused_otherAddress_succeeds(address _address) external { + vm.assume(_address != address(0)); + vm.assume(_address != address(optimismPortal2)); + + // Initially not paused + assertFalse(systemConfig.paused()); - vm.warp(block.timestamp + PAUSE_EXPIRY); + // Pause the system with a different address that's not global or identifier + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(_address); + // Verify still not paused assertFalse(systemConfig.paused()); - assertFalse(systemConfig.pausable()); } } @@ -702,26 +738,18 @@ contract SystemConfig_IsFeatureEnabled_Test is SystemConfig_TestInit { /// @title SystemConfig_Guardian_Test /// @notice Test contract for SystemConfig `guardian` function. contract SystemConfig_Guardian_Test is SystemConfig_TestInit { - /// @notice Tests that `guardian()` returns the configured guardian address. + /// @notice Tests that `guardian()` returns the correct address. function test_guardian_succeeds() external view { - assertEq(systemConfig.guardian(), systemConfig.GUARDIAN()); - - returnIfForkTest("SystemConfig_Guardian_Test: guardian does not match local.json on forked networks"); - assertEq(systemConfig.guardian(), deploy.cfg().guardian()); + assertEq(systemConfig.guardian(), superchainConfig.guardian()); } } -/// @title SystemConfig_IncidentResponder_Test -/// @notice Test contract for SystemConfig `incidentResponder` function. -contract SystemConfig_IncidentResponder_Test is SystemConfig_TestInit { - /// @notice Tests that `incidentResponder()` returns the configured incident responder. - function test_incidentResponder_succeeds() external view { - assertEq(systemConfig.incidentResponder(), systemConfig.INCIDENT_RESPONDER()); - - returnIfForkTest( - "SystemConfig_IncidentResponder_Test: incident responder does not match local.json on forked networks" - ); - assertEq(systemConfig.incidentResponder(), deploy.cfg().incidentResponder()); +/// @title SystemConfig_SuperchainConfig_Test +/// @notice Test contract for SystemConfig `superchainConfig` function. +contract SystemConfig_SuperchainConfig_Test is SystemConfig_TestInit { + /// @notice Tests that `superchainConfig()` returns the correct address. + function test_superchainConfig_succeeds() external view { + assertEq(address(systemConfig.superchainConfig()), address(superchainConfig)); } } @@ -773,153 +801,3 @@ contract SystemConfig_IsCustomGasToken_Test is SystemConfig_TestInit { assertFalse(systemConfig.isCustomGasToken()); } } - -/// @title SystemConfig_PauseExpiry_Test -/// @notice Test contract for SystemConfig `pauseExpiry` function. -contract SystemConfig_PauseExpiry_Test is SystemConfig_TestInit { - /// @notice Tests that `pauseExpiry` returns the correct constant value. - function test_pauseExpiry_succeeds() external view { - assertEq(systemConfig.pauseExpiry(), PAUSE_EXPIRY); - } -} - -/// @title SystemConfig_Pause_Test -/// @notice Test contract for SystemConfig `pause` function. -contract SystemConfig_Pause_Test is SystemConfig_TestInit { - /// @notice Tests that the guardian can pause the system. - function test_pause_guardian_succeeds() external { - vm.expectEmit(address(systemConfig)); - emit Paused(); - - _pauseAsGuardian(); - - assertTrue(systemConfig.paused()); - assertFalse(systemConfig.pausable()); - assertEq(systemConfig.pauseTimestamp(), block.timestamp); - } - - /// @notice Tests that the incident responder can pause the system. - /// @param _incidentResponder The incident responder to test. - function testFuzz_pause_incidentResponder_succeeds(address _incidentResponder) external { - // The incident responder is immutable and unset in the deploy config, so exercise the - // role against a freshly deployed instance that has one. - vm.assume(_incidentResponder != address(0) && _incidentResponder != systemConfig.guardian()); - ISystemConfig config = ISystemConfig( - DeployUtils.create1({ - _name: "SystemConfig", - _args: DeployUtils.encodeConstructor( - abi.encodeCall(ISystemConfig.__constructor__, (systemConfig.guardian(), _incidentResponder)) - ) - }) - ); - - vm.expectEmit(address(config)); - emit Paused(); - - vm.prank(_incidentResponder); - config.pause(); - - assertTrue(config.paused()); - } - - /// @notice Tests that `pause` reverts when called by a non-guardian and non-incident-responder. - /// @param _caller The unauthorized caller to test. - function testFuzz_pause_notGuardianOrIncidentResponder_reverts(address _caller) external { - vm.assume(_caller != systemConfig.guardian() && _caller != systemConfig.incidentResponder()); - - vm.expectRevert(ISystemConfig.SystemConfig_OnlyGuardianOrIncidentResponder.selector); - vm.prank(_caller); - systemConfig.pause(); - } - - /// @notice Tests that `pause` reverts when the system was already paused. - function test_pause_alreadyPaused_reverts() external { - _pauseAsGuardian(); - - vm.prank(systemConfig.guardian()); - vm.expectRevert(ISystemConfig.SystemConfig_AlreadyPaused.selector); - systemConfig.pause(); - } -} - -/// @title SystemConfig_Unpause_Test -/// @notice Test contract for SystemConfig `unpause` function. -contract SystemConfig_Unpause_Test is SystemConfig_TestInit { - /// @notice Tests that the guardian can unpause the system. - function test_unpause_succeeds() external { - _pauseAsGuardian(); - - vm.expectEmit(address(systemConfig)); - emit Unpaused(); - vm.prank(systemConfig.guardian()); - systemConfig.unpause(); - - assertFalse(systemConfig.paused()); - assertTrue(systemConfig.pausable()); - assertEq(systemConfig.pauseTimestamp(), 0); - } - - /// @notice Tests that `unpause` reverts when called by a non-guardian. - /// @param _caller The non-guardian caller to test. - function testFuzz_unpause_notGuardian_reverts(address _caller) external { - vm.assume(_caller != systemConfig.guardian()); - _pauseAsGuardian(); - - vm.expectRevert(ISystemConfig.SystemConfig_OnlyGuardian.selector); - vm.prank(_caller); - systemConfig.unpause(); - } -} - -/// @title SystemConfig_Extend_Test -/// @notice Test contract for SystemConfig `extend` function. -contract SystemConfig_Extend_Test is SystemConfig_TestInit { - /// @notice Tests that the guardian can extend a pause. - function test_extend_succeeds() external { - _pauseAsGuardian(); - vm.warp(block.timestamp + 1); - - vm.expectEmit(address(systemConfig)); - emit PauseExtended(); - vm.prank(systemConfig.guardian()); - systemConfig.extend(); - - assertTrue(systemConfig.paused()); - assertEq(systemConfig.pauseTimestamp(), block.timestamp); - assertEq(systemConfig.expiration(), block.timestamp + PAUSE_EXPIRY); - } - - /// @notice Tests that `extend` reverts when called by a non-guardian. - /// @param _caller The non-guardian caller to test. - function testFuzz_extend_notGuardian_reverts(address _caller) external { - vm.assume(_caller != systemConfig.guardian()); - _pauseAsGuardian(); - - vm.expectRevert(ISystemConfig.SystemConfig_OnlyGuardian.selector); - vm.prank(_caller); - systemConfig.extend(); - } - - /// @notice Tests that `extend` reverts when the system is not paused. - function test_extend_notAlreadyPaused_reverts() external { - vm.prank(systemConfig.guardian()); - vm.expectRevert(ISystemConfig.SystemConfig_NotAlreadyPaused.selector); - systemConfig.extend(); - } -} - -/// @title SystemConfig_Expiration_Test -/// @notice Test contract for SystemConfig `expiration` function. -contract SystemConfig_Expiration_Test is SystemConfig_TestInit { - /// @notice Tests that `expiration` returns 0 when the system is not paused. - function test_expiration_notPaused_succeeds() external view { - assertEq(systemConfig.expiration(), 0); - } - - /// @notice Tests that `expiration` returns the pause expiry timestamp. - function test_expiration_succeeds() external { - _pauseAsGuardian(); - - assertEq(systemConfig.expiration(), block.timestamp + PAUSE_EXPIRY); - } -} diff --git a/test/L1/proofs/AnchorStateRegistry.t.sol b/test/L1/proofs/AnchorStateRegistry.t.sol index d090dcf88..b97f63c1c 100644 --- a/test/L1/proofs/AnchorStateRegistry.t.sol +++ b/test/L1/proofs/AnchorStateRegistry.t.sol @@ -14,6 +14,7 @@ import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; // Contracts import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; @@ -26,6 +27,7 @@ abstract contract AnchorStateRegistry_TestInit is BaseTest { 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF; IDisputeGameFactory disputeGameFactory; + ISuperchainConfig superchainConfig; IDisputeGame gameProxy; /// @dev A valid l2BlockNumber that comes after the current anchor root block. @@ -40,7 +42,11 @@ abstract contract AnchorStateRegistry_TestInit is BaseTest { super.setUp(); disputeGameFactory = IDisputeGameFactory(address(factory)); - vm.mockCall(address(systemConfig), abi.encodeCall(systemConfig.guardian, ()), abi.encode(address(this))); + superchainConfig = ISuperchainConfig(makeAddr("superchain-config")); + vm.mockCall(address(superchainConfig), abi.encodeCall(superchainConfig.guardian, ()), abi.encode(address(this))); + vm.mockCall( + address(systemConfig), abi.encodeCall(systemConfig.superchainConfig, ()), abi.encode(superchainConfig) + ); // Get the actual anchor roots (, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); @@ -57,7 +63,7 @@ abstract contract AnchorStateRegistry_TestInit is BaseTest { } function _setPaused(bool _paused) internal { - vm.mockCall(address(systemConfig), abi.encodeWithSignature("paused()"), abi.encode(_paused)); + vm.mockCall(address(systemConfig), abi.encodeCall(systemConfig.paused, ()), abi.encode(_paused)); } function _mockGameStatus(GameStatus _status) internal { @@ -106,7 +112,7 @@ abstract contract AnchorStateRegistry_TestInit is BaseTest { } function _assumeNotGuardian(address _caller) internal view { - vm.assume(_caller != systemConfig.guardian()); + vm.assume(_caller != superchainConfig.guardian()); } function _expectUnauthorizedGuardianRevert(address _caller) internal { @@ -116,12 +122,12 @@ abstract contract AnchorStateRegistry_TestInit is BaseTest { } function _updateRetirementTimestampAsGuardian() internal { - vm.prank(systemConfig.guardian()); + vm.prank(superchainConfig.guardian()); anchorStateRegistry.updateRetirementTimestamp(); } function _blacklistDisputeGameAsGuardian(IDisputeGame _game) internal { - vm.prank(systemConfig.guardian()); + vm.prank(superchainConfig.guardian()); anchorStateRegistry.blacklistDisputeGame(_game); } @@ -202,6 +208,7 @@ contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_TestInit { // Verify contract addresses. assertEq(address(anchorStateRegistry.systemConfig()), address(systemConfig)); assertEq(address(anchorStateRegistry.disputeGameFactory()), address(disputeGameFactory)); + assertEq(address(anchorStateRegistry.superchainConfig()), address(superchainConfig)); } /// @notice Tests that the initializer value is correct. Trivial test for normal @@ -320,7 +327,7 @@ contract AnchorStateRegistry_SetRespectedGameType_Test is AnchorStateRegistry_Te /// @param _gameType The game type to set as respected function testFuzz_setRespectedGameType_succeeds(GameType _gameType) public { // Call as guardian - vm.prank(systemConfig.guardian()); + vm.prank(superchainConfig.guardian()); vm.expectEmit(address(anchorStateRegistry)); emit RespectedGameTypeSet(_gameType); anchorStateRegistry.setRespectedGameType(_gameType); @@ -395,7 +402,7 @@ contract AnchorStateRegistry_BlacklistDisputeGame_Test is AnchorStateRegistry_Te IDisputeGame secondGame = IDisputeGame(makeAddr("second-game")); // Blacklist both games - vm.startPrank(systemConfig.guardian()); + vm.startPrank(superchainConfig.guardian()); anchorStateRegistry.blacklistDisputeGame(gameProxy); anchorStateRegistry.blacklistDisputeGame(secondGame); vm.stopPrank(); @@ -415,7 +422,7 @@ contract AnchorStateRegistry_BlacklistDisputeGame_Test is AnchorStateRegistry_Te /// @notice Tests that blacklisting a game twice succeeds but doesn't change state function test_blacklistDisputeGame_twice_succeeds() public { // Blacklist the game - vm.startPrank(systemConfig.guardian()); + vm.startPrank(superchainConfig.guardian()); anchorStateRegistry.blacklistDisputeGame(gameProxy); // Blacklist again - should emit event but not change state @@ -900,7 +907,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit _mockGameNotRegistered(); - _expectSetAnchorStateInvalid(gameProxy, systemConfig.guardian()); + _expectSetAnchorStateInvalid(gameProxy, superchainConfig.guardian()); _assertCurrentAnchorRootEq(root, l2BlockNumber); } diff --git a/test/L1/proofs/BaseTest.t.sol b/test/L1/proofs/BaseTest.t.sol index 902e169d8..b0066d574 100644 --- a/test/L1/proofs/BaseTest.t.sol +++ b/test/L1/proofs/BaseTest.t.sol @@ -78,7 +78,7 @@ contract BaseTest is Test { function _deployContractsAndProxies() internal { systemConfig = ISystemConfig(makeAddr("system-config")); vm.mockCall(address(systemConfig), abi.encodeCall(ISystemConfig.guardian, ()), abi.encode(address(this))); - vm.mockCall(address(systemConfig), abi.encodeWithSignature("paused()"), abi.encode(false)); + vm.mockCall(address(systemConfig), abi.encodeCall(ISystemConfig.paused, ()), abi.encode(false)); AnchorStateRegistry _anchorStateRegistry = new AnchorStateRegistry(FINALITY_DELAY); DelayedWETH _delayedWETH = new DelayedWETH(DELAYED_WETH_DELAY); diff --git a/test/L1/proofs/DelayedWETH.t.sol b/test/L1/proofs/DelayedWETH.t.sol index d9f3e057d..131299538 100644 --- a/test/L1/proofs/DelayedWETH.t.sol +++ b/test/L1/proofs/DelayedWETH.t.sol @@ -45,6 +45,7 @@ contract DelayedWETH_Initialize_Test is DelayedWETH_TestBase { function test_initialize_succeeds() public view { assertEq(delayedWeth.proxyAdminOwner(), proxyAdminOwner); assertEq(address(delayedWeth.systemConfig()), address(systemConfig)); + assertEq(address(delayedWeth.config()), address(systemConfig.superchainConfig())); } /// @notice Tests that the initializer value is correct. Trivial test for normal initialization @@ -116,8 +117,8 @@ contract DelayedWETH_Withdraw_Test is DelayedWETH_TestBase { } function _pauseSuperchain() internal { - vm.prank(systemConfig.guardian()); - systemConfig.pause(); + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(address(0)); } function _prepareUnlockedWithdrawal() internal returns (uint256 balanceAfterDeposit_) { diff --git a/test/deploy/SystemDeploy.t.sol b/test/deploy/SystemDeploy.t.sol index 282d20359..02d3e0f7f 100644 --- a/test/deploy/SystemDeploy.t.sol +++ b/test/deploy/SystemDeploy.t.sol @@ -12,9 +12,7 @@ import { ISP1Verifier } from "interfaces/L1/proofs/zk/ISP1Verifier.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { INitroValidator } from "interfaces/L1/proofs/tee/INitroValidator.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; -import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { ProtocolVersions } from "src/L1/ProtocolVersions.sol"; -import { SystemConfig } from "src/L1/SystemConfig.sol"; import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; import { TEEVerifier } from "src/L1/proofs/tee/TEEVerifier.sol"; @@ -47,22 +45,6 @@ contract MockInvalidTEEProverRegistry { } } -contract MockLegacySuperchainConfig { - address internal immutable UNPAUSABLE_IDENTIFIER; - - constructor(address _unpausableIdentifier) { - UNPAUSABLE_IDENTIFIER = _unpausableIdentifier; - } - - function pausable(address _identifier) external view returns (bool) { - return _identifier != UNPAUSABLE_IDENTIFIER; - } - - function paused(address) external pure returns (bool) { - return false; - } -} - contract SystemDeploy_Test is Test, SystemDeployAssertions { Artifacts internal constant artifacts = Artifacts(address(uint160(uint256(keccak256(abi.encode("optimism.artifacts")))))); @@ -86,60 +68,64 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { sp1Verifier = new MockSP1Verifier(); } - function testFuzz_deployImplementations_bindsGuardianRoles_succeeds( + function testFuzz_deploySuperchain_succeeds( + address _superchainProxyAdminOwner, address _guardian, address _incidentResponder ) public { + vm.assume(_superchainProxyAdminOwner != address(0)); vm.assume(_guardian != address(0)); - SystemDeploy.DeployInput memory input = _defaultDeployInput(); - input.implementationsInput.guardian = _guardian; - input.implementationsInput.incidentResponder = _incidentResponder; + SystemDeploy.SuperchainOutput memory output = systemDeploy.deploySuperchain( + SystemDeploy.SuperchainInput({ + guardian: _guardian, + incidentResponder: _incidentResponder, + superchainProxyAdminOwner: _superchainProxyAdminOwner + }) + ); - SystemDeploy.DeployOutput memory output = systemDeploy.deploy(input); - ISystemConfig systemConfigProxy = output.opChain.systemConfigProxy; + assertEq(output.superchainProxyAdmin.owner(), _superchainProxyAdminOwner, "proxy admin owner"); + assertEq(output.superchainConfigProxy.guardian(), _guardian, "proxy guardian"); + assertEq(output.superchainConfigImpl.guardian(), _guardian, "impl guardian"); + assertEq(output.superchainConfigProxy.incidentResponder(), _incidentResponder, "proxy incident responder"); + assertEq(output.superchainConfigImpl.incidentResponder(), _incidentResponder, "impl incident responder"); - assertEq(systemConfigProxy.guardian(), _guardian, "proxy guardian"); - assertEq(ISystemConfig(output.impls.systemConfigImpl).guardian(), _guardian, "impl guardian"); - assertEq(systemConfigProxy.incidentResponder(), _incidentResponder, "proxy incident responder"); assertEq( - ISystemConfig(output.impls.systemConfigImpl).incidentResponder(), - _incidentResponder, - "impl incident responder" + EIP1967Helper.getImplementation(address(output.superchainConfigProxy)), + address(output.superchainConfigImpl), + "implementation" ); - assertEq( - EIP1967Helper.getImplementation(address(systemConfigProxy)), output.impls.systemConfigImpl, "implementation" + EIP1967Helper.getAdmin(address(output.superchainConfigProxy)), address(output.superchainProxyAdmin), "admin" ); } - function test_deployImplementations_nullGuardian_reverts() public { - SystemDeploy.DeployInput memory input = _defaultDeployInput(); - input.implementationsInput.guardian = address(0); + function test_deploySuperchain_nullInput_reverts() public { + SystemDeploy.SuperchainInput memory input = SystemDeploy.SuperchainInput({ + guardian: guardian, incidentResponder: incidentResponder, superchainProxyAdminOwner: address(0) + }); + vm.expectRevert(abi.encodeWithSelector(SystemDeploy.InvalidRoleAddress.selector, "superchainProxyAdminOwner")); + systemDeploy.deploySuperchain(input); + input = SystemDeploy.SuperchainInput({ + guardian: address(0), incidentResponder: incidentResponder, superchainProxyAdminOwner: owner + }); vm.expectRevert(abi.encodeWithSelector(SystemDeploy.InvalidRoleAddress.selector, "guardian")); - systemDeploy.deploy(input); + systemDeploy.deploySuperchain(input); } - function test_deploy_reuseImplementationAddresses_succeeds() public { - SystemDeploy.DeployInput memory input = _defaultDeployInput(); - SystemDeploy.DeployOutput memory output0 = systemDeploy.deploy(input); + function test_deploySuperchain_reuseAddresses_succeeds() public { + SystemDeploy.SuperchainInput memory input = SystemDeploy.SuperchainInput({ + guardian: guardian, incidentResponder: incidentResponder, superchainProxyAdminOwner: owner + }); - // Implementations are deployed deterministically, so a second chain on the same - // implementation input reuses them while getting fresh proxies. - input.opChainInput.l2ChainId = l2ChainId + 1; - input.opChainInput.saltMixer = "system-deploy-reuse-impls-test"; - SystemDeploy.DeployOutput memory output1 = systemDeploy.deploy(input); - - assertEq(output0.impls.systemConfigImpl, output1.impls.systemConfigImpl, "system config impl"); - assertEq(output0.impls.optimismPortalImpl, output1.impls.optimismPortalImpl, "portal impl"); - assertNotEq( - address(output0.opChain.systemConfigProxy), - address(output1.opChain.systemConfigProxy), - "system config proxy" - ); + SystemDeploy.SuperchainOutput memory output0 = systemDeploy.deploySuperchain(input); + SystemDeploy.SuperchainOutput memory output1 = systemDeploy.deploySuperchain(input); + + assertEq(address(output0.superchainConfigImpl), address(output1.superchainConfigImpl), "implementation"); + assertNotEq(address(output0.superchainConfigProxy), address(output1.superchainConfigProxy), "proxy"); } function test_deploy_withoutManagerAddress_succeeds() public { @@ -160,9 +146,10 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { assertEq(output.opChain.opChainProxyAdmin.owner(), owner, "op chain proxy admin owner"); assertEq(output.opChain.systemConfigProxy.batchInbox(), Types.chainIdToBatchInboxAddress(l2ChainId), "inbox"); _assertMultiproofDeployed(output, input); - assertEq(output.opChain.systemConfigProxy.guardian(), guardian, "system config guardian"); assertEq( - output.opChain.systemConfigProxy.incidentResponder(), incidentResponder, "system config incident responder" + address(output.opChain.systemConfigProxy.superchainConfig()), + address(output.superchain.superchainConfigProxy), + "superchain config" ); assertValidStandardSystem(_expected(output, input)); } @@ -199,27 +186,27 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { ); } - /// @notice Pins ProtocolVersions input validation before implementation deployment can broadcast. + /// @notice Pins ProtocolVersions input validation before superchain deployment can broadcast. function test_deploy_zeroInitialMinimumProtocolVersion_reverts() public { SystemDeploy.DeployInput memory input = _defaultDeployInput(); input.opChainInput.initialMinimumProtocolVersion = 0; - input.implementationsInput.guardian = address(0); + input.superchainInput.superchainProxyAdminOwner = address(0); vm.expectRevert(IProtocolVersions.ProtocolVersions_InvalidProtocolVersion.selector); systemDeploy.deploy(input); } - /// @notice Pins the packed-version bound check before implementation deployment can broadcast. + /// @notice Pins the packed-version bound check before superchain deployment can broadcast. function test_deploy_initialMinimumProtocolVersionTooLarge_reverts() public { SystemDeploy.DeployInput memory input = _defaultDeployInput(); input.opChainInput.initialMinimumProtocolVersion = uint256(type(uint128).max) + 1; - input.implementationsInput.guardian = address(0); + input.superchainInput.superchainProxyAdminOwner = address(0); vm.expectRevert(IProtocolVersions.ProtocolVersions_InvalidProtocolVersion.selector); systemDeploy.deploy(input); } - /// @notice Pins imported schedule ordering validation before implementation deployment can broadcast. + /// @notice Pins imported schedule ordering validation before superchain deployment can broadcast. function test_deploy_unorderedInitialSchedule_reverts() public { vm.warp(31); uint64[] memory schedule = new uint64[](3); @@ -229,7 +216,7 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { SystemDeploy.DeployInput memory input = _defaultDeployInput(); input.opChainInput.initialUpgradeSchedule = schedule; input.opChainInput.initialMinimumProtocolVersion = 42; - input.implementationsInput.guardian = address(0); + input.superchainInput.superchainProxyAdminOwner = address(0); vm.expectRevert( abi.encodeWithSelector( @@ -252,7 +239,7 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { SystemDeploy.DeployInput memory input = _defaultDeployInput(); input.opChainInput.initialUpgradeSchedule = schedule; input.opChainInput.initialMinimumProtocolVersion = 42; - input.implementationsInput.guardian = address(0); + input.superchainInput.superchainProxyAdminOwner = address(0); vm.expectRevert( abi.encodeWithSelector(IProtocolVersions.ProtocolVersions_InsufficientNotice.selector, activation) @@ -316,6 +303,7 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { systemDeploy.upgrade( SystemDeploy.UpgradeInput({ saveArtifacts: false, + superchainConfigProxy: output.superchain.superchainConfigProxy, implementations: implementations, systemConfigProxy: output.opChain.systemConfigProxy, protocolVersionsProxy: output.opChain.protocolVersionsProxy @@ -323,40 +311,6 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { ); } - function testFuzz_upgrade_expiredLegacyPause_reverts(bool _globalPause) public { - SystemDeploy.DeployInput memory input = _defaultDeployInput(); - SystemDeploy.DeployOutput memory output = systemDeploy.deploy(input); - address systemConfigProxy = address(output.opChain.systemConfigProxy); - address pausedIdentifier = _globalPause ? address(0) : address(output.opChain.optimismPortalProxy); - MockLegacySuperchainConfig legacySuperchainConfig = new MockLegacySuperchainConfig(pausedIdentifier); - address systemConfigImpl = output.opChain.opChainProxyAdmin.getProxyImplementation(systemConfigProxy); - - assertFalse(legacySuperchainConfig.paused(pausedIdentifier), "legacy pause inactive"); - assertFalse(legacySuperchainConfig.pausable(pausedIdentifier), "legacy pause record remains"); - - vm.mockCall( - systemConfigProxy, - abi.encodeWithSelector(bytes4(keccak256("superchainConfig()"))), - abi.encode(address(legacySuperchainConfig)) - ); - - vm.expectRevert(SystemDeploy.LegacySuperchainConfigPaused.selector); - systemDeploy.upgrade( - SystemDeploy.UpgradeInput({ - saveArtifacts: false, - implementations: output.impls, - systemConfigProxy: output.opChain.systemConfigProxy, - protocolVersionsProxy: output.opChain.protocolVersionsProxy - }) - ); - - assertEq( - output.opChain.opChainProxyAdmin.getProxyImplementation(systemConfigProxy), - systemConfigImpl, - "system config impl" - ); - } - function test_upgrade_withoutManagerDelegatecall_succeeds() public { SystemDeploy.DeployInput memory input = _defaultDeployInput(); SystemDeploy.DeployOutput memory output = systemDeploy.deploy(input); @@ -367,13 +321,21 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { SystemDeploy.UpgradeOutput memory upgradeOutput = systemDeploy.upgrade( SystemDeploy.UpgradeInput({ saveArtifacts: false, + superchainConfigProxy: output.superchain.superchainConfigProxy, implementations: implementations, systemConfigProxy: output.opChain.systemConfigProxy, protocolVersionsProxy: output.opChain.protocolVersionsProxy }) ); + assertFalse(upgradeOutput.superchainConfigUpgraded, "superchain already current"); assertTrue(upgradeOutput.chainUpgraded, "chain upgraded"); + assertEq( + output.superchain.superchainProxyAdmin + .getProxyImplementation(address(output.superchain.superchainConfigProxy)), + output.impls.superchainConfigImpl, + "superchain config impl" + ); assertEq( output.opChain.opChainProxyAdmin.getProxyImplementation(address(output.opChain.protocolVersionsProxy)), address(protocolVersionsImpl), @@ -403,6 +365,7 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { systemDeploy.upgrade( SystemDeploy.UpgradeInput({ saveArtifacts: false, + superchainConfigProxy: output.superchain.superchainConfigProxy, implementations: implementations, systemConfigProxy: output.opChain.systemConfigProxy, protocolVersionsProxy: output.opChain.protocolVersionsProxy @@ -437,12 +400,14 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { SystemDeploy.UpgradeOutput memory upgradeOutput = systemDeploy.upgrade( SystemDeploy.UpgradeInput({ saveArtifacts: false, + superchainConfigProxy: output.superchain.superchainConfigProxy, implementations: implementations, systemConfigProxy: output.opChain.systemConfigProxy, protocolVersionsProxy: IProtocolVersions(address(0)) }) ); + assertFalse(upgradeOutput.superchainConfigUpgraded, "superchain already current"); assertTrue(upgradeOutput.chainUpgraded, "chain upgraded"); assertEq( output.opChain.opChainProxyAdmin.getProxyImplementation(address(output.opChain.protocolVersionsProxy)), @@ -456,6 +421,7 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { SystemDeploy.DeployOutput memory output = systemDeploy.deploy(input); input.saveArtifacts = true; + input.superchainConfigProxy = output.superchain.superchainConfigProxy; input.implementations = output.impls; input.opChainInput.l2ChainId = l2ChainId + 1; input.opChainInput.saltMixer = "system-deploy-reuse-test"; @@ -470,45 +436,11 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { _assertMultiproofDeployed(reuseOutput, input); } - /// @notice The Guardian is immutable on the SystemConfig implementation, so a supplied implementation would - /// otherwise install a pause authority that the deploy input never asked for. - function test_deploy_suppliedSystemConfigImplWithWrongGuardian_reverts() public { - SystemDeploy.DeployInput memory input = _reuseImplementationsInput(); - input.implementations.systemConfigImpl = address(new SystemConfig(makeAddr("otherGuardian"), incidentResponder)); - - vm.expectRevert("SystemDeploy: SystemConfig guardian mismatch"); - systemDeploy.deploy(input); - } - - /// @notice Same hazard as the Guardian, for the second role that can pause the system. - function test_deploy_suppliedSystemConfigImplWithWrongIncidentResponder_reverts() public { - SystemDeploy.DeployInput memory input = _reuseImplementationsInput(); - input.implementations.systemConfigImpl = address(new SystemConfig(guardian, makeAddr("otherIncidentResponder"))); - - vm.expectRevert("SystemDeploy: SystemConfig incident responder mismatch"); - systemDeploy.deploy(input); - } - - /// @notice Supplied implementations must clear the same Guardian check as freshly deployed ones, otherwise the - /// chain deploys with nobody able to unpause it. - function test_deploy_suppliedImplementationsWithoutGuardian_reverts() public { - SystemDeploy.DeployInput memory input = _reuseImplementationsInput(); - input.implementationsInput.guardian = address(0); - - vm.expectRevert(abi.encodeWithSelector(SystemDeploy.InvalidRoleAddress.selector, "guardian")); - systemDeploy.deploy(input); - } - - /// @notice Returns a deploy input for a second chain that reuses the implementations of a completed deploy. - function _reuseImplementationsInput() internal returns (SystemDeploy.DeployInput memory input_) { - input_ = _defaultDeployInput(); - input_.implementations = systemDeploy.deploy(input_).impls; - input_.opChainInput.l2ChainId = l2ChainId + 1; - input_.opChainInput.saltMixer = "system-deploy-supplied-impls-test"; - } - function _defaultDeployInput() internal view returns (SystemDeploy.DeployInput memory input_) { input_.saveArtifacts = false; + input_.superchainInput = SystemDeploy.SuperchainInput({ + guardian: guardian, incidentResponder: incidentResponder, superchainProxyAdminOwner: owner + }); input_.implementationsInput = SystemDeploy.ImplementationInput({ withdrawalDelaySeconds: 100, proofMaturityDelaySeconds: 400, @@ -625,6 +557,7 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { expected_ = SystemDeployAssertions.ExpectedSystemDeployState({ systemConfig: _output.opChain.systemConfigProxy, anchorStateRegistry: _output.opChain.anchorStateRegistryProxy, + superchainConfig: _output.superchain.superchainConfigProxy, implementations: _output.impls, delayedWETH: _output.opChain.delayedWETHProxy, proxyAdminOwner: _input.opChainInput.roles.opChainProxyAdminOwner, diff --git a/test/deploy/SystemDeployAssertions.sol b/test/deploy/SystemDeployAssertions.sol index 5858c082e..749ed5dcb 100644 --- a/test/deploy/SystemDeployAssertions.sol +++ b/test/deploy/SystemDeployAssertions.sol @@ -21,6 +21,7 @@ import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; @@ -30,6 +31,7 @@ abstract contract SystemDeployAssertions is Test { struct ExpectedSystemDeployState { ISystemConfig systemConfig; IAnchorStateRegistry anchorStateRegistry; + ISuperchainConfig superchainConfig; Types.Implementations implementations; IDelayedWETH delayedWETH; address proxyAdminOwner; @@ -52,6 +54,7 @@ abstract contract SystemDeployAssertions is Test { function assertValidStandardSystem(ExpectedSystemDeployState memory _expected) internal view { IProxyAdmin proxyAdmin = _expected.systemConfig.proxyAdmin(); + _assertSuperchainConfig(_expected); _assertProxyAdmin(_expected, proxyAdmin); _assertSystemConfig(_expected, proxyAdmin); _assertBridgeAndPortalWiring(_expected, proxyAdmin); @@ -59,6 +62,10 @@ abstract contract SystemDeployAssertions is Test { _assertGame(_expected, proxyAdmin, _expected.multiproofGameType); } + function _assertSuperchainConfig(ExpectedSystemDeployState memory _expected) private view { + assertFalse(_expected.superchainConfig.paused(address(0)), "SPRCFG-10"); + } + function _assertProxyAdmin(ExpectedSystemDeployState memory _expected, IProxyAdmin _proxyAdmin) private view { assertEq(_proxyAdmin.owner(), _expected.proxyAdminOwner, "PROXYA-10"); } @@ -82,7 +89,7 @@ abstract contract SystemDeployAssertions is Test { assertEq(outputConfig.maximumBaseFee, expectedConfig.maximumBaseFee, "SYSCON-100"); assertEq(sysCfg.operatorFeeScalar(), 0, "SYSCON-110"); assertEq(sysCfg.operatorFeeConstant(), 0, "SYSCON-120"); - assertFalse(sysCfg.paused(), "SYSCON-130"); + assertEq(address(sysCfg.superchainConfig()), address(_expected.superchainConfig), "SYSCON-130"); assertEq(sysCfg.batchInbox(), Types.chainIdToBatchInboxAddress(_expected.l2ChainId), "SYSCON-140"); assertEq(sysCfg.l2ChainId(), _expected.l2ChainId, "SYSCON-150"); assertEq(sysCfg.delayedWETH(), address(_expected.delayedWETH), "SYSCON-160"); diff --git a/test/invariants/CrossDomainMessenger.t.sol b/test/invariants/CrossDomainMessenger.t.sol index cc677f22e..ceecba327 100644 --- a/test/invariants/CrossDomainMessenger.t.sol +++ b/test/invariants/CrossDomainMessenger.t.sol @@ -40,8 +40,9 @@ contract RelayActor { // For the failure case, we use an impossibly large minGasLimit so that the hasMinGas // check always fails regardless of available gas. We provide baseGas-level gas (enough // for relayMessage's overhead) to avoid OOG reverts. Limiting gas directly is fragile - // because the proxy-to-proxy call overhead (SystemConfig, OptimismPortal) leaves a - // razor-thin window between "enough to not OOG" and "not enough for hasMinGas to pass". + // because the proxy-to-proxy call overhead (SystemConfig → SuperchainConfig, + // OptimismPortal) leaves a razor-thin window between "enough to not OOG" and + // "not enough for hasMinGas to pass". uint32 relayMinGasLimit = shouldFail ? type(uint32).max : minGasLimit; // `relayMessage` always re-encodes as a v1 hash after checking the v0 hash hasn't been diff --git a/test/setup/Events.sol b/test/setup/Events.sol index d611bdbb3..4ed90fc29 100644 --- a/test/setup/Events.sol +++ b/test/setup/Events.sol @@ -75,9 +75,9 @@ abstract contract Events { bytes data ); - event Paused(); + event Paused(address identifier); - event Unpaused(); + event Unpaused(address identifier); - event PauseExtended(); + event PauseExtended(address identifier); } diff --git a/test/setup/ForkLive.s.sol b/test/setup/ForkLive.s.sol index 8d2aa610f..6b58d140f 100644 --- a/test/setup/ForkLive.s.sol +++ b/test/setup/ForkLive.s.sol @@ -16,6 +16,7 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { Types } from "scripts/libraries/Types.sol"; // Interfaces +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { IAggregateVerifier } from "interfaces/L1/proofs/IAggregateVerifier.sol"; import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; @@ -47,6 +48,7 @@ contract ForkLive is Script { struct SystemAddresses { address systemConfig; + address superchainConfig; } struct GameAddresses { @@ -60,11 +62,19 @@ contract ForkLive is Script { /// @notice Returns the production entrypoints for the current L1 fork. function forkSystemAddresses() internal view returns (SystemAddresses memory system_) { if (block.chainid == 1) { - system_ = SystemAddresses({ systemConfig: 0x73a79Fab69143498Ed3712e519A88a918e1f4072 }); + system_ = SystemAddresses({ + systemConfig: 0x73a79Fab69143498Ed3712e519A88a918e1f4072, + superchainConfig: 0xb535ff7F118260a952CE65e7fF41B1743De8EE6c + }); } else if (block.chainid == 11155111) { - system_ = SystemAddresses({ systemConfig: 0xf272670eb55e895584501d564AfEB048bEd26194 }); + system_ = SystemAddresses({ + systemConfig: 0xf272670eb55e895584501d564AfEB048bEd26194, + superchainConfig: 0xE4401EB53AE90a5335a51fe1828d7BeCf7a63508 + }); } else if (block.chainid == 560048) { - system_ = SystemAddresses({ systemConfig: 0xcC7c76564bea74A963A0Bd75E0bC9BcE3FF0EA80 }); + system_ = SystemAddresses({ + systemConfig: 0xcC7c76564bea74A963A0Bd75E0bC9BcE3FF0EA80, superchainConfig: address(0) + }); } else { revert UnsupportedChainId(); } @@ -112,6 +122,16 @@ contract ForkLive is Script { // Slightly hacky, we encode the uint chainId as an address to save it in Artifacts artifacts.save("L2ChainId", address(uint160(systemConfig.l2ChainId()))); + // Superchain shared contracts + address superchainConfig = system.superchainConfig; + if (superchainConfig == address(0)) { + try systemConfig.superchainConfig() returns (ISuperchainConfig superchainConfig_) { + superchainConfig = address(superchainConfig_); + } catch { } + } + if (superchainConfig != address(0)) { + _saveProxyAndImpl("SuperchainConfig", superchainConfig); + } // Core contracts artifacts.save("ProxyAdmin", EIP1967Helper.getAdmin(address(systemConfig))); _saveProxyAndImpl("SystemConfig", address(systemConfig)); @@ -156,13 +176,31 @@ contract ForkLive is Script { function _doUpgrade(address _upgrader, ISystemConfig _systemConfigProxy) internal { SystemDeploy systemDeploy = new SystemDeploy(); Types.Implementations memory implementations = _latestImplementations(); + + ISuperchainConfig superchainConfig = ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")); + IProxyAdmin superchainProxyAdmin = IProxyAdmin(EIP1967Helper.getAdmin(address(superchainConfig))); + address superchainPAO = superchainProxyAdmin.owner(); IProtocolVersions protocolVersionsProxy = IProtocolVersions(artifacts.getAddress("ProtocolVersionsProxy")); + // Run the shared SuperchainConfig upgrade as the Superchain ProxyAdmin owner. The script + // skips this step when the proxy is already at or above the target implementation version. + vm.prank(superchainPAO); + systemDeploy.upgrade( + SystemDeploy.UpgradeInput({ + saveArtifacts: false, + superchainConfigProxy: superchainConfig, + implementations: implementations, + systemConfigProxy: ISystemConfig(address(0)), + protocolVersionsProxy: IProtocolVersions(address(0)) + }) + ); + // Run the per-chain upgrade as the OP Chain ProxyAdmin owner. vm.prank(_upgrader); systemDeploy.upgrade( SystemDeploy.UpgradeInput({ saveArtifacts: false, + superchainConfigProxy: ISuperchainConfig(address(0)), implementations: implementations, systemConfigProxy: _systemConfigProxy, protocolVersionsProxy: protocolVersionsProxy diff --git a/test/setup/Setup.sol b/test/setup/Setup.sol index cef498fa5..07e0e9168 100644 --- a/test/setup/Setup.sol +++ b/test/setup/Setup.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.15; // Testing import { console2 as console } from "lib/forge-std/src/console2.sol"; import { Vm, VmSafe } from "lib/forge-std/src/Vm.sol"; +import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { FeatureFlags } from "test/setup/FeatureFlags.sol"; // Scripts @@ -26,6 +27,7 @@ import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; @@ -94,6 +96,8 @@ abstract contract Setup is FeatureFlags { // L1 contracts - core address proxyAdminOwner; IProxyAdmin proxyAdmin; + address superchainProxyAdminOwner; + IProxyAdmin superchainProxyAdmin; IOptimismPortal optimismPortal2; ISystemConfig systemConfig; IL1StandardBridge l1StandardBridge; @@ -101,6 +105,7 @@ abstract contract Setup is FeatureFlags { IAddressManager addressManager; IL1ERC721Bridge l1ERC721Bridge; IOptimismMintableERC20Factory l1OptimismMintableERC20Factory; + ISuperchainConfig superchainConfig; IProtocolVersions protocolVersions; // L2 contracts @@ -227,6 +232,7 @@ abstract contract Setup is FeatureFlags { l1ERC721Bridge = IL1ERC721Bridge(artifacts.mustGetAddress("L1ERC721BridgeProxy")); l1OptimismMintableERC20Factory = IOptimismMintableERC20Factory(artifacts.mustGetAddress("OptimismMintableERC20FactoryProxy")); + superchainConfig = ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")); anchorStateRegistry = IAnchorStateRegistry(artifacts.mustGetAddress("AnchorStateRegistryProxy")); disputeGameFactory = IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy")); delayedWeth = IDelayedWETH(artifacts.mustGetAddress("DelayedWETHProxy")); @@ -235,6 +241,8 @@ abstract contract Setup is FeatureFlags { protocolVersions = IProtocolVersions(artifacts.getAddress("ProtocolVersionsProxy")); proxyAdmin = IProxyAdmin(artifacts.mustGetAddress("ProxyAdmin")); proxyAdminOwner = proxyAdmin.owner(); + superchainProxyAdmin = IProxyAdmin(EIP1967Helper.getAdmin(address(superchainConfig))); + superchainProxyAdminOwner = superchainProxyAdmin.owner(); nitroValidator = INitroValidator(artifacts.getAddress("NitroValidator")); teeProverRegistry = TEEProverRegistry(artifacts.getAddress("TEEProverRegistry")); diff --git a/test/universal/ExtendedPause.t.sol b/test/universal/ExtendedPause.t.sol index cd002e71c..19e2b1714 100644 --- a/test/universal/ExtendedPause.t.sol +++ b/test/universal/ExtendedPause.t.sol @@ -4,37 +4,37 @@ pragma solidity 0.8.15; import { CommonTest } from "test/setup/CommonTest.sol"; /// @title ExtendedPause_Test -/// @notice These tests are somewhat redundant with tests in the SystemConfig and other +/// @notice These tests are somewhat redundant with tests in the SuperchainConfig and other /// pausable contracts, however it is worthwhile to pull them into one location to ensure /// that the behavior is consistent. contract ExtendedPause_Test is CommonTest { - /// @notice Tests that other contracts are paused when the system config is paused + /// @notice Tests that other contracts are paused when the superchain config is paused function test_pause_fullSystem_succeeds() external { _assertFullSystemPaused(false); - _pauseSystem(); + _pauseSuperchain(); _assertFullSystemPaused(true); } - /// @notice Tests that other contracts are unpaused when the system config is paused and + /// @notice Tests that other contracts are unpaused when the superchain config is paused and /// then unpaused. function test_unpause_fullSystem_succeeds() external { - _pauseSystem(); + _pauseSuperchain(); - vm.prank(systemConfig.guardian()); - systemConfig.unpause(); + vm.prank(superchainConfig.guardian()); + superchainConfig.unpause(address(0)); _assertFullSystemPaused(false); } - function _pauseSystem() internal { - vm.prank(systemConfig.guardian()); - systemConfig.pause(); + function _pauseSuperchain() internal { + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(address(0)); } function _assertFullSystemPaused(bool _paused) internal view { - assertEq(systemConfig.paused(), _paused); + assertEq(superchainConfig.paused(address(0)), _paused); assertEq(optimismPortal2.paused(), _paused); assertEq(l1CrossDomainMessenger.paused(), _paused); assertEq(l1StandardBridge.paused(), _paused); diff --git a/test/vendor/Initializable.t.sol b/test/vendor/Initializable.t.sol index 074f7af00..828e20807 100644 --- a/test/vendor/Initializable.t.sol +++ b/test/vendor/Initializable.t.sol @@ -14,6 +14,7 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; // Interfaces import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; @@ -126,7 +127,8 @@ contract Initializer_Test is CommonTest { optimismMintableERC20Factory: address(0), delayedWETH: address(0) }), - 0 + 0, + ISuperchainConfig(address(0)) ) ); contracts.push(