Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ See [cli/README.md](cli/README.md) for usage instructions.
- **orchestrator**: Cross-contract coordination
- **reporting**: Financial reporting and insights

### Reporting Archive Cleanup Safety

The `reporting` contract cleanup flow is expected to be idempotent and safe for
retries:

- `cleanup_old_reports` can be called repeatedly with the same cutoff without
duplicate deletions.
- Repeated cleanup calls return `0` once matching archives are already removed.
- Storage counters are recalculated from source maps after mutations to avoid
counter drift/corruption.
- Archive and cleanup operations are admin-gated.

## Prerequisites

- Rust (latest stable version)
Expand Down
40 changes: 32 additions & 8 deletions benchmarks/baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,56 @@
"contract": "bill_payments",
"method": "get_total_unpaid",
"scenario": "100_bills_50_cancelled",
"cpu": 0,
"mem": 0,
"cpu": 1416937,
"mem": 301710,
"description": "Worst-case scenario with 100 bills, 50 cancelled"
},
{
"contract": "savings_goals",
"method": "batch_add_to_goals",
"scenario": "50_items",
"cpu": 4602200,
"mem": 904612,
"description": "Batch add funds to 50 various goals"
},
{
"contract": "savings_goals",
"method": "create_savings_schedule",
"scenario": "single_schedule",
"cpu": 124297,
"mem": 21437,
"description": "Create a single savings schedule"
},
{
"contract": "savings_goals",
"method": "execute_due_savings_schedules",
"scenario": "50_schedules",
"cpu": 7072018,
"mem": 1445311,
"description": "Execute 50 due savings schedules in a single invocation"
},
{
"contract": "savings_goals",
"method": "get_all_goals",
"scenario": "100_goals_single_owner",
"cpu": 0,
"mem": 0,
"cpu": 3468767,
"mem": 429349,
"description": "Retrieve 100 goals for a single owner"
},
{
"contract": "insurance",
"method": "get_total_monthly_premium",
"scenario": "100_active_policies",
"cpu": 0,
"mem": 0,
"cpu": 2196650,
"mem": 415609,
"description": "Calculate total premium for 100 active policies"
},
{
"contract": "family_wallet",
"method": "configure_multisig",
"scenario": "9_signers_threshold_all",
"cpu": 0,
"mem": 0,
"cpu": 343463,
"mem": 69170,
"description": "Configure multisig with 9 signers requiring all signatures"
},
{
Expand Down
8 changes: 8 additions & 0 deletions bill_payments/proptest-regressions/lib.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 00945c7774e21939868c61f6452d4ebda5f89e26e52a3bae86a5a69c716d2f43 # shrinks to now = 2000000, n_overdue = 1, n_future = 0
cc a0e060a9af49b11c4d0540c419352d1081e79907d5107c386534ad5d67446962 # shrinks to base_due = 1000000, pay_offset = 1, freq_days = 1
20 changes: 8 additions & 12 deletions bill_payments/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,6 @@ impl BillPayments {
Ok(())
}

/// Clamp a caller-supplied limit to [1, MAX_PAGE_LIMIT].
/// A value of 0 is treated as DEFAULT_PAGE_LIMIT.

// -----------------------------------------------------------------------
// Pause / upgrade
// -----------------------------------------------------------------------
Expand Down Expand Up @@ -627,6 +624,9 @@ impl BillPayments {
env.storage()
.instance()
.set(&symbol_short!("NEXT_ID"), &next_id);
env.storage()
.instance()
.set(&symbol_short!("NEXT_ID"), &next_id);
Self::adjust_unpaid_total(&env, &bill_owner, amount);

// Emit event for audit trail
Expand Down Expand Up @@ -947,7 +947,6 @@ impl BillPayments {
Ok(())
}


// -----------------------------------------------------------------------
// Backward-compat helpers
// -----------------------------------------------------------------------
Expand Down Expand Up @@ -1658,7 +1657,9 @@ impl BillPayments {
.get(&STORAGE_UNPAID_TOTALS)
.unwrap_or_else(|| Map::new(env));
let current = totals.get(owner.clone()).unwrap_or(0);
let next = current.checked_add(delta).expect("overflow");
let next = current
.checked_add(delta)
.unwrap_or_else(|| panic!("overflow"));
totals.set(owner.clone(), next);
env.storage()
.instance()
Expand All @@ -1673,6 +1674,7 @@ impl BillPayments {
mod test {
use super::*;
use proptest::prelude::*;
use remitwise_common::MAX_PAGE_LIMIT;
use soroban_sdk::{
testutils::{Address as _, Ledger},
Env, String,
Expand Down Expand Up @@ -2910,9 +2912,6 @@ mod test {
// Strict Owner Authorization Lifecycle Tests
// -----------------------------------------------------------------------

/// ### Test: `test_create_bill_no_auth_fails`
/// **Objective**: Verify that `create_bill` reverts if the owner doesn't authorize the call.
/// **Expected**: Reverts with a Soroban AuthError.
#[test]
#[should_panic(expected = "Error(Auth, InvalidAction)")]
fn test_create_bill_no_auth_fails() {
Expand Down Expand Up @@ -2964,9 +2963,6 @@ mod test {
assert_eq!(result, Err(Ok(Error::Unauthorized)));
}

/// ### Test: `test_pay_bill_no_auth_fails`
/// **Objective**: Verify that `pay_bill` reverts if the caller is the owner but does not authorize the call.
/// **Expected**: Reverts with a Soroban AuthError.
#[test]
#[should_panic(expected = "Error(Auth, InvalidAction)")]
fn test_pay_bill_no_auth_fails() {
Expand Down Expand Up @@ -3132,7 +3128,7 @@ mod test {
let client = BillPaymentsClient::new(&env, &cid);
let admin = Address::generate(&env);

client.bulk_cleanup_bills(&admin, &1000000);
client.bulk_cleanup_bills(&admin, &1_000_000);
}

#[test]
Expand Down
Loading