@stacks/rendezvous 0.2.0 → 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/dist/citizen.js +33 -32
- package/dist/heatstroke.js +35 -32
- package/dist/heatstroke.types.js +2 -0
- package/dist/invariant.js +85 -63
- package/dist/package.json +6 -2
- package/dist/property.js +38 -24
- package/dist/shared.js +44 -35
- package/dist/traits.js +8 -7
- package/package.json +6 -2
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
|
|
23
|
-
* as a first-class citizen,
|
|
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
|
|
30
|
-
* @param sutContractName
|
|
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
|
|
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.
|
|
47
|
-
|
|
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
|
|
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
|
-
*
|
|
89
|
+
* Deploys the contracts to the simnet in the correct order.
|
|
90
90
|
* @param simnet The simnet instance.
|
|
91
|
-
* @param contractsByEpoch
|
|
92
|
-
* @param getContractSourceFn
|
|
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
|
|
112
|
+
* Conditionally retrieves the contract source based on whether the contract is
|
|
113
113
|
* a SUT contract or not.
|
|
114
|
-
* @param
|
|
115
|
-
* @param
|
|
116
|
-
*
|
|
117
|
-
* @param
|
|
118
|
-
* @param
|
|
119
|
-
* @
|
|
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 = (
|
|
122
|
-
if (
|
|
123
|
-
const contractSource =
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
213
|
-
*
|
|
214
|
-
* @param
|
|
215
|
-
* @param
|
|
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(
|
|
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 `${
|
|
231
|
+
return `${targetContractSource}\n\n${context}\n\n${tests}`;
|
|
231
232
|
}
|
package/dist/heatstroke.js
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* @
|
|
23
|
-
* @property runDetails.
|
|
24
|
-
* @property runDetails.
|
|
25
|
-
* @property runDetails.
|
|
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
|
-
|
|
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", `-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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.
|
|
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.
|
|
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`);
|
package/dist/invariant.js
CHANGED
|
@@ -10,7 +10,21 @@ const heatstroke_1 = require("./heatstroke");
|
|
|
10
10
|
const fast_check_1 = __importDefault(require("fast-check"));
|
|
11
11
|
const ansicolor_1 = require("ansicolor");
|
|
12
12
|
const traits_1 = require("./traits");
|
|
13
|
-
|
|
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) => {
|
|
14
28
|
// A map where the keys are the Rendezvous identifiers and the values are
|
|
15
29
|
// arrays of their SUT (System Under Test) functions. This map will be used
|
|
16
30
|
// to access the SUT functions for each Rendezvous contract afterwards.
|
|
@@ -44,27 +58,27 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
44
58
|
return;
|
|
45
59
|
}
|
|
46
60
|
const enrichedSutFunctionsInterfaces = traitReferenceSutFunctions.length > 0
|
|
47
|
-
? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(
|
|
61
|
+
? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(targetContractName), (0, traits_1.buildTraitReferenceMap)(rendezvousSutFunctions.get(rendezvousContractId)), rendezvousSutFunctions.get(rendezvousContractId), rendezvousContractId)
|
|
48
62
|
: rendezvousSutFunctions;
|
|
49
63
|
const enrichedInvariantFunctionsInterfaces = traitReferenceInvariantFunctions.length > 0
|
|
50
|
-
? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(
|
|
64
|
+
? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(targetContractName), (0, traits_1.buildTraitReferenceMap)(rendezvousInvariantFunctions.get(rendezvousContractId)), rendezvousInvariantFunctions.get(rendezvousContractId), rendezvousContractId)
|
|
51
65
|
: rendezvousInvariantFunctions;
|
|
52
66
|
// Set up local context to track SUT function call counts.
|
|
53
67
|
const localContext = (0, exports.initializeLocalContext)(enrichedSutFunctionsInterfaces);
|
|
54
68
|
// Set up context in simnet by initializing state for SUT.
|
|
55
69
|
(0, exports.initializeClarityContext)(simnet, enrichedSutFunctionsInterfaces);
|
|
56
|
-
radio.emit("logMessage", `\nStarting invariant testing type for the ${
|
|
70
|
+
radio.emit("logMessage", `\nStarting invariant testing type for the ${targetContractName} contract...\n`);
|
|
57
71
|
const simnetAccounts = simnet.getAccounts();
|
|
58
72
|
const eligibleAccounts = new Map([...simnetAccounts].filter(([key]) => key !== "faucet"));
|
|
59
73
|
const simnetAddresses = Array.from(simnetAccounts.values());
|
|
60
74
|
const functions = (0, shared_1.getFunctionsListForContract)(enrichedSutFunctionsInterfaces, rendezvousContractId);
|
|
61
75
|
const invariants = (0, shared_1.getFunctionsListForContract)(enrichedInvariantFunctionsInterfaces, rendezvousContractId);
|
|
62
76
|
if ((functions === null || functions === void 0 ? void 0 : functions.length) === 0) {
|
|
63
|
-
radio.emit("logMessage", (0, ansicolor_1.red)(`No public functions found for the "${
|
|
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`));
|
|
64
78
|
return;
|
|
65
79
|
}
|
|
66
80
|
if ((invariants === null || invariants === void 0 ? void 0 : invariants.length) === 0) {
|
|
67
|
-
radio.emit("logMessage", (0, ansicolor_1.red)(`No invariant functions found for the "${
|
|
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`));
|
|
68
82
|
return;
|
|
69
83
|
}
|
|
70
84
|
const radioReporter = (runDetails) => {
|
|
@@ -76,20 +90,25 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
76
90
|
// to the first contract in the list. The arbitrary is still needed,
|
|
77
91
|
// being used for reporting purposes in `heatstroke.ts`.
|
|
78
92
|
rendezvousContractId: fast_check_1.default.constant(rendezvousContractId),
|
|
79
|
-
sutCaller: fast_check_1.default.constantFrom(...eligibleAccounts.entries()),
|
|
80
93
|
invariantCaller: fast_check_1.default.constantFrom(...eligibleAccounts.entries()),
|
|
81
94
|
canMineBlocks: fast_check_1.default.boolean(),
|
|
82
95
|
})
|
|
83
96
|
.chain((r) => fast_check_1.default
|
|
84
97
|
.record({
|
|
85
|
-
|
|
98
|
+
selectedFunctions: fast_check_1.default.array(fast_check_1.default.constantFrom(...functions), {
|
|
99
|
+
minLength: 1, // At least one function must be selected.
|
|
100
|
+
}),
|
|
86
101
|
selectedInvariant: fast_check_1.default.constantFrom(...invariants),
|
|
87
102
|
})
|
|
88
103
|
.map((selectedFunctions) => (Object.assign(Object.assign({}, r), selectedFunctions))))
|
|
89
104
|
.chain((r) => fast_check_1.default
|
|
90
105
|
.record({
|
|
91
|
-
|
|
92
|
-
|
|
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)),
|
|
93
112
|
})
|
|
94
113
|
.map((args) => (Object.assign(Object.assign({}, r), args))))
|
|
95
114
|
.chain((r) => fast_check_1.default
|
|
@@ -108,58 +127,60 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
108
127
|
: fast_check_1.default.constant(0),
|
|
109
128
|
})
|
|
110
129
|
.map((burnBlocks) => (Object.assign(Object.assign({}, r), burnBlocks)))), (r) => {
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
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(" ");
|
|
115
146
|
try {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
+
}
|
|
119
170
|
}
|
|
120
|
-
catch (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
.join(" ");
|
|
125
|
-
const [sutCallerWallet, sutCallerAddress] = r.sutCaller;
|
|
126
|
-
try {
|
|
127
|
-
const { result: functionCallResult } = simnet.callPublicFn(r.rendezvousContractId, r.selectedFunction.name, selectedFunctionArgs, sutCallerAddress);
|
|
128
|
-
const functionCallResultJson = (0, transactions_1.cvToJSON)(functionCallResult);
|
|
129
|
-
if (functionCallResultJson.success) {
|
|
130
|
-
localContext[r.rendezvousContractId][r.selectedFunction.name]++;
|
|
131
|
-
simnet.callPublicFn(r.rendezvousContractId, "update-context", [
|
|
132
|
-
transactions_1.Cl.stringAscii(r.selectedFunction.name),
|
|
133
|
-
transactions_1.Cl.uint(localContext[r.rendezvousContractId][r.selectedFunction.name]),
|
|
134
|
-
], simnet.deployer);
|
|
135
|
-
radio.emit("logMessage", `₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
|
|
136
|
-
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
137
|
-
(0, ansicolor_1.dim)(`${sutCallerWallet} `) +
|
|
138
|
-
`${sutContractName} ` +
|
|
139
|
-
`${(0, ansicolor_1.underline)(r.selectedFunction.name)} ` +
|
|
140
|
-
printedFunctionArgs);
|
|
141
|
-
}
|
|
142
|
-
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.
|
|
143
175
|
radio.emit("logMessage", (0, ansicolor_1.dim)(`₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
|
|
144
176
|
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
145
177
|
`${sutCallerWallet} ` +
|
|
146
|
-
`${
|
|
147
|
-
`${(0, ansicolor_1.underline)(
|
|
178
|
+
`${targetContractName} ` +
|
|
179
|
+
`${(0, ansicolor_1.underline)(selectedFunction.name)} ` +
|
|
148
180
|
printedFunctionArgs));
|
|
149
181
|
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// If the function call fails with a runtime error, log a dimmed
|
|
153
|
-
// message. Since the public function result is ignored, there's
|
|
154
|
-
// no need to throw an error.
|
|
155
|
-
radio.emit("logMessage", (0, ansicolor_1.dim)(`₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
|
|
156
|
-
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
157
|
-
`${sutCallerWallet} ` +
|
|
158
|
-
`${sutContractName} ` +
|
|
159
|
-
`${(0, ansicolor_1.underline)(r.selectedFunction.name)} ` +
|
|
160
|
-
printedFunctionArgs));
|
|
161
|
-
}
|
|
162
|
-
const printedInvariantArgs = r.invariantArgsArb
|
|
182
|
+
});
|
|
183
|
+
const printedInvariantArgs = r.invariantArgs
|
|
163
184
|
.map((arg) => {
|
|
164
185
|
try {
|
|
165
186
|
return typeof arg === "object"
|
|
@@ -173,19 +194,19 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
173
194
|
.join(" ");
|
|
174
195
|
const [invariantCallerWallet, invariantCallerAddress] = r.invariantCaller;
|
|
175
196
|
try {
|
|
176
|
-
const { result: invariantCallResult } = simnet.callReadOnlyFn(r.rendezvousContractId, r.selectedInvariant.name,
|
|
197
|
+
const { result: invariantCallResult } = simnet.callReadOnlyFn(r.rendezvousContractId, r.selectedInvariant.name, selectedInvariantArgsCV, invariantCallerAddress);
|
|
177
198
|
const invariantCallResultJson = (0, transactions_1.cvToJSON)(invariantCallResult);
|
|
178
199
|
if (invariantCallResultJson.value === true) {
|
|
179
200
|
radio.emit("logMessage", `₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
|
|
180
201
|
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
181
202
|
`${(0, ansicolor_1.dim)(invariantCallerWallet)} ` +
|
|
182
203
|
`${(0, ansicolor_1.green)("[PASS]")} ` +
|
|
183
|
-
`${
|
|
204
|
+
`${targetContractName} ` +
|
|
184
205
|
`${(0, ansicolor_1.underline)(r.selectedInvariant.name)} ` +
|
|
185
206
|
printedInvariantArgs);
|
|
186
207
|
}
|
|
187
208
|
if (!invariantCallResultJson.value) {
|
|
188
|
-
throw new Error(`Invariant failed for ${
|
|
209
|
+
throw new Error(`Invariant failed for ${targetContractName} contract: "${r.selectedInvariant.name}" returned ${invariantCallResultJson.value}`);
|
|
189
210
|
}
|
|
190
211
|
}
|
|
191
212
|
catch (error) {
|
|
@@ -196,7 +217,7 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
196
217
|
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
197
218
|
`${invariantCallerWallet} ` +
|
|
198
219
|
`[FAIL] ` +
|
|
199
|
-
`${
|
|
220
|
+
`${targetContractName} ` +
|
|
200
221
|
`${(0, ansicolor_1.underline)(r.selectedInvariant.name)} ` +
|
|
201
222
|
printedInvariantArgs));
|
|
202
223
|
// Re-throw the error for fast-check to catch and process.
|
|
@@ -215,7 +236,7 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
215
236
|
};
|
|
216
237
|
exports.checkInvariants = checkInvariants;
|
|
217
238
|
/**
|
|
218
|
-
*
|
|
239
|
+
* Initializes the local context, setting the number of times each function
|
|
219
240
|
* has been called to zero.
|
|
220
241
|
* @param rendezvousSutFunctions The Rendezvous functions.
|
|
221
242
|
* @returns The initialized local context.
|
|
@@ -236,14 +257,15 @@ const initializeClarityContext = (simnet, rendezvousSutFunctions) => rendezvousS
|
|
|
236
257
|
});
|
|
237
258
|
exports.initializeClarityContext = initializeClarityContext;
|
|
238
259
|
/**
|
|
239
|
-
* Filter the System Under Test (`SUT`) functions from the map of all
|
|
240
|
-
*
|
|
260
|
+
* Filter the System Under Test (`SUT`) functions from the map of all contract
|
|
261
|
+
* functions.
|
|
241
262
|
*
|
|
242
263
|
* The SUT functions are the ones that have `public` access since they are
|
|
243
264
|
* capable of changing the contract state, and they are not test functions.
|
|
244
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
|
|
245
268
|
* contract.
|
|
246
|
-
* @returns A map containing only the SUT functions for each contract.
|
|
247
269
|
*/
|
|
248
270
|
const filterSutFunctions = (allFunctionsMap) => new Map(Array.from(allFunctionsMap, ([contractId, functions]) => [
|
|
249
271
|
contractId,
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacks/rendezvous",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Meet your contract's vulnerabilities head-on.",
|
|
5
5
|
"main": "app.js",
|
|
6
6
|
"bin": {
|
|
@@ -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.
|
|
43
|
+
"ts-jest": "^29.2.5",
|
|
40
44
|
"typescript": "^5.5.4"
|
|
41
45
|
}
|
|
42
46
|
}
|
package/dist/property.js
CHANGED
|
@@ -10,7 +10,21 @@ const heatstroke_1 = require("./heatstroke");
|
|
|
10
10
|
const shared_1 = require("./shared");
|
|
11
11
|
const ansicolor_1 = require("ansicolor");
|
|
12
12
|
const traits_1 = require("./traits");
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Runs property-based tests on the target contract and logs the progress.
|
|
15
|
+
* Reports 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 A map of all target contract IDs to their
|
|
20
|
+
* function interfaces.
|
|
21
|
+
* @param seed The seed for reproducible property-based tests.
|
|
22
|
+
* @param path The path for reproducible property-based tests.
|
|
23
|
+
* @param runs The number of test runs.
|
|
24
|
+
* @param radio The custom logging event emitter.
|
|
25
|
+
* @returns void
|
|
26
|
+
*/
|
|
27
|
+
const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousAllFunctions, seed, path, runs, radio) => {
|
|
14
28
|
const testContractId = rendezvousList[0];
|
|
15
29
|
// A map where the keys are the test contract identifiers and the values are
|
|
16
30
|
// arrays of their test functions. This map will be used to access the test
|
|
@@ -28,9 +42,9 @@ const checkProperties = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
28
42
|
return;
|
|
29
43
|
}
|
|
30
44
|
const enrichedTestFunctionsInterfaces = traitReferenceFunctions.length > 0
|
|
31
|
-
? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(
|
|
45
|
+
? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(targetContractName), (0, traits_1.buildTraitReferenceMap)(testContractsTestFunctions.get(testContractId)), testContractsTestFunctions.get(testContractId), testContractId)
|
|
32
46
|
: testContractsTestFunctions;
|
|
33
|
-
radio.emit("logMessage", `\nStarting property testing type for the ${
|
|
47
|
+
radio.emit("logMessage", `\nStarting property testing type for the ${targetContractName} contract...\n`);
|
|
34
48
|
// Search for discard functions, for each test function. This map will
|
|
35
49
|
// be used to pair the test functions with their corresponding discard
|
|
36
50
|
// functions.
|
|
@@ -61,7 +75,7 @@ const checkProperties = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
61
75
|
const simnetAddresses = Array.from(simnetAccounts.values());
|
|
62
76
|
const testFunctions = (0, shared_1.getFunctionsListForContract)(enrichedTestFunctionsInterfaces, testContractId);
|
|
63
77
|
if ((testFunctions === null || testFunctions === void 0 ? void 0 : testFunctions.length) === 0) {
|
|
64
|
-
radio.emit("logMessage", (0, ansicolor_1.red)(`No test functions found for the "${
|
|
78
|
+
radio.emit("logMessage", (0, ansicolor_1.red)(`No test functions found for the "${targetContractName}" contract.\n`));
|
|
65
79
|
return;
|
|
66
80
|
}
|
|
67
81
|
const radioReporter = (runDetails) => {
|
|
@@ -80,7 +94,7 @@ const checkProperties = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
80
94
|
.map((selectedTestFunction) => (Object.assign(Object.assign({}, r), selectedTestFunction))))
|
|
81
95
|
.chain((r) => fast_check_1.default
|
|
82
96
|
.record({
|
|
83
|
-
|
|
97
|
+
functionArgs: fast_check_1.default.tuple(...(0, shared_1.functionToArbitrary)(r.selectedTestFunction, simnetAddresses, projectTraitImplementations)),
|
|
84
98
|
})
|
|
85
99
|
.map((args) => (Object.assign(Object.assign({}, r), args))))
|
|
86
100
|
.chain((r) => fast_check_1.default
|
|
@@ -99,8 +113,8 @@ const checkProperties = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
99
113
|
: fast_check_1.default.constant(0),
|
|
100
114
|
})
|
|
101
115
|
.map((burnBlocks) => (Object.assign(Object.assign({}, r), burnBlocks)))), (r) => {
|
|
102
|
-
const selectedTestFunctionArgs = (0, shared_1.argsToCV)(r.selectedTestFunction, r.
|
|
103
|
-
const printedTestFunctionArgs = r.
|
|
116
|
+
const selectedTestFunctionArgs = (0, shared_1.argsToCV)(r.selectedTestFunction, r.functionArgs);
|
|
117
|
+
const printedTestFunctionArgs = r.functionArgs
|
|
104
118
|
.map((arg) => {
|
|
105
119
|
try {
|
|
106
120
|
return typeof arg === "object"
|
|
@@ -122,7 +136,7 @@ const checkProperties = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
122
136
|
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
123
137
|
`${(0, ansicolor_1.dim)(testCallerWallet)} ` +
|
|
124
138
|
`${(0, ansicolor_1.yellow)("[WARN]")} ` +
|
|
125
|
-
`${
|
|
139
|
+
`${targetContractName} ` +
|
|
126
140
|
`${(0, ansicolor_1.underline)(r.selectedTestFunction.name)} ` +
|
|
127
141
|
(0, ansicolor_1.dim)(printedTestFunctionArgs));
|
|
128
142
|
}
|
|
@@ -138,7 +152,7 @@ const checkProperties = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
138
152
|
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
139
153
|
`${(0, ansicolor_1.dim)(testCallerWallet)} ` +
|
|
140
154
|
`${(0, ansicolor_1.yellow)("[WARN]")} ` +
|
|
141
|
-
`${
|
|
155
|
+
`${targetContractName} ` +
|
|
142
156
|
`${(0, ansicolor_1.underline)(r.selectedTestFunction.name)} ` +
|
|
143
157
|
(0, ansicolor_1.dim)(printedTestFunctionArgs));
|
|
144
158
|
}
|
|
@@ -149,7 +163,7 @@ const checkProperties = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
149
163
|
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
150
164
|
`${(0, ansicolor_1.dim)(testCallerWallet)} ` +
|
|
151
165
|
`${(0, ansicolor_1.green)("[PASS]")} ` +
|
|
152
|
-
`${
|
|
166
|
+
`${targetContractName} ` +
|
|
153
167
|
`${(0, ansicolor_1.underline)(r.selectedTestFunction.name)} ` +
|
|
154
168
|
printedTestFunctionArgs);
|
|
155
169
|
if (r.canMineBlocks) {
|
|
@@ -157,7 +171,7 @@ const checkProperties = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
157
171
|
}
|
|
158
172
|
}
|
|
159
173
|
else {
|
|
160
|
-
throw new Error(`Test failed for ${
|
|
174
|
+
throw new Error(`Test failed for ${targetContractName} contract: "${r.selectedTestFunction.name}" returned ${testFunctionCallResultJson.value.value}`);
|
|
161
175
|
}
|
|
162
176
|
}
|
|
163
177
|
catch (error) {
|
|
@@ -166,7 +180,7 @@ const checkProperties = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
166
180
|
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
167
181
|
`${testCallerWallet} ` +
|
|
168
182
|
`[FAIL] ` +
|
|
169
|
-
`${
|
|
183
|
+
`${targetContractName} ` +
|
|
170
184
|
`${(0, ansicolor_1.underline)(r.selectedTestFunction.name)} ` +
|
|
171
185
|
printedTestFunctionArgs));
|
|
172
186
|
// Re-throw the error for fast-check to catch and process.
|
|
@@ -190,12 +204,12 @@ const isTestDiscardedInPlace = (testFunctionCallResultJson) => testFunctionCallR
|
|
|
190
204
|
testFunctionCallResultJson.value.value === false;
|
|
191
205
|
exports.isTestDiscardedInPlace = isTestDiscardedInPlace;
|
|
192
206
|
/**
|
|
193
|
-
*
|
|
207
|
+
* Checks if the test function has to be discarded.
|
|
194
208
|
* @param discardFunctionName The discard function name.
|
|
195
209
|
* @param selectedTestFunctionArgs The generated test function arguments.
|
|
196
210
|
* @param contractId The contract identifier.
|
|
197
211
|
* @param simnet The simnet instance.
|
|
198
|
-
* @param selectedCaller The selected caller.
|
|
212
|
+
* @param selectedCaller The selected caller address.
|
|
199
213
|
* @returns A boolean indicating if the test function has to be discarded.
|
|
200
214
|
*/
|
|
201
215
|
const isTestDiscarded = (discardFunctionName, selectedTestFunctionArgs, contractId, simnet, selectedCaller) => {
|
|
@@ -206,7 +220,7 @@ const isTestDiscarded = (discardFunctionName, selectedTestFunctionArgs, contract
|
|
|
206
220
|
return jsonDiscardFunctionCallResult.value === false;
|
|
207
221
|
};
|
|
208
222
|
/**
|
|
209
|
-
*
|
|
223
|
+
* Validates a discard function, ensuring that its parameters match the test
|
|
210
224
|
* function's parameters and that its return type is boolean.
|
|
211
225
|
* @param contractId The contract identifier.
|
|
212
226
|
* @param discardFunctionName The discard function name.
|
|
@@ -235,23 +249,23 @@ const validateDiscardFunction = (contractId, discardFunctionName, testFunctionNa
|
|
|
235
249
|
return true;
|
|
236
250
|
};
|
|
237
251
|
/**
|
|
238
|
-
*
|
|
252
|
+
* Checks if the test function parameters match the discard function
|
|
239
253
|
* parameters.
|
|
240
|
-
* @param
|
|
241
|
-
* @param
|
|
254
|
+
* @param testFunctionInterface The test function's interface.
|
|
255
|
+
* @param discardFunctionInterface The discard function's interface.
|
|
242
256
|
* @returns A boolean indicating if the parameters match.
|
|
243
257
|
*/
|
|
244
|
-
const isParamsMatch = (
|
|
245
|
-
const sortedTestFunctionArgs = [...
|
|
246
|
-
const sortedDiscardFunctionArgs = [...
|
|
258
|
+
const isParamsMatch = (testFunctionInterface, discardFunctionInterface) => {
|
|
259
|
+
const sortedTestFunctionArgs = [...testFunctionInterface.args].sort((a, b) => a.name.localeCompare(b.name));
|
|
260
|
+
const sortedDiscardFunctionArgs = [...discardFunctionInterface.args].sort((a, b) => a.name.localeCompare(b.name));
|
|
247
261
|
return (JSON.stringify(sortedTestFunctionArgs) ===
|
|
248
262
|
JSON.stringify(sortedDiscardFunctionArgs));
|
|
249
263
|
};
|
|
250
264
|
exports.isParamsMatch = isParamsMatch;
|
|
251
265
|
/**
|
|
252
|
-
*
|
|
253
|
-
* @param
|
|
266
|
+
* Checks if the discard function's return type is boolean.
|
|
267
|
+
* @param discardFunctionInterface The discard function's interface.
|
|
254
268
|
* @returns A boolean indicating if the return type is boolean.
|
|
255
269
|
*/
|
|
256
|
-
const isReturnTypeBoolean = (
|
|
270
|
+
const isReturnTypeBoolean = (discardFunctionInterface) => discardFunctionInterface.outputs.type === "bool";
|
|
257
271
|
exports.isReturnTypeBoolean = isReturnTypeBoolean;
|
package/dist/shared.js
CHANGED
|
@@ -8,35 +8,42 @@ const fast_check_1 = __importDefault(require("fast-check"));
|
|
|
8
8
|
const transactions_1 = require("@stacks/transactions");
|
|
9
9
|
const traits_1 = require("./traits");
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
* simnet.
|
|
11
|
+
* Retrieves the contract interfaces of the contracts deployed by a specific
|
|
12
|
+
* deployer from the simnet instance.
|
|
13
13
|
* @param simnet The simnet instance.
|
|
14
|
-
* @
|
|
15
|
-
* @returns The contracts interfaces.
|
|
14
|
+
* @returns The contract IDs mapped to their interfaces.
|
|
16
15
|
*/
|
|
17
16
|
const getSimnetDeployerContractsInterfaces = (simnet) => new Map(Array.from(simnet.getContractsInterfaces()).filter(([key]) => key.split(".")[0] === simnet.deployer));
|
|
18
17
|
exports.getSimnetDeployerContractsInterfaces = getSimnetDeployerContractsInterfaces;
|
|
19
18
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* @
|
|
19
|
+
* Retrieves the function interfaces from the contract interfaces. Filters out
|
|
20
|
+
* other contract interface data such as data maps, variables, and constants.
|
|
21
|
+
* @param contractInterfaces The smart contract interfaces map.
|
|
22
|
+
* @returns The contract IDs mapped to their function interfaces.
|
|
23
23
|
*/
|
|
24
|
-
const getFunctionsFromContractInterfaces = (
|
|
24
|
+
const getFunctionsFromContractInterfaces = (contractInterfaces) => new Map(Array.from(contractInterfaces, ([contractId, contractInterface]) => [
|
|
25
25
|
contractId,
|
|
26
26
|
contractInterface.functions,
|
|
27
27
|
]));
|
|
28
28
|
exports.getFunctionsFromContractInterfaces = getFunctionsFromContractInterfaces;
|
|
29
29
|
const getFunctionsListForContract = (functionsMap, contractId) => functionsMap.get(contractId) || [];
|
|
30
30
|
exports.getFunctionsListForContract = getFunctionsListForContract;
|
|
31
|
-
/**
|
|
32
|
-
*
|
|
31
|
+
/** Dynamically generates fast-check arbitraries for a given function
|
|
32
|
+
* interface.
|
|
33
|
+
* @param functionInterface The "enriched" function interface.
|
|
34
|
+
* @param addresses The array of addresses to use for principal types.
|
|
35
|
+
* @param projectTraitImplementations The contract IDs mapped to the traits
|
|
36
|
+
* they implement.
|
|
33
37
|
* @returns Array of fast-check arbitraries.
|
|
34
38
|
*/
|
|
35
|
-
const functionToArbitrary = (
|
|
39
|
+
const functionToArbitrary = (functionInterface, addresses, projectTraitImplementations) => functionInterface.args.map((arg) => parameterTypeToArbitrary(arg.type, addresses, projectTraitImplementations));
|
|
36
40
|
exports.functionToArbitrary = functionToArbitrary;
|
|
37
41
|
/**
|
|
38
|
-
*
|
|
39
|
-
* @param type The parameter type.
|
|
42
|
+
* Generates a fast-check arbitrary for a given parameter type.
|
|
43
|
+
* @param type The "enriched" parameter type.
|
|
44
|
+
* @param addresses The array of addresses to use for principal types.
|
|
45
|
+
* @param projectTraitImplementations The contract IDs mapped to the traits
|
|
46
|
+
* they implement.
|
|
40
47
|
* @returns Fast-check arbitrary.
|
|
41
48
|
*/
|
|
42
49
|
const parameterTypeToArbitrary = (type, addresses, projectTraitImplementations) => {
|
|
@@ -129,8 +136,8 @@ const complexTypesToArbitrary = {
|
|
|
129
136
|
};
|
|
130
137
|
/**
|
|
131
138
|
* Custom hexadecimal string generator. The `hexaString` generator from
|
|
132
|
-
* fast-check has been deprecated. This generator is implemented to
|
|
133
|
-
*
|
|
139
|
+
* fast-check has been deprecated. This generator is implemented to match the
|
|
140
|
+
* behavior of the deprecated generator.
|
|
134
141
|
*
|
|
135
142
|
* @param constraints Fast-check string constraints.
|
|
136
143
|
* @returns Fast-check arbitrary for hexadecimal strings.
|
|
@@ -150,31 +157,31 @@ exports.hexaString = hexaString;
|
|
|
150
157
|
/** The character set used for generating ASCII strings.*/
|
|
151
158
|
const charSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
|
|
152
159
|
/**
|
|
153
|
-
*
|
|
154
|
-
* @param
|
|
155
|
-
* @param
|
|
160
|
+
* Converts JavaScript generated function arguments to Clarity values.
|
|
161
|
+
* @param functionInterface The function interface.
|
|
162
|
+
* @param generatedArguments Array of generated arguments.
|
|
156
163
|
* @returns Array of Clarity values.
|
|
157
164
|
*/
|
|
158
|
-
const argsToCV = (
|
|
165
|
+
const argsToCV = (functionInterface, generatedArguments) => functionInterface.args.map((arg, i) => argToCV(generatedArguments[i], arg.type));
|
|
159
166
|
exports.argsToCV = argsToCV;
|
|
160
167
|
/**
|
|
161
|
-
*
|
|
162
|
-
* @param
|
|
168
|
+
* Converts a JavaScript generated function argument to a Clarity value.
|
|
169
|
+
* @param generatedArgument Generated argument.
|
|
163
170
|
* @param type Argument type (base or complex).
|
|
164
171
|
* @returns Clarity value.
|
|
165
172
|
*/
|
|
166
|
-
const argToCV = (
|
|
173
|
+
const argToCV = (generatedArgument, type) => {
|
|
167
174
|
if (isBaseType(type)) {
|
|
168
175
|
// Base type.
|
|
169
176
|
switch (type) {
|
|
170
177
|
case "int128":
|
|
171
|
-
return baseTypesToCV.int128(
|
|
178
|
+
return baseTypesToCV.int128(generatedArgument);
|
|
172
179
|
case "uint128":
|
|
173
|
-
return baseTypesToCV.uint128(
|
|
180
|
+
return baseTypesToCV.uint128(generatedArgument);
|
|
174
181
|
case "bool":
|
|
175
|
-
return baseTypesToCV.bool(
|
|
182
|
+
return baseTypesToCV.bool(generatedArgument);
|
|
176
183
|
case "principal":
|
|
177
|
-
return baseTypesToCV.principal(
|
|
184
|
+
return baseTypesToCV.principal(generatedArgument);
|
|
178
185
|
default:
|
|
179
186
|
throw new Error(`Unsupported base parameter type: ${type}`);
|
|
180
187
|
}
|
|
@@ -182,36 +189,38 @@ const argToCV = (arg, type) => {
|
|
|
182
189
|
else {
|
|
183
190
|
// Complex type.
|
|
184
191
|
if ("buffer" in type) {
|
|
185
|
-
return complexTypesToCV.buffer(
|
|
192
|
+
return complexTypesToCV.buffer(generatedArgument);
|
|
186
193
|
}
|
|
187
194
|
else if ("string-ascii" in type) {
|
|
188
|
-
return complexTypesToCV["string-ascii"](
|
|
195
|
+
return complexTypesToCV["string-ascii"](generatedArgument);
|
|
189
196
|
}
|
|
190
197
|
else if ("string-utf8" in type) {
|
|
191
|
-
return complexTypesToCV["string-utf8"](
|
|
198
|
+
return complexTypesToCV["string-utf8"](generatedArgument);
|
|
192
199
|
}
|
|
193
200
|
else if ("list" in type) {
|
|
194
|
-
const listItems =
|
|
201
|
+
const listItems = generatedArgument.map((item) => argToCV(item, type.list.type));
|
|
195
202
|
return complexTypesToCV.list(listItems);
|
|
196
203
|
}
|
|
197
204
|
else if ("tuple" in type) {
|
|
198
205
|
const tupleData = {};
|
|
199
206
|
type.tuple.forEach((field) => {
|
|
200
|
-
tupleData[field.name] = argToCV(
|
|
207
|
+
tupleData[field.name] = argToCV(generatedArgument[field.name], field.type);
|
|
201
208
|
});
|
|
202
209
|
return complexTypesToCV.tuple(tupleData);
|
|
203
210
|
}
|
|
204
211
|
else if ("optional" in type) {
|
|
205
|
-
return (0, transactions_1.optionalCVOf)(
|
|
212
|
+
return (0, transactions_1.optionalCVOf)(generatedArgument
|
|
213
|
+
? argToCV(generatedArgument, type.optional)
|
|
214
|
+
: undefined);
|
|
206
215
|
}
|
|
207
216
|
else if ("response" in type) {
|
|
208
|
-
const status =
|
|
217
|
+
const status = generatedArgument.status;
|
|
209
218
|
const branchType = type.response[status];
|
|
210
|
-
const responseValue = argToCV(
|
|
219
|
+
const responseValue = argToCV(generatedArgument.value, branchType);
|
|
211
220
|
return complexTypesToCV.response(status, responseValue);
|
|
212
221
|
}
|
|
213
222
|
else if ("trait_reference" in type) {
|
|
214
|
-
return complexTypesToCV.trait_reference(
|
|
223
|
+
return complexTypesToCV.trait_reference(generatedArgument);
|
|
215
224
|
}
|
|
216
225
|
else {
|
|
217
226
|
throw new Error(`Unsupported complex parameter type: ${JSON.stringify(type)}`);
|
package/dist/traits.js
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.extractProjectTraitImplementations = exports.isTraitReferenceFunction = exports.getContractIdsImplementingTrait = exports.buildTraitReferenceMap = exports.getTraitReferenceData = exports.enrichInterfaceWithTraitData = void 0;
|
|
4
4
|
/**
|
|
5
|
-
* Enriches contract interface with trait reference data. Before enrichment,
|
|
5
|
+
* Enriches a contract interface with trait reference data. Before enrichment,
|
|
6
6
|
* the contract interface lacks trait reference data for parameters. This
|
|
7
7
|
* function constructs a copy of the contract interface with trait reference
|
|
8
8
|
* data for parameters that are trait references.
|
|
9
9
|
* @param ast The contract AST.
|
|
10
|
-
* @param traitReferenceMap The
|
|
10
|
+
* @param traitReferenceMap The function names mapped to their trait reference
|
|
11
|
+
* parameter paths.
|
|
11
12
|
* @param functionInterfaceList The list of function interfaces for a contract.
|
|
12
13
|
* @param targetContractId The contract ID to enrich with trait reference data.
|
|
13
|
-
* @returns
|
|
14
|
-
* functions.
|
|
14
|
+
* @returns The contract IDs mapped to a list of enriched function interfaces.
|
|
15
15
|
*/
|
|
16
16
|
const enrichInterfaceWithTraitData = (ast, traitReferenceMap, functionInterfaceList, targetContractId) => {
|
|
17
17
|
const enriched = new Map();
|
|
@@ -233,9 +233,10 @@ const getTraitReferenceData = (ast, functionName, parameterPath) => {
|
|
|
233
233
|
exports.getTraitReferenceData = getTraitReferenceData;
|
|
234
234
|
/**
|
|
235
235
|
* Builds a map of function names to trait reference paths. The trait reference
|
|
236
|
-
* path is the path
|
|
236
|
+
* path is the nesting path of the trait reference in the function parameter
|
|
237
|
+
* list.
|
|
237
238
|
* @param functionInterfaces The list of function interfaces for a contract.
|
|
238
|
-
* @returns
|
|
239
|
+
* @returns The function names mapped to their trait reference parameter paths.
|
|
239
240
|
*/
|
|
240
241
|
const buildTraitReferenceMap = (functionInterfaces) => {
|
|
241
242
|
const traitReferenceMap = new Map();
|
|
@@ -363,7 +364,7 @@ exports.isTraitReferenceFunction = isTraitReferenceFunction;
|
|
|
363
364
|
* Iterates over all project contracts's ASTs excluding the boot ones and
|
|
364
365
|
* extracts a record of contract IDs to their implemented traits.
|
|
365
366
|
* @param simnet The Simnet instance.
|
|
366
|
-
* @returns
|
|
367
|
+
* @returns The contract IDs mapped to their implemented traits.
|
|
367
368
|
*/
|
|
368
369
|
const extractProjectTraitImplementations = (simnet) => {
|
|
369
370
|
const allProjectContracts = [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacks/rendezvous",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Meet your contract's vulnerabilities head-on.",
|
|
5
5
|
"main": "app.js",
|
|
6
6
|
"bin": {
|
|
@@ -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.
|
|
43
|
+
"ts-jest": "^29.2.5",
|
|
40
44
|
"typescript": "^5.5.4"
|
|
41
45
|
}
|
|
42
46
|
}
|