@stacks/rendezvous 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/app.js +45 -5
- package/dist/citizen.js +39 -38
- package/dist/dialer.js +115 -0
- package/dist/dialer.types.js +2 -0
- package/dist/heatstroke.js +35 -32
- package/dist/heatstroke.types.js +2 -0
- package/dist/invariant.js +131 -68
- 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/README.md
CHANGED
|
@@ -56,6 +56,8 @@ npx rv <path-to-clarinet-project> <contract-name> <type>
|
|
|
56
56
|
- `--path` – The path to use for the replay functionality.
|
|
57
57
|
- `--runs` – The number of test iterations to use for exercising the contracts.
|
|
58
58
|
(default: `100`)
|
|
59
|
+
- `--dial` – The path to a JavaScript file containing custom pre- and
|
|
60
|
+
post-execution functions (dialers).
|
|
59
61
|
|
|
60
62
|
---
|
|
61
63
|
|
package/dist/app.js
CHANGED
|
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
});
|
|
11
11
|
};
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.getManifestFileName = void 0;
|
|
13
14
|
exports.main = main;
|
|
14
15
|
const path_1 = require("path");
|
|
15
16
|
const events_1 = require("events");
|
|
@@ -19,13 +20,31 @@ const shared_1 = require("./shared");
|
|
|
19
20
|
const citizen_1 = require("./citizen");
|
|
20
21
|
const package_json_1 = require("./package.json");
|
|
21
22
|
const ansicolor_1 = require("ansicolor");
|
|
23
|
+
const fs_1 = require("fs");
|
|
24
|
+
const dialer_1 = require("./dialer");
|
|
22
25
|
const logger = (log, logLevel = "log") => {
|
|
23
26
|
console[logLevel](log);
|
|
24
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* Gets the manifest file name for a Clarinet project.
|
|
30
|
+
* If a custom manifest exists (`Clarinet-<contract-name>.toml`), it is used.
|
|
31
|
+
* Otherwise, the default `Clarinet.toml` is returned.
|
|
32
|
+
* @param manifestDir The relative path to the Clarinet project directory.
|
|
33
|
+
* @param targetContractName The target contract name.
|
|
34
|
+
* @returns The manifest file name.
|
|
35
|
+
*/
|
|
36
|
+
const getManifestFileName = (manifestDir, targetContractName) => {
|
|
37
|
+
const isCustomManifest = (0, fs_1.existsSync)((0, path_1.resolve)(manifestDir, `Clarinet-${targetContractName}.toml`));
|
|
38
|
+
if (isCustomManifest) {
|
|
39
|
+
return `Clarinet-${targetContractName}.toml`;
|
|
40
|
+
}
|
|
41
|
+
return "Clarinet.toml";
|
|
42
|
+
};
|
|
43
|
+
exports.getManifestFileName = getManifestFileName;
|
|
25
44
|
const helpMessage = `
|
|
26
45
|
rv v${package_json_1.version}
|
|
27
46
|
|
|
28
|
-
Usage: rv <path-to-clarinet-project> <contract-name> <type> [--seed=<seed>] [--path=<path>] [--runs=<runs>]
|
|
47
|
+
Usage: rv <path-to-clarinet-project> <contract-name> <type> [--seed=<seed>] [--path=<path>] [--runs=<runs>] [--dial=<path-to-dialers-file>] [--help]
|
|
29
48
|
|
|
30
49
|
Positional arguments:
|
|
31
50
|
path-to-clarinet-project - The path to the Clarinet project.
|
|
@@ -36,6 +55,7 @@ const helpMessage = `
|
|
|
36
55
|
--seed - The seed to use for the replay functionality.
|
|
37
56
|
--path - The path to use for the replay functionality.
|
|
38
57
|
--runs - The runs to use for iterating over the tests. Default: 100.
|
|
58
|
+
--dial – The path to a JavaScript file containing custom pre- and post-execution functions (dialers).
|
|
39
59
|
--help - Show the help message.
|
|
40
60
|
`;
|
|
41
61
|
const parseOptionalArgument = (argName) => {
|
|
@@ -74,8 +94,11 @@ function main() {
|
|
|
74
94
|
radio.emit("logMessage", helpMessage);
|
|
75
95
|
return;
|
|
76
96
|
}
|
|
77
|
-
/**
|
|
78
|
-
|
|
97
|
+
/**
|
|
98
|
+
* The relative path to the manifest file, either `Clarinet.toml` or
|
|
99
|
+
* `Clarinet-<contract-name>.toml`. If the latter exists, it is used.
|
|
100
|
+
*/
|
|
101
|
+
const manifestPath = (0, path_1.join)(manifestDir, (0, exports.getManifestFileName)(manifestDir, sutContractName));
|
|
79
102
|
radio.emit("logMessage", `Using manifest path: ${manifestPath}`);
|
|
80
103
|
radio.emit("logMessage", `Target contract: ${sutContractName}`);
|
|
81
104
|
const seed = parseInt(parseOptionalArgument("seed"), 10) || undefined;
|
|
@@ -90,7 +113,24 @@ function main() {
|
|
|
90
113
|
if (runs !== undefined) {
|
|
91
114
|
radio.emit("logMessage", `Using runs: ${runs}`);
|
|
92
115
|
}
|
|
93
|
-
|
|
116
|
+
/**
|
|
117
|
+
* The path to the dialer file. The dialer file allows the user to register
|
|
118
|
+
* custom pre and post-execution JavaScript functions to be executed before
|
|
119
|
+
* and after the public function calls during invariant testing.
|
|
120
|
+
*/
|
|
121
|
+
const dialPath = parseOptionalArgument("dial") || undefined;
|
|
122
|
+
if (dialPath !== undefined) {
|
|
123
|
+
radio.emit("logMessage", `Using dial path: ${dialPath}`);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The dialer registry, which is used to keep track of all the custom dialers
|
|
127
|
+
* registered by the user using the `--dial` flag.
|
|
128
|
+
*/
|
|
129
|
+
const dialerRegistry = dialPath !== undefined ? new dialer_1.DialerRegistry(dialPath) : undefined;
|
|
130
|
+
if (dialerRegistry !== undefined) {
|
|
131
|
+
dialerRegistry.registerDialers();
|
|
132
|
+
}
|
|
133
|
+
const simnet = yield (0, citizen_1.issueFirstClassCitizenship)(manifestDir, manifestPath, sutContractName);
|
|
94
134
|
/**
|
|
95
135
|
* The list of contract IDs for the SUT contract names, as per the simnet.
|
|
96
136
|
*/
|
|
@@ -101,7 +141,7 @@ function main() {
|
|
|
101
141
|
// If "test", call `checkProperties` for property-based testing.
|
|
102
142
|
switch (type) {
|
|
103
143
|
case "invariant": {
|
|
104
|
-
(0, invariant_1.checkInvariants)(simnet, sutContractName, rendezvousList, rendezvousAllFunctions, seed, path, runs, radio);
|
|
144
|
+
yield (0, invariant_1.checkInvariants)(simnet, sutContractName, rendezvousList, rendezvousAllFunctions, seed, path, runs, dialerRegistry, radio);
|
|
105
145
|
break;
|
|
106
146
|
}
|
|
107
147
|
case "test": {
|
package/dist/citizen.js
CHANGED
|
@@ -19,20 +19,20 @@ 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
|
|
29
|
+
* @param manifestDir The relative path to the manifest directory.
|
|
30
|
+
* @param manifestPath The absolute path to the manifest file.
|
|
31
|
+
* @param sutContractName The target contract name.
|
|
31
32
|
* @returns The initialized simnet instance with all contracts deployed, with
|
|
32
|
-
* the
|
|
33
|
+
* the test contract treated as a first-class citizen of the target contract.
|
|
33
34
|
*/
|
|
34
|
-
const issueFirstClassCitizenship = (manifestDir, sutContractName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
35
|
-
const manifestPath = (0, path_1.join)(manifestDir, "Clarinet.toml");
|
|
35
|
+
const issueFirstClassCitizenship = (manifestDir, manifestPath, sutContractName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
36
36
|
// Initialize the simnet, to generate the simnet plan and instance. The empty
|
|
37
37
|
// session will be set up, and contracts will be deployed in the correct
|
|
38
38
|
// order based on the simnet plan a few lines below.
|
|
@@ -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) => {
|
|
@@ -151,13 +152,13 @@ const buildRendezvousData = (simnetPlan, contractName, manifestDir) => {
|
|
|
151
152
|
rendezvousContractName: contractName,
|
|
152
153
|
};
|
|
153
154
|
}
|
|
154
|
-
catch (
|
|
155
|
-
throw new Error(`Error processing "${contractName}" contract: ${
|
|
155
|
+
catch (error) {
|
|
156
|
+
throw new Error(`Error processing "${contractName}" contract: ${error.message}`);
|
|
156
157
|
}
|
|
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.
|
|
@@ -203,19 +204,19 @@ const getTestContractSource = (simnetPlan, sutContractName, manifestDir) => {
|
|
|
203
204
|
encoding: "utf-8",
|
|
204
205
|
}).toString();
|
|
205
206
|
}
|
|
206
|
-
catch (
|
|
207
|
-
throw new Error(`Error retrieving the corresponding test contract for the "${sutContractName}" contract. ${
|
|
207
|
+
catch (error) {
|
|
208
|
+
throw new Error(`Error retrieving the corresponding test contract for the "${sutContractName}" contract. ${error.message}`);
|
|
208
209
|
}
|
|
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/dialer.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.PostDialerError = exports.PreDialerError = exports.DialerRegistry = void 0;
|
|
36
|
+
const fs_1 = require("fs");
|
|
37
|
+
const path_1 = require("path");
|
|
38
|
+
// In telephony, a registry is used for maintaining a known set of handlers,
|
|
39
|
+
// devices, or processes. This aligns with this class's purpose. Dialers are
|
|
40
|
+
// loaded/stored and run in a structured way, so "registry" fits both telephony
|
|
41
|
+
// and DI semantics.
|
|
42
|
+
class DialerRegistry {
|
|
43
|
+
constructor(dialPath) {
|
|
44
|
+
this.preDialers = [];
|
|
45
|
+
this.postDialers = [];
|
|
46
|
+
this.dialPath = dialPath;
|
|
47
|
+
}
|
|
48
|
+
registerPreDialer(dialer) {
|
|
49
|
+
this.preDialers.push(dialer);
|
|
50
|
+
}
|
|
51
|
+
registerPostDialer(dialer) {
|
|
52
|
+
this.postDialers.push(dialer);
|
|
53
|
+
}
|
|
54
|
+
registerDialers() {
|
|
55
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
const resolvedDialPath = (0, path_1.resolve)(this.dialPath);
|
|
57
|
+
if (!(0, fs_1.existsSync)(resolvedDialPath)) {
|
|
58
|
+
console.error(`Error: Dialer file not found: ${resolvedDialPath}`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const userModule = yield Promise.resolve(`${resolvedDialPath}`).then(s => __importStar(require(s)));
|
|
63
|
+
Object.entries(userModule).forEach(([key, fn]) => {
|
|
64
|
+
if (typeof fn === "function") {
|
|
65
|
+
if (key.startsWith("pre")) {
|
|
66
|
+
this.registerPreDialer(fn);
|
|
67
|
+
}
|
|
68
|
+
else if (key.startsWith("post")) {
|
|
69
|
+
this.registerPostDialer(fn);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.error(`Failed to load dialers:`, error);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
executePreDialers(context) {
|
|
81
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
82
|
+
if (this.preDialers.length === 0) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
for (const dial of this.preDialers) {
|
|
86
|
+
yield dial(context);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
executePostDialers(context) {
|
|
91
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
92
|
+
if (this.postDialers.length === 0) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
for (const dial of this.postDialers) {
|
|
96
|
+
yield dial(context);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.DialerRegistry = DialerRegistry;
|
|
102
|
+
class PreDialerError extends Error {
|
|
103
|
+
constructor(message) {
|
|
104
|
+
super(message);
|
|
105
|
+
this.name = "Pre-dialer error";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
exports.PreDialerError = PreDialerError;
|
|
109
|
+
class PostDialerError extends Error {
|
|
110
|
+
constructor(message) {
|
|
111
|
+
super(message);
|
|
112
|
+
this.name = "Post-dialer error";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.PostDialerError = PostDialerError;
|
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
|
@@ -1,4 +1,13 @@
|
|
|
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
|
};
|
|
@@ -10,7 +19,23 @@ const heatstroke_1 = require("./heatstroke");
|
|
|
10
19
|
const fast_check_1 = __importDefault(require("fast-check"));
|
|
11
20
|
const ansicolor_1 = require("ansicolor");
|
|
12
21
|
const traits_1 = require("./traits");
|
|
13
|
-
const
|
|
22
|
+
const dialer_1 = require("./dialer");
|
|
23
|
+
/**
|
|
24
|
+
* Runs invariant testing on the target contract and logs the progress. Reports
|
|
25
|
+
* the test results through a custom reporter.
|
|
26
|
+
* @param simnet The Simnet instance.
|
|
27
|
+
* @param targetContractName The name of the target contract.
|
|
28
|
+
* @param rendezvousList The list of contract IDs for each target contract.
|
|
29
|
+
* @param rendezvousAllFunctions The map of all function interfaces for each
|
|
30
|
+
* target contract.
|
|
31
|
+
* @param seed The seed for reproducible invariant testing.
|
|
32
|
+
* @param path The path for reproducible invariant testing.
|
|
33
|
+
* @param runs The number of test runs.
|
|
34
|
+
* @param dialerRegistry The custom dialer registry.
|
|
35
|
+
* @param radio The custom logging event emitter.
|
|
36
|
+
* @returns void
|
|
37
|
+
*/
|
|
38
|
+
const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousAllFunctions, seed, path, runs, dialerRegistry, radio) => __awaiter(void 0, void 0, void 0, function* () {
|
|
14
39
|
// A map where the keys are the Rendezvous identifiers and the values are
|
|
15
40
|
// arrays of their SUT (System Under Test) functions. This map will be used
|
|
16
41
|
// to access the SUT functions for each Rendezvous contract afterwards.
|
|
@@ -44,52 +69,57 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
44
69
|
return;
|
|
45
70
|
}
|
|
46
71
|
const enrichedSutFunctionsInterfaces = traitReferenceSutFunctions.length > 0
|
|
47
|
-
? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(
|
|
72
|
+
? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(targetContractName), (0, traits_1.buildTraitReferenceMap)(rendezvousSutFunctions.get(rendezvousContractId)), rendezvousSutFunctions.get(rendezvousContractId), rendezvousContractId)
|
|
48
73
|
: rendezvousSutFunctions;
|
|
49
74
|
const enrichedInvariantFunctionsInterfaces = traitReferenceInvariantFunctions.length > 0
|
|
50
|
-
? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(
|
|
75
|
+
? (0, traits_1.enrichInterfaceWithTraitData)(simnet.getContractAST(targetContractName), (0, traits_1.buildTraitReferenceMap)(rendezvousInvariantFunctions.get(rendezvousContractId)), rendezvousInvariantFunctions.get(rendezvousContractId), rendezvousContractId)
|
|
51
76
|
: rendezvousInvariantFunctions;
|
|
52
77
|
// Set up local context to track SUT function call counts.
|
|
53
78
|
const localContext = (0, exports.initializeLocalContext)(enrichedSutFunctionsInterfaces);
|
|
54
79
|
// Set up context in simnet by initializing state for SUT.
|
|
55
80
|
(0, exports.initializeClarityContext)(simnet, enrichedSutFunctionsInterfaces);
|
|
56
|
-
radio.emit("logMessage", `\nStarting invariant testing type for the ${
|
|
81
|
+
radio.emit("logMessage", `\nStarting invariant testing type for the ${targetContractName} contract...\n`);
|
|
57
82
|
const simnetAccounts = simnet.getAccounts();
|
|
58
83
|
const eligibleAccounts = new Map([...simnetAccounts].filter(([key]) => key !== "faucet"));
|
|
59
84
|
const simnetAddresses = Array.from(simnetAccounts.values());
|
|
60
85
|
const functions = (0, shared_1.getFunctionsListForContract)(enrichedSutFunctionsInterfaces, rendezvousContractId);
|
|
61
86
|
const invariants = (0, shared_1.getFunctionsListForContract)(enrichedInvariantFunctionsInterfaces, rendezvousContractId);
|
|
62
87
|
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 "${
|
|
88
|
+
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
89
|
return;
|
|
65
90
|
}
|
|
66
91
|
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 "${
|
|
92
|
+
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
93
|
return;
|
|
69
94
|
}
|
|
70
95
|
const radioReporter = (runDetails) => {
|
|
71
96
|
(0, heatstroke_1.reporter)(runDetails, radio, "invariant");
|
|
72
97
|
};
|
|
73
|
-
fast_check_1.default.assert(fast_check_1.default.
|
|
98
|
+
yield fast_check_1.default.assert(fast_check_1.default.asyncProperty(fast_check_1.default
|
|
74
99
|
.record({
|
|
75
100
|
// The target contract identifier. It is a constant value equal
|
|
76
101
|
// to the first contract in the list. The arbitrary is still needed,
|
|
77
102
|
// being used for reporting purposes in `heatstroke.ts`.
|
|
78
103
|
rendezvousContractId: fast_check_1.default.constant(rendezvousContractId),
|
|
79
|
-
sutCaller: fast_check_1.default.constantFrom(...eligibleAccounts.entries()),
|
|
80
104
|
invariantCaller: fast_check_1.default.constantFrom(...eligibleAccounts.entries()),
|
|
81
105
|
canMineBlocks: fast_check_1.default.boolean(),
|
|
82
106
|
})
|
|
83
107
|
.chain((r) => fast_check_1.default
|
|
84
108
|
.record({
|
|
85
|
-
|
|
109
|
+
selectedFunctions: fast_check_1.default.array(fast_check_1.default.constantFrom(...functions), {
|
|
110
|
+
minLength: 1, // At least one function must be selected.
|
|
111
|
+
}),
|
|
86
112
|
selectedInvariant: fast_check_1.default.constantFrom(...invariants),
|
|
87
113
|
})
|
|
88
114
|
.map((selectedFunctions) => (Object.assign(Object.assign({}, r), selectedFunctions))))
|
|
89
115
|
.chain((r) => fast_check_1.default
|
|
90
116
|
.record({
|
|
91
|
-
|
|
92
|
-
|
|
117
|
+
sutCallers: fast_check_1.default.array(fast_check_1.default.constantFrom(...eligibleAccounts.entries()), {
|
|
118
|
+
minLength: r.selectedFunctions.length,
|
|
119
|
+
maxLength: r.selectedFunctions.length,
|
|
120
|
+
}),
|
|
121
|
+
selectedFunctionsArgsList: fast_check_1.default.tuple(...r.selectedFunctions.map((selectedFunction) => fast_check_1.default.tuple(...(0, shared_1.functionToArbitrary)(selectedFunction, simnetAddresses, projectTraitImplementations)))),
|
|
122
|
+
invariantArgs: fast_check_1.default.tuple(...(0, shared_1.functionToArbitrary)(r.selectedInvariant, simnetAddresses, projectTraitImplementations)),
|
|
93
123
|
})
|
|
94
124
|
.map((args) => (Object.assign(Object.assign({}, r), args))))
|
|
95
125
|
.chain((r) => fast_check_1.default
|
|
@@ -107,59 +137,91 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
107
137
|
})
|
|
108
138
|
: fast_check_1.default.constant(0),
|
|
109
139
|
})
|
|
110
|
-
.map((burnBlocks) => (Object.assign(Object.assign({}, r), burnBlocks)))), (r) => {
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
|
|
140
|
+
.map((burnBlocks) => (Object.assign(Object.assign({}, r), burnBlocks)))), (r) => __awaiter(void 0, void 0, void 0, function* () {
|
|
141
|
+
const selectedFunctionsArgsCV = r.selectedFunctions.map((selectedFunction, index) => (0, shared_1.argsToCV)(selectedFunction, r.selectedFunctionsArgsList[index]));
|
|
142
|
+
const selectedInvariantArgsCV = (0, shared_1.argsToCV)(r.selectedInvariant, r.invariantArgs);
|
|
143
|
+
for (const [index, selectedFunction] of r.selectedFunctions.entries()) {
|
|
144
|
+
const [sutCallerWallet, sutCallerAddress] = r.sutCallers[index];
|
|
145
|
+
const printedFunctionArgs = r.selectedFunctionsArgsList[index]
|
|
146
|
+
.map((arg) => {
|
|
147
|
+
try {
|
|
148
|
+
return typeof arg === "object"
|
|
149
|
+
? JSON.stringify(arg)
|
|
150
|
+
: arg.toString();
|
|
151
|
+
}
|
|
152
|
+
catch (_a) {
|
|
153
|
+
return "[Circular]";
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
.join(" ");
|
|
115
157
|
try {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
158
|
+
if (dialerRegistry !== undefined) {
|
|
159
|
+
yield dialerRegistry.executePreDialers({
|
|
160
|
+
selectedFunction: selectedFunction,
|
|
161
|
+
functionCall: undefined,
|
|
162
|
+
clarityValueArguments: selectedFunctionsArgsCV[index],
|
|
163
|
+
});
|
|
164
|
+
}
|
|
119
165
|
}
|
|
120
|
-
catch (
|
|
121
|
-
|
|
166
|
+
catch (error) {
|
|
167
|
+
throw new dialer_1.PreDialerError(error.message);
|
|
122
168
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
169
|
+
try {
|
|
170
|
+
const functionCall = simnet.callPublicFn(r.rendezvousContractId, selectedFunction.name, selectedFunctionsArgsCV[index], sutCallerAddress);
|
|
171
|
+
const functionCallResultJson = (0, transactions_1.cvToJSON)(functionCall.result);
|
|
172
|
+
if (functionCallResultJson.success) {
|
|
173
|
+
localContext[r.rendezvousContractId][selectedFunction.name]++;
|
|
174
|
+
simnet.callPublicFn(r.rendezvousContractId, "update-context", [
|
|
175
|
+
transactions_1.Cl.stringAscii(selectedFunction.name),
|
|
176
|
+
transactions_1.Cl.uint(localContext[r.rendezvousContractId][selectedFunction.name]),
|
|
177
|
+
], simnet.deployer);
|
|
178
|
+
radio.emit("logMessage", `₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
|
|
179
|
+
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
180
|
+
(0, ansicolor_1.dim)(`${sutCallerWallet} `) +
|
|
181
|
+
`${targetContractName} ` +
|
|
182
|
+
`${(0, ansicolor_1.underline)(selectedFunction.name)} ` +
|
|
183
|
+
printedFunctionArgs);
|
|
184
|
+
try {
|
|
185
|
+
if (dialerRegistry !== undefined) {
|
|
186
|
+
yield dialerRegistry.executePostDialers({
|
|
187
|
+
selectedFunction: selectedFunction,
|
|
188
|
+
functionCall: functionCall,
|
|
189
|
+
clarityValueArguments: selectedFunctionsArgsCV[index],
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
throw new dialer_1.PostDialerError(error.message);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
radio.emit("logMessage", (0, ansicolor_1.dim)(`₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
|
|
199
|
+
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
200
|
+
`${sutCallerWallet} ` +
|
|
201
|
+
`${targetContractName} ` +
|
|
202
|
+
`${(0, ansicolor_1.underline)(selectedFunction.name)} ` +
|
|
203
|
+
printedFunctionArgs));
|
|
204
|
+
}
|
|
141
205
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
206
|
+
catch (error) {
|
|
207
|
+
if (error instanceof dialer_1.PreDialerError ||
|
|
208
|
+
error instanceof dialer_1.PostDialerError) {
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
// If the function call fails with a runtime error, log a dimmed
|
|
213
|
+
// message. Since the public function result is ignored, there's
|
|
214
|
+
// no need to throw an error.
|
|
215
|
+
radio.emit("logMessage", (0, ansicolor_1.dim)(`₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
|
|
216
|
+
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
217
|
+
`${sutCallerWallet} ` +
|
|
218
|
+
`${targetContractName} ` +
|
|
219
|
+
`${(0, ansicolor_1.underline)(selectedFunction.name)} ` +
|
|
220
|
+
printedFunctionArgs));
|
|
221
|
+
}
|
|
149
222
|
}
|
|
150
223
|
}
|
|
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
|
|
224
|
+
const printedInvariantArgs = r.invariantArgs
|
|
163
225
|
.map((arg) => {
|
|
164
226
|
try {
|
|
165
227
|
return typeof arg === "object"
|
|
@@ -173,19 +235,19 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
173
235
|
.join(" ");
|
|
174
236
|
const [invariantCallerWallet, invariantCallerAddress] = r.invariantCaller;
|
|
175
237
|
try {
|
|
176
|
-
const { result: invariantCallResult } = simnet.callReadOnlyFn(r.rendezvousContractId, r.selectedInvariant.name,
|
|
238
|
+
const { result: invariantCallResult } = simnet.callReadOnlyFn(r.rendezvousContractId, r.selectedInvariant.name, selectedInvariantArgsCV, invariantCallerAddress);
|
|
177
239
|
const invariantCallResultJson = (0, transactions_1.cvToJSON)(invariantCallResult);
|
|
178
240
|
if (invariantCallResultJson.value === true) {
|
|
179
241
|
radio.emit("logMessage", `₿ ${simnet.burnBlockHeight.toString().padStart(8)} ` +
|
|
180
242
|
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
181
243
|
`${(0, ansicolor_1.dim)(invariantCallerWallet)} ` +
|
|
182
244
|
`${(0, ansicolor_1.green)("[PASS]")} ` +
|
|
183
|
-
`${
|
|
245
|
+
`${targetContractName} ` +
|
|
184
246
|
`${(0, ansicolor_1.underline)(r.selectedInvariant.name)} ` +
|
|
185
247
|
printedInvariantArgs);
|
|
186
248
|
}
|
|
187
249
|
if (!invariantCallResultJson.value) {
|
|
188
|
-
throw new Error(`Invariant failed for ${
|
|
250
|
+
throw new Error(`Invariant failed for ${targetContractName} contract: "${r.selectedInvariant.name}" returned ${invariantCallResultJson.value}`);
|
|
189
251
|
}
|
|
190
252
|
}
|
|
191
253
|
catch (error) {
|
|
@@ -196,7 +258,7 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
196
258
|
`Ӿ ${simnet.blockHeight.toString().padStart(8)} ` +
|
|
197
259
|
`${invariantCallerWallet} ` +
|
|
198
260
|
`[FAIL] ` +
|
|
199
|
-
`${
|
|
261
|
+
`${targetContractName} ` +
|
|
200
262
|
`${(0, ansicolor_1.underline)(r.selectedInvariant.name)} ` +
|
|
201
263
|
printedInvariantArgs));
|
|
202
264
|
// Re-throw the error for fast-check to catch and process.
|
|
@@ -205,17 +267,17 @@ const checkInvariants = (simnet, sutContractName, rendezvousList, rendezvousAllF
|
|
|
205
267
|
if (r.canMineBlocks) {
|
|
206
268
|
simnet.mineEmptyBurnBlocks(r.burnBlocks);
|
|
207
269
|
}
|
|
208
|
-
}), {
|
|
270
|
+
})), {
|
|
209
271
|
verbose: true,
|
|
210
272
|
reporter: radioReporter,
|
|
211
273
|
seed: seed,
|
|
212
274
|
path: path,
|
|
213
275
|
numRuns: runs,
|
|
214
276
|
});
|
|
215
|
-
};
|
|
277
|
+
});
|
|
216
278
|
exports.checkInvariants = checkInvariants;
|
|
217
279
|
/**
|
|
218
|
-
*
|
|
280
|
+
* Initializes the local context, setting the number of times each function
|
|
219
281
|
* has been called to zero.
|
|
220
282
|
* @param rendezvousSutFunctions The Rendezvous functions.
|
|
221
283
|
* @returns The initialized local context.
|
|
@@ -236,14 +298,15 @@ const initializeClarityContext = (simnet, rendezvousSutFunctions) => rendezvousS
|
|
|
236
298
|
});
|
|
237
299
|
exports.initializeClarityContext = initializeClarityContext;
|
|
238
300
|
/**
|
|
239
|
-
* Filter the System Under Test (`SUT`) functions from the map of all
|
|
240
|
-
*
|
|
301
|
+
* Filter the System Under Test (`SUT`) functions from the map of all contract
|
|
302
|
+
* functions.
|
|
241
303
|
*
|
|
242
304
|
* The SUT functions are the ones that have `public` access since they are
|
|
243
305
|
* capable of changing the contract state, and they are not test functions.
|
|
244
306
|
* @param allFunctionsMap The map containing all the functions for each
|
|
307
|
+
* Rendezvous contract.
|
|
308
|
+
* @returns A map containing the filtered SUT functions for each Rendezvous
|
|
245
309
|
* contract.
|
|
246
|
-
* @returns A map containing only the SUT functions for each contract.
|
|
247
310
|
*/
|
|
248
311
|
const filterSutFunctions = (allFunctionsMap) => new Map(Array.from(allFunctionsMap, ([contractId, functions]) => [
|
|
249
312
|
contractId,
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacks/rendezvous",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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.4.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
|
}
|