@diamondslab/diamonds-hardhat-foundry 1.0.2 → 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 (59) hide show
  1. package/CHANGELOG.md +160 -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/DiamondDeployment.sol.template +38 -0
  34. package/dist/templates/ExampleFuzzTest.t.sol.template +118 -0
  35. package/dist/templates/ExampleIntegrationTest.t.sol.template +87 -0
  36. package/dist/templates/ExampleUnitTest.t.sol.template +65 -0
  37. package/dist/types/hardhat.d.ts +1 -1
  38. package/dist/types/hardhat.d.ts.map +1 -1
  39. package/dist/types/hardhat.js +1 -0
  40. package/dist/types/hardhat.js.map +1 -1
  41. package/dist/utils/foundry.d.ts +1 -0
  42. package/dist/utils/foundry.d.ts.map +1 -1
  43. package/dist/utils/foundry.js +3 -0
  44. package/dist/utils/foundry.js.map +1 -1
  45. package/package.json +7 -4
  46. package/src/foundry.ts +104 -0
  47. package/src/framework/DeploymentManager.ts +74 -69
  48. package/src/framework/ForgeFuzzingFramework.ts +23 -1
  49. package/src/framework/HelperGenerator.ts +13 -0
  50. package/src/index.ts +0 -5
  51. package/src/tasks/deploy.ts +3 -1
  52. package/src/tasks/generate-helpers.ts +6 -2
  53. package/src/tasks/init.ts +3 -1
  54. package/src/tasks/test.ts +12 -1
  55. package/src/templates/ExampleFuzzTest.t.sol.template +26 -17
  56. package/src/templates/ExampleIntegrationTest.t.sol.template +9 -1
  57. package/src/templates/ExampleUnitTest.t.sol.template +7 -1
  58. package/src/types/hardhat.ts +1 -1
  59. package/src/utils/foundry.ts +5 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diamondslab/diamonds-hardhat-foundry",
3
- "version": "1.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "Hardhat plugin that integrates Foundry testing with Diamond proxy contracts, providing deployment helpers and fuzzing support",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,7 +35,8 @@
35
35
  "prettier": "prettier \"**/*.{js,md,json}\"",
36
36
  "pretest": "cd ../.. && yarn build",
37
37
  "test": "mocha --recursive \"test/**/*.ts\" --exit",
38
- "build": "tsc --build .",
38
+ "build": "tsc --build . && yarn copy-templates",
39
+ "copy-templates": "mkdir -p dist/templates && cp src/templates/*.template dist/templates/",
39
40
  "prepublishOnly": "yarn build",
40
41
  "clean": "rimraf dist"
41
42
  },
@@ -67,8 +68,10 @@
67
68
  "typescript": "~5.0.0"
68
69
  },
69
70
  "peerDependencies": {
70
- "@diamondslab/diamonds": "^1.2.1",
71
- "@diamondslab/hardhat-diamonds": "^1.1.9",
71
+ "@diamondslab/diamonds": "^1.3.0",
72
+ "@diamondslab/hardhat-diamonds": "^1.1.12",
73
+ "@nomicfoundation/hardhat-ethers": "^3.0.8",
74
+ "ethers": "^6.0.0",
72
75
  "hardhat": "^2.26.0"
73
76
  },
74
77
  "dependencies": {
package/src/foundry.ts CHANGED
@@ -132,3 +132,107 @@ function buildForgeExecutionError(
132
132
  );
133
133
  }
134
134
  }
