Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@stacks/rendezvous

Package Overview
Dependencies
Maintainers
4
Versions
25
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@stacks/rendezvous - npm Package Compare versions

Comparing version
0.13.1
to
0.14.0
+97
dist/persistence.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadFailures = exports.persistFailure = exports.getFailureFilePath = void 0;
const fs_1 = require("fs");
const path_1 = require("path");
/** Default configuration for persistence behavior. */
const DEFAULT_CONFIG = {
baseDir: ".rendezvous-regressions",
};
/**
* Gets the absolute file path for a contract's failure store.
* Uses contractId as filename (e.g., "ST1...ADDR.counter.json")
* @param contractId The contract identifier being tested
* @param baseDir The base directory for storing regression files
* @returns The file path for the failure store
*/
const getFailureFilePath = (contractId, baseDir = DEFAULT_CONFIG.baseDir) => {
return (0, path_1.resolve)(baseDir, `${contractId}.json`);
};
exports.getFailureFilePath = getFailureFilePath;
/**
* Loads the failure store for a contract, or creates an empty one.
* @param contractId The contract identifier being tested
* @param baseDir The base directory for storing regression files
* @returns The failure store
*/
const loadFailureStore = (contractId, baseDir = DEFAULT_CONFIG.baseDir) => {
const filePath = (0, exports.getFailureFilePath)(contractId, baseDir);
try {
const content = (0, fs_1.readFileSync)(filePath, "utf-8");
return JSON.parse(content);
}
catch (error) {
return { invariant: [], test: [] };
}
};
/**
* Saves the failure store for a contract.
* @param contractId The contract identifier being tested
* @param baseDir The base directory for storing regression files
* @param store The failure store to save
*/
const saveFailureStore = (contractId, baseDir, store) => {
// Ensure the base directory exists.
(0, fs_1.mkdirSync)(baseDir, { recursive: true });
const filePath = (0, exports.getFailureFilePath)(contractId, baseDir);
(0, fs_1.writeFileSync)(filePath, JSON.stringify(store, null, 2), "utf-8");
};
/**
* Persists a test failure for future regression testing.
*
* @param runDetails The test run details from fast-check
* @param type The type of test that failed
* @param contractId The contract identifier being tested
* @param dial The path to the dialer file used for this test run
* @param config Optional configuration for persistence behavior
*/
const persistFailure = (runDetails, type, contractId, dial, config) => {
const { baseDir } = Object.assign(Object.assign({}, DEFAULT_CONFIG), config);
// Load existing store.
const store = loadFailureStore(contractId, baseDir);
const record = {
seed: runDetails.seed,
dial: dial,
numRuns: runDetails.numRuns,
timestamp: Date.now(),
};
// Get the array for this test type.
const failures = store[type];
// Check if this seed already exists.
const seedExists = failures.some((f) => f.seed === record.seed);
if (seedExists) {
// Already recorded.
return;
}
// Add new failure.
failures.push(record);
// Sort the failures in descending order by timestamp.
failures.sort((a, b) => b.timestamp - a.timestamp);
// Save back to file.
saveFailureStore(contractId, baseDir, store);
};
exports.persistFailure = persistFailure;
/**
* Loads persisted failures for a given contract and test type.
*
* @param contractId The contract identifier
* @param type The type of test ("invariant" or "test")
* @param config Optional configuration
* @returns Array of failure records, or empty array if none exist
*/
const loadFailures = (contractId, type, config) => {
const { baseDir } = Object.assign(Object.assign({}, DEFAULT_CONFIG), config);
const store = loadFailureStore(contractId, baseDir);
return store[type];
};
exports.loadFailures = loadFailures;
+46
-39

@@ -25,3 +25,2 @@ #!/usr/bin/env node

