recon-generate 0.0.22 → 0.0.24

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.
@@ -0,0 +1,25 @@
1
+ interface ConstructorParam {
2
+ name: string;
3
+ type: string;
4
+ }
5
+ interface ContractInfo {
6
+ constructor: ConstructorParam[];
7
+ state_variables: {
8
+ [variableName: string]: string;
9
+ };
10
+ }
11
+ interface FileContracts {
12
+ [contractName: string]: ContractInfo;
13
+ }
14
+ interface DictionaryOutput {
15
+ [filePath: string]: FileContracts;
16
+ }
17
+ /**
18
+ * Run the dictionary command
19
+ */
20
+ export declare const runDictionary: (buildInfoPath: string, options?: {
21
+ outputPath?: string;
22
+ json?: boolean;
23
+ srcPrefix?: string;
24
+ }) => Promise<DictionaryOutput>;
25
+ export {};
@@ -0,0 +1,246 @@
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.runDictionary = void 0;
37
+ const fs = __importStar(require("fs/promises"));
38
+ const solc_typed_ast_1 = require("solc-typed-ast");
39
+ const utils_1 = require("./utils");
40
+ /**
41
+ * Load build-info from a JSON file
42
+ */
43
+ const loadBuildInfo = async (buildInfoPath) => {
44
+ var _a;
45
+ const fileContent = await fs.readFile(buildInfoPath, 'utf-8');
46
+ const buildInfo = JSON.parse(fileContent);
47
+ const buildOutput = (_a = buildInfo.output) !== null && _a !== void 0 ? _a : buildInfo;
48
+ if (!buildOutput) {
49
+ throw new Error(`Build-info file ${buildInfoPath} is missing output data.`);
50
+ }
51
+ const filteredAstData = { ...buildOutput };
52
+ if (filteredAstData.sources) {
53
+ const validSources = {};
54
+ for (const [key, content] of Object.entries(filteredAstData.sources)) {
55
+ const ast = content.ast || content.legacyAST || content.AST;
56
+ if (ast && (ast.nodeType === 'SourceUnit' || ast.name === 'SourceUnit')) {
57
+ validSources[key] = content;
58
+ }
59
+ }
60
+ filteredAstData.sources = validSources;
61
+ }
62
+ const reader = new solc_typed_ast_1.ASTReader();
63
+ const sourceUnits = reader.read(filteredAstData);
64
+ return { sourceUnits };
65
+ };
66
+ /**
67
+ * Check if a type string represents an address, contract, or interface type
68
+ */
69
+ const isRelevantType = (typeString) => {
70
+ return (typeString === 'address' ||
71
+ typeString === 'address payable' ||
72
+ typeString.startsWith('contract ') ||
73
+ typeString.startsWith('interface '));
74
+ };
75
+ /**
76
+ * Extract the type name from a typeString
77
+ * e.g., "contract IHub" -> "IHub", "address" -> "address"
78
+ */
79
+ const extractTypeName = (typeString) => {
80
+ if (typeString.startsWith('contract ')) {
81
+ return typeString.slice('contract '.length);
82
+ }
83
+ if (typeString.startsWith('interface ')) {
84
+ return typeString.slice('interface '.length);
85
+ }
86
+ return typeString;
87
+ };
88
+ /**
89
+ * Get state variable map by ID for a contract (including inherited)
90
+ */
91
+ const getStateVarMap = (contract) => {
92
+ const stateVars = (0, utils_1.getDefinitions)(contract, 'vStateVariables', true);
93
+ const varMap = new Map();
94
+ for (const stateVar of stateVars) {
95
+ varMap.set(stateVar.id, stateVar);
96
+ }
97
+ return varMap;
98
+ };
99
+ /**
100
+ * Check if an expression references a state variable and return the variable name
101
+ */
102
+ const getReferencedStateVar = (node, stateVarIds) => {
103
+ const ref = node.vReferencedDeclaration;
104
+ if (ref instanceof solc_typed_ast_1.VariableDeclaration && stateVarIds.has(ref.id)) {
105
+ return ref.name;
106
+ }
107
+ return null;
108
+ };
109
+ /**
110
+ * Extract the casted type name from a type conversion call
111
+ * e.g., IERC20(token) -> "IERC20"
112
+ */
113
+ const getCastedTypeName = (call) => {
114
+ if (call.kind !== solc_typed_ast_1.FunctionCallKind.TypeConversion) {
115
+ return null;
116
+ }
117
+ const expr = call.vExpression;
118
+ if (expr instanceof solc_typed_ast_1.Identifier) {
119
+ const ref = expr.vReferencedDeclaration;
120
+ if (ref instanceof solc_typed_ast_1.ContractDefinition) {
121
+ return ref.name;
122
+ }
123
+ }
124
+ return null;
125
+ };
126
+ /**
127
+ * Collect state variables that are address, contract, or interface types
128
+ */
129
+ const collectRelevantStateVars = (stateVars) => {
130
+ const vars = new Map();
131
+ for (const stateVar of stateVars) {
132
+ const typeString = stateVar.typeString;
133
+ if (typeString && isRelevantType(typeString)) {
134
+ const typeName = extractTypeName(typeString);
135
+ vars.set(stateVar.name, typeName);
136
+ }
137
+ }
138
+ return vars;
139
+ };
140
+ /**
141
+ * Get constructor parameters for a contract
142
+ */
143
+ const getConstructorParams = (contract) => {
144
+ const functions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true);
145
+ const constructor = functions.find(fn => fn.kind === solc_typed_ast_1.FunctionKind.Constructor);
146
+ if (!constructor) {
147
+ return [];
148
+ }
149
+ return constructor.vParameters.vParameters.map(param => ({
150
+ name: param.name,
151
+ type: extractTypeName(param.typeString),
152
+ }));
153
+ };
154
+ /**
155
+ * Find all state variable casts in a contract
156
+ * e.g., IFoo(stateVar) or Foo(stateVar)
157
+ */
158
+ const findStateVarCasts = (contract, stateVarMap) => {
159
+ const casts = new Map();
160
+ for (const call of contract.getChildrenByType(solc_typed_ast_1.FunctionCall)) {
161
+ const castedType = getCastedTypeName(call);
162
+ if (!castedType)
163
+ continue;
164
+ for (const arg of call.vArguments) {
165
+ let stateVarName = null;
166
+ if (arg instanceof solc_typed_ast_1.Identifier) {
167
+ stateVarName = getReferencedStateVar(arg, stateVarMap);
168
+ }
169
+ else if (arg instanceof solc_typed_ast_1.MemberAccess) {
170
+ stateVarName = getReferencedStateVar(arg, stateVarMap);
171
+ }
172
+ if (stateVarName) {
173
+ if (!casts.has(stateVarName)) {
174
+ casts.set(stateVarName, castedType);
175
+ }
176
+ }
177
+ }
178
+ }
179
+ return casts;
180
+ };
181
+ /**
182
+ * Run the dictionary command
183
+ */
184
+ const runDictionary = async (buildInfoPath, options = {}) => {
185
+ var _a;
186
+ const log = options.json ? () => { } : console.log.bind(console);
187
+ const srcPrefix = (_a = options.srcPrefix) !== null && _a !== void 0 ? _a : 'src/';
188
+ log(`[recon-generate] Loading build-info from ${buildInfoPath}`);
189
+ const { sourceUnits } = await loadBuildInfo(buildInfoPath);
190
+ if (!sourceUnits || sourceUnits.length === 0) {
191
+ throw new Error('No source units were produced from the build-info; cannot generate dictionary.');
192
+ }
193
+ log(`[recon-generate] Found ${sourceUnits.length} source units`);
194
+ const output = {};
195
+ for (const unit of sourceUnits) {
196
+ const filePath = unit.absolutePath;
197
+ if (!filePath.startsWith(srcPrefix)) {
198
+ continue;
199
+ }
200
+ for (const contract of unit.getChildrenByType(solc_typed_ast_1.ContractDefinition)) {
201
+ // Skip interfaces, libraries, and abstract contracts
202
+ if (contract.kind !== solc_typed_ast_1.ContractKind.Contract) {
203
+ continue;
204
+ }
205
+ if (contract.abstract) {
206
+ continue;
207
+ }
208
+ // Get all state variables including inherited ones
209
+ const stateVars = (0, utils_1.getDefinitions)(contract, 'vStateVariables', true);
210
+ const stateVarMap = getStateVarMap(contract);
211
+ // 1. Get constructor parameters
212
+ const constructorParams = getConstructorParams(contract);
213
+ // 2. Collect state variables that are address/contract/interface types
214
+ const declaredVars = collectRelevantStateVars(stateVars);
215
+ // 3. Find all casts of state variables to contract/interface types
216
+ const casts = findStateVarCasts(contract, stateVarMap);
217
+ // Merge: casts override declared types
218
+ const mergedVars = {};
219
+ for (const [varName, typeName] of declaredVars) {
220
+ mergedVars[varName] = typeName;
221
+ }
222
+ for (const [varName, castedType] of casts) {
223
+ mergedVars[varName] = castedType;
224
+ }
225
+ // Include all implemented contracts
226
+ if (!output[filePath]) {
227
+ output[filePath] = {};
228
+ }
229
+ output[filePath][contract.name] = {
230
+ constructor: constructorParams,
231
+ state_variables: mergedVars,
232
+ };
233
+ }
234
+ }
235
+ const jsonOutput = JSON.stringify(output, null, 2);
236
+ if (options.json) {
237
+ console.log(jsonOutput);
238
+ }
239
+ if (options.outputPath || !options.json) {
240
+ const dictPath = options.outputPath || 'recon-dictionary.json';
241
+ await fs.writeFile(dictPath, jsonOutput);
242
+ log(`[recon-generate] Wrote dictionary file to ${dictPath}`);
243
+ }
244
+ return output;
245
+ };
246
+ exports.runDictionary = runDictionary;
@@ -17,6 +17,7 @@ export interface GeneratorOptions {
17
17
  suiteNameSnake: string;
18
18
  suiteNamePascal: string;
19
19
  coverage?: boolean;
20
+ params?: boolean;
20
21
  }
