Skip to content

Commit

Permalink
ctb: Add Guardian Safe to ownership deployment
Browse files Browse the repository at this point in the history
  • Loading branch information
maurelian committed May 22, 2024
1 parent f4a3252 commit 3f25851
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 24 deletions.
70 changes: 56 additions & 14 deletions packages/contracts-bedrock/scripts/DeployOwnership.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,21 @@ struct LivenessModuleConfig {
address fallbackOwner;
}

/// @notice Configuration for the Security Council Safe.
struct SecurityCouncilConfig {
SafeConfig safeConfig;
LivenessModuleConfig livenessModuleConfig;
}

/// @notice Configuration for the Deputy Guardian Module
struct DeputyGuardianModuleConfig {
address deputyGuardian;
SuperchainConfig superchainConfig;
}

/// @notice Configuration for the Security Council Safe.
struct SecurityCouncilConfig {
/// @notice Configuration for the Guardian Safe.
struct GuardianConfig {
SafeConfig safeConfig;
LivenessModuleConfig livenessModuleConfig;
DeputyGuardianModuleConfig deputyGuardianModuleConfig;
}

Expand All @@ -62,6 +67,8 @@ contract DeployOwnership is Deploy {

deployFoundationSafe();
deploySecurityCouncilSafe();
deployGuardianSafe();
configureGuardianSafe();
configureSecurityCouncilSafe();

console.log("Ownership contracts completed");
Expand All @@ -76,6 +83,19 @@ contract DeployOwnership is Deploy {
safeConfig_ = SafeConfig({ threshold: 5, owners: exampleFoundationOwners });
}

/// @notice Returns a GuardianConfig similar to that of the Guardian Safe on Mainnet.
function _getExampleGuardianConfig() internal returns (GuardianConfig memory guardianConfig_) {
address[] memory exampleGuardianOwners = new address[](1);
exampleGuardianOwners[0] = mustGetAddress("SecurityCouncilSafe");
guardianConfig_ = GuardianConfig({
safeConfig: SafeConfig({ threshold: 1, owners: exampleGuardianOwners }),
deputyGuardianModuleConfig: DeputyGuardianModuleConfig({
deputyGuardian: mustGetAddress("FoundationSafe"),
superchainConfig: SuperchainConfig(mustGetAddress("SuperchainConfig"))
})
});
}

/// @notice Returns a SafeConfig similar to that of the Security Council Safe on Mainnet.
function _getExampleCouncilConfig() internal returns (SecurityCouncilConfig memory councilConfig_) {
address[] memory exampleCouncilOwners = new address[](13);
Expand All @@ -90,10 +110,6 @@ contract DeployOwnership is Deploy {
thresholdPercentage: 75,
minOwners: 8,
fallbackOwner: mustGetAddress("FoundationSafe")
}),
deputyGuardianModuleConfig: DeputyGuardianModuleConfig({
deputyGuardian: mustGetAddress("FoundationSafe"),
superchainConfig: SuperchainConfig(mustGetAddress("SuperchainConfig"))
})
});
}
Expand Down Expand Up @@ -147,7 +163,8 @@ contract DeployOwnership is Deploy {
function deployDeputyGuardianModule() public returns (address addr_) {
SecurityCouncilConfig memory councilConfig = _getExampleCouncilConfig();
Safe councilSafe = Safe(payable(mustGetAddress("SecurityCouncilSafe")));
DeputyGuardianModuleConfig memory deputyGuardianModuleConfig = councilConfig.deputyGuardianModuleConfig;
DeputyGuardianModuleConfig memory deputyGuardianModuleConfig =
_getExampleGuardianConfig().deputyGuardianModuleConfig;
addr_ = address(
new DeputyGuardianModule({
_safe: councilSafe,
Expand All @@ -174,18 +191,43 @@ contract DeployOwnership is Deploy {
);
}

/// @notice Configure the Security Council Safe with the LivenessModule, DeputyGuardianModule, and LivenessGuard.
function configureSecurityCouncilSafe() public broadcast returns (address addr_) {
// Deploy and add the Deputy Guardian Module.
SecurityCouncilConfig memory exampleCouncilConfig = _getExampleCouncilConfig();
Safe safe = Safe(mustGetAddress("SecurityCouncilSafe"));
/// @notice Deploy Guardian Safe.
function deployGuardianSafe() public broadcast returns (address addr_) {
// Config is hardcoded here as the Guardian Safe's configuration is inflexible.
address[] memory owners = new address[](1);
owners[0] = mustGetAddress("SecurityCouncilSafe");
addr_ = deploySafe({ _name: "GuardianSafe", _owners: owners, _threshold: 1, _keepDeployer: true });

console.log("Deployed and configured the Guardian Safe!");
}

/// @notice Configure the Guardian Safe with the DeputyGuardianModule.
function configureGuardianSafe() public broadcast returns (address addr_) {
Safe safe = Safe(payable(mustGetAddress("GuardianSafe")));
address deputyGuardianModule = deployDeputyGuardianModule();
_callViaSafe({
_safe: safe,
_target: address(safe),
_data: abi.encodeCall(ModuleManager.enableModule, (deputyGuardianModule))
});
console.log("DeputyGuardianModule enabled on SecurityCouncilSafe");

// Remove the deployer address (msg.sender) which was used to setup the Security Council Safe thus far
// this call is also used to update the threshold.
// Because deploySafe() always adds msg.sender first (if keepDeployer is true), we know that the previousOwner
// will be SENTINEL_OWNERS.
_callViaSafe({
_safe: safe,
_target: address(safe),
_data: abi.encodeCall(OwnerManager.removeOwner, (SENTINEL_OWNERS, msg.sender, 1))
});
console.log("DeputyGuardianModule enabled on GuardianSafe");
}

/// @notice Configure the Security Council Safe with the LivenessModule and LivenessGuard.
function configureSecurityCouncilSafe() public broadcast returns (address addr_) {
// Deploy and add the Deputy Guardian Module.
SecurityCouncilConfig memory exampleCouncilConfig = _getExampleCouncilConfig();
Safe safe = Safe(mustGetAddress("SecurityCouncilSafe"));

// Deploy and add the Liveness Guard.
address guard = deployLivenessGuard();
Expand Down
42 changes: 32 additions & 10 deletions packages/contracts-bedrock/test/Safe/DeployOwnership.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
DeployOwnership,
SafeConfig,
SecurityCouncilConfig,
GuardianConfig,
DeputyGuardianModuleConfig,
LivenessModuleConfig
} from "scripts/DeployOwnership.s.sol";
Expand All @@ -29,6 +30,7 @@ contract DeployOwnershipTest is Test, DeployOwnership {
run();
}

/// @dev Helper function to make assertions on basic Safe config properties.
function _checkSafeConfig(SafeConfig memory _safeConfig, Safe _safe) internal view {
assertEq(_safe.getThreshold(), _safeConfig.threshold);

Expand All @@ -39,13 +41,15 @@ contract DeployOwnershipTest is Test, DeployOwnership {
}
}

/// @dev Test the example Foundation Safe configuration.
function test_exampleFoundationSafe() public {
Safe foundationSafe = Safe(payable(mustGetAddress("FoundationSafe")));
SafeConfig memory exampleFoundationConfig = _getExampleFoundationConfig();

_checkSafeConfig(exampleFoundationConfig, foundationSafe);
}

/// @dev Test the example Security Council Safe configuration.
function test_exampleSecurityCouncilSafe() public {
Safe securityCouncilSafe = Safe(payable(mustGetAddress("SecurityCouncilSafe")));
SecurityCouncilConfig memory exampleSecurityCouncilConfig = _getExampleCouncilConfig();
Expand All @@ -69,19 +73,11 @@ contract DeployOwnershipTest is Test, DeployOwnership {
address livenessModule = mustGetAddress("LivenessModule");
address deputyGuardianModule = mustGetAddress("DeputyGuardianModule");
(address[] memory modules, address nextModule) =
ModuleManager(securityCouncilSafe).getModulesPaginated(SENTINEL_MODULES, 3);
assertEq(modules.length, 2);
ModuleManager(securityCouncilSafe).getModulesPaginated(SENTINEL_MODULES, 2);
assertEq(modules.length, 1);
assertEq(modules[0], livenessModule);
assertEq(modules[1], deputyGuardianModule);
assertEq(nextModule, SENTINEL_MODULES); // ensures there are no more modules in the list

// DeputyGuardianModule checks
DeputyGuardianModuleConfig memory dgmConfig = exampleSecurityCouncilConfig.deputyGuardianModuleConfig;
assertEq(DeputyGuardianModule(deputyGuardianModule).deputyGuardian(), dgmConfig.deputyGuardian);
assertEq(
address(DeputyGuardianModule(deputyGuardianModule).superchainConfig()), address(dgmConfig.superchainConfig)
);

// LivenessModule checks
LivenessModuleConfig memory lmConfig = exampleSecurityCouncilConfig.livenessModuleConfig;
assertEq(address(LivenessModule(livenessModule).livenessGuard()), livenessGuard);
Expand All @@ -92,4 +88,30 @@ contract DeployOwnershipTest is Test, DeployOwnership {
// Ensure the threshold on the safe agrees with the LivenessModule's required threshold
assertEq(securityCouncilSafe.getThreshold(), LivenessModule(livenessModule).getRequiredThreshold(owners.length));
}

/// @dev Test the example Guardian Safe configuration.
function test_exampleGuardianSafe() public {
Safe guardianSafe = Safe(payable(mustGetAddress("GuardianSafe")));
address[] memory owners = new address[](1);
owners[0] = mustGetAddress("SecurityCouncilSafe");
GuardianConfig memory guardianConfig = _getExampleGuardianConfig();
_checkSafeConfig(guardianConfig.safeConfig, guardianSafe);

// DeputyGuardianModule checks
address deputyGuardianModule = mustGetAddress("DeputyGuardianModule");
(address[] memory modules, address nextModule) =
ModuleManager(guardianSafe).getModulesPaginated(SENTINEL_MODULES, 2);
assertEq(modules.length, 1);
assertEq(modules[0], deputyGuardianModule);
assertEq(nextModule, SENTINEL_MODULES); // ensures there are no more modules in the list

assertEq(
DeputyGuardianModule(deputyGuardianModule).deputyGuardian(),
guardianConfig.deputyGuardianModuleConfig.deputyGuardian
);
assertEq(
address(DeputyGuardianModule(deputyGuardianModule).superchainConfig()),
address(guardianConfig.deputyGuardianModuleConfig.superchainConfig)
);
}
}

0 comments on commit 3f25851

Please sign in to comment.