Skip to content

test(ledger): add proptest coverage for balance math#719

Draft
nicolasburtey wants to merge 1 commit into
mainfrom
nb/proptest-balance-math
Draft

test(ledger): add proptest coverage for balance math#719
nicolasburtey wants to merge 1 commit into
mainfrom
nb/proptest-balance-math

Conversation

@nicolasburtey
Copy link
Copy Markdown
Member

@nicolasburtey nicolasburtey commented May 10, 2026

Summary

Adds property-based test coverage for the pure balance math in `cala-ledger` — the closest CALA analog to "debits == credits" invariants. 11 properties at 4096 cases each, ~0.7s combined runtime. No production code changes.

`Snapshots` (cala-ledger/src/balance/snapshot.rs) — 5 properties

  • `new_snapshot_only_touches_target_cell` — initial snapshot has all-zero balance cells except the (layer, direction) the entry targets.
  • `version_increments_by_one` — every `update_snapshot` bumps version by 1.
  • `sum_of_units_lands_on_target_cell` — N entries to one (layer, direction) yield total == sum of units; cross-layer cells stay zero.
  • `balance_amounts_are_order_independent` — applying any permutation of the same multiset of entries yields the same balance amounts. The additive-commutativity property the per-account double-entry contract rests on.
  • `available_rollup_composition` — `BalanceSnapshot::available` rollup is (settled, settled+pending, settled+pending+encumbrance) on dr and cr.

`AccountBalance` / `BalanceWithDirection` (cala-ledger/src/balance/account_balance.rs) — 6 properties

  • `settled_direction_dependent` — Credit account: `cr - dr`; Debit account: `dr - cr`; sign flip on direction.
  • `pending_and_encumbrance_direction_dependent` — same flip rule.
  • `available_layered_composition` — `available(Settled) == settled()`; `available(Pending) == pending() + settled()`; `available(Encumbrance) == encumbrance() + pending() + settled()`.
  • `account_balance_available_matches_view` — `AccountBalance::available` agrees with `BalanceWithDirection::available` for any direction/layer.
  • `derive_diff_with_zero_is_identity` — diff against zero baseline returns self on all four amount fields.
  • `derive_diff_with_self_is_zero` — diff against self returns zero on all four amount fields.

Notes

  • Tests live as inline `#[cfg(test)] mod proptests` next to the implementations, since `Snapshots::new_snapshot` and `update_snapshot` are `pub(crate)`.
  • `proptest = "1.5"` added to workspace dependencies and to `cala-ledger` `[dev-dependencies]`.
  • All entries in the test fixtures use `Uuid::nil()` for ids since the balance math under test only reads `entry.layer`, `entry.direction`, and `entry.units` — id values don't affect the asserted invariants.
  • Existing 22 unit tests in `balance::` continue to pass.

Test plan

  • `cargo test -p cala-ledger --lib balance::` — 33/33 pass (11 new + 22 existing)
  • CI green

Note

Low Risk
Adds only property-based tests and dev-dependencies (proptest) around balance/snapshot math, with no production logic changes. Main risk is increased test runtime/flakiness or dependency/lockfile churn.

Overview
Adds proptest to the workspace and cala-ledger dev-dependencies (updating Cargo.lock) to enable property-based testing.

Introduces new #[cfg(test)] proptest modules in balance/account_balance.rs and balance/snapshot.rs that exercise invariants for direction-aware signed balances, available() rollups, snapshot update behavior (cell targeting, version increments, sum-of-units), order-independence, and AccountBalance::derive_diff identity/zero-diff properties.

Reviewed by Cursor Bugbot for commit be424d5. Bugbot is set up for automated code reviews on this repo. Configure here.

Adds 11 properties at 4096 cases each across two modules in cala-ledger,
~0.7s combined runtime. No production code changes.

Snapshots (cala-ledger/src/balance/snapshot.rs, 5 properties):
- new_snapshot_only_touches_target_cell — initial snapshot has all-zero
  balance cells except the (layer, direction) the entry targets
- version_increments_by_one — every update_snapshot bumps version by 1
- sum_of_units_lands_on_target_cell — N entries to one (layer, direction)
  yield total == sum of units; cross-layer cells stay zero
- balance_amounts_are_order_independent — applying any permutation of
  the same multiset of entries yields the same balance amounts. The
  additive-commutativity property the per-account double-entry contract
  rests on
- available_rollup_composition — BalanceSnapshot::available rollup is
  (settled, settled+pending, settled+pending+encumbrance) on dr/cr

AccountBalance (cala-ledger/src/balance/account_balance.rs, 6 properties):
- settled_direction_dependent — Credit account: cr-dr; Debit account:
  dr-cr; sign flip on direction
- pending_and_encumbrance_direction_dependent
- available_layered_composition — available(Settled) == settled();
  available(Pending) == pending() + settled();
  available(Encumbrance) == encumbrance() + pending() + settled()
- account_balance_available_matches_view — AccountBalance::available
  agrees with BalanceWithDirection::available for any direction/layer
- derive_diff_with_zero_is_identity — diff against zero baseline returns
  self on all four amount fields
- derive_diff_with_self_is_zero — diff against self returns zero on all
  four amount fields

Adds proptest = "1.5" to workspace dependencies and to cala-ledger
[dev-dependencies].

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

📊 Performance Report

Commit: be424d5
Updated: 2026-05-10 12:21:41 UTC

Cala Performance Benchmark Results (non-representative)

Criterion Benchmark Results (single-threaded)

Benchmark Time per Run Throughput % vs Baseline
post_simple_transaction 7.151ms 139 tx/s 0 (baseline)
post_and_recalculate_ec_account_set 17.119ms 58 tx/s -139.0%
post_and_batch_recalculate_ec_account_set 12.586ms 79 tx/s -75.0%
post_multi_layer_transaction 8.404ms 118 tx/s -17.0%
post_simple_transaction_with_effective_balances 8.606ms 116 tx/s -20.0%
post_simple_transaction_with_skipped_velocity 6.373ms 156 tx/s +10.0%
post_simple_transaction_with_velocity 7.829ms 127 tx/s -9.0%
post_simple_transaction_with_hit_velocity 4.134ms 241 tx/s +42.0%
post_simple_transaction_with_one_account_set 6.666ms 150 tx/s +6.0%
post_simple_transaction_with_five_account_sets 7.994ms 125 tx/s -11.0%
post_simple_transaction_with_ec_account_set 6.334ms 157 tx/s +11.0%

Load Testing Results (parallel-execution)

Scenario tx/s
1 parallel 83.45
2 parallel 137.41
5 parallel 157.60
10 parallel 161.01
20 parallel 159.36
2 contention 121.04
5 contention 139.22
2 acct_sets 111.16
5 acct_sets 123.37

Note: Performance results may vary based on system resources and database state.

Last updated by commit be424d5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant