Skip to content

Commit

Permalink
feat: Scaffolding for DeployAuthSystem Script
Browse files Browse the repository at this point in the history
  • Loading branch information
maurelian committed Sep 13, 2024
1 parent 74d4c3f commit 186d201
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 2 deletions.
68 changes: 68 additions & 0 deletions packages/contracts-bedrock/scripts/DeployAuthSystem.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ contract DeployAuthSystemInput is CommonBase {
}

function owners() public view returns (address[] memory) {
// expecting to trigger this
require(_owners.length != 0, "DeployAuthSystemInput: owners not set");
return _owners;
}
Expand Down Expand Up @@ -85,3 +86,70 @@ contract DeployAuthSystemOutput is CommonBase {
return _safe;
}
}

// For all broadcasts in this script 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 would be the broadcaster during test, but the broadcaster needs to be the deployer
// since they are set to the initial proxy admin owner.
contract DeployAuthSystem is Script {
// -------- Core Deployment Methods --------

// 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, string memory _outfile) public {
// End-user without file IO, so etch the IO helper contracts.
(DeployAuthSystemInput dasi, DeployAuthSystemOutput daso) = etchIOContracts();

// Load the input file into the input contract.
dasi.loadInputFile(_infile);

// Run the deployment script and write outputs to the DeployAuthSystemOutput contract.
run(dasi, daso);

// Write the output data to a file.
daso.writeOutputFile(_outfile);
}

// This entrypoint is useful for testing purposes, as it doesn't use any file I/O.
function run(DeployAuthSystemInput _dasi, DeployAuthSystemOutput _daso) public {
deploySafe(_dasi, _daso);
}

function deploySafe(DeployAuthSystemInput _dasi, DeployAuthSystemOutput _daso) public {
address[] memory owners = _dasi.owners();
uint256 threshold = _dasi.threshold();
// Silence unused variable warnings
owners;
threshold;

// TODO: replace with a real deployment. The safe deployment logic is fairly complex, so for the purposes of
// this scaffolding PR we'll just etch the code.
// makeAddr("safe") = 0xDC93f9959c0F9c3849461B6468B4592a19567E09
address safe = 0xDC93f9959c0F9c3849461B6468B4592a19567E09;
vm.label(safe, "Safe");
vm.etch(safe, type(Safe).runtimeCode);
vm.store(safe, bytes32(uint256(3)), bytes32(uint256(owners.length)));
vm.store(safe, bytes32(uint256(4)), bytes32(uint256(threshold)));

_daso.set(_daso.safe.selector, safe);
}

// 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 `DeployAuthSystem.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 (DeployAuthSystemInput dasi_, DeployAuthSystemOutput daso_) {
(dasi_, daso_) = getIOContracts();
vm.etch(address(dasi_), type(DeployAuthSystemInput).runtimeCode);
vm.etch(address(daso_), type(DeployAuthSystemOutput).runtimeCode);
vm.allowCheatcodes(address(dasi_));
vm.allowCheatcodes(address(daso_));
}

// This returns the addresses of the IO contracts for this script.
function getIOContracts() public view returns (DeployAuthSystemInput dasi_, DeployAuthSystemOutput daso_) {
dasi_ = DeployAuthSystemInput(DeployUtils.toIOAddress(msg.sender, "optimism.DeployAuthSystemInput"));
daso_ = DeployAuthSystemOutput(DeployUtils.toIOAddress(msg.sender, "optimism.DeployAuthSystemOutput"));
}
}
104 changes: 102 additions & 2 deletions packages/contracts-bedrock/test/DeployAuthSystem.t.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// 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 { Solarray } from "scripts/libraries/Solarray.sol";

import { DeployAuthSystemInput, DeployAuthSystemOutput } from "scripts/DeployAuthSystem.s.sol";
import { DeployAuthSystemInput, DeployAuthSystem, DeployAuthSystemOutput } from "scripts/DeployAuthSystem.s.sol";

