soma.cell

Loot trade — an item exists in exactly one inventory, ever

Item duplication broke Diablo II's economy so completely that duped Stones of Jordan became the server currency. Most dupes had one shape: a trade that crashed or desynced mid-swap left the item in BOTH inventories. Here that shape is unrepresentable. Ownership is a single registry entry (one item -> one owner), the copy counter is walled at <= 1 by the memory itself — a second copy of an item id CANNOT BE STORED — and a trade is a machine soma verify proves: `executed` and `withdrawn` are terminal, so a completed swap can never replay.

A Soma program from the games_economy part of the verified corpus: 101 lines, cells TradePost, TradeTests. It passes soma check with soma 3.2.4.

How it is built

The cell TradePost declares 6 signals in its face: mint(item: String, player: String) -> Map, offer_trade(trade: String, item: String, to_player: String) -> Map, seal_trade(trade: String) -> Map, swap(trade: String) -> Map, owner_of(item: String) -> String and copies_of(item: String) -> Int; keeps 4 memory slots: copies (Map<String, Int>, persistent and consistent) under the invariant copies >= 0 && copies <= 1, owner (Map<String, String>, persistent and consistent), offer_item (Map<String, String>, persistent and consistent) and offer_to (Map<String, String>, persistent and consistent). Its state machine trade_flow starts in proposed and allows 3 transitions: proposed -> sealed, proposed -> withdrawn and sealed -> executed. executed and withdrawn are terminal. Handlers: mint (line 40), offer_trade (line 48), seal_trade (line 56), swap (line 63), owner_of (line 74) and copies_of (line 75).

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

Generated from soma describe on this file.

The source

loot_trade.cell

// ============================================================================
// LOOT TRADE — an item exists in exactly one inventory, ever
// ============================================================================
// Item duplication broke Diablo II's economy so completely that duped
// Stones of Jordan became the server currency. Most dupes had one shape:
// a trade that crashed or desynced mid-swap left the item in BOTH
// inventories. Here that shape is unrepresentable. Ownership is a single
// registry entry (one item -> one owner), the copy counter is walled at
// <= 1 by the memory itself — a second copy of an item id CANNOT BE
// STORED — and a trade is a machine soma verify proves: `executed` and
// `withdrawn` are terminal, so a completed swap can never replay.
// ============================================================================

