Vault Form
The Vault form defines an asset vault with three state fields — deposited (Nat per Address), total (Nat), and lock_until (Nat per Address) — and proves two invariants at compile time: deposit conservation sum(deposited) == total and time-lock enforcement requiring block.time >= lock_until before any withdrawal. Each transition (deposit, withdraw) preserves conservation by algebraic co-variance of the mapped and scalar state, while the time-lock guard is verified as a precondition on every path reaching withdrawal mutations. Both proven invariants are erased from the WASM output, producing zero-overhead verified vault contracts deployable to CosmWasm, Near, Stylus, and Polkadot.
form Vault { state { deposited : Nat per Address total : Nat lock_until : Nat per Address } invariant conservation : sum(deposited) == total invariant time_lock : withdraw => block.time >= lock_until[msg.sender] transition deposit(amount, lock_duration) { require amount > 0 deposited[msg.sender] += amount total += amount lock_until[msg.sender] = block.time + lock_duration } -- conservation: sum(deposited') = sum(deposited) + amount = total + amount = total' transition withdraw(amount) { require deposited[msg.sender] >= amount require block.time >= lock_until[msg.sender] deposited[msg.sender] -= amount total -= amount } -- conservation: sum(deposited') = sum(deposited) - amount = total - amount = total' -- time_lock: require guard enforces block.time >= lock_until }
State Fields
deposited : Nat per Address — maps each address to its deposited asset amount. The per keyword declares a mapping over the Address domain.
total : Nat — the total deposited scalar. Must always equal the sum of all individual deposits.
lock_until : Nat per Address — maps each address to the block timestamp after which withdrawal is permitted.
Invariant: Conservation
The conservation invariant sum(deposited) == total asserts that the aggregate of all per-address deposits equals the total variable. The compiler proves this holds for every reachable state:
Deposit: deposited[msg.sender] increases by amount, total increases by amount. Both sides of the equation increase by identical deltas. Conservation holds by algebraic co-variance.
Withdraw: deposited[msg.sender] decreases by amount, total decreases by amount. Both sides decrease equally. Conservation holds.
Invariant: Time-Lock
The time-lock invariant ensures that no withdrawal can execute before the depositor's lock period expires. The compiler verifies that every execution path reaching the withdraw state mutations passes through the require block.time >= lock_until[msg.sender] guard. Since lock_until is set during deposit as block.time + lock_duration, funds are guaranteed to remain locked for the specified duration.
Transitions
deposit(amount, lock_duration) — deposits assets and sets a time-lock. The require clause ensures a positive amount. Conservation is maintained by co-varying deposited and total by the same delta.
withdraw(amount) — withdraws assets after the lock expires. Requires sufficient deposited balance and that the current block time exceeds the lock_until timestamp. Both invariants are preserved: conservation by co-variance, time-lock by the require guard.
Frequently Asked Questions
- What invariants does a Vault form prove?
- The Vault form proves two invariants: deposit conservation (sum(deposited) == total) guaranteeing no assets appear or vanish through any sequence of transitions, and time-lock enforcement guaranteeing withdrawals only execute when block.time >= lock_until. Both are verified algebraically at compile time and erased from the WASM output — zero runtime overhead.
- How does Formagine verify deposit conservation in the Vault?
- For deposit: the compiler extracts sum(deposited') = sum(deposited) + amount and total' = total + amount, confirming both sides increase by identical deltas. For withdraw: sum(deposited') = sum(deposited) - amount and total' = total - amount. The algebraic identity sum(deposited') = total' holds in both cases by arithmetic co-variance. This proof runs at compile time and is erased — no runtime assertions needed.
- How does the Vault form enforce time-lock constraints?
- The withdraw transition includes
require block.time >= lock_until[msg.sender]as a precondition. The compiler proves that every path reaching the withdrawal mutations passes through this guard. Since lock_until is set to block.time + lock_duration during deposit, the compiler can verify that no execution sequence can bypass the temporal constraint — the guard is structurally mandatory, not merely conventional. - What chains can the Vault form compile to?
- The Vault 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 vault contracts for all targets with identical conservation and time-lock guarantees.