diff --git a/docs/build/clarinet/local-blockchain-development.md b/docs/build/clarinet/local-blockchain-development.md index dfb6aa34ee..2994e64cde 100644 --- a/docs/build/clarinet/local-blockchain-development.md +++ b/docs/build/clarinet/local-blockchain-development.md @@ -34,7 +34,6 @@ Useful flags: | `--deployment-plan-path ` | Apply a specific deployment plan | | `--use-on-disk-deployment-plan` | Use an existing plan without recomputing | | `--use-computed-deployment-plan` | Recompute and overwrite the plan | -| `--package ` | Load a packaged Devnet configuration | | `--from-genesis` | Skip the embedded Epoch 4.0 snapshot and boot from genesis, walking through every epoch | | `--create-new-snapshot` | Boot from genesis and save a new global snapshot after the first Epoch 4.0 Stacks block | @@ -318,22 +317,6 @@ clarinet devnet start -### Package deployment - -Create reusable Devnet configurations: - -```bash -$ clarinet devnet package --name demo-env -Packaging devnet configuration... -Created demo-env.json -``` - -Use a packaged configuration: - -```bash -$ clarinet devnet start --package demo-env.json -``` - ## Common issues
diff --git a/docs/build/clarinet/project-structure.md b/docs/build/clarinet/project-structure.md index 9b5563a8d4..d15383b059 100644 --- a/docs/build/clarinet/project-structure.md +++ b/docs/build/clarinet/project-structure.md @@ -104,13 +104,13 @@ The **package.json** defines your testing environment and dependencies: }, "author": "", "license": "ISC", - "dependencies": { - "@stacks/clarinet-sdk": "^3.9.1", - "@stacks/transactions": "^7.2.0", + "devDependencies": { + "@stacks/clarinet-sdk": "^4.0.0", + "@stacks/transactions": "^7.6.0", "@types/node": "^24.4.0", "chokidar-cli": "^3.0.0", - "vitest": "^4.0.7", - "vitest-environment-clarinet": "^3.0.0" + "vitest": "^5.0.5", + "vitest-environment-clarinet": "^4.0.0" } } ``` diff --git a/docs/build/clarinet/testing-with-clarinet-sdk.md b/docs/build/clarinet/testing-with-clarinet-sdk.md index d572146247..4117d25e72 100644 --- a/docs/build/clarinet/testing-with-clarinet-sdk.md +++ b/docs/build/clarinet/testing-with-clarinet-sdk.md @@ -288,7 +288,7 @@ const totalDeposits = simnet.getDataVar("defi", "total-deposits"); expect(totalDeposits).toBeUint(1000); const balance = simnet.getMapEntry("defi", "balances", Cl.principal(wallet)); -expect(balance).toBeUint(1000); +expect(balance).toBeSome(Cl.uint(1000)); // none if the entry does not exist ``` ## Other Examples diff --git a/docs/build/clarinet/validation-and-analysis.md b/docs/build/clarinet/validation-and-analysis.md index 01e9acdad3..7eb7856364 100644 --- a/docs/build/clarinet/validation-and-analysis.md +++ b/docs/build/clarinet/validation-and-analysis.md @@ -64,6 +64,8 @@ Focus validation during development on a single contract file: ```bash clarinet check contracts/nft.clar ``` + +Clarinet applies the `[linter]` settings from the `Clarinet.toml` given with `--manifest-path`, or from the nearest `Clarinet.toml` above the current directory. If there is no manifest, it uses the default lints. The project's `include` and `exclude` filters do not apply to a file you name explicitly. {% endstep %} {% step %} @@ -153,10 +155,19 @@ Add the `--output=` option to `clarinet check` to print diagnostics as J Clarinet includes a built-in linter as part of `clarinet check` that flags common mistakes, inefficiencies, and unused code in Clarity contracts. -Clarinet v3.24.1 has 23 lints in four groups: `unused` (dead code), `perf` (inefficient code), `safety` (code that might not do what you intended) and `style` (naming conventions). Each lint can be configured on its own or through its group. +Clarinet 4.0 has 24 lints in five groups: + +* `correctness`: code that is certainly wrong +* `unused`: dead code +* `perf`: inefficient code +* `safety`: code that might not do what you intended +* `style`: naming conventions + +You can configure each lint on its own or through its group. The `correctness` lints are the exception: you cannot configure them. | Identifier | Description | Lint group | | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `call_checker` | Detects calls that pass the wrong number of arguments to a function. | `correctness` | | `unused_binding` | Detects unused function parameters and `let` bindings. | `unused` | | `unused_const` | Detects unused `define-constant` declarations. | `unused` | | `unused_data_var` | Detects `define-data-var` declarations that are never used, never read, or never written (a value that is never written can be a constant). | `unused` | @@ -195,13 +206,25 @@ Individual lints can also be disabled for a specific line using Clarity's annota #### Configuration -By default, lints in the `unused`, `perf` and `safety` groups report at the `warning` level and `style` lints at the `notice` level. Change the level per lint or per group in `Clarinet.toml`. +By default, lints in the `unused`, `perf` and `safety` groups report at the `warning` level and `style` lints at the `notice` level. Change the level per lint or per group in the `[linter]` section of `Clarinet.toml`. + +Lints in the `correctness` group always report at the `error` level and reject the contract. `[linter]` settings do not change them: `call_checker = "warning"`, `correctness = false` and `all = false` have no effect on these lints. + +Each value is a level (`"error"`, `"warning"` or `"notice"`), `false` or `"off"` to turn the lint off, or `true` or `"on"` to turn it on at its default level. A lint that is already on keeps its current level, so `style = true` gives notices, not warnings. To get warnings, set `"warning"` explicitly. + +**Files to analyze** + +```toml +[linter] +include = ["contracts/*.clar"] +exclude = [] +``` **Individual lint configuration** ```toml -[repl.analysis.lints] -noop = true # Defaults to "warning" +[linter.lints] +noop = true # Turns the lint on at its default level ("warning") unused_const = "warning" unused_data_var = "error" unused_map = false @@ -210,12 +233,20 @@ unused_map = false **Lint group configuration** ```toml -[repl.analysis.lint_groups] -all = true # Sets the default level to "warning" for all lints -style = "notice" # Cosmetic lints only generate notices +[linter.groups] +all = true # Turns on all lints at their default levels +style = "warning" # Report style lints as warnings instead of notices unused = "error" # Enforces removal of unused code ``` +{% hint style="info" %} +**Migrating from Clarinet 3.x** + +Clarinet 4.0 moved the linter settings out of `[repl.analysis]`. Rename `[repl.analysis.lints]` to `[linter.lints]` and `[repl.analysis.lint_groups]` to `[linter.groups]`. Clarinet 4.0 also removed the check-checker pass, so remove `passes = ["check_checker"]` and the `[repl.analysis.check_checker]` settings. Clarinet warns when `Clarinet.toml` still contains `[repl.analysis]` or an `analysis` key in `[project]`. + +The check-checker annotations `#[filter(...)]`, `#[allow(unchecked_data)]` and `#[allow(unchecked_params)]` are still accepted, so existing contracts compile, but they have no effect. +{% endhint %} + ## Runtime analysis The Clarinet console has commands for inspecting behavior during execution. diff --git a/docs/build/get-started/create-a-token/fungible-tokens.md b/docs/build/get-started/create-a-token/fungible-tokens.md index afdb165c46..1f95e8b163 100644 --- a/docs/build/get-started/create-a-token/fungible-tokens.md +++ b/docs/build/get-started/create-a-token/fungible-tokens.md @@ -147,7 +147,6 @@ Any token contract that wants to conform to the SIP-010 fungible token standard (memo (optional (buff 34))) ) (begin - ;; #[filter(amount, recipient)] (asserts! (or (is-eq tx-sender sender) (is-eq contract-caller sender)) ERR_NOT_TOKEN_OWNER) (try! (ft-transfer? clarity-coin amount sender recipient)) (match memo to-print (print to-print) 0x) diff --git a/docs/build/get-started/create-a-token/non-fungible-tokens.md b/docs/build/get-started/create-a-token/non-fungible-tokens.md index ef064e622e..ffc5062d8c 100644 --- a/docs/build/get-started/create-a-token/non-fungible-tokens.md +++ b/docs/build/get-started/create-a-token/non-fungible-tokens.md @@ -109,7 +109,6 @@ Any NFT contract that wants to conform to the SIP-009 non-fungible token standar ;; SIP-009 function: Transfer NFT token to another owner. (define-public (transfer (token-id uint) (sender principal) (recipient principal)) (begin - ;; #[filter(sender)] (asserts! (is-eq tx-sender sender) ERR_NOT_TOKEN_OWNER) (nft-transfer? Your-NFT-Name token-id sender recipient) ) diff --git a/docs/build/get-started/create-a-token/semi-fungible-tokens.md b/docs/build/get-started/create-a-token/semi-fungible-tokens.md index 9d5673471c..c7e8bd6d47 100644 --- a/docs/build/get-started/create-a-token/semi-fungible-tokens.md +++ b/docs/build/get-started/create-a-token/semi-fungible-tokens.md @@ -132,7 +132,6 @@ Any SFT contract that wants to conform to the SIP-013 semi-fungible token standa (ok none) ) -;; #[allow(unchecked_params)] (define-public (transfer (token-id uint) (amount uint) (sender principal) (recipient principal)) (let ( diff --git a/docs/build/more-guides/price-oracles/pyth.md b/docs/build/more-guides/price-oracles/pyth.md index d4169d3f4b..c4de378592 100644 --- a/docs/build/more-guides/price-oracles/pyth.md +++ b/docs/build/more-guides/price-oracles/pyth.md @@ -133,7 +133,6 @@ Below are how the contracts and mainfest files are setup in this example Clarine (recipient principal) ) (begin - ;; #[filter(sender)] (asserts! (is-eq tx-sender sender) ERR_NOT_TOKEN_OWNER) (nft-transfer? benjamin token-id sender recipient) ) @@ -198,17 +197,6 @@ path = 'contracts/nft.clar' clarity_version = 4 epoch = 'latest' -[repl.analysis] -passes = ['check_checker'] - -[repl.analysis.lints] - -[repl.analysis.check_checker] -strict = false -trusted_sender = false -trusted_caller = false -callee_filter = false - [repl.remote_data] enabled = true api_url = 'https://api.hiro.so' diff --git a/docs/cookbook/clarity/example-contracts/fungible-token.md b/docs/cookbook/clarity/example-contracts/fungible-token.md index 96d877dfa9..c6edfbdf53 100644 --- a/docs/cookbook/clarity/example-contracts/fungible-token.md +++ b/docs/cookbook/clarity/example-contracts/fungible-token.md @@ -70,7 +70,6 @@ (memo (optional (buff 34))) ) (begin - ;; #[filter(amount, recipient)] (asserts! (or (is-eq tx-sender sender) (is-eq contract-caller sender)) ERR_NOT_TOKEN_OWNER ) @@ -110,4 +109,3 @@ A complete implementation of a SIP-010 compliant fungible token called "Clarity * Preventing unauthorized transfers with sender validation * Returning standardized responses that comply with trait requirements * Using constants for immutable contract configuration -* Filter annotations for security analysis (`#[filter]`) diff --git a/docs/cookbook/clarity/example-contracts/non-fungible-token.md b/docs/cookbook/clarity/example-contracts/non-fungible-token.md index 9c4d7d2eb4..3dd280a8e8 100644 --- a/docs/cookbook/clarity/example-contracts/non-fungible-token.md +++ b/docs/cookbook/clarity/example-contracts/non-fungible-token.md @@ -43,7 +43,6 @@ (recipient principal) ) (begin - ;; #[filter(sender)] (asserts! (is-eq tx-sender sender) ERR_NOT_TOKEN_OWNER) (nft-transfer? Your-NFT-Name token-id sender recipient) ) @@ -96,4 +95,3 @@ A complete implementation of a SIP-009 compliant non-fungible token (NFT) contra * Metadata URI management for off-chain content * Using data variables to track minting state * Returning standardized responses for trait compliance -* Filter annotations for security (`#[filter(sender)]`) diff --git a/docs/reference/clarinet-js-sdk/browser-sdk-reference.md b/docs/reference/clarinet-js-sdk/browser-sdk-reference.md index d65ed456eb..ff49118a01 100644 --- a/docs/reference/clarinet-js-sdk/browser-sdk-reference.md +++ b/docs/reference/clarinet-js-sdk/browser-sdk-reference.md @@ -24,8 +24,8 @@ const simnet = await initSimnet(); await simnet.initEmptySession(); // Execute Clarity code directly -const result = simnet.runSnippet("(+ 1 2)"); -console.log(result); // 3 +const { result } = simnet.execute("(+ 1 2)"); +console.log(result); // Cl.int(3) ``` {% endcode %} diff --git a/docs/reference/clarinet-js-sdk/sdk-reference.md b/docs/reference/clarinet-js-sdk/sdk-reference.md index e72ff3c653..3b7a66509b 100644 --- a/docs/reference/clarinet-js-sdk/sdk-reference.md +++ b/docs/reference/clarinet-js-sdk/sdk-reference.md @@ -134,7 +134,7 @@ const count = simnet.getDataVar('counter', 'count'); ### getMapEntry -`getMapEntry` retrieves a value from a contract map by its key. +`getMapEntry` retrieves a value from a contract map by its key. Like the node's map-entry endpoint, it returns `(some value)` for an existing entry and `none` otherwise, including when the map or contract does not exist. Usage: @@ -150,7 +150,7 @@ const hasParticipated = simnet.getMapEntry( "Participants", Cl.standardPrincipal(wallet) ); -// Returns: { type: 10, value: { type: 3 } } +// Returns: Cl.some(Cl.bool(true)), or Cl.none() if there is no entry ``` | Parameter | Type | Description | @@ -222,6 +222,8 @@ const result = simnet.callPublicFn( // Mines block and returns result ``` +A returned `(err ...)` response does not throw. `callPublicFn`, `callPrivateFn`, `deployContract` and `transferSTX` throw when simnet rejects the transaction. They also throw when simnet includes the transaction but it fails with a runtime error or a post-condition abort. An included failure still consumes the sender's nonce and mines its block. To tell included failures from rejections, use [`mineBlock`](#mineblock). + ### callPrivateFn `callPrivateFn` calls private functions (testing only) and mines a block. @@ -248,7 +250,9 @@ const result = simnet.callPrivateFn( ## Transfer STX -`transferSTX` transfers STX between addresses and mines a block. +`transferSTX` transfers STX between addresses and mines a block. Like `callPublicFn`, it advances one block before executing, even when simnet rejects the transfer. + +Self-transfers, zero-amount transfers, and transfers that exceed the sender's unlocked balance throw without consuming a nonce. Inside `mineBlock`, these transfers return a `rejected` outcome instead. Usage: @@ -277,7 +281,9 @@ const transfer = simnet.transferSTX( ## Deploy contracts -`deployContract` deploys a new contract to simnet and mines a block. +`deployContract` deploys a new contract to simnet and mines a block. A successful deployment returns `(ok true)`, whatever the contract's final top-level expression evaluates to. To check what the contract did when it initialized, use read-only calls, `getDataVar` and events. To evaluate a snippet and get its value, use [`execute`](#execute). + +An unsupported `clarityVersion` throws instead of falling back to the default version. Simnet rejects a duplicate contract name without consuming a nonce. Usage: @@ -312,12 +318,12 @@ const contract = simnet.deployContract( ### mineBlock -`mineBlock` mines a block with multiple transactions. +`mineBlock` mines a block with multiple transactions. It advances exactly one block, and all entries execute at that height. It first mines any signed transactions queued with [`broadcastTransaction`](#broadcasttransaction), then the entries in `txs`, in input order. Usage: ``` -mineBlock(txs: Tx[]): ParsedTransactionResult[] +mineBlock(txs?: Tx[]): BlockTransactionResult[] ``` ```ts @@ -328,8 +334,32 @@ const block = simnet.mineBlock([ tx.callPublicFn("counter", "increment", [], simnet.deployer), tx.transferSTX(19000000, wallet, simnet.deployer), ]); + +for (const outcome of block) { + if (outcome.status === "success") { + console.log(outcome.execution.result); + } else { + console.log(outcome.status, outcome.error); + } +} ``` +Each result has one of three statuses. A failed entry does not stop the batch. Later entries still execute. + +| Status | Meaning | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `success` | `execution` holds the parsed result, events and costs. | +| `included_failure` | Simnet included the transaction, but it failed. `error` describes the failure and `kind` is `abort_by_response`, `abort_by_post_condition`, `runtime_error` or `analysis_error`. `execution` is always present. The transaction consumes the nonce, and its payload writes roll back. | +| `rejected` | Simnet did not include the transaction. `error` gives the reason. Simnet commits no writes and no nonce changes. | + +Results for signed transactions also have a `transaction` property with the `txid`, `fee` and `nonce`. + +{% hint style="warning" %} +**Migrating from Clarinet SDK 3.x** + +Before 4.0, each entry returned the execution result directly, and most failures threw and stopped the batch. Replace direct access such as `block[0].result` with a status check and `block[0].execution.result`. +{% endhint %} + ### mineEmptyBlock `mineEmptyBlock` mines an empty block and increases block height. @@ -362,23 +392,68 @@ const newHeight = simnet.blockHeight; // Returns: 6 ``` +## Signed transactions + +### broadcastTransaction + +`broadcastTransaction` submits a signed, serialized Stacks transaction to the simnet's local mempool. It runs the node's mempool admission checks but does not execute the transaction, charge fees, consume nonces, or advance the chain. Call `mineBlock()` to mine queued transactions. + +Usage: + +``` +broadcastTransaction(rawHex: string): BroadcastTransactionResult +``` + +On acceptance it returns `{ txid }`. On rejection it returns `{ error, reason, reason_data?, txid? }`. + +```ts +import { makeSTXTokenTransfer } from '@stacks/transactions'; + +const transaction = await makeSTXTokenTransfer({ + network: "testnet", + senderKey, + recipient, + amount: 100n, + fee: 1000n, + nonce: simnet.getAccountNonce(sender), +}); + +const admission = simnet.broadcastTransaction(transaction.serialize()); +if ("error" in admission) { + throw new Error(admission.reason); +} + +const [outcome] = simnet.mineBlock(); +if (outcome.status === "success") { + console.log(outcome.transaction?.txid, outcome.execution.result); +} +``` + +Admission checks signatures, network and epoch rules, nonces, unlocked funds, transfer validity, duplicate deployments, and public-function access and argument types. Admission rejects transactions that call read-only, private or missing functions. The minimum fee is 1 micro-STX per serialized byte. Set `fee` and `nonce` explicitly so Stacks.js does not request them from a remote node. + +Admission does not guarantee inclusion, because `mineBlock` checks the current state again. A transaction with a future nonce stays in the queue until `mineBlock` mines the transactions with the earlier nonces. To execute a signed transaction directly, without mempool admission, pass it to `mineBlock` as `{ broadcastTx: rawHex }`. + ## Utility methods -### runSnippet +### execute -`runSnippet` executes arbitrary Clarity code without deploying. +`execute` evaluates arbitrary Clarity code without deploying a contract. The `result` property holds the parsed Clarity value of the expression. If evaluation fails, `execute` throws, so wrap it in `try`/`catch` when you expect a failure. Usage: ``` -runSnippet(snippet: string): string | ClarityValue +execute(snippet: string): ParsedTransactionResult ``` ```ts -const result = simnet.runSnippet('(stx-account tx-sender)'); -// Returns account balance information +const { result } = simnet.execute('(stx-account tx-sender)'); +// Returns a tuple with the account's locked, unlocked and unlock-height values ``` +{% hint style="warning" %} +Clarinet SDK 4.0 removed `simnet.runSnippet(code)`. Replace it with `simnet.execute(code).result`. Unlike `runSnippet`, `execute` throws on failure instead of returning an `"error:"` string. +{% endhint %} + ### getContractsInterfaces `getContractsInterfaces` returns contract interfaces with function signatures and storage. diff --git a/docs/reference/clarinet/cli-reference.md b/docs/reference/clarinet/cli-reference.md index 60850f5aa7..e08706e62b 100644 --- a/docs/reference/clarinet/cli-reference.md +++ b/docs/reference/clarinet/cli-reference.md @@ -90,7 +90,7 @@ Updated Clarinet.toml ### clarinet check -`clarinet check` checks contracts syntax and performs type checking. +`clarinet check` checks contract syntax and types, and runs the linter. When you give it a single file, it applies the `[linter]` settings from `--manifest-path` or from the nearest `Clarinet.toml` above the current directory. If there is no manifest, it uses the default lints. See [Validation and Analysis](../../build/clarinet/validation-and-analysis.md#linter-analysis) for lint configuration. Usage @@ -173,12 +173,11 @@ The Clarinet console offers a variety of commands for contract interaction: ### clarinet devnet -`clarinet devnet` is a subcommand for working with Devnet. It has two subcommands: +`clarinet devnet` is a subcommand for working with Devnet. It has one subcommand: | Command | Description | | --------- | ---------------------------------------------------------------- | | `start` | Start a local Devnet network for interacting with your contracts | -| `package` | Generate package of all required devnet artifacts | Usage with `start` @@ -197,24 +196,6 @@ clarinet devnet start | `--deployment-plan-path ` | `-p` | If specified, use this deployment file | | `--use-on-disk-deployment-plan` | `-d` | Use on disk deployment plan (prevent updates computing) | | `--use-computed-deployment-plan` | `-c` | Use computed deployment plan (will overwrite on disk version if any update) | -| `--package ` | | Path to Package.json produced by 'clarinet devnet package' | - -Usage with `package` - -``` -clarinet devnet package [OPTIONS] -``` - -```bash -$ clarinet devnet package --name my-devnet -Packaging devnet artifacts... -Created file my-devnet.json -``` - -| Option | Short | Description | -| ------------------------ | ----- | --------------------- | -| `--name ` | `-n` | Output json file name | -| `--manifest-path ` | `-m` | Path to Clarinet.toml | ## Manage your deployments