recon-generate 0.0.3 → 0.0.4

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/README.md CHANGED
@@ -31,6 +31,7 @@ Key options:
31
31
  - `--exclude "A:{foo(uint256)},D"` — exclude these contracts/functions.
32
32
  - `--admin "A:{fnSig,fnSig}"` — mark listed functions as admin (`asAdmin`).
33
33
  - `--mock "A,B"` — generate mocks for the listed contracts (compiled ABIs required); mocks go under `recon[-name]/mocks` and are added to targets.
34
+ - `--dynamic-deploy "Foo,Bar"` — keep dynamic lists for the listed contracts: deploys one instance, tracks addresses in an array, exposes `_getRandom<Contract>` and `switch<Contract>(entropy)` helpers to rotate among deployed instances.
34
35
  - `--name <suite>` — name the suite; affects folder (`recon-<name>`), config filenames (`echidna-<name>.yaml`, `medusa-<name>.json`, `halmos-<name>.toml`), and Crytic tester/runner names.
35
36
  - `--force` — replace existing generated suite output under `--output` (does **not** rebuild `.recon/out`).
36
37
  - `--force-build` — delete `.recon/out` to force a fresh compile before generation.
@@ -63,6 +64,9 @@ recon-generate --name foo --force
63
64
 
64
65
  # Generate mocks and include them in targets
65
66
  recon-generate --mock "Morpho,Other" --force
