recon-generate 0.0.15 → 0.0.17

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,11 @@
1
+ import { FunctionDefinition } from 'solc-typed-ast';
2
+ interface Z3Solution {
3
+ variable: string;
4
+ value: string;
5
+ type: string;
6
+ expression: string;
7
+ }
8
+ export declare const setZ3Silent: (silent: boolean) => void;
9
+ export declare const findZ3Solutions: (fnDef: FunctionDefinition) => Promise<Z3Solution[]>;
10
+ export declare const cleanupZ3: () => Promise<void>;
11
+ export {};
@@ -0,0 +1,425 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cleanupZ3 = exports.findZ3Solutions = exports.setZ3Silent = void 0;
4
+ const z3_solver_1 = require("z3-solver");
5
+ const solc_typed_ast_1 = require("solc-typed-ast");
6
+ let z3Context = null;
7
+ const initZ3 = async () => {
8
+ if (z3Context)
9
+ return z3Context;
10
+ const { Context } = await (0, z3_solver_1.init)();
11
+ const ctx = Context('main');
12
+ const solver = new ctx.Solver();
13
+ z3Context = { ctx, solver };
14
+ return z3Context;
15
+ };
16
+ const getBitSize = (typeString) => {
17
+ // Extract bit size from type like int128, uint256, etc.
18
+ const match = typeString.match(/u?int(\d+)/);
19
+ if (match)
20
+ return parseInt(match[1], 10);
21
+ // Handle bytes types (bytes1 = 8 bits, bytes32 = 256 bits)
22
+ const bytesMatch = typeString.match(/^bytes(\d+)$/);
23
+ if (bytesMatch)
24
+ return parseInt(bytesMatch[1], 10) * 8;
25
+ // Default sizes
26
+ if (typeString.includes('int'))
27
+ return 256;
28
+ if (typeString === 'bool')
29
+ return 8;
30
+ if (typeString.startsWith('bytes'))
31
+ return 256; // dynamic bytes
32
+ return 256;
33
+ };
34
+ const isSigned = (typeString) => {
35
+ return typeString.startsWith('int') && !typeString.startsWith('uint');
36
+ };
37
+ const extractVariables = (expr) => {
38
+ const vars = [];
39
+ const visit = (node) => {
40
+ if (node instanceof solc_typed_ast_1.Identifier) {
41
+ // Skip constants and special identifiers
42
+ if (node.name !== 'true' && node.name !== 'false') {
43
+ vars.push({
44
+ name: node.name,
45
+ type: node.typeString || 'uint256',
46
+ node
47
+ });
48
+ }
49
+ }
50
+ else if (node instanceof solc_typed_ast_1.IndexAccess) {
51
+ // foo[0] - treat as a variable
52
+ const baseName = getExpressionName(node);
53
+ vars.push({
54
+ name: baseName,
55
+ type: node.typeString || 'uint256',
56
+ node
57
+ });
58
+ }
59
+ else if (node instanceof solc_typed_ast_1.MemberAccess) {
60
+ // foo.bar - could be a variable access
61
+ // Check if it's not a function call parent
62
+ const baseName = getExpressionName(node);
63
+ vars.push({
64
+ name: baseName,
65
+ type: node.typeString || 'uint256',
66
+ node
67
+ });
68
+ }
69
+ else if (node instanceof solc_typed_ast_1.FunctionCall) {
70
+ // Handle type conversions - recurse into the argument
71
+ if (node.kind === solc_typed_ast_1.FunctionCallKind.TypeConversion && node.vArguments.length === 1) {
72
+ visit(node.vArguments[0]);
73
+ }
74
+ else {
75
+ // foo.bar() - treat whole thing as a variable
76
+ const baseName = getExpressionName(node);
77
+ vars.push({
78
+ name: baseName,
79
+ type: node.typeString || 'uint256',
80
+ node
81
+ });
82
+ }
83
+ }
84
+ else if (node instanceof solc_typed_ast_1.BinaryOperation) {
85
+ visit(node.vLeftExpression);
86
+ visit(node.vRightExpression);
87
+ }
88
+ else if (node instanceof solc_typed_ast_1.UnaryOperation) {
89
+ visit(node.vSubExpression);
90
+ }
91
+ else if (node instanceof solc_typed_ast_1.TupleExpression) {
92
+ // Handle parentheses - visit all components
93
+ for (const comp of node.vComponents) {
94
+ if (comp)
95
+ visit(comp);
96
+ }
97
+ }
98
+ };
99
+ visit(expr);
100
+ return vars;
101
+ };
102
+ const getExpressionName = (expr) => {
103
+ if (expr instanceof solc_typed_ast_1.Identifier) {
104
+ return expr.name;
105
+ }
106
+ else if (expr instanceof solc_typed_ast_1.IndexAccess) {
107
+ const base = getExpressionName(expr.vBaseExpression);
108
+ const index = expr.vIndexExpression ? getExpressionName(expr.vIndexExpression) : '0';
109
+ return `${base}_${index}`.replace(/[^a-zA-Z0-9_]/g, '');
110
+ }
111
+ else if (expr instanceof solc_typed_ast_1.MemberAccess) {
112
+ const base = getExpressionName(expr.vExpression);
113
+ return `${base}_${expr.memberName}`.replace(/[^a-zA-Z0-9_]/g, '');
114
+ }
115
+ else if (expr instanceof solc_typed_ast_1.FunctionCall) {
116
+ if (expr.vExpression instanceof solc_typed_ast_1.MemberAccess) {
117
+ return getExpressionName(expr.vExpression).replace(/[^a-zA-Z0-9_]/g, '');
118
+ }
119
+ return getExpressionName(expr.vExpression).replace(/[^a-zA-Z0-9_]/g, '');
120
+ }
121
+ else if (expr instanceof solc_typed_ast_1.Literal) {
122
+ return expr.value || '0';
123
+ }
124
+ return 'unknown';
125
+ };
126
+ const expressionToZ3 = (ctx, expr, varMap, bitSize, signed) => {
127
+ if (expr instanceof solc_typed_ast_1.Literal) {
128
+ let value = expr.value || '0';
129
+ // Handle hex values
130
+ if (value.startsWith('0x')) {
131
+ value = BigInt(value).toString();
132
+ }
133
+ // Handle negative values for signed types
134
+ if (value.startsWith('-')) {
135
+ const absVal = BigInt(value.slice(1));
136
+ // Two's complement
137
+ const maxVal = BigInt(1) << BigInt(bitSize);
138
+ const signedVal = maxVal - absVal;
139
+ return ctx.BitVec.val(signedVal, bitSize);
140
+ }
141
+ return ctx.BitVec.val(BigInt(value), bitSize);
142
+ }
143
+ if (expr instanceof solc_typed_ast_1.Identifier) {
144
+ const z3Var = varMap.get(expr.name);
145
+ if (z3Var)
146
+ return z3Var;
147
+ // Create new variable
148
+ const newVar = ctx.BitVec.const(expr.name, bitSize);
149
+ varMap.set(expr.name, newVar);
150
+ return newVar;
151
+ }
152
+ if (expr instanceof solc_typed_ast_1.FunctionCall) {
153
+ // Handle type conversions like uint24(...), uint256(...)
154
+ if (expr.kind === solc_typed_ast_1.FunctionCallKind.TypeConversion && expr.vArguments.length === 1) {
155
+ // Recursively process the inner expression
156
+ // The type conversion just changes interpretation, not the bits we're solving for
157
+ return expressionToZ3(ctx, expr.vArguments[0], varMap, bitSize, signed);
158
+ }
159
+ // Other function calls - treat as opaque variable
160
+ const name = getExpressionName(expr);
161
+ const z3Var = varMap.get(name);
162
+ if (z3Var)
163
+ return z3Var;
164
+ const newVar = ctx.BitVec.const(name, bitSize);
165
+ varMap.set(name, newVar);
166
+ return newVar;
167
+ }
168
+ if (expr instanceof solc_typed_ast_1.IndexAccess || expr instanceof solc_typed_ast_1.MemberAccess) {
169
+ const name = getExpressionName(expr);
170
+ const z3Var = varMap.get(name);
171
+ if (z3Var)
172
+ return z3Var;
173
+ const newVar = ctx.BitVec.const(name, bitSize);
174
+ varMap.set(name, newVar);
175
+ return newVar;
176
+ }
177
+ if (expr instanceof solc_typed_ast_1.UnaryOperation) {
178
+ const subExpr = expressionToZ3(ctx, expr.vSubExpression, varMap, bitSize, signed);
179
+ if (!subExpr)
180
+ return null;
181
+ switch (expr.operator) {
182
+ case '-':
183
+ return ctx.BitVec.val(0, bitSize).sub(subExpr);
184
+ case '~':
185
+ return subExpr.neg(); // bitwise not
186
+ case '!':
187
+ // Logical not - treat as comparison with 0
188
+ return null; // Skip for now
189
+ default:
190
+ return null;
191
+ }
192
+ }
193
+ if (expr instanceof solc_typed_ast_1.BinaryOperation) {
194
+ const left = expressionToZ3(ctx, expr.vLeftExpression, varMap, bitSize, signed);
195
+ const right = expressionToZ3(ctx, expr.vRightExpression, varMap, bitSize, signed);
196
+ if (!left || !right)
197
+ return null;
198
+ switch (expr.operator) {
199
+ case '+':
200
+ return left.add(right);
201
+ case '-':
202
+ return left.sub(right);
203
+ case '*':
204
+ return left.mul(right);
205
+ case '/':
206
+ return signed ? left.sdiv(right) : left.udiv(right);
207
+ case '%':
208
+ return signed ? left.smod(right) : left.urem(right);
209
+ case '>>':
210
+ return signed ? left.shr(right) : left.lshr(right);
211
+ case '<<':
212
+ return left.shl(right);
213
+ case '&':
214
+ return left.and(right);
215
+ case '|':
216
+ return left.or(right);
217
+ case '^':
218
+ return left.xor(right);
219
+ default:
220
+ return null;
221
+ }
222
+ }
223
+ if (expr instanceof solc_typed_ast_1.TupleExpression) {
224
+ // Handle parentheses - just unwrap if single component
225
+ if (expr.vComponents.length === 1 && expr.vComponents[0]) {
226
+ return expressionToZ3(ctx, expr.vComponents[0], varMap, bitSize, signed);
227
+ }
228
+ return null;
229
+ }
230
+ return null;
231
+ };
232
+ const createZ3Constraint = (ctx, expr, varMap, bitSize, signed) => {
233
+ const left = expressionToZ3(ctx, expr.vLeftExpression, varMap, bitSize, signed);
234
+ const right = expressionToZ3(ctx, expr.vRightExpression, varMap, bitSize, signed);
235
+ if (!left || !right)
236
+ return null;
237
+ switch (expr.operator) {
238
+ case '==':
239
+ return left.eq(right);
240
+ case '!=':
241
+ return left.neq(right);
242
+ case '<':
243
+ return signed ? left.slt(right) : left.ult(right);
244
+ case '<=':
245
+ return signed ? left.sle(right) : left.ule(right);
246
+ case '>':
247
+ return signed ? left.sgt(right) : left.ugt(right);
248
+ case '>=':
249
+ return signed ? left.sge(right) : left.uge(right);
250
+ default:
251
+ return null;
252
+ }
253
+ };
254
+ /**
255
+ * Parse Z3 value string to BigInt
256
+ * Z3 returns hex values in format like #xFFFF... or decimal strings
257
+ */
258
+ const parseZ3Value = (valueStr) => {
259
+ // Handle Z3 hex format: #xFFFF... -> 0xFFFF...
260
+ if (valueStr.startsWith('#x')) {
261
+ return BigInt('0x' + valueStr.slice(2));
262
+ }
263
+ // Handle Z3 binary format: #b0101... -> 0b0101...
264
+ if (valueStr.startsWith('#b')) {
265
+ return BigInt('0b' + valueStr.slice(2));
266
+ }
267
+ // Handle regular decimal or hex
268
+ return BigInt(valueStr);
269
+ };
270
+ const solveConstraint = async (ctx, constraint, varMap, bitSize, signed) => {
271
+ const solver = new ctx.Solver();
272
+ solver.add(constraint);
273
+ const result = await solver.check();
274
+ if (result !== 'sat')
275
+ return null;
276
+ const model = solver.model();
277
+ const solutions = new Map();
278
+ for (const [name, z3Var] of varMap) {
279
+ const value = model.eval(z3Var);
280
+ const valueStr = value.toString();
281
+ let numValue = parseZ3Value(valueStr);
282
+ // Convert to signed if needed
283
+ if (signed && numValue >= BigInt(1) << BigInt(bitSize - 1)) {
284
+ numValue = numValue - (BigInt(1) << BigInt(bitSize));
285
+ }
286
+ solutions.set(name, numValue);
287
+ }
288
+ return solutions;
289
+ };
290
+ // Global silent mode flag
291
+ let silentMode = false;
292
+ const setZ3Silent = (silent) => {
293
+ silentMode = silent;
294
+ };
295
+ exports.setZ3Silent = setZ3Silent;
296
+ const findZ3Solutions = async (fnDef) => {
297
+ const solutions = [];
298
+ try {
299
+ const { ctx } = await initZ3();
300
+ // Find all comparison operations
301
+ const binOps = fnDef.getChildrenByType(solc_typed_ast_1.BinaryOperation);
302
+ for (const binOp of binOps) {
303
+ // Only handle equality operator
304
+ if (binOp.operator !== '==') {
305
+ continue;
306
+ }
307
+ // Skip trivial comparisons where left side is just a simple variable or member access
308
+ // e.g., "x == 5" or "block.number == 100000" - the answer is obvious
309
+ const leftIsTrivial = binOp.vLeftExpression instanceof solc_typed_ast_1.Identifier || binOp.vLeftExpression instanceof solc_typed_ast_1.MemberAccess;
310
+ const rightIsTrivial = binOp.vRightExpression instanceof solc_typed_ast_1.Identifier || binOp.vRightExpression instanceof solc_typed_ast_1.MemberAccess;
311
+ const leftIsLiteral = binOp.vLeftExpression instanceof solc_typed_ast_1.Literal;
312
+ const rightIsLiteral = binOp.vRightExpression instanceof solc_typed_ast_1.Literal;
313
+ // Check if right side is an enum value (MemberAccess with type reference)
314
+ const rightIsEnum = binOp.vRightExpression instanceof solc_typed_ast_1.MemberAccess &&
315
+ binOp.vRightExpression.vExpression instanceof solc_typed_ast_1.Identifier &&
316
+ /^[A-Z]/.test(binOp.vRightExpression.vExpression.name); // Enum types typically start with uppercase
317
+ const leftIsEnum = binOp.vLeftExpression instanceof solc_typed_ast_1.MemberAccess &&
318
+ binOp.vLeftExpression.vExpression instanceof solc_typed_ast_1.Identifier &&
319
+ /^[A-Z]/.test(binOp.vLeftExpression.vExpression.name);
320
+ if (leftIsTrivial && (rightIsLiteral || rightIsEnum)) {
321
+ continue;
322
+ }
323
+ // Also skip the reverse: "5 == x" or "ProposalState.Approved == p.state"
324
+ if (rightIsTrivial && (leftIsLiteral || leftIsEnum)) {
325
+ continue;
326
+ }
327
+ const exprStr = getExpressionString(binOp);
328
+ // Extract variables from both sides
329
+ const leftVars = extractVariables(binOp.vLeftExpression);
330
+ const rightVars = extractVariables(binOp.vRightExpression);
331
+ const allVars = [...leftVars, ...rightVars];
332
+ // Only solve if there's exactly one unique variable
333
+ const uniqueVars = new Map();
334
+ for (const v of allVars) {
335
+ // Skip if the "variable" is actually a literal-like number
336
+ if (/^\d+$/.test(v.name))
337
+ continue;
338
+ uniqueVars.set(v.name, { type: v.type, node: v.node });
339
+ }
340
+ if (uniqueVars.size !== 1) {
341
+ continue;
342
+ }
343
+ const [varName, varInfo] = [...uniqueVars.entries()][0];
344
+ const bitSize = getBitSize(varInfo.type);
345
+ const signed = isSigned(varInfo.type);
346
+ const varMap = new Map();
347
+ const z3Var = ctx.BitVec.const(varName, bitSize);
348
+ varMap.set(varName, z3Var);
349
+ const constraint = createZ3Constraint(ctx, binOp, varMap, bitSize, signed);
350
+ if (!constraint) {
351
+ continue;
352
+ }
353
+ const result = await solveConstraint(ctx, constraint, varMap, bitSize, signed);
354
+ if (!result) {
355
+ continue;
356
+ }
357
+ for (const [name, value] of result) {
358
+ solutions.push({
359
+ variable: name,
360
+ value: value.toString(),
361
+ type: varInfo.type.replace(/\s+/g, ''),
362
+ expression: exprStr
363
+ });
364
+ }
365
+ }
366
+ }
367
+ catch (e) {
368
+ // Z3 errors are non-fatal
369
+ if (!silentMode) {
370
+ console.warn(`[z3] Warning: ${e}`);
371
+ }
372
+ }
373
+ return solutions;
374
+ };
375
+ exports.findZ3Solutions = findZ3Solutions;
376
+ const getExpressionString = (expr) => {
377
+ if (expr instanceof solc_typed_ast_1.Literal) {
378
+ return expr.value || '0';
379
+ }
380
+ if (expr instanceof solc_typed_ast_1.Identifier) {
381
+ return expr.name;
382
+ }
383
+ if (expr instanceof solc_typed_ast_1.IndexAccess) {
384
+ const base = getExpressionString(expr.vBaseExpression);
385
+ const index = expr.vIndexExpression ? getExpressionString(expr.vIndexExpression) : '0';
386
+ return `${base}[${index}]`;
387
+ }
388
+ if (expr instanceof solc_typed_ast_1.MemberAccess) {
389
+ const base = getExpressionString(expr.vExpression);
390
+ return `${base}.${expr.memberName}`;
391
+ }
392
+ if (expr instanceof solc_typed_ast_1.FunctionCall) {
393
+ const fn = getExpressionString(expr.vExpression);
394
+ const args = expr.vArguments.map(a => getExpressionString(a)).join(', ');
395
+ return `${fn}(${args})`;
396
+ }
397
+ if (expr instanceof solc_typed_ast_1.UnaryOperation) {
398
+ const sub = getExpressionString(expr.vSubExpression);
399
+ if (expr.prefix) {
400
+ return `${expr.operator}${sub}`;
401
+ }
402
+ return `${sub}${expr.operator}`;
403
+ }
404
+ if (expr instanceof solc_typed_ast_1.BinaryOperation) {
405
+ const left = getExpressionString(expr.vLeftExpression);
406
+ const right = getExpressionString(expr.vRightExpression);
407
+ return `(${left} ${expr.operator} ${right})`;
408
+ }
409
+ if (expr instanceof solc_typed_ast_1.TupleExpression) {
410
+ // TupleExpression with single component is just parentheses
411
+ if (expr.vComponents.length === 1 && expr.vComponents[0]) {
412
+ return getExpressionString(expr.vComponents[0]);
413
+ }
414
+ // Multi-component tuple
415
+ return `(${expr.vComponents.map(c => c ? getExpressionString(c) : '').join(', ')})`;
416
+ }
417
+ return '?';
418
+ };
419
+ const cleanupZ3 = async () => {
420
+ if (z3Context) {
421
+ // Z3 context cleanup if needed
422
+ z3Context = null;
423
+ }
424
+ };
425
+ exports.cleanupZ3 = cleanupZ3;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recon-generate",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "CLI to scaffold Recon fuzzing suite inside Foundry projects",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -26,7 +26,8 @@
26
26
  "handlebars": "^4.7.8",
27
27
  "solc-typed-ast": "^19.1.0",
28
28
  "src-location": "^1.1.0",
29
- "yaml": "^2.8.2"
29
+ "yaml": "^2.8.2",
30
+ "z3-solver": "^4.15.4"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@types/node": "^24.0.4",