• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

safe-global / safe-smart-account / 16195934108

10 Jul 2025 01:05PM UTC coverage: 94.479%. Remained the same
16195934108

push

github

nlordell
Add Warning for Self-Owned Safes (#1018)

Follow-up to comment by @remedcu, that we should warn against a Safe
self-owning itself unless it is a EIP-7702 delegated account.

304 of 336 branches covered (90.48%)

Branch coverage included in aggregate %.

449 of 461 relevant lines covered (97.4%)

109.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.86
/contracts/base/ModuleManager.sol
1
// SPDX-License-Identifier: LGPL-3.0-only
2
/* solhint-disable one-contract-per-file */
3
pragma solidity >=0.7.0 <0.9.0;
4
import {SelfAuthorized} from "./../common/SelfAuthorized.sol";
5
import {IERC165} from "./../interfaces/IERC165.sol";
6
import {IModuleManager} from "./../interfaces/IModuleManager.sol";
7
import {Enum} from "./../libraries/Enum.sol";
8
// solhint-disable-next-line no-unused-import
9
import {MODULE_GUARD_STORAGE_SLOT} from "./../libraries/SafeStorage.sol";
10
import {Executor} from "./Executor.sol";
11

12
/**
13
 * @title IModuleGuard Interface
14
 */
15
interface IModuleGuard is IERC165 {
16
    /**
17
     * @notice Checks the module transaction details.
18
     * @dev The function needs to implement module transaction validation logic.
19
     * @param to The address to which the transaction is intended.
20
     * @param value The value of the transaction in Wei.
21
     * @param data The transaction data.
22
     * @param operation Operation type (0 for `CALL`, 1 for `DELEGATECALL`) of the module transaction.
23
     * @param module The module involved in the transaction.
24
     * @return moduleTxHash A guard-specific module transaction hash. This value is passed to the matching {checkAfterModuleExecution} call.
25
     */
26
    function checkModuleTransaction(
27
        address to,
28
        uint256 value,
29
        bytes memory data,
30
        Enum.Operation operation,
31
        address module
32
    ) external returns (bytes32 moduleTxHash);
33

34
    /**
35
     * @notice Checks after execution of module transaction.
36
     * @dev The function needs to implement a check after the execution of the module transaction.
37
     * @param txHash The guard-specific module transaction hash returned from the matching {checkModuleTransaction} call.
38
     * @param success The status of the module transaction execution.
39
     */
40
    function checkAfterModuleExecution(bytes32 txHash, bool success) external;
41
}
42

43
/**
44
 * @title Base Module Guard
45
 */
46
abstract contract BaseModuleGuard is IModuleGuard {
47
    /**
48
     * @inheritdoc IERC165
49
     */
50
    function supportsInterface(bytes4 interfaceId) external view virtual override returns (bool) {
51
        return
×
52
            interfaceId == type(IModuleGuard).interfaceId || // 0x58401ed8
×
53
            interfaceId == type(IERC165).interfaceId; // 0x01ffc9a7
54
    }
55
}
56

57
/**
58
 * @title Module Manager
59
 * @notice A contract managing Safe modules.
60
 * @dev Modules are extensions with unlimited access to a Safe that can be added to a Safe by its owners.
61
 *      ⚠️⚠️⚠️ WARNING: Modules are a security risk since they can execute arbitrary transactions,
62
 *      so only trusted and audited modules should be added to a Safe. A malicious module can
63
 *      completely take over a Safe. ⚠️⚠️⚠️
64
 * @author Stefan George - @Georgi87
65
 * @author Richard Meissner - @rmeissner
66
 */
67
abstract contract ModuleManager is SelfAuthorized, Executor, IModuleManager {
68
    /**
69
     * @dev The sentinel module value in the {modules} linked list.
70
     *      `SENTINEL_MODULES` is used to traverse {modules}, such that:
71
     *      1. `modules[SENTINEL_MODULES]` contains the first module
72
     *      2. `modules[last_module]` points back to `SENTINEL_MODULES`
73
     */
74
    address internal constant SENTINEL_MODULES = address(0x1);
75

76
    /**
77
     * @dev The linked list of modules, where `modules[module]` points to the next in the list.
78
     *      A mapping is used to allow for `O(1)` inclusion checks.
79
     */
80
    mapping(address => address) internal modules;
81

82
    /**
83
     * @notice Setup function sets the initial storage of the contract.
84
     *         Optionally executes a delegate call to another contract to setup the modules.
85
     * @param to Optional destination address of the call to execute.
86
     * @param data Optional data of call to execute.
87
     */
88
    function setupModules(address to, bytes memory data) internal {
89
        if (modules[SENTINEL_MODULES] != address(0)) revertWithError("GS100");
181!
90
        modules[SENTINEL_MODULES] = SENTINEL_MODULES;
181✔
91
        if (to != address(0)) {
181✔
92
            if (!isContract(to)) revertWithError("GS002");
18✔
93
            // Setup has to complete successfully or the transaction fails.
94
            if (!execute(to, 0, data, Enum.Operation.DelegateCall, type(uint256).max)) revertWithError("GS000");
16✔
95
        }
96
    }
97

98
    /**
99
     * @notice Runs pre-execution checks for module transactions if a guard is enabled.
100
     * @param to Target address of module transaction.
101
     * @param value Native token value of module transaction.
102
     * @param data Data payload of module transaction.
103
     * @param operation Operation type (0 for `CALL`, 1 for `DELEGATECALL`) of the module transaction.
104
     * @return guard Guard to be used for checking.
105
     * @return guardHash Hash returned from the guard tx check.
106
     */
107
    function preModuleExecution(
108
        address to,
109
        uint256 value,
110
        bytes memory data,
111
        Enum.Operation operation
112
    ) internal returns (address guard, bytes32 guardHash) {
113
        onBeforeExecTransactionFromModule(to, value, data, operation);
52✔
114
        guard = getModuleGuard();
52✔
115

116
        // Only allow-listed modules are allowed.
117
        if (msg.sender == SENTINEL_MODULES || modules[msg.sender] == address(0)) revertWithError("GS104");
52✔
118

119
        if (guard != address(0)) {
34✔
120
            guardHash = IModuleGuard(guard).checkModuleTransaction(to, value, data, operation, msg.sender);
6✔
121
        }
122
    }
123

124
    /**
125
     * @notice Runs post-execution checks for module transactions if a guard is enabled.
126
     * @dev Emits event based on module transaction success.
127
     * @param guard Guard to be used for checking.
128
     * @param guardHash Hash returned from the guard during pre execution check.
129
     * @param success Boolean flag indicating if the call succeeded.
130
     */
131
    function postModuleExecution(address guard, bytes32 guardHash, bool success) internal {
132
        if (guard != address(0)) {
34✔
133
            IModuleGuard(guard).checkAfterModuleExecution(guardHash, success);
6✔
134
        }
135
        if (success) emit ExecutionFromModuleSuccess(msg.sender);
34✔
136
        else emit ExecutionFromModuleFailure(msg.sender);
137
    }
138

139
    /**
140
     * @inheritdoc IModuleManager
141
     */
142
    function enableModule(address module) public override authorized {
80✔
143
        // Module address cannot be null or sentinel.
144
        if (module == address(0) || module == SENTINEL_MODULES) revertWithError("GS101");
78✔
145
        // Module cannot be added twice.
146
        if (modules[module] != address(0)) revertWithError("GS102");
74✔
147
        modules[module] = modules[SENTINEL_MODULES];
72✔
148
        modules[SENTINEL_MODULES] = module;
72✔
149
        emit EnabledModule(module);
72✔
150
    }
151

152
    /**
153
     * @inheritdoc IModuleManager
154
     */
155
    function disableModule(address prevModule, address module) public override authorized {
16✔
156
        // Validate module address and check that it corresponds to a module index.
157
        if (module == address(0) || module == SENTINEL_MODULES) revertWithError("GS101");
14✔
158
        if (modules[prevModule] != module) revertWithError("GS103");
10✔
159
        modules[prevModule] = modules[module];
4✔
160
        modules[module] = address(0);
4✔
161
        emit DisabledModule(module);
4✔
162
    }
163

164
    /**
165
     * @inheritdoc IModuleManager
166
     */
167
    function execTransactionFromModule(
168
        address to,
169
        uint256 value,
170
        bytes memory data,
171
        Enum.Operation operation
172
    ) external override returns (bool success) {
173
        (address guard, bytes32 guardHash) = preModuleExecution(to, value, data, operation);
31✔
174
        success = execute(to, value, data, operation, type(uint256).max);
21✔
175
        postModuleExecution(guard, guardHash, success);
21✔
176
    }
177

178
    /**
179
     * @inheritdoc IModuleManager
180
     */
181
    function execTransactionFromModuleReturnData(
182
        address to,
183
        uint256 value,
184
        bytes memory data,
185
        Enum.Operation operation
186
    ) external override returns (bool success, bytes memory returnData) {
187
        (address guard, bytes32 guardHash) = preModuleExecution(to, value, data, operation);
21✔
188
        success = execute(to, value, data, operation, type(uint256).max);
13✔
189
        /* solhint-disable no-inline-assembly */
190
        /// @solidity memory-safe-assembly
191
        assembly {
13✔
192
            // Load free memory location.
193
            returnData := mload(0x40)
194
            // We allocate memory for the return data by setting the free memory location to
195
            // current free memory location plus the return data size with an additional 32
196
            // bytes for storing the length of the return data.
197
            mstore(0x40, add(returnData, add(returndatasize(), 0x20)))
198
            // Store the size.
199
            mstore(returnData, returndatasize())
200
            // Store the data.
201
            returndatacopy(add(returnData, 0x20), 0, returndatasize())
202
        }
203
        /* solhint-enable no-inline-assembly */
204
        postModuleExecution(guard, guardHash, success);
13✔
205
    }
206

207
    /**
208
     * @inheritdoc IModuleManager
209
     */
210
    function isModuleEnabled(address module) public view override returns (bool) {
211
        return SENTINEL_MODULES != module && modules[module] != address(0);
44✔
212
    }
213

214
    /**
215
     * @inheritdoc IModuleManager
216
     */
217
    function getModulesPaginated(address start, uint256 pageSize) external view override returns (address[] memory array, address next) {
218
        if (start != SENTINEL_MODULES && !isModuleEnabled(start)) revertWithError("GS105");
44✔
219
        if (pageSize == 0) revertWithError("GS106");
40✔
220
        // Init array with max page size.
221
        array = new address[](pageSize);
38✔
222

223
        // Populate return array.
224
        uint256 moduleCount = 0;
38✔
225
        next = modules[start];
38✔
226
        while (next != address(0) && next != SENTINEL_MODULES && moduleCount < pageSize) {
38✔
227
            array[moduleCount] = next;
34✔
228
            next = modules[next];
34✔
229
            ++moduleCount;
34✔
230
        }
231

232
        // Because of the argument validation, we can assume that the loop will always iterate over the valid module list values
233
        // and the `next` variable will either be an enabled module or a sentinel address (signalling the end).
234
        //
235
        // If we haven't reached the end inside the loop, we need to set the next pointer to the last element of the modules array
236
        // because the `next` variable (which is a module by itself) acting as a pointer to the start of the next page is neither
237
        // included to the current page, nor will it be included in the next one if you pass it as a start.
238
        if (next != SENTINEL_MODULES) {
38✔
239
            next = array[moduleCount - 1];
6✔
240
        }
241
        // Set the correct size of the returned array.
242
        /* solhint-disable no-inline-assembly */
243
        /// @solidity memory-safe-assembly
244
        assembly {
36✔
245
            mstore(array, moduleCount)
246
        }
247
        /* solhint-enable no-inline-assembly */
248
    }
249

250
    /**
251
     * @notice Returns true if `account` appears to be a contract.
252
     * @dev This function will return false if invoked during the constructor of a contract,
253
     *      as the code is not created until after the constructor finishes.
254
     * @param account The address being queried.
255
     */
256
    function isContract(address account) internal view returns (bool) {
257
        uint256 size;
18✔
258
        /* solhint-disable no-inline-assembly */
259
        /// @solidity memory-safe-assembly
260
        assembly {
18✔
261
            size := extcodesize(account)
262
        }
263
        /* solhint-enable no-inline-assembly */
264
        return size > 0;
18✔
265
    }
266

267
    /**
268
     * @inheritdoc IModuleManager
269
     */
270
    function setModuleGuard(address moduleGuard) external override authorized {
16!
271
        if (moduleGuard != address(0) && !IModuleGuard(moduleGuard).supportsInterface(type(IModuleGuard).interfaceId))
16!
272
            revertWithError("GS301");
×
273

274
        /* solhint-disable no-inline-assembly */
275
        /// @solidity memory-safe-assembly
276
        assembly {
14✔
277
            sstore(MODULE_GUARD_STORAGE_SLOT, moduleGuard)
278
        }
279
        /* solhint-enable no-inline-assembly */
280
        emit ChangedModuleGuard(moduleGuard);
14✔
281
    }
282

283
    /**
284
     * @dev Internal method to retrieve the current module guard.
285
     * @return moduleGuard The address of the module guard.
286
     */
287
    function getModuleGuard() internal view returns (address moduleGuard) {
288
        /* solhint-disable no-inline-assembly */
289
        /// @solidity memory-safe-assembly
290
        assembly {
52✔
291
            moduleGuard := sload(MODULE_GUARD_STORAGE_SLOT)
292
        }
293
        /* solhint-enable no-inline-assembly */
294
    }
295

296
    /**
297
     * @notice A hook that gets called before execution of {execTransactionFromModule*} methods.
298
     * @param to Destination address of module transaction.
299
     * @param value Native token value of module transaction.
300
     * @param data Data payload of module transaction.
301
     * @param operation Operation type of module transaction.
302
     */
303
    function onBeforeExecTransactionFromModule(address to, uint256 value, bytes memory data, Enum.Operation operation) internal virtual {}
304
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc