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.