recon-generate 0.0.33 → 0.0.34

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.
@@ -160,7 +160,8 @@ const runDictionary = async (foundryRoot, options = {}) => {
160
160
  const log = options.json ? () => { } : console.log.bind(console);
161
161
  const srcPrefix = (_a = options.srcPrefix) !== null && _a !== void 0 ? _a : 'src/';
162
162
  log(`[recon-generate] Loading build-info from ${foundryRoot}`);
163
- const { sourceUnits } = await (0, utils_1.loadLatestBuildInfo)(foundryRoot);
163
+ const foundryConfig = await (0, utils_1.getFoundryConfig)(foundryRoot);
164
+ const { sourceUnits } = await (0, utils_1.loadLatestBuildInfo)(foundryRoot, foundryConfig.out || 'out');
164
165
  if (!sourceUnits || sourceUnits.length === 0) {
165
166
  throw new Error('No source units were produced from the build; cannot generate dictionary.');
166
167
  }
package/dist/info.js CHANGED
@@ -922,7 +922,8 @@ const runInfo = async (foundryRoot, contractName, options = {}) => {
922
922
  if (options.json) {
923
923
  (0, z3Solver_1.setZ3Silent)(true);
924
924
  }
925
- const { sourceUnits, buildOutput } = await (0, utils_1.loadLatestBuildInfo)(foundryRoot);
925
+ const foundryConfig = await (0, utils_1.getFoundryConfig)(foundryRoot);
926
+ const { sourceUnits, buildOutput } = await (0, utils_1.loadLatestBuildInfo)(foundryRoot, foundryConfig.out || 'out');
926
927
  if (!sourceUnits || sourceUnits.length === 0) {
927
928
  throw new Error('No source units were produced from the build; cannot generate info.');
928
929
  }
package/dist/link2.js CHANGED
@@ -95,7 +95,8 @@ const runForgeBuild = async (cwd, verbose = false) => {
95
95
  });
96
96
  });
97
97
  // Find and read the most recent build-info JSON file
98
- const buildInfoDir = path.join(cwd, 'out', 'build-info');
98
+ const foundryConfig = await (0, utils_1.getFoundryConfig)(cwd);
99
+ const buildInfoDir = path.join(cwd, foundryConfig.out, 'build-info');
99
100
  if (verbose) {
100
101
  console.log(`[VERBOSE] Looking for build-info files in: ${buildInfoDir}`);
101
102
  }
package/dist/sourcemap.js CHANGED
@@ -70,8 +70,8 @@ function findFoundryProject(startDir = process.cwd()) {
70
70
  }
71
71
  return null;
72
72
  }
