@diamondslab/diamonds-hardhat-foundry 1.0.3 → 2.2.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 (59) hide show
  1. package/CHANGELOG.md +249 -0
  2. package/README.md +650 -4
  3. package/contracts/DiamondABILoader.sol +329 -0
  4. package/contracts/DiamondForgeHelpers.sol +309 -85
  5. package/contracts/DiamondFuzzBase.sol +322 -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 +11 -4
  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/types/hardhat.d.ts +1 -1
  34. package/dist/types/hardhat.d.ts.map +1 -1
  35. package/dist/types/hardhat.js +1 -0
  36. package/dist/types/hardhat.js.map +1 -1
  37. package/dist/utils/foundry.d.ts +1 -0
  38. package/dist/utils/foundry.d.ts.map +1 -1
  39. package/dist/utils/foundry.js +3 -0
  40. package/dist/utils/foundry.js.map +1 -1
  41. package/package.json +5 -3
  42. package/src/foundry.ts +104 -0
  43. package/src/framework/DeploymentManager.ts +74 -69
  44. package/src/framework/ForgeFuzzingFramework.ts +23 -1
  45. package/src/framework/HelperGenerator.ts +13 -0
  46. package/src/index.ts +0 -5
  47. package/src/tasks/deploy.ts +14 -3
  48. package/src/tasks/generate-helpers.ts +6 -2
  49. package/src/tasks/init.ts +3 -1
  50. package/src/tasks/test.ts +12 -1
  51. package/src/templates/ExampleFuzzTest.t.sol.template +26 -17
  52. package/src/templates/ExampleIntegrationTest.t.sol.template +9 -1
  53. package/src/templates/ExampleUnitTest.t.sol.template +7 -1
  54. package/src/types/hardhat.ts +1 -1
  55. package/src/utils/foundry.ts +5 -0
  56. package/dist/templates/DiamondDeployment.sol.template +0 -38
  57. package/dist/templates/ExampleFuzzTest.t.sol.template +0 -109
  58. package/dist/templates/ExampleIntegrationTest.t.sol.template +0 -79
  59. package/dist/templates/ExampleUnitTest.t.sol.template +0 -59