contract DeployAuthSystemInput_Test is Test {
DeployAuthSystemInput dsi;
Expand Down Expand Up @@ -109,3 +109,103 @@ contract DeployAuthSystemOutput_Test is Test {
assertEq(expOutToml, actOutToml);
}
}

contract DeployAuthSystem_Test is Test {
using stdStorage for StdStorage;

DeployAuthSystem deployAuthSystem;
DeployAuthSystemInput dasi;
DeployAuthSystemOutput daso;

// Define default input variables for testing.
uint256 defaultThreshold = 5;
uint256 defaultOwnersLength = 7;
address[] defaultOwners;

function setUp() public {
deployAuthSystem = new DeployAuthSystem();
(dasi, daso) = deployAuthSystem.etchIOContracts();
for (uint256 i = 0; i < defaultOwnersLength; i++) {
defaultOwners.push(makeAddr(string.concat("owner", vm.toString(i))));
}
}

function hash(bytes32 _seed, uint256 _i) internal pure returns (bytes32) {
return keccak256(abi.encode(_seed, _i));
}

function testFuzz_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[] memory _owners = Solarray.addresses(
address(uint160(uint256(hash(_seed, 0)))),
address(uint160(uint256(hash(_seed, 1)))),
address(uint160(uint256(hash(_seed, 2)))),
address(uint160(uint256(hash(_seed, 3)))),
address(uint160(uint256(hash(_seed, 4)))),
address(uint160(uint256(hash(_seed, 5)))),
address(uint160(uint256(hash(_seed, 6))))
);

uint256 threshold = bound(uint256(_seed), 1, _owners.length - 1);

dasi.set(dasi.owners.selector, _owners);
dasi.set(dasi.threshold.selector, threshold);

// Run the deployment script.
deployAuthSystem.run(dasi, daso);

// Assert inputs were properly passed through to the contract initializers.
assertNotEq(address(daso.safe()), address(0), "100");
assertEq(daso.safe().getThreshold(), threshold, "200");
// TODO: the getOwners() method requires iterating over the owners linked list.
// Since we're not yet performing a proper deployment of the Safe, this call will revert.
// assertEq(daso.safe().getOwners().length, _owners.length, "300");

// Architecture assertions.
// TODO: these will become relevant as we add more contracts to the auth system, and need to test their
// relationships.

daso.checkOutput();
}

function test_run_io_succeeds() public {
string memory root = vm.projectRoot();
string memory inpath = string.concat(root, "/test/fixtures/test-deploy-auth-system-in.toml");
string memory outpath = string.concat(root, "/.testdata/test-deploy-auth-system-out.toml");

deployAuthSystem.run(inpath, outpath);

string memory actOutToml = vm.readFile(outpath);
string memory expOutToml = vm.readFile(string.concat(root, "/test/fixtures/test-deploy-auth-system-out.toml"));

// Clean up before asserting so that we don't leave any files behind.
vm.removeFile(outpath);
assertEq(expOutToml, actOutToml);
}

function test_run_NullInput_reverts() public {
// Set default values for all inputs.
dasi.set(dasi.owners.selector, defaultOwners);
dasi.set(dasi.threshold.selector, defaultThreshold);

// Zero out the owners length slot
uint256 slot = 9;
vm.store(address(dasi), bytes32(uint256(9)), bytes32(0));
vm.expectRevert("DeployAuthSystemInput: owners not set");
deployAuthSystem.run(dasi, daso);
vm.store(address(dasi), bytes32(uint256(9)), bytes32(defaultOwnersLength));

// Zero out the threshold slot
slot = zeroOutSlotForSelector(dasi.threshold.selector);
vm.expectRevert("DeployAuthSystemInput: threshold not set");
deployAuthSystem.run(dasi, daso);
vm.store(address(dasi), bytes32(slot), bytes32(defaultThreshold));
}

function zeroOutSlotForSelector(bytes4 _selector) internal returns (uint256 slot_) {
slot_ = stdstore.enable_packed_slots().target(address(dasi)).sig(_selector).find();
vm.store(address(dasi), bytes32(slot_), bytes32(0));
}
}

0 comments on commit 186d201

Please sign in to comment.