@stacks/rendezvous 0.10.0 → 0.12.0

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
@@ -8,7 +8,7 @@ Rendezvous `rv` is a Clarity fuzzer designed to cut through your smart contract'
8
8
 
9
9
  ### Prerequisites
10
10
 
11
- - **Node.js**: Supported versions include 18, 20, and 22. Other versions may work, but they are untested.
11
+ - **Node.js**: Supported versions include 20, 22, and 23. Other versions may work, but they are untested.
12
12
 
13
13
  ### Inspiration
14
14
 
@@ -53,7 +53,6 @@ npx rv <path-to-clarinet-project> <contract-name> <type>
53
53
  **Options:**
54
54
 
55
55
  - `--seed` – The seed to use for the replay functionality.
56
- - `--path` – The path to use for the replay functionality.
57
56
  - `--runs` – The number of test iterations to use for exercising the contracts.
58
57
  (default: `100`)
59
58
  - `--bail` – Stop after the first failure.
package/dist/app.js CHANGED
@@ -25,6 +25,7 @@ const citizen_1 = require("./citizen");
25
25
  const package_json_1 = require("./package.json");
26
26
  const ansicolor_1 = require("ansicolor");
27
27
  const fs_1 = require("fs");
28
+ const util_1 = require("util");
28
29
  const dialer_1 = require("./dialer");
