@diamondslab/diamonds-hardhat-foundry 1.0.3 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/README.md +568 -4
  3. package/contracts/DiamondABILoader.sol +329 -0
  4. package/contracts/DiamondForgeHelpers.sol +309 -85
  5. package/contracts/DiamondFuzzBase.sol +305 -114
  6. package/dist/foundry.d.ts +28 -0
  7. package/dist/foundry.d.ts.map +1 -1
  8. package/dist/foundry.js +82 -1
  9. package/dist/foundry.js.map +1 -1
  10. package/dist/framework/DeploymentManager.d.ts +10 -11
  11. package/dist/framework/DeploymentManager.d.ts.map +1 -1
  12. package/dist/framework/DeploymentManager.js +56 -45
  13. package/dist/framework/DeploymentManager.js.map +1 -1
  14. package/dist/framework/ForgeFuzzingFramework.d.ts +4 -0
  15. package/dist/framework/ForgeFuzzingFramework.d.ts.map +1 -1
  16. package/dist/framework/ForgeFuzzingFramework.js +16 -2
  17. package/dist/framework/ForgeFuzzingFramework.js.map +1 -1
  18. package/dist/framework/HelperGenerator.d.ts.map +1 -1
  19. package/dist/framework/HelperGenerator.js +11 -0
  20. package/dist/framework/HelperGenerator.js.map +1 -1
  21. package/dist/index.d.ts +0 -3
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +0 -8
  24. package/dist/index.js.map +1 -1
  25. package/dist/tasks/deploy.js +3 -2
  26. package/dist/tasks/deploy.js.map +1 -1
  27. package/dist/tasks/generate-helpers.js +6 -4
  28. package/dist/tasks/generate-helpers.js.map +1 -1
  29. package/dist/tasks/init.js +3 -2
  30. package/dist/tasks/init.js.map +1 -1
  31. package/dist/tasks/test.js +13 -2
  32. package/dist/tasks/test.js.map +1 -1
  33. package/dist/templates/ExampleFuzzTest.t.sol.template +26 -17
  34. package/dist/templates/ExampleIntegrationTest.t.sol.template +9 -1
  35. package/dist/templates/ExampleUnitTest.t.sol.template +7 -1
  36. package/dist/types/hardhat.d.ts +1 -1
  37. package/dist/types/hardhat.d.ts.map +1 -1
  38. package/dist/types/hardhat.js +1 -0
  39. package/dist/types/hardhat.js.map +1 -1
  40. package/dist/utils/foundry.d.ts +1 -0
  41. package/dist/utils/foundry.d.ts.map +1 -1
  42. package/dist/utils/foundry.js +3 -0
  43. package/dist/utils/foundry.js.map +1 -1
  44. package/package.json +5 -3
  45. package/src/foundry.ts +104 -0
  46. package/src/framework/DeploymentManager.ts +74 -69
  47. package/src/framework/ForgeFuzzingFramework.ts +23 -1
  48. package/src/framework/HelperGenerator.ts +13 -0
  49. package/src/index.ts +0 -5
  50. package/src/tasks/deploy.ts +3 -1
  51. package/src/tasks/generate-helpers.ts +6 -2
  52. package/src/tasks/init.ts +3 -1
  53. package/src/tasks/test.ts +12 -1
  54. package/src/templates/ExampleFuzzTest.t.sol.template +26 -17
  55. package/src/templates/ExampleIntegrationTest.t.sol.template +9 -1
  56. package/src/templates/ExampleUnitTest.t.sol.template +7 -1
  57. package/src/types/hardhat.ts +1 -1
  58. package/src/utils/foundry.ts +5 -0
@@ -2,95 +2,319 @@
2
2
  pragma solidity ^0.8.0;
3
3
 
4
4
  import "forge-std/Test.sol";
5
- import "forge-std/console.sol";
5
+ import "forge-std/Vm.sol";
6
6
 
