Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ctb: Add removeDeployerFromSafe method to deploy scripts #10620

Merged
merged 3 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/contracts-bedrock/scripts/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { console2 as console } from "forge-std/console2.sol";
import { stdJson } from "forge-std/StdJson.sol";

import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol";
import { OwnerManager } from "safe-contracts/base/OwnerManager.sol";
import { GnosisSafeProxyFactory as SafeProxyFactory } from "safe-contracts/proxies/GnosisSafeProxyFactory.sol";
import { Enum as SafeOps } from "safe-contracts/common/Enum.sol";

Expand Down Expand Up @@ -426,6 +427,13 @@ contract Deploy is Deployer {
addr_ = deploySafe(_name, owners, 1, true);
}

/// @notice Deploy a new Safe contract. If the keepDeployer option is used to enable further setup actions, then
/// the removeDeployerFromSafe() function should be called on that safe after setup is complete.
/// Note this function does not have the broadcast modifier.
/// @param _name The name of the Safe to deploy.
/// @param _owners The owners of the Safe.
/// @param _threshold The threshold of the Safe.
/// @param _keepDeployer Wether or not the deployer address will be added as an owner of the Safe.
function deploySafe(
string memory _name,
address[] memory _owners,
Expand Down Expand Up @@ -462,6 +470,24 @@ contract Deploy is Deployer {
console.log("New safe: %s deployed at %s\n Note that this safe is owned by the deployer key", _name, addr_);
}

/// @notice If the keepDeployer option was used with deploySafe(), this function can be used to remove the deployer.
/// Note this function does not have the broadcast modifier.
function removeDeployerFromSafe(string memory _name, uint256 _newThreshold) public {
Safe safe = Safe(mustGetAddress(_name));

// The sentinel address is used to mark the start and end of the linked list of owners in the Safe.
address sentinelOwners = address(0x1);

// Because deploySafe() always adds msg.sender first (if keepDeployer is true), we know that the previousOwner
// will be sentinelOwners.
maurelian marked this conversation as resolved.
Show resolved Hide resolved
_callViaSafe({
_safe: safe,
_target: address(safe),
_data: abi.encodeCall(OwnerManager.removeOwner, (sentinelOwners, msg.sender, _newThreshold))
});
console.log("Removed deployer owner from ", _name);
}

/// @notice Deploy the AddressManager
function deployAddressManager() public broadcast returns (address addr_) {
console.log("Deploying AddressManager");
Expand Down
38 changes: 10 additions & 28 deletions packages/contracts-bedrock/scripts/DeployOwnership.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ struct GuardianConfig {
DeputyGuardianModuleConfig deputyGuardianModuleConfig;
}

// The sentinel address is used to mark the start and end of the linked list of owners in the Safe.
address constant SENTINEL_OWNERS = address(0x1);

/// @title Deploy
/// @notice Script used to deploy and configure the Safe contracts which are used to manage the Superchain,
/// as the ProxyAdminOwner and other roles in the system. Note that this script is not executable in a
Expand Down Expand Up @@ -84,7 +81,7 @@ contract DeployOwnership is Deploy {
}

/// @notice Returns a GuardianConfig similar to that of the Guardian Safe on Mainnet.
function _getExampleGuardianConfig() internal returns (GuardianConfig memory guardianConfig_) {
function _getExampleGuardianConfig() internal view returns (GuardianConfig memory guardianConfig_) {
address[] memory exampleGuardianOwners = new address[](1);
exampleGuardianOwners[0] = mustGetAddress("SecurityCouncilSafe");
guardianConfig_ = GuardianConfig({
Expand Down Expand Up @@ -161,7 +158,6 @@ contract DeployOwnership is Deploy {
/// @notice Deploy a DeputyGuardianModule for use on the Security Council Safe.
/// Note this function does not have the broadcast modifier.
function deployDeputyGuardianModule() public returns (address addr_) {
SecurityCouncilConfig memory councilConfig = _getExampleCouncilConfig();
Safe councilSafe = Safe(payable(mustGetAddress("SecurityCouncilSafe")));
DeputyGuardianModuleConfig memory deputyGuardianModuleConfig =
_getExampleGuardianConfig().deputyGuardianModuleConfig;
Expand Down Expand Up @@ -203,23 +199,16 @@ contract DeployOwnership is Deploy {

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

// 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))
});
// Finalize configuration by removing the additional deployer key.
removeDeployerFromSafe({ _name: "GuardianSafe", _newThreshold: 1 });
console.log("DeputyGuardianModule enabled on GuardianSafe");
}

Expand All @@ -242,22 +231,15 @@ contract DeployOwnership is Deploy {
_data: abi.encodeCall(ModuleManager.enableModule, (livenessModule))
});

// 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, exampleCouncilConfig.safeConfig.threshold)
)
});
// Finalize configuration by removing the additional deployer key.
removeDeployerFromSafe({ _name: "SecurityCouncilSafe", _newThreshold: exampleCouncilConfig.safeConfig.threshold });

address[] memory owners = safe.getOwners();
require(
safe.getThreshold() == LivenessModule(livenessModule).getRequiredThreshold(owners.length),
"Safe threshold must be equal to the LivenessModule's required threshold"
);

addr_ = address(safe);
console.log("Deployed and configured the Security Council Safe!");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ contract DeployOwnershipTest is Test, DeployOwnership {

address[] memory safeOwners = _safe.getOwners();
assertEq(_safeConfig.owners.length, safeOwners.length);
assertFalse(_safe.isOwner(msg.sender));
for (uint256 i = 0; i < safeOwners.length; i++) {
assertEq(safeOwners[i], _safeConfig.owners[i]);
}
Expand Down