recon-generate 0.0.17 β 0.0.19
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/dist/cfg/builder.d.ts +89 -0
- package/dist/cfg/builder.js +1295 -0
- package/dist/cfg/edge-mapper.d.ts +103 -0
- package/dist/cfg/edge-mapper.js +297 -0
- package/dist/cfg/emitter.d.ts +11 -0
- package/dist/cfg/emitter.js +492 -0
- package/dist/cfg/index.d.ts +35 -0
- package/dist/cfg/index.js +77 -0
- package/dist/cfg/sourcemap.d.ts +169 -0
- package/dist/cfg/sourcemap.js +605 -0
- package/dist/cfg/types.d.ts +270 -0
- package/dist/cfg/types.js +41 -0
- package/dist/cfg/unified-coverage.d.ts +87 -0
- package/dist/cfg/unified-coverage.js +248 -0
- package/dist/generator.js +1 -1
- package/dist/index.js +19 -0
- package/dist/info.js +0 -13
- package/dist/sourcemap.d.ts +14 -0
- package/dist/sourcemap.js +317 -0
- package/dist/types.d.ts +5 -0
- package/dist/wakeGenerator.js +2 -1
- package/package.json +2 -1
package/dist/info.js
CHANGED
|
@@ -34,25 +34,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.runInfo = void 0;
|
|
37
|
-
const child_process_1 = require("child_process");
|
|
38
37
|
const fs = __importStar(require("fs/promises"));
|
|
39
38
|
const path = __importStar(require("path"));
|
|
40
39
|
const solc_typed_ast_1 = require("solc-typed-ast");
|
|
41
40
|
const utils_1 = require("./utils");
|
|
42
41
|
const z3Solver_1 = require("./z3Solver");
|
|
43
42
|
const call_tree_builder_1 = require("./analyzer/call-tree-builder");
|
|
44
|
-
const runCmd = (cmd, cwd) => {
|
|
45
|
-
return new Promise((resolve, reject) => {
|
|
46
|
-
(0, child_process_1.exec)(cmd, { cwd, env: { ...process.env, PATH: (0, utils_1.getEnvPath)() } }, (err, _stdout, stderr) => {
|
|
47
|
-
if (err) {
|
|
48
|
-
reject(new Error(stderr || err.message));
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
resolve();
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
};
|
|
56
43
|
const loadLatestBuildInfo = async (foundryRoot) => {
|
|
57
44
|
var _a;
|
|
58
45
|
const outDir = path.join(foundryRoot, 'out');
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Recon Expander
|
|
4
|
+
*
|
|
5
|
+
* Generates CFGs and coverage-map.json for coverage-directed fuzzing.
|
|
6
|
+
* Auto-detects Foundry project in current directory or parent directories.
|
|
7
|
+
*
|
|
8
|
+
* Usage: recon-expander
|
|
9
|
+
*/
|
|
10
|
+
export interface SourcemapOptions {
|
|
11
|
+
outputDir?: string;
|
|
12
|
+
verbose?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function runSourcemap(foundryRoot: string, options?: SourcemapOptions): Promise<void>;
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Recon Expander
|
|
5
|
+
*
|
|
6
|
+
* Generates CFGs and coverage-map.json for coverage-directed fuzzing.
|
|
7
|
+
* Auto-detects Foundry project in current directory or parent directories.
|
|
8
|
+
*
|
|
9
|
+
* Usage: recon-expander
|
|
10
|
+
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.runSourcemap = runSourcemap;
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const solc_typed_ast_1 = require("solc-typed-ast");
|
|
49
|
+
const utils_1 = require("./utils");
|
|
50
|
+
const call_tree_builder_1 = require("./analyzer/call-tree-builder");
|
|
51
|
+
const cfg_1 = require("./cfg");
|
|
52
|
+
// Functions to exclude from CFG analysis (harness setup, not coverage targets)
|
|
53
|
+
const EXCLUDED_FUNCTIONS = new Set([
|
|
54
|
+
'setUp', 'targetContracts', 'targetSelectors', 'targetSenders',
|
|
55
|
+
'targetInterfaces', 'targetArtifacts', 'targetArtifactSelectors',
|
|
56
|
+
'excludeContracts', 'excludeSelectors', 'excludeSenders',
|
|
57
|
+
'excludeArtifacts', 'excludeArtifactSelectors', 'vm', 'kevm', 'stdstore',
|
|
58
|
+
'stdChainsInitialized', 'stdChains'
|
|
59
|
+
]);
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Project Detection
|
|
62
|
+
// ============================================================================
|
|
63
|
+
function findFoundryProject(startDir = process.cwd()) {
|
|
64
|
+
let dir = startDir;
|
|
65
|
+
while (dir !== path.dirname(dir)) {
|
|
66
|
+
if (fs.existsSync(path.join(dir, 'foundry.toml'))) {
|
|
67
|
+
return dir;
|
|
68
|
+
}
|
|
69
|
+
dir = path.dirname(dir);
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
function findBuildInfo(projectDir) {
|
|
74
|
+
const buildInfoDir = path.join(projectDir, 'out', 'build-info');
|
|
75
|
+
if (!fs.existsSync(buildInfoDir))
|
|
76
|
+
return null;
|
|
77
|
+
const files = fs.readdirSync(buildInfoDir).filter(f => f.endsWith('.json'));
|
|
78
|
+
if (files.length === 0)
|
|
79
|
+
return null;
|
|
80
|
+
// Return the most recent build-info file
|
|
81
|
+
const sorted = files.sort((a, b) => {
|
|
82
|
+
const statA = fs.statSync(path.join(buildInfoDir, a));
|
|
83
|
+
const statB = fs.statSync(path.join(buildInfoDir, b));
|
|
84
|
+
return statB.mtimeMs - statA.mtimeMs;
|
|
85
|
+
});
|
|
86
|
+
return path.join(buildInfoDir, sorted[0]);
|
|
87
|
+
}
|
|
88
|
+
function loadBuildInfo(buildInfoPath) {
|
|
89
|
+
const content = fs.readFileSync(buildInfoPath, 'utf-8');
|
|
90
|
+
const buildInfo = JSON.parse(content);
|
|
91
|
+
return buildInfo.output || buildInfo;
|
|
92
|
+
}
|
|
93
|
+
function getAllSourceContracts(sourceUnits, astData) {
|
|
94
|
+
const results = [];
|
|
95
|
+
const sources = astData.sources || {};
|
|
96
|
+
for (const su of sourceUnits) {
|
|
97
|
+
const sourcePath = Object.keys(sources).find(key => {
|
|
98
|
+
var _a;
|
|
99
|
+
const src = sources[key];
|
|
100
|
+
return ((_a = src === null || src === void 0 ? void 0 : src.ast) === null || _a === void 0 ? void 0 : _a.id) === su.id;
|
|
101
|
+
}) || '';
|
|
102
|
+
// Only include contracts from src/ directory
|
|
103
|
+
if (!sourcePath.startsWith('src/'))
|
|
104
|
+
continue;
|
|
105
|
+
for (const contract of su.vContracts) {
|
|
106
|
+
if (contract.kind === solc_typed_ast_1.ContractKind.Interface || contract.abstract)
|
|
107
|
+
continue;
|
|
108
|
+
if (contract.kind === solc_typed_ast_1.ContractKind.Library)
|
|
109
|
+
continue;
|
|
110
|
+
results.push({ contract, sourcePath });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return results;
|
|
114
|
+
}
|
|
115
|
+
// ============================================================================
|
|
116
|
+
// Call Tree Building
|
|
117
|
+
// ============================================================================
|
|
118
|
+
function buildCallTreesForContract(contract) {
|
|
119
|
+
const callTrees = [];
|
|
120
|
+
const inheritedFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', false).reverse();
|
|
121
|
+
const processedSignatures = new Set();
|
|
122
|
+
for (const func of [...contract.vFunctions, ...inheritedFunctions]) {
|
|
123
|
+
if (!func.implemented || !func.vBody)
|
|
124
|
+
continue;
|
|
125
|
+
if (EXCLUDED_FUNCTIONS.has(func.name))
|
|
126
|
+
continue;
|
|
127
|
+
const signature = (0, utils_1.getSignature)(func);
|
|
128
|
+
if (processedSignatures.has(signature))
|
|
129
|
+
continue;
|
|
130
|
+
processedSignatures.add(signature);
|
|
131
|
+
if (func.visibility === solc_typed_ast_1.FunctionVisibility.Public ||
|
|
132
|
+
func.visibility === solc_typed_ast_1.FunctionVisibility.External) {
|
|
133
|
+
const context = {
|
|
134
|
+
nodeCounter: { value: 0 },
|
|
135
|
+
contract,
|
|
136
|
+
callStack: [],
|
|
137
|
+
activeNodes: new Map()
|
|
138
|
+
};
|
|
139
|
+
const callTree = (0, call_tree_builder_1.buildCallTree)(func, [], undefined, undefined, context);
|
|
140
|
+
callTrees.push({ contract: contract.name, function: func, callTree });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return callTrees;
|
|
144
|
+
}
|
|
145
|
+
async function runSourcemap(foundryRoot, options = {}) {
|
|
146
|
+
var _a, _b;
|
|
147
|
+
const { verbose = false } = options;
|
|
148
|
+
console.log(`\nπ Recon-Expander: Coverage Map Generation`);
|
|
149
|
+
console.log('β'.repeat(60));
|
|
150
|
+
const projectDir = foundryRoot;
|
|
151
|
+
const outputDir = options.outputDir || path.join(projectDir, '.recon');
|
|
152
|
+
if (verbose) {
|
|
153
|
+
console.log(`[VERBOSE] Foundry root: ${foundryRoot}`);
|
|
154
|
+
console.log(`[VERBOSE] Output dir: ${outputDir}`);
|
|
155
|
+
}
|
|
156
|
+
console.log(`π Project: ${projectDir}`);
|
|
157
|
+
console.log(`π Output: ${outputDir}`);
|
|
158
|
+
const startTime = Date.now();
|
|
159
|
+
// Find build info
|
|
160
|
+
const buildInfoPath = findBuildInfo(projectDir);
|
|
161
|
+
if (!buildInfoPath) {
|
|
162
|
+
throw new Error(`No build-info found in ${projectDir}/out/build-info/. Run "forge build" first to generate build artifacts.`);
|
|
163
|
+
}
|
|
164
|
+
console.log(`π Found build-info: ${path.basename(buildInfoPath)}`);
|
|
165
|
+
// Load AST
|
|
166
|
+
const astData = loadBuildInfo(buildInfoPath);
|
|
167
|
+
const reader = new solc_typed_ast_1.ASTReader();
|
|
168
|
+
const skipPatterns = ['forge-std/', 'lib/forge-std/'];
|
|
169
|
+
const filteredSources = {};
|
|
170
|
+
for (const [key, content] of Object.entries(astData.sources)) {
|
|
171
|
+
const shouldSkip = skipPatterns.some(p => key.includes(p));
|
|
172
|
+
if (shouldSkip)
|
|
173
|
+
continue;
|
|
174
|
+
const ast = content.ast || content.legacyAST;
|
|
175
|
+
if (ast && (ast.nodeType === 'SourceUnit' || ast.name === 'SourceUnit')) {
|
|
176
|
+
filteredSources[key] = content;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const sourceUnits = reader.read({ sources: filteredSources });
|
|
180
|
+
// Get all source contracts
|
|
181
|
+
const srcContracts = getAllSourceContracts(sourceUnits, astData);
|
|
182
|
+
console.log(`\nπ― Found ${srcContracts.length} contracts under src/`);
|
|
183
|
+
// Create output directory
|
|
184
|
+
if (!fs.existsSync(outputDir)) {
|
|
185
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
186
|
+
}
|
|
187
|
+
// Step 1: Build CFGs
|
|
188
|
+
console.log(`\n${'β'.repeat(60)}`);
|
|
189
|
+
console.log(`π Step 1/3: Generating Control Flow Graphs...`);
|
|
190
|
+
console.log(`${'β'.repeat(60)}`);
|
|
191
|
+
let totalFunctions = 0;
|
|
192
|
+
let totalPaths = 0;
|
|
193
|
+
let totalEdges = 0;
|
|
194
|
+
const results = [];
|
|
195
|
+
for (const { contract } of srcContracts) {
|
|
196
|
+
try {
|
|
197
|
+
const callTrees = buildCallTreesForContract(contract);
|
|
198
|
+
if (callTrees.length === 0)
|
|
199
|
+
continue;
|
|
200
|
+
const { module, sexpr } = (0, cfg_1.buildAndEmitCFG)(contract, callTrees);
|
|
201
|
+
const summary = (0, cfg_1.getCoverageSummary)(module);
|
|
202
|
+
const outFile = path.join(outputDir, `${contract.name}.cfg.sexp`);
|
|
203
|
+
fs.writeFileSync(outFile, sexpr);
|
|
204
|
+
totalFunctions += module.functions.length;
|
|
205
|
+
totalPaths += summary.totalPaths;
|
|
206
|
+
totalEdges += summary.totalEdges;
|
|
207
|
+
results.push({ name: contract.name, funcs: module.functions.length, paths: summary.totalPaths });
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
console.log(` β οΈ Skipped ${contract.name}: ${e.message}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
console.log(` β
${results.length} contracts, ${totalFunctions} functions, ${totalPaths} paths`);
|
|
214
|
+
// Write aggregate manifest
|
|
215
|
+
const aggregateManifest = {
|
|
216
|
+
generated: new Date().toISOString(),
|
|
217
|
+
totalContracts: results.length,
|
|
218
|
+
totalFunctions,
|
|
219
|
+
totalPaths,
|
|
220
|
+
totalEdges,
|
|
221
|
+
contracts: results.map(r => ({ name: r.name, functions: r.funcs, paths: r.paths }))
|
|
222
|
+
};
|
|
223
|
+
fs.writeFileSync(path.join(outputDir, 'manifest.json'), JSON.stringify(aggregateManifest, null, 2));
|
|
224
|
+
// Step 2: Generate Branch Mappings
|
|
225
|
+
console.log(`\n${'β'.repeat(60)}`);
|
|
226
|
+
console.log(`πΊοΈ Step 2/3: Generating Branch Mappings...`);
|
|
227
|
+
console.log(`${'β'.repeat(60)}`);
|
|
228
|
+
const { loadBuildInfoSourceMaps, generateBranchMapping } = await Promise.resolve().then(() => __importStar(require('./cfg/sourcemap')));
|
|
229
|
+
const sourceMaps = loadBuildInfoSourceMaps(buildInfoPath);
|
|
230
|
+
const contractsToProcess = Array.from(sourceMaps.keys()).filter(name => {
|
|
231
|
+
const mapData = sourceMaps.get(name);
|
|
232
|
+
return mapData && mapData.sourceFile.startsWith('src/');
|
|
233
|
+
});
|
|
234
|
+
// Build source contents for line number calculation
|
|
235
|
+
const sourceContents = new Map();
|
|
236
|
+
const sourceList = astData.sourceList || Object.keys(astData.sources || {});
|
|
237
|
+
for (let i = 0; i < sourceList.length; i++) {
|
|
238
|
+
const sourcePath = sourceList[i];
|
|
239
|
+
const fullPath = path.join(projectDir, sourcePath);
|
|
240
|
+
if (fs.existsSync(fullPath)) {
|
|
241
|
+
sourceContents.set(i, fs.readFileSync(fullPath, 'utf-8'));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const allBranches = [];
|
|
245
|
+
let totalBranches = 0;
|
|
246
|
+
for (const contractName of contractsToProcess) {
|
|
247
|
+
const mapData = sourceMaps.get(contractName);
|
|
248
|
+
if (!mapData)
|
|
249
|
+
continue;
|
|
250
|
+
const ast = (_b = (_a = astData.sources) === null || _a === void 0 ? void 0 : _a[mapData.sourceFile]) === null || _b === void 0 ? void 0 : _b.ast;
|
|
251
|
+
const branchMapping = generateBranchMapping(contractName, mapData.bytecode, mapData.sourceMap, mapData.sourceFile, ast, undefined, sourceContents);
|
|
252
|
+
allBranches.push(branchMapping);
|
|
253
|
+
totalBranches += branchMapping.branches.length;
|
|
254
|
+
}
|
|
255
|
+
const branchManifest = {
|
|
256
|
+
generated: new Date().toISOString(),
|
|
257
|
+
totalContracts: contractsToProcess.length,
|
|
258
|
+
totalBranches,
|
|
259
|
+
contracts: allBranches
|
|
260
|
+
};
|
|
261
|
+
fs.writeFileSync(path.join(outputDir, 'branches.json'), JSON.stringify(branchManifest, null, 2));
|
|
262
|
+
console.log(` β
${contractsToProcess.length} contracts, ${totalBranches} branches`);
|
|
263
|
+
// Step 3: Generate Edge Mappings and Unified Coverage Map
|
|
264
|
+
console.log(`\n${'β'.repeat(60)}`);
|
|
265
|
+
console.log(`π Step 3/3: Generating Coverage Map...`);
|
|
266
|
+
console.log(`${'β'.repeat(60)}`);
|
|
267
|
+
let edgeMappingsData = null;
|
|
268
|
+
const cfgFiles = fs.readdirSync(outputDir).filter(f => f.endsWith('.cfg.sexp'));
|
|
269
|
+
const contractNames = cfgFiles.map(f => f.replace('.cfg.sexp', ''));
|
|
270
|
+
if (contractNames.length > 0) {
|
|
271
|
+
try {
|
|
272
|
+
const { generateEdgeMappings } = await Promise.resolve().then(() => __importStar(require('./cfg/edge-mapper')));
|
|
273
|
+
const result = await generateEdgeMappings(outputDir, contractNames);
|
|
274
|
+
console.log(` π ${result.totalContracts} contracts, ${result.totalEdges} edges`);
|
|
275
|
+
const edgeMappingsPath = path.join(outputDir, 'edge-mappings.json');
|
|
276
|
+
if (fs.existsSync(edgeMappingsPath)) {
|
|
277
|
+
edgeMappingsData = JSON.parse(fs.readFileSync(edgeMappingsPath, 'utf-8'));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch (e) {
|
|
281
|
+
console.log(` β οΈ Edge mapping failed: ${e.message}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Generate unified coverage map
|
|
285
|
+
try {
|
|
286
|
+
const { createUnifiedFromMemory, writeUnifiedCoverageMap } = await Promise.resolve().then(() => __importStar(require('./cfg/unified-coverage')));
|
|
287
|
+
const unifiedManifest = createUnifiedFromMemory(branchManifest, edgeMappingsData);
|
|
288
|
+
writeUnifiedCoverageMap(outputDir, unifiedManifest);
|
|
289
|
+
console.log(` π coverage-map.json: ${unifiedManifest.totalBranches} branches, ${unifiedManifest.totalEdges} edges`);
|
|
290
|
+
}
|
|
291
|
+
catch (e) {
|
|
292
|
+
console.log(` β οΈ Unified coverage map failed: ${e.message}`);
|
|
293
|
+
}
|
|
294
|
+
// Final summary
|
|
295
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
296
|
+
console.log(`\n${'β'.repeat(60)}`);
|
|
297
|
+
console.log(`β
Done in ${elapsed}s`);
|
|
298
|
+
console.log(`${'β'.repeat(60)}`);
|
|
299
|
+
console.log(`\nπ Output: ${outputDir}/`);
|
|
300
|
+
console.log(` βββ coverage-map.json β Fuzzer reads this`);
|
|
301
|
+
console.log(` βββ *.cfg.sexp CFG for solver`);
|
|
302
|
+
console.log(` βββ manifest.json Stats`);
|
|
303
|
+
console.log(`\nπ‘ Usage: recon <project> --cfgDirectedEnable=true`);
|
|
304
|
+
}
|
|
305
|
+
// CLI entry point when run directly
|
|
306
|
+
if (require.main === module) {
|
|
307
|
+
const projectDir = findFoundryProject();
|
|
308
|
+
if (!projectDir) {
|
|
309
|
+
console.error(`β No Foundry project found (no foundry.toml)`);
|
|
310
|
+
console.error(` Run this command from within a Foundry project directory.`);
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
runSourcemap(projectDir).catch(err => {
|
|
314
|
+
console.error('Error:', err.message || err);
|
|
315
|
+
process.exit(1);
|
|
316
|
+
});
|
|
317
|
+
}
|
package/dist/types.d.ts
CHANGED
package/dist/wakeGenerator.js
CHANGED
|
@@ -390,7 +390,7 @@ class WakeGenerator {
|
|
|
390
390
|
}
|
|
391
391
|
const hasCreationCode = typeof c.creation_code === 'string' && c.creation_code.trim().length > 0;
|
|
392
392
|
if (!hasCreationCode) {
|
|
393
|
-
this.logDebug('Skipping contract without creation code (
|
|
393
|
+
this.logDebug('Skipping contract without creation code (abstract)', { contract: c.name, module: c.module });
|
|
394
394
|
continue;
|
|
395
395
|
}
|
|
396
396
|
if (!this.isContractAllowed(c.name)) {
|
|
@@ -481,6 +481,7 @@ class WakeGenerator {
|
|
|
481
481
|
if (!explicitlyIncluded && isInterface) {
|
|
482
482
|
return false;
|
|
483
483
|
}
|
|
484
|
+
// Always filter abstract contracts (no creation code), even if explicitly included
|
|
484
485
|
const hasCreationCode = typeof c.creation_code === 'string' && c.creation_code.trim().length > 0;
|
|
485
486
|
if (!hasCreationCode) {
|
|
486
487
|
return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "recon-generate",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"description": "CLI to scaffold Recon fuzzing suite inside Foundry projects",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"abi-to-mock": "^1.0.4",
|
|
24
24
|
"case": "^1.6.3",
|
|
25
25
|
"commander": "^14.0.2",
|
|
26
|
+
"ethers": "^6.16.0",
|
|
26
27
|
"handlebars": "^4.7.8",
|
|
27
28
|
"solc-typed-ast": "^19.1.0",
|
|
28
29
|
"src-location": "^1.1.0",
|