soma.cell

Trade settlement as a sum type

A settlement attempt yields one of three tagged variants; `match` over them is exhaustive (drop an arm and `soma check` refuses). Demonstrates struct / tuple / unit variants and exhaustive matching for money outcomes.

A Soma program from the finance part of the verified corpus: 58 lines, cells Settlement, TradeSettlement, SettlementTests. It passes soma check with soma 3.2.4.

How it is built

The cell TradeSettlement declares 3 signals in its face: settle(trade_id: String, qty: Int, price: Int, cash: Int) -> Map, describe(r: Map) -> String and is_terminal(r: Map) -> Bool. Handlers: settle (line 22), describe (line 29) and is_terminal (line 37).

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

Generated from soma describe on this file.

The source

trade_settlement.cell

// Trade settlement as a sum type. A settlement attempt yields one of three
// tagged variants; `match` over them is exhaustive (drop an arm and
// `soma check` refuses). Demonstrates struct / tuple / unit variants and
// exhaustive matching for money outcomes.

cell type Settlement {
    variants {
        Settled { trade_id: String, net: Int }   // struct variant
        Failed(String)                            // tuple variant
        Pending                                   // unit variant
    }
}

cell TradeSettlement {
    face {
        signal settle(trade_id: String, qty: Int, price: Int, cash: Int) -> Map
        signal describe(r: Map) -> String
        signal is_terminal(r: Map) -> Bool
    }

    // net = qty*price; fails if the account can't cover it; pending if qty 0
    on settle(trade_id: String, qty: Int, price: Int, cash: Int) {
        if qty == 0 { return Pending }
        let net = qty * price
        if net > cash { return Failed("insufficient cash") }
        return Settled { trade_id: trade_id, net: net }
    }

    on describe(r: Map) {
        return match r {
            Settled { trade_id, net } -> "settled {trade_id} for {net}"
            Failed(reason)            -> "failed: {reason}"
            Pending                   -> "pending"
        }
    }

    on is_terminal(r: Map) {
        return match r {
            Settled { trade_id, net } -> true
            Failed(reason)            -> true
            Pending                   -> false
        }
    }
}

cell test SettlementTests {
    rules {
        // a funded trade settles
        assert describe(settle("t1", 10, 50, 1000)) == "settled t1 for 500"
        assert is_terminal(settle("t1", 10, 50, 1000)) == true
        // underfunded → failed
        assert describe(settle("t2", 10, 50, 100)) == "failed: insufficient cash"
        // zero qty → pending, not terminal
        assert describe(settle("t3", 0, 50, 1000)) == "pending"
        assert is_terminal(settle("t3", 0, 50, 1000)) == false
    }
}

What the toolchain says

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

$ soma check trade_settlement.cell
✓ All checks passed.
$ soma test trade_settlement.cell
test SettlementTests ...
  ✓ assert describe(settle("t1", 10, 50, 1000)) == "settled t1 for 500"
  ✓ assert is_terminal(settle("t1", 10, 50, 1000)) == true
  ✓ assert describe(settle("t2", 10, 50, 100)) == "failed: insufficient cash"
  ✓ assert describe(settle("t3", 0, 50, 1000)) == "pending"
  ✓ assert is_terminal(settle("t3", 0, 50, 1000)) == false

5 tests: 5 passed, 0 failed
$ soma verify trade_settlement.cell
Verifying trade_settlement.cell...
No state machine in this program: nothing to prove beyond `soma check` (invariants and lifecycles are what verify proves).
VERIFY OK (vacuous — no state machine)

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