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
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ members = [
"orchestrator",
"cli",
"scenarios",
"remitwise-common",

"testutils",
<<<<<<< test/insurance-auth-lifecycle-matrix
Expand Down Expand Up @@ -48,6 +49,7 @@ insurance = { path = "./insurance" }
family_wallet = { path = "./family_wallet" }
reporting = { path = "./reporting" }
orchestrator = { path = "./orchestrator" }
remitwise-common = { path = "./remitwise-common" }

[dev-dependencies]
soroban-sdk = { version = "=21.7.7", features = ["testutils"] }
Expand Down
116 changes: 54 additions & 62 deletions bill_payments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,12 @@ Creates a new bill with currency specification.
- `amount`: Payment amount (must be positive)
- `due_date`: Due date as Unix timestamp
- `recurring`: Whether this is a recurring bill
- `frequency_days`: Frequency in days for recurring bills (0 < frequency_days <= 36500)
- `frequency_days`: Frequency in days for recurring bills (> 0 if recurring)
- `currency`: Currency code (e.g., "XLM", "USDC", "NGN"). Case-insensitive, whitespace trimmed, defaults to "XLM" if empty.

**Returns:** Bill ID on success

**Errors:** InvalidAmount, InvalidFrequency (if 0 or > 36500), InvalidCurrency, InvalidDueDate (if arithmetic overflows on recurrence)
**Errors:** InvalidAmount, InvalidFrequency, InvalidCurrency

**Currency Normalization:**
- Converts to uppercase (e.g., "usdc" → "USDC")
Expand All @@ -133,24 +133,6 @@ Marks a bill as paid.

**Errors:** BillNotFound, BillAlreadyPaid, Unauthorized

#### `batch_pay_bills(env, caller, bill_ids) -> Result<u32, Error>`
Pays multiple bills in a single batch with deterministic partial success reporting.

**Semantics:**
- **Partial Success**: If a bill is invalid (not found, unauthorized, or already paid), it is skipped and an error event is emitted. Valid bills are still processed.
- **Atomic Validation**: Initial checks like `BatchTooLarge` or `ContractPaused` still revert the entire batch.

**Parameters:**
- `caller`: Address of the bill owner (must authorize)
- `bill_ids`: Vector of bill IDs to pay

**Returns:** Number of successfully paid bills.

**Events:**
- `paid`: Per-bill success event.
- `f_pay_*`: Per-bill failure events (e.g., `f_pay_id`, `f_pay_auth`, `f_pay_pd`).
- `batch_res`: Final summary with `(success_count, failure_count)`.

#### `get_bill(env, bill_id) -> Option<Bill>`
Retrieves a bill by ID.

Expand Down Expand Up @@ -330,9 +312,51 @@ let overdue_page = bill_payments::get_overdue_bills(env, 0, 10);

## Events

The contract emits events for audit trails:
- `BillEvent::Created`: When a bill is created
- `BillEvent::Paid`: When a bill is paid
The contract emits **typed, versioned events** using the `RemitwiseEvents` helper from `remitwise-common`. Every event follows a standardized schema to ensure downstream indexers and consumers can reliably decode event data across contract upgrades.

### Topic Convention

All events use a 4-topic tuple:

```text
("Remitwise", category: u32, priority: u32, action: Symbol)
```

| Position | Field | Description |
|----------|------------|----------------------------------------------------|
| 0 | Namespace | Always `"Remitwise"` — immutable across versions |
| 1 | Category | `0`=Transaction, `1`=State, `3`=System |
| 2 | Priority | `0`=Low, `1`=Medium, `2`=High |
| 3 | Action | Short symbol: `"created"`, `"paid"`, `"canceled"`, etc |

### Event Types

| Operation | Event Struct | Action Symbol | Category | Priority |
|------------------------|-----------------------|---------------|-------------|----------|
| `create_bill` | `BillCreatedEvent` | `"created"` | State | Medium |
| `pay_bill` | `BillPaidEvent` | `"paid"` | Transaction | High |
| `cancel_bill` | `BillCancelledEvent` | `"canceled"` | State | Medium |
| `archive_paid_bills` | `BillsArchivedEvent` | `"archived"` | System | Low |
| `restore_bill` | `BillRestoredEvent` | `"restored"` | State | Medium |
| `set_version` | `VersionUpgradeEvent` | `"upgraded"` | System | High |
| `batch_pay_bills` | `BillPaidEvent` × N | `"paid"` | Transaction | High |
| `pause` | `()` | `"paused"` | System | High |
| `unpause` | `()` | `"unpaused"` | System | High |

### Schema Versioning & Backward Compatibility

Every event struct includes a `schema_version` field (currently `1`) that:

1. Allows downstream consumers to branch decoding logic per version.
2. Guarantees that **field ordering is append-only** — new fields are always added at the end.
3. Is enforced at **compile time** via `assert_min_fields!` macros in `events.rs`.

**Guarantees:**
- Topic symbols (e.g., `"created"`, `"paid"`) are **never renamed** across versions.
- The 4-topic structure `(Namespace, Category, Priority, Action)` is **immutable**.
- Existing fields are **never removed or reordered** — only new optional fields may be appended.
- All events are **deterministically reproducible** from the same contract state.


## Integration Patterns

Expand All @@ -354,43 +378,11 @@ Bills can represent insurance premiums, working alongside the insurance contract

## Security Considerations

- All functions require proper authorization
- Owners can only manage their own bills
- Input validation prevents invalid states
- All functions require proper authorization (`require_auth()`)
- Owners can only manage their own bills (enforced by explicit owner check)
- Input validation prevents invalid states (amount, frequency, due_date, currency)
- Currency codes are validated (1-12 alphanumeric chars) and normalized
- Event payloads contain only bill metadata — no sensitive data leakage
- Storage TTL is managed to prevent bloat
## Pause & Security Controls

The Bill Payments contract includes advanced pause controls for operational security and maintenance.

### Global Pause
Pausing the entire contract blocks all state-changing operations:
- `pause(env, admin)`: Freezes all contract activities.
- `unpause(env, admin)`: Resumes contract operations.

### Function-Level Granularity
Individual functions can be paused without affecting the rest of the contract:
- `pause_function(env, admin, func_symbol)`: Pauses a specific function (e.g., `CREATE_BILL`).
- `unpause_function(env, admin, func_symbol)`: Unpauses a specific function.

**Supported Function Symbols:**
- `CREATE_BILL`: `symbol_short!("crt_bill")`
- `PAY_BILL`: `symbol_short!("pay_bill")`
- `CANCEL_BILL`: `symbol_short!("can_bill")`
- `ARCHIVE`: `symbol_short!("archive")`
- `RESTORE`: `symbol_short!("restore")`

### Emergency Controls
- `emergency_pause_all(env, admin)`: Pauses both the global contract and all individual functions simultaneously.

### Scheduled Unpause
- `schedule_unpause(env, admin, at_timestamp)`: Sets a future timestamp for when the contract can be unpaused, providing a security time-lock.

### Administrative Roles
- `set_pause_admin(env, caller, new_admin)`: Sets or transfers the administrative role responsible for pause controls.
- `set_upgrade_admin(env, caller, new_admin)`: Sets or transfers the administrative role responsible for contract upgrades.

### Security Notes
- Global pause blocks all state-changing methods; read-only queries remain available.
- Function-level pause blocks only the specified symbol, leaving other functions operational.
- Scheduled unpause requires a future timestamp; unpause attempts before that time are rejected.
- Pause admin keys should be secured (multisig or cold storage recommended).
- Schema version in events prevents silent breaking changes to consumers
- Compile-time `assert_min_fields!` macros catch accidental field-count regressions
Loading
Loading