Escrow Form

The Escrow form defines a multi-party escrow with six state fields — amount (Nat), depositor (Address), beneficiary (Address), arbiter (Address), released (Bool), and deadline (Nat) — and proves the single-release invariant released => amount == 0 at compile time. This implication guarantees that once funds are disbursed, the escrowed amount is zero and no further extraction is possible. The compiler verifies that both transitions (release by arbiter, refund after deadline) atomically zero the amount before setting released = true, and that the !released guard prevents re-entry. The proven invariant is erased from WASM output, producing zero-overhead verified escrow contracts for CosmWasm, Near, Stylus, and Polkadot.

form Escrow {
  state {
    amount      : Nat
    depositor   : Address
    beneficiary : Address
    arbiter     : Address
    released    : Bool
    deadline    : Nat
  }

  invariant single_release :
    released => amount == 0

  transition release() {
    require msg.sender == arbiter
    require !released
    send(beneficiary, amount)
    amount   = 0
    released = true
  }
  -- single_release: amount set to 0 before released set to true
  -- post-state: released = true, amount = 0 => invariant holds

  transition refund() {
    require block.time >= deadline
    require !released
    send(depositor, amount)
    amount   = 0
    released = true
  }
  -- single_release: same structure — amount zeroed, released set
  -- deadline guard ensures refund only after expiry
}

State Fields

amount : Nat — the escrowed asset amount. Set to zero upon release or refund.

depositor : Address — the address that deposited assets into escrow. Receives refund if deadline passes without release.

beneficiary : Address — the address that receives assets upon arbiter-authorized release.

arbiter : Address — the trusted third party authorized to trigger release to the beneficiary.

released : Bool — flag indicating whether funds have been disbursed. Once true, no further transitions can move funds.

deadline : Nat — the block timestamp after which the depositor may claim a refund.

Invariant: Single Release

The single-release invariant released => amount == 0 is a logical implication: if the released flag is true, then the escrowed amount must be zero. The compiler proves this by analyzing each transition:

Release: The transition sets amount = 0 and then released = true. In the post-state, released is true and amount is 0, so the implication holds. The require !released guard prevents re-entry, ensuring release executes at most once.

Refund: Identical structure — amount is zeroed, released is set to true. The deadline guard adds temporal access control but does not affect the invariant proof.

No-op states: When released is false, the implication holds vacuously (false => anything is true).

Transitions

release() — the arbiter triggers fund disbursement to the beneficiary. Requires msg.sender == arbiter and !released. Atomically sends the escrowed amount, zeros it, and sets the released flag.

refund() — the depositor reclaims funds after the deadline. Requires block.time >= deadline and !released. Atomically sends the escrowed amount back, zeros it, and sets the released flag.

Frequently Asked Questions

What invariants does an Escrow form prove?
The Escrow form proves the single-release invariant: released => amount == 0. This guarantees that once funds have been disbursed — whether to the beneficiary via release or back to the depositor via refund — the escrowed amount is zero and no further extraction is possible. The compiler verifies this implication holds across all transitions at compile time, preventing double-spend by construction.
How does Formagine verify the single-release invariant?
The compiler analyzes each transition that sets released = true and verifies that amount is simultaneously set to 0. Both release and refund follow the same pattern: send funds, set amount = 0, set released = true. The post-state satisfies released => amount == 0 because the consequent is established before the antecedent. The require !released guard on both transitions prevents re-entry after either has executed, making double-disbursement structurally impossible.
How does the Escrow form enforce arbiter-only release and deadline refunds?
The release transition includes require msg.sender == arbiter, restricting release to the designated arbiter address. The refund transition includes require block.time >= deadline, ensuring refunds only execute after the deadline passes. Both transitions also require !released, preventing any action after funds have been disbursed. The compiler verifies these guards are structurally mandatory — every path to fund disbursement passes through the appropriate authorization check.
What chains can the Escrow form compile to?
The Escrow form compiles to optimized WASM targeting CosmWasm (Cosmos ecosystem), Near Protocol, Arbitrum Stylus (Ethereum L2), and Polkadot (ink! contracts). The same form definition produces verified escrow contracts for all targets with identical single-release and access-control guarantees.

Related Forms

Resources