Skip to content

Commit 6a71753

Browse files
mmv08nlordell
andauthored
Refactor Storage Slot Usage in Contracts (#979)
This pull request introduces new top-level constants `SafeStorage` library to centralize and standardize the management of storage slot constants across multiple contracts. Why: To prevent copy-paste, examples would be: - Our own `HandlerContext` contract changed in the PR - [guardrail contract](https://github.com/safe-research/guardrail/blob/13d978060fc245951cf4d0ae02c0110c9e7d8c1f/src/Guardrail.sol#L27) This change reduces gas costs by inlining constants in assembly where they previously were not. --------- Co-authored-by: Nicholas Rodrigues Lordello <nick@safe.global>
1 parent 4597693 commit 6a71753

File tree

8 files changed

+50
-43
lines changed

8 files changed

+50
-43
lines changed

certora/applyHarness.patch

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
diff -druN Safe.sol Safe.sol
2-
--- Safe.sol 2025-05-27 10:10:59
3-
+++ Safe.sol 2025-05-27 10:10:59
2+
--- Safe.sol 2025-07-09 12:21:55.831312348 +0000
3+
+++ Safe.sol 2025-07-09 12:45:47.086078483 +0000
44
@@ -75,22 +75,25 @@
55
* so we create a Safe with 0 owners and threshold 1.
66
* This is an unusable Safe, perfect for the singleton
@@ -31,7 +31,7 @@ diff -druN Safe.sol Safe.sol
3131
// Emit the setup event optimistically. This ensures that changes such as `addOwner` and `changeThreshold` that are part
3232
// of the `to.delegatecall(data)` that happen in the `setupModules` call emit events in order relative to the setup
3333
// event, making it easier for off-chain indexers to reliably reconstruct the Safe configuration.
34-
@@ -397,9 +400,6 @@
34+
@@ -398,9 +401,6 @@
3535
return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, chainId, this));
3636
}
3737

