Commit 8b7226e8 authored by Mark Tyneway's avatar Mark Tyneway

contracts-bedrock: include `initialize` checks for L2 contracts

To also run the checks for L2 contracts with `initialize`, some
changes to the pipelie are required. First off, when foundry
compiles a single contract with 2 different versions of solc, it
appends the solc version to the name of the file. This broke some of the
tooling that we have around reading artifacts from disk. This commit
updates the tooling to first check to see if the file exists and if
not, then it grabs one of the multiple artifacts instead. This means
that we need to be careful about the artifact because it may not exactly
match what was used. There isn't a great way around this because its not
possible to know the compiler version used within the evm.

Also make some modifications that allow for the L2 predeploys to have
their contract addresses fetched based on name. The method isn't super
nice because it will require the script to be updated if new predeploys
are added but its not the end of the world because the tooling
likely will not be used with many different predeploys, and if it
is in the future we can solve the problem then. In go we handle this
problem with an `init` function that registers all of the predeploys
into a map that we can iterate over. That just isn't possible in
solidity.

Add test coverage over the `L2CrossDomainMessenger` to ensure that
it cannot be initialized. This is test coverage that unblocks the
following PRs:
- https://github.com/ethereum-optimism/optimism/pull/8353
- https://github.com/ethereum-optimism/optimism/pull/8365
Both of these are adding new L2 contracts that will be initializable and
we want this test coverage ahead of time to prevent reinit bugs from
being introduced.
parent eef315e9
...@@ -4,8 +4,9 @@ pragma solidity ^0.8.0; ...@@ -4,8 +4,9 @@ pragma solidity ^0.8.0;
import { Script } from "forge-std/Script.sol"; import { Script } from "forge-std/Script.sol";
import { stdJson } from "forge-std/StdJson.sol"; import { stdJson } from "forge-std/StdJson.sol";
import { console2 as console } from "forge-std/console2.sol"; import { console2 as console } from "forge-std/console2.sol";
import { Executables } from "./Executables.sol"; import { Executables } from "scripts/Executables.sol";
import { Chains } from "./Chains.sol"; import { Chains } from "scripts/Chains.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
/// @notice store the new deployment to be saved /// @notice store the new deployment to be saved
struct Deployment { struct Deployment {
...@@ -197,7 +198,7 @@ abstract contract Deployer is Script { ...@@ -197,7 +198,7 @@ abstract contract Deployer is Script {
return _getExistingDeploymentAddress(_name) != address(0); return _getExistingDeploymentAddress(_name) != address(0);
} }
/// @notice Returns the address of a deployment. /// @notice Returns the address of a deployment. Also handles the predeploys.
/// @param _name The name of the deployment. /// @param _name The name of the deployment.
/// @return The address of the deployment. May be `address(0)` if the deployment does not /// @return The address of the deployment. May be `address(0)` if the deployment does not
/// exist. /// exist.
...@@ -209,7 +210,54 @@ abstract contract Deployer is Script { ...@@ -209,7 +210,54 @@ abstract contract Deployer is Script {
} }
return existing.addr; return existing.addr;
} }
return _getExistingDeploymentAddress(_name); address addr = _getExistingDeploymentAddress(_name);
if (addr != address(0)) return payable(addr);
bytes32 digest = keccak256(bytes(_name));
if (digest == keccak256(bytes("L2CrossDomainMessenger"))) {
return payable(Predeploys.L2_CROSS_DOMAIN_MESSENGER);
} else if (digest == keccak256(bytes("L2ToL1MessagePasser"))) {
return payable(Predeploys.L2_TO_L1_MESSAGE_PASSER);
} else if (digest == keccak256(bytes("L2StandardBridge"))) {
return payable(Predeploys.L2_STANDARD_BRIDGE);
} else if (digest == keccak256(bytes("L2ERC721Bridge"))) {
return payable(Predeploys.L2_ERC721_BRIDGE);
} else if (digest == keccak256(bytes("SequencerFeeWallet"))) {
return payable(Predeploys.SEQUENCER_FEE_WALLET);
} else if (digest == keccak256(bytes("OptimismMintableERC20Factory"))) {
return payable(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY);
} else if (digest == keccak256(bytes("OptimismMintableERC721Factory"))) {
return payable(Predeploys.OPTIMISM_MINTABLE_ERC721_FACTORY);
} else if (digest == keccak256(bytes("L1Block"))) {
return payable(Predeploys.L1_BLOCK_ATTRIBUTES);
} else if (digest == keccak256(bytes("GasPriceOracle"))) {
return payable(Predeploys.GAS_PRICE_ORACLE);
} else if (digest == keccak256(bytes("L1MessageSender"))) {
return payable(Predeploys.L1_MESSAGE_SENDER);
} else if (digest == keccak256(bytes("DeployerWhitelist"))) {
return payable(Predeploys.DEPLOYER_WHITELIST);
} else if (digest == keccak256(bytes("WETH9"))) {
return payable(Predeploys.WETH9);
} else if (digest == keccak256(bytes("LegacyERC20ETH"))) {
return payable(Predeploys.LEGACY_ERC20_ETH);
} else if (digest == keccak256(bytes("L1BlockNumber"))) {
return payable(Predeploys.L1_BLOCK_NUMBER);
} else if (digest == keccak256(bytes("LegacyMessagePasser"))) {
return payable(Predeploys.LEGACY_MESSAGE_PASSER);
} else if (digest == keccak256(bytes("ProxyAdmin"))) {
return payable(Predeploys.PROXY_ADMIN);
} else if (digest == keccak256(bytes("BaseFeeVault"))) {
return payable(Predeploys.BASE_FEE_VAULT);
} else if (digest == keccak256(bytes("L1FeeVault"))) {
return payable(Predeploys.L1_FEE_VAULT);
} else if (digest == keccak256(bytes("GovernanceToken"))) {
return payable(Predeploys.GOVERNANCE_TOKEN);
} else if (digest == keccak256(bytes("SchemaRegistry"))) {
return payable(Predeploys.SCHEMA_REGISTRY);
} else if (digest == keccak256(bytes("EAS"))) {
return payable(Predeploys.EAS);
}
return payable(address(0));
} }
/// @notice Returns the address of a deployment and reverts if the deployment /// @notice Returns the address of a deployment and reverts if the deployment
...@@ -348,25 +396,43 @@ abstract contract Deployer is Script { ...@@ -348,25 +396,43 @@ abstract contract Deployer is Script {
return string.concat(sanitized, ".sol:", _name); return string.concat(sanitized, ".sol:", _name);
} }
/// @notice Returns the filesystem path to the artifact path. Assumes that the name of the function _getForgeArtifactDirectory(string memory _name) internal returns (string memory) {
/// file matches the name of the contract.
function _getForgeArtifactPath(string memory _name) internal returns (string memory) {
string[] memory cmd = new string[](3); string[] memory cmd = new string[](3);
cmd[0] = Executables.bash; cmd[0] = Executables.bash;
cmd[1] = "-c"; cmd[1] = "-c";
cmd[2] = string.concat(Executables.forge, " config --json | ", Executables.jq, " -r .out"); cmd[2] = string.concat(Executables.forge, " config --json | ", Executables.jq, " -r .out");
bytes memory res = vm.ffi(cmd); bytes memory res = vm.ffi(cmd);
string memory contractName = _stripSemver(_name); string memory contractName = _stripSemver(_name);
string memory forgeArtifactPath = return string.concat(vm.projectRoot(), "/", string(res), "/", contractName, ".sol");
string.concat(vm.projectRoot(), "/", string(res), "/", contractName, ".sol/", _name, ".json"); }
return forgeArtifactPath;
/// @notice Returns the filesystem path to the artifact path. If the contract was compiled
/// with multiple solidity versions then return the first one based on the result of `ls`.
function _getForgeArtifactPath(string memory _name) internal returns (string memory) {
string memory directory = _getForgeArtifactDirectory(_name);
string memory path = string.concat(directory, "/", _name, ".json");
if (vm.exists(path)) return path;
string[] memory cmd = new string[](3);
cmd[0] = Executables.bash;
cmd[1] = "-c";
cmd[2] = string.concat(
Executables.ls,
" -1 --color=never ",
directory,
" | ",
Executables.jq,
" -R -s -c 'split(\"\n\") | map(select(length > 0))'"
);
bytes memory res = vm.ffi(cmd);
string[] memory files = stdJson.readStringArray(string(res), "");
return string.concat(directory, "/", files[0]);
} }
/// @notice Returns the forge artifact given a contract name. /// @notice Returns the forge artifact given a contract name.
function _getForgeArtifact(string memory _name) internal returns (string memory) { function _getForgeArtifact(string memory _name) internal returns (string memory) {
string memory forgeArtifactPath = _getForgeArtifactPath(_name); string memory forgeArtifactPath = _getForgeArtifactPath(_name);
string memory forgeArtifact = vm.readFile(forgeArtifactPath); return vm.readFile(forgeArtifactPath);
return forgeArtifact;
} }
/// @notice Returns the receipt of a deployment transaction. /// @notice Returns the receipt of a deployment transaction.
...@@ -413,6 +479,9 @@ abstract contract Deployer is Script { ...@@ -413,6 +479,9 @@ abstract contract Deployer is Script {
string[] memory cmd = new string[](3); string[] memory cmd = new string[](3);
cmd[0] = Executables.bash; cmd[0] = Executables.bash;
cmd[1] = "-c"; cmd[1] = "-c";
cmd[2] = "";
cmd[2] = string.concat(Executables.jq, " -r '.abi' < ", _getForgeArtifactPath(_name)); cmd[2] = string.concat(Executables.jq, " -r '.abi' < ", _getForgeArtifactPath(_name));
bytes memory res = vm.ffi(cmd); bytes memory res = vm.ffi(cmd);
abi_ = string(res); abi_ = string(res);
...@@ -464,7 +533,9 @@ abstract contract Deployer is Script { ...@@ -464,7 +533,9 @@ abstract contract Deployer is Script {
if (_isProxy) { if (_isProxy) {
_contractName = string.concat(_contractName, "Proxy"); _contractName = string.concat(_contractName, "Proxy");
} }
bytes32 slotVal = vm.load(mustGetAddress(_contractName), bytes32(vm.parseUint(slot.slot))); address addr = getAddress(_contractName);
bytes32 slotVal = vm.load(addr, bytes32(vm.parseUint(slot.slot)));
initialized_ = uint8((uint256(slotVal) >> (slot.offset * 8)) & 0xFF); initialized_ = uint8((uint256(slotVal) >> (slot.offset * 8)) & 0xFF);
} }
......
...@@ -11,4 +11,5 @@ library Executables { ...@@ -11,4 +11,5 @@ library Executables {
string internal constant echo = "echo"; string internal constant echo = "echo";
string internal constant sed = "sed"; string internal constant sed = "sed";
string internal constant find = "find"; string internal constant find = "find";
string internal constant ls = "ls";
} }
...@@ -103,14 +103,22 @@ contract Initializer_Test is Bridge_Initializer { ...@@ -103,14 +103,22 @@ contract Initializer_Test is Bridge_Initializer {
initializedSlotVal: deploy.loadInitializedSlot("ProtocolVersions", true) initializedSlotVal: deploy.loadInitializedSlot("ProtocolVersions", true)
}) })
); );
// L2CrossDomainMessenger
contracts.push(
InitializeableContract({
target: address(l2CrossDomainMessenger),
initCalldata: abi.encodeCall(l2CrossDomainMessenger.initialize, ()),
initializedSlotVal: deploy.loadInitializedSlot("L2CrossDomainMessenger", false)
})
);
} }
/// @notice Tests that: /// @notice Tests that:
/// 1. All `Initializable` contracts in `src/L1` are accounted for in the `contracts` array. /// 1. All `Initializable` contracts in `src/L1` and `src/L2` are accounted for in the `contracts` array.
/// 2. The `_initialized` flag of each contract is properly set to `1`, signifying that the /// 2. The `_initialized` flag of each contract is properly set to `1`, signifying that the
/// contracts are initialized. /// contracts are initialized.
/// 3. The `initialize()` function of each contract cannot be called more than once. /// 3. The `initialize()` function of each contract cannot be called more than once.
function test_cannotReinitializeL1_succeeds() public { function test_cannotReinitialize_succeeds() public {
// Ensure that all L1 `Initializable` contracts are accounted for. // Ensure that all L1 `Initializable` contracts are accounted for.
assertEq(_getNumL1Initializable(), contracts.length); assertEq(_getNumL1Initializable(), contracts.length);
...@@ -135,7 +143,7 @@ contract Initializer_Test is Bridge_Initializer { ...@@ -135,7 +143,7 @@ contract Initializer_Test is Bridge_Initializer {
command[1] = "-c"; command[1] = "-c";
command[2] = string.concat( command[2] = string.concat(
Executables.find, Executables.find,
" src/L1 -type f -exec basename {} \\;", " src/L1 src/L2 -type f -exec basename {} \\;",
" | ", " | ",
Executables.sed, Executables.sed,
" 's/\\.[^.]*$//'", " 's/\\.[^.]*$//'",
......
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