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.
- package/dist/dictionary.d.ts +25 -0
- package/dist/dictionary.js +246 -0
- package/dist/generator.d.ts +1 -0
- package/dist/generator.js +17 -1
- package/dist/index.js +24 -0
- package/dist/inputParams.d.ts +3 -0
- package/dist/inputParams.js +125 -0
- package/dist/processor.d.ts +12 -1
- package/dist/processor.js +109 -4
- package/dist/templateManager.d.ts +2 -4
- package/dist/templateManager.js +1 -86
- package/dist/templates/echidna-config.js +2 -1
- package/dist/types.d.ts +29 -0
- package/dist/types.js +2 -0
- package/dist/unpack.d.ts +3 -0
- package/dist/unpack.js +276 -0
- package/package.json +3 -2
|
@@ -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;
|
package/dist/generator.d.ts
CHANGED
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(
|
|
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
|
+
}
|
package/dist/processor.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
1
|
import * as $ from 'solc-typed-ast';
|
|
2
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
}
|
package/dist/templateManager.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
package/dist/unpack.d.ts
ADDED
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.
|
|
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": "^
|
|
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"
|