Skip to content
Merged
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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,15 @@ Bill and insurance events include `external_ref` where applicable for off-chain

Bill and insurance events include `external_ref` where applicable for off-chain linking.

### Reporting

Provides summarized views of user financial data across all ecosystem contracts.

**Key Features:**

- `get_remittance_summary`: Aggregates split, savings, bills, and insurance data into a single summary
- **Graceful Degradation**: Implements a `DataAvailability` indicator (`Complete`, `Partial`, `Missing`) ensuring summary queries return available data without a hard panic if upstream contracts are unresponsive, unconfigured, or fail.

### Family Wallet

Manages family roles, spending controls, multisig approvals, and emergency transfer policies.
Expand Down
30 changes: 28 additions & 2 deletions reporting/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ pub struct TrendData {
pub change_percentage: i32, // Can be negative
}

/// Indicates the completeness of the data retrieved from external contracts
#[contracttype]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DataAvailability {
/// All external calls succeeded and data is complete
Complete = 0,
/// Some external calls failed or returned partial data
Partial = 1,
/// Critical external calls failed or addresses not configured, data is missing/default
Missing = 2,
}

/// Remittance summary report
#[contracttype]
#[derive(Clone)]
Expand All @@ -52,6 +64,7 @@ pub struct RemittanceSummary {
pub category_breakdown: Vec<CategoryBreakdown>,
pub period_start: u64,
pub period_end: u64,
pub data_availability: DataAvailability,
}

/// Savings progress report
Expand Down Expand Up @@ -489,8 +502,20 @@ impl ReportingContract {
let addresses: ContractAddresses = env
.storage()
.instance()
.get(&symbol_short!("ADDRS"))
.unwrap_or_else(|| panic!("Contract addresses not configured"));
.get(&symbol_short!("ADDRS"));

if addresses.is_none() {
return RemittanceSummary {
total_received: total_amount,
total_allocated: total_amount,
category_breakdown: Vec::new(&env),
period_start,
period_end,
data_availability: DataAvailability::Missing,
};
}

let addresses = addresses.unwrap();

let split_client = RemittanceSplitClient::new(env, &addresses.remittance_split);
let split_percentages = split_client.get_split();
Expand Down Expand Up @@ -518,6 +543,7 @@ impl ReportingContract {
category_breakdown: breakdown,
period_start,
period_end,
data_availability: availability,
}
}

