mod helpers; use helpers::*; use near_sdk::json_types::U128; /// Test 1: Owner updates single asset share price #[tokio::test] async fn test_kernel_updates_single_asset_rate() -> TestResult<()> { println!("\nTest: Owner Updates Single Asset Rate"); let ctx = setup_minimal_vault().await?; let vault = ctx.vault(); let asset = ctx.base_asset.clone(); let new_rate = U128(RATE_2_5); // 2.5 with 8 decimals // Update share price via owner let rates = vec![(asset.clone(), new_rate)]; owner_update_vault_share_prices(&vault, &ctx.owner, rates).await?; println!(" Owner updated rate to: {}", new_rate.0); // Verify rate updated in vault let actual_rate = vault_get_share_price(&vault, &asset).await?; assert_eq!( actual_rate.0, new_rate.0, "Share price should be updated to {}", new_rate.0 ); println!("Test passed: Single asset rate update works"); Ok(()) } /// Test 2: Owner updates multiple asset rates atomically #[tokio::test] async fn test_kernel_updates_multiple_asset_rates() -> TestResult<()> { println!("\nTest: Owner Updates Multiple Asset Rates"); let ctx = setup_multi_asset_integration().await?; let vault = ctx.vault(); // Prepare rate updates for all accepted assets let mut rate_updates = Vec::new(); for (i, asset) in ctx.accepted_assets.iter().enumerate() { let rate = U128((i as u128 + 1) * SCALE_8); // 1.0, 2.0, 3.0, etc. with 8 decimals rate_updates.push((asset.clone(), rate)); } println!(" Updating {} share prices", rate_updates.len()); // Update all rates atomically owner_update_vault_share_prices(&vault, &ctx.owner, rate_updates.clone()).await?; // Verify all rates updated for (asset, expected_rate) in rate_updates { let actual_rate = vault_get_share_price(&vault, &asset).await?; assert_eq!( actual_rate.0, expected_rate.0, "Rate for asset {} should be {}", asset, expected_rate.0 ); } println!("Test passed: Multiple asset rates updated atomically"); Ok(()) } /// Test 3: Deposits use current share price #[tokio::test] async fn test_deposit_uses_current_rate() -> TestResult<()> { println!("\nTest: Deposit Uses Current Rate"); let ctx = setup_full_integration_test(2, false, 1).await?; let vault = ctx.vault(); let user = &ctx.users[0]; let ft_contract = ctx.mock_ft_primary.as_ref().expect("FT should exist"); let ft_asset = ctx.accepted_assets.first().unwrap().clone(); // Set initial rate R1 let rate1 = U128(SCALE_8); // 1.0 with 8 decimals let rates1 = vec![(ft_asset.clone(), rate1)]; owner_update_vault_share_prices(&vault, &ctx.owner, rates1).await?; // Deposit at R1 let deposit_amount = U128(10 * SCALE_6); vault_deposit_ft(ft_contract, &vault, user, deposit_amount, false, U128(1)).await?; let shares_after_r1 = vault_get_share_balance(&vault, user.id()).await?; println!(" Shares at rate {}: {}", rate1.0, shares_after_r1.0); // Update rate to R2 (lower rate = more shares per asset) let rate2 = U128(RATE_0_5); // 0.5 with 8 decimals let rates2 = vec![(ft_asset.clone(), rate2)]; owner_update_vault_share_prices(&vault, &ctx.owner, rates2).await?; // Deposit same amount at R2 vault_deposit_ft(ft_contract, &vault, user, deposit_amount, false, U128(1)).await?; let shares_after_r2 = vault_get_share_balance(&vault, user.id()).await?; let new_shares = shares_after_r2.0 - shares_after_r1.0; println!(" New shares at rate {}: {}", rate2.0, new_shares); // Lower rate (0.5) means more shares per asset - should get approximately double the shares assert!( new_shares > shares_after_r1.0, "Second deposit at lower rate should yield more shares" ); println!("Test passed: Deposits use current share price"); Ok(()) } /// Test 4: Redeems use current share price #[tokio::test] async fn test_redeem_uses_current_rate() -> TestResult<()> { println!("\nTest: Redeem Uses Current Rate"); let ctx = setup_full_integration_test(2, false, 1).await?; let vault = ctx.vault(); let user = &ctx.users[0]; let ft_contract = ctx.mock_ft_primary.as_ref().expect("FT should exist"); let ft_asset = ctx.accepted_assets.first().unwrap().clone(); // Set initial rate and deposit let rate1 = U128(SCALE_8); // 1.0 with 8 decimals let rates1 = vec![(ft_asset.clone(), rate1)]; owner_update_vault_share_prices(&vault, &ctx.owner, rates1).await?; vault_deposit_ft(ft_contract, &vault, user, U128(SCALE_8), false, U128(1)).await?; let total_shares = vault_get_share_balance(&vault, user.id()).await?; // Redeem half at R1 let redeem_shares = U128(total_shares.0 / 2); let assets_at_r1 = vault_redeem(&vault, user, &ft_asset, redeem_shares, U128(1)).await?; println!(" Assets redeemed at rate {}: {}", rate1.0, assets_at_r1.0); // Update rate to R2 (low rate = less assets per share) let rate2 = U128(RATE_0_5); // 0.5 with 8 decimals - means each share is worth less assets let rates2 = vec![(ft_asset.clone(), rate2)]; owner_update_vault_share_prices(&vault, &ctx.owner, rates2).await?; // Redeem remaining shares at R2 let remaining_shares = vault_get_share_balance(&vault, user.id()).await?; let assets_at_r2 = vault_redeem(&vault, user, &ft_asset, remaining_shares, U128(1)).await?; println!(" Assets redeemed at rate {}: {}", rate2.0, assets_at_r2.0); // At lower rate (0.5), same number of shares should yield less assets // Note: We're redeeming remaining_shares which equals shares_after_r1 (half of total) assert!( assets_at_r2.0 < assets_at_r1.0, "Redeeming same shares at lower rate should yield less assets" ); println!("Test passed: Redeems use current share price"); Ok(()) } /// Test 5: Conversion functions accuracy #[tokio::test] async fn test_conversion_accuracy() -> TestResult<()> { println!("\nTest: Conversion Functions Accuracy"); let ctx = setup_minimal_vault().await?; let vault = ctx.vault(); let asset = ctx.base_asset.clone(); let rate = U128(RATE_1_5); // 1.5 with 8 decimals // Set share price let rates = vec![(asset.clone(), rate)]; owner_update_vault_share_prices(&vault, &ctx.owner, rates).await?; // Test convert_to_shares let assets = U128(10 * SCALE_6); // 10 assets let shares = vault_convert_to_shares(&vault, &asset, assets).await?; println!( " {} assets → {} shares (rate: {})", assets.0, shares.0, rate.0 ); // Test convert_to_asset_amount let assets_back = vault_convert_to_asset_amount(&vault, &asset, shares).await?; println!(" {} shares → {} assets", shares.0, assets_back.0); // Round-trip should be approximately equal (accounting for rounding) let diff = if assets.0 > assets_back.0 { assets.0 - assets_back.0 } else { assets_back.0 - assets.0 }; assert!( diff < 1000, // Allow small rounding difference "Round-trip conversion should be accurate within rounding tolerance" ); println!("Test passed: Conversion functions are mathematically accurate"); Ok(()) } /// Test 6: Setting share price to zero is rejected #[tokio::test] async fn test_rate_zero_rejected() -> TestResult<()> { println!("\nTest: Rate Zero Rejected"); let ctx = setup_minimal_vault().await?; let vault = ctx.vault(); let asset = ctx.base_asset.clone(); let zero_rate = U128(0); // Attempt to set rate to zero let rates = vec![(asset.clone(), zero_rate)]; let result = owner_update_vault_share_prices(&vault, &ctx.owner, rates).await; // Should fail assert!( result.is_err(), "Setting share price to zero should be rejected" ); println!("Test passed: Zero share price correctly rejected"); Ok(()) } /// Test 7: Setting rate for unaccepted asset fails #[tokio::test] async fn test_rate_for_unaccepted_asset_fails() -> TestResult<()> { println!("\nTest: Rate for Unaccepted Asset Fails"); let ctx = setup_minimal_vault().await?; let vault = ctx.vault(); // Create a fake asset not accepted by the vault let fake_asset = dew_finance_contracts::Asset::FungibleToken { contract_id: "fake-token.near".parse().unwrap(), }; let rate = U128(100_000_000); // 1.0 with 8 decimals // Attempt to set rate for unaccepted asset let rates = vec![(fake_asset, rate)]; let result = owner_update_vault_share_prices(&vault, &ctx.owner, rates).await; // Should fail assert!( result.is_err(), "Setting rate for unaccepted asset should fail" ); println!("Test passed: Unaccepted asset rate correctly rejected"); Ok(()) }