Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OPCM Upgrade implementations only #13574

Open
wants to merge 8 commits into
base: opcm-up/add-deployImpls-to-fork-live
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions packages/contracts-bedrock/scripts/Artifacts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ abstract contract Artifacts {
string memory addresses = Config.contractAddressesPath();
if (bytes(addresses).length > 0) {
console.log("Loading addresses from %s", addresses);
_loadAddresses(addresses);
loadAddresses(addresses);
}
}

/// @notice Populates the addresses to be used in a script based on a JSON file.
/// The format of the JSON file is the same that it output by this script
/// as well as the JSON files that contain addresses in the `superchain-registry`
/// repo. The JSON key is the name of the contract and the value is an address.
function _loadAddresses(string memory _path) internal {
function loadAddresses(string memory _path) public {
string memory json = Process.bash(string.concat("jq -cr < ", _path));
string[] memory keys = vm.parseJsonKeys(json, "");
for (uint256 i; i < keys.length; i++) {
Expand Down Expand Up @@ -183,6 +183,10 @@ abstract contract Artifacts {
revert InvalidDeployment("EmptyName");
}
if (bytes(_namedDeployments[_name].name).length > 0) {
// If the deployment already exists, and the address is the same, do nothing.
if (_namedDeployments[_name].addr == _deployed) {
return;
}
revert InvalidDeployment("AlreadyExists");
}

Expand Down
4 changes: 4 additions & 0 deletions packages/contracts-bedrock/scripts/deploy/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,10 @@ contract Deploy is Deployer {
require(_isInterop == cfg.useInterop(), "Deploy: Interop setting mismatch.");

console.log("Deploying implementations");
if (bytes(_suffix).length > 0) {
console.log("saving with suffix: ", _suffix);
}

DeployImplementations di = new DeployImplementations();
(DeployImplementationsInput dii, DeployImplementationsOutput dio) = di.etchIOContracts();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,24 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ISystemConfig[]",
"name": "_systemConfigs",
"type": "address[]"
},
{
"internalType": "contract IProxyAdmin[]",
"name": "_proxyAdmins",
"type": "address[]"
}
],
"name": "upgrade",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "version",
Expand Down Expand Up @@ -544,6 +562,31 @@
"name": "Deployed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "l2ChainId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "contract ISystemConfig",
"name": "systemConfig",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "upgrader",
"type": "address"
}
],
"name": "Upgraded",
"type": "event"
},
{
"inputs": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,24 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ISystemConfig[]",
"name": "_systemConfigs",
"type": "address[]"
},
{
"internalType": "contract IProxyAdmin[]",
"name": "_proxyAdmins",
"type": "address[]"
}
],
"name": "upgrade",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "version",
Expand Down Expand Up @@ -544,6 +562,31 @@
"name": "Deployed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "l2ChainId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "contract ISystemConfig",
"name": "systemConfig",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "upgrader",
"type": "address"
}
],
"name": "Upgraded",
"type": "event"
},
{
"inputs": [
{
Expand Down
4 changes: 2 additions & 2 deletions packages/contracts-bedrock/snapshots/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
"sourceCodeHash": "0xa91b445bdc666a02ba18e3b91ba94b6d54bbe65da714002fc734814201319d57"
},
"src/L1/OPContractsManager.sol": {
"initCodeHash": "0x4b413cbe79bd10d41d8f3e9f0408e773dd49ced823d457b9f9aa92f446828105",
"sourceCodeHash": "0xe5179a20ae40d4e4773c52df98bac67e73e04044bec9e8750073b4e2f14fe81b"
"initCodeHash": "0x5456d00538d2feef7346ee55d2f8fb92e3ae11b575b99a2b5c543a3f2d4b76da",
"sourceCodeHash": "0x35ac52d11a87d1da31d6d0edff54bd0cfcb0f6c97d8aeac17400642f960a5681"
},
"src/L1/OptimismPortal2.sol": {
"initCodeHash": "0xfd14fd690752519064d6de6c3e15d69ec9146bc8714e56ac286305773dbb1533",
Expand Down
47 changes: 45 additions & 2 deletions packages/contracts-bedrock/src/L1/OPContractsManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ contract OPContractsManager is ISemver {

// -------- Constants and Variables --------

/// @custom:semver 1.0.0-beta.27
string public constant version = "1.0.0-beta.27";
/// @custom:semver 1.0.0-beta.28
string public constant version = "1.0.0-beta.28";

/// @notice Represents the interface version so consumers know how to decode the DeployOutput struct
/// that's emitted in the `Deployed` event. Whenever that struct changes, a new version should be used.
Expand All @@ -139,6 +139,10 @@ contract OPContractsManager is ISemver {
/// @notice Addresses of the latest implementation contracts.
Implementations internal implementation;

/// @notice The OPContractsManager contract that is currently being used. This is needed in the upgrade function
/// which is intended to be DELEGATECALLed.
OPContractsManager internal immutable thisOPCM;

// -------- Events --------

/// @notice Emitted when a new OP Stack chain is deployed.
Expand All @@ -150,6 +154,12 @@ contract OPContractsManager is ISemver {
uint256 indexed outputVersion, uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput
);

/// @notice Emitted when a chain is upgraded
/// @param l2ChainId Chain ID of the upgraded chain
/// @param systemConfig Address of the chain's SystemConfig contract
/// @param upgrader Address that initiated the upgrade
event Upgraded(uint256 indexed l2ChainId, ISystemConfig indexed systemConfig, address indexed upgrader);

// -------- Errors --------

/// @notice Thrown when an address is the zero address.
Expand Down Expand Up @@ -190,6 +200,7 @@ contract OPContractsManager is ISemver {

blueprint = _blueprints;
implementation = _implementations;
thisOPCM = this;
}

function deploy(DeployInput calldata _input) external returns (DeployOutput memory) {
Expand Down Expand Up @@ -363,6 +374,38 @@ contract OPContractsManager is ISemver {
return output;
}

/// @notice Upgrades a set of chains to the latest implementation contracts
/// @param _systemConfigs Array of SystemConfig contracts, one per chain to upgrade
/// @param _proxyAdmins Array of ProxyAdmin contracts, one per chain to upgrade
/// @dev This function is intended to be called via DELEGATECALL from the Upgrade Controller Safe
function upgrade(ISystemConfig[] calldata _systemConfigs, IProxyAdmin[] calldata _proxyAdmins) external {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we are not emitting (or testing for) the Upgraded event

Implementations memory impls = thisOPCM.implementations();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can enforce a delegatecallhere

Suggested change
Implementations memory impls = thisOPCM.implementations();
if (address(this) == address(thisOPCM)) revert OnlyDelegatecall();
Implementations memory impls = thisOPCM.implementations();


for (uint256 i = 0; i < _systemConfigs.length; i++) {
ISystemConfig systemConfig = _systemConfigs[i];
ISystemConfig.Addresses memory opChainAddrs = ISystemConfig.Addresses({
l1CrossDomainMessenger: systemConfig.l1CrossDomainMessenger(),
l1ERC721Bridge: systemConfig.l1ERC721Bridge(),
l1StandardBridge: systemConfig.l1StandardBridge(),
disputeGameFactory: systemConfig.disputeGameFactory(),
optimismPortal: systemConfig.optimismPortal(),
optimismMintableERC20Factory: systemConfig.optimismMintableERC20Factory(),
gasPayingToken: address(0)
});

IProxyAdmin proxyAdmin = _proxyAdmins[i];
proxyAdmin.upgrade(payable(address(systemConfig)), impls.systemConfigImpl);
proxyAdmin.upgrade(payable(opChainAddrs.l1CrossDomainMessenger), impls.l1CrossDomainMessengerImpl);
proxyAdmin.upgrade(payable(opChainAddrs.l1ERC721Bridge), impls.l1ERC721BridgeImpl);
proxyAdmin.upgrade(payable(opChainAddrs.l1StandardBridge), impls.l1StandardBridgeImpl);
proxyAdmin.upgrade(payable(opChainAddrs.disputeGameFactory), impls.disputeGameFactoryImpl);
proxyAdmin.upgrade(payable(opChainAddrs.optimismPortal), impls.optimismPortalImpl);
proxyAdmin.upgrade(
payable(opChainAddrs.optimismMintableERC20Factory), impls.optimismMintableERC20FactoryImpl
);
}
}

// -------- Utilities --------

/// @notice Verifies that all inputs are valid and reverts if any are invalid.
Expand Down
36 changes: 35 additions & 1 deletion packages/contracts-bedrock/test/L1/OPContractsManager.t.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

// Testing
import { Test, stdStorage, StdStorage } from "forge-std/Test.sol";
import { CommonTest } from "test/setup/CommonTest.sol";
import { DeployOPChain_TestBase } from "test/opcm/DeployOPChain.t.sol";

// Scripts
import { DeployOPChainInput } from "scripts/deploy/DeployOPChain.s.sol";
import { DeployOPChain_TestBase } from "test/opcm/DeployOPChain.t.sol";

// Libraries
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";

// Contracts
import { OPContractsManager } from "src/L1/OPContractsManager.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol";
Expand Down Expand Up @@ -148,3 +155,30 @@ contract OPContractsManager_InternalMethods_Test is Test {
vm.assertEq(expected, actual);
}
}

contract OPContractsManager_Upgrade_Test is CommonTest {
function setUp() public override {
super.setUp();
if (!isForkTest()) {
// This test is only supported in forked tests, as we are testing the upgrade.
vm.skip(true);
}
}

function test_upgrade_succeeds() public view {
// TODO: make a state var for opcm in CommonTest
OPContractsManager opcm = OPContractsManager(deploy.mustGetAddress("OPContractsManager_NextVersion"));
OPContractsManager.Implementations memory impls = opcm.implementations();
assertEq(impls.systemConfigImpl, EIP1967Helper.getImplementation(address(systemConfig)));
assertEq(impls.l1ERC721BridgeImpl, EIP1967Helper.getImplementation(address(l1ERC721Bridge)));
assertEq(impls.disputeGameFactoryImpl, EIP1967Helper.getImplementation(address(disputeGameFactory)));
assertEq(impls.optimismPortalImpl, EIP1967Helper.getImplementation(address(optimismPortal2)));
assertEq(
impls.optimismMintableERC20FactoryImpl,
EIP1967Helper.getImplementation(address(l1OptimismMintableERC20Factory))
);
assertEq(impls.l1StandardBridgeImpl, EIP1967Helper.getImplementation(address(l1StandardBridge)));

assertEq(impls.l1CrossDomainMessengerImpl, addressManager.getAddress("OVM_L1CrossDomainMessenger"));
}
}
7 changes: 7 additions & 0 deletions packages/contracts-bedrock/test/mocks/Callers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,10 @@ contract Reverter {
revert();
}
}

contract DelegateCaller {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add natspec explaining why we have this contract?

function dcForward(address _target, bytes calldata _data) external {
(bool success,) = _target.delegatecall(_data);
require(success, "DelegateCaller: Delegatecall failed");
}
}
39 changes: 38 additions & 1 deletion packages/contracts-bedrock/test/setup/ForkLive.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.0;

// Testing
import { stdJson } from "forge-std/StdJson.sol";
import { DelegateCaller } from "test/mocks/Callers.sol";

// Scripts
import { Deployer } from "scripts/deploy/Deployer.sol";
Expand All @@ -16,6 +17,9 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol";
import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol";
import { IAddressManager } from "interfaces/legacy/IAddressManager.sol";
import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol";
import { OPContractsManager } from "src/L1/OPContractsManager.sol";

/// @title ForkLive
/// @notice This script is called by Setup.sol as a preparation step for the foundry test suite, and is run as an
Expand Down Expand Up @@ -96,20 +100,53 @@ contract ForkLive is Deployer {

// Now deploy the updated OPCM and implementations of the contracts
_deployNewImplementations();

// Now upgrade the contracts
_upgrade();
}

/// @notice Etches a new Deploy.s.sol contract at a deterministic address, sets up the environment,
/// and deploys new implementations with a "_NextVersion" suffix. This suffix is necessary to avoid
/// naming collisions with the implementations saved above.
function _deployNewImplementations() internal {
// Etch a copy of the Deploy.s.sol contract so that we can use its deployImplementations() function
Deploy deployNew = Deploy(address(uint160(uint256(keccak256(abi.encode("optimism.deploy.new"))))));
vm.etch(address(deployNew), vm.getDeployedCode("Deploy.s.sol:Deploy"));
vm.label(address(deployNew), "DeployNew");
vm.allowCheatcodes(address(deployNew));
vm.setEnv("CONTRACT_ADDRESSES_PATH", string.concat(vm.projectRoot(), "/deployments/1-deploy.json"));

// Set the CONTRACT_ADDRESSES_PATH environment variable
// This is used in Deploy.setUp() to read and save the addresses it needs (SuperchainConfigProxy,
// ProtocolVersionsProxy)
string memory jsonPath = string.concat(vm.projectRoot(), "/deployments/1-deploy.json");
vm.setEnv("CONTRACT_ADDRESSES_PATH", jsonPath);
deployNew.setUp();
deployNew.deployImplementations({ _isInterop: false, _suffix: "_NextVersion" });

// deployNew.deployImplementations() saves the addresses to its own storage as well as to the json file at
// CONTRACT_ADDRESSES_PATH. In order to use the addresses in the json file, we now need to load them into
// local storage.
loadAddresses(jsonPath);
}

function _upgrade() internal {
OPContractsManager opcm = OPContractsManager(mustGetAddress("OPContractsManager_NextVersion"));

ISystemConfig systemConfig = ISystemConfig(mustGetAddress("SystemConfigProxy"));
IProxyAdmin proxyAdmin = IProxyAdmin(EIP1967Helper.getAdmin(address(systemConfig)));

address upgrader = proxyAdmin.owner();
vm.label(upgrader, "ProxyAdmin Owner");

ISystemConfig[] memory systemConfigs = new ISystemConfig[](1);
systemConfigs[0] = systemConfig;
IProxyAdmin[] memory proxyAdmins = new IProxyAdmin[](1);
proxyAdmins[0] = proxyAdmin;
Comment on lines +141 to +144
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding helpers for this to our vendored Solarray.sol, or if you want to go further we can fork the original repo since improved array UX for these would be useful in superchain-ops too. Can of course be tracked in a separate issue


vm.etch(upgrader, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller"));
DelegateCaller(upgrader).dcForward(
address(opcm), abi.encodeCall(OPContractsManager.upgrade, (systemConfigs, proxyAdmins))
);
}

/// @notice Saves the proxy and implementation addresses for a contract name
Expand Down
2 changes: 2 additions & 0 deletions packages/contracts-bedrock/test/universal/Specs.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,7 @@ contract Specification_Test is CommonTest {
_addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.blueprints.selector });
_addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.chainIdToBatchInboxAddress.selector });
_addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.implementations.selector });
_addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.upgrade.selector });

// OPContractsManagerInterop
_addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("version()") });
Expand All @@ -792,6 +793,7 @@ contract Specification_Test is CommonTest {
_addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.blueprints.selector });
_addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.chainIdToBatchInboxAddress.selector });
_addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.implementations.selector });
_addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.upgrade.selector });

// DeputyGuardianModule
_addSpec({
Expand Down