How Do Predicates Differ from Contracts in Sway: A Complete Technical Guide

Predicates in Sway are stateless, pure functions that return a boolean to unlock UTXOs, while contracts are stateful components that expose an ABI and can persist data to storage.

Understanding how predicates differ from contracts in Sway is essential for building secure applications on the Fuel VM. While both are compiled from Sway source code, they serve fundamentally different purposes in the transaction lifecycle. This guide examines the architectural distinctions, compiler enforcement mechanisms, and practical implementation patterns defined in the FuelLabs/sway repository.

Core Architectural Differences Between Predicates and Contracts in Sway

Program Purpose and Runtime Behavior

A predicate serves as a spending condition for a UTXO. It evaluates to a Boolean value, and if the result is true, the funds owned by the predicate's address become spendable. Predicates execute in a read-only context during transaction validation.

A contract acts as a stateful on-chain component. It defines an Application Binary Interface (ABI), can store persistent data, receives external calls, and emits receipts. Contracts maintain state across multiple transactions and support complex interaction patterns.

Entry Points and Function Signatures

The entry point requirements differ strictly between the two program types.

In sway-core/src/language/ty/program.rs, the compiler defines TyProgramKind::Predicate to require a single main() function that must return bool. The compiler enforces this return type check during type validation.

pub enum TyProgramKind {
    Contract { entry_function: Option<...>, abi_entries: Vec<...> },
    Library { name: String },
    Predicate { entry_function: DeclId<TyFunctionDecl>, main_function: DeclId<TyFunctionDecl> },
    Script { entry_function: DeclId<TyFunctionDecl>, main_function: DeclId<TyFunctionDecl> },
}

Contracts, represented by TyProgramKind::Contract, use an abi block to define external functions. While contracts may include a main() function (called the loader), the public interface is generated from the ABI, not the entry function signature.

Key Technical Constraints: Storage, Purity, and State

Storage Restrictions in Predicates

Predicates are explicitly prohibited from declaring storage blocks. In the compiler's validation logic, the validate_root function checks for storage_decl and rejects predicates that attempt to define storage (lines 49-58 in the validation module).

Contracts, conversely, can declare persistent storage using the storage keyword. The compiler type-checks storage declarations and compiles them into the contract's storage layout, accessible across multiple transactions.

Function Purity Requirements

All functions within a predicate must be pure. The compiler calls disallow_impure_functions for non-contract programs (lines 43-48), rejecting any function that attempts to perform impure operations like storage reads or writes.

Contracts permit impure functions because they must interact with storage. Functions that read from or write to the contract's state are inherently impure, and the compiler allows this behavior within contract contexts.

ABI Generation and Deployment Models

Bytecode Blobs vs. ABI Files

Predicates do not generate an ABI file. Instead, the compiler outputs the bytecode as a blob, identified by its bytecode root (which serves as the predicate address). External systems interact with predicates by providing input data that satisfies the predicate's logic.

Contracts generate a complete ABI file (*.abi.json) that defines the contract's interface, including method signatures, input types, and output types. This ABI is used by callers to construct valid contract calls.

Deployment and Runtime Restrictions

When deploying a predicate, forc deploy uploads the bytecode blob and generates a loader. The predicate address is derived from the bytecode hash.

At runtime, predicates cannot use contract VM instructions such as contract::balance_of. The Fuel VM explicitly restricts predicates from accessing contract-specific opcodes. If a predicate attempts to revert or execute an impure opcode, the evaluation automatically yields false rather than generating a transaction receipt.

Contracts have full access to the Fuel VM instruction set, including contract-specific opcodes for balance checks, contract-to-contract calls, and receipt generation.

Code Examples: Predicate vs. Contract Implementation

Example 1: Simple Predicate with Boolean Return

This predicate always evaluates to true, allowing anyone to spend the UTXO locked to its address:

// src/main.sw
predicate;

fn main() -> bool {
    // Predicate logic – any condition that evaluates to true unlocks the UTXO.
    true
}

The compiled output is a bytecode blob identified by its root hash. As documented in docs/reference/src/documentation/language/program-types/predicate.md, this bytecode serves as the predicate address.

Example 2: Parameterized Predicate with Input Data

Predicates can accept runtime arguments that influence the Boolean decision. This example checks if the spent coin amount matches an expected value:

// src/main.sw
predicate;

fn main(expected: u64) -> bool {
    // `msg_amount` is the amount of coins being spent.
    let msg_amount = msg::coin_amount();
    msg_amount == expected
}

The expected parameter is provided as predicate data when spending the UTXO. According to the predicate documentation, this allows flexible spending conditions without changing the bytecode.

Example 3: Stateful Contract with Storage and ABI

Contracts define persistent storage and expose methods through an ABI:

// src/main.sw
contract;

abi MyContract {
    // Public function exposed via the ABI.
    fn get_balance() -> u64;
    fn set_balance(new_balance: u64);
}

storage {
    balance: u64,
}

impl MyContract for Contract {
    fn get_balance() -> u64 {
        self.balance
    }

    fn set_balance(new_balance: u64) {
        self.balance = new_balance;
    }
}

This contract generates an ABI file (MyContract.abi.json) and can store data persistently across transactions. The implementation references the Contract program type defined in sway-core/src/language/ty/program.rs.

Compiler Implementation and Validation

TyProgramKind Enum Distinction

The Sway compiler distinguishes between predicates and contracts through the TyProgramKind enum in sway-core/src/language/ty/program.rs:

pub enum TyProgramKind {
    Contract { entry_function: Option<...>, abi_entries: Vec<...> },
    Library { name: String },
    Predicate { entry_function: DeclId<TyFunctionDecl>, main_function: DeclId<TyFunctionDecl> },
    Script { entry_function: DeclId<TyFunctionDecl>, main_function: DeclId<TyFunctionDecl> },
}

The Predicate variant stores references to the main function that must return a bool, while the Contract variant tracks ABI entries and an optional loader function.

Validation Logic in validate_root

During type checking, the validate_root function branches on TreeType to enforce program-specific constraints.

For predicates, the compiler:

  1. Ensures exactly one main function exists
  2. Confirms main returns a Boolean via is_bool() checks (lines 52-57)
  3. Calls check_no_ref_main to disallow mutable reference parameters
  4. Prohibits impure functions via disallow_impure_functions (lines 43-48)

For contracts, the compiler:

  1. Allows storage declarations (StorageDecl)
  2. Permits impure functions (no call to disallow_impure_functions)
  3. Collects ABI methods from impl blocks that implement an abi trait

These divergent validation paths guarantee that predicates remain pure, stateless, and boolean-valued, while contracts retain stateful, mutable, and ABI-exposed capabilities.

Summary

  • Predicates are pure, stateless spending conditions that return a boolean to unlock UTXOs, while contracts are stateful on-chain components with persistent storage and exposed ABIs.
  • The Sway compiler enforces these distinctions through the TyProgramKind enum in sway-core/src/language/ty/program.rs, specifically validating that predicates have a single main() returning bool and disallowing storage and impure functions.
  • Predicates compile to bytecode blobs identified by their root hash without generating an ABI, whereas contracts produce *.abi.json files and support full Fuel VM instruction sets including contract-specific opcodes.
  • At runtime, predicates execute in a restricted environment where reverts or impure operations automatically yield false, while contracts can emit receipts and maintain state across transactions.

Frequently Asked Questions

Can a predicate access storage in Sway?

No, predicates cannot declare or access storage in Sway. The compiler explicitly prohibits storage declarations for predicates during the validate_root phase, enforcing the storage_decl check to ensure predicates remain stateless. This restriction ensures predicates evaluate deterministically based only on their inputs and the transaction context, without relying on persistent on-chain state.

Why must predicates return a boolean in Sway?

Predicates must return a boolean because they function as spending conditions for UTXOs. The compiler enforces this requirement in TyProgramKind::Predicate by checking that the main() function returns a bool type. If the predicate evaluates to true, the UTXO is unlocked and can be spent; if false, the transaction is invalid. This binary evaluation model aligns with the Fuel VM's predicate verification system.

How does the Fuel VM treat predicates differently from contracts at runtime?

The Fuel VM executes predicates in a restricted, read-only environment during transaction validation, while contracts run in a full execution context. Predicates cannot use contract-specific VM instructions like contract::balance_of, and any revert or impure operation automatically causes the predicate to evaluate to false rather than generating a receipt. Contracts have full access to the Fuel VM instruction set, can emit receipts, read and write storage, and maintain persistent state across multiple transactions.

Can I call a contract from within a predicate?

No, you cannot call a contract from within a predicate. The Fuel VM explicitly restricts predicates from accessing contract VM instructions, which includes contract calls. Additionally, the Sway compiler enforces purity requirements for predicates through disallow_impure_functions, preventing any operations that could cause side effects or external calls. Predicates must evaluate based solely on their input parameters and the immediate transaction context without interacting with external contracts or storage.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →