Architecture Note: Reach's Compile‑Time Balance Safety Verification
This architecture note outlines how Reach enforces token balance safety at compile time using linear types and SMT‑based verification, covering requirements, minimal design, trust boundaries, operational checks, limitations, and triggers for design change.
23 Feb 2026, 07:36 UTC

Requirements
Reach programs must guarantee that no participant can unintentionally mint or burn tokens. The safety property is a balance invariant: the sum of all participants’ token balances before any step equals the sum after the step, and each individual balance never goes negative.
Smallest Suitable Design
The verification is built into the Reach compiler. It treats each participant’s balance as a linear type, so the type system can track ownership of tokens through every function call and loop. During compilation the compiler emits verification conditions (VCs) that encode the balance invariant for each program point. An SMT solver discharges these VCs; if all are proved, the program is accepted.
Trust and Data Boundaries
Trust boundaries are defined by the participant roles declared in the Reach source file. Only code associated with a role may invoke balance‑modifying actions (e.g., transfer, deposit, withdraw). The compiler assumes that the underlying blockchain respects the Reach memory model: deterministic execution, no hidden state changes, and that the blockchain’s token semantics match the linear type model. Calls to external contracts or inline assembly that are not vetted by Reach cross this trust boundary and can invalidate the proof.
Operational Checks
When the Reach CLI is invoked with the default verification mode (or the explicit --verify flag), the compiler:
- Parses the source and builds an intermediate representation (AST).
- Inserts balance assertions before and after each transaction.
- Generates VCs that assert the pre‑ and post‑state balances satisfy the invariant.
- Invokes the bundled SMT solver (Z3 by default) to discharge the VCs.
- Emits a compile‑time error if any VC cannot be proved.
No runtime balance checks are emitted in the generated bytecode, which reduces gas costs and simplifies the contract logic.
Example: Token Transfer Between Two Participants
Consider a minimal Reach program src/index.rsh with two participants, A and B, where A transfers 10 tokens to B:
participant A;
participant B;
commit() {
only A {
pay(10);
}
only B {
receive(10);
}
}
To check the balance safety property, run the compiler from the project root:
reach compile --verify src/index.rsh
This command requires:
- Read/write access to the project directory.
- The Reach CLI installed (version compatible with the source).
- No additional permissions; the operation is local.
Expected outcome:
- If the program respects the invariant, the compiler finishes with a success message and produces the usual output artifacts.
- If the transfer amount is changed to a value that could exceed
A’s balance (e.g.,pay(100)whenAonly holds 10), the solver will fail to prove a VC and the compiler will emit an error such as "Verification failed: balance invariant violated".
Risk: Should the program include an external call (ct_call) or inline assembly that modifies token balances outside Reach’s type system, the generated VCs no longer cover those actions, and the compiler may incorrectly report success. In that case, manual inspection or a separate audit is required.
Limitations and Practical Verification
The verification assumes:
- The backend blockchain adheres to the Reach memory model (deterministic opcodes, no reentrancy that bypasses the type system).
- The SMT solver version used during compilation is compatible with the VC encoding; solver upgrades may change proof outcomes.
- All balance‑modifying operations are expressed using Reach’s built‑in primitives (
pay,receive,transfer).
A practical way to confirm that the verification holds for a given contract is to:
- Compile with
--verifyand ensure no errors. - Deploy the generated bytecode to a testnet (e.g., Ethereum Goerli or Algorand testnet).
- Submit a transaction that attempts to violate the balance (e.g., send more tokens than the caller holds).
- Observe that the transaction reverts on‑chain, confirming that the compiled contract enforces the invariant.
If the on‑chain transaction succeeds despite the compile‑time check, the trust boundary has been crossed (likely via an unvetted external call) and the design must be revisited.
Conditions That Would Change the Design
The current minimal design would need revision if any of the following occur:
- The target blockchain introduces non‑deterministic opcodes or gas‑dependent state changes that break the linear type assumption.
- Reach adds a feature that allows arbitrary inline assembly without static analysis, requiring a new trust model.
- The underlying SMT solver is replaced with a tool that cannot handle the generated VC format, necessitating a different verification backend.
- Regulatory or protocol changes require runtime balance proofs (e.g., for upgradable contracts), making compile‑time verification insufficient.
In each case, the architecture would need to incorporate additional checks—either extended static analysis, runtime assertions, or a hybrid approach—to restore the balance safety guarantee.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.