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: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
echo "Installed via Homebrew"
else
echo "Homebrew install failed, installing via cargo..."
cargo install --locked --version 21.0.0 soroban-cli
cargo install --locked --version 21.1.0 soroban-cli
fi
fi
# Try both commands in case Homebrew installs it as 'stellar'
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ default-members = [
resolver = "2"

[dependencies]
soroban-sdk = "21.0.0"
soroban-sdk = "21.1.1"
remittance_split = { path = "./remittance_split" }
savings_goals = { path = "./savings_goals" }
bill_payments = { path = "./bill_payments" }
Expand All @@ -45,7 +45,7 @@ reporting = { path = "./reporting" }
orchestrator = { path = "./orchestrator" }

[dev-dependencies]
soroban-sdk = { version = "21.0.0", features = ["testutils"] }
soroban-sdk = { version = "21.1.1", features = ["testutils"] }
[profile.release]
opt-level = "z"
overflow-checks = true
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,21 @@ Manages family roles, spending controls, multisig approvals, and emergency trans

For full design details, see [docs/family-wallet-design.md](docs/family-wallet-design.md).

### Data Migration

Utilities for contract data export and import (JSON, Binary, CSV).

**Key Functions:**

- `export_to_csv`: Export savings goals to a tabular CSV format.
- `import_goals_from_csv`: Import goals from CSV with strict schema validation.
- `export_to_json` / `import_from_json`: Versioned snapshot migration.

**CSV Hardening:**
- Strict header validation (exact match and order).
- Row-level type and value validation (e.g., non-negative amounts).
- Rejection of ambiguous or malformed data.

## Events

All contracts emit events for important state changes, enabling real-time tracking and frontend integration. Events follow Soroban best practices and include:
Expand Down
4 changes: 2 additions & 2 deletions bill_payments/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]

[dependencies]
soroban-sdk = "21.0.0"
soroban-sdk = "21.1.1"
remitwise-common = { path = "../remitwise-common" }

[dev-dependencies]
proptest = "1.10.0"
soroban-sdk = { version = "21.0.0", features = ["testutils"] }
soroban-sdk = { version = "21.1.1", features = ["testutils"] }
testutils = { path = "../testutils" }


66 changes: 55 additions & 11 deletions bill_payments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ This section provides a minimal example of how to interact with the Bill Payment
let bill_id = client.create_bill(
&owner_address,
&String::from_str(&env, "Internet Bill"),
&500_0000000,
&(env.ledger().timestamp() + 2592000),
&false,
&0,
&String::from_str(&env, "XLM")
&500_0000000,
&(env.ledger().timestamp() + 2592000),
&false,
&0,
&None,
&String::from_str(&env, "XLM"),
);

```
Expand Down Expand Up @@ -74,7 +75,8 @@ pub struct Bill {
- `BillAlreadyPaid = 2`: Attempting to pay an already paid bill
- `InvalidAmount = 3`: Amount is zero or negative
- `InvalidFrequency = 4`: Recurring bill has zero frequency
- `Unauthorized = 5`: Caller is not the bill owner
- `Unauthorized = 5`: Caller is not the bill owner (or not the archived row owner where required)
- `InconsistentBillData = 15`: Map key and embedded `id` disagree, or restore would collide with inconsistent active state (see Archive and restore)

### Functions

Expand Down Expand Up @@ -223,9 +225,51 @@ let bills_allocation = split_amounts.get(2).unwrap(); // bills percentage
### With Insurance Contract
Bills can represent insurance premiums, working alongside the insurance contract for comprehensive financial management.

## Security Considerations
## Archive and restore (Issue #272)

- All functions require proper authorization
- Owners can only manage their own bills
- Input validation prevents invalid states
- Storage TTL is managed to prevent bloat
Paid bills can be moved to **archived** storage, restored to active storage, or **cleaned up** (permanently removed from the archive). These flows enforce **owner affinity**: only the recorded bill owner can mutate or read sensitive archive data for their rows.

### Functions

| Function | Authorization | Notes |
|----------|----------------|--------|
| `archive_paid_bills(caller, before_timestamp)` | `caller.require_auth()` | Archives only rows where `bill.owner == caller`, `bill.paid`, `paid_at < before_timestamp`, and **map key `id` matches `bill.id`** (integrity). |
| `restore_bill(caller, bill_id)` | `caller.require_auth()` | Requires archived row exists, `ArchivedBill.owner == caller`, **`ArchivedBill.id == bill_id`**, and no conflicting active bill at `bill_id`. |
| `bulk_cleanup_bills(caller, before_timestamp)` | `caller.require_auth()` | Deletes only archived rows with `owner == caller`, `archived_at < before_timestamp`, and **`id` key matches `ArchivedBill.id`**. |
| `get_archived_bills(owner, cursor, limit)` | **`owner.require_auth()`** | Listing is not anonymous; the `owner` address must sign. |
| `get_archived_bill(caller, bill_id)` | **`caller.require_auth()`** | Returns `Ok(archived)` only if the archived row exists **and** `archived.owner == caller` and ids are consistent; otherwise `BillNotFound`, `Unauthorized`, or `InconsistentBillData`. |

### Error code

- **`InconsistentBillData = 15`**: Thrown when a stored `Bill` / `ArchivedBill` has an `id` field that does not match its map key, when restoring would collide with an existing active row for the same id, or when cleanup detects the same mismatch. This blocks silent cross-owner or corrupted-key exploitation.

### NatSpec-style notes (contract source)

The contract uses `@notice`, `@param`, `@return`, `@dev`, and `@errors` on archive-related entrypoints in `bill_payments/src/lib.rs` for reviewers and tooling.

### Client migration

- **`get_archived_bill`**: Signature is now `(env, caller, bill_id) -> Result<ArchivedBill, Error>` (previously a public `Option` by `bill_id` only). Callers must pass the **authenticated owner** as `caller`.

## Security considerations

- **Authorization**: Mutating functions use `require_auth()` on the acting address; archive listings require the **same** owner address to authorize reads.
- **Owner affinity**: Archive, restore, and cleanup iterate or select rows **only** for `caller` / authenticated `owner`; cross-owner manipulation returns `Unauthorized` or fails integrity checks.
- **Consistency**: Key/id checks reduce the risk of inconsistent maps being used to move or read another user’s data.
- **Input validation**: Amounts, due dates, and recurrence rules are validated on create/update paths.
- **Storage TTL**: Instance and archive TTL bumps keep data available without unbounded retention of abandoned state.

### Validation (tests)

Run from the workspace root:

```bash
cargo test -p bill_payments
```

All `bill_payments` unit tests, integration tests under `bill_payments/tests/`, and Issue #272 cases in `bill_payments/src/test.rs` should pass. Focused security tests cover: non-owner `get_archived_bill` / `restore_bill`, and `bulk_cleanup` not removing another owner’s archived rows.

**Security assumptions**

- Stellar account signatures are unforgeable; `require_auth()` correctly binds the caller to the intended `Address`.
- Archive state is only produced through `archive_paid_bills` (paid bills, owner-checked); callers should treat `InconsistentBillData` as a fatal integrity signal and investigate off-chain.
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 e423d8ab667995ae14b0f72357096ef13d5841ca50ec0bf841863d911e540f49 # shrinks to now = 2000000, n_overdue = 1, n_future = 0
cc 98a9604dfd893d4ba76f8439d887e9062933542f533a9563c9a7ab619e62c37e # shrinks to base_due = 1000000, pay_offset = 1, freq_days = 1
Loading