Temporal References (^x)
Temporal references allow a member transform to access the post-transform
value of another member within the same route execution. The ^ prefix
distinguishes the “after” value from the default “before” value.
The Problem
Consider a counter and a flag that should be set when the counter exceeds a threshold:
m_count: u64 {
in increment() => m_count + 1
}
m_over_limit: bool {
in increment() => m_count > 100 // BUG: uses pre-transform value
}
Here m_count in the m_over_limit transform refers to the value before
the increment. If m_count is 100, the increment makes it 101, but
m_over_limit sees 100 and evaluates to false. The flag is always one step
behind.
The Solution: ^m_name
The ^ prefix accesses the member’s value after its transform has been
applied:
m_count: u64 {
in increment() => m_count + 1
}
m_over_limit: bool {
in increment() => ^m_count > 100 // CORRECT: uses post-transform value
}
Now ^m_count is m_count + 1 (the result of m_count’s own transform in
the increment route). When m_count is 100, ^m_count is 101, and
m_over_limit correctly becomes true.
Syntax
^m_member_name
^m_x evaluates to the value m_x will have after the current route’s
transforms. It is only allowed inside member transform bodies — not in
where clauses, from clauses, or the action list (actions run after the
transforms are stored; see Actions and State
for which value each action reads).
Common Patterns
Derived State
Compute a member value based on another member’s updated value:
m_balance: U256 {
in deposit() => m_balance + msg::value
}
m_is_funded: bool {
in deposit() => ^m_balance > 0
}
Maintaining Invariants
Keep a count consistent with a collection:
m_items: Vec<u64> {
in add_item(item) => m_items.push(item)
}
m_item_count: U256 {
in add_item(item) => ^m_items.len()
}
Chained Dependencies
Temporal references can chain through multiple members:
m_price: U256 {
in update_price(new_price) => new_price
}
m_fee: U256 {
in update_price(new_price) => ^m_price * 3 / 100
}
m_net_price: U256 {
in update_price(new_price) => ^m_price - ^m_fee
}
Each ^ reference resolves to the post-transform value of the referenced
member. The compiler determines the correct evaluation order.
When the referenced member’s transform uses a route parameter (new_price,
item above), bind that parameter by name in the transform that reads ^,
rather than _: the Lean target currently fails to build otherwise.
Without ^ (Pre-Transform Values)
Inside a transform, member references without ^ resolve to the pre-transform
(current) value:
| Reference | Resolves To |
|---|---|
m_count | Value before the current route fires |
^m_count | Value after m_count’s transform |
This distinction is fundamental to Cambrian’s state model. It enables deterministic computation where every transform sees a consistent snapshot of the “before” state, while still allowing members to depend on each other’s updated values when needed.
Complete Example
A staking entity that tracks the peak total stake. ^m_total_staked in the
m_peak_staked transform uses the total after the new stake is added:
entity Staking {
routes {
stake()
where msg::value > 0 : throw 100
=> []
unstake()
where m_stakes[msg::sender] > 0 : throw 101
=> [
~> msg::sender with { value: m_stakes[msg::sender] }
]
}
m_stakes: HashMap<address, U256> {
in stake() => m_stakes.insert(msg::sender, m_stakes[msg::sender] + msg::value)
in unstake() => m_stakes.insert(msg::sender, 0)
}
m_total_staked: U256 {
in stake() => m_total_staked + msg::value
in unstake() => m_total_staked - m_stakes[msg::sender]
}
m_peak_staked: U256 {
in stake() => {
if ^m_total_staked > m_peak_staked { ^m_total_staked } else { m_peak_staked }
}
}
}
unstake pays out a stake that its own transforms reset to zero. Storage is
written first, but the argument of an outbound send reads the member as it was
before the route, so the transfer carries the old stake (see
Actions and State).
Rules
^m_xis allowed only inside member transform bodies. Ifm_xhas no transform for the current route,^m_xis simplym_x.- Circular temporal dependencies (where
^m_adepends on^m_bwhich depends on^m_a, or a member reads its own^value) are rejected by the compiler. - Temporal references are resolved statically at compile time – there is no runtime overhead.
^applies to a whole member or to an expression over it (^m_total,^m_items.len(),^m_balances[k]for the new entry atk).