@stacks/rendezvous
Advanced tools
| "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 @@ } |
+32
-19
@@ -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.`); |
+39
-25
@@ -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); |
+149
-60
@@ -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" | ||
| } | ||
| } |
+125
-36
| "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; |
+3
-1
@@ -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 |
+7
-7
| { | ||
| "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" | ||
| } | ||
| } |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
175723
8.14%22
4.76%2620
13.32%7
40%Updated
Updated
Updated
Updated