const util_1 = require("util");
const dialer_1 = require("./dialer");
const logger = (log, logLevel = "log") => {

@@ -49,15 +48,18 @@ console[logLevel](log);

Usage: rv <path-to-clarinet-project> <contract-name> <type> [--seed=<seed>] [--runs=<runs>] [--dial=<path-to-dialers-file>] [--help]
Usage: rv <path> <contract> <type> [OPTIONS]
Positional arguments:
path-to-clarinet-project - The path to the Clarinet project.
contract-name - The name of the contract to be fuzzed.
type - The type to use for exercising the contracts. Possible values: test, invariant.
Arguments:
<path> Path to the Clarinet project
<contract> Contract name to fuzz
<type> Test type: test | invariant
Options:
--seed - The seed to use for the replay functionality.
--runs - The runs to use for iterating over the tests. Default: 100.
--bail - Stop after the first failure.
--dial – The path to a JavaScript file containing custom pre- and post-execution functions (dialers).
--help - Show the help message.
--seed=<n> Seed for replay functionality
--runs=<n> Number of test iterations [default: 100]
--dial=<f> Path to custom dialers file
--regr Run regression tests only
--bail Stop on first failure
-h, --help Show this message
Learn more: https://stacks-network.github.io/rendezvous/
`;

@@ -77,2 +79,3 @@ function main() {

bail: { type: "boolean" },
regr: { type: "boolean" },
help: { type: "boolean", short: "h" },

@@ -95,2 +98,4 @@ },

bail: options.bail || false,
/** Whether to run regression tests only. */
regr: options.regr || false,
/** The path to the dialer file. */

@@ -120,2 +125,4 @@ dial: options.dial || undefined,

}
// Divider before the run configuration.
radio.emit("logMessage", shared_1.LOG_DIVIDER);
/**

@@ -137,35 +144,35 @@ * The relative path to the manifest file, either `Clarinet.toml` or

}
if (runConfig.regr) {
radio.emit("logMessage", `Running regression tests.`);
}
if (runConfig.dial !== undefined) {
radio.emit("logMessage", `Using dial path: ${runConfig.dial}`);
}
/**
* The dialer registry, which is used to keep track of all the custom dialers
* registered by the user using the `--dial` flag.
*/
const dialerRegistry = runConfig.dial !== undefined
? new dialer_1.DialerRegistry(runConfig.dial)
: undefined;
if (dialerRegistry !== undefined) {
dialerRegistry.registerDialers();
}
const simnet = yield (0, citizen_1.issueFirstClassCitizenship)(runConfig.manifestDir, manifestPath, runConfig.sutContractName, radio);
/**
* The list of contract IDs for the SUT contract names, as per the simnet.
*/
const rendezvousList = Array.from((0, shared_1.getSimnetDeployerContractsInterfaces)(simnet).keys()).filter((deployedContract) => (0, shared_1.getContractNameFromContractId)(deployedContract) ===
runConfig.sutContractName);
const rendezvousAllFunctions = (0, shared_1.getFunctionsFromContractInterfaces)(new Map(Array.from((0, shared_1.getSimnetDeployerContractsInterfaces)(simnet)).filter(([contractId]) => rendezvousList.includes(contractId))));
// Select the testing routine based on `type`.
// If "invariant", call `checkInvariants` to verify contract invariants.
// If "test", call `checkProperties` for property-based testing.
switch (runConfig.type) {
case "invariant": {
yield (0, invariant_1.checkInvariants)(simnet, runConfig.sutContractName, rendezvousList, rendezvousAllFunctions, runConfig.seed, runConfig.runs, runConfig.bail, dialerRegistry, radio);
break;
// Divider between the run configuration and the execution.
radio.emit("logMessage", shared_1.LOG_DIVIDER + "\n");
const { simnet, resetSession, cleanupSession } = yield (0, citizen_1.issueFirstClassCitizenship)(runConfig.manifestDir, manifestPath, runConfig.sutContractName, radio);
try {
/**
* The list of contract IDs for the SUT contract names, as per the simnet.
*/
const rendezvousList = Array.from((0, shared_1.getSimnetDeployerContractsInterfaces)(simnet).keys()).filter((deployedContract) => (0, shared_1.getContractNameFromContractId)(deployedContract) ===
runConfig.sutContractName);
const rendezvousAllFunctions = (0, shared_1.getFunctionsFromContractInterfaces)(new Map(Array.from((0, shared_1.getSimnetDeployerContractsInterfaces)(simnet)).filter(([contractId]) => rendezvousList.includes(contractId))));
// Select the testing routine based on `type`.
// If "invariant", call `checkInvariants` to verify contract invariants.
// If "test", call `checkProperties` for property-based testing.
switch (runConfig.type) {
case "invariant": {
yield (0, invariant_1.checkInvariants)(simnet, resetSession, runConfig.sutContractName, rendezvousList, rendezvousAllFunctions, runConfig.seed, runConfig.runs, runConfig.dial, runConfig.bail, runConfig.regr, radio);
break;
}
case "test": {
yield (0, property_1.checkProperties)(simnet, resetSession, runConfig.sutContractName, rendezvousList, rendezvousAllFunctions, runConfig.seed, runConfig.runs, runConfig.bail, runConfig.regr, radio);
break;
}
}
case "test": {
(0, property_1.checkProperties)(simnet, runConfig.sutContractName, rendezvousList, rendezvousAllFunctions, runConfig.seed, runConfig.runs, runConfig.bail, radio);
break;
}
}
finally {
cleanupSession();
}
});

@@ -172,0 +179,0 @@ }

@@ -37,3 +37,4 @@ "use strict";

* @param radio The event emitter to send log messages to.
* @returns The initialized simnet.
* @returns The initialized simnet session, including the simnet, a function to
* reset the session, and a function to clean up the session.
*/

@@ -45,3 +46,3 @@ const issueFirstClassCitizenship = (manifestDir, manifestPath, sutContractName, radio) => __awaiter(void 0, void 0, void 0, function* () {

try {
radio.emit("logMessage", `\nType-checking your Clarinet project...`);
radio.emit("logMessage", `Type-checking your Clarinet project...\n`);
yield (0, clarinet_sdk_1.generateDeployement)(manifestPath);

@@ -67,3 +68,3 @@ }

(0, fs_1.writeFileSync)(rendezvousPath, rendezvousData.rendezvousSourceCode);
radio.emit("logMessage", `\nType-checking your Rendezvous project...`);
radio.emit("logMessage", `Type-checking your Rendezvous project...\n`);
// Update the manifest in the temp directory to point to the Rendezvous

@@ -117,3 +118,24 @@ // concatenation.

const simnet = yield (0, clarinet_sdk_1.initSimnet)(manifestFileName);
return simnet;
const resetSession = () => __awaiter(void 0, void 0, void 0, function* () {
const cwd = process.cwd();
const origWrite = process.stdout.write;
process.stdout.write = () => true;
try {
process.chdir(tempProjectDir);
yield (0, clarinet_sdk_1.initSimnet)(manifestFileName);
}
finally {
process.stdout.write = origWrite;
process.chdir(cwd);
}
});
const cleanupSession = () => {
try {
(0, fs_1.rmSync)(tempProjectDir, { recursive: true, force: true });
}
catch (error) {
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.`));
}
};
return { simnet, resetSession, cleanupSession };
}