67
+
68
+ # Enable dynamic deploy helpers for Foo and Bar
69
+ recon-generate --dynamic-deploy "Foo,Bar" --force
66
70
  ```
67
71
 
68
72
  ### Behavior
@@ -71,6 +75,7 @@ recon-generate --mock "Morpho,Other" --force
71
75
  - `--force` replaces the suite output folder; it does **not** rebuild `.recon/out`.
72
76
  - `--force-build` deletes `.recon/out` to force a fresh compile before generation.
73
77
  - When `--mock` is used, a fresh build is triggered, mocks are generated under `recon[-name]/mocks`, and a second build picks them up into `.recon/out` and targets.
78
+ - When `--dynamic-deploy` is used, generated `Setup.sol` keeps an `address[]` per listed contract, pushes the first deployment, and emits `_getRandom<Contract>` helper plus `switch<Contract>(entropy)` in `TargetFunctions.sol` to retarget to a previously deployed instance.
74
79
  - `recon.json` is regenerated every run from current filters/contracts.
75
80
  - Include/exclude/admin matching is tolerant of fully qualified struct names vs short names (e.g., `Morpho.MarketParams` vs `MarketParams`).
76
81
  - Admin list sets `actor: admin` for matched functions; others default to `actor`.
@@ -13,6 +13,7 @@ export interface GeneratorOptions {
13
13
  forceBuild?: boolean;
14
14
  admin?: Map<string, Set<string>>;
15
15
  mocks?: Set<string>;
16
+ dynamicDeploy?: Set<string>;
16
17
  suiteNameSnake: string;
17
18
  suiteNamePascal: string;
18
19
  }
package/dist/generator.js CHANGED
@@ -464,7 +464,7 @@ class ReconGenerator {
464
464
  return config;
465
465
  }
466
466
  async run() {
467
- var _a;
467
+ var _a, _b;
468
468
  await this.ensureFoundryConfigExists();
469
469
  const outPath = this.outDir();
470
470
  const mockTargets = (_a = this.options.mocks) !== null && _a !== void 0 ? _a : new Set();
@@ -508,7 +508,7 @@ class ReconGenerator {
508
508
  this.logDebug('No contracts left after include/exclude filters');
509
509
  }
510
510
  const reconConfig = await this.loadReconConfig(filteredContracts);
511
- const tm = new templateManager_1.TemplateManager(this.foundryRoot, this.options.suiteDir, this.options.suiteNameSnake, this.options.suiteNamePascal);
511
+ const tm = new templateManager_1.TemplateManager(this.foundryRoot, this.options.suiteDir, this.options.suiteNameSnake, this.options.suiteNamePascal, (_b = this.options.dynamicDeploy) !== null && _b !== void 0 ? _b : new Set());
512
512
  await tm.generateTemplates(filteredContracts, reconConfig);
513
513
  }
514
514
  async listAvailable() {
package/dist/index.js CHANGED
@@ -114,6 +114,7 @@ async function main() {
114
114
  .option('--admin <spec>', 'Mark functions as admin: e.g. "A:{foo(uint256),bar()},B:{baz(address)}"')
115
115
  .option('--name <suite>', 'Suite name; affects folder (recon-name) and Crytic artifacts')
116
116
  .option('--mock <names>', 'Comma-separated contract names to generate mocks for')
117
+ .option('--dynamic-deploy <names>', 'Comma-separated contract names to enable dynamic deploy lists')
117
118
  .option('--debug', 'Print debug info about filters and selection')
118
119
  .option('--list', 'List available contracts/functions (after filters) and exit')
119
120
  .option('--force', 'Replace existing generated suite output (under --output). Does not rebuild .recon/out.')
@@ -130,6 +131,9 @@ async function main() {
130
131
  const mockSet = opts.mock
131
132
  ? new Set(String(opts.mock).split(',').map((s) => s.trim()).filter(Boolean))
132
133
  : undefined;
134
+ const dynamicDeploySet = opts.dynamicDeploy
135
+ ? new Set(String(opts.dynamicDeploy).split(',').map((s) => s.trim()).filter(Boolean))
136
+ : undefined;
133
137
  const suiteRaw = opts.name ? String(opts.name).trim() : '';
134
138
  const suiteSnake = suiteRaw ? (0, case_1.snake)(suiteRaw) : '';
135
139
  const suitePascal = suiteRaw ? (0, case_1.pascal)(suiteRaw) : '';
@@ -154,6 +158,7 @@ async function main() {
154
158
  force: !!opts.force,
155
159
  forceBuild: !!opts.forceBuild,
156
160
  mocks: mockSet,
161
+ dynamicDeploy: dynamicDeploySet,
157
162
  suiteNameSnake: suiteSnake,
158
163
  suiteNamePascal: suitePascal,
159
164
  });
@@ -4,7 +4,8 @@ export declare class TemplateManager {
4
4
  private suiteDir;
5
5
  private suiteNameSnake;
6
6
  private suiteNamePascal;
7
- constructor(foundryRoot: string, suiteDir: string, suiteNameSnake: string, suiteNamePascal: string);
7
+ private dynamicDeploy;
8
+ constructor(foundryRoot: string, suiteDir: string, suiteNameSnake: string, suiteNamePascal: string, dynamicDeploy?: Set<string>);
8
9
  private skipList;
9
10
  private shouldGenerateFile;
10
11
  private updateTargetFile;
@@ -39,11 +39,12 @@ const fs = __importStar(require("fs/promises"));
39
39
  const templates = __importStar(require("./templates"));
40
40
  const types_1 = require("./types");
41
41
  class TemplateManager {
42
- constructor(foundryRoot, suiteDir, suiteNameSnake, suiteNamePascal) {
42
+ constructor(foundryRoot, suiteDir, suiteNameSnake, suiteNamePascal, dynamicDeploy = new Set()) {
43
43
  this.foundryRoot = foundryRoot;
44
44
  this.suiteDir = suiteDir;
45
45
  this.suiteNameSnake = suiteNameSnake;
46
46
  this.suiteNamePascal = suiteNamePascal;
47
+ this.dynamicDeploy = dynamicDeploy;
47
48
  }
48
49
  skipList() {
49
50
  const suiteRel = path.relative(this.foundryRoot, this.suiteDir);
@@ -180,6 +181,10 @@ class TemplateManager {
180
181
  }
181
182
  async generateTemplates(contracts, reconConfig) {
182
183
  const { enabledContracts, adminFunctions, nonSeparatedFunctions, separatedByContract, allContractNames, } = this.collectFunctions(contracts, reconConfig);
184
+ const contractsForSetup = enabledContracts.map(contract => ({ ...contract, isDynamic: this.dynamicDeploy.has(contract.name) }));
185
+ const dynamicContracts = enabledContracts
186
+ .filter(contract => this.dynamicDeploy.has(contract.name))
187
+ .map(c => ({ name: c.name, path: c.path }));
183
188
  const suiteRel = path.relative(this.foundryRoot, this.suiteDir);
184
189
  const suffix = this.suiteNameSnake ? `-${this.suiteNameSnake}` : '';
185
190
  const cryticTesterName = `CryticTester${this.suiteNamePascal}`;
@@ -193,10 +198,11 @@ class TemplateManager {
193
198
  [path.join(suiteRel, `${cryticTesterName}.sol`)]: templates.cryticTesterTemplate({ cryticTesterName, suiteNameSuffix: suffix }),
194
199
  [path.join(suiteRel, `${cryticToFoundryName}.sol`)]: templates.cryticToFoundryTemplate({ cryticToFoundryName }),
195
200
  [path.join(suiteRel, 'Properties.sol')]: templates.propertiesTemplate({}),
196
- [path.join(suiteRel, 'Setup.sol')]: templates.setupTemplate({ contracts: enabledContracts }),
201
+ [path.join(suiteRel, 'Setup.sol')]: templates.setupTemplate({ contracts: contractsForSetup }),
197
202
  [path.join(suiteRel, 'TargetFunctions.sol')]: templates.targetFunctionsTemplate({
198
203
  functions: nonSeparatedFunctions,
199
204
  contracts: allContractNames,
205
+ dynamicContracts,
200
206
  }),
201
207
  [path.join(suiteTargetsDir, 'AdminTargets.sol')]: templates.adminTargetsTemplate({ functions: adminFunctions }),
202
208
  [path.join(suiteTargetsDir, 'DoomsdayTargets.sol')]: templates.doomsdayTargetsTemplate({}),
@@ -86,13 +86,22 @@ function registerHelpers(handlebars) {
86
86
  : `tempValue${index}`};`)
