@stacks/rendezvous 0.1.3 → 0.3.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
@@ -28,16 +28,11 @@ root
28
28
 
29
29
  ### Installation
30
30
 
31
- ---
32
-
33
- **Install the package locally**
34
-
35
31
  ```
36
- npm install "https://github.com/stacks-network/rendezvous.git"
37
- npm run build
32
+ npm install @stacks/rendezvous
38
33
  ```
39
34
 
40
- Run the fuzzer locally:
35
+ ### Usage
41
36
 
42
37
  ```
43
38
  npx rv <path-to-clarinet-project> <contract-name> <type>
@@ -45,22 +40,6 @@ npx rv <path-to-clarinet-project> <contract-name> <type>
45
40
 
46
41
  ---
47
42
 
48
- **Install the package globally**
49
-
50
- ```
51
- git clone https://github.com/stacks-network/rendezvous
52
- npm install
53
- npm install --global .
54
- ```
55
-
56
- Run the fuzzer from anywhere on your system:
57
-
58
- ```
59
- rv <path-to-clarinet-project> <contract-name> <type>
60
- ```
61
-
62
- ---
63
-
64
43
  ### Configuration
65
44
 
66
45
  **Positional arguments:**
package/dist/citizen.js CHANGED
@@ -19,17 +19,17 @@ const path_1 = require("path");
19
19
  const yaml_1 = __importDefault(require("yaml"));
20
20
  const clarinet_sdk_1 = require("@hirosystems/clarinet-sdk");
21
21
  /**
22
- * Prepares the simnet environment and assures the target contract is treated
23
- * as a first-class citizen, by combining it with its tests. This function
24
- * handles:
22
+ * Prepares the simnet instance and assures the target contract's corresponding
23
+ * test contract is treated as a first-class citizen, relying on their
24
+ * concatenation. This function handles:
25
25
  * - Contract sorting by epoch based on the deployment plan.
26
26
  * - Combining the target contract with its tests and deploying all contracts
27
27
  * to the simnet.
28
28
  *
29
- * @param manifestDir - The relative path to the manifest directory.
30
- * @param sutContractName - The target contract name.
29
+ * @param manifestDir The relative path to the manifest directory.
30
+ * @param sutContractName The target contract name.
31
31
  * @returns The initialized simnet instance with all contracts deployed, with
32
- * the target contract treated as a first-class citizen.
32
+ * the test contract treated as a first-class citizen of the target contract.
33
33
  */
34
34
  const issueFirstClassCitizenship = (manifestDir, sutContractName) => __awaiter(void 0, void 0, void 0, function* () {
35
35
  const manifestPath = (0, path_1.join)(manifestDir, "Clarinet.toml");
@@ -43,8 +43,8 @@ const issueFirstClassCitizenship = (manifestDir, sutContractName) => __awaiter(v
43
43
  const sortedContractsByEpoch = (0, exports.groupContractsByEpochFromSimnetPlan)(simnetPlan);
44
44
  yield simnet.initEmptySession();
45
45
  // Combine the target contract with its tests into a single contract. The
46
- // resulting contract will replace the target contract in the simnet. This
47
- // map stores the contract name and its corresponding source code.
46
+ // resulting contract will replace the target contract in the simnet.
47
+ /** The contract names mapped to the concatenated source code. */
48
48
  const rendezvousSources = new Map([sutContractName]
49
49
  .map((contractName) => (0, exports.buildRendezvousData)(simnetPlan, contractName, manifestDir))
50
50
  .map((rendezvousContractData) => [
@@ -58,7 +58,7 @@ const issueFirstClassCitizenship = (manifestDir, sutContractName) => __awaiter(v
58
58
  exports.issueFirstClassCitizenship = issueFirstClassCitizenship;
59
59
  /**
60
60
  * Groups contracts by epoch from the simnet plan.
61
- * @param simnetPlan - The simnet plan.
61
+ * @param simnetPlan The simnet plan.
62
62
  * @returns A record of contracts grouped by epoch. The record key is the epoch
63
63
  * string, and the value is an array of contracts. Each contract is represented
64
64
  * as a record with the contract name as the key and a record containing the
@@ -86,10 +86,10 @@ const groupContractsByEpochFromSimnetPlan = (simnetPlan) => {
86
86
  };
87
87
  exports.groupContractsByEpochFromSimnetPlan = groupContractsByEpochFromSimnetPlan;
88
88
  /**
89
- * Deploy the contracts to the simnet in the correct order.
90
- * @param simnet - The simnet instance.
91
- * @param contractsByEpoch - The record of contracts by epoch.
92
- * @param getContractSourceFn - The function to retrieve the contract source.
89
+ * Deploys the contracts to the simnet in the correct order.
90
+ * @param simnet The simnet instance.
91
+ * @param contractsByEpoch The record of contracts by epoch.
92
+ * @param getContractSourceFn The function to retrieve the contract source.
93
93
  */
94
94
  const deployContracts = (simnet, contractsByEpoch, getContractSourceFn) => __awaiter(void 0, void 0, void 0, function* () {
95
95
  for (const [epoch, contracts] of Object.entries(contractsByEpoch)) {
@@ -109,18 +109,19 @@ const deployContracts = (simnet, contractsByEpoch, getContractSourceFn) => __awa
109
109
  }
110
110
  });
111
111
  /**
112
- * Conditionally retrieve the contract source based on whether the contract is
112
+ * Conditionally retrieves the contract source based on whether the contract is
113
113
  * a SUT contract or not.
114
- * @param sutContractNames - The list of SUT contract names.
115
- * @param rendezvousMap - The rendezvous map.
116
- * @param contractName - The contract name.
117
- * @param contractProps - The contract properties.
118
- * @param manifestDir - The relative path to the manifest directory.
119
- * @returns The contract source.
114
+ * @param targetContractNames The list of target contract names.
115
+ * @param rendezvousSourcesMap The contract names mapped to the concatenated
116
+ * source code.
117
+ * @param contractName The contract name.
118
+ * @param contractProps The contract deployment properties.
119
+ * @param manifestDir The relative path to the manifest directory.
120
+ * @returns The contract source code.
120
121
  */
121
- const getContractSource = (sutContractNames, rendezvousMap, contractName, contractProps, manifestDir) => {
122
- if (sutContractNames.includes(contractName)) {
123
- const contractSource = rendezvousMap.get(contractName);
122
+ const getContractSource = (targetContractNames, rendezvousSourcesMap, contractName, contractProps, manifestDir) => {
123
+ if (targetContractNames.includes(contractName)) {
124
+ const contractSource = rendezvousSourcesMap.get(contractName);
124
125
  if (!contractSource) {
125
126
  throw new Error(`Contract source not found for ${contractName}`);
126
127
  }
@@ -134,11 +135,11 @@ const getContractSource = (sutContractNames, rendezvousMap, contractName, contra
134
135
  };
135
136
  exports.getContractSource = getContractSource;
136
137
  /**
137
- * Build the Rendezvous data.
138
+ * Builds the Rendezvous data.
138
139
  * @param simnetPlan The parsed simnet plan.
139
140
  * @param contractName The contract name.
140
141
  * @param manifestDir The relative path to the manifest directory.
141
- * @returns The Rendezvous data representing an object. The returned object
142
+ * @returns The Rendezvous data representing a record. The returned record
142
143
  * contains the Rendezvous source code and the Rendezvous contract name.
143
144
  */
144
145
  const buildRendezvousData = (simnetPlan, contractName, manifestDir) => {
@@ -157,7 +158,7 @@ const buildRendezvousData = (simnetPlan, contractName, manifestDir) => {
157
158
  };
158
159
  exports.buildRendezvousData = buildRendezvousData;
159
160
  /**
160
- * Get the contract source code from the simnet plan.
161
+ * Retrieves the contract source code using the simnet plan.
161
162
  * @param simnetPlan The parsed simnet plan.
162
163
  * @param manifestDir The relative path to the manifest directory.
163
164
  * @param sutContractName The target contract name.
@@ -179,7 +180,7 @@ const getSimnetPlanContractSource = (simnetPlan, manifestDir, sutContractName) =
179
180
  }).toString();
180
181
  };
181
182
  /**
182
- * Get the test contract source code.
183
+ * Retrieves the test contract source code.
183
184
  * @param simnetPlan The parsed simnet plan.
184
185
  * @param sutContractName The target contract name.
185
186
  * @param manifestDir The relative path to the manifest directory.
@@ -209,13 +210,13 @@ const getTestContractSource = (simnetPlan, sutContractName, manifestDir) => {
209
210
  };
210
211
  exports.getTestContractSource = getTestContractSource;
211
212
  /**
212
- * Schedule a Rendezvous between the System Under Test (`SUT`) and the
213
- * invariants.
214
- * @param sutContractSource The SUT contract source code.
215
- * @param invariants The invariants contract source code.
213
+ * Schedules a Rendezvous between the System Under Test (`SUT`) and the test
214
+ * contract.
215
+ * @param targetContractSource The target contract source code.
216
+ * @param tests The corresponding test contract source code.
216
217
  * @returns The Rendezvous source code.
217
218
  */
218
- function scheduleRendezvous(sutContractSource, invariants) {
219
+ function scheduleRendezvous(targetContractSource, tests) {
219
220
  /**
220
221
  * The `context` map tracks how many times each function has been called.
221
222
  * This data can be useful for invariant tests to check behavior over time.
@@ -227,5 +228,5 @@ function scheduleRendezvous(sutContractSource, invariants) {
227
228
 
228
229
  (define-public (update-context (function-name (string-ascii 100)) (called uint))
229
230
  (ok (map-set context function-name {called: called})))`;
230
- return `${sutContractSource}\n\n${context}\n\n${invariants}`;
231
+ return `${targetContractSource}\n\n${context}\n\n${tests}`;
231
232
  }
@@ -1,38 +1,29 @@
1
1
  "use strict";
2
- /**
3
- * Heatstrokes Reporter
4
- *
5
- * This reporter integrates with `fast-check` to provide detailed and formatted
6
- * outputs for failed property-based tests. It captures key information such as
7
- * the contract, functions, arguments, outputs, and the specific invariant that
8
- * failed, enabling quick identification of issues.
9
- */
10
2
  Object.defineProperty(exports, "__esModule", { value: true });
11
3
  exports.reporter = reporter;
4
+ const ansicolor_1 = require("ansicolor");
5
+ const shared_1 = require("./shared");
12
6
  /**
13
- * Reports the details of a failed property-based test run.
14
- *
15
- * This function provides a detailed report when a fuzzing test fails including
16
- * the contract, functions, arguments, outputs, and the specific invariant that
17
- * failed.
7
+ * Heatstrokes Reporter
18
8
  *
19
- * @param runDetails - The details of the test run provided by fast-check.
20
- * @property runDetails.failed - Indicates if the property test failed.
21
- * @property runDetails.counterexample - The input that caused the failure.
22
- * @property runDetails.numRuns - The number of test cases that were run.
23
- * @property runDetails.seed - The seed used to generate the test cases.
24
- * @property runDetails.path - The path to reproduce the failing test.
25
- * @property runDetails.error - The error thrown during the test.
9
+ * Provides a detailed report when a test run test ends. In case of failure,
10
+ * includes the contract, functions, arguments, outputs, and the specific
11
+ * invariant or property test that failed.
12
+ * @param runDetails The details of the test run provided by fast-check.
13
+ * @property `runDetails.failed`: Indicates if the test run failed.
14
+ * @property `runDetails.counterexample`: The input that caused the failure.
15
+ * @property `runDetails.numRuns`: The number of test cases that were run.
16
+ * @property `runDetails.seed`: The replay seed for reproducing the failure.
17
+ * @property `runDetails.path`: The replay path for reproducing the failure.
18
+ * @property `runDetails.error`: The error thrown during the test.
19
+ * @param radio The event emitter to log messages.
20
+ * @param type The type of test that failed: invariant or property.
21
+ * @returns void
26
22
  */
27
- const ansicolor_1 = require("ansicolor");
28
- const shared_1 = require("./shared");
29
- function reporter(
30
- //@ts-ignore
31
- runDetails, radio, type) {
23
+ function reporter(runDetails, radio, type) {
32
24
  var _a, _b;
33
25
  if (runDetails.failed) {
34
26
  // Report general run data.
35
- const r = runDetails.counterexample[0];
36
27
  radio.emit("logFailure", `\nError: Property failed after ${runDetails.numRuns} tests.`);
37
28
  radio.emit("logFailure", `Seed : ${runDetails.seed}`);
38
29
  if (runDetails.path) {
@@ -40,15 +31,26 @@ runDetails, radio, type) {
40
31
  }
41
32
  switch (type) {
42
33
  case "invariant": {
34
+ const r = runDetails.counterexample[0];
43
35
  // Report specific run data for the invariant testing type.
44
36
  radio.emit("logFailure", `\nCounterexample:`);
45
37
  radio.emit("logFailure", `- Contract : ${(0, shared_1.getContractNameFromContractId)(r.rendezvousContractId)}`);
46
- radio.emit("logFailure", `- Function : ${r.selectedFunction.name} (${r.selectedFunction.access})`);
47
- radio.emit("logFailure", `- Arguments: ${JSON.stringify(r.functionArgsArb)}`);
48
- radio.emit("logFailure", `- Caller : ${r.sutCaller[0]}`);
49
- radio.emit("logFailure", `- Outputs : ${JSON.stringify(r.selectedFunction.outputs)}`);
38
+ radio.emit("logFailure", `- Functions: ${r.selectedFunctions
39
+ .map((selectedFunction) => selectedFunction.name)
40
+ .join(", ")} (${r.selectedFunctions
41
+ .map((selectedFunction) => selectedFunction.access)
42
+ .join(", ")})`);
43
+ radio.emit("logFailure", `- Arguments: ${r.selectedFunctionsArgsList
44
+ .map((selectedFunctionArgs) => JSON.stringify(selectedFunctionArgs))
45
+ .join(", ")}`);
46
+ radio.emit("logFailure", `- Callers : ${r.sutCallers
47
+ .map((sutCaller) => sutCaller[0])
48
+ .join(", ")}`);
49
+ radio.emit("logFailure", `- Outputs : ${r.selectedFunctions
50
+ .map((selectedFunction) => JSON.stringify(selectedFunction.outputs))
51
+ .join(", ")}`);
50
52
  radio.emit("logFailure", `- Invariant: ${r.selectedInvariant.name} (${r.selectedInvariant.access})`);
51
- radio.emit("logFailure", `- Arguments: ${JSON.stringify(r.invariantArgsArb)}`);
53
+ radio.emit("logFailure", `- Arguments: ${JSON.stringify(r.invariantArgs)}`);
52
54
  radio.emit("logFailure", `- Caller : ${r.invariantCaller[0]}`);
53
55
  radio.emit("logFailure", `\nWhat happened? Rendezvous went on a rampage and found a weak spot:\n`);
54
56
  const formattedError = `The invariant "${r.selectedInvariant.name}" returned:\n\n${(_a = runDetails.error) === null || _a === void 0 ? void 0 : _a.toString().split("\n").map((line) => " " + line).join("\n")}\n`;
@@ -56,11 +58,12 @@ runDetails, radio, type) {
56
58
  break;
57
59
  }
58
60
  case "test": {
61
+ const r = runDetails.counterexample[0];
59
62
  // Report specific run data for the property testing type.
60
63
  radio.emit("logFailure", `\nCounterexample:`);
61
64
  radio.emit("logFailure", `- Test Contract : ${(0, shared_1.getContractNameFromContractId)(r.testContractId)}`);
62
65
  radio.emit("logFailure", `- Test Function : ${r.selectedTestFunction.name} (${r.selectedTestFunction.access})`);
63
- radio.emit("logFailure", `- Arguments : ${JSON.stringify(r.functionArgsArb)}`);
66
+ radio.emit("logFailure", `- Arguments : ${JSON.stringify(r.functionArgs)}`);
64
67
  radio.emit("logFailure", `- Caller : ${r.testCaller[0]}`);
65
68
  radio.emit("logFailure", `- Outputs : ${JSON.stringify(r.selectedTestFunction.outputs)}`);
66
69
  radio.emit("logFailure", `\nWhat happened? Rendezvous went on a rampage and found a weak spot:\n`);
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/invariant.js CHANGED
@@ -9,7 +9,22 @@ const transactions_1 = require("@stacks/transactions");
9
9
  const heatstroke_1 = require("./heatstroke");
10
10
  const fast_check_1 = __importDefault(require("fast-check"));
11
11
  const ansicolor_1 = require("ansicolor");
12
- const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllFunctions, seed, path, runs, radio) => {
12
+ const traits_1 = require("./traits");
13
+ /**
14
+ * Runs invariant testing on the target contract and logs the progress. Reports
15
+ * the test results through a custom reporter.
16
+ * @param simnet The Simnet instance.
17
+ * @param targetContractName The name of the target contract.
18
+ * @param rendezvousList The list of contract IDs for each target contract.
19
+ * @param rendezvousAllFunctions The map of all function interfaces for each
20
+ * target contract.
21
+ * @param seed The seed for reproducible invariant testing.
22
+ * @param path The path for reproducible invariant testing.
23
+ * @param runs The number of test runs.
24
+ * @param radio The custom logging event emitter.
25
+ * @returns void
26
+ */
27
+ const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousAllFunctions, seed, path, runs, radio) => {
13
28
  // A map where the keys are the Rendezvous identifiers and the values are
14
29
  // arrays of their SUT (System Under Test) functions. This map will be used
15
30
  // to access the SUT functions for each Rendezvous contract afterwards.
@@ -18,35 +33,52 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
18
33
  // arrays of their invariant functions. This map will be used to access the
19
34
  // invariant functions for each Rendezvous contract afterwards.
20
35
  const rendezvousInvariantFunctions = filterInvariantFunctions(rendezvousAllFunctions);
36
+ // The Rendezvous identifier is the first one in the list. Only one contract
37
+ // can be fuzzed at a time.
38
+ const rendezvousContractId = rendezvousList[0];
39
+ const traitReferenceSutFunctions = rendezvousSutFunctions
40
+ .get(rendezvousContractId)
41
+ .filter((fn) => (0, traits_1.isTraitReferenceFunction)(fn));
42
+ const traitReferenceInvariantFunctions = rendezvousInvariantFunctions
43
+ .get(rendezvousContractId)
44
+ .filter((fn) => (0, traits_1.isTraitReferenceFunction)(fn));
45
+ const projectTraitImplementations = (0, traits_1.extractProjectTraitImplementations)(simnet);
46
+ if (Object.entries(projectTraitImplementations).length === 0 &&
47
+ (traitReferenceSutFunctions.length > 0 ||
48
+ traitReferenceInvariantFunctions.length > 0)) {
49
+ const foundTraitReferenceMessage = traitReferenceSutFunctions.length > 0 &&
50
+ traitReferenceInvariantFunctions.length > 0
51
+ ? "public functions and invariants"
52
+ : traitReferenceSutFunctions.length > 0
53
+ ? "public functions"
54
+ : "invariants";
55
+ radio.emit("logMessage", (0, ansicolor_1.red)(`\nFound ${foundTraitReferenceMessage} referencing traits, but no trait implementations were found in the project.
56
+ \nNote: You can add contracts implementing traits either as project contracts or as Clarinet requirements. For more details, visit: https://www.hiro.so/clarinet/.
57
+ \n`));
58
+ return;
59
+ }
60
+ const enrichedSutFunctionsInterfaces = traitReferenceSutFunctions.length > 0
61
+ ? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(targetContractName), (0, traits_1.buildTraitReferenceMap)(rendezvousSutFunctions.get(rendezvousContractId)), rendezvousSutFunctions.get(rendezvousContractId), rendezvousContractId)
62
+ : rendezvousSutFunctions;
63
+ const enrichedInvariantFunctionsInterfaces = traitReferenceInvariantFunctions.length > 0
64
+ ? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(targetContractName), (0, traits_1.buildTraitReferenceMap)(rendezvousInvariantFunctions.get(rendezvousContractId)), rendezvousInvariantFunctions.get(rendezvousContractId), rendezvousContractId)
65
+ : rendezvousInvariantFunctions;
21
66
  // Set up local context to track SUT function call counts.
22
- const localContext = (0, exports.initializeLocalContext)(rendezvousSutFunctions);
67
+ const localContext = (0, exports.initializeLocalContext)(enrichedSutFunctionsInterfaces);
23
68
  // Set up context in simnet by initializing state for SUT.
24
- (0, exports.initializeClarityContext)(simnet, rendezvousSutFunctions);
25
- radio.emit("logMessage", `\nStarting invariant testing type for the ${sutContractName} contract...\n`);
69
+ (0, exports.initializeClarityContext)(simnet, enrichedSutFunctionsInterfaces);
70
+ radio.emit("logMessage", `\nStarting invariant testing type for the ${targetContractName} contract...\n`);
26
71
  const simnetAccounts = simnet.getAccounts();
27
72
  const eligibleAccounts = new Map([...simnetAccounts].filter(([key]) => key !== "faucet"));
28
73
  const simnetAddresses = Array.from(simnetAccounts.values());
29
- // The Rendezvous identifier is the first one in the list. Only one contract
30
- // can be fuzzed at a time.
31
- const rendezvousContractId = rendezvousList[0];
32
- const functions = (0, shared_1.getFunctionsListForContract)(rendezvousSutFunctions, rendezvousContractId);
33
- const invariantFunctions = (0, shared_1.getFunctionsListForContract)(rendezvousInvariantFunctions, rendezvousContractId);
74
+ const functions = (0, shared_1.getFunctionsListForContract)(enrichedSutFunctionsInterfaces, rendezvousContractId);
75
+ const invariants = (0, shared_1.getFunctionsListForContract)(enrichedInvariantFunctionsInterfaces, rendezvousContractId);
34
76
  if ((functions === null || functions === void 0 ? void 0 : functions.length) === 0) {
35
- radio.emit("logMessage", (0, ansicolor_1.red)(`No public functions found for the "${sutContractName}" contract. Without public functions, no state transitions can happen inside the contract, and the invariant test is not meaningful.\n`));
36
- return;
37
- }
38
- if ((invariantFunctions === null || invariantFunctions === void 0 ? void 0 : invariantFunctions.length) === 0) {
39
- radio.emit("logMessage", (0, ansicolor_1.red)(`No invariant functions found for the "${sutContractName}" contract. Beware, for your contract may be exposed to unforeseen issues.\n`));
77
+ 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`));
40
78
  return;
41
79
  }
42
- const eligibleFunctions = functions.filter((fn) => !(0, shared_1.isTraitReferenceFunction)(fn));
43
- const eligibleInvariants = invariantFunctions.filter((fn) => !(0, shared_1.isTraitReferenceFunction)(fn));
44
- if (eligibleFunctions.length === 0) {
45
- radio.emit("logMessage", (0, ansicolor_1.red)(`No eligible public functions found for the "${sutContractName}" contract. Note: trait references are not supported.\n`));
46
- return;
47
- }
48
- if (eligibleInvariants.length === 0) {
49
- radio.emit("logMessage", (0, ansicolor_1.red)(`No eligible invariant functions found for the "${sutContractName}" contract. Note: trait references are not supported.\n`));
80
+ if ((invariants === null || invariants === void 0 ? void 0 : invariants.length) === 0) {
81
+ radio.emit("logMessage", (0, ansicolor_1.red)(`No invariant functions found for the "${targetContractName}" contract. Beware, for your contract may be exposed to unforeseen issues.\n`));
50
82
  return;
51
83
  }
52
84
  const radioReporter = (runDetails) => {
@@ -58,20 +90,25 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
58
90
  // to the first contract in the list. The arbitrary is still needed,
59
91
  // being used for reporting purposes in `heatstroke.ts`.
60
92
  rendezvousContractId: fast_check_1.default.constant(rendezvousContractId),
61
- sutCaller: fast_check_1.default.constantFrom(...eligibleAccounts.entries()),
62
93
  invariantCaller: fast_check_1.default.constantFrom(...eligibleAccounts.entries()),
63
94
  canMineBlocks: fast_check_1.default.boolean(),
64
95
  })
65
96
  .chain((r) => fast_check_1.default
66
97
  .record({
67
- selectedFunction: fast_check_1.default.constantFrom(...eligibleFunctions),
68
- selectedInvariant: fast_check_1.default.constantFrom(...eligibleInvariants),
98
+ selectedFunctions: fast_check_1.default.array(fast_check_1.default.constantFrom(...functions), {
99
+ minLength: 1, // At least one function must be selected.
100
+ }),
101
+ selectedInvariant: fast_check_1.default.constantFrom(...invariants),
69
102
  })
70
103
  .map((selectedFunctions) => (Object.assign(Object.assign({}, r), selectedFunctions))))
71
104
  .chain((r) => fast_check_1.default
72
105
  .record({
73
- functionArgsArb: fast_check_1.default.tuple(...(0, shared_1.functionToArbitrary)(r.selectedFunction, simnetAddresses)),
74
- invariantArgsArb: fast_check_1.default.tuple(...(0, shared_1.functionToArbitrary)(r.selectedInvariant, simnetAddresses)),
106
+ sutCallers: fast_check_1.default.array(fast_check_1.default.constantFrom(...eligibleAccounts.entries()), {
107
+ minLength: r.selectedFunctions.length,
108
+ maxLength: r.selectedFunctions.length,
109
+ }),
110
+ selectedFunctionsArgsList: fast_check_1.default.tuple(...r.selectedFunctions.map((selectedFunction) => fast_check_1.default.tuple(...(0, shared_1.functionToArbitrary)(selectedFunction, simnetAddresses, projectTraitImplementations)))),
111
+ invariantArgs: fast_check_1.default.tuple(...(0, shared_1.functionToArbitrary)(r.selectedInvariant, simnetAddresses, projectTraitImplementations)),
75
112
  })
76
113
  .map((args) => (Object.assign(Object.assign({}, r), args))))
77
114
  .chain((r) => fast_check_1.default
@@ -90,58 +127,60 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
90
127
  : fast_check_1.default.constant(0),
91
128
  })
92
129
  .map((burnBlocks) => (Object.assign(Object.assign({}, r), burnBlocks)))), (r) => {
93
- const selectedFunctionArgs = (0, shared_1.argsToCV)(r.selectedFunction, r.functionArgsArb);
94
- const selectedInvariantArgs = (0, shared_1.argsToCV)(r.selectedInvariant, r.invariantArgsArb);
95
- const printedFunctionArgs = r.functionArgsArb
96
- .map((arg) => {
130
+ const selectedFunctionsArgsCV = r.selectedFunctions.map((selectedFunction, index) => (0, shared_1.argsToCV)(selectedFunction, r.selectedFunctionsArgsList[index]));
131
+ const selectedInvariantArgsCV = (0, shared_1.argsToCV)(r.selectedInvariant, r.invariantArgs);
132
+ r.selectedFunctions.forEach((selectedFunction, index) => {
133
+ const [sutCallerWallet, sutCallerAddress] = r.sutCallers[index];
134
+ const printedFunctionArgs = r.selectedFunctionsArgsList[index]
135
+ .map((arg) => {
136
+ try {
137
+ return typeof arg === "object"
138
+ ? JSON.stringify(arg)
139
+ : arg.toString();
140
+ }
141
+ catch (_a) {
142
+ return "[Circular]";
143
+ }
144
+ })
145
+ .join(" ");
97
146
  try {
98
- return typeof arg === "object"
99
- ? JSON.stringify(arg)
100
- : arg.toString();
101
- }
102
- catch (_a) {
103
- return "[Circular]";
147
+ const { result: functionCallResult } = simnet.callPublicFn(r.rendezvousContractId, selectedFunction.name, selectedFunctionsArgsCV[index], sutCallerAddress);
148
+ const functionCallResultJson = (0, transactions_1.cvToJSON)(functionCallResult);
149
+ if (functionCallResultJson.success) {
150
+ localContext[r.rendezvousContractId][selectedFunction.name]++;
151
+ simnet.callPublicFn(r.rendezvousContractId, "update-context", [
152
+ transactions_1.Cl.stringAscii(selectedFunction.name),
153
+ transactions_1.Cl.uint(localContext[r.rendezvousContractId][selectedFunction.name]),
154
+ ], simnet.deployer);
155
+ radio.emit("logMessage", `₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
156
+ `Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
157
+ (0, ansicolor_1.dim)(`${sutCallerWallet} `) +
158
+ `${targetContractName} ` +
159
+ `${(0, ansicolor_1.underline)(selectedFunction.name)} ` +
160
+ printedFunctionArgs);
161
+ }
162
+ else {
163
+ radio.emit("logMessage", (0, ansicolor_1.dim)(`₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
164
+ `Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
165
+ `${sutCallerWallet} ` +
166
+ `${targetContractName} ` +
167
+ `${(0, ansicolor_1.underline)(selectedFunction.name)} ` +
168
+ printedFunctionArgs));
169
+ }
104
170
  }
105
- })
106
- .join(" ");
107
- const [sutCallerWallet, sutCallerAddress] = r.sutCaller;
108
- try {
109
- const { result: functionCallResult } = simnet.callPublicFn(r.rendezvousContractId, r.selectedFunction.name, selectedFunctionArgs, sutCallerAddress);
110
- const functionCallResultJson = (0, transactions_1.cvToJSON)(functionCallResult);
111
- if (functionCallResultJson.success) {
112
- localContext[r.rendezvousContractId][r.selectedFunction.name]++;
113
- simnet.callPublicFn(r.rendezvousContractId, "update-context", [
114
- transactions_1.Cl.stringAscii(r.selectedFunction.name),
115
- transactions_1.Cl.uint(localContext[r.rendezvousContractId][r.selectedFunction.name]),
116
- ], simnet.deployer);
117
- radio.emit("logMessage", `₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
118
- `Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
119
- (0, ansicolor_1.dim)(`${sutCallerWallet} `) +
120
- `${sutContractName} ` +
121
- `${(0, ansicolor_1.underline)(r.selectedFunction.name)} ` +
122
- printedFunctionArgs);
123
- }
124
- else {
171
+ catch (error) {
172
+ // If the function call fails with a runtime error, log a dimmed
173
+ // message. Since the public function result is ignored, there's
174
+ // no need to throw an error.
125
175
  radio.emit("logMessage", (0, ansicolor_1.dim)(`₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
126
176
  `Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
127
177
  `${sutCallerWallet} ` +
128
- `${sutContractName} ` +
129
- `${(0, ansicolor_1.underline)(r.selectedFunction.name)} ` +
178
+ `${targetContractName} ` +
179
+ `${(0, ansicolor_1.underline)(selectedFunction.name)} ` +
130
180
  printedFunctionArgs));
131
181
  }
132
- }
133
- catch (error) {
134
- // If the function call fails with a runtime error, log a dimmed
135
- // message. Since the public function result is ignored, there's
136
- // no need to throw an error.
137
- radio.emit("logMessage", (0, ansicolor_1.dim)(`₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
138
- `Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
139
- `${sutCallerWallet} ` +
140
- `${sutContractName} ` +
141
- `${(0, ansicolor_1.underline)(r.selectedFunction.name)} ` +
142
- printedFunctionArgs));
143
- }
144
- const printedInvariantArgs = r.invariantArgsArb
182
+ });
183
+ const printedInvariantArgs = r.invariantArgs
145
184
  .map((arg) => {
146
185
  try {
147
186
  return typeof arg === "object"
@@ -155,19 +194,19 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
155
194
  .join(" ");
156
195
  const [invariantCallerWallet, invariantCallerAddress] = r.invariantCaller;
157
196
  try {
158
- const { result: invariantCallResult } = simnet.callReadOnlyFn(r.rendezvousContractId, r.selectedInvariant.name, selectedInvariantArgs, invariantCallerAddress);
197
+ const { result: invariantCallResult } = simnet.callReadOnlyFn(r.rendezvousContractId, r.selectedInvariant.name, selectedInvariantArgsCV, invariantCallerAddress);
159
198
  const invariantCallResultJson = (0, transactions_1.cvToJSON)(invariantCallResult);
160
199
  if (invariantCallResultJson.value === true) {
161
200
  radio.emit("logMessage", `₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
162
201
  `Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
163
202
  `${(0, ansicolor_1.dim)(invariantCallerWallet)} ` +
164
203
  `${(0, ansicolor_1.green)("[PASS]")} ` +
165
- `${sutContractName} ` +
204
+ `${targetContractName} ` +
166
205
  `${(0, ansicolor_1.underline)(r.selectedInvariant.name)} ` +
167
206
  printedInvariantArgs);
168
207
  }
169
208
  if (!invariantCallResultJson.value) {
170
- throw new Error(`Invariant failed for ${sutContractName} contract: "${r.selectedInvariant.name}" returned ${invariantCallResultJson.value}`);
209
+ throw new Error(`Invariant failed for ${targetContractName} contract: "${r.selectedInvariant.name}" returned ${invariantCallResultJson.value}`);
171
210
  }
172
211
  }
173
212
  catch (error) {
@@ -178,7 +217,7 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
178
217
  `Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
179
218
  `${invariantCallerWallet} ` +
180
219
  `[FAIL] ` +
181
- `${sutContractName} ` +
220
+ `${targetContractName} ` +
182
221
  `${(0, ansicolor_1.underline)(r.selectedInvariant.name)} ` +
183
222
  printedInvariantArgs));
184
223
  // Re-throw the error for fast-check to catch and process.
@@ -197,7 +236,7 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
197
236
  };
198
237
  exports.checkInvariants = checkInvariants;
199
238
  /**
200
- * Initialize the local context, setting the number of times each function
239
+ * Initializes the local context, setting the number of times each function
201
240
  * has been called to zero.
202
241
  * @param rendezvousSutFunctions The Rendezvous functions.
203
242
  * @returns The initialized local context.
@@ -218,14 +257,15 @@ const initializeClarityContext = (simnet, rendezvousSutFunctions) => rendezvousS
218
257
  });
219
258
  exports.initializeClarityContext = initializeClarityContext;
220
259
  /**
221
- * Filter the System Under Test (`SUT`) functions from the map of all
222
- * contract functions.
260
+ * Filter the System Under Test (`SUT`) functions from the map of all contract
261
+ * functions.
223
262
  *
224
263
  * The SUT functions are the ones that have `public` access since they are
225
264
  * capable of changing the contract state, and they are not test functions.
226
265
  * @param allFunctionsMap The map containing all the functions for each
266
+ * Rendezvous contract.
267
+ * @returns A map containing the filtered SUT functions for each Rendezvous
227
268
  * contract.
228
- * @returns A map containing only the SUT functions for each contract.
229
269
  */
230
270
  const filterSutFunctions = (allFunctionsMap) => new Map(Array.from(allFunctionsMap, ([contractId, functions]) => [
231
271
  contractId,
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stacks/rendezvous",
3
- "version": "0.1.3",
3
+ "version": "0.3.0",
4
4
  "description": "Meet your contract's vulnerabilities head-on.",
5
5
  "main": "app.js",
6
6
  "bin": {
@@ -8,7 +8,7 @@
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 tsc --project tsconfig.json && npx jest",
11
+ "test": "npx jest",
12
12
  "test:coverage": "npx tsc --project tsconfig.json && npx jest --coverage"
13
13
  },
14
14
  "keywords": [
@@ -25,6 +25,10 @@
25
25
  "README.md",
26
26
  "LICENSE"
27
27
  ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/stacks-network/rendezvous.git"
31
+ },
28
32
  "dependencies": {
29
33
  "@hirosystems/clarinet-sdk": "2.12.0",
30
34
  "@stacks/transactions": "^6.16.1",
@@ -36,7 +40,7 @@
36
40
  "@hirosystems/clarinet-sdk-wasm": "2.12.0",
37
41
  "@types/jest": "^29.5.12",
38
42
  "jest": "^29.7.0",
39
- "ts-jest": "^29.2.3",
43
+ "ts-jest": "^29.2.5",
40
44
  "typescript": "^5.5.4"
41
45
  }
42
46
  }