@stacks/rendezvous 0.8.0 → 0.9.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/heatstroke.js +112 -1
- package/dist/invariant.js +27 -4
- package/dist/package.json +1 -1
- package/dist/property.js +18 -1
- package/package.json +1 -1
package/dist/heatstroke.js
CHANGED
|
@@ -20,7 +20,7 @@ const shared_1 = require("./shared");
|
|
|
20
20
|
* @param type The type of test that failed: invariant or property.
|
|
21
21
|
* @returns void
|
|
22
22
|
*/
|
|
23
|
-
function reporter(runDetails, radio, type) {
|
|
23
|
+
function reporter(runDetails, radio, type, statistics) {
|
|
24
24
|
var _a, _b;
|
|
25
25
|
if (runDetails.failed) {
|
|
26
26
|
// Report general run data.
|
|
@@ -79,4 +79,115 @@ function reporter(runDetails, radio, type) {
|
|
|
79
79
|
else {
|
|
80
80
|
radio.emit("logMessage", (0, ansicolor_1.green)(`\nOK, ${type === "invariant" ? "invariants" : "properties"} passed after ${runDetails.numRuns} runs.\n`));
|
|
81
81
|
}
|
|
82
|
+
reportStatistics(statistics, type, radio);
|
|
83
|
+
radio.emit("logMessage", "\n");
|
|
82
84
|
}
|
|
85
|
+
const ARROW = "->";
|
|
86
|
+
const SUCCESS_SYMBOL = "+";
|
|
87
|
+
const FAIL_SYMBOL = "-";
|
|
88
|
+
const WARN_SYMBOL = "!";
|
|
89
|
+
/**
|
|
90
|
+
* Reports execution statistics in a tree-like format.
|
|
91
|
+
* @param statistics The statistics object containing test execution data.
|
|
92
|
+
* @param type The type of test being reported.
|
|
93
|
+
* @param radio The event emitter for logging messages.
|
|
94
|
+
*/
|
|
95
|
+
function reportStatistics(statistics, type, radio) {
|
|
96
|
+
if ((type === "invariant" && (!statistics.invariant || !statistics.sut)) ||
|
|
97
|
+
(type === "test" && !statistics.test)) {
|
|
98
|
+
radio.emit("logMessage", "No statistics available for this run");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
radio.emit("logMessage", `\nEXECUTION STATISTICS\n`);
|
|
102
|
+
switch (type) {
|
|
103
|
+
case "invariant": {
|
|
104
|
+
radio.emit("logMessage", "│ PUBLIC FUNCTION CALLS");
|
|
105
|
+
radio.emit("logMessage", "│");
|
|
106
|
+
radio.emit("logMessage", `├─ ${SUCCESS_SYMBOL} SUCCESSFUL`);
|
|
107
|
+
logAsTree(Object.fromEntries(statistics.sut.successful), radio);
|
|
108
|
+
radio.emit("logMessage", "│");
|
|
109
|
+
radio.emit("logMessage", `├─ ${FAIL_SYMBOL} IGNORED`);
|
|
110
|
+
logAsTree(Object.fromEntries(statistics.sut.failed), radio);
|
|
111
|
+
radio.emit("logMessage", "│");
|
|
112
|
+
radio.emit("logMessage", "│ INVARIANT CHECKS");
|
|
113
|
+
radio.emit("logMessage", "│");
|
|
114
|
+
radio.emit("logMessage", `├─ ${SUCCESS_SYMBOL} PASSED`);
|
|
115
|
+
logAsTree(Object.fromEntries(statistics.invariant.successful), radio);
|
|
116
|
+
radio.emit("logMessage", "│");
|
|
117
|
+
radio.emit("logMessage", `└─ ${FAIL_SYMBOL} FAILED`);
|
|
118
|
+
logAsTree(Object.fromEntries(statistics.invariant.failed), radio, {
|
|
119
|
+
isLastSection: true,
|
|
120
|
+
});
|
|
121
|
+
radio.emit("logMessage", "\nLEGEND:\n");
|
|
122
|
+
radio.emit("logMessage", " SUCCESSFUL calls executed and advanced the test");
|
|
123
|
+
radio.emit("logMessage", " IGNORED calls failed but did not affect the test");
|
|
124
|
+
radio.emit("logMessage", " PASSED invariants maintained system integrity");
|
|
125
|
+
radio.emit("logMessage", " FAILED invariants indicate contract vulnerabilities");
|
|
126
|
+
if (computeTotalCount(statistics.invariant.failed) > 0) {
|
|
127
|
+
radio.emit("logFailure", "\n! FAILED invariants require immediate attention as they indicate that your contract can enter an invalid state under certain conditions.");
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
case "test": {
|
|
132
|
+
radio.emit("logMessage", "│ PROPERTY TEST CALLS");
|
|
133
|
+
radio.emit("logMessage", "│");
|
|
134
|
+
radio.emit("logMessage", `├─ ${SUCCESS_SYMBOL} PASSED`);
|
|
135
|
+
logAsTree(Object.fromEntries(statistics.test.successful), radio);
|
|
136
|
+
radio.emit("logMessage", "│");
|
|
137
|
+
radio.emit("logMessage", `├─ ${WARN_SYMBOL} DISCARDED`);
|
|
138
|
+
logAsTree(Object.fromEntries(statistics.test.discarded), radio);
|
|
139
|
+
radio.emit("logMessage", "│");
|
|
140
|
+
radio.emit("logMessage", `└─ ${FAIL_SYMBOL} FAILED`);
|
|
141
|
+
logAsTree(Object.fromEntries(statistics.test.failed), radio, {
|
|
142
|
+
isLastSection: true,
|
|
143
|
+
});
|
|
144
|
+
radio.emit("logMessage", "\nLEGEND:\n");
|
|
145
|
+
radio.emit("logMessage", " PASSED properties verified for given inputs");
|
|
146
|
+
radio.emit("logMessage", " DISCARDED skipped due to invalid preconditions");
|
|
147
|
+
radio.emit("logMessage", " FAILED property violations or unexpected behavior");
|
|
148
|
+
if (computeTotalCount(statistics.test.failed) > 0) {
|
|
149
|
+
radio.emit("logFailure", "\n! FAILED tests indicate that your function properties don't hold for all inputs. Review the counterexamples above for debugging.");
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Displays a tree structure of data.
|
|
157
|
+
* @param tree The object to display as a tree.
|
|
158
|
+
* @param radio The event emitter for logging messages.
|
|
159
|
+
* @param options Configuration options for tree display.
|
|
160
|
+
*/
|
|
161
|
+
function logAsTree(tree, radio, options = {}) {
|
|
162
|
+
const { isLastSection = false, baseIndent = " " } = options;
|
|
163
|
+
const printTree = (node, indent = baseIndent, isLastParent = true, radio) => {
|
|
164
|
+
const keys = Object.keys(node);
|
|
165
|
+
keys.forEach((key, index) => {
|
|
166
|
+
const isLast = index === keys.length - 1;
|
|
167
|
+
const connector = isLast ? "└─" : "├─";
|
|
168
|
+
const nextIndent = indent + (isLastParent ? " " : "│ ");
|
|
169
|
+
const leadingChar = isLastSection ? " " : "│";
|
|
170
|
+
if (typeof node[key] === "object" && node[key] !== null) {
|
|
171
|
+
radio.emit("logMessage", `${leadingChar} ${indent}${connector} ${ARROW} ${key}`);
|
|
172
|
+
printTree(node[key], nextIndent, isLast, radio);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
const count = node[key];
|
|
176
|
+
radio.emit("logMessage", `${leadingChar} ${indent}${connector} ${key}: x${count}`);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
printTree(tree, baseIndent, true, radio);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Computes the total number of failures from a failure map.
|
|
184
|
+
* @param failedMap Map containing failure counts by test name
|
|
185
|
+
* @returns The sum of all failure counts
|
|
186
|
+
*/
|
|
187
|
+
const computeTotalCount = (failedMap) => {
|
|
188
|
+
let totalFailures = 0;
|
|
189
|
+
for (const count of failedMap.values()) {
|
|
190
|
+
totalFailures += count;
|
|
191
|
+
}
|
|
192
|
+
return totalFailures;
|
|
193
|
+
};
|
package/dist/invariant.js
CHANGED
|
@@ -38,17 +38,35 @@ const dialer_1 = require("./dialer");
|
|
|
38
38
|
* @returns void
|
|
39
39
|
*/
|
|
40
40
|
const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousAllFunctions, seed, path, runs, bail, dialerRegistry, radio) => __awaiter(void 0, void 0, void 0, function* () {
|
|
41
|
+
const statistics = {
|
|
42
|
+
sut: {
|
|
43
|
+
successful: new Map(),
|
|
44
|
+
failed: new Map(),
|
|
45
|
+
},
|
|
46
|
+
invariant: {
|
|
47
|
+
successful: new Map(),
|
|
48
|
+
failed: new Map(),
|
|
49
|
+
},
|
|
50
|
+
};
|
|
41
51
|
// A map where the keys are the Rendezvous identifiers and the values are
|
|
42
52
|
// arrays of their SUT (System Under Test) functions. This map will be used
|
|
43
53
|
// to access the SUT functions for each Rendezvous contract afterwards.
|
|
44
54
|
const rendezvousSutFunctions = filterSutFunctions(rendezvousAllFunctions);
|
|
55
|
+
// The Rendezvous identifier is the first one in the list. Only one contract
|
|
56
|
+
// can be fuzzed at a time.
|
|
57
|
+
const rendezvousContractId = rendezvousList[0];
|
|
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
|
+
}
|
|
45
62
|
// A map where the keys are the Rendezvous identifiers and the values are
|
|
46
63
|
// arrays of their invariant functions. This map will be used to access the
|
|
47
64
|
// invariant functions for each Rendezvous contract afterwards.
|
|
48
65
|
const rendezvousInvariantFunctions = filterInvariantFunctions(rendezvousAllFunctions);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
for (const functionInterface of rendezvousInvariantFunctions.get(rendezvousContractId)) {
|
|
67
|
+
statistics.invariant.successful.set(functionInterface.name, 0);
|
|
68
|
+
statistics.invariant.failed.set(functionInterface.name, 0);
|
|
69
|
+
}
|
|
52
70
|
const traitReferenceSutFunctions = rendezvousSutFunctions
|
|
53
71
|
.get(rendezvousContractId)
|
|
54
72
|
.filter((fn) => (0, traits_1.isTraitReferenceFunction)(fn));
|
|
@@ -95,7 +113,7 @@ const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
95
113
|
return;
|
|
96
114
|
}
|
|
97
115
|
const radioReporter = (runDetails) => {
|
|
98
|
-
(0, heatstroke_1.reporter)(runDetails, radio, "invariant");
|
|
116
|
+
(0, heatstroke_1.reporter)(runDetails, radio, "invariant", statistics);
|
|
99
117
|
};
|
|
100
118
|
yield fast_check_1.default.assert(fast_check_1.default.asyncProperty(fast_check_1.default
|
|
101
119
|
.record({
|
|
@@ -178,6 +196,7 @@ const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
178
196
|
// call during the run.
|
|
179
197
|
const selectedFunctionClarityResult = (0, transactions_1.cvToString)(functionCall.result);
|
|
180
198
|
if (functionCallResultJson.success) {
|
|
199
|
+
statistics.sut.successful.set(selectedFunction.name, statistics.sut.successful.get(selectedFunction.name) + 1);
|
|
181
200
|
localContext[r.rendezvousContractId][selectedFunction.name]++;
|
|
182
201
|
simnet.callPublicFn(r.rendezvousContractId, "update-context", [
|
|
183
202
|
transactions_1.Cl.stringAscii(selectedFunction.name),
|
|
@@ -206,6 +225,7 @@ const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
206
225
|
}
|
|
207
226
|
else {
|
|
208
227
|
// Function call failed.
|
|
228
|
+
statistics.sut.failed.set(selectedFunction.name, statistics.sut.failed.get(selectedFunction.name) + 1);
|
|
209
229
|
radio.emit("logMessage", (0, ansicolor_1.dim)(`₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
|
|
210
230
|
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
211
231
|
`${sutCallerWallet} ` +
|
|
@@ -257,6 +277,8 @@ const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
257
277
|
const invariantCallResultJson = (0, transactions_1.cvToJSON)(invariantCallResult);
|
|
258
278
|
const invariantCallClarityResult = (0, transactions_1.cvToString)(invariantCallResult);
|
|
259
279
|
if (invariantCallResultJson.value === true) {
|
|
280
|
+
statistics.invariant.successful.set(r.selectedInvariant.name, statistics.invariant.successful.get(r.selectedInvariant.name) +
|
|
281
|
+
1);
|
|
260
282
|
radio.emit("logMessage", `₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
|
|
261
283
|
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
262
284
|
`${(0, ansicolor_1.dim)(invariantCallerWallet)} ` +
|
|
@@ -267,6 +289,7 @@ const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
267
289
|
(0, ansicolor_1.green)(invariantCallClarityResult));
|
|
268
290
|
}
|
|
269
291
|
else {
|
|
292
|
+
statistics.invariant.failed.set(r.selectedInvariant.name, statistics.invariant.failed.get(r.selectedInvariant.name) + 1);
|
|
270
293
|
radio.emit("logMessage", (0, ansicolor_1.red)(`₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
|
|
271
294
|
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
272
295
|
`${invariantCallerWallet} ` +
|
package/dist/package.json
CHANGED
package/dist/property.js
CHANGED
|
@@ -27,11 +27,23 @@ const traits_1 = require("./traits");
|
|
|
27
27
|
* @returns void
|
|
28
28
|
*/
|
|
29
29
|
const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousAllFunctions, seed, path, runs, bail, radio) => {
|
|
30
|
+
const statistics = {
|
|
31
|
+
test: {
|
|
32
|
+
successful: new Map(),
|
|
33
|
+
discarded: new Map(),
|
|
34
|
+
failed: new Map(),
|
|
35
|
+
},
|
|
36
|
+
};
|
|
30
37
|
const testContractId = rendezvousList[0];
|
|
31
38
|
// A map where the keys are the test contract identifiers and the values are
|
|
32
39
|
// arrays of their test functions. This map will be used to access the test
|
|
33
40
|
// functions for each test contract in the property-based testing routine.
|
|
34
41
|
const testContractsTestFunctions = filterTestFunctions(rendezvousAllFunctions);
|
|
42
|
+
for (const functionInterface of testContractsTestFunctions.get(testContractId)) {
|
|
43
|
+
statistics.test.successful.set(functionInterface.name, 0);
|
|
44
|
+
statistics.test.discarded.set(functionInterface.name, 0);
|
|
45
|
+
statistics.test.failed.set(functionInterface.name, 0);
|
|
46
|
+
}
|
|
35
47
|
const traitReferenceFunctions = testContractsTestFunctions
|
|
36
48
|
.get(testContractId)
|
|
37
49
|
.filter((fn) => (0, traits_1.isTraitReferenceFunction)(fn));
|
|
@@ -81,7 +93,7 @@ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
81
93
|
return;
|
|
82
94
|
}
|
|
83
95
|
const radioReporter = (runDetails) => {
|
|
84
|
-
(0, heatstroke_1.reporter)(runDetails, radio, "test");
|
|
96
|
+
(0, heatstroke_1.reporter)(runDetails, radio, "test", statistics);
|
|
85
97
|
};
|
|
86
98
|
fast_check_1.default.assert(fast_check_1.default.property(fast_check_1.default
|
|
87
99
|
.record({
|
|
@@ -134,6 +146,7 @@ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
134
146
|
.get(r.selectedTestFunction.name);
|
|
135
147
|
const discarded = isTestDiscarded(discardFunctionName, selectedTestFunctionArgs, r.testContractId, simnet, testCallerAddress);
|
|
136
148
|
if (discarded) {
|
|
149
|
+
statistics.test.discarded.set(r.selectedTestFunction.name, statistics.test.discarded.get(r.selectedTestFunction.name) + 1);
|
|
137
150
|
radio.emit("logMessage", `₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
|
|
138
151
|
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
139
152
|
`${(0, ansicolor_1.dim)(testCallerWallet)} ` +
|
|
@@ -151,6 +164,7 @@ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
151
164
|
const discardedInPlace = (0, exports.isTestDiscardedInPlace)(testFunctionCallResultJson);
|
|
152
165
|
const testFunctionCallClarityResult = (0, transactions_1.cvToString)(testFunctionCallResult);
|
|
153
166
|
if (discardedInPlace) {
|
|
167
|
+
statistics.test.discarded.set(r.selectedTestFunction.name, statistics.test.discarded.get(r.selectedTestFunction.name) + 1);
|
|
154
168
|
radio.emit("logMessage", `₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
|
|
155
169
|
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
156
170
|
`${(0, ansicolor_1.dim)(testCallerWallet)} ` +
|
|
@@ -163,6 +177,8 @@ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
163
177
|
else if (!discardedInPlace &&
|
|
164
178
|
testFunctionCallResultJson.success &&
|
|
165
179
|
testFunctionCallResultJson.value.value === true) {
|
|
180
|
+
statistics.test.successful.set(r.selectedTestFunction.name, statistics.test.successful.get(r.selectedTestFunction.name) +
|
|
181
|
+
1);
|
|
166
182
|
radio.emit("logMessage", `₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
|
|
167
183
|
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
168
184
|
`${(0, ansicolor_1.dim)(testCallerWallet)} ` +
|
|
@@ -176,6 +192,7 @@ const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousA
|
|
|
176
192
|
}
|
|
177
193
|
}
|
|
178
194
|
else {
|
|
195
|
+
statistics.test.failed.set(r.selectedTestFunction.name, statistics.test.failed.get(r.selectedTestFunction.name) + 1);
|
|
179
196
|
// The function call did not result in (ok true) or (ok false).
|
|
180
197
|
// Either the test failed or the test function returned an
|
|
181
198
|
// unexpected value i.e. `(ok 1)`.
|