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.

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(amount) => m_balance + amount
    in withdraw(amount) => m_balance - amount
}

Parameter Matching

Transform parameters must match the route parameters by position. The names can differ, but the count and order must correspond:

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

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

Ignoring Parameters

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

m_owner: address {
    in deploy(_, _, 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 from = msg::sender;
        let from_bal = m_balances[from];
        let to_bal = m_balances.get(to).unwrap_or(0);
        m_balances
            .set(from, from_bal - amount)
            .set(to, to_bal + amount)
    }
}

Conditional Transforms

Transforms can use if/else and match:

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 deploy(_, _, _) => 0
    in deposit(amount) => m_balance + amount
    in withdraw(amount) => m_balance - amount
    in transfer_out(_, amount) => m_balance - amount
    in receive(amount) => m_balance + amount
}

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, 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.set(msg::sender, true)
}

Vec Updates

m_history: Vec<u64> {
    in record(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.