Commit 93361d0d authored by Matt Solomon's avatar Matt Solomon Committed by GitHub

OPSM: Dedupe some code, switch to getter methods for added safety (#11555)

* feat: scaffold impl deploy script and test

* add fault proofs support

* appease semgrep

* additional semgrep fix

* refactor: dedupe libraries

* refactor/test: refactor to use getter methods to add assertions on getters, and add corresponding tests

* fix url

* chore: ignore autogenerated lib
parent bcc734b4
......@@ -22,3 +22,6 @@ op-chain-ops/script/testdata
packages/*/node_modules
packages/*/test
# Autogenerated solidity library
packages/contracts-bedrock/scripts/libraries/Solarray.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { LibString } from "@solady/utils/LibString.sol";
library DeployUtils {
// This takes a sender and an identifier and returns a deterministic address based on the two.
// The result is used to etch the input and output contracts to a deterministic address based on
// those two values, where the identifier represents the input or output contract, such as
// `optimism.DeploySuperchainInput` or `optimism.DeployOPChainOutput`.
// Example: `toIOAddress(msg.sender, "optimism.DeploySuperchainInput")`
function toIOAddress(address _sender, string memory _identifier) internal pure returns (address) {
return address(uint160(uint256(keccak256(abi.encode(_sender, _identifier)))));
}
function assertValidContractAddress(address _who) internal view {
require(_who != address(0), "DeployUtils: zero address");
require(_who.code.length > 0, string.concat("DeployUtils: no code at ", LibString.toHexStringChecksummed(_who)));
}
function assertValidContractAddresses(address[] memory _addrs) internal view {
// Assert that all addresses are non-zero and have code.
// We use LibString to avoid the need for adding cheatcodes to this contract.
for (uint256 i = 0; i < _addrs.length; i++) {
address who = _addrs[i];
assertValidContractAddress(who);
}
// All addresses should be unique.
for (uint256 i = 0; i < _addrs.length; i++) {
for (uint256 j = i + 1; j < _addrs.length; j++) {
string memory err =
string.concat("check failed: duplicates at ", LibString.toString(i), ",", LibString.toString(j));
require(_addrs[i] != _addrs[j], err);
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
// A trimmed-down and formatted version of https://github.com/emo-eth/solarray.
//
// This is provided to provide better UX when generating and using arrays in tests and scripts,
// since Solidity does not have great array UX.
//
// This library was generated using the `generator.py` script from the linked repo with the length
// set to 10, and then everything except the `addresses` functions was removed.
library Solarray {
function addresses(address a) internal pure returns (address[] memory) {
address[] memory arr = new address[](1);
arr[0] = a;
return arr;
}
function addresses(address a, address b) internal pure returns (address[] memory) {
address[] memory arr = new address[](2);
arr[0] = a;
arr[1] = b;
return arr;
}
function addresses(address a, address b, address c) internal pure returns (address[] memory) {
address[] memory arr = new address[](3);
arr[0] = a;
arr[1] = b;
arr[2] = c;
return arr;
}
function addresses(address a, address b, address c, address d) internal pure returns (address[] memory) {
address[] memory arr = new address[](4);
arr[0] = a;
arr[1] = b;
arr[2] = c;
arr[3] = d;
return arr;
}
function addresses(
address a,
address b,
address c,
address d,
address e
)
internal
pure
returns (address[] memory)
{
address[] memory arr = new address[](5);
arr[0] = a;
arr[1] = b;
arr[2] = c;
arr[3] = d;
arr[4] = e;
return arr;
}
function addresses(
address a,
address b,
address c,
address d,
address e,
address f
)
internal
pure
returns (address[] memory)
{
address[] memory arr = new address[](6);
arr[0] = a;
arr[1] = b;
arr[2] = c;
arr[3] = d;
arr[4] = e;
arr[5] = f;
return arr;
}
function addresses(
address a,
address b,
address c,
address d,
address e,
address f,
address g
)
internal
pure
returns (address[] memory)
{
address[] memory arr = new address[](7);
arr[0] = a;
arr[1] = b;
arr[2] = c;
arr[3] = d;
arr[4] = e;
arr[5] = f;
arr[6] = g;
return arr;
}
function addresses(
address a,
address b,
address c,
address d,
address e,
address f,
address g,
address h
)
internal
pure
returns (address[] memory)
{
address[] memory arr = new address[](8);
arr[0] = a;
arr[1] = b;
arr[2] = c;
arr[3] = d;
arr[4] = e;
arr[5] = f;
arr[6] = g;
arr[7] = h;
return arr;
}
function addresses(
address a,
address b,
address c,
address d,
address e,
address f,
address g,
address h,
address i
)
internal
pure
returns (address[] memory)
{
address[] memory arr = new address[](9);
arr[0] = a;
arr[1] = b;
arr[2] = c;
arr[3] = d;
arr[4] = e;
arr[5] = f;
arr[6] = g;
arr[7] = h;
arr[8] = i;
return arr;
}
function addresses(
address a,
address b,
address c,
address d,
address e,
address f,
address g,
address h,
address i,
address j
)
internal
pure
returns (address[] memory)
{
address[] memory arr = new address[](10);
arr[0] = a;
arr[1] = b;
arr[2] = c;
arr[3] = d;
arr[4] = e;
arr[5] = f;
arr[6] = g;
arr[7] = h;
arr[8] = i;
arr[9] = j;
return arr;
}
}
......@@ -3,13 +3,202 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { DelayedWETH } from "src/dispute/weth/DelayedWETH.sol";
import { PreimageOracle } from "src/cannon/PreimageOracle.sol";
import { MIPS } from "src/cannon/MIPS.sol";
import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol";
import { SystemConfig } from "src/L1/SystemConfig.sol";
import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol";
import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol";
import { L1StandardBridge } from "src/L1/L1StandardBridge.sol";
import { OptimismMintableERC20Factory } from "src/universal/OptimismMintableERC20Factory.sol";
import {
DeployImplementationsInput,
DeployImplementations,
DeployImplementationsOutput
} from "scripts/DeployImplementations.s.sol";
/// @notice Deploys the Superchain contracts that can be shared by many chains.
contract DeployImplementationsInput_Test is Test {
DeployImplementationsInput dsi;
DeployImplementationsInput.Input input = DeployImplementationsInput.Input({
withdrawalDelaySeconds: 100,
minProposalSizeBytes: 200,
challengePeriodSeconds: 300,
proofMaturityDelaySeconds: 400,
disputeGameFinalityDelaySeconds: 500
});
function setUp() public {
dsi = new DeployImplementationsInput();
}
function test_loadInput_succeeds() public {
dsi.loadInput(input);
assertTrue(dsi.inputSet(), "100");
// Compare the test input struct to the getter methods.
assertEq(input.withdrawalDelaySeconds, dsi.withdrawalDelaySeconds(), "200");
assertEq(input.minProposalSizeBytes, dsi.minProposalSizeBytes(), "300");
assertEq(input.challengePeriodSeconds, dsi.challengePeriodSeconds(), "400");
assertEq(input.proofMaturityDelaySeconds, dsi.proofMaturityDelaySeconds(), "500");
assertEq(input.disputeGameFinalityDelaySeconds, dsi.disputeGameFinalityDelaySeconds(), "600");
// Compare the test input struct to the `input` getter method.
assertEq(keccak256(abi.encode(input)), keccak256(abi.encode(dsi.input())), "800");
}
function test_getters_whenNotSet_revert() public {
bytes memory expectedErr = "DeployImplementationsInput: input not set";
vm.expectRevert(expectedErr);
dsi.withdrawalDelaySeconds();
vm.expectRevert(expectedErr);
dsi.minProposalSizeBytes();
vm.expectRevert(expectedErr);
dsi.challengePeriodSeconds();
vm.expectRevert(expectedErr);
dsi.proofMaturityDelaySeconds();
vm.expectRevert(expectedErr);
dsi.disputeGameFinalityDelaySeconds();
}
}
contract DeployImplementationsOutput_Test is Test {
DeployImplementationsOutput dso;
function setUp() public {
dso = new DeployImplementationsOutput();
}
function test_set_succeeds() public {
DeployImplementationsOutput.Output memory output = DeployImplementationsOutput.Output({
optimismPortal2Impl: OptimismPortal2(payable(makeAddr("optimismPortal2Impl"))),
delayedWETHImpl: DelayedWETH(payable(makeAddr("delayedWETHImpl"))),
preimageOracleSingleton: PreimageOracle(makeAddr("preimageOracleSingleton")),
mipsSingleton: MIPS(makeAddr("mipsSingleton")),
systemConfigImpl: SystemConfig(makeAddr("systemConfigImpl")),
l1CrossDomainMessengerImpl: L1CrossDomainMessenger(makeAddr("l1CrossDomainMessengerImpl")),
l1ERC721BridgeImpl: L1ERC721Bridge(makeAddr("l1ERC721BridgeImpl")),
l1StandardBridgeImpl: L1StandardBridge(payable(makeAddr("l1StandardBridgeImpl"))),
optimismMintableERC20FactoryImpl: OptimismMintableERC20Factory(makeAddr("optimismMintableERC20FactoryImpl"))
});
vm.etch(address(output.optimismPortal2Impl), hex"01");
vm.etch(address(output.delayedWETHImpl), hex"01");
vm.etch(address(output.preimageOracleSingleton), hex"01");
vm.etch(address(output.mipsSingleton), hex"01");
vm.etch(address(output.systemConfigImpl), hex"01");
vm.etch(address(output.l1CrossDomainMessengerImpl), hex"01");
vm.etch(address(output.l1ERC721BridgeImpl), hex"01");
vm.etch(address(output.l1StandardBridgeImpl), hex"01");
vm.etch(address(output.optimismMintableERC20FactoryImpl), hex"01");
dso.set(dso.optimismPortal2Impl.selector, address(output.optimismPortal2Impl));
dso.set(dso.delayedWETHImpl.selector, address(output.delayedWETHImpl));
dso.set(dso.preimageOracleSingleton.selector, address(output.preimageOracleSingleton));
dso.set(dso.mipsSingleton.selector, address(output.mipsSingleton));
dso.set(dso.systemConfigImpl.selector, address(output.systemConfigImpl));
dso.set(dso.l1CrossDomainMessengerImpl.selector, address(output.l1CrossDomainMessengerImpl));
dso.set(dso.l1ERC721BridgeImpl.selector, address(output.l1ERC721BridgeImpl));
dso.set(dso.l1StandardBridgeImpl.selector, address(output.l1StandardBridgeImpl));
dso.set(dso.optimismMintableERC20FactoryImpl.selector, address(output.optimismMintableERC20FactoryImpl));
assertEq(address(output.optimismPortal2Impl), address(dso.optimismPortal2Impl()), "100");
assertEq(address(output.delayedWETHImpl), address(dso.delayedWETHImpl()), "200");
assertEq(address(output.preimageOracleSingleton), address(dso.preimageOracleSingleton()), "300");
assertEq(address(output.mipsSingleton), address(dso.mipsSingleton()), "400");
assertEq(address(output.systemConfigImpl), address(dso.systemConfigImpl()), "500");
assertEq(address(output.l1CrossDomainMessengerImpl), address(dso.l1CrossDomainMessengerImpl()), "600");
assertEq(address(output.l1ERC721BridgeImpl), address(dso.l1ERC721BridgeImpl()), "700");
assertEq(address(output.l1StandardBridgeImpl), address(dso.l1StandardBridgeImpl()), "800");
assertEq(
address(output.optimismMintableERC20FactoryImpl), address(dso.optimismMintableERC20FactoryImpl()), "900"
);
assertEq(keccak256(abi.encode(output)), keccak256(abi.encode(dso.output())), "1000");
}
function test_getters_whenNotSet_revert() public {
bytes memory expectedErr = "DeployUtils: zero address";
vm.expectRevert(expectedErr);
dso.optimismPortal2Impl();
vm.expectRevert(expectedErr);
dso.delayedWETHImpl();
vm.expectRevert(expectedErr);
dso.preimageOracleSingleton();
vm.expectRevert(expectedErr);
dso.mipsSingleton();
vm.expectRevert(expectedErr);
dso.systemConfigImpl();
vm.expectRevert(expectedErr);
dso.l1CrossDomainMessengerImpl();
vm.expectRevert(expectedErr);
dso.l1ERC721BridgeImpl();
vm.expectRevert(expectedErr);
dso.l1StandardBridgeImpl();
vm.expectRevert(expectedErr);
dso.optimismMintableERC20FactoryImpl();
}
function test_getters_whenAddrHasNoCode_reverts() public {
address emptyAddr = makeAddr("emptyAddr");
bytes memory expectedErr = bytes(string.concat("DeployUtils: no code at ", vm.toString(emptyAddr)));
dso.set(dso.optimismPortal2Impl.selector, emptyAddr);
vm.expectRevert(expectedErr);
dso.optimismPortal2Impl();
dso.set(dso.delayedWETHImpl.selector, emptyAddr);
vm.expectRevert(expectedErr);
dso.delayedWETHImpl();
dso.set(dso.preimageOracleSingleton.selector, emptyAddr);
vm.expectRevert(expectedErr);
dso.preimageOracleSingleton();
dso.set(dso.mipsSingleton.selector, emptyAddr);
vm.expectRevert(expectedErr);
dso.mipsSingleton();
dso.set(dso.systemConfigImpl.selector, emptyAddr);
vm.expectRevert(expectedErr);
dso.systemConfigImpl();
dso.set(dso.l1CrossDomainMessengerImpl.selector, emptyAddr);
vm.expectRevert(expectedErr);
dso.l1CrossDomainMessengerImpl();
dso.set(dso.l1ERC721BridgeImpl.selector, emptyAddr);
vm.expectRevert(expectedErr);
dso.l1ERC721BridgeImpl();
dso.set(dso.l1StandardBridgeImpl.selector, emptyAddr);
vm.expectRevert(expectedErr);
dso.l1StandardBridgeImpl();
dso.set(dso.optimismMintableERC20FactoryImpl.selector, emptyAddr);
vm.expectRevert(expectedErr);
dso.optimismMintableERC20FactoryImpl();
}
}
contract DeployImplementations_Test is Test {
DeployImplementations deployImplementations;
DeployImplementationsInput dsi;
......
......@@ -3,11 +3,170 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { ProxyAdmin } from "src/universal/ProxyAdmin.sol";
import { Proxy } from "src/universal/Proxy.sol";
import { ProtocolVersion } from "src/L1/ProtocolVersions.sol";
import { SuperchainConfig } from "src/L1/SuperchainConfig.sol";
import { ProtocolVersions, ProtocolVersion } from "src/L1/ProtocolVersions.sol";
import { DeploySuperchainInput, DeploySuperchain, DeploySuperchainOutput } from "scripts/DeploySuperchain.s.sol";
/// @notice Deploys the Superchain contracts that can be shared by many chains.
contract DeploySuperchainInput_Test is Test {
DeploySuperchainInput dsi;
DeploySuperchainInput.Input input = DeploySuperchainInput.Input({
roles: DeploySuperchainInput.Roles({
proxyAdminOwner: makeAddr("defaultProxyAdminOwner"),
protocolVersionsOwner: makeAddr("defaultProtocolVersionsOwner"),
guardian: makeAddr("defaultGuardian")
}),
paused: false,
requiredProtocolVersion: ProtocolVersion.wrap(1),
recommendedProtocolVersion: ProtocolVersion.wrap(2)
});
function setUp() public {
dsi = new DeploySuperchainInput();
}
function test_loadInput_succeeds() public {
// We avoid using a fuzz test here because we'd need to modify the inputs of multiple
// parameters to e.g. avoid the zero address. Therefore we hardcode a concrete test case
// which is simpler and still sufficient.
dsi.loadInput(input);
assertTrue(dsi.inputSet(), "100");
// Compare the test input struct to the getter methods.
assertEq(input.roles.proxyAdminOwner, dsi.proxyAdminOwner(), "200");
assertEq(input.roles.protocolVersionsOwner, dsi.protocolVersionsOwner(), "300");
assertEq(input.roles.guardian, dsi.guardian(), "400");
assertEq(input.paused, dsi.paused(), "500");
assertEq(
ProtocolVersion.unwrap(input.requiredProtocolVersion),
ProtocolVersion.unwrap(dsi.requiredProtocolVersion()),
"600"
);
assertEq(
ProtocolVersion.unwrap(input.recommendedProtocolVersion),
ProtocolVersion.unwrap(dsi.recommendedProtocolVersion()),
"700"
);
// Compare the test input struct to the `input` getter method.
assertEq(keccak256(abi.encode(input)), keccak256(abi.encode(dsi.input())), "800");
}
function test_getters_whenNotSet_revert() public {
bytes memory expectedErr = "DeploySuperchainInput: input not set";
vm.expectRevert(expectedErr);
dsi.proxyAdminOwner();
vm.expectRevert(expectedErr);
dsi.protocolVersionsOwner();
vm.expectRevert(expectedErr);
dsi.guardian();
vm.expectRevert(expectedErr);
dsi.paused();
vm.expectRevert(expectedErr);
dsi.requiredProtocolVersion();
vm.expectRevert(expectedErr);
dsi.recommendedProtocolVersion();
}
}
contract DeploySuperchainOutput_Test is Test {
DeploySuperchainOutput dso;
function setUp() public {
dso = new DeploySuperchainOutput();
}
function test_set_succeeds() public {
// We don't fuzz, because we need code at the address, and we can't etch code if the fuzzer
// provides precompiles, so we end up with a lot of boilerplate logic to get valid inputs.
// Hardcoding a concrete set of valid addresses is simpler and still sufficient.
DeploySuperchainOutput.Output memory output = DeploySuperchainOutput.Output({
superchainProxyAdmin: ProxyAdmin(makeAddr("superchainProxyAdmin")),
superchainConfigImpl: SuperchainConfig(makeAddr("superchainConfigImpl")),
superchainConfigProxy: SuperchainConfig(makeAddr("superchainConfigProxy")),
protocolVersionsImpl: ProtocolVersions(makeAddr("protocolVersionsImpl")),
protocolVersionsProxy: ProtocolVersions(makeAddr("protocolVersionsProxy"))
});
// Ensure each address has code, since these are expected to be contracts.
vm.etch(address(output.superchainProxyAdmin), hex"01");
vm.etch(address(output.superchainConfigImpl), hex"01");
vm.etch(address(output.superchainConfigProxy), hex"01");
vm.etch(address(output.protocolVersionsImpl), hex"01");
vm.etch(address(output.protocolVersionsProxy), hex"01");
// Set the output data.
dso.set(dso.superchainProxyAdmin.selector, address(output.superchainProxyAdmin));
dso.set(dso.superchainConfigImpl.selector, address(output.superchainConfigImpl));
dso.set(dso.superchainConfigProxy.selector, address(output.superchainConfigProxy));
dso.set(dso.protocolVersionsImpl.selector, address(output.protocolVersionsImpl));
dso.set(dso.protocolVersionsProxy.selector, address(output.protocolVersionsProxy));
// Compare the test output struct to the getter methods.
assertEq(address(output.superchainProxyAdmin), address(dso.superchainProxyAdmin()), "100");
assertEq(address(output.superchainConfigImpl), address(dso.superchainConfigImpl()), "200");
assertEq(address(output.superchainConfigProxy), address(dso.superchainConfigProxy()), "300");
assertEq(address(output.protocolVersionsImpl), address(dso.protocolVersionsImpl()), "400");
assertEq(address(output.protocolVersionsProxy), address(dso.protocolVersionsProxy()), "500");
// Compare the test output struct to the `output` getter method.
assertEq(keccak256(abi.encode(output)), keccak256(abi.encode(dso.output())), "600");
}
function test_getters_whenNotSet_revert() public {
bytes memory expectedErr = "DeployUtils: zero address";
vm.expectRevert(expectedErr);
dso.superchainProxyAdmin();
vm.expectRevert(expectedErr);
dso.superchainConfigImpl();
vm.expectRevert(expectedErr);
dso.superchainConfigProxy();
vm.expectRevert(expectedErr);
dso.protocolVersionsImpl();
vm.expectRevert(expectedErr);
dso.protocolVersionsProxy();
}
function test_getters_whenAddrHasNoCode_reverts() public {
address emptyAddr = makeAddr("emptyAddr");
bytes memory expectedErr = bytes(string.concat("DeployUtils: no code at ", vm.toString(emptyAddr)));
dso.set(dso.superchainProxyAdmin.selector, emptyAddr);
vm.expectRevert(expectedErr);
dso.superchainProxyAdmin();
dso.set(dso.superchainConfigImpl.selector, emptyAddr);
vm.expectRevert(expectedErr);
dso.superchainConfigImpl();
dso.set(dso.superchainConfigProxy.selector, emptyAddr);
vm.expectRevert(expectedErr);
dso.superchainConfigProxy();
dso.set(dso.protocolVersionsImpl.selector, emptyAddr);
vm.expectRevert(expectedErr);
dso.protocolVersionsImpl();
dso.set(dso.protocolVersionsProxy.selector, emptyAddr);
vm.expectRevert(expectedErr);
dso.protocolVersionsProxy();
}
}
contract DeploySuperchain_Test is Test {
DeploySuperchain deploySuperchain;
DeploySuperchainInput dsi;
......@@ -85,23 +244,23 @@ contract DeploySuperchain_Test is Test {
dso.checkOutput();
}
function test_run_ZeroAddressRoles_reverts() public {
function test_run_ZeroAddressRoleInput_reverts() public {
// Snapshot the state so we can revert to the default `input` struct between assertions.
uint256 snapshotId = vm.snapshot();
// Assert over each role being set to the zero address.
input.roles.proxyAdminOwner = address(0);
vm.expectRevert("DeploySuperchainInput: Null proxyAdminOwner");
vm.expectRevert("DeploySuperchainInput: null proxyAdminOwner");
deploySuperchain.run(input);
vm.revertTo(snapshotId);
input.roles.protocolVersionsOwner = address(0);
vm.expectRevert("DeploySuperchainInput: Null protocolVersionsOwner");
vm.expectRevert("DeploySuperchainInput: null protocolVersionsOwner");
deploySuperchain.run(input);
vm.revertTo(snapshotId);
input.roles.guardian = address(0);
vm.expectRevert("DeploySuperchainInput: Null guardian");
vm.expectRevert("DeploySuperchainInput: null guardian");
deploySuperchain.run(input);
}
}
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