cell TradePost {
    face {
        signal mint(item: String, player: String) -> Map
        signal offer_trade(trade: String, item: String, to_player: String) -> Map
        signal seal_trade(trade: String) -> Map
        signal swap(trade: String) -> Map
        signal owner_of(item: String) -> String
        signal copies_of(item: String) -> Int
    }

    memory {
        copies: Map<String, Int> [persistent, consistent]
        invariant copies >= 0 && copies <= 1
        owner: Map<String, String> [persistent, consistent]
        offer_item: Map<String, String> [persistent, consistent]
        offer_to: Map<String, String> [persistent, consistent]
    }

    // proposed -> sealed locks the offer; only a sealed trade executes
    state trade_flow {
        initial: proposed
        proposed -> sealed
        proposed -> withdrawn
        sealed -> executed
    }

    on mint(item: String, player: String) {
        // THE WALL: a second copy of an existing item id is refused
        let w = try { copies.set(item, (copies.get(item) ?? 0) + 1) }
        if w.error != () { return map("ok", false, "reason", "{item} already exists") }
        owner.set(item, player)
        return map("ok", true, "item", item, "owner", player)
    }

    on offer_trade(trade: String, item: String, to_player: String) {
        if offer_item.get(trade) != () { return map("ok", false, "reason", "trade id in use") }
        if owner.get(item) == () { return map("ok", false, "reason", "{item} does not exist") }
        offer_item.set(trade, item)
        offer_to.set(trade, to_player)
        return map("ok", true, "trade", trade)
    }

    on seal_trade(trade: String) {
        if offer_item.get(trade) == () { return map("ok", false, "reason", "no such offer") }
        let r = try { transition(trade, "sealed") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true)
    }

    on swap(trade: String) {
        let r = try { transition(trade, "executed") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        let item = offer_item.get(trade)
        // ONE registry write: the old owner loses the item by the same
        // assignment that gives it to the new owner. Two inventories holding
        // it is not a bug to catch — it has no representation.
        owner.set(item, offer_to.get(trade))
        return map("ok", true, "item", item, "now_owned_by", owner.get(item))
    }

    on owner_of(item: String) { return owner.get(item) ?? "" }
    on copies_of(item: String) { return copies.get(item) ?? 0 }
}

cell test TradeTests {
    rules {
        assert mint("soj", "ada").ok == true
        assert copies_of("soj") == 1
        // THE DUPE, attempted head-on: a second copy is unrepresentable
        assert mint("soj", "eve").ok == false
        assert_fails copies.set("soj", 2)
        assert owner_of("soj") == "ada"
        // a normal trade: offer, seal, swap
        assert offer_trade("t1", "soj", "bob").ok == true
        assert seal_trade("t1").ok == true
        assert_fails transition("t1", "withdrawn")     // sealed offers can't vanish
        assert swap("t1").ok == true
        assert owner_of("soj") == "bob"
        assert copies_of("soj") == 1
        // EXECUTED IS TERMINAL — a desync cannot re-run the swap
        assert swap("t1").ok == false
        assert_fails transition("t1", "sealed")
        // you cannot seal a trade for an item that does not exist
        assert offer_trade("t2", "ghost-blade", "eve").ok == false
        assert swap("t2").ok == false
    }
}

What the toolchain says

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

$ soma check loot_trade.cell
✓ All checks passed.
$ soma test loot_trade.cell
test TradeTests ...
  ✓ assert mint("soj", "ada").ok == true
  ✓ assert copies_of("soj") == 1
  ✓ assert mint("soj", "eve").ok == false
  ✓ assert_fails copies.set("soj", 2) — raised memory invariant violated on 'copies': copies >= 0 && copies <= 1 — rejected write of 2 (key "soj"); the slot is unchanged
  ✓ assert owner_of("soj") == "ada"
  ✓ assert offer_trade("t1", "soj", "bob").ok == true
  ✓ assert seal_trade("t1").ok == true
  ✓ assert_fails transition("t1", "withdrawn") — raised invalid transition: sealed → withdrawn. Current state: 'sealed'. Valid targets: [executed]
  ✓ assert swap("t1").ok == true
  ✓ assert owner_of("soj") == "bob"
  ✓ assert copies_of("soj") == 1
  ✓ assert swap("t1").ok == false
  ✓ assert_fails transition("t1", "sealed") — raised invalid transition: executed → sealed. Current state: 'executed'. Valid targets: []
  ✓ assert offer_trade("t2", "ghost-blade", "eve").ok == false
  ✓ assert swap("t2").ok == false

15 tests: 15 passed, 0 failed
$ soma verify loot_trade.cell
Verifying loot_trade.cell...

State machine 'trade_flow': 4 states, initial 'proposed'
  States: [executed, proposed, sealed, withdrawn]

  ✓ 4 states, 3 transitions
  ✓ all states reachable from 'proposed'
  ✓ terminal states: [withdrawn, executed]
  ✓ no deadlocks
  ✓ liveness: every state can eventually reach a terminal state
  ✓ think-isolated: CTL safety properties hold regardless of LLM output (2 handlers, 2 literal transitions, 0 dynamic)
  ✓ termination: all 6 handlers structurally terminate
  ⚠ refinement: declared transition `proposed → withdrawn` is never reached by any handler — spec may be aspirational or stale
  ✓ refinement: handler `seal_trade` ⟶ {sealed}
  ✓ refinement: handler `swap` ⟶ {executed}
  · transition sources: `seal_trade`, `swap` 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)

TradePost/invariants:

  ✓ invariant copies >= 0 — writer 'mint' proven by induction (writes (copies.get(item) ?? 0) + 1)
  ⚠ invariant copies >= 0 && copies <= 1 — runtime-checked (computed values): mint → copies [copies <= 1] because `copies` has no known range

10 passed, 2 warnings, 0 failures

  Temporal properties for 'trade_flow':
  ✓ deadlock_free — no deadlocks in any reachable state
Temporal: 1 passed, 0 failed
VERIFY OK

Builtins used

map, transition

Source file: /corpus/games_economy/loot_trade.cell · examples/corpus/games_economy/loot_trade.cell