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

State Transforms

State transforms define how member values change when routes fire. Each transform is an in clause inside a member declaration that specifies the new value as a function of the current state and route parameters.

Syntax

m_name: Type {
    in route_name(param1, param2) => new_value_expression
}

The expression after => is evaluated to produce the member’s new value. The current value of the member is accessible by its name (m_name) within the expression. When the new value needs let bindings, if, match, or a record literal, wrap it in a block: in r(x) => { ... }.

Simple Transforms

The most common transforms compute the new value from the old value and route parameters:

m_count: u64 {
    in increment() => m_count + 1
    in decrement() => m_count - 1
    in reset() => 0
}
m_balance: U256 {
    in deposit() => m_balance + msg::value
    in withdraw(amount) => m_balance - amount
}

Parameter Matching

Transform parameters bind the route parameters by position. The names can differ, but list one pattern per route parameter, in order; a transform that binds a different number of parameters than the route declares is rejected. (in r() with no bindings at all is also allowed, when the transform needs none of the parameters.)

routes {
    transfer(to: address, amount: U256) => []
}

m_balances: HashMap<address, U256> {
    // (to, amount) matches (to: address, amount: U256)
    in transfer(to, amount) => {
        let sender = msg::sender;
        m_balances
            .insert(sender, m_balances[sender] - amount)
            .insert(to, m_balances[to] + amount)
    }
}

Ignoring Parameters

Use _ to ignore route parameters that the member does not need:

m_owner: address {
    in setup(_, _, owner) => owner  // only needs the third parameter
}

m_total_supply: U256 {
    in mint(_, amount) => m_total_supply + amount  // ignores 'to'
}

Block Transforms

When a transform requires multiple steps, use a block expression:

m_balances: HashMap<address, U256> {
    in transfer(to, amount) => {
        let sender = msg::sender;
        let sender_bal = m_balances[sender];
        let to_bal = m_balances[to];
        m_balances
            .insert(sender, sender_bal - amount)
            .insert(to, to_bal + amount)
    }
}

Conditional Transforms

Transforms can use if/else and match inside a block:

m_high_score: u64 {
    in submit(score) => {
        if score > m_high_score { score } else { m_high_score }
    }
}
m_status: Status {
    in toggle() => {
        match m_status {
            Status::Active => Status::Paused,
            Status::Paused => Status::Active,
            _ => m_status
        }
    }
}

Multiple Routes

A member can have transforms for multiple routes. Each in clause specifies how the member changes for one route:

m_balance: U256 {
    in setup(_, _, _) => 0
    in deposit() => m_balance + msg::value
    in withdraw(amount) => m_balance - amount
    in transfer_out(_, amount) => m_balance - amount
    in receive() => m_balance + msg::value
}

If a route is not listed, the member’s value is unchanged when that route fires.

Accessing Other Members

A transform expression can read other members’ values:

m_total_supply: U256 {
    in mint(_, amount) => m_total_supply + amount
}

m_max_reached: bool {
    in mint(_, amount) => (m_total_supply + amount) >= m_cap
}

When referencing other members in a transform, you get their pre-transform values (the values before the current route). To access post-transform values of other members, use temporal references (^m_name). See Temporal References.

Accessing msg:: and sys:: Context

Transforms can access message and system context:

m_last_sender: address {
    in deposit() => msg::sender
    in withdraw(_) => msg::sender
}

m_last_update: u64 {
    in deposit() => sys::now
    in withdraw(_) => sys::now
}

Record and Collection Transforms

Record Functional Update

m_config: Config {
    in update_fee(new_fee) => { m_config { fee_rate: new_fee } }
    in update_limit(new_limit) => { m_config { max_amount: new_limit } }
}

HashMap Updates

m_votes: HashMap<address, bool> {
    in vote() => m_votes.insert(msg::sender, true)
}

Other map updates are m.update(k, v) and m.remove(k); calls chain (m.insert(a, x).insert(b, y)). Reading m[k] for a missing key yields the value type’s default.

Vec Updates

m_history: Vec<u64> {
    in log(value) => m_history.push(value)
}

Transform Atomicity

All member transforms for a given route execute atomically. Either all transforms complete successfully, or the entire transaction reverts. There is no partial state update.

m_balance_a: U256 {
    in swap(amount) => m_balance_a - amount
}

m_balance_b: U256 {
    in swap(amount) => m_balance_b + amount
}

Both m_balance_a and m_balance_b update together. If the subtraction in m_balance_a would underflow (with checked arithmetic), neither member is updated.