@stacks/rendezvous 0.13.0 → 0.14.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/app.js +46 -39
- package/dist/citizen.js +41 -20
- package/dist/heatstroke.js +39 -25
- package/dist/invariant.js +149 -60
- package/dist/package.json +7 -7
- package/dist/persistence.js +97 -0
- package/dist/property.js +125 -36
- package/dist/shared.js +3 -1
- package/package.json +7 -7
package/dist/app.js
CHANGED
|
@@ -22,7 +22,6 @@ const package_json_1 = require("./package.json");
|
|
|
22
22
|
const ansicolor_1 = require("ansicolor");
|
|
23
23
|
const fs_1 = require("fs");
|
|
24
24
|
const util_1 = require("util");
|
|
25
|
-
const dialer_1 = require("./dialer");
|
|
26
25
|
const logger = (log, logLevel = "log") => {
|
|
27
26
|
console[logLevel](log);
|
|
28
27
|
};
|
|
@@ -45,19 +44,22 @@ exports.getManifestFileName = getManifestFileName;
|
|
|
45
44
|
const helpMessage = `
|
|
46
45
|
rv v${package_json_1.version}
|
|
47
46
|
|
|
48
|
-
Usage: rv <path
|
|
47
|
+
Usage: rv <path> <contract> <type> [OPTIONS]
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
path
|
|
52
|
-
contract
|
|
53
|
-
type
|
|
49
|
+
Arguments:
|
|
50
|
+
<path> Path to the Clarinet project
|
|
51
|
+
<contract> Contract name to fuzz
|
|
52
|
+
<type> Test type: test | invariant
|
|
54
53
|
|
|
55
54
|
Options:
|
|
56
|
-
--seed
|
|
57
|
-
--runs
|
|
58
|
-
--
|
|
59
|
-
--
|
|
60
|
-
--
|
|
55
|
+
--seed=<n> Seed for replay functionality
|
|
56
|
+
--runs=<n> Number of test iterations [default: 100]
|
|
57
|
+
--dial=<f> Path to custom dialers file
|
|
58
|
+
--regr Run regression tests only
|
|
59
|
+
--bail Stop on first failure
|
|
60
|
+
-h, --help Show this message
|
|
61
|
+
|
|
62
|
+
Learn more: https://stacks-network.github.io/rendezvous/
|
|
61
63
|
`;
|
|
62
64
|
function main() {
|
|
63
65
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -72,6 +74,7 @@ function main() {
|
|
|
72
74
|
runs: { type: "string" },
|
|
73
75
|
dial: { type: "string" },
|
|
74
76
|
bail: { type: "boolean" },
|
|
77
|
+
regr: { type: "boolean" },
|
|
75
78
|
help: { type: "boolean", short: "h" },
|
|
76
79
|
},
|
|
77
80
|
});
|
|
@@ -89,6 +92,8 @@ function main() {
|
|
|
89
92
|
runs: options.runs ? parseInt(options.runs, 10) : undefined,
|
|
90
93
|
/** Whether to bail on the first failure. */
|
|
91
94
|
bail: options.bail || false,
|
|
95
|
+
/** Whether to run regression tests only. */
|
|
96
|
+
regr: options.regr || false,
|
|
92
97
|
/** The path to the dialer file. */
|
|
93
98
|
dial: options.dial || undefined,
|
|
94
99
|
/** Whether to show the help message. */
|
|
@@ -113,6 +118,8 @@ function main() {
|
|
|
113
118
|
radio.emit("logMessage", helpMessage);
|
|
114
119
|
return;
|
|
115
120
|
}
|
|
121
|
+
// Divider before the run configuration.
|
|
122
|
+
radio.emit("logMessage", shared_1.LOG_DIVIDER);
|
|
116
123
|
/**
|
|
117
124
|
* The relative path to the manifest file, either `Clarinet.toml` or
|
|
118
125
|
* `Clarinet-<contract-name>.toml`. If the latter exists, it is used.
|
|
@@ -129,39 +136,39 @@ function main() {
|
|
|
129
136
|
if (runConfig.bail) {
|
|
130
137
|
radio.emit("logMessage", `Bailing on first failure.`);
|
|
131
138
|
}
|
|
139
|
+
if (runConfig.regr) {
|
|
140
|
+
radio.emit("logMessage", `Running regression tests.`);
|
|
141
|
+
}
|
|
132
142
|
if (runConfig.dial !== undefined) {
|
|
133
143
|
radio.emit("logMessage", `Using dial path: ${runConfig.dial}`);
|
|
134
144
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
yield (0, invariant_1.checkInvariants)(simnet, runConfig.sutContractName, rendezvousList, rendezvousAllFunctions, runConfig.seed, runConfig.runs, runConfig.bail, dialerRegistry, radio);
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
case "test": {
|
|
161
|
-
(0, property_1.checkProperties)(simnet, runConfig.sutContractName, rendezvousList, rendezvousAllFunctions, runConfig.seed, runConfig.runs, runConfig.bail, radio);
|
|
162
|
-
break;
|
|
145
|
+
// Divider between the run configuration and the execution.
|
|
146
|
+
radio.emit("logMessage", shared_1.LOG_DIVIDER + "\n");
|
|
147
|
+
const { simnet, resetSession, cleanupSession } = yield (0, citizen_1.issueFirstClassCitizenship)(runConfig.manifestDir, manifestPath, runConfig.sutContractName, radio);
|
|
148
|
+
try {
|
|
149
|
+
/**
|
|
150
|
+
* The list of contract IDs for the SUT contract names, as per the simnet.
|
|
151
|
+
*/
|
|
152
|
+
const rendezvousList = Array.from((0, shared_1.getSimnetDeployerContractsInterfaces)(simnet).keys()).filter((deployedContract) => (0, shared_1.getContractNameFromContractId)(deployedContract) ===
|
|
153
|
+
runConfig.sutContractName);
|
|
154
|
+
const rendezvousAllFunctions = (0, shared_1.getFunctionsFromContractInterfaces)(new Map(Array.from((0, shared_1.getSimnetDeployerContractsInterfaces)(simnet)).filter(([contractId]) => rendezvousList.includes(contractId))));
|
|
155
|
+
// Select the testing routine based on `type`.
|
|
156
|
+
// If "invariant", call `checkInvariants` to verify contract invariants.
|
|
157
|
+
// If "test", call `checkProperties` for property-based testing.
|
|
158
|
+
switch (runConfig.type) {
|
|
159
|
+
case "invariant": {
|
|
160
|
+
yield (0, invariant_1.checkInvariants)(simnet, resetSession, runConfig.sutContractName, rendezvousList, rendezvousAllFunctions, runConfig.seed, runConfig.runs, runConfig.dial, runConfig.bail, runConfig.regr, radio);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
case "test": {
|
|
164
|
+
yield (0, property_1.checkProperties)(simnet, resetSession, runConfig.sutContractName, rendezvousList, rendezvousAllFunctions, runConfig.seed, runConfig.runs, runConfig.bail, runConfig.regr, radio);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
163
167
|
}
|
|
164
168
|
}
|
|
169
|
+
finally {
|
|
170
|
+
cleanupSession();
|
|
171
|
+
}
|
|
165
172
|
});
|
|
166
173
|
}
|
|
167
174
|
if (require.main === module) {
|
package/dist/citizen.js
CHANGED
|
@@ -34,20 +34,23 @@ const ansicolor_1 = require("ansicolor");
|
|
|
34
34
|
* @param manifestPath The path to the manifest file.
|
|
35
35
|
* @param sutContractName The target contract name.
|
|
36
36
|
* @param radio The event emitter to send log messages to.
|
|
37
|
-
* @returns The initialized simnet
|
|
37
|
+
* @returns The initialized simnet session, including the simnet, a function to
|
|
38
|
+
* reset the session, and a function to clean up the session.
|
|
38
39
|
*/
|
|
39
40
|
const issueFirstClassCitizenship = (manifestDir, manifestPath, sutContractName, radio) => __awaiter(void 0, void 0, void 0, function* () {
|
|
40
41
|
var _a, _b, _c, _d;
|
|
41
42
|
// First simnet initialization: This will generate the deployment plan and
|
|
42
43
|
// will type check the project without any Rendezvous tests.
|
|
43
44
|
try {
|
|
44
|
-
radio.emit("logMessage",
|
|
45
|
+
radio.emit("logMessage", `Type-checking your Clarinet project...\n`);
|
|
45
46
|
yield (0, clarinet_sdk_1.generateDeployement)(manifestPath);
|
|
46
47
|
}
|
|
47
48
|
catch (error) {
|
|
48
49
|
throw new Error(`Error initializing simnet: ${(_a = error.message) !== null && _a !== void 0 ? _a : error}`);
|
|
49
50
|
}
|
|
50
|
-
const
|
|
51
|
+
const deploymentPlanRelativePath = (0, path_1.relative)(manifestDir, (0, path_1.join)(manifestDir, "deployments", "default.simnet-plan.yaml"));
|
|
52
|
+
const deploymentPlanAbsolutePath = (0, path_1.join)(manifestDir, deploymentPlanRelativePath);
|
|
53
|
+
const deploymentPlan = yaml_1.default.parse((0, fs_1.readFileSync)(deploymentPlanAbsolutePath, {
|
|
51
54
|
encoding: "utf-8",
|
|
52
55
|
}));
|
|
53
56
|
const parsedManifest = (0, toml_1.parse)((0, fs_1.readFileSync)(manifestPath, { encoding: "utf-8" }));
|
|
@@ -60,7 +63,7 @@ const issueFirstClassCitizenship = (manifestDir, manifestPath, sutContractName,
|
|
|
60
63
|
const rendezvousContractsDir = (0, path_1.join)(tempProjectDir, "contracts");
|
|
61
64
|
const rendezvousPath = (0, path_1.join)(rendezvousContractsDir, `${contractName}-rendezvous.clar`);
|
|
62
65
|
(0, fs_1.writeFileSync)(rendezvousPath, rendezvousData.rendezvousSourceCode);
|
|
63
|
-
radio.emit("logMessage",
|
|
66
|
+
radio.emit("logMessage", `Type-checking your Rendezvous project...\n`);
|
|
64
67
|
// Update the manifest in the temp directory to point to the Rendezvous
|
|
65
68
|
// concatenation.
|
|
66
69
|
const manifestFileName = (0, path_1.basename)(manifestPath);
|
|
@@ -101,9 +104,36 @@ const issueFirstClassCitizenship = (manifestDir, manifestPath, sutContractName,
|
|
|
101
104
|
// Errors are still printed to stderr to help troubleshoot issues.
|
|
102
105
|
const originalWrite = process.stdout.write;
|
|
103
106
|
process.stdout.write = () => true;
|
|
107
|
+
// Cleanup the deployment plan file if it exists. This will force clarinet
|
|
108
|
+
// to generate the deployment plan from scratch, accounting for the new
|
|
109
|
+
// Rendezvous contract.
|
|
110
|
+
if ((0, fs_1.existsSync)(deploymentPlanRelativePath)) {
|
|
111
|
+
(0, fs_1.rmSync)(deploymentPlanRelativePath, { force: true });
|
|
112
|
+
}
|
|
104
113
|
try {
|
|
105
114
|
const simnet = yield (0, clarinet_sdk_1.initSimnet)(manifestFileName);
|
|
106
|
-
|
|
115
|
+
const resetSession = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
116
|
+
const cwd = process.cwd();
|
|
117
|
+
const origWrite = process.stdout.write;
|
|
118
|
+
process.stdout.write = () => true;
|
|
119
|
+
try {
|
|
120
|
+
process.chdir(tempProjectDir);
|
|
121
|
+
yield (0, clarinet_sdk_1.initSimnet)(manifestFileName);
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
process.stdout.write = origWrite;
|
|
125
|
+
process.chdir(cwd);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
const cleanupSession = () => {
|
|
129
|
+
try {
|
|
130
|
+
(0, fs_1.rmSync)(tempProjectDir, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
radio.emit("logMessage", (0, ansicolor_1.yellow)(`Error cleaning up temporary project directory ${tempProjectDir}: ${error.message}. Remove it manually to avoid unnecessary disk space usage.`));
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
return { simnet, resetSession, cleanupSession };
|
|
107
137
|
}
|
|
108
138
|
finally {
|
|
109
139
|
// Restore stdout.
|
|
@@ -113,13 +143,6 @@ const issueFirstClassCitizenship = (manifestDir, manifestPath, sutContractName,
|
|
|
113
143
|
finally {
|
|
114
144
|
// Restore the original current working directory.
|
|
115
145
|
process.chdir(originalCwd);
|
|
116
|
-
// Cleanup the temp project directory.
|
|
117
|
-
try {
|
|
118
|
-
(0, fs_1.rmSync)(tempProjectDir, { recursive: true, force: true });
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
radio.emit("logMessage", (0, ansicolor_1.yellow)(`Error cleaning up temporary project directory ${tempProjectDir}: ${error.message}. Remove it manually to avoid unnecessary disk space usage.`));
|
|
122
|
-
}
|
|
123
146
|
}
|
|
124
147
|
});
|
|
125
148
|
exports.issueFirstClassCitizenship = issueFirstClassCitizenship;
|
|
@@ -176,14 +199,13 @@ const getDeploymentPlanContractSource = (deploymentPlan, sutContractName, manife
|
|
|
176
199
|
* in the deployment plan.
|
|
177
200
|
*/
|
|
178
201
|
const getSutContractDeploymentPlanEmulatedPublish = (deploymentPlan, sutContractName) => {
|
|
179
|
-
var _a
|
|
202
|
+
var _a;
|
|
180
203
|
// Filter all emulated contract publish transactions matching the target
|
|
181
204
|
// contract name from the deployment plan.
|
|
182
205
|
const contractPublishMatchesByName = deploymentPlan.plan.batches
|
|
183
206
|
.flatMap((batch) => batch.transactions)
|
|
184
|
-
.filter((transaction) => transaction["emulated-contract-publish"
|
|
185
|
-
transaction["
|
|
186
|
-
sutContractName);
|
|
207
|
+
.filter((transaction) => transaction["transaction-type"] === "emulated-contract-publish" &&
|
|
208
|
+
transaction["contract-name"] === sutContractName);
|
|
187
209
|
// If no matches are found, something went wrong.
|
|
188
210
|
if (contractPublishMatchesByName.length === 0) {
|
|
189
211
|
throw new Error(`"${sutContractName}" contract not found in Clarinet.toml.`);
|
|
@@ -198,8 +220,7 @@ const getSutContractDeploymentPlanEmulatedPublish = (deploymentPlan, sutContract
|
|
|
198
220
|
}
|
|
199
221
|
// From the list of filtered emulated contract publish transactions with
|
|
200
222
|
// having the same name, select the one deployed by the deployer.
|
|
201
|
-
const targetContractDeploymentData =
|
|
202
|
-
deployer)) === null || _b === void 0 ? void 0 : _b["emulated-contract-publish"];
|
|
223
|
+
const targetContractDeploymentData = contractPublishMatchesByName.find((transaction) => transaction["emulated-sender"] === deployer);
|
|
203
224
|
// TODO: Consider handling requirements and project contracts separately.
|
|
204
225
|
// Eventually let the user specify if the contract is a requirement or a
|
|
205
226
|
// project contract.
|
|
@@ -212,8 +233,8 @@ const getSutContractDeploymentPlanEmulatedPublish = (deploymentPlan, sutContract
|
|
|
212
233
|
}
|
|
213
234
|
return targetContractDeploymentData;
|
|
214
235
|
}
|
|
215
|
-
// Only one match was found, return the
|
|
216
|
-
const contractNameMatch = contractPublishMatchesByName[0]
|
|
236
|
+
// Only one match was found, return the contract publish data.
|
|
237
|
+
const contractNameMatch = contractPublishMatchesByName[0];
|
|
217
238
|
if (!contractNameMatch) {
|
|
218
239
|
throw new Error(`Could not locate "${sutContractName}" contract.`);
|
|
219
240
|
}
|
package/dist/heatstroke.js
CHANGED
|
@@ -21,53 +21,67 @@ const shared_1 = require("./shared");
|
|
|
21
21
|
* @returns void
|
|
22
22
|
*/
|
|
23
23
|
function reporter(runDetails, radio, type, statistics) {
|
|
24
|
-
|
|
25
|
-
if (
|
|
24
|
+
const { counterexample, failed, numRuns, path, seed } = runDetails;
|
|
25
|
+
if (failed) {
|
|
26
|
+
const error = runDetails.errorInstance || runDetails.error;
|
|
27
|
+
// Extract the actual Clarity error once for both error types.
|
|
28
|
+
const clarityError = (error === null || error === void 0 ? void 0 : error.clarityError) ||
|
|
29
|
+
(error === null || error === void 0 ? void 0 : error.message) ||
|
|
30
|
+
(error === null || error === void 0 ? void 0 : error.toString()) ||
|
|
31
|
+
"Unknown error";
|
|
26
32
|
// Report general run data.
|
|
27
|
-
radio.emit("logFailure", `\nError: Property failed after ${
|
|
28
|
-
radio.emit("logFailure", `Seed : ${
|
|
29
|
-
if (
|
|
30
|
-
radio.emit("logFailure", `Path : ${
|
|
33
|
+
radio.emit("logFailure", `\nError: Property failed after ${numRuns} tests.`);
|
|
34
|
+
radio.emit("logFailure", `Seed : ${seed}`);
|
|
35
|
+
if (path) {
|
|
36
|
+
radio.emit("logFailure", `Path : ${path}`);
|
|
31
37
|
}
|
|
32
38
|
switch (type) {
|
|
33
39
|
case "invariant": {
|
|
34
|
-
const
|
|
40
|
+
const ce = counterexample[0];
|
|
35
41
|
// Report specific run data for the invariant testing type.
|
|
36
42
|
radio.emit("logFailure", `\nCounterexample:`);
|
|
37
|
-
radio.emit("logFailure", `- Contract : ${(0, shared_1.getContractNameFromContractId)(
|
|
38
|
-
radio.emit("logFailure", `- Functions: ${
|
|
43
|
+
radio.emit("logFailure", `- Contract : ${(0, shared_1.getContractNameFromContractId)(ce.rendezvousContractId)}`);
|
|
44
|
+
radio.emit("logFailure", `- Functions: ${ce.selectedFunctions
|
|
39
45
|
.map((selectedFunction) => selectedFunction.name)
|
|
40
|
-
.join(", ")} (${
|
|
46
|
+
.join(", ")} (${ce.selectedFunctions
|
|
41
47
|
.map((selectedFunction) => selectedFunction.access)
|
|
42
48
|
.join(", ")})`);
|
|
43
|
-
radio.emit("logFailure", `- Arguments: ${
|
|
49
|
+
radio.emit("logFailure", `- Arguments: ${ce.selectedFunctionsArgsList
|
|
44
50
|
.map((selectedFunctionArgs) => JSON.stringify(selectedFunctionArgs))
|
|
45
51
|
.join(", ")}`);
|
|
46
|
-
radio.emit("logFailure", `- Callers : ${
|
|
52
|
+
radio.emit("logFailure", `- Callers : ${ce.sutCallers
|
|
47
53
|
.map((sutCaller) => sutCaller[0])
|
|
48
54
|
.join(", ")}`);
|
|
49
|
-
radio.emit("logFailure", `- Outputs : ${
|
|
55
|
+
radio.emit("logFailure", `- Outputs : ${ce.selectedFunctions
|
|
50
56
|
.map((selectedFunction) => JSON.stringify(selectedFunction.outputs))
|
|
51
57
|
.join(", ")}`);
|
|
52
|
-
radio.emit("logFailure", `- Invariant: ${
|
|
53
|
-
radio.emit("logFailure", `- Arguments: ${JSON.stringify(
|
|
54
|
-
radio.emit("logFailure", `- Caller : ${
|
|
58
|
+
radio.emit("logFailure", `- Invariant: ${ce.selectedInvariant.name} (${ce.selectedInvariant.access})`);
|
|
59
|
+
radio.emit("logFailure", `- Arguments: ${JSON.stringify(ce.invariantArgs)}`);
|
|
60
|
+
radio.emit("logFailure", `- Caller : ${ce.invariantCaller[0]}`);
|
|
55
61
|
radio.emit("logFailure", `\nWhat happened? Rendezvous went on a rampage and found a weak spot:\n`);
|
|
56
|
-
const formattedError = `The invariant "${
|
|
62
|
+
const formattedError = `The invariant "${ce.selectedInvariant.name}" returned:\n\n${clarityError
|
|
63
|
+
.toString()
|
|
64
|
+
.split("\n")
|
|
65
|
+
.map((line) => " " + line)
|
|
66
|
+
.join("\n")}\n`;
|
|
57
67
|
radio.emit("logFailure", formattedError);
|
|
58
68
|
break;
|
|
59
69
|
}
|
|
60
70
|
case "test": {
|
|
61
|
-
const
|
|
71
|
+
const ce = counterexample[0];
|
|
62
72
|
// Report specific run data for the property testing type.
|
|
63
73
|
radio.emit("logFailure", `\nCounterexample:`);
|
|
64
|
-
radio.emit("logFailure", `-
|
|
65
|
-
radio.emit("logFailure", `- Test Function : ${
|
|
66
|
-
radio.emit("logFailure", `- Arguments : ${JSON.stringify(
|
|
67
|
-
radio.emit("logFailure", `- Caller : ${
|
|
68
|
-
radio.emit("logFailure", `- Outputs : ${JSON.stringify(
|
|
74
|
+
radio.emit("logFailure", `- Contract : ${(0, shared_1.getContractNameFromContractId)(ce.rendezvousContractId)}`);
|
|
75
|
+
radio.emit("logFailure", `- Test Function : ${ce.selectedTestFunction.name} (${ce.selectedTestFunction.access})`);
|
|
76
|
+
radio.emit("logFailure", `- Arguments : ${JSON.stringify(ce.functionArgs)}`);
|
|
77
|
+
radio.emit("logFailure", `- Caller : ${ce.testCaller[0]}`);
|
|
78
|
+
radio.emit("logFailure", `- Outputs : ${JSON.stringify(ce.selectedTestFunction.outputs)}`);
|
|
69
79
|
radio.emit("logFailure", `\nWhat happened? Rendezvous went on a rampage and found a weak spot:\n`);
|
|
70
|
-
const formattedError = `The test function "${
|
|
80
|
+
const formattedError = `The test function "${ce.selectedTestFunction.name}" returned:\n\n${clarityError
|
|
81
|
+
.toString()
|
|
82
|
+
.split("\n")
|
|
83
|
+
.map((line) => " " + line)
|
|
84
|
+
.join("\n")}\n`;
|
|
71
85
|
radio.emit("logFailure", formattedError);
|
|
72
86
|
break;
|
|
73
87
|
}
|
|
@@ -77,7 +91,7 @@ function reporter(runDetails, radio, type, statistics) {
|
|
|
77
91
|
process.exitCode = 1;
|
|
78
92
|
}
|
|
79
93
|
else {
|
|
80
|
-
radio.emit("logMessage", (0, ansicolor_1.green)(`\nOK, ${type === "invariant" ? "invariants" : "properties"} passed after ${
|
|
94
|
+
radio.emit("logMessage", (0, ansicolor_1.green)(`\nOK, ${type === "invariant" ? "invariants" : "properties"} passed after ${numRuns} runs.\n`));
|
|
81
95
|
}
|
|
82
96
|
reportStatistics(statistics, type, radio);
|
|
83
97
|
radio.emit("logMessage", "\n");
|
package/dist/invariant.js
CHANGED
|
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.initializeClarityContext = exports.initializeLocalContext = exports.checkInvariants = void 0;
|
|
15
|
+
exports.FalsifiedInvariantError = exports.initializeClarityContext = exports.initializeLocalContext = exports.checkInvariants = void 0;
|
|
16
16
|
const shared_1 = require("./shared");
|
|
17
17
|
const transactions_1 = require("@stacks/transactions");
|
|
18
18
|
const heatstroke_1 = require("./heatstroke");
|
|
@@ -20,33 +20,27 @@ const fast_check_1 = __importDefault(require("fast-check"));
|
|
|
20
20
|
const ansicolor_1 = require("ansicolor");
|
|
21
21
|
const traits_1 = require("./traits");
|
|
22
22
|
const dialer_1 = require("./dialer");
|
|
23
|
+
const persistence_1 = require("./persistence");
|
|
24
|
+
const path_1 = require("path");
|
|
23
25
|
/**
|
|
24
26
|
* Runs invariant testing on the target contract and logs the progress. Reports
|
|
25
27
|
* the test results through a custom reporter.
|
|
26
28
|
* @param simnet The Simnet instance.
|
|
29
|
+
* @param resetSession Resets the simnet session to a clean state.
|
|
27
30
|
* @param targetContractName The name of the target contract.
|
|
28
31
|
* @param rendezvousList The list of contract IDs for each target contract.
|
|
29
32
|
* @param rendezvousAllFunctions The map of all function interfaces for each
|
|
30
33
|
* target contract.
|
|
31
34
|
* @param seed The seed for reproducible invariant testing.
|
|
32
35
|
* @param runs The number of test runs.
|
|
36
|
+
* @param dial The path to the dialer file.
|
|
33
37
|
* @param bail Stop execution after the first failure and prevent further
|
|
34
38
|
* shrinking.
|
|
35
|
-
* @param
|
|
39
|
+
* @param regr Whether to run regression tests only.
|
|
36
40
|
* @param radio The custom logging event emitter.
|
|
37
41
|
* @returns void
|
|
38
42
|
*/
|
|
39
|
-
const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousAllFunctions, seed, runs, bail,
|
|
40
|
-
const statistics = {
|
|
41
|
-
sut: {
|
|
42
|
-
successful: new Map(),
|
|
43
|
-
failed: new Map(),
|
|
44
|
-
},
|
|
45
|
-
invariant: {
|
|
46
|
-
successful: new Map(),
|
|
47
|
-
failed: new Map(),
|
|
48
|
-
},
|
|
49
|
-
};
|
|
43
|
+
const checkInvariants = (simnet, resetSession, targetContractName, rendezvousList, rendezvousAllFunctions, seed, runs, dial, bail, regr, radio) => __awaiter(void 0, void 0, void 0, function* () {
|
|
50
44
|
// The Rendezvous identifier is the first one in the list. Only one contract
|
|
51
45
|
// can be fuzzed at a time.
|
|
52
46
|
const rendezvousContractId = rendezvousList[0];
|
|
@@ -54,20 +48,10 @@ const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
54
48
|
// arrays of their SUT (System Under Test) functions. This map will be used
|
|
55
49
|
// to access the SUT functions for each Rendezvous contract afterwards.
|
|
56
50
|
const rendezvousSutFunctions = filterSutFunctions(rendezvousAllFunctions);
|
|
57
|
-
// Initialize the statistics for the SUT functions.
|
|
58
|
-
for (const functionInterface of rendezvousSutFunctions.get(rendezvousContractId)) {
|
|
59
|
-
statistics.sut.successful.set(functionInterface.name, 0);
|
|
60
|
-
statistics.sut.failed.set(functionInterface.name, 0);
|
|
61
|
-
}
|
|
62
51
|
// A map where the keys are the Rendezvous identifiers and the values are
|
|
63
52
|
// arrays of their invariant functions. This map will be used to access the
|
|
64
53
|
// invariant functions for each Rendezvous contract afterwards.
|
|
65
54
|
const rendezvousInvariantFunctions = filterInvariantFunctions(rendezvousAllFunctions);
|
|
66
|
-
// Initialize the statistics for the invariant functions.
|
|
67
|
-
for (const functionInterface of rendezvousInvariantFunctions.get(rendezvousContractId)) {
|
|
68
|
-
statistics.invariant.successful.set(functionInterface.name, 0);
|
|
69
|
-
statistics.invariant.failed.set(functionInterface.name, 0);
|
|
70
|
-
}
|
|
71
55
|
const sutFunctions = rendezvousSutFunctions.get(rendezvousContractId);
|
|
72
56
|
const traitReferenceSutFunctions = sutFunctions.filter(traits_1.isTraitReferenceFunction);
|
|
73
57
|
const invariantFunctions = rendezvousInvariantFunctions.get(rendezvousContractId);
|
|
@@ -90,22 +74,8 @@ const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
90
74
|
// functions will be skipped during invariant testing. Otherwise, the
|
|
91
75
|
// invariant testing routine can fail during argument generation.
|
|
92
76
|
const invariantFunctionsWithMissingTraits = (0, traits_1.getNonTestableTraitFunctions)(enrichedInvariantFunctionsInterfaces, invariantTraitReferenceMap, projectTraitImplementations, rendezvousContractId);
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
}
|
|
77
|
+
// Emit warnings for functions with missing trait implementations
|
|
78
|
+
emitMissingTraitWarnings(radio, sutFunctionsWithMissingTraits, invariantFunctionsWithMissingTraits);
|
|
109
79
|
// Filter out functions with missing trait implementations from the enriched
|
|
110
80
|
// map.
|
|
111
81
|
const executableSutFunctions = new Map([
|
|
@@ -126,14 +96,6 @@ const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
126
96
|
.filter((f) => !invariantFunctionsWithMissingTraits.includes(f.name)),
|
|
127
97
|
],
|
|
128
98
|
]);
|
|
129
|
-
// Set up local context to track SUT function call counts.
|
|
130
|
-
const localContext = (0, exports.initializeLocalContext)(executableSutFunctions);
|
|
131
|
-
// Set up context in simnet by initializing state for SUT.
|
|
132
|
-
(0, exports.initializeClarityContext)(simnet, executableSutFunctions);
|
|
133
|
-
radio.emit("logMessage", `\nStarting invariant testing type for the ${targetContractName} contract...\n`);
|
|
134
|
-
const simnetAccounts = simnet.getAccounts();
|
|
135
|
-
const eligibleAccounts = new Map([...simnetAccounts].filter(([key]) => key !== "faucet"));
|
|
136
|
-
const simnetAddresses = Array.from(simnetAccounts.values());
|
|
137
99
|
const functions = (0, shared_1.getFunctionsListForContract)(executableSutFunctions, rendezvousContractId);
|
|
138
100
|
const invariants = (0, shared_1.getFunctionsListForContract)(executableInvariantFunctions, rendezvousContractId);
|
|
139
101
|
if ((functions === null || functions === void 0 ? void 0 : functions.length) === 0) {
|
|
@@ -144,9 +106,100 @@ const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
144
106
|
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`));
|
|
145
107
|
return;
|
|
146
108
|
}
|
|
147
|
-
|
|
148
|
-
|
|
109
|
+
if (regr) {
|
|
110
|
+
// Run regression tests only.
|
|
111
|
+
radio.emit("logMessage", `Regressions loaded from: ${(0, path_1.resolve)((0, persistence_1.getFailureFilePath)(rendezvousContractId))}`);
|
|
112
|
+
radio.emit("logMessage", `Loading ${targetContractName} contract regressions...\n`);
|
|
113
|
+
const regressions = (0, persistence_1.loadFailures)(rendezvousContractId, "invariant");
|
|
114
|
+
radio.emit("logMessage", `Found ${(0, ansicolor_1.underline)(`${regressions.length} regressions`)} for the ${targetContractName} contract.\n`);
|
|
115
|
+
for (const regression of regressions) {
|
|
116
|
+
emitInvariantRegressionTestHeader(radio, targetContractName, regression.seed, regression.numRuns, regression.dial, regression.timestamp);
|
|
117
|
+
yield resetSession();
|
|
118
|
+
yield invariantTest({
|
|
119
|
+
simnet,
|
|
120
|
+
targetContractName,
|
|
121
|
+
rendezvousContractId,
|
|
122
|
+
runs: regression.numRuns < 100 ? 100 : regression.numRuns,
|
|
123
|
+
seed: regression.seed,
|
|
124
|
+
bail,
|
|
125
|
+
dial: regression.dial,
|
|
126
|
+
radio,
|
|
127
|
+
functions,
|
|
128
|
+
invariants,
|
|
129
|
+
projectTraitImplementations,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Run fresh invariant tests using user-provided configuration.
|
|
135
|
+
radio.emit("logMessage", `Starting fresh round of invariant testing for the ${targetContractName} contract using user-provided configuration...\n`);
|
|
136
|
+
yield invariantTest({
|
|
137
|
+
simnet,
|
|
138
|
+
targetContractName,
|
|
139
|
+
rendezvousContractId,
|
|
140
|
+
runs,
|
|
141
|
+
seed,
|
|
142
|
+
bail,
|
|
143
|
+
dial,
|
|
144
|
+
radio,
|
|
145
|
+
functions,
|
|
146
|
+
invariants,
|
|
147
|
+
projectTraitImplementations,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
exports.checkInvariants = checkInvariants;
|
|
152
|
+
/**
|
|
153
|
+
* Runs an invariant test.
|
|
154
|
+
* @param config The union of the configuration and context for the invariant
|
|
155
|
+
* test.
|
|
156
|
+
* @returns A promise that resolves when the invariant test is complete.
|
|
157
|
+
*/
|
|
158
|
+
const invariantTest = (config) => __awaiter(void 0, void 0, void 0, function* () {
|
|
159
|
+
const { simnet, targetContractName, rendezvousContractId, runs, seed, bail, dial, radio, functions, invariants, projectTraitImplementations, } = config;
|
|
160
|
+
// Derive accounts and addresses from simnet.
|
|
161
|
+
const simnetAccounts = simnet.getAccounts();
|
|
162
|
+
const eligibleAccounts = new Map([...simnetAccounts].filter(([key]) => key !== "faucet"));
|
|
163
|
+
const simnetAddresses = Array.from(simnetAccounts.values());
|
|
164
|
+
/**
|
|
165
|
+
* The dialer registry, which is used to keep track of all the custom dialers
|
|
166
|
+
* registered by the user using the `--dial` flag.
|
|
167
|
+
*/
|
|
168
|
+
const dialerRegistry = dial !== undefined ? new dialer_1.DialerRegistry(dial) : undefined;
|
|
169
|
+
if (dialerRegistry !== undefined) {
|
|
170
|
+
dialerRegistry.registerDialers();
|
|
171
|
+
}
|
|
172
|
+
const statistics = {
|
|
173
|
+
sut: {
|
|
174
|
+
successful: new Map(),
|
|
175
|
+
failed: new Map(),
|
|
176
|
+
},
|
|
177
|
+
invariant: {
|
|
178
|
+
successful: new Map(),
|
|
179
|
+
failed: new Map(),
|
|
180
|
+
},
|
|
149
181
|
};
|
|
182
|
+
// Initialize the statistics for the SUT functions.
|
|
183
|
+
for (const functionInterface of functions) {
|
|
184
|
+
statistics.sut.successful.set(functionInterface.name, 0);
|
|
185
|
+
statistics.sut.failed.set(functionInterface.name, 0);
|
|
186
|
+
}
|
|
187
|
+
// Initialize the statistics for the invariant functions.
|
|
188
|
+
for (const functionInterface of invariants) {
|
|
189
|
+
statistics.invariant.successful.set(functionInterface.name, 0);
|
|
190
|
+
statistics.invariant.failed.set(functionInterface.name, 0);
|
|
191
|
+
}
|
|
192
|
+
const radioReporter = (runDetails) => __awaiter(void 0, void 0, void 0, function* () {
|
|
193
|
+
(0, heatstroke_1.reporter)(runDetails, radio, "invariant", statistics);
|
|
194
|
+
// Persist failures for regression testing.
|
|
195
|
+
if (runDetails.failed) {
|
|
196
|
+
(0, persistence_1.persistFailure)(runDetails, "invariant", rendezvousContractId, dial);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
// Set up local context to track SUT function call counts.
|
|
200
|
+
const localContext = (0, exports.initializeLocalContext)(rendezvousContractId, functions);
|
|
201
|
+
// Set up context in simnet by initializing state for SUT.
|
|
202
|
+
(0, exports.initializeClarityContext)(simnet, rendezvousContractId, functions);
|
|
150
203
|
yield fast_check_1.default.assert(fast_check_1.default.asyncProperty(fast_check_1.default
|
|
151
204
|
.record({
|
|
152
205
|
// The target contract identifier. It is a constant value equal
|
|
@@ -333,7 +386,7 @@ const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
333
386
|
// Invariant call went through, but returned something other than
|
|
334
387
|
// `true`. Create a custom error to distinguish this case from
|
|
335
388
|
// runtime errors.
|
|
336
|
-
throw new FalsifiedInvariantError(`Invariant failed for ${targetContractName} contract: "${r.selectedInvariant.name}" returned ${invariantCallClarityResult}
|
|
389
|
+
throw new FalsifiedInvariantError(`Invariant failed for ${targetContractName} contract: "${r.selectedInvariant.name}" returned ${invariantCallClarityResult}`, invariantCallClarityResult);
|
|
337
390
|
}
|
|
338
391
|
}
|
|
339
392
|
catch (error) {
|
|
@@ -362,27 +415,51 @@ const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
362
415
|
verbose: true,
|
|
363
416
|
});
|
|
364
417
|
});
|
|
365
|
-
|
|
418
|
+
/**
|
|
419
|
+
* Emits warnings for functions that reference traits without eligible
|
|
420
|
+
* implementations.
|
|
421
|
+
*/
|
|
422
|
+
function emitMissingTraitWarnings(radio, sutFunctions, invariantFunctions) {
|
|
423
|
+
if (sutFunctions.length === 0 && invariantFunctions.length === 0) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (sutFunctions.length > 0) {
|
|
427
|
+
const functionList = sutFunctions.map((fn) => ` - ${fn}`).join("\n");
|
|
428
|
+
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`));
|
|
429
|
+
}
|
|
430
|
+
if (invariantFunctions.length > 0) {
|
|
431
|
+
const functionList = invariantFunctions.map((fn) => ` - ${fn}`).join("\n");
|
|
432
|
+
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`));
|
|
433
|
+
}
|
|
434
|
+
radio.emit("logMessage", (0, ansicolor_1.yellow)(`Note: You can add contracts implementing traits either as project contracts or as Clarinet requirements.\n`));
|
|
435
|
+
}
|
|
366
436
|
/**
|
|
367
437
|
* Initializes the local context, setting the number of times each function
|
|
368
438
|
* has been called to zero.
|
|
369
|
-
* @param
|
|
439
|
+
* @param contractId The contract identifier.
|
|
440
|
+
* @param functions The SUT functions for the contract.
|
|
370
441
|
* @returns The initialized local context.
|
|
371
442
|
*/
|
|
372
|
-
const initializeLocalContext = (
|
|
373
|
-
contractId,
|
|
374
|
-
|
|
375
|
-
]));
|
|
443
|
+
const initializeLocalContext = (contractId, functions) => ({
|
|
444
|
+
[contractId]: Object.fromEntries(functions.map((f) => [f.name, 0])),
|
|
445
|
+
});
|
|
376
446
|
exports.initializeLocalContext = initializeLocalContext;
|
|
377
|
-
|
|
378
|
-
|
|
447
|
+
/**
|
|
448
|
+
* Initializes the Clarity context by calling update-context for each SUT
|
|
449
|
+
* function.
|
|
450
|
+
* @param simnet The Simnet instance.
|
|
451
|
+
* @param contractId The contract identifier.
|
|
452
|
+
* @param functions The SUT functions for the contract.
|
|
453
|
+
*/
|
|
454
|
+
const initializeClarityContext = (simnet, contractId, functions) => {
|
|
455
|
+
functions.forEach((fn) => {
|
|
379
456
|
const { result: initialize } = simnet.callPublicFn(contractId, "update-context", [transactions_1.Cl.stringAscii(fn.name), transactions_1.Cl.uint(0)], simnet.deployer);
|
|
380
457
|
const jsonResult = (0, transactions_1.cvToJSON)(initialize);
|
|
381
458
|
if (!jsonResult.value || !jsonResult.success) {
|
|
382
459
|
throw new Error(`Failed to initialize the context for function: ${fn.name}.`);
|
|
383
460
|
}
|
|
384
461
|
});
|
|
385
|
-
}
|
|
462
|
+
};
|
|
386
463
|
exports.initializeClarityContext = initializeClarityContext;
|
|
387
464
|
/**
|
|
388
465
|
* Filter the System Under Test (`SUT`) functions from the map of all contract
|
|
@@ -406,7 +483,19 @@ const filterInvariantFunctions = (allFunctionsMap) => new Map(Array.from(allFunc
|
|
|
406
483
|
functions.filter(({ access, name }) => access === "read_only" && name.startsWith("invariant-")),
|
|
407
484
|
]));
|
|
408
485
|
class FalsifiedInvariantError extends Error {
|
|
409
|
-
constructor(message) {
|
|
486
|
+
constructor(message, clarityError) {
|
|
410
487
|
super(message);
|
|
488
|
+
this.clarityError = clarityError;
|
|
411
489
|
}
|
|
412
490
|
}
|
|
491
|
+
exports.FalsifiedInvariantError = FalsifiedInvariantError;
|
|
492
|
+
const emitInvariantRegressionTestHeader = (radio, targetContractName, seed, numRuns, dial, timestamp) => {
|
|
493
|
+
radio.emit("logMessage", shared_1.LOG_DIVIDER);
|
|
494
|
+
radio.emit("logMessage", `
|
|
495
|
+
Running ${(0, ansicolor_1.underline)(timestamp)} regression test for the ${targetContractName} contract with:
|
|
496
|
+
|
|
497
|
+
- Seed: ${seed}
|
|
498
|
+
- Runs: ${numRuns}
|
|
499
|
+
- Dial: ${dial !== null && dial !== void 0 ? dial : "none (default)"}
|
|
500
|
+
`);
|
|
501
|
+
};
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacks/rendezvous",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Meet your contract's vulnerabilities head-on.",
|
|
5
5
|
"main": "app.js",
|
|
6
6
|
"bin": {
|
|
@@ -31,17 +31,17 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@iarna/toml": "^2.2.5",
|
|
34
|
-
"@stacks/clarinet-sdk": "^3.
|
|
35
|
-
"@stacks/transactions": "^7.
|
|
34
|
+
"@stacks/clarinet-sdk": "^3.14.0",
|
|
35
|
+
"@stacks/transactions": "^7.3.1",
|
|
36
36
|
"ansicolor": "^2.0.3",
|
|
37
|
-
"fast-check": "^4.3
|
|
38
|
-
"yaml": "^2.8.
|
|
37
|
+
"fast-check": "^4.5.3",
|
|
38
|
+
"yaml": "^2.8.2"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@stacks/clarinet-sdk-wasm": "^3.
|
|
41
|
+
"@stacks/clarinet-sdk-wasm": "^3.14.0",
|
|
42
42
|
"@types/jest": "^30.0.0",
|
|
43
43
|
"jest": "^30.2.0",
|
|
44
|
-
"ts-jest": "^29.4.
|
|
44
|
+
"ts-jest": "^29.4.6",
|
|
45
45
|
"typescript": "^5.9.3"
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadFailures = exports.persistFailure = exports.getFailureFilePath = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
/** Default configuration for persistence behavior. */
|
|
7
|
+
const DEFAULT_CONFIG = {
|
|
8
|
+
baseDir: ".rendezvous-regressions",
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Gets the absolute file path for a contract's failure store.
|
|
12
|
+
* Uses contractId as filename (e.g., "ST1...ADDR.counter.json")
|
|
13
|
+
* @param contractId The contract identifier being tested
|
|
14
|
+
* @param baseDir The base directory for storing regression files
|
|
15
|
+
* @returns The file path for the failure store
|
|
16
|
+
*/
|
|
17
|
+
const getFailureFilePath = (contractId, baseDir = DEFAULT_CONFIG.baseDir) => {
|
|
18
|
+
return (0, path_1.resolve)(baseDir, `${contractId}.json`);
|
|
19
|
+
};
|
|
20
|
+
exports.getFailureFilePath = getFailureFilePath;
|
|
21
|
+
/**
|
|
22
|
+
* Loads the failure store for a contract, or creates an empty one.
|
|
23
|
+
* @param contractId The contract identifier being tested
|
|
24
|
+
* @param baseDir The base directory for storing regression files
|
|
25
|
+
* @returns The failure store
|
|
26
|
+
*/
|
|
27
|
+
const loadFailureStore = (contractId, baseDir = DEFAULT_CONFIG.baseDir) => {
|
|
28
|
+
const filePath = (0, exports.getFailureFilePath)(contractId, baseDir);
|
|
29
|
+
try {
|
|
30
|
+
const content = (0, fs_1.readFileSync)(filePath, "utf-8");
|
|
31
|
+
return JSON.parse(content);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return { invariant: [], test: [] };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Saves the failure store for a contract.
|
|
39
|
+
* @param contractId The contract identifier being tested
|
|
40
|
+
* @param baseDir The base directory for storing regression files
|
|
41
|
+
* @param store The failure store to save
|
|
42
|
+
*/
|
|
43
|
+
const saveFailureStore = (contractId, baseDir, store) => {
|
|
44
|
+
// Ensure the base directory exists.
|
|
45
|
+
(0, fs_1.mkdirSync)(baseDir, { recursive: true });
|
|
46
|
+
const filePath = (0, exports.getFailureFilePath)(contractId, baseDir);
|
|
47
|
+
(0, fs_1.writeFileSync)(filePath, JSON.stringify(store, null, 2), "utf-8");
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Persists a test failure for future regression testing.
|
|
51
|
+
*
|
|
52
|
+
* @param runDetails The test run details from fast-check
|
|
53
|
+
* @param type The type of test that failed
|
|
54
|
+
* @param contractId The contract identifier being tested
|
|
55
|
+
* @param dial The path to the dialer file used for this test run
|
|
56
|
+
* @param config Optional configuration for persistence behavior
|
|
57
|
+
*/
|
|
58
|
+
const persistFailure = (runDetails, type, contractId, dial, config) => {
|
|
59
|
+
const { baseDir } = Object.assign(Object.assign({}, DEFAULT_CONFIG), config);
|
|
60
|
+
// Load existing store.
|
|
61
|
+
const store = loadFailureStore(contractId, baseDir);
|
|
62
|
+
const record = {
|
|
63
|
+
seed: runDetails.seed,
|
|
64
|
+
dial: dial,
|
|
65
|
+
numRuns: runDetails.numRuns,
|
|
66
|
+
timestamp: Date.now(),
|
|
67
|
+
};
|
|
68
|
+
// Get the array for this test type.
|
|
69
|
+
const failures = store[type];
|
|
70
|
+
// Check if this seed already exists.
|
|
71
|
+
const seedExists = failures.some((f) => f.seed === record.seed);
|
|
72
|
+
if (seedExists) {
|
|
73
|
+
// Already recorded.
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// Add new failure.
|
|
77
|
+
failures.push(record);
|
|
78
|
+
// Sort the failures in descending order by timestamp.
|
|
79
|
+
failures.sort((a, b) => b.timestamp - a.timestamp);
|
|
80
|
+
// Save back to file.
|
|
81
|
+
saveFailureStore(contractId, baseDir, store);
|
|
82
|
+
};
|
|
83
|
+
exports.persistFailure = persistFailure;
|
|
84
|
+
/**
|
|
85
|
+
* Loads persisted failures for a given contract and test type.
|
|
86
|
+
*
|
|
87
|
+
* @param contractId The contract identifier
|
|
88
|
+
* @param type The type of test ("invariant" or "test")
|
|
89
|
+
* @param config Optional configuration
|
|
90
|
+
* @returns Array of failure records, or empty array if none exist
|
|
91
|
+
*/
|
|
92
|
+
const loadFailures = (contractId, type, config) => {
|
|
93
|
+
const { baseDir } = Object.assign(Object.assign({}, DEFAULT_CONFIG), config);
|
|
94
|
+
const store = loadFailureStore(contractId, baseDir);
|
|
95
|
+
return store[type];
|
|
96
|
+
};
|
|
97
|
+
exports.loadFailures = loadFailures;
|
package/dist/property.js
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
2
11
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
13
|
};
|
|
5
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.isReturnTypeBoolean = exports.isParamsMatch = exports.isTestDiscardedInPlace = exports.checkProperties = void 0;
|
|
15
|
+
exports.PropertyTestError = exports.isReturnTypeBoolean = exports.isParamsMatch = exports.isTestDiscardedInPlace = exports.checkProperties = void 0;
|
|
7
16
|
const fast_check_1 = __importDefault(require("fast-check"));
|
|
8
17
|
const transactions_1 = require("@stacks/transactions");
|
|
9
18
|
const heatstroke_1 = require("./heatstroke");
|
|
10
19
|
const shared_1 = require("./shared");
|
|
11
20
|
const ansicolor_1 = require("ansicolor");
|
|
12
21
|
const traits_1 = require("./traits");
|
|
22
|
+
const persistence_1 = require("./persistence");
|
|
23
|
+
const path_1 = require("path");
|
|
13
24
|
/**
|
|
14
25
|
* Runs property-based tests on the target contract and logs the progress.
|
|
15
26
|
* Reports the test results through a custom reporter.
|
|
16
27
|
* @param simnet The simnet instance.
|
|
28
|
+
* @param resetSession Resets the simnet session to a clean state.
|
|
17
29
|
* @param targetContractName The name of the target contract.
|
|
18
30
|
* @param rendezvousList The list of contract IDs for each target contract.
|
|
19
31
|
* @param rendezvousAllFunctions A map of all target contract IDs to their
|
|
@@ -22,27 +34,16 @@ const traits_1 = require("./traits");
|
|
|
22
34
|
* @param runs The number of test runs.
|
|
23
35
|
* @param bail Stop execution after the first failure and prevent further
|
|
24
36
|
* shrinking.
|
|
37
|
+
* @param regr Whether to run regression tests only.
|
|
25
38
|
* @param radio The custom logging event emitter.
|
|
26
39
|
* @returns void
|
|
27
40
|
*/
|
|
28
|
-
const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousAllFunctions, seed, runs, bail, radio) => {
|
|
29
|
-
const statistics = {
|
|
30
|
-
test: {
|
|
31
|
-
successful: new Map(),
|
|
32
|
-
discarded: new Map(),
|
|
33
|
-
failed: new Map(),
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
const testContractId = rendezvousList[0];
|
|
41
|
+
const checkProperties = (simnet, resetSession, targetContractName, rendezvousList, rendezvousAllFunctions, seed, runs, bail, regr, radio) => __awaiter(void 0, void 0, void 0, function* () {
|
|
37
42
|
// A map where the keys are the test contract identifiers and the values are
|
|
38
43
|
// arrays of their test functions. This map will be used to access the test
|
|
39
44
|
// functions for each test contract in the property-based testing routine.
|
|
40
45
|
const testContractsTestFunctions = filterTestFunctions(rendezvousAllFunctions);
|
|
41
|
-
|
|
42
|
-
statistics.test.successful.set(functionInterface.name, 0);
|
|
43
|
-
statistics.test.discarded.set(functionInterface.name, 0);
|
|
44
|
-
statistics.test.failed.set(functionInterface.name, 0);
|
|
45
|
-
}
|
|
46
|
+
const testContractId = rendezvousList[0];
|
|
46
47
|
const allTestFunctions = testContractsTestFunctions.get(testContractId);
|
|
47
48
|
const traitReferenceFunctionsCount = allTestFunctions.filter(traits_1.isTraitReferenceFunction).length;
|
|
48
49
|
const traitReferenceMap = (0, traits_1.buildTraitReferenceMap)(allTestFunctions);
|
|
@@ -59,13 +60,7 @@ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
59
60
|
: [];
|
|
60
61
|
// If the tests contain trait reference functions without eligible trait
|
|
61
62
|
// implementations, log a warning and filter out the functions.
|
|
62
|
-
|
|
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`));
|
|
68
|
-
}
|
|
63
|
+
emitMissingTraitWarning(radio, functionsMissingTraitImplementations);
|
|
69
64
|
// Filter out test functions with missing trait implementations from the
|
|
70
65
|
// enriched map.
|
|
71
66
|
const executableTestContractsTestFunctions = new Map([
|
|
@@ -76,7 +71,6 @@ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
76
71
|
.filter((functionInterface) => !functionsMissingTraitImplementations.includes(functionInterface.name)),
|
|
77
72
|
],
|
|
78
73
|
]);
|
|
79
|
-
radio.emit("logMessage", `\nStarting property testing type for the ${targetContractName} contract...\n`);
|
|
80
74
|
// Search for discard functions, for each test function. This map will
|
|
81
75
|
// be used to pair the test functions with their corresponding discard
|
|
82
76
|
// functions.
|
|
@@ -102,20 +96,91 @@ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
102
96
|
if (hasDiscardFunctionErrors) {
|
|
103
97
|
return;
|
|
104
98
|
}
|
|
105
|
-
const simnetAccounts = simnet.getAccounts();
|
|
106
|
-
const eligibleAccounts = new Map([...simnetAccounts].filter(([key]) => key !== "faucet"));
|
|
107
|
-
const simnetAddresses = Array.from(simnetAccounts.values());
|
|
108
99
|
const testFunctions = (0, shared_1.getFunctionsListForContract)(executableTestContractsTestFunctions, testContractId);
|
|
109
100
|
if ((testFunctions === null || testFunctions === void 0 ? void 0 : testFunctions.length) === 0) {
|
|
110
101
|
radio.emit("logMessage", (0, ansicolor_1.red)(`No test functions found for the "${targetContractName}" contract.\n`));
|
|
111
102
|
return;
|
|
112
103
|
}
|
|
113
|
-
|
|
114
|
-
|
|
104
|
+
if (regr) {
|
|
105
|
+
// Run regression tests only.
|
|
106
|
+
radio.emit("logMessage", `Regressions loaded from: ${(0, path_1.resolve)((0, persistence_1.getFailureFilePath)(testContractId))}`);
|
|
107
|
+
radio.emit("logMessage", `Loading ${targetContractName} contract regressions...\n`);
|
|
108
|
+
const regressions = (0, persistence_1.loadFailures)(testContractId, "test");
|
|
109
|
+
radio.emit("logMessage", `Found ${(0, ansicolor_1.underline)(`${regressions.length} regressions`)} for the ${targetContractName} contract.\n`);
|
|
110
|
+
for (const regression of regressions) {
|
|
111
|
+
emitPropertyRegressionTestHeader(radio, targetContractName, regression.seed, regression.numRuns, regression.timestamp);
|
|
112
|
+
yield resetSession();
|
|
113
|
+
yield propertyTest({
|
|
114
|
+
simnet,
|
|
115
|
+
targetContractName,
|
|
116
|
+
testContractId,
|
|
117
|
+
// If the number of runs that failed is less than 100, set it to the
|
|
118
|
+
// default value of 100. If more runs were needed to reproduce the
|
|
119
|
+
// failure, use the number of runs that failed.
|
|
120
|
+
runs: regression.numRuns < 100 ? 100 : regression.numRuns,
|
|
121
|
+
seed: regression.seed,
|
|
122
|
+
bail,
|
|
123
|
+
radio,
|
|
124
|
+
testFunctions,
|
|
125
|
+
projectTraitImplementations,
|
|
126
|
+
testContractsPairedFunctions,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// Run fresh tests using user-provided configuration.
|
|
132
|
+
radio.emit("logMessage", `Starting fresh round of property testing for the ${targetContractName} contract using user-provided configuration...\n`);
|
|
133
|
+
yield propertyTest({
|
|
134
|
+
simnet,
|
|
135
|
+
targetContractName,
|
|
136
|
+
testContractId,
|
|
137
|
+
runs,
|
|
138
|
+
seed,
|
|
139
|
+
bail,
|
|
140
|
+
radio,
|
|
141
|
+
testFunctions,
|
|
142
|
+
projectTraitImplementations,
|
|
143
|
+
testContractsPairedFunctions,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
exports.checkProperties = checkProperties;
|
|
148
|
+
/**
|
|
149
|
+
* Runs a property test.
|
|
150
|
+
* @param config The union of the configuration and context for the property
|
|
151
|
+
* test.
|
|
152
|
+
* @returns A promise that resolves when the property test is complete.
|
|
153
|
+
*/
|
|
154
|
+
const propertyTest = (config) => __awaiter(void 0, void 0, void 0, function* () {
|
|
155
|
+
const { simnet, targetContractName, testContractId, runs, seed, bail, radio, testFunctions, projectTraitImplementations, testContractsPairedFunctions, } = config;
|
|
156
|
+
// Derive accounts and addresses from simnet.
|
|
157
|
+
const simnetAccounts = simnet.getAccounts();
|
|
158
|
+
const eligibleAccounts = new Map([...simnetAccounts].filter(([key]) => key !== "faucet"));
|
|
159
|
+
const simnetAddresses = Array.from(simnetAccounts.values());
|
|
160
|
+
const statistics = {
|
|
161
|
+
test: {
|
|
162
|
+
successful: new Map(),
|
|
163
|
+
discarded: new Map(),
|
|
164
|
+
failed: new Map(),
|
|
165
|
+
},
|
|
115
166
|
};
|
|
116
|
-
|
|
167
|
+
for (const functionInterface of testFunctions) {
|
|
168
|
+
statistics.test.successful.set(functionInterface.name, 0);
|
|
169
|
+
statistics.test.discarded.set(functionInterface.name, 0);
|
|
170
|
+
statistics.test.failed.set(functionInterface.name, 0);
|
|
171
|
+
}
|
|
172
|
+
const radioReporter = (runDetails) => __awaiter(void 0, void 0, void 0, function* () {
|
|
173
|
+
(0, heatstroke_1.reporter)(runDetails, radio, "test", statistics);
|
|
174
|
+
// Persist failures for regression testing.
|
|
175
|
+
if (runDetails.failed) {
|
|
176
|
+
(0, persistence_1.persistFailure)(runDetails, "test", testContractId,
|
|
177
|
+
// No dialers in property-based testing.
|
|
178
|
+
undefined);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
yield fast_check_1.default.assert(fast_check_1.default.asyncProperty(fast_check_1.default
|
|
117
182
|
.record({
|
|
118
|
-
|
|
183
|
+
rendezvousContractId: fast_check_1.default.constant(testContractId),
|
|
119
184
|
testCaller: fast_check_1.default.constantFrom(...eligibleAccounts.entries()),
|
|
120
185
|
canMineBlocks: fast_check_1.default.boolean(),
|
|
121
186
|
})
|
|
@@ -144,7 +209,7 @@ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
144
209
|
})
|
|
145
210
|
: fast_check_1.default.constant(0),
|
|
146
211
|
})
|
|
147
|
-
.map((burnBlocks) => (Object.assign(Object.assign({}, r), burnBlocks)))), (r) => {
|
|
212
|
+
.map((burnBlocks) => (Object.assign(Object.assign({}, r), burnBlocks)))), (r) => __awaiter(void 0, void 0, void 0, function* () {
|
|
148
213
|
const selectedTestFunctionArgs = (0, shared_1.argsToCV)(r.selectedTestFunction, r.functionArgs);
|
|
149
214
|
const printedTestFunctionArgs = r.functionArgs
|
|
150
215
|
.map((arg) => {
|
|
@@ -160,9 +225,9 @@ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
160
225
|
.join(" ");
|
|
161
226
|
const [testCallerWallet, testCallerAddress] = r.testCaller;
|
|
162
227
|
const discardFunctionName = testContractsPairedFunctions
|
|
163
|
-
.get(r.
|
|
228
|
+
.get(r.rendezvousContractId)
|
|
164
229
|
.get(r.selectedTestFunction.name);
|
|
165
|
-
const discarded = isTestDiscarded(discardFunctionName, selectedTestFunctionArgs, r.
|
|
230
|
+
const discarded = isTestDiscarded(discardFunctionName, selectedTestFunctionArgs, r.rendezvousContractId, simnet, testCallerAddress);
|
|
166
231
|
if (discarded) {
|
|
167
232
|
statistics.test.discarded.set(r.selectedTestFunction.name, statistics.test.discarded.get(r.selectedTestFunction.name) + 1);
|
|
168
233
|
radio.emit("logMessage", `₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
|
|
@@ -177,7 +242,7 @@ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
177
242
|
try {
|
|
178
243
|
// If the function call results in a runtime error, the error will
|
|
179
244
|
// be caught and logged as a test failure in the catch block.
|
|
180
|
-
const { result: testFunctionCallResult } = simnet.callPublicFn(r.
|
|
245
|
+
const { result: testFunctionCallResult } = simnet.callPublicFn(r.rendezvousContractId, r.selectedTestFunction.name, selectedTestFunctionArgs, testCallerAddress);
|
|
181
246
|
const testFunctionCallResultJson = (0, transactions_1.cvToJSON)(testFunctionCallResult);
|
|
182
247
|
const discardedInPlace = (0, exports.isTestDiscardedInPlace)(testFunctionCallResultJson);
|
|
183
248
|
const testFunctionCallClarityResult = (0, transactions_1.cvToString)(testFunctionCallResult);
|
|
@@ -238,15 +303,38 @@ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
238
303
|
throw error;
|
|
239
304
|
}
|
|
240
305
|
}
|
|
241
|
-
}), {
|
|
306
|
+
})), {
|
|
242
307
|
endOnFailure: bail,
|
|
243
308
|
numRuns: runs,
|
|
244
309
|
reporter: radioReporter,
|
|
245
310
|
seed: seed,
|
|
246
311
|
verbose: true,
|
|
247
312
|
});
|
|
313
|
+
});
|
|
314
|
+
/**
|
|
315
|
+
* Emits a warning for test functions that reference traits without eligible
|
|
316
|
+
* implementations.
|
|
317
|
+
*/
|
|
318
|
+
const emitMissingTraitWarning = (radio, functionNames) => {
|
|
319
|
+
if (functionNames.length === 0) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const functionList = functionNames.map((fn) => ` - ${fn}`).join("\n");
|
|
323
|
+
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`));
|
|
324
|
+
radio.emit("logMessage", (0, ansicolor_1.yellow)(`Note: You can add contracts implementing traits either as project contracts or as requirements.\n`));
|
|
325
|
+
};
|
|
326
|
+
/**
|
|
327
|
+
* Emits a header for a regression test run with seed and run count information.
|
|
328
|
+
*/
|
|
329
|
+
const emitPropertyRegressionTestHeader = (radio, targetContractName, seed, numRuns, timestamp) => {
|
|
330
|
+
radio.emit("logMessage", shared_1.LOG_DIVIDER);
|
|
331
|
+
radio.emit("logMessage", `
|
|
332
|
+
Running ${(0, ansicolor_1.underline)(timestamp)} regression test for the ${targetContractName} contract with:
|
|
333
|
+
|
|
334
|
+
- Seed: ${seed}
|
|
335
|
+
- Runs: ${numRuns}
|
|
336
|
+
`);
|
|
248
337
|
};
|
|
249
|
-
exports.checkProperties = checkProperties;
|
|
250
338
|
const filterTestFunctions = (allFunctionsMap) => new Map(Array.from(allFunctionsMap, ([contractId, functions]) => [
|
|
251
339
|
contractId,
|
|
252
340
|
functions.filter((f) => f.access === "public" && f.name.startsWith("test-")),
|
|
@@ -326,3 +414,4 @@ class PropertyTestError extends Error {
|
|
|
326
414
|
this.clarityError = clarityError;
|
|
327
415
|
}
|
|
328
416
|
}
|
|
417
|
+
exports.PropertyTestError = PropertyTestError;
|
package/dist/shared.js
CHANGED
|
@@ -3,10 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getContractNameFromContractId = exports.argsToCV = exports.hexaString = exports.functionToArbitrary = exports.getFunctionsListForContract = exports.getFunctionsFromContractInterfaces = exports.getSimnetDeployerContractsInterfaces = void 0;
|
|
6
|
+
exports.getContractNameFromContractId = exports.argsToCV = exports.hexaString = exports.functionToArbitrary = exports.getFunctionsListForContract = exports.getFunctionsFromContractInterfaces = exports.getSimnetDeployerContractsInterfaces = exports.LOG_DIVIDER = void 0;
|
|
7
7
|
const fast_check_1 = __importDefault(require("fast-check"));
|
|
8
8
|
const transactions_1 = require("@stacks/transactions");
|
|
9
9
|
const traits_1 = require("./traits");
|
|
10
|
+
/** 79 characters long divider for logging. */
|
|
11
|
+
exports.LOG_DIVIDER = "-------------------------------------------------------------------------------";
|
|
10
12
|
/**
|
|
11
13
|
* Retrieves the contract interfaces of the contracts deployed by a specific
|
|
12
14
|
* deployer from the simnet instance.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacks/rendezvous",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Meet your contract's vulnerabilities head-on.",
|
|
5
5
|
"main": "app.js",
|
|
6
6
|
"bin": {
|
|
@@ -31,17 +31,17 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@iarna/toml": "^2.2.5",
|
|
34
|
-
"@stacks/clarinet-sdk": "^3.
|
|
35
|
-
"@stacks/transactions": "^7.
|
|
34
|
+
"@stacks/clarinet-sdk": "^3.14.0",
|
|
35
|
+
"@stacks/transactions": "^7.3.1",
|
|
36
36
|
"ansicolor": "^2.0.3",
|
|
37
|
-
"fast-check": "^4.3
|
|
38
|
-
"yaml": "^2.8.
|
|
37
|
+
"fast-check": "^4.5.3",
|
|
38
|
+
"yaml": "^2.8.2"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@stacks/clarinet-sdk-wasm": "^3.
|
|
41
|
+
"@stacks/clarinet-sdk-wasm": "^3.14.0",
|
|
42
42
|
"@types/jest": "^30.0.0",
|
|
43
43
|
"jest": "^30.2.0",
|
|
44
|
-
"ts-jest": "^29.4.
|
|
44
|
+
"ts-jest": "^29.4.6",
|
|
45
45
|
"typescript": "^5.9.3"
|
|
46
46
|
}
|
|
47
47
|
}
|