Skip to content

Commit eed9012

Browse files
authored
feat: IERC165 support on SaferSafes (#17864)
* feat: IERC165 support * fixes * fmt * fix: maurelian's feedback * feat: matt's review * fix: pre-pr * fix: semver
1 parent 4e57f1e commit eed9012

File tree

6 files changed

+102
-5
lines changed

6 files changed

+102
-5
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import { Enum } from "safe-contracts/common/Enum.sol";
5+
import { IERC165 } from "safe-contracts/interfaces/IERC165.sol";
6+
7+
/// @title ITransactionGuard Interface
8+
interface ITransactionGuard is IERC165 {
9+
/// @notice Checks the transaction details.
10+
/// @dev The function needs to implement transaction validation logic.
11+
/// @param to The address to which the transaction is intended.
12+
/// @param value The native token value of the transaction in Wei.
13+
/// @param data The transaction data.
14+
/// @param operation Operation type (0 for `CALL`, 1 for `DELEGATECALL`).
15+
/// @param safeTxGas Gas used for the transaction.
16+
/// @param baseGas The base gas for the transaction.
17+
/// @param gasPrice The price of gas in Wei for the transaction.
18+
/// @param gasToken The token used to pay for gas.
19+
/// @param refundReceiver The address which should receive the refund.
20+
/// @param signatures The signatures of the transaction.
21+
/// @param msgSender The address of the message sender.
22+
function checkTransaction(
23+
address to,
24+
uint256 value,
25+
bytes memory data,
26+
Enum.Operation operation,
27+
uint256 safeTxGas,
28+
uint256 baseGas,
29+
uint256 gasPrice,
30+
address gasToken,
31+
address payable refundReceiver,
32+
bytes memory signatures,
33+
address msgSender
34+
) external;
35+
36+
/// @notice Checks after execution of the transaction.
37+
/// @dev The function needs to implement a check after the execution of the transaction.
38+
/// @param hash The hash of the executed transaction.
39+
/// @param success The status of the transaction execution.
40+
function checkAfterExecution(bytes32 hash, bool success) external;
41+
}

packages/contracts-bedrock/snapshots/abi/SaferSafes.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,25 @@
559559
"stateMutability": "nonpayable",
560560
"type": "function"
561561
},
562+
{
563+
"inputs": [
564+
{
565+
"internalType": "bytes4",
566+
"name": "_interfaceId",
567+
"type": "bytes4"
568+
}
569+
],
570+
"name": "supportsInterface",
571+
"outputs": [
572+
{
573+
"internalType": "bool",
574+
"name": "",
575+
"type": "bool"
576+
}
577+
],
578+
"stateMutability": "view",
579+
"type": "function"
580+
},
562581
{
563582
"inputs": [
564583
{

packages/contracts-bedrock/snapshots/semver-lock.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,8 @@
208208
"sourceCodeHash": "0x950725f8b9ad9bb3b6b5e836f67e18db824a7864bac547ee0eeba88ada3de0e9"
209209
},
210210
"src/safe/SaferSafes.sol:SaferSafes": {
211-
"initCodeHash": "0xaa17bb150c9bcf19675a33e9762b050148aceae9f6a9a6ba020fc6947ebaab39",
212-
"sourceCodeHash": "0xc4201612048ff051ed795521efa3eece1a6556f2c514a268b180d84a2ad8b2d1"
211+
"initCodeHash": "0x95ee7ae09ee281f224425f152c9154e43e49838edbe3eee48c15301e5f410d25",
212+
"sourceCodeHash": "0xdc794e3d6decb47c51d86bb2f69523feade4fd3f81feb4734800f24b40d40a50"
213213
},
214214
"src/universal/OptimismMintableERC20.sol:OptimismMintableERC20": {
215215
"initCodeHash": "0x3c85eed0d017dca8eda6396aa842ddc12492587b061e8c756a8d32c4610a9658",

packages/contracts-bedrock/src/safe/SaferSafes.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import { ISemver } from "interfaces/universal/ISemver.sol";
2222
/// functionality is not desired, then there is no need to enable or configure it.
2323
contract SaferSafes is LivenessModule2, TimelockGuard, ISemver {
2424
/// @notice Semantic version.
25-
/// @custom:semver 1.2.0
26-
string public constant version = "1.2.0";
25+
/// @custom:semver 1.3.0
26+
string public constant version = "1.3.0";
2727

2828
/// @notice Error for when the liveness response period is insufficient.
2929
error SaferSafes_InsufficientLivenessResponsePeriod();

packages/contracts-bedrock/src/safe/TimelockGuard.sol

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ pragma solidity 0.8.15;
55
import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol";
66
import { Enum } from "safe-contracts/common/Enum.sol";
77
import { Guard as IGuard } from "safe-contracts/base/GuardManager.sol";
8+
import { IERC165 } from "safe-contracts/interfaces/IERC165.sol";
89

910
// Libraries
1011
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
1112
import { SemverComp } from "src/libraries/SemverComp.sol";
1213
import { Constants } from "src/libraries/Constants.sol";
1314

15+
// Interfaces
16+
import { ITransactionGuard } from "interfaces/safe/ITransactionGuard.sol";
17+
1418
/// @title TimelockGuard
1519
/// @notice This guard provides timelock functionality for Safe transactions
1620
/// @dev This is a singleton contract, any Safe on the network can use this guard to enforce a timelock delay, and
@@ -65,7 +69,7 @@ import { Constants } from "src/libraries/Constants.sol";
6569
/// | Quorum+ | challenge + | cancelTransaction |
6670
/// | | changeOwnershipToFallback | |
6771
/// +-------------------------------------------------------------------------------------------------+
68-
abstract contract TimelockGuard is IGuard {
72+
abstract contract TimelockGuard is IGuard, IERC165 {
6973
using EnumerableSet for EnumerableSet.Bytes32Set;
7074

7175
/// @notice Allowed states of a transaction
@@ -673,4 +677,16 @@ abstract contract TimelockGuard is IGuard {
673677
function signCancellation(bytes32) public {
674678
emit Message("This function is not meant to be called, did you mean to call cancelTransaction?");
675679
}
680+
681+
////////////////////////////////////////////////////////////////
682+
// ERC165 Support //
683+
////////////////////////////////////////////////////////////////
684+
685+
/// @notice ERC165 interface detection
686+
/// @param _interfaceId The interface identifier to check
687+
/// @return True if the contract implements the interface
688+
function supportsInterface(bytes4 _interfaceId) external view virtual override returns (bool) {
689+
return _interfaceId == type(ITransactionGuard).interfaceId // 0xe6d7a83a
690+
|| _interfaceId == type(IERC165).interfaceId; // 0x01ffc9a7
691+
}
676692
}

packages/contracts-bedrock/test/safe/TimelockGuard.t.sol

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pragma solidity 0.8.15;
44
import { Test } from "forge-std/Test.sol";
55
import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol";
66
import { GuardManager } from "safe-contracts/base/GuardManager.sol";
7+
import { ITransactionGuard } from "interfaces/safe/ITransactionGuard.sol";
78
import "test/safe-tools/SafeTestTools.sol";
89

910
import { TimelockGuard } from "src/safe/TimelockGuard.sol";
@@ -1045,3 +1046,23 @@ contract TimelockGuard_ClearTimelockGuard_Test is TimelockGuard_TestInit {
10451046
timelockGuard.clearTimelockGuard();
10461047
}
10471048
}
1049+
1050+
/// @title TimelockGuard_SupportsInterface_Test
1051+
/// @notice Tests ERC165 interface support for TimelockGuard
1052+
contract TimelockGuard_SupportsInterface_Test is TimelockGuard_TestInit {
1053+
function test_supportsInterface_iTransactionGuard_succeeds() external view {
1054+
bytes4 interfaceId = 0xe6d7a83a; // ITransactionGuard interface ID
1055+
assertTrue(timelockGuard.supportsInterface(interfaceId), "Should support ITransactionGuard");
1056+
}
1057+
1058+
function test_supportsInterface_ierc165_succeeds() external view {
1059+
bytes4 interfaceId = 0x01ffc9a7; // IERC165 interface ID
1060+
assertTrue(timelockGuard.supportsInterface(interfaceId), "Should support IERC165");
1061+
}
1062+
1063+
function test_supportsInterface_invalidInterface_fails(bytes4 _interfaceId) external view {
1064+
vm.assume(_interfaceId != type(ITransactionGuard).interfaceId);
1065+
vm.assume(_interfaceId != type(IERC165).interfaceId);
1066+
assertFalse(timelockGuard.supportsInterface(_interfaceId), "Should not support invalid interface");
1067+
}
1068+
}

0 commit comments

Comments
 (0)