73
- function findBuildInfo(projectDir) {
74
- const buildInfoDir = path.join(projectDir, 'out', 'build-info');
73
+ function findBuildInfo(projectDir, outDir = 'out') {
74
+ const buildInfoDir = path.join(projectDir, outDir, 'build-info');
75
75
  if (!fs.existsSync(buildInfoDir))
76
76
  return null;
77
77
  const files = fs.readdirSync(buildInfoDir).filter(f => f.endsWith('.json'));
@@ -153,13 +153,15 @@ async function runSourcemap(foundryRoot, options = {}) {
153
153
  console.log(`[VERBOSE] Foundry root: ${foundryRoot}`);
154
154
  console.log(`[VERBOSE] Output dir: ${outputDir}`);
155
155
  }
156
+ const foundryConfig = await (0, utils_1.getFoundryConfig)(projectDir);
157
+ const forgeOutDir = foundryConfig.out || 'out';
156
158
  console.log(`📁 Project: ${projectDir}`);
157
159
  console.log(`📂 Output: ${outputDir}`);
158
160
  const startTime = Date.now();
159
161
  // Find build info
160
- const buildInfoPath = findBuildInfo(projectDir);
162
+ const buildInfoPath = findBuildInfo(projectDir, forgeOutDir);
161
163
  if (!buildInfoPath) {
162
- throw new Error(`No build-info found in ${projectDir}/out/build-info/. Run "forge build" first to generate build artifacts.`);
164
+ throw new Error(`No build-info found in ${projectDir}/${forgeOutDir}/build-info/. Run "forge build" first to generate build artifacts.`);
163
165
  }
164
166
  console.log(`📂 Found build-info: ${path.basename(buildInfoPath)}`);
165
167
  // Load AST
@@ -1,5 +1,5 @@
1
- import { FunctionDefinitionParams, ContractMetadata } from './types';
2
- import { SymbolsInfo } from './utils';
1
+ import { FunctionDefinitionParams, ContractMetadata } from "./types";
2
+ import { SymbolsInfo } from "./utils";
3
3
  export declare class TemplateManager {
4
4
  private foundryRoot;
5
5
  private suiteDir;
@@ -49,25 +49,26 @@ class TemplateManager {
49
49
  }
50
50
  skipList() {
51
51
  const suiteRel = path.relative(this.foundryRoot, this.suiteDir);
52
- const suffix = this.suiteNameSnake ? `-${this.suiteNameSnake}` : '';
52
+ const suffix = this.suiteNameSnake ? `-${this.suiteNameSnake}` : "";
53
53
  const cryticTesterName = `CryticTester${this.suiteNamePascal}`;
54
54
  const cryticToFoundryName = `CryticToFoundry${this.suiteNamePascal}`;
55
55
  return [
56
56
  `echidna${suffix}.yaml`,
57
57
  `medusa${suffix}.json`,
58
58
  `halmos${suffix}.toml`,
59
- path.join(suiteRel, 'BeforeAfter.sol'),
59
+ path.join(suiteRel, "BeforeAfter.sol"),
60
+ path.join(suiteRel, "SelectorStorage.sol"),
60
61
  path.join(suiteRel, `${cryticTesterName}.sol`),
61
62
  path.join(suiteRel, `${cryticToFoundryName}.sol`),
62
- path.join(suiteRel, 'Properties.sol'),
63
- path.join(suiteRel, 'helpers/Utils.sol'),
64
- path.join(suiteRel, 'helpers/Panic.sol'),
65
- path.join(suiteRel, 'managers/ActorManager.sol'),
66
- path.join(suiteRel, 'managers/AssetManager.sol'),
67
- path.join(suiteRel, 'managers/utils/EnumerableSet.sol'),
68
- path.join(suiteRel, 'mocks/MockERC20.sol'),
69
- path.join(suiteRel, 'targets/DoomsdayTargets.sol'),
70
- path.join(suiteRel, 'targets/ManagersTargets.sol'),
63
+ path.join(suiteRel, "Properties.sol"),
64
+ path.join(suiteRel, "helpers/Utils.sol"),
65
+ path.join(suiteRel, "helpers/Panic.sol"),
66
+ path.join(suiteRel, "managers/ActorManager.sol"),
67
+ path.join(suiteRel, "managers/AssetManager.sol"),
68
+ path.join(suiteRel, "managers/utils/EnumerableSet.sol"),
69
+ path.join(suiteRel, "mocks/MockERC20.sol"),
70
+ path.join(suiteRel, "targets/DoomsdayTargets.sol"),
71
+ path.join(suiteRel, "targets/ManagersTargets.sol"),
71
72
  ];
72
73
  }
73
74
  async shouldGenerateFile(filePath) {
@@ -75,7 +76,9 @@ class TemplateManager {
75
76
  return true;
76
77
  }
77
78
  try {
78
- const targetPath = path.isAbsolute(filePath) ? filePath : path.join(this.foundryRoot, filePath);
79
+ const targetPath = path.isAbsolute(filePath)
80
+ ? filePath
81
+ : path.join(this.foundryRoot, filePath);
79
82
  await fs.access(targetPath);
80
83
  return false;
81
84
  }
@@ -85,11 +88,13 @@ class TemplateManager {
85
88
  }
86
89
  async updateTargetFile(filepath, newContent) {
87
90
  try {
88
- const existingContent = await fs.readFile(filepath, 'utf8');
89
- if (existingContent.includes('/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///')) {
90
- const [beforeMarker, afterMarker] = existingContent.split('/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///');
91
- const [, newAutoGenerated] = newContent.split('/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///');
92
- return beforeMarker + '/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///' + newAutoGenerated;
91
+ const existingContent = await fs.readFile(filepath, "utf8");
92
+ if (existingContent.includes("/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///")) {
93
+ const [beforeMarker, afterMarker] = existingContent.split("/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///");
94
+ const [, newAutoGenerated] = newContent.split("/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///");
95
+ return (beforeMarker +
96
+ "/// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///" +
97
+ newAutoGenerated);
93
98
  }
94
99
  return newContent;
95
100
  }
@@ -98,39 +103,100 @@ class TemplateManager {
98
103
  }
99
104
  }
100
105
  async generateTemplates(enabledContracts, adminFunctions, nonSeparatedFunctions, separatedByContract, allContractNames, symbolsInfo) {
101
- const contractsForSetup = enabledContracts.map(contract => ({ ...contract, isDynamic: this.dynamicDeploy.has(contract.name) }));
106
+ const contractsForSetup = enabledContracts.map((contract) => ({
107
+ ...contract,
108
+ isDynamic: this.dynamicDeploy.has(contract.name),
109
+ }));
102
110
  const dynamicContracts = enabledContracts
103
- .filter(contract => this.dynamicDeploy.has(contract.name))
104
- .map(c => ({ name: c.name, path: c.path }));
111
+ .filter((contract) => this.dynamicDeploy.has(contract.name))
112
+ .map((c) => ({ name: c.name, path: c.path }));
105
113
  const suiteRel = path.relative(this.foundryRoot, this.suiteDir);
106
- const suffix = this.suiteNameSnake ? `-${this.suiteNameSnake}` : '';
114
+ const suffix = this.suiteNameSnake ? `-${this.suiteNameSnake}` : "";
107
115
  const cryticTesterName = `CryticTester${this.suiteNamePascal}`;
108
116
  const cryticToFoundryName = `CryticToFoundry${this.suiteNamePascal}`;
109
- const suiteTargetsDir = path.join(suiteRel, 'targets');
117
+ const suiteTargetsDir = path.join(suiteRel, "targets");
118
+ // Collect all functions to count overloads
119
+ const allFunctionsRaw = [
120
+ ...adminFunctions,
121
+ ...nonSeparatedFunctions,
122
+ ...Object.values(separatedByContract).flat(),
123
+ ];
124
+ // Simple overload tracking with plain objects
125
+ const counts = {};
126
+ for (const fn of allFunctionsRaw) {
127
+ const key = `${fn.contractName}_${fn.abi.name}`;
128
+ counts[key] = (counts[key] || 0) + 1;
129
+ }
130
+ // Helper to add overloadIndex to a function array
131
+ const addOverloadIndices = (functions) => {
132
+ const indices = {};
133
+ return functions.map((fn) => {
134
+ const key = `${fn.contractName}_${fn.abi.name}`;
135
+ if (counts[key] > 1) {
136
+ const idx = indices[key] || 0;
137
+ indices[key] = idx + 1;
138
+ return { ...fn, overloadIndex: idx };
139
+ }
140
+ return fn;
141
+ });
142
+ };
143
+ // Apply overload indexing to all function arrays
144
+ const indexedAdminFunctions = addOverloadIndices(adminFunctions);
145
+ const indexedNonSeparatedFunctions = addOverloadIndices(nonSeparatedFunctions);
146
+ const indexedSeparatedByContract = {};
147
+ for (const [contractName, functions] of Object.entries(separatedByContract)) {
148
+ indexedSeparatedByContract[contractName] = addOverloadIndices(functions);
149
+ }
150
+ // All functions with overload indices for SelectorStorage
151
+ const allFunctions = [
152
+ ...indexedAdminFunctions,
153
+ ...indexedNonSeparatedFunctions,
154
+ ...Object.values(indexedSeparatedByContract).flat(),
155
+ ];
110
156
  const files = {
111
- [`echidna${suffix}.yaml`]: templates.echidnaConfigTemplate({ corpusDir: `echidna${suffix}` }),
112
- [`medusa${suffix}.json`]: templates.medusaConfigTemplate({ cryticTesterName }),
113
- [`halmos${suffix}.toml`]: templates.halmosConfigTemplate({ cryticToFoundryName }),
114
- [path.join(suiteRel, 'BeforeAfter.sol')]: templates.beforeAfterTemplate({}),
115
- [path.join(suiteRel, `${cryticTesterName}.sol`)]: templates.cryticTesterTemplate({ cryticTesterName, suiteNameSuffix: suffix }),
157
+ [`echidna${suffix}.yaml`]: templates.echidnaConfigTemplate({
158
+ corpusDir: `echidna${suffix}`,
159
+ }),
160
+ [`medusa${suffix}.json`]: templates.medusaConfigTemplate({
161
+ cryticTesterName,
162
+ }),
163
+ [`halmos${suffix}.toml`]: templates.halmosConfigTemplate({
164
+ cryticToFoundryName,
165
+ }),
166
+ [path.join(suiteRel, "BeforeAfter.sol")]: templates.beforeAfterTemplate({}),
167
+ [path.join(suiteRel, "SelectorStorage.sol")]: templates.selectorStorageTemplate({
168
+ functions: allFunctions,
169
+ }),
170
+ [path.join(suiteRel, `${cryticTesterName}.sol`)]: templates.cryticTesterTemplate({
171
+ cryticTesterName,
172
+ suiteNameSuffix: suffix,
173
+ }),
116
174
  [path.join(suiteRel, `${cryticToFoundryName}.sol`)]: templates.cryticToFoundryTemplate({ cryticToFoundryName }),
117
- [path.join(suiteRel, 'Properties.sol')]: templates.propertiesTemplate({}),
118
- [path.join(suiteRel, 'Setup.sol')]: templates.setupTemplate({ contracts: contractsForSetup }),
119
- [path.join(suiteRel, 'TargetFunctions.sol')]: templates.targetFunctionsTemplate({
120
- functions: nonSeparatedFunctions,
175
+ [path.join(suiteRel, "Properties.sol")]: templates.propertiesTemplate({}),
176
+ [path.join(suiteRel, "Setup.sol")]: templates.setupTemplate({
177
+ contracts: contractsForSetup,
178
+ }),
179
+ [path.join(suiteRel, "TargetFunctions.sol")]: templates.targetFunctionsTemplate({
180
+ functions: indexedNonSeparatedFunctions,
121
181
  contracts: allContractNames,
122
182
  dynamicContracts,
123
183
  }),
124
- [path.join(suiteTargetsDir, 'AdminTargets.sol')]: templates.adminTargetsTemplate({
125
- functions: adminFunctions,
126
- extraImports: symbolsInfo ? (0, utils_1.resolveExtraImports)([], adminFunctions, symbolsInfo) : [],
184
+ [path.join(suiteTargetsDir, "AdminTargets.sol")]: templates.adminTargetsTemplate({
185
+ functions: indexedAdminFunctions,
186
+ extraImports: symbolsInfo ? (0, utils_1.resolveExtraImports)([], indexedAdminFunctions, symbolsInfo) : [],
187
+ }),
188
+ [path.join(suiteTargetsDir, "DoomsdayTargets.sol")]: templates.doomsdayTargetsTemplate({}),
189
+ [path.join(suiteTargetsDir, "ManagersTargets.sol")]: templates.managersTargetsTemplate({}),
190
+ [path.join(suiteRel, "README.md")]: templates.readmeTemplate({
191
+ suiteFolder: path.basename(this.suiteDir),
192
+ cryticTesterName,
193
+ cryticToFoundryName,
194
+ configSuffix: suffix,
127
195
  }),
128
- [path.join(suiteTargetsDir, 'DoomsdayTargets.sol')]: templates.doomsdayTargetsTemplate({}),
129
- [path.join(suiteTargetsDir, 'ManagersTargets.sol')]: templates.managersTargetsTemplate({}),
130
196
  };
131
- for (const [contractName, functions] of Object.entries(separatedByContract)) {
197
+ for (const [contractName, functions] of Object.entries(indexedSeparatedByContract)) {
132
198
  const targetPath = path.join(suiteTargetsDir, `${(0, case_1.pascal)(contractName)}Targets.sol`);
133
- const contractPath = functions.length > 0 ? functions[0].contractPath : '';
199
+ const contractPath = functions.length > 0 ? functions[0].contractPath : "";
134
200
  const importedPaths = contractPath ? [contractPath] : [];
135
201
  files[targetPath] = templates.targetsTemplate({
136
202
  contractName,
@@ -140,19 +206,21 @@ class TemplateManager {
140
206
  });
141
207
  }
142
208
  for (const [name, content] of Object.entries(files)) {
143
- const outputPath = path.isAbsolute(name) ? name : path.join(this.foundryRoot, name);
209
+ const outputPath = path.isAbsolute(name)
210
+ ? name
211
+ : path.join(this.foundryRoot, name);
144
212
  if (!(await this.shouldGenerateFile(name))) {
145
213
  continue;
146
214
  }
147
215
  await fs.mkdir(path.dirname(outputPath), { recursive: true });
148
- if (name.endsWith('Setup.sol')) {
216
+ if (name.endsWith("Setup.sol")) {
149
217
  try {
150
218
  await fs.access(outputPath);
151
219
  continue; // do not overwrite user setup
152
220
  }
153
221
  catch { }
154
222
  }
155
- if (name.includes('targets/') && name.endsWith('Targets.sol')) {
223
+ if (name.includes("targets/") && name.endsWith("Targets.sol")) {
156
224
  const finalContent = await this.updateTargetFile(outputPath, content);
157
225
  await fs.writeFile(outputPath, finalContent);
158
226
  }
@@ -11,9 +11,15 @@ exports.beforeAfterTemplate = handlebars_1.default.compile(`// SPDX-License-Iden
11
11
  pragma solidity ^0.8.0;
12
12
 
13
13
  import {Setup} from "./Setup.sol";
14
+ import {SelectorStorage} from "./SelectorStorage.sol";
14
15
 
15
16
  // ghost variables for tracking state variable values before and after function calls
16
17
  abstract contract BeforeAfter is Setup {
18
+
19
+ // Tracks which function selector is currently executing
20
+ // Using bytes4 selector instead of enum to avoid 256 member limit
21
+ bytes4 public currentOperation;
22
+
17
23
  struct Vars {
18
24
  uint256 __ignore__;
19
25
  }
@@ -27,6 +33,15 @@ abstract contract BeforeAfter is Setup {
27
33
  __after();
28
34
  }
29
35
 
36
+
37
+ // Sets currentOperation to the function selector before execution
38
+ modifier trackOp(bytes4 op) {
39
+ currentOperation = op;
40
+ __before();
41
+ _;
42
+ __after();
43
+ }
44
+
30
45
  function __before() internal {
31
46
 
32
47
  }
@@ -31,13 +31,17 @@ function getUsableInternalType(internalType) {
31
31
  }
32
32
  function conditionallyAddMemoryLocation(type, internalType) {
33
33
  if (type.indexOf("[") !== -1) {
34
- if (internalType.startsWith("struct ") || internalType.startsWith("enum ") || internalType.startsWith("contract ")) {
34
+ if (internalType.startsWith("struct ") ||
35
+ internalType.startsWith("enum ") ||
36
+ internalType.startsWith("contract ")) {
35
37
  const usable = getUsableInternalType(internalType);
36
38
  return `${usable} memory`;
37
39
  }
38
40
  return `${type} memory`;
39
41
  }
40
- if (internalType.startsWith("struct ") || internalType.startsWith("enum ") || internalType.startsWith("contract ")) {
42
+ if (internalType.startsWith("struct ") ||
43
+ internalType.startsWith("enum ") ||
44
+ internalType.startsWith("contract ")) {
41
45
  return `${getUsableInternalType(internalType)}${internalType.startsWith("struct ") ? " memory" : ""}`;
42
46
  }
43
47
  if (type === "bytes" || type === "string") {
@@ -51,37 +55,69 @@ function extractType(inputOrOutput) {
51
55
  }
52
56
  return inputOrOutput.type;
53
57
  }
58
+ /**
59
+ * Recursively expands a ParamDefinition to its canonical ABI type string.
60
+ * Tuple types (structs) are expanded to "(type1,type2,...)" using components.
61
+ * Array suffixes like "[]" or "[5]" are preserved.
62
+ */
63
+ function canonicalParamType(param) {
64
+ if (!param.type.startsWith("tuple") || !param.components || param.components.length === 0) {
65
+ return param.type;
66
+ }
67
+ // Extract array suffix: "tuple[]" -> "[]", "tuple[5]" -> "[5]", "tuple" -> ""
68
+ const arraySuffix = param.type.slice("tuple".length);
69
+ const inner = param.components.map((c) => canonicalParamType(c)).join(",");
70
+ return `(${inner})${arraySuffix}`;
71
+ }
54
72
  function registerHelpers(handlebars) {
55
- handlebars.registerHelper('snake', function (str) {
73
+ handlebars.registerHelper("snake", function (str) {
56
74
  return (0, case_1.snake)(str);
57
75
  });
58
- handlebars.registerHelper('scream', function (str) {
76
+ handlebars.registerHelper("scream", function (str) {
59
77
  return (0, case_1.snake)(str).toUpperCase();
60
78
  });
61
- handlebars.registerHelper('camel', function (str) {
79
+ handlebars.registerHelper("camel", function (str) {
62
80
  return (0, case_1.camel)(str);
63
81
  });
64
- handlebars.registerHelper('pascal', function (str) {
82
+ handlebars.registerHelper("pascal", function (str) {
65
83
  return (0, case_1.pascal)(str);
66
84
  });
67
- handlebars.registerHelper('instanceName', function (str) {
85
+ handlebars.registerHelper("instanceName", function (str) {
68
86
  return safeInstanceName(str);
69
87
  });
70
- handlebars.registerHelper('functionDefinition', function ({ contractName, abi, actor, mode }) {
88
+ // Selector constant name: CONTRACT_FUNCTION or CONTRACT_FUNCTION_0 for overloads
89
+ handlebars.registerHelper("selectorConstName", function (fn) {
90
+ const contract = (0, case_1.snake)(fn.contractName).toUpperCase();
91
+ const func = (0, case_1.snake)(fn.abi.name).toUpperCase();
92
+ const suffix = fn.overloadIndex !== undefined ? `_${fn.overloadIndex}` : "";
93
+ return `${contract}_${func}${suffix}`;
94
+ });
95
+ // Function signature: "functionName(type1,type2)"
96
+ // Recursively expands tuple types using components for correct keccak256 selectors
97
+ handlebars.registerHelper("functionSignature", function (fn) {
98
+ const paramTypes = fn.abi.inputs.map((input) => canonicalParamType(input)).join(",");
99
+ return `${fn.abi.name}(${paramTypes})`;
100
+ });
101
+ handlebars.registerHelper("functionDefinition", function ({ contractName, abi, actor, mode, overloadIndex }) {
71
102
  const funcPrefix = (0, case_1.camel)(contractName); // For function names: fB_method
72
103
  const instanceVar = safeInstanceName(contractName); // For instance access: _fB.method
73
104
  let modifiers = [];
74
- if (abi.stateMutability === 'payable') {
75
- modifiers.push('payable');
105
+ // Use constant from SelectorStorage
106
+ const contract = (0, case_1.snake)(contractName).toUpperCase();
107
+ const func = (0, case_1.snake)(abi.name).toUpperCase();
108
+ const suffix = overloadIndex !== undefined ? `_${overloadIndex}` : "";
109
+ modifiers.push(`trackOp(SelectorStorage.${contract}_${func}${suffix})`);
110
+ if (abi.stateMutability === "payable") {
111
+ modifiers.push("payable");
76
112
  }
77
113
  if (actor === types_1.Actor.ADMIN) {
78
- modifiers.push('asAdmin');
114
+ modifiers.push("asAdmin");
79
115
  }
80
116
  else if (actor === types_1.Actor.ACTOR) {
81
- modifiers.push('asActor');
117
+ modifiers.push("asActor");
82
118
  }
83
- const valueStr = abi.stateMutability === 'payable' ? '{value: msg.value}' : '';
84
- const modifiersStr = modifiers.length ? modifiers.join(' ') + ' ' : '';
119
+ const valueStr = abi.stateMutability === "payable" ? "{value: msg.value}" : "";
120
+ const modifiersStr = modifiers.length ? modifiers.join(" ") + " " : "";
85
121
  const hasOutputs = abi.outputs && abi.outputs.length > 0;
86
122
  const outputs = abi.outputs && abi.outputs.length > 0
87
123
  ? abi.outputs
@@ -118,7 +154,7 @@ function registerHelpers(handlebars) {
118
154
  .join(", ")}) public ${modifiersStr}{
119
155
  ${instanceVar}.${abi.name}${valueStr}(${abi.inputs
120
156
  .map((input, index) => inputNames[index] || getDefaultValue(input.type))
121
- .join(", ")});${mode === 'fail'
157
+ .join(", ")});${mode === "fail"
122
158
  ? `
123
159
  t(false, "${funcPrefix}_${abi.name}");`
124
160
  : ""}
@@ -129,10 +165,11 @@ function registerHelpers(handlebars) {
129
165
  function ${funcPrefix}_${abi.name}(${abi.inputs
130
166
  .map((input) => `${conditionallyAddMemoryLocation(input.type, extractType(input))} ${inputNames[abi.inputs.indexOf(input)]}`)
131
167
  .join(", ")}) public ${modifiersStr}{
132
- ${hasOutputs ? `${outputs}
168
+ ${hasOutputs
169
+ ? `${outputs}
133
170
  try ${instanceVar}.${abi.name}${valueStr}(${abi.inputs
134
- .map((input, index) => inputNames[index] || getDefaultValue(input.type))
135
- .join(", ")}) returns (${returnTypes}) {
171
+ .map((input, index) => inputNames[index] || getDefaultValue(input.type))
172
+ .join(", ")}) returns (${returnTypes}) {
136
173
  ${assignValues}
137
174
  }`
138
175
  : `try ${instanceVar}.${abi.name}(${abi.inputs
@@ -1,14 +1,16 @@
1
- import { echidnaConfigTemplate } from './echidna-config';
2
- import { halmosConfigTemplate } from './halmos-config';
3
- import { medusaConfigTemplate } from './medusa-config';
4
- import { beforeAfterTemplate } from './before-after';
5
- import { cryticTesterTemplate } from './crytic-tester';
6
- import { cryticToFoundryTemplate } from './crytic-to-foundry';
7
- import { propertiesTemplate } from './properties';
8
- import { setupTemplate } from './setup';
9
- import { targetFunctionsTemplate } from './target-functions';
10
- import { adminTargetsTemplate } from './targets/admin-targets';
11
- import { doomsdayTargetsTemplate } from './targets/doomsday-targets';
12
- import { managersTargetsTemplate } from './targets/managers-targets';
13
- import { targetsTemplate } from './targets/targets';
14
- export { echidnaConfigTemplate, halmosConfigTemplate, medusaConfigTemplate, beforeAfterTemplate, cryticTesterTemplate, cryticToFoundryTemplate, propertiesTemplate, setupTemplate, targetFunctionsTemplate, adminTargetsTemplate, doomsdayTargetsTemplate, managersTargetsTemplate, targetsTemplate, };
1
+ import { echidnaConfigTemplate } from "./echidna-config";
2
+ import { halmosConfigTemplate } from "./halmos-config";
3
+ import { medusaConfigTemplate } from "./medusa-config";
4
+ import { beforeAfterTemplate } from "./before-after";
5
+ import { cryticTesterTemplate } from "./crytic-tester";
6
+ import { cryticToFoundryTemplate } from "./crytic-to-foundry";
7
+ import { propertiesTemplate } from "./properties";
8
+ import { setupTemplate } from "./setup";
9
+ import { targetFunctionsTemplate } from "./target-functions";
10
+ import { adminTargetsTemplate } from "./targets/admin-targets";
11
+ import { doomsdayTargetsTemplate } from "./targets/doomsday-targets";
12
+ import { managersTargetsTemplate } from "./targets/managers-targets";
13
+ import { targetsTemplate } from "./targets/targets";
14
+ import { selectorStorageTemplate } from "./selector-storage";
15
+ import { readmeTemplate } from "./readme";
16
+ export { echidnaConfigTemplate, halmosConfigTemplate, medusaConfigTemplate, beforeAfterTemplate, cryticTesterTemplate, cryticToFoundryTemplate, propertiesTemplate, setupTemplate, targetFunctionsTemplate, adminTargetsTemplate, doomsdayTargetsTemplate, managersTargetsTemplate, targetsTemplate, selectorStorageTemplate, readmeTemplate, };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.targetsTemplate = exports.managersTargetsTemplate = exports.doomsdayTargetsTemplate = exports.adminTargetsTemplate = exports.targetFunctionsTemplate = exports.setupTemplate = exports.propertiesTemplate = exports.cryticToFoundryTemplate = exports.cryticTesterTemplate = exports.beforeAfterTemplate = exports.medusaConfigTemplate = exports.halmosConfigTemplate = exports.echidnaConfigTemplate = void 0;
3
+ exports.readmeTemplate = exports.selectorStorageTemplate = exports.targetsTemplate = exports.managersTargetsTemplate = exports.doomsdayTargetsTemplate = exports.adminTargetsTemplate = exports.targetFunctionsTemplate = exports.setupTemplate = exports.propertiesTemplate = exports.cryticToFoundryTemplate = exports.cryticTesterTemplate = exports.beforeAfterTemplate = exports.medusaConfigTemplate = exports.halmosConfigTemplate = exports.echidnaConfigTemplate = void 0;
4
4
  const echidna_config_1 = require("./echidna-config");
5
5
  Object.defineProperty(exports, "echidnaConfigTemplate", { enumerable: true, get: function () { return echidna_config_1.echidnaConfigTemplate; } });
6
6
  const halmos_config_1 = require("./halmos-config");
@@ -27,3 +27,7 @@ const managers_targets_1 = require("./targets/managers-targets");
27
27
  Object.defineProperty(exports, "managersTargetsTemplate", { enumerable: true, get: function () { return managers_targets_1.managersTargetsTemplate; } });
28
28
  const targets_1 = require("./targets/targets");
29
29
  Object.defineProperty(exports, "targetsTemplate", { enumerable: true, get: function () { return targets_1.targetsTemplate; } });
30
+ const selector_storage_1 = require("./selector-storage");
31
+ Object.defineProperty(exports, "selectorStorageTemplate", { enumerable: true, get: function () { return selector_storage_1.selectorStorageTemplate; } });
32
+ const readme_1 = require("./readme");
33
+ Object.defineProperty(exports, "readmeTemplate", { enumerable: true, get: function () { return readme_1.readmeTemplate; } });
@@ -0,0 +1 @@
1
+ export declare const readmeTemplate: HandlebarsTemplateDelegate<any>;
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.readmeTemplate = void 0;
7
+ const handlebars_1 = __importDefault(require("handlebars"));
8
+ const handlebars_helpers_1 = require("./handlebars-helpers");
9
+ (0, handlebars_helpers_1.registerHelpers)(handlebars_1.default);
10
+ exports.readmeTemplate = handlebars_1.default.compile(`# Recon Fuzzing Suite
11
+
12
+ This is an auto-generated fuzzing test suite created by [Recon](https://getrecon.xyz/).
13
+
14
+ ## File Structure
15
+
16
+ \`\`\`
17
+ {{suiteFolder}}/
18
+ ├── Setup.sol # Contract deployment and initialization
19
+ ├── BeforeAfter.sol # Ghost variables for state tracking
20
+ ├── SelectorStorage.sol # Function selector constants
21
+ ├── Properties.sol # YOUR INVARIANTS GO HERE
22
+ ├── TargetFunctions.sol # Non-separated target functions
23
+ ├── {{cryticTesterName}}.sol # Entry point for Echidna/Medusa
24
+ ├── {{cryticToFoundryName}}.sol # Foundry test wrapper
25
+ ├── helpers/
26
+ │ └── Utils.sol
27
+ ├── managers/
28
+ │ ├── ActorManager.sol
29
+ │ └── AssetManager.sol
30
+ └── targets/
31
+ ├── AdminTargets.sol
32
+ └── *Targets.sol # Per-contract targets
33
+ \`\`\`
34
+
35
+ ## Key Concepts
36
+
37
+ ### Inheritance Chain
38
+
39
+ \`\`\`
40
+ Setup (deploys contracts)
41
+ └── BeforeAfter (ghost variables, trackOp)
42
+ └── Properties (YOUR invariants)
43
+ └── *Targets (function wrappers)
44
+ └── TargetFunctions
45
+ └── {{cryticTesterName}}
46
+ \`\`\`
47
+
48
+ ### Operation Tracking
49
+
50
+ Each target function uses \`trackOp(selector)\` which:
51
+ 1. Sets \`currentOperation\` to the function's selector
52
+ 2. Calls \`__before()\` to capture pre-state
53
+ 3. Executes the function
54
+ 4. Calls \`__after()\` to capture post-state
55
+
56
+ Example property using operation tracking:
57
+
58
+ \`\`\`solidity
59
+ function property_deposit_increases_balance() public {
60
+ if (currentOperation == SelectorStorage.VAULT_DEPOSIT) {
61
+ gte(_after.vaultBalance, _before.vaultBalance, "deposit should increase balance");
62
+ }
63
+ }
64
+ \`\`\`
65
+
66
+ ### Ghost Variables
67
+
68
+ In \`BeforeAfter.sol\`, the \`Vars\` struct captures state:
69
+
70
+ \`\`\`solidity
71
+ struct Vars {
72
+ uint256 vaultTotalAssets;
73
+ uint256 userBalance;
74
+ }
75
+
76
+ function __before() internal {
77
+ _before.vaultTotalAssets = vault.totalAssets();
78
+ _before.userBalance = token.balanceOf(actor);
79
+ }
80
+
81
+ function __after() internal {
82
+ _after.vaultTotalAssets = vault.totalAssets();
83
+ _after.userBalance = token.balanceOf(actor);
84
+ }
85
+ \`\`\`
86
+
87
+ ### SelectorStorage
88
+
89
+ Contains named constants for function selectors:
90
+
91
+ \`\`\`solidity
92
+ bytes4 constant VAULT_DEPOSIT = bytes4(keccak256("deposit(uint256)"));
93
+ bytes4 constant VAULT_WITHDRAW_0 = bytes4(keccak256("withdraw(uint256)"));
94
+ bytes4 constant VAULT_WITHDRAW_1 = bytes4(keccak256("withdraw(uint256,address)"));
95
+ \`\`\`
96
+
97
+ Overloaded functions get \_0, \_1 suffixes.
98
+
99
+ ## Files to Edit
100
+
101
+ | File | Purpose |
102
+ |------|---------|
103
+ | \`Setup.sol\` | Deploy contracts, set initial state, configure actors |
104
+ | \`BeforeAfter.sol\` | Add variables to \`Vars\` struct, implement \`__before()\`/\`__after()\` |
105
+ | \`Properties.sol\` | Write invariant functions (prefix with \`property_\`) |
106
+
107
+ ## Auto-Generated Files (Don't Edit Below Marker)
108
+
109
+ - \`SelectorStorage.sol\` - Regenerated from ABIs
110
+ - \`*Targets.sol\` - Code below \`/// AUTO GENERATED TARGET FUNCTIONS\` marker is regenerated
111
+
112
+ ## Writing Properties
113
+
114
+ \`\`\`solidity
115
+ // Global invariant - checked after every operation
116
+ function property_solvent() public {
117
+ gte(vault.totalAssets(), vault.totalSupply(), "insolvent");
118
+ }
119
+
120
+ // Operation-specific invariant
121
+ function property_withdraw_decreases_shares() public {
122
+ if (currentOperation == SelectorStorage.VAULT_WITHDRAW_0) {
123
+ lt(_after.userShares, _before.userShares, "shares should decrease");
124
+ }
125
+ }
126
+ \`\`\`
127
+
128
+ ### Assertion Helpers
129
+
130
+ - \`t(bool, string)\` - assert true
131
+ - \`eq(a, b, string)\` - equal
132
+ - \`gt(a, b, string)\` - greater than
133
+ - \`gte(a, b, string)\` - greater or equal
134
+ - \`lt(a, b, string)\` - less than
135
+ - \`lte(a, b, string)\` - less or equal
136
+
137
+ ## Running Fuzzers
138
+
139
+ \`\`\`bash
140
+ # Echidna
141
+ echidna . --contract {{cryticTesterName}} --config echidna{{configSuffix}}.yaml
142
+
143
+ # Medusa
144
+ medusa fuzz --config medusa{{configSuffix}}.json
145
+
146
+ # Halmos
147
+ halmos --config halmos{{configSuffix}}.toml
148
+ \`\`\`
149
+
150
+ ## Regenerating
151
+
152
+ After contract changes:
153
+ \`\`\`bash
154
+ recon-generate --force
155
+ \`\`\`
156
+
157
+ This preserves Setup.sol, BeforeAfter.sol, Properties.sol, and config files.
158
+ `, { noEscape: true });
@@ -0,0 +1 @@
1
+ export declare const selectorStorageTemplate: HandlebarsTemplateDelegate<any>;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.selectorStorageTemplate = void 0;
7
+ const handlebars_1 = __importDefault(require("handlebars"));
8
+ const handlebars_helpers_1 = require("./handlebars-helpers");
9
+ (0, handlebars_helpers_1.registerHelpers)(handlebars_1.default);
10
+ exports.selectorStorageTemplate = handlebars_1.default.compile(`// SPDX-License-Identifier: GPL-2.0
11
+ pragma solidity ^0.8.0;
12
+
13
+ /// @notice Auto-generated selector constants for operation tracking
14
+ library SelectorStorage {
15
+ {{#each functions}}
16
+ bytes4 constant {{selectorConstName this}} = bytes4(keccak256("{{functionSignature this}}"));
17
+ {{/each}}
18
+ }
19
+ `, { noEscape: true });
@@ -16,6 +16,8 @@ import {vm} from "@chimera/Hevm.sol";
16
16
  // Helpers
17
17
  import {Panic} from "@recon/Panic.sol";
18
18
 
19
+ import {SelectorStorage} from "./SelectorStorage.sol";
20
+
19
21
  // Targets
20
22
  // NOTE: Always import and apply them in alphabetical order, so much easier to debug!
21
23
  {{#each contracts}}
@@ -22,6 +22,8 @@ import {Panic} from "@recon/Panic.sol";
22
22
  import { {{this.name}} } from "{{this.path}}";
23
23
  {{/each}}
24
24
 
25
+ import {SelectorStorage} from "../SelectorStorage.sol";
26
+
25
27
  abstract contract AdminTargets is
26
28
  BaseTargetFunctions,
27
29
  Properties
@@ -19,6 +19,8 @@ import {vm} from "@chimera/Hevm.sol";
19
19
  // Helpers
20
20
  import {Panic} from "@recon/Panic.sol";
21
21
 
22
+ import {SelectorStorage} from "../SelectorStorage.sol";
23
+
22
24
  {{#if path}}import "{{path}}";{{/if}}
23
25
  {{#each extraImports}}
24
26
  import { {{this.name}} } from "{{this.path}}";
package/dist/types.d.ts CHANGED
@@ -46,6 +46,7 @@ export interface FunctionDefinitionParams {
46
46
  sig: string;
47
47
  ast?: ASTNode;
48
48
  code?: string;
49
+ overloadIndex?: number;
49
50
  }
50
51
  export declare enum CallType {
51
52
  Internal = "internal",
package/dist/types.js CHANGED
@@ -18,5 +18,3 @@ var CallType;
18
18
  CallType["HighLevel"] = "high-level";
19
19
  CallType["LowLevel"] = "low-level";
20
20
  })(CallType || (exports.CallType = CallType = {}));
21
- ;
22
- ;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recon-generate",
3
- "version": "0.0.33",
3
+ "version": "0.0.34",
4
4
  "description": "CLI to scaffold Recon fuzzing suite inside Foundry projects",
5
5
  "main": "dist/index.js",
6
6
  "bin": {