Delivery-versus-payment — no securities leave before the cash arrives
On 26 June 1974, Bankhaus Herstatt was shut down by regulators AFTER its counterparties had irrevocably paid the Deutschmark leg of their FX trades but BEFORE Herstatt paid out the dollar leg. The money was simply gone — "Herstatt risk" got its name and CLS Bank got founded. The cure is delivery-versus-payment: the two legs are ONE state machine, and the deliver edge exists only after cash is confirmed. A trade abandoned after cash arrives must pass through `refunded` — cash can never be stranded — and `soma verify` proves every path ends settled or made-whole.
A Soma program from the escrow_finance part of the verified corpus: 111 lines, cells DvP, DvPTests. It passes soma check with soma 3.2.4.
How it is built
The cell DvP declares 5 signals in its face: match_trade(id: String, cash: Int, units: Int) -> Map, confirm_cash(id: String) -> Map, deliver(id: String) -> Map, abort_trade(id: String) -> Map and legs(id: String) -> Map; keeps 2 memory slots: cash_held (Map<String, Int>, persistent and consistent) under the invariant cash_held >= 0 and units_due (Map<String, Int>, persistent and consistent) under the invariant units_due >= 0. Its state machine trade starts in matched and allows 6 transitions: matched -> instructed, instructed -> cash_in, instructed -> cancelled, cash_in -> delivered, cash_in -> refunded and delivered -> settled. cancelled, refunded and settled are terminal. Handlers: match_trade (line 45), confirm_cash (line 53), deliver (line 59), abort_trade (line 66) and legs (line 77).
The file also holds 1 test cell (DvPTests) with 18 assertions, run by soma test.
The source
dvp_settlement.cell
// ============================================================================
// DELIVERY-VERSUS-PAYMENT — no securities leave before the cash arrives
// ============================================================================
//
// On 26 June 1974, Bankhaus Herstatt was shut down by regulators AFTER its
// counterparties had irrevocably paid the Deutschmark leg of their FX
// trades but BEFORE Herstatt paid out the dollar leg. The money was simply
// gone — "Herstatt risk" got its name and CLS Bank got founded. The cure
// is delivery-versus-payment: the two legs are ONE state machine, and the
// deliver edge exists only after cash is confirmed. A trade abandoned
// after cash arrives must pass through `refunded` — cash can never be
// stranded — and `soma verify` proves every path ends settled or made-whole.
// ============================================================================
cell DvP {
face {
signal match_trade(id: String, cash: Int, units: Int) -> Map
signal confirm_cash(id: String) -> Map
signal deliver(id: String) -> Map
signal abort_trade(id: String) -> Map
signal legs(id: String) -> Map
}
memory {
cash_held: Map<String, Int> [persistent, consistent]
invariant cash_held >= 0
units_due: Map<String, Int> [persistent, consistent]
invariant units_due >= 0
}
// settled, cancelled, refunded: terminal. The Herstatt edge —
// instructed -> delivered with no cash — does not exist. And once
// cash is in, the ONLY exits are delivery or refund: no path
// strands the counterparty's money.
state trade {
initial: matched
matched -> instructed
instructed -> cash_in
instructed -> cancelled
cash_in -> delivered
cash_in -> refunded
delivered -> settled
}
on match_trade(id: String, cash: Int, units: Int) {
if cash <= 0 || units <= 0 { return map("ok", false, "reason", "bad legs") }
transition(id, "instructed")
cash_held.set(id, 0)
units_due.set(id, units)
return map("ok", true, "state", get_status(id), "cash_leg", cash)
}
on confirm_cash(id: String) {
transition(id, "cash_in")
cash_held.set(id, 1) // marker: the cash leg is irrevocable
return map("ok", true, "state", get_status(id))
}
on deliver(id: String) {
transition(id, "delivered") // raises unless cash is in
units_due.set(id, 0)
transition(id, "settled")
return map("ok", true, "state", get_status(id))
}
on abort_trade(id: String) {
let st = get_status(id)
if st == "cash_in" {
transition(id, "refunded") // cash already paid? it MUST go back
cash_held.set(id, 0)
return map("ok", true, "state", "refunded")
}
transition(id, "cancelled") // raises once delivery has begun
return map("ok", true, "state", "cancelled")
}
on legs(id: String) {
let c = cash_held.get(id) ?? 0
let u = units_due.get(id) ?? 0
return map("cash_in", c == 1, "units_due", u, "state", get_status(id))
}
}
cell test DvPTests {
rules {
assert match_trade("fx1", 1000000, 500).ok == true
// THE HERSTATT EDGE: deliver before the cash leg — does not exist
assert_fails transition("fx1", "delivered")
assert_fails deliver("fx1")
assert confirm_cash("fx1").ok == true
// once cash is in, plain cancellation is impossible: only refund
assert_fails transition("fx1", "cancelled")
assert deliver("fx1").state == "settled"
assert legs("fx1").units_due == 0
// settled is terminal — no unwinding a finished trade
assert_fails transition("fx1", "refunded")
assert_fails transition("fx1", "cash_in")
// abort AFTER cash: the counterparty is made whole, then it's final
assert match_trade("fx2", 5000, 10).ok == true
assert confirm_cash("fx2").ok == true
assert abort_trade("fx2").state == "refunded"
assert legs("fx2").cash_in == false
assert_fails transition("fx2", "delivered")
// abort BEFORE cash: a clean cancel, equally terminal
assert match_trade("fx3", 5000, 10).ok == true
assert abort_trade("fx3").state == "cancelled"
assert_fails transition("fx3", "cash_in")
assert_fails units_due.set("fx1", -1)
}
}What the toolchain says
$ soma check dvp_settlement.cell
✓ All checks passed.$ soma test dvp_settlement.cell
test DvPTests ...
✓ assert match_trade("fx1", 1000000, 500).ok == true
✓ assert_fails transition("fx1", "delivered") — raised invalid transition: instructed → delivered. Current state: 'instructed'. Valid targets: [cash_in, cancelled]
✓ assert_fails deliver("fx1") — raised invalid transition: instructed → delivered. Current state: 'instructed'. Valid targets: [cash_in, cancelled]
✓ assert confirm_cash("fx1").ok == true
✓ assert_fails transition("fx1", "cancelled") — raised invalid transition: cash_in → cancelled. Current state: 'cash_in'. Valid targets: [delivered, refunded]
✓ assert deliver("fx1").state == "settled"
✓ assert legs("fx1").units_due == 0
✓ assert_fails transition("fx1", "refunded") — raised invalid transition: settled → refunded. Current state: 'settled'. Valid targets: []
✓ assert_fails transition("fx1", "cash_in") — raised invalid transition: settled → cash_in. Current state: 'settled'. Valid targets: []
✓ assert match_trade("fx2", 5000, 10).ok == true
✓ assert confirm_cash("fx2").ok == true
✓ assert abort_trade("fx2").state == "refunded"
✓ assert legs("fx2").cash_in == false
✓ assert_fails transition("fx2", "delivered") — raised invalid transition: refunded → delivered. Current state: 'refunded'. Valid targets: []
✓ assert match_trade("fx3", 5000, 10).ok == true
✓ assert abort_trade("fx3").state == "cancelled"
✓ assert_fails transition("fx3", "cash_in") — raised invalid transition: cancelled → cash_in. Current state: 'cancelled'. Valid targets: []
✓ assert_fails units_due.set("fx1", -1) — raised memory invariant violated on 'units_due': units_due >= 0 — rejected write of -1 (key "fx1"); the slot is unchanged
18 tests: 18 passed, 0 failed$ soma verify dvp_settlement.cell
Verifying dvp_settlement.cell...
State machine 'trade': 7 states, initial 'matched'
States: [cancelled, cash_in, delivered, instructed, matched, refunded, settled]
✓ 7 states, 6 transitions
✓ all states reachable from 'matched'
✓ terminal states: [cancelled, refunded, settled]
✓ no deadlocks
✓ liveness: every state can eventually reach a terminal state
✓ think-isolated: CTL safety properties hold regardless of LLM output (4 handlers, 6 literal transitions, 0 dynamic)
✓ termination: all 5 handlers structurally terminate
✓ refinement: handler `match_trade` ⟶ {instructed}
✓ refinement: handler `confirm_cash` ⟶ {cash_in}
✓ refinement: handler `deliver` ⟶ {delivered, delivered → settled}
✓ refinement: handler `abort_trade` ⟶ {cash_in → refunded [if st == "cash_in"], cancelled}
· transition sources: `match_trade`, `confirm_cash`, `deliver`, `abort_trade` 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)
DvP/invariants:
✓ invariant cash_held >= 0 — writer 'abort_trade' proven (writes 0)
✓ invariant cash_held >= 0 — writer 'confirm_cash' proven (writes 1)
✓ invariant cash_held >= 0 — writer 'match_trade' proven (writes 0)
✓ invariant units_due >= 0 — writer 'deliver' proven (writes 0)
✓ invariant units_due >= 0 — writer 'match_trade' proven by induction (writes units)
16 passed, 0 warnings, 0 failures
Temporal properties for 'trade':
✓ deadlock_free — no deadlocks in any reachable state
Temporal: 1 passed, 0 failed
VERIFY OK