|
| 1 | +# Gas Usage: Batch Payment Loop |
| 2 | + |
| 3 | +## Problem (issue #831) |
| 4 | + |
| 5 | +The original `batch_payment` implementation performed **O(N) storage round-trips** for operations that could be collapsed into a single read+write: |
| 6 | + |
| 7 | +| Operation | Original (per N-item batch) | Optimised | |
| 8 | +|---|---|---| |
| 9 | +| `get_global_stats` | N reads | 1 read | |
| 10 | +| `set_global_stats` | N writes | 1 write | |
| 11 | +| `get_payer_payment_ids` | N reads | 1 read | |
| 12 | +| `set_payer_payment_ids` | N writes | 1 write | |
| 13 | +| `env.ledger().timestamp()` | N calls | 1 call | |
| 14 | + |
| 15 | +For the maximum batch size of **10 items** this eliminated **18 redundant ledger entry operations** and **9 redundant timestamp host-function calls**. |
| 16 | + |
| 17 | +## Changes |
| 18 | + |
| 19 | +### `contracts/lumenflow/src/lib.rs` — `batch_payment` |
| 20 | + |
| 21 | +1. **Validation-first pass** — all per-item checks (token allowance, duplicate |
| 22 | + order ID, merchant existence/activity, signature) run in a first pass _before_ |
| 23 | + any state is mutated. This prevents partial state writes on failure. |
| 24 | + |
| 25 | +2. **Hoisted globals** — `env.ledger().timestamp()`, `get_global_stats`, and |
| 26 | + `get_payer_payment_ids` are called once before the loop. Counters are |
| 27 | + accumulated in-memory, and the results are written after the loop completes. |
| 28 | + |
| 29 | +### `contracts/lumenflow/src/storage.rs` |
| 30 | + |
| 31 | +- Added `set_payer_payment_ids(env, payer, ids)` — writes the full payer ID |
| 32 | + list in a single operation, used by the optimised batch path. |
| 33 | + |
| 34 | +## Measuring gas locally |
| 35 | + |
| 36 | +Soroban's test framework exposes CPU instruction and memory byte budgets when |
| 37 | +the `SOROBAN_TEST_BUDGET` environment variable is set: |
| 38 | + |
| 39 | +```bash |
| 40 | +SOROBAN_TEST_BUDGET=1 cargo test test_batch_payment -- --nocapture 2>&1 \ |
| 41 | + | grep -E "cpu_insns|mem_bytes" |
| 42 | +``` |
| 43 | + |
| 44 | +Compare the output against the `main` branch to quantify the savings. |
| 45 | + |
| 46 | +## Test coverage |
| 47 | + |
| 48 | +Six new unit tests in `test.rs`: |
| 49 | + |
| 50 | +| Test | Verifies | |
| 51 | +|---|---| |
| 52 | +| `test_batch_payment_single_item` | Basic correctness | |
| 53 | +| `test_batch_payment_multiple_items_stats_correct` | Stats accumulated correctly across items | |
| 54 | +| `test_batch_payment_payer_history_contains_all_items` | Payer index contains all IDs | |
| 55 | +| `test_batch_payment_max_10_items_enforced` | Batch size limit | |
| 56 | +| `test_batch_payment_fails_on_duplicate_order_id` | Duplicate rejection | |
| 57 | +| `test_batch_payment_validation_pass_prevents_partial_state` | No partial state writes on error | |
0 commit comments