29
30
  const logger = (log, logLevel = "log") => {
30
31
  console[logLevel](log);
@@ -71,7 +72,7 @@ exports.tryParseRemoteDataSettings = tryParseRemoteDataSettings;
71
72
  const helpMessage = `
72
73
  rv v${package_json_1.version}
73
74
 
74
- Usage: rv <path-to-clarinet-project> <contract-name> <type> [--seed=<seed>] [--path=<path>] [--runs=<runs>] [--dial=<path-to-dialers-file>] [--help]
75
+ Usage: rv <path-to-clarinet-project> <contract-name> <type> [--seed=<seed>] [--runs=<runs>] [--dial=<path-to-dialers-file>] [--help]
75
76
 
76
77
  Positional arguments:
77
78
  path-to-clarinet-project - The path to the Clarinet project.
@@ -80,45 +81,61 @@ const helpMessage = `
80
81
 
81
82
  Options:
82
83
  --seed - The seed to use for the replay functionality.
83
- --path - The path to use for the replay functionality.
84
84
  --runs - The runs to use for iterating over the tests. Default: 100.
85
85
  --bail - Stop after the first failure.
86
86
  --dial – The path to a JavaScript file containing custom pre- and post-execution functions (dialers).
87
87
  --help - Show the help message.
88
88
  `;
89
- const parseBooleanOption = (argName) => process.argv.slice(4).includes(`--${argName}`);
90
- const parseOption = (argName) => {
91
- var _a;
92
- return (_a = process.argv
93
- .find((arg, idx) => idx >= 4 && arg.toLowerCase().startsWith(`--${argName}`))) === null || _a === void 0 ? void 0 : _a.split("=")[1];
94
- };
95
89
  function main() {
96
90
  return __awaiter(this, void 0, void 0, function* () {
97
- var _a;
98
91
  const radio = new events_1.EventEmitter();
99
92
  radio.on("logMessage", (log) => logger(log));
100
93
  radio.on("logFailure", (log) => logger((0, ansicolor_1.red)(log), "error"));
101
- const args = process.argv;
102
- if (args.includes("--help")) {
94
+ const { positionals: positionalArgs, values: options } = (0, util_1.parseArgs)({
95
+ allowPositionals: true,
96
+ args: process.argv.slice(2),
97
+ options: {
98
+ seed: { type: "string" },
99
+ runs: { type: "string" },
100
+ dial: { type: "string" },
101
+ bail: { type: "boolean" },
102
+ help: { type: "boolean", short: "h" },
103
+ },
104
+ });
105
+ const [manifestDir, sutContractName, type] = positionalArgs;
106
+ const runConfig = {
107
+ /** The relative path to the Clarinet project. */
108
+ manifestDir: manifestDir,
109
+ /** The target contract name. */
110
+ sutContractName: sutContractName,
111
+ /** The type of testing to be executed. Valid values: test, invariant. */
112
+ type: type === null || type === void 0 ? void 0 : type.toLowerCase(),
113
+ /** The seed to use for the replay functionality. */
114
+ seed: options.seed ? parseInt(options.seed, 10) : undefined,
115
+ /** The number of runs to use. */
116
+ runs: options.runs ? parseInt(options.runs, 10) : undefined,
117
+ /** Whether to bail on the first failure. */
118
+ bail: options.bail || false,
119
+ /** The path to the dialer file. */
120
+ dial: options.dial || undefined,
121
+ /** Whether to show the help message. */
122
+ help: options.help || false,
123
+ };
124
+ if (runConfig.help) {
103
125
  radio.emit("logMessage", helpMessage);
104
126
  return;
105
127
  }
106
- /** The relative path to the Clarinet project. */
107
- const manifestDir = args[2];
108
- if (!manifestDir || manifestDir.startsWith("--")) {
128
+ if (!runConfig.manifestDir) {
109
129
  radio.emit("logMessage", (0, ansicolor_1.red)("\nNo path to Clarinet project provided. Supply it immediately or face the relentless scrutiny of your contract's vulnerabilities."));
110
130
  radio.emit("logMessage", helpMessage);
111
131
  return;
112
132
  }
113
- /** The target contract name. */
114
- const sutContractName = args[3];
115
- if (!sutContractName || sutContractName.startsWith("--")) {
133
+ if (!runConfig.sutContractName) {
116
134
  radio.emit("logMessage", (0, ansicolor_1.red)("\nNo target contract name provided. Please provide the contract name to be fuzzed."));
117
135
  radio.emit("logMessage", helpMessage);
118
136
  return;
119
137
  }
120
- const type = (_a = args[4]) === null || _a === void 0 ? void 0 : _a.toLowerCase();
121
- if (!type || type.startsWith("--") || !["test", "invariant"].includes(type)) {
138
+ if (!runConfig.type || !["test", "invariant"].includes(runConfig.type)) {
122
139
  radio.emit("logMessage", (0, ansicolor_1.red)("\nInvalid type provided. Please provide the type of test to be executed. Possible values: test, invariant."));
123
140
  radio.emit("logMessage", helpMessage);
124
141
  return;
@@ -127,59 +144,49 @@ function main() {
127
144
  * The relative path to the manifest file, either `Clarinet.toml` or
128
145
  * `Clarinet-<contract-name>.toml`. If the latter exists, it is used.
129
146
  */
130
- const manifestPath = (0, path_1.join)(manifestDir, (0, exports.getManifestFileName)(manifestDir, sutContractName));
147
+ const manifestPath = (0, path_1.join)(runConfig.manifestDir, (0, exports.getManifestFileName)(runConfig.manifestDir, runConfig.sutContractName));
131
148
  radio.emit("logMessage", `Using manifest path: ${manifestPath}`);
132
- radio.emit("logMessage", `Target contract: ${sutContractName}`);
133
- const seed = parseInt(parseOption("seed"), 10) || undefined;
134
- if (seed !== undefined) {
135
- radio.emit("logMessage", `Using seed: ${seed}`);
136
- }
137
- const path = parseOption("path") || undefined;
138
- if (path !== undefined) {
139
- radio.emit("logMessage", `Using path: ${path}`);
149
+ radio.emit("logMessage", `Target contract: ${runConfig.sutContractName}`);
150
+ if (runConfig.seed !== undefined) {
151
+ radio.emit("logMessage", `Using seed: ${runConfig.seed}`);
140
152
  }
141
- const runs = parseInt(parseOption("runs"), 10) || undefined;
142
- if (runs !== undefined) {
143
- radio.emit("logMessage", `Using runs: ${runs}`);
153
+ if (runConfig.runs !== undefined) {
154
+ radio.emit("logMessage", `Using runs: ${runConfig.runs}`);
144
155
  }
145
- const bail = parseBooleanOption("bail");
146
- if (bail) {
156
+ if (runConfig.bail) {
147
157
  radio.emit("logMessage", `Bailing on first failure.`);
148
158
  }
149
- /**
150
- * The path to the dialer file. The dialer file allows the user to register
151
- * custom pre and post-execution JavaScript functions to be executed before
152
- * and after the public function calls during invariant testing.
153
- */
154
- const dialPath = parseOption("dial") || undefined;
155
- if (dialPath !== undefined) {
156
- radio.emit("logMessage", `Using dial path: ${dialPath}`);
159
+ if (runConfig.dial !== undefined) {
160
+ radio.emit("logMessage", `Using dial path: ${runConfig.dial}`);
157
161
  }
158
162
  /**
159
163
  * The dialer registry, which is used to keep track of all the custom dialers
160
164
  * registered by the user using the `--dial` flag.
161
165
  */
162
- const dialerRegistry = dialPath !== undefined ? new dialer_1.DialerRegistry(dialPath) : undefined;
166
+ const dialerRegistry = runConfig.dial !== undefined
167
+ ? new dialer_1.DialerRegistry(runConfig.dial)
168
+ : undefined;
163
169
  if (dialerRegistry !== undefined) {
164
170
  dialerRegistry.registerDialers();
165
171
  }
166
172
  const remoteDataSettings = (0, exports.tryParseRemoteDataSettings)(manifestPath, radio);
167
- const simnet = yield (0, citizen_1.issueFirstClassCitizenship)(manifestDir, manifestPath, remoteDataSettings, sutContractName);
173
+ const simnet = yield (0, citizen_1.issueFirstClassCitizenship)(runConfig.manifestDir, manifestPath, remoteDataSettings, runConfig.sutContractName);
168
174
  /**
169
175
  * The list of contract IDs for the SUT contract names, as per the simnet.
170
176
  */
171
- const rendezvousList = Array.from((0, shared_1.getSimnetDeployerContractsInterfaces)(simnet).keys()).filter((deployedContract) => (0, shared_1.getContractNameFromContractId)(deployedContract) === sutContractName);
177
+ const rendezvousList = Array.from((0, shared_1.getSimnetDeployerContractsInterfaces)(simnet).keys()).filter((deployedContract) => (0, shared_1.getContractNameFromContractId)(deployedContract) ===
178
+ runConfig.sutContractName);
172
179
  const rendezvousAllFunctions = (0, shared_1.getFunctionsFromContractInterfaces)(new Map(Array.from((0, shared_1.getSimnetDeployerContractsInterfaces)(simnet)).filter(([contractId]) => rendezvousList.includes(contractId))));
173
180
  // Select the testing routine based on `type`.
174
181
  // If "invariant", call `checkInvariants` to verify contract invariants.
175
182
  // If "test", call `checkProperties` for property-based testing.
176
- switch (type) {
183
+ switch (runConfig.type) {
177
184
  case "invariant": {
178
- yield (0, invariant_1.checkInvariants)(simnet, sutContractName, rendezvousList, rendezvousAllFunctions, seed, path, runs, bail, dialerRegistry, radio);
185
+ yield (0, invariant_1.checkInvariants)(simnet, runConfig.sutContractName, rendezvousList, rendezvousAllFunctions, runConfig.seed, runConfig.runs, runConfig.bail, dialerRegistry, radio);
179
186
  break;
180
187
  }
181
188
  case "test": {
182
- (0, property_1.checkProperties)(simnet, sutContractName, rendezvousList, rendezvousAllFunctions, seed, path, runs, bail, radio);
189
+ (0, property_1.checkProperties)(simnet, runConfig.sutContractName, rendezvousList, rendezvousAllFunctions, runConfig.seed, runConfig.runs, runConfig.bail, radio);
183
190
  break;
184
191
  }
185
192
  }
package/dist/citizen.js CHANGED
@@ -18,7 +18,7 @@ const fs_1 = require("fs");
18
18
  const path_1 = require("path");
19
19
  const toml_1 = __importDefault(require("toml"));
20
20
  const yaml_1 = __importDefault(require("yaml"));
21
- const clarinet_sdk_1 = require("@hirosystems/clarinet-sdk");
21
+ const clarinet_sdk_1 = require("@stacks/clarinet-sdk");
22
22
  const transactions_1 = require("@stacks/transactions");
23
23
  /**
24
24
  * Prepares the simnet instance and assures the target contract's corresponding
package/dist/dialer.js CHANGED
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
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
+ })();
25
35
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
36
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
37
  return new (P || (P = Promise))(function (resolve, reject) {
package/dist/invariant.js CHANGED
@@ -29,7 +29,6 @@ const dialer_1 = require("./dialer");
29
29
  * @param rendezvousAllFunctions The map of all function interfaces for each
30
30
  * target contract.
31
31
  * @param seed The seed for reproducible invariant testing.
32
- * @param path The path for reproducible invariant testing.
33
32
  * @param runs The number of test runs.
34
33
  * @param bail Stop execution after the first failure and prevent further
35
34
  * shrinking.
@@ -37,7 +36,7 @@ const dialer_1 = require("./dialer");
37
36
  * @param radio The custom logging event emitter.
38
37
  * @returns void
39
38
  */
40
- const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousAllFunctions, seed, path, runs, bail, dialerRegistry, radio) => __awaiter(void 0, void 0, void 0, function* () {
39
+ const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousAllFunctions, seed, runs, bail, dialerRegistry, radio) => __awaiter(void 0, void 0, void 0, function* () {
41
40
  const statistics = {
42
41
  sut: {
43
42
  successful: new Map(),
@@ -48,13 +47,14 @@ const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousA
48
47
  failed: new Map(),
49
48
  },
50
49
  };
50
+ // The Rendezvous identifier is the first one in the list. Only one contract
51
+ // can be fuzzed at a time.
52
+ const rendezvousContractId = rendezvousList[0];
51
53
  // A map where the keys are the Rendezvous identifiers and the values are
52
54
  // arrays of their SUT (System Under Test) functions. This map will be used
53
55
  // to access the SUT functions for each Rendezvous contract afterwards.
54
56
  const rendezvousSutFunctions = filterSutFunctions(rendezvousAllFunctions);
55
- // The Rendezvous identifier is the first one in the list. Only one contract
56
- // can be fuzzed at a time.
57
- const rendezvousContractId = rendezvousList[0];
57
+ // Initialize the statistics for the SUT functions.
58
58
  for (const functionInterface of rendezvousSutFunctions.get(rendezvousContractId)) {
59
59
  statistics.sut.successful.set(functionInterface.name, 0);
60
60
  statistics.sut.failed.set(functionInterface.name, 0);
@@ -63,47 +63,79 @@ const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousA
63
63
  // arrays of their invariant functions. This map will be used to access the
64
64
  // invariant functions for each Rendezvous contract afterwards.
65
65
  const rendezvousInvariantFunctions = filterInvariantFunctions(rendezvousAllFunctions);
66
+ // Initialize the statistics for the invariant functions.
66
67
  for (const functionInterface of rendezvousInvariantFunctions.get(rendezvousContractId)) {
67
68
  statistics.invariant.successful.set(functionInterface.name, 0);
68
69
  statistics.invariant.failed.set(functionInterface.name, 0);
69
70
  }
70
- const traitReferenceSutFunctions = rendezvousSutFunctions
71
- .get(rendezvousContractId)
72
- .filter((fn) => (0, traits_1.isTraitReferenceFunction)(fn));
73
- const traitReferenceInvariantFunctions = rendezvousInvariantFunctions
74
- .get(rendezvousContractId)
75
- .filter((fn) => (0, traits_1.isTraitReferenceFunction)(fn));
76
- const projectTraitImplementations = (0, traits_1.extractProjectTraitImplementations)(simnet);
77
- if (Object.entries(projectTraitImplementations).length === 0 &&
78
- (traitReferenceSutFunctions.length > 0 ||
79
- traitReferenceInvariantFunctions.length > 0)) {
80
- const foundTraitReferenceMessage = traitReferenceSutFunctions.length > 0 &&
81
- traitReferenceInvariantFunctions.length > 0
82
- ? "public functions and invariants"
83
- : traitReferenceSutFunctions.length > 0
84
- ? "public functions"
85
- : "invariants";
86
- radio.emit("logMessage", (0, ansicolor_1.red)(`\nFound ${foundTraitReferenceMessage} referencing traits, but no trait implementations were found in the project.
87
- \nNote: You can add contracts implementing traits either as project contracts or as Clarinet requirements. For more details, visit: https://www.hiro.so/clarinet/.
88
- \n`));
89
- return;
90
- }
71
+ const sutFunctions = rendezvousSutFunctions.get(rendezvousContractId);
72
+ const traitReferenceSutFunctions = sutFunctions.filter(traits_1.isTraitReferenceFunction);
73
+ const invariantFunctions = rendezvousInvariantFunctions.get(rendezvousContractId);
74
+ const traitReferenceInvariantFunctions = invariantFunctions.filter(traits_1.isTraitReferenceFunction);
75
+ const sutTraitReferenceMap = (0, traits_1.buildTraitReferenceMap)(sutFunctions);
76
+ const invariantTraitReferenceMap = (0, traits_1.buildTraitReferenceMap)(invariantFunctions);
91
77
  const enrichedSutFunctionsInterfaces = traitReferenceSutFunctions.length > 0
92
- ? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(targetContractName), (0, traits_1.buildTraitReferenceMap)(rendezvousSutFunctions.get(rendezvousContractId)), rendezvousSutFunctions.get(rendezvousContractId), rendezvousContractId)
78
+ ? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(targetContractName), sutTraitReferenceMap, sutFunctions, rendezvousContractId)
93
79
  : rendezvousSutFunctions;
94
80
  const enrichedInvariantFunctionsInterfaces = traitReferenceInvariantFunctions.length > 0
95
- ? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(targetContractName), (0, traits_1.buildTraitReferenceMap)(rendezvousInvariantFunctions.get(rendezvousContractId)), rendezvousInvariantFunctions.get(rendezvousContractId), rendezvousContractId)
81
+ ? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(targetContractName), invariantTraitReferenceMap, invariantFunctions, rendezvousContractId)
96
82
  : rendezvousInvariantFunctions;
83
+ // Map all the project/requirement contracts to the traits they implement.
84
+ const projectTraitImplementations = (0, traits_1.extractProjectTraitImplementations)(simnet);
85
+ // Extract SUT functions with missing trait implementations. These functions
86
+ // will be skipped during invariant testing. Otherwise, the invariant testing
87
+ // routine can fail during argument generation.
88
+ const sutFunctionsWithMissingTraits = (0, traits_1.getNonTestableTraitFunctions)(enrichedSutFunctionsInterfaces, sutTraitReferenceMap, projectTraitImplementations, rendezvousContractId);
89
+ // Extract invariant functions with missing trait implementations. These
90
+ // functions will be skipped during invariant testing. Otherwise, the
91
+ // invariant testing routine can fail during argument generation.
92
+ const invariantFunctionsWithMissingTraits = (0, traits_1.getNonTestableTraitFunctions)(enrichedInvariantFunctionsInterfaces, invariantTraitReferenceMap, projectTraitImplementations, rendezvousContractId);
93
+ if (sutFunctionsWithMissingTraits.length > 0 ||
94
+ invariantFunctionsWithMissingTraits.length > 0) {
95
+ if (sutFunctionsWithMissingTraits.length > 0) {
96
+ const functionList = sutFunctionsWithMissingTraits
97
+ .map((fn) => ` - ${fn}`)
98
+ .join("\n");
99
+ radio.emit("logMessage", (0, ansicolor_1.yellow)(`\nWarning: The following SUT functions reference traits without eligible implementations and will be skipped:\n\n${functionList}\n`));
100
+ }
101
+ if (invariantFunctionsWithMissingTraits.length > 0) {
102
+ const functionList = invariantFunctionsWithMissingTraits
103
+ .map((fn) => ` - ${fn}`)
104
+ .join("\n");
105
+ radio.emit("logMessage", (0, ansicolor_1.yellow)(`\nWarning: The following invariant functions reference traits without eligible implementations and will be skipped:\n\n${functionList}\n`));
106
+ }
107
+ radio.emit("logMessage", (0, ansicolor_1.yellow)(`Note: You can add contracts implementing traits either as project contracts or as Clarinet requirements.\n`));
108
+ }
109
+ // Filter out functions with missing trait implementations from the enriched
110
+ // map.
111
+ const executableSutFunctions = new Map([
112
+ [
113
+ rendezvousContractId,
114
+ enrichedSutFunctionsInterfaces
115
+ .get(rendezvousContractId)
116
+ .filter((f) => !sutFunctionsWithMissingTraits.includes(f.name)),
117
+ ],
118
+ ]);
119
+ // Filter out functions with missing trait implementations from the enriched
120
+ // map.
121
+ const executableInvariantFunctions = new Map([
122
+ [
123
+ rendezvousContractId,
124
+ enrichedInvariantFunctionsInterfaces
125
+ .get(rendezvousContractId)
126
+ .filter((f) => !invariantFunctionsWithMissingTraits.includes(f.name)),
127
+ ],
128
+ ]);
97
129
  // Set up local context to track SUT function call counts.
98
- const localContext = (0, exports.initializeLocalContext)(enrichedSutFunctionsInterfaces);
130
+ const localContext = (0, exports.initializeLocalContext)(executableSutFunctions);
99
131
  // Set up context in simnet by initializing state for SUT.
100
- (0, exports.initializeClarityContext)(simnet, enrichedSutFunctionsInterfaces);
132
+ (0, exports.initializeClarityContext)(simnet, executableSutFunctions);
101
133
  radio.emit("logMessage", `\nStarting invariant testing type for the ${targetContractName} contract...\n`);
102
134
  const simnetAccounts = simnet.getAccounts();
103
135
  const eligibleAccounts = new Map([...simnetAccounts].filter(([key]) => key !== "faucet"));
104
136
  const simnetAddresses = Array.from(simnetAccounts.values());
105
- const functions = (0, shared_1.getFunctionsListForContract)(enrichedSutFunctionsInterfaces, rendezvousContractId);
106
- const invariants = (0, shared_1.getFunctionsListForContract)(enrichedInvariantFunctionsInterfaces, rendezvousContractId);
137
+ const functions = (0, shared_1.getFunctionsListForContract)(executableSutFunctions, rendezvousContractId);
138
+ const invariants = (0, shared_1.getFunctionsListForContract)(executableInvariantFunctions, rendezvousContractId);
107
139
  if ((functions === null || functions === void 0 ? void 0 : functions.length) === 0) {
108
140
  radio.emit("logMessage", (0, ansicolor_1.red)(`No public functions found for the "${targetContractName}" contract. Without public functions, no state transitions can happen inside the contract, and the invariant test is not meaningful.\n`));
109
141
  return;
@@ -325,7 +357,6 @@ const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousA
325
357
  })), {
326
358
  endOnFailure: bail,
327
359
  numRuns: runs,
328
- path: path,
329
360
  reporter: radioReporter,
330
361
  seed: seed,
331
362
  verbose: true,
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stacks/rendezvous",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Meet your contract's vulnerabilities head-on.",
5
5
  "main": "app.js",
6
6
  "bin": {
@@ -8,8 +8,8 @@
8
8
  },
9
9
  "scripts": {
10
10
  "build": "npx -p typescript tsc --project tsconfig.json && node -e \"if (process.platform !== 'win32') require('fs').chmodSync('./dist/app.js', 0o755);\"",
11
- "test": "npx jest --testTimeout=15000",
12
- "test:coverage": "npx tsc --project tsconfig.json && npx jest --coverage"
11
+ "test": "npx jest",
12
+ "test:coverage": "npx jest --coverage"
13
13
  },
14
14
  "keywords": [
15
15
  "stacks",
@@ -30,18 +30,18 @@
30
30
  "url": "https://github.com/stacks-network/rendezvous.git"
31
31
  },
32
32
  "dependencies": {
33
- "@hirosystems/clarinet-sdk": "^3.7.0",
33
+ "@stacks/clarinet-sdk": "^3.9.1",
34
34
  "@stacks/transactions": "^7.2.0",
35
35
  "ansicolor": "^2.0.3",
36
- "fast-check": "^3.20.0",
36
+ "fast-check": "^4.3.0",
37
37
  "toml": "^3.0.0",
38
- "yaml": "^2.6.1"
38
+ "yaml": "^2.8.1"
39
39
  },
40
40
  "devDependencies": {
41
- "@hirosystems/clarinet-sdk-wasm": "^3.7.0",
42
- "@types/jest": "^29.5.12",
43
- "jest": "^29.7.0",
44
- "ts-jest": "^29.2.5",
45
- "typescript": "^5.5.4"
41
+ "@stacks/clarinet-sdk-wasm": "^3.9.1",
42
+ "@types/jest": "^30.0.0",
43
+ "jest": "^30.2.0",
44
+ "ts-jest": "^29.4.5",
45
+ "typescript": "^5.9.3"
46
46
  }
47
47
  }
package/dist/property.js CHANGED
@@ -19,14 +19,13 @@ const traits_1 = require("./traits");
19
19
  * @param rendezvousAllFunctions A map of all target contract IDs to their
20
20
  * function interfaces.
21
21
  * @param seed The seed for reproducible property-based tests.
22
- * @param path The path for reproducible property-based tests.
23
22
  * @param runs The number of test runs.
24
23
  * @param bail Stop execution after the first failure and prevent further
25
24
  * shrinking.
26
25
  * @param radio The custom logging event emitter.
27
26
  * @returns void
28
27
  */
29
- const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousAllFunctions, seed, path, runs, bail, radio) => {
28
+ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousAllFunctions, seed, runs, bail, radio) => {
30
29
  const statistics = {
31
30
  test: {
32
31
  successful: new Map(),
@@ -44,20 +43,39 @@ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousA
44
43
  statistics.test.discarded.set(functionInterface.name, 0);
45
44
  statistics.test.failed.set(functionInterface.name, 0);
46
45
  }
47
- const traitReferenceFunctions = testContractsTestFunctions
48
- .get(testContractId)
49
- .filter((fn) => (0, traits_1.isTraitReferenceFunction)(fn));
46
+ const allTestFunctions = testContractsTestFunctions.get(testContractId);
47
+ const traitReferenceFunctionsCount = allTestFunctions.filter(traits_1.isTraitReferenceFunction).length;
48
+ const traitReferenceMap = (0, traits_1.buildTraitReferenceMap)(allTestFunctions);
49
+ const enrichedTestFunctionsInterfaces = traitReferenceFunctionsCount > 0
50
+ ? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(targetContractName), traitReferenceMap, allTestFunctions, testContractId)
51
+ : testContractsTestFunctions;
50
52
  const projectTraitImplementations = (0, traits_1.extractProjectTraitImplementations)(simnet);
51
- if (Object.entries(projectTraitImplementations).length === 0 &&
52
- traitReferenceFunctions.length > 0) {
53
- radio.emit("logMessage", (0, ansicolor_1.red)(`\nFound test functions referencing traits, but no trait implementations were found in the project.
54
- \nNote: You can add contracts implementing traits either as project contracts or as Clarinet requirements. For more details, visit: https://www.hiro.so/clarinet/.
55
- \n`));
56
- return;
53
+ // If the tests contain trait reference functions, extract the functions with
54
+ // missing trait implementations. These functions need to be skipped during
55
+ // property-based testing. Otherwise, the property-based testing routine will
56
+ // eventually fail during argument generation.
57
+ const functionsMissingTraitImplementations = traitReferenceFunctionsCount > 0
58
+ ? (0, traits_1.getNonTestableTraitFunctions)(enrichedTestFunctionsInterfaces, traitReferenceMap, projectTraitImplementations, testContractId)
59
+ : [];
60
+ // If the tests contain trait reference functions without eligible trait
61
+ // implementations, log a warning and filter out the functions.
62
+ if (functionsMissingTraitImplementations.length > 0) {
63
+ const functionList = functionsMissingTraitImplementations
64
+ .map((fn) => ` - ${fn}`)
65
+ .join("\n");
66
+ radio.emit("logMessage", (0, ansicolor_1.yellow)(`\nWarning: The following test functions reference traits without eligible implementations and will be skipped:\n\n${functionList}\n`));
67
+ radio.emit("logMessage", (0, ansicolor_1.yellow)(`Note: You can add contracts implementing traits either as project contracts or as requirements.\n`));
57
68
  }
58
- const enrichedTestFunctionsInterfaces = traitReferenceFunctions.length > 0
59
- ? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(targetContractName), (0, traits_1.buildTraitReferenceMap)(testContractsTestFunctions.get(testContractId)), testContractsTestFunctions.get(testContractId), testContractId)
60
- : testContractsTestFunctions;
69
+ // Filter out test functions with missing trait implementations from the
70
+ // enriched map.
71
+ const executableTestContractsTestFunctions = new Map([
72
+ [
73
+ testContractId,
74
+ enrichedTestFunctionsInterfaces
75
+ .get(testContractId)
76
+ .filter((functionInterface) => !functionsMissingTraitImplementations.includes(functionInterface.name)),
77
+ ],
78
+ ]);
61
79
  radio.emit("logMessage", `\nStarting property testing type for the ${targetContractName} contract...\n`);
62
80
  // Search for discard functions, for each test function. This map will
63
81
  // be used to pair the test functions with their corresponding discard
@@ -69,7 +87,7 @@ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousA
69
87
  // Pair each test function with its corresponding discard function. When a
70
88
  // test function is selected, Rendezvous will first call its discard
71
89
  // function, to allow or prevent the property test from running.
72
- const testContractsPairedFunctions = new Map(Array.from(testContractsTestFunctions, ([contractId, functions]) => [
90
+ const testContractsPairedFunctions = new Map(Array.from(executableTestContractsTestFunctions, ([contractId, functions]) => [
73
91
  contractId,
74
92
  new Map(functions.map((f) => {
75
93
  var _a;
@@ -87,7 +105,7 @@ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousA
87
105
  const simnetAccounts = simnet.getAccounts();
88
106
  const eligibleAccounts = new Map([...simnetAccounts].filter(([key]) => key !== "faucet"));
89
107
  const simnetAddresses = Array.from(simnetAccounts.values());
90
- const testFunctions = (0, shared_1.getFunctionsListForContract)(enrichedTestFunctionsInterfaces, testContractId);
108
+ const testFunctions = (0, shared_1.getFunctionsListForContract)(executableTestContractsTestFunctions, testContractId);
91
109
  if ((testFunctions === null || testFunctions === void 0 ? void 0 : testFunctions.length) === 0) {
92
110
  radio.emit("logMessage", (0, ansicolor_1.red)(`No test functions found for the "${targetContractName}" contract.\n`));
93
111
  return;
@@ -223,7 +241,6 @@ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousA
223
241
  }), {
224
242
  endOnFailure: bail,
225
243
  numRuns: runs,
226
- path: path,
227
244
  reporter: radioReporter,
228
245
  seed: seed,
229
246
  verbose: true,
package/dist/shared.js CHANGED
@@ -105,7 +105,7 @@ const complexTypesToArbitrary = {
105
105
  // in hex. The `argToCV` function expects this format for `buff` ClarityValue
106
106
  // conversion. The UInt8Array will have half the length of the corresponding
107
107
  // hex string. Stacks.js reference:
108
- // https://github.com/hirosystems/stacks.js/blob/fd0bf26b5f29fc3c1bf79581d0ad9b89f0d7f15a/packages/common/src/utils.ts#L522
108
+ // https://github.com/stx-labs/stacks.js/blob/fd0bf26b5f29fc3c1bf79581d0ad9b89f0d7f15a/packages/common/src/utils.ts#L522
109
109
  buffer: (length) => (0, exports.hexaString)({ maxLength: 2 * length }),
110
110
  "string-ascii": (length) => fast_check_1.default.string({
111
111
  unit: fast_check_1.default.constantFrom(...charSet),
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createIsolatedTestEnvironment = createIsolatedTestEnvironment;
4
+ const path_1 = require("path");
5
+ const fs_1 = require("fs");
6
+ const os_1 = require("os");
7
+ /**
8
+ * Creates an isolated test environment by copying the Clarinet project to a
9
+ * unique temporary directory. This prevents race conditions when multiple
10
+ * tests try to initialize simnet concurrently.
11
+ *
12
+ * @param manifestDir - The absolute path to the manifest directory.
13
+ * @param testPrefix - Prefix for the temporary directory name.
14
+ * @returns The path to the temporary directory containing the isolated project
15
+ * copy.
16
+ */
17
+ function createIsolatedTestEnvironment(manifestDir, testPrefix) {
18
+ const tempDir = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), testPrefix));
19
+ (0, fs_1.cpSync)(manifestDir, tempDir, { recursive: true });
20
+ return tempDir;
21
+ }
package/dist/traits.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractProjectTraitImplementations = exports.isTraitReferenceFunction = exports.getContractIdsImplementingTrait = exports.buildTraitReferenceMap = exports.getTraitReferenceData = exports.enrichInterfaceWithTraitData = void 0;
3
+ exports.getNonTestableTraitFunctions = exports.extractProjectTraitImplementations = exports.isTraitReferenceFunction = exports.getContractIdsImplementingTrait = exports.buildTraitReferenceMap = exports.getTraitReferenceData = exports.enrichInterfaceWithTraitData = void 0;
4
4
  /**
5
5
  * Enriches a contract interface with trait reference data. Before enrichment,
6
6
  * the contract interface lacks trait reference data for parameters. This
@@ -329,10 +329,15 @@ const getContractIdsImplementingTrait = (trait, projectTraitImplementations) =>
329
329
  const filteredContracts = contracts.filter((contractId) => {
330
330
  var _a;
331
331
  const traitImplemented = (_a = projectTraitImplementations[contractId]) === null || _a === void 0 ? void 0 : _a.some((implementedTrait) => {
332
- var _a, _b;
333
- const isTraitNamesMatch = implementedTrait.name === ((_a = trait.import.Imported) === null || _a === void 0 ? void 0 : _a.name);
332
+ var _a, _b, _c, _d;
333
+ const isTraitNamesMatch = implementedTrait.name ===
334
+ ((_a = trait.import.Imported) === null || _a === void 0 ? void 0 : _a.name) ||
335
+ implementedTrait.name ===
336
+ ((_b = trait.import.Defined) === null || _b === void 0 ? void 0 : _b.name);
334
337
  const isTraitIssuersMatch = JSON.stringify(implementedTrait.contract_identifier.issuer) ===
335
- JSON.stringify((_b = trait.import.Imported) === null || _b === void 0 ? void 0 : _b.contract_identifier.issuer);
338
+ JSON.stringify((_c = trait.import.Imported) === null || _c === void 0 ? void 0 : _c.contract_identifier.issuer) ||
339
+ JSON.stringify(implementedTrait.contract_identifier.issuer) ===
340
+ JSON.stringify((_d = trait.import.Defined) === null || _d === void 0 ? void 0 : _d.contract_identifier.issuer);
336
341
  return isTraitNamesMatch && isTraitIssuersMatch;
337
342
  });
338
343
  return traitImplemented;
@@ -403,3 +408,45 @@ const extractProjectTraitImplementations = (simnet) => {
403
408
  return projectTraitImplementations;
404
409
  };
405
410
  exports.extractProjectTraitImplementations = extractProjectTraitImplementations;
411
+ /**
412
+ * Filters functions that reference traits without eligible implementations in
413
+ * the project. Recursively checks nested parameters for trait reference types.
414
+ * @param enrichedFunctionsInterfaces The map of contract IDs to enriched
415
+ * function interfaces with trait reference data.
416
+ * @param traitReferenceMap The map of function names to their trait reference
417
+ * parameter data.
418
+ * @param projectTraitImplementations The record of contract IDs to their
419
+ * implemented traits.
420
+ * @param contractId The contract ID to filter functions for.
421
+ * @returns An array of function names with trait references without eligible
422
+ * trait implementations.
423
+ */
424
+ const getNonTestableTraitFunctions = (enrichedFunctionsInterfaces, traitReferenceMap, projectTraitImplementations, contractId) => {
425
+ const hasTraitReferenceWithoutImplementation = (type) => {
426
+ if (!type)
427
+ return false;
428
+ if (typeof type === "object" && "trait_reference" in type) {
429
+ const contractIdsImplementingTrait = (0, exports.getContractIdsImplementingTrait)(type.trait_reference, projectTraitImplementations);
430
+ return contractIdsImplementingTrait.length === 0;
431
+ }
432
+ if (typeof type === "object") {
433
+ return (("list" in type &&
434
+ hasTraitReferenceWithoutImplementation(type.list.type)) ||
435
+ ("tuple" in type &&
436
+ type.tuple.some((item) => hasTraitReferenceWithoutImplementation(item.type))) ||
437
+ ("optional" in type &&
438
+ hasTraitReferenceWithoutImplementation(type.optional)) ||
439
+ ("response" in type &&
440
+ (hasTraitReferenceWithoutImplementation(type.response.ok) ||
441
+ hasTraitReferenceWithoutImplementation(type.response.error))));
442
+ }
443
+ return false;
444
+ };
445
+ return Array.from(traitReferenceMap.keys()).filter((functionName) => {
446
+ var _a, _b;
447
+ const enrichedFunctionInterface = (_a = enrichedFunctionsInterfaces
448
+ .get(contractId)) === null || _a === void 0 ? void 0 : _a.find((f) => f.name === functionName);
449
+ return ((_b = enrichedFunctionInterface === null || enrichedFunctionInterface === void 0 ? void 0 : enrichedFunctionInterface.args.some((param) => hasTraitReferenceWithoutImplementation(param.type))) !== null && _b !== void 0 ? _b : false);
450
+ });
451
+ };
452
+ exports.getNonTestableTraitFunctions = getNonTestableTraitFunctions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stacks/rendezvous",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Meet your contract's vulnerabilities head-on.",
5
5
  "main": "app.js",
6
6
  "bin": {
@@ -8,8 +8,8 @@
8
8
  },
9
9
  "scripts": {
10
10
  "build": "npx -p typescript tsc --project tsconfig.json && node -e \"if (process.platform !== 'win32') require('fs').chmodSync('./dist/app.js', 0o755);\"",
11
- "test": "npx jest --testTimeout=15000",
12
- "test:coverage": "npx tsc --project tsconfig.json && npx jest --coverage"
11
+ "test": "npx jest",
12
+ "test:coverage": "npx jest --coverage"
13
13
  },
14
14
  "keywords": [
15
15
  "stacks",
@@ -30,18 +30,18 @@
30
30
  "url": "https://github.com/stacks-network/rendezvous.git"
31
31
  },
32
32
  "dependencies": {
33
- "@hirosystems/clarinet-sdk": "^3.7.0",
33
+ "@stacks/clarinet-sdk": "^3.9.1",
34
34
  "@stacks/transactions": "^7.2.0",
35
35
  "ansicolor": "^2.0.3",
36
- "fast-check": "^3.20.0",
36
+ "fast-check": "^4.3.0",
37
37
  "toml": "^3.0.0",
38
- "yaml": "^2.6.1"
38
+ "yaml": "^2.8.1"
39
39
  },
40
40
  "devDependencies": {
41
- "@hirosystems/clarinet-sdk-wasm": "^3.7.0",
42
- "@types/jest": "^29.5.12",
43
- "jest": "^29.7.0",
44
- "ts-jest": "^29.2.5",
45
- "typescript": "^5.5.4"
41
+ "@stacks/clarinet-sdk-wasm": "^3.9.1",
42
+ "@types/jest": "^30.0.0",
43
+ "jest": "^30.2.0",
44
+ "ts-jest": "^29.4.5",
45
+ "typescript": "^5.9.3"
46
46
  }
47
47
  }