This document details the roadmap for developing a functional MVP of soroban-cost-linter, designed to statically catch input-independent, structurally expensive patterns in Soroban smart contracts.
To avoid the fragility of regex or basic parsing, we will leverage Dylint to build and run dynamic library lints by hooking directly into the Rust compiler (rustc).
graph TD
Cargo["cargo cost-lint"] --> Dylint[Dylint CLI/Library]
Dylint --> LintLib[soroban_cost_lints dynamic lib]
LintLib --> Rustc[rustc compiler internals]
Rustc --> AST[AST & HIR analysis]
Rustc --> Types[Type Checking]
- Type Safety / Resolution: Using
synalone only allows syntax checking. It cannot definitively distinguish if astoragevariable is of typesoroban_sdk::storage::Storage. Dylint gives us full access to the High-Level Intermediate Representation (HIR) and type checker. - Suppression System: Inherits standard
#[allow(...)]attributes automatically.
For the MVP, we will focus on one highly expensive, structurally clear anti-pattern as the first implemented lint.
- Target Pattern: Calling
env.storage().instance().set(...)(orget,has, etc.) inside afor,while, orloopblock. - Cost Impact: Storage operations are the most expensive resource in Soroban. Calling them in loops wastes CPU instructions and ledger write/read throughput.
- Lint Suggestion: Suggest pulling the read/write out of the loop by accumulating mutations in memory (e.g., using a local
MaporVec) and executing a single storage operation after the loop.
redundant_env_clone: Flags calling.clone()on theEnvobject since it is designed to be passed cheaply by reference or copy value.unnecessary_host_function_call: Flags repeated calls to host functions (e.g.,env.ledger().sequence()) inside loop bodies.
Ensuring developer trust and minimizing friction is crucial for adoption.
- Rust Attributes: Developers can suppress warnings directly in code:
#[allow(soroban_storage_in_loop)] fn allowed_storage_in_loop(env: Env) { for item in items { // Deliberate storage loop } }
- Config file (
budget.toml): Allows workspace-level severity adjustments:[lints] soroban_storage_in_loop = "deny"
Lints will be categorized by confidence and impact:
- High Impact & High Confidence (e.g.,
storage_in_loop): Emits a Deny or Severe Warning by default. - Medium Impact or Context-Dependent (e.g., host calls in tiny loops): Emits a Warn by default.
The linter must fit seamlessly into existing Tollcraft workflows and CI/CD pipelines.
A custom Cargo subcommand wrapper will be provided to orchestrate Dylint under the hood.
cargo cost-lint- Exit Codes: Returns a non-zero exit code if a
denylevel lint is triggered, allowing it to block PRs. - Standardized Outputs: Emits warnings/errors in the standard compiler format for IDE integration, as well as a JSON format to pair with GitHub Actions.