135
+
136
+ /**
137
+ * Check if Foundry is installed
138
+ */
139
+ export function isFoundryInstalled(): boolean {
140
+ try {
141
+ execSync("forge --version", { stdio: "pipe" });
142
+ return true;
143
+ } catch {
144
+ return false;
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Compile Forge contracts
150
+ */
151
+ export async function compileForge(options: {
152
+ cwd?: string;
153
+ verbose?: boolean;
154
+ }): Promise<{ success: boolean; output?: string }> {
155
+ const { cwd = process.cwd(), verbose = false } = options;
156
+
157
+ try {
158
+ const args = ["build"];
159
+ if (!verbose) {
160
+ args.push("--quiet");
161
+ }
162
+
163
+ const { stdout, stderr } = await exec(`forge ${args.join(" ")}`, { cwd });
164
+ return { success: true, output: stdout || stderr };
165
+ } catch (error: any) {
166
+ console.error("Forge build failed:", error.message);
167
+ return { success: false, output: error.message };
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Run Forge tests with optional forking
173
+ */
174
+ export async function runForgeTest(options: {
175
+ matchTest?: string;
176
+ matchContract?: string;
177
+ verbosity?: number;
178
+ gasReport?: boolean;
179
+ forkUrl?: string;
180
+ cwd?: string;
181
+ }): Promise<{ success: boolean; output?: string }> {
182
+ const {
183
+ matchTest,
184
+ matchContract,
185
+ verbosity = 2,
186
+ gasReport = false,
187
+ forkUrl,
188
+ cwd = process.cwd(),
189
+ } = options;
190
+
191
+ try {
192
+ const args = ["test"];
193
+
194
+ // Add verbosity
195
+ if (verbosity > 0 && verbosity <= 5) {
196
+ args.push(`-${"v".repeat(verbosity)}`);
197
+ }
198
+
199
+ // Add test filters
200
+ if (matchTest) {
201
+ args.push("--match-test", matchTest);
202
+ }
203
+
204
+ if (matchContract) {
205
+ args.push("--match-contract", matchContract);
206
+ }
207
+
208
+ // Add gas reporting
209
+ if (gasReport) {
210
+ args.push("--gas-report");
211
+ }
212
+
213
+ // CRITICAL: Add fork URL to connect to deployed Diamond
214
+ // This allows Forge tests to access the Hardhat-deployed Diamond contract
215
+ if (forkUrl) {
216
+ args.push("--fork-url", forkUrl);
217
+ }
218
+
219
+ const cmd = `forge ${args.join(" ")}`;
220
+ console.log(`\nRunning: ${picocolors.blue(cmd)}\n`);
221
+
222
+ const { stdout, stderr } = await exec(cmd, { cwd, maxBuffer: 10 * 1024 * 1024 });
223
+ console.log(stdout);
224
+ if (stderr) console.error(stderr);
225
+
226
+ return { success: true, output: stdout };
227
+ } catch (error: any) {
228
+ // Forge returns non-zero exit code when tests fail
229
+ // We still want to show the output
230
+ if (error.stdout) console.log(error.stdout);
231
+ if (error.stderr) console.error(error.stderr);
232
+
233
+ return {
234
+ success: false,
235
+ output: error.stdout || error.stderr || error.message,
236
+ };
237
+ }
238
+ }
@@ -1,59 +1,38 @@
1
- import { Diamond } from "@diamondslab/diamonds";
1
+ import { Diamond } from "@diamondslab/diamonds/core";
2
+ import {
3
+ LocalDiamondDeployer,
4
+ LocalDiamondDeployerConfig,
5
+ } from "@diamondslab/hardhat-diamonds/dist/lib";
6
+ import "@nomicfoundation/hardhat-ethers";
2
7
  import { existsSync } from "fs";
3
- import { HardhatRuntimeEnvironment } from "hardhat/types";
8
+ import type { HardhatRuntimeEnvironment } from "hardhat/types";
4
9
  import { join } from "path";
5
10
  import { Logger } from "../utils/logger";
6
11
 
7
- // Type for LocalDiamondDeployer (avoiding import issues)
8
- type LocalDiamondDeployerConfig = {
9
- diamondName: string;
10
- networkName: string;
11
- provider: any;
12
- chainId: number;
13
- writeDeployedDiamondData: boolean;
14
- };
15
-
16
12
  /**
17
13
  * DeploymentManager - Manages Diamond deployment lifecycle for Forge testing
18
14
  *
19
- * Note: This class dynamically requires LocalDiamondDeployer from the workspace
20
- * to avoid module resolution issues in the published package.
15
+ * Uses LocalDiamondDeployer from @diamondslab/hardhat-diamonds peer dependency
16
+ * for portable, dependency-managed Diamond deployments.
21
17
  */
22
18
  export class DeploymentManager {
23
- constructor(private hre: HardhatRuntimeEnvironment) {}
19
+ // In-memory cache for ephemeral deployments (when writeDeployedDiamondData: false)
20
+ private deploymentCache: Map<string, Diamond> = new Map();
24
21
 
25
- /**
26
- * Get LocalDiamondDeployer class
27
- * @private
28
- */
29
- private async getDeployerClass(): Promise<any> {
30
- // LocalDiamondDeployer is in the workspace scripts, not exported from the module
31
- // This will need to be available in user projects
32
- const localDeployerPath = join(
33
- this.hre.config.paths.root,
34
- "scripts/setup/LocalDiamondDeployer"
35
- );
36
-
37
- try {
38
- const deployer = await import(localDeployerPath);
39
- return deployer.LocalDiamondDeployer;
40
- } catch {
41
- throw new Error(
42
- "LocalDiamondDeployer not found. Make sure your project has scripts/setup/LocalDiamondDeployer.ts"
43
- );
44
- }
45
- }
22
+ constructor(private hre: HardhatRuntimeEnvironment) {}
46
23
 
47
24
  /**
48
25
  * Deploy a Diamond using LocalDiamondDeployer
49
26
  * @param diamondName - Name of the Diamond to deploy
50
27
  * @param networkName - Target network (hardhat, localhost, anvil)
51
28
  * @param force - Force redeployment even if exists
29
+ * @param writeDeployedDiamondData - Whether to write deployment record to file
52
30
  */
53
31
  async deploy(
54
32
  diamondName: string = "ExampleDiamond",
55
33
  networkName: string = "hardhat",
56
- force: boolean = false
34
+ force: boolean = false,
35
+ writeDeployedDiamondData: boolean = false
57
36
  ): Promise<Diamond> {
58
37
  Logger.section(`Deploying ${diamondName} to ${networkName}`);
59
38
 
@@ -67,42 +46,60 @@ export class DeploymentManager {
67
46
  Logger.info("Deployment record exists, using existing deployment");
68
47
  Logger.info("Use --force to redeploy");
69
48
 
70
- const LocalDiamondDeployer = await this.getDeployerClass();
71
- const deployer = await LocalDiamondDeployer.getInstance({
49
+ try {
50
+ const deployer = await LocalDiamondDeployer.getInstance(this.hre, {
51
+ diamondName,
52
+ networkName,
53
+ provider,
54
+ chainId,
55
+ writeDeployedDiamondData,
56
+ } as LocalDiamondDeployerConfig);
57
+
58
+ return await deployer.getDiamond();
59
+ } catch (error) {
60
+ throw new Error(
61
+ `Failed to load deployment. Ensure @diamondslab/hardhat-diamonds is installed.\n` +
62
+ `Error: ${error instanceof Error ? error.message : String(error)}`
63
+ );
64
+ }
65
+ }
66
+
67
+ Logger.step("Initializing LocalDiamondDeployer...");
68
+
69
+ try {
70
+ const deployer = await LocalDiamondDeployer.getInstance(this.hre as any, {
72
71
  diamondName,
73
72
  networkName,
74
73
  provider,
75
74
  chainId,
76
- writeDeployedDiamondData: true,
75
+ writeDeployedDiamondData,
77
76
  } as LocalDiamondDeployerConfig);
78
-
79
- return await deployer.getDiamond();
80
- }
81
-
82
- Logger.step("Initializing LocalDiamondDeployer...");
83
-
84
- const LocalDiamondDeployer = await this.getDeployerClass();
85
-
86
- const deployer = await LocalDiamondDeployer.getInstance({
87
- diamondName,
88
- networkName,
89
- provider,
90
- chainId,
91
- writeDeployedDiamondData: true,
92
- } as LocalDiamondDeployerConfig);
93
77
 
94
- await deployer.setVerbose(false); // Reduce noise, use our logger instead
78
+ await deployer.setVerbose(false); // Reduce noise, use our logger instead
95
79
 
96
- Logger.step("Deploying Diamond contract...");
97
- const diamond = await deployer.getDiamondDeployed();
98
-
99
- const deployedData = diamond.getDeployedDiamondData();
100
-
101
- Logger.success(`Diamond deployed at: ${deployedData.DiamondAddress}`);
102
- Logger.info(`Deployer: ${deployedData.DeployerAddress}`);
103
- Logger.info(`Facets deployed: ${Object.keys(deployedData.DeployedFacets || {}).length}`);
80
+ Logger.step("Deploying Diamond contract...");
81
+ const diamond = await deployer.getDiamondDeployed();
82
+
83
+ const deployedData = diamond.getDeployedDiamondData();
84
+
85
+ Logger.success(`Diamond deployed at: ${deployedData.DiamondAddress}`);
86
+ Logger.info(`Deployer: ${deployedData.DeployerAddress}`);
87
+ Logger.info(`Facets deployed: ${Object.keys(deployedData.DeployedFacets || {}).length}`);
88
+
89
+ // Cache deployment if not writing to file
90
+ if (!writeDeployedDiamondData) {
91
+ const cacheKey = `${diamondName}-${networkName}-${chainId}`;
92
+ this.deploymentCache.set(cacheKey, diamond);
93
+ Logger.info("Deployment cached in memory (not persisted to file)");
94
+ }
104
95
 
105
- return diamond;
96
+ return diamond;
97
+ } catch (error) {
98
+ throw new Error(
99
+ `Failed to deploy diamond. Ensure @diamondslab/hardhat-diamonds is installed.\n` +
100
+ `Error: ${error instanceof Error ? error.message : String(error)}`
101
+ );
102
+ }
106
103
  }
107
104
 
108
105
  /**
@@ -121,13 +118,19 @@ export class DeploymentManager {
121
118
  const network = await provider.getNetwork();
122
119
  const actualChainId = chainId ?? Number(network.chainId);
123
120
 
121
+ // Check in-memory cache first (for ephemeral deployments)
122
+ const cacheKey = `${diamondName}-${networkName}-${actualChainId}`;
123
+ if (this.deploymentCache.has(cacheKey)) {
124
+ Logger.info("Using cached deployment (ephemeral)");
125
+ return this.deploymentCache.get(cacheKey)!;
126
+ }
127
+
124
128
  if (!this.hasDeploymentRecord(diamondName, networkName, actualChainId)) {
125
129
  Logger.warn("No deployment record found");
126
130
  return null;
127
131
  }
128
132
 
129
- const LocalDiamondDeployer = await this.getDeployerClass();
130
- const deployer = await LocalDiamondDeployer.getInstance({
133
+ const deployer = await LocalDiamondDeployer.getInstance(this.hre as any, {
131
134
  diamondName,
132
135
  networkName,
133
136
  provider,
@@ -137,7 +140,7 @@ export class DeploymentManager {
137
140
 
138
141
  return await deployer.getDiamond();
139
142
  } catch (error) {
140
- Logger.error(`Failed to get deployment: ${error}`);
143
+ Logger.error(`Failed to get deployment. Ensure @diamondslab/hardhat-diamonds is installed: ${error}`);
141
144
  return null;
142
145
  }
143
146
  }
@@ -147,11 +150,13 @@ export class DeploymentManager {
147
150
  * @param diamondName - Name of the Diamond
148
151
  * @param networkName - Network name
149
152
  * @param force - Force redeployment
153
+ * @param writeDeployedDiamondData - Whether to write deployment record to file
150
154
  */
151
155
  async ensureDeployment(
152
156
  diamondName: string = "ExampleDiamond",
153
157
  networkName: string = "hardhat",
154
- force: boolean = false
158
+ force: boolean = false,
159
+ writeDeployedDiamondData: boolean = false
155
160
  ): Promise<Diamond> {
156
161
  const provider = this.hre.ethers.provider;
157
162
  const network = await provider.getNetwork();
@@ -166,7 +171,7 @@ export class DeploymentManager {
166
171
  }
167
172
 
168
173
  // Deploy if not exists or force is true
169
- return await this.deploy(diamondName, networkName, force);
174
+ return await this.deploy(diamondName, networkName, force, writeDeployedDiamondData);
170
175
  }
171
176
 
172
177
  /**
@@ -23,6 +23,10 @@ export interface ForgeTestOptions {
23
23
  skipHelpers?: boolean;
24
24
  /** Skip deployment (use existing) */
25
25
  skipDeployment?: boolean;
26
+ /** Write deployment data to file (default: false for ephemeral) */
27
+ writeDeployedDiamondData?: boolean;
28
+ /** Use EVM snapshots for test isolation */
29
+ useSnapshot?: boolean;
26
30
  }
27
31
 
28
32
  /**
@@ -58,6 +62,8 @@ export class ForgeFuzzingFramework {
58
62
  const {
59
63
  diamondName = "ExampleDiamond",
60
64
  networkName = "hardhat",
65
+ writeDeployedDiamondData = false,
66
+ useSnapshot = false,
61
67
  force = false,
62
68
  matchTest,
63
69
  matchContract,
@@ -82,7 +88,8 @@ export class ForgeFuzzingFramework {
82
88
  await this.deploymentManager.ensureDeployment(
83
89
  diamondName,
84
90
  networkName,
85
- force
91
+ force,
92
+ writeDeployedDiamondData
86
93
  );
87
94
  } else {
88
95
  Logger.info("Skipping deployment (using existing)");
@@ -131,11 +138,26 @@ export class ForgeFuzzingFramework {
131
138
 
132
139
  // Step 5: Run tests
133
140
  Logger.section("Step 4/4: Running Forge Tests");
141
+
142
+ // Only fork when using persistent networks (localhost, sepolia, mainnet, etc.)
143
+ // The ephemeral "hardhat" network cannot be forked from
144
+ let forkUrl: string | undefined;
145
+ if (networkName !== "hardhat") {
146
+ const provider = this.hre.ethers.provider;
147
+ forkUrl = (provider as any)._hardhatProvider?._wrapped?.url || "http://127.0.0.1:8545";
148
+ Logger.info(`Forking from ${networkName}: ${forkUrl}`);
149
+ } else {
150
+ Logger.warn(`⚠️ Using ephemeral "${networkName}" network - tests requiring deployed Diamond will fail`);
151
+ Logger.warn(`💡 For deployed Diamond testing, use: npx hardhat diamonds-forge:test --network localhost`);
152
+ Logger.warn(`💡 Make sure to start Hardhat node first: npx hardhat node`);
153
+ }
154
+
134
155
  const testResult = await runForgeTest({
135
156
  matchTest,
136
157
  matchContract,
137
158
  verbosity,
138
159
  gasReport,
160
+ forkUrl, // Only set for persistent networks
139
161
  cwd: this.hre.config.paths.root,
140
162
  });
141
163
 
@@ -184,6 +184,11 @@ export class HelperGenerator {
184
184
  source += ` /// @dev This is the main Diamond proxy address\n`;
185
185
  source += ` address constant DIAMOND_ADDRESS = ${deploymentData.DiamondAddress};\n\n`;
186
186
 
187
+ // Deployer address
188
+ source += ` /// @notice Address of the deployer account\n`;
189
+ source += ` /// @dev Account that deployed the Diamond\n`;
190
+ source += ` address constant DEPLOYER_ADDRESS = ${deploymentData.DeployerAddress};\n\n`;
191
+
187
192
  // Facet addresses
188
193
  source += " // ========================================\n";
189
194
  source += " // Facet Addresses\n";
@@ -215,6 +220,14 @@ export class HelperGenerator {
215
220
  source += " return DIAMOND_ADDRESS;\n";
216
221
  source += " }\n\n";
217
222
 
223
+ source += " /**\n";
224
+ source += " * @notice Get the deployer address\n";
225
+ source += " * @return The address of the deployer account\n";
226
+ source += " */\n";
227
+ source += " function getDeployerAddress() internal pure returns (address) {\n";
228
+ source += " return DEPLOYER_ADDRESS;\n";
229
+ source += " }\n\n";
230
+
218
231
  source += " /**\n";
219
232
  source += " * @notice Get facet implementation address by name\n";
220
233
  source += " * @param facetName The name of the facet\n";
package/src/index.ts CHANGED
@@ -21,11 +21,6 @@ import {
21
21
  } from "./foundry";
22
22
  import { validateConfig } from "./utils/validation";
23
23
 
24
- // Export framework classes for programmatic use
25
- export { DeploymentManager } from "./framework/DeploymentManager";
26
- export { ForgeFuzzingFramework } from "./framework/ForgeFuzzingFramework";
27
- export { HelperGenerator } from "./framework/HelperGenerator";
28
-
29
24
  // Export types
30
25
  export * from "./types/config";
31
26
 
@@ -1,6 +1,5 @@
1
1
  import { task, types } from "hardhat/config";
2
2
  import { HardhatRuntimeEnvironment } from "hardhat/types";
3
- import { DeploymentManager } from "../framework/DeploymentManager";
4
3
  import { Logger } from "../utils/logger";
5
4
  import { validateFoundryInstallation } from "../utils/validation";
6
5
 
@@ -56,6 +55,9 @@ task("diamonds-forge:deploy", "Deploy Diamond contract for Forge testing")
56
55
 
57
56
  // Step 2: Deploy or reuse Diamond
58
57
  Logger.step("Deploying Diamond contract...");
58
+
59
+ // Lazy-load DeploymentManager to avoid circular dependency
60
+ const { DeploymentManager } = await import("../framework/DeploymentManager.js");
59
61
  const deploymentManager = new DeploymentManager(hre);
60
62
 
61
63
  try {
@@ -1,7 +1,5 @@
1
1
  import { task, types } from "hardhat/config";
2
2
  import { HardhatRuntimeEnvironment } from "hardhat/types";
3
- import { DeploymentManager } from "../framework/DeploymentManager";
4
- import { HelperGenerator } from "../framework/HelperGenerator";
5
3
  import { Logger } from "../utils/logger";
6
4
 
7
5
  /**
@@ -39,6 +37,9 @@ task("diamonds-forge:generate-helpers", "Generate Solidity helpers from Diamond
39
37
  Logger.info(`Network: ${networkName}`);
40
38
  Logger.info(`Output: ${outputDir}`);
41
39
 
40
+ // Lazy-load framework to avoid circular dependency during config loading
41
+ const { DeploymentManager } = await import("../framework/DeploymentManager.js");
42
+
42
43
  // Step 1: Get deployment
43
44
  Logger.step("Loading deployment data...");
44
45
  const deploymentManager = new DeploymentManager(hre);
@@ -62,6 +63,9 @@ task("diamonds-forge:generate-helpers", "Generate Solidity helpers from Diamond
62
63
  const network = await provider.getNetwork();
63
64
  const chainId = Number(network.chainId);
64
65
 
66
+ // Lazy-load HelperGenerator
67
+ const { HelperGenerator } = await import("../framework/HelperGenerator.js");
68
+
65
69
  // Step 3: Generate helpers
66
70
  Logger.step("Generating Solidity helpers...");
67
71
  const generator = new HelperGenerator(hre);
package/src/tasks/init.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { task, types } from "hardhat/config";
2
2
  import { HardhatRuntimeEnvironment } from "hardhat/types";
3
- import { HelperGenerator } from "../framework/HelperGenerator";
4
3
  import { Logger } from "../utils/logger";
5
4
  import { validateConfig, validateFoundryInstallation } from "../utils/validation";
6
5
 
@@ -50,6 +49,9 @@ task("diamonds-forge:init", "Initialize Forge testing structure for Diamond cont
50
49
 
51
50
  // Step 3: Scaffold project structure
52
51
  Logger.step("Creating test directory structure...");
52
+
53
+ // Lazy-load framework classes to avoid circular dependency
54
+ const { HelperGenerator } = await import("../framework/HelperGenerator.js");
53
55
  const generator = new HelperGenerator(hre);
54
56
 
55
57
  try {
package/src/tasks/test.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { task, types } from "hardhat/config";
2
2
  import { HardhatRuntimeEnvironment } from "hardhat/types";
3
- import { ForgeFuzzingFramework, ForgeTestOptions } from "../framework/ForgeFuzzingFramework";
4
3
  import { Logger } from "../utils/logger";
5
4
 
6
5
  /**
@@ -43,6 +42,8 @@ task("diamonds-forge:test", "Run Forge tests with Diamond deployment")
43
42
  .addFlag("skipDeployment", "Skip Diamond deployment step")
44
43
  .addFlag("skipHelpers", "Skip helper generation step")
45
44
  .addFlag("force", "Force redeployment of Diamond")
45
+ .addFlag("saveDeployment", "Write deployment data to file for reuse")
46
+ .addFlag("useSnapshot", "Use EVM snapshots for test isolation")
46
47
  .setAction(async (taskArgs, hre: HardhatRuntimeEnvironment) => {
47
48
  Logger.section("Running Forge Tests with Diamond");
48
49
 
@@ -56,6 +57,8 @@ task("diamonds-forge:test", "Run Forge tests with Diamond deployment")
56
57
  const skipDeployment = taskArgs.skipDeployment;
57
58
  const skipHelpers = taskArgs.skipHelpers;
58
59
  const force = taskArgs.force;
60
+ const saveDeployment = taskArgs.saveDeployment;
61
+ const useSnapshot = taskArgs.useSnapshot;
59
62
 
60
63
  Logger.info(`Diamond: ${diamondName}`);
61
64
  Logger.info(`Network: ${networkName}`);
@@ -65,6 +68,12 @@ task("diamonds-forge:test", "Run Forge tests with Diamond deployment")
65
68
  if (gasReport) Logger.info("Gas Report: enabled");
66
69
  if (skipDeployment) Logger.info("Skip Deployment: true");
67
70
  if (skipHelpers) Logger.info("Skip Helpers: true");
71
+ if (saveDeployment) Logger.info("Save Deployment: true");
72
+ if (useSnapshot) Logger.info("Use Snapshot: true");
73
+
74
+ // Lazy-load framework to avoid circular dependency during config loading
75
+ const { ForgeFuzzingFramework } = await import("../framework/ForgeFuzzingFramework.js");
76
+ type ForgeTestOptions = Parameters<InstanceType<typeof ForgeFuzzingFramework>["runTests"]>[0];
68
77
 
69
78
  // Create test options
70
79
  const options: ForgeTestOptions = {
@@ -77,6 +86,8 @@ task("diamonds-forge:test", "Run Forge tests with Diamond deployment")
77
86
  gasReport,
78
87
  skipHelpers,
79
88
  skipDeployment,
89
+ writeDeployedDiamondData: saveDeployment,
90
+ useSnapshot,
80
91
  };
81
92
 
82
93
  // Run tests using the framework
@@ -3,8 +3,9 @@ pragma solidity ^0.8.0;
3
3
 
4
4
  import "forge-std/Test.sol";
5
5
  import "forge-std/console.sol";
6
- import "../../contracts/DiamondFuzzBase.sol";
7
- import "../../helpers/DiamondDeployment.sol";
6
+ import "@diamondslab/diamonds-hardhat-foundry/contracts/DiamondFuzzBase.sol";
7
+ import "@diamondslab/diamonds-hardhat-foundry/contracts/DiamondForgeHelpers.sol";
8
+ import "../helpers/DiamondDeployment.sol";
8
9
 
9
10
  /**
10
11
  * @title ExampleFuzzTest
@@ -12,17 +13,19 @@ import "../../helpers/DiamondDeployment.sol";
12
13
  * @dev Uses DiamondFuzzBase for common fuzz testing utilities
13
14
  */
14
15
  contract ExampleFuzzTest is DiamondFuzzBase {
16
+ using DiamondForgeHelpers for address;
17
+
18
+ /// @notice Override to load Diamond from deployment
19
+ function _loadDiamondAddress() internal view override returns (address) {
20
+ return DiamondDeployment.diamond();
21
+ }
22
+
15
23
  function setUp() public override {
16
24
  super.setUp();
17
25
 
18
- // Load Diamond deployment
19
- setDiamondAddress(DiamondDeployment.diamond());
20
-
21
- // Register facets (customize for your Diamond)
22
- // registerFacet("FacetName", DiamondDeployment.facetName());
23
-
24
26
  console.log("Fuzz test setup complete");
25
27
  console.log("Diamond:", diamond);
28
+ console.log("Functions loaded:", diamondSelectors.length);
26
29
  }
27
30
 
28
31
  /**
@@ -31,11 +34,14 @@ contract ExampleFuzzTest is DiamondFuzzBase {
31
34
  */
32
35
  function testFuzz_AddressInput(address randomAddress) public {
33
36
  // Filter invalid addresses
34
- assumeValidAddress(randomAddress);
37
+ vm.assume(DiamondForgeHelpers.isValidTestAddress(randomAddress));
35
38
 
36
39
  // TODO: Test your Diamond function with fuzzed address
37
40
  // Example:
38
- // MyDiamond(diamond).someFunction(randomAddress);
41
+ // bytes4 selector = bytes4(keccak256("someFunction(address)"));
42
+ // bytes memory data = abi.encode(randomAddress);
43
+ // (bool success,) = _callDiamond(selector, data);
44
+ // assertTrue(success);
39
45
 
40
46
  assertTrue(true, "Replace with actual fuzz test");
41
47
  }
@@ -46,11 +52,13 @@ contract ExampleFuzzTest is DiamondFuzzBase {
46
52
  */
47
53
  function testFuzz_AmountInput(uint256 amount) public {
48
54
  // Bound the amount to valid range
49
- assumeValidAmount(amount);
55
+ vm.assume(DiamondForgeHelpers.isValidTestAmount(amount));
50
56
 
51
57
  // TODO: Test your Diamond function with fuzzed amount
52
58
  // Example:
53
- // MyDiamond(diamond).transfer(user1, amount);
59
+ // bytes4 selector = bytes4(keccak256("transfer(address,uint256)"));
60
+ // bytes memory data = abi.encode(user1, amount);
61
+ // (bool success,) = _callDiamond(selector, data);
54
62
 
55
63
  assertTrue(true, "Replace with actual fuzz test");
56
64
  }
@@ -67,8 +75,8 @@ contract ExampleFuzzTest is DiamondFuzzBase {
67
75
  bytes memory data
68
76
  ) public {
69
77
  // Filter inputs
70
- assumeValidAddress(addr);
71
- assumeValidAmount(value);
78
+ vm.assume(DiamondForgeHelpers.isValidTestAddress(addr));
79
+ vm.assume(DiamondForgeHelpers.isValidTestAmount(value));
72
80
  vm.assume(data.length > 0);
73
81
  vm.assume(data.length < 1024); // Reasonable size limit
74
82
 
@@ -86,8 +94,9 @@ contract ExampleFuzzTest is DiamondFuzzBase {
86
94
  vm.assume(badValue > type(uint128).max);
87
95
 
88
96
  // TODO: Test that function reverts with invalid input
89
- // expectRevertWithMessage("Invalid amount");
90
- // MyDiamond(diamond).someFunction(badValue);
97
+ // bytes4 selector = bytes4(keccak256("someFunction(uint256)"));
98
+ // bytes memory data = abi.encode(badValue);
99
+ // _expectDiamondRevert(selector, data, bytes(""));
91
100
 
92
101
  assertTrue(true, "Replace with actual revert fuzz test");
93
102
  }
@@ -98,7 +107,7 @@ contract ExampleFuzzTest is DiamondFuzzBase {
98
107
  */
99
108
  function testFuzz_BoundedValue(uint256 rawValue) public {
100
109
  // Bound value to specific range (e.g., 1 to 1000)
101
- uint256 boundedValue = boundValue(rawValue, 1, 1000);
110
+ uint256 boundedValue = bound(rawValue, 1, 1000);
102
111
 
103
112
  // TODO: Test with bounded value
104
113
  assertGe(boundedValue, 1, "Value should be >= 1");