From 4c7626884d9029074582b8996c39f9fb30a8eb80 Mon Sep 17 00:00:00 2001 From: skylar Date: Sun, 28 Jun 2026 15:58:29 +0000 Subject: [PATCH 1/5] feat: implement cross-chain asset support and bridge event verification - Add source_chain and source_tx_hash metadata to Plan struct for tracking bridged asset origins - Extend Beneficiary with destination_chain and destination_address for cross-chain payouts - Implement BridgePayoutEvent struct for Allbridge messaging protocol compatibility - Add admin-managed wrapped token registry with register/unregister/query functions - Validate bridged assets correspond to supported wrapped tokens on Stellar - Implement safe bridge fee deduction (1% BPS) with integer division - Emit bridge payout events with gross/fee/net amounts for non-Stellar destinations - Update all contract tests with new metadata fields and bridge parameters - Pass acceptance criteria: metadata acceptance, event emission, safe fee deduction --- contracts/inheritance-contract/src/lib.rs | 144 ++++++++++++++++++++- contracts/inheritance-contract/src/test.rs | 64 +++++++++ 2 files changed, 206 insertions(+), 2 deletions(-) diff --git a/contracts/inheritance-contract/src/lib.rs b/contracts/inheritance-contract/src/lib.rs index eb8099ad0..a0479b866 100644 --- a/contracts/inheritance-contract/src/lib.rs +++ b/contracts/inheritance-contract/src/lib.rs @@ -1,9 +1,11 @@ #![no_std] -use soroban_sdk::{contract, contracterror, contractimpl, contracttype, Address, Env, String, Vec}; +use soroban_sdk::{contract, contracterror, contractimpl, contracttype, Address, Env, String, Symbol, Vec}; const MAX_BENEFICIARIES: u32 = 100; const PLAN_TTL_THRESHOLD: u32 = 500; const PLAN_TTL_LEEWAY: u32 = 100; +const BRIDGE_FEE_BPS: u32 = 100; // 1% bridge fee +const STELLAR_CHAIN: &str = "Stellar"; #[contracterror] #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -18,6 +20,10 @@ pub enum Error { TooManyBeneficiaries = 8, TimelockNotExpired = 9, PayoutNotTriggered = 10, + UnsupportedToken = 11, + InvalidBridgeMetadata = 12, + MathOverflow = 13, + AlreadyInitialized = 14, } #[contracttype] @@ -26,6 +32,8 @@ pub struct Beneficiary { pub address: Address, pub allocation_bps: u32, pub fiat_anchor_info: String, + pub destination_chain: String, + pub destination_address: String, } #[contracttype] @@ -41,15 +49,33 @@ pub struct Plan { pub yield_rate_bps: u32, pub is_active: bool, pub timelock_duration: u64, + pub source_chain: String, + pub source_tx_hash: String, } pub type InheritancePlan = Plan; +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BridgePayoutEvent { + pub owner: Address, + pub token: Address, + pub beneficiary: Address, + pub destination_chain: String, + pub destination_address: String, + pub gross_amount: i128, + pub fee_amount: i128, + pub net_amount: i128, + pub source_chain: String, + pub source_tx_hash: String, +} + #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub enum DataKey { Plan(Address), ClaimStatus(Address), + SupportedWrappedToken(Address), } #[contracttype] @@ -67,6 +93,22 @@ impl InheritanceContract { .persistent() .extend_ttl(key, PLAN_TTL_LEEWAY, PLAN_TTL_THRESHOLD); } + + fn emit_bridge_payout_event(env: &Env, event: &BridgePayoutEvent) { + let topic = (Symbol::short("BridgePayout"), env.current_contract_address()); + env.events().publish(topic, event); + } + + fn is_stellar_chain(chain: &String, env: &Env) -> bool { + let stellar = String::from_str(env, STELLAR_CHAIN); + chain == &stellar + } + + fn supported_wrapped_token(env: &Env, token: &Address) -> bool { + env.storage() + .persistent() + .has(&DataKey::SupportedWrappedToken(token.clone())) + } } #[contractimpl] @@ -85,6 +127,8 @@ impl InheritanceContract { earn_yield: bool, yield_rate_bps: u32, timelock_duration: u64, + source_chain: String, + source_tx_hash: String, ) -> Result<(), Error> { owner.require_auth(); @@ -104,11 +148,25 @@ impl InheritanceContract { let mut total_bps: u32 = 0; for beneficiary in beneficiaries.iter() { total_bps += beneficiary.allocation_bps; + let empty = String::from_str(&env, ""); + if beneficiary.destination_chain == empty || beneficiary.destination_address == empty { + return Err(Error::InvalidBridgeMetadata); + } } if total_bps != 10000 { return Err(Error::InvalidBasisPoints); } + let empty = String::from_str(&env, ""); + if source_chain == empty || source_tx_hash == empty { + return Err(Error::InvalidBridgeMetadata); + } + + let stellarchain = String::from_str(&env, STELLAR_CHAIN); + if source_chain != stellarchain && !Self::supported_wrapped_token(&env, &token) { + return Err(Error::UnsupportedToken); + } + let token_client = soroban_sdk::token::Client::new(&env, &token); let balance = token_client.balance(&owner); if balance < amount { @@ -128,6 +186,8 @@ impl InheritanceContract { yield_rate_bps, is_active: true, timelock_duration, + source_chain: source_chain.clone(), + source_tx_hash: source_tx_hash.clone(), }; env.storage().persistent().set(&key, &plan); @@ -155,6 +215,56 @@ impl InheritanceContract { Ok(()) } + pub fn initialize(env: Env, admin: Address) -> Result<(), Error> { + let admin_key = InstanceDataKey::Admin; + if env.storage().instance().has(&admin_key) { + return Err(Error::AlreadyInitialized); + } + env.storage().instance().set(&admin_key, &admin); + Ok(()) + } + + fn require_admin(env: &Env, admin: &Address) -> Result<(), Error> { + let admin_key = InstanceDataKey::Admin; + let configured_admin: Address = env + .storage() + .instance() + .get(&admin_key) + .ok_or(Error::Unauthorized)?; + if &configured_admin != admin { + return Err(Error::Unauthorized); + } + Ok(()) + } + + pub fn register_supported_wrapped_token( + env: Env, + admin: Address, + token: Address, + ) -> Result<(), Error> { + admin.require_auth(); + Self::require_admin(&env, &admin)?; + let key = DataKey::SupportedWrappedToken(token); + env.storage().persistent().set(&key, &true); + Ok(()) + } + + pub fn unregister_supported_wrapped_token( + env: Env, + admin: Address, + token: Address, + ) -> Result<(), Error> { + admin.require_auth(); + Self::require_admin(&env, &admin)?; + let key = DataKey::SupportedWrappedToken(token); + env.storage().persistent().remove(&key); + Ok(()) + } + + pub fn is_supported_wrapped_token(env: Env, token: Address) -> Result { + Ok(Self::supported_wrapped_token(&env, &token)) + } + /// Claim payout once the plan owner has been inactive beyond the grace period. /// Contributors: Calculate final yield-bearing payout, split assets among beneficiaries, /// emit payout events, and trigger anchor event emissions for fiat recipients. @@ -301,11 +411,41 @@ impl InheritanceContract { remaining -= amount; amount }; + + let destination_stellar = Self::is_stellar_chain(&beneficiary.destination_chain, &env); + let (fee_amount, net_amount) = if destination_stellar { + (0_i128, share) + } else { + let fee = share + .checked_mul(BRIDGE_FEE_BPS as i128) + .ok_or(Error::MathOverflow)? + .checked_div(10000) + .ok_or(Error::MathOverflow)?; + let net = share.checked_sub(fee).ok_or(Error::MathOverflow)?; + (fee, net) + }; + token_client.transfer( &env.current_contract_address(), &beneficiary.address, - &share, + &net_amount, ); + + if !destination_stellar { + let event = BridgePayoutEvent { + owner: plan.owner.clone(), + token: plan.token.clone(), + beneficiary: beneficiary.address.clone(), + destination_chain: beneficiary.destination_chain.clone(), + destination_address: beneficiary.destination_address.clone(), + gross_amount: share, + fee_amount, + net_amount, + source_chain: plan.source_chain.clone(), + source_tx_hash: plan.source_tx_hash.clone(), + }; + Self::emit_bridge_payout_event(&env, &event); + } } Ok(()) diff --git a/contracts/inheritance-contract/src/test.rs b/contracts/inheritance-contract/src/test.rs index b441860e2..e14fc1fdd 100644 --- a/contracts/inheritance-contract/src/test.rs +++ b/contracts/inheritance-contract/src/test.rs @@ -34,6 +34,8 @@ fn test_create_plan_success() { address: beneficiary_address.clone(), allocation_bps: 10000, fiat_anchor_info: String::from_str(&env, "NGN_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; client.create_plan( @@ -45,6 +47,8 @@ fn test_create_plan_success() { &true, &500, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); // Verify balances @@ -86,6 +90,8 @@ fn test_create_plan_insufficient_balance() { address: Address::generate(&env), allocation_bps: 10000, fiat_anchor_info: String::from_str(&env, "NGN_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; // Attempting to create plan for 1500 (owner only has 1000) @@ -98,6 +104,8 @@ fn test_create_plan_insufficient_balance() { &true, &500, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); assert_eq!(result, Err(Ok(Error::InsufficientBalance))); @@ -121,6 +129,8 @@ fn test_create_plan_negative_or_zero_amount() { address: Address::generate(&env), allocation_bps: 10000, fiat_anchor_info: String::from_str(&env, "NGN_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; // Amount = 0 @@ -133,6 +143,8 @@ fn test_create_plan_negative_or_zero_amount() { &true, &500, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); assert_eq!(result_zero, Err(Ok(Error::NegativeAmount))); @@ -146,6 +158,8 @@ fn test_create_plan_negative_or_zero_amount() { &true, &500, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); assert_eq!(result_neg, Err(Ok(Error::NegativeAmount))); } @@ -168,12 +182,16 @@ fn test_create_plan_invalid_basis_points() { address: Address::generate(&env), allocation_bps: 4000, fiat_anchor_info: String::from_str(&env, "NGN_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let beneficiary2 = Beneficiary { address: Address::generate(&env), allocation_bps: 5000, // Total = 9000 BPS (less than 10000) fiat_anchor_info: String::from_str(&env, "NGN_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let result = client.try_create_plan( @@ -185,6 +203,8 @@ fn test_create_plan_invalid_basis_points() { &true, &500, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); assert_eq!(result, Err(Ok(Error::InvalidBasisPoints))); @@ -208,6 +228,8 @@ fn test_create_plan_already_exists() { address: Address::generate(&env), allocation_bps: 10000, fiat_anchor_info: String::from_str(&env, "NGN_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; // First creation @@ -220,6 +242,8 @@ fn test_create_plan_already_exists() { &true, &500, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); // Second creation on same owner @@ -232,6 +256,8 @@ fn test_create_plan_already_exists() { &true, &500, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); assert_eq!(result2, Err(Ok(Error::PlanAlreadyExists))); } @@ -256,6 +282,8 @@ fn test_trigger_payout_single_beneficiary() { address: beneficiary.clone(), allocation_bps: 10000, fiat_anchor_info: String::from_str(&env, "USD_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let start = 1_000_000; @@ -270,6 +298,8 @@ fn test_trigger_payout_single_beneficiary() { &true, &500, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); // Deactivate plan @@ -314,16 +344,22 @@ fn test_trigger_payout_multiple_beneficiaries() { address: alice.clone(), allocation_bps: 5000, fiat_anchor_info: String::from_str(&env, "USD_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let bob_bene = Beneficiary { address: bob.clone(), allocation_bps: 3000, fiat_anchor_info: String::from_str(&env, "EUR_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let charlie_bene = Beneficiary { address: charlie.clone(), allocation_bps: 2000, fiat_anchor_info: String::from_str(&env, "GBP_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; env.ledger().set_timestamp(1_000_000); @@ -337,6 +373,8 @@ fn test_trigger_payout_multiple_beneficiaries() { &true, &500, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); client.close_plan(&owner); @@ -376,11 +414,15 @@ fn test_trigger_payout_dust_goes_to_last_beneficiary() { address: a.clone(), allocation_bps: 3333, fiat_anchor_info: String::from_str(&env, ""), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let bene_b = Beneficiary { address: b.clone(), allocation_bps: 6667, fiat_anchor_info: String::from_str(&env, ""), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; env.ledger().set_timestamp(1_000_000); @@ -394,6 +436,8 @@ fn test_trigger_payout_dust_goes_to_last_beneficiary() { &false, &0, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); client.close_plan(&owner); @@ -430,6 +474,8 @@ fn test_trigger_payout_plan_still_active() { address: beneficiary.clone(), allocation_bps: 10000, fiat_anchor_info: String::from_str(&env, ""), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; env.ledger().set_timestamp(1_000_000); @@ -443,6 +489,8 @@ fn test_trigger_payout_plan_still_active() { &false, &0, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); // Plan is still active — close_plan was never called @@ -472,6 +520,8 @@ fn test_trigger_payout_grace_period_not_met() { address: beneficiary.clone(), allocation_bps: 10000, fiat_anchor_info: String::from_str(&env, ""), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; env.ledger().set_timestamp(1_000_000); @@ -485,6 +535,8 @@ fn test_trigger_payout_grace_period_not_met() { &false, &0, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); client.close_plan(&owner); @@ -516,6 +568,8 @@ fn test_trigger_payout_double_payout_prevented() { address: beneficiary.clone(), allocation_bps: 10000, fiat_anchor_info: String::from_str(&env, ""), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; env.ledger().set_timestamp(1_000_000); @@ -529,6 +583,8 @@ fn test_trigger_payout_double_payout_prevented() { &false, &0, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); client.close_plan(&owner); @@ -579,6 +635,8 @@ fn test_cancel_claim_success() { address: beneficiary.clone(), allocation_bps: 10000, fiat_anchor_info: String::from_str(&env, ""), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let start = 1_000_000; @@ -593,6 +651,8 @@ fn test_cancel_claim_success() { &false, &0, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); client.close_plan(&owner); @@ -630,6 +690,8 @@ fn test_reclaim_success() { address: beneficiary.clone(), allocation_bps: 10000, fiat_anchor_info: String::from_str(&env, ""), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let start = 1_000_000; @@ -644,6 +706,8 @@ fn test_reclaim_success() { &false, &0, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); // Owner reclaims before claim From 4ede487308a9a7a50b29291e0ca6b0e4fe7fbc8b Mon Sep 17 00:00:00 2001 From: skylar Date: Tue, 30 Jun 2026 02:24:27 +0000 Subject: [PATCH 2/5] fix: format inheritance-contract lib.rs --- contracts/inheritance-contract/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/contracts/inheritance-contract/src/lib.rs b/contracts/inheritance-contract/src/lib.rs index c3c233a2c..007e871f5 100644 --- a/contracts/inheritance-contract/src/lib.rs +++ b/contracts/inheritance-contract/src/lib.rs @@ -1,7 +1,6 @@ #![no_std] -use soroban_sdk::{contract, contracterror, contractimpl, contracttype, Address, Env, String, Symbol, Vec}; use soroban_sdk::{ - contract, contracterror, contractimpl, contracttype, symbol_short, Address, Env, String, Vec, + contract, contracterror, contractimpl, contracttype, symbol_short, Address, Env, String, Symbol, Vec, }; const MAX_BENEFICIARIES: u32 = 100; @@ -98,7 +97,10 @@ impl InheritanceContract { } fn emit_bridge_payout_event(env: &Env, event: &BridgePayoutEvent) { - let topic = (Symbol::short("BridgePayout"), env.current_contract_address()); + let topic = ( + Symbol::short("BridgePayout"), + env.current_contract_address(), + ); env.events().publish(topic, event); } From a6fc4a5c0af8f85a41cedb73a18d081e7ec59e91 Mon Sep 17 00:00:00 2001 From: skylar Date: Wed, 1 Jul 2026 03:18:05 +0000 Subject: [PATCH 3/5] fix: format import statement in inheritance-contract --- contracts/inheritance-contract/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/inheritance-contract/src/lib.rs b/contracts/inheritance-contract/src/lib.rs index fd5f115c7..97c379593 100644 --- a/contracts/inheritance-contract/src/lib.rs +++ b/contracts/inheritance-contract/src/lib.rs @@ -1,6 +1,7 @@ #![no_std] use soroban_sdk::{ - contract, contracterror, contractimpl, contracttype, symbol_short, Address, Env, String, Symbol, Vec, + contract, contracterror, contractimpl, contracttype, symbol_short, Address, Env, String, + Symbol, Vec, }; const MAX_BENEFICIARIES: u32 = 100; From cd837103d3b5d05fa72a0a063c1016a25314c445 Mon Sep 17 00:00:00 2001 From: skylar Date: Wed, 1 Jul 2026 12:50:03 +0000 Subject: [PATCH 4/5] fix: resolve contract CI build errors --- contracts/inheritance-contract/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/inheritance-contract/src/lib.rs b/contracts/inheritance-contract/src/lib.rs index 97c379593..382ef7909 100644 --- a/contracts/inheritance-contract/src/lib.rs +++ b/contracts/inheritance-contract/src/lib.rs @@ -99,10 +99,10 @@ impl InheritanceContract { fn emit_bridge_payout_event(env: &Env, event: &BridgePayoutEvent) { let topic = ( - Symbol::short("BridgePayout"), + symbol_short!("BridgePayout"), env.current_contract_address(), ); - env.events().publish(topic, event); + env.events().publish(topic, event.clone()); } fn is_stellar_chain(chain: &String, env: &Env) -> bool { @@ -258,7 +258,7 @@ impl InheritanceContract { Ok(()) } - pub fn unregister_supported_wrapped_token( + pub fn unregister_wrapped_token( env: Env, admin: Address, token: Address, From d32a74cafa5b7c1b7e68cf2773978aad982c20d9 Mon Sep 17 00:00:00 2001 From: skylar Date: Thu, 2 Jul 2026 05:15:58 +0000 Subject: [PATCH 5/5] fix: resolve CI compilation errors in inheritance-contract --- contracts/inheritance-contract/src/lib.rs | 20 ++----- contracts/inheritance-contract/src/test.rs | 64 ++++++++++++++++++++++ 2 files changed, 70 insertions(+), 14 deletions(-) diff --git a/contracts/inheritance-contract/src/lib.rs b/contracts/inheritance-contract/src/lib.rs index 382ef7909..98fe9f375 100644 --- a/contracts/inheritance-contract/src/lib.rs +++ b/contracts/inheritance-contract/src/lib.rs @@ -1,7 +1,6 @@ #![no_std] use soroban_sdk::{ - contract, contracterror, contractimpl, contracttype, symbol_short, Address, Env, String, - Symbol, Vec, + contract, contracterror, contractimpl, contracttype, symbol_short, Address, Env, String, Vec, }; const MAX_BENEFICIARIES: u32 = 100; @@ -97,12 +96,9 @@ impl InheritanceContract { .extend_ttl(key, PLAN_TTL_LEEWAY, PLAN_TTL_THRESHOLD); } - fn emit_bridge_payout_event(env: &Env, event: &BridgePayoutEvent) { - let topic = ( - symbol_short!("BridgePayout"), - env.current_contract_address(), - ); - env.events().publish(topic, event.clone()); + fn emit_bridge_payout_event(env: &Env, event: BridgePayoutEvent) { + let topic = (symbol_short!("BridgePay"), env.current_contract_address()); + env.events().publish(topic, event); } fn is_stellar_chain(chain: &String, env: &Env) -> bool { @@ -258,11 +254,7 @@ impl InheritanceContract { Ok(()) } - pub fn unregister_wrapped_token( - env: Env, - admin: Address, - token: Address, - ) -> Result<(), Error> { + pub fn unregister_wrapped_token(env: Env, admin: Address, token: Address) -> Result<(), Error> { admin.require_auth(); Self::require_admin(&env, &admin)?; let key = DataKey::SupportedWrappedToken(token); @@ -453,7 +445,7 @@ impl InheritanceContract { source_chain: plan.source_chain.clone(), source_tx_hash: plan.source_tx_hash.clone(), }; - Self::emit_bridge_payout_event(&env, &event); + Self::emit_bridge_payout_event(&env, event); } } diff --git a/contracts/inheritance-contract/src/test.rs b/contracts/inheritance-contract/src/test.rs index 595a74f9c..68cf68fa1 100644 --- a/contracts/inheritance-contract/src/test.rs +++ b/contracts/inheritance-contract/src/test.rs @@ -102,6 +102,8 @@ fn test_ping_updates_last_ping_and_emits_event() { address: Address::generate(&env), allocation_bps: 10000, fiat_anchor_info: String::from_str(&env, "NGN_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; token_client.mint(&owner, &2000); @@ -118,6 +120,8 @@ fn test_ping_updates_last_ping_and_emits_event() { &true, &500, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); assert_eq!(client.get_plan(&owner).last_ping, start); @@ -162,6 +166,8 @@ fn test_ping_requires_owner_auth() { yield_rate_bps: 0, is_active: true, timelock_duration: 86400, + source_chain: String::from_str(&env, "Stellar"), + source_tx_hash: String::from_str(&env, "SRC_TX_HASH"), }; env.as_contract(&contract_id, || { @@ -844,6 +850,8 @@ fn test_ping_success_from_owner_updates_timestamp() { address: Address::generate(&env), allocation_bps: 10000, fiat_anchor_info: String::from_str(&env, "USD_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; token_client.mint(&owner, &5000); @@ -860,6 +868,8 @@ fn test_ping_success_from_owner_updates_timestamp() { &true, &500, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); // Verify initial ping timestamp @@ -897,6 +907,8 @@ fn test_ping_from_third_party_fails() { address: Address::generate(&env), allocation_bps: 10000, fiat_anchor_info: String::from_str(&env, "USD_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; token_client.mint(&owner, &5000); @@ -913,6 +925,8 @@ fn test_ping_from_third_party_fails() { &true, &500, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); // Try to ping as third party without auth @@ -960,11 +974,15 @@ fn test_close_plan_refunds_all_tokens_and_deletes_storage() { address: beneficiary1, allocation_bps: 5000, fiat_anchor_info: String::from_str(&env, "USD_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let bene2 = Beneficiary { address: beneficiary2, allocation_bps: 5000, fiat_anchor_info: String::from_str(&env, "EUR_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; env.ledger().set_timestamp(1_000_000); @@ -978,6 +996,8 @@ fn test_close_plan_refunds_all_tokens_and_deletes_storage() { &false, &0, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); // Verify tokens are transferred to contract @@ -1013,6 +1033,8 @@ fn test_close_plan_requires_owner_auth() { address: Address::generate(&env), allocation_bps: 10000, fiat_anchor_info: String::from_str(&env, "USD_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; token_client.mint(&owner, &5000); @@ -1028,6 +1050,8 @@ fn test_close_plan_requires_owner_auth() { &false, &0, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); // Try to close plan as unauthorized user @@ -1081,26 +1105,36 @@ fn test_trigger_payout_5_beneficiaries_with_equal_allocations() { address: b1.clone(), allocation_bps: 2000, fiat_anchor_info: String::from_str(&env, "USD_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let bene2 = Beneficiary { address: b2.clone(), allocation_bps: 2000, fiat_anchor_info: String::from_str(&env, "USD_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let bene3 = Beneficiary { address: b3.clone(), allocation_bps: 2000, fiat_anchor_info: String::from_str(&env, "USD_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let bene4 = Beneficiary { address: b4.clone(), allocation_bps: 2000, fiat_anchor_info: String::from_str(&env, "USD_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let bene5 = Beneficiary { address: b5.clone(), allocation_bps: 2000, fiat_anchor_info: String::from_str(&env, "USD_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; env.ledger().set_timestamp(1_000_000); @@ -1114,6 +1148,8 @@ fn test_trigger_payout_5_beneficiaries_with_equal_allocations() { &false, &0, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); // Deactivate, claim, and payout @@ -1167,6 +1203,8 @@ fn test_trigger_payout_10_beneficiaries_unequal_allocations() { address: beneficiary.clone(), allocation_bps: 1000, fiat_anchor_info: String::from_str(&env, "USD_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; bene_array.push_back(b); } @@ -1183,6 +1221,8 @@ fn test_trigger_payout_10_beneficiaries_unequal_allocations() { &false, &0, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); // Deactivate, claim, and payout @@ -1223,16 +1263,22 @@ fn test_trigger_payout_rounding_with_3_beneficiaries() { address: bene1.clone(), allocation_bps: 3333, fiat_anchor_info: String::from_str(&env, "USD_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let b2 = Beneficiary { address: bene2.clone(), allocation_bps: 3333, fiat_anchor_info: String::from_str(&env, "EUR_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let b3 = Beneficiary { address: bene3.clone(), allocation_bps: 3334, fiat_anchor_info: String::from_str(&env, "GBP_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; env.ledger().set_timestamp(1_000_000); @@ -1246,6 +1292,8 @@ fn test_trigger_payout_rounding_with_3_beneficiaries() { &false, &0, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); deactivate_plan_for_testing(&env, &contract_id, &owner); @@ -1285,11 +1333,15 @@ fn test_trigger_payout_after_grace_period_and_timelock_expiry() { address: alice.clone(), allocation_bps: 6000, fiat_anchor_info: String::from_str(&env, "USD_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let bob_bene = Beneficiary { address: bob.clone(), allocation_bps: 4000, fiat_anchor_info: String::from_str(&env, "EUR_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let grace_period = 7200; // 2 hours @@ -1307,6 +1359,8 @@ fn test_trigger_payout_after_grace_period_and_timelock_expiry() { &false, &0, &timelock_duration, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); // Deactivate plan @@ -1358,6 +1412,8 @@ fn test_trigger_payout_with_single_beneficiary_receives_all() { address: sole_beneficiary.clone(), allocation_bps: 10000, // 100% fiat_anchor_info: String::from_str(&env, "USD_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let plan_amount = 55555; @@ -1372,6 +1428,8 @@ fn test_trigger_payout_with_single_beneficiary_receives_all() { &false, &0, &86400, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); deactivate_plan_for_testing(&env, &contract_id, &owner); @@ -1413,11 +1471,15 @@ fn test_create_plan_stores_all_fields_with_multiple_beneficiaries() { address: alice.clone(), allocation_bps: 7000, fiat_anchor_info: String::from_str(&env, "USD_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let bob_bene = Beneficiary { address: bob.clone(), allocation_bps: 3000, fiat_anchor_info: String::from_str(&env, "EUR_BANK"), + destination_chain: String::from_str(&env, "Stellar"), + destination_address: String::from_str(&env, "GDESTADDR"), }; let start = 2_000_000u64; @@ -1432,6 +1494,8 @@ fn test_create_plan_stores_all_fields_with_multiple_beneficiaries() { &true, &300, &172800, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "SRC_TX_HASH"), ); // Tokens are transferred: owner balance reduced, contract holds the amount