Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
36 changes: 36 additions & 0 deletions packages/contracts-bedrock/interfaces/safe/IModuleGuard.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { Enum } from "safe-contracts/common/Enum.sol";
import { IERC165 } from "safe-contracts/interfaces/IERC165.sol";

/**
* @title IModuleGuard Interface
*/
interface IModuleGuard is IERC165 {
/**
* @notice Checks the module transaction details.
* @dev The function needs to implement module transaction validation logic.
Comment thread
JosepBove marked this conversation as resolved.
Outdated
* @param to The address to which the transaction is intended.
* @param value The value of the transaction in Wei.
* @param data The transaction data.
* @param operation Operation type (0 for `CALL`, 1 for `DELEGATECALL`) of the module transaction.
* @param module The module involved in the transaction.
* @return moduleTxHash A guard-specific module transaction hash. This value is passed to the matching {checkAfterModuleExecution} call.
*/
function checkModuleTransaction(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation,
address module
) external returns (bytes32 moduleTxHash);

/**
* @notice Checks after execution of module transaction.
* @dev The function needs to implement a check after the execution of the module transaction.
* @param txHash The guard-specific module transaction hash returned from the matching {checkModuleTransaction} call.
* @param success The status of the module transaction execution.
*/
function checkAfterModuleExecution(bytes32 txHash, bool success) external;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { Enum } from "safe-contracts/common/Enum.sol";
import { IERC165 } from "safe-contracts/interfaces/IERC165.sol";

/**
* @title ITransactionGuard Interface
*/
interface ITransactionGuard is IERC165 {
/**
* @notice Checks the transaction details.
* @dev The function needs to implement transaction validation logic.
* @param to The address to which the transaction is intended.
* @param value The native token value of the transaction in Wei.
* @param data The transaction data.
* @param operation Operation type (0 for `CALL`, 1 for `DELEGATECALL`).
* @param safeTxGas Gas used for the transaction.
* @param baseGas The base gas for the transaction.
* @param gasPrice The price of gas in Wei for the transaction.
* @param gasToken The token used to pay for gas.
* @param refundReceiver The address which should receive the refund.
* @param signatures The signatures of the transaction.
* @param msgSender The address of the message sender.
*/
function checkTransaction(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes memory signatures,
address msgSender
) external;

/**
* @notice Checks after execution of the transaction.
* @dev The function needs to implement a check after the execution of the transaction.
* @param hash The hash of the executed transaction.
* @param success The status of the transaction execution.
*/
function checkAfterExecution(bytes32 hash, bool success) external;
}
15 changes: 14 additions & 1 deletion packages/contracts-bedrock/src/safe/LivenessModule2.sol
Comment thread
JosepBove marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ pragma solidity 0.8.15;
import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol";
import { Enum } from "safe-contracts/common/Enum.sol";
import { OwnerManager } from "safe-contracts/base/OwnerManager.sol";
import { IERC165 } from "safe-contracts/interfaces/IERC165.sol";

// Interfaces
import { IModuleGuard } from "interfaces/safe/IModuleGuard.sol";

/// @title LivenessModule2
/// @notice This module allows challenge-based ownership transfer to a fallback owner
Expand All @@ -14,7 +18,7 @@ import { OwnerManager } from "safe-contracts/base/OwnerManager.sol";
/// @dev This is a singleton contract. To use it:
/// 1. The Safe must first enable this module using ModuleManager.enableModule()
/// 2. The Safe must then configure the module by calling configure() with params
abstract contract LivenessModule2 {
abstract contract LivenessModule2 is IERC165 {
/// @notice Configuration for a Safe's liveness module.
/// @custom:field livenessResponsePeriod The duration in seconds that Safe owners have to
/// respond to a challenge.
Expand Down Expand Up @@ -322,4 +326,13 @@ abstract contract LivenessModule2 {
delete challengeStartTime[_safe];
emit ChallengeCancelled(_safe);
}

/// @notice ERC165 interface detection
/// @param interfaceId The interface identifier to check
/// @return True if the contract implements the interface
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return
interfaceId == type(IModuleGuard).interfaceId || // 0x58401ed8
interfaceId == type(IERC165).interfaceId; // 0x01ffc9a7
}
}
13 changes: 13 additions & 0 deletions packages/contracts-bedrock/src/safe/SaferSafes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,17 @@ contract SaferSafes is LivenessModule2, TimelockGuard, ISemver {
revert SaferSafes_InsufficientLivenessResponsePeriod();
}
}