7
- /**
8
- * @title DiamondForgeHelpers
9
- * @notice Common test utilities for Diamond contract testing with Forge
10
- * @dev Provides helper functions for accessing Diamond deployment data and logging
11
- */
7
+ /// @title DiamondForgeHelpers
8
+ /// @author DiamondsLab
9
+ /// @notice Utility library for Diamond testing with Forge
10
+ /// @dev Provides assertion helpers, address validation, and common test utilities for Diamond contracts
11
+ /// @custom:security This library is intended for testing purposes only
12
12
  library DiamondForgeHelpers {
13
- /**
14
- * @notice Get the Diamond contract address
15
- * @param diamondAddress The address of the deployed Diamond
16
- * @return The Diamond address
17
- */
18
- function getDiamondAddress(address diamondAddress) internal pure returns (address) {
19
- return diamondAddress;
20
- }
21
-
22
- /**
23
- * @notice Get a facet address from the deployment
24
- * @param facetAddress The address of the facet
25
- * @return The facet address
26
- */
27
- function getFacetAddress(address facetAddress) internal pure returns (address) {
28
- return facetAddress;
29
- }
30
-
31
- /**
32
- * @notice Log Diamond deployment information
33
- * @param diamondAddress The Diamond contract address
34
- * @param facetNames Array of facet names
35
- * @param facetAddresses Array of facet addresses
36
- */
37
- function logDiamondInfo(
38
- address diamondAddress,
39
- string[] memory facetNames,
40
- address[] memory facetAddresses
13
+ /// @notice Forge VM interface for cheat codes
14
+ Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
15
+
16
+ /// @notice Error for invalid Diamond address
17
+ /// @param diamondAddress The invalid address
18
+ /// @param reason The reason it's invalid
19
+ error InvalidDiamond(address diamondAddress, string reason);
20
+
21
+ /// @notice Error for invalid facet address
22
+ /// @param facetAddress The invalid facet address
23
+ /// @param facetName The facet name
24
+ /// @param reason The reason it's invalid
25
+ error InvalidFacet(address facetAddress, string facetName, string reason);
26
+
27
+ /// @notice Assert that an address is a valid Diamond contract
28
+ /// @dev Checks that:
29
+ /// - Address is not zero
30
+ /// - Contract has code deployed
31
+ /// - Contract supports DiamondLoupe interface
32
+ /// @param diamond The Diamond contract address to validate
33
+ function assertValidDiamond(address diamond) internal view {
34
+ // Check non-zero address
35
+ if (diamond == address(0)) {
36
+ revert InvalidDiamond(diamond, "Diamond address is zero");
37
+ }
38
+
39
+ // Check contract has code
40
+ if (diamond.code.length == 0) {
41
+ revert InvalidDiamond(diamond, "Diamond has no code deployed");
42
+ }
43
+
44
+ // Check DiamondLoupe interface support
45
+ // Try calling facets() which should exist in any EIP-2535 Diamond
46
+ (bool success, ) = diamond.staticcall(abi.encodeWithSignature("facets()"));
47
+ if (!success) {
48
+ revert InvalidDiamond(
49
+ diamond,
50
+ "Diamond does not support DiamondLoupe (facets() call failed)"
51
+ );
52
+ }
53
+ }
54
+
55
+ /// @notice Assert that an address is a valid facet contract
56
+ /// @dev Checks that:
57
+ /// - Address is not zero
58
+ /// - Contract has code deployed
59
+ /// @param facetAddress The facet contract address to validate
60
+ /// @param facetName The facet name for error messaging
61
+ function assertValidFacet(address facetAddress, string memory facetName) internal view {
62
+ // Check non-zero address
63
+ if (facetAddress == address(0)) {
64
+ revert InvalidFacet(facetAddress, facetName, "Facet address is zero");
65
+ }
66
+
67
+ // Check contract has code
68
+ if (facetAddress.code.length == 0) {
69
+ revert InvalidFacet(facetAddress, facetName, "Facet has no code deployed");
70
+ }
71
+ }
72
+
73
+ /// @notice Validate address is not zero or known invalid address
74
+ /// @dev Use with vm.assume() in fuzz tests to filter invalid addresses
75
+ /// @param addr The address to validate
76
+ /// @return valid True if address passes basic validation
77
+ function isValidTestAddress(address addr) internal pure returns (bool valid) {
78
+ return
79
+ addr != address(0) &&
80
+ addr != address(0x000000000000000000000000000000000000dEaD) &&
81
+ uint160(addr) > 0xFF; // Avoid precompiles
82
+ }
83
+
84
+ /// @notice Validate amount is reasonable for testing
85
+ /// @dev Use with vm.assume() in fuzz tests to filter extreme values
86
+ /// @param amount The amount to validate
87
+ /// @return valid True if amount is within reasonable bounds
88
+ function isValidTestAmount(uint256 amount) internal pure returns (bool valid) {
89
+ return amount > 0 && amount < type(uint128).max;
90
+ }
91
+
92
+ /// @notice Assert selector exists in Diamond
93
+ /// @dev Uses DiamondLoupe facetAddress() to verify selector is registered
94
+ /// @param diamond The Diamond contract address
95
+ /// @param selector The function selector to check
96
+ function assertSelectorExists(address diamond, bytes4 selector) internal view {
97
+ bytes memory callData = abi.encodeWithSignature("facetAddress(bytes4)", selector);
98
+ (bool success, bytes memory returnData) = diamond.staticcall(callData);
99
+
100
+ require(success, "facetAddress() call failed");
101
+
102
+ address facetAddress = abi.decode(returnData, (address));
103
+ require(facetAddress != address(0), "Selector not found in Diamond");
104
+ }
105
+
106
+ /// @notice Assert selector routes to expected facet
107
+ /// @dev Uses DiamondLoupe facetAddress() to verify routing
108
+ /// @param diamond The Diamond contract address
109
+ /// @param selector The function selector to check
110
+ /// @param expectedFacet The expected facet address
111
+ function assertSelectorRouting(
112
+ address diamond,
113
+ bytes4 selector,
114
+ address expectedFacet
41
115
  ) internal view {
42
- console.log("=== Diamond Deployment Info ===");
43
- console.log("Diamond Address:", diamondAddress);
44
- console.log("Total Facets:", facetAddresses.length);
116
+ bytes memory callData = abi.encodeWithSignature("facetAddress(bytes4)", selector);
117
+ (bool success, bytes memory returnData) = diamond.staticcall(callData);
118
+
119
+ require(success, "facetAddress() call failed");
120
+
121
+ address actualFacet = abi.decode(returnData, (address));
122
+ require(actualFacet == expectedFacet, "Selector routes to unexpected facet");
123
+ }
124
+
125
+ /// @notice Get facet address for a selector
126
+ /// @dev Wrapper around DiamondLoupe facetAddress()
127
+ /// @param diamond The Diamond contract address
128
+ /// @param selector The function selector
129
+ /// @return facetAddress The facet address that implements this selector
130
+ function getFacetAddress(
131
+ address diamond,
132
+ bytes4 selector
133
+ ) internal view returns (address facetAddress) {
134
+ bytes memory callData = abi.encodeWithSignature("facetAddress(bytes4)", selector);
135
+ (bool success, bytes memory returnData) = diamond.staticcall(callData);
136
+
137
+ require(success, "facetAddress() call failed");
138
+ facetAddress = abi.decode(returnData, (address));
139
+ }
140
+
141
+ /// @notice Get all facets in Diamond
142
+ /// @dev Calls DiamondLoupe facets()
143
+ /// @param diamond The Diamond contract address
144
+ /// @return facets Array of Facet structs with address and selectors
145
+ function getAllFacets(address diamond) internal view returns (bytes memory facets) {
146
+ bytes memory callData = abi.encodeWithSignature("facets()");
147
+ (bool success, bytes memory returnData) = diamond.staticcall(callData);
148
+
149
+ require(success, "facets() call failed");
150
+ return returnData;
151
+ }
152
+
153
+ /// @notice Get facet addresses
154
+ /// @dev Calls DiamondLoupe facetAddresses()
155
+ /// @param diamond The Diamond contract address
156
+ /// @return facetAddresses Array of all facet addresses
157
+ function getFacetAddresses(
158
+ address diamond
159
+ ) internal view returns (address[] memory facetAddresses) {
160
+ bytes memory callData = abi.encodeWithSignature("facetAddresses()");
161
+ (bool success, bytes memory returnData) = diamond.staticcall(callData);
162
+
163
+ require(success, "facetAddresses() call failed");
164
+ facetAddresses = abi.decode(returnData, (address[]));
165
+ }
166
+
167
+ /// @notice Get function selectors for a facet
168
+ /// @dev Calls DiamondLoupe facetFunctionSelectors()
169
+ /// @param diamond The Diamond contract address
170
+ /// @param facet The facet address to query
171
+ /// @return selectors Array of function selectors
172
+ function getFacetSelectors(
173
+ address diamond,
174
+ address facet
175
+ ) internal view returns (bytes4[] memory selectors) {
176
+ bytes memory callData = abi.encodeWithSignature("facetFunctionSelectors(address)", facet);
177
+ (bool success, bytes memory returnData) = diamond.staticcall(callData);
178
+
179
+ require(success, "facetFunctionSelectors() call failed");
180
+ selectors = abi.decode(returnData, (bytes4[]));
181
+ }
182
+
183
+ /// @notice Assert Diamond owner matches expected address
184
+ /// @dev Assumes owner() function exists in Diamond
185
+ /// @param diamond The Diamond contract address
186
+ /// @param expectedOwner The expected owner address
187
+ function assertDiamondOwner(address diamond, address expectedOwner) internal view {
188
+ bytes memory callData = abi.encodeWithSignature("owner()");
189
+ (bool success, bytes memory returnData) = diamond.staticcall(callData);
190
+
191
+ require(success, "owner() call failed");
192
+
193
+ address actualOwner = abi.decode(returnData, (address));
194
+ require(actualOwner == expectedOwner, "Diamond owner does not match expected");
195
+ }
196
+
197
+ /// @notice Get Diamond owner address
198
+ /// @dev Assumes owner() function exists in Diamond
199
+ /// @param diamond The Diamond contract address
200
+ /// @return owner The current Diamond owner
201
+ function getDiamondOwner(address diamond) internal view returns (address owner) {
202
+ bytes memory callData = abi.encodeWithSignature("owner()");
203
+ (bool success, bytes memory returnData) = diamond.staticcall(callData);
204
+
205
+ require(success, "owner() call failed");
206
+ owner = abi.decode(returnData, (address));
207
+ }
208
+
209
+ /// @notice Format bytes4 selector as hex string
210
+ /// @dev Utility for logging and debugging
211
+ /// @param selector The bytes4 selector
212
+ /// @return hexString The hex string representation
213
+ function toHexString(bytes4 selector) internal pure returns (string memory hexString) {
214
+ bytes memory buffer = new bytes(10);
215
+ buffer[0] = "0";
216
+ buffer[1] = "x";
217
+
218
+ bytes memory hexChars = "0123456789abcdef";
219
+ bytes memory selectorBytes = abi.encodePacked(selector);
220
+
221
+ for (uint256 i = 0; i < 4; i++) {
222
+ uint8 value = uint8(selectorBytes[i]);
223
+ buffer[2 + i * 2] = hexChars[value >> 4];
224
+ buffer[3 + i * 2] = hexChars[value & 0x0f];
225
+ }
226
+
227
+ return string(buffer);
228
+ }
45
229
 
46
- for (uint256 i = 0; i < facetNames.length; i++) {
47
- console.log("Facet:", facetNames[i]);
48
- console.log(" Address:", facetAddresses[i]);
230
+ /// @notice Create a bounded random address for fuzzing
231
+ /// @dev Ensures address is valid for testing
232
+ /// @param seed The fuzzing seed value
233
+ /// @return addr A valid test address
234
+ function boundAddress(uint256 seed) internal pure returns (address addr) {
235
+ // Create address from seed, ensuring it's valid
236
+ addr = address(uint160(seed));
237
+
238
+ // Re-hash if address is invalid
239
+ while (!isValidTestAddress(addr)) {
240
+ seed = uint256(keccak256(abi.encodePacked(seed)));
241
+ addr = address(uint160(seed));
49
242
  }
50
- console.log("================================");
51
- }
52
-
53
- /**
54
- * @notice Log a single facet's information
55
- * @param facetName Name of the facet
56
- * @param facetAddress Address of the facet
57
- */
58
- function logFacetInfo(string memory facetName, address facetAddress) internal view {
59
- console.log("Facet:", facetName);
60
- console.log("Address:", facetAddress);
61
- }
62
-
63
- /**
64
- * @notice Assert that an address is not zero
65
- * @param addr The address to check
66
- * @param message Error message if assertion fails
67
- */
68
- function assertNonZeroAddress(address addr, string memory message) internal pure {
69
- require(addr != address(0), message);
70
- }
71
-
72
- /**
73
- * @notice Assert that Diamond address is valid
74
- * @param diamondAddress The Diamond address to validate
75
- */
76
- function assertValidDiamond(address diamondAddress) internal pure {
77
- require(diamondAddress != address(0), "Invalid Diamond address: zero address");
78
- require(diamondAddress.code.length > 0, "Invalid Diamond address: no code deployed");
79
- }
80
-
81
- /**
82
- * @notice Assert that a facet address is valid
83
- * @param facetAddress The facet address to validate
84
- * @param facetName Name of the facet for error messaging
85
- */
86
- function assertValidFacet(address facetAddress, string memory facetName) internal pure {
87
- require(
88
- facetAddress != address(0),
89
- string(abi.encodePacked("Invalid ", facetName, ": zero address"))
90
- );
91
- require(
92
- facetAddress.code.length > 0,
93
- string(abi.encodePacked("Invalid ", facetName, ": no code deployed"))
94
- );
243
+
244
+ return addr;
245
+ }
246
+
247
+ /// @notice Create a bounded random amount for fuzzing
248
+ /// @dev Ensures amount is reasonable for testing
249
+ /// @param seed The fuzzing seed value
250
+ /// @param min The minimum amount (inclusive)
251
+ /// @param max The maximum amount (inclusive)
252
+ /// @return amount A valid test amount
253
+ function boundAmount(
254
+ uint256 seed,
255
+ uint256 min,
256
+ uint256 max
257
+ ) internal pure returns (uint256 amount) {
258
+ require(max >= min, "max must be >= min");
259
+ require(max < type(uint128).max, "max must be < uint128 max");
260
+
261
+ amount = min + (seed % (max - min + 1));
262
+ return amount;
263
+ }
264
+
265
+ /// @notice Compare two bytes arrays for equality
266
+ /// @dev Utility for comparing ABI-encoded data
267
+ /// @param a First bytes array
268
+ /// @param b Second bytes array
269
+ /// @return equal True if arrays are equal
270
+ function bytesEqual(bytes memory a, bytes memory b) internal pure returns (bool equal) {
271
+ return keccak256(a) == keccak256(b);
272
+ }
273
+
274
+ /// @notice Compare two bytes4 arrays for equality
275
+ /// @dev Utility for comparing selector arrays
276
+ /// @param a First array
277
+ /// @param b Second array
278
+ /// @return equal True if arrays contain same selectors (order doesn't matter)
279
+ function selectorsEqual(
280
+ bytes4[] memory a,
281
+ bytes4[] memory b
282
+ ) internal pure returns (bool equal) {
283
+ if (a.length != b.length) {
284
+ return false;
285
+ }
286
+
287
+ // Simple O(n²) comparison for small arrays (typical in Diamond tests)
288
+ for (uint256 i = 0; i < a.length; i++) {
289
+ bool found = false;
290
+ for (uint256 j = 0; j < b.length; j++) {
291
+ if (a[i] == b[j]) {
292
+ found = true;
293
+ break;
294
+ }
295
+ }
296
+ if (!found) {
297
+ return false;
298
+ }
299
+ }
300
+
301
+ return true;
302
+ }
303
+
304
+ /// @notice Take a snapshot of current blockchain state
305
+ /// @dev Uses Foundry's vm.snapshotState() to save state
306
+ /// @dev Only works on networks that support snapshots (Hardhat, Anvil)
307
+ /// @return snapshotId The snapshot identifier to use for reverting
308
+ function snapshotState() internal returns (uint256 snapshotId) {
309
+ snapshotId = vm.snapshotState();
310
+ }
311
+
312
+ /// @notice Revert blockchain state to a previously saved snapshot
313
+ /// @dev Uses Foundry's vm.revertToState() to restore state
314
+ /// @dev The snapshot is consumed after reverting
315
+ /// @param snapshotId The snapshot identifier from snapshotState()
316
+ /// @return success True if revert was successful
317
+ function revertToSnapshot(uint256 snapshotId) internal returns (bool success) {
318
+ success = vm.revertToState(snapshotId);
95
319
  }
96
320
  }