Skip to content

Commit 2b83e04

Browse files
authored
Merge pull request #443 from raizo07/test/bills-archive-batch-gas-regressions
test: add gas regression suite for bill archive and batch ops
2 parents b0e41bd + 09416ab commit 2b83e04

File tree

5 files changed

+433
-45
lines changed

5 files changed

+433
-45
lines changed

benchmarks/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ Each benchmark outputs JSON with the following structure:
6262
}
6363
```
6464

65+
For CI parsing, gas suites may also emit lines prefixed with:
66+
- `GAS_BENCH_RESULT `: machine-readable benchmark result with baseline/threshold metadata
67+
- `cpu regression ...` / `mem regression ...`: assertion failures when thresholds are exceeded
68+
69+
This keeps `--nocapture` logs easy to scrape in CI while preserving normal Rust test output.
70+
6571
## Remittance Split Schedule Operations
6672

6773
The remittance split contract includes comprehensive benchmarks for schedule lifecycle operations:
@@ -91,6 +97,20 @@ All benchmarks include security validations:
9197
3. **Input Validation**: Tests with valid parameters to ensure proper validation
9298
4. **Edge Cases**: Covers boundary conditions and error scenarios
9399

100+
## Bill Payments Archive and Batch Suite
101+
102+
`bill_payments/tests/gas_bench.rs` includes dedicated regression coverage for:
103+
- `archive_paid_bills/120_paid_1_unpaid_preserved`
104+
- `restore_bill/single_archived_owner_restore`
105+
- `bulk_cleanup_bills/mixed_age_20_of_30_deleted`
106+
- `batch_pay_bills/mixed_batch_50_partial_success`
107+
108+
Security assumptions validated in these benches:
109+
- Archive and cleanup are maintenance operations over paid/archived data only
110+
- Restore is owner-only
111+
- Batch pay preserves owner isolation and deterministic partial success
112+
- Oversized batches are rejected (`BatchTooLarge`)
113+
94114
## Regression Detection
95115

96116
The system automatically detects regressions by comparing current measurements against baselines:

benchmarks/baseline.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,38 @@
102102
"cpu": 1251484,
103103
"mem": 250040,
104104
"description": "Query schedules in worst-case scenario with 50 schedules"
105+
},
106+
{
107+
"contract": "bill_payments",
108+
"method": "archive_paid_bills",
109+
"scenario": "120_paid_1_unpaid_preserved",
110+
"cpu": 640000,
111+
"mem": 150000,
112+
"description": "Archive 120 paid bills while preserving one unpaid bill"
113+
},
114+
{
115+
"contract": "bill_payments",
116+
"method": "restore_bill",
117+
"scenario": "single_archived_owner_restore",
118+
"cpu": 90000,
119+
"mem": 20000,
120+
"description": "Restore one archived bill with owner-only authorization checks"
121+
},
122+
{
123+
"contract": "bill_payments",
124+
"method": "bulk_cleanup_bills",
125+
"scenario": "mixed_age_20_of_30_deleted",
126+
"cpu": 250000,
127+
"mem": 70000,
128+
"description": "Cleanup deletes only older archived entries in a mixed-age archive set"
129+
},
130+
{
131+
"contract": "bill_payments",
132+
"method": "batch_pay_bills",
133+
"scenario": "mixed_batch_50_partial_success",
134+
"cpu": 900000,
135+
"mem": 140000,
136+
"description": "Batch pay with valid, already-paid, unauthorized, and missing bill IDs"
105137
}
106138
,
107139
{

benchmarks/thresholds.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,26 @@
3737
"mem_percent": 10,
3838
"description": "Higher CPU threshold for aggregation operations"
3939
},
40+
"archive_paid_bills": {
41+
"cpu_percent": 15,
42+
"mem_percent": 12,
43+
"description": "Archive scans bill storage and writes archive records"
44+
},
45+
"restore_bill": {
46+
"cpu_percent": 12,
47+
"mem_percent": 10,
48+
"description": "Single-record restore with owner authorization checks"
49+
},
50+
"bulk_cleanup_bills": {
51+
"cpu_percent": 15,
52+
"mem_percent": 12,
53+
"description": "Cleanup iterates archived records and removes matched entries"
54+
},
55+
"batch_pay_bills": {
56+
"cpu_percent": 15,
57+
"mem_percent": 12,
58+
"description": "Mixed batch processing with per-item validation and partial success"
59+
},
4060
"get_all_goals": {
4161
"cpu_percent": 12,
4262
"mem_percent": 12,

bill_payments/src/lib.rs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,14 @@ impl BillPayments {
10851085
Ok(())
10861086
}
10871087

1088+
/// @notice Archive paid bills with `paid_at < before_timestamp`.
1089+
/// @dev Permissionless maintenance operation. Caller must authenticate, but does not need to
1090+
/// own each archived bill. Only paid bills with a historical payment timestamp are moved from
1091+
/// active storage into archival storage.
1092+
/// @param caller Authenticated caller executing archive maintenance.
1093+
/// @param before_timestamp Exclusive upper bound for `paid_at`.
1094+
/// @return Number of bills archived in this call.
1095+
/// @security Unpaid bills are never archived; owner data is preserved on archived records.
10881096
pub fn archive_paid_bills(
10891097
env: Env,
10901098
caller: Address,
@@ -1216,6 +1224,12 @@ impl BillPayments {
12161224
Ok(())
12171225
}
12181226

1227+
/// @notice Permanently delete archived bills with `archived_at < before_timestamp`.
1228+
/// @dev Permissionless maintenance operation for archive compaction.
1229+
/// @param caller Authenticated caller executing cleanup.
1230+
/// @param before_timestamp Exclusive upper bound for `archived_at`.
1231+
/// @return Number of archived records removed.
1232+
/// @security Only archived data is touched; active bills are unaffected.
12191233
pub fn bulk_cleanup_bills(
12201234
env: Env,
12211235
caller: Address,
@@ -1258,25 +1272,16 @@ impl BillPayments {
12581272
Ok(deleted_count)
12591273
}
12601274

1261-
/// Pay multiple bills in a single batch.
1275+
/// @notice Pay multiple bills in one call.
12621276
///
1263-
/// # Semantics: Partial Success
1264-
/// This function implements deterministic partial result reporting. If a bill in the batch
1265-
/// is invalid (e.g., not found, unauthorized, or already paid), it will be skipped,
1266-
/// and an error event will be emitted. Other valid bills in the same batch will still be processed.
1277+
/// @dev Partial-success semantics are deterministic: invalid bill IDs are skipped and reported,
1278+
/// while valid IDs continue processing.
12671279
///
1268-
/// # Arguments
1269-
/// * `env` - The Soroban environment
1270-
/// * `caller` - Address of the bill owner (must authorize)
1271-
/// * `bill_ids` - Vector of bill IDs to pay
1272-
///
1273-
/// # Returns
1274-
/// The number of successfully paid bills.
1275-
///
1276-
/// # Events
1277-
/// - `paid`: Emitted for each successful payment.
1278-
/// - `bill_pay_failed`: Emitted for each failed payment with (bill_id, error_code).
1279-
/// - `batch_pay_summary`: Emitted at the end with (success_count, failure_count).
1280+
/// @param caller Authenticated owner attempting the batch payment.
1281+
/// @param bill_ids Candidate bill IDs to process.
1282+
/// @return Number of successfully paid bills.
1283+
/// @security Cross-owner payments are rejected per item; oversized batches are rejected
1284+
/// before iteration.
12801285
pub fn batch_pay_bills(env: Env, caller: Address, bill_ids: Vec<u32>) -> Result<u32, Error> {
12811286
caller.require_auth();
12821287
Self::require_not_paused(&env, pause_functions::PAY_BILL)?;

0 commit comments

Comments
 (0)