Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
9d13224
start
JosepBove Aug 28, 2025
8102495
DeployOwnership scripts update
JosepBove Aug 28, 2025
dd75f3e
cleanup
JosepBove Aug 29, 2025
5f934f7
reset challangeStartTime when re-enabling the module
JosepBove Aug 29, 2025
ec5b31a
remove duplicated comment
JosepBove Aug 29, 2025
59cae4d
test cleanup
JosepBove Aug 29, 2025
e93c34f
fmt
JosepBove Aug 29, 2025
ee88fb3
semverlock
JosepBove Aug 29, 2025
dc1887d
add livenessmodule2 to checkfrozenfiles
JosepBove Aug 29, 2025
989ae74
address alcueca's feedback
JosepBove Aug 29, 2025
d3bf748
change ownershiptransfer logic
JosepBove Aug 29, 2025
34419fe
pre-pr
JosepBove Aug 29, 2025
548fcf4
LivenessModule - Slightly simplified code (#17277)
alcueca Aug 29, 2025
874836d
address smartcontract's feedback
JosepBove Aug 29, 2025
37ae803
alcueca's feedback
JosepBove Sep 2, 2025
93494c7
smartcontract's feedback
JosepBove Sep 2, 2025
f5fb1d3
add extra checks suggested by alcueca
JosepBove Sep 2, 2025
ee4de98
fix clear error
JosepBove Sep 2, 2025
e971a46
update snapshots
JosepBove Sep 2, 2025
9c0f85c
conflicts
JosepBove Sep 2, 2025
53efa74
Merge branch 'develop' into jb/liveness-module
JosepBove Sep 2, 2025
6a29e0d
name nitpicks and coverage at 100%
JosepBove Sep 2, 2025
39f603c
fix tests
JosepBove Sep 2, 2025
f0abb54
remove interface extra lines
JosepBove Sep 2, 2025
6545424
fix event on test
JosepBove Sep 2, 2025
c670f52
rename isChallenged function to getChallengePeriodEnd
JosepBove Sep 2, 2025
737affc
update snapshots
JosepBove Sep 2, 2025
d42b2b7
Merge branch 'develop' into jb/liveness-module
JosepBove Sep 3, 2025
f671313
update implementation from specs
JosepBove Sep 4, 2025
669f2e1
sentinel owner and interface fix
JosepBove Sep 8, 2025
e4669ac
DRYness comments
JosepBove Sep 8, 2025
405ae9c
move getter at the top
JosepBove Sep 8, 2025
7b7923f
update comment
JosepBove Sep 8, 2025
dc3d59f
rename event
JosepBove Sep 8, 2025
d0dc8a5
add _assertModuleConfigured
JosepBove Sep 8, 2025
60a11a4
add _assertModuleEnabled
JosepBove Sep 8, 2025
a93ac08
assert module enable fixes
JosepBove Sep 8, 2025
1c27cb6
_cancelChallenge internal function
JosepBove Sep 8, 2025
44c3ada
fix comments
JosepBove Sep 8, 2025
3fb2639
more comments
JosepBove Sep 8, 2025
e177a22
split configure errors
JosepBove Sep 8, 2025
a8a54cd
owner transfer invariant
JosepBove Sep 8, 2025
e03a5d7
address comments
JosepBove Sep 15, 2025
01eade2
address kelvin's feedback
JosepBove Sep 16, 2025
5818472
lint
JosepBove Sep 16, 2025
e8f4b89
more comments
JosepBove Sep 16, 2025
5f77577
Merge branch 'develop' into jb/liveness-module
JosepBove Sep 17, 2025
5be1c2d
alcueca's comments
JosepBove Sep 17, 2025
d071478
Merge branch 'develop' into jb/liveness-module
JosepBove Sep 19, 2025
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
50 changes: 50 additions & 0 deletions packages/contracts-bedrock/interfaces/safe/ILivenessModule2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { ISemver } from "interfaces/universal/ISemver.sol";

/// @title ILivenessModule2
/// @notice Interface for LivenessModule2, a singleton module for challenge-based ownership transfer
interface ILivenessModule2 is ISemver {
/// @notice Configuration for a Safe's liveness module
struct ModuleConfig {
uint256 livenessResponsePeriod;
address fallbackOwner;
}

/// @notice Returns the configuration for a Safe
/// @return livenessResponsePeriod The response period
/// @return fallbackOwner The fallback owner address
function livenessSafeConfiguration(address) external view returns (uint256 livenessResponsePeriod, address fallbackOwner);

/// @notice Returns the challenge start time for a Safe (0 if no challenge)
/// @return The challenge start timestamp
function challengeStartTime(address) external view returns (uint256);

/// @notice Semantic version
/// @return version The contract version
function version() external view returns (string memory);

/// @notice Configures the module for a Safe that has already enabled it
/// @param _config The configuration parameters for the module
function configureLivenessModule(ModuleConfig memory _config) external;

/// @notice Clears the module configuration for a Safe
function clearLivenessModule() external;

/// @notice Returns challenge_start_time + liveness_response_period if there is a challenge, or 0 if not
/// @param _safe The Safe address to query
/// @return The challenge end timestamp, or 0 if no challenge
function getChallengePeriodEnd(address _safe) external view returns (uint256);

/// @notice Challenges an enabled safe
/// @param _safe The Safe to challenge
function challenge(address _safe) external;

/// @notice Responds to a challenge for an enabled safe, canceling it
function respond() external;

/// @notice Removes all current owners from an enabled safe and appoints fallback as sole owner
/// @param _safe The Safe to transfer ownership of
function changeOwnershipToFallback(address _safe) external;
}
69 changes: 31 additions & 38 deletions packages/contracts-bedrock/scripts/deploy/DeployOwnership.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol";
import { GnosisSafeProxyFactory as SafeProxyFactory } from "safe-contracts/proxies/GnosisSafeProxyFactory.sol";
import { OwnerManager } from "safe-contracts/base/OwnerManager.sol";
import { ModuleManager } from "safe-contracts/base/ModuleManager.sol";
import { GuardManager } from "safe-contracts/base/GuardManager.sol";
import { Enum as SafeOps } from "safe-contracts/common/Enum.sol";

import { DeployUtils } from "scripts/libraries/DeployUtils.sol";

import { LivenessGuard } from "src/safe/LivenessGuard.sol";
import { LivenessModule } from "src/safe/LivenessModule.sol";
import { LivenessModule2 } from "src/safe/LivenessModule2.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";

import { Deploy } from "./Deploy.s.sol";
Expand Down Expand Up @@ -240,36 +238,14 @@ contract DeployOwnership is Deploy {
});
}