@@ -41,7 +41,7 @@ diff -druN Safe.sol Safe.sol
4141
function getTransactionHash(
4242
address to,
4343
uint256 value,
44-
@@ -411,7 +411,9 @@
44+
@@ -412,7 +412,9 @@
4545
address gasToken,
4646
address refundReceiver,
4747
uint256 _nonce
@@ -52,7 +52,7 @@ diff -druN Safe.sol Safe.sol
5252
bytes32 domainHash = domainSeparator();
5353

5454
// We opted for using assembly code here, because the way Solidity compiler we use (0.7.6) allocates memory is
55-
@@ -466,7 +468,8 @@
55+
@@ -467,7 +469,8 @@
5656
// Store the domain separator
5757
mstore(add(ptr, 32), domainHash)
5858
// Calculate the hash
@@ -63,8 +63,8 @@ diff -druN Safe.sol Safe.sol
6363
/* solhint-enable no-inline-assembly */
6464
}
6565
diff -druN base/Executor.sol base/Executor.sol
66-
--- base/Executor.sol 2025-05-27 10:10:59
67-
+++ base/Executor.sol 2025-05-27 10:10:59
66+
--- base/Executor.sol 2025-07-03 08:51:19.308739177 +0000
67+
+++ base/Executor.sol 2025-07-09 12:45:47.086392895 +0000
6868
@@ -26,12 +26,8 @@
6969
uint256 txGas
7070
) internal returns (bool success) {
@@ -81,9 +81,9 @@ diff -druN base/Executor.sol base/Executor.sol
8181
/* solhint-disable no-inline-assembly */
8282
/// @solidity memory-safe-assembly
8383
diff -druN base/FallbackManager.sol base/FallbackManager.sol
84-
--- base/FallbackManager.sol 2025-05-27 10:10:59
85-
+++ base/FallbackManager.sol 2025-05-27 10:10:59
86-
@@ -61,7 +61,8 @@
84+
--- base/FallbackManager.sol 2025-07-09 12:42:34.885183896 +0000
85+
+++ base/FallbackManager.sol 2025-07-09 12:45:47.086663906 +0000
86+
@@ -60,7 +60,8 @@
8787
// not going beyond the scratch space, etc)
8888
// Solidity docs: https://docs.soliditylang.org/en/latest/assembly.html#memory-safety
8989

@@ -94,8 +94,8 @@ diff -druN base/FallbackManager.sol base/FallbackManager.sol
9494
if iszero(handler) {
9595
return(0, 0)
9696
diff -druN interfaces/ISafe.sol interfaces/ISafe.sol
97-
--- interfaces/ISafe.sol 2025-05-27 10:10:59
98-
+++ interfaces/ISafe.sol 2025-05-27 10:10:59
97+
--- interfaces/ISafe.sol 2025-07-03 08:51:19.312340984 +0000
98+
+++ interfaces/ISafe.sol 2025-07-09 12:45:47.087200687 +0000
9999
@@ -117,7 +117,7 @@
100100
*/
101101
function domainSeparator() external view returns (bytes32);

certora/harnesses/ExtensibleFallbackHandlerHarness.sol

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ import "../munged/handler/ExtensibleFallbackHandler.sol";
33
import {ISafe} from "../munged/interfaces/ISafe.sol";
44

55
contract ExtensibleFallbackHandlerHarness is ExtensibleFallbackHandler {
6-
76
function getSafeMethod(ISafe safe, bytes4 selector) public view returns (bytes32) {
87
return safeMethods[safe][selector];
98
}
10-
11-
}
9+
}

certora/harnesses/SafeHarness.sol

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import "../munged/Safe.sol";
33
import {SafeMath} from "../munged/external/SafeMath.sol";
44
import {ISafe, IStaticFallbackMethod, IFallbackMethod, ExtensibleBase} from "../munged/handler/extensible/ExtensibleBase.sol";
55
import {IFallbackHandler, FallbackHandler} from "../munged/handler/extensible/FallbackHandler.sol";
6-
6+
import {FALLBACK_HANDLER_STORAGE_SLOT} from "../munged/libraries/SafeStorage.sol";
77

88
contract SafeHarness is Safe {
99
constructor(
@@ -35,8 +35,8 @@ contract SafeHarness is Safe {
3535
}
3636
}
3737

38-
function numSigsSufficient(bytes memory signatures,uint256 requiredSignatures) public pure returns (bool) {
39-
return (signatures.length >= SafeMath.mul(requiredSignatures,65));
38+
function numSigsSufficient(bytes memory signatures, uint256 requiredSignatures) public pure returns (bool) {
39+
return (signatures.length >= SafeMath.mul(requiredSignatures, 65));
4040
}
4141

4242
// harnessed getters
@@ -74,14 +74,14 @@ contract SafeHarness is Safe {
7474

7575
function getFallbackHandler() public view returns (address) {
7676
address handler;
77-
assembly{
77+
assembly {
7878
handler := sload(FALLBACK_HANDLER_STORAGE_SLOT)
7979
}
80-
return handler ;
80+
return handler;
8181
}
8282

8383
function callSetSafeMethod(bytes4 selector, bytes32 newMethod) public {
84-
IFallbackHandler(address(this)).setSafeMethod(selector,newMethod);
84+
IFallbackHandler(address(this)).setSafeMethod(selector, newMethod);
8585
}
8686

8787
function callDummyHandler(bytes4 selector) public {

contracts/base/FallbackManager.sol

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@ pragma solidity >=0.7.0 <0.9.0;
33

44
import {SelfAuthorized} from "../common/SelfAuthorized.sol";
55
import {IFallbackManager} from "../interfaces/IFallbackManager.sol";
6+
// solhint-disable-next-line no-unused-import
7+
import {FALLBACK_HANDLER_STORAGE_SLOT} from "../libraries/SafeStorage.sol";
68

79
/**
810
* @title Fallback Manager - A contract managing fallback calls made to this contract
911
* @author Richard Meissner - @rmeissner
1012
*/
1113
abstract contract FallbackManager is SelfAuthorized, IFallbackManager {
12-
// keccak256("fallback_manager.handler.address")
13-
bytes32 internal constant FALLBACK_HANDLER_STORAGE_SLOT = 0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5;
14-
1514
/**
1615
* @notice Internal function to set the fallback handler.
1716
* @param handler contract to handle fallback calls.

contracts/base/GuardManager.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {SelfAuthorized} from "./../common/SelfAuthorized.sol";
66
import {IERC165} from "./../interfaces/IERC165.sol";
77
import {IGuardManager} from "./../interfaces/IGuardManager.sol";
88
import {Enum} from "./../libraries/Enum.sol";
9+
// solhint-disable-next-line no-unused-import
10+
import {GUARD_STORAGE_SLOT} from "../libraries/SafeStorage.sol";
911

1012
/**
1113
* @title ITransactionGuard Interface
@@ -62,15 +64,13 @@ abstract contract BaseTransactionGuard is ITransactionGuard {
6264
* @author Richard Meissner - @rmeissner
6365
*/
6466
abstract contract GuardManager is SelfAuthorized, IGuardManager {
65-
// keccak256("guard_manager.guard.address")
66-
bytes32 internal constant GUARD_STORAGE_SLOT = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8;
67-
6867
/**
6968
* @inheritdoc IGuardManager
7069
*/
7170
function setGuard(address guard) external override authorized {
7271
if (guard != address(0) && !ITransactionGuard(guard).supportsInterface(type(ITransactionGuard).interfaceId))
7372
revertWithError("GS300");
73+
7474
/* solhint-disable no-inline-assembly */
7575
/// @solidity memory-safe-assembly
7676
assembly {

contracts/base/ModuleManager.sol

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {SelfAuthorized} from "./../common/SelfAuthorized.sol";
55
import {IERC165} from "./../interfaces/IERC165.sol";
66
import {IModuleManager} from "./../interfaces/IModuleManager.sol";
77
import {Enum} from "./../libraries/Enum.sol";
8+
// solhint-disable-next-line no-unused-import
9+
import {MODULE_GUARD_STORAGE_SLOT} from "./../libraries/SafeStorage.sol";
810
import {Executor} from "./Executor.sol";
911

1012
/**
@@ -61,9 +63,6 @@ abstract contract ModuleManager is SelfAuthorized, Executor, IModuleManager {
6163
// 2. `modules[last_module]` points back to SENTINEL_MODULES
6264
address internal constant SENTINEL_MODULES = address(0x1);
6365

64-
// keccak256("module_manager.module_guard.address")
65-
bytes32 internal constant MODULE_GUARD_STORAGE_SLOT = 0xb104e0b93118902c651344349b610029d694cfdec91c589c91ebafbcd0289947;
66-
6766
mapping(address => address) internal modules;
6867

6968
/**
@@ -217,10 +216,10 @@ abstract contract ModuleManager is SelfAuthorized, Executor, IModuleManager {
217216

218217
/**
219218
Because of the argument validation, we can assume that the loop will always iterate over the valid module list values
220-
and the `next` variable will either be an enabled module or a sentinel address (signalling the end).
221-
219+
and the `next` variable will either be an enabled module or a sentinel address (signalling the end).
220+
222221
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
223-
because the `next` variable (which is a module by itself) acting as a pointer to the start of the next page is neither
222+
because the `next` variable (which is a module by itself) acting as a pointer to the start of the next page is neither
224223
included to the current page, nor will it be included in the next one if you pass it as a start.
225224
*/
226225
if (next != SENTINEL_MODULES) {
@@ -259,11 +258,10 @@ abstract contract ModuleManager is SelfAuthorized, Executor, IModuleManager {
259258
if (moduleGuard != address(0) && !IModuleGuard(moduleGuard).supportsInterface(type(IModuleGuard).interfaceId))
260259
revertWithError("GS301");
261260

262-
bytes32 slot = MODULE_GUARD_STORAGE_SLOT;
263261
/* solhint-disable no-inline-assembly */
264262
/// @solidity memory-safe-assembly
265263
assembly {
266-
sstore(slot, moduleGuard)
264+
sstore(MODULE_GUARD_STORAGE_SLOT, moduleGuard)
267265
}
268266
/* solhint-enable no-inline-assembly */
269267
emit ChangedModuleGuard(moduleGuard);
@@ -274,11 +272,10 @@ abstract contract ModuleManager is SelfAuthorized, Executor, IModuleManager {
274272
* @return moduleGuard The address of the guard
275273
*/
276274
function getModuleGuard() internal view returns (address moduleGuard) {
277-
bytes32 slot = MODULE_GUARD_STORAGE_SLOT;
278275
/* solhint-disable no-inline-assembly */
279276
/// @solidity memory-safe-assembly
280277
assembly {
281-
moduleGuard := sload(slot)
278+
moduleGuard := sload(MODULE_GUARD_STORAGE_SLOT)
282279
}
283280
/* solhint-enable no-inline-assembly */
284281
}

contracts/handler/HandlerContext.sol

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
pragma solidity >=0.7.0 <0.9.0;
33

44
import {ISafe} from "../interfaces/ISafe.sol";
5+
import {FALLBACK_HANDLER_STORAGE_SLOT} from "../libraries/SafeStorage.sol";
56

67
/**
78
* @title Handler Context - Allows the fallback handler to extract additional context from the calldata
@@ -11,12 +12,6 @@ import {ISafe} from "../interfaces/ISafe.sol";
1112
* @author Richard Meissner - @rmeissner
1213
*/
1314
abstract contract HandlerContext {
14-
/**
15-
* @dev The storage slot used for storing the currently configured fallback handler address.
16-
* Precomputed value of: `keccak256("fallback_manager.handler.address")`.
17-
*/
18-
bytes32 internal constant FALLBACK_HANDLER_STORAGE_SLOT = 0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5;
19-
2015
/**
2116
* @notice A modifier that reverts if not called by a Safe as a fallback handler.
2217
* @dev Note that this modifier does a **best effort** attempt at not allowing calls that are

contracts/libraries/SafeStorage.sol

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,21 @@ abstract contract SafeStorage {
2222
mapping(bytes32 => uint256) internal signedMessages;
2323
mapping(address => mapping(bytes32 => uint256)) internal approvedHashes;
2424
}
25+
26+
/**
27+
* @dev The storage slot used for storing the currently configured fallback handler address.
28+
* Precomputed value of: `keccak256("fallback_manager.handler.address")`.
29+
*/
30+
bytes32 constant FALLBACK_HANDLER_STORAGE_SLOT = 0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5;
31+
32+
/**
33+
* @dev The storage slot used for storing the currently configured transaction guard.
34+
* Precomputed value of: `keccak256("guard_manager.guard.address")`.
35+
*/
36+
bytes32 constant GUARD_STORAGE_SLOT = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8;
37+
38+
/**
39+
* @dev The storage slot used for storing the currently configured module guard.
40+
* Precomputed value of: `keccak256("module_manager.module_guard.address")`.
41+
*/
42+
bytes32 constant MODULE_GUARD_STORAGE_SLOT = 0xb104e0b93118902c651344349b610029d694cfdec91c589c91ebafbcd0289947;

0 commit comments

Comments
 (0)