@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.
- package/CHANGELOG.md +154 -0
- package/README.md +568 -4
- package/contracts/DiamondABILoader.sol +329 -0
- package/contracts/DiamondForgeHelpers.sol +309 -85
- package/contracts/DiamondFuzzBase.sol +305 -114
- package/dist/foundry.d.ts +28 -0
- package/dist/foundry.d.ts.map +1 -1
- package/dist/foundry.js +82 -1
- package/dist/foundry.js.map +1 -1
- package/dist/framework/DeploymentManager.d.ts +10 -11
- package/dist/framework/DeploymentManager.d.ts.map +1 -1
- package/dist/framework/DeploymentManager.js +56 -45
- package/dist/framework/DeploymentManager.js.map +1 -1
- package/dist/framework/ForgeFuzzingFramework.d.ts +4 -0
- package/dist/framework/ForgeFuzzingFramework.d.ts.map +1 -1
- package/dist/framework/ForgeFuzzingFramework.js +16 -2
- package/dist/framework/ForgeFuzzingFramework.js.map +1 -1
- package/dist/framework/HelperGenerator.d.ts.map +1 -1
- package/dist/framework/HelperGenerator.js +11 -0
- package/dist/framework/HelperGenerator.js.map +1 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -8
- package/dist/index.js.map +1 -1
- package/dist/tasks/deploy.js +3 -2
- package/dist/tasks/deploy.js.map +1 -1
- package/dist/tasks/generate-helpers.js +6 -4
- package/dist/tasks/generate-helpers.js.map +1 -1
- package/dist/tasks/init.js +3 -2
- package/dist/tasks/init.js.map +1 -1
- package/dist/tasks/test.js +13 -2
- package/dist/tasks/test.js.map +1 -1
- package/dist/templates/ExampleFuzzTest.t.sol.template +26 -17
- package/dist/templates/ExampleIntegrationTest.t.sol.template +9 -1
- package/dist/templates/ExampleUnitTest.t.sol.template +7 -1
- package/dist/types/hardhat.d.ts +1 -1
- package/dist/types/hardhat.d.ts.map +1 -1
- package/dist/types/hardhat.js +1 -0
- package/dist/types/hardhat.js.map +1 -1
- package/dist/utils/foundry.d.ts +1 -0
- package/dist/utils/foundry.d.ts.map +1 -1
- package/dist/utils/foundry.js +3 -0
- package/dist/utils/foundry.js.map +1 -1
- package/package.json +5 -3
- package/src/foundry.ts +104 -0
- package/src/framework/DeploymentManager.ts +74 -69
- package/src/framework/ForgeFuzzingFramework.ts +23 -1
- package/src/framework/HelperGenerator.ts +13 -0
- package/src/index.ts +0 -5
- package/src/tasks/deploy.ts +3 -1
- package/src/tasks/generate-helpers.ts +6 -2
- package/src/tasks/init.ts +3 -1
- package/src/tasks/test.ts +12 -1
- package/src/templates/ExampleFuzzTest.t.sol.template +26 -17
- package/src/templates/ExampleIntegrationTest.t.sol.template +9 -1
- package/src/templates/ExampleUnitTest.t.sol.template +7 -1
- package/src/types/hardhat.ts +1 -1
- package/src/utils/foundry.ts +5 -0
|
@@ -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
|
+
}
|