/// @notice Deploy a LivenessGuard for use on the Security Council Safe.
/// Note this function does not have the broadcast modifier.
function deployLivenessGuard() public returns (address addr_) {
Safe councilSafe = Safe(payable(artifacts.mustGetAddress("SecurityCouncilSafe")));
addr_ = address(new LivenessGuard(councilSafe));

artifacts.save("LivenessGuard", address(addr_));
console.log("New LivenessGuard deployed at %s", address(addr_));
}

/// @notice Deploy a LivenessModule for use on the Security Council Safe
/// @notice Deploy a LivenessModule2 singleton for use on Security Council Safes
/// Note this function does not have the broadcast modifier.
function deployLivenessModule() public returns (address addr_) {
Safe councilSafe = Safe(payable(artifacts.mustGetAddress("SecurityCouncilSafe")));
address guard = artifacts.mustGetAddress("LivenessGuard");
LivenessModuleConfig memory livenessModuleConfig = _getExampleCouncilConfig().livenessModuleConfig;

addr_ = address(
new LivenessModule({
_safe: councilSafe,
_livenessGuard: LivenessGuard(guard),
_livenessInterval: livenessModuleConfig.livenessInterval,
_thresholdPercentage: livenessModuleConfig.thresholdPercentage,
_minOwners: livenessModuleConfig.minOwners,
_fallbackOwner: livenessModuleConfig.fallbackOwner
})
);
// Deploy the singleton LivenessModule2 (no parameters needed)
addr_ = address(new LivenessModule2());

artifacts.save("LivenessModule", address(addr_));
console.log("New LivenessModule deployed at %s", address(addr_));
artifacts.save("LivenessModule2", address(addr_));
console.log("New LivenessModule2 deployed at %s", address(addr_));
}

/// @notice Deploy a Security Council Safe.
Expand Down Expand Up @@ -319,11 +295,6 @@ contract DeployOwnership is Deploy {
SecurityCouncilConfig memory exampleCouncilConfig = _getExampleCouncilConfig();
Safe safe = Safe(artifacts.mustGetAddress("SecurityCouncilSafe"));

// Deploy and add the Liveness Guard.
address guard = deployLivenessGuard();
_callViaSafe({ _safe: safe, _target: address(safe), _data: abi.encodeCall(GuardManager.setGuard, (guard)) });
console.log("LivenessGuard setup on SecurityCouncilSafe");

// Deploy and add the Liveness Module.
address livenessModule = deployLivenessModule();
_callViaSafe({
Expand All @@ -332,13 +303,35 @@ contract DeployOwnership is Deploy {
_data: abi.encodeCall(ModuleManager.enableModule, (livenessModule))
});

// Configure the LivenessModule2 (second step of installation)
LivenessModuleConfig memory livenessModuleConfig = exampleCouncilConfig.livenessModuleConfig;
_callViaSafe({
_safe: safe,
_target: livenessModule,
_data: abi.encodeCall(
LivenessModule2.configureLivenessModule,
(
LivenessModule2.ModuleConfig({
livenessResponsePeriod: livenessModuleConfig.livenessInterval,
fallbackOwner: livenessModuleConfig.fallbackOwner
})
)
)
});

// Finalize configuration by removing the additional deployer key.
removeDeployerFromSafe({ _name: "SecurityCouncilSafe", _newThreshold: exampleCouncilConfig.safeConfig.threshold });

address[] memory owners = safe.getOwners();
// Verify the module was configured correctly
(uint256 configuredPeriod, address configuredFallback) =
LivenessModule2(livenessModule).livenessSafeConfiguration(address(safe));
require(
configuredPeriod == exampleCouncilConfig.livenessModuleConfig.livenessInterval,
"DeployOwnership: configured liveness interval must match expected value"
);
require(
safe.getThreshold() == LivenessModule(livenessModule).getRequiredThreshold(owners.length),
"DeployOwnership: safe threshold must be equal to the LivenessModule's required threshold"
configuredFallback == exampleCouncilConfig.livenessModuleConfig.fallbackOwner,
"DeployOwnership: configured fallback owner must match expected value"
);

addr_ = address(safe);
Expand Down
Loading