Commit ec06858a authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

contracts-bedrock: Add OPSM file I/O for superchain deployments (#11750)

* contracts-bedrock: Add OPSM file I/O for superchain deployments

* Update packages/contracts-bedrock/scripts/DeploySuperchain.s.sol
Co-authored-by: default avatarMatt Solomon <matt@mattsolomon.dev>

* Update packages/contracts-bedrock/test/DeploySuperchain.t.sol
Co-authored-by: default avatarMatt Solomon <matt@mattsolomon.dev>

* Address feedback from code review

* Linter

---------
Co-authored-by: default avatarMatt Solomon <matt@mattsolomon.dev>
parent df4d723f
...@@ -49,7 +49,8 @@ fs_permissions = [ ...@@ -49,7 +49,8 @@ fs_permissions = [
{ access='read', path = './forge-artifacts/' }, { access='read', path = './forge-artifacts/' },
{ access='write', path='./semver-lock.json' }, { access='write', path='./semver-lock.json' },
{ access='read-write', path='./.testdata/' }, { access='read-write', path='./.testdata/' },
{ access='read', path='./kout-deployment' } { access='read', path='./kout-deployment' },
{ access='read', path='./test/fixtures' },
] ]
libs = ["node_modules", "lib"] libs = ["node_modules", "lib"]
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { Script } from "forge-std/Script.sol"; import { Script } from "forge-std/Script.sol";
import { CommonBase } from "forge-std/Base.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";
...@@ -57,19 +58,20 @@ import { Solarray } from "scripts/libraries/Solarray.sol"; ...@@ -57,19 +58,20 @@ import { Solarray } from "scripts/libraries/Solarray.sol";
* scripts from the existing ones that "Config" and "Artifacts" terminology. * scripts from the existing ones that "Config" and "Artifacts" terminology.
*/ */
contract DeploySuperchainInput { contract DeploySuperchainInput is CommonBase {
// The input struct contains all the input data required for the deployment. // 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 { struct Input {
Roles roles;
bool paused; bool paused;
ProtocolVersion requiredProtocolVersion;
ProtocolVersion recommendedProtocolVersion; ProtocolVersion recommendedProtocolVersion;
ProtocolVersion requiredProtocolVersion;
Roles roles;
} }
struct Roles { struct Roles {
address proxyAdminOwner;
address protocolVersionsOwner;
address guardian; address guardian;
address protocolVersionsOwner;
address proxyAdminOwner;
} }
// This flag tells us if all inputs have been set. An `input()` getter method that returns all // This flag tells us if all inputs have been set. An `input()` getter method that returns all
...@@ -84,10 +86,10 @@ contract DeploySuperchainInput { ...@@ -84,10 +86,10 @@ contract DeploySuperchainInput {
// Load the input from a TOML file. // Load the input from a TOML file.
function loadInputFile(string memory _infile) public { function loadInputFile(string memory _infile) public {
_infile; string memory toml = vm.readFile(_infile);
Input memory parsedInput; bytes memory data = vm.parseToml(toml);
Input memory parsedInput = abi.decode(data, (Input));
loadInput(parsedInput); loadInput(parsedInput);
require(false, "DeploySuperchainInput: not implemented");
} }
// Load the input from a struct. // Load the input from a struct.
...@@ -153,14 +155,15 @@ contract DeploySuperchainInput { ...@@ -153,14 +155,15 @@ contract DeploySuperchainInput {
} }
} }
contract DeploySuperchainOutput { contract DeploySuperchainOutput is CommonBase {
// The output struct contains all the output data from the deployment. // 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 { struct Output {
ProxyAdmin superchainProxyAdmin;
SuperchainConfig superchainConfigImpl;
SuperchainConfig superchainConfigProxy;
ProtocolVersions protocolVersionsImpl; ProtocolVersions protocolVersionsImpl;
ProtocolVersions protocolVersionsProxy; ProtocolVersions protocolVersionsProxy;
SuperchainConfig superchainConfigImpl;
SuperchainConfig superchainConfigProxy;
ProxyAdmin superchainProxyAdmin;
} }
// We use a similar pattern as the input contract to expose outputs. Because outputs are set // We use a similar pattern as the input contract to expose outputs. Because outputs are set
...@@ -182,9 +185,14 @@ contract DeploySuperchainOutput { ...@@ -182,9 +185,14 @@ contract DeploySuperchainOutput {
} }
// Save the output to a TOML file. // Save the output to a TOML file.
function writeOutputFile(string memory _outfile) public pure { function writeOutputFile(string memory _outfile) public {
_outfile; string memory key = "dso-outfile";
require(false, "DeploySuperchainOutput: not implemented"); 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.writeToml(out, _outfile);
} }
function output() public view returns (Output memory) { function output() public view returns (Output memory) {
...@@ -238,7 +246,7 @@ contract DeploySuperchain is Script { ...@@ -238,7 +246,7 @@ contract DeploySuperchain is Script {
// This entrypoint is for end-users to deploy from an input file and write to an output file. // This entrypoint is for end-users to deploy from an input file and write to an output file.
// In this usage, we don't need the input and output contract functionality, so we deploy them // In this usage, we don't need the input and output contract functionality, so we deploy them
// here and abstract that architectural detail away from the end user. // here and abstract that architectural detail away from the end user.
function run(string memory _infile) public { function run(string memory _infile, string memory _outfile) public {
// End-user without file IO, so etch the IO helper contracts. // End-user without file IO, so etch the IO helper contracts.
(DeploySuperchainInput dsi, DeploySuperchainOutput dso) = etchIOContracts(); (DeploySuperchainInput dsi, DeploySuperchainOutput dso) = etchIOContracts();
...@@ -248,10 +256,8 @@ contract DeploySuperchain is Script { ...@@ -248,10 +256,8 @@ contract DeploySuperchain is Script {
// Run the deployment script and write outputs to the DeploySuperchainOutput contract. // Run the deployment script and write outputs to the DeploySuperchainOutput contract.
run(dsi, dso); run(dsi, dso);
// Write the output data to a file. The file // Write the output data to a file.
string memory outfile = ""; // This will be derived from input file name, e.g. `foo.in.toml` -> `foo.out.toml` dso.writeOutputFile(_outfile);
dso.writeOutputFile(outfile);
require(false, "DeploySuperchain: run is not implemented");
} }
// This entrypoint is for use with Solidity tests, where the input and outputs are structs. // This entrypoint is for use with Solidity tests, where the input and outputs are structs.
......
...@@ -32,27 +32,15 @@ contract DeploySuperchainInput_Test is Test { ...@@ -32,27 +32,15 @@ contract DeploySuperchainInput_Test is Test {
// parameters to e.g. avoid the zero address. Therefore we hardcode a concrete test case // parameters to e.g. avoid the zero address. Therefore we hardcode a concrete test case
// which is simpler and still sufficient. // which is simpler and still sufficient.
dsi.loadInput(input); dsi.loadInput(input);
assertLoadInput();
}
assertTrue(dsi.inputSet(), "100"); function test_loadInputFile_succeeds() public {
string memory root = vm.projectRoot();
string memory path = string.concat(root, "/test/fixtures/test-deploy-superchain-in.toml");
// Compare the test input struct to the getter methods. dsi.loadInputFile(path);
assertEq(input.roles.proxyAdminOwner, dsi.proxyAdminOwner(), "200"); assertLoadInput();
assertEq(input.roles.protocolVersionsOwner, dsi.protocolVersionsOwner(), "300");
assertEq(input.roles.guardian, dsi.guardian(), "400");
assertEq(input.paused, dsi.paused(), "500");
assertEq(
ProtocolVersion.unwrap(input.requiredProtocolVersion),
ProtocolVersion.unwrap(dsi.requiredProtocolVersion()),
"600"
);
assertEq(
ProtocolVersion.unwrap(input.recommendedProtocolVersion),
ProtocolVersion.unwrap(dsi.recommendedProtocolVersion()),
"700"
);
// Compare the test input struct to the `input` getter method.
assertEq(keccak256(abi.encode(input)), keccak256(abi.encode(dsi.input())), "800");
} }
function test_getters_whenNotSet_revert() public { function test_getters_whenNotSet_revert() public {
...@@ -76,6 +64,29 @@ contract DeploySuperchainInput_Test is Test { ...@@ -76,6 +64,29 @@ contract DeploySuperchainInput_Test is Test {
vm.expectRevert(expectedErr); vm.expectRevert(expectedErr);
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 {
...@@ -165,6 +176,31 @@ contract DeploySuperchainOutput_Test is Test { ...@@ -165,6 +176,31 @@ contract DeploySuperchainOutput_Test is Test {
vm.expectRevert(expectedErr); vm.expectRevert(expectedErr);
dso.protocolVersionsProxy(); dso.protocolVersionsProxy();
} }
function test_writeOutputFile_succeeds() public {
string memory root = vm.projectRoot();
// 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));
string memory actOutPath = string.concat(root, "/.testdata/test-deploy-superchain-output.toml");
dso.writeOutputFile(actOutPath);
string memory actOutToml = vm.readFile(actOutPath);
// Clean up before asserting so that we don't leave any files behind.
vm.removeFile(actOutPath);
assertEq(expOutToml, actOutToml);
}
} }
contract DeploySuperchain_Test is Test { contract DeploySuperchain_Test is Test {
...@@ -193,7 +229,7 @@ contract DeploySuperchain_Test is Test { ...@@ -193,7 +229,7 @@ contract DeploySuperchain_Test is Test {
return ProtocolVersion.unwrap(_pv); return ProtocolVersion.unwrap(_pv);
} }
function test_run_succeeds(DeploySuperchainInput.Input memory _input) public { function test_run_memory_succeeds(DeploySuperchainInput.Input memory _input) public {
vm.assume(_input.roles.proxyAdminOwner != address(0)); vm.assume(_input.roles.proxyAdminOwner != address(0));
vm.assume(_input.roles.protocolVersionsOwner != address(0)); vm.assume(_input.roles.protocolVersionsOwner != address(0));
vm.assume(_input.roles.guardian != address(0)); vm.assume(_input.roles.guardian != address(0));
...@@ -244,6 +280,20 @@ contract DeploySuperchain_Test is Test { ...@@ -244,6 +280,20 @@ contract DeploySuperchain_Test is Test {
dso.checkOutput(); dso.checkOutput();
} }
function test_run_io_succeeds() public {
string memory root = vm.projectRoot();
string memory inpath = string.concat(root, "/test/fixtures/test-deploy-superchain-in.toml");
string memory outpath = string.concat(root, "/.testdata/test-deploy-superchain-out.toml");
deploySuperchain.run(inpath, outpath);
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 { function test_run_ZeroAddressRoleInput_reverts() public {
// Snapshot the state so we can revert to the default `input` struct between assertions. // Snapshot the state so we can revert to the default `input` struct between assertions.
uint256 snapshotId = vm.snapshot(); uint256 snapshotId = vm.snapshot();
......
paused = false
requiredProtocolVersion = 1
recommendedProtocolVersion = 2
[roles]
proxyAdminOwner = "0x51f0348a9fA2aAbaB45E82825Fbd13d406e04497"
protocolVersionsOwner = "0xeEB4cc05dC0dE43c465f97cfc703D165418CA93A"
guardian = "0xE5DbA98c65F4B9EB0aeEBb3674fE64f88509a1eC"
\ No newline at end of file
protocolVersionsImpl = "0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9"
protocolVersionsProxy = "0x1d1499e622D69689cdf9004d05Ec547d650Ff211"
superchainConfigImpl = "0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"
superchainConfigProxy = "0xc7183455a4C133Ae270771860664b6B7ec320bB1"
superchainProxyAdmin = "0x2e234DAe75C793f67A35089C9d99245E1C58470b"
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