@@ -0,0 +1,329 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ import "forge-std/Test.sol";
5
+
6
+ /// @title Diamond ABI Loader
7
+ /// @author DiamondsLab
8
+ /// @notice Helper library to load and parse Diamond ABI files in Foundry tests
9
+ /// @dev Uses Foundry's vm.readFile() and vm.parseJson() cheatcodes to extract function information from generated Diamond ABI files
10
+ /// @custom:security This library is intended for testing purposes only and should not be used in production contracts
11
+ library DiamondABILoader {
12
+ using stdJson for string;
13
+
14
+ /// @notice Structure to hold parsed function information
15
+ /// @dev Contains all metadata needed to interact with a function via the Diamond proxy
16
+ struct FunctionInfo {
17
+ string name; // Function name (e.g., "owner")
18
+ string signature; // Full signature (e.g., "owner()")
19
+ bytes4 selector; // Function selector (bytes4(keccak256("owner()")))
20
+ bool isView; // True if view/pure function
21
+ bool exists; // True if function was found in ABI
22
+ }
23
+
24
+ /// @notice Load Diamond ABI from file path
25
+ /// @dev Reads the entire ABI JSON file into memory for parsing
26
+ /// @param abiPath Relative or absolute path to the Diamond ABI JSON file (e.g., "./diamond-abi/ExampleDiamond.json")
27
+ /// @return abiJson The raw JSON string content of the ABI file
28
+ /// @custom:example
29
+ /// ```solidity
30
+ /// string memory abi = DiamondABILoader.loadDiamondABI("./diamond-abi/MyDiamond.json");
31
+ /// ```
32
+ function loadDiamondABI(string memory abiPath) internal view returns (string memory abiJson) {
33
+ Vm vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
34
+ abiJson = vm.readFile(abiPath);
35
+ require(bytes(abiJson).length > 0, "DiamondABILoader: Empty ABI file");
36
+ }
37
+
38
+ /// @notice Extract all function selectors from Diamond ABI
39
+ /// @dev Parses the ABI JSON and computes selectors for all function entries
40
+ /// @param abiJson The raw JSON string from Diamond ABI file (from loadDiamondABI)
41
+ /// @return selectors Array of all function selectors found in the ABI
42
+ /// @custom:example
43
+ /// ```solidity
44
+ /// string memory abi = DiamondABILoader.loadDiamondABI("./diamond-abi/MyDiamond.json");
45
+ /// bytes4[] memory selectors = DiamondABILoader.extractSelectors(abi);
46
+ /// ```
47
+ function extractSelectors(
48
+ string memory abiJson
49
+ ) internal pure returns (bytes4[] memory selectors) {
50
+ Vm vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
51
+
52
+ // First pass: count functions (optimized to stop early if possible)
53
+ uint256 functionCount = 0;
54
+ uint256 maxIndex = 0;
55
+
56
+ for (uint256 i = 0; i < 300; i++) {
57
+ bytes memory typeBytes = vm.parseJson(
58
+ abiJson,
59
+ string(abi.encodePacked(".abi[", vm.toString(i), "].type"))
60
+ );
61
+ if (typeBytes.length == 0) {
62
+ maxIndex = i;
63
+ break;
64
+ }
65
+ string memory entryType = abi.decode(typeBytes, (string));
66
+ if (keccak256(bytes(entryType)) == keccak256(bytes("function"))) {
67
+ functionCount++;
68
+ }
69
+ }
70
+
71
+ require(functionCount > 0, "DiamondABILoader: No functions found in ABI");
72
+
73
+ // Allocate and populate in single pass
74
+ selectors = new bytes4[](functionCount);
75
+ uint256 selectorIndex = 0;
76
+
77
+ for (uint256 i = 0; i < maxIndex && selectorIndex < functionCount; i++) {
78
+ bytes memory typeBytes = vm.parseJson(
79
+ abiJson,
80
+ string(abi.encodePacked(".abi[", vm.toString(i), "].type"))
81
+ );
82
+ string memory entryType = abi.decode(typeBytes, (string));
83
+
84
+ if (keccak256(bytes(entryType)) == keccak256(bytes("function"))) {
85
+ string memory indexPath = string(abi.encodePacked(".abi[", vm.toString(i), "]"));
86
+
87
+ // Extract function name
88
+ string memory functionName = abi.decode(
89
+ vm.parseJson(abiJson, string(abi.encodePacked(indexPath, ".name"))),
90
+ (string)
91
+ );
92
+
93
+ // Build signature and compute selector
94
+ string memory signature = _buildSignature(vm, abiJson, indexPath, functionName);
95
+ selectors[selectorIndex] = bytes4(keccak256(bytes(signature)));
96
+ selectorIndex++;
97
+ }
98
+ }
99
+
100
+ return selectors;
101
+ }
102
+
103
+ /// @notice Extract function signatures from Diamond ABI
104
+ /// @dev Returns human-readable function signatures for all functions in the ABI
105
+ /// @param abiJson The raw JSON string from Diamond ABI file
106
+ /// @return signatures Array of function signatures (e.g., ["owner()", "transferOwnership(address)"])
107
+ /// @custom:example
108
+ /// ```solidity
109
+ /// string memory abi = DiamondABILoader.loadDiamondABI("./diamond-abi/MyDiamond.json");
110
+ /// string[] memory sigs = DiamondABILoader.extractSignatures(abi);
111
+ /// // sigs[0] might be "owner()"
112
+ /// // sigs[1] might be "transferOwnership(address)"
113
+ /// ```
114
+ function extractSignatures(
115
+ string memory abiJson
116
+ ) internal pure returns (string[] memory signatures) {
117
+ Vm vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
118
+
119
+ // First pass: count functions
120
+ uint256 functionCount = 0;
121
+ uint256 maxIndex = 0;
122
+
123
+ for (uint256 i = 0; i < 300; i++) {
124
+ bytes memory typeBytes = vm.parseJson(
125
+ abiJson,
126
+ string(abi.encodePacked(".abi[", vm.toString(i), "].type"))
127
+ );
128
+ if (typeBytes.length == 0) {
129
+ maxIndex = i;
130
+ break;
131
+ }
132
+ string memory entryType = abi.decode(typeBytes, (string));
133
+ if (keccak256(bytes(entryType)) == keccak256(bytes("function"))) {
134
+ functionCount++;
135
+ }
136
+ }
137
+
138
+ require(functionCount > 0, "DiamondABILoader: No functions found in ABI");
139
+
140
+ // Allocate and populate in single pass
141
+ signatures = new string[](functionCount);
142
+ uint256 signatureIndex = 0;
143
+
144
+ for (uint256 i = 0; i < maxIndex && signatureIndex < functionCount; i++) {
145
+ bytes memory typeBytes = vm.parseJson(
146
+ abiJson,
147
+ string(abi.encodePacked(".abi[", vm.toString(i), "].type"))
148
+ );
149
+ string memory entryType = abi.decode(typeBytes, (string));
150
+
151
+ if (keccak256(bytes(entryType)) == keccak256(bytes("function"))) {
152
+ string memory indexPath = string(abi.encodePacked(".abi[", vm.toString(i), "]"));
153
+
154
+ // Extract function name
155
+ string memory functionName = abi.decode(
156
+ vm.parseJson(abiJson, string(abi.encodePacked(indexPath, ".name"))),
157
+ (string)
158
+ );
159
+
160
+ // Build and store signature
161
+ signatures[signatureIndex] = _buildSignature(vm, abiJson, indexPath, functionName);
162
+ signatureIndex++;
163
+ }
164
+ }
165
+
166
+ return signatures;
167
+ }
168
+
169
+ /// @notice Get detailed information about a specific function by name
170
+ /// @dev Searches the ABI for a function with the given name and returns all its metadata
171
+ /// @param abiJson The raw JSON string from Diamond ABI file
172
+ /// @param targetFunctionName The name of the function to find (e.g., "owner" or "transferOwnership")
173
+ /// @return info FunctionInfo struct containing name, signature, selector, view/pure status, and exists flag
174
+ /// @custom:example
175
+ /// ```solidity
176
+ /// string memory abi = DiamondABILoader.loadDiamondABI("./diamond-abi/MyDiamond.json");
177
+ /// FunctionInfo memory ownerInfo = DiamondABILoader.getFunctionInfo(abi, "owner");
178
+ /// if (ownerInfo.exists) {
179
+ /// console.log("Selector:", uint32(ownerInfo.selector));
180
+ /// console.log("Signature:", ownerInfo.signature);
181
+ /// }
182
+ /// ```
183
+ function getFunctionInfo(
184
+ string memory abiJson,
185
+ string memory targetFunctionName
186
+ ) internal pure returns (FunctionInfo memory info) {
187
+ Vm vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
188
+
189
+ info.exists = false;
190
+
191
+ for (uint256 i = 0; i < 300; i++) {
192
+ bytes memory typeBytes = vm.parseJson(
193
+ abiJson,
194
+ string(abi.encodePacked(".abi[", vm.toString(i), "].type"))
195
+ );
196
+ if (typeBytes.length == 0) {
197
+ break; // Reached end of ABI array
198
+ }
199
+
200
+ string memory entryType = abi.decode(typeBytes, (string));
201
+
202
+ if (keccak256(bytes(entryType)) == keccak256(bytes("function"))) {
203
+ string memory indexPath = string(abi.encodePacked(".abi[", vm.toString(i), "]"));
204
+
205
+ // Extract function name
206
+ string memory functionName = abi.decode(
207
+ vm.parseJson(abiJson, string(abi.encodePacked(indexPath, ".name"))),
208
+ (string)
209
+ );
210
+
211
+ // Check if this is the target function
212
+ if (keccak256(bytes(functionName)) == keccak256(bytes(targetFunctionName))) {
213
+ info.name = functionName;
214
+ info.signature = _buildSignature(vm, abiJson, indexPath, functionName);
215
+ info.selector = bytes4(keccak256(bytes(info.signature)));
216
+
217
+ // Check if view/pure
218
+ bytes memory mutabilityBytes = vm.parseJson(
219
+ abiJson,
220
+ string(abi.encodePacked(indexPath, ".stateMutability"))
221
+ );
222
+ if (mutabilityBytes.length > 0) {
223
+ string memory mutability = abi.decode(mutabilityBytes, (string));
224
+ info.isView = (keccak256(bytes(mutability)) == keccak256(bytes("view")) ||
225
+ keccak256(bytes(mutability)) == keccak256(bytes("pure")));
226
+ }
227
+
228
+ info.exists = true;
229
+ return info;
230
+ }
231
+ }
232
+ }
233
+
234
+ return info;
235
+ }
236
+
237
+ /// @notice Build function signature from ABI entry
238
+ /// @dev Internal helper to construct signatures like "transfer(address,uint256)" from ABI JSON
239
+ /// @param vm The Vm instance for cheatcodes
240
+ /// @param abiJson The raw JSON string from Diamond ABI file
241
+ /// @param indexPath The JSON path to the function entry (e.g., ".abi[5]")
242
+ /// @param functionName The name of the function
243
+ /// @return signature The complete function signature
244
+ function _buildSignature(
245
+ Vm vm,
246
+ string memory abiJson,
247
+ string memory indexPath,
248
+ string memory functionName
249
+ ) private pure returns (string memory signature) {
250
+ // Start with function name
251
+ signature = functionName;
252
+ signature = string(abi.encodePacked(signature, "("));
253
+
254
+ // Try to access first input element's type
255
+ // vm.parseJson returns 0-length bytes for empty array access
256
+ bytes memory firstInputBytes = vm.parseJson(
257
+ abiJson,
258
+ string(abi.encodePacked(indexPath, ".inputs[0].type"))
259
+ );
260
+
261
+ if (firstInputBytes.length > 0) {
262
+ // Has at least one input
263
+ uint256 inputCount = 1;
264
+
265
+ // Count remaining inputs
266
+ for (uint256 j = 1; j < 20; j++) {
267
+ bytes memory inputBytes = vm.parseJson(
268
+ abiJson,
269
+ string(abi.encodePacked(indexPath, ".inputs[", vm.toString(j), "].type"))
270
+ );
271
+ if (inputBytes.length == 0) {
272
+ break;
273
+ }
274
+ inputCount++;
275
+ }
276
+
277
+ // Build parameter list
278
+ for (uint256 j = 0; j < inputCount; j++) {
279
+ string memory inputPath = string(
280
+ abi.encodePacked(indexPath, ".inputs[", vm.toString(j), "].type")
281
+ );
282
+ string memory paramType = abi.decode(vm.parseJson(abiJson, inputPath), (string));
283
+
284
+ signature = string(abi.encodePacked(signature, paramType));
285
+ if (j < inputCount - 1) {
286
+ signature = string(abi.encodePacked(signature, ","));
287
+ }
288
+ }
289
+ }
290
+ // If firstInputBytes.length == 0, inputs array is empty (no parameters)
291
+
292
+ signature = string(abi.encodePacked(signature, ")"));
293
+ return signature;
294
+ }
295
+
296
+ /// @notice Verify that extracted selectors match deployed Diamond's selectors
297
+ /// @dev Compares selectors from ABI file with selectors from on-chain Diamond (via DiamondLoupe)
298
+ /// @param extractedSelectors Array of selectors from ABI file (from extractSelectors)
299
+ /// @param deployedSelectors Array of selectors from DiamondLoupe.facetFunctionSelectors()
300
+ /// @return matches True if all extracted selectors exist in deployed selectors
301
+ /// @custom:example
302
+ /// ```solidity
303
+ /// string memory abi = DiamondABILoader.loadDiamondABI("./diamond-abi/MyDiamond.json");
304
+ /// bytes4[] memory abiSelectors = DiamondABILoader.extractSelectors(abi);
305
+ /// bytes4[] memory onChainSelectors = IDiamondLoupe(diamond).facetFunctionSelectors(facetAddress);
306
+ /// bool match = DiamondABILoader.verifySelectorsMatch(abiSelectors, onChainSelectors);
307
+ /// ```
308
+ function verifySelectorsMatch(
309
+ bytes4[] memory extractedSelectors,
310
+ bytes4[] memory deployedSelectors
311
+ ) internal pure returns (bool matches) {
312
+ if (extractedSelectors.length == 0 || deployedSelectors.length == 0) {
313
+ return false;
314
+ }
315
+
316
+ // Check that all extracted selectors exist in deployed selectors
317
+ uint256 matchCount = 0;
318
+ for (uint256 i = 0; i < extractedSelectors.length; i++) {
319
+ for (uint256 j = 0; j < deployedSelectors.length; j++) {
320
+ if (extractedSelectors[i] == deployedSelectors[j]) {
321
+ matchCount++;
322
+ break;
323
+ }
324
+ }
325
+ }
326
+
327
+ return matchCount == extractedSelectors.length;
328
+ }
329
+ }