diff --git a/op-chain-ops/interopgen/deploy.go b/op-chain-ops/interopgen/deploy.go index 8013438e4c44..b7e4fe943e41 100644 --- a/op-chain-ops/interopgen/deploy.go +++ b/op-chain-ops/interopgen/deploy.go @@ -171,6 +171,7 @@ func DeploySuperchainToL1(l1Host *script.Host, superCfg *SuperchainConfig) (*Sup L1ContractsRelease: superCfg.Implementations.L1ContractsRelease, SuperchainConfigProxy: superDeployment.SuperchainConfigProxy, ProtocolVersionsProxy: superDeployment.ProtocolVersionsProxy, + UpgradeController: superCfg.ProxyAdminOwner, UseInterop: superCfg.Implementations.UseInterop, }) if err != nil { diff --git a/op-deployer/pkg/deployer/bootstrap/implementations.go b/op-deployer/pkg/deployer/bootstrap/implementations.go index 101db67fb8d3..9d2841a0a8cd 100644 --- a/op-deployer/pkg/deployer/bootstrap/implementations.go +++ b/op-deployer/pkg/deployer/bootstrap/implementations.go @@ -39,6 +39,7 @@ type ImplementationsConfig struct { DisputeGameFinalityDelaySeconds uint64 `cli:"dispute-game-finality-delay-seconds"` SuperchainConfigProxy common.Address `cli:"superchain-config-proxy"` ProtocolVersionsProxy common.Address `cli:"protocol-versions-proxy"` + UpgradeController common.Address `cli:"upgrade-controller"` UseInterop bool `cli:"use-interop"` Logger log.Logger @@ -192,6 +193,7 @@ func Implementations(ctx context.Context, cfg ImplementationsConfig) (opcm.Deplo L1ContractsRelease: cfg.L1ContractsRelease, SuperchainConfigProxy: cfg.SuperchainConfigProxy, ProtocolVersionsProxy: cfg.ProtocolVersionsProxy, + UpgradeController: cfg.UpgradeController, UseInterop: cfg.UseInterop, }, ); err != nil { diff --git a/op-deployer/pkg/deployer/bootstrap/implementations_test.go b/op-deployer/pkg/deployer/bootstrap/implementations_test.go index 3f4952f853e4..5d4391dbefa8 100644 --- a/op-deployer/pkg/deployer/bootstrap/implementations_test.go +++ b/op-deployer/pkg/deployer/bootstrap/implementations_test.go @@ -70,6 +70,8 @@ func testImplementations(t *testing.T, forkRPCURL string) { loc, _ := testutil.LocalArtifacts(t) + proxyAdminOwner, err := standard.L1ProxyAdminOwner(uint64(chainID.Uint64())) + require.NoError(t, err) deploy := func() opcm.DeployImplementationsOutput { out, err := Implementations(ctx, ImplementationsConfig{ L1RPCUrl: runner.RPCUrl(), @@ -85,6 +87,7 @@ func testImplementations(t *testing.T, forkRPCURL string) { MIPSVersion: 1, SuperchainConfigProxy: common.Address(*superchain.Config.SuperchainConfigAddr), ProtocolVersionsProxy: common.Address(*superchain.Config.ProtocolVersionsAddr), + UpgradeController: proxyAdminOwner, UseInterop: false, }) require.NoError(t, err) diff --git a/op-deployer/pkg/deployer/opcm/implementations.go b/op-deployer/pkg/deployer/opcm/implementations.go index f1df05a365e2..9c232bf987c7 100644 --- a/op-deployer/pkg/deployer/opcm/implementations.go +++ b/op-deployer/pkg/deployer/opcm/implementations.go @@ -20,6 +20,7 @@ type DeployImplementationsInput struct { L1ContractsRelease string SuperchainConfigProxy common.Address ProtocolVersionsProxy common.Address + UpgradeController common.Address UseInterop bool // if true, deploy Interop implementations } diff --git a/op-deployer/pkg/deployer/opcm/opcm.go b/op-deployer/pkg/deployer/opcm/opcm.go index b8e26384200c..808556aba28d 100644 --- a/op-deployer/pkg/deployer/opcm/opcm.go +++ b/op-deployer/pkg/deployer/opcm/opcm.go @@ -11,6 +11,7 @@ type DeployOPCMInput struct { SuperchainConfig common.Address ProtocolVersions common.Address L1ContractsRelease string + UpgradeController common.Address AddressManagerBlueprint common.Address ProxyBlueprint common.Address diff --git a/op-deployer/pkg/deployer/pipeline/implementations.go b/op-deployer/pkg/deployer/pipeline/implementations.go index 063b8ae02f4d..7e2b69766e90 100644 --- a/op-deployer/pkg/deployer/pipeline/implementations.go +++ b/op-deployer/pkg/deployer/pipeline/implementations.go @@ -56,6 +56,7 @@ func DeployImplementations(env *Env, intent *state.Intent, st *state.State) erro L1ContractsRelease: contractsRelease, SuperchainConfigProxy: st.SuperchainDeployment.SuperchainConfigProxyAddress, ProtocolVersionsProxy: st.SuperchainDeployment.ProtocolVersionsProxyAddress, + UpgradeController: intent.SuperchainRoles.ProxyAdminOwner, UseInterop: intent.UseInterop, }, ) diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol index f320205f2d68..28ae6fec82de 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol @@ -170,6 +170,7 @@ interface IOPContractsManager { error ReservedBitsSet(); error UnexpectedPreambleData(bytes data); error UnsupportedERCVersion(uint8 version); + error OnlyUpgradeController(); /// @notice Thrown when an address is the zero address. error AddressNotFound(address who); @@ -208,7 +209,8 @@ interface IOPContractsManager { IProtocolVersions _protocolVersions, string memory _l1ContractsRelease, Blueprints memory _blueprints, - Implementations memory _implementations + Implementations memory _implementations, + address _upgradeController ) external; @@ -234,4 +236,10 @@ interface IOPContractsManager { /// @notice Returns the implementation contract addresses. function implementations() external view returns (Implementations memory); + + function upgradeController() external view returns (address); + + function isRC() external view returns (bool); + + function setRC(bool _isRC) external; } diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerInterop.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerInterop.sol index 343659970878..2f4cfdbda60b 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerInterop.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerInterop.sol @@ -11,7 +11,8 @@ interface IOPContractsManagerInterop is IOPContractsManager { IProtocolVersions _protocolVersions, string memory _l1ContractsRelease, Blueprints memory _blueprints, - Implementations memory _implementations + Implementations memory _implementations, + address _upgradeController ) external; } diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index 024e36c15afb..eecb71e2eeeb 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -292,6 +292,10 @@ contract Deploy is Deployer { dii.set(dii.l1ContractsRelease.selector, release); dii.set(dii.superchainConfigProxy.selector, artifacts.mustGetAddress("SuperchainConfigProxy")); dii.set(dii.protocolVersionsProxy.selector, artifacts.mustGetAddress("ProtocolVersionsProxy")); + dii.set( + dii.upgradeController.selector, + IProxyAdmin(EIP1967Helper.getAdmin(artifacts.mustGetAddress("SuperchainConfigProxy"))).owner() + ); if (_isInterop) { di = DeployImplementations(new DeployImplementationsInterop()); diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index ec2a39b5e874..13510a24c31f 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol @@ -45,6 +45,7 @@ contract DeployImplementationsInput is BaseDeployIO { // Outputs from DeploySuperchain.s.sol. ISuperchainConfig internal _superchainConfigProxy; IProtocolVersions internal _protocolVersionsProxy; + address internal _upgradeController; function set(bytes4 _sel, uint256 _value) public { require(_value != 0, "DeployImplementationsInput: cannot set zero value"); @@ -77,6 +78,7 @@ contract DeployImplementationsInput is BaseDeployIO { require(_addr != address(0), "DeployImplementationsInput: cannot set zero address"); if (_sel == this.superchainConfigProxy.selector) _superchainConfigProxy = ISuperchainConfig(_addr); else if (_sel == this.protocolVersionsProxy.selector) _protocolVersionsProxy = IProtocolVersions(_addr); + else if (_sel == this.upgradeController.selector) _upgradeController = _addr; else revert("DeployImplementationsInput: unknown selector"); } @@ -127,6 +129,11 @@ contract DeployImplementationsInput is BaseDeployIO { require(address(_protocolVersionsProxy) != address(0), "DeployImplementationsInput: not set"); return _protocolVersionsProxy; } + + function upgradeController() public view returns (address) { + require(address(_upgradeController) != address(0), "DeployImplementationsInput: not set"); + return _upgradeController; + } } contract DeployImplementationsOutput is BaseDeployIO { @@ -269,6 +276,7 @@ contract DeployImplementationsOutput is BaseDeployIO { IOPContractsManager impl = IOPContractsManager(address(opcm())); require(address(impl.superchainConfig()) == address(_dii.superchainConfigProxy()), "OPCMI-10"); require(address(impl.protocolVersions()) == address(_dii.protocolVersionsProxy()), "OPCMI-20"); + require(impl.upgradeController() == _dii.upgradeController(), "OPCMI-30"); } function assertValidOptimismPortalImpl(DeployImplementationsInput) internal view { @@ -444,6 +452,7 @@ contract DeployImplementations is Script { { ISuperchainConfig superchainConfigProxy = _dii.superchainConfigProxy(); IProtocolVersions protocolVersionsProxy = _dii.protocolVersionsProxy(); + address upgradeController = _dii.upgradeController(); IOPContractsManager.Implementations memory implementations = IOPContractsManager.Implementations({ l1ERC721BridgeImpl: address(_dio.l1ERC721BridgeImpl()), @@ -465,7 +474,14 @@ contract DeployImplementations is Script { _args: DeployUtils.encodeConstructor( abi.encodeCall( IOPContractsManager.__constructor__, - (superchainConfigProxy, protocolVersionsProxy, _l1ContractsRelease, _blueprints, implementations) + ( + superchainConfigProxy, + protocolVersionsProxy, + _l1ContractsRelease, + _blueprints, + implementations, + upgradeController + ) ) ), _salt: _salt @@ -790,6 +806,7 @@ contract DeployImplementationsInterop is DeployImplementations { { ISuperchainConfig superchainConfigProxy = _dii.superchainConfigProxy(); IProtocolVersions protocolVersionsProxy = _dii.protocolVersionsProxy(); + address upgradeController = _dii.upgradeController(); IOPContractsManager.Implementations memory implementations = IOPContractsManager.Implementations({ l1ERC721BridgeImpl: address(_dio.l1ERC721BridgeImpl()), @@ -811,7 +828,14 @@ contract DeployImplementationsInterop is DeployImplementations { _args: DeployUtils.encodeConstructor( abi.encodeCall( IOPContractsManagerInterop.__constructor__, - (superchainConfigProxy, protocolVersionsProxy, _l1ContractsRelease, _blueprints, implementations) + ( + superchainConfigProxy, + protocolVersionsProxy, + _l1ContractsRelease, + _blueprints, + implementations, + upgradeController + ) ) ), _salt: _salt diff --git a/packages/contracts-bedrock/scripts/deploy/DeployOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployOPCM.s.sol index f08b79cf5799..0358fc9a459e 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployOPCM.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployOPCM.s.sol @@ -16,6 +16,7 @@ contract DeployOPCMInput is BaseDeployIO { ISuperchainConfig internal _superchainConfig; IProtocolVersions internal _protocolVersions; string internal _l1ContractsRelease; + address internal _upgradeController; address internal _addressManagerBlueprint; address internal _proxyBlueprint; @@ -42,6 +43,7 @@ contract DeployOPCMInput is BaseDeployIO { if (_sel == this.superchainConfig.selector) _superchainConfig = ISuperchainConfig(_addr); else if (_sel == this.protocolVersions.selector) _protocolVersions = IProtocolVersions(_addr); + else if (_sel == this.upgradeController.selector) _upgradeController = _addr; else if (_sel == this.addressManagerBlueprint.selector) _addressManagerBlueprint = _addr; else if (_sel == this.proxyBlueprint.selector) _proxyBlueprint = _addr; else if (_sel == this.proxyAdminBlueprint.selector) _proxyAdminBlueprint = _addr; @@ -85,6 +87,11 @@ contract DeployOPCMInput is BaseDeployIO { return _l1ContractsRelease; } + function upgradeController() public view returns (address) { + require(_upgradeController != address(0), "DeployOPCMInput: not set"); + return _upgradeController; + } + function addressManagerBlueprint() public view returns (address) { require(_addressManagerBlueprint != address(0), "DeployOPCMInput: not set"); return _addressManagerBlueprint; @@ -215,7 +222,12 @@ contract DeployOPCM is Script { }); IOPContractsManager opcm_ = deployOPCM( - _doi.superchainConfig(), _doi.protocolVersions(), blueprints, implementations, _doi.l1ContractsRelease() + _doi.superchainConfig(), + _doi.protocolVersions(), + blueprints, + implementations, + _doi.l1ContractsRelease(), + _doi.upgradeController() ); _doo.set(_doo.opcm.selector, address(opcm_)); @@ -227,7 +239,8 @@ contract DeployOPCM is Script { IProtocolVersions _protocolVersions, IOPContractsManager.Blueprints memory _blueprints, IOPContractsManager.Implementations memory _implementations, - string memory _l1ContractsRelease + string memory _l1ContractsRelease, + address _upgradeController ) public returns (IOPContractsManager opcm_) @@ -239,7 +252,14 @@ contract DeployOPCM is Script { _args: DeployUtils.encodeConstructor( abi.encodeCall( IOPContractsManager.__constructor__, - (_superchainConfig, _protocolVersions, _l1ContractsRelease, _blueprints, _implementations) + ( + _superchainConfig, + _protocolVersions, + _l1ContractsRelease, + _blueprints, + _implementations, + _upgradeController + ) ) ), _salt: DeployUtils.DEFAULT_SALT @@ -252,7 +272,9 @@ contract DeployOPCM is Script { IOPContractsManager impl = IOPContractsManager(address(_doo.opcm())); require(address(impl.superchainConfig()) == address(_doi.superchainConfig()), "OPCMI-10"); require(address(impl.protocolVersions()) == address(_doi.protocolVersions()), "OPCMI-20"); - require(LibString.eq(impl.l1ContractsRelease(), _doi.l1ContractsRelease()), "OPCMI-30"); + require(LibString.eq(impl.l1ContractsRelease(), string.concat(_doi.l1ContractsRelease(), "-rc")), "OPCMI-30"); + + require(impl.upgradeController() == _doi.upgradeController(), "OPCMI-40"); IOPContractsManager.Blueprints memory blueprints = impl.blueprints(); require(blueprints.addressManager == _doi.addressManagerBlueprint(), "OPCMI-40"); diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json index 59413335649e..684cc0bbb379 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json @@ -124,6 +124,11 @@ "internalType": "struct OPContractsManager.Implementations", "name": "_implementations", "type": "tuple" + }, + { + "internalType": "address", + "name": "_upgradeController", + "type": "address" } ], "stateMutability": "nonpayable", @@ -561,6 +566,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "isRC", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "l1ContractsRelease", @@ -587,6 +605,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_isRC", + "type": "bool" + } + ], + "name": "setRC", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "superchainConfig", @@ -625,6 +656,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "upgradeController", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", @@ -776,6 +820,11 @@ "name": "OnlyDelegatecall", "type": "error" }, + { + "inputs": [], + "name": "OnlyUpgradeController", + "type": "error" + }, { "inputs": [], "name": "ReservedBitsSet", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json index 59413335649e..684cc0bbb379 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json @@ -124,6 +124,11 @@ "internalType": "struct OPContractsManager.Implementations", "name": "_implementations", "type": "tuple" + }, + { + "internalType": "address", + "name": "_upgradeController", + "type": "address" } ], "stateMutability": "nonpayable", @@ -561,6 +566,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "isRC", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "l1ContractsRelease", @@ -587,6 +605,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_isRC", + "type": "bool" + } + ], + "name": "setRC", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "superchainConfig", @@ -625,6 +656,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "upgradeController", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", @@ -776,6 +820,11 @@ "name": "OnlyDelegatecall", "type": "error" }, + { + "inputs": [], + "name": "OnlyUpgradeController", + "type": "error" + }, { "inputs": [], "name": "ReservedBitsSet", diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 44a2812ac807..b8e7e10b2696 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -16,12 +16,12 @@ "sourceCodeHash": "0xc6613d35d1ad95cbef26a503a10b5dd8663ceb80426f8c528835d39f79e4b4cf" }, "src/L1/OPContractsManager.sol": { - "initCodeHash": "0xa277d25e1aae34f68e9e8a0814c34f6adada5248992dc5e62831ef0082f130fb", - "sourceCodeHash": "0xaa2c1ae210bf8c53b21e8fe3280bf17040118530eafd1f33e8ed64bc17ee4a17" + "initCodeHash": "0x9fc601820098fb3308ece91087c9209a4bff049f1f8d1e7c33a7a7a27cf5f96f", + "sourceCodeHash": "0xcfd0f82cb6481c22841747d9ed9022dc9e6b01a7e03edffb13587bfeae8eaee8" }, "src/L1/OPContractsManagerInterop.sol": { - "initCodeHash": "0x5cbfb22cb4ca0465a0bc8d87034763f9a103384ff60ac88af8a4c2ced7c96b7d", - "sourceCodeHash": "0x1bbe8f918f1d5b1cc75924cdf23b10664b890fd306c2aa5d195a7b4177670431" + "initCodeHash": "0xc7f77833e13a5aa7460df755c60d36068bc10868f9071b4350b139eaa391c071", + "sourceCodeHash": "0x5a9180a546a0c79baae5f2b42f5110bcae15d3ae8a3e05fa536eb8659774b733" }, "src/L1/OptimismPortal2.sol": { "initCodeHash": "0x969e3687d4497cc168af61e610ba0ae187e80f86aaa7b5d5bb598de19f279f08", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManager.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManager.json index 4cb41dcc3887..1a1d69deddf7 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManager.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManager.json @@ -1,7 +1,7 @@ [ { "bytes": "32", - "label": "l1ContractsRelease", + "label": "L1_CONTRACTS_RELEASE", "offset": 0, "slot": "0", "type": "string" @@ -19,5 +19,12 @@ "offset": 0, "slot": "10", "type": "struct OPContractsManager.Implementations" + }, + { + "bytes": "1", + "label": "isRC", + "offset": 0, + "slot": "20", + "type": "bool" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json index 4cb41dcc3887..1a1d69deddf7 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json @@ -1,7 +1,7 @@ [ { "bytes": "32", - "label": "l1ContractsRelease", + "label": "L1_CONTRACTS_RELEASE", "offset": 0, "slot": "0", "type": "string" @@ -19,5 +19,12 @@ "offset": 0, "slot": "10", "type": "struct OPContractsManager.Implementations" + }, + { + "bytes": "1", + "label": "isRC", + "offset": 0, + "slot": "20", + "type": "bool" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 9fddfa30724b..9d25744b0544 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -141,9 +141,9 @@ contract OPContractsManager is ISemver { // -------- Constants and Variables -------- - /// @custom:semver 1.0.0-beta.33 + /// @custom:semver 1.0.0-beta.34 function version() public pure virtual returns (string memory) { - return "1.0.0-beta.33"; + return "1.0.0-beta.34"; } /// @notice Address of the SuperchainConfig contract shared by all chains. @@ -154,7 +154,7 @@ contract OPContractsManager is ISemver { /// @notice L1 smart contracts release deployed by this version of OPCM. This is used in opcm to signal which /// version of the L1 smart contracts is deployed. It takes the format of `op-contracts/vX.Y.Z`. - string public l1ContractsRelease; + string internal L1_CONTRACTS_RELEASE; /// @notice Addresses of the Blueprint contracts. /// This is internal because if public the autogenerated getter method would return a tuple of @@ -168,6 +168,17 @@ contract OPContractsManager is ISemver { /// which is intended to be DELEGATECALLed. OPContractsManager internal immutable thisOPCM; + /// @notice The address of the upgrade controller. + address public immutable upgradeController; + + /// @notice Whether this is a release candidate. + bool public isRC = true; + + /// @notice Returns the release string. Appends "-rc" if this is a release candidate. + function l1ContractsRelease() external view returns (string memory) { + return isRC ? string.concat(L1_CONTRACTS_RELEASE, "-rc") : L1_CONTRACTS_RELEASE; + } + // -------- Events -------- /// @notice Emitted when a new OP Stack chain is deployed. @@ -183,6 +194,9 @@ contract OPContractsManager is ISemver { // -------- Errors -------- + /// @notice Thrown when an address other than the upgrade controller calls the setRC function. + error OnlyUpgradeController(); + /// @notice Thrown when an address is the zero address. error AddressNotFound(address who); @@ -220,17 +234,19 @@ contract OPContractsManager is ISemver { IProtocolVersions _protocolVersions, string memory _l1ContractsRelease, Blueprints memory _blueprints, - Implementations memory _implementations + Implementations memory _implementations, + address _upgradeController ) { assertValidContractAddress(address(_superchainConfig)); assertValidContractAddress(address(_protocolVersions)); superchainConfig = _superchainConfig; protocolVersions = _protocolVersions; - l1ContractsRelease = _l1ContractsRelease; + L1_CONTRACTS_RELEASE = _l1ContractsRelease; blueprint = _blueprints; implementation = _implementations; thisOPCM = this; + upgradeController = _upgradeController; } function deploy(DeployInput calldata _input) external returns (DeployOutput memory) { @@ -416,6 +432,13 @@ contract OPContractsManager is ISemver { function upgrade(OpChain[] memory _opChains) external { if (address(this) == address(thisOPCM)) revert OnlyDelegatecall(); + // If this is delegatecalled by the upgrade controller, set isRC to false first, else, continue execution. + if (address(this) == upgradeController) { + // Set isRC to false. + // This function asserts that the caller is the upgrade controller. + thisOPCM.setRC(false); + } + Implementations memory impls = thisOPCM.implementations(); // TODO: upgrading the SuperchainConfig and ProtocolVersions (in a new function) @@ -874,4 +897,10 @@ contract OPContractsManager is ISemver { { return _disputeGameFactory.gameImpls(_gameType); } + + /// @notice Sets the RC flag. + function setRC(bool _isRC) external { + if (msg.sender != upgradeController) revert OnlyUpgradeController(); + isRC = _isRC; + } } diff --git a/packages/contracts-bedrock/src/L1/OPContractsManagerInterop.sol b/packages/contracts-bedrock/src/L1/OPContractsManagerInterop.sol index 3b23e5026b4b..9f91603f93c9 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManagerInterop.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManagerInterop.sol @@ -12,9 +12,9 @@ import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { ISystemConfigInterop } from "interfaces/L1/ISystemConfigInterop.sol"; contract OPContractsManagerInterop is OPContractsManager { - /// @custom:semver +interop-beta.1 + /// @custom:semver +interop-beta.2 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop-beta.1"); + return string.concat(super.version(), "+interop-beta.2"); } constructor( @@ -22,9 +22,17 @@ contract OPContractsManagerInterop is OPContractsManager { IProtocolVersions _protocolVersions, string memory _l1ContractsRelease, Blueprints memory _blueprints, - Implementations memory _implementations + Implementations memory _implementations, + address _upgradeController ) - OPContractsManager(_superchainConfig, _protocolVersions, _l1ContractsRelease, _blueprints, _implementations) + OPContractsManager( + _superchainConfig, + _protocolVersions, + _l1ContractsRelease, + _blueprints, + _implementations, + _upgradeController + ) { } // The `SystemConfigInterop` contract has an extra `address _dependencyManager` argument diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index eb585da2631d..619968532b69 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -15,6 +15,7 @@ import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { Blueprint } from "src/libraries/Blueprint.sol"; import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; +import { Bytes } from "src/libraries/Bytes.sol"; // Interfaces import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; @@ -53,9 +54,17 @@ contract OPContractsManager_Harness is OPContractsManager { IProtocolVersions _protocolVersions, string memory _l1ContractsRelease, Blueprints memory _blueprints, - Implementations memory _implementations + Implementations memory _implementations, + address _upgradeController ) - OPContractsManager(_superchainConfig, _protocolVersions, _l1ContractsRelease, _blueprints, _implementations) + OPContractsManager( + _superchainConfig, + _protocolVersions, + _l1ContractsRelease, + _blueprints, + _implementations, + _upgradeController + ) { } function chainIdToBatchInboxAddress_exposed(uint256 l2ChainId) public pure returns (address) { @@ -157,6 +166,7 @@ contract OPContractsManager_InternalMethods_Test is Test { function setUp() public { ISuperchainConfig superchainConfigProxy = ISuperchainConfig(makeAddr("superchainConfig")); IProtocolVersions protocolVersionsProxy = IProtocolVersions(makeAddr("protocolVersions")); + address upgradeController = makeAddr("upgradeController"); OPContractsManager.Blueprints memory emptyBlueprints; OPContractsManager.Implementations memory emptyImpls; vm.etch(address(superchainConfigProxy), hex"01"); @@ -167,7 +177,8 @@ contract OPContractsManager_InternalMethods_Test is Test { _protocolVersions: protocolVersionsProxy, _l1ContractsRelease: "dev", _blueprints: emptyBlueprints, - _implementations: emptyImpls + _implementations: emptyImpls, + _upgradeController: upgradeController }); } @@ -229,8 +240,12 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { } contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { - function test_upgrade_succeeds() public { - vm.etch(upgrader, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + function runUpgradeTestAndChecks(address delegateCaller) public { + assertTrue(opcm.isRC(), "isRC should be true"); + bytes memory releaseBytes = bytes(opcm.l1ContractsRelease()); + assertEq(Bytes.slice(releaseBytes, releaseBytes.length - 3, 3), "-rc", "release should end with '-rc'"); + + vm.etch(delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); IOPContractsManager.Implementations memory impls = opcm.implementations(); address oldL1CrossDomainMessenger = addressManager.getAddress("OVM_L1CrossDomainMessenger"); @@ -248,10 +263,18 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { if (address(delayedWeth) != address(0)) { expectEmitUpgraded(impls.delayedWETHImpl, address(delayedWeth)); } - vm.expectEmit(true, true, true, true, address(upgrader)); - emit Upgraded(l2ChainId, opChains[0].systemConfigProxy, address(upgrader)); - DelegateCaller(upgrader).dcForward(address(opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChains))); - + vm.expectEmit(true, true, true, true, address(delegateCaller)); + emit Upgraded(l2ChainId, opChains[0].systemConfigProxy, address(delegateCaller)); + DelegateCaller(delegateCaller).dcForward(address(opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChains))); + vm.stopPrank(); + + if (delegateCaller == upgrader) { + assertFalse(opcm.isRC(), "isRC should be false"); + releaseBytes = bytes(opcm.l1ContractsRelease()); + assertNotEq( + Bytes.slice(releaseBytes, releaseBytes.length - 3, 3), "-rc", "release should not end with '-rc'" + ); + } assertEq(impls.systemConfigImpl, EIP1967Helper.getImplementation(address(systemConfig))); assertEq(impls.l1ERC721BridgeImpl, EIP1967Helper.getImplementation(address(l1ERC721Bridge))); assertEq(impls.disputeGameFactoryImpl, EIP1967Helper.getImplementation(address(disputeGameFactory))); @@ -272,6 +295,32 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { // TODO: ensure dispute games are updated (upcoming PR) } + function test_upgrade_succeeds() public { + // Run the upgrade test and checks + runUpgradeTestAndChecks(upgrader); + } + + function test_upgrade_nonUpgradeControllerDelegatecallerShouldNotSetIsRCToFalse_works(address _nonUpgradeController) + public + { + if ( + _nonUpgradeController == upgrader || _nonUpgradeController == address(0) + || _nonUpgradeController < address(0x4200000000000000000000000000000000000000) + || _nonUpgradeController > address(0x4200000000000000000000000000000000000800) + || _nonUpgradeController == address(vm) + || _nonUpgradeController == 0x000000000000000000636F6e736F6c652e6c6f67 + || _nonUpgradeController == 0x4e59b44847b379578588920cA78FbF26c0B4956C + ) { + _nonUpgradeController = makeAddr("nonUpgradeController"); + } + + // Set the proxy admin owner to be the non-upgrade controller + vm.store(address(proxyAdmin), bytes32(0), bytes32(uint256(uint160(_nonUpgradeController)))); + + // Run the upgrade test and checks + runUpgradeTestAndChecks(_nonUpgradeController); + } + function expectEmitUpgraded(address impl, address proxy) public { vm.expectEmit(proxy); emit Upgraded(impl); @@ -286,6 +335,7 @@ contract OPContractsManager_Upgrade_TestFails is OPContractsManager_Upgrade_Harn } function test_upgrade_superchainConfigMismatch_reverts() public { + upgrader = proxyAdmin.owner(); vm.etch(upgrader, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); // Set the superchainConfig to a different address in the OptimismPortal2 contract. vm.store( @@ -301,6 +351,43 @@ contract OPContractsManager_Upgrade_TestFails is OPContractsManager_Upgrade_Harn } } +contract OPContractsManager_SetRC_Test is OPContractsManager_Upgrade_Harness { + /// @notice Tests the setRC function can be set by the upgrade controller. + function test_setRC_succeeds(bool _isRC) public { + vm.prank(upgrader); + + opcm.setRC(_isRC); + assertTrue(opcm.isRC() == _isRC, "isRC should be true"); + bytes memory releaseBytes = bytes(opcm.l1ContractsRelease()); + if (_isRC) { + assertEq(Bytes.slice(releaseBytes, releaseBytes.length - 3, 3), "-rc", "release should end with '-rc'"); + } else { + assertNotEq( + Bytes.slice(releaseBytes, releaseBytes.length - 3, 3), "-rc", "release should not end with '-rc'" + ); + } + } + + /// @notice Tests the setRC function can not be set by non-upgrade controller. + function test_setRC_nonUpgradeController_reverts(address _nonUpgradeController) public { + if ( + _nonUpgradeController == upgrader || _nonUpgradeController == address(0) + || _nonUpgradeController < address(0x4200000000000000000000000000000000000000) + || _nonUpgradeController > address(0x4200000000000000000000000000000000000800) + || _nonUpgradeController == address(vm) + || _nonUpgradeController == 0x000000000000000000636F6e736F6c652e6c6f67 + || _nonUpgradeController == 0x4e59b44847b379578588920cA78FbF26c0B4956C + ) { + _nonUpgradeController = makeAddr("nonUpgradeController"); + } + + vm.prank(_nonUpgradeController); + + vm.expectRevert(IOPContractsManager.OnlyUpgradeController.selector); + opcm.setRC(true); + } +} + contract OPContractsManager_AddGameType_Test is Test { IOPContractsManager internal opcm; @@ -345,7 +432,7 @@ contract OPContractsManager_AddGameType_Test is Test { _args: DeployUtils.encodeConstructor( abi.encodeCall( IOPContractsManager.__constructor__, - (superchainConfigProxy, protocolVersionsProxy, "dev", blueprints, impls) + (superchainConfigProxy, protocolVersionsProxy, "dev", blueprints, impls, address(this)) ) ), _salt: DeployUtils.DEFAULT_SALT diff --git a/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol b/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol index fb7bc1610279..02eeb3f60909 100644 --- a/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol @@ -69,6 +69,9 @@ contract DeployImplementationsInput_Test is Test { vm.expectRevert("DeployImplementationsInput: not set"); dii.protocolVersionsProxy(); + + vm.expectRevert("DeployImplementationsInput: not set"); + dii.upgradeController(); } } @@ -228,6 +231,7 @@ contract DeployImplementations_Test is Test { uint256 disputeGameFinalityDelaySeconds = 500; ISuperchainConfig superchainConfigProxy = ISuperchainConfig(makeAddr("superchainConfigProxy")); IProtocolVersions protocolVersionsProxy = IProtocolVersions(makeAddr("protocolVersionsProxy")); + address upgradeController = makeAddr("upgradeController"); function setUp() public virtual { vm.etch(address(superchainConfigProxy), hex"01"); @@ -263,6 +267,7 @@ contract DeployImplementations_Test is Test { dii.set(dii.mipsVersion.selector, 1); dii.set(dii.superchainConfigProxy.selector, address(superchainConfigProxy)); dii.set(dii.protocolVersionsProxy.selector, address(protocolVersionsProxy)); + dii.set(dii.upgradeController.selector, upgradeController); // Perform the initial deployment. deployImplementations.deploySystemConfigImpl(dio); @@ -363,6 +368,7 @@ contract DeployImplementations_Test is Test { dii.set(dii.l1ContractsRelease.selector, release); dii.set(dii.superchainConfigProxy.selector, address(superchainConfigProxy)); dii.set(dii.protocolVersionsProxy.selector, address(protocolVersionsProxy)); + dii.set(dii.upgradeController.selector, upgradeController); deployImplementations.run(dii, dio); @@ -376,6 +382,7 @@ contract DeployImplementations_Test is Test { assertEq(release, dii.l1ContractsRelease(), "525"); assertEq(address(superchainConfigProxy), address(dii.superchainConfigProxy()), "550"); assertEq(address(protocolVersionsProxy), address(dii.protocolVersionsProxy()), "575"); + assertEq(upgradeController, dii.upgradeController(), "600"); // Architecture assertions. assertEq(address(dio.mipsSingleton().oracle()), address(dio.preimageOracleSingleton()), "600"); diff --git a/packages/contracts-bedrock/test/opcm/DeployOPCM.t.sol b/packages/contracts-bedrock/test/opcm/DeployOPCM.t.sol index 7449d316fb3f..6ffc38184d2c 100644 --- a/packages/contracts-bedrock/test/opcm/DeployOPCM.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeployOPCM.t.sol @@ -82,6 +82,7 @@ contract DeployOPCMInput_Test is Test { function test_set_part1_succeeds() public { ISuperchainConfig superchainConfig = ISuperchainConfig(makeAddr("superchainConfig")); IProtocolVersions protocolVersions = IProtocolVersions(makeAddr("protocolVersions")); + address upgradeController = makeAddr("upgradeController"); address addressManagerBlueprint = makeAddr("addressManagerBlueprint"); address proxyBlueprint = makeAddr("proxyBlueprint"); address proxyAdminBlueprint = makeAddr("proxyAdminBlueprint"); @@ -93,6 +94,7 @@ contract DeployOPCMInput_Test is Test { dii.set(dii.superchainConfig.selector, address(superchainConfig)); dii.set(dii.protocolVersions.selector, address(protocolVersions)); dii.set(dii.l1ContractsRelease.selector, release); + dii.set(dii.upgradeController.selector, upgradeController); dii.set(dii.addressManagerBlueprint.selector, addressManagerBlueprint); dii.set(dii.proxyBlueprint.selector, proxyBlueprint); dii.set(dii.proxyAdminBlueprint.selector, proxyAdminBlueprint); @@ -111,6 +113,7 @@ contract DeployOPCMInput_Test is Test { assertEq(dii.resolvedDelegateProxyBlueprint(), resolvedDelegateProxyBlueprint, "400"); assertEq(dii.permissionedDisputeGame1Blueprint(), permissionedDisputeGame1Blueprint, "500"); assertEq(dii.permissionedDisputeGame2Blueprint(), permissionedDisputeGame2Blueprint, "550"); + assertEq(dii.upgradeController(), upgradeController, "600"); } function test_set_part2_succeeds() public { @@ -207,6 +210,7 @@ contract DeployOPCMTest is Test { ISuperchainConfig superchainConfigProxy = ISuperchainConfig(makeAddr("superchainConfigProxy")); IProtocolVersions protocolVersionsProxy = IProtocolVersions(makeAddr("protocolVersionsProxy")); + address upgradeController = makeAddr("upgradeController"); function setUp() public virtual { deployOPCM = new DeployOPCM(); @@ -217,6 +221,7 @@ contract DeployOPCMTest is Test { doi.set(doi.superchainConfig.selector, address(superchainConfigProxy)); doi.set(doi.protocolVersions.selector, address(protocolVersionsProxy)); doi.set(doi.l1ContractsRelease.selector, "1.0.0"); + doi.set(doi.upgradeController.selector, upgradeController); // Set and etch blueprints doi.set(doi.addressManagerBlueprint.selector, makeAddr("addressManagerBlueprint")); @@ -242,6 +247,7 @@ contract DeployOPCMTest is Test { // Etch all addresses with dummy bytecode vm.etch(address(doi.superchainConfig()), hex"01"); vm.etch(address(doi.protocolVersions()), hex"01"); + vm.etch(address(doi.upgradeController()), hex"01"); vm.etch(doi.addressManagerBlueprint(), hex"01"); vm.etch(doi.proxyBlueprint(), hex"01"); diff --git a/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol b/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol index 3d391b59e186..12085975f557 100644 --- a/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol @@ -315,7 +315,7 @@ contract DeployOPChain_TestBase is Test { string release = "dev-release"; // this means implementation contracts will be deployed ISuperchainConfig superchainConfigProxy; IProtocolVersions protocolVersionsProxy; - + address upgradeController; // Define default inputs for DeployOPChain. // `opcm` is set during `setUp` since it is an output of the previous step. address opChainProxyAdminOwner = makeAddr("defaultOPChainProxyAdminOwner"); @@ -356,6 +356,7 @@ contract DeployOPChain_TestBase is Test { // Populate the inputs for DeployImplementations based on the output of DeploySuperchain. superchainConfigProxy = dso.superchainConfigProxy(); protocolVersionsProxy = dso.protocolVersionsProxy(); + upgradeController = dso.superchainProxyAdmin().owner(); // Configure and deploy Implementation contracts DeployImplementations deployImplementations = createDeployImplementationsContract(); @@ -370,6 +371,7 @@ contract DeployOPChain_TestBase is Test { dii.set(dii.l1ContractsRelease.selector, release); dii.set(dii.superchainConfigProxy.selector, address(superchainConfigProxy)); dii.set(dii.protocolVersionsProxy.selector, address(protocolVersionsProxy)); + dii.set(dii.upgradeController.selector, upgradeController); deployImplementations.run(dii, dio); diff --git a/packages/contracts-bedrock/test/universal/Specs.t.sol b/packages/contracts-bedrock/test/universal/Specs.t.sol index c56bc756f520..878e7ecb2297 100644 --- a/packages/contracts-bedrock/test/universal/Specs.t.sol +++ b/packages/contracts-bedrock/test/universal/Specs.t.sol @@ -785,6 +785,9 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "OPContractsManager", _sel: IOPContractsManager.implementations.selector }); _addSpec({ _name: "OPContractsManager", _sel: IOPContractsManager.upgrade.selector }); _addSpec({ _name: "OPContractsManager", _sel: IOPContractsManager.addGameType.selector }); + _addSpec({ _name: "OPContractsManager", _sel: _getSel("isRC()") }); + _addSpec({ _name: "OPContractsManager", _sel: _getSel("setRC(bool)") }); + _addSpec({ _name: "OPContractsManager", _sel: _getSel("upgradeController()") }); // OPContractsManagerInterop _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("version()") }); @@ -797,6 +800,9 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "OPContractsManagerInterop", _sel: IOPContractsManager.implementations.selector }); _addSpec({ _name: "OPContractsManagerInterop", _sel: IOPContractsManager.upgrade.selector }); _addSpec({ _name: "OPContractsManagerInterop", _sel: IOPContractsManager.addGameType.selector }); + _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("isRC()") }); + _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("setRC(bool)") }); + _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("upgradeController()") }); // DeputyGuardianModule _addSpec({