21
22
  export declare class ReconGenerator {
22
23
  private foundryRoot;
package/dist/generator.js CHANGED
@@ -47,6 +47,7 @@ const processor_1 = require("./processor");
47
47
  const types_1 = require("./types");
48
48
  const utils_1 = require("./utils");
49
49
  const solc_typed_ast_1 = require("solc-typed-ast");
50
+ const inputParams_1 = require("./inputParams");
50
51
  class ReconGenerator {
51
52
  constructor(foundryRoot, options) {
52
53
  this.foundryRoot = foundryRoot;
@@ -707,8 +708,23 @@ class ReconGenerator {
707
708
  }
708
709
  }
709
710
  }
711
+ const collected = (0, processor_1.collectFunctions)(filteredContracts, reconConfig);
712
+ if (this.options.params) {
713
+ if (!sourceUnits || sourceUnits.length === 0) {
714
+ console.warn('Input params map requested but no source units were available; skipping params file.');
715
+ }
716
+ else {
717
+ const inputParamsMap = (0, inputParams_1.buildInputParamsMap)(sourceUnits, collected.adminFunctions, collected.constructors, collected.otherFunctions);
718
+ const paramName = this.options.suiteNameSnake
719
+ ? `recon-${this.options.suiteNameSnake}-params.json`
720
+ : 'recon-params.json';
721
+ const paramPath = path.join(this.foundryRoot, paramName);
722
+ await fs.writeFile(paramPath, JSON.stringify(inputParamsMap, null, 2));
723
+ this.logDebug('Wrote parameter file', { paramPath });
724
+ }
725
+ }
710
726
  const tm = new templateManager_1.TemplateManager(this.foundryRoot, this.options.suiteDir, this.options.suiteNameSnake, this.options.suiteNamePascal, (_e = this.options.dynamicDeploy) !== null && _e !== void 0 ? _e : new Set());
711
- await tm.generateTemplates(filteredContracts, reconConfig);
727
+ await tm.generateTemplates(collected.enabledContracts, collected.adminFunctions, collected.nonSeparatedFunctions, collected.separatedByContract, collected.allContractNames);
712
728
  }
