From 5c2a55e39c2c8b8254de2c4de720f64b439165c2 Mon Sep 17 00:00:00 2001 From: Mc01 Date: Tue, 24 Feb 2026 12:54:58 +0100 Subject: [PATCH] Celo op-deployer changes: optionally upgrade superchain config + run from superchain ops. --- op-chain-ops/script/script.go | 16 +++- op-deployer/pkg/deployer/standard/standard.go | 50 ++++++++--- .../pkg/deployer/upgrade/v2_0_0/upgrade.go | 1 + .../interfaces/L1/IOPContractsManager.sol | 5 +- .../scripts/deploy/UpgradeOPChain.s.sol | 17 +++- .../src/L1/OPContractsManager.sol | 27 ++++-- .../OPContractsManagerStandardValidator.sol | 37 ++++++++- .../test/L1/OPContractsManager.t.sol | 4 +- .../OPContractsManagerStandardValidator.t.sol | 82 +++++++++++++++++++ .../test/opcm/UpgradeOPChain.t.sol | 2 +- .../test/setup/ForkLive.s.sol | 2 +- 11 files changed, 211 insertions(+), 32 deletions(-) diff --git a/op-chain-ops/script/script.go b/op-chain-ops/script/script.go index 68c072a56884e..4ebd1301c4ab5 100644 --- a/op-chain-ops/script/script.go +++ b/op-chain-ops/script/script.go @@ -354,10 +354,18 @@ func (h *Host) Call(from common.Address, to common.Address, input []byte, gas ui defer func() { if r := recover(); r != nil { - // Cast to a string to check the error message. If it's not a string it's - // an unexpected panic and we should re-raise it. - rStr, ok := r.(string) - if !ok || !strings.Contains(strings.ToLower(rStr), "revision id 1") { + // Extract the panic message as a string. op-geth's journal.go panics with + // fmt.Errorf (an error type), so we must handle both string and error. + var rStr string + switch v := r.(type) { + case string: + rStr = v + case error: + rStr = v.Error() + default: + panic(r) + } + if !strings.Contains(strings.ToLower(rStr), "revision id") { fmt.Println("panic", rStr) panic(r) } diff --git a/op-deployer/pkg/deployer/standard/standard.go b/op-deployer/pkg/deployer/standard/standard.go index 7967deeaad5ee..6a806e81009cd 100644 --- a/op-deployer/pkg/deployer/standard/standard.go +++ b/op-deployer/pkg/deployer/standard/standard.go @@ -65,9 +65,15 @@ func L1VersionsFor(chainID uint64) (validation.Versions, error) { func GuardianAddressFor(chainID uint64) (common.Address, error) { switch chainID { case 1: - return common.Address(validation.StandardConfigRolesMainnet.Guardian), nil + // Celo Mainnet + return common.HexToAddress("0x6E226fa22e5F19363d231D3FA048aaBa73CC1f47"), nil + // Superchain + // return common.Address(validation.StandardConfigRolesMainnet.Guardian), nil case 11155111: - return common.Address(validation.StandardConfigRolesSepolia.Guardian), nil + // Celo Mainnet + return common.HexToAddress("0x5e60d897Cd62588291656b54655e98ee73f0aabF"), nil + // Superchain + // return common.Address(validation.StandardConfigRolesSepolia.Guardian), nil default: return common.Address{}, fmt.Errorf("unsupported chain ID: %d", chainID) } @@ -76,9 +82,15 @@ func GuardianAddressFor(chainID uint64) (common.Address, error) { func ChallengerAddressFor(chainID uint64) (common.Address, error) { switch chainID { case 1: - return common.Address(validation.StandardConfigRolesMainnet.Challenger), nil + // Celo Mainnet + return common.HexToAddress("0x6b145ebf66602ec524b196426b46631259689583"), nil + // Superchain + // return common.Address(validation.StandardConfigRolesMainnet.Challenger), nil case 11155111: - return common.Address(validation.StandardConfigRolesSepolia.Challenger), nil + // Celo Mainnet + return common.HexToAddress("0xC813b28614BD4CFA3d5Fdf153df41B273AB9D497"), nil + // Superchain + // return common.Address(validation.StandardConfigRolesSepolia.Challenger), nil default: return common.Address{}, fmt.Errorf("unsupported chain ID: %d", chainID) } @@ -121,9 +133,15 @@ func OPCMImplAddressFor(chainID uint64, tag string) (common.Address, error) { func SuperchainProxyAdminAddrFor(chainID uint64) (common.Address, error) { switch chainID { case 1: - return common.HexToAddress("0x543bA4AADBAb8f9025686Bd03993043599c6fB04"), nil + // Celo Mainnet + return common.HexToAddress("0x783A434532Ee94667979213af1711505E8bFE374"), nil + // Superchain + // return common.HexToAddress("0x543bA4AADBAb8f9025686Bd03993043599c6fB04"), nil case 11155111: - return common.HexToAddress("0x189aBAAaa82DfC015A588A7dbaD6F13b1D3485Bc"), nil + // Celo Mainnet + return common.HexToAddress("0xf7d7a3d3bb8abb6829249b3d3ad3d525d052027e"), nil + // Superchain + // return common.HexToAddress("0x189aBAAaa82DfC015A588A7dbaD6F13b1D3485Bc"), nil default: return common.Address{}, fmt.Errorf("unsupported chain ID: %d", chainID) } @@ -132,9 +150,15 @@ func SuperchainProxyAdminAddrFor(chainID uint64) (common.Address, error) { func L1ProxyAdminOwner(chainID uint64) (common.Address, error) { switch chainID { case 1: - return common.Address(validation.StandardConfigRolesMainnet.L1ProxyAdminOwner), nil + // Celo Mainnet + return common.HexToAddress("0x4092A77bAF58fef0309452cEaCb09221e556E112"), nil + // Superchain + // return common.Address(validation.StandardConfigRolesMainnet.L1ProxyAdminOwner), nil case 11155111: - return common.Address(validation.StandardConfigRolesSepolia.L1ProxyAdminOwner), nil + // Celo Mainnet + return common.HexToAddress("0x5e60d897Cd62588291656b54655e98ee73f0aabF"), nil + // Superchain + // return common.Address(validation.StandardConfigRolesSepolia.L1ProxyAdminOwner), nil default: return common.Address{}, fmt.Errorf("unsupported chain ID: %d", chainID) } @@ -154,9 +178,15 @@ func L2ProxyAdminOwner(chainID uint64) (common.Address, error) { func ProtocolVersionsOwner(chainID uint64) (common.Address, error) { switch chainID { case 1: - return common.Address(validation.StandardConfigRolesMainnet.ProtocolVersionsOwner), nil + // Celo Mainnet + return common.HexToAddress("0x4092A77bAF58fef0309452cEaCb09221e556E112"), nil + // Superchain + // return common.Address(validation.StandardConfigRolesMainnet.ProtocolVersionsOwner), nil case 11155111: - return common.Address(validation.StandardConfigRolesSepolia.ProtocolVersionsOwner), nil + // Celo Mainnet + return common.HexToAddress("0x5e60d897Cd62588291656b54655e98ee73f0aabF"), nil + // Superchain + // return common.Address(validation.StandardConfigRolesSepolia.ProtocolVersionsOwner), nil default: return common.Address{}, fmt.Errorf("unsupported chain ID: %d", chainID) } diff --git a/op-deployer/pkg/deployer/upgrade/v2_0_0/upgrade.go b/op-deployer/pkg/deployer/upgrade/v2_0_0/upgrade.go index 3124853628fc1..78311e5be043d 100644 --- a/op-deployer/pkg/deployer/upgrade/v2_0_0/upgrade.go +++ b/op-deployer/pkg/deployer/upgrade/v2_0_0/upgrade.go @@ -16,6 +16,7 @@ type UpgradeOPChainInput struct { Prank common.Address `json:"prank"` Opcm common.Address `json:"opcm"` EncodedChainConfigs []OPChainConfig `evm:"-" json:"chainConfigs"` + UpgradeSuperchainConfig bool `json:"upgradeSuperchainConfig"` } type OPChainConfig struct { diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol index 7b0c94669fbe4..68375c8612484 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol @@ -92,7 +92,7 @@ interface IOPContractsManagerUpgrader { function __constructor__(IOPContractsManagerContractsContainer _contractsContainer) external; - function upgrade(IOPContractsManager.OpChainConfig[] memory _opChainConfigs) external; + function upgrade(IOPContractsManager.OpChainConfig[] memory _opChainConfigs, bool _upgradeSuperchainConfig) external; function upgradeSuperchainConfig(ISuperchainConfig _superchainConfig, IProxyAdmin _superchainProxyAdmin) external; @@ -339,7 +339,8 @@ interface IOPContractsManager { /// @notice Upgrades the implementation of all proxies in the specified chains /// @param _opChainConfigs The chains to upgrade - function upgrade(OpChainConfig[] memory _opChainConfigs) external; + /// @param _upgradeSuperchainConfig Flag to optionally upgrade superchain config + function upgrade(OpChainConfig[] memory _opChainConfigs, bool _upgradeSuperchainConfig) external; /// @notice Upgrades the SuperchainConfig contract. /// @param _superchainConfig The SuperchainConfig contract to upgrade. diff --git a/packages/contracts-bedrock/scripts/deploy/UpgradeOPChain.s.sol b/packages/contracts-bedrock/scripts/deploy/UpgradeOPChain.s.sol index d238b55edc23c..54179e19ead23 100644 --- a/packages/contracts-bedrock/scripts/deploy/UpgradeOPChain.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/UpgradeOPChain.s.sol @@ -9,6 +9,7 @@ contract UpgradeOPChainInput is BaseDeployIO { address internal _prank; OPContractsManager internal _opcm; bytes _opChainConfigs; + bool _upgradeSuperchainConfig; // Setter for OPContractsManager type function set(bytes4 _sel, address _value) public { @@ -26,6 +27,11 @@ contract UpgradeOPChainInput is BaseDeployIO { else revert("UpgradeOPCMInput: unknown selector"); } + function set(bytes4 _sel, bool _value) public { + if (_sel == this.upgradeSuperchainConfig.selector) _upgradeSuperchainConfig = _value; + else revert("UpgradeOPCMInput: unknown selector"); + } + function prank() public view returns (address) { require(address(_prank) != address(0), "UpgradeOPCMInput: prank not set"); return _prank; @@ -40,6 +46,10 @@ contract UpgradeOPChainInput is BaseDeployIO { require(_opChainConfigs.length > 0, "UpgradeOPCMInput: not set"); return _opChainConfigs; } + + function upgradeSuperchainConfig() public view returns (bool) { + return _upgradeSuperchainConfig; + } } contract UpgradeOPChain is Script { @@ -47,6 +57,7 @@ contract UpgradeOPChain is Script { OPContractsManager opcm = _uoci.opcm(); OPContractsManager.OpChainConfig[] memory opChainConfigs = abi.decode(_uoci.opChainConfigs(), (OPContractsManager.OpChainConfig[])); + bool upgradeSuperchainConfig_ = _uoci.upgradeSuperchainConfig(); // Etch DummyCaller contract. This contract is used to mimic the contract that is used // as the source of the delegatecall to the OPCM. In practice this will be the governance @@ -60,7 +71,7 @@ contract UpgradeOPChain is Script { // Call into the DummyCaller. This will perform the delegatecall under the hood and // return the result. vm.broadcast(msg.sender); - (bool success,) = DummyCaller(prank).upgrade(opChainConfigs); + (bool success,) = DummyCaller(prank).upgrade(opChainConfigs, upgradeSuperchainConfig_); require(success, "UpgradeChain: upgrade failed"); } } @@ -68,8 +79,8 @@ contract UpgradeOPChain is Script { contract DummyCaller { address internal _opcmAddr; - function upgrade(OPContractsManager.OpChainConfig[] memory _opChainConfigs) external returns (bool, bytes memory) { - bytes memory data = abi.encodeCall(DummyCaller.upgrade, _opChainConfigs); + function upgrade(OPContractsManager.OpChainConfig[] memory _opChainConfigs, bool _upgradeSuperchainConfig) external returns (bool, bytes memory) { + bytes memory data = abi.encodeCall(DummyCaller.upgrade, (_opChainConfigs, _upgradeSuperchainConfig)); (bool success, bytes memory result) = _opcmAddr.delegatecall(data); return (success, result); } diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index f9607145b4b70..964391d3c6a03 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -29,6 +29,7 @@ import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisput import { ISuperFaultDisputeGame } from "interfaces/dispute/ISuperFaultDisputeGame.sol"; import { ISuperPermissionedDisputeGame } from "interfaces/dispute/ISuperPermissionedDisputeGame.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IHasSuperchainConfig } from "interfaces/L1/IHasSuperchainConfig.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { IOptimismPortalInterop } from "interfaces/L1/IOptimismPortalInterop.sol"; @@ -732,10 +733,11 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @notice Upgrades a set of chains to the latest implementation contracts /// @param _opChainConfigs Array of OpChain structs, one per chain to upgrade + /// @param _upgradeSuperchainConfig Flag to indicate if superchainConfig should be upgraded /// @dev This function is intended to be DELEGATECALLed by an address that is the common owner of every chain in /// `_opChainConfigs`'s ProxyAdmin. /// @dev This function requires that each chain's superchainConfig is already upgraded. - function upgrade(OPContractsManager.OpChainConfig[] memory _opChainConfigs) external virtual { + function upgrade(OPContractsManager.OpChainConfig[] memory _opChainConfigs, bool _upgradeSuperchainConfig) external virtual { // Grab the implementations. OPContractsManager.Implementations memory impls = getImplementations(); @@ -745,11 +747,17 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { uint256 l2ChainId = _opChainConfigs[i].systemConfigProxy.l2ChainId(); // Grab the SuperchainConfig. - ISuperchainConfig superchainConfig = _opChainConfigs[i].systemConfigProxy.superchainConfig(); + ISuperchainConfig celoSuperchainConfig = _opChainConfigs[i].systemConfigProxy.superchainConfig(); + ISuperchainConfig superchainConfig = IHasSuperchainConfig(address(celoSuperchainConfig)).superchainConfig(); // SuperchainConfig should be in older version than impl on Celo L1. - if (!SemverComp.lt(superchainConfig.version(), ISuperchainConfig(impls.superchainConfigImpl).version())) { - revert OPContractsManagerUpgrader_SuperchainConfigMismatch(); + { + if (!SemverComp.lt(superchainConfig.version(), ISuperchainConfig(impls.superchainConfigImpl).version())) { + revert OPContractsManagerUpgrader_SuperchainConfigMismatch(); + } else if (_upgradeSuperchainConfig) { + // Celo: some chains (like Celo Mainnet) follow external superchain config that is not desired to be upgraded + __upgradeSuperchainConfig(superchainConfig, _opChainConfigs[i].proxyAdmin); + } } // Do the chain upgrade. @@ -898,12 +906,16 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { } } + function upgradeSuperchainConfig(ISuperchainConfig _superchainConfig, IProxyAdmin _superchainProxyAdmin) external { + __upgradeSuperchainConfig(_superchainConfig, _superchainProxyAdmin); + } + /// @notice Upgrades the SuperchainConfig contract. /// @param _superchainConfig The SuperchainConfig contract to upgrade. /// @param _superchainProxyAdmin The ProxyAdmin contract to use for the upgrade. /// @dev This function is intended to be DELEGATECALLed by the superchainConfig's ProxyAdminOwner. /// @dev This function will revert if the SuperchainConfig is already at or above the target version. - function upgradeSuperchainConfig(ISuperchainConfig _superchainConfig, IProxyAdmin _superchainProxyAdmin) external { + function __upgradeSuperchainConfig(ISuperchainConfig _superchainConfig, IProxyAdmin _superchainProxyAdmin) internal { // Only upgrade the superchainConfig if the current version is less than the target version. if ( SemverComp.gte( @@ -2031,13 +2043,14 @@ contract OPContractsManager is ISemver { /// @notice Upgrades a set of chains to the latest implementation contracts /// @param _opChainConfigs Array of OpChain structs, one per chain to upgrade + /// @param _upgradeSuperchainConfig Flag to indicate if superchainConfig should be upgraded /// @dev This function is intended to be DELEGATECALLed by an address that is the common owner of every chain in /// `_opChainConfigs`'s ProxyAdmin. /// @dev This function requires that each chain's superchainConfig is already upgraded. - function upgrade(OpChainConfig[] memory _opChainConfigs) external virtual { + function upgrade(OpChainConfig[] memory _opChainConfigs, bool _upgradeSuperchainConfig) external virtual { if (address(this) == address(thisOPCM)) revert OnlyDelegatecall(); - bytes memory data = abi.encodeCall(OPContractsManagerUpgrader.upgrade, (_opChainConfigs)); + bytes memory data = abi.encodeCall(OPContractsManagerUpgrader.upgrade, (_opChainConfigs, _upgradeSuperchainConfig)); _performDelegateCall(address(opcmUpgrader), data); } diff --git a/packages/contracts-bedrock/src/L1/OPContractsManagerStandardValidator.sol b/packages/contracts-bedrock/src/L1/OPContractsManagerStandardValidator.sol index 64464b6fda7b8..6ef42904a320f 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManagerStandardValidator.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManagerStandardValidator.sol @@ -17,6 +17,7 @@ import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.so import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IHasSuperchainConfig } from "interfaces/L1/IHasSuperchainConfig.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; @@ -232,6 +233,10 @@ contract OPContractsManagerStandardValidator is ISemver { return _errors; } + function celoSuperchainConfig(address _celoSuperchainConfig) internal view returns (ISuperchainConfig) { + return IHasSuperchainConfig(_celoSuperchainConfig).superchainConfig(); + } + /// @notice Asserts that the SystemConfig contract is valid. function assertValidSystemConfig( string memory _errors, @@ -261,13 +266,31 @@ contract OPContractsManagerStandardValidator is ISemver { _errors = internalRequire(_sysCfg.operatorFeeScalar() == 0, "SYSCON-110", _errors); _errors = internalRequire(_sysCfg.operatorFeeConstant() == 0, "SYSCON-120", _errors); _errors = internalRequire(getProxyAdmin(address(_sysCfg)) == _admin, "SYSCON-130", _errors); - _errors = internalRequire(_sysCfg.superchainConfig() == superchainConfig, "SYSCON-140", _errors); + _errors = internalRequire(celoSuperchainConfig(address(_sysCfg.superchainConfig())) == superchainConfig, "SYSCON-140", _errors); // CGT prevents Lockbox if (_sysCfg.isCustomGasToken()) { _errors = internalRequire(!_sysCfg.isFeatureEnabled(Features.ETH_LOCKBOX), "SYSCON-150", _errors); } + // Celo extra validation: custom gas token + (address address_, uint8 decimals_) = _sysCfg.gasPayingToken(); + address expected_; + string memory name_; + if (address(_sysCfg) == address(0x760a5F022C9940f4A074e0030be682F560d29818)) { + // sepolia + expected_ = address(0x3C7011fD5e6Aed460cAa4985cF8d8Caba435b092); + name_ = "Celo"; + } else if (address(_sysCfg) == address(0x89E31965D844a309231B1f17759Ccaf1b7c09861)) { + // mainnet + expected_ = address(0x057898f3C43F129a17517B9056D23851F124b19f); + name_ = "Celo native asset"; + } + _errors = internalRequire(address_ == expected_, "SYSCON-CEL-10", _errors); + _errors = internalRequire(decimals_ == 18, "SYSCON-CEL-20", _errors); + _errors = internalRequire(LibString.eq(_sysCfg.gasPayingTokenName(), name_) , "SYSCON-CEL-30", _errors); + _errors = internalRequire(LibString.eq(_sysCfg.gasPayingTokenSymbol(), "CELO") , "SYSCON-CEL-40", _errors); + return _errors; } @@ -421,6 +444,16 @@ contract OPContractsManagerStandardValidator is ISemver { _errors = internalRequire(address(_portal.systemConfig()) == address(_sysCfg), "PORTAL-40", _errors); _errors = internalRequire(_portal.l2Sender() == Constants.DEFAULT_L2_SENDER, "PORTAL-80", _errors); _errors = internalRequire(getProxyAdmin(address(_portal)) == _admin, "PORTAL-90", _errors); + + // Celo extra validation: celo superchain config vs external superchain config + address celoSuperchainConfig_ = address(_portal.superchainConfig()); + _errors = internalRequire( + _portal.guardian() != superchainConfig.guardian(), "PORTAL-CEL-10", _errors + ); // True only for Celo Mainnet + _errors = internalRequire( + _portal.guardian() == ISuperchainConfig(celoSuperchainConfig_).guardian(), "PORTAL-CEL-20", _errors + ); // True for Celo Mainnet & Celo Testnets + return _errors; } @@ -451,7 +484,7 @@ contract OPContractsManagerStandardValidator is ISemver { _errors = internalRequire(_lockbox.systemConfig() == _sysCfg, "LOCKBOX-40", _errors); _errors = internalRequire(_lockbox.authorizedPortals(_portal), "LOCKBOX-50", _errors); // Lockbox is not compatible with CGT - _errors = internalRequire(!_sysCfg.isCustomGasToken(), "LOCKBOX-60", _errors); + _errors = internalRequire(!_sysCfg.isCustomGasToken(), "LOCKBOX-CEL-10", _errors); return _errors; } diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index bd0951c487e09..879bed2ae9386 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -216,7 +216,7 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { // Execute the chain upgrade. DelegateCaller(_delegateCaller).dcForward( - address(_opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChainConfigs)) + address(_opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChainConfigs, false)) ); // Return early if a revert was expected. Otherwise we'll get errors below. @@ -1343,7 +1343,7 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { function test_upgrade_notDelegateCalled_reverts() public { vm.prank(upgrader); vm.expectRevert(IOPContractsManager.OnlyDelegatecall.selector); - opcm.upgrade(opChainConfigs); + opcm.upgrade(opChainConfigs, false); } function test_upgrade_notProxyAdminOwner_reverts() public { diff --git a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol index c516d46843fb7..e901488c103c8 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol @@ -38,6 +38,9 @@ import { IStandardBridge } from "interfaces/universal/IStandardBridge.sol"; import { IOPContractsManagerStandardValidator } from "interfaces/L1/IOPContractsManagerStandardValidator.sol"; import { IMIPS64 } from "interfaces/cannon/IMIPS64.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IHasSuperchainConfig } from "interfaces/L1/IHasSuperchainConfig.sol"; + /// @title BadDisputeGameFactoryReturner /// @notice Used to return a bad DisputeGameFactory address to the OPContractsManagerStandardValidator. Far easier /// than the alternative ways of mocking this value since the normal vm.mockCall will cause @@ -195,6 +198,78 @@ contract OPContractsManagerStandardValidator_TestInit is CommonTest { // Add the FaultDisputeGame to the DisputeGameFactory. vm.prank(disputeGameFactory.owner()); disputeGameFactory.setImplementation(GameTypes.CANNON, IDisputeGame(address(fdg))); + + // ======================================== + // Celo-specific topology mocks + // ======================================== + // Celo uses: Portal → CeloSuperchainConfig → SuperchainConfig + // The validator calls IHasSuperchainConfig(addr).superchainConfig() to resolve + // the real SuperchainConfig through the Celo wrapper layer. + + // Mock IHasSuperchainConfig indirection on SuperchainConfig itself. + // In tests, sysCfg.superchainConfig() returns superchainConfig directly, + // so celoSuperchainConfig() needs superchainConfig.superchainConfig() to return itself. + vm.mockCall( + address(superchainConfig), + abi.encodeCall(IHasSuperchainConfig.superchainConfig, ()), + abi.encode(superchainConfig) + ); + + // Create a mock CeloSuperchainConfig for the portal topology + address celoSC = makeAddr("CeloSuperchainConfig"); + + // portal.superchainConfig() returns celoSC (Celo wrapper) + vm.mockCall( + address(optimismPortal2), + abi.encodeCall(IOptimismPortal2.superchainConfig, ()), + abi.encode(celoSC) + ); + + // celoSC.superchainConfig() returns the real superchainConfig + vm.mockCall( + celoSC, + abi.encodeCall(IHasSuperchainConfig.superchainConfig, ()), + abi.encode(superchainConfig) + ); + + // PORTAL-CEL-10 & PORTAL-CEL-20: distinct guardians + // Celo Mainnet: portal.guardian() (from CeloSuperchainConfig) differs from + // superchainConfig.guardian() (Optimism-owned). Mock accordingly. + address celoGuardian = makeAddr("CeloGuardian"); + vm.mockCall( + celoSC, + abi.encodeCall(ISuperchainConfig.guardian, ()), + abi.encode(celoGuardian) + ); + vm.mockCall( + address(optimismPortal2), + abi.encodeCall(IOptimismPortal2.guardian, ()), + abi.encode(celoGuardian) + ); + + // SYSCON-CEL-*: gas paying token mocks + // Test SystemConfig address doesn't match Celo hardcoded addresses, so + // expected_ = address(0) and name_ = "". Mock gasPayingToken to satisfy checks. + vm.mockCall( + address(systemConfig), + abi.encodeCall(ISystemConfig.gasPayingToken, ()), + abi.encode(address(0), uint8(18)) + ); + vm.mockCall( + address(systemConfig), + abi.encodeCall(ISystemConfig.isCustomGasToken, ()), + abi.encode(false) + ); + vm.mockCall( + address(systemConfig), + abi.encodeCall(ISystemConfig.gasPayingTokenName, ()), + abi.encode("") + ); + vm.mockCall( + address(systemConfig), + abi.encodeCall(ISystemConfig.gasPayingTokenSymbol, ()), + abi.encode("CELO") + ); } /// @notice Runs the OPContractsManagerStandardValidator.validate function. @@ -492,6 +567,13 @@ contract OPContractsManagerStandardValidator_SystemConfig_Test is OPContractsMan vm.mockCall( address(systemConfig), abi.encodeCall(ISystemConfig.superchainConfig, ()), abi.encode(address(0xbad)) ); + // Mock the IHasSuperchainConfig indirection on the bad address so it doesn't revert + // when the validator calls celoSuperchainConfig(). + vm.mockCall( + address(0xbad), + abi.encodeCall(IHasSuperchainConfig.superchainConfig, ()), + abi.encode(address(0xbad)) + ); assertEq("SYSCON-140", _validate(true)); } } diff --git a/packages/contracts-bedrock/test/opcm/UpgradeOPChain.t.sol b/packages/contracts-bedrock/test/opcm/UpgradeOPChain.t.sol index 06e20c078c122..3071f413c0df6 100644 --- a/packages/contracts-bedrock/test/opcm/UpgradeOPChain.t.sol +++ b/packages/contracts-bedrock/test/opcm/UpgradeOPChain.t.sol @@ -122,7 +122,7 @@ contract UpgradeOPChainInput_Test is Test { contract MockOPCM { event UpgradeCalled(address indexed sysCfgProxy, address indexed proxyAdmin, bytes32 indexed absolutePrestate); - function upgrade(OPContractsManager.OpChainConfig[] memory _opChainConfigs) public { + function upgrade(OPContractsManager.OpChainConfig[] memory _opChainConfigs, bool) public { emit UpgradeCalled( address(_opChainConfigs[0].systemConfigProxy), address(_opChainConfigs[0].proxyAdmin), diff --git a/packages/contracts-bedrock/test/setup/ForkLive.s.sol b/packages/contracts-bedrock/test/setup/ForkLive.s.sol index 146a25466b88b..ccb2d13d0d6ba 100644 --- a/packages/contracts-bedrock/test/setup/ForkLive.s.sol +++ b/packages/contracts-bedrock/test/setup/ForkLive.s.sol @@ -241,7 +241,7 @@ contract ForkLive is Deployer, StdAssertions { // Upgrade the chain. DelegateCaller(_delegateCaller).dcForward( - address(_opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChains)) + address(_opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChains, false)) ); // Reset the upgrader to the original code.