/// @notice ERC165 interface detection
/// @dev Combines interface support from both LivenessModule2 and TimelockGuard
/// @param interfaceId The interface identifier to check
/// @return True if the contract implements the interface
function supportsInterface(bytes4 interfaceId)
public
view
override(LivenessModule2, TimelockGuard)
returns (bool)
{
return LivenessModule2.supportsInterface(interfaceId) || TimelockGuard.supportsInterface(interfaceId);
}
}
19 changes: 18 additions & 1 deletion packages/contracts-bedrock/src/safe/TimelockGuard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ pragma solidity 0.8.15;
import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol";
import { Enum } from "safe-contracts/common/Enum.sol";
import { Guard as IGuard } from "safe-contracts/base/GuardManager.sol";
import { IERC165 } from "safe-contracts/interfaces/IERC165.sol";

// Libraries
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import { SemverComp } from "src/libraries/SemverComp.sol";

// Interfaces
import { ITransactionGuard } from "interfaces/safe/ITransactionGuard.sol";

/// @title TimelockGuard
/// @notice This guard provides timelock functionality for Safe transactions
/// @dev This is a singleton contract, any Safe on the network can use this guard to enforce a timelock delay, and
Expand Down Expand Up @@ -64,7 +68,7 @@ import { SemverComp } from "src/libraries/SemverComp.sol";
/// | Quorum+ | challenge + | cancelTransaction |
/// | | changeOwnershipToFallback | |
/// +-------------------------------------------------------------------------------------------------+
abstract contract TimelockGuard is IGuard {
abstract contract TimelockGuard is IGuard, IERC165 {
using EnumerableSet for EnumerableSet.Bytes32Set;

/// @notice Allowed states of a transaction
Expand Down Expand Up @@ -612,4 +616,17 @@ abstract contract TimelockGuard is IGuard {
function signCancellation(bytes32) public {
emit Message("This function is not meant to be called, did you mean to call cancelTransaction?");
}

////////////////////////////////////////////////////////////////
// ERC165 Support //
////////////////////////////////////////////////////////////////

/// @notice ERC165 interface detection
/// @param interfaceId The interface identifier to check
/// @return True if the contract implements the interface
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return
interfaceId == type(ITransactionGuard).interfaceId || // 0xe6d7a83a
interfaceId == type(IERC165).interfaceId; // 0x01ffc9a7
}
}
25 changes: 25 additions & 0 deletions packages/contracts-bedrock/test/safe/LivenessModule2.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -584,3 +584,28 @@ contract LivenessModule2_GetChallengePeriodEnd_Test is LivenessModule2_TestInit
assertTrue(bytes(livenessModule2.version()).length > 0);
}
}