713
729
  async listAvailable() {
714
730
  var _a;
package/dist/index.js CHANGED
@@ -46,6 +46,7 @@ const utils_1 = require("./utils");
46
46
  const link_1 = require("./link");
47
47
  const link2_1 = require("./link2");
48
48
  const sourcemap_1 = require("./sourcemap");
49
+ const dictionary_1 = require("./dictionary");
49
50
  function parseFilter(input) {
50
51
  if (!input)
51
52
  return undefined;
@@ -124,6 +125,7 @@ async function main() {
124
125
  .option('--dynamic-deploy <names>', 'Comma-separated contract names to enable dynamic deploy lists')
125
126
  .option('--debug', 'Print debug info about filters and selection')
126
127
  .option('--coverage', 'Write coverage information to recon-coverage.json (or recon-<name>-coverage.json)')
128
+ .option('--params', 'Generate input parameters mapping to recon-params.json (or recon-<name>-params.json)')
127
129
  .option('--list', 'List available contracts/functions (after filters) and exit')
128
130
  .option('--force', 'Replace existing generated suite output (under --output). Does not rebuild .recon/out.')
129
131
  .option('--force-build', 'Delete .recon/out to force a fresh forge build before generating')
@@ -242,6 +244,27 @@ async function main() {
242
244
  verbose: !!opts.verbose,
243
245
  });
244
246
  });
247
+ program
248
+ .command('dictionary <buildInfo>')
249
+ .description('Extract storage variables with contract/interface types from build-info')
250
+ .option('-o, --output <path>', 'Custom output path for the dictionary JSON file')
251
+ .option('--json', 'Output JSON to terminal (also saves to file if -o is specified)')
252
+ .option('--src-prefix <prefix>', 'Source directory prefix to filter (default: "src/")', 'src/')
253
+ .action(async function (buildInfoPath) {
254
+ // @ts-ignore - Commander types are complex
255
+ const opts = this.opts();
256
+ const resolvedBuildInfo = path.isAbsolute(buildInfoPath)
257
+ ? buildInfoPath
258
+ : path.resolve(process.cwd(), buildInfoPath);
259
+ const outputPath = opts.output
260
+ ? (path.isAbsolute(opts.output) ? opts.output : path.resolve(process.cwd(), opts.output))
261
+ : undefined;
262
+ await (0, dictionary_1.runDictionary)(resolvedBuildInfo, {
263
+ outputPath,
264
+ json: !!opts.json,
265
+ srcPrefix: opts.srcPrefix,
266
+ });
267
+ });
245
268
  program
246
269
  .command('info <contractName>')
247
270
  .description('Generate contract info JSON (payable, constants, function relations, etc.)')
@@ -353,6 +376,7 @@ async function main() {
353
376
  suiteNameSnake: suiteSnake,
354
377
  suiteNamePascal: suitePascal,
355
378
  coverage: !!opts.coverage,
379
+ params: !!opts.params,
356
380
  });
