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;
import { Script } from "forge-std/Script.sol";
import { CommonBase } from "forge-std/Base.sol";
import { stdToml } from "forge-std/StdToml.sol";
import { SuperchainConfig } from "src/L1/SuperchainConfig.sol";
import { ProtocolVersions, ProtocolVersion } from "src/L1/ProtocolVersions.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:
// 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.
//
// 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";
//
// 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
// 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
// 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";
// Additionally, we intentionally use "Input" and "Output" terminology to clearly distinguish these
// scripts from the existing ones that use the "Config" and "Artifacts" terminology.
contract DeploySuperchainInput is CommonBase {
// The input struct contains all the input data required for the deployment.
// The fields must be in alphabetical order for vm.parseToml to work.
struct Input {
bool paused;
ProtocolVersion recommendedProtocolVersion;
ProtocolVersion requiredProtocolVersion;
Roles roles;
using stdToml for string;
// All inputs are set in storage individually. We put any roles first, followed by the remaining
// inputs. Inputs are internal and prefixed with an underscore, because we will expose a getter
// method that returns the input value. We use a getter method to allow us to make assertions on
// the input to ensure it's valid before returning it. We also intentionally do not use a struct
// 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 {
address guardian;
address protocolVersionsOwner;
address proxyAdminOwner;
function set(bytes4 _sel, bool _value) public {
if (_sel == this.paused.selector) _paused = _value;
else revert("DeploySuperchainInput: unknown selector");
}
// This flag tells us if all inputs have been set. An `input()` getter method that returns all
// inputs reverts if this flag is false. This ensures the deploy script cannot proceed until all
// inputs are validated and set.
bool public inputSet = false;
// 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;
function set(bytes4 _sel, ProtocolVersion _value) public {
require(ProtocolVersion.unwrap(_value) != 0, "DeploySuperchainInput: cannot set null protocol version");
if (_sel == this.recommendedProtocolVersion.selector) _recommendedProtocolVersion = _value;
else if (_sel == this.requiredProtocolVersion.selector) _requiredProtocolVersion = _value;
else revert("DeploySuperchainInput: unknown selector");
}
// 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 {
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.
function loadInput(Input memory _input) public {
// As a defensive measure, we only allow inputs to be set once.
require(!inputSet, "DeploySuperchainInput: input already set");
// 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;
}
// Parse and set role inputs.
set(this.guardian.selector, toml.readAddress(".roles.guardian"));
set(this.protocolVersionsOwner.selector, toml.readAddress(".roles.protocolVersionsOwner"));
set(this.proxyAdminOwner.selector, toml.readAddress(".roles.proxyAdminOwner"));
function assertInputSet() internal view {
require(inputSet, "DeploySuperchainInput: input not set");
}
// Parse and set other inputs.
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.
function input() public view returns (Input memory) {
assertInputSet();
return inputs;
uint256 recVersion = toml.readUint(".recommendedProtocolVersion");
set(this.recommendedProtocolVersion.selector, ProtocolVersion.wrap(recVersion));
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
// 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.
// Each input field is exposed via it's own getter method. Using public storage variables here
// would be less verbose, but would also be more error-prone, as it would require the caller to
// validate that each input is set before accessing it. With getter methods, we can automatically
// validate that each input is set before allowing any field to be accessed.
function proxyAdminOwner() public view returns (address) {
assertInputSet();
return inputs.roles.proxyAdminOwner;
require(_proxyAdminOwner != address(0), "DeploySuperchainInput: proxyAdminOwner not set");
return _proxyAdminOwner;
}
function protocolVersionsOwner() public view returns (address) {
assertInputSet();
return inputs.roles.protocolVersionsOwner;
require(_protocolVersionsOwner != address(0), "DeploySuperchainInput: protocolVersionsOwner not set");
return _protocolVersionsOwner;
}
function guardian() public view returns (address) {
assertInputSet();
return inputs.roles.guardian;
require(_guardian != address(0), "DeploySuperchainInput: guardian not set");
return _guardian;
}
function paused() public view returns (bool) {
assertInputSet();
return inputs.paused;
return _paused;
}
function requiredProtocolVersion() public view returns (ProtocolVersion) {
assertInputSet();
return inputs.requiredProtocolVersion;
require(
ProtocolVersion.unwrap(_requiredProtocolVersion) != 0,
"DeploySuperchainInput: requiredProtocolVersion not set"
);
return _requiredProtocolVersion;
}
function recommendedProtocolVersion() public view returns (ProtocolVersion) {
assertInputSet();
return inputs.recommendedProtocolVersion;
require(
ProtocolVersion.unwrap(_recommendedProtocolVersion) != 0,
"DeploySuperchainInput: recommendedProtocolVersion not set"
);
return _recommendedProtocolVersion;
}
}
contract DeploySuperchainOutput is CommonBase {
// The output struct contains all the output data from the deployment.
// The fields must be in alphabetical order for vm.parseToml to work.
struct Output {
ProtocolVersions protocolVersionsImpl;
ProtocolVersions protocolVersionsProxy;
SuperchainConfig superchainConfigImpl;
SuperchainConfig superchainConfigProxy;
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;
// All outputs are stored in storage individually, with the same rationale as doing so for
// inputs, and the same pattern is used below to expose the outputs.
ProtocolVersions internal _protocolVersionsImpl;
ProtocolVersions internal _protocolVersionsProxy;
SuperchainConfig internal _superchainConfigImpl;
SuperchainConfig internal _superchainConfigProxy;
ProxyAdmin internal _superchainProxyAdmin;
// This method lets each field be set individually. The selector of an output's getter method
// is used to determine which field to set.
function set(bytes4 sel, address _address) public {
if (sel == this.superchainProxyAdmin.selector) outputs.superchainProxyAdmin = ProxyAdmin(_address);
else if (sel == this.superchainConfigImpl.selector) outputs.superchainConfigImpl = SuperchainConfig(_address);
else if (sel == this.superchainConfigProxy.selector) outputs.superchainConfigProxy = SuperchainConfig(_address);
else if (sel == this.protocolVersionsImpl.selector) outputs.protocolVersionsImpl = ProtocolVersions(_address);
else if (sel == this.protocolVersionsProxy.selector) outputs.protocolVersionsProxy = ProtocolVersions(_address);
if (sel == this.superchainProxyAdmin.selector) _superchainProxyAdmin = ProxyAdmin(_address);
else if (sel == this.superchainConfigImpl.selector) _superchainConfigImpl = SuperchainConfig(_address);
else if (sel == this.superchainConfigProxy.selector) _superchainConfigProxy = SuperchainConfig(_address);
else if (sel == this.protocolVersionsImpl.selector) _protocolVersionsImpl = ProtocolVersions(_address);
else if (sel == this.protocolVersionsProxy.selector) _protocolVersionsProxy = ProtocolVersions(_address);
else revert("DeploySuperchainOutput: unknown selector");
}
// 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 {
string memory key = "dso-outfile";
vm.serializeAddress(key, "superchainProxyAdmin", address(outputs.superchainProxyAdmin));
vm.serializeAddress(key, "superchainConfigImpl", address(outputs.superchainConfigImpl));
vm.serializeAddress(key, "superchainConfigProxy", address(outputs.superchainConfigProxy));
vm.serializeAddress(key, "protocolVersionsImpl", address(outputs.protocolVersionsImpl));
string memory out = vm.serializeAddress(key, "protocolVersionsProxy", address(outputs.protocolVersionsProxy));
vm.serializeAddress(key, "superchainProxyAdmin", address(this.superchainProxyAdmin()));
vm.serializeAddress(key, "superchainConfigImpl", address(this.superchainConfigImpl()));
vm.serializeAddress(key, "superchainConfigProxy", address(this.superchainConfigProxy()));
vm.serializeAddress(key, "protocolVersionsImpl", address(this.protocolVersionsImpl()));
string memory out = vm.serializeAddress(key, "protocolVersionsProxy", address(this.protocolVersionsProxy()));
vm.writeToml(out, _outfile);
}
function output() public view returns (Output memory) {
return outputs;
}
function checkOutput() public view {
// This function can be called to ensure all outputs are correct. Similar to `writeOutputFile`,
// it fetches the output values using external calls to the getter methods for safety.
function checkOutput() public {
address[] memory addrs = Solarray.addresses(
address(outputs.superchainProxyAdmin),
address(outputs.superchainConfigImpl),
address(outputs.superchainConfigProxy),
address(outputs.protocolVersionsImpl),
address(outputs.protocolVersionsProxy)
address(this.superchainProxyAdmin()),
address(this.superchainConfigImpl()),
address(this.superchainConfigProxy()),
address(this.protocolVersionsImpl()),
address(this.protocolVersionsProxy())
);
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) {
DeployUtils.assertValidContractAddress(address(outputs.superchainProxyAdmin));
return outputs.superchainProxyAdmin;
// This does not have to be a contract address, it could be an EOA.
return _superchainProxyAdmin;
}
function superchainConfigImpl() public view returns (SuperchainConfig) {
DeployUtils.assertValidContractAddress(address(outputs.superchainConfigImpl));
return outputs.superchainConfigImpl;
DeployUtils.assertValidContractAddress(address(_superchainConfigImpl));
return _superchainConfigImpl;
}
function superchainConfigProxy() public view returns (SuperchainConfig) {
DeployUtils.assertValidContractAddress(address(outputs.superchainConfigProxy));
return outputs.superchainConfigProxy;
DeployUtils.assertValidContractAddress(address(_superchainConfigProxy));
return _superchainConfigProxy;
}
function protocolVersionsImpl() public view returns (ProtocolVersions) {
DeployUtils.assertValidContractAddress(address(outputs.protocolVersionsImpl));
return outputs.protocolVersionsImpl;
DeployUtils.assertValidContractAddress(address(_protocolVersionsImpl));
return _protocolVersionsImpl;
}
function protocolVersionsProxy() public view returns (ProtocolVersions) {
DeployUtils.assertValidContractAddress(address(outputs.protocolVersionsProxy));
return outputs.protocolVersionsProxy;
DeployUtils.assertValidContractAddress(address(_protocolVersionsProxy));
return _protocolVersionsProxy;
}
}
......@@ -258,25 +266,16 @@ contract DeploySuperchain is Script {
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.
function run(DeploySuperchainInput _dsi, DeploySuperchainOutput _dso) public {
// Verify that the input contract has been set.
require(_dsi.inputSet(), "DeploySuperchain: input not set");
// Notice that we do not do any explicit verification here that inputs are set. This is because
// 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.
deploySuperchainProxyAdmin(_dsi, _dso);
......@@ -377,7 +376,11 @@ contract DeploySuperchain is Script {
// -------- 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();
vm.etch(address(dsi_), type(DeploySuperchainInput).runtimeCode);
vm.etch(address(dso_), type(DeploySuperchainOutput).runtimeCode);
......@@ -385,6 +388,7 @@ contract DeploySuperchain is Script {
vm.allowCheatcodes(address(dso_));
}
// This returns the addresses of the IO contracts for this script.
function getIOContracts() public view returns (DeploySuperchainInput dsi_, DeploySuperchainOutput dso_) {
dsi_ = DeploySuperchainInput(DeployUtils.toIOAddress(msg.sender, "optimism.DeploySuperchainInput"));
dso_ = DeploySuperchainOutput(DeployUtils.toIOAddress(msg.sender, "optimism.DeploySuperchainOutput"));
......
......@@ -321,22 +321,17 @@ contract DeployOPChainOutput_Test is Test {
// DeploySuperchain and DeployImplementations scripts.
contract DeployOPChain_TestBase is Test {
DeployOPChain deployOPChain;
DeployOPChainInput dsi;
DeployOPChainOutput dso;
// We define a default initial input struct for DeploySuperchain. The other input structs are
// dependent on the outputs of the previous scripts, so we initialize them here and populate
// the null values in the `setUp` method.assert
DeploySuperchainInput.Input deploySuperchainInput = DeploySuperchainInput.Input({
roles: DeploySuperchainInput.Roles({
proxyAdminOwner: makeAddr("defaultProxyAdminOwner"),
protocolVersionsOwner: makeAddr("defaultProtocolVersionsOwner"),
guardian: makeAddr("defaultGuardian")
}),
paused: false,
requiredProtocolVersion: ProtocolVersion.wrap(1),
recommendedProtocolVersion: ProtocolVersion.wrap(2)
});
DeployOPChainInput doi;
DeployOPChainOutput doo;
// We define a default initial input set for DeploySuperchain. The other inputs are dependent
// on the outputs of the previous scripts, so we initialize them in the `setUp` method.
address proxyAdminOwner = makeAddr("defaultProxyAdminOwner");
address protocolVersionsOwner = makeAddr("defaultProtocolVersionsOwner");
address guardian = makeAddr("defaultGuardian");
bool paused = false;
ProtocolVersion requiredProtocolVersion = ProtocolVersion.wrap(1);
ProtocolVersion recommendedProtocolVersion = ProtocolVersion.wrap(2);
DeployImplementationsInput.Input deployImplementationsInput = DeployImplementationsInput.Input({
withdrawalDelaySeconds: 100,
......@@ -372,16 +367,24 @@ contract DeployOPChain_TestBase is Test {
function setUp() public {
// Initialize deploy scripts.
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();
deployOPChain = new DeployOPChain();
(dsi, dso) = deployOPChain.getIOContracts();
(doi, doo) = deployOPChain.getIOContracts();
// 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.
deployImplementationsInput.superchainConfigProxy = superchainOutput.superchainConfigProxy;
deployImplementationsInput.protocolVersionsProxy = superchainOutput.protocolVersionsProxy;
deployImplementationsInput.superchainConfigProxy = dso.superchainConfigProxy();
deployImplementationsInput.protocolVersionsProxy = dso.protocolVersionsProxy();
// Deploy the implementations using the updated DeployImplementations input struct.
deployImplementationsOutput = deployImplementations.run(deployImplementationsInput);
......@@ -414,31 +417,31 @@ contract DeployOPChain_Test is DeployOPChain_TestBase {
// 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.
assertEq(_input.roles.opChainProxyAdminOwner, dsi.opChainProxyAdminOwner(), "100");
assertEq(_input.roles.systemConfigOwner, dsi.systemConfigOwner(), "200");
assertEq(_input.roles.batcher, dsi.batcher(), "300");
assertEq(_input.roles.unsafeBlockSigner, dsi.unsafeBlockSigner(), "400");
assertEq(_input.roles.proposer, dsi.proposer(), "500");
assertEq(_input.roles.challenger, dsi.challenger(), "600");
assertEq(_input.basefeeScalar, dsi.basefeeScalar(), "700");
assertEq(_input.blobBaseFeeScalar, dsi.blobBaseFeeScalar(), "800");
assertEq(_input.l2ChainId, dsi.l2ChainId(), "900");
assertEq(_input.roles.opChainProxyAdminOwner, doi.opChainProxyAdminOwner(), "100");
assertEq(_input.roles.systemConfigOwner, doi.systemConfigOwner(), "200");
assertEq(_input.roles.batcher, doi.batcher(), "300");
assertEq(_input.roles.unsafeBlockSigner, doi.unsafeBlockSigner(), "400");
assertEq(_input.roles.proposer, doi.proposer(), "500");
assertEq(_input.roles.challenger, doi.challenger(), "600");
assertEq(_input.basefeeScalar, doi.basefeeScalar(), "700");
assertEq(_input.blobBaseFeeScalar, doi.blobBaseFeeScalar(), "800");
assertEq(_input.l2ChainId, doi.l2ChainId(), "900");
// Assert that individual output fields were properly set based on the output struct.
assertEq(address(output.opChainProxyAdmin), address(dso.opChainProxyAdmin()), "1100");
assertEq(address(output.addressManager), address(dso.addressManager()), "1200");
assertEq(address(output.l1ERC721BridgeProxy), address(dso.l1ERC721BridgeProxy()), "1300");
assertEq(address(output.systemConfigProxy), address(dso.systemConfigProxy()), "1400");
assertEq(address(output.opChainProxyAdmin), address(doo.opChainProxyAdmin()), "1100");
assertEq(address(output.addressManager), address(doo.addressManager()), "1200");
assertEq(address(output.l1ERC721BridgeProxy), address(doo.l1ERC721BridgeProxy()), "1300");
assertEq(address(output.systemConfigProxy), address(doo.systemConfigProxy()), "1400");
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.l1CrossDomainMessengerProxy), address(dso.l1CrossDomainMessengerProxy()), "1700");
assertEq(address(output.optimismPortalProxy), address(dso.optimismPortalProxy()), "1800");
assertEq(address(output.l1StandardBridgeProxy), address(doo.l1StandardBridgeProxy()), "1600");
assertEq(address(output.l1CrossDomainMessengerProxy), address(doo.l1CrossDomainMessengerProxy()), "1700");
assertEq(address(output.optimismPortalProxy), address(doo.optimismPortalProxy()), "1800");
// 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(output)), keccak256(abi.encode(DeployOPChainOutput(dso).output())), "2000");
assertEq(keccak256(abi.encode(_input)), keccak256(abi.encode(DeployOPChainInput(doi).input())), "1900");
assertEq(keccak256(abi.encode(output)), keccak256(abi.encode(DeployOPChainOutput(doo).output())), "2000");
// Assert inputs were properly passed through to the contract initializers.
assertEq(address(output.opChainProxyAdmin.owner()), _input.roles.opChainProxyAdminOwner, "2100");
......
// SPDX-License-Identifier: MIT
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 { Proxy } from "src/universal/Proxy.sol";
......@@ -12,84 +13,60 @@ import { DeploySuperchainInput, DeploySuperchain, DeploySuperchainOutput } from
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)
});
address proxyAdminOwner = makeAddr("defaultProxyAdminOwner");
address protocolVersionsOwner = makeAddr("defaultProtocolVersionsOwner");
address guardian = makeAddr("defaultGuardian");
bool paused = false;
ProtocolVersion requiredProtocolVersion = ProtocolVersion.wrap(1);
ProtocolVersion 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);
assertLoadInput();
}
function test_loadInputFile_succeeds() public {
string memory root = vm.projectRoot();
string memory path = string.concat(root, "/test/fixtures/test-deploy-superchain-in.toml");
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 {
bytes memory expectedErr = "DeploySuperchainInput: input not set";
vm.expectRevert(expectedErr);
vm.expectRevert("DeploySuperchainInput: proxyAdminOwner not set");
dsi.proxyAdminOwner();
vm.expectRevert(expectedErr);
vm.expectRevert("DeploySuperchainInput: protocolVersionsOwner not set");
dsi.protocolVersionsOwner();
vm.expectRevert(expectedErr);
vm.expectRevert("DeploySuperchainInput: guardian not set");
dsi.guardian();
vm.expectRevert(expectedErr);
dsi.paused();
vm.expectRevert(expectedErr);
vm.expectRevert("DeploySuperchainInput: requiredProtocolVersion not set");
dsi.requiredProtocolVersion();
vm.expectRevert(expectedErr);
vm.expectRevert("DeploySuperchainInput: recommendedProtocolVersion not set");
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 {
using stdToml for string;
DeploySuperchainOutput dso;
function setUp() public {
......@@ -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
// 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"))
});
ProxyAdmin superchainProxyAdmin = ProxyAdmin(makeAddr("superchainProxyAdmin"));
SuperchainConfig superchainConfigImpl = SuperchainConfig(makeAddr("superchainConfigImpl"));
SuperchainConfig superchainConfigProxy = SuperchainConfig(makeAddr("superchainConfigProxy"));
ProtocolVersions protocolVersionsImpl = ProtocolVersions(makeAddr("protocolVersionsImpl"));
ProtocolVersions 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");
vm.etch(address(superchainProxyAdmin), hex"01");
vm.etch(address(superchainConfigImpl), hex"01");
vm.etch(address(superchainConfigProxy), hex"01");
vm.etch(address(protocolVersionsImpl), hex"01");
vm.etch(address(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");
dso.set(dso.superchainProxyAdmin.selector, address(superchainProxyAdmin));
dso.set(dso.superchainConfigImpl.selector, address(superchainConfigImpl));
dso.set(dso.superchainConfigProxy.selector, address(superchainConfigProxy));
dso.set(dso.protocolVersionsImpl.selector, address(protocolVersionsImpl));
dso.set(dso.protocolVersionsProxy.selector, address(protocolVersionsProxy));
// Compare the test data to the getter methods.
assertEq(address(superchainProxyAdmin), address(dso.superchainProxyAdmin()), "100");
assertEq(address(superchainConfigImpl), address(dso.superchainConfigImpl()), "200");
assertEq(address(superchainConfigProxy), address(dso.superchainConfigProxy()), "300");
assertEq(address(protocolVersionsImpl), address(dso.protocolVersionsImpl()), "400");
assertEq(address(protocolVersionsProxy), address(dso.protocolVersionsProxy()), "500");
}
function test_getters_whenNotSet_revert() public {
bytes memory expectedErr = "DeployUtils: zero address";
vm.expectRevert(expectedErr);
dso.superchainProxyAdmin();
vm.expectRevert(expectedErr);
vm.expectRevert("DeployUtils: zero address");
dso.superchainConfigImpl();
vm.expectRevert(expectedErr);
vm.expectRevert("DeployUtils: zero address");
dso.superchainConfigProxy();
vm.expectRevert(expectedErr);
vm.expectRevert("DeployUtils: zero address");
dso.protocolVersionsImpl();
vm.expectRevert(expectedErr);
vm.expectRevert("DeployUtils: zero address");
dso.protocolVersionsProxy();
}
......@@ -156,10 +123,6 @@ contract DeploySuperchainOutput_Test is Test {
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();
......@@ -183,14 +146,26 @@ contract DeploySuperchainOutput_Test is Test {
// Use the expected data from the test fixture.
string memory expOutPath = string.concat(root, "/test/fixtures/test-deploy-superchain-out.toml");
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));
dso.set(dso.superchainConfigImpl.selector, address(expOutput.superchainConfigImpl));
dso.set(dso.superchainConfigProxy.selector, address(expOutput.superchainConfigProxy));
dso.set(dso.protocolVersionsImpl.selector, address(expOutput.protocolVersionsImpl));
dso.set(dso.protocolVersionsProxy.selector, address(expOutput.protocolVersionsProxy));
// Parse each field of expOutToml individually.
ProxyAdmin expSuperchainProxyAdmin = ProxyAdmin(expOutToml.readAddress(".superchainProxyAdmin"));
SuperchainConfig expSuperchainConfigImpl = SuperchainConfig(expOutToml.readAddress(".superchainConfigImpl"));
SuperchainConfig expSuperchainConfigProxy = SuperchainConfig(expOutToml.readAddress(".superchainConfigProxy"));
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");
dso.writeOutputFile(actOutPath);
......@@ -204,75 +179,73 @@ contract DeploySuperchainOutput_Test is Test {
}
contract DeploySuperchain_Test is Test {
using stdStorage for StdStorage;
DeploySuperchain deploySuperchain;
DeploySuperchainInput dsi;
DeploySuperchainOutput dso;
// Define a default input struct for testing.
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)
});
// Define default input variables for testing.
address defaultProxyAdminOwner = makeAddr("defaultProxyAdminOwner");
address defaultProtocolVersionsOwner = makeAddr("defaultProtocolVersionsOwner");
address defaultGuardian = makeAddr("defaultGuardian");
bool defaultPaused = false;
ProtocolVersion defaultRequiredProtocolVersion = ProtocolVersion.wrap(1);
ProtocolVersion defaultRecommendedProtocolVersion = ProtocolVersion.wrap(2);
function setUp() public {
deploySuperchain = new DeploySuperchain();
(dsi, dso) = deploySuperchain.getIOContracts();
(dsi, dso) = deploySuperchain.etchIOContracts();
}
function unwrap(ProtocolVersion _pv) internal pure returns (uint256) {
return ProtocolVersion.unwrap(_pv);
}
function test_run_memory_succeeds(DeploySuperchainInput.Input memory _input) public {
vm.assume(_input.roles.proxyAdminOwner != address(0));
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");
function hash(bytes32 _seed, uint256 _i) internal pure returns (bytes32) {
return keccak256(abi.encode(_seed, _i));
}
// Assert that the full input and output structs were properly set.
assertEq(keccak256(abi.encode(_input)), keccak256(abi.encode(DeploySuperchainInput(dsi).input())), "1200");
assertEq(keccak256(abi.encode(output)), keccak256(abi.encode(DeploySuperchainOutput(dso).output())), "1300");
function test_run_memory_succeeds(bytes32 _seed) public {
// Generate random input values from the seed. This doesn't give us the benefit of the forge
// 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.
assertEq(address(output.superchainProxyAdmin.owner()), _input.roles.proxyAdminOwner, "1400");
assertEq(address(output.protocolVersionsProxy.owner()), _input.roles.protocolVersionsOwner, "1500");
assertEq(address(output.superchainConfigProxy.guardian()), _input.roles.guardian, "1600");
assertEq(output.superchainConfigProxy.paused(), _input.paused, "1700");
assertEq(unwrap(output.protocolVersionsProxy.required()), unwrap(_input.requiredProtocolVersion), "1800");
assertEq(unwrap(output.protocolVersionsProxy.recommended()), unwrap(_input.recommendedProtocolVersion), "1900");
assertEq(address(dso.superchainProxyAdmin().owner()), proxyAdminOwner, "100");
assertEq(address(dso.protocolVersionsProxy().owner()), protocolVersionsOwner, "200");
assertEq(address(dso.superchainConfigProxy().guardian()), guardian, "300");
assertEq(dso.superchainConfigProxy().paused(), paused, "400");
assertEq(unwrap(dso.protocolVersionsProxy().required()), unwrap(requiredProtocolVersion), "500");
assertEq(unwrap(dso.protocolVersionsProxy().recommended()), unwrap(recommendedProtocolVersion), "600");
// Architecture assertions.
// We prank as the zero address due to the Proxy's `proxyCallIfNotAdmin` modifier.
Proxy superchainConfigProxy = Proxy(payable(address(output.superchainConfigProxy)));
Proxy protocolVersionsProxy = Proxy(payable(address(output.protocolVersionsProxy)));
Proxy superchainConfigProxy = Proxy(payable(address(dso.superchainConfigProxy())));
Proxy protocolVersionsProxy = Proxy(payable(address(dso.protocolVersionsProxy())));
vm.startPrank(address(0));
assertEq(superchainConfigProxy.implementation(), address(output.superchainConfigImpl), "900");
assertEq(protocolVersionsProxy.implementation(), address(output.protocolVersionsImpl), "1000");
assertEq(superchainConfigProxy.admin(), protocolVersionsProxy.admin(), "1100");
assertEq(superchainConfigProxy.admin(), address(output.superchainProxyAdmin), "1200");
assertEq(superchainConfigProxy.implementation(), address(dso.superchainConfigImpl()), "700");
assertEq(protocolVersionsProxy.implementation(), address(dso.protocolVersionsImpl()), "800");
assertEq(superchainConfigProxy.admin(), protocolVersionsProxy.admin(), "900");
assertEq(superchainConfigProxy.admin(), address(dso.superchainProxyAdmin()), "1000");
vm.stopPrank();
// Ensure that `checkOutput` passes. This is called by the `run` function during execution,
......@@ -289,28 +262,54 @@ contract DeploySuperchain_Test is Test {
string memory actOutToml = vm.readFile(outpath);
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.
vm.removeFile(outpath);
assertEq(expOutToml, actOutToml);
}
function test_run_ZeroAddressRoleInput_reverts() public {
// Snapshot the state so we can revert to the default `input` struct between assertions.
uint256 snapshotId = vm.snapshot();
// Assert over each role being set to the zero address.
input.roles.proxyAdminOwner = address(0);
vm.expectRevert("DeploySuperchainInput: null proxyAdminOwner");
deploySuperchain.run(input);
vm.revertTo(snapshotId);
input.roles.protocolVersionsOwner = address(0);
vm.expectRevert("DeploySuperchainInput: null protocolVersionsOwner");
deploySuperchain.run(input);
function test_run_NullInput_reverts() public {
// Set default values for all inputs.
dsi.set(dsi.proxyAdminOwner.selector, defaultProxyAdminOwner);
dsi.set(dsi.protocolVersionsOwner.selector, defaultProtocolVersionsOwner);
dsi.set(dsi.guardian.selector, defaultGuardian);
dsi.set(dsi.paused.selector, defaultPaused);
dsi.set(dsi.requiredProtocolVersion.selector, defaultRequiredProtocolVersion);
dsi.set(dsi.recommendedProtocolVersion.selector, defaultRecommendedProtocolVersion);
// Assert over each role being set to the zero address. We aren't allowed to use the setter
// methods to set the zero address, so we use StdStorage. We can't use the `checked_write`
// method, because it does a final call to test that the value was set correctly, but for us
// 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);
input.roles.guardian = address(0);
vm.expectRevert("DeploySuperchainInput: null guardian");
deploySuperchain.run(input);
function zeroOutSlotForSelector(bytes4 _selector) internal returns (uint256 slot_) {
slot_ = stdstore.enable_packed_slots().target(address(dsi)).sig(_selector).find();
vm.store(address(dsi), bytes32(slot_), bytes32(0));
}
}
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