Skip to content

Commit ca6b9fe

Browse files
committed
feat: add ModuleStorageLib (#193)
1 parent 31b8b5b commit ca6b9fe

File tree

2 files changed

+155
-0
lines changed

2 files changed

+155
-0
lines changed

src/libraries/ModuleStorageLib.sol

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
pragma solidity ^0.8.20;
3+
4+
type StoragePointer is bytes32;
5+
6+
/// @title Module Storage Library
7+
/// @notice Library for allocating and accessing ERC-4337 address-associated storage within modules.
8+
library ModuleStorageLib {
9+
/// @notice Allocates a memory buffer for an associated storage key, and sets the associated address and batch
10+
/// index.
11+
/// @param addr The address to associate with the storage key.
12+
/// @param batchIndex The batch index to associate with the storage key.
13+
/// @param keySize The size of the key in words, where each word is 32 bytes. Not inclusive of the address and
14+
/// batch index.
15+
/// @return key The allocated memory buffer.
16+
function allocateAssociatedStorageKey(address addr, uint256 batchIndex, uint8 keySize)
17+
internal
18+
pure
19+
returns (bytes memory key)
20+
{
21+
/// @solidity memory-safe-assembly
22+
assembly {
23+
// Clear any dirty upper bits of keySize to prevent overflow
24+
keySize := and(keySize, 0xff)
25+
26+
// compute the total size of the buffer, include the address and batch index
27+
let totalSize := add(64, mul(32, keySize))
28+
29+
// Allocate memory for the key
30+
key := mload(0x40)
31+
mstore(0x40, add(add(key, totalSize), 32))
32+
mstore(key, totalSize)
33+
34+
// Clear any dirty upper bits of address
35+
addr := and(addr, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
36+
// Store the address and batch index in the key buffer
37+
mstore(add(key, 32), addr)
38+
mstore(add(key, 64), batchIndex)
39+
}
40+
}
41+
42+
function associatedStorageLookup(bytes memory key, bytes32 input) internal pure returns (StoragePointer ptr) {
43+
/// @solidity memory-safe-assembly
44+
assembly {
45+
mstore(add(key, 96), input)
46+
ptr := keccak256(add(key, 32), mload(key))
47+
}
48+
}
49+
50+
function associatedStorageLookup(bytes memory key, bytes32 input1, bytes32 input2)
51+
internal
52+
pure
53+
returns (StoragePointer ptr)
54+
{
55+
/// @solidity memory-safe-assembly
56+
assembly {
57+
mstore(add(key, 96), input1)
58+
mstore(add(key, 128), input2)
59+
ptr := keccak256(add(key, 32), mload(key))
60+
}
61+
}
62+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
pragma solidity ^0.8.20;
3+
4+
import {Test} from "forge-std/Test.sol";
5+
6+
import {ModuleStorageLib, StoragePointer} from "../../src/libraries/ModuleStorageLib.sol";
7+
8+
contract ModuleStorageLibTest is Test {
9+
using ModuleStorageLib for bytes;
10+
using ModuleStorageLib for bytes32;
11+
12+
uint256 public constant FUZZ_ARR_SIZE = 32;
13+
14+
address public account1;
15+
16+
struct TestStruct {
17+
uint256 a;
18+
uint256 b;
19+
}
20+
21+
function setUp() public {
22+
account1 = makeAddr("account1");
23+
}
24+
25+
function test_storagePointer() public {
26+
bytes memory key = ModuleStorageLib.allocateAssociatedStorageKey(account1, 0, 1);
27+
28+
StoragePointer ptr = ModuleStorageLib.associatedStorageLookup(
29+
key, hex"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
30+
);
31+
TestStruct storage val = _castPtrToStruct(ptr);
32+
33+
vm.record();
34+
val.a = 0xdeadbeef;
35+
val.b = 123;
36+
(, bytes32[] memory accountWrites) = vm.accesses(address(this));
37+
38+
// printStorageReadsAndWrites(address(this));
39+
40+
assertEq(accountWrites.length, 2);
41+
bytes32 expectedKey = keccak256(
42+
abi.encodePacked(
43+
uint256(uint160(account1)),
44+
uint256(0),
45+
hex"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
46+
)
47+
);
48+
assertEq(accountWrites[0], expectedKey);
49+
assertEq(vm.load(address(this), expectedKey), bytes32(uint256(0xdeadbeef)));
50+
assertEq(accountWrites[1], bytes32(uint256(expectedKey) + 1));
51+
assertEq(vm.load(address(this), bytes32(uint256(expectedKey) + 1)), bytes32(uint256(123)));
52+
}
53+
54+
function testFuzz_storagePointer(
55+
address account,
56+
uint256 batchIndex,
57+
bytes32 inputKey,
58+
uint256[FUZZ_ARR_SIZE] calldata values
59+
) public {
60+
bytes memory key = ModuleStorageLib.allocateAssociatedStorageKey(account, batchIndex, 1);
61+
uint256[FUZZ_ARR_SIZE] storage val =
62+
_castPtrToArray(ModuleStorageLib.associatedStorageLookup(key, inputKey));
63+
// Write values to storage
64+
vm.record();
65+
for (uint256 i = 0; i < FUZZ_ARR_SIZE; i++) {
66+
val[i] = values[i];
67+
}
68+
// Assert the writes took place in the right location, and the correct value is stored there
69+
(, bytes32[] memory accountWrites) = vm.accesses(address(this));
70+
assertEq(accountWrites.length, FUZZ_ARR_SIZE);
71+
for (uint256 i = 0; i < FUZZ_ARR_SIZE; i++) {
72+
bytes32 expectedKey = bytes32(
73+
uint256(keccak256(abi.encodePacked(uint256(uint160(account)), uint256(batchIndex), inputKey))) + i
74+
);
75+
assertEq(accountWrites[i], expectedKey);
76+
assertEq(vm.load(address(this), expectedKey), bytes32(uint256(values[i])));
77+
}
78+
}
79+
80+
function _castPtrToArray(StoragePointer ptr) internal pure returns (uint256[FUZZ_ARR_SIZE] storage val) {
81+
/// @solidity memory-safe-assembly
82+
assembly {
83+
val.slot := ptr
84+
}
85+
}
86+
87+
function _castPtrToStruct(StoragePointer ptr) internal pure returns (TestStruct storage val) {
88+
/// @solidity memory-safe-assembly
89+
assembly {
90+
val.slot := ptr
91+
}
92+
}
93+
}

0 commit comments

Comments
 (0)