soma.cell

Chip/cash ledger with a non-negative balance invariant

The memory itself rejects any write that would overdraw an account — the bad .set() never commits, so the slot keeps its previous value. Demonstrates: memory invariant, try around a guarded write, assert_fails.

A Soma program from the finance part of the verified corpus: 54 lines, cells ChipLedger, ChipLedgerTests. It passes soma check with soma 3.2.4.

How it is built

The cell ChipLedger declares 3 signals in its face: credit(player: String, amount: Int) -> Map, debit(player: String, amount: Int) -> Map and balance_of(player: String) -> Int; keeps 1 memory slot: chips (Map<String, Int>, persistent and consistent) under the invariant chips >= 0. Handlers: balance_of (line 19), credit (line 25) and debit (line 31).

The file also holds 1 test cell (ChipLedgerTests) with 6 assertions, run by soma test.

Generated from soma describe on this file.

The source

chip_ledger.cell

// Chip/cash ledger with a non-negative balance invariant.
// The memory itself rejects any write that would overdraw an account —
// the bad .set() never commits, so the slot keeps its previous value.
// Demonstrates: memory invariant, try around a guarded write, assert_fails.

cell ChipLedger {
    face {
        signal credit(player: String, amount: Int) -> Map
        signal debit(player: String, amount: Int) -> Map
        signal balance_of(player: String) -> Int
        promise all_persistent
    }

    memory {
        chips: Map<String, Int> [persistent, consistent]
        invariant chips >= 0
    }

    on balance_of(player: String) {
        let b = chips.get(player)
        if b == () { return 0 }
        return b
    }

    on credit(player: String, amount: Int) {
        let cur = balance_of(player)
        chips.set(player, cur + amount)
        return map("ok", true, "balance", cur + amount)
    }

    on debit(player: String, amount: Int) {
        let cur = balance_of(player)
        // If cur - amount < 0 the invariant rejects the write.
        let r = try { chips.set(player, cur - amount) }
        if r.error != () {
            return map("ok", false, "reason", "insufficient funds", "balance", cur)
        }
        return map("ok", true, "balance", cur - amount)
    }
}

cell test ChipLedgerTests {
    rules {
        assert credit("alice", 100).ok == true
        assert balance_of("alice") == 100
        assert debit("alice", 40).balance == 60
        // overdraw is refused, balance unchanged
        assert debit("alice", 999).ok == false
        assert balance_of("alice") == 60
        // a direct negative write is a runtime error
        assert_fails chips.set("bob", -1)
    }
}

What the toolchain says

Run on a fresh copy of the program at build time; the outputs are pasted unedited.

$ soma check chip_ledger.cell
✓ All checks passed.
$ soma test chip_ledger.cell
test ChipLedgerTests ...
  ✓ assert credit("alice", 100).ok == true
  ✓ assert balance_of("alice") == 100
  ✓ assert debit("alice", 40).balance == 60
  ✓ assert debit("alice", 999).ok == false
  ✓ assert balance_of("alice") == 60
  ✓ assert_fails chips.set("bob", -1) — raised memory invariant violated on 'chips': chips >= 0 — rejected write of -1 (key "bob"); the slot is unchanged

6 tests: 6 passed, 0 failed
$ soma verify chip_ledger.cell
Verifying chip_ledger.cell...

ChipLedger/invariants:

  ⚠ invariant chips >= 0 — runtime-checked (computed values): credit → chips because `amount` is a parameter (narrow it: `require (cur + amount) >= 0 else …`), debit → chips because `amount` is a parameter (narrow it: `require (cur - amount) >= 0 else …`)

0 passed, 1 warnings, 0 failures
Temporal: 0 passed, 0 failed
VERIFY OK

Builtins used

map

Source file: /corpus/finance/chip_ledger.cell · examples/corpus/finance/chip_ledger.cell