/// @title LivenessModule2_SupportsInterface_Test
/// @notice Tests ERC165 interface support for LivenessModule2
contract LivenessModule2_SupportsInterface_Test is LivenessModule2_TestInit {
function test_supportsInterface_IModuleGuard_succeeds() external {
bytes4 interfaceId = 0x58401ed8; // IModuleGuard interface ID
assertTrue(livenessModule2.supportsInterface(interfaceId), "Should support IModuleGuard");
}

function test_supportsInterface_IERC165_succeeds() external {
bytes4 interfaceId = 0x01ffc9a7; // IERC165 interface ID
assertTrue(livenessModule2.supportsInterface(interfaceId), "Should support IERC165");
}

function test_supportsInterface_InvalidInterface_reverts() external {
Comment thread
JosepBove marked this conversation as resolved.
Outdated
bytes4 interfaceId = 0xffffffff; // Invalid interface ID
assertFalse(livenessModule2.supportsInterface(interfaceId), "Should not support invalid interface");
}

function test_supportsInterface_ITransactionGuard_succeeds() external {
// SaferSafes (which LivenessModule2 is deployed as) also supports ITransactionGuard
bytes4 interfaceId = 0xe6d7a83a; // ITransactionGuard interface ID
assertTrue(livenessModule2.supportsInterface(interfaceId), "Should support ITransactionGuard");
}
Comment thread
JosepBove marked this conversation as resolved.
Outdated
}
24 changes: 24 additions & 0 deletions packages/contracts-bedrock/test/safe/SaferSafes.t.sol
Comment thread
JosepBove marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,27 @@ contract SaferSafes_Uncategorized_Test is SaferSafes_TestInit {
saferSafes.configureLivenessModule(moduleConfig);
}
}

/// @title SaferSafes_SupportsInterface_Test
/// @notice Tests ERC165 interface support for SaferSafes
contract SaferSafes_SupportsInterface_Test is SaferSafes_TestInit {
function test_supportsInterface_IModuleGuard_succeeds() external {
bytes4 interfaceId = 0x58401ed8; // IModuleGuard interface ID
assertTrue(saferSafes.supportsInterface(interfaceId), "Should support IModuleGuard");
}

function test_supportsInterface_ITransactionGuard_succeeds() external {
bytes4 interfaceId = 0xe6d7a83a; // ITransactionGuard interface ID
assertTrue(saferSafes.supportsInterface(interfaceId), "Should support ITransactionGuard");
}

function test_supportsInterface_IERC165_succeeds() external {
bytes4 interfaceId = 0x01ffc9a7; // IERC165 interface ID
assertTrue(saferSafes.supportsInterface(interfaceId), "Should support IERC165");
}

function test_supportsInterface_InvalidInterface_returns_false() external {
bytes4 interfaceId = 0xffffffff; // Invalid interface ID
assertFalse(saferSafes.supportsInterface(interfaceId), "Should not support invalid interface");
}
}
25 changes: 25 additions & 0 deletions packages/contracts-bedrock/test/safe/TimelockGuard.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -880,3 +880,28 @@ contract TimelockGuard_Integration_Test is TimelockGuard_TestInit {
assertEq(timelockGuard.cancellationThreshold(safeInstance.safe), maxThreshold);
}
}

/// @title TimelockGuard_SupportsInterface_Test
/// @notice Tests ERC165 interface support for TimelockGuard
contract TimelockGuard_SupportsInterface_Test is TimelockGuard_TestInit {
function test_supportsInterface_ITransactionGuard_succeeds() external {
bytes4 interfaceId = 0xe6d7a83a; // ITransactionGuard interface ID
assertTrue(timelockGuard.supportsInterface(interfaceId), "Should support ITransactionGuard");
}

function test_supportsInterface_IERC165_succeeds() external {
bytes4 interfaceId = 0x01ffc9a7; // IERC165 interface ID
assertTrue(timelockGuard.supportsInterface(interfaceId), "Should support IERC165");
}

function test_supportsInterface_InvalidInterface_reverts() external {
bytes4 interfaceId = 0xffffffff; // Invalid interface ID
assertFalse(timelockGuard.supportsInterface(interfaceId), "Should not support invalid interface");
}

function test_supportsInterface_IModuleGuard_succeeds() external {
// SaferSafes (which TimelockGuard is deployed as) also supports IModuleGuard
bytes4 interfaceId = 0x58401ed8; // IModuleGuard interface ID
assertTrue(timelockGuard.supportsInterface(interfaceId), "Should support IModuleGuard");
}
Comment thread
JosepBove marked this conversation as resolved.
Outdated
}