From 98a846292bbcaa877f0bb50daffa6442d8fa3992 Mon Sep 17 00:00:00 2001 From: Johnchi Date: Tue, 28 Apr 2026 14:23:25 +0100 Subject: [PATCH] test(reporting): cover verify_dependency_address_set public preflight invariants --- reporting/README.md | 10 +++--- reporting/src/tests.rs | 73 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/reporting/README.md b/reporting/README.md index c636b7c3..050613e4 100644 --- a/reporting/README.md +++ b/reporting/README.md @@ -9,7 +9,6 @@ Aggregates financial health data from the remittance_split, savings_goals, bill_ - Admin-only archival and cleanup of old reports - Storage TTL management (instance: ~30 days, archive: ~180 days) -<<<<<<< feature/reporting-address-config-integrity ## Dependency contract address integrity Reporting stores five downstream contract IDs (`remittance_split`, `savings_goals`, @@ -18,8 +17,8 @@ Reporting stores five downstream contract IDs (`remittance_split`, `savings_goal **Validation (on every `configure_addresses` call)**: - **No self-reference** — None of the five addresses may equal the reporting - contract’s own address. Pointing a role at this contract would create ambiguous - cross-contract calls and break the intended “one deployment per role” model. + contract's own address. Pointing a role at this contract would create ambiguous + cross-contract calls and break the intended "one deployment per role" model. - **Pairwise uniqueness** — All five values must differ. Two roles must not share the same contract ID, or aggregation would silently read the wrong deployment twice (audit and correctness risk). @@ -38,11 +37,10 @@ is rejected. its role (that requires off-chain governance / deployment manifests). It only enforces **structural** integrity: distinct callees and no reporting self-loop. -- Soroban/Stellar contract IDs are not an EVM-style “zero address”; “malformed” +- Soroban/Stellar contract IDs are not an EVM-style "zero address"; "malformed" in this layer means duplicate or self-reference as above. -======= + ## Quickstart ->>>>>>> main ```rust // 1. Initialize diff --git a/reporting/src/tests.rs b/reporting/src/tests.rs index bb06b794..f2c4b2a9 100644 --- a/reporting/src/tests.rs +++ b/reporting/src/tests.rs @@ -1,5 +1,6 @@ use soroban_sdk::testutils::storage::Instance as StorageInstance; use soroban_sdk::{ + symbol_short, testutils::{Address as _, Ledger, LedgerInfo}, Address, Env, }; @@ -470,6 +471,78 @@ fn test_verify_dependency_address_set_rejects_self_reference() { )); } +#[test] +fn test_verify_dependency_address_set_does_not_write_storage() { + let env = create_test_env(); + let contract_id = env.register_contract(None, ReportingContract); + let client = ReportingContractClient::new(&env, &contract_id); + let admin = Address::generate(&env); + client.init(&admin); + + let addrs = ContractAddresses { + remittance_split: Address::generate(&env), + savings_goals: Address::generate(&env), + bill_payments: Address::generate(&env), + insurance: Address::generate(&env), + family_wallet: Address::generate(&env), + }; + + let _ = client.try_verify_dependency_address_set(&addrs); + + let instance_snapshot: Option
= env.storage().instance().get(&symbol_short!("ADMIN")); + assert!(instance_snapshot.is_some(), "ADMIN should still exist"); + + let stored_addrs: Option = + env.storage().instance().get(&symbol_short!("ADDRESSES")); + assert!(stored_addrs.is_none(), "ADDRESSES must not be written by preflight"); +} + +#[test] +fn test_verify_dependency_address_set_rejects_multiple_duplicates() { + let env = create_test_env(); + let contract_id = env.register_contract(None, ReportingContract); + let client = ReportingContractClient::new(&env, &contract_id); + let admin = Address::generate(&env); + client.init(&admin); + + let x = Address::generate(&env); + let addrs = ContractAddresses { + remittance_split: x.clone(), + savings_goals: x.clone(), + bill_payments: x.clone(), + insurance: x.clone(), + family_wallet: x, + }; + let result = client.try_verify_dependency_address_set(&addrs); + assert!(matches!( + result, + Err(Ok(ReportingError::InvalidDependencyAddressConfiguration)) + )); +} + +#[test] +fn test_verify_dependency_address_set_deterministic_error() { + let env = create_test_env(); + let contract_id = env.register_contract(None, ReportingContract); + let client = ReportingContractClient::new(&env, &contract_id); + let admin = Address::generate(&env); + client.init(&admin); + + let x = Address::generate(&env); + let addrs = ContractAddresses { + remittance_split: x.clone(), + savings_goals: x, + bill_payments: Address::generate(&env), + insurance: Address::generate(&env), + family_wallet: Address::generate(&env), + }; + + let result1 = client.try_verify_dependency_address_set(&addrs); + let result2 = client.try_verify_dependency_address_set(&addrs); + assert!(matches!(result1, Err(Ok(ReportingError::InvalidDependencyAddressConfiguration)))); + assert!(matches!(result2, Err(Ok(ReportingError::InvalidDependencyAddressConfiguration)))); +} + #[test] fn test_get_remittance_summary() { let env = Env::default();