357
381
  if (opts.list) {
358
382
  await generator.listAvailable();
@@ -0,0 +1,3 @@
1
+ import { FunctionDefinitionParams, InputParamsMap } from './types';
2
+ import { SourceUnit } from 'solc-typed-ast';
3
+ export declare function buildInputParamsMap(sourceUnits: SourceUnit[], adminFunctions: FunctionDefinitionParams[], constructors: FunctionDefinitionParams[], otherFunctions: FunctionDefinitionParams[]): InputParamsMap;
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildInputParamsMap = buildInputParamsMap;
4
+ const solc_typed_ast_1 = require("solc-typed-ast");
5
+ const processor_1 = require("./processor");
6
+ function buildInputParamsMap(sourceUnits, adminFunctions, constructors, otherFunctions) {
7
+ const formatter = new solc_typed_ast_1.PrettyFormatter(4, 0);
8
+ for (const source of sourceUnits) {
9
+ const pragmas = source.vPragmaDirectives;
10
+ const solidityPragma = pragmas.find(p => p.vIdentifier === 'solidity');
11
+ const compilerVersion = solidityPragma ? solidityPragma.vValue : solc_typed_ast_1.LatestCompilerVersion;
12
+ const writer = new solc_typed_ast_1.ASTWriter(solc_typed_ast_1.DefaultASTWriterMapping, formatter, compilerVersion);
13
+ const contracts = source.getChildrenByType(solc_typed_ast_1.ContractDefinition);
14
+ for (const contract of contracts) {
15
+ if (contract.kind === 'interface')
16
+ continue;
17
+ const allowedFunctions = adminFunctions.filter(fn => fn.contractName === contract.name);
18
+ constructors.filter(fn => fn.contractName === contract.name).forEach(fn => allowedFunctions.push(fn));
19
+ otherFunctions.filter(fn => fn.contractName === contract.name).forEach(fn => allowedFunctions.push(fn));
20
+ if (allowedFunctions.length === 0)
21
+ continue;
22
+ const processed = (0, processor_1.processContract)(contract);
23
+ const recs = processed['vFunctions'] || [];
24
+ if (recs.length === 0) {
25
+ continue;
26
+ }
27
+ for (const rec of recs) {
28
+ const sig = (0, processor_1.signatureFromFnDef)(rec.ast);
29
+ const match = allowedFunctions.find(fn => fn.sig === sig);
30
+ if (match) {
31
+ const code = writer.write(rec.ast);
32
+ match.ast = rec.ast;
33
+ match.code = code;
34
+ }
35
+ }
36
+ }
37
+ }
38
+ const data = {
39
+ version: '0.0.1',
40
+ contracts: {},
41
+ };
42
+ if (adminFunctions.length > 0) {
43
+ adminFunctions.forEach(fn => {
44
+ var _a, _b;
45
+ if (!fn.code)
46
+ return;
47
+ (_a = data.contracts)[_b = fn.contractName] || (_a[_b] = {
48
+ struct_params: {},
49
+ input_params: {},
50
+ functions_code: {},
51
+ });
52
+ const contractData = data.contracts[fn.contractName];
53
+ contractData.functions_code[fn.sig] = fn.code;
54
+ fn.abi.inputs.forEach(input => {
55
+ var _a;
56
+ const key = inputKey(input);
57
+ if (input.type === 'tuple')
58
+ recursiveStructHandler(input, contractData.struct_params);
59
+ (_a = contractData.input_params)[key] || (_a[key] = { dependent_function_signatures: [], labels: [] });
60
+ contractData.input_params[key].dependent_function_signatures.push(fn.sig);
61
+ });
62
+ });
63
+ }
64
+ if (constructors.length > 0) {
65
+ constructors.forEach(fn => {
66
+ var _a, _b;
67
+ if (!fn.code)
68
+ return;
69
+ (_a = data.contracts)[_b = fn.contractName] || (_a[_b] = {
70
+ struct_params: {},
71
+ input_params: {},
72
+ functions_code: {},
73
+ });
74
+ const contractData = data.contracts[fn.contractName];
75
+ contractData.functions_code[fn.sig] = fn.code;
76
+ fn.abi.inputs.forEach(input => {
77
+ var _a;
78
+ const key = inputKey(input);
79
+ if (input.type === 'tuple')
80
+ recursiveStructHandler(input, contractData.struct_params);
81
+ (_a = contractData.input_params)[key] || (_a[key] = { dependent_function_signatures: [], labels: [] });
82
+ contractData.input_params[key].dependent_function_signatures.push(fn.sig);
83
+ });
84
+ });
85
+ }
86
+ if (otherFunctions.length > 0) {
87
+ otherFunctions.forEach(fn => {
88
+ var _a, _b;
89
+ if (!fn.code)
90
+ return;
91
+ (_a = data.contracts)[_b = fn.contractName] || (_a[_b] = {
92
+ struct_params: {},
93
+ input_params: {},
94
+ functions_code: {},
95
+ });
96
+ const contractData = data.contracts[fn.contractName];
97
+ contractData.functions_code[fn.sig] = fn.code;
98
+ fn.abi.inputs.forEach(input => {
99
+ var _a;
100
+ const key = inputKey(input);
101
+ if (input.type === 'tuple')
102
+ recursiveStructHandler(input, contractData.struct_params);
103
+ (_a = contractData.input_params)[key] || (_a[key] = { dependent_function_signatures: [], labels: [] });
104
+ contractData.input_params[key].dependent_function_signatures.push(fn.sig);
105
+ });
106
+ });
107
+ }
108
+ return data;
109
+ }
110
+ function inputKey(param) {
111
+ if (param.type === 'tuple') {
112
+ return `${param.internalType.replace(/^struct\s+/, '')} ${param.name}`;
113
+ }
114
+ return `${param.type} ${param.name}`;
115
+ }
116
+ function recursiveStructHandler(struct, struct_params) {
117
+ const key = inputKey(struct);
118
+ const childVars = struct.components.map(c => inputKey(c));
119
+ struct_params[key] = childVars;
120
+ for (const comp of struct.components) {
121
+ if (comp.type === 'tuple') {
122
+ recursiveStructHandler(comp, struct_params);
123
+ }
124
+ }
125
+ }
@@ -1,3 +1,14 @@
1
1
  import * as $ from 'solc-typed-ast';
2
- export declare const processContract: (contract: $.ContractDefinition) => Record<string, any>;
2
+ import { ContractMetadata, FunctionDefinitionParams, ReconConfigContract, RecordItem } from './types';
3
+ export declare const signatureFromFnDef: (fnDef: $.FunctionDefinition) => string;
4
+ export declare const processContract: (contract: $.ContractDefinition) => Record<string, RecordItem[]>;
3
5
  export declare function buildCoverageMap(asts: $.SourceUnit[], foundryRoot: string, contractFunctions: Map<string, Set<string>>): Promise<Record<string, string[]>>;
6
+ export declare function collectFunctions(contracts: ContractMetadata[], reconConfig: Record<string, ReconConfigContract>): {
7
+ enabledContracts: ContractMetadata[];
8
+ adminFunctions: FunctionDefinitionParams[];
9
+ constructors: FunctionDefinitionParams[];
10
+ otherFunctions: FunctionDefinitionParams[];
11
+ nonSeparatedFunctions: FunctionDefinitionParams[];
12
+ separatedByContract: Record<string, FunctionDefinitionParams[]>;
13
+ allContractNames: string[];
14
+ };
package/dist/processor.js CHANGED
@@ -33,8 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.processContract = void 0;
36
+ exports.processContract = exports.signatureFromFnDef = void 0;
37
37
  exports.buildCoverageMap = buildCoverageMap;
38
+ exports.collectFunctions = collectFunctions;
38
39
  const fs = __importStar(require("fs/promises"));
39
40
  const path = __importStar(require("path"));
40
41
  const $ = __importStar(require("solc-typed-ast"));
@@ -90,6 +91,7 @@ const signatureFromFnDef = (fnDef) => {
90
91
  : '');
91
92
  return `${name}(${params})`;
92
93
  };
94
+ exports.signatureFromFnDef = signatureFromFnDef;
93
95
  const matchesSignature = (sig, allowed) => {
94
96
  if (allowed.has(sig))
95
97
  return true;
@@ -149,7 +151,7 @@ const processContract = (contract) => {
149
151
  fnDef.stateMutability === $.FunctionStateMutability.View)) {
150
152
  continue;
151
153
  }
