@diamondslab/diamonds-hardhat-foundry 1.0.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 +66 -0
- package/LICENSE +21 -0
- package/README.md +493 -0
- package/contracts/DiamondForgeHelpers.sol +96 -0
- package/contracts/DiamondFuzzBase.sol +128 -0
- package/package.json +78 -0
- package/src/foundry.ts +134 -0
- package/src/framework/DeploymentManager.ts +210 -0
- package/src/framework/ForgeFuzzingFramework.ts +194 -0
- package/src/framework/HelperGenerator.ts +246 -0
- package/src/index.ts +166 -0
- package/src/tasks/deploy.ts +110 -0
- package/src/tasks/generate-helpers.ts +101 -0
- package/src/tasks/init.ts +90 -0
- package/src/tasks/test.ts +108 -0
- package/src/templates/DiamondDeployment.sol.template +38 -0
- package/src/templates/ExampleFuzzTest.t.sol.template +109 -0
- package/src/templates/ExampleIntegrationTest.t.sol.template +79 -0
- package/src/templates/ExampleUnitTest.t.sol.template +59 -0
- package/src/types/config.ts +54 -0
- package/src/types/hardhat.ts +24 -0
- package/src/utils/foundry.ts +189 -0
- package/src/utils/logger.ts +66 -0
- package/src/utils/validation.ts +144 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { HardhatRuntimeEnvironment } from "hardhat/types";
|
|
2
|
+
import { compileForge, isFoundryInstalled, runForgeTest } from "../utils/foundry";
|
|
3
|
+
import { Logger } from "../utils/logger";
|
|
4
|
+
import { DeploymentManager } from "./DeploymentManager";
|
|
5
|
+
import { HelperGenerator } from "./HelperGenerator";
|
|
6
|
+
|
|
7
|
+
export interface ForgeTestOptions {
|
|
8
|
+
/** Name of the Diamond to deploy */
|
|
9
|
+
diamondName?: string;
|
|
10
|
+
/** Network to deploy on */
|
|
11
|
+
networkName?: string;
|
|
12
|
+
/** Force redeployment */
|
|
13
|
+
force?: boolean;
|
|
14
|
+
/** Match test pattern */
|
|
15
|
+
matchTest?: string;
|
|
16
|
+
/** Match contract pattern */
|
|
17
|
+
matchContract?: string;
|
|
18
|
+
/** Verbosity level (1-5) */
|
|
19
|
+
verbosity?: number;
|
|
20
|
+
/** Show gas report */
|
|
21
|
+
gasReport?: boolean;
|
|
22
|
+
/** Skip helper generation */
|
|
23
|
+
skipHelpers?: boolean;
|
|
24
|
+
/** Skip deployment (use existing) */
|
|
25
|
+
skipDeployment?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* ForgeFuzzingFramework - Main orchestration class for Forge testing with Diamonds
|
|
30
|
+
*
|
|
31
|
+
* Coordinates:
|
|
32
|
+
* 1. Diamond deployment via DeploymentManager
|
|
33
|
+
* 2. Helper generation via HelperGenerator
|
|
34
|
+
* 3. Forge test execution
|
|
35
|
+
*/
|
|
36
|
+
export class ForgeFuzzingFramework {
|
|
37
|
+
private deploymentManager: DeploymentManager;
|
|
38
|
+
private helperGenerator: HelperGenerator;
|
|
39
|
+
|
|
40
|
+
constructor(private hre: HardhatRuntimeEnvironment) {
|
|
41
|
+
this.deploymentManager = new DeploymentManager(hre);
|
|
42
|
+
this.helperGenerator = new HelperGenerator(hre);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Run complete Forge testing workflow
|
|
47
|
+
*
|
|
48
|
+
* Workflow:
|
|
49
|
+
* 1. Validate Foundry installation
|
|
50
|
+
* 2. Deploy or reuse Diamond
|
|
51
|
+
* 3. Generate Solidity helpers
|
|
52
|
+
* 4. Compile Forge contracts
|
|
53
|
+
* 5. Run Forge tests
|
|
54
|
+
*
|
|
55
|
+
* @param options - Test execution options
|
|
56
|
+
*/
|
|
57
|
+
async runTests(options: ForgeTestOptions = {}): Promise<boolean> {
|
|
58
|
+
const {
|
|
59
|
+
diamondName = "ExampleDiamond",
|
|
60
|
+
networkName = "hardhat",
|
|
61
|
+
force = false,
|
|
62
|
+
matchTest,
|
|
63
|
+
matchContract,
|
|
64
|
+
verbosity = 2,
|
|
65
|
+
gasReport = false,
|
|
66
|
+
skipHelpers = false,
|
|
67
|
+
skipDeployment = false,
|
|
68
|
+
} = options;
|
|
69
|
+
|
|
70
|
+
Logger.section("Forge Fuzzing Framework - Test Execution");
|
|
71
|
+
|
|
72
|
+
// Step 1: Validate Foundry
|
|
73
|
+
if (!isFoundryInstalled()) {
|
|
74
|
+
Logger.error("Foundry is not installed. Please install it: https://book.getfoundry.sh/getting-started/installation");
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
// Step 2: Ensure Diamond deployment
|
|
80
|
+
if (!skipDeployment) {
|
|
81
|
+
Logger.section("Step 1/4: Ensuring Diamond Deployment");
|
|
82
|
+
await this.deploymentManager.ensureDeployment(
|
|
83
|
+
diamondName,
|
|
84
|
+
networkName,
|
|
85
|
+
force
|
|
86
|
+
);
|
|
87
|
+
} else {
|
|
88
|
+
Logger.info("Skipping deployment (using existing)");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Step 3: Generate helpers
|
|
92
|
+
if (!skipHelpers) {
|
|
93
|
+
Logger.section("Step 2/4: Generating Solidity Helpers");
|
|
94
|
+
const deployment = await this.deploymentManager.getDeployment(
|
|
95
|
+
diamondName,
|
|
96
|
+
networkName
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (!deployment) {
|
|
100
|
+
Logger.error("No deployment found. Cannot generate helpers.");
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const provider = this.hre.ethers.provider;
|
|
105
|
+
const network = await provider.getNetwork();
|
|
106
|
+
const chainId = Number(network.chainId);
|
|
107
|
+
|
|
108
|
+
const deploymentData = deployment.getDeployedDiamondData();
|
|
109
|
+
|
|
110
|
+
await this.helperGenerator.generateDeploymentHelpers(
|
|
111
|
+
diamondName,
|
|
112
|
+
networkName,
|
|
113
|
+
chainId,
|
|
114
|
+
deploymentData
|
|
115
|
+
);
|
|
116
|
+
} else {
|
|
117
|
+
Logger.info("Skipping helper generation");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Step 4: Compile Forge contracts
|
|
121
|
+
Logger.section("Step 3/4: Compiling Forge Contracts");
|
|
122
|
+
const compileResult = await compileForge({
|
|
123
|
+
cwd: this.hre.config.paths.root,
|
|
124
|
+
verbose: verbosity >= 3,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (!compileResult.success) {
|
|
128
|
+
Logger.error("Forge compilation failed");
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Step 5: Run tests
|
|
133
|
+
Logger.section("Step 4/4: Running Forge Tests");
|
|
134
|
+
const testResult = await runForgeTest({
|
|
135
|
+
matchTest,
|
|
136
|
+
matchContract,
|
|
137
|
+
verbosity,
|
|
138
|
+
gasReport,
|
|
139
|
+
cwd: this.hre.config.paths.root,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return testResult.success;
|
|
143
|
+
} catch (error: any) {
|
|
144
|
+
Logger.error(`Test execution failed: ${error.message}`);
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Deploy Diamond only (no testing)
|
|
151
|
+
*/
|
|
152
|
+
async deployOnly(
|
|
153
|
+
diamondName: string = "ExampleDiamond",
|
|
154
|
+
networkName: string = "hardhat",
|
|
155
|
+
force: boolean = false
|
|
156
|
+
) {
|
|
157
|
+
return await this.deploymentManager.ensureDeployment(
|
|
158
|
+
diamondName,
|
|
159
|
+
networkName,
|
|
160
|
+
force
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Generate helpers only (no deployment or testing)
|
|
166
|
+
*/
|
|
167
|
+
async generateHelpersOnly(
|
|
168
|
+
diamondName: string = "ExampleDiamond",
|
|
169
|
+
networkName: string = "hardhat"
|
|
170
|
+
) {
|
|
171
|
+
const deployment = await this.deploymentManager.getDeployment(
|
|
172
|
+
diamondName,
|
|
173
|
+
networkName
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (!deployment) {
|
|
177
|
+
throw new Error("No deployment found. Deploy first using deployOnly()");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const provider = this.hre.ethers.provider;
|
|
181
|
+
const network = await provider.getNetwork();
|
|
182
|
+
const chainId = Number(network.chainId);
|
|
183
|
+
|
|
184
|
+
const deploymentData = deployment.getDeployedDiamondData();
|
|
185
|
+
|
|
186
|
+
return await this.helperGenerator.generateDeploymentHelpers(
|
|
187
|
+
diamondName,
|
|
188
|
+
networkName,
|
|
189
|
+
chainId,
|
|
190
|
+
deploymentData
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { DeployedDiamondData } from "@diamondslab/diamonds";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { HardhatRuntimeEnvironment } from "hardhat/types";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { Logger } from "../utils/logger";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* HelperGenerator - Generates Solidity helper files for testing
|
|
9
|
+
*/
|
|
10
|
+
export class HelperGenerator {
|
|
11
|
+
constructor(private hre: HardhatRuntimeEnvironment) {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Scaffold project with initial test structure
|
|
15
|
+
*/
|
|
16
|
+
async scaffoldProject(outputDir?: string): Promise<void> {
|
|
17
|
+
const helpersDir = outputDir || this.hre.diamondsFoundry.helpersDir;
|
|
18
|
+
const basePath = join(this.hre.config.paths.root, helpersDir);
|
|
19
|
+
|
|
20
|
+
Logger.section("Scaffolding Forge Test Structure");
|
|
21
|
+
|
|
22
|
+
// Create directories
|
|
23
|
+
Logger.step("Creating directories...");
|
|
24
|
+
mkdirSync(basePath, { recursive: true });
|
|
25
|
+
mkdirSync(join(basePath, "../unit"), { recursive: true });
|
|
26
|
+
mkdirSync(join(basePath, "../integration"), { recursive: true });
|
|
27
|
+
mkdirSync(join(basePath, "../fuzz"), { recursive: true });
|
|
28
|
+
|
|
29
|
+
Logger.success(`Test structure created at ${basePath}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Generate DiamondDeployment.sol from deployment record
|
|
34
|
+
*/
|
|
35
|
+
async generateDeploymentHelpers(
|
|
36
|
+
diamondName: string,
|
|
37
|
+
networkName: string,
|
|
38
|
+
chainId: number,
|
|
39
|
+
deploymentData: DeployedDiamondData
|
|
40
|
+
): Promise<string> {
|
|
41
|
+
Logger.section("Generating Diamond Deployment Helper");
|
|
42
|
+
|
|
43
|
+
const helpersDir = this.hre.diamondsFoundry.helpersDir;
|
|
44
|
+
const outputPath = join(
|
|
45
|
+
this.hre.config.paths.root,
|
|
46
|
+
helpersDir,
|
|
47
|
+
"DiamondDeployment.sol"
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const content = this.generateLibrarySource(
|
|
51
|
+
diamondName,
|
|
52
|
+
networkName,
|
|
53
|
+
chainId,
|
|
54
|
+
deploymentData
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Ensure directory exists
|
|
58
|
+
mkdirSync(join(this.hre.config.paths.root, helpersDir), {
|
|
59
|
+
recursive: true,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Write file
|
|
63
|
+
writeFileSync(outputPath, content, "utf8");
|
|
64
|
+
|
|
65
|
+
Logger.success(`Generated: ${outputPath}`);
|
|
66
|
+
return outputPath;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Generate example test files
|
|
71
|
+
*/
|
|
72
|
+
async generateExampleTests(): Promise<string[]> {
|
|
73
|
+
const generated: string[] = [];
|
|
74
|
+
const examples = this.hre.diamondsFoundry.exampleTests;
|
|
75
|
+
|
|
76
|
+
if (!this.hre.diamondsFoundry.generateExamples) {
|
|
77
|
+
Logger.info("Example generation disabled in config");
|
|
78
|
+
return generated;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
Logger.section("Generating Example Tests");
|
|
82
|
+
|
|
83
|
+
const basePath = join(this.hre.config.paths.root, "test", "foundry");
|
|
84
|
+
const templatesPath = join(__dirname, "../templates");
|
|
85
|
+
|
|
86
|
+
for (const type of examples) {
|
|
87
|
+
let templateFile = "";
|
|
88
|
+
let outputPath = "";
|
|
89
|
+
|
|
90
|
+
switch (type) {
|
|
91
|
+
case "unit":
|
|
92
|
+
templateFile = join(templatesPath, "ExampleUnitTest.t.sol.template");
|
|
93
|
+
outputPath = join(basePath, "unit", "ExampleUnit.t.sol");
|
|
94
|
+
break;
|
|
95
|
+
case "integration":
|
|
96
|
+
templateFile = join(templatesPath, "ExampleIntegrationTest.t.sol.template");
|
|
97
|
+
outputPath = join(basePath, "integration", "ExampleIntegration.t.sol");
|
|
98
|
+
break;
|
|
99
|
+
case "fuzz":
|
|
100
|
+
templateFile = join(templatesPath, "ExampleFuzzTest.t.sol.template");
|
|
101
|
+
outputPath = join(basePath, "fuzz", "ExampleFuzz.t.sol");
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
Logger.warn(`Unknown example type: ${type}`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
// Check if template exists
|
|
110
|
+
if (!existsSync(templateFile)) {
|
|
111
|
+
Logger.warn(`Template not found: ${templateFile}`);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Read template content
|
|
116
|
+
const templateContent = readFileSync(templateFile, "utf8");
|
|
117
|
+
|
|
118
|
+
// Ensure output directory exists
|
|
119
|
+
mkdirSync(join(basePath, type), { recursive: true });
|
|
120
|
+
|
|
121
|
+
// Check if file already exists
|
|
122
|
+
if (existsSync(outputPath)) {
|
|
123
|
+
Logger.info(`Skipping ${type} example (already exists): ${outputPath}`);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Write example test file
|
|
128
|
+
writeFileSync(outputPath, templateContent, "utf8");
|
|
129
|
+
Logger.success(`Generated ${type} example: ${outputPath}`);
|
|
130
|
+
generated.push(outputPath);
|
|
131
|
+
} catch (error: any) {
|
|
132
|
+
Logger.error(`Failed to generate ${type} example: ${error.message}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (generated.length === 0) {
|
|
137
|
+
Logger.info("No new example tests generated (may already exist)");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return generated;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Generate Solidity library source from deployment data
|
|
145
|
+
* @private
|
|
146
|
+
*/
|
|
147
|
+
private generateLibrarySource(
|
|
148
|
+
diamondName: string,
|
|
149
|
+
networkName: string,
|
|
150
|
+
chainId: number,
|
|
151
|
+
deploymentData: DeployedDiamondData
|
|
152
|
+
): string {
|
|
153
|
+
const timestamp = new Date().toISOString();
|
|
154
|
+
const networkInfo = `${networkName}-${chainId}`;
|
|
155
|
+
const deploymentFileName = `${diamondName.toLowerCase()}-${networkInfo}.json`;
|
|
156
|
+
const deploymentFilePath = `diamonds/${diamondName}/deployments/${deploymentFileName}`;
|
|
157
|
+
|
|
158
|
+
let source = "";
|
|
159
|
+
|
|
160
|
+
// SPDX and pragma
|
|
161
|
+
source += "// SPDX-License-Identifier: MIT\n";
|
|
162
|
+
source += "pragma solidity ^0.8.19;\n\n";
|
|
163
|
+
|
|
164
|
+
// Header comments
|
|
165
|
+
source += "/**\n";
|
|
166
|
+
source += ` * @title DiamondDeployment\n`;
|
|
167
|
+
source += ` * @notice Auto-generated deployment data for ${diamondName}\n`;
|
|
168
|
+
source += ` * @dev This library provides constants and helper functions for accessing\n`;
|
|
169
|
+
source += ` * deployment data in Forge tests. It is auto-generated from the deployment\n`;
|
|
170
|
+
source += ` * record and should not be edited manually.\n`;
|
|
171
|
+
source += ` *\n`;
|
|
172
|
+
source += ` * Generated from: ${deploymentFilePath}\n`;
|
|
173
|
+
source += ` * Generated at: ${timestamp}\n`;
|
|
174
|
+
source += ` *\n`;
|
|
175
|
+
source += ` * To regenerate this file:\n`;
|
|
176
|
+
source += ` * npx hardhat diamonds-forge:generate-helpers --diamond ${diamondName}\n`;
|
|
177
|
+
source += ` *\n`;
|
|
178
|
+
source += ` * ⚠️ DO NOT EDIT MANUALLY - Changes will be overwritten on next generation\n`;
|
|
179
|
+
source += " */\n";
|
|
180
|
+
source += "library DiamondDeployment {\n";
|
|
181
|
+
|
|
182
|
+
// Diamond address
|
|
183
|
+
source += ` /// @notice Address of the deployed ${diamondName} contract\n`;
|
|
184
|
+
source += ` /// @dev This is the main Diamond proxy address\n`;
|
|
185
|
+
source += ` address constant DIAMOND_ADDRESS = ${deploymentData.DiamondAddress};\n\n`;
|
|
186
|
+
|
|
187
|
+
// Facet addresses
|
|
188
|
+
source += " // ========================================\n";
|
|
189
|
+
source += " // Facet Addresses\n";
|
|
190
|
+
source += " // ========================================\n\n";
|
|
191
|
+
|
|
192
|
+
const facets = deploymentData.DeployedFacets ?? {};
|
|
193
|
+
for (const [facetName, facetData] of Object.entries(facets)) {
|
|
194
|
+
const constantName = facetName
|
|
195
|
+
.replace(/Facet$/, "")
|
|
196
|
+
.replace(/([A-Z])/g, "_$1")
|
|
197
|
+
.toUpperCase()
|
|
198
|
+
.replace(/^_/, "") + "_FACET";
|
|
199
|
+
|
|
200
|
+
source += ` /// @notice Address of ${facetName} implementation\n`;
|
|
201
|
+
source += ` address constant ${constantName} = ${facetData.address};\n`;
|
|
202
|
+
}
|
|
203
|
+
source += "\n";
|
|
204
|
+
|
|
205
|
+
// Helper functions
|
|
206
|
+
source += " // ========================================\n";
|
|
207
|
+
source += " // Helper Functions\n";
|
|
208
|
+
source += " // ========================================\n\n";
|
|
209
|
+
|
|
210
|
+
source += " /**\n";
|
|
211
|
+
source += " * @notice Get the Diamond contract address\n";
|
|
212
|
+
source += " * @return The address of the deployed Diamond proxy\n";
|
|
213
|
+
source += " */\n";
|
|
214
|
+
source += " function getDiamondAddress() internal pure returns (address) {\n";
|
|
215
|
+
source += " return DIAMOND_ADDRESS;\n";
|
|
216
|
+
source += " }\n\n";
|
|
217
|
+
|
|
218
|
+
source += " /**\n";
|
|
219
|
+
source += " * @notice Get facet implementation address by name\n";
|
|
220
|
+
source += " * @param facetName The name of the facet\n";
|
|
221
|
+
source += " * @return The address of the facet implementation\n";
|
|
222
|
+
source += " */\n";
|
|
223
|
+
source += " function getFacetAddress(string memory facetName) internal pure returns (address) {\n";
|
|
224
|
+
|
|
225
|
+
let firstFacet = true;
|
|
226
|
+
for (const [facetName, facetData] of Object.entries(facets)) {
|
|
227
|
+
const constantName = facetName
|
|
228
|
+
.replace(/Facet$/, "")
|
|
229
|
+
.replace(/([A-Z])/g, "_$1")
|
|
230
|
+
.toUpperCase()
|
|
231
|
+
.replace(/^_/, "") + "_FACET";
|
|
232
|
+
|
|
233
|
+
const condition = firstFacet ? "if" : "else if";
|
|
234
|
+
source += ` ${condition} (keccak256(bytes(facetName)) == keccak256(bytes("${facetName}"))) {\n`;
|
|
235
|
+
source += ` return ${constantName};\n`;
|
|
236
|
+
source += " }\n";
|
|
237
|
+
firstFacet = false;
|
|
238
|
+
}
|
|
239
|
+
source += " return address(0);\n";
|
|
240
|
+
source += " }\n";
|
|
241
|
+
|
|
242
|
+
source += "}\n";
|
|
243
|
+
|
|
244
|
+
return source;
|
|
245
|
+
}
|
|
246
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { extendConfig, extendEnvironment, internalTask, task } from "hardhat/config";
|
|
2
|
+
import "./tasks/deploy";
|
|
3
|
+
import "./tasks/generate-helpers";
|
|
4
|
+
import "./tasks/init";
|
|
5
|
+
import "./tasks/test";
|
|
6
|
+
import "./types/hardhat";
|
|
7
|
+
|
|
8
|
+
import { existsSync, writeFileSync } from "fs";
|
|
9
|
+
import {
|
|
10
|
+
TASK_COMPILE_GET_REMAPPINGS,
|
|
11
|
+
TASK_COMPILE_TRANSFORM_IMPORT_NAME,
|
|
12
|
+
} from "hardhat/builtin-tasks/task-names";
|
|
13
|
+
import { HardhatRuntimeEnvironment } from "hardhat/types";
|
|
14
|
+
import path from "path";
|
|
15
|
+
import picocolors from "picocolors";
|
|
16
|
+
import {
|
|
17
|
+
getForgeConfig,
|
|
18
|
+
getRemappings,
|
|
19
|
+
HardhatFoundryError,
|
|
20
|
+
installDependency,
|
|
21
|
+
} from "./foundry";
|
|
22
|
+
import { validateConfig } from "./utils/validation";
|
|
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
|
+
// Export types
|
|
30
|
+
export * from "./types/config";
|
|
31
|
+
|
|
32
|
+
const TASK_INIT_FOUNDRY = "init-foundry";
|
|
33
|
+
|
|
34
|
+
let pluginActivated = false;
|
|
35
|
+
|
|
36
|
+
// Extend config with diamondsFoundry settings
|
|
37
|
+
extendConfig((config, userConfig) => {
|
|
38
|
+
// Validate and set diamondsFoundry config
|
|
39
|
+
config.diamondsFoundry = validateConfig(userConfig.diamondsFoundry);
|
|
40
|
+
|
|
41
|
+
// Check foundry.toml presence. Don't warn when running foundry initialization task
|
|
42
|
+
if (!existsSync(path.join(config.paths.root, "foundry.toml"))) {
|
|
43
|
+
if (!process.argv.includes(TASK_INIT_FOUNDRY)) {
|
|
44
|
+
console.log(
|
|
45
|
+
picocolors.yellow(
|
|
46
|
+
`Warning: You are using the diamonds-hardhat-foundry plugin but there isn't a foundry.toml file in your project. Run 'npx hardhat ${TASK_INIT_FOUNDRY}' to create one.`
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Load foundry config
|
|
54
|
+
const foundryConfig = getForgeConfig();
|
|
55
|
+
|
|
56
|
+
// Ensure required keys exist
|
|
57
|
+
if (
|
|
58
|
+
foundryConfig?.src === undefined ||
|
|
59
|
+
foundryConfig?.cache_path === undefined
|
|
60
|
+
) {
|
|
61
|
+
throw new HardhatFoundryError(
|
|
62
|
+
"Couldn't find `src` or `cache_path` config keys after running `forge config --json`"
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Ensure foundry src path doesn't mismatch user-configured path
|
|
67
|
+
const userSourcesPath = userConfig.paths?.sources;
|
|
68
|
+
const foundrySourcesPath = foundryConfig.src;
|
|
69
|
+
|
|
70
|
+
if (
|
|
71
|
+
userSourcesPath !== undefined &&
|
|
72
|
+
path.resolve(userSourcesPath) !== path.resolve(foundrySourcesPath)
|
|
73
|
+
) {
|
|
74
|
+
throw new HardhatFoundryError(
|
|
75
|
+
`User-configured sources path (${userSourcesPath}) doesn't match path configured in foundry (${foundrySourcesPath})`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Set sources path
|
|
80
|
+
config.paths.sources = path.resolve(config.paths.root, foundrySourcesPath);
|
|
81
|
+
|
|
82
|
+
// Change hardhat's cache path if it clashes with foundry's
|
|
83
|
+
const foundryCachePath = path.resolve(
|
|
84
|
+
config.paths.root,
|
|
85
|
+
foundryConfig.cache_path
|
|
86
|
+
);
|
|
87
|
+
if (config.paths.cache === foundryCachePath) {
|
|
88
|
+
config.paths.cache = "cache_hardhat";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
pluginActivated = true;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Extend environment to add diamondsFoundry config to HRE
|
|
95
|
+
extendEnvironment((hre: HardhatRuntimeEnvironment) => {
|
|
96
|
+
hre.diamondsFoundry = hre.config.diamondsFoundry;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// This task is in place to detect old hardhat-core versions
|
|
100
|
+
internalTask(TASK_COMPILE_TRANSFORM_IMPORT_NAME).setAction(
|
|
101
|
+
async (
|
|
102
|
+
{
|
|
103
|
+
importName,
|
|
104
|
+
deprecationCheck,
|
|
105
|
+
}: { importName: string; deprecationCheck: boolean },
|
|
106
|
+
_hre
|
|
107
|
+
): Promise<string> => {
|
|
108
|
+
// When the deprecationCheck param is passed, it means a new enough hardhat-core is being used
|
|
109
|
+
if (deprecationCheck) {
|
|
110
|
+
return importName;
|
|
111
|
+
}
|
|
112
|
+
throw new HardhatFoundryError(
|
|
113
|
+
"This version of diamonds-hardhat-foundry depends on hardhat version >= 2.17.2"
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
internalTask(TASK_COMPILE_GET_REMAPPINGS).setAction(
|
|
119
|
+
async (): Promise<Record<string, string>> => {
|
|
120
|
+
if (!pluginActivated) {
|
|
121
|
+
return {};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return getRemappings();
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
task(
|
|
129
|
+
TASK_INIT_FOUNDRY,
|
|
130
|
+
"Initialize foundry setup in current hardhat project",
|
|
131
|
+
async (_, hre: HardhatRuntimeEnvironment) => {
|
|
132
|
+
const foundryConfigPath = path.resolve(
|
|
133
|
+
hre.config.paths.root,
|
|
134
|
+
"foundry.toml"
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (existsSync(foundryConfigPath)) {
|
|
138
|
+
console.warn(
|
|
139
|
+
picocolors.yellow(`File foundry.toml already exists. Aborting.`)
|
|
140
|
+
);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(`Creating foundry.toml file...`);
|
|
145
|
+
|
|
146
|
+
writeFileSync(
|
|
147
|
+
foundryConfigPath,
|
|
148
|
+
[
|
|
149
|
+
`[profile.default]`,
|
|
150
|
+
`src = '${path.relative(
|
|
151
|
+
hre.config.paths.root,
|
|
152
|
+
hre.config.paths.sources
|
|
153
|
+
)}'`,
|
|
154
|
+
`out = 'out'`,
|
|
155
|
+
`libs = ['node_modules', 'lib']`,
|
|
156
|
+
`test = '${path.relative(
|
|
157
|
+
hre.config.paths.root,
|
|
158
|
+
hre.config.paths.tests
|
|
159
|
+
)}'`,
|
|
160
|
+
`cache_path = 'cache_forge'`,
|
|
161
|
+
].join("\n")
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
await installDependency("foundry-rs/forge-std");
|
|
165
|
+
}
|
|
166
|
+
);
|