Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
82e8ce3
Add SaferSafes as child of the module and guard
maurelian Oct 6, 2025
b6a5561
Add ISaferSafes
maurelian Oct 9, 2025
3a33850
Test comment and assertion fixes
maurelian Oct 9, 2025
81399b2
Improve comments
maurelian Oct 9, 2025
05aa13d
Make LivenessModule2 and TimelockGuard abstract
maurelian Oct 9, 2025
d35175c
fix test contract name
maurelian Oct 9, 2025
3da8e5b
Move semver to SaferSafes
maurelian Oct 9, 2025
64e0153
Disable the guard and module upon ownership transfer
maurelian Oct 9, 2025
300ab95
Add _disableThisGuard function
maurelian Oct 9, 2025
5efbf2a
Update tests
maurelian Oct 9, 2025
6e2acad
Add config resets
maurelian Oct 9, 2025
8f161cf
fmt
maurelian Oct 9, 2025
b5461ff
fix test_changeOwnershipToFallback_canRechallenge_succeeds
maurelian Oct 9, 2025
46fb155
Simplify by clearing config directly
maurelian Oct 9, 2025
993232a
Put _disableThisGuard into child contract
maurelian Oct 9, 2025
7f1d9ef
Add timelockDelay reset on _disableThisGuard
maurelian Oct 9, 2025
aa50113
semver-lock
maurelian Oct 9, 2025
6cb5167
Move _disableThisGuard logic into TimelockGuard
maurelian Oct 9, 2025
74113a7
clear livenessSafeConfig at tend of _disableThisModule
maurelian Oct 9, 2025
7242c80
Clarify use of SENTINEL_OWNER
maurelian Oct 9, 2025
cfb6110
Fix the ordering of the disableGuard and disableModule calls
maurelian Oct 10, 2025
3b1132b
semver-lock
maurelian Oct 10, 2025
48663a6
remove unused imports
maurelian Oct 10, 2025
c6ea913
rename _disableThisGuard to _disableGuard
maurelian Oct 10, 2025
819a246
bump semver
maurelian Oct 10, 2025
fcd9fa4
Add test to remove unrelated guard
maurelian Oct 14, 2025
a643404
Add SENTINEL_MODULE constant
maurelian Oct 14, 2025
a2d6019
Clean up using ternary if
maurelian Oct 14, 2025
539dd58
Reset cancellationThreshold to 0 on changeOwnership
maurelian Oct 14, 2025
9db0218
Fix moduleFound if/else handling
maurelian Oct 14, 2025
f7763dc
Clear pending transactions
maurelian Oct 15, 2025
f0855e5
Pre-pr fixes
maurelian Oct 15, 2025
6b2b25d
Add test contract to test name lint exclusions
maurelian Oct 15, 2025
cb58df1
fix name of test contract
maurelian Oct 15, 2025
8dd8e55
Move _disableGuard impl into TimelockGuard
maurelian Oct 15, 2025
a36b79e
Add missing natspec
maurelian Oct 15, 2025
62fed33
Add gas limit testing on changeOwnershipToFallback
maurelian Oct 15, 2025
bd03288
Remove interfaces for abstract contracts
maurelian Oct 15, 2025
97e49d2
Merge branch 'develop' into jm/disable-extensions-on-transfer
maurelian Oct 15, 2025
f75e019
Move state changes out into internal _clearLivenessModule
maurelian Oct 15, 2025
b50c952
Improve names on the internal _disableX methods
maurelian Oct 15, 2025
866067b
Add clearTimelockGuard function
maurelian Oct 16, 2025
1d4fdda
Add _disableGuard helper to TLG tests
maurelian Oct 16, 2025
220a421
Limit number of transactions cancelled to 100
maurelian Oct 16, 2025
275f950
Revert "Remove interfaces for abstract contracts"
maurelian Oct 16, 2025
e4b4457
Move livenessModule2 address into TestUtils
maurelian Oct 16, 2025
4bf722e
Reduce diff somewhat
maurelian Oct 16, 2025
67f13d2
Remove unused arg
maurelian Oct 16, 2025
f62065a
Update packages/contracts-bedrock/src/safe/TimelockGuard.sol
maurelian Oct 16, 2025
415d98f
Fix iface
maurelian Oct 16, 2025
9b0e831
update abi for iface fix
maurelian Oct 16, 2025
4ee99aa
Do not clear or disable the module during ownership transfer
maurelian Oct 16, 2025
75430d4
Fix inaccurate comment on _disableAndClearGuard
maurelian Oct 16, 2025
b459c34
Further improve comment
maurelian Oct 16, 2025
da19fe8
remove unused import
maurelian Oct 16, 2025
491f484
fix test name
maurelian Oct 16, 2025
5d36c21
Merge branch 'develop' into jm/disable-extensions-on-transfer
maurelian Oct 16, 2025
e6a057b
Merge branch 'develop' into jm/disable-extensions-on-transfer
maurelian Oct 16, 2025
56b439d
Do not clear guard during changeOwnershipToFallback
maurelian Oct 16, 2025
4f499e8
Remove unused SENTINEL_MODULE var
maurelian Oct 16, 2025
d266d12
Remove dangling comment
maurelian Oct 16, 2025
9f50cdc
Revert "Remove dangling comment"
maurelian Oct 16, 2025
3ce855f
Fix whitespace
maurelian Oct 16, 2025
7a93d4e
Merge branch 'develop' into jm/disable-extensions-on-transfer
maurelian Oct 16, 2025
af77e1b
remove unnecessary internal _clearTimelockGuard function
maurelian Oct 16, 2025
39434fc
Address feedback
maurelian Oct 17, 2025
3f2595b
Add missing assertion
maurelian Oct 17, 2025
6977109
Move guard slot into constants
maurelian Oct 17, 2025
0b38113
semver-lock
maurelian Oct 17, 2025
e525173
Remove LivenessModule from semver-lock
maurelian Oct 17, 2025
07ecba6
fix: fmt, semver-lock, unused imports
maurelian Oct 17, 2025
3f2a72c
Remove unused variable
maurelian Oct 17, 2025
7369668
fix semver lock by resetting old LivenessModule
maurelian Oct 17, 2025
0434bfa
fix unused import
maurelian Oct 17, 2025
60e0f98
Require that msgSender be an owner of the Safe
maurelian Oct 16, 2025
38e6529
fix compiler error
maurelian Oct 16, 2025
f87683a
Fix placement of _msgSender check
maurelian Oct 16, 2025
b9ea64a
semver-lock
maurelian Oct 16, 2025
2bc29d1
Add TimelockGuard_NotOwner test
maurelian Oct 17, 2025
441cdc6
Bump semver
maurelian Oct 18, 2025
c6889fe
Merge branch 'develop' into jm/msgSender-check
maurelian Oct 18, 2025
ef5010f
Add test comment, make into fuzz test
maurelian Oct 20, 2025
bc167dc
Improvements to SaferSafes styling (#17903)
maurelian Oct 20, 2025
54585b7
fix import and abi snapshot
maurelian Oct 20, 2025
eb8b081
fix: off by one error in challenge period test
maurelian Oct 21, 2025
8b65cd8
fix test name
maurelian Oct 21, 2025
97b2ce4
Merge branch 'develop' into jm/msgSender-check
maurelian Oct 21, 2025
8fe6f50
refactor(test): split clearLivenessModule tests into separate contract
maurelian Oct 20, 2025
50f122d
test(LivenessModule2): add challenge cancellation to clearLivenessMod…
maurelian Oct 20, 2025
e6718e9
test(LivenessModule2): add guard removal check to ownership transfer …
maurelian Oct 20, 2025
c6a1e3b
Remove pointless check
maurelian Oct 20, 2025
ea5b5b0
Convert to fuzz tests
maurelian Oct 20, 2025
ad3633d
test(TimelockGuard): enhance test coverage and convert to fuzz tests
maurelian Oct 20, 2025
2f940e0
fix test name to match function name
maurelian Oct 20, 2025
8fcb127
Merge remote-tracking branch 'origin/develop' into jm/test-cleanup
JosepBove Oct 21, 2025
b6acc71
feat: two extra tests to fuzz
JosepBove Oct 21, 2025
af493af
fix: fmt
JosepBove Oct 21, 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
38 changes: 33 additions & 5 deletions packages/contracts-bedrock/test/safe/LivenessModule2.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Constants } from "src/libraries/Constants.sol";
import { LivenessModule2 } from "src/safe/LivenessModule2.sol";
import { SaferSafes } from "src/safe/SaferSafes.sol";
import { ModuleManager } from "safe-contracts/base/ModuleManager.sol";
import { GuardManager } from "safe-contracts/base/GuardManager.sol";

