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

Phased Execution Model

Given phases P₁, P₂, …, Pₙ in declaration order, each phase runs three steps before the next begins:

for each phase Pᵢ:
    1. Compute transforms tagged Pᵢ
       (all expressions see state_{Pᵢ₋₁} — the pre-phase snapshot)
    2. Apply those transforms atomically → state_Pᵢ
    3. Execute effects of Pᵢ
       (effects see post-transform state_Pᵢ)

Members without a transform at Pᵢ keep their value from Pᵢ₋₁.

Atomicity inside a phase

Transforms in the same phase are computed from the same pre-phase snapshot and written together. Two members can swap without observing a half-updated state:

m_a: u64 { in example() => p1: m_b }
m_b: u64 { in example() => p1: m_a }

After p1, m_a and m_b have exchanged their previous values.

var capture

A phase action may bind the return value of a synchronous external call:

fetch: [
    var bal = balanceOf(who) ~> m_token;
]

The var is in scope from the moment its ~> runs through all later phases of the same invocation. Later transforms and per-phase where clauses may read it; earlier phases and the route-level where may not.

Per-phase where (V27 / V28)

Attach a precondition to a later phase with:

phaseName where (cond) : throw N : [ … ]

The condition lowers to a require(...) at the top of that phase — after previous phases have finished (so earlier vars are bound) and before this phase’s transforms or effects.

withdraw(who: address) => [
    fetch: [
        var bal = balanceOf(who) ~> m_token;
    ]
    act where bal > 0 : throw 401 : [
    ]
]

Scoping rules:

ClauseMay reference vars from…Error if violated
Route-level whereNever (params + pre-existing state only)V27
Per-phase where on PᵢStrictly earlier phases P₁…Pᵢ₋₁V28

Move checks that depend on a captured value onto the phase that needs them — do not put them on the route-level where.

Route-level guards still run first

A route-level where runs once, before any phase. If it fails, no phase executes and no transforms apply. Use it for conditions over parameters and members that already exist at entry.

Reentrancy across phase boundaries (EVM)

Unphased routes keep CEI: all SSTOREs before any CALL.

Phased routes intentionally allow CALLs between groups of storage writes. That is useful (cross-entity reads, flash callbacks) but means a callee can re-enter before later phases run:

P₁ transforms → P₁ effects (external call) → P₂ transforms → …

On reentry, later phases of the outer invocation have not applied yet. Design accordingly:

  • Prefer unphased routes when you only need CEI.
  • When using phases, put critical authorization and balance updates in phases that complete before the external call, or gate later phases with per-phase where on captured post-call state (as Uniswap V2 swap’s check: phase does for the K-invariant).

See Phased Routes for when to introduce phases at all.