From 58971adcae79d25d9d3dcd21fec69e6ba9b38e75 Mon Sep 17 00:00:00 2001 From: fadesany Date: Fri, 27 Mar 2026 10:15:31 +0100 Subject: [PATCH 1/2] feat: Refactor `coverage_type` to use u32, update contract event topics, and add data migration documentation. --- data_migration/README.md | 27 + insurance/Cargo.toml | 7 +- insurance/README.md | 2 +- insurance/src/lib.rs | 1548 ++++++++++++++++- insurance/src/test.rs | 1524 +++------------- .../test_create_policy_emits_event.1.json | 44 +- ...t_active_policies_excludes_inactive.1.json | 60 +- ...tive_policies_multi_owner_isolation.1.json | 101 +- ..._get_active_policies_multiple_pages.1.json | 70 +- ...est_get_active_policies_single_page.1.json | 70 +- ...olicies_for_owner_includes_inactive.1.json | 64 +- ...tance_ttl_extended_on_create_policy.1.json | 28 +- ...e_ttl_extended_on_deactivate_policy.1.json | 30 +- ...stance_ttl_refreshed_on_pay_premium.1.json | 32 +- .../test/test_limit_clamped_to_max.1.json | 560 +++--- .../test/test_limit_zero_uses_default.1.json | 50 +- .../test_pay_premium_after_deactivate.1.json | 86 +- .../test/test_pay_premium_emits_event.1.json | 60 +- .../test_pay_premium_policy_not_found.1.json | 25 +- ...persists_across_ledger_advancements.1.json | 44 +- ...t_policy_lifecycle_emits_all_events.1.json | 34 +- insurance/tests/stress_tests.rs | 21 +- remitwise-common/src/lib.rs | 8 +- 23 files changed, 2338 insertions(+), 2157 deletions(-) create mode 100644 data_migration/README.md diff --git a/data_migration/README.md b/data_migration/README.md new file mode 100644 index 00000000..f970a343 --- /dev/null +++ b/data_migration/README.md @@ -0,0 +1,27 @@ +# Data Migration + +Utility library for exporting and importing contract state snapshots for RemitWise. + +## Features + +- **Multi-format Support**: JSON, Binary (Bincode), CSV, and Encrypted. +- **Integrity Checks**: SHA-256 checksum validation for all snapshots. +- **Version Compatibility**: Enforces semantic versioning rules for snapshots (`MIN_SUPPORTED_VERSION`). +- **Replay Protection**: The `MigrationTracker` prevents duplicate/repeated imports of the same snapshot by tracking checksum/version pairs. + +## Replay Protection + +To prevent accidental double-restores or replaying of old data, all import functions (`import_from_json`, `import_from_binary`) require a `MigrationTracker`. + +```rust +use data_migration::{MigrationTracker, import_from_json}; + +let mut tracker = MigrationTracker::new(); +let result = import_from_json(&mut tracker, json_data); + +if let Err(MigrationError::DuplicateImport) = result { + println!("This snapshot has already been processed!"); +} +``` + +The tracker should be persisted or maintained throughout the lifecycle of the migration process to ensure consistent protection. diff --git a/insurance/Cargo.toml b/insurance/Cargo.toml index d38e259c..bc94df7d 100644 --- a/insurance/Cargo.toml +++ b/insurance/Cargo.toml @@ -7,10 +7,13 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] -soroban-sdk = "21.0.0" +soroban-sdk = "=21.7.7" +remitwise-common = { path = "../remitwise-common" } [dev-dependencies] -soroban-sdk = { version = "21.0.0", features = ["testutils"] } +proptest = "1.10.0" +soroban-sdk = { version = "=21.7.7", features = ["testutils"] } +testutils = { path = "../testutils" } [profile.release] opt-level = "z" diff --git a/insurance/README.md b/insurance/README.md index b081c516..14cdd1fb 100644 --- a/insurance/README.md +++ b/insurance/README.md @@ -172,7 +172,7 @@ Owner-only. Updates or clears the `external_ref` field of a policy. Owner-only. Marks a policy as inactive and removes it from the active-policy list. -**Emits**: `PolicyDeactivatedEvent` +This function is **idempotent**: if the policy is already inactive, it returns `false` without emitting duplicate events or updating storage. --- diff --git a/insurance/src/lib.rs b/insurance/src/lib.rs index 8063705a..9715ec1d 100644 --- a/insurance/src/lib.rs +++ b/insurance/src/lib.rs @@ -1,9 +1,1539 @@ -pub fn pay_premium(env: Env, policy_id: BytesN<32>) { - let killswitch_id = get_killswitch_id(&env); - let is_paused: bool = env.invoke_contract(&killswitch_id, &symbol_short!("is_paused"), vec![&env, Symbol::new(&env, "insurance")].into()); - - if is_paused { - panic!("Contract is currently paused for emergency maintenance."); - } - // ... rest of the logic -} \ No newline at end of file +#![no_std] +use soroban_sdk::{ + contract, contractimpl, contracttype, symbol_short, Address, Env, Map, String, Symbol, Vec, +}; + +use remitwise_common::CoverageType; + +// Event topics +const POLICY_CREATED: Symbol = symbol_short!("created"); +const PREMIUM_PAID: Symbol = symbol_short!("paid"); +const POLICY_DEACTIVATED: Symbol = symbol_short!("deactive"); + +#[derive(Clone)] +#[contracttype] +pub struct PolicyCreatedEvent { + pub policy_id: u32, + pub name: String, + pub coverage_type: CoverageType, + pub monthly_premium: i128, + pub coverage_amount: i128, + pub timestamp: u64, +} + +#[derive(Clone)] +#[contracttype] +pub struct PremiumPaidEvent { + pub policy_id: u32, + pub name: String, + pub amount: i128, + pub next_payment_date: u64, + pub timestamp: u64, +} + +#[derive(Clone)] +#[contracttype] +pub struct PolicyDeactivatedEvent { + pub policy_id: u32, + pub name: String, + pub timestamp: u64, +} + +const INSTANCE_LIFETIME_THRESHOLD: u32 = 17280; +const INSTANCE_BUMP_AMOUNT: u32 = 518400; + +const CONTRACT_VERSION: u32 = 1; +const MAX_BATCH_SIZE: u32 = 50; + +/// Pagination constants +pub const DEFAULT_PAGE_LIMIT: u32 = 20; +pub const MAX_PAGE_LIMIT: u32 = 50; + +pub mod pause_functions { + use soroban_sdk::{symbol_short, Symbol}; + pub const CREATE_POLICY: Symbol = symbol_short!("crt_pol"); + pub const PAY_PREMIUM: Symbol = symbol_short!("pay_prem"); + pub const DEACTIVATE: Symbol = symbol_short!("deact"); + pub const CREATE_SCHED: Symbol = symbol_short!("crt_sch"); + pub const MODIFY_SCHED: Symbol = symbol_short!("mod_sch"); + pub const CANCEL_SCHED: Symbol = symbol_short!("can_sch"); +} + +#[derive(Clone)] +#[contracttype] +pub struct InsurancePolicy { + pub id: u32, + pub owner: Address, + pub name: String, + pub coverage_type: CoverageType, + pub monthly_premium: i128, + pub coverage_amount: i128, + pub active: bool, + pub next_payment_date: u64, + pub schedule_id: Option, +} + +/// Paginated result for insurance policy queries +#[contracttype] +#[derive(Clone)] +pub struct PolicyPage { + /// Policies for this page + pub items: Vec, + /// Pass as `cursor` for the next page. 0 = no more pages. + pub next_cursor: u32, + /// Number of items returned + pub count: u32, +} + +#[contracttype] +#[derive(Clone)] +pub struct PremiumSchedule { + pub id: u32, + pub owner: Address, + pub policy_id: u32, + pub next_due: u64, + pub interval: u64, + pub recurring: bool, + pub active: bool, + pub created_at: u64, + pub last_executed: Option, + pub missed_count: u32, +} + +#[contracttype] +#[derive(Clone)] +pub enum InsuranceEvent { + PolicyCreated, + PremiumPaid, + PolicyDeactivated, + ScheduleCreated, + ScheduleExecuted, + ScheduleMissed, + ScheduleModified, + ScheduleCancelled, +} + +#[contract] +pub struct Insurance; + +#[contractimpl] +impl Insurance { + // ----------------------------------------------------------------------- + // Internal helpers + // ----------------------------------------------------------------------- + + fn clamp_limit(limit: u32) -> u32 { + if limit == 0 { + DEFAULT_PAGE_LIMIT + } else if limit > MAX_PAGE_LIMIT { + MAX_PAGE_LIMIT + } else { + limit + } + } + + fn get_pause_admin(env: &Env) -> Option
{ + env.storage().instance().get(&symbol_short!("PAUSE_ADM")) + } + fn get_global_paused(env: &Env) -> bool { + env.storage() + .instance() + .get(&symbol_short!("PAUSED")) + .unwrap_or(false) + } + fn is_function_paused(env: &Env, func: Symbol) -> bool { + env.storage() + .instance() + .get::<_, Map>(&symbol_short!("PAUSED_FN")) + .unwrap_or_else(|| Map::new(env)) + .get(func) + .unwrap_or(false) + } + fn require_not_paused(env: &Env, func: Symbol) { + if Self::get_global_paused(env) { + panic!("Contract is paused"); + } + if Self::is_function_paused(env, func) { + panic!("Function is paused"); + } + } + + // ----------------------------------------------------------------------- + // Pause / upgrade (unchanged) + // ----------------------------------------------------------------------- + + pub fn set_pause_admin(env: Env, caller: Address, new_admin: Address) { + caller.require_auth(); + let current = Self::get_pause_admin(&env); + match current { + None => { + if caller != new_admin { + panic!("Unauthorized"); + } + } + Some(admin) if admin != caller => panic!("Unauthorized"), + _ => {} + } + env.storage() + .instance() + .set(&symbol_short!("PAUSE_ADM"), &new_admin); + } + pub fn pause(env: Env, caller: Address) { + caller.require_auth(); + let admin = Self::get_pause_admin(&env).expect("No pause admin set"); + if admin != caller { + panic!("Unauthorized"); + } + env.storage() + .instance() + .set(&symbol_short!("PAUSED"), &true); + env.events() + .publish((symbol_short!("insure"), symbol_short!("paused")), ()); + } + pub fn unpause(env: Env, caller: Address) { + caller.require_auth(); + let admin = Self::get_pause_admin(&env).expect("No pause admin set"); + if admin != caller { + panic!("Unauthorized"); + } + let unpause_at: Option = env.storage().instance().get(&symbol_short!("UNP_AT")); + if let Some(at) = unpause_at { + if env.ledger().timestamp() < at { + panic!("Time-locked unpause not yet reached"); + } + env.storage().instance().remove(&symbol_short!("UNP_AT")); + } + env.storage() + .instance() + .set(&symbol_short!("PAUSED"), &false); + env.events() + .publish((symbol_short!("insure"), symbol_short!("unpaused")), ()); + } + pub fn pause_function(env: Env, caller: Address, func: Symbol) { + caller.require_auth(); + let admin = Self::get_pause_admin(&env).expect("No pause admin set"); + if admin != caller { + panic!("Unauthorized"); + } + let mut m: Map = env + .storage() + .instance() + .get(&symbol_short!("PAUSED_FN")) + .unwrap_or_else(|| Map::new(&env)); + m.set(func, true); + env.storage() + .instance() + .set(&symbol_short!("PAUSED_FN"), &m); + } + pub fn unpause_function(env: Env, caller: Address, func: Symbol) { + caller.require_auth(); + let admin = Self::get_pause_admin(&env).expect("No pause admin set"); + if admin != caller { + panic!("Unauthorized"); + } + let mut m: Map = env + .storage() + .instance() + .get(&symbol_short!("PAUSED_FN")) + .unwrap_or_else(|| Map::new(&env)); + m.set(func, false); + env.storage() + .instance() + .set(&symbol_short!("PAUSED_FN"), &m); + } + pub fn emergency_pause_all(env: Env, caller: Address) { + Self::pause(env.clone(), caller.clone()); + for func in [ + pause_functions::CREATE_POLICY, + pause_functions::PAY_PREMIUM, + pause_functions::DEACTIVATE, + pause_functions::CREATE_SCHED, + pause_functions::MODIFY_SCHED, + pause_functions::CANCEL_SCHED, + ] { + Self::pause_function(env.clone(), caller.clone(), func); + } + } + pub fn is_paused(env: Env) -> bool { + Self::get_global_paused(&env) + } + pub fn get_version(env: Env) -> u32 { + env.storage() + .instance() + .get(&symbol_short!("VERSION")) + .unwrap_or(CONTRACT_VERSION) + } + fn get_upgrade_admin(env: &Env) -> Option
{ + env.storage().instance().get(&symbol_short!("UPG_ADM")) + } + pub fn set_upgrade_admin(env: Env, caller: Address, new_admin: Address) { + caller.require_auth(); + let current = Self::get_upgrade_admin(&env); + match current { + None => { + if caller != new_admin { + panic!("Unauthorized"); + } + } + Some(adm) if adm != caller => panic!("Unauthorized"), + _ => {} + } + env.storage() + .instance() + .set(&symbol_short!("UPG_ADM"), &new_admin); + } + pub fn set_version(env: Env, caller: Address, new_version: u32) { + caller.require_auth(); + let admin = Self::get_upgrade_admin(&env).expect("No upgrade admin set"); + if admin != caller { + panic!("Unauthorized"); + } + let prev = Self::get_version(env.clone()); + env.storage() + .instance() + .set(&symbol_short!("VERSION"), &new_version); + env.events().publish( + (symbol_short!("insure"), symbol_short!("upgraded")), + (prev, new_version), + ); + } + + // ----------------------------------------------------------------------- + // Core policy operations (unchanged) + // ----------------------------------------------------------------------- + + pub fn create_policy( + env: Env, + owner: Address, + name: String, + coverage_type: CoverageType, + monthly_premium: i128, + coverage_amount: i128, + ) -> u32 { + owner.require_auth(); + Self::require_not_paused(&env, pause_functions::CREATE_POLICY); + + if monthly_premium <= 0 { + panic!("Monthly premium must be positive"); + } + if coverage_amount <= 0 { + panic!("Coverage amount must be positive"); + } + + Self::extend_instance_ttl(&env); + + let mut policies: Map = env + .storage() + .instance() + .get(&symbol_short!("POLICIES")) + .unwrap_or_else(|| Map::new(&env)); + + let next_id = env + .storage() + .instance() + .get(&symbol_short!("NEXT_ID")) + .unwrap_or(0u32) + + 1; + + let next_payment_date = env.ledger().timestamp() + (30 * 86400); + + let policy = InsurancePolicy { + id: next_id, + owner: owner.clone(), + name: name.clone(), + coverage_type: coverage_type.clone(), + monthly_premium, + coverage_amount, + active: true, + next_payment_date, + schedule_id: None, + }; + + let policy_owner = policy.owner.clone(); + policies.set(next_id, policy); + env.storage() + .instance() + .set(&symbol_short!("POLICIES"), &policies); + env.storage() + .instance() + .set(&symbol_short!("NEXT_ID"), &next_id); + + let event = PolicyCreatedEvent { + policy_id: next_id, + name: name.clone(), + coverage_type: coverage_type.clone(), + monthly_premium, + coverage_amount, + timestamp: env.ledger().timestamp(), + }; + env.events().publish((POLICY_CREATED,), event); + env.events().publish( + (symbol_short!("insure"), InsuranceEvent::PolicyCreated), + (next_id, policy_owner), + ); + + next_id + } + + pub fn pay_premium(env: Env, caller: Address, policy_id: u32) -> bool { + caller.require_auth(); + Self::require_not_paused(&env, pause_functions::PAY_PREMIUM); + Self::extend_instance_ttl(&env); + + let mut policies: Map = env + .storage() + .instance() + .get(&symbol_short!("POLICIES")) + .unwrap_or_else(|| Map::new(&env)); + + let mut policy = policies.get(policy_id).expect("Policy not found"); + + if policy.owner != caller { + panic!("Only the policy owner can pay premiums"); + } + if !policy.active { + panic!("Policy is not active"); + } + + policy.next_payment_date = env.ledger().timestamp() + (30 * 86400); + + let event = PremiumPaidEvent { + policy_id, + name: policy.name.clone(), + amount: policy.monthly_premium, + next_payment_date: policy.next_payment_date, + timestamp: env.ledger().timestamp(), + }; + env.events().publish((PREMIUM_PAID,), event); + + policies.set(policy_id, policy); + env.storage() + .instance() + .set(&symbol_short!("POLICIES"), &policies); + + env.events().publish( + (symbol_short!("insure"), InsuranceEvent::PremiumPaid), + (policy_id, caller), + ); + + true + } + + pub fn batch_pay_premiums(env: Env, caller: Address, policy_ids: Vec) -> u32 { + caller.require_auth(); + Self::require_not_paused(&env, pause_functions::PAY_PREMIUM); + if policy_ids.len() > MAX_BATCH_SIZE { + panic!("Batch too large"); + } + let policies_map: Map = env + .storage() + .instance() + .get(&symbol_short!("POLICIES")) + .unwrap_or_else(|| Map::new(&env)); + for id in policy_ids.iter() { + let policy = policies_map.get(id).expect("Policy not found"); + if policy.owner != caller { + panic!("Not owner of all policies"); + } + if !policy.active { + panic!("Policy not active"); + } + } + Self::extend_instance_ttl(&env); + let mut policies: Map = env + .storage() + .instance() + .get(&symbol_short!("POLICIES")) + .unwrap_or_else(|| Map::new(&env)); + let current_time = env.ledger().timestamp(); + let mut paid_count = 0u32; + for id in policy_ids.iter() { + let mut policy = policies.get(id).expect("Policy not found"); + if policy.owner != caller || !policy.active { + panic!("Batch validation failed"); + } + policy.next_payment_date = current_time + (30 * 86400); + let event = PremiumPaidEvent { + policy_id: id, + name: policy.name.clone(), + amount: policy.monthly_premium, + next_payment_date: policy.next_payment_date, + timestamp: current_time, + }; + env.events().publish((PREMIUM_PAID,), event); + env.events().publish( + (symbol_short!("insure"), InsuranceEvent::PremiumPaid), + (id, caller.clone()), + ); + policies.set(id, policy); + paid_count += 1; + } + env.storage() + .instance() + .set(&symbol_short!("POLICIES"), &policies); + env.events().publish( + (symbol_short!("insure"), symbol_short!("batch_pay")), + (paid_count, caller), + ); + paid_count + } + + pub fn get_policy(env: Env, policy_id: u32) -> Option { + let policies: Map = env + .storage() + .instance() + .get(&symbol_short!("POLICIES")) + .unwrap_or_else(|| Map::new(&env)); + policies.get(policy_id) + } + + // ----------------------------------------------------------------------- + // PAGINATED LIST QUERIES (new in this version) + // ----------------------------------------------------------------------- + + /// Get a page of ACTIVE policies for `owner`. + /// + /// # Arguments + /// * `owner` – whose policies to return + /// * `cursor` – start after this policy ID (pass 0 for the first page) + /// * `limit` – max items per page (0 → DEFAULT_PAGE_LIMIT, capped at MAX_PAGE_LIMIT) + /// + /// # Returns + /// `PolicyPage { items, next_cursor, count }`. + /// `next_cursor == 0` means no more pages. + pub fn get_active_policies(env: Env, owner: Address, cursor: u32, limit: u32) -> PolicyPage { + let limit = Self::clamp_limit(limit); + let policies: Map = env + .storage() + .instance() + .get(&symbol_short!("POLICIES")) + .unwrap_or_else(|| Map::new(&env)); + + let mut result = Vec::new(&env); + let mut next_cursor: u32 = 0; + let mut collected: u32 = 0; + + for (id, policy) in policies.iter() { + if id <= cursor { + continue; + } + if !policy.active || policy.owner != owner { + continue; + } + if collected < limit { + result.push_back(policy); + collected += 1; + next_cursor = id; // ← track last returned ID as we go + } else { + break; // ← stop without touching next_cursor + } + } + + // Then reset next_cursor to 0 if we didn't fill the page (no more items) + if collected < limit { + next_cursor = 0; + } + + PolicyPage { + items: result, + next_cursor, + count: collected, + } + } + + /// Get a page of ALL policies (active + inactive) for `owner`. + /// + /// Same cursor/limit semantics as `get_active_policies`. + pub fn get_all_policies_for_owner( + env: Env, + owner: Address, + cursor: u32, + limit: u32, + ) -> PolicyPage { + owner.require_auth(); + let limit = Self::clamp_limit(limit); + let policies: Map = env + .storage() + .instance() + .get(&symbol_short!("POLICIES")) + .unwrap_or_else(|| Map::new(&env)); + + let mut result = Vec::new(&env); + let mut next_cursor: u32 = 0; + let mut collected: u32 = 0; + + for (id, policy) in policies.iter() { + if id <= cursor { + continue; + } + if policy.owner != owner { + continue; + } + if collected < limit { + result.push_back(policy); + collected += 1; + } else { + next_cursor = id; + break; + } + } + + PolicyPage { + items: result, + next_cursor, + count: collected, + } + } + + pub fn get_total_monthly_premium(env: Env, owner: Address) -> i128 { + let mut total = 0i128; + let policies: Map = env + .storage() + .instance() + .get(&symbol_short!("POLICIES")) + .unwrap_or_else(|| Map::new(&env)); + for (_, policy) in policies.iter() { + if policy.active && policy.owner == owner { + total += policy.monthly_premium; + } + } + total + } + + /// Deactivate a policy. + /// + /// This function is idempotent: if the policy is already inactive, it will + /// return `false` without emitting duplicate events or updating storage. + pub fn deactivate_policy(env: Env, caller: Address, policy_id: u32) -> bool { + caller.require_auth(); + Self::require_not_paused(&env, pause_functions::DEACTIVATE); + Self::extend_instance_ttl(&env); + + let mut policies: Map = env + .storage() + .instance() + .get(&symbol_short!("POLICIES")) + .unwrap_or_else(|| Map::new(&env)); + + let mut policy = policies.get(policy_id).expect("Policy not found"); + + if policy.owner != caller { + panic!("Only the policy owner can deactivate this policy"); + } + + // Idempotency check: if already inactive, return false and stop + if !policy.active { + return false; + } + + policy.active = false; + policies.set(policy_id, policy.clone()); + env.storage() + .instance() + .set(&symbol_short!("POLICIES"), &policies); + + let event = PolicyDeactivatedEvent { + policy_id, + name: policy.name.clone(), + timestamp: env.ledger().timestamp(), + }; + env.events().publish((POLICY_DEACTIVATED,), event); + env.events().publish( + (symbol_short!("insuranc"), InsuranceEvent::PolicyDeactivated), + (policy_id, caller), + ); + + true + } + + fn extend_instance_ttl(env: &Env) { + env.storage() + .instance() + .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); + } + + // ----------------------------------------------------------------------- + // Schedule operations (unchanged) + // ----------------------------------------------------------------------- + + pub fn create_premium_schedule( + env: Env, + owner: Address, + policy_id: u32, + next_due: u64, + interval: u64, + ) -> u32 { + owner.require_auth(); + Self::require_not_paused(&env, pause_functions::CREATE_SCHED); + + let mut policies: Map = env + .storage() + .instance() + .get(&symbol_short!("POLICIES")) + .unwrap_or_else(|| Map::new(&env)); + + let mut policy = policies.get(policy_id).expect("Policy not found"); + + if policy.owner != owner { + panic!("Only the policy owner can create schedules"); + } + + let current_time = env.ledger().timestamp(); + if next_due <= current_time { + panic!("Next due date must be in the future"); + } + + Self::extend_instance_ttl(&env); + + let mut schedules: Map = env + .storage() + .instance() + .get(&symbol_short!("PREM_SCH")) + .unwrap_or_else(|| Map::new(&env)); + + let next_schedule_id = env + .storage() + .instance() + .get(&symbol_short!("NEXT_PSCH")) + .unwrap_or(0u32) + + 1; + + let schedule = PremiumSchedule { + id: next_schedule_id, + owner: owner.clone(), + policy_id, + next_due, + interval, + recurring: interval > 0, + active: true, + created_at: current_time, + last_executed: None, + missed_count: 0, + }; + + policy.schedule_id = Some(next_schedule_id); + + schedules.set(next_schedule_id, schedule); + env.storage() + .instance() + .set(&symbol_short!("PREM_SCH"), &schedules); + env.storage() + .instance() + .set(&symbol_short!("NEXT_PSCH"), &next_schedule_id); + + policies.set(policy_id, policy); + env.storage() + .instance() + .set(&symbol_short!("POLICIES"), &policies); + + env.events().publish( + (symbol_short!("insure"), InsuranceEvent::ScheduleCreated), + (next_schedule_id, owner), + ); + + next_schedule_id + } + + pub fn modify_premium_schedule( + env: Env, + caller: Address, + schedule_id: u32, + next_due: u64, + interval: u64, + ) -> bool { + caller.require_auth(); + Self::require_not_paused(&env, pause_functions::MODIFY_SCHED); + + let current_time = env.ledger().timestamp(); + if next_due <= current_time { + panic!("Next due date must be in the future"); + } + + Self::extend_instance_ttl(&env); + + let mut schedules: Map = env + .storage() + .instance() + .get(&symbol_short!("PREM_SCH")) + .unwrap_or_else(|| Map::new(&env)); + + let mut schedule = schedules.get(schedule_id).expect("Schedule not found"); + + if schedule.owner != caller { + panic!("Only the schedule owner can modify it"); + } + + schedule.next_due = next_due; + schedule.interval = interval; + schedule.recurring = interval > 0; + + schedules.set(schedule_id, schedule); + env.storage() + .instance() + .set(&symbol_short!("PREM_SCH"), &schedules); + + env.events().publish( + (symbol_short!("insure"), InsuranceEvent::ScheduleModified), + (schedule_id, caller), + ); + + true + } + + pub fn cancel_premium_schedule(env: Env, caller: Address, schedule_id: u32) -> bool { + caller.require_auth(); + Self::require_not_paused(&env, pause_functions::CANCEL_SCHED); + Self::extend_instance_ttl(&env); + + let mut schedules: Map = env + .storage() + .instance() + .get(&symbol_short!("PREM_SCH")) + .unwrap_or_else(|| Map::new(&env)); + + let mut schedule = schedules.get(schedule_id).expect("Schedule not found"); + + if schedule.owner != caller { + panic!("Only the schedule owner can cancel it"); + } + + schedule.active = false; + + schedules.set(schedule_id, schedule); + env.storage() + .instance() + .set(&symbol_short!("PREM_SCH"), &schedules); + + env.events().publish( + (symbol_short!("insure"), InsuranceEvent::ScheduleCancelled), + (schedule_id, caller), + ); + + true + } + + pub fn execute_due_premium_schedules(env: Env) -> Vec { + Self::extend_instance_ttl(&env); + + let current_time = env.ledger().timestamp(); + let mut executed = Vec::new(&env); + + let mut schedules: Map = env + .storage() + .instance() + .get(&symbol_short!("PREM_SCH")) + .unwrap_or_else(|| Map::new(&env)); + + let mut policies: Map = env + .storage() + .instance() + .get(&symbol_short!("POLICIES")) + .unwrap_or_else(|| Map::new(&env)); + + for (schedule_id, mut schedule) in schedules.iter() { + if !schedule.active || schedule.next_due > current_time { + continue; + } + + if let Some(mut policy) = policies.get(schedule.policy_id) { + if policy.active { + policy.next_payment_date = current_time + (30 * 86400); + policies.set(schedule.policy_id, policy.clone()); + env.events().publish( + (symbol_short!("insure"), InsuranceEvent::PremiumPaid), + (schedule.policy_id, policy.owner), + ); + } + } + + schedule.last_executed = Some(current_time); + + if schedule.recurring && schedule.interval > 0 { + let mut missed = 0u32; + let mut next = schedule.next_due + schedule.interval; + while next <= current_time { + missed += 1; + next += schedule.interval; + } + schedule.missed_count += missed; + schedule.next_due = next; + + if missed > 0 { + env.events().publish( + (symbol_short!("insure"), InsuranceEvent::ScheduleMissed), + (schedule_id, missed), + ); + } + } else { + schedule.active = false; + } + + schedules.set(schedule_id, schedule); + executed.push_back(schedule_id); + + env.events().publish( + (symbol_short!("insure"), InsuranceEvent::ScheduleExecuted), + schedule_id, + ); + } + + env.storage() + .instance() + .set(&symbol_short!("PREM_SCH"), &schedules); + env.storage() + .instance() + .set(&symbol_short!("POLICIES"), &policies); + + executed + } + + pub fn get_premium_schedules(env: Env, owner: Address) -> Vec { + let schedules: Map = env + .storage() + .instance() + .get(&symbol_short!("PREM_SCH")) + .unwrap_or_else(|| Map::new(&env)); + let mut result = Vec::new(&env); + for (_, schedule) in schedules.iter() { + if schedule.owner == owner { + result.push_back(schedule); + } + } + result + } + + pub fn get_premium_schedule(env: Env, schedule_id: u32) -> Option { + let schedules: Map = env + .storage() + .instance() + .get(&symbol_short!("PREM_SCH")) + .unwrap_or_else(|| Map::new(&env)); + schedules.get(schedule_id) + } +} + +// ----------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------- +#[cfg(test)] +mod test { + use super::*; + use proptest::prelude::*; + use soroban_sdk::testutils::storage::Instance as _; + use soroban_sdk::testutils::{Address as _, Events, Ledger, LedgerInfo}; + use soroban_sdk::{Env, String}; + + fn make_env() -> Env { + Env::default() + } + + fn setup_policies( + env: &Env, + client: &InsuranceClient, + owner: &Address, + count: u32, + ) -> Vec { + let mut ids = Vec::new(env); + for i in 0..count { + let id = client.create_policy( + owner, + &String::from_str(env, "Policy"), + &CoverageType::Health, + &(50i128 * (i as i128 + 1)), + &(10000i128 * (i as i128 + 1)), + ); + ids.push_back(id); + } + ids + } + + // --- get_active_policies --- + + #[test] + fn test_get_active_policies_empty() { + let env = make_env(); + env.mock_all_auths(); + let id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &id); + let owner = Address::generate(&env); + + let page = client.get_active_policies(&owner, &0, &0); + assert_eq!(page.count, 0); + assert_eq!(page.next_cursor, 0); + } + + #[test] + fn test_get_active_policies_single_page() { + let env = make_env(); + env.mock_all_auths(); + let id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &id); + let owner = Address::generate(&env); + + setup_policies(&env, &client, &owner, 5); + + let page = client.get_active_policies(&owner, &0, &10); + assert_eq!(page.count, 5); + assert_eq!(page.next_cursor, 0); + } + + #[test] + fn test_pay_premium_policy_not_found() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &contract_id); + let owner = Address::generate(&env); + + // No policies created — policy ID 999 does not exist; contract panics + let result = client.try_pay_premium(&owner, &999u32); + assert!(result.is_err(), "paying for non-existent policy must fail"); + } + + #[test] + fn test_get_active_policies_multiple_pages() { + let env = Env::default(); + env.mock_all_auths(); + let id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &id); + let owner = Address::generate(&env); + + setup_policies(&env, &client, &owner, 7); + + let page1 = client.get_active_policies(&owner, &0, &3); + assert_eq!(page1.count, 3); + assert!(page1.next_cursor > 0); + + let page2 = client.get_active_policies(&owner, &page1.next_cursor, &3); + assert_eq!(page2.count, 3); + assert!(page2.next_cursor > 0); + + let page3 = client.get_active_policies(&owner, &page2.next_cursor, &3); + assert_eq!(page3.count, 1); + assert_eq!(page3.next_cursor, 0); + } + + #[test] + fn test_get_active_policies_excludes_inactive() { + let env = make_env(); + env.mock_all_auths(); + let id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &id); + let owner = Address::generate(&env); + + let ids = setup_policies(&env, &client, &owner, 4); + // Deactivate policy #2 + client.deactivate_policy(&owner, &ids.get(1).unwrap()); + + let page = client.get_active_policies(&owner, &0, &10); + assert_eq!(page.count, 3); // only 3 active + for p in page.items.iter() { + assert!(p.active, "only active policies should be returned"); + } + } + + #[test] + fn test_get_active_policies_multi_owner_isolation() { + let env = make_env(); + env.mock_all_auths(); + let id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &id); + let owner_a = Address::generate(&env); + let owner_b = Address::generate(&env); + + setup_policies(&env, &client, &owner_a, 3); + setup_policies(&env, &client, &owner_b, 5); + + let page = client.get_active_policies(&owner_a, &0, &20); + assert_eq!(page.count, 3); + for p in page.items.iter() { + assert_eq!(p.owner, owner_a); + } + } + + #[test] + fn test_get_all_policies_for_owner_includes_inactive() { + let env = make_env(); + env.mock_all_auths(); + let id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &id); + let owner = Address::generate(&env); + + let ids = setup_policies(&env, &client, &owner, 4); + client.deactivate_policy(&owner, &ids.get(0).unwrap()); + client.deactivate_policy(&owner, &ids.get(2).unwrap()); + + let page = client.get_all_policies_for_owner(&owner, &0, &10); + assert_eq!(page.count, 4); // all 4 regardless of active status + } + + // --- limit clamping --- + + #[test] + fn test_limit_zero_uses_default() { + let env = make_env(); + env.mock_all_auths(); + let id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &id); + let owner = Address::generate(&env); + + setup_policies(&env, &client, &owner, 3); + let page = client.get_active_policies(&owner, &0, &0); + assert_eq!(page.count, 3); + } + + #[test] + fn test_limit_clamped_to_max() { + let env = make_env(); + env.mock_all_auths(); + let id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &id); + let owner = Address::generate(&env); + + setup_policies(&env, &client, &owner, 55); + let page = client.get_active_policies(&owner, &0, &9999); + assert_eq!(page.count, MAX_PAGE_LIMIT); + assert!(page.next_cursor > 0); + } + + // --- existing event tests (unchanged) --- + + #[test] + fn test_create_policy_emits_event() { + let env = make_env(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &contract_id); + let owner = Address::generate(&env); + + let policy_id = client.create_policy( + &owner, + &String::from_str(&env, "Health Insurance"), + &CoverageType::Health, + &100, + &50000, + ); + assert_eq!(policy_id, 1); + + let events = env.events().all(); + assert_eq!(events.len(), 2); + } + + #[test] + fn test_pay_premium_emits_event() { + let env = make_env(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &contract_id); + let owner = Address::generate(&env); + + let policy_id = client.create_policy( + &owner, + &String::from_str(&env, "Emergency Coverage"), + &CoverageType::Emergency, + &75, + &25000, + ); + let events_before = env.events().all().len(); + + let result = client.pay_premium(&owner, &policy_id); + assert!(result); + + let events_after = env.events().all().len(); + assert_eq!(events_after - events_before, 2); + } + + #[test] + fn test_policy_lifecycle_emits_all_events() { + let env = make_env(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &contract_id); + let owner = Address::generate(&env); + + let policy_id = client.create_policy( + &owner, + &String::from_str(&env, "Complete Lifecycle"), + &CoverageType::Health, + &150, + &75000, + ); + + client.pay_premium(&owner, &policy_id); + client.deactivate_policy(&owner, &policy_id); + + let events = env.events().all(); + assert_eq!(events.len(), 6); + } + + // ==================================================================== + // Storage TTL Extension Tests + // + // Verify that instance storage TTL is properly extended on + // state-changing operations, preventing unexpected data expiration. + // + // Contract TTL configuration: + // INSTANCE_LIFETIME_THRESHOLD = 17,280 ledgers (~1 day) + // INSTANCE_BUMP_AMOUNT = 518,400 ledgers (~30 days) + // + // Operations extending instance TTL: + // create_policy, pay_premium, batch_pay_premiums, + // deactivate_policy, create_premium_schedule, + // modify_premium_schedule, cancel_premium_schedule, + // execute_due_premium_schedules + // ==================================================================== + + /// Verify that create_policy extends instance storage TTL. + #[test] + fn test_instance_ttl_extended_on_create_policy() { + let env = Env::default(); + env.mock_all_auths(); + + env.ledger().set(LedgerInfo { + protocol_version: 20, + sequence_number: 100, + timestamp: 1000, + network_id: [0; 32], + base_reserve: 10, + min_temp_entry_ttl: 100, + min_persistent_entry_ttl: 100, + max_entry_ttl: 700_000, + }); + + let contract_id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &contract_id); + let owner = Address::generate(&env); + + // create_policy calls extend_instance_ttl + let policy_id = client.create_policy( + &owner, + &String::from_str(&env, "Health Insurance"), + &CoverageType::Health, + &100, + &50000, + ); + assert_eq!(policy_id, 1); + + // Inspect instance TTL — must be at least INSTANCE_BUMP_AMOUNT + let ttl = env.as_contract(&contract_id, || env.storage().instance().get_ttl()); + assert!( + ttl >= 518_400, + "Instance TTL ({}) must be >= INSTANCE_BUMP_AMOUNT (518,400) after create_policy", + ttl + ); + } + + /// Verify that pay_premium refreshes instance TTL after ledger advancement. + /// + /// extend_ttl(threshold, extend_to) only extends when TTL <= threshold. + /// We advance the ledger far enough for TTL to drop below 17,280. + #[test] + fn test_instance_ttl_refreshed_on_pay_premium() { + let env = Env::default(); + env.mock_all_auths(); + + env.ledger().set(LedgerInfo { + protocol_version: 20, + sequence_number: 100, + timestamp: 1000, + network_id: [0; 32], + base_reserve: 10, + min_temp_entry_ttl: 100, + min_persistent_entry_ttl: 100, + max_entry_ttl: 700_000, + }); + + let contract_id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &contract_id); + let owner = Address::generate(&env); + + client.create_policy( + &owner, + &String::from_str(&env, "Life Insurance"), + &CoverageType::Life, + &200, + &100000, + ); + + // Advance ledger so TTL drops below threshold (17,280) + // After create_policy: live_until = 518,500. At seq 510,000: TTL = 8,500 + env.ledger().set(LedgerInfo { + protocol_version: 20, + sequence_number: 510_000, + timestamp: 500_000, + network_id: [0; 32], + base_reserve: 10, + min_temp_entry_ttl: 100, + min_persistent_entry_ttl: 100, + max_entry_ttl: 700_000, + }); + + // pay_premium calls extend_instance_ttl → re-extends TTL to 518,400 + client.pay_premium(&owner, &1); + + let ttl = env.as_contract(&contract_id, || env.storage().instance().get_ttl()); + assert!( + ttl >= 518_400, + "Instance TTL ({}) must be >= 518,400 after pay_premium", + ttl + ); + } + + /// Verify data persists across repeated operations spanning multiple + /// ledger advancements, proving TTL is continuously renewed. + #[test] + fn test_policy_data_persists_across_ledger_advancements() { + let env = Env::default(); + env.mock_all_auths(); + + env.ledger().set(LedgerInfo { + protocol_version: 20, + sequence_number: 100, + timestamp: 1000, + network_id: [0; 32], + base_reserve: 10, + min_temp_entry_ttl: 100, + min_persistent_entry_ttl: 100, + max_entry_ttl: 700_000, + }); + + let contract_id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &contract_id); + let owner = Address::generate(&env); + + // Phase 1: Create policy at seq 100. live_until = 518,500 + let policy_id = client.create_policy( + &owner, + &String::from_str(&env, "Auto Insurance"), + &CoverageType::Auto, + &150, + &75000, + ); + + // Phase 2: Advance to seq 510,000 (TTL = 8,500 < 17,280) + env.ledger().set(LedgerInfo { + protocol_version: 20, + sequence_number: 510_000, + timestamp: 510_000, + network_id: [0; 32], + base_reserve: 10, + min_temp_entry_ttl: 100, + min_persistent_entry_ttl: 100, + max_entry_ttl: 700_000, + }); + + client.pay_premium(&owner, &policy_id); + + // Phase 3: Advance to seq 1,020,000 (TTL = 8,400 < 17,280) + env.ledger().set(LedgerInfo { + protocol_version: 20, + sequence_number: 1_020_000, + timestamp: 1_020_000, + network_id: [0; 32], + base_reserve: 10, + min_temp_entry_ttl: 100, + min_persistent_entry_ttl: 100, + max_entry_ttl: 700_000, + }); + + let policy_id2 = client.create_policy( + &owner, + &String::from_str(&env, "Travel Insurance"), + &CoverageType::Travel, + &50, + &20000, + ); + + // All policies should be accessible + let p1 = client.get_policy(&policy_id); + assert!( + p1.is_some(), + "First policy must persist across ledger advancements" + ); + assert_eq!(p1.unwrap().monthly_premium, 150); + + let p2 = client.get_policy(&policy_id2); + assert!(p2.is_some(), "Second policy must persist"); + + // TTL should be fully refreshed + let ttl = env.as_contract(&contract_id, || env.storage().instance().get_ttl()); + assert!( + ttl >= 518_400, + "Instance TTL ({}) must remain >= 518,400 after repeated operations", + ttl + ); + } + + /// Verify that deactivate_policy extends instance TTL. + #[test] + fn test_instance_ttl_extended_on_deactivate_policy() { + let env = Env::default(); + env.mock_all_auths(); + + env.ledger().set(LedgerInfo { + protocol_version: 20, + sequence_number: 100, + timestamp: 1000, + network_id: [0; 32], + base_reserve: 10, + min_temp_entry_ttl: 100, + min_persistent_entry_ttl: 100, + max_entry_ttl: 700_000, + }); + + let contract_id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &contract_id); + let owner = Address::generate(&env); + + let policy_id = client.create_policy( + &owner, + &String::from_str(&env, "Dental"), + &CoverageType::Dental, + &75, + &25000, + ); + + // Advance ledger past threshold + env.ledger().set(LedgerInfo { + protocol_version: 20, + sequence_number: 510_000, + timestamp: 510_000, + network_id: [0; 32], + base_reserve: 10, + min_temp_entry_ttl: 100, + min_persistent_entry_ttl: 100, + max_entry_ttl: 700_000, + }); + + // deactivate_policy calls extend_instance_ttl + client.deactivate_policy(&owner, &policy_id); + + let ttl = env.as_contract(&contract_id, || env.storage().instance().get_ttl()); + assert!( + ttl >= 518_400, + "Instance TTL ({}) must be >= 518,400 after deactivate_policy", + ttl + ); + } + + // ────────────────────────────────────────────────────────────────── + // Test: pay_premium after deactivate_policy (#104) + // ────────────────────────────────────────────────────────────────── + + /// After deactivating a policy, `pay_premium` must panic with + /// "Policy is not active". The policy must remain inactive. + #[test] + #[should_panic(expected = "Policy is not active")] + fn test_pay_premium_after_deactivate() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &contract_id); + let owner = Address::generate(&env); + + // 1. Create a policy + let policy_id = client.create_policy( + &owner, + &String::from_str(&env, "Health Plan"), + &CoverageType::Health, + &150, + &50000, + ); + + // Sanity: policy should be active after creation + let policy_before = client.get_policy(&policy_id).unwrap(); + assert!(policy_before.active); + + // 2. Deactivate the policy + let deactivated = client.deactivate_policy(&owner, &policy_id); + assert!(deactivated); + + // Confirm it is now inactive + let policy_after_deactivate = client.get_policy(&policy_id).unwrap(); + assert!(!policy_after_deactivate.active); + + // 3. Attempt to pay premium — must panic + client.pay_premium(&owner, &policy_id); + } + + // ----------------------------------------------------------------------- + // Property-based tests: time-dependent behavior + // ----------------------------------------------------------------------- + + proptest! { + /// After paying a premium at any timestamp `now`, + /// next_payment_date must always equal now + 30 days. + #[test] + fn prop_pay_premium_sets_next_payment_date( + now in 1_000_000u64..100_000_000u64, + ) { + let env = make_env(); + env.ledger().set_timestamp(now); + env.mock_all_auths(); + let cid = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &cid); + let owner = Address::generate(&env); + + let policy_id = client.create_policy( + &owner, + &String::from_str(&env, "Policy"), + &CoverageType::Health, + &100, + &10000, + ); + + client.pay_premium(&owner, &policy_id); + + let policy = client.get_policy(&policy_id).unwrap(); + prop_assert_eq!( + policy.next_payment_date, + now + 30 * 86400, + "next_payment_date must equal now + 30 days after premium payment" + ); + } + } + + proptest! { + /// A premium schedule must not execute before its due date, + /// and must execute at or after its due date. + #[test] + fn prop_execute_due_schedules_only_triggers_past_due( + creation_time in 1_000_000u64..5_000_000u64, + gap in 1000u64..1_000_000u64, + ) { + let env = make_env(); + env.ledger().set_timestamp(creation_time); + env.mock_all_auths(); + let cid = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &cid); + let owner = Address::generate(&env); + + let policy_id = client.create_policy( + &owner, + &String::from_str(&env, "Policy"), + &CoverageType::Health, + &100, + &10000, + ); + + // Schedule fires at creation_time + gap (strictly in the future) + let next_due = creation_time + gap; + let schedule_id = client.create_premium_schedule(&owner, &policy_id, &next_due, &0); + + // One tick before due: schedule must not execute + env.ledger().set_timestamp(next_due - 1); + let executed_before = client.execute_due_premium_schedules(); + prop_assert_eq!( + executed_before.len(), + 0u32, + "schedule must not fire before its due date" + ); + + // Exactly at due date: schedule must execute + env.ledger().set_timestamp(next_due); + let executed_at = client.execute_due_premium_schedules(); + prop_assert_eq!(executed_at.len(), 1u32); + prop_assert_eq!(executed_at.get(0).unwrap(), schedule_id); + } + } +} diff --git a/insurance/src/test.rs b/insurance/src/test.rs index ec536c69..897fa972 100644 --- a/insurance/src/test.rs +++ b/insurance/src/test.rs @@ -1,20 +1,20 @@ #![cfg(test)] use super::*; -use crate::InsuranceError; +use remitwise_common::CoverageType; use soroban_sdk::{ - testutils::{Address as AddressTrait, Ledger, LedgerInfo}, + testutils::{Address as _, Ledger}, Address, Env, String, }; -use proptest::prelude::*; - -use testutils::{set_ledger_time, setup_test_env}; - -// Removed local set_time in favor of testutils::set_ledger_time #[test] -fn test_create_policy_succeeds() { - setup_test_env!(env, Insurance, InsuranceClient, client, owner); +fn test_create_policy() { + let env = Env::default(); + let contract_id = env.register_contract(None, Insurance); + let client = InsuranceClient::new(&env, &contract_id); + let owner = Address::generate(&env); + + env.mock_all_auths(); let name = String::from_str(&env, "Health Policy"); let coverage_type = CoverageType::Health; @@ -47,10 +47,9 @@ fn test_create_policy_invalid_premium() { env.mock_all_auths(); client.create_policy( - let result = client.try_create_policy( &owner, &String::from_str(&env, "Bad"), - &String::from_str(&env, "Type"), + &CoverageType::Health, &0, &10000, ); @@ -58,10 +57,6 @@ fn test_create_policy_invalid_premium() { #[test] #[should_panic(expected = "Coverage amount must be positive")] - assert_eq!(result, Err(Ok(InsuranceError::InvalidPremium))); -} - -#[test] fn test_create_policy_invalid_coverage() { let env = Env::default(); let contract_id = env.register_contract(None, Insurance); @@ -71,14 +66,12 @@ fn test_create_policy_invalid_coverage() { env.mock_all_auths(); client.create_policy( - let result = client.try_create_policy( &owner, &String::from_str(&env, "Bad"), - &String::from_str(&env, "Type"), + &CoverageType::Health, &100, &0, ); - assert_eq!(result, Err(Ok(InsuranceError::InvalidCoverage))); } #[test] @@ -93,7 +86,7 @@ fn test_pay_premium() { let policy_id = client.create_policy( &owner, &String::from_str(&env, "Policy"), - &String::from_str(&env, "Type"), + &CoverageType::Health, &100, &10000, ); @@ -104,9 +97,12 @@ fn test_pay_premium() { let initial_due = initial_policy.next_payment_date; // Advance ledger time to simulate paying slightly later - set_ledger_time(&env, 1, env.ledger().timestamp() + 1000); + let mut ledger_info = env.ledger().get(); + ledger_info.timestamp += 1000; + env.ledger().set(ledger_info); - client.pay_premium(&owner, &policy_id); + let success = client.pay_premium(&owner, &policy_id); + assert!(success); let updated_policy = client.get_policy(&policy_id).unwrap(); @@ -129,15 +125,13 @@ fn test_pay_premium_unauthorized() { let policy_id = client.create_policy( &owner, &String::from_str(&env, "Policy"), - &String::from_str(&env, "Type"), + &CoverageType::Health, &100, &10000, ); // unauthorized payer client.pay_premium(&other, &policy_id); - let result = client.try_pay_premium(&other, &policy_id); - assert_eq!(result, Err(Ok(InsuranceError::Unauthorized))); } #[test] @@ -152,7 +146,7 @@ fn test_deactivate_policy() { let policy_id = client.create_policy( &owner, &String::from_str(&env, "Policy"), - &String::from_str(&env, "Type"), + &CoverageType::Health, &100, &10000, ); @@ -165,7 +159,7 @@ fn test_deactivate_policy() { } #[test] -fn test_get_active_policies() { +fn test_deactivate_policy_idempotency() { let env = Env::default(); let contract_id = env.register_contract(None, Insurance); let client = InsuranceClient::new(&env, &contract_id); @@ -173,140 +167,65 @@ fn test_get_active_policies() { env.mock_all_auths(); - // Create 3 policies - client.create_policy( + let policy_id = client.create_policy( &owner, - &String::from_str(&env, "P1"), - &String::from_str(&env, "T1"), + &String::from_str(&env, "IdempotentPolicy"), + &CoverageType::Health, &100, - &1000, - ); - let p2 = client.create_policy( - &owner, - &String::from_str(&env, "P2"), - &String::from_str(&env, "T2"), - &200, - &2000, - ); - client.create_policy( - &owner, - &String::from_str(&env, "P3"), - &String::from_str(&env, "T3"), - &300, - &3000, + &10000, ); - // Deactivate P2 - client.deactivate_policy(&owner, &p2); - - let active = client.get_active_policies(&owner); - assert_eq!(active.len(), 2); - - // Check specific IDs if needed, but length 2 confirms one was filtered -} - -#[test] -fn test_get_active_policies_excludes_deactivated() { - let env = Env::default(); - let contract_id = env.register_contract(None, Insurance); - let client = InsuranceClient::new(&env, &contract_id); - let owner = Address::generate(&env); - - env.mock_all_auths(); - - // Create policy 1 and policy 2 for the same owner - let policy_id_1 = client.create_policy( - &owner, - &String::from_str(&env, "Policy 1"), - &String::from_str(&env, "Type 1"), - &100, - &1000, - ); - let policy_id_2 = client.create_policy( - &owner, - &String::from_str(&env, "Policy 2"), - &String::from_str(&env, "Type 2"), - &200, - &2000, - ); + // First deactivation should return true + let first = client.deactivate_policy(&owner, &policy_id); + assert!(first); - // Deactivate policy 1 - client.deactivate_policy(&owner, &policy_id_1); + // Second deactivation should return false (it's already inactive) + let second = client.deactivate_policy(&owner, &policy_id); + assert!(!second); - // get_active_policies must return only the still-active policy - let active = client.get_active_policies(&owner, &0, &DEFAULT_PAGE_LIMIT); - assert_eq!( - active.items.len(), - 1, - "get_active_policies must return exactly one policy" - ); - let only = active.items.get(0).unwrap(); - assert_eq!( - only.id, policy_id_2, - "the returned policy must be the active one (policy_id_2)" - ); - assert!(only.active, "returned policy must have active == true"); + let policy = client.get_policy(&policy_id).unwrap(); + assert!(!policy.active); } #[test] -fn test_get_all_policies_for_owner_pagination() { +fn test_get_active_policies() { let env = Env::default(); let contract_id = env.register_contract(None, Insurance); let client = InsuranceClient::new(&env, &contract_id); let owner = Address::generate(&env); - let other = Address::generate(&env); env.mock_all_auths(); - // Create 3 policies for owner + // Create 3 policies client.create_policy( &owner, &String::from_str(&env, "P1"), - &String::from_str(&env, "T1"), + &CoverageType::Health, &100, &1000, ); let p2 = client.create_policy( &owner, &String::from_str(&env, "P2"), - &String::from_str(&env, "T2"), + &CoverageType::Health, &200, &2000, ); client.create_policy( &owner, &String::from_str(&env, "P3"), - &String::from_str(&env, "T3"), + &CoverageType::Health, &300, &3000, ); - // Create 1 policy for other - client.create_policy( - &other, - &String::from_str(&env, "Other P"), - &String::from_str(&env, "Type"), - &500, - &5000, - ); - // Deactivate P2 client.deactivate_policy(&owner, &p2); - // get_all_policies_for_owner should return all 3 for owner - let page = client.get_all_policies_for_owner(&owner, &0, &10); - assert_eq!(page.items.len(), 3); - assert_eq!(page.count, 3); - - // verify p2 is in the list and is inactive - let mut found_p2 = false; - for policy in page.items.iter() { - if policy.id == p2 { - found_p2 = true; - assert!(!policy.active); - } - } - assert!(found_p2); + let active = client.get_active_policies(&owner); + assert_eq!(active.len(), 2); + + // Check specific IDs if needed, but length 2 confirms one was filtered } #[test] @@ -321,14 +240,14 @@ fn test_get_total_monthly_premium() { client.create_policy( &owner, &String::from_str(&env, "P1"), - &String::from_str(&env, "T1"), + &CoverageType::Health, &100, &1000, ); client.create_policy( &owner, &String::from_str(&env, "P2"), - &String::from_str(&env, "T2"), + &CoverageType::Health, &200, &2000, ); @@ -338,21 +257,7 @@ fn test_get_total_monthly_premium() { } #[test] -fn test_get_total_monthly_premium_zero_policies() { - let env = Env::default(); - let contract_id = env.register_contract(None, Insurance); - let client = InsuranceClient::new(&env, &contract_id); - let owner = Address::generate(&env); - - env.mock_all_auths(); - - // Fresh address with no policies - let total = client.get_total_monthly_premium(&owner); - assert_eq!(total, 0); -} - -#[test] -fn test_get_total_monthly_premium_one_policy() { +fn test_multiple_premium_payments() { let env = Env::default(); let contract_id = env.register_contract(None, Insurance); let client = InsuranceClient::new(&env, &contract_id); @@ -360,21 +265,46 @@ fn test_get_total_monthly_premium_one_policy() { env.mock_all_auths(); - // Create one policy with monthly_premium = 500 - client.create_policy( + let policy_id = client.create_policy( &owner, - &String::from_str(&env, "Single Policy"), - &CoverageType::Health, - &500, + &String::from_str(&env, "LongTerm"), + &CoverageType::Life, + &100, &10000, ); - let total = client.get_total_monthly_premium(&owner); - assert_eq!(total, 500); + let p1 = client.get_policy(&policy_id).unwrap(); + let first_due = p1.next_payment_date; + + // First payment + client.pay_premium(&owner, &policy_id); + + // Simulate time passing (still before next due) + let mut ledger = env.ledger().get(); + ledger.timestamp += 5000; + env.ledger().set(ledger); + + // Second payment + client.pay_premium(&owner, &policy_id); + + let p2 = client.get_policy(&policy_id).unwrap(); + + // The logic in contract sets next_payment_date to 'now + 30 days' + // So paying twice in quick succession just pushes it to 30 days from the SECOND payment + // It does NOT add 60 days from start. This test verifies that behavior. + assert!(p2.next_payment_date > first_due); + assert_eq!( + p2.next_payment_date, + env.ledger().timestamp() + (30 * 86400) + ); } +// ============================================ +// Storage Optimization and Archival Tests +// ============================================ + #[test] -fn test_get_total_monthly_premium_multiple_active_policies() { +fn test_archive_inactive_policies() { let env = Env::default(); let contract_id = env.register_contract(None, Insurance); let client = InsuranceClient::new(&env, &contract_id); @@ -382,35 +312,49 @@ fn test_get_total_monthly_premium_multiple_active_policies() { env.mock_all_auths(); - // Create three policies with premiums 100, 200, 300 - client.create_policy( + // Create policies + let id1 = client.create_policy( &owner, - &String::from_str(&env, "Policy 1"), + &String::from_str(&env, "Policy1"), &CoverageType::Health, &100, - &1000, + &10000, ); - client.create_policy( + let id2 = client.create_policy( &owner, - &String::from_str(&env, "Policy 2"), + &String::from_str(&env, "Policy2"), &CoverageType::Life, &200, - &2000, + &20000, ); + // Keep one active client.create_policy( &owner, - &String::from_str(&env, "Policy 3"), + &String::from_str(&env, "Policy3"), &CoverageType::Auto, - &300, - &3000, + &150, + &15000, ); - let total = client.get_total_monthly_premium(&owner); - assert_eq!(total, 600); // 100 + 200 + 300 + // Deactivate policies 1 and 2 + client.deactivate_policy(&owner, &id1); + client.deactivate_policy(&owner, &id2); + + // Archive inactive policies + let archived_count = client.archive_inactive_policies(&owner, &3_000_000_000); + assert_eq!(archived_count, 2); + + // Verify only active policy remains + let active = client.get_active_policies(&owner); + assert_eq!(active.len(), 1); + + // Verify archived policies + let archived = client.get_archived_policies(&owner); + assert_eq!(archived.len(), 2); } #[test] -fn test_get_total_monthly_premium_deactivated_policy_excluded() { +fn test_archive_empty_when_all_active() { let env = Env::default(); let contract_id = env.register_contract(None, Insurance); let client = InsuranceClient::new(&env, &contract_id); @@ -418,85 +362,56 @@ fn test_get_total_monthly_premium_deactivated_policy_excluded() { env.mock_all_auths(); - // Create two policies with premiums 100 and 200 - let policy1 = client.create_policy( + client.create_policy( &owner, - &String::from_str(&env, "Policy 1"), + &String::from_str(&env, "Active1"), &CoverageType::Health, &100, - &1000, + &10000, ); - let policy2 = client.create_policy( + client.create_policy( &owner, - &String::from_str(&env, "Policy 2"), + &String::from_str(&env, "Active2"), &CoverageType::Life, &200, - &2000, + &20000, ); - // Verify total includes both policies initially - let total_initial = client.get_total_monthly_premium(&owner); - assert_eq!(total_initial, 300); // 100 + 200 + let archived_count = client.archive_inactive_policies(&owner, &3_000_000_000); + assert_eq!(archived_count, 0); - // Deactivate the first policy - client.deactivate_policy(&owner, &policy1); - - // Verify total only includes the active policy - let total_after_deactivation = client.get_total_monthly_premium(&owner); - assert_eq!(total_after_deactivation, 200); // Only policy 2 + assert_eq!(client.get_active_policies(&owner).len(), 2); + assert_eq!(client.get_archived_policies(&owner).len(), 0); } #[test] -fn test_get_total_monthly_premium_different_owner_isolation() { +fn test_get_archived_policy() { let env = Env::default(); let contract_id = env.register_contract(None, Insurance); let client = InsuranceClient::new(&env, &contract_id); - let owner_a = Address::generate(&env); - let owner_b = Address::generate(&env); + let owner = Address::generate(&env); env.mock_all_auths(); - // Create policies for owner_a - client.create_policy( - &owner_a, - &String::from_str(&env, "Policy A1"), + let id = client.create_policy( + &owner, + &String::from_str(&env, "Archive"), &CoverageType::Health, &100, - &1000, - ); - client.create_policy( - &owner_a, - &String::from_str(&env, "Policy A2"), - &CoverageType::Life, - &200, - &2000, - ); - - // Create policies for owner_b - client.create_policy( - &owner_b, - &String::from_str(&env, "Policy B1"), - &String::from_str(&env, "emergency"), - &300, - &3000, + &5000, ); + client.deactivate_policy(&owner, &id); + client.archive_inactive_policies(&owner, &3_000_000_000); - // Verify owner_a's total only includes their policies - let total_a = client.get_total_monthly_premium(&owner_a); - assert_eq!(total_a, 300); // 100 + 200 - - // Verify owner_b's total only includes their policies - let total_b = client.get_total_monthly_premium(&owner_b); - assert_eq!(total_b, 300); // 300 - - // Verify no cross-owner leakage - assert_ne!(total_a, 0); // owner_a has policies - assert_ne!(total_b, 0); // owner_b has policies - assert_eq!(total_a, total_b); // Both have same total but different policies + let archived_policy = client.get_archived_policy(&id); + assert!(archived_policy.is_some()); + let policy = archived_policy.unwrap(); + assert_eq!(policy.id, id); + assert_eq!(policy.total_coverage, 5000); } #[test] -fn test_multiple_premium_payments() { +fn test_restore_policy() { let env = Env::default(); let contract_id = env.register_contract(None, Insurance); let client = InsuranceClient::new(&env, &contract_id); @@ -504,1176 +419,131 @@ fn test_multiple_premium_payments() { env.mock_all_auths(); - let policy_id = client.create_policy( + let id = client.create_policy( &owner, - &String::from_str(&env, "LongTerm"), - &String::from_str(&env, "Life"), - &100, - &10000, + &String::from_str(&env, "Restore"), + &CoverageType::Life, + &150, + &15000, ); + client.deactivate_policy(&owner, &id); + client.archive_inactive_policies(&owner, &3_000_000_000); - let p1 = client.get_policy(&policy_id).unwrap(); - let first_due = p1.next_payment_date; - - // First payment - client.pay_premium(&owner, &policy_id); - - // Simulate time passing (still before next due) - set_ledger_time(&env, 1, env.ledger().timestamp() + 5000); - - // Second payment - client.pay_premium(&owner, &policy_id); + assert!(client.get_policy(&id).is_none()); + assert!(client.get_archived_policy(&id).is_some()); - let p2 = client.get_policy(&policy_id).unwrap(); + let restored = client.restore_policy(&owner, &id); + assert!(restored); - // The logic in contract sets next_payment_date to 'now + 30 days' - // So paying twice in quick succession just pushes it to 30 days from the SECOND payment - // It does NOT add 60 days from start. This test verifies that behavior. - assert!(p2.next_payment_date > first_due); - assert_eq!( - p2.next_payment_date, - env.ledger().timestamp() + (30 * 86400) - ); + assert!(client.get_policy(&id).is_some()); + assert!(client.get_archived_policy(&id).is_none()); } #[test] -fn test_create_premium_schedule_succeeds() { - setup_test_env!(env, Insurance, InsuranceClient, client, owner); - set_ledger_time(&env, 1000); - - let policy_id = client.create_policy( - &owner, - &String::from_str(&env, "Health Insurance"), - &CoverageType::Health, - &500, - &50000, - ); - - let schedule_id = client.create_premium_schedule(&owner, &policy_id, &3000, &2592000); - assert_eq!(schedule_id, 1); - - let schedule = client.get_premium_schedule(&schedule_id); - assert!(schedule.is_some()); - let schedule = schedule.unwrap(); - assert_eq!(schedule.next_due, 3000); - assert_eq!(schedule.interval, 2592000); - assert!(schedule.active); -} - -#[test] -fn test_modify_premium_schedule() { +#[should_panic(expected = "Archived policy not found")] +fn test_restore_nonexistent_policy() { let env = Env::default(); let contract_id = env.register_contract(None, Insurance); let client = InsuranceClient::new(&env, &contract_id); - let owner = ::generate(&env); + let owner = Address::generate(&env); env.mock_all_auths(); - set_ledger_time(&env, 1, 1000); - - let policy_id = client.create_policy( - &owner, - &String::from_str(&env, "Health Insurance"), - &CoverageType::Health, - &500, - &50000, - ); - - let schedule_id = client.create_premium_schedule(&owner, &policy_id, &3000, &2592000); - client.modify_premium_schedule(&owner, &schedule_id, &4000, &2678400); - let schedule = client.get_premium_schedule(&schedule_id).unwrap(); - assert_eq!(schedule.next_due, 4000); - assert_eq!(schedule.interval, 2678400); + client.restore_policy(&owner, &999); } #[test] -fn test_cancel_premium_schedule() { +#[should_panic(expected = "Only the policy owner can restore this policy")] +fn test_restore_policy_unauthorized() { let env = Env::default(); let contract_id = env.register_contract(None, Insurance); let client = InsuranceClient::new(&env, &contract_id); - let owner = ::generate(&env); + let owner = Address::generate(&env); + let other = Address::generate(&env); env.mock_all_auths(); - set_ledger_time(&env, 1, 1000); - let policy_id = client.create_policy( + let id = client.create_policy( &owner, - &String::from_str(&env, "Health Insurance"), + &String::from_str(&env, "Auth"), &CoverageType::Health, - &500, - &50000, + &100, + &10000, ); + client.deactivate_policy(&owner, &id); + client.archive_inactive_policies(&owner, &3_000_000_000); - let schedule_id = client.create_premium_schedule(&owner, &policy_id, &3000, &2592000); - client.cancel_premium_schedule(&owner, &schedule_id); - - let schedule = client.get_premium_schedule(&schedule_id).unwrap(); - assert!(!schedule.active); + client.restore_policy(&other, &id); } #[test] -fn test_execute_due_premium_schedules() { +fn test_bulk_cleanup_policies() { let env = Env::default(); let contract_id = env.register_contract(None, Insurance); let client = InsuranceClient::new(&env, &contract_id); - let owner = ::generate(&env); + let owner = Address::generate(&env); env.mock_all_auths(); - set_ledger_time(&env, 1, 1000); - let policy_id = client.create_policy( + let id1 = client.create_policy( &owner, - &String::from_str(&env, "Health Insurance"), + &String::from_str(&env, "Old1"), &CoverageType::Health, - &500, - &50000, + &100, + &1000, ); - - let schedule_id = client.create_premium_schedule(&owner, &policy_id, &3000, &0); - - set_ledger_time(&env, 1, 3500); - let executed = client.execute_due_premium_schedules(); - - assert_eq!(executed.len(), 1); - assert_eq!(executed.get(0).unwrap(), schedule_id); - - let policy = client.get_policy(&policy_id).unwrap(); - assert_eq!(policy.next_payment_date, 3500 + 30 * 86400); -} - -#[test] -fn test_execute_recurring_premium_schedule() { - let env = Env::default(); - let contract_id = env.register_contract(None, Insurance); - let client = InsuranceClient::new(&env, &contract_id); - let owner = ::generate(&env); - - env.mock_all_auths(); - set_ledger_time(&env, 1, 1000); - - let policy_id = client.create_policy( + let id2 = client.create_policy( &owner, - &String::from_str(&env, "Health Insurance"), - &String::from_str(&env, "health"), - &500, - &50000, + &String::from_str(&env, "Old2"), + &CoverageType::Life, + &200, + &2000, ); + client.deactivate_policy(&owner, &id1); + client.deactivate_policy(&owner, &id2); - let schedule_id = client.create_premium_schedule(&owner, &policy_id, &3000, &2592000); + client.archive_inactive_policies(&owner, &3_000_000_000); + assert_eq!(client.get_archived_policies(&owner).len(), 2); - set_ledger_time(&env, 1, 3500); - client.execute_due_premium_schedules(); - - let schedule = client.get_premium_schedule(&schedule_id).unwrap(); - assert!(schedule.active); - assert_eq!(schedule.next_due, 3000 + 2592000); + let deleted = client.bulk_cleanup_policies(&owner, &1000000); + assert_eq!(deleted, 2); + assert_eq!(client.get_archived_policies(&owner).len(), 0); } #[test] -fn test_execute_missed_premium_schedules() { +fn test_storage_stats() { let env = Env::default(); let contract_id = env.register_contract(None, Insurance); let client = InsuranceClient::new(&env, &contract_id); - let owner = ::generate(&env); + let owner = Address::generate(&env); env.mock_all_auths(); - set_ledger_time(&env, 1, 1000); - - let policy_id = client.create_policy( - &owner, - &String::from_str(&env, "Health Insurance"), - &CoverageType::Health, - &500, - &50000, - ); - - let schedule_id = client.create_premium_schedule(&owner, &policy_id, &3000, &2592000); - - set_time(&env, 3000 + 2592000 * 3 + 100); - client.execute_due_premium_schedules(); - - let schedule = client.get_premium_schedule(&schedule_id).unwrap(); - assert_eq!(schedule.missed_count, 3); - assert!(schedule.next_due > 3000 + 2592000 * 3); -} - -#[test] -fn test_get_premium_schedules() { - let env = Env::default(); - let contract_id = env.register_contract(None, Insurance); - let client = InsuranceClient::new(&env, &contract_id); - let owner = ::generate(&env); - env.mock_all_auths(); - set_ledger_time(&env, 1, 1000); + let stats = client.get_storage_stats(); + assert_eq!(stats.active_policies, 0); + assert_eq!(stats.archived_policies, 0); - let policy_id1 = client.create_policy( + let id1 = client.create_policy( &owner, - &String::from_str(&env, "Health Insurance"), + &String::from_str(&env, "P1"), &CoverageType::Health, - &500, - &50000, + &100, + &10000, ); - - let policy_id2 = client.create_policy( + client.create_policy( &owner, - &String::from_str(&env, "Life Insurance"), - &String::from_str(&env, "life"), - &300, - &100000, + &String::from_str(&env, "P2"), + &CoverageType::Life, + &200, + &20000, ); + client.deactivate_policy(&owner, &id1); - client.create_premium_schedule(&owner, &policy_id1, &3000, &2592000); - client.create_premium_schedule(&owner, &policy_id2, &4000, &2592000); - - // ----------------------------------------------------------------------- - // 3. create_policy — boundary conditions - // ----------------------------------------------------------------------- - - // --- Health min/max boundaries --- - - #[test] - fn test_health_premium_at_minimum_boundary() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // min_premium for Health = 1_000_000 - client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &1_000_000i128, - &10_000_000i128, // min coverage - &None, - ); - } - - #[test] - fn test_health_premium_at_maximum_boundary() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // max_premium = 500_000_000; need coverage ≤ 500M * 12 * 500 = 3T (within 100B limit) - client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &500_000_000i128, - &100_000_000_000i128, // max coverage for Health - &None, - ); - } - - #[test] - fn test_health_coverage_at_minimum_boundary() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &10_000_000i128, // exactly min_coverage - &None, - ); - } - - #[test] - fn test_health_coverage_at_maximum_boundary() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // max_coverage = 100_000_000_000; need premium ≥ 100B / (12*500) ≈ 16_666_667 - client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &500_000_000i128, // max premium to allow max coverage via ratio - &100_000_000_000i128, // exactly max_coverage - &None, - ); - } - - // --- Life boundaries --- - - #[test] - fn test_life_premium_at_minimum_boundary() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - client.create_policy( - &caller, - &String::from_str(&env, "Life Min"), - &CoverageType::Life, - &500_000i128, // min_premium - &50_000_000i128, // min_coverage - &None, - ); - } - - #[test] - fn test_liability_premium_at_minimum_boundary() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - client.create_policy( - &caller, - &String::from_str(&env, "Liability Min"), - &CoverageType::Liability, - &800_000i128, // min_premium - &5_000_000i128, // min_coverage - &None, - ); - } - - // ----------------------------------------------------------------------- - // 4. create_policy — name validation - // ----------------------------------------------------------------------- - - #[test] - #[should_panic(expected = "name cannot be empty")] - fn test_create_policy_empty_name_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - client.create_policy( - &caller, - &String::from_str(&env, ""), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "name too long")] - fn test_create_policy_name_exceeds_max_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // 65 character name — exceeds MAX_NAME_LEN (64) - let long_name = String::from_str( - &env, - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1", - ); - client.create_policy( - &caller, - &long_name, - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - } - - #[test] - fn test_create_policy_name_at_max_length_succeeds() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Exactly 64 characters - let max_name = String::from_str( - &env, - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - ); - client.create_policy( - &caller, - &max_name, - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - } - - // ----------------------------------------------------------------------- - // 5. create_policy — premium validation failures - // ----------------------------------------------------------------------- - - #[test] - #[should_panic(expected = "monthly_premium must be positive")] - fn test_create_policy_zero_premium_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &0i128, - &50_000_000i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "monthly_premium must be positive")] - fn test_create_policy_negative_premium_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &-1i128, - &50_000_000i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "monthly_premium out of range for coverage type")] - fn test_create_health_policy_premium_below_min_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Health min_premium = 1_000_000; supply 999_999 - client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &999_999i128, - &50_000_000i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "monthly_premium out of range for coverage type")] - fn test_create_health_policy_premium_above_max_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Health max_premium = 500_000_000; supply 500_000_001 - client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &500_000_001i128, - &10_000_000i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "monthly_premium out of range for coverage type")] - fn test_create_life_policy_premium_below_min_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Life min_premium = 500_000; supply 499_999 - client.create_policy( - &caller, - &String::from_str(&env, "Life"), - &CoverageType::Life, - &499_999i128, - &50_000_000i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "monthly_premium out of range for coverage type")] - fn test_create_property_policy_premium_below_min_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Property min_premium = 2_000_000; supply 1_999_999 - client.create_policy( - &caller, - &String::from_str(&env, "Property"), - &CoverageType::Property, - &1_999_999i128, - &100_000_000i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "monthly_premium out of range for coverage type")] - fn test_create_auto_policy_premium_below_min_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Auto min_premium = 1_500_000; supply 1_499_999 - client.create_policy( - &caller, - &String::from_str(&env, "Auto"), - &CoverageType::Auto, - &1_499_999i128, - &20_000_000i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "monthly_premium out of range for coverage type")] - fn test_create_liability_policy_premium_below_min_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Liability min_premium = 800_000; supply 799_999 - client.create_policy( - &caller, - &String::from_str(&env, "Liability"), - &CoverageType::Liability, - &799_999i128, - &5_000_000i128, - &None, - ); - } - - // ----------------------------------------------------------------------- - // 6. create_policy — coverage amount validation failures - // ----------------------------------------------------------------------- - - #[test] - #[should_panic(expected = "coverage_amount must be positive")] - fn test_create_policy_zero_coverage_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &0i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "coverage_amount must be positive")] - fn test_create_policy_negative_coverage_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &-1i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "coverage_amount out of range for coverage type")] - fn test_create_health_policy_coverage_below_min_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Health min_coverage = 10_000_000; supply 9_999_999 - client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &9_999_999i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "coverage_amount out of range for coverage type")] - fn test_create_health_policy_coverage_above_max_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Health max_coverage = 100_000_000_000; supply 100_000_000_001 - client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &500_000_000i128, - &100_000_000_001i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "coverage_amount out of range for coverage type")] - fn test_create_life_policy_coverage_below_min_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Life min_coverage = 50_000_000; supply 49_999_999 - client.create_policy( - &caller, - &String::from_str(&env, "Life"), - &CoverageType::Life, - &1_000_000i128, - &49_999_999i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "coverage_amount out of range for coverage type")] - fn test_create_property_policy_coverage_below_min_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Property min_coverage = 100_000_000; supply 99_999_999 - client.create_policy( - &caller, - &String::from_str(&env, "Property"), - &CoverageType::Property, - &5_000_000i128, - &99_999_999i128, - &None, - ); - } - - // ----------------------------------------------------------------------- - // 7. create_policy — ratio guard (unsupported combination) - // ----------------------------------------------------------------------- - - #[test] - #[should_panic(expected = "unsupported combination: coverage_amount too high relative to premium")] - fn test_create_policy_coverage_too_high_for_premium_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // premium = 1_000_000 → annual = 12_000_000 → max_coverage = 6_000_000_000 - // supply coverage = 6_000_000_001 (just over the ratio limit, but within Health's hard max) - // Need premium high enough so health range isn't hit, but ratio is - // Health max_coverage = 100_000_000_000 - // Use premium = 1_000_000, coverage = 7_000_000_000 → over ratio (6B), under hard cap (100B) - client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &1_000_000i128, - &7_000_000_000i128, - &None, - ); - } - - #[test] - fn test_create_policy_coverage_exactly_at_ratio_limit_succeeds() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // premium = 1_000_000 → ratio limit = 1M * 12 * 500 = 6_000_000_000 - // Health max_coverage = 100B, so 6B is fine - client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &1_000_000i128, - &6_000_000_000i128, - &None, - ); - } - - // ----------------------------------------------------------------------- - // 8. External ref validation - // ----------------------------------------------------------------------- - - #[test] - #[should_panic(expected = "external_ref length out of range")] - fn test_create_policy_ext_ref_too_long_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // 129 character external ref — exceeds MAX_EXT_REF_LEN (128) - let long_ref = String::from_str( - &env, - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1", - ); - client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &Some(long_ref), - ); - } - - #[test] - fn test_create_policy_ext_ref_at_max_length_succeeds() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Exactly 128 characters - let max_ref = String::from_str( - &env, - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - ); - client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &Some(max_ref), - ); - } - - // ----------------------------------------------------------------------- - // 9. pay_premium — happy path - // ----------------------------------------------------------------------- - - #[test] - fn test_pay_premium_success() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - let id = client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - let result = client.pay_premium(&caller, &id, &5_000_000i128); - assert!(result); - } - - #[test] - fn test_pay_premium_updates_next_payment_date() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - env.ledger().set_timestamp(1_000_000u64); - let id = client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - env.ledger().set_timestamp(2_000_000u64); - client.pay_premium(&caller, &id, &5_000_000i128); - let policy = client.get_policy(&id); - // next_payment_due should be 2_000_000 + 30 days - assert_eq!(policy.next_payment_due, 2_000_000 + 30 * 24 * 60 * 60); - assert_eq!(policy.last_payment_at, 2_000_000u64); - } - - // ----------------------------------------------------------------------- - // 10. pay_premium — failure cases - // ----------------------------------------------------------------------- - - #[test] - #[should_panic(expected = "policy not found")] - fn test_pay_premium_nonexistent_policy_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - client.pay_premium(&caller, &999u32, &5_000_000i128); - } - - #[test] - #[should_panic(expected = "amount must equal monthly_premium")] - fn test_pay_premium_wrong_amount_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - let id = client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - client.pay_premium(&caller, &id, &4_999_999i128); - } - - #[test] - #[should_panic(expected = "policy inactive")] - fn test_pay_premium_on_inactive_policy_panics() { - let (env, client, owner) = setup(); - let caller = Address::generate(&env); - let id = client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - client.deactivate_policy(&owner, &id); - client.pay_premium(&caller, &id, &5_000_000i128); - } - - // ----------------------------------------------------------------------- - // 11. deactivate_policy — happy path - // ----------------------------------------------------------------------- - - #[test] - fn test_deactivate_policy_success() { - let (env, client, owner) = setup(); - let caller = Address::generate(&env); - let id = client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - let result = client.deactivate_policy(&owner, &id); - assert!(result); - - let policy = client.get_policy(&id); - assert!(!policy.active); - } - - #[test] - fn test_deactivate_removes_from_active_list() { - let (env, client, owner) = setup(); - let caller = Address::generate(&env); - let id = client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - assert_eq!(client.get_active_policies().len(), 1); - client.deactivate_policy(&owner, &id); - assert_eq!(client.get_active_policies().len(), 0); - } - - // ----------------------------------------------------------------------- - // 12. deactivate_policy — failure cases - // ----------------------------------------------------------------------- - - #[test] - #[should_panic(expected = "unauthorized")] - fn test_deactivate_policy_non_owner_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - let id = client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - let non_owner = Address::generate(&env); - client.deactivate_policy(&non_owner, &id); - } - - #[test] - #[should_panic(expected = "policy not found")] - fn test_deactivate_nonexistent_policy_panics() { - let (_env, client, owner) = setup(); - client.deactivate_policy(&owner, &999u32); - } - - #[test] - #[should_panic(expected = "policy already inactive")] - fn test_deactivate_already_inactive_policy_panics() { - let (env, client, owner) = setup(); - let caller = Address::generate(&env); - let id = client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - client.deactivate_policy(&owner, &id); - // Second deactivation must panic - client.deactivate_policy(&owner, &id); - } - - // ----------------------------------------------------------------------- - // 13. set_external_ref - // ----------------------------------------------------------------------- - - #[test] - fn test_set_external_ref_success() { - let (env, client, owner) = setup(); - let caller = Address::generate(&env); - let id = client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - let new_ref = String::from_str(&env, "NEW-REF-001"); - client.set_external_ref(&owner, &id, &Some(new_ref)); - let policy = client.get_policy(&id); - assert!(policy.external_ref.is_some()); - } - - #[test] - fn test_set_external_ref_clear() { - let (env, client, owner) = setup(); - let caller = Address::generate(&env); - let ext_ref = String::from_str(&env, "INITIAL-REF"); - let id = client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &Some(ext_ref), - ); - // Clear the ref - client.set_external_ref(&owner, &id, &None); - let policy = client.get_policy(&id); - assert!(policy.external_ref.is_none()); - } - - #[test] - #[should_panic(expected = "unauthorized")] - fn test_set_external_ref_non_owner_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - let id = client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - let non_owner = Address::generate(&env); - let new_ref = String::from_str(&env, "HACK"); - client.set_external_ref(&non_owner, &id, &Some(new_ref)); - } - - #[test] - #[should_panic(expected = "external_ref length out of range")] - fn test_set_external_ref_too_long_panics() { - let (env, client, owner) = setup(); - let caller = Address::generate(&env); - let id = client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - let long_ref = String::from_str( - &env, - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1", - ); - client.set_external_ref(&owner, &id, &Some(long_ref)); - } - - // ----------------------------------------------------------------------- - // 14. Queries - // ----------------------------------------------------------------------- - - #[test] - fn test_get_active_policies_empty_initially() { - let (_env, client, _owner) = setup(); - assert_eq!(client.get_active_policies().len(), 0); - } - - #[test] - fn test_get_active_policies_reflects_creates_and_deactivations() { - let (env, client, owner) = setup(); - let caller = Address::generate(&env); - let id1 = client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - client.create_policy( - &caller, - &String::from_str(&env, "Second Policy"), - &CoverageType::Life, - &1_000_000i128, - &60_000_000i128, - &None, - ); - assert_eq!(client.get_active_policies().len(), 2); - client.deactivate_policy(&owner, &id1); - assert_eq!(client.get_active_policies().len(), 1); - } - - #[test] - fn test_get_total_monthly_premium_sums_active_only() { - let (env, client, owner) = setup(); - let caller = Address::generate(&env); - let id1 = client.create_policy( - &caller, - &short_name(&env), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - client.create_policy( - &caller, - &String::from_str(&env, "Second"), - &CoverageType::Life, - &1_000_000i128, - &60_000_000i128, - &None, - ); - assert_eq!(client.get_total_monthly_premium(), 6_000_000i128); - client.deactivate_policy(&owner, &id1); - assert_eq!(client.get_total_monthly_premium(), 1_000_000i128); - } - - #[test] - fn test_get_total_monthly_premium_zero_when_no_policies() { - let (_env, client, _owner) = setup(); - assert_eq!(client.get_total_monthly_premium(), 0i128); - } - - #[test] - #[should_panic(expected = "policy not found")] - fn test_get_policy_nonexistent_panics() { - let (_env, client, _owner) = setup(); - client.get_policy(&999u32); - } - - // ----------------------------------------------------------------------- - // 15. Uninitialized contract guard - // ----------------------------------------------------------------------- - - #[test] - #[should_panic(expected = "not initialized")] - fn test_create_policy_without_init_panics() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register_contract(None, InsuranceContract); - let client = InsuranceContractClient::new(&env, &contract_id); - let caller = Address::generate(&env); - client.create_policy( - &caller, - &String::from_str(&env, "Test"), - &CoverageType::Health, - &5_000_000i128, - &50_000_000i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "not initialized")] - fn test_get_active_policies_without_init_panics() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register_contract(None, InsuranceContract); - let client = InsuranceContractClient::new(&env, &contract_id); - client.get_active_policies(); - } - - // ----------------------------------------------------------------------- - // 16. Policy data integrity - // ----------------------------------------------------------------------- - - #[test] - fn test_policy_fields_stored_correctly() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - env.ledger().set_timestamp(1_700_000_000u64); - let id = client.create_policy( - &caller, - &String::from_str(&env, "My Health Plan"), - &CoverageType::Health, - &10_000_000i128, - &100_000_000i128, - &Some(String::from_str(&env, "EXT-001")), - ); - let policy = client.get_policy(&id); - assert_eq!(policy.id, 1u32); - assert_eq!(policy.monthly_premium, 10_000_000i128); - assert_eq!(policy.coverage_amount, 100_000_000i128); - assert!(policy.active); - assert_eq!(policy.last_payment_at, 0u64); - assert_eq!(policy.created_at, 1_700_000_000u64); - assert_eq!( - policy.next_payment_due, - 1_700_000_000u64 + 30 * 24 * 60 * 60 - ); - assert!(policy.external_ref.is_some()); - } - - // ----------------------------------------------------------------------- - // 17. Cross-coverage-type boundary checks - // ----------------------------------------------------------------------- - - #[test] - #[should_panic(expected = "monthly_premium out of range for coverage type")] - fn test_property_premium_above_max_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Property max_premium = 2_000_000_000; supply 2_000_000_001 - client.create_policy( - &caller, - &String::from_str(&env, "Property"), - &CoverageType::Property, - &2_000_000_001i128, - &100_000_000i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "monthly_premium out of range for coverage type")] - fn test_auto_premium_above_max_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Auto max_premium = 750_000_000; supply 750_000_001 - client.create_policy( - &caller, - &String::from_str(&env, "Auto"), - &CoverageType::Auto, - &750_000_001i128, - &20_000_000i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "monthly_premium out of range for coverage type")] - fn test_liability_premium_above_max_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Liability max_premium = 400_000_000; supply 400_000_001 - client.create_policy( - &caller, - &String::from_str(&env, "Liability"), - &CoverageType::Liability, - &400_000_001i128, - &5_000_000i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "coverage_amount out of range for coverage type")] - fn test_life_coverage_above_max_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Life max_coverage = 500_000_000_000; supply 500_000_000_001 - client.create_policy( - &caller, - &String::from_str(&env, "Life"), - &CoverageType::Life, - &1_000_000_000i128, // max premium for Life - &500_000_000_001i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "coverage_amount out of range for coverage type")] - fn test_auto_coverage_above_max_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Auto max_coverage = 200_000_000_000; supply 200_000_000_001 - client.create_policy( - &caller, - &String::from_str(&env, "Auto"), - &CoverageType::Auto, - &750_000_000i128, - &200_000_000_001i128, - &None, - ); - } - - #[test] - #[should_panic(expected = "coverage_amount out of range for coverage type")] - fn test_liability_coverage_above_max_panics() { - let (env, client, _owner) = setup(); - let caller = Address::generate(&env); - // Liability max_coverage = 50_000_000_000; supply 50_000_000_001 - client.create_policy( - &caller, - &String::from_str(&env, "Liability"), - &CoverageType::Liability, - &400_000_000i128, - &50_000_000_001i128, - &None, - ); - } -} \ No newline at end of file + client.archive_inactive_policies(&owner, &3_000_000_000); + + let stats = client.get_storage_stats(); + assert_eq!(stats.active_policies, 1); + assert_eq!(stats.archived_policies, 1); + assert_eq!(stats.total_active_coverage, 20000); + assert_eq!(stats.total_archived_coverage, 10000); +} diff --git a/insurance/test_snapshots/test/test_create_policy_emits_event.1.json b/insurance/test_snapshots/test/test_create_policy_emits_event.1.json index 0514df87..b6f5c6d5 100644 --- a/insurance/test_snapshots/test/test_create_policy_emits_event.1.json +++ b/insurance/test_snapshots/test/test_create_policy_emits_event.1.json @@ -17,10 +17,10 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" }, { - "string": "Health Policy" + "string": "Health Insurance" }, { - "string": "Health" + "u32": 1 }, { "i128": { @@ -31,7 +31,7 @@ { "i128": { "hi": 0, - "lo": 10000 + "lo": 50000 } } ] @@ -110,7 +110,7 @@ "val": { "i128": { "hi": 0, - "lo": 10000 + "lo": 50000 } } }, @@ -119,7 +119,7 @@ "symbol": "coverage_type" }, "val": { - "string": "Health" + "u32": 1 } }, { @@ -146,7 +146,7 @@ "symbol": "name" }, "val": { - "string": "Health Policy" + "string": "Health Insurance" } }, { @@ -176,26 +176,6 @@ } ] } - }, - { - "key": { - "symbol": "PRM_TOT" - }, - "val": { - "map": [ - { - "key": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - "val": { - "i128": { - "hi": 0, - "lo": 100 - } - } - } - ] - } } ] } @@ -288,10 +268,10 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" }, { - "string": "Health Policy" + "string": "Health Insurance" }, { - "string": "Health" + "u32": 1 }, { "i128": { @@ -302,7 +282,7 @@ { "i128": { "hi": 0, - "lo": 10000 + "lo": 50000 } } ] @@ -333,7 +313,7 @@ "val": { "i128": { "hi": 0, - "lo": 10000 + "lo": 50000 } } }, @@ -342,7 +322,7 @@ "symbol": "coverage_type" }, "val": { - "string": "Health" + "u32": 1 } }, { @@ -361,7 +341,7 @@ "symbol": "name" }, "val": { - "string": "Health Policy" + "string": "Health Insurance" } }, { diff --git a/insurance/test_snapshots/test/test_get_active_policies_excludes_inactive.1.json b/insurance/test_snapshots/test/test_get_active_policies_excludes_inactive.1.json index 867aa8c3..cf379a8f 100644 --- a/insurance/test_snapshots/test/test_get_active_policies_excludes_inactive.1.json +++ b/insurance/test_snapshots/test/test_get_active_policies_excludes_inactive.1.json @@ -20,7 +20,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -57,7 +57,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -94,7 +94,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -131,7 +131,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -253,7 +253,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -338,7 +338,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -423,7 +423,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -508,7 +508,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -565,26 +565,6 @@ } ] } - }, - { - "key": { - "symbol": "PRM_TOT" - }, - "val": { - "map": [ - { - "key": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - "val": { - "i128": { - "hi": 0, - "lo": 400 - } - } - } - ] - } } ] } @@ -812,7 +792,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -863,7 +843,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -992,7 +972,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1043,7 +1023,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1172,7 +1152,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1223,7 +1203,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1352,7 +1332,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1403,7 +1383,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1592,7 +1572,7 @@ "v0": { "topics": [ { - "symbol": "insure" + "symbol": "insuranc" }, { "vec": [ @@ -1733,7 +1713,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1813,7 +1793,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1893,7 +1873,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { diff --git a/insurance/test_snapshots/test/test_get_active_policies_multi_owner_isolation.1.json b/insurance/test_snapshots/test/test_get_active_policies_multi_owner_isolation.1.json index 6198a00f..dc473bab 100644 --- a/insurance/test_snapshots/test/test_get_active_policies_multi_owner_isolation.1.json +++ b/insurance/test_snapshots/test/test_get_active_policies_multi_owner_isolation.1.json @@ -20,7 +20,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -57,7 +57,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -94,7 +94,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -131,7 +131,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -168,7 +168,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -205,7 +205,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -242,7 +242,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -279,7 +279,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -379,7 +379,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -464,7 +464,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -549,7 +549,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -634,7 +634,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -719,7 +719,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -804,7 +804,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -889,7 +889,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -974,7 +974,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1031,37 +1031,6 @@ } ] } - }, - { - "key": { - "symbol": "PRM_TOT" - }, - "val": { - "map": [ - { - "key": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - "val": { - "i128": { - "hi": 0, - "lo": 300 - } - } - }, - { - "key": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" - }, - "val": { - "i128": { - "hi": 0, - "lo": 750 - } - } - } - ] - } } ] } @@ -1388,7 +1357,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1439,7 +1408,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1568,7 +1537,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1619,7 +1588,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1748,7 +1717,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1799,7 +1768,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1928,7 +1897,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1979,7 +1948,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2108,7 +2077,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -2159,7 +2128,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2288,7 +2257,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -2339,7 +2308,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2468,7 +2437,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -2519,7 +2488,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2648,7 +2617,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -2699,7 +2668,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2894,7 +2863,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2974,7 +2943,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -3054,7 +3023,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { diff --git a/insurance/test_snapshots/test/test_get_active_policies_multiple_pages.1.json b/insurance/test_snapshots/test/test_get_active_policies_multiple_pages.1.json index 7635fb27..2bb64ccf 100644 --- a/insurance/test_snapshots/test/test_get_active_policies_multiple_pages.1.json +++ b/insurance/test_snapshots/test/test_get_active_policies_multiple_pages.1.json @@ -20,7 +20,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -57,7 +57,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -94,7 +94,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -131,7 +131,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -168,7 +168,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -205,7 +205,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -242,7 +242,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -344,7 +344,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -429,7 +429,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -514,7 +514,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -599,7 +599,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -684,7 +684,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -769,7 +769,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -854,7 +854,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1204,7 +1204,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1255,7 +1255,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1384,7 +1384,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1435,7 +1435,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1564,7 +1564,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1615,7 +1615,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1744,7 +1744,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1795,7 +1795,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1924,7 +1924,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1975,7 +1975,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2104,7 +2104,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -2155,7 +2155,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2284,7 +2284,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -2335,7 +2335,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2530,7 +2530,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2610,7 +2610,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2690,7 +2690,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2855,7 +2855,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2935,7 +2935,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -3015,7 +3015,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -3180,7 +3180,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { diff --git a/insurance/test_snapshots/test/test_get_active_policies_single_page.1.json b/insurance/test_snapshots/test/test_get_active_policies_single_page.1.json index d94677b1..ded8012e 100644 --- a/insurance/test_snapshots/test/test_get_active_policies_single_page.1.json +++ b/insurance/test_snapshots/test/test_get_active_policies_single_page.1.json @@ -20,7 +20,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -57,7 +57,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -94,7 +94,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -131,7 +131,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -168,7 +168,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -268,7 +268,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -353,7 +353,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -438,7 +438,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -523,7 +523,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -608,7 +608,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -665,26 +665,6 @@ } ] } - }, - { - "key": { - "symbol": "PRM_TOT" - }, - "val": { - "map": [ - { - "key": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - "val": { - "i128": { - "hi": 0, - "lo": 750 - } - } - } - ] - } } ] } @@ -912,7 +892,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -963,7 +943,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1092,7 +1072,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1143,7 +1123,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1272,7 +1252,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1323,7 +1303,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1452,7 +1432,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1503,7 +1483,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1632,7 +1612,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1683,7 +1663,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1878,7 +1858,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1958,7 +1938,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2038,7 +2018,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2118,7 +2098,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2198,7 +2178,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { diff --git a/insurance/test_snapshots/test/test_get_all_policies_for_owner_includes_inactive.1.json b/insurance/test_snapshots/test/test_get_all_policies_for_owner_includes_inactive.1.json index e6331976..fabdca4f 100644 --- a/insurance/test_snapshots/test/test_get_all_policies_for_owner_includes_inactive.1.json +++ b/insurance/test_snapshots/test/test_get_all_policies_for_owner_includes_inactive.1.json @@ -20,7 +20,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -57,7 +57,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -94,7 +94,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -131,7 +131,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -299,7 +299,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -384,7 +384,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -469,7 +469,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -554,7 +554,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -611,26 +611,6 @@ } ] } - }, - { - "key": { - "symbol": "PRM_TOT" - }, - "val": { - "map": [ - { - "key": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - "val": { - "i128": { - "hi": 0, - "lo": 300 - } - } - } - ] - } } ] } @@ -924,7 +904,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -975,7 +955,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1104,7 +1084,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1155,7 +1135,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1284,7 +1264,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1335,7 +1315,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1464,7 +1444,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1515,7 +1495,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1704,7 +1684,7 @@ "v0": { "topics": [ { - "symbol": "insure" + "symbol": "insuranc" }, { "vec": [ @@ -1839,7 +1819,7 @@ "v0": { "topics": [ { - "symbol": "insure" + "symbol": "insuranc" }, { "vec": [ @@ -1980,7 +1960,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2060,7 +2040,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2140,7 +2120,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2220,7 +2200,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { diff --git a/insurance/test_snapshots/test/test_instance_ttl_extended_on_create_policy.1.json b/insurance/test_snapshots/test/test_instance_ttl_extended_on_create_policy.1.json index 63bbe397..5fae9cb1 100644 --- a/insurance/test_snapshots/test/test_instance_ttl_extended_on_create_policy.1.json +++ b/insurance/test_snapshots/test/test_instance_ttl_extended_on_create_policy.1.json @@ -20,7 +20,7 @@ "string": "Health Insurance" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -120,7 +120,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -177,26 +177,6 @@ } ] } - }, - { - "key": { - "symbol": "PRM_TOT" - }, - "val": { - "map": [ - { - "key": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - "val": { - "i128": { - "hi": 0, - "lo": 100 - } - } - } - ] - } } ] } @@ -292,7 +272,7 @@ "string": "Health Insurance" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -343,7 +323,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { diff --git a/insurance/test_snapshots/test/test_instance_ttl_extended_on_deactivate_policy.1.json b/insurance/test_snapshots/test/test_instance_ttl_extended_on_deactivate_policy.1.json index 9bae42c5..06f73f8b 100644 --- a/insurance/test_snapshots/test/test_instance_ttl_extended_on_deactivate_policy.1.json +++ b/insurance/test_snapshots/test/test_instance_ttl_extended_on_deactivate_policy.1.json @@ -20,7 +20,7 @@ "string": "Dental" }, { - "string": "dental" + "u32": 8 }, { "i128": { @@ -142,7 +142,7 @@ "symbol": "coverage_type" }, "val": { - "string": "dental" + "u32": 8 } }, { @@ -199,26 +199,6 @@ } ] } - }, - { - "key": { - "symbol": "PRM_TOT" - }, - "val": { - "map": [ - { - "key": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - "val": { - "i128": { - "hi": 0, - "lo": 0 - } - } - } - ] - } } ] } @@ -347,7 +327,7 @@ "string": "Dental" }, { - "string": "dental" + "u32": 8 }, { "i128": { @@ -398,7 +378,7 @@ "symbol": "coverage_type" }, "val": { - "string": "dental" + "u32": 8 } }, { @@ -587,7 +567,7 @@ "v0": { "topics": [ { - "symbol": "insure" + "symbol": "insuranc" }, { "vec": [ diff --git a/insurance/test_snapshots/test/test_instance_ttl_refreshed_on_pay_premium.1.json b/insurance/test_snapshots/test/test_instance_ttl_refreshed_on_pay_premium.1.json index b94935a0..ee19a328 100644 --- a/insurance/test_snapshots/test/test_instance_ttl_refreshed_on_pay_premium.1.json +++ b/insurance/test_snapshots/test/test_instance_ttl_refreshed_on_pay_premium.1.json @@ -20,7 +20,7 @@ "string": "Life Insurance" }, { - "string": "life" + "u32": 2 }, { "i128": { @@ -142,7 +142,7 @@ "symbol": "coverage_type" }, "val": { - "string": "life" + "u32": 2 } }, { @@ -199,26 +199,6 @@ } ] } - }, - { - "key": { - "symbol": "PRM_TOT" - }, - "val": { - "map": [ - { - "key": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - "val": { - "i128": { - "hi": 0, - "lo": 200 - } - } - } - ] - } } ] } @@ -347,7 +327,7 @@ "string": "Life Insurance" }, { - "string": "life" + "u32": 2 }, { "i128": { @@ -398,7 +378,7 @@ "symbol": "coverage_type" }, "val": { - "string": "life" + "u32": 2 } }, { @@ -646,7 +626,9 @@ "symbol": "pay_premium" } ], - "data": "void" + "data": { + "bool": true + } } } }, diff --git a/insurance/test_snapshots/test/test_limit_clamped_to_max.1.json b/insurance/test_snapshots/test/test_limit_clamped_to_max.1.json index e6f74734..303a0df3 100644 --- a/insurance/test_snapshots/test/test_limit_clamped_to_max.1.json +++ b/insurance/test_snapshots/test/test_limit_clamped_to_max.1.json @@ -20,7 +20,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -57,7 +57,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -94,7 +94,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -131,7 +131,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -168,7 +168,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -205,7 +205,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -242,7 +242,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -279,7 +279,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -316,7 +316,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -353,7 +353,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -390,7 +390,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -427,7 +427,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -464,7 +464,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -501,7 +501,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -538,7 +538,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -575,7 +575,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -612,7 +612,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -649,7 +649,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -686,7 +686,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -723,7 +723,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -760,7 +760,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -797,7 +797,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -834,7 +834,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -871,7 +871,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -908,7 +908,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -945,7 +945,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -982,7 +982,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1019,7 +1019,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1056,7 +1056,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1093,7 +1093,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1130,7 +1130,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1167,7 +1167,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1204,7 +1204,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1241,7 +1241,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1278,7 +1278,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1315,7 +1315,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1352,7 +1352,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1389,7 +1389,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1426,7 +1426,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1463,7 +1463,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1500,7 +1500,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1537,7 +1537,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1574,7 +1574,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1611,7 +1611,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1648,7 +1648,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1685,7 +1685,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1722,7 +1722,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1759,7 +1759,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1796,7 +1796,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1833,7 +1833,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1870,7 +1870,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1907,7 +1907,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1944,7 +1944,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1981,7 +1981,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -2018,7 +2018,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -2118,7 +2118,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2203,7 +2203,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2288,7 +2288,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2373,7 +2373,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2458,7 +2458,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2543,7 +2543,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2628,7 +2628,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2713,7 +2713,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2798,7 +2798,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2883,7 +2883,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -2968,7 +2968,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -3053,7 +3053,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -3138,7 +3138,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -3223,7 +3223,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -3308,7 +3308,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -3393,7 +3393,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -3478,7 +3478,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -3563,7 +3563,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -3648,7 +3648,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -3733,7 +3733,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -3818,7 +3818,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -3903,7 +3903,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -3988,7 +3988,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -4073,7 +4073,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -4158,7 +4158,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -4243,7 +4243,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -4328,7 +4328,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -4413,7 +4413,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -4498,7 +4498,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -4583,7 +4583,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -4668,7 +4668,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -4753,7 +4753,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -4838,7 +4838,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -4923,7 +4923,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -5008,7 +5008,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -5093,7 +5093,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -5178,7 +5178,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -5263,7 +5263,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -5348,7 +5348,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -5433,7 +5433,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -5518,7 +5518,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -5603,7 +5603,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -5688,7 +5688,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -5773,7 +5773,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -5858,7 +5858,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -5943,7 +5943,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -6028,7 +6028,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -6113,7 +6113,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -6198,7 +6198,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -6283,7 +6283,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -6368,7 +6368,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -6453,7 +6453,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -6538,7 +6538,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -6623,7 +6623,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -6708,7 +6708,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -6765,26 +6765,6 @@ } ] } - }, - { - "key": { - "symbol": "PRM_TOT" - }, - "val": { - "map": [ - { - "key": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - "val": { - "i128": { - "hi": 0, - "lo": 77000 - } - } - } - ] - } } ] } @@ -8662,7 +8642,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -8713,7 +8693,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -8842,7 +8822,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -8893,7 +8873,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -9022,7 +9002,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -9073,7 +9053,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -9202,7 +9182,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -9253,7 +9233,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -9382,7 +9362,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -9433,7 +9413,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -9562,7 +9542,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -9613,7 +9593,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -9742,7 +9722,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -9793,7 +9773,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -9922,7 +9902,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -9973,7 +9953,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -10102,7 +10082,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -10153,7 +10133,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -10282,7 +10262,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -10333,7 +10313,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -10462,7 +10442,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -10513,7 +10493,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -10642,7 +10622,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -10693,7 +10673,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -10822,7 +10802,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -10873,7 +10853,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -11002,7 +10982,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -11053,7 +11033,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -11182,7 +11162,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -11233,7 +11213,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -11362,7 +11342,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -11413,7 +11393,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -11542,7 +11522,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -11593,7 +11573,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -11722,7 +11702,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -11773,7 +11753,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -11902,7 +11882,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -11953,7 +11933,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -12082,7 +12062,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -12133,7 +12113,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -12262,7 +12242,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -12313,7 +12293,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -12442,7 +12422,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -12493,7 +12473,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -12622,7 +12602,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -12673,7 +12653,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -12802,7 +12782,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -12853,7 +12833,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -12982,7 +12962,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -13033,7 +13013,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -13162,7 +13142,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -13213,7 +13193,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -13342,7 +13322,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -13393,7 +13373,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -13522,7 +13502,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -13573,7 +13553,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -13702,7 +13682,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -13753,7 +13733,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -13882,7 +13862,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -13933,7 +13913,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -14062,7 +14042,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -14113,7 +14093,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -14242,7 +14222,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -14293,7 +14273,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -14422,7 +14402,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -14473,7 +14453,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -14602,7 +14582,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -14653,7 +14633,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -14782,7 +14762,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -14833,7 +14813,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -14962,7 +14942,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -15013,7 +14993,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -15142,7 +15122,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -15193,7 +15173,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -15322,7 +15302,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -15373,7 +15353,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -15502,7 +15482,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -15553,7 +15533,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -15682,7 +15662,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -15733,7 +15713,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -15862,7 +15842,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -15913,7 +15893,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -16042,7 +16022,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -16093,7 +16073,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -16222,7 +16202,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -16273,7 +16253,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -16402,7 +16382,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -16453,7 +16433,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -16582,7 +16562,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -16633,7 +16613,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -16762,7 +16742,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -16813,7 +16793,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -16942,7 +16922,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -16993,7 +16973,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -17122,7 +17102,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -17173,7 +17153,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -17302,7 +17282,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -17353,7 +17333,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -17482,7 +17462,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -17533,7 +17513,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -17662,7 +17642,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -17713,7 +17693,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -17842,7 +17822,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -17893,7 +17873,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -18022,7 +18002,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -18073,7 +18053,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -18202,7 +18182,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -18253,7 +18233,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -18382,7 +18362,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -18433,7 +18413,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -18628,7 +18608,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -18708,7 +18688,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -18788,7 +18768,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -18868,7 +18848,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -18948,7 +18928,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -19028,7 +19008,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -19108,7 +19088,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -19188,7 +19168,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -19268,7 +19248,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -19348,7 +19328,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -19428,7 +19408,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -19508,7 +19488,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -19588,7 +19568,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -19668,7 +19648,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -19748,7 +19728,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -19828,7 +19808,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -19908,7 +19888,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -19988,7 +19968,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -20068,7 +20048,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -20148,7 +20128,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -20228,7 +20208,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -20308,7 +20288,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -20388,7 +20368,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -20468,7 +20448,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -20548,7 +20528,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -20628,7 +20608,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -20708,7 +20688,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -20788,7 +20768,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -20868,7 +20848,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -20948,7 +20928,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -21028,7 +21008,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -21108,7 +21088,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -21188,7 +21168,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -21268,7 +21248,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -21348,7 +21328,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -21428,7 +21408,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -21508,7 +21488,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -21588,7 +21568,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -21668,7 +21648,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -21748,7 +21728,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -21828,7 +21808,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -21908,7 +21888,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -21988,7 +21968,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -22068,7 +22048,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -22148,7 +22128,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -22228,7 +22208,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -22308,7 +22288,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -22388,7 +22368,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -22468,7 +22448,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -22548,7 +22528,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { diff --git a/insurance/test_snapshots/test/test_limit_zero_uses_default.1.json b/insurance/test_snapshots/test/test_limit_zero_uses_default.1.json index 548c8b34..867582f7 100644 --- a/insurance/test_snapshots/test/test_limit_zero_uses_default.1.json +++ b/insurance/test_snapshots/test/test_limit_zero_uses_default.1.json @@ -20,7 +20,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -57,7 +57,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -94,7 +94,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -194,7 +194,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -279,7 +279,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -364,7 +364,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -421,26 +421,6 @@ } ] } - }, - { - "key": { - "symbol": "PRM_TOT" - }, - "val": { - "map": [ - { - "key": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - "val": { - "i128": { - "hi": 0, - "lo": 300 - } - } - } - ] - } } ] } @@ -602,7 +582,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -653,7 +633,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -782,7 +762,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -833,7 +813,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -962,7 +942,7 @@ "string": "Policy" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -1013,7 +993,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1208,7 +1188,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1288,7 +1268,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -1368,7 +1348,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { diff --git a/insurance/test_snapshots/test/test_pay_premium_after_deactivate.1.json b/insurance/test_snapshots/test/test_pay_premium_after_deactivate.1.json index 70c1d8a9..6c65b353 100644 --- a/insurance/test_snapshots/test/test_pay_premium_after_deactivate.1.json +++ b/insurance/test_snapshots/test/test_pay_premium_after_deactivate.1.json @@ -20,7 +20,7 @@ "string": "Health Plan" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -144,7 +144,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -201,26 +201,6 @@ } ] } - }, - { - "key": { - "symbol": "PRM_TOT" - }, - "val": { - "map": [ - { - "key": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - "val": { - "i128": { - "hi": 0, - "lo": 0 - } - } - } - ] - } } ] } @@ -349,7 +329,7 @@ "string": "Health Plan" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -400,7 +380,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -569,7 +549,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -715,7 +695,7 @@ "v0": { "topics": [ { - "symbol": "insure" + "symbol": "insuranc" }, { "vec": [ @@ -830,7 +810,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -931,16 +911,21 @@ "v0": { "topics": [ { - "symbol": "fn_return" - }, - { - "symbol": "pay_premium" + "symbol": "log" } ], "data": { - "error": { - "contract": 6 - } + "vec": [ + { + "string": "caught panic 'Policy is not active' from contract function 'Symbol(obj#187)'" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "u32": 1 + } + ] } } } @@ -960,12 +945,12 @@ }, { "error": { - "contract": 6 + "wasm_vm": "invalid_action" } } ], "data": { - "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + "string": "caught error from function" } } } @@ -985,14 +970,14 @@ }, { "error": { - "contract": 6 + "wasm_vm": "invalid_action" } } ], "data": { "vec": [ { - "string": "contract try_call failed" + "string": "contract call failed" }, { "symbol": "pay_premium" @@ -1013,6 +998,31 @@ } }, "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "error" + }, + { + "error": { + "wasm_vm": "invalid_action" + } + } + ], + "data": { + "string": "escalating error to panic" + } + } + } + }, + "failed_call": false } ] } \ No newline at end of file diff --git a/insurance/test_snapshots/test/test_pay_premium_emits_event.1.json b/insurance/test_snapshots/test/test_pay_premium_emits_event.1.json index 78dbe178..92b86759 100644 --- a/insurance/test_snapshots/test/test_pay_premium_emits_event.1.json +++ b/insurance/test_snapshots/test/test_pay_premium_emits_event.1.json @@ -17,21 +17,21 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" }, { - "string": "Health Policy" + "string": "Emergency Coverage" }, { - "string": "Health" + "u32": 6 }, { "i128": { "hi": 0, - "lo": 100 + "lo": 75 } }, { "i128": { "hi": 0, - "lo": 10000 + "lo": 25000 } } ] @@ -132,7 +132,7 @@ "val": { "i128": { "hi": 0, - "lo": 10000 + "lo": 25000 } } }, @@ -141,7 +141,7 @@ "symbol": "coverage_type" }, "val": { - "string": "Health" + "u32": 6 } }, { @@ -159,7 +159,7 @@ "val": { "i128": { "hi": 0, - "lo": 100 + "lo": 75 } } }, @@ -168,7 +168,7 @@ "symbol": "name" }, "val": { - "string": "Health Policy" + "string": "Emergency Coverage" } }, { @@ -198,26 +198,6 @@ } ] } - }, - { - "key": { - "symbol": "PRM_TOT" - }, - "val": { - "map": [ - { - "key": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - "val": { - "i128": { - "hi": 0, - "lo": 100 - } - } - } - ] - } } ] } @@ -343,21 +323,21 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" }, { - "string": "Health Policy" + "string": "Emergency Coverage" }, { - "string": "Health" + "u32": 6 }, { "i128": { "hi": 0, - "lo": 100 + "lo": 75 } }, { "i128": { "hi": 0, - "lo": 10000 + "lo": 25000 } } ] @@ -388,7 +368,7 @@ "val": { "i128": { "hi": 0, - "lo": 10000 + "lo": 25000 } } }, @@ -397,7 +377,7 @@ "symbol": "coverage_type" }, "val": { - "string": "Health" + "u32": 6 } }, { @@ -407,7 +387,7 @@ "val": { "i128": { "hi": 0, - "lo": 100 + "lo": 75 } } }, @@ -416,7 +396,7 @@ "symbol": "name" }, "val": { - "string": "Health Policy" + "string": "Emergency Coverage" } }, { @@ -553,7 +533,7 @@ "val": { "i128": { "hi": 0, - "lo": 100 + "lo": 75 } } }, @@ -562,7 +542,7 @@ "symbol": "name" }, "val": { - "string": "Health Policy" + "string": "Emergency Coverage" } }, { @@ -645,7 +625,9 @@ "symbol": "pay_premium" } ], - "data": "void" + "data": { + "bool": true + } } } }, diff --git a/insurance/test_snapshots/test/test_pay_premium_policy_not_found.1.json b/insurance/test_snapshots/test/test_pay_premium_policy_not_found.1.json index 14950723..5832f3d1 100644 --- a/insurance/test_snapshots/test/test_pay_premium_policy_not_found.1.json +++ b/insurance/test_snapshots/test/test_pay_premium_policy_not_found.1.json @@ -114,16 +114,21 @@ "v0": { "topics": [ { - "symbol": "fn_return" - }, - { - "symbol": "pay_premium" + "symbol": "log" } ], "data": { - "error": { - "contract": 3 - } + "vec": [ + { + "string": "caught panic 'Policy not found' from contract function 'Symbol(obj#7)'" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "u32": 999 + } + ] } } } @@ -143,12 +148,12 @@ }, { "error": { - "contract": 3 + "wasm_vm": "invalid_action" } } ], "data": { - "string": "escalating Ok(ScErrorType::Contract) frame-exit to Err" + "string": "caught error from function" } } } @@ -168,7 +173,7 @@ }, { "error": { - "contract": 3 + "wasm_vm": "invalid_action" } } ], diff --git a/insurance/test_snapshots/test/test_policy_data_persists_across_ledger_advancements.1.json b/insurance/test_snapshots/test/test_policy_data_persists_across_ledger_advancements.1.json index 04e0f4e3..9a5fed34 100644 --- a/insurance/test_snapshots/test/test_policy_data_persists_across_ledger_advancements.1.json +++ b/insurance/test_snapshots/test/test_policy_data_persists_across_ledger_advancements.1.json @@ -20,7 +20,7 @@ "string": "Auto Insurance" }, { - "string": "auto" + "u32": 4 }, { "i128": { @@ -79,7 +79,7 @@ "string": "Travel Insurance" }, { - "string": "travel" + "u32": 7 }, { "i128": { @@ -181,7 +181,7 @@ "symbol": "coverage_type" }, "val": { - "string": "auto" + "u32": 4 } }, { @@ -266,7 +266,7 @@ "symbol": "coverage_type" }, "val": { - "string": "travel" + "u32": 7 } }, { @@ -323,26 +323,6 @@ } ] } - }, - { - "key": { - "symbol": "PRM_TOT" - }, - "val": { - "map": [ - { - "key": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - "val": { - "i128": { - "hi": 0, - "lo": 200 - } - } - } - ] - } } ] } @@ -504,7 +484,7 @@ "string": "Auto Insurance" }, { - "string": "auto" + "u32": 4 }, { "i128": { @@ -555,7 +535,7 @@ "symbol": "coverage_type" }, "val": { - "string": "auto" + "u32": 4 } }, { @@ -803,7 +783,9 @@ "symbol": "pay_premium" } ], - "data": "void" + "data": { + "bool": true + } } } }, @@ -836,7 +818,7 @@ "string": "Travel Insurance" }, { - "string": "travel" + "u32": 7 }, { "i128": { @@ -887,7 +869,7 @@ "symbol": "coverage_type" }, "val": { - "string": "travel" + "u32": 7 } }, { @@ -1056,7 +1038,7 @@ "symbol": "coverage_type" }, "val": { - "string": "auto" + "u32": 4 } }, { @@ -1182,7 +1164,7 @@ "symbol": "coverage_type" }, "val": { - "string": "travel" + "u32": 7 } }, { diff --git a/insurance/test_snapshots/test/test_policy_lifecycle_emits_all_events.1.json b/insurance/test_snapshots/test/test_policy_lifecycle_emits_all_events.1.json index a6ebad9f..6b1b95df 100644 --- a/insurance/test_snapshots/test/test_policy_lifecycle_emits_all_events.1.json +++ b/insurance/test_snapshots/test/test_policy_lifecycle_emits_all_events.1.json @@ -20,7 +20,7 @@ "string": "Complete Lifecycle" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -163,7 +163,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -220,26 +220,6 @@ } ] } - }, - { - "key": { - "symbol": "PRM_TOT" - }, - "val": { - "map": [ - { - "key": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - "val": { - "i128": { - "hi": 0, - "lo": 0 - } - } - } - ] - } } ] } @@ -401,7 +381,7 @@ "string": "Complete Lifecycle" }, { - "string": "health" + "u32": 1 }, { "i128": { @@ -452,7 +432,7 @@ "symbol": "coverage_type" }, "val": { - "string": "health" + "u32": 1 } }, { @@ -700,7 +680,9 @@ "symbol": "pay_premium" } ], - "data": "void" + "data": { + "bool": true + } } } }, @@ -793,7 +775,7 @@ "v0": { "topics": [ { - "symbol": "insure" + "symbol": "insuranc" }, { "vec": [ diff --git a/insurance/tests/stress_tests.rs b/insurance/tests/stress_tests.rs index 0063a6ad..89ca7702 100644 --- a/insurance/tests/stress_tests.rs +++ b/insurance/tests/stress_tests.rs @@ -18,6 +18,7 @@ //! MAX_BATCH_SIZE = 50 use insurance::{Insurance, InsuranceClient}; +use remitwise_common::CoverageType; use soroban_sdk::testutils::storage::Instance as _; use soroban_sdk::testutils::{Address as AddressTrait, EnvTestConfig, Ledger, LedgerInfo}; use soroban_sdk::{Address, Env, String}; @@ -73,7 +74,7 @@ fn stress_200_policies_single_user() { let owner = Address::generate(&env); let name = String::from_str(&env, "StressPolicy"); - let coverage_type = String::from_str(&env, "health"); + let coverage_type = CoverageType::Health; for _ in 0..200 { client.create_policy(&owner, &name, &coverage_type, &100i128, &10_000i128); @@ -123,7 +124,7 @@ fn stress_instance_ttl_valid_after_200_policies() { let owner = Address::generate(&env); let name = String::from_str(&env, "TTLPolicy"); - let coverage_type = String::from_str(&env, "life"); + let coverage_type = CoverageType::Life; for _ in 0..200 { client.create_policy(&owner, &name, &coverage_type, &50i128, &5_000i128); @@ -153,7 +154,7 @@ fn stress_policies_across_10_users() { const POLICIES_PER_USER: u32 = 20; const PREMIUM_PER_POLICY: i128 = 150; let name = String::from_str(&env, "UserPolicy"); - let coverage_type = String::from_str(&env, "health"); + let coverage_type = CoverageType::Health; let users: std::vec::Vec
= (0..N_USERS).map(|_| Address::generate(&env)).collect(); @@ -214,7 +215,7 @@ fn stress_ttl_re_bumped_after_ledger_advancement() { let owner = Address::generate(&env); let name = String::from_str(&env, "TTLStress"); - let coverage_type = String::from_str(&env, "health"); + let coverage_type = CoverageType::Health; // Phase 1: 50 creates for _ in 0..50 { @@ -270,7 +271,7 @@ fn stress_ttl_re_bumped_by_pay_premium_after_ledger_advancement() { let policy_id = client.create_policy( &owner, &String::from_str(&env, "PayTTL"), - &String::from_str(&env, "health"), + &CoverageType::Health, &200i128, &20_000i128, ); @@ -314,7 +315,7 @@ fn stress_batch_pay_premiums_at_max_batch_size() { const BATCH_SIZE: u32 = 50; // MAX_BATCH_SIZE let name = String::from_str(&env, "BatchPolicy"); - let coverage_type = String::from_str(&env, "health"); + let coverage_type = CoverageType::Health; let mut policy_ids = std::vec![]; for _ in 0..BATCH_SIZE { @@ -365,7 +366,7 @@ fn stress_deactivate_half_of_200_policies() { let owner = Address::generate(&env); let name = String::from_str(&env, "DeactPolicy"); - let coverage_type = String::from_str(&env, "life"); + let coverage_type = CoverageType::Life; for _ in 0..200 { client.create_policy(&owner, &name, &coverage_type, &80i128, &8_000i128); @@ -415,7 +416,7 @@ fn bench_get_active_policies_first_page_of_200() { let owner = Address::generate(&env); let name = String::from_str(&env, "BenchPolicy"); - let coverage_type = String::from_str(&env, "health"); + let coverage_type = CoverageType::Health; for _ in 0..200 { client.create_policy(&owner, &name, &coverage_type, &100i128, &10_000i128); @@ -439,7 +440,7 @@ fn bench_get_total_monthly_premium_200_policies() { let owner = Address::generate(&env); let name = String::from_str(&env, "PremBench"); - let coverage_type = String::from_str(&env, "health"); + let coverage_type = CoverageType::Health; for _ in 0..200 { client.create_policy(&owner, &name, &coverage_type, &100i128, &10_000i128); @@ -464,7 +465,7 @@ fn bench_batch_pay_premiums_50_policies() { let owner = Address::generate(&env); let name = String::from_str(&env, "BatchBench"); - let coverage_type = String::from_str(&env, "health"); + let coverage_type = CoverageType::Health; let mut policy_ids = std::vec![]; for _ in 0..50 { diff --git a/remitwise-common/src/lib.rs b/remitwise-common/src/lib.rs index 038c09f7..30d6e04d 100644 --- a/remitwise-common/src/lib.rs +++ b/remitwise-common/src/lib.rs @@ -34,6 +34,9 @@ pub enum CoverageType { Property = 3, Auto = 4, Liability = 5, + Emergency = 6, + Travel = 7, + Dental = 8, } /// Event categories for logging @@ -164,8 +167,3 @@ impl RemitwiseEvents { // Standardized TTL Constants (Ledger Counts) pub const DAY_IN_LEDGERS: u32 = 17280; // ~5 seconds per ledger -pub const INSTANCE_BUMP_AMOUNT: u32 = 30 * DAY_IN_LEDGERS; // 30 days -pub const INSTANCE_LIFETIME_THRESHOLD: u32 = 7 * DAY_IN_LEDGERS; // 7 days - -pub const PERSISTENT_BUMP_AMOUNT: u32 = 60 * DAY_IN_LEDGERS; // 60 days -pub const PERSISTENT_LIFETIME_THRESHOLD: u32 = 15 * DAY_IN_LEDGERS; // 15 days From 7ddd6cfc191df8eccb53ffcd440824d13a4bc586 Mon Sep 17 00:00:00 2001 From: fadesany Date: Fri, 27 Mar 2026 12:30:32 +0100 Subject: [PATCH 2/2] refactor: Refine upgrade admin authorization logic, add orchestrator audit log test helpers, and remove legacy bill payment functions and redundant code. --- bill_payments/src/lib.rs | 1237 +++++++++++++++++------------------ orchestrator/src/lib.rs | 50 +- orchestrator/src/test.rs | 73 ++- remittance_split/src/lib.rs | 8 +- savings_goals/src/lib.rs | 4 +- 5 files changed, 738 insertions(+), 634 deletions(-) diff --git a/bill_payments/src/lib.rs b/bill_payments/src/lib.rs index 3289a9de..7f0d8e1a 100644 --- a/bill_payments/src/lib.rs +++ b/bill_payments/src/lib.rs @@ -12,8 +12,6 @@ use soroban_sdk::{ Symbol, Vec, }; -#[derive(Clone, Debug)] -#[contracttype] #[derive(Clone, Debug)] #[contracttype] pub struct Bill { @@ -35,7 +33,6 @@ pub struct Bill { pub currency: String, } - /// Paginated result for bill queries #[contracttype] #[derive(Clone)] @@ -57,8 +54,6 @@ pub mod pause_functions { pub const RESTORE: soroban_sdk::Symbol = symbol_short!("restore"); } -const CONTRACT_VERSION: u32 = 1; -const MAX_BATCH_SIZE: u32 = 50; const STORAGE_UNPAID_TOTALS: Symbol = symbol_short!("UNPD_TOT"); #[contracterror] @@ -82,8 +77,6 @@ pub enum Error { InvalidCurrency = 15, } -#[derive(Clone)] -#[contracttype] #[derive(Clone)] #[contracttype] pub struct ArchivedBill { @@ -98,7 +91,6 @@ pub struct ArchivedBill { pub currency: String, } - /// Paginated result for archived bill queries #[contracttype] #[derive(Clone)] @@ -393,24 +385,24 @@ impl BillPayments { env.storage().instance().get(&symbol_short!("UPG_ADM")) } /// Set or transfer the upgrade admin role. - /// + /// /// # Security Requirements /// - If no upgrade admin exists, caller must equal new_admin (bootstrap pattern) /// - If upgrade admin exists, only current upgrade admin can transfer /// - Caller must be authenticated via require_auth() - /// + /// /// # Parameters /// - `caller`: The address attempting to set the upgrade admin /// - `new_admin`: The address to become the new upgrade admin - /// + /// /// # Returns /// - `Ok(())` on successful admin transfer /// - `Err(Error::Unauthorized)` if caller lacks permission pub fn set_upgrade_admin(env: Env, caller: Address, new_admin: Address) -> Result<(), Error> { caller.require_auth(); - + let current_upgrade_admin = Self::get_upgrade_admin(&env); - + // Authorization logic: // 1. If no upgrade admin exists, caller must equal new_admin (bootstrap) // 2. If upgrade admin exists, only current upgrade admin can transfer @@ -421,18 +413,18 @@ impl BillPayments { return Err(Error::Unauthorized); } } - Some(current_admin) => { + Some(ref current_admin) => { // Admin transfer - only current admin can transfer - if current_admin != caller { + if current_admin != &caller { return Err(Error::Unauthorized); } } } - + env.storage() .instance() .set(&symbol_short!("UPG_ADM"), &new_admin); - + // Emit admin transfer event for audit trail RemitwiseEvents::emit( &env, @@ -441,12 +433,12 @@ impl BillPayments { symbol_short!("adm_xfr"), (current_upgrade_admin, new_admin.clone()), ); - + Ok(()) } /// Get the current upgrade admin address. - /// + /// /// # Returns /// - `Some(Address)` if upgrade admin is set /// - `None` if no upgrade admin has been configured @@ -889,659 +881,661 @@ impl BillPayments { Ok(()) } - /// Get all bills (paid and unpaid) - /// - /// # Returns - /// Vec of all Bill structs - pub fn get_all_bills(env: Env) -> Vec { - // ----------------------------------------------------------------------- - // Backward-compat helpers - // ----------------------------------------------------------------------- - /// Legacy helper: returns ALL unpaid bills for owner in one Vec. - /// Only safe for owners with a small number of bills. Prefer the - /// paginated `get_unpaid_bills` for production use. - pub fn get_all_unpaid_bills_legacy(env: Env, owner: Address) -> Vec { - let bills: Map = env - .storage() - .instance() - .get(&symbol_short!("BILLS")) - .unwrap_or_else(|| Map::new(&env)); - let mut result = Vec::new(&env); - for (_, bill) in bills.iter() { - if !bill.paid && bill.owner == owner { - result.push_back(bill); + // ----------------------------------------------------------------------- + // Backward-compat helpers + // ----------------------------------------------------------------------- + + /// Legacy helper: returns ALL unpaid bills for owner in one Vec. + /// Only safe for owners with a small number of bills. Prefer the + /// paginated `get_unpaid_bills` for production use. + pub fn get_all_unpaid_bills_legacy(env: Env, owner: Address) -> Vec { + let bills: Map = env + .storage() + .instance() + .get(&symbol_short!("BILLS")) + .unwrap_or_else(|| Map::new(&env)); + let mut result = Vec::new(&env); + for (_, bill) in bills.iter() { + if !bill.paid && bill.owner == owner { + result.push_back(bill); + } } - } - result - } + result + } + + // ----------------------------------------------------------------------- + // Archived bill queries (paginated) + // ----------------------------------------------------------------------- + + /// Get a page of archived bills for `owner`. + pub fn get_archived_bills( + env: Env, + owner: Address, + cursor: u32, + limit: u32, + ) -> ArchivedBillPage { + let limit = clamp_limit(limit); + let archived: Map = env + .storage() + .instance() + .get(&symbol_short!("ARCH_BILL")) + .unwrap_or_else(|| Map::new(&env)); - // ----------------------------------------------------------------------- - // Archived bill queries (paginated) - // ----------------------------------------------------------------------- + let mut staging: Vec<(u32, ArchivedBill)> = Vec::new(&env); + for (id, bill) in archived.iter() { + if id <= cursor { + continue; + } + if bill.owner != owner { + continue; + } + staging.push_back((id, bill)); + if staging.len() > limit { + break; + } + } - /// Get a page of archived bills for `owner`. - pub fn get_archived_bills( - env: Env, - owner: Address, - cursor: u32, - limit: u32, - ) -> ArchivedBillPage { - let limit = clamp_limit(limit); - let archived: Map = env - .storage() - .instance() - .get(&symbol_short!("ARCH_BILL")) - .unwrap_or_else(|| Map::new(&env)); + let has_next = staging.len() > limit; + let mut items = Vec::new(&env); + let mut next_cursor: u32 = 0; + let take = if has_next { + staging.len() - 1 + } else { + staging.len() + }; - let mut staging: Vec<(u32, ArchivedBill)> = Vec::new(&env); - for (id, bill) in archived.iter() { - if id <= cursor { - continue; - } - if bill.owner != owner { - continue; + for i in 0..take { + if let Some((_, bill)) = staging.get(i) { + items.push_back(bill); + } } - staging.push_back((id, bill)); - if staging.len() > limit { - break; + if has_next { + if let Some((id, _)) = staging.get(take - 1) { + next_cursor = id; + } } - } - - let has_next = staging.len() > limit; - let mut items = Vec::new(&env); - let mut next_cursor: u32 = 0; - let take = if has_next { - staging.len() - 1 - } else { - staging.len() - }; - for i in 0..take { - if let Some((_, bill)) = staging.get(i) { - items.push_back(bill); - } - } - if has_next { - if let Some((id, _)) = staging.get(take - 1) { - next_cursor = id; + let count = items.len(); + ArchivedBillPage { + items, + next_cursor, + count, } } - let count = items.len(); - ArchivedBillPage { - items, - next_cursor, - count, + pub fn get_archived_bill(env: Env, bill_id: u32) -> Option { + let archived: Map = env + .storage() + .instance() + .get(&symbol_short!("ARCH_BILL")) + .unwrap_or_else(|| Map::new(&env)); + archived.get(bill_id) } - } - - pub fn get_archived_bill(env: Env, bill_id: u32) -> Option { - let archived: Map = env - .storage() - .instance() - .get(&symbol_short!("ARCH_BILL")) - .unwrap_or_else(|| Map::new(&env)); - archived.get(bill_id) - } - // ----------------------------------------------------------------------- - // Remaining operations - // ----------------------------------------------------------------------- + // ----------------------------------------------------------------------- + // Remaining operations + // ----------------------------------------------------------------------- - pub fn cancel_bill(env: Env, caller: Address, bill_id: u32) -> Result<(), Error> { - caller.require_auth(); - Self::require_not_paused(&env, pause_functions::CANCEL_BILL)?; - let mut bills: Map = env - .storage() - .instance() - .get(&symbol_short!("BILLS")) - .unwrap_or_else(|| Map::new(&env)); - let bill = bills.get(bill_id).ok_or(Error::BillNotFound)?; - if bill.owner != caller { - return Err(Error::Unauthorized); - } - let removed_unpaid_amount = if bill.paid { 0 } else { bill.amount }; - bills.remove(bill_id); - env.storage() - .instance() - .set(&symbol_short!("BILLS"), &bills); - if removed_unpaid_amount > 0 { - Self::adjust_unpaid_total(&env, &caller, -removed_unpaid_amount); + pub fn cancel_bill(env: Env, caller: Address, bill_id: u32) -> Result<(), Error> { + caller.require_auth(); + Self::require_not_paused(&env, pause_functions::CANCEL_BILL)?; + let mut bills: Map = env + .storage() + .instance() + .get(&symbol_short!("BILLS")) + .unwrap_or_else(|| Map::new(&env)); + let bill = bills.get(bill_id).ok_or(Error::BillNotFound)?; + if bill.owner != caller { + return Err(Error::Unauthorized); + } + let removed_unpaid_amount = if bill.paid { 0 } else { bill.amount }; + bills.remove(bill_id); + env.storage() + .instance() + .set(&symbol_short!("BILLS"), &bills); + if removed_unpaid_amount > 0 { + Self::adjust_unpaid_total(&env, &caller, -removed_unpaid_amount); + } + RemitwiseEvents::emit( + &env, + EventCategory::State, + EventPriority::Medium, + symbol_short!("canceled"), + bill_id, + ); + Ok(()) } - RemitwiseEvents::emit( - &env, - EventCategory::State, - EventPriority::Medium, - symbol_short!("canceled"), - bill_id, - ); - Ok(()) - } - - pub fn archive_paid_bills( - env: Env, - caller: Address, - before_timestamp: u64, - ) -> Result { - caller.require_auth(); - Self::require_not_paused(&env, pause_functions::ARCHIVE)?; - Self::extend_instance_ttl(&env); - let mut bills: Map = env - .storage() - .instance() - .get(&symbol_short!("BILLS")) - .unwrap_or_else(|| Map::new(&env)); - let mut archived: Map = env - .storage() - .instance() - .get(&symbol_short!("ARCH_BILL")) - .unwrap_or_else(|| Map::new(&env)); - - let current_time = env.ledger().timestamp(); - let mut archived_count = 0u32; - let mut to_remove: Vec = Vec::new(&env); + pub fn archive_paid_bills( + env: Env, + caller: Address, + before_timestamp: u64, + ) -> Result { + caller.require_auth(); + Self::require_not_paused(&env, pause_functions::ARCHIVE)?; + Self::extend_instance_ttl(&env); - for (id, bill) in bills.iter() { - if let Some(paid_at) = bill.paid_at { - if bill.paid && paid_at < before_timestamp { - let archived_bill = ArchivedBill { - id: bill.id, - owner: bill.owner.clone(), - name: bill.name.clone(), - amount: bill.amount, - paid_at, - archived_at: current_time, - tags: bill.tags.clone(), - currency: bill.currency.clone(), - }; - archived.set(id, archived_bill); - to_remove.push_back(id); - archived_count += 1; + let mut bills: Map = env + .storage() + .instance() + .get(&symbol_short!("BILLS")) + .unwrap_or_else(|| Map::new(&env)); + let mut archived: Map = env + .storage() + .instance() + .get(&symbol_short!("ARCH_BILL")) + .unwrap_or_else(|| Map::new(&env)); + + let current_time = env.ledger().timestamp(); + let mut archived_count = 0u32; + let mut to_remove: Vec = Vec::new(&env); + + for (id, bill) in bills.iter() { + if let Some(paid_at) = bill.paid_at { + if bill.paid && paid_at < before_timestamp { + let archived_bill = ArchivedBill { + id: bill.id, + owner: bill.owner.clone(), + name: bill.name.clone(), + amount: bill.amount, + paid_at, + archived_at: current_time, + tags: bill.tags.clone(), + currency: bill.currency.clone(), + }; + archived.set(id, archived_bill); + to_remove.push_back(id); + archived_count += 1; + } } } - } - - for id in to_remove.iter() { - bills.remove(id); - } - env.storage() - .instance() - .set(&symbol_short!("BILLS"), &bills); - env.storage() - .instance() - .set(&symbol_short!("ARCH_BILL"), &archived); + for id in to_remove.iter() { + bills.remove(id); + } - Self::extend_archive_ttl(&env); - Self::update_storage_stats(&env); + env.storage() + .instance() + .set(&symbol_short!("BILLS"), &bills); + env.storage() + .instance() + .set(&symbol_short!("ARCH_BILL"), &archived); - RemitwiseEvents::emit_batch( - &env, - EventCategory::System, - symbol_short!("archived"), - archived_count, - ); + Self::extend_archive_ttl(&env); + Self::update_storage_stats(&env); - Ok(archived_count) - } + RemitwiseEvents::emit_batch( + &env, + EventCategory::System, + symbol_short!("archived"), + archived_count, + ); - pub fn restore_bill(env: Env, caller: Address, bill_id: u32) -> Result<(), Error> { - caller.require_auth(); - Self::require_not_paused(&env, pause_functions::RESTORE)?; - Self::extend_instance_ttl(&env); + Ok(archived_count) + } - let mut archived: Map = env - .storage() - .instance() - .get(&symbol_short!("ARCH_BILL")) - .unwrap_or_else(|| Map::new(&env)); - let archived_bill = archived.get(bill_id).ok_or(Error::BillNotFound)?; + pub fn restore_bill(env: Env, caller: Address, bill_id: u32) -> Result<(), Error> { + caller.require_auth(); + Self::require_not_paused(&env, pause_functions::RESTORE)?; + Self::extend_instance_ttl(&env); - if archived_bill.owner != caller { - return Err(Error::Unauthorized); - } + let mut archived: Map = env + .storage() + .instance() + .get(&symbol_short!("ARCH_BILL")) + .unwrap_or_else(|| Map::new(&env)); + let archived_bill = archived.get(bill_id).ok_or(Error::BillNotFound)?; - let mut bills: Map = env - .storage() - .instance() - .get(&symbol_short!("BILLS")) - .unwrap_or_else(|| Map::new(&env)); + if archived_bill.owner != caller { + return Err(Error::Unauthorized); + } - let restored_bill = Bill { - id: archived_bill.id, - owner: archived_bill.owner.clone(), - name: archived_bill.name.clone(), - amount: archived_bill.amount, - due_date: env.ledger().timestamp() + 2592000, - recurring: false, - frequency_days: 0, - paid: true, - created_at: archived_bill.paid_at, - paid_at: Some(archived_bill.paid_at), - schedule_id: None, - tags: archived_bill.tags.clone(), - currency: archived_bill.currency.clone(), - }; + let mut bills: Map = env + .storage() + .instance() + .get(&symbol_short!("BILLS")) + .unwrap_or_else(|| Map::new(&env)); + + let restored_bill = Bill { + id: archived_bill.id, + owner: archived_bill.owner.clone(), + name: archived_bill.name.clone(), + amount: archived_bill.amount, + due_date: env.ledger().timestamp() + 2592000, + recurring: false, + frequency_days: 0, + paid: true, + created_at: archived_bill.paid_at, + paid_at: Some(archived_bill.paid_at), + schedule_id: None, + tags: archived_bill.tags.clone(), + currency: archived_bill.currency.clone(), + external_ref: None, + }; - bills.set(bill_id, restored_bill); - archived.remove(bill_id); + bills.set(bill_id, restored_bill); + archived.remove(bill_id); - env.storage() - .instance() - .set(&symbol_short!("BILLS"), &bills); - env.storage() - .instance() - .set(&symbol_short!("ARCH_BILL"), &archived); + env.storage() + .instance() + .set(&symbol_short!("BILLS"), &bills); + env.storage() + .instance() + .set(&symbol_short!("ARCH_BILL"), &archived); - Self::update_storage_stats(&env); + Self::update_storage_stats(&env); - RemitwiseEvents::emit( - &env, - EventCategory::State, - EventPriority::Medium, - symbol_short!("restored"), - bill_id, - ); - Ok(()) - } + RemitwiseEvents::emit( + &env, + EventCategory::State, + EventPriority::Medium, + symbol_short!("restored"), + bill_id, + ); + Ok(()) + } - pub fn bulk_cleanup_bills( - env: Env, - caller: Address, - before_timestamp: u64, - ) -> Result { - caller.require_auth(); - Self::require_not_paused(&env, pause_functions::ARCHIVE)?; - Self::extend_instance_ttl(&env); + pub fn bulk_cleanup_bills( + env: Env, + caller: Address, + before_timestamp: u64, + ) -> Result { + caller.require_auth(); + Self::require_not_paused(&env, pause_functions::ARCHIVE)?; + Self::extend_instance_ttl(&env); - let mut archived: Map = env - .storage() - .instance() - .get(&symbol_short!("ARCH_BILL")) - .unwrap_or_else(|| Map::new(&env)); - let mut deleted_count = 0u32; - let mut to_remove: Vec = Vec::new(&env); + let mut archived: Map = env + .storage() + .instance() + .get(&symbol_short!("ARCH_BILL")) + .unwrap_or_else(|| Map::new(&env)); + let mut deleted_count = 0u32; + let mut to_remove: Vec = Vec::new(&env); - for (id, bill) in archived.iter() { - if bill.archived_at < before_timestamp { - to_remove.push_back(id); - deleted_count += 1; + for (id, bill) in archived.iter() { + if bill.archived_at < before_timestamp { + to_remove.push_back(id); + deleted_count += 1; + } } - } - for id in to_remove.iter() { - archived.remove(id); - } - - env.storage() - .instance() - .set(&symbol_short!("ARCH_BILL"), &archived); - Self::update_storage_stats(&env); + for id in to_remove.iter() { + archived.remove(id); + } - RemitwiseEvents::emit_batch( - &env, - EventCategory::System, - symbol_short!("cleaned"), - deleted_count, - ); - Ok(deleted_count) - } + env.storage() + .instance() + .set(&symbol_short!("ARCH_BILL"), &archived); + Self::update_storage_stats(&env); - pub fn batch_pay_bills(env: Env, caller: Address, bill_ids: Vec) -> Result { - caller.require_auth(); - Self::require_not_paused(&env, pause_functions::PAY_BILL)?; - if bill_ids.len() > (MAX_BATCH_SIZE as usize).try_into().unwrap_or(u32::MAX) { - return Err(Error::BatchTooLarge); - } - let bills_map: Map = env - .storage() - .instance() - .get(&symbol_short!("BILLS")) - .unwrap_or_else(|| Map::new(&env)); - for id in bill_ids.iter() { - let bill = bills_map.get(id).ok_or(Error::BillNotFound)?; - if bill.owner != caller { - return Err(Error::Unauthorized); + RemitwiseEvents::emit_batch( + &env, + EventCategory::System, + symbol_short!("cleaned"), + deleted_count, + ); + Ok(deleted_count) + } + + pub fn batch_pay_bills( + env: Env, + caller: Address, + bill_ids: Vec, + ) -> Result { + caller.require_auth(); + Self::require_not_paused(&env, pause_functions::PAY_BILL)?; + if bill_ids.len() > (MAX_BATCH_SIZE as usize).try_into().unwrap_or(u32::MAX) { + return Err(Error::BatchTooLarge); } - if bill.paid { - return Err(Error::BillAlreadyPaid); + let bills_map: Map = env + .storage() + .instance() + .get(&symbol_short!("BILLS")) + .unwrap_or_else(|| Map::new(&env)); + for id in bill_ids.iter() { + let bill = bills_map.get(id).ok_or(Error::BillNotFound)?; + if bill.owner != caller { + return Err(Error::Unauthorized); + } + if bill.paid { + return Err(Error::BillAlreadyPaid); + } } - } - Self::extend_instance_ttl(&env); - let mut bills: Map = env - .storage() - .instance() - .get(&symbol_short!("BILLS")) - .unwrap_or_else(|| Map::new(&env)); - let current_time = env.ledger().timestamp(); - let mut next_id: u32 = env - .storage() - .instance() - .get(&symbol_short!("NEXT_ID")) - .unwrap_or(0u32); - let mut paid_count = 0u32; - let mut unpaid_delta = 0i128; - for id in bill_ids.iter() { - let mut bill = bills.get(id).ok_or(Error::BillNotFound)?; - if bill.owner != caller || bill.paid { - return Err(Error::BatchValidationFailed); + Self::extend_instance_ttl(&env); + let mut bills: Map = env + .storage() + .instance() + .get(&symbol_short!("BILLS")) + .unwrap_or_else(|| Map::new(&env)); + let current_time = env.ledger().timestamp(); + let mut next_id: u32 = env + .storage() + .instance() + .get(&symbol_short!("NEXT_ID")) + .unwrap_or(0u32); + let mut paid_count = 0u32; + let mut unpaid_delta = 0i128; + for id in bill_ids.iter() { + let mut bill = bills.get(id).ok_or(Error::BillNotFound)?; + if bill.owner != caller || bill.paid { + return Err(Error::BatchValidationFailed); + } + let amount = bill.amount; + bill.paid = true; + bill.paid_at = Some(current_time); + if bill.recurring { + next_id = next_id.saturating_add(1); + let next_due_date = bill.due_date + (bill.frequency_days as u64 * 86400); + let next_bill = Bill { + id: next_id, + owner: bill.owner.clone(), + name: bill.name.clone(), + amount: bill.amount, + due_date: next_due_date, + recurring: true, + frequency_days: bill.frequency_days, + paid: false, + created_at: current_time, + paid_at: None, + schedule_id: bill.schedule_id, + tags: bill.tags.clone(), + currency: bill.currency.clone(), + external_ref: bill.external_ref.clone(), + }; + bills.set(next_id, next_bill); + } else { + unpaid_delta = unpaid_delta.saturating_sub(amount); + } + bills.set(id, bill); + paid_count += 1; + RemitwiseEvents::emit( + &env, + EventCategory::Transaction, + EventPriority::High, + symbol_short!("paid"), + (id, caller.clone(), amount), + ); } - let amount = bill.amount; - bill.paid = true; - bill.paid_at = Some(current_time); - if bill.recurring { - next_id = next_id.saturating_add(1); - let next_due_date = bill.due_date + (bill.frequency_days as u64 * 86400); - let next_bill = Bill { - id: next_id, - owner: bill.owner.clone(), - name: bill.name.clone(), - amount: bill.amount, - due_date: next_due_date, - recurring: true, - frequency_days: bill.frequency_days, - paid: false, - created_at: current_time, - paid_at: None, - schedule_id: bill.schedule_id, - tags: bill.tags.clone(), - currency: bill.currency.clone(), - }; - bills.set(next_id, next_bill); - } else { - unpaid_delta = unpaid_delta.saturating_sub(amount); + env.storage() + .instance() + .set(&symbol_short!("NEXT_ID"), &next_id); + env.storage() + .instance() + .set(&symbol_short!("BILLS"), &bills); + if unpaid_delta != 0 { + Self::adjust_unpaid_total(&env, &caller, unpaid_delta); } - bills.set(id, bill); - paid_count += 1; + Self::update_storage_stats(&env); RemitwiseEvents::emit( &env, - EventCategory::Transaction, - EventPriority::High, - symbol_short!("paid"), - (id, caller.clone(), amount), + EventCategory::System, + EventPriority::Medium, + symbol_short!("batch_pay"), + (paid_count, caller), ); + Ok(paid_count) } - env.storage() - .instance() - .set(&symbol_short!("NEXT_ID"), &next_id); - env.storage() - .instance() - .set(&symbol_short!("BILLS"), &bills); - if unpaid_delta != 0 { - Self::adjust_unpaid_total(&env, &caller, unpaid_delta); - } - Self::update_storage_stats(&env); - RemitwiseEvents::emit( - &env, - EventCategory::System, - EventPriority::Medium, - symbol_short!("batch_pay"), - (paid_count, caller), - ); - Ok(paid_count) - } - pub fn get_total_unpaid(env: Env, owner: Address) -> i128 { - if let Some(totals) = Self::get_unpaid_totals_map(&env) { - if let Some(total) = totals.get(owner.clone()) { - return total; + pub fn get_total_unpaid(env: Env, owner: Address) -> i128 { + if let Some(totals) = Self::get_unpaid_totals_map(&env) { + let total_opt: Option = totals.get(owner.clone()); + if let Some(total) = total_opt { + return total; + } } - } - let bills: Map = env - .storage() - .instance() - .get(&symbol_short!("BILLS")) - .unwrap_or_else(|| Map::new(&env)); - let mut total = 0i128; - for (_, bill) in bills.iter() { - if !bill.paid && bill.owner == owner { - total += bill.amount; + let bills: Map = env + .storage() + .instance() + .get(&symbol_short!("BILLS")) + .unwrap_or_else(|| Map::new(&env)); + let mut total = 0i128; + for (_, bill) in bills.iter() { + if !bill.paid && bill.owner == owner { + total += bill.amount; + } } + total } - total - } - pub fn get_storage_stats(env: Env) -> StorageStats { - env.storage() - .instance() - .get(&symbol_short!("STOR_STAT")) - .unwrap_or(StorageStats { - active_bills: 0, - archived_bills: 0, - total_unpaid_amount: 0, - total_archived_amount: 0, - last_updated: 0, - }) - } + pub fn get_storage_stats(env: Env) -> StorageStats { + env.storage() + .instance() + .get(&symbol_short!("STOR_STAT")) + .unwrap_or(StorageStats { + active_bills: 0, + archived_bills: 0, + total_unpaid_amount: 0, + total_archived_amount: 0, + last_updated: 0, + }) + } + + // ----------------------------------------------------------------------- + // Currency-filter helper queries + // ----------------------------------------------------------------------- + + /// Get a page of ALL bills (paid + unpaid) for `owner` that match `currency`. + /// + /// # Arguments + /// * `owner` – Address of the bill owner + /// * `currency` – Currency code to filter by, e.g. `"USDC"`, `"XLM"` + /// * `cursor` – Start after this bill ID (pass 0 for the first page) + /// * `limit` – Max items per page (0 → DEFAULT_PAGE_LIMIT, capped at MAX_PAGE_LIMIT) + /// + /// # Returns + /// `BillPage { items, next_cursor, count }`. `next_cursor == 0` means no more pages. + /// + /// # Currency Comparison + /// Currency comparison is case-insensitive and whitespace-insensitive: + /// - "usdc", "USDC", "UsDc", " usdc " all match + /// - Empty currency defaults to "XLM" for comparison + /// + /// # Examples + /// ```rust + /// // Get all USDC bills for owner + /// let page = client.get_bills_by_currency(&owner, &"USDC".into(), &0, &10); + /// ``` + pub fn get_bills_by_currency( + env: Env, + owner: Address, + currency: String, + cursor: u32, + limit: u32, + ) -> BillPage { + let limit = clamp_limit(limit); + let normalized_currency = Self::normalize_currency(&env, ¤cy); + let bills: Map = env + .storage() + .instance() + .get(&symbol_short!("BILLS")) + .unwrap_or_else(|| Map::new(&env)); - // ----------------------------------------------------------------------- - // Currency-filter helper queries - // ----------------------------------------------------------------------- + let mut staging: Vec<(u32, Bill)> = Vec::new(&env); + for (id, bill) in bills.iter() { + if id <= cursor { + continue; + } + if bill.owner != owner || bill.currency != normalized_currency { + continue; + } + staging.push_back((id, bill)); + if staging.len() > limit { + break; + } + } - /// Get a page of ALL bills (paid + unpaid) for `owner` that match `currency`. - /// - /// # Arguments - /// * `owner` – Address of the bill owner - /// * `currency` – Currency code to filter by, e.g. `"USDC"`, `"XLM"` - /// * `cursor` – Start after this bill ID (pass 0 for the first page) - /// * `limit` – Max items per page (0 → DEFAULT_PAGE_LIMIT, capped at MAX_PAGE_LIMIT) - /// - /// # Returns - /// `BillPage { items, next_cursor, count }`. `next_cursor == 0` means no more pages. - /// - /// # Currency Comparison - /// Currency comparison is case-insensitive and whitespace-insensitive: - /// - "usdc", "USDC", "UsDc", " usdc " all match - /// - Empty currency defaults to "XLM" for comparison - /// - /// # Examples - /// ```rust - /// // Get all USDC bills for owner - /// let page = client.get_bills_by_currency(&owner, &"USDC".into(), &0, &10); - /// ``` - pub fn get_bills_by_currency( - env: Env, - owner: Address, - currency: String, - cursor: u32, - limit: u32, - ) -> BillPage { - let limit = Self::clamp_limit(limit); - let normalized_currency = Self::normalize_currency(&env, ¤cy); - let bills: Map = env - .storage() - .instance() - .get(&symbol_short!("BILLS")) - .unwrap_or_else(|| Map::new(&env)); + Self::build_page(&env, staging, limit) + } + + /// Get a page of **unpaid** bills for `owner` that match `currency`. + /// + /// # Arguments + /// * `owner` – Address of the bill owner + /// * `currency` – Currency code to filter by, e.g. `"USDC"`, `"XLM"` + /// * `cursor` – Start after this bill ID (pass 0 for the first page) + /// * `limit` – Max items per page (0 → DEFAULT_PAGE_LIMIT, capped at MAX_PAGE_LIMIT) + /// + /// # Returns + /// `BillPage { items, next_cursor, count }`. `next_cursor == 0` means no more pages. + /// + /// # Currency Comparison + /// Currency comparison is case-insensitive and whitespace-insensitive: + /// - "usdc", "USDC", "UsDc", " usdc " all match + /// - Empty currency defaults to "XLM" for comparison + /// + /// # Examples + /// ```rust + /// // Get unpaid USDC bills for owner + /// let page = client.get_unpaid_bills_by_currency(&owner, &"USDC".into(), &0, &10); + /// ``` + pub fn get_unpaid_bills_by_currency( + env: Env, + owner: Address, + currency: String, + cursor: u32, + limit: u32, + ) -> BillPage { + let limit = clamp_limit(limit); + let normalized_currency = Self::normalize_currency(&env, ¤cy); + let bills: Map = env + .storage() + .instance() + .get(&symbol_short!("BILLS")) + .unwrap_or_else(|| Map::new(&env)); - let mut staging: Vec<(u32, Bill)> = Vec::new(&env); - for (id, bill) in bills.iter() { - if id <= cursor { - continue; - } - if bill.owner != owner || bill.currency != normalized_currency { - continue; + let mut staging: Vec<(u32, Bill)> = Vec::new(&env); + for (id, bill) in bills.iter() { + if id <= cursor { + continue; + } + if bill.owner != owner || bill.paid || bill.currency != normalized_currency { + continue; + } + staging.push_back((id, bill)); + if staging.len() > limit { + break; + } } - staging.push_back((id, bill)); - if staging.len() > limit { - break; + + Self::build_page(&env, staging, limit) + } + + /// Sum of all **unpaid** bill amounts for `owner` denominated in `currency`. + /// + /// # Arguments + /// * `owner` – Address of the bill owner + /// * `currency` – Currency code to filter by, e.g. `"USDC"`, `"XLM"` + /// + /// # Returns + /// Total unpaid amount in the specified currency + /// + /// # Currency Comparison + /// Currency comparison is case-insensitive and whitespace-insensitive: + /// - "usdc", "USDC", "UsDc", " usdc " all match + /// - Empty currency defaults to "XLM" for comparison + /// + /// # Examples + /// ```rust + /// // Get total unpaid amount in USDC + /// let total_usdc = client.get_total_unpaid_by_currency(&owner, &"USDC".into()); + /// // Get total unpaid amount in XLM + /// let total_xlm = client.get_total_unpaid_by_currency(&owner, &"XLM".into()); + /// ``` + pub fn get_total_unpaid_by_currency(env: Env, owner: Address, currency: String) -> i128 { + let normalized_currency = Self::normalize_currency(&env, ¤cy); + let bills: Map = env + .storage() + .instance() + .get(&symbol_short!("BILLS")) + .unwrap_or_else(|| Map::new(&env)); + let mut total = 0i128; + for (_, bill) in bills.iter() { + if !bill.paid && bill.owner == owner && bill.currency == normalized_currency { + total += bill.amount; + } } + total } - Self::build_page(&env, staging, limit) - } - - /// Get a page of **unpaid** bills for `owner` that match `currency`. - /// - /// # Arguments - /// * `owner` – Address of the bill owner - /// * `currency` – Currency code to filter by, e.g. `"USDC"`, `"XLM"` - /// * `cursor` – Start after this bill ID (pass 0 for the first page) - /// * `limit` – Max items per page (0 → DEFAULT_PAGE_LIMIT, capped at MAX_PAGE_LIMIT) - /// - /// # Returns - /// `BillPage { items, next_cursor, count }`. `next_cursor == 0` means no more pages. - /// - /// # Currency Comparison - /// Currency comparison is case-insensitive and whitespace-insensitive: - /// - "usdc", "USDC", "UsDc", " usdc " all match - /// - Empty currency defaults to "XLM" for comparison - /// - /// # Examples - /// ```rust - /// // Get unpaid USDC bills for owner - /// let page = client.get_unpaid_bills_by_currency(&owner, &"USDC".into(), &0, &10); - /// ``` - pub fn get_unpaid_bills_by_currency( - env: Env, - owner: Address, - currency: String, - cursor: u32, - limit: u32, - ) -> BillPage { - let limit = Self::clamp_limit(limit); - let normalized_currency = Self::normalize_currency(&env, ¤cy); - let bills: Map = env - .storage() - .instance() - .get(&symbol_short!("BILLS")) - .unwrap_or_else(|| Map::new(&env)); + // ----------------------------------------------------------------------- + // Internal helpers + // ----------------------------------------------------------------------- - let mut staging: Vec<(u32, Bill)> = Vec::new(&env); - for (id, bill) in bills.iter() { - if id <= cursor { - continue; - } - if bill.owner != owner || bill.paid || bill.currency != normalized_currency { - continue; - } - staging.push_back((id, bill)); - if staging.len() > limit { - break; - } + fn extend_instance_ttl(env: &Env) { + env.storage() + .instance() + .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); } - Self::build_page(&env, staging, limit) - } - - /// Sum of all **unpaid** bill amounts for `owner` denominated in `currency`. - /// - /// # Arguments - /// * `owner` – Address of the bill owner - /// * `currency` – Currency code to filter by, e.g. `"USDC"`, `"XLM"` - /// - /// # Returns - /// Total unpaid amount in the specified currency - /// - /// # Currency Comparison - /// Currency comparison is case-insensitive and whitespace-insensitive: - /// - "usdc", "USDC", "UsDc", " usdc " all match - /// - Empty currency defaults to "XLM" for comparison - /// - /// # Examples - /// ```rust - /// // Get total unpaid amount in USDC - /// let total_usdc = client.get_total_unpaid_by_currency(&owner, &"USDC".into()); - /// // Get total unpaid amount in XLM - /// let total_xlm = client.get_total_unpaid_by_currency(&owner, &"XLM".into()); - /// ``` - pub fn get_total_unpaid_by_currency(env: Env, owner: Address, currency: String) -> i128 { - let normalized_currency = Self::normalize_currency(&env, ¤cy); - let bills: Map = env - .storage() - .instance() - .get(&symbol_short!("BILLS")) - .unwrap_or_else(|| Map::new(&env)); - let mut total = 0i128; - for (_, bill) in bills.iter() { - if !bill.paid && bill.owner == owner && bill.currency == normalized_currency { - total += bill.amount; - } + fn extend_archive_ttl(env: &Env) { + env.storage() + .instance() + .extend_ttl(ARCHIVE_LIFETIME_THRESHOLD, ARCHIVE_BUMP_AMOUNT); } - total - } - // ----------------------------------------------------------------------- - // Internal helpers - // ----------------------------------------------------------------------- + fn update_storage_stats(env: &Env) { + let bills: Map = env + .storage() + .instance() + .get(&symbol_short!("BILLS")) + .unwrap_or_else(|| Map::new(env)); + let archived: Map = env + .storage() + .instance() + .get(&symbol_short!("ARCH_BILL")) + .unwrap_or_else(|| Map::new(env)); + + let mut active_count = 0u32; + let mut unpaid_amount = 0i128; + for (_, bill) in bills.iter() { + active_count += 1; + if !bill.paid { + unpaid_amount = unpaid_amount.saturating_add(bill.amount); + } + } - fn extend_instance_ttl(env: &Env) { - env.storage() - .instance() - .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); - } + let mut archived_count = 0u32; + let mut archived_amount = 0i128; + for (_, bill) in archived.iter() { + archived_count += 1; + archived_amount = archived_amount.saturating_add(bill.amount); + } - fn extend_archive_ttl(env: &Env) { - env.storage() - .instance() - .extend_ttl(ARCHIVE_LIFETIME_THRESHOLD, ARCHIVE_BUMP_AMOUNT); - } + let stats = StorageStats { + active_bills: active_count, + archived_bills: archived_count, + total_unpaid_amount: unpaid_amount, + total_archived_amount: archived_amount, + last_updated: env.ledger().timestamp(), + }; - fn update_storage_stats(env: &Env) { - let bills: Map = env - .storage() - .instance() - .get(&symbol_short!("BILLS")) - .unwrap_or_else(|| Map::new(env)); - let archived: Map = env - .storage() - .instance() - .get(&symbol_short!("ARCH_BILL")) - .unwrap_or_else(|| Map::new(env)); - - let mut active_count = 0u32; - let mut unpaid_amount = 0i128; - for (_, bill) in bills.iter() { - active_count += 1; - if !bill.paid { - unpaid_amount = unpaid_amount.saturating_add(bill.amount); - } + env.storage() + .instance() + .set(&symbol_short!("STOR_STAT"), &stats); } - - let mut archived_count = 0u32; - let mut archived_amount = 0i128; - for (_, bill) in archived.iter() { - archived_count += 1; - archived_amount = archived_amount.saturating_add(bill.amount); + fn get_unpaid_totals_map(env: &Env) -> Option> { + env.storage().instance().get(&STORAGE_UNPAID_TOTALS) } - let stats = StorageStats { - active_bills: active_count, - archived_bills: archived_count, - total_unpaid_amount: unpaid_amount, - total_archived_amount: archived_amount, - last_updated: env.ledger().timestamp(), - }; - - env.storage() - .instance() - .set(&symbol_short!("STOR_STAT"), &stats); - } - fn get_unpaid_totals_map(env: &Env) -> Option> { - env.storage().instance().get(&STORAGE_UNPAID_TOTALS) - } - - fn adjust_unpaid_total(env: &Env, owner: &Address, delta: i128) { - if delta == 0 { - return; + fn adjust_unpaid_total(env: &Env, owner: &Address, delta: i128) { + if delta == 0 { + return; + } + let mut totals: Map = env + .storage() + .instance() + .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"); + totals.set(owner.clone(), next); + env.storage() + .instance() + .set(&STORAGE_UNPAID_TOTALS, &totals); } - let mut totals: Map = env - .storage() - .instance() - .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"); - totals.set(owner.clone(), next); - env.storage() - .instance() - .set(&STORAGE_UNPAID_TOTALS, &totals); } -} - // ----------------------------------------------------------------------- // Tests // ----------------------------------------------------------------------- @@ -2876,7 +2870,8 @@ mod test { &String::from_str(&env, "XLM"), ); - let result = client.try_set_external_ref(&other, &bill_id, &Some(String::from_str(&env, "REF"))); + let result = + client.try_set_external_ref(&other, &bill_id, &Some(String::from_str(&env, "REF"))); assert_eq!(result, Err(Ok(Error::Unauthorized))); } @@ -2900,7 +2895,7 @@ mod test { &String::from_str(&env, "XLM"), ); client.pay_bill(&owner, &bill_id); - + // Archive it client.archive_paid_bills(&owner, &2000000); @@ -2918,8 +2913,26 @@ mod test { let bob = Address::generate(&env); env.mock_all_auths(); - let alice_bill = client.create_bill(&alice, &String::from_str(&env, "Alice"), &100, &1000000, &false, &0, &None, &String::from_str(&env, "XLM")); - let bob_bill = client.create_bill(&bob, &String::from_str(&env, "Bob"), &200, &1000000, &false, &0, &None, &String::from_str(&env, "XLM")); + let alice_bill = client.create_bill( + &alice, + &String::from_str(&env, "Alice"), + &100, + &1000000, + &false, + &0, + &None, + &String::from_str(&env, "XLM"), + ); + let bob_bill = client.create_bill( + &bob, + &String::from_str(&env, "Bob"), + &200, + &1000000, + &false, + &0, + &None, + &String::from_str(&env, "XLM"), + ); let mut ids = Vec::new(&env); ids.push_back(alice_bill); @@ -2953,21 +2966,3 @@ mod test { client.bulk_cleanup_bills(&admin, &1000000); } } -} - -fn extend_instance_ttl(env: &Env) { - // Extend the contract instance itself - env.storage().instance().extend_ttl( - INSTANCE_LIFETIME_THRESHOLD, - INSTANCE_BUMP_AMOUNT - ); -} -} - -pub fn create_bill(env: Env, ...) { - extend_instance_ttl(&env); // Keep contract alive - // ... logic to create bill ... - let key = DataKey::Bill(bill_id); - env.storage().persistent().set(&key, &bill); - extend_ttl(&env, &key); // Keep this specific bill alive -} \ No newline at end of file diff --git a/orchestrator/src/lib.rs b/orchestrator/src/lib.rs index c20eaf03..3adf3534 100644 --- a/orchestrator/src/lib.rs +++ b/orchestrator/src/lib.rs @@ -183,6 +183,7 @@ pub trait InsuranceTrait { #[contracterror] #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[repr(u32)] + pub enum OrchestratorError { /// Permission denied by family wallet PermissionDenied = 1, @@ -200,14 +201,18 @@ pub enum OrchestratorError { InvalidAmount = 7, /// Invalid contract address provided InvalidContractAddress = 8, + /// Self reference not allowed + SelfReferenceNotAllowed = 9, + /// Duplicate contract address detected + DuplicateContractAddress = 10, /// Generic cross-contract call failure - CrossContractCallFailed = 9, + CrossContractCallFailed = 11, /// Reentrancy detected - execution is already in progress /// /// This error is returned when a public entry point is called while another /// execution is already in progress. This prevents nested execution attacks /// and partial-state corruption. - ReentrancyDetected = 10, + ReentrancyDetected = 12, } /// Execution state tracking for reentrancy protection. @@ -224,7 +229,7 @@ pub enum OrchestratorError { /// At most one execution can be active at any time. Any attempt to enter /// `Executing` state while already executing returns `ReentrancyDetected`. #[contracttype] -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u32)] pub enum ExecutionState { /// No execution in progress; entry points may be called @@ -1014,6 +1019,45 @@ impl Orchestrator { result } + /// Validate that all contract addresses are distinct and valid + fn validate_remittance_flow_addresses( + env: &Env, + family_wallet_addr: &Address, + remittance_split_addr: &Address, + savings_addr: &Address, + bills_addr: &Address, + insurance_addr: &Address, + ) -> Result<(), OrchestratorError> { + let orchestrator_id = env.current_contract_address(); + + // Check for self-referential calls + if family_wallet_addr == &orchestrator_id + || remittance_split_addr == &orchestrator_id + || savings_addr == &orchestrator_id + || bills_addr == &orchestrator_id + || insurance_addr == &orchestrator_id + { + return Err(OrchestratorError::SelfReferenceNotAllowed); + } + + // Check all addresses are distinct + if family_wallet_addr == remittance_split_addr + || family_wallet_addr == savings_addr + || family_wallet_addr == bills_addr + || family_wallet_addr == insurance_addr + || remittance_split_addr == savings_addr + || remittance_split_addr == bills_addr + || remittance_split_addr == insurance_addr + || savings_addr == bills_addr + || savings_addr == insurance_addr + || bills_addr == insurance_addr + { + return Err(OrchestratorError::DuplicateContractAddress); + } + + Ok(()) + } + // ============================================================================ // Public Functions - Complete Remittance Flow // ============================================================================ diff --git a/orchestrator/src/test.rs b/orchestrator/src/test.rs index cd0ece6f..bb812cb1 100644 --- a/orchestrator/src/test.rs +++ b/orchestrator/src/test.rs @@ -1,5 +1,54 @@ -use crate::{ExecutionState, Orchestrator, OrchestratorClient, OrchestratorError}; +use crate::{ExecutionState, Orchestrator, OrchestratorClient, OrchestratorError, OrchestratorAuditEntry}; use soroban_sdk::{contract, contractimpl, Address, Env, Vec}; +use testutils::generate_test_address; +use soroban_sdk::testutils::Address as AddressTrait; +use soroban_sdk::symbol_short; +use soroban_sdk::testutils::Address as _; +extern crate std; +use std::collections::BTreeSet; +// =========================================================================== +// Test Helpers +// =========================================================================== + +/// Helper to provide test environment similar to setup() + + // Reuse the existing setup function for consistency + setup() +} + +/// Stub for seeding audit log; no-op for now +fn seed_audit_log(_env: &Env, _user: &Address, _seeded: u32) { + // In a full implementation this would populate audit logs for testing. + // For current tests a no-op is sufficient. +} + + +fn collect_all_pages(client: &OrchestratorClient, page_size: u32) -> std::vec::Vec { + let mut entries: std::vec::Vec = std::vec::Vec::new(); + let mut cursor: u32 = 0; + loop { + let page = client.get_audit_log(&cursor, &page_size); + if page.is_empty() { + break; + } + for entry in page.iter() { + entries.push(entry.clone()); + } + let page_len = page.len() as u32; + cursor += page_len; + if page_len < page_size { + break; + } + } + entries +} + + + +// =========================================================================== +// Existing Tests (preserved) +// =========================================================================== + // ============================================================================ // Mock Contract Implementations @@ -132,7 +181,25 @@ mod tests { ) } - // ============================================================================ + // =========================================================================== + // Test Helpers + // =========================================================================== + + /// Helper to provide test environment similar to setup() + fn setup_test_env() -> (Env, Address, Address, Address, Address, Address, Address, Address) { + // Reuse the existing setup function for consistency + setup() + } + + /// Stub for seeding audit log; no-op for now + fn seed_audit_log(_env: &Env, _user: &Address, _seeded: u32) { + // In a full implementation this would populate audit logs for testing. + // For current tests a no-op is sufficient. + } + + // =========================================================================== + // Existing Tests (preserved) + // =========================================================================== // Existing Tests (preserved) // ============================================================================ @@ -1392,7 +1459,7 @@ mod tests { for entry in &entries { dedupe.insert(entry.amount); } - assert_eq!(dedupe.len(), entries.len()); + assert_eq!(dedupe.len(), entries.len() as usize); } #[test] diff --git a/remittance_split/src/lib.rs b/remittance_split/src/lib.rs index e1c25662..918fce08 100644 --- a/remittance_split/src/lib.rs +++ b/remittance_split/src/lib.rs @@ -281,11 +281,10 @@ impl RemittanceSplit { .ok_or(RemittanceSplitError::NotInitialized)?; let current_upgrade_admin = Self::get_upgrade_admin(&env); - // Authorization logic: // 1. If no upgrade admin exists, only contract owner can set initial admin // 2. If upgrade admin exists, only current upgrade admin can transfer - match current_upgrade_admin { + match ¤t_upgrade_admin { None => { // Initial admin setup - only owner can set if config.owner != caller { @@ -294,12 +293,11 @@ impl RemittanceSplit { } Some(current_admin) => { // Admin transfer - only current admin can transfer - if current_admin != caller { + if current_admin != &caller { return Err(RemittanceSplitError::Unauthorized); } } } - env.storage() .instance() .set(&symbol_short!("UPG_ADM"), &new_admin); @@ -307,7 +305,7 @@ impl RemittanceSplit { // Emit admin transfer event for audit trail env.events().publish( (symbol_short!("split"), symbol_short!("adm_xfr")), - (current_upgrade_admin, new_admin.clone()), + (current_upgrade_admin.clone(), new_admin.clone()), ); Ok(()) diff --git a/savings_goals/src/lib.rs b/savings_goals/src/lib.rs index cfa0a2c0..edc7f5f2 100644 --- a/savings_goals/src/lib.rs +++ b/savings_goals/src/lib.rs @@ -421,9 +421,9 @@ impl SavingsGoalContract { panic!("Unauthorized: bootstrap requires caller == new_admin"); } } - Some(current_admin) => { + Some(ref current_admin) => { // Admin transfer - only current admin can transfer - if current_admin != caller { + if current_admin != &caller { panic!("Unauthorized: only current upgrade admin can transfer"); } }