From 5c616ccccfc5af88bf1081cd4a8da5c990b179fb Mon Sep 17 00:00:00 2001 From: Awwal-dev34 Date: Mon, 28 Sep 2026 18:14:23 +0100 Subject: [PATCH] fix(factory): validate the staking asset token in create_pool (#430) --- soroban/contracts/factory/src/lib.rs | 17 +- soroban/contracts/factory/src/test.rs | 339 +++++++++--------- .../factory/tests/factory_pool_integration.rs | 16 +- 3 files changed, 197 insertions(+), 175 deletions(-) diff --git a/soroban/contracts/factory/src/lib.rs b/soroban/contracts/factory/src/lib.rs index 378b0f8..d484e8b 100644 --- a/soroban/contracts/factory/src/lib.rs +++ b/soroban/contracts/factory/src/lib.rs @@ -210,15 +210,18 @@ fn pool_salt(env: &Env, pool_id: u32) -> BytesN<32> { } fn validate_asset(env: &Env, asset: &Address) -> Result<(), FactoryError> { - let args: Vec = vec![&env, env.current_contract_address().into_val(env)]; - match env.try_invoke_contract::( + // Probe a read-only SEP-41 entry point (`symbol`) and require the address to + // answer it. An address that is not a deployed token contract never answers, + // so accepting that case (the previous `Err(_) => Ok(())` fallback) let a + // plain account or unrelated contract be registered as a pool's staking + // asset (#430). + match env.try_invoke_contract::( asset, - &Symbol::new(env, "balance"), - args, + &Symbol::new(env, "symbol"), + vec![env], ) { - Ok(Ok(balance)) if balance >= 0 => Ok(()), - Ok(_) => Err(FactoryError::InvalidAsset), - Err(_) => Ok(()), + Ok(Ok(_)) => Ok(()), + _ => Err(FactoryError::InvalidAsset), } } diff --git a/soroban/contracts/factory/src/test.rs b/soroban/contracts/factory/src/test.rs index 63d32cb..31b01a7 100644 --- a/soroban/contracts/factory/src/test.rs +++ b/soroban/contracts/factory/src/test.rs @@ -82,6 +82,16 @@ fn upload_replacement_wasm(env: &Env) -> BytesN<32> { env.deployer().upload_contract_wasm(ADD_I32_WASM) } +/// Registers a real SEP-41 token contract and returns its address. +/// +/// `create_pool` validates that the staking asset answers the SEP-41 +/// interface (#430), so tests must pass a deployed token rather than a bare +/// generated address. +fn test_asset(env: &Env) -> Address { + env.register_stellar_asset_contract_v2(Address::generate(env)) + .address() +} + /// Builds an initialised factory using the real farming-pool WASM. fn setup() -> TestEnv { let env = Env::default(); @@ -129,6 +139,8 @@ fn setup_with_pool_records(count: u32) -> TestEnv { for pool_id in 0..count { let record = PoolRecord { address: Address::generate(&t.env), + // Synthetic record written straight to storage — never routed + // through `create_pool`, so its asset need not be a real token. asset: Address::generate(&t.env), credit_rate: 100 + pool_id as i128, global_multiplier: 1 + pool_id, @@ -231,7 +243,7 @@ fn test_pool_wasm_hash_uninitialized_returns_not_initialized() { #[test] fn test_create_pool_uninitialized_returns_not_initialized() { let (env, client) = setup_uninitialized(); - let asset = Address::generate(&env); + let asset = test_asset(&env); match client.try_create_pool(&asset, &1_000u128, &2u32, &86_400u64, &0i128) { Err(Ok(FactoryError::NotInitialized)) => {} _ => panic!("expected FactoryError::NotInitialized"), @@ -269,7 +281,7 @@ fn test_list_pools_uninitialized_returns_not_initialized() { #[test] fn test_get_pools_by_asset_uninitialized_returns_not_initialized() { let (env, client) = setup_uninitialized(); - let asset = Address::generate(&env); + let asset = test_asset(&env); match client.try_get_pools_by_asset(&asset, &0u32, &10u32) { Err(Ok(FactoryError::NotInitialized)) => {} _ => panic!("expected FactoryError::NotInitialized"), @@ -510,7 +522,7 @@ fn test_list_pools_has_more_flag_accuracy() { #[test] fn test_get_pools_by_asset_returns_empty_when_no_pools() { let t = setup(); - let asset = Address::generate(&t.env); + let asset = test_asset(&t.env); let page = t.client.get_pools_by_asset(&asset, &0u32, &10u32); assert_eq!( page.records.len(), @@ -617,13 +629,9 @@ fn test_transfer_admin_emits_event_with_old_and_new_admin() { fn test_upgrade_pool_hot_swaps_registered_pool_without_changing_factory_hash() { let t = setup(); let original_factory_hash = t.client.pool_wasm_hash(); - let pool_id = t.client.create_pool( - &Address::generate(&t.env), - &1_728_000u128, - &2u32, - &10u64, - &0i128, - ); + let pool_id = t + .client + .create_pool(&test_asset(&t.env), &1_728_000u128, &2u32, &10u64, &0i128); let pool_addr = t.client.get_pool(&pool_id).address; let new_wasm_hash = upload_replacement_wasm(&t.env); @@ -676,13 +684,9 @@ fn test_upgrade_pool_hot_swaps_registered_pool_without_changing_factory_hash() { #[test] fn test_upgrade_pool_same_hash_returns_pool_upgrade_failed() { let t = setup(); - let pool_id = t.client.create_pool( - &Address::generate(&t.env), - &1_728_000u128, - &2u32, - &10u64, - &0i128, - ); + let pool_id = t + .client + .create_pool(&test_asset(&t.env), &1_728_000u128, &2u32, &10u64, &0i128); assert_eq!( t.client.try_upgrade_pool(&pool_id, &t.wasm_hash), @@ -703,13 +707,9 @@ fn test_upgrade_pool_missing_pool_returns_not_found() { #[test] fn test_upgrade_pool_requires_factory_admin_auth() { let t = setup(); - let pool_id = t.client.create_pool( - &Address::generate(&t.env), - &1_728_000u128, - &2u32, - &10u64, - &0i128, - ); + let pool_id = t + .client + .create_pool(&test_asset(&t.env), &1_728_000u128, &2u32, &10u64, &0i128); let not_admin = Address::generate(&t.env); let new_wasm_hash = t.wasm_hash.clone(); @@ -747,7 +747,7 @@ fn test_upgrade_pool_non_upgradable_pool_returns_pool_upgrade_failed() { let non_upgradable_addr = t.env.register(NonUpgradableContract, ()); let fake_record = PoolRecord { address: non_upgradable_addr, - asset: Address::generate(&t.env), + asset: test_asset(&t.env), credit_rate: 100, global_multiplier: 1, min_lock_period: 10, @@ -787,7 +787,7 @@ fn test_create_pool_rejects_missing_pool_wasm_hash() { env2.mock_all_auths(); client2.initialize(&admin, &wasm_hash2); - let asset = Address::generate(&env2); + let asset = test_asset(&env2); let result = client2.try_create_pool(&asset, &17_280_000u128, &2u32, &86_400u64, &0i128); assert!( result.is_err(), @@ -807,20 +807,8 @@ fn test_create_pool_returns_incrementing_ids() { let client = FactoryClient::new(&env, &factory_addr); client.initialize(&admin, &wasm_hash); - let id_a = client.create_pool( - &Address::generate(&env), - &8_640_000u128, - &2u32, - &100u64, - &0i128, - ); - let id_b = client.create_pool( - &Address::generate(&env), - &17_280_000u128, - &3u32, - &200u64, - &0i128, - ); + let id_a = client.create_pool(&test_asset(&env), &8_640_000u128, &2u32, &100u64, &0i128); + let id_b = client.create_pool(&test_asset(&env), &17_280_000u128, &3u32, &200u64, &0i128); assert_eq!(id_a, 0); assert_eq!(id_b, 1); assert_eq!(client.pool_count(), 2); @@ -829,8 +817,8 @@ fn test_create_pool_returns_incrementing_ids() { #[test] fn test_create_pools_batch_returns_ids_and_registers_every_pool() { let t = setup(); - let asset_a = Address::generate(&t.env); - let asset_b = Address::generate(&t.env); + let asset_a = test_asset(&t.env); + let asset_b = test_asset(&t.env); let pools = vec![ &t.env, PoolParams { @@ -863,14 +851,14 @@ fn test_create_pools_batch_rolls_back_when_any_pool_is_invalid() { let pools = vec![ &t.env, PoolParams { - asset: Address::generate(&t.env), + asset: test_asset(&t.env), daily_rate: 17_280_000, global_multiplier: 2, min_lock_period: 100, min_stake_amount: 1_000_000, }, PoolParams { - asset: Address::generate(&t.env), + asset: test_asset(&t.env), daily_rate: 17_280_000, global_multiplier: 0, min_lock_period: 100, @@ -895,7 +883,7 @@ fn test_get_pool_returns_correct_record() { let client = FactoryClient::new(&env, &factory_addr); client.initialize(&admin, &wasm_hash); - let asset = Address::generate(&env); + let asset = test_asset(&env); let id = client.create_pool(&asset, &4_320_000u128, &2u32, &50u64, &0i128); let record = client.get_pool(&id); assert_eq!(record.asset, asset); @@ -914,8 +902,8 @@ fn test_get_pools_by_asset_returns_matching_ids() { let client = FactoryClient::new(&env, &factory_addr); client.initialize(&admin, &wasm_hash); - let asset_a = Address::generate(&env); - let asset_b = Address::generate(&env); + let asset_a = test_asset(&env); + let asset_b = test_asset(&env); let id_0 = client.create_pool(&asset_a, &1_728_000u128, &2u32, &10u64, &0i128); let id_1 = client.create_pool(&asset_b, &3_456_000u128, &2u32, &20u64, &0i128); @@ -941,13 +929,7 @@ fn test_get_pools_by_asset_unknown_asset_returns_empty() { let client = FactoryClient::new(&env, &factory_addr); client.initialize(&admin, &wasm_hash); - client.create_pool( - &Address::generate(&env), - &1_728_000u128, - &2u32, - &10u64, - &0i128, - ); + client.create_pool(&test_asset(&env), &1_728_000u128, &2u32, &10u64, &0i128); let unknown = Address::generate(&env); let result = client.get_pools_by_asset(&unknown, &0u32, &10u32); assert_eq!(result.records.len(), 0); @@ -964,7 +946,7 @@ fn test_get_pools_by_asset_paginates_large_matching_registry() { client.initialize(&admin, &wasm_hash); // Create 25 pools all sharing the same asset - let asset = Address::generate(&env); + let asset = test_asset(&env); for i in 0..25 { client.create_pool( &asset, @@ -1008,8 +990,8 @@ fn test_get_pools_by_asset_bounds_scan_for_sparse_matches() { let client = FactoryClient::new(&env, &factory_addr); client.initialize(&admin, &wasm_hash); - let sparse_asset = Address::generate(&env); - let other_asset = Address::generate(&env); + let sparse_asset = test_asset(&env); + let other_asset = test_asset(&env); for i in 0..500 { let asset = if i == 499 { sparse_asset.clone() @@ -1042,8 +1024,8 @@ fn test_get_pools_by_asset_can_page_sparse_matches_to_completion() { let client = FactoryClient::new(&env, &factory_addr); client.initialize(&admin, &wasm_hash); - let sparse_asset = Address::generate(&env); - let other_asset = Address::generate(&env); + let sparse_asset = test_asset(&env); + let other_asset = test_asset(&env); for i in 0..500 { let asset = if i == 250 || i == 499 { sparse_asset.clone() @@ -1088,8 +1070,8 @@ fn test_get_pools_by_asset_range_custom_scan_limit() { let client = FactoryClient::new(&env, &factory_addr); client.initialize(&admin, &wasm_hash); - let sparse_asset = Address::generate(&env); - let other_asset = Address::generate(&env); + let sparse_asset = test_asset(&env); + let other_asset = test_asset(&env); for i in 0..100 { let asset = if i == 90 { sparse_asset.clone() @@ -1128,13 +1110,7 @@ fn test_create_pool_emits_pool_crtd_event() { let client = FactoryClient::new(&env, &factory_addr); client.initialize(&admin, &wasm_hash); - client.create_pool( - &Address::generate(&env), - &5_184_000u128, - &2u32, - &30u64, - &0i128, - ); + client.create_pool(&test_asset(&env), &5_184_000u128, &2u32, &30u64, &0i128); assert!( !env.events().all().events().is_empty(), "expected pool_crtd event" @@ -1150,8 +1126,8 @@ fn test_multiple_pools_stored_independently() { let factory_addr = env.register(Factory, ()); let client = FactoryClient::new(&env, &factory_addr); client.initialize(&admin, &wasm_hash); - let asset_a = Address::generate(&env); - let asset_b = Address::generate(&env); + let asset_a = test_asset(&env); + let asset_b = test_asset(&env); let id_a = client.create_pool(&asset_a, &1_728_000u128, &2u32, &10u64, &0i128); let id_b = client.create_pool(&asset_b, &3_456_000u128, &2u32, &20u64, &0i128); let rec_a = client.get_pool(&id_a); @@ -1165,7 +1141,7 @@ fn test_multiple_pools_stored_independently() { fn test_create_pool_rejects_unmatched_non_admin_auth() { let t = setup(); let not_admin = Address::generate(&t.env); - let asset = Address::generate(&t.env); + let asset = test_asset(&t.env); let args = (&asset, 17_280_000u128, 2u32, 86_400u64, 0i128).into_val(&t.env); let invoke = MockAuthInvoke { contract: &t.factory_addr, @@ -1193,23 +1169,15 @@ fn test_create_pool_increments_count_after_each_pool() { let t = setup(); assert_eq!(t.client.pool_count(), 0); - let id_a = t.client.create_pool( - &Address::generate(&t.env), - &8_640_000u128, - &2u32, - &100u64, - &0i128, - ); + let id_a = t + .client + .create_pool(&test_asset(&t.env), &8_640_000u128, &2u32, &100u64, &0i128); assert_eq!(id_a, 0); assert_eq!(t.client.pool_count(), 1); - let id_b = t.client.create_pool( - &Address::generate(&t.env), - &17_280_000u128, - &2u32, - &200u64, - &0i128, - ); + let id_b = t + .client + .create_pool(&test_asset(&t.env), &17_280_000u128, &2u32, &200u64, &0i128); assert_eq!(id_b, 1); assert_eq!(t.client.pool_count(), 2); } @@ -1225,13 +1193,9 @@ fn test_create_pool_returns_typed_error_when_pool_count_overflows() { .set(&DataKey::PoolCount, &u32::MAX); }); - let result = t.client.try_create_pool( - &Address::generate(&t.env), - &1_728_000u128, - &2u32, - &100u64, - &0i128, - ); + let result = + t.client + .try_create_pool(&test_asset(&t.env), &1_728_000u128, &2u32, &100u64, &0i128); assert_eq!(result, Err(Ok(FactoryError::PoolCountOverflow))); assert_eq!(t.client.pool_count(), u32::MAX); @@ -1243,7 +1207,7 @@ fn test_create_pool_returns_typed_error_when_pool_count_overflows() { #[test] fn test_create_pool_uses_deterministic_pool_addresses() { let t = setup(); - let asset = Address::generate(&t.env); + let asset = test_asset(&t.env); let expected_before = expected_pool_address(&t.env, &t.factory_addr, 0); let expected_again = expected_pool_address(&t.env, &t.factory_addr, 0); @@ -1260,7 +1224,7 @@ fn test_create_pool_uses_deterministic_pool_addresses() { #[test] fn test_create_pool_rejects_zero_daily_rate() { let t = setup(); - let asset = Address::generate(&t.env); + let asset = test_asset(&t.env); // Below LEDGERS_PER_DAY (17_280), the daily_rate -> credit_rate conversion // truncates to zero, which FarmingPool::initialize would reject anyway @@ -1276,7 +1240,7 @@ fn test_create_pool_rejects_zero_daily_rate() { #[test] fn test_create_pool_rejects_dust_minimum_stake() { let t = setup(); - let asset = Address::generate(&t.env); + let asset = test_asset(&t.env); let result = t .client @@ -1289,7 +1253,7 @@ fn test_create_pool_rejects_dust_minimum_stake() { #[test] fn test_create_pool_accepts_daily_rate_below_ledgers_per_day_with_ceiling_rounding() { let t = setup(); - let asset = Address::generate(&t.env); + let asset = test_asset(&t.env); // Before #148 the daily_rate -> credit_rate conversion truncated, so a // sub-LEDGERS_PER_DAY daily_rate (17_279) became 0 and was rejected. Now it @@ -1308,7 +1272,7 @@ fn test_create_pool_accepts_daily_rate_below_ledgers_per_day_with_ceiling_roundi #[test] fn test_create_pool_rejects_global_multiplier_below_one() { let t = setup(); - let asset = Address::generate(&t.env); + let asset = test_asset(&t.env); let result = t .client @@ -1319,7 +1283,7 @@ fn test_create_pool_rejects_global_multiplier_below_one() { #[test] fn test_create_pool_rejects_min_lock_period_out_of_u32_range() { let t = setup(); - let asset = Address::generate(&t.env); + let asset = test_asset(&t.env); let too_large = (u32::MAX as u64) + 1; let result = t @@ -1329,15 +1293,83 @@ fn test_create_pool_rejects_min_lock_period_out_of_u32_range() { } #[test] -fn test_get_pool_bumps_pool_record_ttl() { +fn test_create_pool_rejects_non_token_asset() { let t = setup(); - let id = t.client.create_pool( - &Address::generate(&t.env), - &4_320_000u128, - &2u32, - &50u64, - &0i128, + // A bare generated address is not a deployed SEP-41 token contract, so it + // cannot answer the interface probe and must be rejected (#430). + let asset = Address::generate(&t.env); + + let result = t + .client + .try_create_pool(&asset, &1_728_000u128, &2u32, &25u64, &0i128); + + assert_eq!(result, Err(Ok(FactoryError::InvalidAsset))); + assert_eq!(t.client.pool_count(), 0); +} + +#[test] +fn test_create_pool_rejects_contract_without_sep41_interface() { + let t = setup(); + // The factory itself is a real deployed contract, but it exposes no SEP-41 + // read entry point, so it must not pass token validation either. + let asset = t.factory_addr.clone(); + + let result = t + .client + .try_create_pool(&asset, &1_728_000u128, &2u32, &25u64, &0i128); + + assert_eq!(result, Err(Ok(FactoryError::InvalidAsset))); + assert_eq!(t.client.pool_count(), 0); +} + +#[test] +fn test_create_pool_accepts_sep41_token_asset() { + let t = setup(); + let asset = test_asset(&t.env); + + let pool_id = t + .client + .create_pool(&asset, &1_728_000u128, &2u32, &25u64, &0i128); + + assert_eq!(t.client.get_pool(&pool_id).asset, asset); + assert_eq!(t.client.pool_count(), 1); +} + +#[test] +fn test_create_pools_batch_rejects_non_token_asset() { + let t = setup(); + let pools = vec![ + &t.env, + PoolParams { + asset: test_asset(&t.env), + daily_rate: 17_280_000, + global_multiplier: 2, + min_lock_period: 100, + min_stake_amount: 1_000_000, + }, + PoolParams { + asset: Address::generate(&t.env), + daily_rate: 17_280_000, + global_multiplier: 2, + min_lock_period: 100, + min_stake_amount: 1_000_000, + }, + ]; + + assert_eq!( + t.client.try_create_pools_batch(&pools), + Err(Ok(FactoryError::InvalidAsset)) ); + // Atomic batch: the leading valid pool must be rolled back too. + assert_eq!(t.client.pool_count(), 0); +} + +#[test] +fn test_get_pool_bumps_pool_record_ttl() { + let t = setup(); + let id = t + .client + .create_pool(&test_asset(&t.env), &4_320_000u128, &2u32, &50u64, &0i128); assert_eq!(pool_record_ttl(&t.env, &t.factory_addr, id), TTL_EXTEND_TO); @@ -1351,13 +1383,9 @@ fn test_get_pool_bumps_pool_record_ttl() { #[test] fn test_list_pools_bumps_pool_record_ttl() { let t = setup(); - let id = t.client.create_pool( - &Address::generate(&t.env), - &4_320_000u128, - &2u32, - &50u64, - &0i128, - ); + let id = t + .client + .create_pool(&test_asset(&t.env), &4_320_000u128, &2u32, &50u64, &0i128); assert_eq!(pool_record_ttl(&t.env, &t.factory_addr, id), TTL_EXTEND_TO); @@ -1371,13 +1399,9 @@ fn test_list_pools_bumps_pool_record_ttl() { #[test] fn test_refresh_pool_ttls_restores_ttl_for_unqueried_pool() { let t = setup(); - let id = t.client.create_pool( - &Address::generate(&t.env), - &1_728_000u128, - &2u32, - &50u64, - &0i128, - ); + let id = t + .client + .create_pool(&test_asset(&t.env), &1_728_000u128, &2u32, &50u64, &0i128); // Initial TTL after creation assert_eq!(pool_record_ttl(&t.env, &t.factory_addr, id), TTL_EXTEND_TO); @@ -1439,27 +1463,15 @@ fn test_refresh_pool_ttls_reports_only_existing_pools_and_counts_gaps() { // the surviving records bumped. let t = setup(); - let p0 = t.client.create_pool( - &Address::generate(&t.env), - &1_728_000u128, - &2u32, - &50u64, - &0i128, - ); - let p1 = t.client.create_pool( - &Address::generate(&t.env), - &1_728_000u128, - &2u32, - &50u64, - &0i128, - ); - let p2 = t.client.create_pool( - &Address::generate(&t.env), - &1_728_000u128, - &2u32, - &50u64, - &0i128, - ); + let p0 = t + .client + .create_pool(&test_asset(&t.env), &1_728_000u128, &2u32, &50u64, &0i128); + let p1 = t + .client + .create_pool(&test_asset(&t.env), &1_728_000u128, &2u32, &50u64, &0i128); + let p2 = t + .client + .create_pool(&test_asset(&t.env), &1_728_000u128, &2u32, &50u64, &0i128); // Archive the middle record out from under the registry, as a TTL lapse // would, leaving the count intact so the ID stays inside the sweep range. @@ -1512,7 +1524,7 @@ fn test_refresh_pool_ttls_reports_empty_sweep_past_the_registry() { #[test] fn test_create_pool_emits_pool_crtd_event_with_payload() { let t = setup(); - let asset = Address::generate(&t.env); + let asset = test_asset(&t.env); let expected_address = expected_pool_address(&t.env, &t.factory_addr, 0); let id = t .client @@ -1555,7 +1567,7 @@ fn test_old_admin_cannot_create_pool_after_transfer_but_new_admin_can() { let new_admin = Address::generate(&t.env); t.client.transfer_admin(&new_admin); - let old_asset = Address::generate(&t.env); + let old_asset = test_asset(&t.env); let old_args = (&old_asset, 1_728_000u128, 2u32, 10u64, 0i128).into_val(&t.env); let old_invoke = MockAuthInvoke { contract: &t.factory_addr, @@ -1577,7 +1589,7 @@ fn test_old_admin_cannot_create_pool_after_transfer_but_new_admin_can() { ); assert_eq!(t.client.pool_count(), 0); - let new_asset = Address::generate(&t.env); + let new_asset = test_asset(&t.env); let new_args = (&new_asset, 3_456_000u128, 2u32, 20u64, 0i128).into_val(&t.env); let new_invoke = MockAuthInvoke { contract: &t.factory_addr, @@ -1615,7 +1627,7 @@ fn test_create_pool_initializes_real_farming_pool_atomically() { let client = FactoryClient::new(&env, &factory_addr); client.initialize(&admin, &wasm_hash); - let asset = Address::generate(&env); + let asset = test_asset(&env); let pool_id = client.create_pool(&asset, &1_728_000u128, &2u32, &10u64, &0i128); let record = client.get_pool(&pool_id); @@ -1636,7 +1648,7 @@ fn test_pause_pool_creation_prevents_create_pool() { t.client.pause_pool_creation(); assert!(t.client.is_pool_creation_paused()); - let asset = Address::generate(&t.env); + let asset = test_asset(&t.env); let result = t .client .try_create_pool(&asset, &1_728_000u128, &2u32, &10u64, &0i128); @@ -1653,7 +1665,7 @@ fn test_unpause_pool_creation_allows_create_pool() { t.client.unpause_pool_creation(); assert!(!t.client.is_pool_creation_paused()); - let asset = Address::generate(&t.env); + let asset = test_asset(&t.env); let pool_id = t .client .create_pool(&asset, &1_728_000u128, &2u32, &10u64, &0i128); @@ -1726,12 +1738,12 @@ fn test_get_pools_by_admin_returns_created_pools() { let t = setup(); assert_eq!(t.client.get_pools_by_admin(&t.admin), vec![&t.env]); - let asset1 = Address::generate(&t.env); + let asset1 = test_asset(&t.env); let id1 = t .client .create_pool(&asset1, &1_728_000u128, &2u32, &10u64, &0i128); - let asset2 = Address::generate(&t.env); + let asset2 = test_asset(&t.env); let id2 = t .client .create_pool(&asset2, &3_456_000u128, &2u32, &20u64, &0i128); @@ -1745,12 +1757,12 @@ fn test_get_admin_pool_count_tracks_pools_created_by_admin() { let t = setup(); assert_eq!(t.client.get_admin_pool_count(&t.admin), 0); - let asset1 = Address::generate(&t.env); + let asset1 = test_asset(&t.env); t.client .create_pool(&asset1, &1_728_000u128, &2u32, &10u64, &0i128); assert_eq!(t.client.get_admin_pool_count(&t.admin), 1); - let asset2 = Address::generate(&t.env); + let asset2 = test_asset(&t.env); t.client .create_pool(&asset2, &3_456_000u128, &2u32, &20u64, &0i128); assert_eq!(t.client.get_admin_pool_count(&t.admin), 2); @@ -1772,7 +1784,7 @@ fn test_get_admin_pool_count_uninitialized_returns_not_initialized() { #[test] fn test_create_pool_rejects_minimum_stake_above_the_maximum() { let t = setup(); - let asset = Address::generate(&t.env); + let asset = test_asset(&t.env); let result = t.client.try_create_pool( &asset, @@ -1790,13 +1802,9 @@ fn test_create_pool_rejects_minimum_stake_above_the_maximum() { fn test_create_pool_treats_a_non_positive_minimum_stake_as_the_default() { let t = setup(); - let negative = t.client.create_pool( - &Address::generate(&t.env), - &1_728_000u128, - &2u32, - &25u64, - &-5i128, - ); + let negative = + t.client + .create_pool(&test_asset(&t.env), &1_728_000u128, &2u32, &25u64, &-5i128); assert_eq!(negative, 0); } @@ -1843,8 +1851,8 @@ fn test_list_pools_reports_missing_records_with_a_pool_gap_event() { #[test] fn test_create_pool_maintains_the_asset_index() { let t = setup(); - let asset = Address::generate(&t.env); - let other_asset = Address::generate(&t.env); + let asset = test_asset(&t.env); + let other_asset = test_asset(&t.env); let first = t.client.create_pool(&asset, &1_728_000u128, &2u32, &10u64, &0i128); let second = t.client.create_pool(&asset, &1_728_000u128, &2u32, &10u64, &0i128); @@ -1868,7 +1876,7 @@ fn test_create_pool_maintains_the_asset_index() { #[test] fn test_asset_pool_count_agrees_with_the_index() { let t = setup(); - let asset = Address::generate(&t.env); + let asset = test_asset(&t.env); assert_eq!(t.client.pool_count_by_asset(&asset), 0); @@ -1885,8 +1893,8 @@ fn test_asset_pool_count_agrees_with_the_index() { #[test] fn test_create_pools_batch_maintains_the_asset_index() { let t = setup(); - let asset = Address::generate(&t.env); - let other_asset = Address::generate(&t.env); + let asset = test_asset(&t.env); + let other_asset = test_asset(&t.env); let mut batch = vec![&t.env]; for _ in 0..2 { @@ -1921,11 +1929,12 @@ fn test_create_pools_batch_maintains_the_asset_index() { #[test] fn test_asset_index_survives_a_paginated_walk() { let t = setup(); - let asset = Address::generate(&t.env); + let asset = test_asset(&t.env); for _ in 0..3 { t.client.create_pool(&asset, &1_728_000u128, &2u32, &10u64, &0i128); } - t.client.create_pool(&Address::generate(&t.env), &1_728_000u128, &2u32, &10u64, &0i128); + t.client + .create_pool(&test_asset(&t.env), &1_728_000u128, &2u32, &10u64, &0i128); // Resuming from the previous page's `next_start_id` must not drop or // duplicate indexed pools (the #327 resume invariant, index path). diff --git a/soroban/contracts/factory/tests/factory_pool_integration.rs b/soroban/contracts/factory/tests/factory_pool_integration.rs index adbb282..2b68d1a 100644 --- a/soroban/contracts/factory/tests/factory_pool_integration.rs +++ b/soroban/contracts/factory/tests/factory_pool_integration.rs @@ -71,6 +71,16 @@ use farming_pool::{FarmingPoolClient, PoolError}; /// how this fixture is produced and kept fresh. const FARMING_POOL_WASM: &[u8] = include_bytes!("fixtures/farming_pool.wasm"); +/// Registers a real SEP-41 token contract and returns its address. +/// +/// `create_pool` validates that the staking asset answers the SEP-41 +/// interface (#430), so these tests must pass a deployed token rather than a +/// bare generated address. +fn test_asset(env: &Env) -> Address { + env.register_stellar_asset_contract_v2(Address::generate(env)) + .address() +} + fn advance_ledgers(env: &Env, by: u32) { let current = env.ledger().sequence(); env.ledger().with_mut(|ledger| { @@ -121,7 +131,7 @@ fn smoke_create_pool_returns_live_pool_address() { env.mock_all_auths(); let admin = Address::generate(&env); - let asset = Address::generate(&env); + let asset = test_asset(&env); let wasm_hash = env.deployer().upload_contract_wasm(FARMING_POOL_WASM); let factory_addr = env.register(Factory, ()); @@ -150,7 +160,7 @@ fn test_create_pool_pool_admin_is_set_atomically() { env.mock_all_auths(); let admin = Address::generate(&env); - let asset = Address::generate(&env); + let asset = test_asset(&env); let wasm_hash = env.deployer().upload_contract_wasm(FARMING_POOL_WASM); let factory_addr = env.register(Factory, ()); @@ -177,7 +187,7 @@ fn test_third_party_cannot_reinitialize_deployed_pool() { env.mock_all_auths(); let admin = Address::generate(&env); - let asset = Address::generate(&env); + let asset = test_asset(&env); let attacker = Address::generate(&env); let wasm_hash = env.deployer().upload_contract_wasm(FARMING_POOL_WASM);