Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Macros

Macros are entity-scoped helper expressions that can access member state. They provide reusable logic fragments for conditions, computations, and assertions that need to reference the entity’s persistent state.

Syntax

macro name() -> ReturnType = {
    body_expression
}

Macros are declared inside an entity body, alongside routes { … } and member fields — not inside route action lists.

Basic Examples

entity Wallet {
    macro is_owner() -> bool = {
        msg::sender == m_owner
    }

    macro is_funded(amount: U256) -> bool = {
        m_balance >= amount
    }

    macro require_active() -> bool = {
        m_status == Status::Active
    }
}

Invocation

Macros are called with the @ prefix:

routes {
    withdraw(amount: U256)
        where @is_owner() : throw 100
        && @is_funded(amount) : throw 101
    => [
        ~> msg::sender with { value: amount }
    ]
}

The @ prefix distinguishes macro calls from pure function calls and makes it clear that the expression accesses entity state.

Parameters

Macros can accept parameters:

macro has_allowance(owner: address, spender: address, amount: U256) -> bool = {
    m_allowances.get(owner)
        .unwrap_or({})
        .get(spender)
        .unwrap_or(0) >= amount
}

State Access

Unlike pure functions, macros can read member values. This is their primary advantage:

macro available_balance() -> U256 = {
    m_balance - m_locked
}

macro is_whitelisted(account: address) -> bool = {
    m_whitelist.exists(account)
}

macro current_price() -> U256 = {
    if m_supply > 0 {
        m_reserve * 1_000_000 / m_supply
    } else {
        0
    }
}

Context Access

Macros can also access msg:: and sys:: context:

macro is_owner() -> bool = {
    msg::sender == m_owner
}

macro is_expired() -> bool = {
    sys::now > m_deadline
}

macro has_sufficient_value(required: U256) -> bool = {
    msg::value >= required
}

Common Patterns

Access Control

macro is_admin() -> bool = {
    msg::sender == m_admin
}

macro is_operator(account: address) -> bool = {
    m_operators.exists(account)
}

State Validation

macro is_active() -> bool = {
    m_status == Status::Active && sys::now < m_deadline
}

macro has_quorum(proposal_id: u64) -> bool = {
    m_vote_counts[proposal_id] >= m_quorum
}

Computed Values

macro fee_for(amount: U256) -> U256 = {
    amount * m_fee_rate / 10000
}

macro net_amount(amount: U256) -> U256 = {
    amount - @fee_for(amount)
}

Macro Calling Macro

Macros can call other macros in the same entity:

macro can_execute(amount: U256) -> bool = {
    @is_owner() && @is_funded(amount) && @is_active()
}

Where to Use Macros

Macros can appear in:

ContextExample
Where clauseswhere @is_owner() : throw 100
Route actionslet fee = @fee_for(amount);
Member transformsin deposit(amount) => m_balance + @fee_for(amount)
Conditional actionsif @is_active() => [...]

Macros vs. Pure Functions

FeatureMacroPure Function
State accessYes (reads members)No
Context accessYes (msg::, sys::)No
ScopeEnclosing entity onlyAll entities in the file
Call syntax@name(args)name(args)
DeclarationInside entityTop-level

Use macros when the helper needs entity state. Use pure functions when the computation depends only on its arguments.

Complete Example

entity Multisig {
    macro is_signer() -> bool = {
        m_signers.exists(msg::sender)
    }

    macro has_enough_confirmations(tx_id: u64) -> bool = {
        m_confirmation_count.get(tx_id).unwrap_or(0) >= m_required
    }

    macro is_pending(tx_id: u64) -> bool = {
        m_executed.get(tx_id).unwrap_or(false) == false
    }

    routes {
        submit(to: address, amount: U256)
            where @is_signer() : throw 200
        => []

        confirm(tx_id: u64)
            where @is_signer() : throw 200
            && @is_pending(tx_id) : throw 201
        => []

        execute(tx_id: u64)
            where @is_signer() : throw 200
            && @is_pending(tx_id) : throw 201
            && @has_enough_confirmations(tx_id) : throw 202
        => [
            ~> m_transactions[tx_id].to with {
                value: m_transactions[tx_id].amount
            }
        ]
    }
}