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:
| 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
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)
}
}