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(params) -> ReturnType = {
    body_expression
}

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

Basic Examples

enum Status { Active, Paused }

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

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

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

    m_owner: address {}
    m_balance: U256 {}
    m_status: Status {}
}

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.

Give each boolean macro its own where clause, as above. Combining several macro calls in one boolean expression (@is_owner() && @is_funded(amount)) does not yet compile on EVM.

Parameters

Macros can accept parameters:

macro has_allowance(owner: address, spender: address, amount: U256) -> bool = {
    m_allowances[owner][spender] >= 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[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[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, as net_amount does with @fee_for above.

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

Membership maps use HashMap<address, bool>: a missing key reads as false.

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

    macro has_enough_confirmations(tx_id: u64) -> bool = {
        m_confirmations[tx_id] >= m_required
    }

    macro is_pending(tx_id: u64) -> bool = {
        tx_id < m_next_id && m_executed[tx_id] == false
    }

    routes {
        #[factory_only]
        init setup(first_signer: address, required: u64) => []

        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_to[tx_id] with { value: m_amount[tx_id] }
        ]
    }

    m_signers: HashMap<address, bool> {
        in setup(first_signer, _) => m_signers.insert(first_signer, true)
    }

    m_required: u64 {
        in setup(_, required) => required
    }

    m_next_id: u64 {
        in submit(_, _) => m_next_id + 1
    }

    m_to: HashMap<u64, address> {
        in submit(to, _) => m_to.insert(m_next_id, to)
    }

    m_amount: HashMap<u64, U256> {
        in submit(_, amount) => m_amount.insert(m_next_id, amount)
    }

    m_confirmations: HashMap<u64, u64> {
        in confirm(tx_id) => m_confirmations.insert(tx_id, m_confirmations[tx_id] + 1)
    }

    m_executed: HashMap<u64, bool> {
        in execute(tx_id) => m_executed.insert(tx_id, true)
    }
}