Commit 0c62c8e7 authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

Merge pull request #8240 from ethereum-optimism/cl/ctb/deployment-checks

feat(ctb): Further modularization of post-deployment checks
parents fbe7bad4 f1b1f4b3
...@@ -8,4 +8,4 @@ GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (g ...@@ -8,4 +8,4 @@ GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (g
GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 86653) GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 86653)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68485) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68485)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68988) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68988)
GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 143255) GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 143259)
\ No newline at end of file \ No newline at end of file
...@@ -20,38 +20,49 @@ import { ISystemConfigV0 } from "scripts/interfaces/ISystemConfigV0.sol"; ...@@ -20,38 +20,49 @@ import { ISystemConfigV0 } from "scripts/interfaces/ISystemConfigV0.sol";
import { console2 as console } from "forge-std/console2.sol"; import { console2 as console } from "forge-std/console2.sol";
library ChainAssertions { library ChainAssertions {
/// @notice Asserts the correctness of an L1 deployment /// @notice Asserts the correctness of an L1 deployment. This function expects that all contracts
/// within the `prox` ContractSet are proxies that have been setup and initialized.
function postDeployAssertions( function postDeployAssertions(
Types.ContractSet memory prox, Types.ContractSet memory _prox,
DeployConfig cfg, DeployConfig _cfg,
uint256 l2OutputOracleStartingTimestamp, uint256 _l2OutputOracleStartingBlockNumber,
Vm vm uint256 _l2OutputOracleStartingTimestamp,
Vm _vm
) )
internal internal
view view
{ {
ResourceMetering.ResourceConfig memory rcfg = SystemConfig(prox.SystemConfig).resourceConfig(); ResourceMetering.ResourceConfig memory rcfg = SystemConfig(_prox.SystemConfig).resourceConfig();
ResourceMetering.ResourceConfig memory dflt = Constants.DEFAULT_RESOURCE_CONFIG(); ResourceMetering.ResourceConfig memory dflt = Constants.DEFAULT_RESOURCE_CONFIG();
require(keccak256(abi.encode(rcfg)) == keccak256(abi.encode(dflt))); require(keccak256(abi.encode(rcfg)) == keccak256(abi.encode(dflt)));
checkSystemConfig(prox, cfg); checkSystemConfig({ _contracts: _prox, _cfg: _cfg, _isProxy: true });
checkL1CrossDomainMessenger(prox, vm); checkL1CrossDomainMessenger(_prox, _vm);
checkL1StandardBridge(prox); checkL1StandardBridge(_prox);
checkL2OutputOracle(prox, cfg, l2OutputOracleStartingTimestamp); checkL2OutputOracle(_prox, _cfg, _l2OutputOracleStartingTimestamp, _l2OutputOracleStartingBlockNumber);
checkOptimismMintableERC20Factory(prox); checkOptimismMintableERC20Factory(_prox);
checkL1ERC721Bridge(prox); checkL1ERC721Bridge(_prox);
checkOptimismPortal(prox, cfg); checkOptimismPortal({ _contracts: _prox, _cfg: _cfg, _isPaused: false });
checkProtocolVersions(prox, cfg); checkProtocolVersions({ _contracts: _prox, _cfg: _cfg, _isProxy: true });
} }
/// @notice Asserts that the SystemConfig is setup correctly /// @notice Asserts that the SystemConfig is setup correctly
function checkSystemConfig(Types.ContractSet memory proxies, DeployConfig cfg) internal view { function checkSystemConfig(Types.ContractSet memory _contracts, DeployConfig _cfg, bool _isProxy) internal view {
ISystemConfigV0 config = ISystemConfigV0(proxies.SystemConfig); ISystemConfigV0 config = ISystemConfigV0(_contracts.SystemConfig);
require(config.owner() == cfg.finalSystemOwner());
require(config.overhead() == cfg.gasPriceOracleOverhead()); if (_isProxy) {
require(config.scalar() == cfg.gasPriceOracleScalar()); require(config.owner() == _cfg.finalSystemOwner());
require(config.batcherHash() == bytes32(uint256(uint160(cfg.batchSenderAddress())))); require(config.overhead() == _cfg.gasPriceOracleOverhead());
require(config.unsafeBlockSigner() == cfg.p2pSequencerAddress()); require(config.scalar() == _cfg.gasPriceOracleScalar());
require(config.batcherHash() == bytes32(uint256(uint160(_cfg.batchSenderAddress()))));
require(config.unsafeBlockSigner() == _cfg.p2pSequencerAddress());
} else {
require(config.owner() == address(0xdead));
require(config.overhead() == 0);
require(config.scalar() == 0);
require(config.batcherHash() == bytes32(0));
require(config.unsafeBlockSigner() == address(0));
}
ResourceMetering.ResourceConfig memory rconfig = Constants.DEFAULT_RESOURCE_CONFIG(); ResourceMetering.ResourceConfig memory rconfig = Constants.DEFAULT_RESOURCE_CONFIG();
ResourceMetering.ResourceConfig memory resourceConfig = config.resourceConfig(); ResourceMetering.ResourceConfig memory resourceConfig = config.resourceConfig();
...@@ -64,86 +75,107 @@ library ChainAssertions { ...@@ -64,86 +75,107 @@ library ChainAssertions {
} }
/// @notice Asserts that the L1CrossDomainMessenger is setup correctly /// @notice Asserts that the L1CrossDomainMessenger is setup correctly
function checkL1CrossDomainMessenger(Types.ContractSet memory proxies, Vm vm) internal view { function checkL1CrossDomainMessenger(Types.ContractSet memory _contracts, Vm _vm) internal view {
L1CrossDomainMessenger messenger = L1CrossDomainMessenger(proxies.L1CrossDomainMessenger); L1CrossDomainMessenger messenger = L1CrossDomainMessenger(_contracts.L1CrossDomainMessenger);
require(address(messenger.portal()) == proxies.OptimismPortal); require(address(messenger.portal()) == _contracts.OptimismPortal);
require(address(messenger.PORTAL()) == proxies.OptimismPortal); require(address(messenger.PORTAL()) == _contracts.OptimismPortal);
bytes32 xdmSenderSlot = vm.load(address(messenger), bytes32(uint256(204))); bytes32 xdmSenderSlot = _vm.load(address(messenger), bytes32(uint256(204)));
require(address(uint160(uint256(xdmSenderSlot))) == Constants.DEFAULT_L2_SENDER); require(address(uint160(uint256(xdmSenderSlot))) == Constants.DEFAULT_L2_SENDER);
} }
/// @notice Asserts that the L1StandardBridge is setup correctly /// @notice Asserts that the L1StandardBridge is setup correctly
function checkL1StandardBridge(Types.ContractSet memory proxies) internal view { function checkL1StandardBridge(Types.ContractSet memory _contracts) internal view {
L1StandardBridge bridge = L1StandardBridge(payable(proxies.L1StandardBridge)); L1StandardBridge bridge = L1StandardBridge(payable(_contracts.L1StandardBridge));
require(address(bridge.MESSENGER()) == proxies.L1CrossDomainMessenger); require(address(bridge.MESSENGER()) == _contracts.L1CrossDomainMessenger);
require(address(bridge.messenger()) == proxies.L1CrossDomainMessenger); require(address(bridge.messenger()) == _contracts.L1CrossDomainMessenger);
require(address(bridge.OTHER_BRIDGE()) == Predeploys.L2_STANDARD_BRIDGE); require(address(bridge.OTHER_BRIDGE()) == Predeploys.L2_STANDARD_BRIDGE);
require(address(bridge.otherBridge()) == Predeploys.L2_STANDARD_BRIDGE); require(address(bridge.otherBridge()) == Predeploys.L2_STANDARD_BRIDGE);
} }
/// @notice Asserts that the L2OutputOracle is setup correctly /// @notice Asserts that the L2OutputOracle is setup correctly
function checkL2OutputOracle( function checkL2OutputOracle(
Types.ContractSet memory proxies, Types.ContractSet memory _contracts,
DeployConfig cfg, DeployConfig _cfg,
uint256 l2OutputOracleStartingTimestamp uint256 _l2OutputOracleStartingBlockNumber,
uint256 _l2OutputOracleStartingTimestamp
) )
internal internal
view view
{ {
L2OutputOracle oracle = L2OutputOracle(proxies.L2OutputOracle); L2OutputOracle oracle = L2OutputOracle(_contracts.L2OutputOracle);
require(oracle.SUBMISSION_INTERVAL() == cfg.l2OutputOracleSubmissionInterval()); require(oracle.SUBMISSION_INTERVAL() == _cfg.l2OutputOracleSubmissionInterval());
require(oracle.submissionInterval() == cfg.l2OutputOracleSubmissionInterval()); require(oracle.submissionInterval() == _cfg.l2OutputOracleSubmissionInterval());
require(oracle.L2_BLOCK_TIME() == cfg.l2BlockTime()); require(oracle.L2_BLOCK_TIME() == _cfg.l2BlockTime());
require(oracle.l2BlockTime() == cfg.l2BlockTime()); require(oracle.l2BlockTime() == _cfg.l2BlockTime());
require(oracle.PROPOSER() == cfg.l2OutputOracleProposer()); require(oracle.PROPOSER() == _cfg.l2OutputOracleProposer());
require(oracle.proposer() == cfg.l2OutputOracleProposer()); require(oracle.proposer() == _cfg.l2OutputOracleProposer());
require(oracle.CHALLENGER() == cfg.l2OutputOracleChallenger()); require(oracle.CHALLENGER() == _cfg.l2OutputOracleChallenger());
require(oracle.challenger() == cfg.l2OutputOracleChallenger()); require(oracle.challenger() == _cfg.l2OutputOracleChallenger());
require(oracle.FINALIZATION_PERIOD_SECONDS() == cfg.finalizationPeriodSeconds()); require(oracle.FINALIZATION_PERIOD_SECONDS() == _cfg.finalizationPeriodSeconds());
require(oracle.finalizationPeriodSeconds() == cfg.finalizationPeriodSeconds()); require(oracle.finalizationPeriodSeconds() == _cfg.finalizationPeriodSeconds());
require(oracle.startingBlockNumber() == cfg.l2OutputOracleStartingBlockNumber()); require(oracle.startingBlockNumber() == _l2OutputOracleStartingBlockNumber);
require(oracle.startingTimestamp() == l2OutputOracleStartingTimestamp); require(oracle.startingTimestamp() == _l2OutputOracleStartingTimestamp);
} }
/// @notice Asserts that the OptimismMintableERC20Factory is setup correctly /// @notice Asserts that the OptimismMintableERC20Factory is setup correctly
function checkOptimismMintableERC20Factory(Types.ContractSet memory proxies) internal view { function checkOptimismMintableERC20Factory(Types.ContractSet memory _contracts) internal view {
OptimismMintableERC20Factory factory = OptimismMintableERC20Factory(proxies.OptimismMintableERC20Factory); OptimismMintableERC20Factory factory = OptimismMintableERC20Factory(_contracts.OptimismMintableERC20Factory);
require(factory.BRIDGE() == proxies.L1StandardBridge); require(factory.BRIDGE() == _contracts.L1StandardBridge);
require(factory.bridge() == proxies.L1StandardBridge); require(factory.bridge() == _contracts.L1StandardBridge);
} }
/// @notice Asserts that the L1ERC721Bridge is setup correctly /// @notice Asserts that the L1ERC721Bridge is setup correctly
function checkL1ERC721Bridge(Types.ContractSet memory proxies) internal view { function checkL1ERC721Bridge(Types.ContractSet memory _contracts) internal view {
L1ERC721Bridge bridge = L1ERC721Bridge(proxies.L1ERC721Bridge); L1ERC721Bridge bridge = L1ERC721Bridge(_contracts.L1ERC721Bridge);
require(address(bridge.MESSENGER()) == proxies.L1CrossDomainMessenger); require(address(bridge.MESSENGER()) == _contracts.L1CrossDomainMessenger);
require(address(bridge.messenger()) == proxies.L1CrossDomainMessenger); require(address(bridge.messenger()) == _contracts.L1CrossDomainMessenger);
require(bridge.OTHER_BRIDGE() == Predeploys.L2_ERC721_BRIDGE); require(bridge.OTHER_BRIDGE() == Predeploys.L2_ERC721_BRIDGE);
require(bridge.otherBridge() == Predeploys.L2_ERC721_BRIDGE); require(bridge.otherBridge() == Predeploys.L2_ERC721_BRIDGE);
} }
/// @notice Asserts the OptimismPortal is setup correctly /// @notice Asserts the OptimismPortal is setup correctly
function checkOptimismPortal(Types.ContractSet memory proxies, DeployConfig cfg) internal view { function checkOptimismPortal(
OptimismPortal portal = OptimismPortal(payable(proxies.OptimismPortal)); Types.ContractSet memory _contracts,
DeployConfig _cfg,
bool _isPaused
)
internal
view
{
OptimismPortal portal = OptimismPortal(payable(_contracts.OptimismPortal));
address guardian = cfg.portalGuardian(); address guardian = _cfg.portalGuardian();
if (guardian.code.length == 0) { if (guardian.code.length == 0) {
console.log("Portal guardian has no code: %s", guardian); console.log("Portal guardian has no code: %s", guardian);
} }
require(address(portal.L2_ORACLE()) == proxies.L2OutputOracle); require(address(portal.L2_ORACLE()) == _contracts.L2OutputOracle);
require(address(portal.l2Oracle()) == proxies.L2OutputOracle); require(address(portal.l2Oracle()) == _contracts.L2OutputOracle);
require(portal.GUARDIAN() == cfg.portalGuardian()); require(portal.GUARDIAN() == _cfg.portalGuardian());
require(portal.guardian() == cfg.portalGuardian()); require(portal.guardian() == _cfg.portalGuardian());
require(address(portal.SYSTEM_CONFIG()) == proxies.SystemConfig); require(address(portal.SYSTEM_CONFIG()) == _contracts.SystemConfig);
require(address(portal.systemConfig()) == proxies.SystemConfig); require(address(portal.systemConfig()) == _contracts.SystemConfig);
require(portal.paused() == false); require(portal.paused() == _isPaused);
} }
/// @notice Asserts that the ProtocolVersions is setup correctly /// @notice Asserts that the ProtocolVersions is setup correctly
function checkProtocolVersions(Types.ContractSet memory proxies, DeployConfig cfg) internal view { function checkProtocolVersions(
ProtocolVersions versions = ProtocolVersions(proxies.ProtocolVersions); Types.ContractSet memory _contracts,
require(versions.owner() == cfg.finalSystemOwner()); DeployConfig _cfg,
require(ProtocolVersion.unwrap(versions.required()) == cfg.requiredProtocolVersion()); bool _isProxy
require(ProtocolVersion.unwrap(versions.recommended()) == cfg.recommendedProtocolVersion()); )
internal
view
{
ProtocolVersions versions = ProtocolVersions(_contracts.ProtocolVersions);
if (_isProxy) {
require(versions.owner() == _cfg.finalSystemOwner());
require(ProtocolVersion.unwrap(versions.required()) == _cfg.requiredProtocolVersion());
require(ProtocolVersion.unwrap(versions.recommended()) == _cfg.recommendedProtocolVersion());
} else {
require(versions.owner() == address(0xdead));
require(ProtocolVersion.unwrap(versions.required()) == 0);
require(ProtocolVersion.unwrap(versions.recommended()) == 0);
}
} }
} }
...@@ -11,6 +11,7 @@ import { SafeProxyFactory } from "safe-contracts/proxies/SafeProxyFactory.sol"; ...@@ -11,6 +11,7 @@ import { SafeProxyFactory } from "safe-contracts/proxies/SafeProxyFactory.sol";
import { Enum as SafeOps } from "safe-contracts/common/Enum.sol"; import { Enum as SafeOps } from "safe-contracts/common/Enum.sol";
import { Deployer } from "scripts/Deployer.sol"; import { Deployer } from "scripts/Deployer.sol";
import "scripts/Deployer.sol";
import { DeployConfig } from "scripts/DeployConfig.s.sol"; import { DeployConfig } from "scripts/DeployConfig.s.sol";
import { Safe } from "safe-contracts/Safe.sol"; import { Safe } from "safe-contracts/Safe.sol";
...@@ -115,6 +116,20 @@ contract Deploy is Deployer { ...@@ -115,6 +116,20 @@ contract Deploy is Deployer {
}); });
} }
/// @notice Returns the proxy addresses, not reverting if any are unset.
function _proxiesUnstrict() private view returns (Types.ContractSet memory proxies_) {
proxies_ = Types.ContractSet({
L1CrossDomainMessenger: getAddress("L1CrossDomainMessengerProxy"),
L1StandardBridge: getAddress("L1StandardBridgeProxy"),
L2OutputOracle: getAddress("L2OutputOracleProxy"),
OptimismMintableERC20Factory: getAddress("OptimismMintableERC20FactoryProxy"),
OptimismPortal: getAddress("OptimismPortalProxy"),
SystemConfig: getAddress("SystemConfigProxy"),
L1ERC721Bridge: getAddress("L1ERC721BridgeProxy"),
ProtocolVersions: getAddress("ProtocolVersionsProxy")
});
}
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// State Changing Helper Functions // // State Changing Helper Functions //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
...@@ -377,15 +392,18 @@ contract Deploy is Deployer { ...@@ -377,15 +392,18 @@ contract Deploy is Deployer {
_portal: OptimismPortal(payable(portal)) _portal: OptimismPortal(payable(portal))
}); });
require(address(messenger.PORTAL()) == portal);
require(address(messenger.portal()) == portal);
bytes32 xdmSenderSlot = vm.load(address(messenger), bytes32(uint256(204)));
require(address(uint160(uint256(xdmSenderSlot))) == Constants.DEFAULT_L2_SENDER);
save("L1CrossDomainMessenger", address(messenger)); save("L1CrossDomainMessenger", address(messenger));
console.log("L1CrossDomainMessenger deployed at %s", address(messenger)); console.log("L1CrossDomainMessenger deployed at %s", address(messenger));
// Override the `L1CrossDomainMessenger` contract to the deployed implementation. This is necessary
// to check the `L1CrossDomainMessenger` implementation alongside dependent contracts, which
// are always proxies.
Types.ContractSet memory contracts = _proxiesUnstrict();
contracts.L1CrossDomainMessenger = address(messenger);
ChainAssertions.checkL1CrossDomainMessenger(contracts, vm);
require(loadInitializedSlot("L1CrossDomainMessenger", false) == 1, "L1CrossDomainMessenger is not initialized");
addr_ = address(messenger); addr_ = address(messenger);
} }
...@@ -406,17 +424,18 @@ contract Deploy is Deployer { ...@@ -406,17 +424,18 @@ contract Deploy is Deployer {
_systemConfig: systemConfig _systemConfig: systemConfig
}); });
require(address(portal.L2_ORACLE()) == address(l2OutputOracle));
require(address(portal.l2Oracle()) == address(l2OutputOracle));
require(portal.GUARDIAN() == guardian);
require(portal.guardian() == guardian);
require(address(portal.SYSTEM_CONFIG()) == address(systemConfig));
require(address(portal.systemConfig()) == address(systemConfig));
require(portal.paused() == true);
save("OptimismPortal", address(portal)); save("OptimismPortal", address(portal));
console.log("OptimismPortal deployed at %s", address(portal)); console.log("OptimismPortal deployed at %s", address(portal));
// Override the `OptimismPortal` contract to the deployed implementation. This is necessary
// to check the `OptimismPortal` implementation alongside dependent contracts, which
// are always proxies.
Types.ContractSet memory contracts = _proxiesUnstrict();
contracts.OptimismPortal = address(portal);
ChainAssertions.checkOptimismPortal({ _contracts: contracts, _cfg: cfg, _isPaused: true });
require(loadInitializedSlot("OptimismPortal", false) == 1, "OptimismPortal is not initialized");
addr_ = address(portal); addr_ = address(portal);
} }
...@@ -432,22 +451,18 @@ contract Deploy is Deployer { ...@@ -432,22 +451,18 @@ contract Deploy is Deployer {
_finalizationPeriodSeconds: cfg.finalizationPeriodSeconds() _finalizationPeriodSeconds: cfg.finalizationPeriodSeconds()
}); });
require(oracle.SUBMISSION_INTERVAL() == cfg.l2OutputOracleSubmissionInterval());
require(oracle.submissionInterval() == cfg.l2OutputOracleSubmissionInterval());
require(oracle.L2_BLOCK_TIME() == cfg.l2BlockTime());
require(oracle.l2BlockTime() == cfg.l2BlockTime());
require(oracle.PROPOSER() == cfg.l2OutputOracleProposer());
require(oracle.proposer() == cfg.l2OutputOracleProposer());
require(oracle.CHALLENGER() == cfg.l2OutputOracleChallenger());
require(oracle.challenger() == cfg.l2OutputOracleChallenger());
require(oracle.FINALIZATION_PERIOD_SECONDS() == cfg.finalizationPeriodSeconds());
require(oracle.finalizationPeriodSeconds() == cfg.finalizationPeriodSeconds());
require(oracle.startingBlockNumber() == 0);
require(oracle.startingTimestamp() == 0);
save("L2OutputOracle", address(oracle)); save("L2OutputOracle", address(oracle));
console.log("L2OutputOracle deployed at %s", address(oracle)); console.log("L2OutputOracle deployed at %s", address(oracle));
// Override the `L2OutputOracle` contract to the deployed implementation. This is necessary
// to check the `L2OutputOracle` implementation alongside dependent contracts, which
// are always proxies.
Types.ContractSet memory contracts = _proxiesUnstrict();
contracts.L2OutputOracle = address(oracle);
ChainAssertions.checkL2OutputOracle(contracts, cfg, 0, 0);
require(loadInitializedSlot("L2OutputOracle", false) == 1, "L2OutputOracle is not initialized");
addr_ = address(oracle); addr_ = address(oracle);
} }
...@@ -457,12 +472,16 @@ contract Deploy is Deployer { ...@@ -457,12 +472,16 @@ contract Deploy is Deployer {
OptimismMintableERC20Factory factory = OptimismMintableERC20Factory factory =
new OptimismMintableERC20Factory{ salt: _implSalt() }({_bridge: l1standardBridgeProxy}); new OptimismMintableERC20Factory{ salt: _implSalt() }({_bridge: l1standardBridgeProxy});
require(factory.BRIDGE() == l1standardBridgeProxy);
require(factory.bridge() == l1standardBridgeProxy);
save("OptimismMintableERC20Factory", address(factory)); save("OptimismMintableERC20Factory", address(factory));
console.log("OptimismMintableERC20Factory deployed at %s", address(factory)); console.log("OptimismMintableERC20Factory deployed at %s", address(factory));
// Override the `OptimismMintableERC20Factory` contract to the deployed implementation. This is necessary
// to check the `OptimismMintableERC20Factory` implementation alongside dependent contracts, which
// are always proxies.
Types.ContractSet memory contracts = _proxiesUnstrict();
contracts.OptimismMintableERC20Factory = address(factory);
ChainAssertions.checkOptimismMintableERC20Factory(contracts);
addr_ = address(factory); addr_ = address(factory);
} }
...@@ -490,6 +509,15 @@ contract Deploy is Deployer { ...@@ -490,6 +509,15 @@ contract Deploy is Deployer {
save("ProtocolVersions", address(versions)); save("ProtocolVersions", address(versions));
console.log("ProtocolVersions deployed at %s", address(versions)); console.log("ProtocolVersions deployed at %s", address(versions));
// Override the `ProtocolVersions` contract to the deployed implementation. This is necessary
// to check the `ProtocolVersions` implementation alongside dependent contracts, which
// are always proxies.
Types.ContractSet memory contracts = _proxiesUnstrict();
contracts.ProtocolVersions = address(versions);
ChainAssertions.checkProtocolVersions({ _contracts: contracts, _cfg: cfg, _isProxy: false });
require(loadInitializedSlot("ProtocolVersions", false) == 1, "ProtocolVersions is not initialized");
addr_ = address(versions); addr_ = address(versions);
} }
...@@ -525,24 +553,18 @@ contract Deploy is Deployer { ...@@ -525,24 +553,18 @@ contract Deploy is Deployer {
_config: defaultConfig _config: defaultConfig
}); });
require(config.owner() == address(0xdEaD));
require(config.overhead() == 0);
require(config.scalar() == 0);
require(config.unsafeBlockSigner() == address(0));
require(config.batcherHash() == bytes32(0));
require(config.gasLimit() == minimumGasLimit);
ResourceMetering.ResourceConfig memory resourceConfig = config.resourceConfig();
require(resourceConfig.maxResourceLimit == defaultConfig.maxResourceLimit);
require(resourceConfig.elasticityMultiplier == defaultConfig.elasticityMultiplier);
require(resourceConfig.baseFeeMaxChangeDenominator == defaultConfig.baseFeeMaxChangeDenominator);
require(resourceConfig.systemTxMaxGas == defaultConfig.systemTxMaxGas);
require(resourceConfig.minimumBaseFee == defaultConfig.minimumBaseFee);
require(resourceConfig.maximumBaseFee == defaultConfig.maximumBaseFee);
save("SystemConfig", address(config)); save("SystemConfig", address(config));
console.log("SystemConfig deployed at %s", address(config)); console.log("SystemConfig deployed at %s", address(config));
// Override the `SystemConfig` contract to the deployed implementation. This is necessary
// to check the `SystemConfig` implementation alongside dependent contracts, which
// are always proxies.
Types.ContractSet memory contracts = _proxiesUnstrict();
contracts.SystemConfig = address(config);
ChainAssertions.checkSystemConfig({ _contracts: contracts, _cfg: cfg, _isProxy: false });
require(loadInitializedSlot("SystemConfig", false) == 1, "SystemConfig is not initialized");
addr_ = address(config); addr_ = address(config);
} }
...@@ -554,14 +576,16 @@ contract Deploy is Deployer { ...@@ -554,14 +576,16 @@ contract Deploy is Deployer {
_messenger: payable(l1CrossDomainMessengerProxy) _messenger: payable(l1CrossDomainMessengerProxy)
}); });
require(address(bridge.MESSENGER()) == l1CrossDomainMessengerProxy);
require(address(bridge.messenger()) == l1CrossDomainMessengerProxy);
require(address(bridge.OTHER_BRIDGE()) == Predeploys.L2_STANDARD_BRIDGE);
require(address(bridge.otherBridge()) == Predeploys.L2_STANDARD_BRIDGE);
save("L1StandardBridge", address(bridge)); save("L1StandardBridge", address(bridge));
console.log("L1StandardBridge deployed at %s", address(bridge)); console.log("L1StandardBridge deployed at %s", address(bridge));
// Override the `L1StandardBridge` contract to the deployed implementation. This is necessary
// to check the `L1StandardBridge` implementation alongside dependent contracts, which
// are always proxies.
Types.ContractSet memory contracts = _proxiesUnstrict();
contracts.L1StandardBridge = address(bridge);
ChainAssertions.checkL1StandardBridge(contracts);
addr_ = address(bridge); addr_ = address(bridge);
} }
...@@ -573,14 +597,16 @@ contract Deploy is Deployer { ...@@ -573,14 +597,16 @@ contract Deploy is Deployer {
_otherBridge: Predeploys.L2_ERC721_BRIDGE _otherBridge: Predeploys.L2_ERC721_BRIDGE
}); });
require(address(bridge.MESSENGER()) == l1CrossDomainMessengerProxy);
require(address(bridge.messenger()) == l1CrossDomainMessengerProxy);
require(bridge.OTHER_BRIDGE() == Predeploys.L2_ERC721_BRIDGE);
require(bridge.otherBridge() == Predeploys.L2_ERC721_BRIDGE);
save("L1ERC721Bridge", address(bridge)); save("L1ERC721Bridge", address(bridge));
console.log("L1ERC721Bridge deployed at %s", address(bridge)); console.log("L1ERC721Bridge deployed at %s", address(bridge));
// Override the `L1ERC721Bridge` contract to the deployed implementation. This is necessary
// to check the `L1ERC721Bridge` implementation alongside dependent contracts, which
// are always proxies.
Types.ContractSet memory contracts = _proxiesUnstrict();
contracts.L1ERC721Bridge = address(bridge);
ChainAssertions.checkL1ERC721Bridge(contracts);
addr_ = address(bridge); addr_ = address(bridge);
} }
...@@ -644,7 +670,9 @@ contract Deploy is Deployer { ...@@ -644,7 +670,9 @@ contract Deploy is Deployer {
string memory version = config.version(); string memory version = config.version();
console.log("SystemConfig version: %s", version); console.log("SystemConfig version: %s", version);
ChainAssertions.checkSystemConfig(_proxies(), cfg); ChainAssertions.checkSystemConfig({ _contracts: _proxies(), _cfg: cfg, _isProxy: true });
require(loadInitializedSlot("SystemConfig", true) == 1, "SystemConfigProxy is not initialized");
} }
/// @notice Initialize the L1StandardBridge /// @notice Initialize the L1StandardBridge
...@@ -750,6 +778,10 @@ contract Deploy is Deployer { ...@@ -750,6 +778,10 @@ contract Deploy is Deployer {
console.log("L1CrossDomainMessenger version: %s", version); console.log("L1CrossDomainMessenger version: %s", version);
ChainAssertions.checkL1CrossDomainMessenger(_proxies(), vm); ChainAssertions.checkL1CrossDomainMessenger(_proxies(), vm);
require(
loadInitializedSlot("L1CrossDomainMessenger", true) == 1, "L1CrossDomainMessengerProxy is not initialized"
);
} }
/// @notice Initialize the L2OutputOracle /// @notice Initialize the L2OutputOracle
...@@ -769,7 +801,11 @@ contract Deploy is Deployer { ...@@ -769,7 +801,11 @@ contract Deploy is Deployer {
string memory version = oracle.version(); string memory version = oracle.version();
console.log("L2OutputOracle version: %s", version); console.log("L2OutputOracle version: %s", version);
ChainAssertions.checkL2OutputOracle(_proxies(), cfg, cfg.l2OutputOracleStartingTimestamp()); ChainAssertions.checkL2OutputOracle(
_proxies(), cfg, cfg.l2OutputOracleStartingTimestamp(), cfg.l2OutputOracleStartingBlockNumber()
);
require(loadInitializedSlot("L2OutputOracle", true) == 1, "L2OutputOracleProxy is not initialized");
} }
/// @notice Initialize the OptimismPortal /// @notice Initialize the OptimismPortal
...@@ -787,7 +823,9 @@ contract Deploy is Deployer { ...@@ -787,7 +823,9 @@ contract Deploy is Deployer {
string memory version = portal.version(); string memory version = portal.version();
console.log("OptimismPortal version: %s", version); console.log("OptimismPortal version: %s", version);
ChainAssertions.checkOptimismPortal(_proxies(), cfg); ChainAssertions.checkOptimismPortal({ _contracts: _proxies(), _cfg: cfg, _isPaused: false });
require(loadInitializedSlot("OptimismPortal", true) == 1, "OptimismPortalProxy is not initialized");
} }
function initializeProtocolVersions() public broadcast { function initializeProtocolVersions() public broadcast {
...@@ -815,7 +853,9 @@ contract Deploy is Deployer { ...@@ -815,7 +853,9 @@ contract Deploy is Deployer {
string memory version = versions.version(); string memory version = versions.version();
console.log("ProtocolVersions version: %s", version); console.log("ProtocolVersions version: %s", version);
ChainAssertions.checkProtocolVersions(_proxies(), cfg); ChainAssertions.checkProtocolVersions({ _contracts: _proxies(), _cfg: cfg, _isProxy: true });
require(loadInitializedSlot("ProtocolVersions", true) == 1, "ProtocolVersionsProxy is not initialized");
} }
/// @notice Transfer ownership of the DisputeGameFactory contract to the final system owner /// @notice Transfer ownership of the DisputeGameFactory contract to the final system owner
......
...@@ -30,6 +30,17 @@ struct Artifact { ...@@ -30,6 +30,17 @@ struct Artifact {
string userdoc; string userdoc;
} }
/// @notice Contains information about a storage slot. Mirrors the layout of the storage
/// slot object in Forge artifacts so that we can deserialize JSON into this struct.
struct StorageSlot {
uint256 astId;
string _contract;
string label;
uint256 offset;
string slot;
string _type;
}
/// @title Deployer /// @title Deployer
/// @author tynes /// @author tynes
/// @notice A contract that can make deploying and interacting with deployments easy. /// @notice A contract that can make deploying and interacting with deployments easy.
...@@ -427,6 +438,36 @@ abstract contract Deployer is Script { ...@@ -427,6 +438,36 @@ abstract contract Deployer is Script {
return string(res); return string(res);
} }
/// @dev Pulls the `_initialized` storage slot information from the Forge artifacts for a given contract.
function getInitializedSlot(string memory _contractName) internal returns (StorageSlot memory slot_) {
string memory storageLayout = getStorageLayout(_contractName);
string[] memory command = new string[](3);
command[0] = Executables.bash;
command[1] = "-c";
command[2] = string.concat(
Executables.echo,
" '",
storageLayout,
"'",
" | ",
Executables.jq,
" '.storage[] | select(.label == \"_initialized\" and .type == \"t_uint8\")'"
);
bytes memory rawSlot = vm.parseJson(string(vm.ffi(command)));
slot_ = abi.decode(rawSlot, (StorageSlot));
}
/// @dev Returns the value of the internal `_initialized` storage slot for a given contract.
function loadInitializedSlot(string memory _contractName, bool _isProxy) internal returns (uint8 initialized_) {
StorageSlot memory slot = getInitializedSlot(_contractName);
if (_isProxy) {
_contractName = string.concat(_contractName, "Proxy");
}
bytes32 slotVal = vm.load(mustGetAddress(_contractName), bytes32(vm.parseUint(slot.slot)));
initialized_ = uint8((uint256(slotVal) >> (slot.offset * 8)) & 0xFF);
}
/// @notice Adds a deployment to the temp deployments file /// @notice Adds a deployment to the temp deployments file
function _writeTemp(string memory _name, address _deployed) internal { function _writeTemp(string memory _name, address _deployed) internal {
vm.writeJson({ json: stdJson.serialize("", _name, _deployed), path: tempDeploymentsPath }); vm.writeJson({ json: stdJson.serialize("", _name, _deployed), path: tempDeploymentsPath });
......
...@@ -9,6 +9,7 @@ import { SystemConfig } from "src/L1/SystemConfig.sol"; ...@@ -9,6 +9,7 @@ import { SystemConfig } from "src/L1/SystemConfig.sol";
import { ResourceMetering } from "src/L1/ResourceMetering.sol"; import { ResourceMetering } from "src/L1/ResourceMetering.sol";
import { OptimismPortal } from "src/L1/OptimismPortal.sol"; import { OptimismPortal } from "src/L1/OptimismPortal.sol";
import "src/L1/ProtocolVersions.sol"; import "src/L1/ProtocolVersions.sol";
import "scripts/Deployer.sol";
/// @title Initializer_Test /// @title Initializer_Test
/// @dev Ensures that the `initialize()` function on contracts cannot be called more than /// @dev Ensures that the `initialize()` function on contracts cannot be called more than
...@@ -20,18 +21,7 @@ contract Initializer_Test is Bridge_Initializer { ...@@ -20,18 +21,7 @@ contract Initializer_Test is Bridge_Initializer {
struct InitializeableContract { struct InitializeableContract {
address target; address target;
bytes initCalldata; bytes initCalldata;
StorageSlot initializedSlot; uint8 initializedSlotVal;
}
/// @notice Contains information about a storage slot. Mirrors the layout of the storage
/// slot object in Forge artifacts so that we can deserialize JSON into this struct.
struct StorageSlot {
uint256 astId;
string _contract;
string label;
uint256 offset;
string slot;
string _type;
} }
/// @notice Contains the addresses of the contracts to test as well as the calldata /// @notice Contains the addresses of the contracts to test as well as the calldata
...@@ -50,7 +40,7 @@ contract Initializer_Test is Bridge_Initializer { ...@@ -50,7 +40,7 @@ contract Initializer_Test is Bridge_Initializer {
InitializeableContract({ InitializeableContract({
target: address(l1CrossDomainMessenger), target: address(l1CrossDomainMessenger),
initCalldata: abi.encodeCall(l1CrossDomainMessenger.initialize, ()), initCalldata: abi.encodeCall(l1CrossDomainMessenger.initialize, ()),
initializedSlot: _getInitializedSlot("L1CrossDomainMessenger") initializedSlotVal: loadInitializedSlot("L1CrossDomainMessenger", true)
}) })
); );
// L2OutputOracle // L2OutputOracle
...@@ -58,7 +48,7 @@ contract Initializer_Test is Bridge_Initializer { ...@@ -58,7 +48,7 @@ contract Initializer_Test is Bridge_Initializer {
InitializeableContract({ InitializeableContract({
target: address(l2OutputOracle), target: address(l2OutputOracle),
initCalldata: abi.encodeCall(l2OutputOracle.initialize, (0, 0)), initCalldata: abi.encodeCall(l2OutputOracle.initialize, (0, 0)),
initializedSlot: _getInitializedSlot("L2OutputOracle") initializedSlotVal: loadInitializedSlot("L2OutputOracle", true)
}) })
); );
// OptimismPortal // OptimismPortal
...@@ -66,7 +56,7 @@ contract Initializer_Test is Bridge_Initializer { ...@@ -66,7 +56,7 @@ contract Initializer_Test is Bridge_Initializer {
InitializeableContract({ InitializeableContract({
target: address(optimismPortal), target: address(optimismPortal),
initCalldata: abi.encodeCall(optimismPortal.initialize, (false)), initCalldata: abi.encodeCall(optimismPortal.initialize, (false)),
initializedSlot: _getInitializedSlot("OptimismPortal") initializedSlotVal: loadInitializedSlot("OptimismPortal", true)
}) })
); );
// SystemConfig // SystemConfig
...@@ -92,7 +82,7 @@ contract Initializer_Test is Bridge_Initializer { ...@@ -92,7 +82,7 @@ contract Initializer_Test is Bridge_Initializer {
}) })
) )
), ),
initializedSlot: _getInitializedSlot("SystemConfig") initializedSlotVal: loadInitializedSlot("SystemConfig", true)
}) })
); );
// ProtocolVersions // ProtocolVersions
...@@ -102,7 +92,7 @@ contract Initializer_Test is Bridge_Initializer { ...@@ -102,7 +92,7 @@ contract Initializer_Test is Bridge_Initializer {
initCalldata: abi.encodeCall( initCalldata: abi.encodeCall(
protocolVersions.initialize, (address(0), ProtocolVersion.wrap(1), ProtocolVersion.wrap(2)) protocolVersions.initialize, (address(0), ProtocolVersion.wrap(1), ProtocolVersion.wrap(2))
), ),
initializedSlot: _getInitializedSlot("ProtocolVersions") initializedSlotVal: loadInitializedSlot("ProtocolVersions", true)
}) })
); );
} }
...@@ -120,15 +110,8 @@ contract Initializer_Test is Bridge_Initializer { ...@@ -120,15 +110,8 @@ contract Initializer_Test is Bridge_Initializer {
for (uint256 i; i < contracts.length; i++) { for (uint256 i; i < contracts.length; i++) {
InitializeableContract memory _contract = contracts[i]; InitializeableContract memory _contract = contracts[i];
// Load the `_initialized` slot from the storage of the target contract. // Assert that the contract is already initialized.
uint256 initSlotOffset = _contract.initializedSlot.offset; assertEq(_contract.initializedSlotVal, 1);
bytes32 initSlotVal = vm.load(_contract.target, bytes32(vm.parseUint(_contract.initializedSlot.slot)));
// Pull out the 8-bit `_initialized` flag from the storage slot. The offset in forge artifacts is
// relative to the least-significant bit and signifies the *byte offset*, so we need to shift the
// value to the right by the offset * 8 and then mask out the low-order byte to retrieve the flag.
uint8 init = uint8((uint256(initSlotVal) >> (initSlotOffset * 8)) & 0xFF);
assertEq(init, 1);
// Then, attempt to re-initialize the contract. This should fail. // Then, attempt to re-initialize the contract. This should fail.
(bool success, bytes memory returnData) = _contract.target.call(_contract.initCalldata); (bool success, bytes memory returnData) = _contract.target.call(_contract.initCalldata);
...@@ -137,26 +120,6 @@ contract Initializer_Test is Bridge_Initializer { ...@@ -137,26 +120,6 @@ contract Initializer_Test is Bridge_Initializer {
} }
} }
/// @dev Pulls the `_initialized` storage slot information from the Forge artifacts for a given contract.
function _getInitializedSlot(string memory _contractName) internal returns (StorageSlot memory slot_) {
string memory storageLayout = getStorageLayout(_contractName);
string[] memory command = new string[](3);
command[0] = Executables.bash;
command[1] = "-c";
command[2] = string.concat(
Executables.echo,
" '",
storageLayout,
"'",
" | ",
Executables.jq,
" '.storage[] | select(.label == \"_initialized\" and .type == \"t_uint8\")'"
);
bytes memory rawSlot = vm.parseJson(string(vm.ffi(command)));
slot_ = abi.decode(rawSlot, (StorageSlot));
}
/// @dev Returns the number of contracts that are `Initializable` in `src/L1`. /// @dev Returns the number of contracts that are `Initializable` in `src/L1`.
function _getNumL1Initializable() internal returns (uint256 numContracts_) { function _getNumL1Initializable() internal returns (uint256 numContracts_) {
string[] memory command = new string[](3); string[] memory command = new string[](3);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment