Dividend distribution — payments can never exceed the declared pool
When a board declares a dividend, that number is legally binding — and transfer agents have paid out MORE than the declaration through duplicate shareholder records or a re-run batch job (the same disease as the 2021 "accidental" duplicate ACH dividend runs brokers had to claw back). Structure: the declared pool is a slot every payment decrements, with `pool >= 0` as the invariant — the payment that would overspend the declaration is a rejected write. A shareholder can be paid once per declaration, and a closed distribution is terminal.
A Soma program from the escrow_finance part of the verified corpus: 96 lines, cells Dividend, DividendTests. It passes soma check with soma 3.2.4.
How it is built
The cell Dividend declares 4 signals in its face: declare(id: String, total: Int) -> Map, pay_holder(id: String, holder: String, amount: Int) -> Map, close_books(id: String) -> Map and pool_left(id: String) -> Int; keeps 2 memory slots: pool (Map<String, Int>, persistent and consistent) under the invariant pool >= 0 and paid (Map<String, Int>, persistent and consistent) under the invariant paid >= 0. Its state machine distribution starts in proposed and allows 3 transitions: proposed -> declared, declared -> paying and paying -> closed. closed is terminal. Handlers: declare (line 40), pay_holder (line 48), close_books (line 64) and pool_left (line 69).
The file also holds 1 test cell (DividendTests) with 14 assertions, run by soma test.
The source
dividend_pool.cell
// ============================================================================
// DIVIDEND DISTRIBUTION — payments can never exceed the declared pool
// ============================================================================
//
// When a board declares a dividend, that number is legally binding — and
// transfer agents have paid out MORE than the declaration through duplicate
// shareholder records or a re-run batch job (the same disease as the 2021
// "accidental" duplicate ACH dividend runs brokers had to claw back).
// Structure: the declared pool is a slot every payment decrements, with
// `pool >= 0` as the invariant — the payment that would overspend the
// declaration is a rejected write. A shareholder can be paid once per
// declaration, and a closed distribution is terminal.
// ============================================================================
cell Dividend {
face {
signal declare(id: String, total: Int) -> Map
signal pay_holder(id: String, holder: String, amount: Int) -> Map
signal close_books(id: String) -> Map
signal pool_left(id: String) -> Int
}
memory {
// the declared pool — strictly drawn down, never overdrawn
pool: Map<String, Int> [persistent, consistent]
invariant pool >= 0
paid: Map<String, Int> [persistent, consistent]
invariant paid >= 0
}
// distribution lifecycle; closed is terminal — the batch job that
// re-runs against a closed declaration finds no state to act in.
state distribution {
initial: proposed
proposed -> declared
declared -> paying
paying -> closed
}
on declare(id: String, total: Int) {
if total <= 0 { return map("ok", false, "reason", "bad total") }
transition(id, "declared")
transition(id, "paying")
pool.set(id, total)
return map("ok", true, "declared", total)
}
on pay_holder(id: String, holder: String, amount: Int) {
if get_status(id) != "paying" {
return map("ok", false, "reason", "distribution is not paying")
}
if amount <= 0 { return map("ok", false, "reason", "bad amount") }
let k = "{id}:{holder}"
if (paid.get(k) ?? 0) > 0 {
// THE DUPLICATE RECORD: same holder, same declaration — once
return map("ok", false, "reason", "{holder} already paid")
}
let left = pool.get(id) ?? 0
pool.set(id, left - amount) // raises past the declaration
paid.set(k, amount)
return map("ok", true, "holder", holder, "left", left - amount)
}
on close_books(id: String) {
transition(id, "closed")
return map("ok", true, "unclaimed", pool.get(id) ?? 0)
}
on pool_left(id: String) {
return pool.get(id) ?? 0
}
}
cell test DividendTests {
rules {
assert declare("q2", 10000).ok == true
assert pay_holder("q2", "fund-a", 6000).ok == true
assert pay_holder("q2", "fund-b", 3000).ok == true
// THE RE-RUN BATCH: fund-a appears again — refused
assert pay_holder("q2", "fund-a", 6000).ok == false
// THE OVERSPEND: 1,500 against the 1,000 left — the write raises
assert_fails pay_holder("q2", "fund-c", 1500)
assert pool_left("q2") == 1000
assert pay_holder("q2", "fund-c", 1000).ok == true
assert pool_left("q2") == 0
// nothing left: even one more cent is an impossible write
assert_fails pay_holder("q2", "fund-d", 1)
assert_fails pool.set("q2", -1)
assert close_books("q2").unclaimed == 0
// closed is terminal — the distribution cannot re-open and re-pay
assert pay_holder("q2", "fund-e", 1).ok == false
assert_fails transition("q2", "paying")
assert_fails transition("q2", "declared")
}
}What the toolchain says
$ soma check dividend_pool.cell
✓ All checks passed.$ soma test dividend_pool.cell
test DividendTests ...
✓ assert declare("q2", 10000).ok == true
✓ assert pay_holder("q2", "fund-a", 6000).ok == true
✓ assert pay_holder("q2", "fund-b", 3000).ok == true
✓ assert pay_holder("q2", "fund-a", 6000).ok == false
✓ assert_fails pay_holder("q2", "fund-c", 1500) — raised memory invariant violated on 'pool': pool >= 0 — rejected write of -500 (key "q2"); the slot is unchanged
✓ assert pool_left("q2") == 1000
✓ assert pay_holder("q2", "fund-c", 1000).ok == true
✓ assert pool_left("q2") == 0
✓ assert_fails pay_holder("q2", "fund-d", 1) — raised memory invariant violated on 'pool': pool >= 0 — rejected write of -1 (key "q2"); the slot is unchanged
✓ assert_fails pool.set("q2", -1) — raised memory invariant violated on 'pool': pool >= 0 — rejected write of -1 (key "q2"); the slot is unchanged
✓ assert close_books("q2").unclaimed == 0
✓ assert pay_holder("q2", "fund-e", 1).ok == false
✓ assert_fails transition("q2", "paying") — raised invalid transition: closed → paying. Current state: 'closed'. Valid targets: []
✓ assert_fails transition("q2", "declared") — raised invalid transition: closed → declared. Current state: 'closed'. Valid targets: []
14 tests: 14 passed, 0 failed$ soma verify dividend_pool.cell
Verifying dividend_pool.cell...
State machine 'distribution': 4 states, initial 'proposed'
States: [closed, declared, paying, proposed]
✓ 4 states, 3 transitions
✓ all states reachable from 'proposed'
✓ terminal states: [closed]
✓ no deadlocks
✓ liveness: every state can eventually reach a terminal state
✓ think-isolated: CTL safety properties hold regardless of LLM output (2 handlers, 3 literal transitions, 0 dynamic)
✓ termination: all 4 handlers structurally terminate
✓ refinement: handler `declare` ⟶ {declared, declared → paying}
✓ refinement: handler `close_books` ⟶ {closed}
· transition sources: `declare`, `close_books` call transition(id, target) without declaring the source — the runtime refuses a move the graph forbids, but the proof does not see where the handler starts; `transition(id, from, to)` names the edge (checked by `soma check`, refused at run time from any other state)
Dividend/invariants:
✓ invariant pool >= 0 — writer 'declare' proven by induction (writes total)
⚠ invariant pool >= 0 — runtime-checked (computed values): pay_holder → pool
✓ invariant paid >= 0 — writer 'pay_holder' proven by induction (writes amount)
11 passed, 1 warnings, 0 failures
Temporal properties for 'distribution':
✓ deadlock_free — no deadlocks in any reachable state
Temporal: 1 passed, 0 failed
VERIFY OK