152
- if (!result['vFunctions'].some((x) => (0, utils_1.signatureEquals)(x, fnDef))) {
154
+ if (!result['vFunctions'].some((x) => (0, utils_1.signatureEquals)(x.ast, fnDef))) {
153
155
  const rec = {
154
156
  ast: fnDef,
155
157
  children: processFunction(fnDef),
@@ -347,12 +349,12 @@ async function buildCoverageMap(asts, foundryRoot, contractFunctions) {
347
349
  continue;
348
350
  }
349
351
  const processed = (0, exports.processContract)(contract);
350
- const recs = (processed['vFunctions'] || []);
352
+ const recs = processed['vFunctions'] || [];
351
353
  if (recs.length === 0) {
352
354
  continue;
353
355
  }
354
356
  for (const rec of recs) {
355
- const sig = signatureFromFnDef(rec.ast);
357
+ const sig = (0, exports.signatureFromFnDef)(rec.ast);
356
358
  if (!matchesSignature(sig, allowedFns)) {
357
359
  continue;
358
360
  }
@@ -416,3 +418,106 @@ async function buildCoverageMap(asts, foundryRoot, contractFunctions) {
416
418
  }
417
419
  return normalized;
418
420
  }
421
+ function computeSignature(item) {
422
+ var _a;
423
+ const inputs = item.inputs.map(i => i.internalType && i.internalType.startsWith('struct ')
424
+ ? i.internalType.replace(/^struct\s+/, '')
425
+ : i.type);
426
+ return `${(_a = item.name) !== null && _a !== void 0 ? _a : 'constructor'}(${inputs.join(',')})`;
427
+ }
428
+ function defaultFunctionsFromAbi(contract) {
429
+ var _a;
430
+ const functions = [];
431
+ for (const item of contract.abi) {
432
+ if (['constructor', 'function'].includes(item.type) && !((_a = item.stateMutability) === null || _a === void 0 ? void 0 : _a.match(/^(view|pure)$/))) {
433
+ const sig = computeSignature(item);
434
+ functions.push({ signature: sig, actor: types_1.Actor.ACTOR, mode: types_1.Mode.NORMAL });
435
+ }
436
+ }
437
+ return functions;
438
+ }
439
+ function collectFunctions(contracts, reconConfig) {
440
+ const enabledContracts = [];
441
+ const adminFunctions = [];
442
+ const constructors = [];
443
+ const otherFunctions = [];
444
+ const nonSeparatedFunctions = [];
445
+ const separatedByContract = {};
446
+ for (const contract of contracts) {
447
+ const cfg = reconConfig[contract.jsonPath] || { enabled: true, functions: [], separated: true };
448
+ if (cfg.enabled === false) {
449
+ continue;
450
+ }
451
+ enabledContracts.push(contract);
452
+ const separated = cfg.separated !== false;
453
+ const functionsConfig = Array.isArray(cfg.functions) ? cfg.functions : [];
454
+ const functionList = functionsConfig.length > 0 ? functionsConfig : defaultFunctionsFromAbi(contract);
455
+ for (const fnCfg of functionList) {
456
+ const fnName = fnCfg.signature.split('(')[0];
457
+ const fnAbi = contract.abi.find((item) => {
458
+ if (item.type !== 'function' || item.name !== fnName) {
459
+ return false;
460
+ }
461
+ const sig = computeSignature(item);
462
+ return sig === fnCfg.signature;
463
+ });
464
+ if (!fnAbi) {
465
+ continue;
466
+ }
467
+ const fnInfo = {
468
+ contractName: contract.name,
469
+ contractPath: contract.path,
470
+ functionName: fnAbi.name,
471
+ abi: fnAbi,
472
+ actor: fnCfg.actor || types_1.Actor.ACTOR,
473
+ mode: fnCfg.mode || types_1.Mode.NORMAL,
474
+ separated,
475
+ sig: fnCfg.signature,
476
+ };
477
+ if (fnCfg.actor === types_1.Actor.ADMIN) {
478
+ adminFunctions.push(fnInfo);
479
+ }
480
+ else {
481
+ otherFunctions.push(fnInfo);
482
+ if (separated) {
483
+ if (!separatedByContract[contract.name]) {
484
+ separatedByContract[contract.name] = [];
485
+ }
486
+ separatedByContract[contract.name].push(fnInfo);
487
+ }
488
+ else {
489
+ nonSeparatedFunctions.push(fnInfo);
490
+ }
491
+ }
492
+ }
493
+ const constructorAbi = contract.abi.find(item => item.type === 'constructor');
494
+ if (constructorAbi) {
495
+ const constructorInfo = {
496
+ contractName: contract.name,
497
+ contractPath: contract.path,
498
+ functionName: 'constructor',
499
+ abi: constructorAbi,
500
+ actor: types_1.Actor.ACTOR,
501
+ mode: types_1.Mode.NORMAL,
502
+ separated: false,
503
+ sig: computeSignature(constructorAbi),
504
+ };
505
+ constructors.push(constructorInfo);
506
+ }
507
+ }
508
+ const allContractNames = [
509
+ 'Admin',
510
+ 'Doomsday',
511
+ 'Managers',
512
+ ...Object.keys(separatedByContract)
513
+ ].sort();
514
+ return {
515
+ enabledContracts,
516
+ adminFunctions,
517
+ constructors,
518
+ otherFunctions,
519
+ nonSeparatedFunctions,
520
+ separatedByContract,
521
+ allContractNames,
522
+ };
523
+ }
@@ -1,4 +1,4 @@
1
- import { ContractMetadata } from './types';
1
+ import { FunctionDefinitionParams, ContractMetadata } from './types';
2
2
  export declare class TemplateManager {
3
3
  private foundryRoot;
4
4
  private suiteDir;
@@ -9,7 +9,5 @@ export declare class TemplateManager {
9
9
  private skipList;
10
10
  private shouldGenerateFile;
11
11
  private updateTargetFile;
12
- private collectFunctions;
13
- private defaultFunctionsFromAbi;
14
- generateTemplates(contracts: ContractMetadata[], reconConfig: Record<string, any>): Promise<void>;
12
+ generateTemplates(enabledContracts: ContractMetadata[], adminFunctions: FunctionDefinitionParams[], nonSeparatedFunctions: FunctionDefinitionParams[], separatedByContract: Record<string, FunctionDefinitionParams[]>, allContractNames: string[]): Promise<void>;
15
13
  }
@@ -38,7 +38,6 @@ const path = __importStar(require("path"));
38
38
  const fs = __importStar(require("fs/promises"));
39
39
  const case_1 = require("case");
40
40
  const templates = __importStar(require("./templates"));
41
- const types_1 = require("./types");
42
41
  class TemplateManager {
43
42
  constructor(foundryRoot, suiteDir, suiteNameSnake, suiteNamePascal, dynamicDeploy = new Set()) {
44
43
  this.foundryRoot = foundryRoot;
@@ -97,91 +96,7 @@ class TemplateManager {
97
96
  return newContent;
98
97
  }
99
98
  }
100
- collectFunctions(contracts, reconConfig) {
101
- const enabledContracts = [];
102
- const adminFunctions = [];
103
- const otherFunctions = [];
104
- const nonSeparatedFunctions = [];
105
- const separatedByContract = {};
106
- for (const contract of contracts) {
107
- const cfg = reconConfig[contract.jsonPath] || { enabled: true, functions: [], separated: true };
108
- if (cfg.enabled === false) {
109
- continue;
110
- }
111
- enabledContracts.push(contract);
112
- const separated = cfg.separated !== false;
113
- const functionsConfig = Array.isArray(cfg.functions) ? cfg.functions : [];
114
- const functionList = functionsConfig.length > 0 ? functionsConfig : this.defaultFunctionsFromAbi(contract);
115
- for (const fnCfg of functionList) {
116
- const fnName = fnCfg.signature.split('(')[0];
117
- const fnAbi = contract.abi.find((item) => {
118
- if (item.type !== 'function' || item.name !== fnName) {
119
- return false;
120
- }
121
- const sig = `${item.name}(${item.inputs.map((i) => i.internalType && i.internalType.startsWith('struct ')
122
- ? i.internalType.replace(/^struct\s+/, '')
123
- : i.type).join(',')})`;
124
- return sig === fnCfg.signature;
125
- });
126
- if (!fnAbi) {
127
- continue;
128
- }
129
- const fnInfo = {
130
- contractName: contract.name,
131
- contractPath: contract.path,
132
- functionName: fnAbi.name,
133
- abi: fnAbi,
134
- actor: fnCfg.actor || types_1.Actor.ACTOR,
135
- mode: fnCfg.mode || types_1.Mode.NORMAL,
136
- separated,
137
- };
138
- if (fnCfg.actor === types_1.Actor.ADMIN) {
139
- adminFunctions.push(fnInfo);
140
- }
141
- else {
142
- otherFunctions.push(fnInfo);
143
- if (separated) {
144
- if (!separatedByContract[contract.name]) {
145
- separatedByContract[contract.name] = [];
146
- }
147
- separatedByContract[contract.name].push(fnInfo);
148
- }
149
- else {
150
- nonSeparatedFunctions.push(fnInfo);
151
- }
152
- }
153
- }
154
- }
155
- const allContractNames = [
156
- 'Admin',
157
- 'Doomsday',
158
- 'Managers',
159
- ...Object.keys(separatedByContract)
160
- ].sort();
161
- return {
162
- enabledContracts,
163
- adminFunctions,
164
- otherFunctions,
165
- nonSeparatedFunctions,
166
- separatedByContract,
167
- allContractNames,
168
- };
169
- }
170
- defaultFunctionsFromAbi(contract) {
171
- var _a;
172
- const functions = [];
173
- for (const item of contract.abi) {
174
- if (item.type === 'function' && !((_a = item.stateMutability) === null || _a === void 0 ? void 0 : _a.match(/^(view|pure)$/))) {
175
- const sig = `${item.name}(${item.inputs.map((i) => i.internalType && i.internalType.startsWith('struct ')
176
- ? i.internalType.replace(/^struct\s+/, '')
177
- : i.type).join(',')})`;
178
- functions.push({ signature: sig, actor: types_1.Actor.ACTOR, mode: types_1.Mode.NORMAL });
179
- }
180
- }
181
- return functions;
182
- }
183
- async generateTemplates(contracts, reconConfig) {
184
- const { enabledContracts, adminFunctions, nonSeparatedFunctions, separatedByContract, allContractNames, } = this.collectFunctions(contracts, reconConfig);
99
+ async generateTemplates(enabledContracts, adminFunctions, nonSeparatedFunctions, separatedByContract, allContractNames) {
185
100
  const contractsForSetup = enabledContracts.map(contract => ({ ...contract, isDynamic: this.dynamicDeploy.has(contract.name) }));
186
101
  const dynamicContracts = enabledContracts
187
102
  .filter(contract => this.dynamicDeploy.has(contract.name))
@@ -17,4 +17,5 @@ filterFunctions: []
17
17
  cryticArgs: ["--foundry-compile-all"]
18
18
  deployer: "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38"
19
19
  contractAddr: "0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496"
20
- shrinkLimit: 100000`, { noEscape: true });
20
+ shrinkLimit: 100000
21
+ symExec: true`, { noEscape: true });
package/dist/types.d.ts CHANGED
@@ -43,6 +43,9 @@ export interface FunctionDefinitionParams {
43
43
  actor: Actor;
44
44
  mode: Mode;
45
45
  separated?: boolean;
46
+ sig: string;
47
+ ast?: ASTNode;
48
+ code?: string;
46
49
  }
47
50
  export declare enum CallType {
48
51
  Internal = "internal",
@@ -64,6 +67,32 @@ export type CallTree = {
64
67
  isRecursive?: boolean;
65
68
  recursiveTargetNodeId?: number;
66
69
  };
70
+ export interface ReconConfigFunction {
71
+ signature: string;
72
+ actor: Actor;
73
+ mode: Mode;
74
+ }
75
+ export interface ReconConfigContract {
76
+ enabled: boolean;
77
+ functions: ReconConfigFunction[];
78
+ separated: boolean;
79
+ }
80
+ export interface InputParam {
81
+ dependent_function_signatures: string[];
82
+ labels: {
83
+ label: string;
84
+ reason: string;
85
+ }[];
86
+ }
87
+ export interface InputVariablesConfig {
88
+ struct_params: Record<string, string[]>;
89
+ input_params: Record<string, InputParam>;
90
+ functions_code: Record<string, string>;
91
+ }
92
+ export interface InputParamsMap {
93
+ version: string;
94
+ contracts: Record<string, InputVariablesConfig>;
95
+ }
67
96
  export type CallTreeData = {
68
97
  contract: string;
69
98
  function: FunctionDefinition;
package/dist/types.js CHANGED
@@ -18,3 +18,5 @@ var CallType;
18
18
  CallType["HighLevel"] = "high-level";
19
19
  CallType["LowLevel"] = "low-level";
20
20
  })(CallType || (exports.CallType = CallType = {}));
21
+ ;
22
+ ;
@@ -0,0 +1,3 @@
1
+ export interface UnpackOptions {
2
+ }
3
+ export declare function unpackBuildInfo(inputPath: string, outputDir: string, _options?: UnpackOptions): Promise<void>;
package/dist/unpack.js ADDED
@@ -0,0 +1,276 @@
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.unpackBuildInfo = unpackBuildInfo;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ /**
40
+ * Load a single build-info JSON file
41
+ */
42
+ async function loadBuildInfo(filePath) {
43
+ const content = await fs.promises.readFile(filePath, 'utf8');
44
+ return JSON.parse(content);
45
+ }
46
+ /**
47
+ * Load all build-info files from a path (file or directory)
48
+ */
49
+ async function loadAllBuildInfos(inputPath) {
50
+ const stat = await fs.promises.stat(inputPath);
51
+ if (stat.isFile()) {
52
+ const buildInfo = await loadBuildInfo(inputPath);
53
+ return [buildInfo];
54
+ }
55
+ else if (stat.isDirectory()) {
56
+ const entries = await fs.promises.readdir(inputPath);
57
+ const jsonFiles = entries.filter(f => f.endsWith('.json'));
58
+ if (jsonFiles.length === 0) {
59
+ throw new Error(`No JSON files found in directory: ${inputPath}`);
60
+ }
61
+ console.log(`Found ${jsonFiles.length} build-info files in directory`);
62
+ const buildInfos = [];
63
+ for (const file of jsonFiles) {
64
+ const filePath = path.join(inputPath, file);
65
+ console.log(` Loading: ${file}`);
66
+ const buildInfo = await loadBuildInfo(filePath);
67
+ buildInfos.push(buildInfo);
68
+ }
69
+ return buildInfos;
70
+ }
71
+ else {
72
+ throw new Error(`Invalid input path: ${inputPath}`);
73
+ }
74
+ }
75
+ /**
76
+ * Merge multiple build-info files into a single structure
77
+ */
78
+ function mergeBuildInfos(buildInfos) {
79
+ var _a, _b, _c;
80
+ const merged = {
81
+ sources: {},
82
+ remappings: new Set(),
83
+ includePaths: new Set(),
84
+ basePath: undefined,
85
+ solcVersions: new Set(),
86
+ optimizer: undefined,
87
+ viaIR: false,
88
+ };
89
+ for (const buildInfo of buildInfos) {
90
+ merged.solcVersions.add(buildInfo.solcVersion);
91
+ for (const [sourcePath, sourceData] of Object.entries(buildInfo.input.sources)) {
92
+ if (!merged.sources[sourcePath]) {
93
+ merged.sources[sourcePath] = sourceData;
94
+ }
95
+ }
96
+ if ((_a = buildInfo.input.settings) === null || _a === void 0 ? void 0 : _a.remappings) {
97
+ for (const remap of buildInfo.input.settings.remappings) {
98
+ merged.remappings.add(remap);
99
+ }
100
+ }
101
+ if (buildInfo.input.includePaths) {
102
+ for (const includePath of buildInfo.input.includePaths) {
103
+ merged.includePaths.add(includePath);
104
+ }
105
+ }
106
+ if (!merged.basePath && buildInfo.input.basePath) {
107
+ merged.basePath = buildInfo.input.basePath;
108
+ }
109
+ if (!merged.optimizer && ((_b = buildInfo.input.settings) === null || _b === void 0 ? void 0 : _b.optimizer)) {
110
+ merged.optimizer = buildInfo.input.settings.optimizer;
111
+ }
112
+ if ((_c = buildInfo.input.settings) === null || _c === void 0 ? void 0 : _c.viaIR) {
113
+ merged.viaIR = true;
114
+ }
115
+ }
116
+ return merged;
117
+ }
118
+ /**
119
+ * Normalize a source path: contracts/ -> src/
120
+ */
121
+ function normalizePath(sourcePath) {
122
+ if (sourcePath.startsWith('contracts/')) {
123
+ return 'src/' + sourcePath.slice('contracts/'.length);
124
+ }
125
+ return sourcePath;
126
+ }
127
+ /**
128
+ * Rewrite imports in source content:
129
+ * - contracts/ -> src/
130
+ * - Handle includePath-based src/ imports
131
+ */
132
+ function rewriteImports(sourcePath, content, includePaths, basePath) {
133
+ let result = content;
134
+ // Rewrite contracts/ imports to src/
135
+ result = result.replace(/(import\s+(?:{[^}]+}\s+from\s+)?["'])contracts\//g, '$1src/');
136
+ // Handle includePath-based src/ imports (for libraries with self-referential imports)
137
+ if (includePaths.size > 0) {
138
+ let relativeIncludePath;
139
+ for (const includePath of includePaths) {
140
+ let relative;
141
+ if (basePath && includePath.startsWith(basePath)) {
142
+ relative = includePath.slice(basePath.length).replace(/^\//, '');
143
+ }
144
+ else {
145
+ continue;
146
+ }
147
+ // Normalize the path for comparison
148
+ const normalizedSourcePath = normalizePath(sourcePath);
149
+ const normalizedRelative = normalizePath(relative);
150
+ if (normalizedRelative && normalizedSourcePath.startsWith(`${normalizedRelative}/`)) {
151
+ relativeIncludePath = normalizedRelative;
152
+ break;
153
+ }
154
+ }
155
+ if (relativeIncludePath) {
156
+ const importRegex = /import\s+(?:{[^}]+}\s+from\s+)?["']src\/([^"']+)["']/g;
157
+ result = result.replace(importRegex, (match, importPath) => {
158
+ const currentDir = path.dirname(normalizePath(sourcePath));
159
+ const targetPath = `${relativeIncludePath}/src/${importPath}`;
160
+ let relativeTo = path.relative(currentDir, path.dirname(targetPath));
161
+ if (!relativeTo) {
162
+ relativeTo = '.';
163
+ }
164
+ else if (!relativeTo.startsWith('.')) {
165
+ relativeTo = './' + relativeTo;
166
+ }
167
+ let newImport = path.posix.join(relativeTo, path.basename(targetPath));
168
+ if (!newImport.startsWith('.')) {
169
+ newImport = './' + newImport;
170
+ }
171
+ return match.replace(`"src/${importPath}"`, `"${newImport}"`).replace(`'src/${importPath}'`, `'${newImport}'`);
172
+ });
173
+ }
174
+ }
175
+ return result;
176
+ }
177
+ /**
178
+ * Detect remappings needed based on source paths (for Hardhat-style @scope/ imports)
179
+ */
180
+ function detectRemappings(sources) {
181
+ const remappings = new Set();
182
+ const scopedDirs = new Set();
183
+ // Find all @scope/ style directories
184
+ for (const sourcePath of Object.keys(sources)) {
185
+ if (sourcePath.startsWith('@')) {
186
+ const parts = sourcePath.split('/');
187
+ if (parts.length >= 2) {
188
+ const scopeDir = parts[0] + '/';
189
+ scopedDirs.add(scopeDir);
190
+ }
191
+ }
192
+ }
193
+ // Create remappings for each scoped directory
194
+ for (const scopeDir of scopedDirs) {
195
+ remappings.add(`${scopeDir}=${scopeDir}`);
196
+ }
197
+ return remappings;
198
+ }
199
+ async function unpackBuildInfo(inputPath, outputDir, _options = {}) {
200
+ var _a;
201
+ console.log(`Reading build-info from: ${inputPath}`);
202
+ const buildInfos = await loadAllBuildInfos(inputPath);
203
+ const merged = mergeBuildInfos(buildInfos);
204
+ const solcVersionsArray = Array.from(merged.solcVersions);
205
+ console.log(`Solc Version(s): ${solcVersionsArray.join(', ')}`);
206
+ const sourceCount = Object.keys(merged.sources).length;
207
+ console.log(`Found ${sourceCount} unique source files`);
208
+ if (merged.includePaths.size > 0) {
209
+ console.log(`Found ${merged.includePaths.size} include paths`);
210
+ }
211
+ await fs.promises.mkdir(outputDir, { recursive: true });
212
+ // Write each source file
213
+ let written = 0;
214
+ for (const [sourcePath, sourceData] of Object.entries(merged.sources)) {
215
+ // Normalize path: contracts/ -> src/
216
+ const normalizedPath = normalizePath(sourcePath);
217
+ const fullPath = path.join(outputDir, normalizedPath);
218
+ const dir = path.dirname(fullPath);
219
+ await fs.promises.mkdir(dir, { recursive: true });
220
+ // Rewrite imports
221
+ const content = rewriteImports(sourcePath, sourceData.content, merged.includePaths, merged.basePath);
222
+ await fs.promises.writeFile(fullPath, content);
223
+ written++;
224
+ if (written % 50 === 0) {
225
+ console.log(` Written ${written}/${sourceCount} files...`);
226
+ }
227
+ }
228
+ console.log(`Written ${written} source files to ${outputDir}`);
229
+ // Detect if this is a Hardhat-style project (has @scope/ directories)
230
+ const hasNodeModulesStyle = Array.from(Object.keys(merged.sources)).some(p => p.startsWith('@'));
231
+ const libsDir = hasNodeModulesStyle ? '.' : 'lib';
232
+ // Generate foundry.toml - no solc version specified, let auto_detect handle it
233
+ let foundryToml = `[profile.default]\nsrc = "src"\nout = "out"\nlibs = ["${libsDir}"]\nauto_detect_solc = true\n`;
234
+ if ((_a = merged.optimizer) === null || _a === void 0 ? void 0 : _a.enabled) {
235
+ foundryToml += `optimizer = true\noptimizer_runs = ${merged.optimizer.runs}\n`;
236
+ }
237
+ if (merged.viaIR) {
238
+ foundryToml += `via_ir = true\n`;
239
+ }
240
+ // Collect remappings: from build-info + auto-detected @scope/ dirs
241
+ const allRemappings = new Set();
242
+ for (const remap of merged.remappings) {
243
+ // Also normalize contracts/ -> src/ in remappings
244
+ allRemappings.add(remap.replace(/contracts\//g, 'src/'));
245
+ }
246
+ const detectedRemappings = detectRemappings(merged.sources);
247
+ for (const remap of detectedRemappings) {
248
+ allRemappings.add(remap);
249
+ }
250
+ if (allRemappings.size > 0) {
251
+ foundryToml += `\nremappings = [\n`;
252
+ for (const remap of allRemappings) {
253
+ foundryToml += ` "${remap}",\n`;
254
+ }
255
+ foundryToml += `]\n`;
256
+ }
257
+ const foundryTomlPath = path.join(outputDir, 'foundry.toml');
258
+ await fs.promises.writeFile(foundryTomlPath, foundryToml);
259
+ console.log(`Generated ${foundryTomlPath}`);
260
+ console.log('\nDone! To build the project:');
261
+ console.log(` cd ${outputDir}`);
262
+ console.log(' forge build');
263
+ }
264
+ // CLI entry point
265
+ if (require.main === module) {
266
+ const args = process.argv.slice(2);
267
+ if (args.length < 2) {
268
+ console.error('Usage: npx ts-node src/unpack.ts <build-info.json|build-info-dir> <output-dir>');
269
+ process.exit(1);
270
+ }
271
+ const [inputPath, outputDir] = args;
272
+ unpackBuildInfo(inputPath, outputDir).catch(err => {
273
+ console.error('Failed:', err.message);
274
+ process.exit(1);
275
+ });
276
+ }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "recon-generate",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "description": "CLI to scaffold Recon fuzzing suite inside Foundry projects",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "recon-generate": "dist/index.js"
8
8
  },
9
9
  "scripts": {
10
+ "dev": "tsc -p . --watch",
10
11
  "build": "tsc -p .",
11
12
  "postbuild": "node scripts/copy-builtins.js",
12
13
  "prepare": "npm run build"
@@ -25,7 +26,7 @@
25
26
  "commander": "^14.0.2",
26
27
  "ethers": "^6.16.0",
27
28
  "handlebars": "^4.7.8",
28
- "solc-typed-ast": "^19.1.0",
29
+ "solc-typed-ast": "^8.0.1",
29
30
  "src-location": "^1.1.0",
30
31
  "yaml": "^2.8.2",
31
32
  "z3-solver": "^4.15.4"