Test: 42-wallet settlement
The question: does batched settlement stay exact when a book needs many transactions? That means batches sent in any order, sent twice, overlapping, and with a pro-rata tier split across batch boundaries.
The book
Section titled “The book”Auction C8pGy4va4btnEQTMbbptnwiFNxnoH2d9md5tSuMff3Mb: 42 orders from 42 distinct wallets.
- 22 buys of 10 @ 100.37;
- 18 sells of 10 @ 90.00;
- 2 sells of 10 @ 100.37;
- interleaved, so the buys sit at even order indices across the whole book.
Cleared at 100.37 × 200 shares. The 22 buys form one tier of 220 shares competing for 200, so every buy is pro-rated. Because the buys are spread across the book, the pro-rata tier spans every batch boundary.
The fills and money legs, as the cross fixed them
Section titled “The fills and money legs, as the cross fixed them”- Buy fills: 909,090,910 raw units for orders #0 and #2, and 909,090,909 for the other 20. The two leftover units from floor division go to the lowest indices. Buy total 20,000,000,000 = sell total 20,000,000,000.
- Buy charges: 912,454,547, 912,454,546 or 912,454,545 micro-dollars, by the same rule. Buyers charged 20,074,000,000 = sellers paid 20,074,000,000, exactly.
Settlement, deliberately abused
Section titled “Settlement, deliberately abused”Settlement ran in 7-order batches, the most that fit in one transaction with distinct owners:
| Step | Result |
|---|---|
| Batch #3 [21–27] first | 7/42 settled, status stays Cleared, settle path locks to clear |
| Batch #3 again | A no-op: settled count and both vaults byte-identical |
compute_clearing mid-settlement |
A no-op: clearing price, every fill and every money leg unchanged |
cancel_and_refund [0–6] |
Refused: SettlementPathLocked |
| Overlap [25–31]: 3 settled, 4 not | Settles exactly the 4 new orders (11/42) |
| Batches #5, #4, #2, #1, #0 | 18 → 21 → 28 → 35 → 42/42, status Settled |
Final: both vaults exactly zero, and all 42 wallets’ balance changes match their fill and money leg to the unit. Buyers show +fill shares and −charge; sellers −fill shares and +proceeds. Each was measured against a snapshot taken before the auction opened.
Every settle transaction was 1,162 bytes (limit 1,232) and used 70k–129k compute units.
The indicative price moved as the book filled: 100.37 with no cross, 95.185 once the first sell arrived, then back to 100.37 once the buy side outgrew the sells at 90. The final indicative price matched compute_clearing.
The bugs this work found and fixed
Section titled “The bugs this work found and fixed”The first multi-batch run was meant to confirm the crank loop. Instead it found three accounting bugs, because earlier tests had only used books whose numbers divided evenly.
- The two sides could fill to different totals. Pro-rating with floor division gave 22 buyers 9 each: 198 against the sell side’s 200. Sellers would have been paid for shares buyers never paid for. Fix: the flooring remainder is handed back one unit per order, lowest index first, so each side fills to exactly the executable volume.
- Per-order rounding could leave the quote vault short. Two buyers each owing half a unit floor to 0, against a seller owed 1. Fix: buy escrow rounds up. Every order’s money leg is decided once, at the cross. Sellers get floor(fill × price), and buyers are charged exactly that total between them.
- A refund after partial settlement could double-spend the vault. A seller whose shares went to a buyer in one batch could be refunded their full escrow in the next. Fix: an auction commits to one wind-down path on its first batch, and the other is refused thereafter.
And one measurement correction: the batch limit is 7, not 18. Phase 0 measured 18–19 transfers per transaction, each adding one account. settle_batch adds three accounts per order: the order and the owner’s two token accounts. Measured on the real instruction, 7 orders fit with distinct owners, and 8 do not (1,275 bytes against a limit of 1,232).
A separate earlier run, a real 38-order, 38-owner book, was cranked to completion by the production keeper in six batches (7, 7, 7, 7, 7, 3). It settled in order and never exercised a pro-rata tier, so it is not the stress test. Its signatures are in Transaction index.
Signatures
Section titled “Signatures”| Step | Signature |
|---|---|
initialize_auction |
3ckf99Ax…QGFJp5ci |
compute_clearing |
2gzwsuTp…wHFnXwrN |
| settle #3 [21–27] | 5Ty7nfDD…5TVYrggX |
| settle #3 again (no-op) | 3zCC9tLi…p1H4tnzX |
compute_clearing mid-settlement (no-op) |
2YhJpaFX…nkXobbA5 |
| settle overlap [25–31] | M4JDaNje…1Xcmu6Tp |
| settle #5 [35–41] | 5unU2SE8…MNoqZkqV |
| settle #4 [28–34] | tVXozm3q…9Y4jEBQi |
| settle #2 [14–20] | 3QLGU1Wo…5MuAFALv |
| settle #1 [7–13] | 4ggsfkUk…68fxp1rY |
| settle #0 [0–6] | JPyVhrxU…xTEA9u4a |
The 42 order signatures are in the run log of uncross/scripts/devnet-multibatch.mjs, and placement is reproducible with that script.
Sources: docs/phase2.md (Task 1, multi-batch run, stress run), uncross/programs/uncross/src/clearing.rs (tests both_sides_fill_to_the_same_total, per_order_floor_cannot_starve_the_seller, realistic_book_balances_at_a_non_round_price).