Skip to content

Commit e9e203a

Browse files
authored
Add deployImplementations to ForkLive flow (#13573)
* Add deployImplementations to ForkLive flow * feat: remove legacy code * feat: Add comments * fix comment * feat: Compatibility with standalone Artifacts * feat: use etchLabelAndAllowCheatcodes * feat: more readable ForkLive * refactor deploy and forklive flow * fix checks * feat: Remove the _suffix arg from deployImpls * feat: Allow overwriting a saved deployment (logs a warning) * feat: update comment * feat: Save the impls we need to save * fix comment * feat: replace mustGetAddress(Impl) with EIP1967 helper * fix: impl retrieval in l1xDM * fix: impl retrieval in l1xDM * feat: restore import * lint * delete outdated comment
1 parent 7964731 commit e9e203a

File tree

10 files changed

+106
-66
lines changed

10 files changed

+106
-66
lines changed

packages/contracts-bedrock/scripts/Artifacts.s.sol

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,10 @@ contract Artifacts {
158158
if (bytes(_name).length == 0) {
159159
revert InvalidDeployment("EmptyName");
160160
}
161-
if (bytes(_namedDeployments[_name].name).length > 0) {
162-
revert InvalidDeployment("AlreadyExists");
161+
Deployment memory existing = _namedDeployments[_name];
162+
if (bytes(existing.name).length > 0) {
163+
console.log("Warning: Deployment already exists for %s.", _name);
164+
console.log("Overwriting %s with %s", existing.addr, _deployed);
163165
}
164166

165167
Deployment memory deployment = Deployment({ name: _name, addr: payable(_deployed) });

packages/contracts-bedrock/scripts/deploy/Deploy.s.sol

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -275,10 +275,12 @@ contract Deploy is Deployer {
275275
}
276276

277277
/// @notice Deploy all of the implementations
278+
/// @param _isInterop Whether to use interop
278279
function deployImplementations(bool _isInterop) public {
279280
require(_isInterop == cfg.useInterop(), "Deploy: Interop setting mismatch.");
280281

281282
console.log("Deploying implementations");
283+
282284
DeployImplementations di = new DeployImplementations();
283285
(DeployImplementationsInput dii, DeployImplementationsOutput dio) = di.etchIOContracts();
284286

@@ -302,21 +304,30 @@ contract Deploy is Deployer {
302304
}
303305
di.run(dii, dio);
304306

305-
artifacts.save("L1CrossDomainMessengerImpl", address(dio.l1CrossDomainMessengerImpl()));
306-
artifacts.save("OptimismMintableERC20FactoryImpl", address(dio.optimismMintableERC20FactoryImpl()));
307-
artifacts.save("SystemConfigImpl", address(dio.systemConfigImpl()));
308-
artifacts.save("L1StandardBridgeImpl", address(dio.l1StandardBridgeImpl()));
309-
artifacts.save("L1ERC721BridgeImpl", address(dio.l1ERC721BridgeImpl()));
310-
311-
// Fault proofs
312-
artifacts.save("OptimismPortal2Impl", address(dio.optimismPortalImpl()));
313-
artifacts.save("DisputeGameFactoryImpl", address(dio.disputeGameFactoryImpl()));
314-
artifacts.save("DelayedWETHImpl", address(dio.delayedWETHImpl()));
315-
artifacts.save("PreimageOracleSingleton", address(dio.preimageOracleSingleton()));
307+
// Save the implementation addresses which are needed outside of this function or script.
308+
// When called in a fork test, this will overwrite the existing implementations.
316309
artifacts.save("MipsSingleton", address(dio.mipsSingleton()));
317310
artifacts.save("OPContractsManager", address(dio.opcm()));
311+
artifacts.save("DelayedWETHImpl", address(dio.delayedWETHImpl()));
312+
313+
// Get a contract set from the implementation addresses which were just deployed.
314+
Types.ContractSet memory contracts = Types.ContractSet({
315+
L1CrossDomainMessenger: address(dio.l1CrossDomainMessengerImpl()),
316+
L1StandardBridge: address(dio.l1StandardBridgeImpl()),
317+
L2OutputOracle: address(0),
318+
DisputeGameFactory: address(dio.disputeGameFactoryImpl()),
319+
DelayedWETH: address(dio.delayedWETHImpl()),
320+
PermissionedDelayedWETH: address(dio.delayedWETHImpl()),
321+
AnchorStateRegistry: address(0),
322+
OptimismMintableERC20Factory: address(dio.optimismMintableERC20FactoryImpl()),
323+
OptimismPortal: address(dio.optimismPortalImpl()),
324+
SystemConfig: address(dio.systemConfigImpl()),
325+
L1ERC721Bridge: address(dio.l1ERC721BridgeImpl()),
326+
// We didn't deploy new versions of these in this function, so just read the existing ones.
327+
ProtocolVersions: artifacts.mustGetAddress("ProtocolVersionsImpl"),
328+
SuperchainConfig: artifacts.mustGetAddress("SuperchainConfigImpl")
329+
});
318330

319-
Types.ContractSet memory contracts = _impls();
320331
ChainAssertions.checkL1CrossDomainMessenger({ _contracts: contracts, _vm: vm, _isProxy: false });
321332
ChainAssertions.checkL1StandardBridge({ _contracts: contracts, _isProxy: false });
322333
ChainAssertions.checkL1ERC721Bridge({ _contracts: contracts, _isProxy: false });
@@ -339,8 +350,8 @@ contract Deploy is Deployer {
339350
});
340351
ChainAssertions.checkOPContractsManager({
341352
_contracts: contracts,
342-
_opcm: OPContractsManager(artifacts.mustGetAddress("OPContractsManager")),
343-
_mips: IMIPS(artifacts.mustGetAddress("MipsSingleton"))
353+
_opcm: OPContractsManager(address(dio.opcm())),
354+
_mips: IMIPS(address(dio.mipsSingleton()))
344355
});
345356
if (_isInterop) {
346357
ChainAssertions.checkSystemConfigInterop({ _contracts: contracts, _cfg: cfg, _isProxy: false });

packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ contract L1CrossDomainMessenger_Test is CommonTest {
2929
/// @notice Marked virtual to be overridden in
3030
/// test/kontrol/deployment/DeploymentSummary.t.sol
3131
function test_constructor_succeeds() external virtual {
32-
IL1CrossDomainMessenger impl = IL1CrossDomainMessenger(artifacts.mustGetAddress("L1CrossDomainMessengerImpl"));
32+
IL1CrossDomainMessenger impl = IL1CrossDomainMessenger(addressManager.getAddress("OVM_L1CrossDomainMessenger"));
3333
assertEq(address(impl.superchainConfig()), address(0));
3434
assertEq(address(impl.PORTAL()), address(0));
3535
assertEq(address(impl.portal()), address(0));

packages/contracts-bedrock/test/L1/L1ERC721Bridge.t.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
99

1010
// Libraries
1111
import { Predeploys } from "src/libraries/Predeploys.sol";
12+
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
1213

1314
// Interfaces
1415
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
@@ -69,7 +70,7 @@ contract L1ERC721Bridge_Test is CommonTest {
6970
/// @notice Marked virtual to be overridden in
7071
/// test/kontrol/deployment/DeploymentSummary.t.sol
7172
function test_constructor_succeeds() public virtual {
72-
IL1ERC721Bridge impl = IL1ERC721Bridge(artifacts.mustGetAddress("L1ERC721BridgeImpl"));
73+
IL1ERC721Bridge impl = IL1ERC721Bridge(EIP1967Helper.getImplementation(address(l1ERC721Bridge)));
7374
assertEq(address(impl.MESSENGER()), address(0));
7475
assertEq(address(impl.messenger()), address(0));
7576
assertEq(address(impl.superchainConfig()), address(0));

packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { StandardBridge } from "src/universal/StandardBridge.sol";
1212
// Libraries
1313
import { Predeploys } from "src/libraries/Predeploys.sol";
1414
import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol";
15+
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
1516

1617
// Interfaces
1718
import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol";
@@ -38,7 +39,7 @@ contract L1StandardBridge_Initialize_Test is CommonTest {
3839
/// @notice Marked virtual to be overridden in
3940
/// test/kontrol/deployment/DeploymentSummary.t.sol
4041
function test_constructor_succeeds() external virtual {
41-
IL1StandardBridge impl = IL1StandardBridge(artifacts.mustGetAddress("L1StandardBridgeImpl"));
42+
IL1StandardBridge impl = IL1StandardBridge(payable(EIP1967Helper.getImplementation(address(l1StandardBridge))));
4243
assertEq(address(impl.superchainConfig()), address(0));
4344

4445
// The constructor now uses _disableInitializers, whereas OP Mainnet has these values in storage

packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { Constants } from "src/libraries/Constants.sol";
1919
import { Predeploys } from "src/libraries/Predeploys.sol";
2020
import { GasPayingToken } from "src/libraries/GasPayingToken.sol";
2121
import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol";
22+
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
2223
import "src/dispute/lib/Types.sol";
2324
import "src/libraries/PortalErrors.sol";
2425

@@ -42,7 +43,7 @@ contract OptimismPortal2_Test is CommonTest {
4243
/// @notice Marked virtual to be overridden in
4344
/// test/kontrol/deployment/DeploymentSummary.t.sol
4445
function test_constructor_succeeds() external virtual {
45-
IOptimismPortal2 opImpl = IOptimismPortal2(payable(artifacts.mustGetAddress("OptimismPortal2Impl")));
46+
IOptimismPortal2 opImpl = IOptimismPortal2(payable(EIP1967Helper.getImplementation(address(optimismPortal2))));
4647
assertEq(address(opImpl.disputeGameFactory()), address(0));
4748
assertEq(address(opImpl.systemConfig()), address(0));
4849
assertEq(address(opImpl.superchainConfig()), address(0));

packages/contracts-bedrock/test/L1/SystemConfig.t.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
1111
import { Constants } from "src/libraries/Constants.sol";
1212
import { Predeploys } from "src/libraries/Predeploys.sol";
1313
import { GasPayingToken } from "src/libraries/GasPayingToken.sol";
14+
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
1415

1516
// Interfaces
1617
import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol";
@@ -42,7 +43,7 @@ contract SystemConfig_Initialize_Test is SystemConfig_Init {
4243
batcherHash = bytes32(uint256(uint160(deploy.cfg().batchSenderAddress())));
4344
gasLimit = uint64(deploy.cfg().l2GenesisBlockGasLimit());
4445
unsafeBlockSigner = deploy.cfg().p2pSequencerAddress();
45-
systemConfigImpl = artifacts.mustGetAddress("SystemConfigImpl");
46+
systemConfigImpl = EIP1967Helper.getImplementation(address(systemConfig));
4647
optimismMintableERC20Factory = artifacts.mustGetAddress("OptimismMintableERC20FactoryProxy");
4748
}
4849

packages/contracts-bedrock/test/setup/ForkLive.s.sol

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { stdJson } from "forge-std/StdJson.sol";
66

77
// Scripts
88
import { Deployer } from "scripts/deploy/Deployer.sol";
9+
import { Deploy } from "scripts/deploy/Deploy.s.sol";
910

1011
// Libraries
1112
import { GameTypes } from "src/dispute/lib/Types.sol";
@@ -19,9 +20,9 @@ import { IAddressManager } from "interfaces/legacy/IAddressManager.sol";
1920
/// @title ForkLive
2021
/// @notice This script is called by Setup.sol as a preparation step for the foundry test suite, and is run as an
2122
/// alternative to Deploy.s.sol, when `FORK_TEST=true` is set in the env.
22-
/// Like Deploy.s.sol this script saves the system addresses to disk so that they can be read into memory later
23-
/// on, however rather than deploying new contracts from the local source code, it simply reads the addresses
24-
/// from the superchain-registry.
23+
/// Like Deploy.s.sol this script saves the system addresses to the Artifacts contract so that they can be
24+
/// read by other contracts. However, rather than deploying new contracts from the local source code, it
25+
/// simply reads the addresses from the superchain-registry.
2526
/// Therefore this script can only be run against a fork of a production network which is listed in the
2627
/// superchain-registry.
2728
/// This contract must not have constructor logic because it is set into state using `etch`.
@@ -40,17 +41,29 @@ contract ForkLive is Deployer {
4041
return vm.envOr("FORK_OP_CHAIN", string("op"));
4142
}
4243

43-
/// @notice Reads a standard chains addresses from the superchain-registry and saves them to disk.
44+
/// @notice Forks, upgrades and tests a production network.
45+
/// @dev This function sets up the system to test by:
46+
/// 1. reading the superchain-registry to get the contract addresses we wish to test from that network.
47+
/// 2. deploying the updated OPCM and implementations of the contracts.
48+
/// 3. upgrading the system using the OPCM.upgrade() function.
4449
function run() public {
50+
// Read the superchain registry and save the addresses to the Artifacts contract.
51+
_readSuperchainRegistry();
52+
53+
// Now deploy the updated OPCM and implementations of the contracts
54+
_deployNewImplementations();
55+
}
56+
57+
/// @notice Reads the superchain config files and saves the addresses to disk.
58+
/// @dev During development of an upgrade which adds a new contract, the contract will not yet be present in the
59+
/// superchain-registry. In this case, the contract will be deployed by the upgrade process, and will need to
60+
/// be stored by artifacts.save() after the call to opcm.upgrade().
61+
/// After the upgrade is complete, the superchain-registry will be updated and the contract will be present. At
62+
/// that point, this function will need to be updated to read the new contract from the superchain-registry
63+
/// using either the `saveProxyAndImpl` or `artifacts.save()` functions.
64+
function _readSuperchainRegistry() internal {
4565
string memory superchainBasePath = "./lib/superchain-registry/superchain/configs/";
4666

47-
// Read the superchain config files
48-
// During development of an upgrade which adds a new contract, the contract will not yet be present in the
49-
// superchain-registry. In this case, the contract will be deployed by the upgrade process, and will need to
50-
// be saved by after the call to opcm.upgrade().
51-
// After the upgrade is complete, the superchain-registry will be updated and the contract will be present.
52-
// At this point, the test will need to be updated to read the new contract from the superchain-registry using
53-
// either the `saveProxyAndImpl` or `save` functions.
5467
string memory superchainToml = vm.readFile(string.concat(superchainBasePath, baseChain(), "/superchain.toml"));
5568
string memory opToml = vm.readFile(string.concat(superchainBasePath, baseChain(), "/", opChain(), ".toml"));
5669

@@ -99,13 +112,21 @@ contract ForkLive is Deployer {
99112
artifacts.save("PermissionedDelayedWETHProxy", address(permissionedDisputeGame.weth()));
100113
}
101114

115+
/// @notice Calls to the Deploy.s.sol contract etched by Setup.sol to a deterministic address, sets up the
116+
/// environment, and deploys new implementations.
117+
function _deployNewImplementations() internal {
118+
Deploy deploy = Deploy(address(uint160(uint256(keccak256(abi.encode("optimism.deploy"))))));
119+
deploy.deployImplementations({ _isInterop: false });
120+
}
121+
102122
/// @notice Saves the proxy and implementation addresses for a contract name
103123
/// @param _contractName The name of the contract to save
104124
/// @param _tomlPath The path to the superchain config file
105125
/// @param _tomlKey The key in the superchain config file to get the proxy address
106126
function saveProxyAndImpl(string memory _contractName, string memory _tomlPath, string memory _tomlKey) internal {
107127
address proxy = vm.parseTomlAddress(_tomlPath, _tomlKey);
108128
artifacts.save(string.concat(_contractName, "Proxy"), proxy);
129+
109130
address impl = EIP1967Helper.getImplementation(proxy);
110131
require(impl != address(0), "Upgrade: Implementation address is zero");
111132
artifacts.save(string.concat(_contractName, "Impl"), impl);

packages/contracts-bedrock/test/setup/Setup.sol

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ contract Setup {
6767
/// mutating any nonces. MUST not have constructor logic.
6868
Deploy internal constant deploy = Deploy(address(uint160(uint256(keccak256(abi.encode("optimism.deploy"))))));
6969

70+
/// @notice The address of the ForkLive contract. Set into state with `etch` to avoid
71+
/// mutating any nonces. MUST not have constructor logic.
72+
ForkLive internal constant forkLive =
73+
ForkLive(address(uint160(uint256(keccak256(abi.encode("optimism.forklive"))))));
74+
7075
/// @notice The address of the Artifacts contract. Set into state by Deployer.setUp() with `etch` to avoid
7176
/// mutating any nonces. MUST not have constructor logic.
7277
Artifacts public constant artifacts =
@@ -78,9 +83,6 @@ contract Setup {
7883
/// @notice Allows users of Setup to override what L2 genesis is being created.
7984
Fork l2Fork = LATEST_FORK;
8085

81-
/// @notice Indicates whether a test is running against a forked production network.
82-
bool private _isForkTest;
83-
8486
// L1 contracts
8587
IDisputeGameFactory disputeGameFactory;
8688
IAnchorStateRegistry anchorStateRegistry;
@@ -122,7 +124,7 @@ contract Setup {
122124

123125
/// @notice Indicates whether a test is running against a forked production network.
124126
function isForkTest() public view returns (bool) {
125-
return _isForkTest;
127+
return vm.envOr("FORK_TEST", false);
126128
}
127129

128130
/// @dev Deploys either the Deploy.s.sol or Fork.s.sol contract, by fetching the bytecode dynamically using
@@ -135,30 +137,24 @@ contract Setup {
135137
function setUp() public virtual {
136138
console.log("Setup: L1 setup start!");
137139

138-
// Optimistically etch, label and allow cheatcodes for the Deploy.s.sol contract
139-
DeployUtils.etchLabelAndAllowCheatcodes({ _etchTo: address(deploy), _cname: "Deploy" });
140-
141-
_isForkTest = vm.envOr("FORK_TEST", false);
142-
if (_isForkTest) {
140+
if (isForkTest()) {
143141
vm.createSelectFork(vm.envString("FORK_RPC_URL"), vm.envUint("FORK_BLOCK_NUMBER"));
144142
require(
145143
block.chainid == Chains.Sepolia || block.chainid == Chains.Mainnet,
146144
"Setup: ETH_RPC_URL must be set to a production (Sepolia or Mainnet) RPC URL"
147145
);
148-
149-
// Overwrite the Deploy.s.sol contract with the ForkLive.s.sol contract
150-
DeployUtils.etchLabelAndAllowCheatcodes({ _etchTo: address(deploy), _cname: "ForkLive" });
151146
}
152147

153-
// deploy.setUp() will either:
154-
// 1. deploy a fresh system or
155-
// 2. fork from L1
156-
// It will then save the appropriate name/address pairs to disk using artifacts.save()
148+
// Etch the contracts used to setup the test environment
149+
DeployUtils.etchLabelAndAllowCheatcodes({ _etchTo: address(deploy), _cname: "Deploy" });
150+
DeployUtils.etchLabelAndAllowCheatcodes({ _etchTo: address(forkLive), _cname: "ForkLive" });
151+
157152
deploy.setUp();
153+
forkLive.setUp();
158154
console.log("Setup: L1 setup done!");
159155

160-
// Return early if this is a fork test
161-
if (_isForkTest) {
156+
if (isForkTest()) {
157+
// Return early if this is a fork test as we don't need to setup L2
162158
console.log("Setup: fork test detected, skipping L2 genesis generation");
163159
return;
164160
}
@@ -172,7 +168,7 @@ contract Setup {
172168

173169
/// @dev Skips tests when running against a forked production network.
174170
function skipIfForkTest(string memory message) public {
175-
if (_isForkTest) {
171+
if (isForkTest()) {
176172
vm.skip(true);
177173
console.log(string.concat("Skipping fork test: ", message));
178174
}
@@ -181,7 +177,7 @@ contract Setup {
181177
/// @dev Returns early when running against a forked production network. Useful for allowing a portion of a test
182178
/// to run.
183179
function returnIfForkTest(string memory message) public view {
184-
if (_isForkTest) {
180+
if (isForkTest()) {
185181
console.log(string.concat("Returning early from fork test: ", message));
186182
assembly {
187183
return(0, 0)
@@ -198,7 +194,12 @@ contract Setup {
198194
hex"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"
199195
);
200196

201-
deploy.run();
197+
if (isForkTest()) {
198+
forkLive.run();
199+
} else {
200+
deploy.run();
201+
}
202+
202203
console.log("Setup: completed L1 deployment, registering addresses now");
203204

204205
optimismPortal2 = IOptimismPortal2(artifacts.mustGetAddress("OptimismPortalProxy"));
@@ -228,7 +229,7 @@ contract Setup {
228229
/// @dev Sets up the L2 contracts. Depends on `L1()` being called first.
229230
function L2() public {
230231
// Fork tests focus on L1 contracts so there is no need to do all the work of setting up L2.
231-
if (_isForkTest) {
232+
if (isForkTest()) {
232233
console.log("Setup: fork test detected, skipping L2 setup");
233234
return;
234235
}

0 commit comments

Comments
 (0)