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

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:

ReferenceResolves To
m_countValue before the current route fires
^m_countValue 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

  1. ^m_x is allowed only inside member transform bodies. If m_x has no transform for the current route, ^m_x is simply m_x.
  2. Circular temporal dependencies (where ^m_a depends on ^m_b which depends on ^m_a, or a member reads its own ^ value) are rejected by the compiler.
  3. Temporal references are resolved statically at compile time – there is no runtime overhead.
  4. ^ applies to a whole member or to an expression over it (^m_total, ^m_items.len(), ^m_balances[k] for the new entry at k).