87
87
  .join("\n ")
88
88
  : "";
89
+ const inputNames = abi.inputs.map((input, index) => {
90
+ if (input.name && input.name.trim() !== "") {
91
+ return input.name;
92
+ }
93
+ if (input.type === "tuple") {
94
+ return `empty${index}`;
95
+ }
96
+ return `arg${index}`;
97
+ });
89
98
  if (mode === types_1.Mode.NORMAL || mode === types_1.Mode.FAIL) {
90
99
  return `
91
100
  function ${contractName}_${abi.name}(${abi.inputs
92
- .map((input) => `${conditionallyAddMemoryLocation(input.type, extractType(input))} ${input.name}`)
101
+ .map((input) => `${conditionallyAddMemoryLocation(input.type, extractType(input))} ${inputNames[abi.inputs.indexOf(input)]}`)
93
102
  .join(", ")}) public ${modifiersStr}{
94
103
  ${contractName}.${abi.name}${valueStr}(${abi.inputs
95
- .map((input) => input.name ? input.name : getDefaultValue(input.type))
104
+ .map((input, index) => inputNames[index] || getDefaultValue(input.type))
96
105
  .join(", ")});${mode === 'fail'
97
106
  ? `
98
107
  t(false, "${contractName}_${abi.name}");`
@@ -102,16 +111,16 @@ function registerHelpers(handlebars) {
102
111
  else {
103
112
  return `
104
113
  function ${contractName}_${abi.name}(${abi.inputs
105
- .map((input) => `${conditionallyAddMemoryLocation(input.type, extractType(input))} ${input.name}`)
114
+ .map((input) => `${conditionallyAddMemoryLocation(input.type, extractType(input))} ${inputNames[abi.inputs.indexOf(input)]}`)
106
115
  .join(", ")}) public ${modifiersStr}{
107
116
  ${hasOutputs ? `${outputs}
108
117
  try ${contractName}.${abi.name}${valueStr}(${abi.inputs
109
- .map((input) => input.name ? input.name : getDefaultValue(input.type))
118
+ .map((input, index) => inputNames[index] || getDefaultValue(input.type))
110
119
  .join(", ")}) returns (${returnTypes}) {
111
120
  ${assignValues}
112
121
  }`
113
122
  : `try ${contractName}.${abi.name}(${abi.inputs
114
- .map((input) => input.name ? input.name : getDefaultValue(input.type))
123
+ .map((input, index) => inputNames[index] || getDefaultValue(input.type))
115
124
  .join(", ")}) {}`} catch {
116
125
  ${hasOutputs ? " " : " "}t(false, "${contractName}_${abi.name}");
117
126
  ${hasOutputs ? " " : " "}}
@@ -29,6 +29,8 @@ import "{{this.path}}";
29
29
  abstract contract Setup is BaseSetup, ActorManager, AssetManager, Utils {
30
30
  {{#each contracts}}
31
31
  {{this.name}} {{camel this.name}};
32
+ {{#if this.isDynamic}}address[] {{camel this.name}}_s;
33
+ {{/if}}
32
34
  {{/each}}
33
35
 
34
36
  /// === Setup === ///
@@ -36,9 +38,22 @@ abstract contract Setup is BaseSetup, ActorManager, AssetManager, Utils {
36
38
  function setup() internal virtual override {
37
39
  {{#each contracts}}
38
40
  {{camel this.name}} = new {{this.name}}(); // TODO: Add parameters here
41
+ {{#if this.isDynamic}}
42
+ {{camel this.name}}_s.push(address({{camel this.name}}));
43
+ {{/if}}
39
44
  {{/each}}
40
45
  }
41
46
 
47
+ /// === Dynamic deploy helpers === ///
48
+ {{#each contracts}}
49
+ {{#if this.isDynamic}}
50
+ function _getRandom{{pascal this.name}}(uint8 index) internal view returns (address) {
51
+ return {{camel this.name}}_s[index % {{camel this.name}}_s.length];
52
+ }
53
+
54
+ {{/if}}
55
+ {{/each}}
56
+
42
57
  /// === MODIFIERS === ///
43
58
  /// Prank admin and actor
44
59
 
@@ -22,6 +22,11 @@ import {Panic} from "@recon/Panic.sol";
22
22
  import { {{pascal this}}Targets } from "./targets/{{pascal this}}Targets.sol";
23
23
  {{/each}}
24
24
 
25
+ // Dynamic deploy contract types
26
+ {{#each dynamicContracts}}
27
+ import "{{this.path}}";
28
+ {{/each}}
29
+
25
30
  abstract contract TargetFunctions is
26
31
  {{#each contracts}}{{pascal this}}Targets{{#unless @last}},
27
32
  {{/unless}}{{/each}}
@@ -32,6 +37,14 @@ abstract contract TargetFunctions is
32
37
  /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///
33
38
  {{#each functions}}
34
39
  {{functionDefinition this}}
40
+ {{/each}}
41
+
42
+ /// AUTO GENERATED DYNAMIC DEPLOY SWITCHES ///
43
+ {{#each dynamicContracts}}
44
+ function switch{{pascal this.name}}(uint8 entropy) public {
45
+ {{camel this.name}} = {{this.name}}(_getRandom{{pascal this.name}}(entropy));
46
+ }
47
+
35
48
  {{/each}}
36
49
  }
37
50
  `, { noEscape: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recon-generate",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "CLI to scaffold Recon fuzzing suite inside Foundry projects",
5
5
  "main": "dist/index.js",
6
6
  "bin": {