diff --git a/packages/contracts-bedrock/.testdata/.gitkeep b/packages/contracts-bedrock/.testdata/.gitkeep new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/contracts-bedrock/foundry.toml b/packages/contracts-bedrock/foundry.toml index 9edf752f983d6..cef9f85bbaebd 100644 --- a/packages/contracts-bedrock/foundry.toml +++ b/packages/contracts-bedrock/foundry.toml @@ -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"] diff --git a/packages/contracts-bedrock/scripts/DeploySuperchain.s.sol b/packages/contracts-bedrock/scripts/DeploySuperchain.s.sol index 8a3e004b2c10c..f90208ce81bdc 100644 --- a/packages/contracts-bedrock/scripts/DeploySuperchain.s.sol +++ b/packages/contracts-bedrock/scripts/DeploySuperchain.s.sol @@ -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"; @@ -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 @@ -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. @@ -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 @@ -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) { @@ -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(); @@ -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. diff --git a/packages/contracts-bedrock/test/DeploySuperchain.t.sol b/packages/contracts-bedrock/test/DeploySuperchain.t.sol index 2eeffb68e48a9..8babb4877a25a 100644 --- a/packages/contracts-bedrock/test/DeploySuperchain.t.sol +++ b/packages/contracts-bedrock/test/DeploySuperchain.t.sol @@ -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 { @@ -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 { @@ -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 { @@ -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)); @@ -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(); diff --git a/packages/contracts-bedrock/test/fixtures/test-deploy-superchain-in.toml b/packages/contracts-bedrock/test/fixtures/test-deploy-superchain-in.toml new file mode 100644 index 0000000000000..4cbcce25d004b --- /dev/null +++ b/packages/contracts-bedrock/test/fixtures/test-deploy-superchain-in.toml @@ -0,0 +1,8 @@ +paused = false +requiredProtocolVersion = 1 +recommendedProtocolVersion = 2 + +[roles] +proxyAdminOwner = "0x51f0348a9fA2aAbaB45E82825Fbd13d406e04497" +protocolVersionsOwner = "0xeEB4cc05dC0dE43c465f97cfc703D165418CA93A" +guardian = "0xE5DbA98c65F4B9EB0aeEBb3674fE64f88509a1eC" \ No newline at end of file diff --git a/packages/contracts-bedrock/test/fixtures/test-deploy-superchain-out.toml b/packages/contracts-bedrock/test/fixtures/test-deploy-superchain-out.toml new file mode 100644 index 0000000000000..ceb558a79d5a0 --- /dev/null +++ b/packages/contracts-bedrock/test/fixtures/test-deploy-superchain-out.toml @@ -0,0 +1,5 @@ +protocolVersionsImpl = "0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9" +protocolVersionsProxy = "0x1d1499e622D69689cdf9004d05Ec547d650Ff211" +superchainConfigImpl = "0xF62849F9A0B5Bf2913b396098F7c7019b51A820a" +superchainConfigProxy = "0xc7183455a4C133Ae270771860664b6B7ec320bB1" +superchainProxyAdmin = "0x2e234DAe75C793f67A35089C9d99245E1C58470b"