Skip to content

Commit

Permalink
contracts-bedrock: Add OPSM file I/O for superchain deployments (ethe…
Browse files Browse the repository at this point in the history
…reum-optimism#11750)

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

* Update packages/contracts-bedrock/scripts/DeploySuperchain.s.sol

Co-authored-by: Matt Solomon <[email protected]>

* Update packages/contracts-bedrock/test/DeploySuperchain.t.sol

Co-authored-by: Matt Solomon <[email protected]>

* Address feedback from code review

* Linter

---------

Co-authored-by: Matt Solomon <[email protected]>
  • Loading branch information
2 people authored and samlaf committed Nov 10, 2024
1 parent cb37d0d commit ffeb416
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 41 deletions.
Empty file.
3 changes: 2 additions & 1 deletion packages/contracts-bedrock/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ fs_permissions = [
{ access='read', path = './forge-artifacts/' },
{ access='write', path='./semver-lock.json' },
{ access='read-write', path='./.testdata/' },
{ access='read', path='./kout-deployment' }
{ access='read', path='./kout-deployment' },
{ access='read', path='./test/fixtures' },
]
libs = ["node_modules", "lib"]

Expand Down
46 changes: 26 additions & 20 deletions packages/contracts-bedrock/scripts/DeploySuperchain.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity 0.8.15;

import { Script } from "forge-std/Script.sol";
import { CommonBase } from "forge-std/Base.sol";

import { SuperchainConfig } from "src/L1/SuperchainConfig.sol";
import { ProtocolVersions, ProtocolVersion } from "src/L1/ProtocolVersions.sol";
Expand Down Expand Up @@ -57,19 +58,20 @@ import { Solarray } from "scripts/libraries/Solarray.sol";
* 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 fields must be in alphabetical order for vm.parseToml to work.
struct Input {
Roles roles;
bool paused;
ProtocolVersion requiredProtocolVersion;
ProtocolVersion recommendedProtocolVersion;
ProtocolVersion requiredProtocolVersion;
Roles roles;
}

struct Roles {
address proxyAdminOwner;
address protocolVersionsOwner;
address guardian;
address protocolVersionsOwner;
address proxyAdminOwner;
}

// This flag tells us if all inputs have been set. An `input()` getter method that returns all
Expand All @@ -84,10 +86,10 @@ contract DeploySuperchainInput {

// Load the input from a TOML file.
function loadInputFile(string memory _infile) public {
_infile;
Input memory parsedInput;
string memory toml = vm.readFile(_infile);
bytes memory data = vm.parseToml(toml);
Input memory parsedInput = abi.decode(data, (Input));
loadInput(parsedInput);
require(false, "DeploySuperchainInput: not implemented");
}

// Load the input from a struct.
Expand Down Expand Up @@ -153,14 +155,15 @@ contract DeploySuperchainInput {
}
}

contract DeploySuperchainOutput {
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 {
ProxyAdmin superchainProxyAdmin;
SuperchainConfig superchainConfigImpl;
SuperchainConfig superchainConfigProxy;
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
Expand All @@ -182,9 +185,14 @@ contract DeploySuperchainOutput {
}

// Save the output to a TOML file.
function writeOutputFile(string memory _outfile) public pure {
_outfile;
require(false, "DeploySuperchainOutput: not implemented");
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.writeToml(out, _outfile);
}

function output() public view returns (Output memory) {
Expand Down Expand Up @@ -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.
// 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.
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.
(DeploySuperchainInput dsi, DeploySuperchainOutput dso) = etchIOContracts();

Expand All @@ -248,10 +256,8 @@ contract DeploySuperchain is Script {
// Run the deployment script and write outputs to the DeploySuperchainOutput contract.
run(dsi, dso);

// Write the output data to a file. The file
string memory outfile = ""; // This will be derived from input file name, e.g. `foo.in.toml` -> `foo.out.toml`
dso.writeOutputFile(outfile);
require(false, "DeploySuperchain: run is not implemented");
// Write the output data to a file.
dso.writeOutputFile(_outfile);
}

// This entrypoint is for use with Solidity tests, where the input and outputs are structs.
Expand Down
90 changes: 70 additions & 20 deletions packages/contracts-bedrock/test/DeploySuperchain.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,15 @@ contract DeploySuperchainInput_Test is Test {
// 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();
}

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.
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");
dsi.loadInputFile(path);
assertLoadInput();
}

function test_getters_whenNotSet_revert() public {
Expand All @@ -76,6 +64,29 @@ contract DeploySuperchainInput_Test is Test {
vm.expectRevert(expectedErr);
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 {
Expand Down Expand Up @@ -165,6 +176,31 @@ contract DeploySuperchainOutput_Test is Test {
vm.expectRevert(expectedErr);
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 {
Expand Down Expand Up @@ -193,7 +229,7 @@ contract DeploySuperchain_Test is Test {
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.protocolVersionsOwner != address(0));
vm.assume(_input.roles.guardian != address(0));
Expand Down Expand Up @@ -244,6 +280,20 @@ contract DeploySuperchain_Test is Test {
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 {
// Snapshot the state so we can revert to the default `input` struct between assertions.
uint256 snapshotId = vm.snapshot();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
paused = false
requiredProtocolVersion = 1
recommendedProtocolVersion = 2

[roles]
proxyAdminOwner = "0x51f0348a9fA2aAbaB45E82825Fbd13d406e04497"
protocolVersionsOwner = "0xeEB4cc05dC0dE43c465f97cfc703D165418CA93A"
guardian = "0xE5DbA98c65F4B9EB0aeEBb3674fE64f88509a1eC"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
protocolVersionsImpl = "0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9"
protocolVersionsProxy = "0x1d1499e622D69689cdf9004d05Ec547d650Ff211"
superchainConfigImpl = "0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"
superchainConfigProxy = "0xc7183455a4C133Ae270771860664b6B7ec320bB1"
superchainProxyAdmin = "0x2e234DAe75C793f67A35089C9d99245E1C58470b"

0 comments on commit ffeb416

Please sign in to comment.