diff --git a/Cargo.lock b/Cargo.lock index 3f3024b..4c22bef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -843,7 +843,7 @@ dependencies = [ [[package]] name = "dexter-vault" -version = "1.2.0" +version = "1.2.2" dependencies = [ "const_format", "cosmwasm-schema", diff --git a/artifacts/checksums.txt b/artifacts/checksums.txt index ca8de46..caac644 100644 --- a/artifacts/checksums.txt +++ b/artifacts/checksums.txt @@ -5,5 +5,5 @@ c6bb97648dfef5c69d42924d48dc579bb5db0c4fc0514cee1d54ee387a657a05 dexter_lp_toke 520307ff5f915ad232cafb3b3a14747491bef37e3cd1e2b630d9ad0cb915023f dexter_router.wasm 030b85563cefa2f87246cf383498220fa577ae7e5711138a69f6ce6e5839f792 dexter_stable_pool.wasm 3acb65a778fc3f467d29a0e22a5c093b78648b893a18e5b611db25e8c8368587 dexter_superfluid_lp.wasm -036795694b5d5d2ed947c457219f09b9de3db52a86a1f90b1cc2284ee3d6c90c dexter_vault.wasm +96ed1c5d47a49987f88f7584784bde3a24706c4c9bc4834b81147a99bcda9b90 dexter_vault.wasm 899428140866ec5249b9382f8e13f1874394f9bbd1beee9fd5f020b8ba1d82fd dexter_weighted_pool.wasm diff --git a/artifacts/dexter_vault.wasm b/artifacts/dexter_vault.wasm index d879768..15eafca 100644 Binary files a/artifacts/dexter_vault.wasm and b/artifacts/dexter_vault.wasm differ diff --git a/artifacts/old_version_artifacts/dexter_vault_v1.2.1.wasm b/artifacts/old_version_artifacts/dexter_vault_v1.2.1.wasm new file mode 100644 index 0000000..f3efe4c Binary files /dev/null and b/artifacts/old_version_artifacts/dexter_vault_v1.2.1.wasm differ diff --git a/artifacts/old_version_artifacts/dexter_vault_v1.2.2.wasm b/artifacts/old_version_artifacts/dexter_vault_v1.2.2.wasm new file mode 100644 index 0000000..15eafca Binary files /dev/null and b/artifacts/old_version_artifacts/dexter_vault_v1.2.2.wasm differ diff --git a/contracts/vault/Cargo.toml b/contracts/vault/Cargo.toml index ba78c85..4496032 100644 --- a/contracts/vault/Cargo.toml +++ b/contracts/vault/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dexter-vault" -version = "1.2.0" +version = "1.2.2" authors = ["Persistence Labs"] edition = "2021" description = "Dexter Factory contract - entry point to create new pools. Maintains directory for all pools" diff --git a/contracts/vault/src/contract.rs b/contracts/vault/src/contract.rs index f8958a6..c9ee4ce 100644 --- a/contracts/vault/src/contract.rs +++ b/contracts/vault/src/contract.rs @@ -40,8 +40,7 @@ use dexter::pool; const CONTRACT_NAME: &str = "dexter-vault"; /// Contract version that is used for migration. const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -const CONTRACT_VERSION_V1: &str = "1.0.0"; -const CONTRACT_VERSION_V1_1: &str = "1.1.0"; +const CONTRACT_VERSION_V1_2_1: &str = "1.2.1"; /// A `reply` call code ID of sub-message. const INSTANTIATE_LP_REPLY_ID: u64 = 1; @@ -1985,9 +1984,10 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { (pool_id.to_string().as_bytes(), user.as_str()), ); to_json_binary(&is_refunded) - }, + } QueryMsg::RewardScheduleValidationAssets {} => { - let reward_schedule_validation_assets = REWARD_SCHEDULE_VALIDATION_ASSETS.load(deps.storage)?; + let reward_schedule_validation_assets = + REWARD_SCHEDULE_VALIDATION_ASSETS.load(deps.storage)?; to_json_binary(&reward_schedule_validation_assets) } } @@ -2028,15 +2028,23 @@ pub fn query_pools( let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT); let start = start_after.unwrap_or_default().u128() + 1u128; - let mut end = start + Uint128::from(limit).u128(); - if end > config.next_pool_id.u128() { - end = config.next_pool_id.u128(); - } - let mut response: Vec = vec![]; - for pool_id in start..end { - response - .push(ACTIVE_POOLS.load(deps.storage, Uint128::from(pool_id).to_string().as_bytes())?); + let mut collected = 0; + + for pool_id in start..config.next_pool_id.u128() { + if collected >= limit as usize { + break; + } + + // Try to load from ACTIVE_POOLS, skip if not found (could be defunct) + if let Ok(maybe_pool_info) = + ACTIVE_POOLS.may_load(deps.storage, Uint128::from(pool_id).to_string().as_bytes()) + { + if let Some(pool_info) = maybe_pool_info { + response.push(pool_info); + collected += 1; + } + } } Ok(response) @@ -2087,49 +2095,9 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { - // validate contract name - if contract_version.contract != CONTRACT_NAME { - return Err(ContractError::InvalidContractNameForMigration { - expected: CONTRACT_NAME.to_string(), - actual: contract_version.contract, - }); - } + MigrateMsg::V1_2_2 {} => { + // This migration includes the query_pools fix for defunct pools - // validate that current version is v1.0 - if contract_version.version != CONTRACT_VERSION_V1 { - return Err(ContractError::InvalidContractVersionForUpgrade { - upgrade_version: CONTRACT_VERSION.to_string(), - expected: CONTRACT_VERSION_V1.to_string(), - actual: contract_version.version, - }); - } - - // update pool type configs to new values. This makes sure we instantiate new pools with the new configs particularly the - // Code ID for each pool type which has been updated to a new value with the new version of the pool contracts - for pool_type_config in updated_pool_type_configs { - // Check if code id is valid - if pool_type_config.code_id == 0 { - return Err(ContractError::InvalidCodeId {}); - } - // validate fee bps limits - if !pool_type_config.default_fee_info.valid_fee_info() { - return Err(ContractError::InvalidFeeInfo {}); - } - REGISTRY.save( - deps.storage, - pool_type_config.pool_type.to_string(), - &pool_type_config, - )?; - } - - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - } - MigrateMsg::V1_2 { - reward_schedule_validation_assets, - } => { // validate contract name if contract_version.contract != CONTRACT_NAME { return Err(ContractError::InvalidContractNameForMigration { @@ -2138,22 +2106,14 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert!(pools.is_empty(), "Should have no pools initially"); + + // Initialize tokens + let (token1, _token2, _token3) = utils::initialize_3_tokens(&mut app, &owner); + + // Create first pool (pool_id = 1) + let (_pool_addr_1, _lp_token_1, pool_id_1) = utils::initialize_weighted_pool( + &mut app, + &owner, + vault_instance.clone(), + token1.clone(), + _token2.clone(), + _token3.clone(), + "denom1".to_string(), + "denom2".to_string(), + ); + + // Query pools - should return 1 pool + let pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!(pools.len(), 1, "Should have 1 pool"); + assert_eq!(pools[0].pool_id, pool_id_1, "Pool ID should match"); + + // Create second pool (pool_id = 2) + let (_pool_addr_2, _lp_token_2, pool_id_2) = utils::initialize_stable_5_pool_2_asset( + &mut app, + &owner, + vault_instance.clone(), + token1.clone(), + "denom3".to_string(), + ); + + // Query pools - should return 2 pools + let pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!(pools.len(), 2, "Should have 2 pools"); + assert_eq!(pools[0].pool_id, pool_id_1, "First pool ID should match"); + assert_eq!(pools[1].pool_id, pool_id_2, "Second pool ID should match"); +} + +#[test] +fn test_query_pools_with_pagination() { + let owner = Addr::unchecked("owner"); + let mut app = utils::mock_app(owner.clone(), coins(100_000_000_000u128, "uusd")); + let vault_instance = utils::instantiate_contract(&mut app, &owner); + + // Initialize tokens + let (token1, _token2, _token3) = utils::initialize_3_tokens(&mut app, &owner); + + // Create 3 pools + let (_pool_addr_1, _lp_token_1, pool_id_1) = utils::initialize_weighted_pool( + &mut app, + &owner, + vault_instance.clone(), + token1.clone(), + _token2.clone(), + _token3.clone(), + "denom1".to_string(), + "denom2".to_string(), + ); + + let (_pool_addr_2, _lp_token_2, pool_id_2) = utils::initialize_stable_5_pool_2_asset( + &mut app, + &owner, + vault_instance.clone(), + token1.clone(), + "denom3".to_string(), + ); + + let (_pool_addr_3, _lp_token_3, pool_id_3) = utils::initialize_stable_5_pool_2_asset( + &mut app, + &owner, + vault_instance.clone(), + _token2.clone(), + "denom4".to_string(), + ); + + // Test limit = 2 + let pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: None, + limit: Some(2), + }, + ) + .unwrap(); + assert_eq!(pools.len(), 2, "Should return 2 pools with limit=2"); + assert_eq!(pools[0].pool_id, pool_id_1); + assert_eq!(pools[1].pool_id, pool_id_2); + + // Test pagination with start_after + let pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: Some(pool_id_1), + limit: Some(2), + }, + ) + .unwrap(); + assert_eq!(pools.len(), 2, "Should return 2 pools after pool_id_1"); + assert_eq!(pools[0].pool_id, pool_id_2); + assert_eq!(pools[1].pool_id, pool_id_3); + + // Test start_after with remaining pools + let pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: Some(pool_id_2), + limit: None, + }, + ) + .unwrap(); + assert_eq!(pools.len(), 1, "Should return 1 pool after pool_id_2"); + assert_eq!(pools[0].pool_id, pool_id_3); +} + +#[test] +fn test_query_pools_with_defunct_pools() { + let owner = Addr::unchecked("owner"); + let mut app = utils::mock_app(owner.clone(), coins(100_000_000_000u128, "uusd")); + let vault_instance = utils::instantiate_contract(&mut app, &owner); + + // Initialize tokens + let (token1, _token2, _token3) = utils::initialize_3_tokens(&mut app, &owner); + + // Create 4 pools + let (_pool_addr_1, _lp_token_1, pool_id_1) = utils::initialize_weighted_pool( + &mut app, + &owner, + vault_instance.clone(), + token1.clone(), + _token2.clone(), + _token3.clone(), + "denom1".to_string(), + "denom2".to_string(), + ); + + let (_pool_addr_2, _lp_token_2, pool_id_2) = utils::initialize_stable_5_pool_2_asset( + &mut app, + &owner, + vault_instance.clone(), + token1.clone(), + "denom3".to_string(), + ); + + let (_pool_addr_3, _lp_token_3, pool_id_3) = utils::initialize_stable_5_pool_2_asset( + &mut app, + &owner, + vault_instance.clone(), + _token2.clone(), + "denom4".to_string(), + ); + + let (_pool_addr_4, _lp_token_4, pool_id_4) = utils::initialize_stable_5_pool_2_asset( + &mut app, + &owner, + vault_instance.clone(), + _token3.clone(), + "denom5".to_string(), + ); + + // Verify all 4 pools are active + let pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!(pools.len(), 4, "Should have 4 active pools"); + + // Make pool_id_2 defunct + let defunct_msg = ExecuteMsg::DefunctPool { pool_id: pool_id_2 }; + let result = app.execute_contract(owner.clone(), vault_instance.clone(), &defunct_msg, &[]); + assert!(result.is_ok(), "Should successfully make pool defunct"); + + // Query pools again - should return 3 active pools (skipping defunct pool_id_2) + let pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!( + pools.len(), + 3, + "Should have 3 active pools after making one defunct" + ); + + // Verify the returned pools are the correct ones (not including defunct pool_id_2) + let returned_pool_ids: Vec = pools.iter().map(|p| p.pool_id).collect(); + assert!( + returned_pool_ids.contains(&pool_id_1), + "Should include pool_id_1" + ); + assert!( + !returned_pool_ids.contains(&pool_id_2), + "Should NOT include defunct pool_id_2" + ); + assert!( + returned_pool_ids.contains(&pool_id_3), + "Should include pool_id_3" + ); + assert!( + returned_pool_ids.contains(&pool_id_4), + "Should include pool_id_4" + ); + + // Make pool_id_1 defunct as well + let defunct_msg = ExecuteMsg::DefunctPool { pool_id: pool_id_1 }; + let result = app.execute_contract(owner.clone(), vault_instance.clone(), &defunct_msg, &[]); + assert!( + result.is_ok(), + "Should successfully make second pool defunct" + ); + + // Query pools - should return 2 active pools + let pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!( + pools.len(), + 2, + "Should have 2 active pools after making two defunct" + ); + + let returned_pool_ids: Vec = pools.iter().map(|p| p.pool_id).collect(); + assert!( + !returned_pool_ids.contains(&pool_id_1), + "Should NOT include defunct pool_id_1" + ); + assert!( + !returned_pool_ids.contains(&pool_id_2), + "Should NOT include defunct pool_id_2" + ); + assert!( + returned_pool_ids.contains(&pool_id_3), + "Should include pool_id_3" + ); + assert!( + returned_pool_ids.contains(&pool_id_4), + "Should include pool_id_4" + ); +} + +#[test] +fn test_query_pools_with_defunct_pools_and_pagination() { + let owner = Addr::unchecked("owner"); + let mut app = utils::mock_app(owner.clone(), coins(100_000_000_000u128, "uusd")); + let vault_instance = utils::instantiate_contract(&mut app, &owner); + + // Initialize tokens + let (token1, _token2, _token3) = utils::initialize_3_tokens(&mut app, &owner); + + // Create 5 pools + let mut pool_ids = Vec::new(); + for i in 0..5 { + let denom = format!("denom{}", i + 1); + let (_pool_addr, _lp_token, pool_id) = utils::initialize_stable_5_pool_2_asset( + &mut app, + &owner, + vault_instance.clone(), + token1.clone(), + denom, + ); + pool_ids.push(pool_id); + } + + // Make pools 2 and 4 defunct (pool_ids[1] and pool_ids[3]) + for &pool_id in &[pool_ids[1], pool_ids[3]] { + let defunct_msg = ExecuteMsg::DefunctPool { pool_id }; + let result = app.execute_contract(owner.clone(), vault_instance.clone(), &defunct_msg, &[]); + assert!(result.is_ok(), "Should successfully make pool defunct"); + } + + // Test pagination with limit=2 - should return first 2 active pools + let pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: None, + limit: Some(2), + }, + ) + .unwrap(); + assert_eq!(pools.len(), 2, "Should return 2 active pools with limit=2"); + assert_eq!( + pools[0].pool_id, pool_ids[0], + "First pool should be pool_ids[0]" + ); + assert_eq!( + pools[1].pool_id, pool_ids[2], + "Second pool should be pool_ids[2] (skipping defunct pool_ids[1])" + ); + + // Test pagination starting after first pool - should return remaining active pools + let pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: Some(pool_ids[0]), + limit: Some(2), + }, + ) + .unwrap(); + assert_eq!( + pools.len(), + 2, + "Should return 2 active pools after pool_ids[0]" + ); + assert_eq!( + pools[0].pool_id, pool_ids[2], + "First pool should be pool_ids[2] (skipping defunct pool_ids[1])" + ); + assert_eq!( + pools[1].pool_id, pool_ids[4], + "Second pool should be pool_ids[4] (skipping defunct pool_ids[3])" + ); + + // Test that we get all remaining active pools + let all_pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!(all_pools.len(), 3, "Should have 3 active pools total"); + + let active_pool_ids: Vec = all_pools.iter().map(|p| p.pool_id).collect(); + assert_eq!( + active_pool_ids, + vec![pool_ids[0], pool_ids[2], pool_ids[4]], + "Should return only active pools in order" + ); +} + +#[test] +fn test_query_pools_all_pools_defunct() { + let owner = Addr::unchecked("owner"); + let mut app = utils::mock_app(owner.clone(), coins(100_000_000_000u128, "uusd")); + let vault_instance = utils::instantiate_contract(&mut app, &owner); + + // Initialize tokens + let (token1, _token2, _token3) = utils::initialize_3_tokens(&mut app, &owner); + + // Create 2 pools + let (_pool_addr_1, _lp_token_1, pool_id_1) = utils::initialize_weighted_pool( + &mut app, + &owner, + vault_instance.clone(), + token1.clone(), + _token2.clone(), + _token3.clone(), + "denom1".to_string(), + "denom2".to_string(), + ); + + let (_pool_addr_2, _lp_token_2, pool_id_2) = utils::initialize_stable_5_pool_2_asset( + &mut app, + &owner, + vault_instance.clone(), + token1.clone(), + "denom3".to_string(), + ); + + // Make both pools defunct + for &pool_id in &[pool_id_1, pool_id_2] { + let defunct_msg = ExecuteMsg::DefunctPool { pool_id }; + let result = app.execute_contract(owner.clone(), vault_instance.clone(), &defunct_msg, &[]); + assert!(result.is_ok(), "Should successfully make pool defunct"); + } + + // Query pools - should return empty list + let pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert!( + pools.is_empty(), + "Should return empty list when all pools are defunct" + ); + + // Test with pagination parameters - should still return empty + let pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: Some(Uint128::zero()), + limit: Some(10), + }, + ) + .unwrap(); + assert!( + pools.is_empty(), + "Should return empty list with pagination when all pools are defunct" + ); +} + +#[test] +fn test_query_pools_edge_cases() { + let owner = Addr::unchecked("owner"); + let mut app = utils::mock_app(owner.clone(), coins(100_000_000_000u128, "uusd")); + let vault_instance = utils::instantiate_contract(&mut app, &owner); + + // Initialize tokens + let (token1, _token2, _token3) = utils::initialize_3_tokens(&mut app, &owner); + + // Create 3 pools + let (_pool_addr_1, _lp_token_1, pool_id_1) = utils::initialize_weighted_pool( + &mut app, + &owner, + vault_instance.clone(), + token1.clone(), + _token2.clone(), + _token3.clone(), + "denom1".to_string(), + "denom2".to_string(), + ); + + let (_pool_addr_2, _lp_token_2, pool_id_2) = utils::initialize_stable_5_pool_2_asset( + &mut app, + &owner, + vault_instance.clone(), + token1.clone(), + "denom3".to_string(), + ); + + let (_pool_addr_3, _lp_token_3, pool_id_3) = utils::initialize_stable_5_pool_2_asset( + &mut app, + &owner, + vault_instance.clone(), + _token2.clone(), + "denom4".to_string(), + ); + + // Test limit = 0 (should return empty) + let pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: None, + limit: Some(0), + }, + ) + .unwrap(); + assert!(pools.is_empty(), "Should return empty list with limit=0"); + + // Test start_after beyond existing pools + let pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: Some(Uint128::from(999u128)), + limit: None, + }, + ) + .unwrap(); + assert!( + pools.is_empty(), + "Should return empty list when start_after is beyond all pools" + ); + + // Test start_after equal to last pool + let pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: Some(pool_id_3), + limit: None, + }, + ) + .unwrap(); + assert!( + pools.is_empty(), + "Should return empty list when start_after equals last pool" + ); + + // Make middle pool defunct and test edge case + let defunct_msg = ExecuteMsg::DefunctPool { pool_id: pool_id_2 }; + let result = app.execute_contract(owner.clone(), vault_instance.clone(), &defunct_msg, &[]); + assert!( + result.is_ok(), + "Should successfully make middle pool defunct" + ); + + // Test limit=1 starting from first pool + let pools: Vec = app + .wrap() + .query_wasm_smart( + vault_instance.clone(), + &QueryMsg::Pools { + start_after: Some(pool_id_1), + limit: Some(1), + }, + ) + .unwrap(); + assert_eq!(pools.len(), 1, "Should return 1 pool"); + assert_eq!( + pools[0].pool_id, pool_id_3, + "Should skip defunct pool and return pool_id_3" + ); +} diff --git a/contracts/vault/tests/test-tube-x/defunct_pool.rs b/contracts/vault/tests/test-tube-x/defunct_pool.rs index af6b73f..518aed9 100644 --- a/contracts/vault/tests/test-tube-x/defunct_pool.rs +++ b/contracts/vault/tests/test-tube-x/defunct_pool.rs @@ -1,14 +1,17 @@ #![cfg(test)] use cosmwasm_std::{coins, from_json, to_json_binary, Addr, Uint128}; +use cw2::ContractVersion; +use cw20::BalanceResponse; use dexter::asset::{Asset, AssetInfo}; use dexter::vault::{DefunctPoolInfo, ExecuteMsg, QueryMsg}; +use persistence_std::types::cosmwasm::wasm::v1::{ + MsgMigrateContract, MsgMigrateContractResponse, QueryRawContractStateRequest, + QueryRawContractStateResponse, +}; use persistence_test_tube::{Account, Module, Runner, RunnerExecuteResult, Wasm}; -use rand::{Rng, SeedableRng}; use rand::rngs::StdRng; -use persistence_std::types::cosmwasm::wasm::v1::{MsgMigrateContractResponse, MsgMigrateContract, QueryRawContractStateRequest, QueryRawContractStateResponse}; -use cw20::BalanceResponse; -use cw2::ContractVersion; +use rand::{Rng, SeedableRng}; pub mod utils; @@ -817,23 +820,24 @@ impl DefunctPoolTestSuite { for i in 0..10 { let user = self .app - .init_account(&[cosmwasm_std::Coin { - denom: "uusd".to_string(), - amount: Uint128::from(100_000_000_000u128), - }, - cosmwasm_std::Coin { - denom: "uxprt".to_string(), - amount: Uint128::from(100_000_000_000u128), - }, - cosmwasm_std::Coin { - denom: "denom1".to_string(), - amount: Uint128::from(100_000_000_000u128), - }, - cosmwasm_std::Coin { - denom: "denom2".to_string(), - amount: Uint128::from(100_000_000_000u128), - }] - ) + .init_account(&[ + cosmwasm_std::Coin { + denom: "uusd".to_string(), + amount: Uint128::from(100_000_000_000u128), + }, + cosmwasm_std::Coin { + denom: "uxprt".to_string(), + amount: Uint128::from(100_000_000_000u128), + }, + cosmwasm_std::Coin { + denom: "denom1".to_string(), + amount: Uint128::from(100_000_000_000u128), + }, + cosmwasm_std::Coin { + denom: "denom2".to_string(), + amount: Uint128::from(100_000_000_000u128), + }, + ]) .unwrap(); user_addresses.push(user.address().to_string()); @@ -930,9 +934,11 @@ impl DefunctPoolTestSuite { let initial_lp_balance: BalanceResponse = wasm .query( &lp_token_instance, - &cw20::Cw20QueryMsg::Balance { address: user.address().to_string() }, + &cw20::Cw20QueryMsg::Balance { + address: user.address().to_string(), + }, ) - .unwrap(); + .unwrap(); let result = wasm.execute( &self.vault_instance, @@ -955,7 +961,9 @@ impl DefunctPoolTestSuite { let final_lp_balance: BalanceResponse = wasm .query( &lp_token_instance, - &cw20::Cw20QueryMsg::Balance { address: user.address().to_string() }, + &cw20::Cw20QueryMsg::Balance { + address: user.address().to_string(), + }, ) .unwrap(); let lp_received = final_lp_balance.balance - initial_lp_balance.balance; @@ -1021,16 +1029,14 @@ impl DefunctPoolTestSuite { let post_refund_balance = utils::query_asset_balance(&self.app, user_addr, &asset.info); let pre_refund_balance = pre_refund_balances.get(&asset.info.to_string()).unwrap(); - let actual_refund_received = post_refund_balance.checked_sub(*pre_refund_balance).unwrap(); + let actual_refund_received = post_refund_balance + .checked_sub(*pre_refund_balance) + .unwrap(); assert_eq!( - actual_refund_received, - expected_refund, + actual_refund_received, expected_refund, "Mismatched refund for user {} and asset {}. Expected {}, got {}", - user_addr, - asset.info, - expected_refund, - actual_refund_received + user_addr, asset.info, expected_refund, actual_refund_received ); } } @@ -1150,14 +1156,30 @@ impl DefunctPoolTestSuite { .data .address; - // --- Test successful migration --- - let migrate_msg = dexter::vault::MigrateMsg::V1_2 { - reward_schedule_validation_assets: Some(vec![AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, AssetInfo::NativeToken { - denom: "uxprt".to_string(), - }]) + // Pre-seed RewardScheduleValidationAssets on old vault so migration preserves it + let set_assets_msg = dexter::vault::ExecuteMsg::UpdateConfig { + lp_token_code_id: None, + fee_collector: None, + pool_creation_fee: None, + auto_stake_impl: None, + paused: None, + reward_schedule_validation_assets: Some(vec![ + AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + AssetInfo::NativeToken { + denom: "uxprt".to_string(), + }, + ]), }; + let preset_result = wasm.execute(&old_vault_instance, &set_assets_msg, &[], &self.owner); + assert!( + preset_result.is_ok(), + "Pre-seeding validation assets should succeed" + ); + + // --- Test successful migration --- + let migrate_msg = dexter::vault::MigrateMsg::V1_2_2 {}; // We need to send a message directly on the persistence test app since we have runner in scope, we can just send the whole message @@ -1174,7 +1196,7 @@ impl DefunctPoolTestSuite { &self.owner, ); assert!(result.is_ok(), "Migration should succeed with valid input"); - + // Verify contract version after migration let contract_info_res = self .app @@ -1188,22 +1210,27 @@ impl DefunctPoolTestSuite { .unwrap(); let contract_info: ContractVersion = from_json(&contract_info_res.data).unwrap(); - assert_eq!(contract_info.version, "1.2.0"); + assert_eq!(contract_info.version, "1.2.2"); assert_eq!(contract_info.contract, "dexter-vault"); - // Verify config after successful migration + // Verify config after successful migration - pre-seeded value must persist let reward_schedule_validation_assets: Vec = wasm - .query(&old_vault_instance, &QueryMsg::RewardScheduleValidationAssets {}) + .query( + &old_vault_instance, + &QueryMsg::RewardScheduleValidationAssets {}, + ) .unwrap(); - assert_eq!( reward_schedule_validation_assets, - vec![AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, AssetInfo::NativeToken { - denom: "uxprt".to_string(), - }] + vec![ + AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + AssetInfo::NativeToken { + denom: "uxprt".to_string(), + }, + ] ); // --- Test migration with None (should use default assets) --- @@ -1228,9 +1255,7 @@ impl DefunctPoolTestSuite { .data .address; - let migrate_msg_none = dexter::vault::MigrateMsg::V1_2 { - reward_schedule_validation_assets: None - }; + let migrate_msg_none = dexter::vault::MigrateMsg::V1_2_2 {}; let migrate_cosmos_msg = MsgMigrateContract { contract: old_vault_instance2.to_string(), code_id: new_vault_code_id, @@ -1242,16 +1267,11 @@ impl DefunctPoolTestSuite { "/cosmwasm.wasm.v1.MsgMigrateContract", &self.owner, ); - assert!(result.is_err(), "Migration should fail when no validation assets are provided"); - - let error_msg = result.unwrap_err().to_string(); assert!( - error_msg.contains("reward_schedule_validation_assets must be provided"), - "Unexpected error for migration with None: {}", - error_msg + result.is_ok(), + "Migration should succeed without validation assets configured" ); - // --- Test unauthorized migration --- let unauthorized_user = self .app @@ -1297,8 +1317,7 @@ impl DefunctPoolTestSuite { assert!(result.is_err(), "Unauthorized migration should fail"); let error_msg = result.unwrap_err().to_string(); assert!( - error_msg.contains("Unauthorized") - || error_msg.contains("unauthorized"), + error_msg.contains("Unauthorized") || error_msg.contains("unauthorized"), "Unexpected error for unauthorized migration: {}", error_msg ); diff --git a/contracts/vault/tests/test-tube-x/utils.rs b/contracts/vault/tests/test-tube-x/utils.rs index 8c6ddb4..d009c5e 100644 --- a/contracts/vault/tests/test-tube-x/utils.rs +++ b/contracts/vault/tests/test-tube-x/utils.rs @@ -31,7 +31,10 @@ pub fn mock_app(init_coins: Vec) -> (PersistenceTestApp, SigningAccount) { } pub fn store_vault_code(app: &PersistenceTestApp, signer: &SigningAccount) -> u64 { - let wasm_bytes = get_wasm_bytes("dexter_vault"); + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let vault_path = + manifest_dir.join("../../artifacts/old_version_artifacts/dexter_vault_v1.2.2.wasm"); + let wasm_bytes = std::fs::read(vault_path).unwrap(); Wasm::new(app) .store_code(&wasm_bytes, None, signer) .unwrap() @@ -41,8 +44,8 @@ pub fn store_vault_code(app: &PersistenceTestApp, signer: &SigningAccount) -> u6 pub fn store_old_vault_code(app: &PersistenceTestApp, signer: &SigningAccount) -> u64 { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let old_vault_path = manifest_dir - .join("../../artifacts/old_version_artifacts/dexter_vault_v1.1.0.wasm"); + let old_vault_path = + manifest_dir.join("../../artifacts/old_version_artifacts/dexter_vault_v1.2.1.wasm"); let wasm_bytes = std::fs::read(old_vault_path).unwrap(); Wasm::new(app) .store_code(&wasm_bytes, None, signer) diff --git a/packages/dexter/src/vault.rs b/packages/dexter/src/vault.rs index f7f22aa..3e913ea 100644 --- a/packages/dexter/src/vault.rs +++ b/packages/dexter/src/vault.rs @@ -476,15 +476,8 @@ pub enum QueryMsg { /// ## Description - This struct describes a migration message. #[cw_serde] pub enum MigrateMsg { - - V1_1 { - updated_pool_type_configs: Vec, - }, - /// Migration for defunct pool functionality and configurable reward schedule validation assets - V1_2 { - /// List of reward assets to check when validating reward schedules during defunct operations - reward_schedule_validation_assets: Option>, - } + /// Migration from V1_2_1 to V1_2_2 - includes query_pools fix for defunct pools + V1_2_2 {}, } // ----------------x----------------x----------------x----------------x----------------x----------------