Commit 9a222407 authored by Matt Solomon's avatar Matt Solomon Committed by GitHub

OPSM: remove structs from `DeploySuperchain` (#11833)

* refactor: remove structs from DeploySuperchainInput and DeploySuperchainOutput

* test: fix tests

* test: fix test

* chore: remove outdated struct references

* pr feedback

* chore: code comments
parent f84c92cc
...@@ -3,6 +3,7 @@ pragma solidity 0.8.15; ...@@ -3,6 +3,7 @@ pragma solidity 0.8.15;
import { Script } from "forge-std/Script.sol"; import { Script } from "forge-std/Script.sol";
import { CommonBase } from "forge-std/Base.sol"; import { CommonBase } from "forge-std/Base.sol";
import { stdToml } from "forge-std/StdToml.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";
...@@ -24,7 +25,7 @@ import { Solarray } from "scripts/libraries/Solarray.sol"; ...@@ -24,7 +25,7 @@ import { Solarray } from "scripts/libraries/Solarray.sol";
// //
// We want each user to interact with the scripts in the way that's simplest for their use case: // We want each user to interact with the scripts in the way that's simplest for their use case:
// 1. End users: TOML input files that define config, and TOML output files with all output data. // 1. End users: TOML input files that define config, and TOML output files with all output data.
// 2. Solidity developers: Direct calls to the script with input structs and output structs. // 2. Solidity developers: Direct calls to the script, with the input and output contracts configured.
// 3. Go developers: The forge scripts can be executed directly in Go. // 3. Go developers: The forge scripts can be executed directly in Go.
// //
// The following architecture is used to meet the requirements of each user. We use this file's // The following architecture is used to meet the requirements of each user. We use this file's
...@@ -38,7 +39,7 @@ import { Solarray } from "scripts/libraries/Solarray.sol"; ...@@ -38,7 +39,7 @@ import { Solarray } from "scripts/libraries/Solarray.sol";
// //
// Because the core script performs calls to the input and output contracts, Go developers can // Because the core script performs calls to the input and output contracts, Go developers can
// intercept calls to these addresses (analogous to how forge intercepts calls to the `Vm` address // intercept calls to these addresses (analogous to how forge intercepts calls to the `Vm` address
// to execute cheatcodes), to avoid the need for file I/O or hardcoding the input/output structs. // to execute cheatcodes), to avoid the need for file I/O or hardcoding the input/output values.
// //
// Public getter methods on the input and output contracts allow individual fields to be accessed // Public getter methods on the input and output contracts allow individual fields to be accessed
// in a strong, type-safe manner (as opposed to a single struct getter where the caller may // in a strong, type-safe manner (as opposed to a single struct getter where the caller may
...@@ -57,180 +58,187 @@ import { Solarray } from "scripts/libraries/Solarray.sol"; ...@@ -57,180 +58,187 @@ import { Solarray } from "scripts/libraries/Solarray.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 use the "Config" and "Artifacts" terminology. // scripts from the existing ones that use the "Config" and "Artifacts" terminology.
contract DeploySuperchainInput is CommonBase { contract DeploySuperchainInput is CommonBase {
// The input struct contains all the input data required for the deployment. using stdToml for string;
// The fields must be in alphabetical order for vm.parseToml to work.
struct Input { // All inputs are set in storage individually. We put any roles first, followed by the remaining
bool paused; // inputs. Inputs are internal and prefixed with an underscore, because we will expose a getter
ProtocolVersion recommendedProtocolVersion; // method that returns the input value. We use a getter method to allow us to make assertions on
ProtocolVersion requiredProtocolVersion; // the input to ensure it's valid before returning it. We also intentionally do not use a struct
Roles roles; // to hold all inputs, because as features are developed the set of inputs will change, and
// modifying structs in Solidity is not very simple.
// Role inputs.
address internal _guardian;
address internal _protocolVersionsOwner;
address internal _proxyAdminOwner;
// Other inputs.
bool internal _paused;
ProtocolVersion internal _recommendedProtocolVersion;
ProtocolVersion internal _requiredProtocolVersion;
// These `set` methods let each input be set individually. The selector of an input's getter method
// is used to determine which field to set.
function set(bytes4 _sel, address _address) public {
require(_address != address(0), "DeploySuperchainInput: cannot set null address");
if (_sel == this.guardian.selector) _guardian = _address;
else if (_sel == this.protocolVersionsOwner.selector) _protocolVersionsOwner = _address;
else if (_sel == this.proxyAdminOwner.selector) _proxyAdminOwner = _address;
else revert("DeploySuperchainInput: unknown selector");
} }
struct Roles { function set(bytes4 _sel, bool _value) public {
address guardian; if (_sel == this.paused.selector) _paused = _value;
address protocolVersionsOwner; else revert("DeploySuperchainInput: unknown selector");
address proxyAdminOwner;
} }
// This flag tells us if all inputs have been set. An `input()` getter method that returns all function set(bytes4 _sel, ProtocolVersion _value) public {
// inputs reverts if this flag is false. This ensures the deploy script cannot proceed until all require(ProtocolVersion.unwrap(_value) != 0, "DeploySuperchainInput: cannot set null protocol version");
// inputs are validated and set. if (_sel == this.recommendedProtocolVersion.selector) _recommendedProtocolVersion = _value;
bool public inputSet = false; else if (_sel == this.requiredProtocolVersion.selector) _requiredProtocolVersion = _value;
else revert("DeploySuperchainInput: unknown selector");
// The full input struct is kept in storage. It is not exposed because the return type would be }
// 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 below.
Input internal inputs;
// Load the input from a TOML file. // Load the input from a TOML file.
// When setting inputs from a TOML file, we use the setter methods instead of writing directly
// to storage. This allows us to validate each input as it is set.
function loadInputFile(string memory _infile) public { function loadInputFile(string memory _infile) public {
string memory toml = vm.readFile(_infile); string memory toml = vm.readFile(_infile);
bytes memory data = vm.parseToml(toml);
Input memory parsedInput = abi.decode(data, (Input));
loadInput(parsedInput);
}
// Load the input from a struct. // Parse and set role inputs.
function loadInput(Input memory _input) public { set(this.guardian.selector, toml.readAddress(".roles.guardian"));
// As a defensive measure, we only allow inputs to be set once. set(this.protocolVersionsOwner.selector, toml.readAddress(".roles.protocolVersionsOwner"));
require(!inputSet, "DeploySuperchainInput: input already set"); set(this.proxyAdminOwner.selector, toml.readAddress(".roles.proxyAdminOwner"));
// 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 are set individually by
// treating the input and output contracts as precompiles and intercepting calls to them.
require(_input.roles.proxyAdminOwner != address(0), "DeploySuperchainInput: null proxyAdminOwner");
require(_input.roles.protocolVersionsOwner != address(0), "DeploySuperchainInput: null protocolVersionsOwner");
require(_input.roles.guardian != address(0), "DeploySuperchainInput: null guardian");
// We now set all values in storage.
inputSet = true;
inputs = _input;
}
function assertInputSet() internal view { // Parse and set other inputs.
require(inputSet, "DeploySuperchainInput: input not set"); set(this.paused.selector, toml.readBool(".paused"));
}
// This exposes the full input data as a struct, and it reverts if the input has not been set. uint256 recVersion = toml.readUint(".recommendedProtocolVersion");
function input() public view returns (Input memory) { set(this.recommendedProtocolVersion.selector, ProtocolVersion.wrap(recVersion));
assertInputSet();
return inputs; uint256 reqVersion = toml.readUint(".requiredProtocolVersion");
set(this.requiredProtocolVersion.selector, ProtocolVersion.wrap(reqVersion));
} }
// Each field of the input struct is exposed via it's own getter method. Using public storage // Each input field is exposed via it's own getter method. Using public storage variables here
// variables here would be more verbose, but would also be more error-prone, as it would // would be less verbose, but would also be more error-prone, as it would require the caller to
// require the caller to remember to check the `inputSet` flag before accessing any of the // validate that each input is set before accessing it. With getter methods, we can automatically
// fields. With getter methods, we can be sure that the input is set before accessing any field. // validate that each input is set before allowing any field to be accessed.
function proxyAdminOwner() public view returns (address) { function proxyAdminOwner() public view returns (address) {
assertInputSet(); require(_proxyAdminOwner != address(0), "DeploySuperchainInput: proxyAdminOwner not set");
return inputs.roles.proxyAdminOwner; return _proxyAdminOwner;
} }
function protocolVersionsOwner() public view returns (address) { function protocolVersionsOwner() public view returns (address) {
assertInputSet(); require(_protocolVersionsOwner != address(0), "DeploySuperchainInput: protocolVersionsOwner not set");
return inputs.roles.protocolVersionsOwner; return _protocolVersionsOwner;
} }
function guardian() public view returns (address) { function guardian() public view returns (address) {
assertInputSet(); require(_guardian != address(0), "DeploySuperchainInput: guardian not set");
return inputs.roles.guardian; return _guardian;
} }
function paused() public view returns (bool) { function paused() public view returns (bool) {
assertInputSet(); return _paused;
return inputs.paused;
} }
function requiredProtocolVersion() public view returns (ProtocolVersion) { function requiredProtocolVersion() public view returns (ProtocolVersion) {
assertInputSet(); require(
return inputs.requiredProtocolVersion; ProtocolVersion.unwrap(_requiredProtocolVersion) != 0,
"DeploySuperchainInput: requiredProtocolVersion not set"
);
return _requiredProtocolVersion;
} }
function recommendedProtocolVersion() public view returns (ProtocolVersion) { function recommendedProtocolVersion() public view returns (ProtocolVersion) {
assertInputSet(); require(
return inputs.recommendedProtocolVersion; ProtocolVersion.unwrap(_recommendedProtocolVersion) != 0,
"DeploySuperchainInput: recommendedProtocolVersion not set"
);
return _recommendedProtocolVersion;
} }
} }
contract DeploySuperchainOutput is CommonBase { contract DeploySuperchainOutput is CommonBase {
// The output struct contains all the output data from the deployment. // All outputs are stored in storage individually, with the same rationale as doing so for
// The fields must be in alphabetical order for vm.parseToml to work. // inputs, and the same pattern is used below to expose the outputs.
struct Output { ProtocolVersions internal _protocolVersionsImpl;
ProtocolVersions protocolVersionsImpl; ProtocolVersions internal _protocolVersionsProxy;
ProtocolVersions protocolVersionsProxy; SuperchainConfig internal _superchainConfigImpl;
SuperchainConfig superchainConfigImpl; SuperchainConfig internal _superchainConfigProxy;
SuperchainConfig superchainConfigProxy; ProxyAdmin internal _superchainProxyAdmin;
ProxyAdmin superchainProxyAdmin;
}
// 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
// to the overall `inputSet` variable. However, we do hold everything in a struct, then
// similarly expose each field via a getter method. This getter method reverts if the output has
// not been set, ensuring that the caller cannot access any output fields until they have been set.
Output internal outputs;
// 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) outputs.superchainProxyAdmin = ProxyAdmin(_address); if (sel == this.superchainProxyAdmin.selector) _superchainProxyAdmin = ProxyAdmin(_address);
else if (sel == this.superchainConfigImpl.selector) outputs.superchainConfigImpl = SuperchainConfig(_address); else if (sel == this.superchainConfigImpl.selector) _superchainConfigImpl = SuperchainConfig(_address);
else if (sel == this.superchainConfigProxy.selector) outputs.superchainConfigProxy = SuperchainConfig(_address); else if (sel == this.superchainConfigProxy.selector) _superchainConfigProxy = SuperchainConfig(_address);
else if (sel == this.protocolVersionsImpl.selector) outputs.protocolVersionsImpl = ProtocolVersions(_address); else if (sel == this.protocolVersionsImpl.selector) _protocolVersionsImpl = ProtocolVersions(_address);
else if (sel == this.protocolVersionsProxy.selector) outputs.protocolVersionsProxy = ProtocolVersions(_address); else if (sel == this.protocolVersionsProxy.selector) _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.
// We fetch the output values using external calls to the getters to verify that all outputs are
// set correctly before writing them to the file.
function writeOutputFile(string memory _outfile) public { function writeOutputFile(string memory _outfile) public {
string memory key = "dso-outfile"; string memory key = "dso-outfile";
vm.serializeAddress(key, "superchainProxyAdmin", address(outputs.superchainProxyAdmin)); vm.serializeAddress(key, "superchainProxyAdmin", address(this.superchainProxyAdmin()));
vm.serializeAddress(key, "superchainConfigImpl", address(outputs.superchainConfigImpl)); vm.serializeAddress(key, "superchainConfigImpl", address(this.superchainConfigImpl()));
vm.serializeAddress(key, "superchainConfigProxy", address(outputs.superchainConfigProxy)); vm.serializeAddress(key, "superchainConfigProxy", address(this.superchainConfigProxy()));
vm.serializeAddress(key, "protocolVersionsImpl", address(outputs.protocolVersionsImpl)); vm.serializeAddress(key, "protocolVersionsImpl", address(this.protocolVersionsImpl()));
string memory out = vm.serializeAddress(key, "protocolVersionsProxy", address(outputs.protocolVersionsProxy)); string memory out = vm.serializeAddress(key, "protocolVersionsProxy", address(this.protocolVersionsProxy()));
vm.writeToml(out, _outfile); vm.writeToml(out, _outfile);
} }
function output() public view returns (Output memory) { // This function can be called to ensure all outputs are correct. Similar to `writeOutputFile`,
return outputs; // it fetches the output values using external calls to the getter methods for safety.
} function checkOutput() public {
function checkOutput() public view {
address[] memory addrs = Solarray.addresses( address[] memory addrs = Solarray.addresses(
address(outputs.superchainProxyAdmin), address(this.superchainProxyAdmin()),
address(outputs.superchainConfigImpl), address(this.superchainConfigImpl()),
address(outputs.superchainConfigProxy), address(this.superchainConfigProxy()),
address(outputs.protocolVersionsImpl), address(this.protocolVersionsImpl()),
address(outputs.protocolVersionsProxy) address(this.protocolVersionsProxy())
); );
DeployUtils.assertValidContractAddresses(addrs); DeployUtils.assertValidContractAddresses(addrs);
// To read the implementations we prank as the zero address due to the proxyCallIfNotAdmin modifier.
vm.startPrank(address(0));
address actualSuperchainConfigImpl = Proxy(payable(address(_superchainConfigProxy))).implementation();
address actualProtocolVersionsImpl = Proxy(payable(address(_protocolVersionsProxy))).implementation();
vm.stopPrank();
require(actualSuperchainConfigImpl == address(_superchainConfigImpl), "100");
require(actualProtocolVersionsImpl == address(_protocolVersionsImpl), "200");
} }
function superchainProxyAdmin() public view returns (ProxyAdmin) { function superchainProxyAdmin() public view returns (ProxyAdmin) {
DeployUtils.assertValidContractAddress(address(outputs.superchainProxyAdmin)); // This does not have to be a contract address, it could be an EOA.
return outputs.superchainProxyAdmin; return _superchainProxyAdmin;
} }
function superchainConfigImpl() public view returns (SuperchainConfig) { function superchainConfigImpl() public view returns (SuperchainConfig) {
DeployUtils.assertValidContractAddress(address(outputs.superchainConfigImpl)); DeployUtils.assertValidContractAddress(address(_superchainConfigImpl));
return outputs.superchainConfigImpl; return _superchainConfigImpl;
} }
function superchainConfigProxy() public view returns (SuperchainConfig) { function superchainConfigProxy() public view returns (SuperchainConfig) {
DeployUtils.assertValidContractAddress(address(outputs.superchainConfigProxy)); DeployUtils.assertValidContractAddress(address(_superchainConfigProxy));
return outputs.superchainConfigProxy; return _superchainConfigProxy;
} }
function protocolVersionsImpl() public view returns (ProtocolVersions) { function protocolVersionsImpl() public view returns (ProtocolVersions) {
DeployUtils.assertValidContractAddress(address(outputs.protocolVersionsImpl)); DeployUtils.assertValidContractAddress(address(_protocolVersionsImpl));
return outputs.protocolVersionsImpl; return _protocolVersionsImpl;
} }
function protocolVersionsProxy() public view returns (ProtocolVersions) { function protocolVersionsProxy() public view returns (ProtocolVersions) {
DeployUtils.assertValidContractAddress(address(outputs.protocolVersionsProxy)); DeployUtils.assertValidContractAddress(address(_protocolVersionsProxy));
return outputs.protocolVersionsProxy; return _protocolVersionsProxy;
} }
} }
...@@ -258,25 +266,16 @@ contract DeploySuperchain is Script { ...@@ -258,25 +266,16 @@ contract DeploySuperchain is Script {
dso.writeOutputFile(_outfile); dso.writeOutputFile(_outfile);
} }
// This entrypoint is for use with Solidity tests, where the input and outputs are structs.
function run(DeploySuperchainInput.Input memory _input) public returns (DeploySuperchainOutput.Output memory) {
// Solidity without file IO, so etch the IO helper contracts.
(DeploySuperchainInput dsi, DeploySuperchainOutput dso) = etchIOContracts();
// Load the input struct into the input contract.
dsi.loadInput(_input);
// Run the deployment script and write outputs to the DeploySuperchainOutput contract.
run(dsi, dso);
// Return the output struct from the output contract.
return dso.output();
}
// 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. // Notice that we do not do any explicit verification here that inputs are set. This is because
require(_dsi.inputSet(), "DeploySuperchain: input not set"); // the verification happens elsewhere:
// - Getter methods on the input contract provide sanity checks that values are set, when applicable.
// - The individual methods below that we use to compose the deployment are responsible for handling
// their own verification.
// This pattern ensures that other deploy scripts that might compose these contracts and
// methods in different ways are still protected from invalid inputs without need to implement
// additional verification logic.
// 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);
...@@ -377,7 +376,11 @@ contract DeploySuperchain is Script { ...@@ -377,7 +376,11 @@ contract DeploySuperchain is Script {
// -------- Utilities -------- // -------- Utilities --------
function etchIOContracts() internal returns (DeploySuperchainInput dsi_, DeploySuperchainOutput dso_) { // This etches the IO contracts into memory so that we can use them in tests. When using file IO
// we don't need to call this directly, as the `DeploySuperchain.run(file, file)` entrypoint
// handles it. But when interacting with the script programmatically (e.g. in a Solidity test),
// this must be called.
function etchIOContracts() public 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);
vm.etch(address(dso_), type(DeploySuperchainOutput).runtimeCode); vm.etch(address(dso_), type(DeploySuperchainOutput).runtimeCode);
...@@ -385,6 +388,7 @@ contract DeploySuperchain is Script { ...@@ -385,6 +388,7 @@ contract DeploySuperchain is Script {
vm.allowCheatcodes(address(dso_)); vm.allowCheatcodes(address(dso_));
} }
// This returns the addresses of the IO contracts for this script.
function getIOContracts() public view returns (DeploySuperchainInput dsi_, DeploySuperchainOutput dso_) { function getIOContracts() public view returns (DeploySuperchainInput dsi_, DeploySuperchainOutput dso_) {
dsi_ = DeploySuperchainInput(DeployUtils.toIOAddress(msg.sender, "optimism.DeploySuperchainInput")); dsi_ = DeploySuperchainInput(DeployUtils.toIOAddress(msg.sender, "optimism.DeploySuperchainInput"));
dso_ = DeploySuperchainOutput(DeployUtils.toIOAddress(msg.sender, "optimism.DeploySuperchainOutput")); dso_ = DeploySuperchainOutput(DeployUtils.toIOAddress(msg.sender, "optimism.DeploySuperchainOutput"));
......
...@@ -321,22 +321,17 @@ contract DeployOPChainOutput_Test is Test { ...@@ -321,22 +321,17 @@ contract DeployOPChainOutput_Test is Test {
// DeploySuperchain and DeployImplementations scripts. // DeploySuperchain and DeployImplementations scripts.
contract DeployOPChain_TestBase is Test { contract DeployOPChain_TestBase is Test {
DeployOPChain deployOPChain; DeployOPChain deployOPChain;
DeployOPChainInput dsi; DeployOPChainInput doi;
DeployOPChainOutput dso; DeployOPChainOutput doo;
// We define a default initial input struct for DeploySuperchain. The other input structs are // We define a default initial input set for DeploySuperchain. The other inputs are dependent
// dependent on the outputs of the previous scripts, so we initialize them here and populate // on the outputs of the previous scripts, so we initialize them in the `setUp` method.
// the null values in the `setUp` method.assert address proxyAdminOwner = makeAddr("defaultProxyAdminOwner");
DeploySuperchainInput.Input deploySuperchainInput = DeploySuperchainInput.Input({ address protocolVersionsOwner = makeAddr("defaultProtocolVersionsOwner");
roles: DeploySuperchainInput.Roles({ address guardian = makeAddr("defaultGuardian");
proxyAdminOwner: makeAddr("defaultProxyAdminOwner"), bool paused = false;
protocolVersionsOwner: makeAddr("defaultProtocolVersionsOwner"), ProtocolVersion requiredProtocolVersion = ProtocolVersion.wrap(1);
guardian: makeAddr("defaultGuardian") ProtocolVersion recommendedProtocolVersion = ProtocolVersion.wrap(2);
}),
paused: false,
requiredProtocolVersion: ProtocolVersion.wrap(1),
recommendedProtocolVersion: ProtocolVersion.wrap(2)
});
DeployImplementationsInput.Input deployImplementationsInput = DeployImplementationsInput.Input({ DeployImplementationsInput.Input deployImplementationsInput = DeployImplementationsInput.Input({
withdrawalDelaySeconds: 100, withdrawalDelaySeconds: 100,
...@@ -372,16 +367,24 @@ contract DeployOPChain_TestBase is Test { ...@@ -372,16 +367,24 @@ contract DeployOPChain_TestBase is Test {
function setUp() public { function setUp() public {
// Initialize deploy scripts. // Initialize deploy scripts.
DeploySuperchain deploySuperchain = new DeploySuperchain(); DeploySuperchain deploySuperchain = new DeploySuperchain();
(DeploySuperchainInput dsi, DeploySuperchainOutput dso) = deploySuperchain.etchIOContracts();
dsi.set(dsi.proxyAdminOwner.selector, proxyAdminOwner);
dsi.set(dsi.protocolVersionsOwner.selector, protocolVersionsOwner);
dsi.set(dsi.guardian.selector, guardian);
dsi.set(dsi.paused.selector, paused);
dsi.set(dsi.requiredProtocolVersion.selector, requiredProtocolVersion);
dsi.set(dsi.recommendedProtocolVersion.selector, recommendedProtocolVersion);
DeployImplementations deployImplementations = new DeployImplementations(); DeployImplementations deployImplementations = new DeployImplementations();
deployOPChain = new DeployOPChain(); deployOPChain = new DeployOPChain();
(dsi, dso) = deployOPChain.getIOContracts(); (doi, doo) = deployOPChain.getIOContracts();
// Deploy the superchain contracts. // Deploy the superchain contracts.
DeploySuperchainOutput.Output memory superchainOutput = deploySuperchain.run(deploySuperchainInput); deploySuperchain.run(dsi, dso);
// Populate the input struct for DeployImplementations based on the output of DeploySuperchain. // Populate the input struct for DeployImplementations based on the output of DeploySuperchain.
deployImplementationsInput.superchainConfigProxy = superchainOutput.superchainConfigProxy; deployImplementationsInput.superchainConfigProxy = dso.superchainConfigProxy();
deployImplementationsInput.protocolVersionsProxy = superchainOutput.protocolVersionsProxy; deployImplementationsInput.protocolVersionsProxy = dso.protocolVersionsProxy();
// Deploy the implementations using the updated DeployImplementations input struct. // Deploy the implementations using the updated DeployImplementations input struct.
deployImplementationsOutput = deployImplementations.run(deployImplementationsInput); deployImplementationsOutput = deployImplementations.run(deployImplementationsInput);
...@@ -414,31 +417,31 @@ contract DeployOPChain_Test is DeployOPChain_TestBase { ...@@ -414,31 +417,31 @@ contract DeployOPChain_Test is DeployOPChain_TestBase {
// TODO Add fault proof contract assertions below once OPSM fully supports them. // TODO Add fault proof contract assertions below once OPSM fully supports them.
// Assert that individual input fields were properly set based on the input struct. // Assert that individual input fields were properly set based on the input struct.
assertEq(_input.roles.opChainProxyAdminOwner, dsi.opChainProxyAdminOwner(), "100"); assertEq(_input.roles.opChainProxyAdminOwner, doi.opChainProxyAdminOwner(), "100");
assertEq(_input.roles.systemConfigOwner, dsi.systemConfigOwner(), "200"); assertEq(_input.roles.systemConfigOwner, doi.systemConfigOwner(), "200");
assertEq(_input.roles.batcher, dsi.batcher(), "300"); assertEq(_input.roles.batcher, doi.batcher(), "300");
assertEq(_input.roles.unsafeBlockSigner, dsi.unsafeBlockSigner(), "400"); assertEq(_input.roles.unsafeBlockSigner, doi.unsafeBlockSigner(), "400");
assertEq(_input.roles.proposer, dsi.proposer(), "500"); assertEq(_input.roles.proposer, doi.proposer(), "500");
assertEq(_input.roles.challenger, dsi.challenger(), "600"); assertEq(_input.roles.challenger, doi.challenger(), "600");
assertEq(_input.basefeeScalar, dsi.basefeeScalar(), "700"); assertEq(_input.basefeeScalar, doi.basefeeScalar(), "700");
assertEq(_input.blobBaseFeeScalar, dsi.blobBaseFeeScalar(), "800"); assertEq(_input.blobBaseFeeScalar, doi.blobBaseFeeScalar(), "800");
assertEq(_input.l2ChainId, dsi.l2ChainId(), "900"); assertEq(_input.l2ChainId, doi.l2ChainId(), "900");
// Assert that individual output fields were properly set based on the output struct. // Assert that individual output fields were properly set based on the output struct.
assertEq(address(output.opChainProxyAdmin), address(dso.opChainProxyAdmin()), "1100"); assertEq(address(output.opChainProxyAdmin), address(doo.opChainProxyAdmin()), "1100");
assertEq(address(output.addressManager), address(dso.addressManager()), "1200"); assertEq(address(output.addressManager), address(doo.addressManager()), "1200");
assertEq(address(output.l1ERC721BridgeProxy), address(dso.l1ERC721BridgeProxy()), "1300"); assertEq(address(output.l1ERC721BridgeProxy), address(doo.l1ERC721BridgeProxy()), "1300");
assertEq(address(output.systemConfigProxy), address(dso.systemConfigProxy()), "1400"); assertEq(address(output.systemConfigProxy), address(doo.systemConfigProxy()), "1400");
assertEq( assertEq(
address(output.optimismMintableERC20FactoryProxy), address(dso.optimismMintableERC20FactoryProxy()), "1500" address(output.optimismMintableERC20FactoryProxy), address(doo.optimismMintableERC20FactoryProxy()), "1500"
); );
assertEq(address(output.l1StandardBridgeProxy), address(dso.l1StandardBridgeProxy()), "1600"); assertEq(address(output.l1StandardBridgeProxy), address(doo.l1StandardBridgeProxy()), "1600");
assertEq(address(output.l1CrossDomainMessengerProxy), address(dso.l1CrossDomainMessengerProxy()), "1700"); assertEq(address(output.l1CrossDomainMessengerProxy), address(doo.l1CrossDomainMessengerProxy()), "1700");
assertEq(address(output.optimismPortalProxy), address(dso.optimismPortalProxy()), "1800"); assertEq(address(output.optimismPortalProxy), address(doo.optimismPortalProxy()), "1800");
// Assert that the full input and output structs were properly set. // Assert that the full input and output structs were properly set.
assertEq(keccak256(abi.encode(_input)), keccak256(abi.encode(DeployOPChainInput(dsi).input())), "1900"); assertEq(keccak256(abi.encode(_input)), keccak256(abi.encode(DeployOPChainInput(doi).input())), "1900");
assertEq(keccak256(abi.encode(output)), keccak256(abi.encode(DeployOPChainOutput(dso).output())), "2000"); assertEq(keccak256(abi.encode(output)), keccak256(abi.encode(DeployOPChainOutput(doo).output())), "2000");
// Assert inputs were properly passed through to the contract initializers. // Assert inputs were properly passed through to the contract initializers.
assertEq(address(output.opChainProxyAdmin.owner()), _input.roles.opChainProxyAdminOwner, "2100"); assertEq(address(output.opChainProxyAdmin.owner()), _input.roles.opChainProxyAdminOwner, "2100");
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test, stdStorage, StdStorage } from "forge-std/Test.sol";
import { stdToml } from "forge-std/StdToml.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";
...@@ -12,84 +13,60 @@ import { DeploySuperchainInput, DeploySuperchain, DeploySuperchainOutput } from ...@@ -12,84 +13,60 @@ import { DeploySuperchainInput, DeploySuperchain, DeploySuperchainOutput } from
contract DeploySuperchainInput_Test is Test { contract DeploySuperchainInput_Test is Test {
DeploySuperchainInput dsi; DeploySuperchainInput dsi;
DeploySuperchainInput.Input input = DeploySuperchainInput.Input({ address proxyAdminOwner = makeAddr("defaultProxyAdminOwner");
roles: DeploySuperchainInput.Roles({ address protocolVersionsOwner = makeAddr("defaultProtocolVersionsOwner");
proxyAdminOwner: makeAddr("defaultProxyAdminOwner"), address guardian = makeAddr("defaultGuardian");
protocolVersionsOwner: makeAddr("defaultProtocolVersionsOwner"), bool paused = false;
guardian: makeAddr("defaultGuardian") ProtocolVersion requiredProtocolVersion = ProtocolVersion.wrap(1);
}), ProtocolVersion recommendedProtocolVersion = ProtocolVersion.wrap(2);
paused: false,
requiredProtocolVersion: ProtocolVersion.wrap(1),
recommendedProtocolVersion: ProtocolVersion.wrap(2)
});
function setUp() public { function setUp() public {
dsi = new DeploySuperchainInput(); 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);
assertLoadInput();
}
function test_loadInputFile_succeeds() public { function test_loadInputFile_succeeds() public {
string memory root = vm.projectRoot(); string memory root = vm.projectRoot();
string memory path = string.concat(root, "/test/fixtures/test-deploy-superchain-in.toml"); string memory path = string.concat(root, "/test/fixtures/test-deploy-superchain-in.toml");
dsi.loadInputFile(path); dsi.loadInputFile(path);
assertLoadInput();
assertEq(proxyAdminOwner, dsi.proxyAdminOwner(), "100");
assertEq(protocolVersionsOwner, dsi.protocolVersionsOwner(), "200");
assertEq(guardian, dsi.guardian(), "300");
assertEq(paused, dsi.paused(), "400");
assertEq(
ProtocolVersion.unwrap(requiredProtocolVersion),
ProtocolVersion.unwrap(dsi.requiredProtocolVersion()),
"500"
);
assertEq(
ProtocolVersion.unwrap(recommendedProtocolVersion),
ProtocolVersion.unwrap(dsi.recommendedProtocolVersion()),
"600"
);
} }
function test_getters_whenNotSet_revert() public { function test_getters_whenNotSet_revert() public {
bytes memory expectedErr = "DeploySuperchainInput: input not set"; vm.expectRevert("DeploySuperchainInput: proxyAdminOwner not set");
vm.expectRevert(expectedErr);
dsi.proxyAdminOwner(); dsi.proxyAdminOwner();
vm.expectRevert(expectedErr); vm.expectRevert("DeploySuperchainInput: protocolVersionsOwner not set");
dsi.protocolVersionsOwner(); dsi.protocolVersionsOwner();
vm.expectRevert(expectedErr); vm.expectRevert("DeploySuperchainInput: guardian not set");
dsi.guardian(); dsi.guardian();
vm.expectRevert(expectedErr); vm.expectRevert("DeploySuperchainInput: requiredProtocolVersion not set");
dsi.paused();
vm.expectRevert(expectedErr);
dsi.requiredProtocolVersion(); dsi.requiredProtocolVersion();
vm.expectRevert(expectedErr); vm.expectRevert("DeploySuperchainInput: recommendedProtocolVersion not set");
dsi.recommendedProtocolVersion(); dsi.recommendedProtocolVersion();
} }
function assertLoadInput() internal view {
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");
}
} }
contract DeploySuperchainOutput_Test is Test { contract DeploySuperchainOutput_Test is Test {
using stdToml for string;
DeploySuperchainOutput dso; DeploySuperchainOutput dso;
function setUp() public { function setUp() public {
...@@ -100,55 +77,45 @@ contract DeploySuperchainOutput_Test is Test { ...@@ -100,55 +77,45 @@ contract DeploySuperchainOutput_Test is Test {
// We don't fuzz, because we need code at the address, and we can't etch code if the fuzzer // 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. // 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. // Hardcoding a concrete set of valid addresses is simpler and still sufficient.
DeploySuperchainOutput.Output memory output = DeploySuperchainOutput.Output({ ProxyAdmin superchainProxyAdmin = ProxyAdmin(makeAddr("superchainProxyAdmin"));
superchainProxyAdmin: ProxyAdmin(makeAddr("superchainProxyAdmin")), SuperchainConfig superchainConfigImpl = SuperchainConfig(makeAddr("superchainConfigImpl"));
superchainConfigImpl: SuperchainConfig(makeAddr("superchainConfigImpl")), SuperchainConfig superchainConfigProxy = SuperchainConfig(makeAddr("superchainConfigProxy"));
superchainConfigProxy: SuperchainConfig(makeAddr("superchainConfigProxy")), ProtocolVersions protocolVersionsImpl = ProtocolVersions(makeAddr("protocolVersionsImpl"));
protocolVersionsImpl: ProtocolVersions(makeAddr("protocolVersionsImpl")), ProtocolVersions protocolVersionsProxy = ProtocolVersions(makeAddr("protocolVersionsProxy"));
protocolVersionsProxy: ProtocolVersions(makeAddr("protocolVersionsProxy"))
});
// Ensure each address has code, since these are expected to be contracts. // Ensure each address has code, since these are expected to be contracts.
vm.etch(address(output.superchainProxyAdmin), hex"01"); vm.etch(address(superchainProxyAdmin), hex"01");
vm.etch(address(output.superchainConfigImpl), hex"01"); vm.etch(address(superchainConfigImpl), hex"01");
vm.etch(address(output.superchainConfigProxy), hex"01"); vm.etch(address(superchainConfigProxy), hex"01");
vm.etch(address(output.protocolVersionsImpl), hex"01"); vm.etch(address(protocolVersionsImpl), hex"01");
vm.etch(address(output.protocolVersionsProxy), hex"01"); vm.etch(address(protocolVersionsProxy), hex"01");
// Set the output data. // Set the output data.
dso.set(dso.superchainProxyAdmin.selector, address(output.superchainProxyAdmin)); dso.set(dso.superchainProxyAdmin.selector, address(superchainProxyAdmin));
dso.set(dso.superchainConfigImpl.selector, address(output.superchainConfigImpl)); dso.set(dso.superchainConfigImpl.selector, address(superchainConfigImpl));
dso.set(dso.superchainConfigProxy.selector, address(output.superchainConfigProxy)); dso.set(dso.superchainConfigProxy.selector, address(superchainConfigProxy));
dso.set(dso.protocolVersionsImpl.selector, address(output.protocolVersionsImpl)); dso.set(dso.protocolVersionsImpl.selector, address(protocolVersionsImpl));
dso.set(dso.protocolVersionsProxy.selector, address(output.protocolVersionsProxy)); dso.set(dso.protocolVersionsProxy.selector, address(protocolVersionsProxy));
// Compare the test output struct to the getter methods. // Compare the test data to the getter methods.
assertEq(address(output.superchainProxyAdmin), address(dso.superchainProxyAdmin()), "100"); assertEq(address(superchainProxyAdmin), address(dso.superchainProxyAdmin()), "100");
assertEq(address(output.superchainConfigImpl), address(dso.superchainConfigImpl()), "200"); assertEq(address(superchainConfigImpl), address(dso.superchainConfigImpl()), "200");
assertEq(address(output.superchainConfigProxy), address(dso.superchainConfigProxy()), "300"); assertEq(address(superchainConfigProxy), address(dso.superchainConfigProxy()), "300");
assertEq(address(output.protocolVersionsImpl), address(dso.protocolVersionsImpl()), "400"); assertEq(address(protocolVersionsImpl), address(dso.protocolVersionsImpl()), "400");
assertEq(address(output.protocolVersionsProxy), address(dso.protocolVersionsProxy()), "500"); assertEq(address(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 { function test_getters_whenNotSet_revert() public {
bytes memory expectedErr = "DeployUtils: zero address"; vm.expectRevert("DeployUtils: zero address");
vm.expectRevert(expectedErr);
dso.superchainProxyAdmin();
vm.expectRevert(expectedErr);
dso.superchainConfigImpl(); dso.superchainConfigImpl();
vm.expectRevert(expectedErr); vm.expectRevert("DeployUtils: zero address");
dso.superchainConfigProxy(); dso.superchainConfigProxy();
vm.expectRevert(expectedErr); vm.expectRevert("DeployUtils: zero address");
dso.protocolVersionsImpl(); dso.protocolVersionsImpl();
vm.expectRevert(expectedErr); vm.expectRevert("DeployUtils: zero address");
dso.protocolVersionsProxy(); dso.protocolVersionsProxy();
} }
...@@ -156,10 +123,6 @@ contract DeploySuperchainOutput_Test is Test { ...@@ -156,10 +123,6 @@ contract DeploySuperchainOutput_Test is Test {
address emptyAddr = makeAddr("emptyAddr"); address emptyAddr = makeAddr("emptyAddr");
bytes memory expectedErr = bytes(string.concat("DeployUtils: no code at ", vm.toString(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); dso.set(dso.superchainConfigImpl.selector, emptyAddr);
vm.expectRevert(expectedErr); vm.expectRevert(expectedErr);
dso.superchainConfigImpl(); dso.superchainConfigImpl();
...@@ -183,14 +146,26 @@ contract DeploySuperchainOutput_Test is Test { ...@@ -183,14 +146,26 @@ contract DeploySuperchainOutput_Test is Test {
// Use the expected data from the test fixture. // Use the expected data from the test fixture.
string memory expOutPath = string.concat(root, "/test/fixtures/test-deploy-superchain-out.toml"); string memory expOutPath = string.concat(root, "/test/fixtures/test-deploy-superchain-out.toml");
string memory expOutToml = vm.readFile(expOutPath); string memory expOutToml = vm.readFile(expOutPath);
bytes memory expOutData = vm.parseToml(expOutToml);
DeploySuperchainOutput.Output memory expOutput = abi.decode(expOutData, (DeploySuperchainOutput.Output));
dso.set(dso.superchainProxyAdmin.selector, address(expOutput.superchainProxyAdmin)); // Parse each field of expOutToml individually.
dso.set(dso.superchainConfigImpl.selector, address(expOutput.superchainConfigImpl)); ProxyAdmin expSuperchainProxyAdmin = ProxyAdmin(expOutToml.readAddress(".superchainProxyAdmin"));
dso.set(dso.superchainConfigProxy.selector, address(expOutput.superchainConfigProxy)); SuperchainConfig expSuperchainConfigImpl = SuperchainConfig(expOutToml.readAddress(".superchainConfigImpl"));
dso.set(dso.protocolVersionsImpl.selector, address(expOutput.protocolVersionsImpl)); SuperchainConfig expSuperchainConfigProxy = SuperchainConfig(expOutToml.readAddress(".superchainConfigProxy"));
dso.set(dso.protocolVersionsProxy.selector, address(expOutput.protocolVersionsProxy)); ProtocolVersions expProtocolVersionsImpl = ProtocolVersions(expOutToml.readAddress(".protocolVersionsImpl"));
ProtocolVersions expProtocolVersionsProxy = ProtocolVersions(expOutToml.readAddress(".protocolVersionsProxy"));
// Etch code at each address so the code checks pass when settings values.
vm.etch(address(expSuperchainConfigImpl), hex"01");
vm.etch(address(expSuperchainConfigProxy), hex"01");
vm.etch(address(expProtocolVersionsImpl), hex"01");
vm.etch(address(expProtocolVersionsProxy), hex"01");
dso.set(dso.superchainProxyAdmin.selector, address(expSuperchainProxyAdmin));
dso.set(dso.superchainProxyAdmin.selector, address(expSuperchainProxyAdmin));
dso.set(dso.superchainConfigImpl.selector, address(expSuperchainConfigImpl));
dso.set(dso.superchainConfigProxy.selector, address(expSuperchainConfigProxy));
dso.set(dso.protocolVersionsImpl.selector, address(expProtocolVersionsImpl));
dso.set(dso.protocolVersionsProxy.selector, address(expProtocolVersionsProxy));
string memory actOutPath = string.concat(root, "/.testdata/test-deploy-superchain-output.toml"); string memory actOutPath = string.concat(root, "/.testdata/test-deploy-superchain-output.toml");
dso.writeOutputFile(actOutPath); dso.writeOutputFile(actOutPath);
...@@ -204,75 +179,73 @@ contract DeploySuperchainOutput_Test is Test { ...@@ -204,75 +179,73 @@ contract DeploySuperchainOutput_Test is Test {
} }
contract DeploySuperchain_Test is Test { contract DeploySuperchain_Test is Test {
using stdStorage for StdStorage;
DeploySuperchain deploySuperchain; DeploySuperchain deploySuperchain;
DeploySuperchainInput dsi; DeploySuperchainInput dsi;
DeploySuperchainOutput dso; DeploySuperchainOutput dso;
// Define a default input struct for testing. // Define default input variables for testing.
DeploySuperchainInput.Input input = DeploySuperchainInput.Input({ address defaultProxyAdminOwner = makeAddr("defaultProxyAdminOwner");
roles: DeploySuperchainInput.Roles({ address defaultProtocolVersionsOwner = makeAddr("defaultProtocolVersionsOwner");
proxyAdminOwner: makeAddr("defaultProxyAdminOwner"), address defaultGuardian = makeAddr("defaultGuardian");
protocolVersionsOwner: makeAddr("defaultProtocolVersionsOwner"), bool defaultPaused = false;
guardian: makeAddr("defaultGuardian") ProtocolVersion defaultRequiredProtocolVersion = ProtocolVersion.wrap(1);
}), ProtocolVersion defaultRecommendedProtocolVersion = ProtocolVersion.wrap(2);
paused: false,
requiredProtocolVersion: ProtocolVersion.wrap(1),
recommendedProtocolVersion: ProtocolVersion.wrap(2)
});
function setUp() public { function setUp() public {
deploySuperchain = new DeploySuperchain(); deploySuperchain = new DeploySuperchain();
(dsi, dso) = deploySuperchain.getIOContracts(); (dsi, dso) = deploySuperchain.etchIOContracts();
} }
function unwrap(ProtocolVersion _pv) internal pure returns (uint256) { function unwrap(ProtocolVersion _pv) internal pure returns (uint256) {
return ProtocolVersion.unwrap(_pv); return ProtocolVersion.unwrap(_pv);
} }
function test_run_memory_succeeds(DeploySuperchainInput.Input memory _input) public { function hash(bytes32 _seed, uint256 _i) internal pure returns (bytes32) {
vm.assume(_input.roles.proxyAdminOwner != address(0)); return keccak256(abi.encode(_seed, _i));
vm.assume(_input.roles.protocolVersionsOwner != address(0)); }
vm.assume(_input.roles.guardian != address(0));
DeploySuperchainOutput.Output memory output = deploySuperchain.run(_input);
// Assert that individual input fields were properly set based on the input struct.
assertEq(_input.roles.proxyAdminOwner, dsi.proxyAdminOwner(), "100");
assertEq(_input.roles.protocolVersionsOwner, dsi.protocolVersionsOwner(), "200");
assertEq(_input.roles.guardian, dsi.guardian(), "300");
assertEq(_input.paused, dsi.paused(), "400");
assertEq(unwrap(_input.requiredProtocolVersion), unwrap(dsi.requiredProtocolVersion()), "500");
assertEq(unwrap(_input.recommendedProtocolVersion), unwrap(dsi.recommendedProtocolVersion()), "600");
// Assert that individual output fields were properly set based on the output struct.
assertEq(address(output.superchainProxyAdmin), address(dso.superchainProxyAdmin()), "700");
assertEq(address(output.superchainConfigImpl), address(dso.superchainConfigImpl()), "800");
assertEq(address(output.superchainConfigProxy), address(dso.superchainConfigProxy()), "900");
assertEq(address(output.protocolVersionsImpl), address(dso.protocolVersionsImpl()), "1000");
assertEq(address(output.protocolVersionsProxy), address(dso.protocolVersionsProxy()), "1100");
// Assert that the full input and output structs were properly set. function test_run_memory_succeeds(bytes32 _seed) public {
assertEq(keccak256(abi.encode(_input)), keccak256(abi.encode(DeploySuperchainInput(dsi).input())), "1200"); // Generate random input values from the seed. This doesn't give us the benefit of the forge
assertEq(keccak256(abi.encode(output)), keccak256(abi.encode(DeploySuperchainOutput(dso).output())), "1300"); // fuzzer's dictionary, but that's ok because we are just testing that values are set and
// passed correctly.
address proxyAdminOwner = address(uint160(uint256(hash(_seed, 0))));
address protocolVersionsOwner = address(uint160(uint256(hash(_seed, 1))));
address guardian = address(uint160(uint256(hash(_seed, 2))));
bool paused = bool(uint8(uint256(hash(_seed, 3))) % 2 == 0);
ProtocolVersion requiredProtocolVersion = ProtocolVersion.wrap(uint256(hash(_seed, 4)));
ProtocolVersion recommendedProtocolVersion = ProtocolVersion.wrap(uint256(hash(_seed, 5)));
// Set the input values on the input contract.
dsi.set(dsi.proxyAdminOwner.selector, proxyAdminOwner);
dsi.set(dsi.protocolVersionsOwner.selector, protocolVersionsOwner);
dsi.set(dsi.guardian.selector, guardian);
dsi.set(dsi.paused.selector, paused);
dsi.set(dsi.requiredProtocolVersion.selector, requiredProtocolVersion);
dsi.set(dsi.recommendedProtocolVersion.selector, recommendedProtocolVersion);
// Run the deployment script.
deploySuperchain.run(dsi, dso);
// Assert inputs were properly passed through to the contract initializers. // Assert inputs were properly passed through to the contract initializers.
assertEq(address(output.superchainProxyAdmin.owner()), _input.roles.proxyAdminOwner, "1400"); assertEq(address(dso.superchainProxyAdmin().owner()), proxyAdminOwner, "100");
assertEq(address(output.protocolVersionsProxy.owner()), _input.roles.protocolVersionsOwner, "1500"); assertEq(address(dso.protocolVersionsProxy().owner()), protocolVersionsOwner, "200");
assertEq(address(output.superchainConfigProxy.guardian()), _input.roles.guardian, "1600"); assertEq(address(dso.superchainConfigProxy().guardian()), guardian, "300");
assertEq(output.superchainConfigProxy.paused(), _input.paused, "1700"); assertEq(dso.superchainConfigProxy().paused(), paused, "400");
assertEq(unwrap(output.protocolVersionsProxy.required()), unwrap(_input.requiredProtocolVersion), "1800"); assertEq(unwrap(dso.protocolVersionsProxy().required()), unwrap(requiredProtocolVersion), "500");
assertEq(unwrap(output.protocolVersionsProxy.recommended()), unwrap(_input.recommendedProtocolVersion), "1900"); assertEq(unwrap(dso.protocolVersionsProxy().recommended()), unwrap(recommendedProtocolVersion), "600");
// Architecture assertions. // Architecture assertions.
// We prank as the zero address due to the Proxy's `proxyCallIfNotAdmin` modifier. // We prank as the zero address due to the Proxy's `proxyCallIfNotAdmin` modifier.
Proxy superchainConfigProxy = Proxy(payable(address(output.superchainConfigProxy))); Proxy superchainConfigProxy = Proxy(payable(address(dso.superchainConfigProxy())));
Proxy protocolVersionsProxy = Proxy(payable(address(output.protocolVersionsProxy))); Proxy protocolVersionsProxy = Proxy(payable(address(dso.protocolVersionsProxy())));
vm.startPrank(address(0)); vm.startPrank(address(0));
assertEq(superchainConfigProxy.implementation(), address(output.superchainConfigImpl), "900"); assertEq(superchainConfigProxy.implementation(), address(dso.superchainConfigImpl()), "700");
assertEq(protocolVersionsProxy.implementation(), address(output.protocolVersionsImpl), "1000"); assertEq(protocolVersionsProxy.implementation(), address(dso.protocolVersionsImpl()), "800");
assertEq(superchainConfigProxy.admin(), protocolVersionsProxy.admin(), "1100"); assertEq(superchainConfigProxy.admin(), protocolVersionsProxy.admin(), "900");
assertEq(superchainConfigProxy.admin(), address(output.superchainProxyAdmin), "1200"); assertEq(superchainConfigProxy.admin(), address(dso.superchainProxyAdmin()), "1000");
vm.stopPrank(); vm.stopPrank();
// Ensure that `checkOutput` passes. This is called by the `run` function during execution, // Ensure that `checkOutput` passes. This is called by the `run` function during execution,
...@@ -289,28 +262,54 @@ contract DeploySuperchain_Test is Test { ...@@ -289,28 +262,54 @@ contract DeploySuperchain_Test is Test {
string memory actOutToml = vm.readFile(outpath); string memory actOutToml = vm.readFile(outpath);
string memory expOutToml = vm.readFile(string.concat(root, "/test/fixtures/test-deploy-superchain-out.toml")); string memory expOutToml = vm.readFile(string.concat(root, "/test/fixtures/test-deploy-superchain-out.toml"));
// Clean up before asserting so that we don't leave any files behind. // Clean up before asserting so that we don't leave any files behind.
vm.removeFile(outpath); vm.removeFile(outpath);
assertEq(expOutToml, actOutToml); assertEq(expOutToml, actOutToml);
} }
function test_run_ZeroAddressRoleInput_reverts() public { function test_run_NullInput_reverts() public {
// Snapshot the state so we can revert to the default `input` struct between assertions. // Set default values for all inputs.
uint256 snapshotId = vm.snapshot(); dsi.set(dsi.proxyAdminOwner.selector, defaultProxyAdminOwner);
dsi.set(dsi.protocolVersionsOwner.selector, defaultProtocolVersionsOwner);
// Assert over each role being set to the zero address. dsi.set(dsi.guardian.selector, defaultGuardian);
input.roles.proxyAdminOwner = address(0); dsi.set(dsi.paused.selector, defaultPaused);
vm.expectRevert("DeploySuperchainInput: null proxyAdminOwner"); dsi.set(dsi.requiredProtocolVersion.selector, defaultRequiredProtocolVersion);
deploySuperchain.run(input); dsi.set(dsi.recommendedProtocolVersion.selector, defaultRecommendedProtocolVersion);
vm.revertTo(snapshotId); // Assert over each role being set to the zero address. We aren't allowed to use the setter
input.roles.protocolVersionsOwner = address(0); // methods to set the zero address, so we use StdStorage. We can't use the `checked_write`
vm.expectRevert("DeploySuperchainInput: null protocolVersionsOwner"); // method, because it does a final call to test that the value was set correctly, but for us
deploySuperchain.run(input); // that would revert. Therefore we use StdStorage to find the slot, then we write to it.
uint256 slot = zeroOutSlotForSelector(dsi.proxyAdminOwner.selector);
vm.expectRevert("DeploySuperchainInput: proxyAdminOwner not set");
deploySuperchain.run(dsi, dso);
vm.store(address(dsi), bytes32(slot), bytes32(uint256(uint160(defaultProxyAdminOwner)))); // Restore the value
// we just tested.
slot = zeroOutSlotForSelector(dsi.protocolVersionsOwner.selector);
vm.expectRevert("DeploySuperchainInput: protocolVersionsOwner not set");
deploySuperchain.run(dsi, dso);
vm.store(address(dsi), bytes32(slot), bytes32(uint256(uint160(defaultProtocolVersionsOwner))));
slot = zeroOutSlotForSelector(dsi.guardian.selector);
vm.expectRevert("DeploySuperchainInput: guardian not set");
deploySuperchain.run(dsi, dso);
vm.store(address(dsi), bytes32(slot), bytes32(uint256(uint160(defaultGuardian))));
slot = zeroOutSlotForSelector(dsi.requiredProtocolVersion.selector);
vm.expectRevert("DeploySuperchainInput: requiredProtocolVersion not set");
deploySuperchain.run(dsi, dso);
vm.store(address(dsi), bytes32(slot), bytes32(unwrap(defaultRequiredProtocolVersion)));
slot = zeroOutSlotForSelector(dsi.recommendedProtocolVersion.selector);
vm.expectRevert("DeploySuperchainInput: recommendedProtocolVersion not set");
deploySuperchain.run(dsi, dso);
vm.store(address(dsi), bytes32(slot), bytes32(unwrap(defaultRecommendedProtocolVersion)));
}
vm.revertTo(snapshotId); function zeroOutSlotForSelector(bytes4 _selector) internal returns (uint256 slot_) {
input.roles.guardian = address(0); slot_ = stdstore.enable_packed_slots().target(address(dsi)).sig(_selector).find();
vm.expectRevert("DeploySuperchainInput: null guardian"); vm.store(address(dsi), bytes32(slot_), bytes32(0));
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