recon-generate 0.0.15 → 0.0.16

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/info.js ADDED
@@ -0,0 +1,984 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runInfo = void 0;
37
+ const child_process_1 = require("child_process");
38
+ const fs = __importStar(require("fs/promises"));
39
+ const path = __importStar(require("path"));
40
+ const solc_typed_ast_1 = require("solc-typed-ast");
41
+ const utils_1 = require("./utils");
42
+ const z3Solver_1 = require("./z3Solver");
43
+ 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
+ const loadLatestBuildInfo = async (foundryRoot) => {
57
+ var _a;
58
+ const outDir = path.join(foundryRoot, 'out');
59
+ const buildInfoDir = path.join(outDir, 'build-info');
60
+ let files = [];
61
+ try {
62
+ const entries = await fs.readdir(buildInfoDir);
63
+ const jsonFiles = entries.filter((f) => f.endsWith('.json'));
64
+ files = await Promise.all(jsonFiles.map(async (f) => ({
65
+ name: f,
66
+ path: path.join(buildInfoDir, f),
67
+ mtime: (await fs.stat(path.join(buildInfoDir, f))).mtime,
68
+ })));
69
+ }
70
+ catch (e) {
71
+ throw new Error(`No build-info directory found at ${buildInfoDir}: ${e}`);
72
+ }
73
+ if (files.length === 0) {
74
+ throw new Error(`No build-info JSON files found in ${buildInfoDir}.`);
75
+ }
76
+ const latestFile = files.sort((a, b) => b.mtime.getTime() - a.mtime.getTime())[0].path;
77
+ const fileContent = await fs.readFile(latestFile, 'utf-8');
78
+ const buildInfo = JSON.parse(fileContent);
79
+ const buildOutput = (_a = buildInfo.output) !== null && _a !== void 0 ? _a : buildInfo;
80
+ if (!buildOutput) {
81
+ throw new Error(`Build-info file ${latestFile} is missing output data.`);
82
+ }
83
+ // Extract ABIs from contracts
84
+ const contractAbis = new Map();
85
+ if (buildOutput.contracts) {
86
+ for (const [_filePath, contracts] of Object.entries(buildOutput.contracts)) {
87
+ for (const [contractName, contractData] of Object.entries(contracts)) {
88
+ if (contractData.abi) {
89
+ contractAbis.set(contractName, contractData.abi);
90
+ }
91
+ }
92
+ }
93
+ }
94
+ const filteredAstData = { ...buildOutput };
95
+ if (filteredAstData.sources) {
96
+ const validSources = {};
97
+ for (const [key, content] of Object.entries(filteredAstData.sources)) {
98
+ const ast = content.ast || content.legacyAST || content.AST;
99
+ if (ast && (ast.nodeType === 'SourceUnit' || ast.name === 'SourceUnit')) {
100
+ validSources[key] = content;
101
+ }
102
+ }
103
+ filteredAstData.sources = validSources;
104
+ }
105
+ const reader = new solc_typed_ast_1.ASTReader();
106
+ const sourceUnits = reader.read(filteredAstData);
107
+ return { sourceUnits, contractAbis };
108
+ };
109
+ const stripDataLocation = (raw) => {
110
+ if (!raw)
111
+ return '';
112
+ const noLocation = raw
113
+ .replace(/\s+(storage|memory|calldata)(\s+pointer)?/g, '')
114
+ .replace(/\s+pointer/g, '');
115
+ const noPrefix = noLocation
116
+ .replace(/^struct\s+/, '')
117
+ .replace(/^enum\s+/, '')
118
+ .replace(/^contract\s+/, '')
119
+ .trim();
120
+ return noPrefix;
121
+ };
122
+ const signatureFromFnDef = (fnDef) => {
123
+ const params = fnDef.vParameters.vParameters
124
+ .map((p) => { var _a; return stripDataLocation((_a = p.typeString) !== null && _a !== void 0 ? _a : ''); })
125
+ .join(',');
126
+ const name = fnDef.name
127
+ || (fnDef.kind === solc_typed_ast_1.FunctionKind.Constructor
128
+ ? 'constructor'
129
+ : fnDef.kind === solc_typed_ast_1.FunctionKind.Fallback
130
+ ? 'fallback'
131
+ : fnDef.kind === solc_typed_ast_1.FunctionKind.Receive
132
+ ? 'receive'
133
+ : '');
134
+ return `${name}(${params})`;
135
+ };
136
+ /**
137
+ * Convert typeString to actual Solidity type
138
+ * e.g. "int_const 42" -> "uint256", "bool" -> "bool", etc.
139
+ */
140
+ const typeStringToSolidityType = (typeString, value) => {
141
+ if (!typeString)
142
+ return 'unknown';
143
+ // Handle int_const and rational_const
144
+ if (typeString.startsWith('int_const')) {
145
+ // Check if the value is negative
146
+ if (value && value.startsWith('-')) {
147
+ return 'int256';
148
+ }
149
+ return 'uint256';
150
+ }
151
+ if (typeString.startsWith('rational_const')) {
152
+ return 'uint256';
153
+ }
154
+ // Handle literal_string
155
+ if (typeString.startsWith('literal_string')) {
156
+ return 'string';
157
+ }
158
+ // Handle basic types
159
+ const basicTypes = [
160
+ 'bool', 'address', 'bytes', 'string',
161
+ 'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
162
+ 'int8', 'int16', 'int32', 'int64', 'int128', 'int256',
163
+ 'bytes1', 'bytes2', 'bytes4', 'bytes8', 'bytes16', 'bytes32',
164
+ ];
165
+ for (const t of basicTypes) {
166
+ if (typeString === t || typeString.startsWith(t + ' ')) {
167
+ return t;
168
+ }
169
+ }
170
+ // Default fallback
171
+ return typeString;
172
+ };
173
+ /**
174
+ * Convert literal value for display
175
+ */
176
+ const formatLiteralValue = (lit) => {
177
+ var _a;
178
+ if (lit.kind === 'bool') {
179
+ return lit.value === 'true' ? 'True' : 'False';
180
+ }
181
+ return (_a = lit.value) !== null && _a !== void 0 ? _a : '';
182
+ };
183
+ const getAllContracts = (sourceUnits) => {
184
+ const contracts = new Map();
185
+ for (const unit of sourceUnits) {
186
+ for (const contract of unit.getChildrenByType(solc_typed_ast_1.ContractDefinition)) {
187
+ contracts.set(contract.name, contract);
188
+ }
189
+ }
190
+ return contracts;
191
+ };
192
+ /**
193
+ * Recursively collect all contracts related to the target contract:
194
+ * - Base contracts (inheritance chain)
195
+ * - Contracts referenced in state variables
196
+ * - Contracts called in functions
197
+ */
198
+ const collectRelatedContracts = (targetContract, allContracts) => {
199
+ const visited = new Set();
200
+ const queue = [targetContract];
201
+ while (queue.length > 0) {
202
+ const contract = queue.shift();
203
+ if (visited.has(contract))
204
+ continue;
205
+ visited.add(contract);
206
+ // 1. Collect from linearized base contracts (inheritance)
207
+ for (const baseContract of contract.vLinearizedBaseContracts) {
208
+ if (!visited.has(baseContract)) {
209
+ queue.push(baseContract);
210
+ }
211
+ }
212
+ // 2. Collect from state variable types (contract references)
213
+ const stateVars = (0, utils_1.getDefinitions)(contract, 'vStateVariables', true);
214
+ for (const stateVar of stateVars) {
215
+ // Check if the type references a contract
216
+ const typeNode = stateVar.vType;
217
+ if (typeNode instanceof solc_typed_ast_1.UserDefinedTypeName) {
218
+ const ref = typeNode.vReferencedDeclaration;
219
+ if (ref instanceof solc_typed_ast_1.ContractDefinition && !visited.has(ref)) {
220
+ queue.push(ref);
221
+ }
222
+ }
223
+ // Also check typeString for contract types
224
+ const typeString = stateVar.typeString;
225
+ if (typeString === null || typeString === void 0 ? void 0 : typeString.startsWith('contract ')) {
226
+ const contractName = typeString.replace('contract ', '');
227
+ const refContract = allContracts.get(contractName);
228
+ if (refContract && !visited.has(refContract)) {
229
+ queue.push(refContract);
230
+ }
231
+ }
232
+ }
233
+ // 3. Collect from function calls to other contracts
234
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true);
235
+ for (const fnDef of allFunctions) {
236
+ for (const call of fnDef.getChildrenByType(solc_typed_ast_1.FunctionCall)) {
237
+ // Check if call target is on another contract
238
+ const expr = call.vExpression;
239
+ if (expr instanceof solc_typed_ast_1.MemberAccess) {
240
+ const baseExpr = expr.vExpression;
241
+ // Check typeString for contract type
242
+ const typeString = baseExpr.typeString;
243
+ if (typeString === null || typeString === void 0 ? void 0 : typeString.startsWith('contract ')) {
244
+ const contractName = typeString.replace('contract ', '');
245
+ const refContract = allContracts.get(contractName);
246
+ if (refContract && !visited.has(refContract)) {
247
+ queue.push(refContract);
248
+ }
249
+ }
250
+ // Also check the referenced declaration
251
+ const ref = expr.vReferencedDeclaration;
252
+ if (ref instanceof solc_typed_ast_1.FunctionDefinition) {
253
+ const parentContract = ref.vScope;
254
+ if (parentContract instanceof solc_typed_ast_1.ContractDefinition && !visited.has(parentContract)) {
255
+ queue.push(parentContract);
256
+ }
257
+ }
258
+ }
259
+ }
260
+ }
261
+ }
262
+ return visited;
263
+ };
264
+ const collectPayableFunctions = (contract) => {
265
+ const payable = [];
266
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true).reverse();
267
+ for (const fnDef of allFunctions) {
268
+ if (fnDef.stateMutability === solc_typed_ast_1.FunctionStateMutability.Payable) {
269
+ if (fnDef.kind === solc_typed_ast_1.FunctionKind.Receive) {
270
+ payable.push('()');
271
+ }
272
+ else if (fnDef.kind === solc_typed_ast_1.FunctionKind.Fallback) {
273
+ payable.push('fallback()');
274
+ }
275
+ else {
276
+ payable.push(signatureFromFnDef(fnDef));
277
+ }
278
+ }
279
+ }
280
+ return payable;
281
+ };
282
+ /**
283
+ * Collect constant functions from ABI (view/pure functions)
284
+ * This is more reliable than AST for complex types
285
+ */
286
+ const collectConstantFunctionsFromAbi = (contractName, contractAbis) => {
287
+ const abi = contractAbis.get(contractName);
288
+ if (!abi)
289
+ return [];
290
+ const constants = [];
291
+ for (const entry of abi) {
292
+ if (entry.type === 'function' &&
293
+ (entry.stateMutability === 'view' || entry.stateMutability === 'pure')) {
294
+ const params = (entry.inputs || []).map(inp => inp.type).join(',');
295
+ const sig = `${entry.name}(${params})`;
296
+ constants.push(sig);
297
+ }
298
+ }
299
+ return constants;
300
+ };
301
+ /**
302
+ * Collect constant functions including:
303
+ * - view/pure functions
304
+ * - public state variable getters
305
+ */
306
+ const collectConstantFunctions = (contract) => {
307
+ const constants = [];
308
+ const seen = new Set();
309
+ // 1. Collect view/pure functions
310
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true).reverse();
311
+ for (const fnDef of allFunctions) {
312
+ if (fnDef.visibility === solc_typed_ast_1.FunctionVisibility.External || fnDef.visibility === solc_typed_ast_1.FunctionVisibility.Public) {
313
+ const sig = signatureFromFnDef(fnDef);
314
+ if (!seen.has(sig)) {
315
+ seen.add(sig);
316
+ constants.push(sig);
317
+ }
318
+ }
319
+ }
320
+ // 2. Collect public state variable auto-generated getters
321
+ const stateVars = (0, utils_1.getDefinitions)(contract, 'vStateVariables', true);
322
+ for (const stateVar of stateVars) {
323
+ if (stateVar.visibility === 'public') {
324
+ // Generate getter signature based on type
325
+ const sig = generateGetterSignature(stateVar);
326
+ if (sig && !seen.has(sig)) {
327
+ seen.add(sig);
328
+ constants.push(sig);
329
+ }
330
+ }
331
+ }
332
+ return constants;
333
+ };
334
+ /**
335
+ * Generate getter function signature for public state variables
336
+ */
337
+ const generateGetterSignature = (stateVar) => {
338
+ const name = stateVar.name;
339
+ const typeString = stateVar.typeString;
340
+ if (!typeString)
341
+ return null;
342
+ // Handle mappings - generate signature with key type
343
+ if (typeString.startsWith('mapping(')) {
344
+ const keyType = extractMappingKeyType(typeString);
345
+ if (keyType) {
346
+ return `${name}(${keyType})`;
347
+ }
348
+ return null;
349
+ }
350
+ // Handle arrays - generate signature with uint256 index
351
+ if (typeString.endsWith('[]') || typeString.match(/\[\d+\]$/)) {
352
+ return `${name}(uint256)`;
353
+ }
354
+ // Simple variables - no parameters
355
+ return `${name}()`;
356
+ };
357
+ /**
358
+ * Extract the key type from a mapping type string
359
+ */
360
+ const extractMappingKeyType = (typeString) => {
361
+ // mapping(address => uint256) -> address
362
+ // mapping(uint256 => mapping(address => bool)) -> uint256
363
+ const match = typeString.match(/^mapping\((\w+)\s*=>/);
364
+ if (match) {
365
+ return match[1];
366
+ }
367
+ return null;
368
+ };
369
+ /**
370
+ * Collect assert statements with source location info
371
+ */
372
+ const collectAssertStatements = (contract, foundryRoot, sourceContents) => {
373
+ const asserts = {};
374
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true).reverse();
375
+ for (const fnDef of allFunctions) {
376
+ if (fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.External && fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.Public) {
377
+ continue;
378
+ }
379
+ if (fnDef.kind !== solc_typed_ast_1.FunctionKind.Function && fnDef.kind !== solc_typed_ast_1.FunctionKind.Constructor) {
380
+ continue;
381
+ }
382
+ const sig = signatureFromFnDef(fnDef);
383
+ const assertInfos = [];
384
+ for (const call of fnDef.getChildrenByType(solc_typed_ast_1.FunctionCall)) {
385
+ const expr = call.vExpression;
386
+ if (expr instanceof solc_typed_ast_1.Identifier && expr.name === 'assert') {
387
+ // Get source location
388
+ const src = call.src;
389
+ if (src) {
390
+ const [startStr, lengthStr, fileIdStr] = src.split(':');
391
+ const start = parseInt(startStr, 10);
392
+ const length = parseInt(lengthStr, 10);
393
+ // Get the source file path
394
+ const sourceUnit = call.getClosestParentByType(solc_typed_ast_1.SourceUnit);
395
+ let filePath = (sourceUnit === null || sourceUnit === void 0 ? void 0 : sourceUnit.absolutePath) || '';
396
+ // Make path relative to foundry root
397
+ let relPath = filePath;
398
+ if (filePath.startsWith(foundryRoot)) {
399
+ relPath = filePath.slice(foundryRoot.length + 1);
400
+ }
401
+ // Get line info from source content
402
+ const content = sourceContents.get(filePath) || '';
403
+ let lines = [];
404
+ let startingColumn = 1;
405
+ let endingColumn = 1;
406
+ if (content) {
407
+ const lineInfo = (0, utils_1.getLines)(content, src);
408
+ lines = [];
409
+ for (let i = lineInfo.start; i <= lineInfo.end; i++) {
410
+ lines.push(i);
411
+ }
412
+ // Calculate columns
413
+ const beforeStart = content.substring(0, start);
414
+ const lastNewline = beforeStart.lastIndexOf('\n');
415
+ startingColumn = start - lastNewline;
416
+ endingColumn = startingColumn + length;
417
+ }
418
+ assertInfos.push({
419
+ start,
420
+ length,
421
+ filename_relative: relPath,
422
+ filename_absolute: filePath,
423
+ filename_short: relPath,
424
+ is_dependency: filePath.includes('lib/') || filePath.includes('node_modules/'),
425
+ lines,
426
+ starting_column: startingColumn,
427
+ ending_column: endingColumn,
428
+ });
429
+ }
430
+ }
431
+ }
432
+ // Only include functions that have asserts
433
+ if (assertInfos.length > 0) {
434
+ asserts[sig] = assertInfos;
435
+ }
436
+ }
437
+ return asserts;
438
+ };
439
+ /**
440
+ * Collect constants used in functions with proper type formatting
441
+ * Also includes Z3-solved values from constraint expressions
442
+ */
443
+ const collectConstantsUsed = async (contract) => {
444
+ var _a;
445
+ const constantsUsed = {};
446
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true).reverse();
447
+ for (const fnDef of allFunctions) {
448
+ if (fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.External && fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.Public) {
449
+ continue;
450
+ }
451
+ const sig = signatureFromFnDef(fnDef);
452
+ const literals = [];
453
+ // Collect literal constants
454
+ for (const lit of fnDef.getChildrenByType(solc_typed_ast_1.Literal)) {
455
+ const value = formatLiteralValue(lit);
456
+ const type = typeStringToSolidityType((_a = lit.typeString) !== null && _a !== void 0 ? _a : '', value);
457
+ // Each constant is wrapped in an array (matching slither format)
458
+ literals.push([{ value, type }]);
459
+ }
460
+ // Collect Z3-solved constants from constraint expressions
461
+ try {
462
+ const z3Solutions = await (0, z3Solver_1.findZ3Solutions)(fnDef);
463
+ for (const solution of z3Solutions) {
464
+ // Add Z3 solution as a special constant with metadata
465
+ literals.push([{
466
+ value: solution.value,
467
+ type: solution.type,
468
+ }]);
469
+ }
470
+ }
471
+ catch (e) {
472
+ // Z3 errors are non-fatal, continue without Z3 solutions
473
+ }
474
+ // Only include functions that have constants
475
+ if (literals.length > 0) {
476
+ constantsUsed[sig] = literals;
477
+ }
478
+ }
479
+ return constantsUsed;
480
+ };
481
+ const collectFunctionsRelations = (contract) => {
482
+ const relations = {};
483
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true).reverse();
484
+ const stateVars = (0, utils_1.getDefinitions)(contract, 'vStateVariables', true);
485
+ // Build a map of state variable IDs for faster lookup
486
+ const stateVarIds = new Set(stateVars.map(v => v.id));
487
+ const stateVarNames = new Set(stateVars.map(v => v.name));
488
+ const fnWritesVar = new Map();
489
+ const fnReadsVar = new Map();
490
+ // Helper to check if a node is part of assignment left-hand side
491
+ const isInAssignmentLHS = (node) => {
492
+ const parentAssignment = node.getClosestParentByType(solc_typed_ast_1.Assignment);
493
+ if (!parentAssignment)
494
+ return false;
495
+ // Walk up from the node to see if it's under the LHS
496
+ let current = node;
497
+ while (current && current !== parentAssignment) {
498
+ if (current === parentAssignment.vLeftHandSide) {
499
+ return true;
500
+ }
501
+ current = current.parent;
502
+ }
503
+ return false;
504
+ };
505
+ for (const fnDef of allFunctions) {
506
+ if (fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.External && fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.Public) {
507
+ continue;
508
+ }
509
+ const sig = signatureFromFnDef(fnDef);
510
+ const writes = new Set();
511
+ const reads = new Set();
512
+ // Collect writes (assignments to state variables)
513
+ for (const assignment of fnDef.getChildrenByType(solc_typed_ast_1.Assignment)) {
514
+ if ((0, utils_1.isStateVarAssignment)(assignment)) {
515
+ const varDecl = (0, utils_1.getStateVarAssignment)(assignment);
516
+ if (varDecl && stateVarNames.has(varDecl.name)) {
517
+ writes.add(varDecl.name);
518
+ // Compound assignments (+=, -=, etc.) also read
519
+ if (assignment.operator !== '=') {
520
+ reads.add(varDecl.name);
521
+ }
522
+ }
523
+ }
524
+ }
525
+ // Collect reads (identifiers referencing state variables)
526
+ for (const id of fnDef.getChildrenByType(solc_typed_ast_1.Identifier)) {
527
+ const ref = id.vReferencedDeclaration;
528
+ if (ref instanceof solc_typed_ast_1.VariableDeclaration && stateVarIds.has(ref.id)) {
529
+ // Check if this identifier is in an assignment LHS
530
+ if (!isInAssignmentLHS(id)) {
531
+ reads.add(ref.name);
532
+ }
533
+ else {
534
+ // Even in LHS, compound assignments read
535
+ const parentAssignment = id.getClosestParentByType(solc_typed_ast_1.Assignment);
536
+ if (parentAssignment && parentAssignment.operator !== '=') {
537
+ reads.add(ref.name);
538
+ }
539
+ }
540
+ }
541
+ }
542
+ // Collect reads from member accesses (for struct fields, mapping accesses)
543
+ for (const ma of fnDef.getChildrenByType(solc_typed_ast_1.MemberAccess)) {
544
+ const ref = ma.vReferencedDeclaration;
545
+ if (ref instanceof solc_typed_ast_1.VariableDeclaration && stateVarIds.has(ref.id)) {
546
+ if (!isInAssignmentLHS(ma)) {
547
+ reads.add(ref.name);
548
+ }
549
+ else {
550
+ const parentAssignment = ma.getClosestParentByType(solc_typed_ast_1.Assignment);
551
+ if (parentAssignment && parentAssignment.operator !== '=') {
552
+ reads.add(ref.name);
553
+ }
554
+ }
555
+ }
556
+ }
557
+ fnWritesVar.set(sig, writes);
558
+ fnReadsVar.set(sig, reads);
559
+ }
560
+ // Build relations
561
+ for (const fnDef of allFunctions) {
562
+ if (fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.External && fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.Public) {
563
+ continue;
564
+ }
565
+ const sig = signatureFromFnDef(fnDef);
566
+ const writes = fnWritesVar.get(sig) || new Set();
567
+ const reads = fnReadsVar.get(sig) || new Set();
568
+ const impacts = [];
569
+ const isImpactedBy = [];
570
+ // This function impacts others (including itself) that read variables this function writes
571
+ for (const [otherSig, otherReads] of fnReadsVar) {
572
+ for (const w of writes) {
573
+ if (otherReads.has(w)) {
574
+ if (!impacts.includes(otherSig)) {
575
+ impacts.push(otherSig);
576
+ }
577
+ break;
578
+ }
579
+ }
580
+ }
581
+ // This function is impacted by others (including itself) that write variables this function reads
582
+ for (const [otherSig, otherWrites] of fnWritesVar) {
583
+ for (const r of reads) {
584
+ if (otherWrites.has(r)) {
585
+ if (!isImpactedBy.includes(otherSig)) {
586
+ isImpactedBy.push(otherSig);
587
+ }
588
+ break;
589
+ }
590
+ }
591
+ }
592
+ // Check for single external call (harness wrapper pattern)
593
+ let externalCall;
594
+ const functionCalls = fnDef.getChildrenByType(solc_typed_ast_1.FunctionCall).filter(call => {
595
+ // Only consider regular function calls (not type conversions, struct constructors, etc.)
596
+ if (call.kind !== solc_typed_ast_1.FunctionCallKind.FunctionCall)
597
+ return false;
598
+ // Must be a member access (e.g., foo.bar())
599
+ if (!(call.vExpression instanceof solc_typed_ast_1.MemberAccess))
600
+ return false;
601
+ const memberAccess = call.vExpression;
602
+ const referencedDecl = memberAccess.vReferencedDeclaration;
603
+ // Must reference a function
604
+ if (!(referencedDecl instanceof solc_typed_ast_1.FunctionDefinition))
605
+ return false;
606
+ // The function must be external or public
607
+ if (referencedDecl.visibility !== solc_typed_ast_1.FunctionVisibility.External &&
608
+ referencedDecl.visibility !== solc_typed_ast_1.FunctionVisibility.Public)
609
+ return false;
610
+ // Must be on a contract (not an interface)
611
+ const parentContract = referencedDecl.vScope;
612
+ if (!(parentContract instanceof solc_typed_ast_1.ContractDefinition))
613
+ return false;
614
+ if (parentContract.kind !== solc_typed_ast_1.ContractKind.Contract)
615
+ return false;
616
+ return true;
617
+ });
618
+ // If exactly one external call to a contract, record it
619
+ if (functionCalls.length === 1) {
620
+ const call = functionCalls[0];
621
+ const memberAccess = call.vExpression;
622
+ const referencedFn = memberAccess.vReferencedDeclaration;
623
+ const parentContract = referencedFn.vScope;
624
+ const fnSig = signatureFromFnDef(referencedFn);
625
+ externalCall = `${parentContract.name}::${fnSig}`;
626
+ }
627
+ const relation = {
628
+ impacts,
629
+ is_impacted_by: isImpactedBy
630
+ };
631
+ if (externalCall) {
632
+ relation.external = externalCall;
633
+ }
634
+ relations[sig] = relation;
635
+ }
636
+ return relations;
637
+ };
638
+ /**
639
+ * Get line number from an AST node's src attribute
640
+ */
641
+ const getLineFromSrc = (src, sourceContents, absolutePath) => {
642
+ const content = sourceContents.get(absolutePath);
643
+ if (!content || !src)
644
+ return null;
645
+ const lineInfo = (0, utils_1.getLines)(content, src);
646
+ return lineInfo.start;
647
+ };
648
+ /**
649
+ * Check if a function call is an external/high-level call (not internal)
650
+ */
651
+ const isExternalCall = (call) => {
652
+ // High-level calls to external contracts
653
+ if ((0, utils_1.highLevelCall)(call))
654
+ return true;
655
+ // Member access calls like contract.function()
656
+ if (call.vExpression instanceof solc_typed_ast_1.MemberAccess) {
657
+ const memberAccess = call.vExpression;
658
+ const baseType = memberAccess.vExpression.typeString;
659
+ // If base is a contract type, it's external
660
+ if (baseType === null || baseType === void 0 ? void 0 : baseType.startsWith('contract '))
661
+ return true;
662
+ }
663
+ return false;
664
+ };
665
+ /**
666
+ * Collect external call lines from a list of statements
667
+ */
668
+ const collectExternalCallLines = (statements, sourceContents, absolutePath) => {
669
+ const lines = [];
670
+ for (const stmt of statements) {
671
+ const calls = stmt.getChildrenByType(solc_typed_ast_1.FunctionCall);
672
+ for (const call of calls) {
673
+ if (call.kind === solc_typed_ast_1.FunctionCallKind.FunctionCall && isExternalCall(call)) {
674
+ const line = getLineFromSrc(call.src, sourceContents, absolutePath);
675
+ if (line !== null) {
676
+ const lineStr = line.toString();
677
+ if (!lines.includes(lineStr)) {
678
+ lines.push(lineStr);
679
+ }
680
+ }
681
+ }
682
+ }
683
+ }
684
+ return lines;
685
+ };
686
+ /**
687
+ * Build coverage block from a function/modifier body
688
+ */
689
+ const buildCoverageBlockFromBody = (definition, sourceContents, contract, callTree, visited = new Set()) => {
690
+ const sourceUnit = definition.getClosestParentByType(solc_typed_ast_1.SourceUnit);
691
+ const absolutePath = (sourceUnit === null || sourceUnit === void 0 ? void 0 : sourceUnit.absolutePath) || '';
692
+ const content = sourceContents.get(absolutePath) || '';
693
+ const lines = [];
694
+ const children = [];
695
+ if (!definition.vBody || definition.vBody.vStatements.length === 0) {
696
+ return { lines, absolutePath, children };
697
+ }
698
+ const statements = definition.vBody.vStatements;
699
+ // Add first statement line
700
+ const firstStmt = statements[0];
701
+ if (firstStmt.src && content) {
702
+ const lineInfo = (0, utils_1.getLines)(content, firstStmt.src);
703
+ lines.push(lineInfo.start.toString());
704
+ }
705
+ // Add last statement line (if different from first)
706
+ const lastStmt = statements[statements.length - 1];
707
+ if (lastStmt.src && content && statements.length > 1) {
708
+ const lineInfo = (0, utils_1.getLines)(content, lastStmt.src);
709
+ const lastLine = lineInfo.end.toString();
710
+ if (!lines.includes(lastLine)) {
711
+ lines.push(lastLine);
712
+ }
713
+ }
714
+ // Add external call lines
715
+ const externalLines = collectExternalCallLines(statements, sourceContents, absolutePath);
716
+ for (const line of externalLines) {
717
+ if (!lines.includes(line)) {
718
+ lines.push(line);
719
+ }
720
+ }
721
+ // Process nested blocks (if statements, try/catch, etc.)
722
+ for (const stmt of statements) {
723
+ // Handle if statements
724
+ if (stmt instanceof solc_typed_ast_1.IfStatement) {
725
+ const ifChildren = buildCoverageBlockFromIfStatement(stmt, sourceContents, absolutePath);
726
+ children.push(...ifChildren);
727
+ }
728
+ // Handle try statements
729
+ if (stmt instanceof solc_typed_ast_1.TryStatement) {
730
+ const tryChildren = buildCoverageBlockFromTryStatement(stmt, sourceContents, absolutePath);
731
+ children.push(...tryChildren);
732
+ }
733
+ }
734
+ // Process children from call tree (internal/modifier calls)
735
+ for (const child of callTree.children) {
736
+ // Skip if already visited (recursive call)
737
+ if (child.isRecursive || visited.has(child.nodeId)) {
738
+ continue;
739
+ }
740
+ visited.add(child.nodeId);
741
+ const childBlock = buildCoverageBlockFromBody(child.definition, sourceContents, contract, child, visited);
742
+ if (childBlock.lines.length > 0 || childBlock.children.length > 0) {
743
+ children.push(childBlock);
744
+ }
745
+ }
746
+ return { lines, absolutePath, children };
747
+ };
748
+ /**
749
+ * Build coverage blocks from an if statement
750
+ */
751
+ const buildCoverageBlockFromIfStatement = (ifStmt, sourceContents, absolutePath) => {
752
+ const blocks = [];
753
+ const content = sourceContents.get(absolutePath) || '';
754
+ // True branch
755
+ if (ifStmt.vTrueBody) {
756
+ const trueBranch = buildCoverageBlockFromStatementOrBlock(ifStmt.vTrueBody, sourceContents, absolutePath);
757
+ if (trueBranch.lines.length > 0) {
758
+ blocks.push(trueBranch);
759
+ }
760
+ }
761
+ // False branch (else)
762
+ if (ifStmt.vFalseBody) {
763
+ const falseBranch = buildCoverageBlockFromStatementOrBlock(ifStmt.vFalseBody, sourceContents, absolutePath);
764
+ if (falseBranch.lines.length > 0) {
765
+ blocks.push(falseBranch);
766
+ }
767
+ }
768
+ return blocks;
769
+ };
770
+ /**
771
+ * Build coverage blocks from a try statement
772
+ */
773
+ const buildCoverageBlockFromTryStatement = (tryStmt, sourceContents, absolutePath) => {
774
+ const blocks = [];
775
+ // Process each catch clause
776
+ for (const clause of tryStmt.vClauses) {
777
+ if (clause.vBlock) {
778
+ const clauseBlock = buildCoverageBlockFromStatementOrBlock(clause.vBlock, sourceContents, absolutePath);
779
+ if (clauseBlock.lines.length > 0) {
780
+ blocks.push(clauseBlock);
781
+ }
782
+ }
783
+ }
784
+ return blocks;
785
+ };
786
+ /**
787
+ * Build coverage block from a statement or block
788
+ */
789
+ const buildCoverageBlockFromStatementOrBlock = (node, sourceContents, absolutePath) => {
790
+ const content = sourceContents.get(absolutePath) || '';
791
+ const lines = [];
792
+ const children = [];
793
+ let statements = [];
794
+ if (node instanceof solc_typed_ast_1.Block) {
795
+ statements = node.vStatements;
796
+ }
797
+ else {
798
+ statements = [node];
799
+ }
800
+ if (statements.length === 0) {
801
+ return { lines, absolutePath, children };
802
+ }
803
+ // Add first statement line
804
+ const firstStmt = statements[0];
805
+ if (firstStmt.src && content) {
806
+ const lineInfo = (0, utils_1.getLines)(content, firstStmt.src);
807
+ lines.push(lineInfo.start.toString());
808
+ }
809
+ // Add last statement line
810
+ const lastStmt = statements[statements.length - 1];
811
+ if (lastStmt.src && content && statements.length > 1) {
812
+ const lineInfo = (0, utils_1.getLines)(content, lastStmt.src);
813
+ const lastLine = lineInfo.end.toString();
814
+ if (!lines.includes(lastLine)) {
815
+ lines.push(lastLine);
816
+ }
817
+ }
818
+ // Add external call lines
819
+ const externalLines = collectExternalCallLines(statements, sourceContents, absolutePath);
820
+ for (const line of externalLines) {
821
+ if (!lines.includes(line)) {
822
+ lines.push(line);
823
+ }
824
+ }
825
+ // Process nested blocks
826
+ for (const stmt of statements) {
827
+ if (stmt instanceof solc_typed_ast_1.IfStatement) {
828
+ const ifChildren = buildCoverageBlockFromIfStatement(stmt, sourceContents, absolutePath);
829
+ children.push(...ifChildren);
830
+ }
831
+ if (stmt instanceof solc_typed_ast_1.TryStatement) {
832
+ const tryChildren = buildCoverageBlockFromTryStatement(stmt, sourceContents, absolutePath);
833
+ children.push(...tryChildren);
834
+ }
835
+ }
836
+ return { lines, absolutePath, children };
837
+ };
838
+ /**
839
+ * Collect coverage map for a contract
840
+ */
841
+ const collectCoverageMap = (contract, sourceContents) => {
842
+ const coverageMap = {};
843
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true).reverse();
844
+ for (const fnDef of allFunctions) {
845
+ // Only include external/public functions
846
+ if (fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.External && fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.Public) {
847
+ continue;
848
+ }
849
+ const sig = signatureFromFnDef(fnDef);
850
+ // Build call tree context
851
+ const context = {
852
+ nodeCounter: { value: 0 },
853
+ contract: contract,
854
+ callStack: [],
855
+ activeNodes: new Map()
856
+ };
857
+ // Build call tree for this function
858
+ const callTree = (0, call_tree_builder_1.buildCallTree)(fnDef, [], undefined, undefined, context);
859
+ // Build coverage block from call tree
860
+ const coverageBlock = buildCoverageBlockFromBody(fnDef, sourceContents, contract, callTree);
861
+ coverageMap[sig] = coverageBlock;
862
+ }
863
+ return coverageMap;
864
+ };
865
+ const hasFallback = (contract) => {
866
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true);
867
+ return allFunctions.some(fn => fn.kind === solc_typed_ast_1.FunctionKind.Fallback);
868
+ };
869
+ const hasReceive = (contract) => {
870
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true);
871
+ return allFunctions.some(fn => fn.kind === solc_typed_ast_1.FunctionKind.Receive);
872
+ };
873
+ /**
874
+ * Load source file contents for source location info
875
+ */
876
+ const loadSourceContents = async (sourceUnits) => {
877
+ const contents = new Map();
878
+ for (const unit of sourceUnits) {
879
+ const filePath = unit.absolutePath;
880
+ if (filePath) {
881
+ try {
882
+ const content = await fs.readFile(filePath, 'utf-8');
883
+ contents.set(filePath, content);
884
+ }
885
+ catch {
886
+ // File might not exist or not accessible
887
+ }
888
+ }
889
+ }
890
+ return contents;
891
+ };
892
+ const runInfo = async (foundryRoot, contractName, options = {}) => {
893
+ const log = options.json ? () => { } : console.log.bind(console);
894
+ // Set Z3 to silent mode if --json is used
895
+ if (options.json) {
896
+ (0, z3Solver_1.setZ3Silent)(true);
897
+ }
898
+ const { sourceUnits, contractAbis } = await loadLatestBuildInfo(foundryRoot);
899
+ if (!sourceUnits || sourceUnits.length === 0) {
900
+ throw new Error('No source units were produced from the build; cannot generate info.');
901
+ }
902
+ // Load source file contents for assert info
903
+ const sourceContents = await loadSourceContents(sourceUnits);
904
+ const allContracts = getAllContracts(sourceUnits);
905
+ const targetContract = allContracts.get(contractName);
906
+ if (!targetContract) {
907
+ throw new Error(`Contract ${contractName} not found in build output.`);
908
+ }
909
+ // Recursively collect all related contracts
910
+ const relatedContracts = collectRelatedContracts(targetContract, allContracts);
911
+ log(`[recon-generate] Found ${relatedContracts.size} related contracts`);
912
+ const output = {
913
+ payable: {},
914
+ assert: {},
915
+ constant_functions: {},
916
+ constants_used: {},
917
+ functions_relations: {},
918
+ with_fallback: [],
919
+ with_receive: [],
920
+ coverage_map: {},
921
+ };
922
+ // Collect info for all related contracts
923
+ for (const contract of relatedContracts) {
924
+ // Skip abstract contracts (their functions are inherited by concrete contracts)
925
+ // But include libraries (ContractKind.Library) since they contain important logic
926
+ if (contract.abstract) {
927
+ continue;
928
+ }
929
+ if (contract.kind !== solc_typed_ast_1.ContractKind.Contract && contract.kind !== solc_typed_ast_1.ContractKind.Library) {
930
+ continue;
931
+ }
932
+ // Payable functions
933
+ const payable = collectPayableFunctions(contract);
934
+ if (payable.length > 0) {
935
+ output.payable[contract.name] = payable;
936
+ }
937
+ // Constant functions from ABI (more reliable for complex types)
938
+ const constantFns = collectConstantFunctionsFromAbi(contract.name, contractAbis);
939
+ if (constantFns.length > 0) {
940
+ output.constant_functions[contract.name] = constantFns;
941
+ }
942
+ // Assert statements (only include contracts with asserts)
943
+ const asserts = collectAssertStatements(contract, foundryRoot, sourceContents);
944
+ if (Object.keys(asserts).length > 0) {
945
+ output.assert[contract.name] = asserts;
946
+ }
947
+ // Constants used (only include contracts/functions with constants)
948
+ // Also includes Z3-solved values from constraint expressions
949
+ const constants = await collectConstantsUsed(contract);
950
+ if (Object.keys(constants).length > 0) {
951
+ output.constants_used[contract.name] = constants;
952
+ }
953
+ // Functions relations
954
+ const relations = collectFunctionsRelations(contract);
955
+ output.functions_relations[contract.name] = relations;
956
+ // Coverage map
957
+ const coverage = collectCoverageMap(contract, sourceContents);
958
+ if (Object.keys(coverage).length > 0) {
959
+ output.coverage_map[contract.name] = coverage;
960
+ }
961
+ // Fallback and receive
962
+ if (hasFallback(contract)) {
963
+ output.with_fallback.push(contract.name);
964
+ }
965
+ if (hasReceive(contract)) {
966
+ output.with_receive.push(contract.name);
967
+ }
968
+ }
969
+ const jsonOutput = JSON.stringify(output, null, 2);
970
+ // Cleanup Z3 context
971
+ await (0, z3Solver_1.cleanupZ3)();
972
+ // Output to terminal if --json flag is set
973
+ if (options.json) {
974
+ console.log(jsonOutput);
975
+ }
976
+ // Write to file if outputPath is provided or --json is not set
977
+ if (options.outputPath || !options.json) {
978
+ const infoPath = options.outputPath || path.join(foundryRoot, `recon-info-${contractName}.json`);
979
+ await fs.writeFile(infoPath, jsonOutput);
980
+ log(`[recon-generate] Wrote info file to ${infoPath}`);
981
+ }
982
+ return output;
983
+ };
984
+ exports.runInfo = runInfo;