diff --git a/SUMMARY.md b/SUMMARY.md index 0d008f7580..9c87c89ebd 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -74,6 +74,8 @@ * [Post Conditions with Stacks.js](guides-and-tutorials/frontend/post-conditions-with-stacks.js.md) * [Authentication with Stacks.js](guides-and-tutorials/frontend/authentication-with-stacks.js.md) * [Sending Transactions with Stacks.js](guides-and-tutorials/frontend/sending-transactions-with-stacks.js.md) +* [Testing Smart Contracts](guides-and-tutorials/testing-smart-contracts/README.md) + * [Fuzz Testing](guides-and-tutorials/testing-smart-contracts/fuzz-testing.md) * [Run a Node](guides-and-tutorials/nodes-and-miners/README.md) * [Run a Node with Docker](guides-and-tutorials/nodes-and-miners/run-a-node-with-docker.md) * [Run a Node with Digital Ocean](guides-and-tutorials/nodes-and-miners/run-a-node-with-digital-ocean.md) diff --git a/guides-and-tutorials/testing-smart-contracts/README.md b/guides-and-tutorials/testing-smart-contracts/README.md new file mode 100644 index 0000000000..56cd0c4b4c --- /dev/null +++ b/guides-and-tutorials/testing-smart-contracts/README.md @@ -0,0 +1,12 @@ +# Testing Smart Contracts + +Smart contracts are immutable once deployed. Bugs are permanent. Test them +thoroughly. + +This section covers testing Clarity contracts. + +* [Fuzz Testing](./fuzz-testing.md): Use Rendezvous to hammer your contract with random + inputs. It helps expose edge cases and vulnerabilities. +* [Clarity Unit Testing](https://github.com/stacks-network/clarunit) + +More guides will follow. diff --git a/guides-and-tutorials/testing-smart-contracts/fuzz-testing.md b/guides-and-tutorials/testing-smart-contracts/fuzz-testing.md new file mode 100644 index 0000000000..933154f55f --- /dev/null +++ b/guides-and-tutorials/testing-smart-contracts/fuzz-testing.md @@ -0,0 +1,131 @@ +# Fuzz Testing with Rendezvous + +Smart contracts on Stacks are immutable. Bugs are forever. Test early. Test +often. Fuzzing finds edge cases that unit tests often miss. + +## What is Fuzz Testing? + +Fuzzing hits your code with random inputs. It helps uncover unexpected +behavior and subtle bugs. Unlike unit tests, it explores paths you didn't +think of. + +Rendezvous (`rv`) is a Clarity fuzzer. It supports: + +### Property-Based Testing + +You extract properties about your smart contract using Clarity. Rendezvous checks them multiple times with random inputs, in a stateful manner (the smart contract's state is not refreshed during the run). + +**What is a property?** + +A property is a universal truth about your smart contract's state, functions, etc. + +**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: + +```clarity +(define-public (test-reverse-list (seq (list 127 uint))) + (begin + (asserts! + (is-eq seq + (reverse-uint + (reverse-uint seq) + ) + ) + (err u999) + ) + (ok true) + ) +) +``` + +**Making your property valid for Rendezvous** + +> For a property to be cosidered valid by Rendezvous, it has to comply with the following rules: +> +> - Function name starts with `test-` +> - Function is declared as `public` +> - 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 + +--- + +### Invariant Testing + +You define read-only conditions in Clarity that must always hold true. Rendezvous attempts to create state transitions in your smart contract and continuously checks the conditions you defined to hold. + +**What is an invariant?** + +An invariant is a general truth regarding your smart contract's internal state. It will not be able to mutate the state, its role being solely to check the integrity of the 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` to extract an invariant regarding your smart contract's internal state: + +```clarity +(define-read-only (invariant-counter-gt-zero) + (let + ( + (increment-num-calls + (default-to u0 (get called (map-get? context "increment"))) + ) + (decrement-num-calls + (default-to u0 (get called (map-get? context "decrement"))) + ) + ) + (if + (<= increment-num-calls decrement-num-calls) + true + (> (var-get counter) u0) + ) + ) +) +``` + +**Making your invariant valid for Rendezvous** + +> For an invariant to be cosidered valid by Rendezvous, it has to complain to the following ruleset: +> +> - Function name starts with invariant- +> - Function is declared as read-only (not public) +> - 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 + +## Why Test in Clarity? + +Rendezvous tests run in Clarity, just like your contracts. + +1. Tests operate under the exact same constraints as production code. +2. Better understanding of Clarity. +3. No need to expose internals as public functions. +4. Fewer tools to manage. + +## Getting Started + +Put tests next to contracts. Rendezvous will find them. + +``` +my-project/ +├── Clarinet.toml +├── contracts/ +│ ├── my-contract.clar # Contract +│ ├── my-contract.tests.clar # Tests +└── settings/ + └── Devnet.toml +``` + +### Installation + +To install Rendezvous as a dependency in your project, use `npm`: + +``` +npm install @stacks/rendezvous +``` + +This will add Rendezvous to your project's `node_modules` and update your `package.json`. + +## Rendezvous Docs + +See full docs at: +https://stacks-network.github.io/rendezvous/