/// @title LivenessModule2_TestUtils
/// @notice Reusable helper methods for LivenessModule2 tests.
Expand Down Expand Up @@ -108,8 +109,8 @@ contract LivenessModule2_TestInit is LivenessModule2_TestUtils {
}
}

/// @title LivenessModule2_Configure_Test
/// @notice Tests configuring and clearing the module
/// @title LivenessModule2_ConfigureLivenessModule_Test
/// @notice Tests configuring the module
contract LivenessModule2_ConfigureLivenessModule_Test is LivenessModule2_TestInit {
function test_configureLivenessModule_succeeds() external {
vm.expectEmit(true, true, true, true);
Expand Down Expand Up @@ -219,10 +220,19 @@ contract LivenessModule2_ConfigureLivenessModule_Test is LivenessModule2_TestIni
challengeEndTime = livenessModule2.getChallengePeriodEnd(safeInstance.safe);
assertEq(challengeEndTime, 0);
}
}

function test_clear_succeeds() external {
/// @title LivenessModule2_ClearLivenessModule_Test
/// @notice Tests clearing the module configuration
contract LivenessModule2_ClearLivenessModule_Test is LivenessModule2_TestInit {
function test_clearLivenessModule_succeeds() external {
_enableModule(safeInstance, CHALLENGE_PERIOD, fallbackOwner);

// Start a challenge to test that clearing also cancels it
vm.prank(fallbackOwner);
livenessModule2.challenge(safeInstance.safe);
assertGt(livenessModule2.challengeStartTime(safeInstance.safe), 0);

// First disable the module at the Safe level
SafeTestLib.execTransaction(
safeInstance,
Expand All @@ -232,6 +242,9 @@ contract LivenessModule2_ConfigureLivenessModule_Test is LivenessModule2_TestIni
Enum.Operation.Call
);

// Clear should emit ChallengeCancelled then ModuleCleared
vm.expectEmit(true, true, true, true);
emit ChallengeCancelled(address(safeInstance.safe));
vm.expectEmit(true, true, true, true);
emit ModuleCleared(address(safeInstance.safe));

Expand All @@ -247,15 +260,16 @@ contract LivenessModule2_ConfigureLivenessModule_Test is LivenessModule2_TestIni
LivenessModule2.ModuleConfig memory clearedConfig = livenessModule2.livenessSafeConfiguration(safeInstance.safe);
assertEq(clearedConfig.livenessResponsePeriod, 0);
assertEq(clearedConfig.fallbackOwner, address(0));
assertEq(livenessModule2.challengeStartTime(safeInstance.safe), 0);
}

function test_clear_notEnabled_reverts() external {
function test_clearLivenessModule_notConfigured_reverts() external {
vm.expectRevert(LivenessModule2.LivenessModule2_ModuleNotConfigured.selector);
vm.prank(address(safeInstance.safe));
livenessModule2.clearLivenessModule();
}

function test_clear_moduleStillEnabled_reverts() external {
function test_clearLivenessModule_moduleStillEnabled_reverts() external {
_enableModule(safeInstance, CHALLENGE_PERIOD, fallbackOwner);

// Try to clear while module is still enabled (should revert)
Expand Down Expand Up @@ -450,6 +464,17 @@ contract LivenessModule2_ChangeOwnershipToFallback_Test is LivenessModule2_TestI
// Bound time to reasonable values (1 second to 365 days after expiry)
timeAfterExpiry = bound(timeAfterExpiry, 1, 365 days);

// Set a guard to verify it gets removed
address mockGuard = makeAddr("mockGuard");
SafeTestLib.execTransaction(
safeInstance,
address(safeInstance.safe),
0,
abi.encodeCall(GuardManager.setGuard, (mockGuard)),
Enum.Operation.Call
);
assertEq(_getGuard(safeInstance), mockGuard);

// Start a challenge
vm.prank(fallbackOwner);
livenessModule2.challenge(safeInstance.safe);
Expand All @@ -473,6 +498,9 @@ contract LivenessModule2_ChangeOwnershipToFallback_Test is LivenessModule2_TestI
// Verify challenge is reset
uint256 challengeEndTime = livenessModule2.getChallengePeriodEnd(safeInstance.safe);
assertEq(challengeEndTime, 0);

// Verify guard was removed
assertEq(_getGuard(safeInstance), address(0));
}

/// @notice Tests that changeOwnershipToFallback reverts if module is not configured
Expand Down
1 change: 1 addition & 0 deletions packages/contracts-bedrock/test/safe/SaferSafes.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity 0.8.15;

import { Enum } from "safe-contracts/common/Enum.sol";
import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol";
import "test/safe-tools/SafeTestTools.sol";

import { SaferSafes } from "src/safe/SaferSafes.sol";
Expand Down
110 changes: 87 additions & 23 deletions packages/contracts-bedrock/test/safe/TimelockGuard.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -261,49 +261,46 @@ abstract contract TimelockGuard_TestInit is Test, SafeTestTools {
}

/// @title TimelockGuard_TimelockDelay_Test
/// @notice Tests for timelockConfiguration function
/// @notice Tests for TimelockDelay function
contract TimelockGuard_TimelockDelay_Test is TimelockGuard_TestInit {
/// @notice Ensures an unconfigured Safe reports a zero timelock delay.
function test_timelockConfiguration_returnsZeroForUnconfiguredSafe_succeeds() external view {
function test_timelockDelay_returnsZeroForUnconfiguredSafe_succeeds() external view {
uint256 delay = timelockGuard.timelockDelay(safeInstance.safe);
assertEq(delay, 0);
// configured is now determined by timelockDelay == 0
assertEq(delay == 0, true);
}

/// @notice Validates the configuration view reflects the stored timelock delay.
function test_timelockConfiguration_returnsConfigurationForConfiguredSafe_succeeds() external {
_configureGuard(safeInstance, TIMELOCK_DELAY);
uint256 delay = timelockGuard.timelockDelay(safeInstance.safe);
assertEq(delay, TIMELOCK_DELAY);
// configured is now determined by timelockDelay != 0
assertEq(delay != 0, true);
/// @notice Fuzz test: Validates the configuration view reflects the stored timelock delay for any valid delay.
function testFuzz_timelockDelay_returnsConfigurationForConfiguredSafe_succeeds(uint256 _delay_) external {
_delay_ = bound(_delay_, 1, ONE_YEAR); // Restrict to valid range
_configureGuard(safeInstance, _delay_);
uint256 delay_ = timelockGuard.timelockDelay(safeInstance.safe);
assertEq(delay_, _delay_);
}
}

/// @title TimelockGuard_ConfigureTimelockGuard_Test
/// @notice Tests for configureTimelockGuard function
contract TimelockGuard_ConfigureTimelockGuard_Test is TimelockGuard_TestInit {
/// @notice Verifies the guard can be configured with a standard delay.
function test_configureTimelockGuard_succeeds() external {
/// @notice Verifies the guard can be configured with various valid delays.
function testFuzz_configureTimelockGuard_validDelay_succeeds(uint256 _delay) external {
_delay = bound(_delay, 1, ONE_YEAR);

vm.expectEmit(true, true, true, true);
emit GuardConfigured(safe, TIMELOCK_DELAY);
emit GuardConfigured(safe, _delay);

_configureGuard(safeInstance, TIMELOCK_DELAY);
_configureGuard(safeInstance, _delay);

uint256 delay = timelockGuard.timelockDelay(safe);
assertEq(delay, TIMELOCK_DELAY);
// configured is now determined by timelockDelay != 0
assertEq(delay != 0, true);
assertEq(delay, _delay);
}

/// @notice Confirms delays above the maximum revert during configuration.
function test_configureTimelockGuard_revertsIfDelayTooLong_reverts() external {
uint256 tooLongDelay = ONE_YEAR + 1;
function testFuzz_configureTimelockGuard_delayTooLong_reverts(uint256 _delay) external {
_delay = bound(_delay, ONE_YEAR + 1, type(uint256).max);

vm.expectRevert(TimelockGuard.TimelockGuard_InvalidTimelockDelay.selector);
vm.prank(address(safeInstance.safe));
timelockGuard.configureTimelockGuard(tooLongDelay);
timelockGuard.configureTimelockGuard(_delay);
}

/// @notice Checks configuration reverts when the contract is too old.
Expand All @@ -324,8 +321,6 @@ contract TimelockGuard_ConfigureTimelockGuard_Test is TimelockGuard_TestInit {

uint256 delay = timelockGuard.timelockDelay(safe);
assertEq(delay, ONE_YEAR);
// configured is now determined by timelockDelay != 0
assertEq(delay != 0, true);
}

/// @notice Demonstrates the guard can be reconfigured to a new delay.
Expand Down Expand Up @@ -733,6 +728,75 @@ contract TimelockGuard_CheckTransaction_Test is TimelockGuard_TestInit {
}
}

/// @title TimelockGuard_CheckAfterExecution_Test
/// @notice Tests for checkAfterExecution function
contract TimelockGuard_CheckAfterExecution_Test is TimelockGuard_TestInit {
function setUp() public override {
super.setUp();
_configureGuard(safeInstance, TIMELOCK_DELAY);
}

/// @notice Verifies successful execution updates state and resets threshold.
function test_checkAfterExecution_successfulExecution_succeeds() external {
TransactionBuilder.Transaction memory dummyTx = _createDummyTransaction(safeInstance);
dummyTx.scheduleTransaction(timelockGuard);

uint256 expectedExecutionTime = block.timestamp + TIMELOCK_DELAY;
vm.warp(expectedExecutionTime);

// Verify initial cancellation threshold
uint256 initialThreshold = timelockGuard.cancellationThreshold(safeInstance.safe);
assertEq(initialThreshold, 1);

// Call checkAfterExecution with successful execution
vm.expectEmit(true, true, true, true);
emit TransactionExecuted(safeInstance.safe, dummyTx.hash);
vm.prank(address(safeInstance.safe));
timelockGuard.checkAfterExecution(dummyTx.hash, true);

// Verify transaction state changed to Executed
TimelockGuard.ScheduledTransaction memory scheduledTx =
timelockGuard.scheduledTransaction(safeInstance.safe, dummyTx.hash);
assertEq(uint256(scheduledTx.state), uint256(TimelockGuard.TransactionState.Executed));

// Verify transaction removed from pending list
TimelockGuard.ScheduledTransaction[] memory pending = timelockGuard.pendingTransactions(safeInstance.safe);
assertEq(pending.length, 0);

// Verify cancellation threshold was reset to 1
assertEq(timelockGuard.cancellationThreshold(safeInstance.safe), 1);

// Verify transaction cannot be executed again
vm.expectRevert(TimelockGuard.TimelockGuard_TransactionAlreadyExecuted.selector);
dummyTx.executeTransaction(safeInstance.owners[0]);
}

/// @notice Verifies transaction state remains unchanged on execution failure.
function test_checkAfterExecution_failedExecution_succeeds() external {
TransactionBuilder.Transaction memory dummyTx = _createDummyTransaction(safeInstance);
dummyTx.scheduleTransaction(timelockGuard);

uint256 expectedExecutionTime = block.timestamp + TIMELOCK_DELAY;
vm.warp(expectedExecutionTime);

// Call checkAfterExecution with failed execution
vm.prank(address(safeInstance.safe));
timelockGuard.checkAfterExecution(dummyTx.hash, false);

// Verify transaction state remains unchanged
TimelockGuard.ScheduledTransaction memory scheduledTx =
timelockGuard.scheduledTransaction(safeInstance.safe, dummyTx.hash);
assertEq(uint256(scheduledTx.state), uint256(TimelockGuard.TransactionState.Pending));
assertEq(uint256(scheduledTx.executionTime), expectedExecutionTime);
}

/// @notice Fuzz test: Verifies unconfigured guard allows checkAfterExecution for any _hash.
function testFuzz_checkAfterExecution_unconfiguredGuard_succeeds(bytes32 _hash) external {
vm.prank(address(unguardedSafe.safe));
timelockGuard.checkAfterExecution(_hash, true);
}
}

/// @title TimelockGuard_MaxCancellationThreshold_Test
/// @notice Tests for the maxCancellationThreshold function in TimelockGuard
contract TimelockGuard_MaxCancellationThreshold_Test is TimelockGuard_TestInit {
Expand Down