Validation and Analysis

Clarinet provides powerful tools for validating, analyzing, linting, and debugging your smart contracts. From static type checking to real-time cost analysis, you can ensure your contracts are correct and efficient before deployment.

Contract validation spans static analysis, runtime debugging, and cost optimization. Each discipline helps you gain confidence in contract behavior.

Understanding contract validation

Static analysis vs. runtime debugging

Static analysis
Runtime debugging

Catches issues before deployment

Reveals behavior during execution

Flags type mismatches and syntax errors

Shows actual execution costs

Ensures trait compliance

Exposes state changes and side effects

Detects undefined variables

Highlights transaction flow

Validates function signatures

Surfaces performance bottlenecks

Static analysis

Run comprehensive validation with clarinet check:

clarinet check

Successful output resembles:

✔ 3 contracts checked

When validation fails, Clarinet provides detailed diagnostics:

✖ 1 error detected

Error in contracts/token.clar:15:10
  |
15|  (ok (+ balance amount))
  |         ^^^^^^^
  |
  = Type error: expected uint, found (response uint uint)
1

Run basic checks

Use clarinet check to validate your contracts and catch type/syntax errors before deployment.

2

Check a specific contract

Focus validation during development on a single contract file:

3

Integrate into CI

Automate validation in continuous integration pipelines. Example GitHub Actions workflow:

Validation scope

Clarinet validates multiple aspects of your contracts:

Validation type
What it checks

Type safety

Function parameters, return values, variable types

Trait compliance

Implementation matches trait definitions

Response consistency

ok/err branches return the same types

Variable scope

Variables defined before use

Function visibility

Proper use of public, private, and read-only

Linter analysis

Clarinet includes a built-in linter as part of clarinet check to help identify common mistakes, inefficiencies, and unused code in Clarity contracts. Linters play an important role in improving code quality by surfacing issues early in development and encouraging clearer, more maintainable contracts.

Clarinet currently provides a set of lints focused on dead code analysis. These lints detect declarations and expressions that have no effect on contract execution and can be configured individually.

The following lints are available:

Identifier

Description

unused_const

Detects unused define-constant declarations.

unused_data_var

Detects define-data-var declarations that are never written.

unused_map

Detects define-map declarations that are never accessed.

unused_private_fn

Detects private functions that are never called.

unused_token

Detects fungible and non-fungible tokens that are never minted.

unused_trait

Detects traits imported with use-trait that are never used as parameter types.

unused_binding

Detects unused function parameters and let bindings.

In addition, the noop lint detects expressions that have no effect, such as: (is-eq 1)

Bypassing the Linter

In some cases, code may appear unused but may be used in a way the linter can't see. Examples include private functions used only in tests, or bindings whose evaluation has side effects.

Clarinet follows a convention similar to Rust: identifiers with a trailing _ might generate other kinds of warnings for them but the linter will allow them to be unused.

circle-info

Note: prefixing identifiers with _ is not currently supported, only suffixing is.

Individual lints can also be disabled for a specific line using Clarity’s annotation syntax:

Configuration

All non-style/non-cosmetic lints are enabled by default at the warning level and can be customized in Clarinet.toml.

Individual lint configuration

Lint group configuration

Runtime analysis

The Clarinet console offers runtime tools that help you inspect behavior during execution.

Cost analysis with ::toggle_costs

Enable automatic cost display after every expression:

Execution tracing with ::trace

Trace function calls to understand execution flow:

Interactive debugging with ::debug

Set breakpoints and step through execution:

Common navigation commands:

circle-info
  • step or s – step into subexpressions

  • finish or f – complete the current expression

  • next or n – step over subexpressions

  • continue or c – resume execution

Using ::get_costs for targeted analysis

Spotting costly operations with ::trace

Review the trace for loops with high iteration counts, nested map/filter operations, repeated contract calls, and large data structure manipulations.

Debugging workflows

Master interactive debugging to identify issues quickly:

Analyzing failed transactions with ::trace

Using ::encode and ::decode for inspection

Testing time-dependent logic

Last updated

Was this helpful?