diff --git a/Contract/Cargo.toml b/Contract/Cargo.toml index a5a1fea..5a33c7a 100644 --- a/Contract/Cargo.toml +++ b/Contract/Cargo.toml @@ -3,8 +3,6 @@ resolver = "2" members = [ "vault", "streaks", - "lending", - "borrowing", "rewards", "shared", ] @@ -16,6 +14,9 @@ edition = "2021" [workspace.dependencies] soroban-sdk = "21.7.6" shared = { path = "shared" } +vault = { path = "vault" } +streaks = { path = "streaks" } +rewards = { path = "rewards" } # soroban-env-host 21.x requires ed25519-dalek >=2.0.0; v3.0.0 pulls in # rand_core 0.10 which is incompatible with soroban's rand 0.8 test utils. ed25519-dalek = ">=2.0.0, <3" diff --git a/Contract/tests/lending_integration.rs b/Contract/tests/lending_integration.rs deleted file mode 100644 index 964f2c0..0000000 --- a/Contract/tests/lending_integration.rs +++ /dev/null @@ -1,198 +0,0 @@ -use soroban_sdk::{Address, BytesN, Env}; -use lending::LendingContract; -use borrowing::BorrowingContract; -use shared::errors::Error; - -#[test] -fn test_lending_borrowing_integration() { - let env = Env::default(); - - let lending_contract_id = env.register_contract(None, LendingContract); - let borrowing_contract_id = env.register_contract(None, BorrowingContract); - - let pool_id = BytesN::from_array(&[1u8; 32]); - let asset = BytesN::from_array(&[2u8; 32]); - let admin = Address::generate(&env); - let lender = Address::generate(&env); - let borrower = Address::generate(&env); - - // Create lending pool - LendingContract::create_pool( - env.clone(), - pool_id.clone(), - asset.clone(), - admin.clone(), - 500i128, - 1000i128, - ).unwrap(); - - // Configure collateral for borrowing - BorrowingContract::configure_collateral( - env.clone(), - asset.clone(), - 8000i128, // 80% liquidation threshold - 7500i128, // 75% LTV - 11000i128, // 110% safety factor - ).unwrap(); - - // Lender deposits - LendingContract::deposit(env.clone(), pool_id.clone(), lender.clone(), 10000i128).unwrap(); - - // Borrower borrows against collateral - let loan_id = BytesN::from_array(&[3u8; 32]); - BorrowingContract::borrow( - env.clone(), - loan_id.clone(), - borrower.clone(), - asset.clone(), - 8000i128, // collateral - asset.clone(), - 5000i128, // borrow amount - ).unwrap(); - - // Update debt in lending pool - LendingContract::update_debt(env.clone(), pool_id.clone(), 5000i128).unwrap(); - - // Verify pool state - let accounting = LendingContract::get_pool_accounting(env.clone(), pool_id.clone()).unwrap(); - assert_eq!(accounting.total_assets, 10000); - assert_eq!(accounting.outstanding_debt, 5000); - assert_eq!(accounting.available_liquidity, 5000); - - // Verify loan state - let loan = BorrowingContract::get_loan(env.clone(), loan_id.clone()).unwrap(); - assert_eq!(loan.borrow_amount, 5000); - assert_eq!(loan.collateral_amount, 8000); - - // Borrower repays - BorrowingContract::repay(env.clone(), loan_id.clone(), borrower.clone(), 2000i128).unwrap(); - - // Update debt in lending pool - LendingContract::update_debt(env.clone(), pool_id.clone(), -2000i128).unwrap(); - - // Verify updated state - let accounting = LendingContract::get_pool_accounting(env.clone(), pool_id.clone()).unwrap(); - assert_eq!(accounting.outstanding_debt, 3000); - assert_eq!(accounting.available_liquidity, 7000); -} - -#[test] -fn test_emergency_stop_integration() { - let env = Env::default(); - - let lending_contract_id = env.register_contract(None, LendingContract); - - let pool_id = BytesN::from_array(&[1u8; 32]); - let asset = BytesN::from_array(&[2u8; 32]); - let admin = Address::generate(&env); - let user = Address::generate(&env); - - // Create pool and grant admin - LendingContract::create_pool( - env.clone(), - pool_id.clone(), - asset.clone(), - admin.clone(), - 500i128, - 1000i128, - ).unwrap(); - - LendingContract::grant_admin(env.clone(), admin.clone()).unwrap(); - - // Trigger emergency stop - LendingContract::trigger_emergency_stop( - env.clone(), - pool_id.clone(), - admin.clone(), - BytesN::from_array(&[0u8; 32]), - ).unwrap(); - - // Verify operations are blocked - let result = LendingContract::deposit(env.clone(), pool_id.clone(), user.clone(), 1000i128); - assert_eq!(result, Err(Error::EmergencyStopActive)); - - // Lift emergency stop - LendingContract::lift_emergency_stop(env.clone(), pool_id.clone(), admin.clone()).unwrap(); - - // Operations should work again - LendingContract::deposit(env.clone(), pool_id.clone(), user.clone(), 1000i128).unwrap(); -} - -#[test] -fn test_rate_limiting_integration() { - let env = Env::default(); - - let lending_contract_id = env.register_contract(None, LendingContract); - - let pool_id = BytesN::from_array(&[1u8; 32]); - let asset = BytesN::from_array(&[2u8; 32]); - let admin = Address::generate(&env); - let user = Address::generate(&env); - - // Create pool - LendingContract::create_pool( - env.clone(), - pool_id.clone(), - asset.clone(), - admin.clone(), - 500i128, - 1000i128, - ).unwrap(); - - // Set strict rate limit - LendingContract::set_rate_limit(env.clone(), pool_id.clone(), 5, 60).unwrap(); - - // Make multiple deposits - for i in 0..5 { - LendingContract::deposit(env.clone(), pool_id.clone(), user.clone(), 100i128).unwrap(); - } - - // Next deposit should fail due to rate limit - let result = LendingContract::deposit(env.clone(), pool_id.clone(), user.clone(), 100i128); - assert_eq!(result, Err(Error::RateLimitExceeded)); -} - -#[test] -fn test_multi_contract_user_flow() { - let env = Env::default(); - - let lending_contract_id = env.register_contract(None, LendingContract); - let vault_contract_id = env.register_contract(None, vault::VaultContract); - - let pool_id = BytesN::from_array(&[1u8; 32]); - let asset = BytesN::from_array(&[2u8; 32]); - let admin = Address::generate(&env); - let user = Address::generate(&env); - - // Create lending pool - LendingContract::create_pool( - env.clone(), - pool_id.clone(), - asset.clone(), - admin.clone(), - 500i128, - 1000i128, - ).unwrap(); - - // User deposits to lending pool - LendingContract::deposit(env.clone(), pool_id.clone(), user.clone(), 5000i128).unwrap(); - - // User also creates a vault - let vault_id = vault::VaultContract::create_vault( - env.clone(), - user.clone(), - asset.clone(), - None, - 86400, // 1 day lock - ).unwrap(); - - // User deposits to vault - vault::VaultContract::deposit(env.clone(), vault_id.clone(), user.clone(), 3000i128).unwrap(); - - // Verify both positions - let pool_balance = LendingContract::get_pool_balance(env.clone(), pool_id.clone()).unwrap(); - assert_eq!(pool_balance, 5000); - - let vault_balance = vault::VaultContract::get_balance(env.clone(), vault_id.clone()).unwrap(); - assert_eq!(vault_balance, 3000); -} diff --git a/Contract/tests/progression_integration.rs b/Contract/tests/progression_integration.rs deleted file mode 100644 index 13a82c4..0000000 --- a/Contract/tests/progression_integration.rs +++ /dev/null @@ -1,175 +0,0 @@ -#![cfg(test)] -extern crate std; - -use soroban_sdk::{ - testutils::{Address as _, Ledger}, - Address, BytesN, Env, -}; -use streaks::StreaksContractClient; -use rewards::RewardsContractClient; -use vault::VaultContractClient; - -#[test] -fn test_full_progression_flow() { - let env = Env::default(); - env.budget().reset_unlimited(); - - // Create test accounts - let admin = Address::generate(&env); - let user = Address::generate(&env); - let token_issuer = Address::generate(&env); - - // Deploy all contracts - let streaks_id = env.register_contract(None, streaks::StreaksContract); - let rewards_id = env.register_contract(None, rewards::RewardsContract); - let vault_id = env.register_contract(None, vault::VaultContract); - - // Create clients - let mut streaks = StreaksContractClient::new(&env, &streaks_id); - let mut rewards = RewardsContractClient::new(&env, &rewards_id); - let mut vault = VaultContractClient::new(&env, &vault_id); - - // Initialize all contracts - streaks.initialize(&vault_id); // Vault is the only authorized caller for streaks - let reward_asset: BytesN<32> = BytesN::from_array(&env, &[0u8; 32]); - rewards.initialize(&admin, &reward_asset, &streaks_id); - vault.initialize(&streaks_id, &rewards_id); - - // Register vault with streaks to add it as authorized caller - vault.register_with_streaks(); - - // Fund the rewards pool as admin - rewards.require_auth_for_args(&admin, &()).fund_rewards_pool(&10000_0000000); - - // Create a vault for the user - let asset_code: BytesN<32> = BytesN::from_array(&env, &[1u8; 32]); - let vault_id_val = vault.create_vault( - &user, - &asset_code, - Some(token_issuer), - 86400, // 1 day lock - ); - - // DAY 1: First deposit creates streak - env.ledger().set_timestamp(1704067200); // 2024-01-01 00:00:00 UTC - vault.deposit(&vault_id_val, &user, &100); - - // Verify streak is 1 - let streak = streaks.get_streak(&user); - assert_eq!(streak, 1); - - // DAY 2: Second consecutive deposit increments streak to 2 - env.ledger().set_timestamp(1704153600); // 2024-01-02 00:00:00 UTC - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - vault.deposit(&vault_id_val, &user, &100); - })); - assert!(result.is_ok(), "Second deposit should succeed"); - - let streak = streaks.get_streak(&user); - assert_eq!(streak, 2); - - // Try to deposit again same day - should fail with duplicate activity - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - vault.deposit(&vault_id_val, &user, &100); - })); - assert!(result.is_err(), "Same-day duplicate deposit should fail"); - - // Continue depositing daily to reach 7-day milestone - for day in 3..=7 { - let timestamp = 1704067200 + (day as u64 - 1) * 86400; - env.ledger().set_timestamp(timestamp); - vault.deposit(&vault_id_val, &user, &100); - } - - // After day 7, streak should be 7 - let streak = streaks.get_streak(&user); - assert_eq!(streak, 7); - - // Check that pending rewards include the 7-day milestone - let pending = rewards.get_pending_rewards(&user); - assert_eq!(pending, 10_0000000); // 10 tokens from 7-day milestone - - // Claim the rewards - let claimed = rewards.claim_rewards(&user); - assert_eq!(claimed, 10_0000000); - - // Pending should now be 0 - let pending = rewards.get_pending_rewards(&user); - assert_eq!(pending, 0); - - // Test freeze usage - // Miss one day, use a freeze - env.ledger().set_timestamp(1704067200 + 9 * 86400); // Skip day 8, go to day 9 - let user_streak = streaks.get_user_streak(&user); - assert_eq!(user_streak.available_freezes, 3); // Started with 3 - - vault.deposit(&vault_id_val, &user, &100); - let user_streak = streaks.get_user_streak(&user); - assert_eq!(user_streak.available_freezes, 2); // Used one freeze - assert_eq!(user_streak.current_streak, 8); // Streak continued - - // Miss two days - streak resets - env.ledger().set_timestamp(1704067200 + 12 * 86400); // Skip 2 full days - vault.deposit(&vault_id_val, &user, &100); - let streak = streaks.get_streak(&user); - assert_eq!(streak, 1); // Streak reset to 1 -} - -#[test] -#[should_panic(expected = "Unauthorized")] -fn test_unauthorized_streaks_caller() { - let env = Env::default(); - let streaks_id = env.register_contract(None, streaks::StreaksContract); - let mut streaks = StreaksContractClient::new(&env, &streaks_id); - - let vault_id = Address::generate(&env); - streaks.initialize(&vault_id); - - // Try to call update_streak from unauthorized address - let user = Address::generate(&env); - streaks.update_streak(&user); // Should panic -} - -#[test] -#[should_panic(expected = "RewardAlreadyClaimed")] -fn test_double_claim_prevention() { - let env = Env::default(); - env.budget().reset_unlimited(); - - let admin = Address::generate(&env); - let user = Address::generate(&env); - - let streaks_id = env.register_contract(None, streaks::StreaksContract); - let rewards_id = env.register_contract(None, rewards::RewardsContract); - let vault_id = env.register_contract(None, vault::VaultContract); - - let mut streaks = StreaksContractClient::new(&env, &streaks_id); - let mut rewards = RewardsContractClient::new(&env, &rewards_id); - let mut vault = VaultContractClient::new(&env, &vault_id); - - // Initialize - streaks.initialize(&vault_id); - let reward_asset: BytesN<32> = BytesN::from_array(&env, &[0u8; 32]); - rewards.initialize(&admin, &reward_asset, &streaks_id); - vault.initialize(&streaks_id, &rewards_id); - vault.register_with_streaks(); - rewards.fund_rewards_pool(&10000_0000000); - - // Create user vault - let asset_code: BytesN<32> = BytesN::from_array(&env, &[1u8; 32]); - let token_issuer = Address::generate(&env); - let vault_id_val = vault.create_vault(&user, &asset_code, Some(token_issuer), 86400); - - // Reach 7-day milestone - for day in 1..=7 { - env.ledger().set_timestamp(1704067200 + (day as u64 - 1) * 86400); - vault.deposit(&vault_id_val, &user, &100); - } - - // Claim first time succeeds - let claimed = rewards.claim_rewards(&user); - assert_eq!(claimed, 10_0000000); - - // Claim second time should panic - rewards.claim_rewards(&user); -} \ No newline at end of file diff --git a/Contract/vault/Cargo.toml b/Contract/vault/Cargo.toml index 8509c18..20e9d29 100644 --- a/Contract/vault/Cargo.toml +++ b/Contract/vault/Cargo.toml @@ -9,3 +9,5 @@ shared.workspace = true [dev-dependencies] soroban-sdk = { workspace = true, features = ["testutils"] } +streaks = { path = "../streaks" } +rewards = { path = "../rewards" } diff --git a/Contract/vault/tests/progression_integration.rs b/Contract/vault/tests/progression_integration.rs new file mode 100644 index 0000000..520fdd8 --- /dev/null +++ b/Contract/vault/tests/progression_integration.rs @@ -0,0 +1,355 @@ +#![cfg(test)] +extern crate std; + +use soroban_sdk::{ + testutils::{Address as _, Ledger}, + Address, BytesN, Env, +}; +use streaks::StreaksContractClient; +use rewards::RewardsContractClient; +use vault::VaultContractClient; + +fn setup_integration_test() -> (Env, Address, Address, VaultContractClient, StreaksContractClient, RewardsContractClient) { + let env = Env::default(); + env.budget().reset_unlimited(); + + // Create test accounts + let admin = Address::generate(&env); + let user = Address::generate(&env); + + // Deploy all contracts + let streaks_id = env.register_contract(None, streaks::StreaksContract); + let rewards_id = env.register_contract(None, rewards::RewardsContract); + let vault_id = env.register_contract(None, vault::VaultContract); + + // Create clients + let mut streaks = StreaksContractClient::new(&env, &streaks_id); + let mut rewards = RewardsContractClient::new(&env, &rewards_id); + let vault = VaultContractClient::new(&env, &vault_id); + + // Initialize all contracts in correct order + streaks.initialize(&vault_id); + + let reward_asset: BytesN<32> = BytesN::from_array(&env, &[0u8; 32]); + rewards.initialize(&admin, &reward_asset, &streaks_id); + + vault.initialize(&streaks_id, &rewards_id); + + // Register vault with streaks to add it as authorized caller + vault.register_with_streaks(); + + // Fund the rewards pool as admin + rewards.require_auth_for_args(&admin, &()).fund_rewards_pool(&10000_0000000); + + (env, admin, user, vault, streaks, rewards) +} + +#[test] +fn test_full_progression_flow() { + let (env, _admin, user, vault, streaks, rewards) = setup_integration_test(); + + // Create a vault for the user + let asset_code: BytesN<32> = BytesN::from_array(&env, &[1u8; 32]); + let token_issuer = Address::generate(&env); + let vault_id_val = vault.create_vault( + &user, + &asset_code, + Some(token_issuer), + 86400, // 1 day lock + ); + + // DAY 1: First deposit creates streak + env.ledger().set_timestamp(1704067200); // 2024-01-01 00:00:00 UTC + vault.deposit(&vault_id_val, &user, &100); + + // Verify streak is 1 + let streak = streaks.get_streak(&user); + assert_eq!(streak, 1); + + // DAY 2: Second consecutive deposit increments streak to 2 + env.ledger().set_timestamp(1704153600); // 2024-01-02 00:00:00 UTC + vault.deposit(&vault_id_val, &user, &100); + + let streak = streaks.get_streak(&user); + assert_eq!(streak, 2); + + // Continue depositing daily to reach 7-day milestone + for day in 3..=7 { + let timestamp = 1704067200 + (day as u64 - 1) * 86400; + env.ledger().set_timestamp(timestamp); + vault.deposit(&vault_id_val, &user, &100); + } + + // After day 7, streak should be 7 + let streak = streaks.get_streak(&user); + assert_eq!(streak, 7); + + // Check that pending rewards include the 7-day milestone + let pending = rewards.get_pending_rewards(&user); + assert_eq!(pending, 10_0000000); // 10 tokens from 7-day milestone + + // Claim the rewards + let claimed = rewards.claim_rewards(&user); + assert_eq!(claimed, 10_0000000); + + // Pending should now be 0 + let pending = rewards.get_pending_rewards(&user); + assert_eq!(pending, 0); + + // Test freeze usage + // Miss one day, use a freeze + env.ledger().set_timestamp(1704067200 + 9 * 86400); // Skip day 8, go to day 9 + let user_streak = streaks.get_user_streak(&user); + assert_eq!(user_streak.available_freezes, 3); // Started with 3 + + vault.deposit(&vault_id_val, &user, &100); + let user_streak = streaks.get_user_streak(&user); + assert_eq!(user_streak.available_freezes, 2); // Used one freeze + assert_eq!(user_streak.current_streak, 8); // Streak continued + + // Miss two days - streak resets + env.ledger().set_timestamp(1704067200 + 12 * 86400); // Skip 2 full days + vault.deposit(&vault_id_val, &user, &100); + let streak = streaks.get_streak(&user); + assert_eq!(streak, 1); // Streak reset to 1 +} + +#[test] +#[should_panic(expected = "Unauthorized")] +fn test_unauthorized_streaks_caller() { + let env = Env::default(); + let streaks_id = env.register_contract(None, streaks::StreaksContract); + let mut streaks = StreaksContractClient::new(&env, &streaks_id); + + let vault_id = Address::generate(&env); + streaks.initialize(&vault_id); + + // Try to call update_streak from unauthorized address + let user = Address::generate(&env); + streaks.update_streak(&user); // Should panic +} + +#[test] +#[should_panic(expected = "RewardAlreadyClaimed")] +fn test_double_claim_prevention() { + let (env, _admin, user, vault, streaks, rewards) = setup_integration_test(); + + // Create user vault + let asset_code: BytesN<32> = BytesN::from_array(&env, &[1u8; 32]); + let token_issuer = Address::generate(&env); + let vault_id_val = vault.create_vault(&user, &asset_code, Some(token_issuer), 86400); + + // Reach 7-day milestone + for day in 1..=7 { + env.ledger().set_timestamp(1704067200 + (day as u64 - 1) * 86400); + vault.deposit(&vault_id_val, &user, &100); + } + + // Claim first time succeeds + let claimed = rewards.claim_rewards(&user); + assert_eq!(claimed, 10_0000000); + + // Claim second time should panic + rewards.claim_rewards(&user); +} + +#[test] +fn test_qualifying_deposit_updates_streak_once() { + let (env, _admin, user, vault, streaks, _rewards) = setup_integration_test(); + + // Create a vault for the user + let asset_code: BytesN<32> = BytesN::from_array(&env, &[1u8; 32]); + let token_issuer = Address::generate(&env); + let vault_id_val = vault.create_vault( + &user, + &asset_code, + Some(token_issuer), + 86400, // 1 day lock + ); + + // Set timestamp to day 1 + env.ledger().set_timestamp(1704067200); // 2024-01-01 00:00:00 UTC + + // Make first deposit + vault.deposit(&vault_id_val, &user, &100); + + // Verify streak is 1 + let streak = streaks.get_streak(&user); + assert_eq!(streak, 1, "First deposit should create streak of 1"); + + // Verify vault balance is correct + let balance = vault.get_balance(&vault_id_val); + assert_eq!(balance, 100, "Vault balance should be 100"); +} + +#[test] +fn test_same_day_second_deposit_does_not_add_streak() { + let (env, _admin, user, vault, streaks, _rewards) = setup_integration_test(); + + // Create a vault for the user + let asset_code: BytesN<32> = BytesN::from_array(&env, &[1u8; 32]); + let token_issuer = Address::generate(&env); + let vault_id_val = vault.create_vault( + &user, + &asset_code, + Some(token_issuer), + 86400, // 1 day lock + ); + + // Set timestamp to day 1 + env.ledger().set_timestamp(1704067200); // 2024-01-01 00:00:00 UTC + + // Make first deposit + vault.deposit(&vault_id_val, &user, &100); + + // Verify streak is 1 + let streak = streaks.get_streak(&user); + assert_eq!(streak, 1); + + // Try second deposit same day - should fail due to duplicate activity + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + vault.deposit(&vault_id_val, &user, &50); + })); + + assert!(result.is_err(), "Same-day second deposit should fail with duplicate activity error"); + + // Verify streak is still 1 (not incremented) + let streak = streaks.get_streak(&user); + assert_eq!(streak, 1, "Streak should not increment on same-day duplicate deposit"); + + // Verify vault balance is still 100 (second deposit should not have gone through) + let balance = vault.get_balance(&vault_id_val); + assert_eq!(balance, 100, "Vault balance should remain unchanged after failed duplicate deposit"); +} + +#[test] +fn test_milestone_deposit_creates_pending_reward() { + let (env, _admin, user, vault, streaks, rewards) = setup_integration_test(); + + // Create a vault for the user + let asset_code: BytesN<32> = BytesN::from_array(&env, &[1u8; 32]); + let token_issuer = Address::generate(&env); + let vault_id_val = vault.create_vault( + &user, + &asset_code, + Some(token_issuer), + 86400, // 1 day lock + ); + + // Make deposits for 7 consecutive days to reach 7-day milestone + for day in 1..=7 { + let timestamp = 1704067200 + (day as u64 - 1) * 86400; + env.ledger().set_timestamp(timestamp); + vault.deposit(&vault_id_val, &user, &100); + } + + // Verify streak is 7 + let streak = streaks.get_streak(&user); + assert_eq!(streak, 7, "Streak should be 7 after 7 consecutive deposits"); + + // Check that pending rewards include the 7-day milestone (10 tokens) + let pending = rewards.get_pending_rewards(&user); + assert_eq!(pending, 10_0000000, "Pending rewards should be 10 tokens for 7-day milestone"); + + // Verify vault balance is 700 (7 deposits of 100 each) + let balance = vault.get_balance(&vault_id_val); + assert_eq!(balance, 700, "Vault balance should be 700 after 7 deposits"); +} + +#[test] +fn test_failed_streak_call_does_not_corrupt_vault_state() { + let (env, _admin, user, vault, streaks, _rewards) = setup_integration_test(); + + // Create a vault for the user + let asset_code: BytesN<32> = BytesN::from_array(&env, &[1u8; 32]); + let token_issuer = Address::generate(&env); + let vault_id_val = vault.create_vault( + &user, + &asset_code, + Some(token_issuer), + 86400, // 1 day lock + ); + + // Set timestamp to day 1 + env.ledger().set_timestamp(1704067200); + + // Make first deposit - this should succeed + vault.deposit(&vault_id_val, &user, &100); + + // Verify initial state + let initial_balance = vault.get_balance(&vault_id_val); + assert_eq!(initial_balance, 100); + + let initial_streak = streaks.get_streak(&user); + assert_eq!(initial_streak, 1); + + // Try same-day deposit - this will fail at streak level due to duplicate activity + // but should not corrupt vault balance + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + vault.deposit(&vault_id_val, &user, &50); + })); + + assert!(result.is_err(), "Same-day deposit should fail"); + + // Verify vault state is not corrupted + let final_balance = vault.get_balance(&vault_id_val); + assert_eq!(final_balance, 100, "Vault balance should remain unchanged after failed streak call"); + + let final_streak = streaks.get_streak(&user); + assert_eq!(final_streak, 1, "Streak should remain unchanged after failed call"); + + // Verify vault metadata is intact + let vault_metadata = vault.get_vault(&vault_id_val); + assert_eq!(vault_metadata.owner, user); + assert_eq!(vault_metadata.status, 1); // Locked status +} + +#[test] +fn test_failed_reward_call_does_not_corrupt_vault_state() { + let (env, admin, user, vault, streaks, rewards) = setup_integration_test(); + + // Create a vault for the user + let asset_code: BytesN<32> = BytesN::from_array(&env, &[1u8; 32]); + let token_issuer = Address::generate(&env); + let vault_id_val = vault.create_vault( + &user, + &asset_code, + Some(token_issuer), + 86400, // 1 day lock + ); + + // Empty the rewards pool to simulate liquidity failure + rewards.require_auth_for_args(&admin, &()).fund_rewards_pool(&0_0000000); + + // Make deposits for 7 consecutive days + for day in 1..=7 { + let timestamp = 1704067200 + (day as u64 - 1) * 86400; + env.ledger().set_timestamp(timestamp); + + // The 7th day deposit might fail when trying to grant reward due to insufficient liquidity + // but should not corrupt vault state + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + vault.deposit(&vault_id_val, &user, &100); + })); + + // On day 7, the reward grant will fail due to insufficient liquidity + if day == 7 { + assert!(result.is_err(), "Deposit on day 7 should fail due to insufficient reward liquidity"); + } + } + + // Verify vault state is not corrupted despite reward failure + // The vault should still have processed the deposits correctly + let balance = vault.get_balance(&vault_id_val); + // Since day 7 failed, we should have 6 deposits of 100 each + assert_eq!(balance, 600, "Vault balance should reflect successful deposits only"); + + // Verify streak is still correct + let streak = streaks.get_streak(&user); + assert_eq!(streak, 6, "Streak should be 6 after 6 successful deposits"); + + // Verify vault metadata is intact + let vault_metadata = vault.get_vault(&vault_id_val); + assert_eq!(vault_metadata.owner, user); + assert_eq!(vault_metadata.status, 1); // Locked status +} \ No newline at end of file