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 ...@@ -22,3 +22,6 @@ op-chain-ops/script/testdata
packages/*/node_modules packages/*/node_modules
packages/*/test packages/*/test
# Autogenerated solidity library
packages/contracts-bedrock/scripts/libraries/Solarray.sol
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { Script } from "forge-std/Script.sol"; import { Script } from "forge-std/Script.sol";
import { LibString } from "@solady/utils/LibString.sol";
import { DelayedWETH } from "src/dispute/weth/DelayedWETH.sol"; import { DelayedWETH } from "src/dispute/weth/DelayedWETH.sol";
import { PreimageOracle } from "src/cannon/PreimageOracle.sol"; import { PreimageOracle } from "src/cannon/PreimageOracle.sol";
...@@ -16,6 +15,9 @@ import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol"; ...@@ -16,6 +15,9 @@ import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol";
import { L1StandardBridge } from "src/L1/L1StandardBridge.sol"; import { L1StandardBridge } from "src/L1/L1StandardBridge.sol";
import { OptimismMintableERC20Factory } from "src/universal/OptimismMintableERC20Factory.sol"; import { OptimismMintableERC20Factory } from "src/universal/OptimismMintableERC20Factory.sol";
import { DeployUtils } from "scripts/libraries/DeployUtils.sol";
import { Solarray } from "scripts/libraries/Solarray.sol";
// See DeploySuperchain.s.sol for detailed comments on the script architecture used here. // See DeploySuperchain.s.sol for detailed comments on the script architecture used here.
contract DeployImplementationsInput { contract DeployImplementationsInput {
struct Input { struct Input {
...@@ -29,12 +31,6 @@ contract DeployImplementationsInput { ...@@ -29,12 +31,6 @@ contract DeployImplementationsInput {
bool public inputSet = false; bool public inputSet = false;
Input internal inputs; Input internal inputs;
uint256 public withdrawalDelaySeconds;
uint256 public minProposalSizeBytes;
uint256 public challengePeriodSeconds;
uint256 public proofMaturityDelaySeconds;
uint256 public disputeGameFinalityDelaySeconds;
function loadInputFile(string memory _infile) public { function loadInputFile(string memory _infile) public {
_infile; _infile;
Input memory parsedInput; Input memory parsedInput;
...@@ -50,18 +46,41 @@ contract DeployImplementationsInput { ...@@ -50,18 +46,41 @@ contract DeployImplementationsInput {
inputSet = true; inputSet = true;
inputs = _input; inputs = _input;
}
withdrawalDelaySeconds = _input.withdrawalDelaySeconds; function assertInputSet() internal view {
minProposalSizeBytes = _input.minProposalSizeBytes; require(inputSet, "DeployImplementationsInput: input not set");
challengePeriodSeconds = _input.challengePeriodSeconds;
proofMaturityDelaySeconds = _input.proofMaturityDelaySeconds;
disputeGameFinalityDelaySeconds = _input.disputeGameFinalityDelaySeconds;
} }
function input() public view returns (Input memory) { function input() public view returns (Input memory) {
require(inputSet, "DeployImplementationsInput: input not set"); assertInputSet();
return inputs; return inputs;
} }
function withdrawalDelaySeconds() public view returns (uint256) {
assertInputSet();
return inputs.withdrawalDelaySeconds;
}
function minProposalSizeBytes() public view returns (uint256) {
assertInputSet();
return inputs.minProposalSizeBytes;
}
function challengePeriodSeconds() public view returns (uint256) {
assertInputSet();
return inputs.challengePeriodSeconds;
}
function proofMaturityDelaySeconds() public view returns (uint256) {
assertInputSet();
return inputs.proofMaturityDelaySeconds;
}
function disputeGameFinalityDelaySeconds() public view returns (uint256) {
assertInputSet();
return inputs.disputeGameFinalityDelaySeconds;
}
} }
contract DeployImplementationsOutput { contract DeployImplementationsOutput {
...@@ -77,27 +96,19 @@ contract DeployImplementationsOutput { ...@@ -77,27 +96,19 @@ contract DeployImplementationsOutput {
OptimismMintableERC20Factory optimismMintableERC20FactoryImpl; OptimismMintableERC20Factory optimismMintableERC20FactoryImpl;
} }
OptimismPortal2 public optimismPortal2Impl; Output internal outputs;
DelayedWETH public delayedWETHImpl;
PreimageOracle public preimageOracleSingleton;
MIPS public mipsSingleton;
SystemConfig public systemConfigImpl;
L1CrossDomainMessenger public l1CrossDomainMessengerImpl;
L1ERC721Bridge public l1ERC721BridgeImpl;
L1StandardBridge public l1StandardBridgeImpl;
OptimismMintableERC20Factory public optimismMintableERC20FactoryImpl;
function set(bytes4 sel, address _addr) public { function set(bytes4 sel, address _addr) public {
// forgefmt: disable-start // forgefmt: disable-start
if (sel == this.optimismPortal2Impl.selector) optimismPortal2Impl = OptimismPortal2(payable(_addr)); if (sel == this.optimismPortal2Impl.selector) outputs.optimismPortal2Impl = OptimismPortal2(payable(_addr));
else if (sel == this.delayedWETHImpl.selector) delayedWETHImpl = DelayedWETH(payable(_addr)); else if (sel == this.delayedWETHImpl.selector) outputs.delayedWETHImpl = DelayedWETH(payable(_addr));
else if (sel == this.preimageOracleSingleton.selector) preimageOracleSingleton = PreimageOracle(_addr); else if (sel == this.preimageOracleSingleton.selector) outputs.preimageOracleSingleton = PreimageOracle(_addr);
else if (sel == this.mipsSingleton.selector) mipsSingleton = MIPS(_addr); else if (sel == this.mipsSingleton.selector) outputs.mipsSingleton = MIPS(_addr);
else if (sel == this.systemConfigImpl.selector) systemConfigImpl = SystemConfig(_addr); else if (sel == this.systemConfigImpl.selector) outputs.systemConfigImpl = SystemConfig(_addr);
else if (sel == this.l1CrossDomainMessengerImpl.selector) l1CrossDomainMessengerImpl = L1CrossDomainMessenger(_addr); else if (sel == this.l1CrossDomainMessengerImpl.selector) outputs.l1CrossDomainMessengerImpl = L1CrossDomainMessenger(_addr);
else if (sel == this.l1ERC721BridgeImpl.selector) l1ERC721BridgeImpl = L1ERC721Bridge(_addr); else if (sel == this.l1ERC721BridgeImpl.selector) outputs.l1ERC721BridgeImpl = L1ERC721Bridge(_addr);
else if (sel == this.l1StandardBridgeImpl.selector) l1StandardBridgeImpl = L1StandardBridge(payable(_addr)); else if (sel == this.l1StandardBridgeImpl.selector) outputs.l1StandardBridgeImpl = L1StandardBridge(payable(_addr));
else if (sel == this.optimismMintableERC20FactoryImpl.selector) optimismMintableERC20FactoryImpl = OptimismMintableERC20Factory(_addr); else if (sel == this.optimismMintableERC20FactoryImpl.selector) outputs.optimismMintableERC20FactoryImpl = OptimismMintableERC20Factory(_addr);
else revert("DeployImplementationsOutput: unknown selector"); else revert("DeployImplementationsOutput: unknown selector");
// forgefmt: disable-end // forgefmt: disable-end
} }
...@@ -108,46 +119,67 @@ contract DeployImplementationsOutput { ...@@ -108,46 +119,67 @@ contract DeployImplementationsOutput {
} }
function output() public view returns (Output memory) { function output() public view returns (Output memory) {
return Output({ return outputs;
optimismPortal2Impl: optimismPortal2Impl,
delayedWETHImpl: delayedWETHImpl,
preimageOracleSingleton: preimageOracleSingleton,
mipsSingleton: mipsSingleton,
systemConfigImpl: systemConfigImpl,
l1CrossDomainMessengerImpl: l1CrossDomainMessengerImpl,
l1ERC721BridgeImpl: l1ERC721BridgeImpl,
l1StandardBridgeImpl: l1StandardBridgeImpl,
optimismMintableERC20FactoryImpl: optimismMintableERC20FactoryImpl
});
} }
function checkOutput() public view { function checkOutput() public view {
address[] memory addresses = new address[](9); address[] memory addrs = Solarray.addresses(
addresses[0] = address(optimismPortal2Impl); address(outputs.optimismPortal2Impl),
addresses[1] = address(delayedWETHImpl); address(outputs.delayedWETHImpl),
addresses[2] = address(preimageOracleSingleton); address(outputs.preimageOracleSingleton),
addresses[3] = address(mipsSingleton); address(outputs.mipsSingleton),
addresses[4] = address(systemConfigImpl); address(outputs.systemConfigImpl),
addresses[5] = address(l1CrossDomainMessengerImpl); address(outputs.l1CrossDomainMessengerImpl),
addresses[6] = address(l1ERC721BridgeImpl); address(outputs.l1ERC721BridgeImpl),
addresses[7] = address(l1StandardBridgeImpl); address(outputs.l1StandardBridgeImpl),
addresses[8] = address(optimismMintableERC20FactoryImpl); address(outputs.optimismMintableERC20FactoryImpl)
);
for (uint256 i = 0; i < addresses.length; i++) { DeployUtils.assertValidContractAddresses(addrs);
address who = addresses[i]; }
require(who != address(0), string.concat("check failed: zero address at index ", LibString.toString(i)));
require( function optimismPortal2Impl() public view returns (OptimismPortal2) {
who.code.length > 0, string.concat("check failed: no code at ", LibString.toHexStringChecksummed(who)) DeployUtils.assertValidContractAddress(address(outputs.optimismPortal2Impl));
); return outputs.optimismPortal2Impl;
} }
for (uint256 i = 0; i < addresses.length; i++) { function delayedWETHImpl() public view returns (DelayedWETH) {
for (uint256 j = i + 1; j < addresses.length; j++) { DeployUtils.assertValidContractAddress(address(outputs.delayedWETHImpl));
string memory err = return outputs.delayedWETHImpl;
string.concat("check failed: duplicates at ", LibString.toString(i), ",", LibString.toString(j)); }
require(addresses[i] != addresses[j], err);
} function preimageOracleSingleton() public view returns (PreimageOracle) {
} DeployUtils.assertValidContractAddress(address(outputs.preimageOracleSingleton));
return outputs.preimageOracleSingleton;
}
function mipsSingleton() public view returns (MIPS) {
DeployUtils.assertValidContractAddress(address(outputs.mipsSingleton));
return outputs.mipsSingleton;
}
function systemConfigImpl() public view returns (SystemConfig) {
DeployUtils.assertValidContractAddress(address(outputs.systemConfigImpl));
return outputs.systemConfigImpl;
}
function l1CrossDomainMessengerImpl() public view returns (L1CrossDomainMessenger) {
DeployUtils.assertValidContractAddress(address(outputs.l1CrossDomainMessengerImpl));
return outputs.l1CrossDomainMessengerImpl;
}
function l1ERC721BridgeImpl() public view returns (L1ERC721Bridge) {
DeployUtils.assertValidContractAddress(address(outputs.l1ERC721BridgeImpl));
return outputs.l1ERC721BridgeImpl;
}
function l1StandardBridgeImpl() public view returns (L1StandardBridge) {
DeployUtils.assertValidContractAddress(address(outputs.l1StandardBridgeImpl));
return outputs.l1StandardBridgeImpl;
}
function optimismMintableERC20FactoryImpl() public view returns (OptimismMintableERC20Factory) {
DeployUtils.assertValidContractAddress(address(outputs.optimismMintableERC20FactoryImpl));
return outputs.optimismMintableERC20FactoryImpl;
} }
} }
...@@ -303,10 +335,6 @@ contract DeployImplementations is Script { ...@@ -303,10 +335,6 @@ contract DeployImplementations is Script {
// -------- Utilities -------- // -------- Utilities --------
function toIOAddress(address _sender, string memory _identifier) internal pure returns (address) {
return address(uint160(uint256(keccak256(abi.encode(_sender, _identifier)))));
}
function etchIOContracts() internal returns (DeployImplementationsInput dsi_, DeployImplementationsOutput dso_) { function etchIOContracts() internal returns (DeployImplementationsInput dsi_, DeployImplementationsOutput dso_) {
(dsi_, dso_) = getIOContracts(); (dsi_, dso_) = getIOContracts();
vm.etch(address(dsi_), type(DeployImplementationsInput).runtimeCode); vm.etch(address(dsi_), type(DeployImplementationsInput).runtimeCode);
...@@ -314,12 +342,7 @@ contract DeployImplementations is Script { ...@@ -314,12 +342,7 @@ contract DeployImplementations is Script {
} }
function getIOContracts() public view returns (DeployImplementationsInput dsi_, DeployImplementationsOutput dso_) { function getIOContracts() public view returns (DeployImplementationsInput dsi_, DeployImplementationsOutput dso_) {
dsi_ = DeployImplementationsInput(toIOAddress(msg.sender, "optimism.DeployImplementationsInput")); dsi_ = DeployImplementationsInput(DeployUtils.toIOAddress(msg.sender, "optimism.DeployImplementationsInput"));
dso_ = DeployImplementationsOutput(toIOAddress(msg.sender, "optimism.DeployImplementationsOutput")); dso_ = DeployImplementationsOutput(DeployUtils.toIOAddress(msg.sender, "optimism.DeployImplementationsOutput"));
}
function assertValidContractAddress(address _address) internal view {
require(_address != address(0), "DeployImplementations: zero address");
require(_address.code.length > 0, "DeployImplementations: no code");
} }
} }
...@@ -2,13 +2,14 @@ ...@@ -2,13 +2,14 @@
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { Script } from "forge-std/Script.sol"; import { Script } from "forge-std/Script.sol";
import { LibString } from "@solady/utils/LibString.sol";
import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; import { SuperchainConfig } from "src/L1/SuperchainConfig.sol";
import { ProtocolVersions, ProtocolVersion } from "src/L1/ProtocolVersions.sol"; import { ProtocolVersions, ProtocolVersion } from "src/L1/ProtocolVersions.sol";
import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { ProxyAdmin } from "src/universal/ProxyAdmin.sol";
import { Proxy } from "src/universal/Proxy.sol"; import { Proxy } from "src/universal/Proxy.sol";
import { DeployUtils } from "scripts/libraries/DeployUtils.sol";
import { Solarray } from "scripts/libraries/Solarray.sol";
/** /**
* This comment block defines the requirements and rationale for the architecture used in this forge * This comment block defines the requirements and rationale for the architecture used in this forge
* script, along with other scripts that are being written as new Superchain-first deploy scripts to * script, along with other scripts that are being written as new Superchain-first deploy scripts to
...@@ -55,6 +56,7 @@ import { Proxy } from "src/universal/Proxy.sol"; ...@@ -55,6 +56,7 @@ import { Proxy } from "src/universal/Proxy.sol";
* Additionally, we intentionally use "Input" and "Output" terminology to clearly distinguish these * Additionally, we intentionally use "Input" and "Output" terminology to clearly distinguish these
* scripts from the existing ones that "Config" and "Artifacts" terminology. * scripts from the existing ones that "Config" and "Artifacts" terminology.
*/ */
contract DeploySuperchainInput { contract DeploySuperchainInput {
// The input struct contains all the input data required for the deployment. // The input struct contains all the input data required for the deployment.
struct Input { struct Input {
...@@ -76,57 +78,78 @@ contract DeploySuperchainInput { ...@@ -76,57 +78,78 @@ contract DeploySuperchainInput {
bool public inputSet = false; bool public inputSet = false;
// The full input struct is kept in storage. It is not exposed because the return type would be // The full input struct is kept in storage. It is not exposed because the return type would be
// a tuple, but it's more convenient for the return type to be the struct itself. Therefore the // a tuple, and it's more convenient for the return type to be the struct itself. Therefore the
// struct is exposed via the `input()` getter method. // struct is exposed via the `input()` getter method below.
Input internal inputs; Input internal inputs;
// And each field is exposed via it's own getter method. We can equivalently remove these
// storage variables and add getter methods that return the input struct fields directly, but
// that is more verbose with more boilerplate, especially for larger scripts with many inputs.
// Unlike the `input()` getter, these getters do not revert if the input is not set. The caller
// should check the `inputSet` value before calling any of these getters.
address public proxyAdminOwner;
address public protocolVersionsOwner;
address public guardian;
bool public paused;
ProtocolVersion public requiredProtocolVersion;
ProtocolVersion public recommendedProtocolVersion;
// Load the input from a TOML file. // Load the input from a TOML file.
function loadInputFile(string memory _infile) public { function loadInputFile(string memory _infile) public {
_infile; _infile;
Input memory parsedInput; Input memory parsedInput;
loadInput(parsedInput); loadInput(parsedInput);
require(false, "DeploySuperchainInput: loadInput is not implemented"); require(false, "DeploySuperchainInput: not implemented");
} }
// Load the input from a struct. // Load the input from a struct.
function loadInput(Input memory _input) public { function loadInput(Input memory _input) public {
// As a defensive measure, we only allow inputs to be set once. // As a defensive measure, we only allow inputs to be set once.
require(!inputSet, "DeploySuperchainInput: Input already set"); require(!inputSet, "DeploySuperchainInput: input already set");
// All assertions on inputs happen here. You cannot set any inputs in Solidity unless // All assertions on inputs happen here. You cannot set any inputs in Solidity unless
// they're all valid. For Go testing, the input and outputs // they're all valid. For Go testing, the input and outputs
require(_input.roles.proxyAdminOwner != address(0), "DeploySuperchainInput: Null proxyAdminOwner"); require(_input.roles.proxyAdminOwner != address(0), "DeploySuperchainInput: null proxyAdminOwner");
require(_input.roles.protocolVersionsOwner != address(0), "DeploySuperchainInput: Null protocolVersionsOwner"); require(_input.roles.protocolVersionsOwner != address(0), "DeploySuperchainInput: null protocolVersionsOwner");
require(_input.roles.guardian != address(0), "DeploySuperchainInput: Null guardian"); require(_input.roles.guardian != address(0), "DeploySuperchainInput: null guardian");
// We now set all values in storage. // We now set all values in storage.
inputSet = true; inputSet = true;
inputs = _input; inputs = _input;
}
proxyAdminOwner = _input.roles.proxyAdminOwner; function assertInputSet() internal view {
protocolVersionsOwner = _input.roles.protocolVersionsOwner; require(inputSet, "DeploySuperchainInput: input not set");
guardian = _input.roles.guardian;
paused = _input.paused;
requiredProtocolVersion = _input.requiredProtocolVersion;
recommendedProtocolVersion = _input.recommendedProtocolVersion;
} }
// This exposes the full input data as a struct, and it reverts if the input has not been set.
function input() public view returns (Input memory) { function input() public view returns (Input memory) {
require(inputSet, "DeploySuperchainInput: Input not set"); assertInputSet();
return inputs; return inputs;
} }
// Each field of the input struct is exposed via it's own getter method. Using public storage
// variables here would be more verbose, but would also be more error-prone, as it would
// require the caller to remember to check the `inputSet` flag before accessing any of the
// fields. With getter methods, we can be sure that the input is set before accessing any field.
function proxyAdminOwner() public view returns (address) {
assertInputSet();
return inputs.roles.proxyAdminOwner;
}
function protocolVersionsOwner() public view returns (address) {
assertInputSet();
return inputs.roles.protocolVersionsOwner;
}
function guardian() public view returns (address) {
assertInputSet();
return inputs.roles.guardian;
}
function paused() public view returns (bool) {
assertInputSet();
return inputs.paused;
}
function requiredProtocolVersion() public view returns (ProtocolVersion) {
assertInputSet();
return inputs.requiredProtocolVersion;
}
function recommendedProtocolVersion() public view returns (ProtocolVersion) {
assertInputSet();
return inputs.recommendedProtocolVersion;
}
} }
contract DeploySuperchainOutput { contract DeploySuperchainOutput {
...@@ -141,66 +164,66 @@ contract DeploySuperchainOutput { ...@@ -141,66 +164,66 @@ contract DeploySuperchainOutput {
// We use a similar pattern as the input contract to expose outputs. Because outputs are set // We use a similar pattern as the input contract to expose outputs. Because outputs are set
// individually, and deployment steps are modular and composable, we do not have an equivalent // individually, and deployment steps are modular and composable, we do not have an equivalent
// to the overall `input` and `inputSet` variables. // to the overall `inputSet` variable. However, we do hold everything in a struct, then
ProxyAdmin public superchainProxyAdmin; // similarly expose each field via a getter method. This getter method reverts if the output has
SuperchainConfig public superchainConfigImpl; // not been set, ensuring that the caller cannot access any output fields until they have been set.
SuperchainConfig public superchainConfigProxy; Output internal outputs;
ProtocolVersions public protocolVersionsImpl;
ProtocolVersions public protocolVersionsProxy;
// This method lets each field be set individually. The selector of an output's getter method // This method lets each field be set individually. The selector of an output's getter method
// is used to determine which field to set. // is used to determine which field to set.
function set(bytes4 sel, address _address) public { function set(bytes4 sel, address _address) public {
if (sel == this.superchainProxyAdmin.selector) superchainProxyAdmin = ProxyAdmin(_address); if (sel == this.superchainProxyAdmin.selector) outputs.superchainProxyAdmin = ProxyAdmin(_address);
else if (sel == this.superchainConfigImpl.selector) superchainConfigImpl = SuperchainConfig(_address); else if (sel == this.superchainConfigImpl.selector) outputs.superchainConfigImpl = SuperchainConfig(_address);
else if (sel == this.superchainConfigProxy.selector) superchainConfigProxy = SuperchainConfig(_address); else if (sel == this.superchainConfigProxy.selector) outputs.superchainConfigProxy = SuperchainConfig(_address);
else if (sel == this.protocolVersionsImpl.selector) protocolVersionsImpl = ProtocolVersions(_address); else if (sel == this.protocolVersionsImpl.selector) outputs.protocolVersionsImpl = ProtocolVersions(_address);
else if (sel == this.protocolVersionsProxy.selector) protocolVersionsProxy = ProtocolVersions(_address); else if (sel == this.protocolVersionsProxy.selector) outputs.protocolVersionsProxy = ProtocolVersions(_address);
else revert("DeploySuperchainOutput: Unknown selector"); else revert("DeploySuperchainOutput: unknown selector");
} }
// Save the output to a TOML file. // Save the output to a TOML file.
function writeOutputFile(string memory _outfile) public pure { function writeOutputFile(string memory _outfile) public pure {
_outfile; _outfile;
require(false, "DeploySuperchainOutput: saveOutput not implemented"); require(false, "DeploySuperchainOutput: not implemented");
} }
function output() public view returns (Output memory) { function output() public view returns (Output memory) {
return Output({ return outputs;
superchainProxyAdmin: superchainProxyAdmin,
superchainConfigImpl: superchainConfigImpl,
superchainConfigProxy: superchainConfigProxy,
protocolVersionsImpl: protocolVersionsImpl,
protocolVersionsProxy: protocolVersionsProxy
});
} }
function checkOutput() public view { function checkOutput() public view {
// Assert that all addresses are non-zero and have code. address[] memory addrs = Solarray.addresses(
// We use LibString to avoid the need for adding cheatcodes to this contract. address(outputs.superchainProxyAdmin),
address[] memory addresses = new address[](5); address(outputs.superchainConfigImpl),
addresses[0] = address(superchainProxyAdmin); address(outputs.superchainConfigProxy),
addresses[1] = address(superchainConfigImpl); address(outputs.protocolVersionsImpl),
addresses[2] = address(superchainConfigProxy); address(outputs.protocolVersionsProxy)
addresses[3] = address(protocolVersionsImpl); );
addresses[4] = address(protocolVersionsProxy); DeployUtils.assertValidContractAddresses(addrs);
}
for (uint256 i = 0; i < addresses.length; i++) {
address who = addresses[i]; function superchainProxyAdmin() public view returns (ProxyAdmin) {
require(who != address(0), string.concat("check failed: zero address at index ", LibString.toString(i))); DeployUtils.assertValidContractAddress(address(outputs.superchainProxyAdmin));
require( return outputs.superchainProxyAdmin;
who.code.length > 0, string.concat("check failed: no code at ", LibString.toHexStringChecksummed(who)) }
);
} function superchainConfigImpl() public view returns (SuperchainConfig) {
DeployUtils.assertValidContractAddress(address(outputs.superchainConfigImpl));
// All addresses should be unique. return outputs.superchainConfigImpl;
for (uint256 i = 0; i < addresses.length; i++) { }
for (uint256 j = i + 1; j < addresses.length; j++) {
string memory err = function superchainConfigProxy() public view returns (SuperchainConfig) {
string.concat("check failed: duplicates at ", LibString.toString(i), ",", LibString.toString(j)); DeployUtils.assertValidContractAddress(address(outputs.superchainConfigProxy));
require(addresses[i] != addresses[j], err); return outputs.superchainConfigProxy;
} }
}
function protocolVersionsImpl() public view returns (ProtocolVersions) {
DeployUtils.assertValidContractAddress(address(outputs.protocolVersionsImpl));
return outputs.protocolVersionsImpl;
}
function protocolVersionsProxy() public view returns (ProtocolVersions) {
DeployUtils.assertValidContractAddress(address(outputs.protocolVersionsProxy));
return outputs.protocolVersionsProxy;
} }
} }
...@@ -248,7 +271,7 @@ contract DeploySuperchain is Script { ...@@ -248,7 +271,7 @@ contract DeploySuperchain is Script {
// This entrypoint is useful for testing purposes, as it doesn't use any file I/O. // This entrypoint is useful for testing purposes, as it doesn't use any file I/O.
function run(DeploySuperchainInput _dsi, DeploySuperchainOutput _dso) public { function run(DeploySuperchainInput _dsi, DeploySuperchainOutput _dso) public {
// Verify that the input contract has been set. // Verify that the input contract has been set.
require(_dsi.inputSet(), "DeploySuperchain: Input not set"); require(_dsi.inputSet(), "DeploySuperchain: input not set");
// Deploy the proxy admin, with the owner set to the deployer. // Deploy the proxy admin, with the owner set to the deployer.
deploySuperchainProxyAdmin(_dsi, _dso); deploySuperchainProxyAdmin(_dsi, _dso);
...@@ -294,14 +317,11 @@ contract DeploySuperchain is Script { ...@@ -294,14 +317,11 @@ contract DeploySuperchain is Script {
} }
function deployAndInitializeSuperchainConfig(DeploySuperchainInput _dsi, DeploySuperchainOutput _dso) public { function deployAndInitializeSuperchainConfig(DeploySuperchainInput _dsi, DeploySuperchainOutput _dso) public {
require(_dsi.inputSet(), "DeploySuperchain: Input not set");
address guardian = _dsi.guardian(); address guardian = _dsi.guardian();
bool paused = _dsi.paused(); bool paused = _dsi.paused();
ProxyAdmin superchainProxyAdmin = _dso.superchainProxyAdmin(); ProxyAdmin superchainProxyAdmin = _dso.superchainProxyAdmin();
SuperchainConfig superchainConfigImpl = _dso.superchainConfigImpl(); SuperchainConfig superchainConfigImpl = _dso.superchainConfigImpl();
assertValidContractAddress(address(superchainProxyAdmin));
assertValidContractAddress(address(superchainConfigImpl));
vm.startBroadcast(msg.sender); vm.startBroadcast(msg.sender);
SuperchainConfig superchainConfigProxy = SuperchainConfig(address(new Proxy(address(superchainProxyAdmin)))); SuperchainConfig superchainConfigProxy = SuperchainConfig(address(new Proxy(address(superchainProxyAdmin))));
...@@ -317,16 +337,12 @@ contract DeploySuperchain is Script { ...@@ -317,16 +337,12 @@ contract DeploySuperchain is Script {
} }
function deployAndInitializeProtocolVersions(DeploySuperchainInput _dsi, DeploySuperchainOutput _dso) public { function deployAndInitializeProtocolVersions(DeploySuperchainInput _dsi, DeploySuperchainOutput _dso) public {
require(_dsi.inputSet(), "DeploySuperchain: Input not set");
address protocolVersionsOwner = _dsi.protocolVersionsOwner(); address protocolVersionsOwner = _dsi.protocolVersionsOwner();
ProtocolVersion requiredProtocolVersion = _dsi.requiredProtocolVersion(); ProtocolVersion requiredProtocolVersion = _dsi.requiredProtocolVersion();
ProtocolVersion recommendedProtocolVersion = _dsi.recommendedProtocolVersion(); ProtocolVersion recommendedProtocolVersion = _dsi.recommendedProtocolVersion();
ProxyAdmin superchainProxyAdmin = _dso.superchainProxyAdmin(); ProxyAdmin superchainProxyAdmin = _dso.superchainProxyAdmin();
ProtocolVersions protocolVersionsImpl = _dso.protocolVersionsImpl(); ProtocolVersions protocolVersionsImpl = _dso.protocolVersionsImpl();
assertValidContractAddress(address(superchainProxyAdmin));
assertValidContractAddress(address(protocolVersionsImpl));
vm.startBroadcast(msg.sender); vm.startBroadcast(msg.sender);
ProtocolVersions protocolVersionsProxy = ProtocolVersions(address(new Proxy(address(superchainProxyAdmin)))); ProtocolVersions protocolVersionsProxy = ProtocolVersions(address(new Proxy(address(superchainProxyAdmin))));
...@@ -345,11 +361,10 @@ contract DeploySuperchain is Script { ...@@ -345,11 +361,10 @@ contract DeploySuperchain is Script {
} }
function transferProxyAdminOwnership(DeploySuperchainInput _dsi, DeploySuperchainOutput _dso) public { function transferProxyAdminOwnership(DeploySuperchainInput _dsi, DeploySuperchainOutput _dso) public {
require(_dsi.inputSet(), "DeploySuperchain: Input not set");
address proxyAdminOwner = _dsi.proxyAdminOwner(); address proxyAdminOwner = _dsi.proxyAdminOwner();
ProxyAdmin superchainProxyAdmin = _dso.superchainProxyAdmin(); ProxyAdmin superchainProxyAdmin = _dso.superchainProxyAdmin();
assertValidContractAddress(address(superchainProxyAdmin)); DeployUtils.assertValidContractAddress(address(superchainProxyAdmin));
vm.broadcast(msg.sender); vm.broadcast(msg.sender);
superchainProxyAdmin.transferOwnership(proxyAdminOwner); superchainProxyAdmin.transferOwnership(proxyAdminOwner);
...@@ -357,14 +372,6 @@ contract DeploySuperchain is Script { ...@@ -357,14 +372,6 @@ contract DeploySuperchain is Script {
// -------- Utilities -------- // -------- Utilities --------
// This takes a sender and an identifier and returns a deterministic address based on the two.
// The resulting 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`.
function toIOAddress(address _sender, string memory _identifier) internal pure returns (address) {
return address(uint160(uint256(keccak256(abi.encode(_sender, _identifier)))));
}
function etchIOContracts() internal returns (DeploySuperchainInput dsi_, DeploySuperchainOutput dso_) { function etchIOContracts() internal returns (DeploySuperchainInput dsi_, DeploySuperchainOutput dso_) {
(dsi_, dso_) = getIOContracts(); (dsi_, dso_) = getIOContracts();
vm.etch(address(dsi_), type(DeploySuperchainInput).runtimeCode); vm.etch(address(dsi_), type(DeploySuperchainInput).runtimeCode);
...@@ -372,12 +379,7 @@ contract DeploySuperchain is Script { ...@@ -372,12 +379,7 @@ contract DeploySuperchain is Script {
} }
function getIOContracts() public view returns (DeploySuperchainInput dsi_, DeploySuperchainOutput dso_) { function getIOContracts() public view returns (DeploySuperchainInput dsi_, DeploySuperchainOutput dso_) {
dsi_ = DeploySuperchainInput(toIOAddress(msg.sender, "optimism.DeploySuperchainInput")); dsi_ = DeploySuperchainInput(DeployUtils.toIOAddress(msg.sender, "optimism.DeploySuperchainInput"));
dso_ = DeploySuperchainOutput(toIOAddress(msg.sender, "optimism.DeploySuperchainOutput")); dso_ = DeploySuperchainOutput(DeployUtils.toIOAddress(msg.sender, "optimism.DeploySuperchainOutput"));
}
function assertValidContractAddress(address _address) internal view {
require(_address != address(0), "DeploySuperchain: zero address");
require(_address.code.length > 0, "DeploySuperchain: no code");
} }
} }
// 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; ...@@ -3,13 +3,202 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; 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 { import {
DeployImplementationsInput, DeployImplementationsInput,
DeployImplementations, DeployImplementations,
DeployImplementationsOutput DeployImplementationsOutput
} from "scripts/DeployImplementations.s.sol"; } 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 { contract DeployImplementations_Test is Test {
DeployImplementations deployImplementations; DeployImplementations deployImplementations;
DeployImplementationsInput dsi; DeployImplementationsInput dsi;
......
...@@ -3,11 +3,170 @@ pragma solidity 0.8.15; ...@@ -3,11 +3,170 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { ProxyAdmin } from "src/universal/ProxyAdmin.sol";
import { Proxy } from "src/universal/Proxy.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"; 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 { contract DeploySuperchain_Test is Test {
DeploySuperchain deploySuperchain; DeploySuperchain deploySuperchain;
DeploySuperchainInput dsi; DeploySuperchainInput dsi;
...@@ -85,23 +244,23 @@ contract DeploySuperchain_Test is Test { ...@@ -85,23 +244,23 @@ contract DeploySuperchain_Test is Test {
dso.checkOutput(); 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. // Snapshot the state so we can revert to the default `input` struct between assertions.
uint256 snapshotId = vm.snapshot(); uint256 snapshotId = vm.snapshot();
// Assert over each role being set to the zero address. // Assert over each role being set to the zero address.
input.roles.proxyAdminOwner = address(0); input.roles.proxyAdminOwner = address(0);
vm.expectRevert("DeploySuperchainInput: Null proxyAdminOwner"); vm.expectRevert("DeploySuperchainInput: null proxyAdminOwner");
deploySuperchain.run(input); deploySuperchain.run(input);
vm.revertTo(snapshotId); vm.revertTo(snapshotId);
input.roles.protocolVersionsOwner = address(0); input.roles.protocolVersionsOwner = address(0);
vm.expectRevert("DeploySuperchainInput: Null protocolVersionsOwner"); vm.expectRevert("DeploySuperchainInput: null protocolVersionsOwner");
deploySuperchain.run(input); deploySuperchain.run(input);
vm.revertTo(snapshotId); vm.revertTo(snapshotId);
input.roles.guardian = address(0); input.roles.guardian = address(0);
vm.expectRevert("DeploySuperchainInput: Null guardian"); vm.expectRevert("DeploySuperchainInput: null guardian");
deploySuperchain.run(input); 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