@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.
- package/CHANGELOG.md +249 -0
- package/README.md +650 -4
- package/contracts/DiamondABILoader.sol +329 -0
- package/contracts/DiamondForgeHelpers.sol +309 -85
- package/contracts/DiamondFuzzBase.sol +322 -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 +11 -4
- 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/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 +14 -3
- 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
- package/dist/templates/DiamondDeployment.sol.template +0 -38
- package/dist/templates/ExampleFuzzTest.t.sol.template +0 -109
- package/dist/templates/ExampleIntegrationTest.t.sol.template +0 -79
- package/dist/templates/ExampleUnitTest.t.sol.template +0 -59
|
@@ -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
|
-
*
|
|
20
|
-
*
|
|
15
|
+
* Uses LocalDiamondDeployer from @diamondslab/hardhat-diamonds peer dependency
|
|
16
|
+
* for portable, dependency-managed Diamond deployments.
|
|
21
17
|
*/
|
|
22
18
|
export class DeploymentManager {
|
|
23
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
|
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
|
-
|
|
78
|
+
await deployer.setVerbose(false); // Reduce noise, use our logger instead
|
|
95
79
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
package/src/tasks/deploy.ts
CHANGED
|
@@ -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
|
|
|
@@ -24,6 +23,7 @@ task("diamonds-forge:deploy", "Deploy Diamond contract for Forge testing")
|
|
|
24
23
|
)
|
|
25
24
|
.addFlag("reuse", "Reuse existing deployment if available")
|
|
26
25
|
.addFlag("force", "Force redeployment even if deployment exists")
|
|
26
|
+
.addFlag("saveDeployment", "Write deployment data to file (default: true for localhost/testnet)")
|
|
27
27
|
.setAction(async (taskArgs, hre: HardhatRuntimeEnvironment) => {
|
|
28
28
|
Logger.section("Deploying Diamond for Forge Testing");
|
|
29
29
|
|
|
@@ -32,6 +32,11 @@ task("diamonds-forge:deploy", "Deploy Diamond contract for Forge testing")
|
|
|
32
32
|
const diamondName = taskArgs.diamondName;
|
|
33
33
|
const reuse = taskArgs.reuse;
|
|
34
34
|
const force = taskArgs.force;
|
|
35
|
+
|
|
36
|
+
// Default to saving deployment for persistent networks (localhost, sepolia, etc.)
|
|
37
|
+
// but not for ephemeral hardhat network.
|
|
38
|
+
// Flags default to false when not provided, so we check if explicitly passed
|
|
39
|
+
const saveDeployment = taskArgs.saveDeployment || networkName !== "hardhat";
|
|
35
40
|
|
|
36
41
|
// Validate flags
|
|
37
42
|
if (reuse && force) {
|
|
@@ -42,6 +47,7 @@ task("diamonds-forge:deploy", "Deploy Diamond contract for Forge testing")
|
|
|
42
47
|
Logger.info(`Diamond: ${diamondName}`);
|
|
43
48
|
Logger.info(`Network: ${networkName}`);
|
|
44
49
|
Logger.info(`Mode: ${force ? "force deploy" : reuse ? "reuse if exists" : "deploy new"}`);
|
|
50
|
+
Logger.info(`Save Deployment: ${saveDeployment}`);
|
|
45
51
|
|
|
46
52
|
// Step 1: Validate Foundry (optional for deployment, but recommended)
|
|
47
53
|
Logger.step("Checking Foundry installation...");
|
|
@@ -56,6 +62,9 @@ task("diamonds-forge:deploy", "Deploy Diamond contract for Forge testing")
|
|
|
56
62
|
|
|
57
63
|
// Step 2: Deploy or reuse Diamond
|
|
58
64
|
Logger.step("Deploying Diamond contract...");
|
|
65
|
+
|
|
66
|
+
// Lazy-load DeploymentManager to avoid circular dependency
|
|
67
|
+
const { DeploymentManager } = await import("../framework/DeploymentManager.js");
|
|
59
68
|
const deploymentManager = new DeploymentManager(hre);
|
|
60
69
|
|
|
61
70
|
try {
|
|
@@ -66,14 +75,16 @@ task("diamonds-forge:deploy", "Deploy Diamond contract for Forge testing")
|
|
|
66
75
|
diamond = await deploymentManager.ensureDeployment(
|
|
67
76
|
diamondName,
|
|
68
77
|
networkName,
|
|
69
|
-
false
|
|
78
|
+
false,
|
|
79
|
+
saveDeployment
|
|
70
80
|
);
|
|
71
81
|
} else {
|
|
72
82
|
// Deploy (force if flag is set)
|
|
73
83
|
diamond = await deploymentManager.deploy(
|
|
74
84
|
diamondName,
|
|
75
85
|
networkName,
|
|
76
|
-
force
|
|
86
|
+
force,
|
|
87
|
+
saveDeployment
|
|
77
88
|
);
|
|
78
89
|
}
|
|
79
90
|
|
|
@@ -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 "
|
|
7
|
-
import "
|
|
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
|
-
|
|
37
|
+
vm.assume(DiamondForgeHelpers.isValidTestAddress(randomAddress));
|
|
35
38
|
|
|
36
39
|
// TODO: Test your Diamond function with fuzzed address
|
|
37
40
|
// Example:
|
|
38
|
-
//
|
|
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
|
-
|
|
55
|
+
vm.assume(DiamondForgeHelpers.isValidTestAmount(amount));
|
|
50
56
|
|
|
51
57
|
// TODO: Test your Diamond function with fuzzed amount
|
|
52
58
|
// Example:
|
|
53
|
-
//
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
//
|
|
90
|
-
//
|
|
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 =
|
|
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");
|
|
@@ -3,7 +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 "
|
|
6
|
+
import "@diamondslab/diamonds-hardhat-foundry/contracts/DiamondForgeHelpers.sol";
|
|
7
|
+
import "@diamondslab/diamonds-hardhat-foundry/contracts/DiamondABILoader.sol";
|
|
8
|
+
import "../helpers/DiamondDeployment.sol";
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* @title ExampleIntegrationTest
|
|
@@ -11,6 +13,9 @@ import "../../helpers/DiamondDeployment.sol";
|
|
|
11
13
|
* @dev Tests interactions between multiple facets and Diamond functionality
|
|
12
14
|
*/
|
|
13
15
|
contract ExampleIntegrationTest is Test {
|
|
16
|
+
using DiamondForgeHelpers for address;
|
|
17
|
+
using DiamondABILoader for string;
|
|
18
|
+
|
|
14
19
|
address diamond;
|
|
15
20
|
address deployer;
|
|
16
21
|
|
|
@@ -23,6 +28,9 @@ contract ExampleIntegrationTest is Test {
|
|
|
23
28
|
diamond = DiamondDeployment.diamond();
|
|
24
29
|
deployer = DiamondDeployment.deployer();
|
|
25
30
|
|
|
31
|
+
// Validate Diamond
|
|
32
|
+
DiamondForgeHelpers.assertValidDiamond(diamond);
|
|
33
|
+
|
|
26
34
|
// Set up test users
|
|
27
35
|
user1 = makeAddr("user1");
|
|
28
36
|
user2 = makeAddr("user2");
|
|
@@ -3,7 +3,8 @@ pragma solidity ^0.8.0;
|
|
|
3
3
|
|
|
4
4
|
import "forge-std/Test.sol";
|
|
5
5
|
import "forge-std/console.sol";
|
|
6
|
-
import "
|
|
6
|
+
import "@diamondslab/diamonds-hardhat-foundry/contracts/DiamondForgeHelpers.sol";
|
|
7
|
+
import "../helpers/DiamondDeployment.sol";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @title ExampleUnitTest
|
|
@@ -11,6 +12,8 @@ import "../../helpers/DiamondDeployment.sol";
|
|
|
11
12
|
* @dev This is a template - customize for your specific Diamond implementation
|
|
12
13
|
*/
|
|
13
14
|
contract ExampleUnitTest is Test {
|
|
15
|
+
using DiamondForgeHelpers for address;
|
|
16
|
+
|
|
14
17
|
address diamond;
|
|
15
18
|
address deployer;
|
|
16
19
|
|
|
@@ -21,6 +24,9 @@ contract ExampleUnitTest is Test {
|
|
|
21
24
|
|
|
22
25
|
console.log("Diamond deployed at:", diamond);
|
|
23
26
|
console.log("Deployed by:", deployer);
|
|
27
|
+
|
|
28
|
+
// Validate Diamond deployment
|
|
29
|
+
DiamondForgeHelpers.assertValidDiamond(diamond);
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
/**
|
package/src/types/hardhat.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Type extensions for Hardhat Runtime Environment
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import "@nomicfoundation/hardhat-ethers";
|
|
5
6
|
import "hardhat/types/config";
|
|
6
7
|
import "hardhat/types/runtime";
|
|
7
8
|
import { DiamondsFoundryConfig } from "../types/config";
|
|
@@ -19,6 +20,5 @@ declare module "hardhat/types/config" {
|
|
|
19
20
|
declare module "hardhat/types/runtime" {
|
|
20
21
|
export interface HardhatRuntimeEnvironment {
|
|
21
22
|
diamondsFoundry: Required<DiamondsFoundryConfig>;
|
|
22
|
-
ethers: any; // Will be provided by @nomicfoundation/hardhat-ethers
|
|
23
23
|
}
|
|
24
24
|
}
|
package/src/utils/foundry.ts
CHANGED
|
@@ -89,6 +89,7 @@ export async function runForgeTest(options: {
|
|
|
89
89
|
matchContract?: string;
|
|
90
90
|
verbosity?: number;
|
|
91
91
|
gasReport?: boolean;
|
|
92
|
+
forkUrl?: string;
|
|
92
93
|
cwd?: string;
|
|
93
94
|
env?: Record<string, string>;
|
|
94
95
|
}): Promise<{ success: boolean; output: string }> {
|
|
@@ -110,6 +111,10 @@ export async function runForgeTest(options: {
|
|
|
110
111
|
args.push("--gas-report");
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
if (options.forkUrl) {
|
|
115
|
+
args.push("--fork-url", options.forkUrl);
|
|
116
|
+
}
|
|
117
|
+
|
|
113
118
|
try {
|
|
114
119
|
const result = await execForgeAsync("forge", args, {
|
|
115
120
|
cwd: options.cwd,
|