-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OPSM: Deploy Superchain #11468
Closed
Closed
OPSM: Deploy Superchain #11468
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
136 changes: 136 additions & 0 deletions
136
packages/contracts-bedrock/scripts/DeploySuperchain.s.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.15; | ||
|
||
import { Script } from "forge-std/Script.sol"; | ||
|
||
import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; | ||
import { ProtocolVersions, ProtocolVersion } from "src/L1/ProtocolVersions.sol"; | ||
import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; | ||
import { Proxy } from "src/universal/Proxy.sol"; | ||
|
||
/// @notice Deploys the Superchain contracts that can be shared by many chains. | ||
/// We intentionally use the terms "Input" and "Output" to clearly distinguish this script from the | ||
/// existing ones that use terms of "Config" and "Artifacts". | ||
contract DeploySuperchain is Script { | ||
struct Roles { | ||
address proxyAdminOwner; | ||
address protocolVersionsOwner; | ||
address guardian; | ||
} | ||
|
||
struct Input { | ||
Roles roles; | ||
bool paused; | ||
ProtocolVersion requiredProtocolVersion; | ||
ProtocolVersion recommendedProtocolVersion; | ||
} | ||
|
||
struct Output { | ||
ProxyAdmin superchainProxyAdmin; | ||
SuperchainConfig superchainConfigImpl; | ||
SuperchainConfig superchainConfigProxy; | ||
ProtocolVersions protocolVersionsImpl; | ||
ProtocolVersions protocolVersionsProxy; | ||
} | ||
|
||
/// @notice This entrypoint is for end-users to deploy from an input file and write to an output file. | ||
function run(string memory _infile) public returns (Output memory output_, string memory outfile_) { | ||
Input memory _input = parseInputFile(_infile); | ||
output_ = runWithoutIO(_input); | ||
outfile_ = writeOutputFile(_infile, _input, output_); | ||
require(false, "DeploySuperchain: run is not implemented"); | ||
} | ||
|
||
/// @notice This entrypoint is useful for e2e testing purposes, and doesn't use any file I/O. | ||
function runWithoutIO(Input memory _input) public returns (Output memory output_) { | ||
// Validate inputs. | ||
require(_input.roles.proxyAdminOwner != address(0), "zero address: proxyAdminOwner"); | ||
require(_input.roles.protocolVersionsOwner != address(0), "zero address: protocolVersionsOwner"); | ||
require(_input.roles.guardian != address(0), "zero address: guardian"); | ||
|
||
// Deploy the proxy admin, with the owner set to the deployer. | ||
// We explicitly specify the deployer as `msg.sender` because for testing we deploy this script from a test | ||
// contract. If we provide no argument, the foundry default sender is be the broadcaster during test, but the | ||
// broadcaster needs to be the deployer since they are set to the initial proxy admin owner. | ||
vm.startBroadcast(msg.sender); | ||
|
||
output_.superchainProxyAdmin = new ProxyAdmin(msg.sender); | ||
vm.label(address(output_.superchainProxyAdmin), "SuperchainProxyAdmin"); | ||
|
||
// Deploy implementation contracts. | ||
output_.superchainConfigImpl = new SuperchainConfig(); | ||
output_.protocolVersionsImpl = new ProtocolVersions(); | ||
|
||
// Deploy and initialize the proxies. | ||
output_.superchainConfigProxy = SuperchainConfig(address(new Proxy(address(output_.superchainProxyAdmin)))); | ||
vm.label(address(output_.superchainConfigProxy), "SuperchainConfigProxy"); | ||
output_.superchainProxyAdmin.upgradeAndCall( | ||
payable(address(output_.superchainConfigProxy)), | ||
address(output_.superchainConfigImpl), | ||
abi.encodeCall(SuperchainConfig.initialize, (_input.roles.guardian, _input.paused)) | ||
); | ||
|
||
output_.protocolVersionsProxy = ProtocolVersions(address(new Proxy(address(output_.superchainProxyAdmin)))); | ||
vm.label(address(output_.protocolVersionsProxy), "ProtocolVersionsProxy"); | ||
output_.superchainProxyAdmin.upgradeAndCall( | ||
payable(address(output_.protocolVersionsProxy)), | ||
address(output_.protocolVersionsImpl), | ||
abi.encodeCall( | ||
ProtocolVersions.initialize, | ||
(_input.roles.protocolVersionsOwner, _input.requiredProtocolVersion, _input.recommendedProtocolVersion) | ||
) | ||
); | ||
|
||
// Transfer ownership of the ProxyAdmin from the deployer to the specified owner. | ||
output_.superchainProxyAdmin.transferOwnership(_input.roles.proxyAdminOwner); | ||
|
||
vm.stopBroadcast(); | ||
|
||
// Output assertions, to make sure outputs were assigned correctly. | ||
address[] memory addresses = new address[](5); | ||
addresses[0] = address(output_.superchainProxyAdmin); | ||
addresses[1] = address(output_.superchainConfigImpl); | ||
addresses[2] = address(output_.superchainConfigProxy); | ||
addresses[3] = address(output_.protocolVersionsImpl); | ||
addresses[4] = address(output_.protocolVersionsProxy); | ||
|
||
for (uint256 i = 0; i < addresses.length; i++) { | ||
require(addresses[i] != address(0), string.concat("zero address at index ", vm.toString(i))); | ||
require(addresses[i].code.length > 0, string.concat("no code at index ", vm.toString(i))); | ||
} | ||
|
||
// All addresses should be unique. | ||
for (uint256 i = 0; i < addresses.length; i++) { | ||
for (uint256 j = i + 1; j < addresses.length; j++) { | ||
string memory err = string.concat("duplicates at: ", vm.toString(i), ",", vm.toString(j)); | ||
require(addresses[i] != addresses[j], err); | ||
} | ||
} | ||
} | ||
|
||
/// @notice This method is public for testing purposes, but there isn't a need for users to call this method | ||
/// directly. | ||
function parseInputFile(string memory _infile) public pure returns (Input memory input_) { | ||
_infile; | ||
input_; | ||
require(false, "DeploySuperchain: parseInputFile is not implemented"); | ||
} | ||
|
||
/// @notice Writes an output file, where the filename is based on the input filename, e.g. an input file of | ||
/// `DeploySuperchain.in.toml` results in an output file of `DeploySuperchain.out.toml`. | ||
function writeOutputFile( | ||
string memory _infile, | ||
Input memory _input, | ||
Output memory _output | ||
) | ||
internal | ||
pure | ||
returns (string memory outfile_) | ||
{ | ||
_infile; | ||
_input; | ||
_output; | ||
outfile_; | ||
require(false, "DeploySuperchain: writeOutputFile not implemented"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.15; | ||
|
||
import { Test } from "forge-std/Test.sol"; | ||
|
||
import { Proxy } from "src/universal/Proxy.sol"; | ||
import { ProtocolVersion } from "src/L1/ProtocolVersions.sol"; | ||
import { DeploySuperchain } from "scripts/DeploySuperchain.s.sol"; | ||
|
||
/// @notice Deploys the Superchain contracts that can be shared by many chains. | ||
contract DeploySuperchain_Test is Test { | ||
DeploySuperchain deploySuperchain; | ||
|
||
// Define a default input struct for testing. | ||
DeploySuperchain.Input input = DeploySuperchain.Input({ | ||
roles: DeploySuperchain.Roles({ | ||
proxyAdminOwner: makeAddr("defaultProxyAdminOwner"), | ||
protocolVersionsOwner: makeAddr("defaultProtocolVersionsOwner"), | ||
guardian: makeAddr("defaultGuardian") | ||
}), | ||
paused: false, | ||
requiredProtocolVersion: ProtocolVersion.wrap(1), | ||
recommendedProtocolVersion: ProtocolVersion.wrap(2) | ||
}); | ||
|
||
function setUp() public { | ||
deploySuperchain = new DeploySuperchain(); | ||
} | ||
|
||
function unwrap(ProtocolVersion _pv) internal pure returns (uint256) { | ||
return ProtocolVersion.unwrap(_pv); | ||
} | ||
|
||
function test_runWithoutIO_succeeds(DeploySuperchain.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)); | ||
|
||
DeploySuperchain.Output memory output = deploySuperchain.runWithoutIO(_input); | ||
|
||
// Assert the inputs were properly set. | ||
assertEq(address(output.superchainProxyAdmin.owner()), _input.roles.proxyAdminOwner, "100"); | ||
assertEq(address(output.protocolVersionsProxy.owner()), _input.roles.protocolVersionsOwner, "200"); | ||
assertEq(address(output.superchainConfigProxy.guardian()), _input.roles.guardian, "300"); | ||
assertEq(output.superchainConfigProxy.paused(), _input.paused, "400"); | ||
assertEq(unwrap(output.protocolVersionsProxy.required()), unwrap(_input.requiredProtocolVersion), "500"); | ||
assertEq(unwrap(output.protocolVersionsProxy.recommended()), unwrap(_input.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))); | ||
vm.startPrank(address(0)); | ||
|
||
assertEq(superchainConfigProxy.implementation(), address(output.superchainConfigImpl), "700"); | ||
assertEq(protocolVersionsProxy.implementation(), address(output.protocolVersionsImpl), "800"); | ||
assertEq(superchainConfigProxy.admin(), protocolVersionsProxy.admin(), "900"); | ||
assertEq(superchainConfigProxy.admin(), address(output.superchainProxyAdmin), "1000"); | ||
} | ||
|
||
function test_runWithoutIOAndZeroAddressRoles_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("zero address: proxyAdminOwner"); | ||
deploySuperchain.runWithoutIO(input); | ||
|
||
vm.revertTo(snapshotId); | ||
input.roles.protocolVersionsOwner = address(0); | ||
vm.expectRevert("zero address: protocolVersionsOwner"); | ||
deploySuperchain.runWithoutIO(input); | ||
|
||
vm.revertTo(snapshotId); | ||
input.roles.guardian = address(0); | ||
vm.expectRevert("zero address: guardian"); | ||
deploySuperchain.runWithoutIO(input); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of passing input/output as
memory
, it would be nice to have it structured as:SuperchainConfig
contract that has the attributes ofInput
(as public getters), and a method to load config from a file.SuperchainDeployment
contract with setters to write the results to, and a method that writes the final output as file to disk.And then etch in each contract to some address that is a hash of the contract name, maybe combined with the sender address (so configs/deployments of different tests don't collide).
This would then work well with the Go forge execution, where I can insert precompiles to form the Go config + capture the outputs, without IO, by overriding these input/output contracts with Go.
Any unit-tests could read the addresses from the
SuperchainDeployment
contract, not that different from reading from a contract struct variable.And, we can then
Semver
the input/output contracts; versioned deploy configuration would be very nice to have.And the setters/getters of a contract would then all be "type safe" from an ABI standpoint, whereas a memory/calldata struct can have a layout with two swapped address fields and it would be hard to tell apart. The ABI signature here is:
runWithoutIO(((address,address,address),bool,uint256,uint256))
.But instead it can be one ABI per config variable, and adding/removing configurables and deployment outputs would be much easier to keep compatible (not multiple struct definitions, but rather just additional getters/setters).
And, even better would be to standardize around something likeEdit: Putting acheck()
on the config input contract and the deployment output contract, where we can do some invariant checks (ensure inputs are correct, and ensure outputs are complete/consistent).check()
in theDeploySuperchain
contract that uses the input/output would both be easier (no need to source the inputs to check outputs), and allow thecheck()
to run even when the config/output are overridden.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Took a shot at this alternate approach in #11480 (though it doesn't have
check()
yet), keeping both PRs in draft pending a decision