@@ -128,9 +150,2 @@ finally {

process.chdir(originalCwd);
// Cleanup the temp project directory.
try {
(0, fs_1.rmSync)(tempProjectDir, { recursive: true, force: true });
}
catch (error) {
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.`));
}
}

@@ -192,3 +207,3 @@ });

const getSutContractDeploymentPlanEmulatedPublish = (deploymentPlan, sutContractName) => {
var _a, _b;
var _a;
// Filter all emulated contract publish transactions matching the target

@@ -198,5 +213,4 @@ // contract name from the deployment plan.

.flatMap((batch) => batch.transactions)
.filter((transaction) => transaction["emulated-contract-publish"] &&
transaction["emulated-contract-publish"]["contract-name"] ===
sutContractName);
.filter((transaction) => transaction["transaction-type"] === "emulated-contract-publish" &&
transaction["contract-name"] === sutContractName);
// If no matches are found, something went wrong.

@@ -216,4 +230,3 @@ if (contractPublishMatchesByName.length === 0) {

// having the same name, select the one deployed by the deployer.
const targetContractDeploymentData = (_b = contractPublishMatchesByName.find((transaction) => transaction["emulated-contract-publish"]["emulated-sender"] ===
deployer)) === null || _b === void 0 ? void 0 : _b["emulated-contract-publish"];
const targetContractDeploymentData = contractPublishMatchesByName.find((transaction) => transaction["emulated-sender"] === deployer);
// TODO: Consider handling requirements and project contracts separately.

@@ -231,4 +244,4 @@ // Eventually let the user specify if the contract is a requirement or a

}
// Only one match was found, return the path to the contract.
const contractNameMatch = contractPublishMatchesByName[0]["emulated-contract-publish"];
// Only one match was found, return the contract publish data.
const contractNameMatch = contractPublishMatchesByName[0];
if (!contractNameMatch) {

@@ -235,0 +248,0 @@ throw new Error(`Could not locate "${sutContractName}" contract.`);

@@ -24,35 +24,45 @@ "use strict";

function reporter(runDetails, radio, type, statistics) {
var _a, _b;
if (runDetails.failed) {
const { counterexample, failed, numRuns, path, seed } = runDetails;
if (failed) {
const error = runDetails.errorInstance || runDetails.error;
// Extract the actual Clarity error once for both error types.
const clarityError = (error === null || error === void 0 ? void 0 : error.clarityError) ||
(error === null || error === void 0 ? void 0 : error.message) ||
(error === null || error === void 0 ? void 0 : error.toString()) ||
"Unknown error";
// Report general run data.
radio.emit("logFailure", `\nError: Property failed after ${runDetails.numRuns} tests.`);
radio.emit("logFailure", `Seed : ${runDetails.seed}`);
if (runDetails.path) {
radio.emit("logFailure", `Path : ${runDetails.path}`);
radio.emit("logFailure", `\nError: Property failed after ${numRuns} tests.`);
radio.emit("logFailure", `Seed : ${seed}`);
if (path) {
radio.emit("logFailure", `Path : ${path}`);
}
switch (type) {
case "invariant": {
const r = runDetails.counterexample[0];
const ce = counterexample[0];
// Report specific run data for the invariant testing type.
radio.emit("logFailure", `\nCounterexample:`);
radio.emit("logFailure", `- Contract : ${(0, shared_1.getContractNameFromContractId)(r.rendezvousContractId)}`);
radio.emit("logFailure", `- Functions: ${r.selectedFunctions
radio.emit("logFailure", `- Contract : ${(0, shared_1.getContractNameFromContractId)(ce.rendezvousContractId)}`);
radio.emit("logFailure", `- Functions: ${ce.selectedFunctions
.map((selectedFunction) => selectedFunction.name)
.join(", ")} (${r.selectedFunctions
.join(", ")} (${ce.selectedFunctions
.map((selectedFunction) => selectedFunction.access)
.join(", ")})`);
radio.emit("logFailure", `- Arguments: ${r.selectedFunctionsArgsList
radio.emit("logFailure", `- Arguments: ${ce.selectedFunctionsArgsList
.map((selectedFunctionArgs) => JSON.stringify(selectedFunctionArgs))
.join(", ")}`);
radio.emit("logFailure", `- Callers : ${r.sutCallers
radio.emit("logFailure", `- Callers : ${ce.sutCallers
.map((sutCaller) => sutCaller[0])
.join(", ")}`);
radio.emit("logFailure", `- Outputs : ${r.selectedFunctions
radio.emit("logFailure", `- Outputs : ${ce.selectedFunctions
.map((selectedFunction) => JSON.stringify(selectedFunction.outputs))
.join(", ")}`);
radio.emit("logFailure", `- Invariant: ${r.selectedInvariant.name} (${r.selectedInvariant.access})`);
radio.emit("logFailure", `- Arguments: ${JSON.stringify(r.invariantArgs)}`);
radio.emit("logFailure", `- Caller : ${r.invariantCaller[0]}`);
radio.emit("logFailure", `- Invariant: ${ce.selectedInvariant.name} (${ce.selectedInvariant.access})`);
radio.emit("logFailure", `- Arguments: ${JSON.stringify(ce.invariantArgs)}`);
radio.emit("logFailure", `- Caller : ${ce.invariantCaller[0]}`);
radio.emit("logFailure", `\nWhat happened? Rendezvous went on a rampage and found a weak spot:\n`);
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`;
const formattedError = `The invariant "${ce.selectedInvariant.name}" returned:\n\n${clarityError
.toString()
.split("\n")
.map((line) => " " + line)
.join("\n")}\n`;
radio.emit("logFailure", formattedError);

@@ -62,12 +72,16 @@ break;

case "test": {
const r = runDetails.counterexample[0];
const ce = counterexample[0];
// Report specific run data for the property testing type.
radio.emit("logFailure", `\nCounterexample:`);
radio.emit("logFailure", `- Test Contract : ${(0, shared_1.getContractNameFromContractId)(r.testContractId)}`);
radio.emit("logFailure", `- Test Function : ${r.selectedTestFunction.name} (${r.selectedTestFunction.access})`);
radio.emit("logFailure", `- Arguments : ${JSON.stringify(r.functionArgs)}`);
radio.emit("logFailure", `- Caller : ${r.testCaller[0]}`);
radio.emit("logFailure", `- Outputs : ${JSON.stringify(r.selectedTestFunction.outputs)}`);
radio.emit("logFailure", `- Contract : ${(0, shared_1.getContractNameFromContractId)(ce.rendezvousContractId)}`);
radio.emit("logFailure", `- Test Function : ${ce.selectedTestFunction.name} (${ce.selectedTestFunction.access})`);
radio.emit("logFailure", `- Arguments : ${JSON.stringify(ce.functionArgs)}`);
radio.emit("logFailure", `- Caller : ${ce.testCaller[0]}`);
radio.emit("logFailure", `- Outputs : ${JSON.stringify(ce.selectedTestFunction.outputs)}`);
radio.emit("logFailure", `\nWhat happened? Rendezvous went on a rampage and found a weak spot:\n`);
const formattedError = `The test function "${r.selectedTestFunction.name}" returned:\n\n${(_b = runDetails.error) === null || _b === void 0 ? void 0 : _b.toString().split("\n").map((line) => " " + line).join("\n")}\n`;
const formattedError = `The test function "${ce.selectedTestFunction.name}" returned:\n\n${clarityError
.toString()
.split("\n")
.map((line) => " " + line)
.join("\n")}\n`;
radio.emit("logFailure", formattedError);

@@ -82,3 +96,3 @@ break;

else {
radio.emit("logMessage", (0, ansicolor_1.green)(`\nOK, ${type === "invariant" ? "invariants" : "properties"} passed after ${runDetails.numRuns} runs.\n`));
radio.emit("logMessage", (0, ansicolor_1.green)(`\nOK, ${type === "invariant" ? "invariants" : "properties"} passed after ${numRuns} runs.\n`));
}

@@ -85,0 +99,0 @@ reportStatistics(statistics, type, radio);

@@ -15,3 +15,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.initializeClarityContext = exports.initializeLocalContext = exports.checkInvariants = void 0;
exports.FalsifiedInvariantError = exports.initializeClarityContext = exports.initializeLocalContext = exports.checkInvariants = void 0;
const shared_1 = require("./shared");

@@ -24,2 +24,4 @@ const transactions_1 = require("@stacks/transactions");

const dialer_1 = require("./dialer");
const persistence_1 = require("./persistence");
const path_1 = require("path");
/**

@@ -29,2 +31,3 @@ * Runs invariant testing on the target contract and logs the progress. Reports

* @param simnet The Simnet instance.
* @param resetSession Resets the simnet session to a clean state.
* @param targetContractName The name of the target contract.

@@ -36,19 +39,10 @@ * @param rendezvousList The list of contract IDs for each target contract.

* @param runs The number of test runs.
* @param dial The path to the dialer file.
* @param bail Stop execution after the first failure and prevent further
* shrinking.
* @param dialerRegistry The custom dialer registry.
* @param regr Whether to run regression tests only.
* @param radio The custom logging event emitter.
* @returns void
*/
const checkInvariants = (simnet, targetContractName, rendezvousList, rendezvousAllFunctions, seed, runs, bail, dialerRegistry, radio) => __awaiter(void 0, void 0, void 0, function* () {
const statistics = {
sut: {
successful: new Map(),
failed: new Map(),
},
invariant: {
successful: new Map(),
failed: new Map(),
},
};
const checkInvariants = (simnet, resetSession, targetContractName, rendezvousList, rendezvousAllFunctions, seed, runs, dial, bail, regr, radio) => __awaiter(void 0, void 0, void 0, function* () {
// The Rendezvous identifier is the first one in the list. Only one contract

@@ -61,7 +55,2 @@ // can be fuzzed at a time.

const rendezvousSutFunctions = filterSutFunctions(rendezvousAllFunctions);
// Initialize the statistics for the SUT functions.
for (const functionInterface of rendezvousSutFunctions.get(rendezvousContractId)) {
statistics.sut.successful.set(functionInterface.name, 0);
statistics.sut.failed.set(functionInterface.name, 0);
}
// A map where the keys are the Rendezvous identifiers and the values are

@@ -71,7 +60,2 @@ // arrays of their invariant functions. This map will be used to access the

const rendezvousInvariantFunctions = filterInvariantFunctions(rendezvousAllFunctions);
// Initialize the statistics for the invariant functions.
for (const functionInterface of rendezvousInvariantFunctions.get(rendezvousContractId)) {
statistics.invariant.successful.set(functionInterface.name, 0);
statistics.invariant.failed.set(functionInterface.name, 0);
}
const sutFunctions = rendezvousSutFunctions.get(rendezvousContractId);

@@ -99,18 +83,4 @@ const traitReferenceSutFunctions = sutFunctions.filter(traits_1.isTraitReferenceFunction);

const invariantFunctionsWithMissingTraits = (0, traits_1.getNonTestableTraitFunctions)(enrichedInvariantFunctionsInterfaces, invariantTraitReferenceMap, projectTraitImplementations, rendezvousContractId);
if (sutFunctionsWithMissingTraits.length > 0 ||
invariantFunctionsWithMissingTraits.length > 0) {
if (sutFunctionsWithMissingTraits.length > 0) {
const functionList = sutFunctionsWithMissingTraits
.map((fn) => ` - ${fn}`)
.join("\n");
radio.emit("logMessage", (0, ansicolor_1.yellow)(`\nWarning: The following SUT functions reference traits without eligible implementations and will be skipped:\n\n${functionList}\n`));
}
if (invariantFunctionsWithMissingTraits.length > 0) {
const functionList = invariantFunctionsWithMissingTraits
.map((fn) => ` - ${fn}`)
.join("\n");
radio.emit("logMessage", (0, ansicolor_1.yellow)(`\nWarning: The following invariant functions reference traits without eligible implementations and will be skipped:\n\n${functionList}\n`));
}
radio.emit("logMessage", (0, ansicolor_1.yellow)(`Note: You can add contracts implementing traits either as project contracts or as Clarinet requirements.\n`));
}
// Emit warnings for functions with missing trait implementations
emitMissingTraitWarnings(radio, sutFunctionsWithMissingTraits, invariantFunctionsWithMissingTraits);
// Filter out functions with missing trait implementations from the enriched

@@ -136,10 +106,2 @@ // map.

]);
// Set up local context to track SUT function call counts.
const localContext = (0, exports.initializeLocalContext)(executableSutFunctions);
// Set up context in simnet by initializing state for SUT.
(0, exports.initializeClarityContext)(simnet, executableSutFunctions);
radio.emit("logMessage", `\nStarting invariant testing type for the ${targetContractName} contract...\n`);
const simnetAccounts = simnet.getAccounts();
const eligibleAccounts = new Map([...simnetAccounts].filter(([key]) => key !== "faucet"));
const simnetAddresses = Array.from(simnetAccounts.values());
const functions = (0, shared_1.getFunctionsListForContract)(executableSutFunctions, rendezvousContractId);

@@ -155,5 +117,96 @@ const invariants = (0, shared_1.getFunctionsListForContract)(executableInvariantFunctions, rendezvousContractId);

}
const radioReporter = (runDetails) => {
if (regr) {
// Run regression tests only.
radio.emit("logMessage", `Regressions loaded from: ${(0, path_1.resolve)((0, persistence_1.getFailureFilePath)(rendezvousContractId))}`);
radio.emit("logMessage", `Loading ${targetContractName} contract regressions...\n`);
const regressions = (0, persistence_1.loadFailures)(rendezvousContractId, "invariant");
radio.emit("logMessage", `Found ${(0, ansicolor_1.underline)(`${regressions.length} regressions`)} for the ${targetContractName} contract.\n`);
for (const regression of regressions) {
emitInvariantRegressionTestHeader(radio, targetContractName, regression.seed, regression.numRuns, regression.dial, regression.timestamp);
yield resetSession();
yield invariantTest({
simnet,
targetContractName,
rendezvousContractId,
runs: regression.numRuns < 100 ? 100 : regression.numRuns,
seed: regression.seed,
bail,
dial: regression.dial,
radio,
functions,
invariants,
projectTraitImplementations,
});
}
}
else {
// Run fresh invariant tests using user-provided configuration.
radio.emit("logMessage", `Starting fresh round of invariant testing for the ${targetContractName} contract using user-provided configuration...\n`);
yield invariantTest({
simnet,
targetContractName,
rendezvousContractId,
runs,
seed,
bail,
dial,
radio,
functions,
invariants,
projectTraitImplementations,
});
}
});
exports.checkInvariants = checkInvariants;
/**
* Runs an invariant test.
* @param config The union of the configuration and context for the invariant
* test.
* @returns A promise that resolves when the invariant test is complete.
*/
const invariantTest = (config) => __awaiter(void 0, void 0, void 0, function* () {
const { simnet, targetContractName, rendezvousContractId, runs, seed, bail, dial, radio, functions, invariants, projectTraitImplementations, } = config;
// Derive accounts and addresses from simnet.
const simnetAccounts = simnet.getAccounts();
const eligibleAccounts = new Map([...simnetAccounts].filter(([key]) => key !== "faucet"));
const simnetAddresses = Array.from(simnetAccounts.values());
/**
* The dialer registry, which is used to keep track of all the custom dialers
* registered by the user using the `--dial` flag.
*/
const dialerRegistry = dial !== undefined ? new dialer_1.DialerRegistry(dial) : undefined;
if (dialerRegistry !== undefined) {
dialerRegistry.registerDialers();
}
const statistics = {
sut: {
successful: new Map(),
failed: new Map(),
},
invariant: {
successful: new Map(),
failed: new Map(),
},
};
// Initialize the statistics for the SUT functions.
for (const functionInterface of functions) {
statistics.sut.successful.set(functionInterface.name, 0);
statistics.sut.failed.set(functionInterface.name, 0);
}
// Initialize the statistics for the invariant functions.
for (const functionInterface of invariants) {
statistics.invariant.successful.set(functionInterface.name, 0);
statistics.invariant.failed.set(functionInterface.name, 0);
}
const radioReporter = (runDetails) => __awaiter(void 0, void 0, void 0, function* () {
(0, heatstroke_1.reporter)(runDetails, radio, "invariant", statistics);
};
// Persist failures for regression testing.
if (runDetails.failed) {
(0, persistence_1.persistFailure)(runDetails, "invariant", rendezvousContractId, dial);
}
});
// Set up local context to track SUT function call counts.
const localContext = (0, exports.initializeLocalContext)(rendezvousContractId, functions);
// Set up context in simnet by initializing state for SUT.
(0, exports.initializeClarityContext)(simnet, rendezvousContractId, functions);
yield fast_check_1.default.assert(fast_check_1.default.asyncProperty(fast_check_1.default

@@ -345,3 +398,3 @@ .record({

// runtime errors.
throw new FalsifiedInvariantError(`Invariant failed for ${targetContractName} contract: "${r.selectedInvariant.name}" returned ${invariantCallClarityResult}`);
throw new FalsifiedInvariantError(`Invariant failed for ${targetContractName} contract: "${r.selectedInvariant.name}" returned ${invariantCallClarityResult}`, invariantCallClarityResult);
}

@@ -375,16 +428,40 @@ }

});
exports.checkInvariants = checkInvariants;
/**
* Emits warnings for functions that reference traits without eligible
* implementations.
*/
function emitMissingTraitWarnings(radio, sutFunctions, invariantFunctions) {
if (sutFunctions.length === 0 && invariantFunctions.length === 0) {
return;
}
if (sutFunctions.length > 0) {
const functionList = sutFunctions.map((fn) => ` - ${fn}`).join("\n");
radio.emit("logMessage", (0, ansicolor_1.yellow)(`\nWarning: The following SUT functions reference traits without eligible implementations and will be skipped:\n\n${functionList}\n`));
}
if (invariantFunctions.length > 0) {
const functionList = invariantFunctions.map((fn) => ` - ${fn}`).join("\n");
radio.emit("logMessage", (0, ansicolor_1.yellow)(`\nWarning: The following invariant functions reference traits without eligible implementations and will be skipped:\n\n${functionList}\n`));
}
radio.emit("logMessage", (0, ansicolor_1.yellow)(`Note: You can add contracts implementing traits either as project contracts or as Clarinet requirements.\n`));
}
/**
* Initializes the local context, setting the number of times each function
* has been called to zero.
* @param rendezvousSutFunctions The Rendezvous functions.
* @param contractId The contract identifier.
* @param functions The SUT functions for the contract.
* @returns The initialized local context.
*/
const initializeLocalContext = (rendezvousSutFunctions) => Object.fromEntries(Array.from(rendezvousSutFunctions.entries()).map(([contractId, functions]) => [
contractId,
Object.fromEntries(functions.map((f) => [f.name, 0])),
]));
const initializeLocalContext = (contractId, functions) => ({
[contractId]: Object.fromEntries(functions.map((f) => [f.name, 0])),
});
exports.initializeLocalContext = initializeLocalContext;
const initializeClarityContext = (simnet, rendezvousSutFunctions) => rendezvousSutFunctions.forEach((fns, contractId) => {
fns.forEach((fn) => {
/**
* Initializes the Clarity context by calling update-context for each SUT
* function.
* @param simnet The Simnet instance.
* @param contractId The contract identifier.
* @param functions The SUT functions for the contract.
*/
const initializeClarityContext = (simnet, contractId, functions) => {
functions.forEach((fn) => {
const { result: initialize } = simnet.callPublicFn(contractId, "update-context", [transactions_1.Cl.stringAscii(fn.name), transactions_1.Cl.uint(0)], simnet.deployer);

@@ -396,3 +473,3 @@ const jsonResult = (0, transactions_1.cvToJSON)(initialize);

});
});
};
exports.initializeClarityContext = initializeClarityContext;

@@ -421,5 +498,17 @@ /**

class FalsifiedInvariantError extends Error {
constructor(message) {
constructor(message, clarityError) {
super(message);
this.clarityError = clarityError;
}
}
exports.FalsifiedInvariantError = FalsifiedInvariantError;
const emitInvariantRegressionTestHeader = (radio, targetContractName, seed, numRuns, dial, timestamp) => {
radio.emit("logMessage", shared_1.LOG_DIVIDER);
radio.emit("logMessage", `
Running ${(0, ansicolor_1.underline)(timestamp)} regression test for the ${targetContractName} contract with:
- Seed: ${seed}
- Runs: ${numRuns}
- Dial: ${dial !== null && dial !== void 0 ? dial : "none (default)"}
`);
};
{
"name": "@stacks/rendezvous",
"version": "0.13.1",
"version": "0.14.0",
"description": "Meet your contract's vulnerabilities head-on.",

@@ -34,15 +34,15 @@ "main": "app.js",

"@iarna/toml": "^2.2.5",
"@stacks/clarinet-sdk": "^3.12.0",
"@stacks/transactions": "^7.2.0",
"@stacks/clarinet-sdk": "^3.14.0",
"@stacks/transactions": "^7.3.1",
"ansicolor": "^2.0.3",
"fast-check": "^4.3.0",
"yaml": "^2.8.1"
"fast-check": "^4.5.3",
"yaml": "^2.8.2"
},
"devDependencies": {
"@stacks/clarinet-sdk-wasm": "^3.12.0",
"@stacks/clarinet-sdk-wasm": "^3.14.0",
"@types/jest": "^30.0.0",
"jest": "^30.2.0",
"ts-jest": "^29.4.5",
"ts-jest": "^29.4.6",
"typescript": "^5.9.3"
}
}
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {

@@ -6,3 +15,3 @@ return (mod && mod.__esModule) ? mod : { "default": mod };

Object.defineProperty(exports, "__esModule", { value: true });
exports.isReturnTypeBoolean = exports.isParamsMatch = exports.isTestDiscardedInPlace = exports.checkProperties = void 0;
exports.PropertyTestError = exports.isReturnTypeBoolean = exports.isParamsMatch = exports.isTestDiscardedInPlace = exports.checkProperties = void 0;
const fast_check_1 = __importDefault(require("fast-check"));

@@ -14,2 +23,4 @@ const transactions_1 = require("@stacks/transactions");

const traits_1 = require("./traits");
const persistence_1 = require("./persistence");
const path_1 = require("path");
/**

@@ -19,2 +30,3 @@ * Runs property-based tests on the target contract and logs the progress.

* @param simnet The simnet instance.
* @param resetSession Resets the simnet session to a clean state.
* @param targetContractName The name of the target contract.

@@ -28,14 +40,7 @@ * @param rendezvousList The list of contract IDs for each target contract.

* shrinking.
* @param regr Whether to run regression tests only.
* @param radio The custom logging event emitter.
* @returns void
*/
const checkProperties = (simnet, targetContractName, rendezvousList, rendezvousAllFunctions, seed, runs, bail, radio) => {
const statistics = {
test: {
successful: new Map(),
discarded: new Map(),
failed: new Map(),
},
};
const testContractId = rendezvousList[0];
const checkProperties = (simnet, resetSession, targetContractName, rendezvousList, rendezvousAllFunctions, seed, runs, bail, regr, radio) => __awaiter(void 0, void 0, void 0, function* () {
// A map where the keys are the test contract identifiers and the values are

@@ -45,7 +50,3 @@ // arrays of their test functions. This map will be used to access the test

const testContractsTestFunctions = filterTestFunctions(rendezvousAllFunctions);
for (const functionInterface of testContractsTestFunctions.get(testContractId)) {
statistics.test.successful.set(functionInterface.name, 0);
statistics.test.discarded.set(functionInterface.name, 0);
statistics.test.failed.set(functionInterface.name, 0);
}
const testContractId = rendezvousList[0];
const allTestFunctions = testContractsTestFunctions.get(testContractId);

@@ -67,9 +68,3 @@ const traitReferenceFunctionsCount = allTestFunctions.filter(traits_1.isTraitReferenceFunction).length;

// implementations, log a warning and filter out the functions.
if (functionsMissingTraitImplementations.length > 0) {
const functionList = functionsMissingTraitImplementations
.map((fn) => ` - ${fn}`)
.join("\n");
radio.emit("logMessage", (0, ansicolor_1.yellow)(`\nWarning: The following test functions reference traits without eligible implementations and will be skipped:\n\n${functionList}\n`));
radio.emit("logMessage", (0, ansicolor_1.yellow)(`Note: You can add contracts implementing traits either as project contracts or as requirements.\n`));
}
emitMissingTraitWarning(radio, functionsMissingTraitImplementations);
// Filter out test functions with missing trait implementations from the

@@ -85,3 +80,2 @@ // enriched map.

]);
radio.emit("logMessage", `\nStarting property testing type for the ${targetContractName} contract...\n`);
// Search for discard functions, for each test function. This map will

@@ -112,5 +106,2 @@ // be used to pair the test functions with their corresponding discard

}
const simnetAccounts = simnet.getAccounts();
const eligibleAccounts = new Map([...simnetAccounts].filter(([key]) => key !== "faucet"));
const simnetAddresses = Array.from(simnetAccounts.values());
const testFunctions = (0, shared_1.getFunctionsListForContract)(executableTestContractsTestFunctions, testContractId);

@@ -121,8 +112,82 @@ if ((testFunctions === null || testFunctions === void 0 ? void 0 : testFunctions.length) === 0) {

}
const radioReporter = (runDetails) => {
if (regr) {
// Run regression tests only.
radio.emit("logMessage", `Regressions loaded from: ${(0, path_1.resolve)((0, persistence_1.getFailureFilePath)(testContractId))}`);
radio.emit("logMessage", `Loading ${targetContractName} contract regressions...\n`);
const regressions = (0, persistence_1.loadFailures)(testContractId, "test");
radio.emit("logMessage", `Found ${(0, ansicolor_1.underline)(`${regressions.length} regressions`)} for the ${targetContractName} contract.\n`);
for (const regression of regressions) {
emitPropertyRegressionTestHeader(radio, targetContractName, regression.seed, regression.numRuns, regression.timestamp);
yield resetSession();
yield propertyTest({
simnet,
targetContractName,
testContractId,
// If the number of runs that failed is less than 100, set it to the
// default value of 100. If more runs were needed to reproduce the
// failure, use the number of runs that failed.
runs: regression.numRuns < 100 ? 100 : regression.numRuns,
seed: regression.seed,
bail,
radio,
testFunctions,
projectTraitImplementations,
testContractsPairedFunctions,
});
}
}
else {
// Run fresh tests using user-provided configuration.
radio.emit("logMessage", `Starting fresh round of property testing for the ${targetContractName} contract using user-provided configuration...\n`);
yield propertyTest({
simnet,
targetContractName,
testContractId,
runs,
seed,
bail,
radio,
testFunctions,
projectTraitImplementations,
testContractsPairedFunctions,
});
}
});
exports.checkProperties = checkProperties;
/**
* Runs a property test.
* @param config The union of the configuration and context for the property
* test.
* @returns A promise that resolves when the property test is complete.
*/
const propertyTest = (config) => __awaiter(void 0, void 0, void 0, function* () {
const { simnet, targetContractName, testContractId, runs, seed, bail, radio, testFunctions, projectTraitImplementations, testContractsPairedFunctions, } = config;
// Derive accounts and addresses from simnet.
const simnetAccounts = simnet.getAccounts();
const eligibleAccounts = new Map([...simnetAccounts].filter(([key]) => key !== "faucet"));
const simnetAddresses = Array.from(simnetAccounts.values());
const statistics = {
test: {
successful: new Map(),
discarded: new Map(),
failed: new Map(),
},
};
for (const functionInterface of testFunctions) {
statistics.test.successful.set(functionInterface.name, 0);
statistics.test.discarded.set(functionInterface.name, 0);
statistics.test.failed.set(functionInterface.name, 0);
}
const radioReporter = (runDetails) => __awaiter(void 0, void 0, void 0, function* () {
(0, heatstroke_1.reporter)(runDetails, radio, "test", statistics);
};
fast_check_1.default.assert(fast_check_1.default.property(fast_check_1.default
// Persist failures for regression testing.
if (runDetails.failed) {
(0, persistence_1.persistFailure)(runDetails, "test", testContractId,
// No dialers in property-based testing.
undefined);
}
});
yield fast_check_1.default.assert(fast_check_1.default.asyncProperty(fast_check_1.default
.record({
testContractId: fast_check_1.default.constant(testContractId),
rendezvousContractId: fast_check_1.default.constant(testContractId),
testCaller: fast_check_1.default.constantFrom(...eligibleAccounts.entries()),

@@ -156,3 +221,3 @@ canMineBlocks: fast_check_1.default.boolean(),

})
.map((burnBlocks) => (Object.assign(Object.assign({}, r), burnBlocks)))), (r) => {
.map((burnBlocks) => (Object.assign(Object.assign({}, r), burnBlocks)))), (r) => __awaiter(void 0, void 0, void 0, function* () {
const selectedTestFunctionArgs = (0, shared_1.argsToCV)(r.selectedTestFunction, r.functionArgs);

@@ -173,5 +238,5 @@ const printedTestFunctionArgs = r.functionArgs

const discardFunctionName = testContractsPairedFunctions
.get(r.testContractId)
.get(r.rendezvousContractId)
.get(r.selectedTestFunction.name);
const discarded = isTestDiscarded(discardFunctionName, selectedTestFunctionArgs, r.testContractId, simnet, testCallerAddress);
const discarded = isTestDiscarded(discardFunctionName, selectedTestFunctionArgs, r.rendezvousContractId, simnet, testCallerAddress);
if (discarded) {

@@ -191,3 +256,3 @@ statistics.test.discarded.set(r.selectedTestFunction.name, statistics.test.discarded.get(r.selectedTestFunction.name) + 1);

// be caught and logged as a test failure in the catch block.
const { result: testFunctionCallResult } = simnet.callPublicFn(r.testContractId, r.selectedTestFunction.name, selectedTestFunctionArgs, testCallerAddress);
const { result: testFunctionCallResult } = simnet.callPublicFn(r.rendezvousContractId, r.selectedTestFunction.name, selectedTestFunctionArgs, testCallerAddress);
const testFunctionCallResultJson = (0, transactions_1.cvToJSON)(testFunctionCallResult);

@@ -253,3 +318,3 @@ const discardedInPlace = (0, exports.isTestDiscardedInPlace)(testFunctionCallResultJson);

}
}), {
})), {
endOnFailure: bail,

@@ -261,4 +326,27 @@ numRuns: runs,

});
});
/**
* Emits a warning for test functions that reference traits without eligible
* implementations.
*/
const emitMissingTraitWarning = (radio, functionNames) => {
if (functionNames.length === 0) {
return;
}
const functionList = functionNames.map((fn) => ` - ${fn}`).join("\n");
radio.emit("logMessage", (0, ansicolor_1.yellow)(`\nWarning: The following test functions reference traits without eligible implementations and will be skipped:\n\n${functionList}\n`));
radio.emit("logMessage", (0, ansicolor_1.yellow)(`Note: You can add contracts implementing traits either as project contracts or as requirements.\n`));
};
exports.checkProperties = checkProperties;
/**
* Emits a header for a regression test run with seed and run count information.
*/
const emitPropertyRegressionTestHeader = (radio, targetContractName, seed, numRuns, timestamp) => {
radio.emit("logMessage", shared_1.LOG_DIVIDER);
radio.emit("logMessage", `
Running ${(0, ansicolor_1.underline)(timestamp)} regression test for the ${targetContractName} contract with:
- Seed: ${seed}
- Runs: ${numRuns}
`);
};
const filterTestFunctions = (allFunctionsMap) => new Map(Array.from(allFunctionsMap, ([contractId, functions]) => [

@@ -343,1 +431,2 @@ contractId,

}
exports.PropertyTestError = PropertyTestError;

@@ -6,6 +6,8 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.getContractNameFromContractId = exports.argsToCV = exports.hexaString = exports.functionToArbitrary = exports.getFunctionsListForContract = exports.getFunctionsFromContractInterfaces = exports.getSimnetDeployerContractsInterfaces = void 0;
exports.getContractNameFromContractId = exports.argsToCV = exports.hexaString = exports.functionToArbitrary = exports.getFunctionsListForContract = exports.getFunctionsFromContractInterfaces = exports.getSimnetDeployerContractsInterfaces = exports.LOG_DIVIDER = void 0;
const fast_check_1 = __importDefault(require("fast-check"));
const transactions_1 = require("@stacks/transactions");
const traits_1 = require("./traits");
/** 79 characters long divider for logging. */
exports.LOG_DIVIDER = "-------------------------------------------------------------------------------";
/**

@@ -12,0 +14,0 @@ * Retrieves the contract interfaces of the contracts deployed by a specific

{
"name": "@stacks/rendezvous",
"version": "0.13.1",
"version": "0.14.0",
"description": "Meet your contract's vulnerabilities head-on.",

@@ -34,15 +34,15 @@ "main": "app.js",

"@iarna/toml": "^2.2.5",
"@stacks/clarinet-sdk": "^3.12.0",
"@stacks/transactions": "^7.2.0",
"@stacks/clarinet-sdk": "^3.14.0",
"@stacks/transactions": "^7.3.1",
"ansicolor": "^2.0.3",
"fast-check": "^4.3.0",
"yaml": "^2.8.1"
"fast-check": "^4.5.3",
"yaml": "^2.8.2"
},
"devDependencies": {
"@stacks/clarinet-sdk-wasm": "^3.12.0",
"@stacks/clarinet-sdk-wasm": "^3.14.0",
"@types/jest": "^30.0.0",
"jest": "^30.2.0",
"ts-jest": "^29.4.5",
"ts-jest": "^29.4.6",
"typescript": "^5.9.3"
}
}