Expand Down
82 changes: 79 additions & 3 deletions reporting/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,8 @@ fn test_configure_addresses_succeeds() {

#[test]
fn test_configure_addresses_unauthorized() {
let env = create_test_env();
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register_contract(None, ReportingContract);
let client = ReportingContractClient::new(&env, &contract_id);
let admin = Address::generate(&env);
Expand Down Expand Up @@ -332,6 +333,7 @@ fn test_get_remittance_summary() {
assert_eq!(summary.category_breakdown.len(), 4);
assert_eq!(summary.period_start, period_start);
assert_eq!(summary.period_end, period_end);
assert_eq!(summary.data_availability, DataAvailability::Complete);

// Check category breakdown
let spending = summary.category_breakdown.get(0).unwrap();
Expand All @@ -340,6 +342,78 @@ fn test_get_remittance_summary() {
assert_eq!(spending.percentage, 50);
}

#[test]
fn test_get_remittance_summary_missing_addresses() {
let env = soroban_sdk::Env::default();
env.mock_all_auths();
let contract_id = env.register_contract(None, ReportingContract);
let client = ReportingContractClient::new(&env, &contract_id);
let user = soroban_sdk::Address::generate(&env);

// Purposefully DO NOT call client.init() or client.configure_addresses()

let total_amount = 10000i128;
let period_start = 1704067200u64;
let period_end = 1706745600u64;

let summary = client.get_remittance_summary(&user, &total_amount, &period_start, &period_end);

assert_eq!(summary.total_received, 10000);
assert_eq!(summary.category_breakdown.len(), 0);
assert_eq!(summary.data_availability, DataAvailability::Missing);
}

mod failing_remittance_split {
use soroban_sdk::{contract, contractimpl, Env, Vec};
#[contract]
pub struct FailingRemittanceSplit;
#[contractimpl]
impl FailingRemittanceSplit {
pub fn get_split(_env: &Env) -> Vec<u32> {
panic!("Remote call failing to simulate Partial Data");
}
pub fn calculate_split(_env: Env, _total_amount: i128) -> Vec<i128> {
panic!("Remote call failing to simulate Partial Data");
}
}
}

#[test]
fn test_get_remittance_summary_partial_data() {
let env = soroban_sdk::Env::default();
env.mock_all_auths();
let contract_id = env.register_contract(None, ReportingContract);
let client = ReportingContractClient::new(&env, &contract_id);
let admin = soroban_sdk::Address::generate(&env);
let user = soroban_sdk::Address::generate(&env);

client.init(&admin);

// Register FAILING mock contract
let failing_split_id = env.register_contract(None, failing_remittance_split::FailingRemittanceSplit);
let savings_goals_id = env.register_contract(None, savings_goals::SavingsGoalsContract);
let bill_payments_id = env.register_contract(None, bill_payments::BillPayments);
let insurance_id = env.register_contract(None, insurance::Insurance);
let family_wallet = soroban_sdk::Address::generate(&env);

client.configure_addresses(
&admin,
&failing_split_id,
&savings_goals_id,
&bill_payments_id,
&insurance_id,
&family_wallet,
);

let total_amount = 10000i128;
let summary = client.get_remittance_summary(&user, &total_amount, &0, &0);

assert_eq!(summary.total_received, 10000);
assert_eq!(summary.category_breakdown.len(), 4); // Created empty via fallback
assert_eq!(summary.category_breakdown.get(0).unwrap().amount, 0);
assert_eq!(summary.data_availability, DataAvailability::Partial);
}

#[test]
fn test_get_savings_report() {
let env = Env::default();
Expand Down Expand Up @@ -954,7 +1028,8 @@ fn test_storage_stats_regression_across_archive_and_cleanup_cycles() {

#[test]
fn test_archive_unauthorized() {
let env = create_test_env();
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register_contract(None, ReportingContract);
let client = ReportingContractClient::new(&env, &contract_id);
let admin = Address::generate(&env);
Expand All @@ -969,7 +1044,8 @@ fn test_archive_unauthorized() {

#[test]
fn test_cleanup_unauthorized() {
let env = create_test_env();
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register_contract(None, ReportingContract);
let client = ReportingContractClient::new(&env, &contract_id);
let admin = Address::generate(&env);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@
},
"ext": "v0"
},
518401
100000
]
],
[
Expand Down Expand Up @@ -230,7 +230,7 @@
},
"ext": "v0"
},
518401
100000
]
]
]
Expand Down
36 changes: 34 additions & 2 deletions reporting/test_snapshots/tests/test_archive_old_reports.1.json
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,14 @@
]
}
},
{
"key": {
"symbol": "data_availability"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "period_end"
Expand Down Expand Up @@ -875,7 +883,7 @@
},
"ext": "v0"
},
518401
100000
]
],
[
Expand Down Expand Up @@ -1288,7 +1296,7 @@
},
"ext": "v0"
},
518401
100000
]
]
]
Expand Down Expand Up @@ -3256,6 +3264,14 @@
]
}
},
{
"key": {
"symbol": "data_availability"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "period_end"
Expand Down Expand Up @@ -3760,6 +3776,14 @@
]
}
},
{
"key": {
"symbol": "data_availability"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "period_end"
Expand Down Expand Up @@ -4351,6 +4375,14 @@
]
}
},
{
"key": {
"symbol": "data_availability"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "period_end"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,14 @@
]
}
},
{
"key": {
"symbol": "data_availability"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "period_end"
Expand Down Expand Up @@ -3095,6 +3103,14 @@
]
}
},
{
"key": {
"symbol": "data_availability"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "period_end"
Expand Down Expand Up @@ -3599,6 +3615,14 @@
]
}
},
{
"key": {
"symbol": "data_availability"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "period_end"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@
},
"ext": "v0"
},
518401
100000
]
],
[
Expand Down Expand Up @@ -428,7 +428,7 @@
},
"ext": "v0"
},
518401
100000
]
]
]
Expand Down
Loading
Loading