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

safe-global / safe-smart-account / 14491351564

16 Apr 2025 11:13AM UTC coverage: 93.957%. Remained the same
14491351564

Pull #946

github

nlordell
Fix up the munging patch file from Certora
Pull Request #946: Proofread NatSpec Documentation

325 of 366 branches covered (88.8%)

Branch coverage included in aggregate %.

8 of 8 new or added lines in 3 files covered. (100.0%)

1 existing line in 1 file now uncovered.

499 of 511 relevant lines covered (97.65%)

100.87 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
import {Executor} from "./Executor.sol";
9

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

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

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

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

73
    /**
74
     * @dev The storage slot used for storing the currently configured module guard.
75
     *      Precomputed value of: `keccak256("module_manager.module_guard.address")`.
76
     */
77
    bytes32 internal constant MODULE_GUARD_STORAGE_SLOT = 0xb104e0b93118902c651344349b610029d694cfdec91c589c91ebafbcd0289947;
78

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

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

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

119
        // Only allow-listed modules are allowed.
120
        require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "GS104");
52✔
121

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

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

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

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

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

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

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

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

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

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

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

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

277
        bytes32 slot = MODULE_GUARD_STORAGE_SLOT;
14✔
278
        /* solhint-disable no-inline-assembly */
279
        /// @solidity memory-safe-assembly
280
        assembly {
14✔
281
            sstore(slot, moduleGuard)
282
        }
283
        /* solhint-enable no-inline-assembly */
284
        emit ChangedModuleGuard(moduleGuard);
14✔
285
    }
286

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

301
    /**
302
     * @notice A hook that gets called before execution of {execTransactionFromModule*} methods.
303
     * @param to Destination address of module transaction.
304
     * @param value Native token value of module transaction.
305
     * @param data Data payload of module transaction.
306
     * @param operation Operation type of module transaction.
307
     */
308
    function onBeforeExecTransactionFromModule(address to, uint256 value, bytes memory data, Enum.Operation operation) internal virtual {}
309
}
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