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:
| Context | Example |
|---|---|
| Where clauses | where @is_owner() : throw 100 |
| Route actions | let fee = @fee_for(amount); |
| Member transforms | in deposit(amount) => m_balance + @fee_for(amount) |
| Conditional actions | if @is_active() => [...] |
Macros vs. Pure Functions
| Feature | Macro | Pure Function |
|---|---|---|
| State access | Yes (reads members) | No |
| Context access | Yes (msg::, sys::) | No |
| Scope | Enclosing entity only | All entities in the file |
| Call syntax | @name(args) | name(args) |
| Declaration | Inside entity | Top-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
}
]
}
}