@stacks/rendezvous 0.11.0 → 0.13.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 +1 -1
- package/dist/app.js +2 -30
- package/dist/app.types.js +1 -1
- package/dist/citizen.js +180 -318
- package/dist/dialer.js +17 -7
- package/dist/package.json +10 -10
- package/dist/shared.js +1 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Rendezvous `rv` is a Clarity fuzzer designed to cut through your smart contract'
|
|
|
8
8
|
|
|
9
9
|
### Prerequisites
|
|
10
10
|
|
|
11
|
-
- **Node.js**: Supported versions include 20, 22, and
|
|
11
|
+
- **Node.js**: Supported versions include 20, 22, and 24. Other versions may work, but they are untested.
|
|
12
12
|
|
|
13
13
|
### Inspiration
|
|
14
14
|
|
package/dist/app.js
CHANGED
|
@@ -9,15 +9,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
10
10
|
});
|
|
11
11
|
};
|
|
12
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
|
-
};
|
|
15
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.
|
|
13
|
+
exports.getManifestFileName = void 0;
|
|
17
14
|
exports.main = main;
|
|
18
15
|
const path_1 = require("path");
|
|
19
16
|
const events_1 = require("events");
|
|
20
|
-
const toml_1 = __importDefault(require("toml"));
|
|
21
17
|
const property_1 = require("./property");
|
|
22
18
|
const invariant_1 = require("./invariant");
|
|
23
19
|
const shared_1 = require("./shared");
|
|
@@ -30,13 +26,6 @@ const dialer_1 = require("./dialer");
|
|
|
30
26
|
const logger = (log, logLevel = "log") => {
|
|
31
27
|
console[logLevel](log);
|
|
32
28
|
};
|
|
33
|
-
/**
|
|
34
|
-
* The object used to initialize an empty simnet session with, when no remote
|
|
35
|
-
* data is enabled in the `Clarinet.toml` file.
|
|
36
|
-
*/
|
|
37
|
-
exports.noRemoteData = {
|
|
38
|
-
enabled: false,
|
|
39
|
-
};
|
|
40
29
|
/**
|
|
41
30
|
* Gets the manifest file name for a Clarinet project.
|
|
42
31
|
* If a custom manifest exists (`Clarinet-<contract-name>.toml`), it is used.
|
|
@@ -53,22 +42,6 @@ const getManifestFileName = (manifestDir, targetContractName) => {
|
|
|
53
42
|
return "Clarinet.toml";
|
|
54
43
|
};
|
|
55
44
|
exports.getManifestFileName = getManifestFileName;
|
|
56
|
-
const tryParseRemoteDataSettings = (manifestPath, radio) => {
|
|
57
|
-
var _a, _b;
|
|
58
|
-
const clarinetToml = toml_1.default.parse((0, fs_1.readFileSync)((0, path_1.resolve)(manifestPath), "utf-8"));
|
|
59
|
-
const remoteDataUserSettings = (_b = (_a = clarinetToml.repl) === null || _a === void 0 ? void 0 : _a.remote_data) !== null && _b !== void 0 ? _b : undefined;
|
|
60
|
-
if (remoteDataUserSettings && (remoteDataUserSettings === null || remoteDataUserSettings === void 0 ? void 0 : remoteDataUserSettings.enabled) === true) {
|
|
61
|
-
radio.emit("logMessage", (0, ansicolor_1.yellow)("\nUsing remote data. Setting up the environment can take up to a minute..."));
|
|
62
|
-
}
|
|
63
|
-
// If no remote data settings are provided, we still need to return an object
|
|
64
|
-
// with the `enabled` property set to `false`. That is what simnet expects
|
|
65
|
-
// at least in order to initialize an empty simnet session.
|
|
66
|
-
if (!remoteDataUserSettings) {
|
|
67
|
-
return exports.noRemoteData;
|
|
68
|
-
}
|
|
69
|
-
return remoteDataUserSettings;
|
|
70
|
-
};
|
|
71
|
-
exports.tryParseRemoteDataSettings = tryParseRemoteDataSettings;
|
|
72
45
|
const helpMessage = `
|
|
73
46
|
rv v${package_json_1.version}
|
|
74
47
|
|
|
@@ -169,8 +142,7 @@ function main() {
|
|
|
169
142
|
if (dialerRegistry !== undefined) {
|
|
170
143
|
dialerRegistry.registerDialers();
|
|
171
144
|
}
|
|
172
|
-
const
|
|
173
|
-
const simnet = yield (0, citizen_1.issueFirstClassCitizenship)(runConfig.manifestDir, manifestPath, remoteDataSettings, runConfig.sutContractName);
|
|
145
|
+
const simnet = yield (0, citizen_1.issueFirstClassCitizenship)(runConfig.manifestDir, manifestPath, runConfig.sutContractName, radio);
|
|
174
146
|
/**
|
|
175
147
|
* The list of contract IDs for the SUT contract names, as per the simnet.
|
|
176
148
|
*/
|
package/dist/app.types.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
2
|
+
// This file is a placeholder for the Rendezvous CLI-related types.
|
package/dist/citizen.js
CHANGED
|
@@ -12,181 +12,130 @@ 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.
|
|
15
|
+
exports.getTestContractSource = exports.buildRendezvousData = exports.issueFirstClassCitizenship = void 0;
|
|
16
16
|
exports.scheduleRendezvous = scheduleRendezvous;
|
|
17
17
|
const fs_1 = require("fs");
|
|
18
18
|
const path_1 = require("path");
|
|
19
|
-
const
|
|
19
|
+
const os_1 = require("os");
|
|
20
|
+
const toml_1 = require("@iarna/toml");
|
|
20
21
|
const yaml_1 = __importDefault(require("yaml"));
|
|
21
|
-
const clarinet_sdk_1 = require("@
|
|
22
|
-
const
|
|
22
|
+
const clarinet_sdk_1 = require("@stacks/clarinet-sdk");
|
|
23
|
+
const ansicolor_1 = require("ansicolor");
|
|
23
24
|
/**
|
|
24
|
-
* Prepares the simnet
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
25
|
+
* Prepares the simnet with the Rendezvous tests as first-class citizens of the
|
|
26
|
+
* target contract.
|
|
27
|
+
*
|
|
28
|
+
* This function works in an isolated temporary copy of the Clarinet project
|
|
29
|
+
* in /tmp/ to avoid lingering temporary files in the user's project directory.
|
|
30
|
+
* In case of system crashes, power outages, etc., the temp directory is
|
|
31
|
+
* automatically cleaned up by the OS on reboot.
|
|
30
32
|
*
|
|
31
33
|
* @param manifestDir The relative path to the manifest directory.
|
|
32
|
-
* @param manifestPath The
|
|
33
|
-
* @param remoteDataSettings The remote data settings.
|
|
34
|
+
* @param manifestPath The path to the manifest file.
|
|
34
35
|
* @param sutContractName The target contract name.
|
|
35
|
-
* @
|
|
36
|
-
*
|
|
36
|
+
* @param radio The event emitter to send log messages to.
|
|
37
|
+
* @returns The initialized simnet.
|
|
37
38
|
*/
|
|
38
|
-
const issueFirstClassCitizenship = (manifestDir, manifestPath,
|
|
39
|
-
var _a;
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
const issueFirstClassCitizenship = (manifestDir, manifestPath, sutContractName, radio) => __awaiter(void 0, void 0, void 0, function* () {
|
|
40
|
+
var _a, _b, _c, _d;
|
|
41
|
+
// First simnet initialization: This will generate the deployment plan and
|
|
42
|
+
// will type check the project without any Rendezvous tests.
|
|
43
|
+
try {
|
|
44
|
+
radio.emit("logMessage", `\nType-checking your Clarinet project...`);
|
|
45
|
+
yield (0, clarinet_sdk_1.generateDeployement)(manifestPath);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
throw new Error(`Error initializing simnet: ${(_a = error.message) !== null && _a !== void 0 ? _a : error}`);
|
|
49
|
+
}
|
|
44
50
|
const deploymentPlan = yaml_1.default.parse((0, fs_1.readFileSync)((0, path_1.join)(manifestDir, "deployments", "default.simnet-plan.yaml"), {
|
|
45
51
|
encoding: "utf-8",
|
|
46
52
|
}));
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// concatenated contract source code and the contract ID.
|
|
66
|
-
.map((contractName) => (0, exports.buildRendezvousData)(deploymentPlan, contractName, manifestDir))
|
|
67
|
-
// Use the contract ID as a key, mapping to the concatenated contract
|
|
68
|
-
// source code.
|
|
69
|
-
.map((rendezvousContractData) => [
|
|
70
|
-
rendezvousContractData.rendezvousContractId,
|
|
71
|
-
rendezvousContractData.rendezvousSourceCode,
|
|
72
|
-
]));
|
|
73
|
-
const clarinetToml = toml_1.default.parse((0, fs_1.readFileSync)(manifestPath, { encoding: "utf-8" }));
|
|
74
|
-
const cacheDir = ((_a = clarinetToml.project) === null || _a === void 0 ? void 0 : _a.cache_dir) || "./.cache";
|
|
75
|
-
// Deploy the contracts to the empty simnet session in the correct order.
|
|
76
|
-
yield (0, exports.deployContracts)(simnet, sortedContractsByEpoch, manifestDir, cacheDir, (name, sender, props) => (0, exports.getContractSource)([sutContractName], rendezvousSources, name, sender, props, manifestDir));
|
|
77
|
-
// Filter out addresses with zero balance. They do not need to be restored.
|
|
78
|
-
const sbtcBalancesToRestore = new Map([...sbtcBalancesMap.entries()].filter(([_, balance]) => balance !== 0));
|
|
79
|
-
// After all the contracts and requirements are deployed, if the test wallets
|
|
80
|
-
// had sBTC balances previously, restore them. If no test wallet previously
|
|
81
|
-
// owned sBTC, skip this step.
|
|
82
|
-
if ([...sbtcBalancesToRestore.keys()].length > 0) {
|
|
83
|
-
restoreSbtcBalances(simnet, sbtcBalancesToRestore);
|
|
53
|
+
const parsedManifest = (0, toml_1.parse)((0, fs_1.readFileSync)(manifestPath, { encoding: "utf-8" }));
|
|
54
|
+
const cacheDir = (_c = (_b = parsedManifest.project) === null || _b === void 0 ? void 0 : _b.cache_dir) !== null && _c !== void 0 ? _c : "./.cache";
|
|
55
|
+
const rendezvousData = (0, exports.buildRendezvousData)(cacheDir, deploymentPlan, sutContractName, manifestDir);
|
|
56
|
+
// Create isolated temp directory for the Rendezvous testing run.
|
|
57
|
+
const tempProjectDir = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), "rendezvous-run-"));
|
|
58
|
+
(0, fs_1.cpSync)(manifestDir, tempProjectDir, { recursive: true });
|
|
59
|
+
const [, contractName] = rendezvousData.rendezvousContractId.split(".");
|
|
60
|
+
const rendezvousContractsDir = (0, path_1.join)(tempProjectDir, "contracts");
|
|
61
|
+
const rendezvousPath = (0, path_1.join)(rendezvousContractsDir, `${contractName}-rendezvous.clar`);
|
|
62
|
+
(0, fs_1.writeFileSync)(rendezvousPath, rendezvousData.rendezvousSourceCode);
|
|
63
|
+
radio.emit("logMessage", `\nType-checking your Rendezvous project...`);
|
|
64
|
+
// Update the manifest in the temp directory to point to the Rendezvous
|
|
65
|
+
// concatenation.
|
|
66
|
+
const manifestFileName = (0, path_1.basename)(manifestPath);
|
|
67
|
+
const tempManifestPath = (0, path_1.join)(tempProjectDir, manifestFileName);
|
|
68
|
+
const tempParsedManifest = (0, toml_1.parse)((0, fs_1.readFileSync)(tempManifestPath, { encoding: "utf-8" }));
|
|
69
|
+
if (!tempParsedManifest.contracts) {
|
|
70
|
+
tempParsedManifest.contracts = {};
|
|
84
71
|
}
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const epoch = batch.epoch;
|
|
99
|
-
const contracts = batch.transactions
|
|
100
|
-
.filter((tx) => tx["emulated-contract-publish"])
|
|
101
|
-
.map((tx) => {
|
|
102
|
-
const contract = tx["emulated-contract-publish"];
|
|
103
|
-
return {
|
|
104
|
-
[contract["contract-name"]]: {
|
|
105
|
-
path: contract.path,
|
|
106
|
-
clarity_version: contract["clarity-version"],
|
|
107
|
-
},
|
|
108
|
-
};
|
|
109
|
-
});
|
|
110
|
-
if (contracts.length > 0) {
|
|
111
|
-
acc[epoch] = (acc[epoch] || []).concat(contracts);
|
|
112
|
-
}
|
|
113
|
-
return acc;
|
|
114
|
-
}, {});
|
|
115
|
-
};
|
|
116
|
-
exports.groupContractsByEpochFromDeploymentPlan = groupContractsByEpochFromDeploymentPlan;
|
|
117
|
-
/**
|
|
118
|
-
* Deploys the contracts to the simnet in the correct order.
|
|
119
|
-
* @param simnet The simnet instance.
|
|
120
|
-
* @param contractsByEpoch The record of contracts by epoch.
|
|
121
|
-
* @param getContractSourceFn The function to retrieve the contract source.
|
|
122
|
-
*/
|
|
123
|
-
const deployContracts = (simnet, contractsByEpoch, manifestDir, cacheDir, getContractSourceFn) => __awaiter(void 0, void 0, void 0, function* () {
|
|
124
|
-
for (const [epoch, contracts] of Object.entries(contractsByEpoch)) {
|
|
125
|
-
// Move to the next epoch and deploy the contracts in the correct order.
|
|
126
|
-
simnet.setEpoch(epoch);
|
|
127
|
-
for (const contract of contracts.flatMap(Object.entries)) {
|
|
128
|
-
const [name, props] = contract;
|
|
129
|
-
// Resolve paths to absolute for proper comparison.
|
|
130
|
-
const absoluteContractPath = (0, path_1.resolve)(manifestDir, props.path);
|
|
131
|
-
const absoluteRequirementsPath = (0, path_1.resolve)(manifestDir, cacheDir, "requirements");
|
|
132
|
-
// Check if contract is in requirements directory.
|
|
133
|
-
const isRequirement = absoluteContractPath.startsWith(absoluteRequirementsPath);
|
|
134
|
-
const sender = isRequirement
|
|
135
|
-
? (0, path_1.basename)(props.path).split(".")[0]
|
|
136
|
-
: simnet.deployer;
|
|
137
|
-
const source = getContractSourceFn(name, sender, props);
|
|
138
|
-
simnet.deployContract(name, source, { clarityVersion: props.clarity_version }, sender);
|
|
72
|
+
if (!tempParsedManifest.contracts[sutContractName]) {
|
|
73
|
+
tempParsedManifest.contracts[sutContractName] = {};
|
|
74
|
+
}
|
|
75
|
+
const relativeRendezvousPath = (0, path_1.relative)(tempProjectDir, rendezvousPath);
|
|
76
|
+
tempParsedManifest.contracts[sutContractName] = {
|
|
77
|
+
epoch: ((_d = tempParsedManifest.contracts[sutContractName].epoch) !== null && _d !== void 0 ? _d : "latest"),
|
|
78
|
+
path: relativeRendezvousPath,
|
|
79
|
+
};
|
|
80
|
+
// Convert epoch values to strings for TOML compatibility.
|
|
81
|
+
for (const contractName in tempParsedManifest.contracts) {
|
|
82
|
+
const contract = tempParsedManifest.contracts[contractName];
|
|
83
|
+
if ((contract === null || contract === void 0 ? void 0 : contract.epoch) && typeof contract.epoch === "number") {
|
|
84
|
+
contract.epoch = String(contract.epoch);
|
|
139
85
|
}
|
|
140
86
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const contractSource = rendezvousSourcesMap.get(contractId);
|
|
166
|
-
if (!contractSource) {
|
|
167
|
-
throw new Error(`Contract source not found for ${contractName}`);
|
|
87
|
+
(0, fs_1.writeFileSync)(tempManifestPath, (0, toml_1.stringify)(tempParsedManifest));
|
|
88
|
+
// Final simnet initialization: This will initialize the simnet with the
|
|
89
|
+
// target contract containing Rendezvous tests as first-class citizens.
|
|
90
|
+
//
|
|
91
|
+
// Windows cannot initialize simnet with absolute paths. Use relative path
|
|
92
|
+
// from the temp project directory.
|
|
93
|
+
// See: https://github.com/stx-labs/clarinet/issues/1634
|
|
94
|
+
const originalCwd = process.cwd();
|
|
95
|
+
try {
|
|
96
|
+
// Change the current working directory to the temp project directory.
|
|
97
|
+
// This is necessary because the simnet initialization requires the
|
|
98
|
+
// manifest file to be in the current working directory.
|
|
99
|
+
process.chdir(tempProjectDir);
|
|
100
|
+
// Initialize the simnet while suppressing stdout to avoid polluting output.
|
|
101
|
+
// Errors are still printed to stderr to help troubleshoot issues.
|
|
102
|
+
const originalWrite = process.stdout.write;
|
|
103
|
+
process.stdout.write = () => true;
|
|
104
|
+
try {
|
|
105
|
+
const simnet = yield (0, clarinet_sdk_1.initSimnet)(manifestFileName);
|
|
106
|
+
return simnet;
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
// Restore stdout.
|
|
110
|
+
process.stdout.write = originalWrite;
|
|
168
111
|
}
|
|
169
|
-
return contractSource;
|
|
170
112
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
113
|
+
finally {
|
|
114
|
+
// Restore the original current working directory.
|
|
115
|
+
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
|
+
}
|
|
175
123
|
}
|
|
176
|
-
};
|
|
177
|
-
exports.
|
|
124
|
+
});
|
|
125
|
+
exports.issueFirstClassCitizenship = issueFirstClassCitizenship;
|
|
178
126
|
/**
|
|
179
127
|
* Builds the Rendezvous data.
|
|
128
|
+
* @param cacheDir The cache directory path.
|
|
180
129
|
* @param deploymentPlan The parsed deployment plan.
|
|
181
130
|
* @param contractName The contract name.
|
|
182
131
|
* @param manifestDir The relative path to the manifest directory.
|
|
183
132
|
* @returns The Rendezvous data representing a record. The returned record
|
|
184
133
|
* contains the Rendezvous source code and the unique Rendezvous contract ID.
|
|
185
134
|
*/
|
|
186
|
-
const buildRendezvousData = (deploymentPlan, contractName, manifestDir) => {
|
|
135
|
+
const buildRendezvousData = (cacheDir, deploymentPlan, contractName, manifestDir) => {
|
|
187
136
|
try {
|
|
188
137
|
const sutContractSource = getDeploymentPlanContractSource(deploymentPlan, contractName, manifestDir);
|
|
189
|
-
const testContractSource = (0, exports.getTestContractSource)(deploymentPlan, contractName, manifestDir);
|
|
138
|
+
const testContractSource = (0, exports.getTestContractSource)(cacheDir, deploymentPlan, contractName, manifestDir);
|
|
190
139
|
const rendezvousSource = scheduleRendezvous(sutContractSource, testContractSource);
|
|
191
140
|
const rendezvousContractEmulatedSender = getSutContractDeploymentPlanEmulatedPublish(deploymentPlan, contractName)["emulated-sender"];
|
|
192
141
|
// Use the contract ID as a unique identifier of the contract within the
|
|
@@ -215,43 +164,6 @@ const getDeploymentPlanContractSource = (deploymentPlan, sutContractName, manife
|
|
|
215
164
|
encoding: "utf-8",
|
|
216
165
|
}).toString();
|
|
217
166
|
};
|
|
218
|
-
/**
|
|
219
|
-
* Retrieves the test contract source code.
|
|
220
|
-
* @param deploymentPlan The parsed deployment plan.
|
|
221
|
-
* @param sutContractName The target contract name.
|
|
222
|
-
* @param manifestDir The relative path to the manifest directory.
|
|
223
|
-
* @returns The test contract source code.
|
|
224
|
-
*/
|
|
225
|
-
const getTestContractSource = (deploymentPlan, sutContractName, manifestDir) => {
|
|
226
|
-
const sutContractPath = getSutContractDeploymentPlanEmulatedPublish(deploymentPlan, sutContractName).path;
|
|
227
|
-
const clarityExtension = ".clar";
|
|
228
|
-
if (!sutContractPath.endsWith(clarityExtension)) {
|
|
229
|
-
throw new Error(`Invalid contract extension for the "${sutContractName}" contract.`);
|
|
230
|
-
}
|
|
231
|
-
// If the sutContractPath is located under .cache/requirements/ path, search
|
|
232
|
-
// for the test contract in the classic `contracts` directory.
|
|
233
|
-
if (sutContractPath.includes(".cache")) {
|
|
234
|
-
const relativePath = sutContractPath.split(".cache/requirements/")[1];
|
|
235
|
-
const relativePathTestContract = relativePath.replace(clarityExtension, `.tests${clarityExtension}`);
|
|
236
|
-
return (0, fs_1.readFileSync)((0, path_1.join)(manifestDir, "contracts", relativePathTestContract), {
|
|
237
|
-
encoding: "utf-8",
|
|
238
|
-
}).toString();
|
|
239
|
-
}
|
|
240
|
-
// If the contract is not under the `.cache/requirements/` path, we assume it
|
|
241
|
-
// is located in a regular path specified in the manifest file. Just search
|
|
242
|
-
// for the test contract near the SUT one, following the naming
|
|
243
|
-
// convention: `<contract-name>.tests.clar`.
|
|
244
|
-
const testContractPath = sutContractPath.replace(clarityExtension, `.tests${clarityExtension}`);
|
|
245
|
-
try {
|
|
246
|
-
return (0, fs_1.readFileSync)((0, path_1.join)(manifestDir, testContractPath), {
|
|
247
|
-
encoding: "utf-8",
|
|
248
|
-
}).toString();
|
|
249
|
-
}
|
|
250
|
-
catch (error) {
|
|
251
|
-
throw new Error(`Error retrieving the corresponding test contract for the "${sutContractName}" contract. ${error.message}`);
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
exports.getTestContractSource = getTestContractSource;
|
|
255
167
|
/**
|
|
256
168
|
* Retrieves the emulated contract publish data of the target contract from the
|
|
257
169
|
* deployment plan. If multiple contracts share the same name in the deployment
|
|
@@ -288,6 +200,9 @@ const getSutContractDeploymentPlanEmulatedPublish = (deploymentPlan, sutContract
|
|
|
288
200
|
// having the same name, select the one deployed by the deployer.
|
|
289
201
|
const targetContractDeploymentData = (_b = contractPublishMatchesByName.find((transaction) => transaction["emulated-contract-publish"]["emulated-sender"] ===
|
|
290
202
|
deployer)) === null || _b === void 0 ? void 0 : _b["emulated-contract-publish"];
|
|
203
|
+
// TODO: Consider handling requirements and project contracts separately.
|
|
204
|
+
// Eventually let the user specify if the contract is a requirement or a
|
|
205
|
+
// project contract.
|
|
291
206
|
// This is an edge case that can happen in practice. If the project has two
|
|
292
207
|
// requirements that share the same contract name, Rendezvous will not be
|
|
293
208
|
// able to select the one to be fuzzed. The recommendation for users would
|
|
@@ -304,6 +219,87 @@ const getSutContractDeploymentPlanEmulatedPublish = (deploymentPlan, sutContract
|
|
|
304
219
|
}
|
|
305
220
|
return contractNameMatch;
|
|
306
221
|
};
|
|
222
|
+
/**
|
|
223
|
+
* Retrieves the test contract source code for a project contract.
|
|
224
|
+
* @param contractPath The relative path to the contract.
|
|
225
|
+
* @param manifestDir The relative path to the manifest directory.
|
|
226
|
+
* @returns The test contract source code or `null` if the test contract is not
|
|
227
|
+
* found.
|
|
228
|
+
*/
|
|
229
|
+
const getProjectContractTestSrc = (contractPath, manifestDir) => {
|
|
230
|
+
const clarityExtension = ".clar";
|
|
231
|
+
const lastExtensionIndex = contractPath.lastIndexOf(clarityExtension);
|
|
232
|
+
const testContractPath = lastExtensionIndex !== -1
|
|
233
|
+
? contractPath.slice(0, lastExtensionIndex) +
|
|
234
|
+
`.tests${clarityExtension}` +
|
|
235
|
+
contractPath.slice(lastExtensionIndex + clarityExtension.length)
|
|
236
|
+
: `${contractPath}.tests${clarityExtension}`;
|
|
237
|
+
try {
|
|
238
|
+
const fullPath = (0, path_1.join)(manifestDir, testContractPath);
|
|
239
|
+
const content = (0, fs_1.readFileSync)(fullPath, {
|
|
240
|
+
encoding: "utf-8",
|
|
241
|
+
}).toString();
|
|
242
|
+
return content;
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* Retrieves the test contract source code for a requirement contract. It
|
|
250
|
+
* searches for the test contract in the `contracts` directory of the Clarinet
|
|
251
|
+
* project.
|
|
252
|
+
* @param cacheDir The cache directory path.
|
|
253
|
+
* @param sutContractPath The path to the SUT contract.
|
|
254
|
+
* @param manifestDir The relative path to the manifest directory.
|
|
255
|
+
* @returns The test contract source code or `null` if the test contract is not
|
|
256
|
+
* found.
|
|
257
|
+
*/
|
|
258
|
+
const getRequirementContractTestSrc = (cacheDir, sutContractPath, manifestDir) => {
|
|
259
|
+
const normalizedCacheDir = cacheDir.replace(/[\/\\]$/, "");
|
|
260
|
+
const requirementsRelativePath = `${normalizedCacheDir}/requirements/`;
|
|
261
|
+
if (!sutContractPath.includes(requirementsRelativePath)) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
const relativePath = sutContractPath.split(requirementsRelativePath)[1];
|
|
265
|
+
const clarityExtension = ".clar";
|
|
266
|
+
const lastExtensionIndex = relativePath.lastIndexOf(clarityExtension);
|
|
267
|
+
const relativePathTestContract = lastExtensionIndex !== -1
|
|
268
|
+
? relativePath.slice(0, lastExtensionIndex) +
|
|
269
|
+
`.tests${clarityExtension}` +
|
|
270
|
+
relativePath.slice(lastExtensionIndex + clarityExtension.length)
|
|
271
|
+
: `${relativePath}.tests${clarityExtension}`;
|
|
272
|
+
return (0, fs_1.readFileSync)((0, path_1.join)(manifestDir, "contracts", relativePathTestContract), {
|
|
273
|
+
encoding: "utf-8",
|
|
274
|
+
}).toString();
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* Retrieves the test contract source code.
|
|
278
|
+
* Project contracts have priority. Requirement contracts are only checked
|
|
279
|
+
* if project contract test is not found.
|
|
280
|
+
* @param cacheDir The cache directory path.
|
|
281
|
+
* @param deploymentPlan The parsed deployment plan.
|
|
282
|
+
* @param sutContractName The target contract name.
|
|
283
|
+
* @param manifestDir The relative path to the manifest directory.
|
|
284
|
+
* @returns The test contract source code.
|
|
285
|
+
*/
|
|
286
|
+
const getTestContractSource = (cacheDir, deploymentPlan, sutContractName, manifestDir) => {
|
|
287
|
+
const sutContractPath = getSutContractDeploymentPlanEmulatedPublish(deploymentPlan, sutContractName).path;
|
|
288
|
+
// Prioritize project contracts. Try project contract test first.
|
|
289
|
+
const projectTestContract = getProjectContractTestSrc(sutContractPath, manifestDir);
|
|
290
|
+
if (projectTestContract !== null) {
|
|
291
|
+
return projectTestContract;
|
|
292
|
+
}
|
|
293
|
+
// Fallback to requirement contract test if project contract test not found.
|
|
294
|
+
const normalizedCacheDir = cacheDir || "./.cache";
|
|
295
|
+
const requirementTestContract = getRequirementContractTestSrc(normalizedCacheDir, sutContractPath, manifestDir);
|
|
296
|
+
if (requirementTestContract !== null) {
|
|
297
|
+
return requirementTestContract;
|
|
298
|
+
}
|
|
299
|
+
// No corresponding test contract was found for the SUT contract.
|
|
300
|
+
throw new Error(`Error retrieving the corresponding test contract for the "${sutContractName}" contract.`);
|
|
301
|
+
};
|
|
302
|
+
exports.getTestContractSource = getTestContractSource;
|
|
307
303
|
/**
|
|
308
304
|
* Schedules a Rendezvous between the System Under Test (`SUT`) and the test
|
|
309
305
|
* contract.
|
|
@@ -325,137 +321,3 @@ function scheduleRendezvous(targetContractSource, tests) {
|
|
|
325
321
|
(ok (map-set context function-name {called: called})))`;
|
|
326
322
|
return `${targetContractSource}\n\n${context}\n\n${tests}`;
|
|
327
323
|
}
|
|
328
|
-
/**
|
|
329
|
-
* Checks if a contract can be found in the deployment plan.
|
|
330
|
-
* @param deploymentPlan The parsed deployment plan.
|
|
331
|
-
* @param contractAddress The address of the contract.
|
|
332
|
-
* @param contractName The name of the contract.
|
|
333
|
-
* @returns True if the contract can be found in the deployment plan, false
|
|
334
|
-
* otherwise.
|
|
335
|
-
*/
|
|
336
|
-
const isContractInDeploymentPlan = (deploymentPlan, contractAddress, contractName) => {
|
|
337
|
-
return deploymentPlan.plan.batches.some((batch) => batch.transactions.some((transaction) => {
|
|
338
|
-
var _a, _b;
|
|
339
|
-
return ((_a = transaction["emulated-contract-publish"]) === null || _a === void 0 ? void 0 : _a["contract-name"]) ===
|
|
340
|
-
contractName &&
|
|
341
|
-
((_b = transaction["emulated-contract-publish"]) === null || _b === void 0 ? void 0 : _b["emulated-sender"]) ===
|
|
342
|
-
contractAddress;
|
|
343
|
-
}));
|
|
344
|
-
};
|
|
345
|
-
/**
|
|
346
|
-
* Maps the simnet accounts to their sBTC balances. The function tries to call
|
|
347
|
-
* the `get-balance` function of the `sbtc-token` contract for each address. If
|
|
348
|
-
* the call fails, it returns a balance of 0 for that address. The call fails
|
|
349
|
-
* if the user is not working with sBTC.
|
|
350
|
-
* @param simnet The simnet instance.
|
|
351
|
-
* @param deploymentPlan The parsed deployment plan.
|
|
352
|
-
* @param remoteDataSettings The remote data settings.
|
|
353
|
-
* @returns A map of addresses to their sBTC balances.
|
|
354
|
-
*/
|
|
355
|
-
const getSbtcBalancesFromSimnet = (simnet, deploymentPlan, remoteDataSettings) => {
|
|
356
|
-
// If the user is not using remote data and the deployment plan does not
|
|
357
|
-
// contain the `sbtc-token` contract, return a map with 0 balances for all
|
|
358
|
-
// addresses. When remote data is enabled, the sbtc-token contract will not
|
|
359
|
-
// necessarily be present in the deployment plan.
|
|
360
|
-
if (!remoteDataSettings.enabled &&
|
|
361
|
-
!isContractInDeploymentPlan(deploymentPlan, "SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4", "sbtc-token")) {
|
|
362
|
-
return new Map([...simnet.getAccounts().values()].map((address) => [address, 0]));
|
|
363
|
-
}
|
|
364
|
-
return new Map([...simnet.getAccounts().values()].map((address) => {
|
|
365
|
-
try {
|
|
366
|
-
const { result: getBalanceResult } = simnet.callReadOnlyFn("SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token", "get-balance", [transactions_1.Cl.principal(address)], address);
|
|
367
|
-
// If the previous read-only call works, the user is working with
|
|
368
|
-
// sBTC. This means we can proceed with restoring sBTC balances.
|
|
369
|
-
const sbtcBalanceJSON = (0, transactions_1.cvToJSON)(getBalanceResult);
|
|
370
|
-
// The `get-balance` function returns a response containing the uint
|
|
371
|
-
// balance of the address. In the JSON representation, the balance is
|
|
372
|
-
// represented as a string. We need to parse it to an integer.
|
|
373
|
-
const sbtcBalance = parseInt(sbtcBalanceJSON.value.value, 10);
|
|
374
|
-
return [address, sbtcBalance];
|
|
375
|
-
}
|
|
376
|
-
catch (e) {
|
|
377
|
-
return [address, 0];
|
|
378
|
-
}
|
|
379
|
-
}));
|
|
380
|
-
};
|
|
381
|
-
exports.getSbtcBalancesFromSimnet = getSbtcBalancesFromSimnet;
|
|
382
|
-
/**
|
|
383
|
-
* Utility function that restores the test wallets' initial sBTC balances in
|
|
384
|
-
* the re-initialized first-class citizenship simnet.
|
|
385
|
-
*
|
|
386
|
-
* @param simnet The simnet instance.
|
|
387
|
-
* @param sbtcBalancesMap A map containing the test wallets' balances to be
|
|
388
|
-
* restored.
|
|
389
|
-
*/
|
|
390
|
-
const restoreSbtcBalances = (simnet, sbtcBalancesMap) => {
|
|
391
|
-
// For each address present in the balances map, restore the balance.
|
|
392
|
-
[...sbtcBalancesMap.entries()]
|
|
393
|
-
// Re-assure the map does not contain nil balances.
|
|
394
|
-
.filter(([_, balance]) => balance !== 0)
|
|
395
|
-
.forEach(([address, balance]) => {
|
|
396
|
-
// To deposit sBTC, one needs a txId and a sweep txId. A deposit transaction
|
|
397
|
-
// must have a unique txId and sweep txId.
|
|
398
|
-
const txId = getUniqueHex();
|
|
399
|
-
const sweepTxId = getUniqueHex();
|
|
400
|
-
mintSbtc(simnet, balance, address, txId, sweepTxId);
|
|
401
|
-
});
|
|
402
|
-
};
|
|
403
|
-
/**
|
|
404
|
-
* Utility function to deposit an amount of sBTC to a Stacks address.
|
|
405
|
-
*
|
|
406
|
-
* @param simnet The simnet instance.
|
|
407
|
-
* @param amountSats The amount to mint in sats.
|
|
408
|
-
* @param recipient The Stacks address to mint sBTC to.
|
|
409
|
-
* @param txId A unique hex to use for the deposit.
|
|
410
|
-
* @param sweepTxId A unique hex to use for the deposit.
|
|
411
|
-
*/
|
|
412
|
-
const mintSbtc = (simnet, amountSats, recipient, txId, sweepTxId) => {
|
|
413
|
-
// Calling `get-burn-block-info?` only works for past burn heights. We mine
|
|
414
|
-
// one empty Bitcoin block if the initial height is 0 and use the previous
|
|
415
|
-
// burn height to retrieve the burn header hash.
|
|
416
|
-
if (simnet.burnBlockHeight === 0) {
|
|
417
|
-
simnet.mineEmptyBurnBlock();
|
|
418
|
-
}
|
|
419
|
-
const previousBurnHeight = simnet.burnBlockHeight - 1;
|
|
420
|
-
const burnHash = (0, transactions_1.hexToCV)(simnet.runSnippet(`(get-burn-block-info? header-hash u${previousBurnHeight})`));
|
|
421
|
-
if (burnHash === null || burnHash.type === transactions_1.ClarityType.OptionalNone) {
|
|
422
|
-
throw new Error("Something went wrong trying to retrieve the burn header.");
|
|
423
|
-
}
|
|
424
|
-
const completeDepositTx = (0, transactions_1.cvToJSON)(simnet.callPublicFn("SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-deposit", "complete-deposit-wrapper", [
|
|
425
|
-
// (txid (buff 32))
|
|
426
|
-
transactions_1.Cl.bufferFromHex(txId),
|
|
427
|
-
// (vout-index uint)
|
|
428
|
-
transactions_1.Cl.uint(1),
|
|
429
|
-
// (amount uint)
|
|
430
|
-
transactions_1.Cl.uint(amountSats),
|
|
431
|
-
// (recipient principal)
|
|
432
|
-
transactions_1.Cl.principal(recipient),
|
|
433
|
-
// (burn-hash (buff 32))
|
|
434
|
-
burnHash.value,
|
|
435
|
-
// (burn-height uint)
|
|
436
|
-
transactions_1.Cl.uint(previousBurnHeight),
|
|
437
|
-
// (sweep-txid (buff 32))
|
|
438
|
-
transactions_1.Cl.bufferFromHex(sweepTxId),
|
|
439
|
-
], "SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4").result);
|
|
440
|
-
// If the deposit transaction fails, an unexpected outcome can happen. Throw
|
|
441
|
-
// an error if the transaction is not successful.
|
|
442
|
-
if (!completeDepositTx.success) {
|
|
443
|
-
throw new Error("Something went wrong trying to restore sBTC balances.");
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
|
-
/**
|
|
447
|
-
* Utility function that generates a random, unique hex to be used as txId in
|
|
448
|
-
* `mintSbtc`.
|
|
449
|
-
*
|
|
450
|
-
* @returns A random hex string.
|
|
451
|
-
*/
|
|
452
|
-
const getUniqueHex = () => {
|
|
453
|
-
let hex;
|
|
454
|
-
// Generate a 32-byte (64 character) random hex string.
|
|
455
|
-
const bytes = new Uint8Array(32);
|
|
456
|
-
crypto.getRandomValues(bytes);
|
|
457
|
-
hex = Array.from(bytes)
|
|
458
|
-
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
459
|
-
.join("");
|
|
460
|
-
return hex;
|
|
461
|
-
};
|
package/dist/dialer.js
CHANGED
|
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
36
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
37
|
return new (P || (P = Promise))(function (resolve, reject) {
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacks/rendezvous",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Meet your contract's vulnerabilities head-on.",
|
|
5
5
|
"main": "app.js",
|
|
6
6
|
"bin": {
|
|
@@ -30,18 +30,18 @@
|
|
|
30
30
|
"url": "https://github.com/stacks-network/rendezvous.git"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@
|
|
33
|
+
"@iarna/toml": "^2.2.5",
|
|
34
|
+
"@stacks/clarinet-sdk": "^3.12.0",
|
|
34
35
|
"@stacks/transactions": "^7.2.0",
|
|
35
36
|
"ansicolor": "^2.0.3",
|
|
36
|
-
"fast-check": "^3.
|
|
37
|
-
"
|
|
38
|
-
"yaml": "^2.6.1"
|
|
37
|
+
"fast-check": "^4.3.0",
|
|
38
|
+
"yaml": "^2.8.1"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@
|
|
42
|
-
"@types/jest": "^
|
|
43
|
-
"jest": "^
|
|
44
|
-
"ts-jest": "^29.
|
|
45
|
-
"typescript": "^5.
|
|
41
|
+
"@stacks/clarinet-sdk-wasm": "^3.12.0",
|
|
42
|
+
"@types/jest": "^30.0.0",
|
|
43
|
+
"jest": "^30.2.0",
|
|
44
|
+
"ts-jest": "^29.4.5",
|
|
45
|
+
"typescript": "^5.9.3"
|
|
46
46
|
}
|
|
47
47
|
}
|
package/dist/shared.js
CHANGED
|
@@ -105,7 +105,7 @@ const complexTypesToArbitrary = {
|
|
|
105
105
|
// in hex. The `argToCV` function expects this format for `buff` ClarityValue
|
|
106
106
|
// conversion. The UInt8Array will have half the length of the corresponding
|
|
107
107
|
// hex string. Stacks.js reference:
|
|
108
|
-
// https://github.com/
|
|
108
|
+
// https://github.com/stx-labs/stacks.js/blob/fd0bf26b5f29fc3c1bf79581d0ad9b89f0d7f15a/packages/common/src/utils.ts#L522
|
|
109
109
|
buffer: (length) => (0, exports.hexaString)({ maxLength: 2 * length }),
|
|
110
110
|
"string-ascii": (length) => fast_check_1.default.string({
|
|
111
111
|
unit: fast_check_1.default.constantFrom(...charSet),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacks/rendezvous",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Meet your contract's vulnerabilities head-on.",
|
|
5
5
|
"main": "app.js",
|
|
6
6
|
"bin": {
|
|
@@ -30,18 +30,18 @@
|
|
|
30
30
|
"url": "https://github.com/stacks-network/rendezvous.git"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@
|
|
33
|
+
"@iarna/toml": "^2.2.5",
|
|
34
|
+
"@stacks/clarinet-sdk": "^3.12.0",
|
|
34
35
|
"@stacks/transactions": "^7.2.0",
|
|
35
36
|
"ansicolor": "^2.0.3",
|
|
36
|
-
"fast-check": "^3.
|
|
37
|
-
"
|
|
38
|
-
"yaml": "^2.6.1"
|
|
37
|
+
"fast-check": "^4.3.0",
|
|
38
|
+
"yaml": "^2.8.1"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@
|
|
42
|
-
"@types/jest": "^
|
|
43
|
-
"jest": "^
|
|
44
|
-
"ts-jest": "^29.
|
|
45
|
-
"typescript": "^5.
|
|
41
|
+
"@stacks/clarinet-sdk-wasm": "^3.12.0",
|
|
42
|
+
"@types/jest": "^30.0.0",
|
|
43
|
+
"jest": "^30.2.0",
|
|
44
|
+
"ts-jest": "^29.4.5",
|
|
45
|
+
"typescript": "^5.9.3"
|
|
46
46
|
}
|
|
47
47
|
}
|