From 05de30f795a36bbb51774fa7ae72602c7ebfb26c Mon Sep 17 00:00:00 2001 From: radu-stacks Date: Fri, 29 May 2026 01:58:00 +0300 Subject: [PATCH] Rendezvous-related updates following v1 release --- .../build/get-started/developer-quickstart.md | 2 +- docs/build/rendezvous/overview.md | 37 +-- docs/build/rendezvous/quickstart.md | 43 ++-- docs/build/stacks-devtools-catalog.md | 2 +- docs/learn/network-fundamentals/audits.md | 2 +- docs/reference/rendezvous/reference.md | 234 ++++++++++++++---- 6 files changed, 225 insertions(+), 95 deletions(-) diff --git a/docs/build/get-started/developer-quickstart.md b/docs/build/get-started/developer-quickstart.md index 6fca69ff15..3e706dee04 100644 --- a/docs/build/get-started/developer-quickstart.md +++ b/docs/build/get-started/developer-quickstart.md @@ -477,7 +477,7 @@ Now with the actual writing of your contract complete, we now need to test its f * Contract interaction in the [Clarinet REPL](../clarinet/contract-interaction.md) * Running your contract in a [local blockchain environment](../clarinet/local-blockchain-development.md) -* Fuzz testing with [Rendezvous](https://stacks-network.github.io/rendezvous/) +* Fuzz testing with [Rendezvous](https://stx-labs.github.io/rendezvous/) * Writing unit tests with the [Clarinet JS SDK](../clarinet/testing-with-clarinet-sdk.md) We'll go with unit testing for now. In your `tests` folder, open up the related `message-board.test.ts` file and let's use the unit test written below. diff --git a/docs/build/rendezvous/overview.md b/docs/build/rendezvous/overview.md index 5e344bbc61..cd3c309379 100644 --- a/docs/build/rendezvous/overview.md +++ b/docs/build/rendezvous/overview.md @@ -28,10 +28,11 @@ A property is a universal truth about your smart contract's state, functions, et **How to extract a property?** -Say that your smart contract has a function that reverses a list of `uint`s. In this case, one property can be that "reversing a list twice returns the original list". The property will look like this: +Say that your smart contract has a function that reverses a list of `uint`s. In this case, one property can be that "reversing a list twice returns the original list". You write the property directly inside your contract, marked as [simnet-only code](../clarinet/simnet-only-code.md): ```clarity -(define-public (test-reverse-list (seq (list 127 uint))) +;; #[env(simnet)] +(define-private (test-reverse-list (seq (list 127 uint))) (begin (asserts! (is-eq seq @@ -48,10 +49,11 @@ Say that your smart contract has a function that reverses a list of `uint`s. In **Making your property valid for Rendezvous** -> For a property to be cosidered valid by Rendezvous, it has to comply with the following rules: +> For a property to be considered valid by Rendezvous, it has to comply with the following rules: > +> * Function lives in the contract file, annotated with `;; #[env(simnet)]` > * Function name starts with `test-` -> * Function is declared as `public` +> * Function is declared as `private` > * Test passes when it returns `(ok true)` > * Test would be discarded if it returned `(ok false)` > * Test fails if it returns an error or throws an exception @@ -68,9 +70,10 @@ An invariant is a general truth regarding your smart contract's internal state. **How to extract an invariant?** -Say that you have a counter contract, having functions to `increment` and `decrement`. In this case, you could use the Rendezvous [`context`](https://stacks-network.github.io/rendezvous/chapter_6.html?#the-rendezvous-context) to extract an invariant regarding your smart contract's internal state: +Say that you have a counter contract, having functions to `increment` and `decrement`. In this case, you could use the Rendezvous [`context`](https://stx-labs.github.io/rendezvous/chapter_6.html#the-rendezvous-context) to extract an invariant regarding your smart contract's internal state: ```clarity +;; #[env(simnet)] (define-read-only (invariant-counter-gt-zero) (let ( @@ -92,12 +95,17 @@ Say that you have a counter contract, having functions to `increment` and `decre **Making your invariant valid for Rendezvous** -> For an invariant to be cosidered valid by Rendezvous, it has to complain to the following ruleset: +> For an invariant to be considered valid by Rendezvous, it has to comply with the following ruleset: > -> * Function name starts with invariant- -> * Function is declared as read-only (not public) +> * Function lives in the contract file, annotated with `;; #[env(simnet)]` +> * Function name starts with `invariant-` +> * Function is declared as `read-only` > * Function returns a boolean value (true if the invariant holds, false if violated) -> * The test can use the special context map to access execution history +> * The test can use the special `context` map to access execution history + +{% hint style="info" %} +Invariant testing requires the `context` map and a `update-context` private function in your contract (both annotated with `;; #[env(simnet)]`). Rendezvous uses them to track how many times each public function has been called. See the [Rendezvous Reference](https://stx-labs.github.io/rendezvous/chapter_6.html#the-rendezvous-context) for the exact definitions. +{% endhint %} ## Why Test in Clarity? @@ -129,14 +137,13 @@ Running tests in Clarity reduces the number of external tools and integrations y ## Getting Started -Put tests next to contracts. Rendezvous will find them. +Write your tests directly inside your contract, marked as [simnet-only code](../clarinet/simnet-only-code.md) with the `;; #[env(simnet)]` annotation. Clarinet strips this code when deploying to real networks, so it never reaches mainnet — and Rendezvous picks it up automatically during testing. ``` my-project/ ├── Clarinet.toml ├── contracts/ -│ ├── my-contract.clar # Contract -│ ├── my-contract.tests.clar # Tests +│ └── my-contract.clar # Contract + its #[env(simnet)] tests └── settings/ └── Devnet.toml ``` @@ -155,5 +162,7 @@ This will add Rendezvous to your project's `node_modules` and update your `packa ### Additional Resources -* \[[Github](https://stacks-network.github.io/rendezvous/)] Rendezvous repo -* \[[Youtube @jofawole](https://youtu.be/deWQxCEy9_M?si=bBpUoKGpJvFLFu_9)] How to Use Rendezvous to Fuzz Clarity Contracts +* \[[GitHub](https://github.com/stx-labs/rendezvous)] Rendezvous repository +* \[[Rendezvous Book](https://stx-labs.github.io/rendezvous/)] Full documentation +* \[[Library API](https://stx-labs.github.io/rendezvous/chapter_9.html)] Drive the testing loop yourself from TypeScript +* \[[YouTube @jofawole](https://youtu.be/deWQxCEy9_M?si=bBpUoKGpJvFLFu_9)] How to Use Rendezvous to Fuzz Clarity Contracts diff --git a/docs/build/rendezvous/quickstart.md b/docs/build/rendezvous/quickstart.md index 28f0b2aa51..4a420d2362 100644 --- a/docs/build/rendezvous/quickstart.md +++ b/docs/build/rendezvous/quickstart.md @@ -246,21 +246,13 @@ The main functions and state of the contract are now covered by tests. Line cove Rendezvous lets you test a broader range of inputs, not just specific examples. Let's see how to write your first property-based test and why it matters. -### Create the Test File - -Create the Rendezvous test file: - -```bash -touch contracts/stx-defi.tests.clar -``` - ### Add an Ice-Breaker Test -Before writing any meaningful properties, it's a good idea to check that Rendezvous can run. Add a simple "always-true" test to verify your setup. -Open `contracts/stx-defi.tests.clar` and add an always-true test: +Before writing any meaningful properties, it's a good idea to check that Rendezvous can run. Add a simple "always-true" test, annotated with `#[env(simnet)]`, to the end of `contracts/stx-defi.clar`: ```clarity -(define-public (test-always-true) +;; #[env(simnet)] +(define-private (test-always-true) (ok true) ) ``` @@ -316,15 +308,14 @@ If you see similar output, your setup works. You're ready to write a **real prop ### Define a Borrowing Property -You want to test that **borrowing always updates the loan amount correctly**: +You want to test that **borrowing always updates the loan amount correctly**. Add this to the end of `contracts/stx-defi.clar`: ```clarity -;; stx-defi.tests.clar - +;; #[env(simnet)] ;; Property: Borrowing should always update the loan amount correctly. ;; The new loan amount should equal the old loan amount plus the borrowed ;; amount. -(define-public (test-borrow (amount uint)) +(define-private (test-borrow (amount uint)) (let ( ;; Record the loan amount before performing any action that would end up ;; changing the internal state of the smart contract. Query the loans map @@ -353,8 +344,8 @@ You want to test that **borrowing always updates the loan amount correctly**: Rendezvous: -1. Injects all property-based tests directly into the deployed contract. -2. Detects all public `test-*` functions automatically. +1. Deploys the contract with all `#[env(simnet)]`-annotated test functions included. +2. Detects all private `test-*` functions automatically. 3. Generates a random sequence to call each test. 4. Produces random argument values for each function parameter. 5. Randomly selects senders from settings/Devnet.toml. @@ -378,13 +369,14 @@ Let's address them one by one. ### Handle Preconditions -**First, you need deposits.** You can create a helper function that Rendezvous will pick up during property-based testing runs. This helper will allow deposits to be created so other tests can check properties that require deposits: +**First, you need deposits.** You can create a helper function that Rendezvous will pick up during property-based testing runs. Add this to `contracts/stx-defi.clar`: ```clarity +;; #[env(simnet)] ;; This is a helper function that will eventually be picked up during ;; property-based-testing runs. It allows creating deposits in the smart ;; contract so other tests can check properties requiring a deposit. -(define-public (test-deposit-helper (amount uint)) +(define-private (test-deposit-helper (amount uint)) (let ( ;; Call the deposit function and ignore the result. (deposit-result (deposit amount)) @@ -394,13 +386,14 @@ Let's address them one by one. ) ``` -**Next, add discard logic to the borrow test.** A test is discarded when it returns `(ok false)`. Wrap the core test logic in a conditional that checks for invalid preconditions (the three cases listed above) and returns `(ok false)` to discard those cases: +**Next, add discard logic to the borrow test.** A test is discarded when it returns `(ok false)`. Replace the previous `test-borrow` with this version that checks for invalid preconditions (the three cases listed above) and returns `(ok false)` to discard those cases: ```clarity +;; #[env(simnet)] ;; Property: Borrowing should always update the loan amount correctly. ;; The new loan amount should equal the old loan amount plus the borrowed ;; amount. -(define-public (test-borrow (amount uint)) +(define-private (test-borrow (amount uint)) (if (or ;; If amount is 0, the STX transfer performed in the borrow operation ;; would fail, resulting in a false negative. @@ -479,7 +472,7 @@ Seed : 1880056597 Counterexample: - Test Contract : stx-defi -- Test Function : test-borrow (public) +- Test Function : test-borrow (private) - Arguments : [1] - Caller : wallet_8 - Outputs : {"type":{"response":{"ok":"bool","error":{"string-ascii":{"length":33}}}}} @@ -678,17 +671,17 @@ You can see a complete step-by-step implementation of this tutorial with commit- Now that you understand the power of Rendezvous, explore: -- **More examples**: Study other smart contracts in the [Examples Chapter](https://stacks-network.github.io/rendezvous/chapter_8.html) of the [Rendezvous Docs](https://stacks-network.github.io/rendezvous/) +- **More examples**: Study other smart contracts in the [Examples Chapter](https://stx-labs.github.io/rendezvous/chapter_8.html) of the [Rendezvous Docs](https://stx-labs.github.io/rendezvous/) - **Your own contracts**: Apply Rendezvous to your projects and find bugs before they reach production --- ## Get Involved -**Found this tutorial useful?** Star the [Rendezvous repository on GitHub](https://github.com/stacks-network/rendezvous) to show your support! +**Found this tutorial useful?** Star the [Rendezvous repository on GitHub](https://github.com/stx-labs/rendezvous) to show your support! Have questions, found a bug, or want to contribute? We'd love to hear from you: -- **Open an issue** on [GitHub](https://github.com/stacks-network/rendezvous/issues) +- **Open an issue** on [GitHub](https://github.com/stx-labs/rendezvous/issues) - **Reach out** with questions or feedback - **Share your findings** — contribute examples of bugs you've caught to show others how powerful advanced testing techniques can be diff --git a/docs/build/stacks-devtools-catalog.md b/docs/build/stacks-devtools-catalog.md index 6475c7cb58..c944bb8387 100644 --- a/docs/build/stacks-devtools-catalog.md +++ b/docs/build/stacks-devtools-catalog.md @@ -80,7 +80,7 @@ Official Stacks devtools are built and maintained by either **Stacks Labs** or * ### Testing & Simulation -* [**Rendezvous**](https://stacks-network.github.io/rendezvous/) - A smart contract fuzzer for Clarity. +* [**Rendezvous**](https://stx-labs.github.io/rendezvous/) - A smart contract fuzzer for Clarity. *** diff --git a/docs/learn/network-fundamentals/audits.md b/docs/learn/network-fundamentals/audits.md index 71cd67342d..e729ff87e3 100644 --- a/docs/learn/network-fundamentals/audits.md +++ b/docs/learn/network-fundamentals/audits.md @@ -14,7 +14,7 @@ For any project, layers of security are crucial. Audits represent one layer, whi * Bitcoin L2 Labs' [whitehat security program](https://bitcoinl2-labs.github.io/2024/06/04/orange-hats.html) * Stacks Foundation's partnership with Staking Defense League * Stacks Founation's ongoing [Immunefi bug bounty program](https://immunefi.com/bug-bounty/stacks/information/) -* Dedicated Stacks Foundation Residents focused exclusively on fuzz and penetration testing (created [Rendezvous](https://stacks-network.github.io/rendezvous/)) +* Dedicated Stacks Foundation Residents focused exclusively on fuzz and penetration testing (created [Rendezvous](https://stx-labs.github.io/rendezvous/)) {% hint style="warning" %} _All 'high' or 'critical' issues listed in audits have either been mitigated or otherwise made obsolete, even if the report states otherwise._ diff --git a/docs/reference/rendezvous/reference.md b/docs/reference/rendezvous/reference.md index 81ffff632f..2a06b2228f 100644 --- a/docs/reference/rendezvous/reference.md +++ b/docs/reference/rendezvous/reference.md @@ -38,6 +38,10 @@ This reference explains how to use Rendezvous in different situations. By the en - [Example](#example-1) - [Adding More Implementations](#adding-more-implementations) +[Library API](#library-api) + - [getContractFunction](#getcontractfunction) + - [strategyFor](#strategyfor) + --- ## Running Rendezvous @@ -45,7 +49,7 @@ This reference explains how to use Rendezvous in different situations. By the en To run Rendezvous, use the following command: ```bash -rv [--seed] [--runs] [--bail] [--dial] +rv [--config] [--seed] [--runs] [--regr] [--bail] [--dial] ``` Let's break down each part of the command. @@ -58,8 +62,7 @@ Consider this example Clarinet project structure: root ├── Clarinet.toml ├── contracts -│ ├── contract.clar -│ ├── contract.tests.clar +│ └── contract.clar └── settings └── Devnet.toml ``` @@ -98,15 +101,11 @@ The `` argument specifies the testing technique to use. The available opti - `test` – Runs property-based tests. - `invariant` – Runs invariant tests. -For a deeper understanding of these techniques and when to use each, see [Testing Methodologies](https://stacks-network.github.io/rendezvous/chapter_4.md) chapter of the [Rendezvous Docs](https://stacks-network.github.io/rendezvous/). +For a deeper understanding of these techniques and when to use each, see the [Testing Methodologies](https://stx-labs.github.io/rendezvous/chapter_4.html) chapter of the [Rendezvous Docs](https://stx-labs.github.io/rendezvous/). **Running property-based tests** -To run property-based tests for the `contract` contract, ensure that your test functions are defined in: - -``` -./root/contracts/contract.tests.clar -``` +Property-based testing requires one or more **test functions** (e.g. `test-xyz`) in the contract file (`contract.clar`), annotated with `;; #[env(simnet)]`. Then, execute: @@ -117,15 +116,14 @@ rv ./root contract test This tells Rendezvous to: - Load the **Clarinet project** located in `./root`. -- Target the **contract** named `contract` as defined in `Clarinet.toml` by executing **property-based tests** defined in `contract.tests.clar`. +- Target the **contract** named `contract` as defined in `Clarinet.toml` by executing **property-based tests** defined within the contract. **Running invariant tests** -To run invariant tests for the `contract` contract, ensure that your invariant functions are defined in: +Invariant testing requires two things in the contract file (`contract.clar`), both annotated with `;; #[env(simnet)]`: -``` -./root/contracts/contract.tests.clar -``` +1. One or more **invariant functions** (e.g. `invariant-xyz`). +2. The **Rendezvous context** — the `context` map and `update-context` function (see [The Rendezvous Context](#the-rendezvous-context)). To run invariant tests, use: @@ -201,7 +199,7 @@ Dialers allow you to define **pre- and post-execution functions** using JavaScri rv root contract invariant --dial=./custom-dialer.js ``` -A good example of a dialer can be found in the Rendezvous repository, within the example Clarinet project, inside the [sip010.js file](https://github.com/stacks-network/rendezvous/blob/272b9247cdfcd5d12da89254e622e712d6e29e5e/example/sip010.js). +A good example of a dialer can be found in the Rendezvous repository, within the example Clarinet project, inside the [sip010.cjs file](https://github.com/stx-labs/rendezvous/blob/12b3e4cc011c0029522b54e0e02342f9d47600eb/example/sip010.cjs). In that file, you’ll find a **post-dialer** designed as a **sanity check** for SIP-010 token contracts. It ensures that the `transfer` function correctly emits the required **print event** containing the `memo`, as specified in [SIP-010](https://github.com/stacksgov/sips/blob/6ea251726353bd1ad1852aabe3d6cf1ebfe02830/sips/sip-010/sip-010-fungible-token-standard.md?plain=1#L69). @@ -269,6 +267,87 @@ async function postTransferSip010PrintEvent(context) { This dialer ensures that any SIP-010 token contract properly emits the **memo print event** during transfers, helping to catch deviations from the standard. +**5. Regression Testing** + +Rendezvous automatically saves failing test cases to prevent regressions. When a test fails, its seed and configuration are persisted to disk. On subsequent runs, you can replay these failures to ensure bugs stay fixed. + +By default, Rendezvous runs fresh random tests: + +```bash +rv root contract test +``` + +To verify that previously discovered bugs remain fixed, use the `--regr` flag: + +```bash +rv root contract test --regr +``` + +Rendezvous loads all saved failures for the contract and replays them using their original seeds. + +**How Failure Persistence Works** + +When Rendezvous detects a failure, it automatically saves the test configuration to: + +``` +.rendezvous-regressions/..json +``` + +For example, failures in the `counter` contract deployed by `ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM` would be saved to: + +``` +.rendezvous-regressions/ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.counter.json +``` + +The regression file stores failures grouped by test type (`test` vs `invariant`). Each failure record includes: + +- `seed` – The random seed that triggered the failure. +- `numRuns` – Number of test iterations needed for the failure to occur. +- `timestamp` – When the failure was discovered (Unix timestamp in milliseconds). +- `dial` (optional) – Path to the dialer file used during the test. + +Failures are sorted by timestamp in descending order, with the most recent first. To clear saved regressions for a contract, delete its regression file (or edit it to remove specific failures while keeping others). + +**6. Using a Config File** + +Instead of passing options as CLI flags, you can provide a JSON config file with `--config`. When a config file is used, **all run options come from the file exclusively** — CLI flags like `--seed` or `--runs` are ignored. + +```bash +rv root contract test --config=rv.config.json +``` + +A config file is a JSON object with optional fields: + +```json +{ + "accounts": [ + { + "name": "whale_1", + "address": "SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE" + } + ], + "accounts_mode": "overwrite", + "seed": 42, + "runs": 500, + "bail": true, + "dial": "./sip010.cjs" +} +``` + +| Field | Type | Description | +| --------------- | ---------------- | ------------------------------------------------------- | +| `accounts` | array of objects | Custom accounts (`name` and `address` fields required). | +| `accounts_mode` | string | `"overwrite"` (default) or `"concatenate"`. | +| `seed` | integer | Seed for replay functionality. | +| `runs` | positive integer | Number of test iterations. | +| `bail` | boolean | Stop on first failure. | +| `regr` | boolean | Run regression tests only. | +| `dial` | string | Path to custom dialers file. | + +The `accounts` field lets you define custom accounts (for example, mainnet "whale" addresses) for testing. By default (`"overwrite"` mode), these replace the `Devnet.toml` accounts entirely. With `"concatenate"` mode, config accounts are merged with the existing Devnet accounts — if a name appears in both, the config account's address takes precedence. + +Rendezvous warns if the config file contains unrecognized keys (e.g. a typo like `"sedd"` instead of `"seed"`), and also warns if CLI flags are passed alongside `--config`. + ### Summary | Argument/Option | Description | Example | @@ -279,6 +358,9 @@ This dialer ensures that any SIP-010 token contract properly emits the **memo pr | `--runs=` | Sets the number of test iterations (default: 100). | `rv root contract test --runs=500` | | `--seed=` | Uses a specific seed for reproducibility. | `rv root contract test --seed=12345` | | `--dial=` | Loads JavaScript dialers from a file for pre/post-processing. | `rv root contract test --dial=./custom-dialer.js` | +| `--regr` | Run regression tests only (replay saved failures). | `rv root contract test --regr` | +| `--bail` | Stop after the first failure. | `rv root contract test --bail` | +| `--config=` | Uses a JSON config file for all run options. | `rv root contract test --config=rv.config.json` | --- @@ -286,69 +368,51 @@ This dialer ensures that any SIP-010 token contract properly emits the **memo pr Rendezvous makes **property-based tests** and **invariant tests** first-class. Tests are written in the same language as the system under test. This helps developers master the contract language. It also pushes boundaries—programmers shape their thoughts first, then express them using the language's tools. -When Rendezvous initializes a **Simnet session** using a given Clarinet project, it **does not modify any contract** listed in Clarinet.toml—except for the **target contract**. During testing, Rendezvous updates the target contract by merging: - -1. **The original contract source code** -2. **The test contract** (which includes property-based tests and invariants) -3. **The Rendezvous context**, which helps track function calls and execution details +When Rendezvous initializes a **Simnet session** using a given Clarinet project, it deploys the contracts as defined in `Clarinet.toml`. The contract source, its test functions, and the **context** all live in the same file — the test code is marked simnet-only with the `;; #[env(simnet)]` annotation, so it is included during Simnet testing but stripped on deployment to real networks. ### Example -Let’s say we have a contract named `checker` with the following source: +Let’s say we have a contract named `checker`. The contract source, test functions, and **context** all live in the same `checker.clar` file: ```clarity ;; checker.clar -(define-public (check-it (flag bool)) - (if flag (ok 1) (err u100)) -) -``` - -And its test contract, `checker.tests`: - -```clarity -;; checker.tests.clar - -(define-public (test-1) - (ok true) -) - -(define-read-only (invariant-1) - true -) -``` - -When Rendezvous runs the tests, it **automatically generates a modified contract** that includes the original contract, the tests, and an additional **context** for tracking execution. The final contract source deployed in the Simnet session will look like this: - -``` (define-public (check-it (flag bool)) (if flag (ok 1) (err u100)) ) +;; #[env(simnet)] (define-map context (string-ascii 100) { called: uint ;; other data } ) -(define-public (update-context (function-name (string-ascii 100)) (called uint)) +;; #[env(simnet)] +(define-private (update-context (function-name (string-ascii 100)) (called uint)) (ok (map-set context function-name {called: called})) ) -(define-public (test-1) +;; #[env(simnet)] +(define-private (test-1) (ok true) ) +;; #[env(simnet)] (define-read-only (invariant-1) true ) ``` -While the original contract source and test functions are familiar, the **context** is new. Let's take a closer look at it. +The `;; #[env(simnet)]` annotation ensures the test functions and context are only deployed during Simnet testing. While the contract source and test functions are familiar, the **context** is new. Let's take a closer look at it. ## The Rendezvous Context -Rendezvous introduces a **context** to track function calls and execution details during testing. This allows for better tracking of execution details and invariant validation. +Rendezvous uses a **context** to track function calls and execution details during invariant testing. This allows for better tracking of execution details and invariant validation. + +{% hint style="warning" %} +Every contract tested with Rendezvous **invariant testing** must include the `context` map and the `update-context` private function (both annotated with `;; #[env(simnet)]`). During invariant testing, Rendezvous calls public functions and uses `update-context` to track successful executions, which lets invariants reason about how many times each function has been called. If these are missing during invariant testing, Rendezvous throws a runtime error. The context is **not** required for property-based testing. +{% endhint %} ### How the Context Works @@ -357,12 +421,14 @@ When a function is successfully executed during a test, Rendezvous records its e Here’s how the context is structured: ```clarity +;; #[env(simnet)] (define-map context (string-ascii 100) { called: uint ;; Additional fields can be added here }) -(define-public (update-context (function-name (string-ascii 100)) (called uint)) +;; #[env(simnet)] +(define-private (update-context (function-name (string-ascii 100)) (called uint)) (ok (map-set context function-name {called: called})) ) ``` @@ -422,7 +488,7 @@ A **separate function** determines whether a test should run. (> n u1) ;; Only allow tests where n > 1 ) -(define-public (test-add (n uint)) +(define-private (test-add (n uint)) (let ((counter-before (get-counter))) (try! (add n)) @@ -441,7 +507,7 @@ Instead of using a separate function, **the test itself decides whether to run** **In-place discarding example** ```clarity -(define-public (test-add (n uint)) +(define-private (test-add (n uint)) (let ((counter-before (get-counter))) (ok @@ -478,7 +544,7 @@ Some smart contracts need a special `Clarinet.toml` file to allow Rendezvous to A great example is the **sBTC contract suite**. -For testing the [`sbtc-token`](https://github.com/stacks-network/sbtc/blob/b624e4a8f08eb589a435719b200873e8aa5b3305/contracts/contracts/sbtc-token.clar#L30-L35) contract, the `sbtc-registry` authorization function [`is-protocol-caller`](https://github.com/stacks-network/sbtc/blob/b624e4a8f08eb589a435719b200873e8aa5b3305/contracts/contracts/sbtc-registry.clar#L361-L369) is **too restrictive**. Normally, it only allows calls from protocol contracts, making it **impossible to directly test certain state transitions** in `sbtc-token`. +For testing the [`sbtc-token`](https://github.com/stacks-sbtc/sbtc/blob/b624e4a8f08eb589a435719b200873e8aa5b3305/contracts/contracts/sbtc-token.clar#L30-L35) contract, the `sbtc-registry` authorization function [`is-protocol-caller`](https://github.com/stacks-sbtc/sbtc/blob/b624e4a8f08eb589a435719b200873e8aa5b3305/contracts/contracts/sbtc-registry.clar#L361-L369) is **too restrictive**. Normally, it only allows calls from protocol contracts, making it **impossible to directly test certain state transitions** in `sbtc-token`. To work around this, you need two things: @@ -549,10 +615,72 @@ This process allows Rendezvous to create meaningful state transitions and valida ### Example -The `example` Clarinet project demonstrates this feature. The [send-tokens](https://github.com/stacks-network/rendezvous/blob/9c02aa7c2571b3795debc657bd433fd9bf7f19eb/example/contracts/send-tokens.clar) contract contains [one public function](https://github.com/stacks-network/rendezvous/blob/9c02aa7c2571b3795debc657bd433fd9bf7f19eb/example/contracts/send-tokens.clar#L3-L7) and [one property-based test](https://github.com/stacks-network/rendezvous/blob/9c02aa7c2571b3795debc657bd433fd9bf7f19eb/example/contracts/send-tokens.tests.clar#L24-L47) that both accept trait references. +The `example` Clarinet project demonstrates this feature. The [send-tokens](https://github.com/stx-labs/rendezvous/blob/1e9fe78b07d8cd971843634f3915186295efb414/example/contracts/send-tokens.clar) contract contains one public function and one property-based test that both accept trait references. -To enable testing, the project includes [rendezvous-token](https://github.com/stacks-network/rendezvous/blob/9c02aa7c2571b3795debc657bd433fd9bf7f19eb/example/contracts/rendezvous-token.clar), which implements the required trait. +To enable testing, the project includes [rendezvous-token](https://github.com/stx-labs/rendezvous/blob/1e9fe78b07d8cd971843634f3915186295efb414/example/contracts/rendezvous-token.clar), which implements the required trait. ### Adding More Implementations You can include multiple eligible trait implementations in your project. Adding more implementations allows Rendezvous to introduce greater randomness during testing and increases behavioral diversity. If a function that accepts a trait implementation parameter is called X times, those calls are distributed across the available implementations. As the number of implementations grows, Rendezvous has more options to choose from on each call, producing a wider range of behaviors — and uncovering edge cases that may be missed when relying on a single implementation. + +## Library API + +Beyond the `rv` CLI, Rendezvous can be used as a **TypeScript library** for building custom property-based testing strategies. You import its argument-generation capabilities directly and compose your own [fast-check](https://github.com/dubzzz/fast-check) properties — useful when you need full control over the testing loop (custom assertions, stateful setups, multi-contract interactions, or integration with Vitest/Jest). + +Rendezvous ships with TypeScript declarations. It relies on `fast-check` and `@stacks/clarinet-sdk` (both already dependencies). + +### getContractFunction + +`getContractFunction(simnet, contractName, functionName, deployer?)` + +Retrieves a function interface from a deployed contract, enriched with trait-reference data when applicable. Throws if the contract or function is not found. + +| Parameter | Type | Description | +| -------------- | -------- | ---------------------------------------------------------- | +| `simnet` | `Simnet` | The simnet instance from `initSimnet`. | +| `contractName` | `string` | The contract name (e.g., `"counter"`). | +| `functionName` | `string` | The function name (e.g., `"increment"`). | +| `deployer` | `string` | Optional. Deployer address. Defaults to `simnet.deployer`. | + +**Returns:** `EnrichedContractInterfaceFunction` + +### strategyFor + +`strategyFor(simnet, fn, allAddresses?, projectTraitImplementations?)` + +Returns an `fc.Arbitrary` ready for use with `simnet.callPublicFn`, `simnet.callReadOnlyFn`, or `simnet.callPrivateFn`. It handles all Clarity types automatically: `uint`, `int`, `bool`, `principal`, `buff`, `string-ascii`, `string-utf8`, `list`, `tuple`, `optional`, `response`, and `trait_reference` (including recursive and nested structures such as a list of tuples or an optional of a response). + +| Parameter | Type | Description | +| ----------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------- | +| `simnet` | `Simnet` | The simnet instance. | +| `fn` | `EnrichedContractInterfaceFunction` | Function interface from `getContractFunction`. | +| `allAddresses` | `string[]` | Optional. Addresses for principal-typed arguments. Defaults to every account in the simnet. | +| `projectTraitImplementations` | `Record` | Optional. Trait implementations keyed by trait. Defaults to extracting them from the simnet. | + +**Returns:** `fc.Arbitrary` + +### Example + +```ts +import { initSimnet } from "@stacks/clarinet-sdk"; +import { getContractFunction, strategyFor } from "@stacks/rendezvous"; +import fc from "fast-check"; + +const simnet = await initSimnet("./Clarinet.toml"); +const add = getContractFunction(simnet, "counter", "add"); +const arb = strategyFor(simnet, add); + +fc.assert( + fc.property(arb, (args) => { + const { result } = simnet.callPublicFn( + `${simnet.deployer}.counter`, + "add", + args, + simnet.deployer, + ); + return result.type !== "err"; + }), +); +``` + +For the full Library API reference — including custom deployers, restricting the principal pool, and the complete supported-type table — see the [Library API chapter](https://stx-labs.github.io/rendezvous/chapter_9.html) of the Rendezvous Book.