diff --git a/soroban/contracts/factory/Cargo.toml b/soroban/contracts/factory/Cargo.toml index 24f7fd0..93bab91 100644 --- a/soroban/contracts/factory/Cargo.toml +++ b/soroban/contracts/factory/Cargo.toml @@ -12,12 +12,8 @@ doctest = false soroban-sdk = { workspace = true } [dev-dependencies] -farming-pool = { path = "../farming-pool" } soroban-sdk = { workspace = true, features = ["testutils"] } -soroban-sdk = { workspace = true, features = ["testutils"] } # Provides `farming_pool::WASM` (compiled contract bytes) and `FarmingPoolClient` # used in `src/test.rs` to prove that `create_pool` deploys a *real*, driveable # farming-pool contract. See `test_create_pool_deploys_uninitialized_real_farming_pool`. -farming-pool = { path = "../farming-pool", features = ["testutils"] } -soroban-sdk = { workspace = true, features = ["testutils"] } -farming-pool = { path = "../farming-pool" } +farming-pool = { path = "../farming-pool", features = ["testutils"] } diff --git a/soroban/contracts/factory/src/lib.rs b/soroban/contracts/factory/src/lib.rs index 8b93608..4451c5c 100644 --- a/soroban/contracts/factory/src/lib.rs +++ b/soroban/contracts/factory/src/lib.rs @@ -75,14 +75,6 @@ fn load_wasm_hash(env: &Env) -> Result, FactoryError> { .ok_or(FactoryError::NotInitialized) } -fn require_initialized(env: &Env) -> Result<(), FactoryError> { - if env.storage().instance().has(&DataKey::Admin) { - Ok(()) - } else { - Err(FactoryError::NotInitialized) - } -} - /// Build a 32-byte salt from a pool ID so each pool gets a unique, reproducible address. fn pool_salt(env: &Env, pool_id: u32) -> BytesN<32> { let mut bytes = [0u8; 32]; @@ -234,8 +226,6 @@ impl Factory { /// would be indistinguishable from "no pools hold this asset". /// /// Returns `NotInitialized` if the factory has not been initialized. - pub fn get_pools_by_asset(env: Env, asset: Address) -> Result, FactoryError> { - require_initialized(&env)?; /// # Secondary Index Consideration /// For very large registries, a secondary per-asset index (e.g., DataKey::AssetPools /// maintained incrementally in create_pool) would avoid full-registry scans entirely. @@ -246,7 +236,8 @@ impl Factory { asset: Address, start_id: u32, limit: u32, - ) -> ListPoolsResponse { + ) -> Result { + require_initialized(&env)?; bump_instance(&env); let count: u32 = env .storage() @@ -270,13 +261,12 @@ impl Factory { } } } - Ok(matches) - ListPoolsResponse { + Ok(ListPoolsResponse { records, next_start_id, total: count, - } + }) } /// Refresh TTLs for a range of pool records to prevent archival. @@ -351,7 +341,7 @@ impl Factory { pool_id: u32, new_wasm_hash: BytesN<32>, ) -> Result<(), FactoryError> { - let admin = load_admin(&env); + let admin = load_admin(&env)?; admin.require_auth(); bump_instance(&env); @@ -386,7 +376,7 @@ impl Factory { /// hash is discoverable off-chain for rollback scenarios. pub fn set_pool_wasm_hash(env: Env, new_hash: BytesN<32>) -> Result<(), FactoryError> { require_initialized(&env)?; - let admin: Address = load_admin(&env); + let admin: Address = load_admin(&env)?; admin.require_auth(); bump_instance(&env); @@ -412,10 +402,6 @@ impl Factory { /// `credit_rate` via `daily_rate_to_credit_rate` — see that function's /// docs for the conversion and its failure modes. /// - /// The `pool_crtd` event now includes `asset`, `daily_rate`, and - /// `min_lock_period` alongside `pool_id` and `pool_address` so off-chain - /// indexers can reconstruct the full pool state without a follow-up RPC call. - /// /// Returns `NotInitialized` if the factory has not been initialized. /// The pool's admin is fixed to this factory's admin *at creation time*. /// A later `transfer_admin` on the factory does not retroactively change @@ -432,14 +418,12 @@ impl Factory { env: Env, asset: Address, daily_rate: u128, + global_multiplier: u32, min_lock_period: u64, + min_stake_amount: i128, ) -> Result { require_initialized(&env)?; let admin = load_admin(&env)?; - global_multiplier: u32, - min_lock_period: u64, - ) -> Result { - let admin = load_admin(&env); admin.require_auth(); bump_instance(&env); @@ -455,7 +439,6 @@ impl Factory { let next_count = pool_id .checked_add(1) .ok_or(FactoryError::PoolCountOverflow)?; - let wasm_hash: BytesN<32> = env.storage().instance().get(&DataKey::WasmHash).unwrap(); let wasm_hash = load_wasm_hash(&env)?; let salt = pool_salt(&env, pool_id); @@ -479,6 +462,7 @@ impl Factory { global_multiplier.into_val(&env), credit_rate.into_val(&env), min_lock_period.into_val(&env), + min_stake_amount.into_val(&env), ]; let _: () = env.invoke_contract(&pool_address, &Symbol::new(&env, "initialize"), init_args); diff --git a/soroban/contracts/factory/src/test.rs b/soroban/contracts/factory/src/test.rs index fdc2740..9378207 100644 --- a/soroban/contracts/factory/src/test.rs +++ b/soroban/contracts/factory/src/test.rs @@ -7,12 +7,10 @@ use soroban_sdk::{ storage::Persistent as _, Address as _, AuthorizedFunction, AuthorizedInvocation, Events as _, Ledger, MockAuth, MockAuthInvoke, }, - token::{StellarAssetClient, TokenClient}, vec, Address, BytesN, Env, IntoVal, Symbol, }; -use farming_pool::{FarmingPoolClient, PoolError as PoolContractError}; - +use farming_pool::FarmingPoolClient; // ── Helpers ─────────────────────────────────────────────────────────────────── @@ -178,7 +176,7 @@ fn test_pool_wasm_hash_uninitialized_returns_not_initialized() { fn test_create_pool_uninitialized_returns_not_initialized() { let (env, client) = setup_uninitialized(); let asset = Address::generate(&env); - match client.try_create_pool(&asset, &1_000u128, &86_400u64) { + match client.try_create_pool(&asset, &1_000u128, &2u32, &86_400u64, &0i128) { Err(Ok(FactoryError::NotInitialized)) => {} _ => panic!("expected FactoryError::NotInitialized"), } @@ -216,7 +214,7 @@ fn test_list_pools_uninitialized_returns_not_initialized() { fn test_get_pools_by_asset_uninitialized_returns_not_initialized() { let (env, client) = setup_uninitialized(); let asset = Address::generate(&env); - match client.try_get_pools_by_asset(&asset) { + match client.try_get_pools_by_asset(&asset, &0u32, &10u32) { Err(Ok(FactoryError::NotInitialized)) => {} _ => panic!("expected FactoryError::NotInitialized"), } @@ -431,9 +429,13 @@ 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); + let pool_id = t.client.create_pool( + &Address::generate(&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); @@ -490,9 +492,13 @@ 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); + let pool_id = t.client.create_pool( + &Address::generate(&t.env), + &1_728_000u128, + &2u32, + &10u64, + &0i128, + ); let not_admin = Address::generate(&t.env); let new_wasm_hash = t.wasm_hash.clone(); @@ -536,7 +542,7 @@ fn test_create_pool_rejects_missing_pool_wasm_hash() { client2.initialize(&admin, &wasm_hash2); let asset = Address::generate(&env2); - let result = client2.try_create_pool(&asset, &17_280_000u128, &2u32, &86_400u64); + let result = client2.try_create_pool(&asset, &17_280_000u128, &2u32, &86_400u64, &0i128); assert!( result.is_err(), "create_pool must reject an unknown pool WASM hash" @@ -555,8 +561,20 @@ 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); - let id_b = client.create_pool(&Address::generate(&env), &17_280_000u128, &3u32, &200u64); + 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, + ); assert_eq!(id_a, 0); assert_eq!(id_b, 1); assert_eq!(client.pool_count(), 2); @@ -573,7 +591,7 @@ fn test_get_pool_returns_correct_record() { client.initialize(&admin, &wasm_hash); let asset = Address::generate(&env); - let id = client.create_pool(&asset, &4_320_000u128, &2u32, &50u64); + let id = client.create_pool(&asset, &4_320_000u128, &2u32, &50u64, &0i128); let record = client.get_pool(&id); assert_eq!(record.asset, asset); assert_eq!(record.credit_rate, 250); @@ -594,9 +612,9 @@ fn test_get_pools_by_asset_returns_matching_ids() { let asset_a = Address::generate(&env); let asset_b = Address::generate(&env); - let id_0 = client.create_pool(&asset_a, &1_728_000u128, &2u32, &10u64); - let id_1 = client.create_pool(&asset_b, &3_456_000u128, &2u32, &20u64); - let id_2 = client.create_pool(&asset_a, &5_184_000u128, &2u32, &30u64); + 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); + let id_2 = client.create_pool(&asset_a, &5_184_000u128, &2u32, &30u64, &0i128); let by_a = client.get_pools_by_asset(&asset_a, &0u32, &10u32); assert_eq!(by_a.records.len(), 2); @@ -618,7 +636,13 @@ 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); + client.create_pool( + &Address::generate(&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); @@ -642,8 +666,8 @@ fn test_get_pools_by_asset_paginates_large_matching_registry() { &(1_728_000 + i as u128 * 17_280), &2u32, &(10 + i as u64), + &0i128, ); - client.create_pool(&asset, &((100 + i as u128) * 17_280), &2u32, &(10 + i as u64)); } // First page should return 20 records @@ -679,7 +703,13 @@ 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); + client.create_pool( + &Address::generate(&env), + &5_184_000u128, + &2u32, + &30u64, + &0i128, + ); assert!( !env.events().all().events().is_empty(), "expected pool_crtd event" @@ -697,8 +727,8 @@ fn test_multiple_pools_stored_independently() { client.initialize(&admin, &wasm_hash); let asset_a = Address::generate(&env); let asset_b = Address::generate(&env); - let id_a = client.create_pool(&asset_a, &1_728_000u128, &2u32, &10u64); - let id_b = client.create_pool(&asset_b, &3_456_000u128, &2u32, &20u64); + 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); let rec_b = client.get_pool(&id_b); assert_eq!(rec_a.asset, asset_a); @@ -711,7 +741,7 @@ 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 args = (&asset, 17_280_000u128, 2u32, 86_400u64).into_val(&t.env); + let args = (&asset, 17_280_000u128, 2u32, 86_400u64, 0i128).into_val(&t.env); let invoke = MockAuthInvoke { contract: &t.factory_addr, fn_name: "create_pool", @@ -724,7 +754,7 @@ fn test_create_pool_rejects_unmatched_non_admin_auth() { address: ¬_admin, invoke: &invoke, }]) - .try_create_pool(&asset, &17_280_000u128, &2u32, &86_400u64); + .try_create_pool(&asset, &17_280_000u128, &2u32, &86_400u64, &0i128); assert!( result.is_err(), @@ -738,15 +768,23 @@ 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); + let id_a = t.client.create_pool( + &Address::generate(&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); + let id_b = t.client.create_pool( + &Address::generate(&t.env), + &17_280_000u128, + &2u32, + &200u64, + &0i128, + ); assert_eq!(id_b, 1); assert_eq!(t.client.pool_count(), 2); } @@ -762,9 +800,13 @@ 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); + let result = t.client.try_create_pool( + &Address::generate(&t.env), + &1_728_000u128, + &2u32, + &100u64, + &0i128, + ); assert_eq!(result, Err(Ok(FactoryError::PoolCountOverflow))); assert_eq!(t.client.pool_count(), u32::MAX); @@ -780,7 +822,9 @@ fn test_create_pool_uses_deterministic_pool_addresses() { let expected_before = expected_pool_address(&t.env, &t.factory_addr, 0); let expected_again = expected_pool_address(&t.env, &t.factory_addr, 0); - let id = t.client.create_pool(&asset, &4_320_000u128, &2u32, &50u64); + let id = t + .client + .create_pool(&asset, &4_320_000u128, &2u32, &50u64, &0i128); let record = t.client.get_pool(&id); assert_eq!(id, 0); @@ -797,7 +841,9 @@ fn test_create_pool_rejects_zero_daily_rate() { // truncates to zero, which FarmingPool::initialize would reject anyway // (credit_rate must be > 0) — create_pool must catch this itself rather // than deploying a pool that can never be initialized. - let result = t.client.try_create_pool(&asset, &0u128, &2u32, &25u64); + let result = t + .client + .try_create_pool(&asset, &0u128, &2u32, &25u64, &0i128); assert_eq!(result, Err(Ok(FactoryError::InvalidCreditRate))); assert_eq!(t.client.pool_count(), 0); } @@ -807,7 +853,9 @@ fn test_create_pool_rejects_daily_rate_below_ledgers_per_day() { let t = setup(); let asset = Address::generate(&t.env); - let result = t.client.try_create_pool(&asset, &17_279u128, &2u32, &25u64); + let result = t + .client + .try_create_pool(&asset, &17_279u128, &2u32, &25u64, &0i128); assert_eq!(result, Err(Ok(FactoryError::InvalidCreditRate))); } @@ -818,7 +866,7 @@ fn test_create_pool_rejects_global_multiplier_below_one() { let result = t .client - .try_create_pool(&asset, &1_728_000u128, &0u32, &25u64); + .try_create_pool(&asset, &1_728_000u128, &0u32, &25u64, &0i128); assert_eq!(result, Err(Ok(FactoryError::InvalidGlobalMultiplier))); } @@ -830,16 +878,20 @@ fn test_create_pool_rejects_min_lock_period_out_of_u32_range() { let too_large = (u32::MAX as u64) + 1; let result = t .client - .try_create_pool(&asset, &1_728_000u128, &2u32, &too_large); + .try_create_pool(&asset, &1_728_000u128, &2u32, &too_large, &0i128); assert_eq!(result, Err(Ok(FactoryError::MinLockPeriodOutOfRange))); } #[test] fn test_get_pool_bumps_pool_record_ttl() { let t = setup(); - let id = t - .client - .create_pool(&Address::generate(&t.env), &4_320_000u128, &2u32, &50u64); + let id = t.client.create_pool( + &Address::generate(&t.env), + &4_320_000u128, + &2u32, + &50u64, + &0i128, + ); assert_eq!(pool_record_ttl(&t.env, &t.factory_addr, id), TTL_EXTEND_TO); @@ -853,10 +905,13 @@ fn test_get_pool_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); - .create_pool(&Address::generate(&t.env), &(250u128 * 17_280), &2u32, &50u64); + let id = t.client.create_pool( + &Address::generate(&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); @@ -877,7 +932,9 @@ fn test_create_pool_emits_pool_crtd_event_with_payload() { let t = setup(); let asset = Address::generate(&t.env); let expected_address = expected_pool_address(&t.env, &t.factory_addr, 0); - let id = t.client.create_pool(&asset, &5_184_000u128, &2u32, &30u64); + let id = t + .client + .create_pool(&asset, &5_184_000u128, &2u32, &30u64, &0i128); assert_eq!( t.env.events().all(), @@ -903,7 +960,7 @@ fn test_old_admin_cannot_create_pool_after_transfer_but_new_admin_can() { t.client.transfer_admin(&new_admin); let old_asset = Address::generate(&t.env); - let old_args = (&old_asset, 1_728_000u128, 2u32, 10u64).into_val(&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, fn_name: "create_pool", @@ -916,7 +973,7 @@ fn test_old_admin_cannot_create_pool_after_transfer_but_new_admin_can() { address: &t.admin, invoke: &old_invoke, }]) - .try_create_pool(&old_asset, &1_728_000u128, &2u32, &10u64); + .try_create_pool(&old_asset, &1_728_000u128, &2u32, &10u64, &0i128); assert!( old_result.is_err(), @@ -925,7 +982,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_args = (&new_asset, 3_456_000u128, 2u32, 20u64).into_val(&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, fn_name: "create_pool", @@ -938,44 +995,37 @@ fn test_old_admin_cannot_create_pool_after_transfer_but_new_admin_can() { address: &new_admin, invoke: &new_invoke, }]) - .create_pool(&new_asset, &3_456_000u128, &2u32, &20u64); + .create_pool(&new_asset, &3_456_000u128, &2u32, &20u64, &0i128); assert_eq!(new_id, 0); -assert_eq!(t.client.pool_count(), 1); + assert_eq!(t.client.pool_count(), 1); } #[test] -fn test_create_pool_deploys_uninitialized_real_farming_pool() { - // Root-cause regression test: - // factory::create_pool deploys the farming-pool WASM with init args `()` and - // never calls FarmingPool::initialize, so any initialized-only entrypoint - // must return PoolError::NotInitialized. - +fn test_create_pool_initializes_real_farming_pool_atomically() { + // Regression test for #79/#80: create_pool must call the deployed pool's + // own `initialize` in the same transaction as the deploy, so there is no + // window where an uninitialized pool address is observable on-chain. + // + // NOTE: We cannot reuse `MOCK_POOL_WASM` here, because that stub contract does + // not implement FarmingPool entrypoints. let env = Env::default(); env.mock_all_auths(); let admin = Address::generate(&env); let factory_addr = env.register(Factory, ()); - // Upload the mock WASM hash matching the real farming-pool contract WASM. - // This test purposefully asserts that the deployed pool is *not* initialized. - // - // NOTE: We cannot reuse `MOCK_POOL_WASM` here, because that stub contract does - // not implement FarmingPool entrypoints. let wasm_hash = env.deployer().upload_contract_wasm(farming_pool::WASM); let client = FactoryClient::new(&env, &factory_addr); client.initialize(&admin, &wasm_hash); let asset = Address::generate(&env); - let pool_id = client.create_pool(&asset, &100u128, &10u64); + let pool_id = client.create_pool(&asset, &1_728_000u128, &2u32, &10u64, &0i128); let record = client.get_pool(&pool_id); let pool_client = FarmingPoolClient::new(&env, &record.address); - // Any initialized-only function should fail with NotInitialized. - // `stake` also requires the caller to be auth'd; env.mock_all_auths() satisfies that. - let user = Address::generate(&env); - let res = pool_client.try_stake(&user, &1_000i128); - assert_eq!(res, Err(Ok(PoolContractError::NotInitialized))); + // The pool must already be initialized: an initialized-only getter must + // succeed and return the factory's admin, not panic with NotInitialized. + assert_eq!(pool_client.admin(), admin); } - diff --git a/soroban/contracts/factory/src/types.rs b/soroban/contracts/factory/src/types.rs index c24f234..2fdfde0 100644 --- a/soroban/contracts/factory/src/types.rs +++ b/soroban/contracts/factory/src/types.rs @@ -68,8 +68,6 @@ pub enum FactoryError { /// also by the read-only getters that would otherwise report a misleading /// empty registry for a factory that does not exist yet. NotInitialized = 4, - /// `create_pool`'s `global_multiplier` was < 1 (mirrors `FarmingPool::initialize`'s own check). - InvalidGlobalMultiplier = 4, /// `create_pool`'s `daily_rate` converts to a `credit_rate` of zero (or doesn't fit `i128`). /// /// `FarmingPool::initialize` requires `credit_rate > 0`; a `daily_rate` below @@ -79,8 +77,8 @@ pub enum FactoryError { InvalidCreditRate = 5, /// `create_pool`'s `min_lock_period` does not fit in the pool's native `u32`. MinLockPeriodOutOfRange = 6, + /// `create_pool`'s `global_multiplier` was < 1 (mirrors `FarmingPool::initialize`'s own check). + InvalidGlobalMultiplier = 7, /// `create_pool` cannot allocate another monotonically increasing pool ID. PoolCountOverflow = 8, - /// A function requiring initialization was called on an uninitialized factory. - NotInitialized = 7, } diff --git a/soroban/contracts/factory/tests/factory_pool_integration.rs b/soroban/contracts/factory/tests/factory_pool_integration.rs index 1db43cd..3f16463 100644 --- a/soroban/contracts/factory/tests/factory_pool_integration.rs +++ b/soroban/contracts/factory/tests/factory_pool_integration.rs @@ -96,9 +96,13 @@ fn deploy_pool_via_factory( let factory_client = FactoryClient::new(env, &factory_addr); factory_client.initialize(admin, &wasm_hash); - let pool_id = - factory_client.create_pool(asset, &daily_rate, &global_multiplier, &min_lock_period); - let pool_id = factory_client.create_pool(asset, &daily_rate, &global_multiplier, &min_lock_period); + let pool_id = factory_client.create_pool( + asset, + &daily_rate, + &global_multiplier, + &min_lock_period, + &0i128, + ); let record = factory_client.get_pool(&pool_id); let pool_address = record.address.clone(); @@ -124,8 +128,7 @@ fn smoke_create_pool_returns_live_pool_address() { let factory_client = FactoryClient::new(&env, &factory_addr); factory_client.initialize(&admin, &wasm_hash); - let pool_id = factory_client.create_pool(&asset, &1_728_000u128, &2u32, &10u64); - let pool_id = factory_client.create_pool(&asset, &17_280u128, &1u32, &10u64); + let pool_id = factory_client.create_pool(&asset, &17_280u128, &1u32, &10u64, &0i128); let record = factory_client.get_pool(&pool_id); // The deployed pool exists at a real address the factory tracked, and it @@ -154,7 +157,7 @@ fn test_create_pool_pool_admin_is_set_atomically() { let factory_client = FactoryClient::new(&env, &factory_addr); factory_client.initialize(&admin, &wasm_hash); - let pool_id = factory_client.create_pool(&asset, &17_280u128, &1u32, &10u64); + let pool_id = factory_client.create_pool(&asset, &17_280u128, &1u32, &10u64, &0i128); let record = factory_client.get_pool(&pool_id); let pool_client = FarmingPoolClient::new(&env, &record.address); @@ -182,11 +185,11 @@ fn test_third_party_cannot_reinitialize_deployed_pool() { let factory_client = FactoryClient::new(&env, &factory_addr); factory_client.initialize(&admin, &wasm_hash); - let pool_id = factory_client.create_pool(&asset, &17_280u128, &1u32, &10u64); + let pool_id = factory_client.create_pool(&asset, &17_280u128, &1u32, &10u64, &0i128); let record = factory_client.get_pool(&pool_id); let pool_client = FarmingPoolClient::new(&env, &record.address); - let result = pool_client.try_initialize(&attacker, &asset, &1u32, &1i128, &10u32); + let result = pool_client.try_initialize(&attacker, &asset, &1u32, &1i128, &10u32, &1i128); assert_eq!(result, Err(Ok(PoolError::AlreadyInitialized))); // The rejected re-initialize attempt must not have changed anything. @@ -228,10 +231,6 @@ fn end_to_end_create_pool_then_stake_and_unstake() { &admin, &asset.address(), daily_rate, - global_multiplier, - min_lock_period as u64, - ); - 1u32, min_lock_period as u64, ); @@ -309,7 +308,6 @@ fn end_to_end_create_pool_then_lock_and_unlock() { token_sac.mint(&user, &INITIAL_MINT); let global_multiplier = 1u32; - let daily_rate = 34_560u128; // global_multiplier is passed as 1 at creation — this test only // exercises lock/unlock, which isn't affected by it. let credit_rate = 2i128; @@ -328,10 +326,6 @@ fn end_to_end_create_pool_then_lock_and_unlock() { min_lock_period_ledgers as u64, ); - 1u32, - min_lock_period_ledgers as u64, - ); - // create_pool (#79) already initialized the pool atomically with the // factory's hardcoded defaults (global_multiplier=1, credit_rate=1); // reconfigure to this test's values via the pool's own admin setters diff --git a/soroban/contracts/factory/tests/fixtures/farming_pool.wasm b/soroban/contracts/factory/tests/fixtures/farming_pool.wasm index 8352463..d15716a 100755 Binary files a/soroban/contracts/factory/tests/fixtures/farming_pool.wasm and b/soroban/contracts/factory/tests/fixtures/farming_pool.wasm differ diff --git a/soroban/contracts/farming-pool/Cargo.toml b/soroban/contracts/farming-pool/Cargo.toml index f83e21b..bc322d0 100644 --- a/soroban/contracts/farming-pool/Cargo.toml +++ b/soroban/contracts/farming-pool/Cargo.toml @@ -15,3 +15,6 @@ testutils = ["soroban-sdk/testutils"] [dependencies] soroban-sdk = { workspace = true } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } diff --git a/soroban/contracts/farming-pool/src/lib.rs b/soroban/contracts/farming-pool/src/lib.rs index 44cd3ee..132d0db 100644 --- a/soroban/contracts/farming-pool/src/lib.rs +++ b/soroban/contracts/farming-pool/src/lib.rs @@ -5,10 +5,7 @@ mod mock_reentrant_token; mod types; -use soroban_sdk::{contract, contractimpl, symbol_short, token, Address, Env, Vec}; -use soroban_sdk::{contract, contractimpl, symbol_short, token, Address, Env}; -use types::{BoostConfig, DataKey, PoolError, Position, UserStake}; -use soroban_sdk::{contract, contractimpl, symbol_short, token, Address, BytesN, Env}; +use soroban_sdk::{contract, contractimpl, symbol_short, token, Address, BytesN, Env, Vec}; pub use types::PoolError; use types::{BoostConfig, DataKey, Position, UserStake}; @@ -18,17 +15,31 @@ use types::{BoostConfig, DataKey, Position, UserStake}; // Gated behind `testutils` feature (enabled by factory's dev-dependency) so it // is never included in on-chain release builds. #[cfg(any(test, feature = "testutils"))] -pub const WASM: &[u8] = soroban_sdk::contractfile!( - file = concat!( - env!("CARGO_MANIFEST_DIR"), - "/../target/wasm32v1-none/release/farming_pool.wasm" - ), -); +mod wasm_import { + // Scoped in its own module (per contractimport!'s documented pattern) so + // its generated `WASM`/`Client` items don't collide with this crate's + // own `#[contract]`-generated names. No sha256 pinning here — unlike + // contractfile!, contractimport! doesn't require it, which matters + // because this WASM is our own sibling crate's freshly-built output on + // every CI run, not a fixed external artifact: Rust/LLVM codegen isn't + // bit-for-bit reproducible across separate `cargo build` invocations of + // the same source, so a hardcoded hash here would break intermittently + // for reasons unrelated to any real content change. + soroban_sdk::contractimport!(file = "../../target/wasm32v1-none/release/farming_pool.wasm"); +} +#[cfg(any(test, feature = "testutils"))] +pub use wasm_import::WASM; // Persistent-storage TTL: extend to ~60 days if below ~30 days (at ~5s/ledger). const USER_TTL_THRESHOLD: u32 = 518_400; const USER_TTL_EXTEND_TO: u32 = 1_036_800; +/// Current contract schema version, written by `initialize`/`migrate`. A pool +/// deployed before `SchemaVersion` was tracked at all reads back `SCHEMA_VERSION` +/// via `read_schema_version`'s `unwrap_or`, so it's treated as already current +/// rather than needing a migration. +const SCHEMA_VERSION: u32 = 1; + /// Sanity ceilings on `global_multiplier` and `credit_rate` (see #89). /// /// `compute_credits` computes @@ -320,6 +331,8 @@ impl FarmingPool { env.storage() .instance() .set(&DataKey::MinStakeAmount, &min_stake_amount); + env.storage() + .instance() .set(&DataKey::SchemaVersion, &SCHEMA_VERSION); bump_instance(&env); Ok(()) @@ -327,15 +340,13 @@ impl FarmingPool { pub fn admin(env: Env) -> Result { bump_instance(&env); - get_admin(&env).unwrap() + get_admin(&env) } /// Admin: transfer admin rights to `new_admin`. Current admin must authorise. /// /// Supports key rotation and governance handoffs without redeploying the pool. /// Emits a `("pool", "adm_xfr")` event with `(old_admin, new_admin)`. - pub fn transfer_admin(env: Env, new_admin: Address) { - let current = get_admin(&env).unwrap(); pub fn transfer_admin(env: Env, new_admin: Address) -> Result<(), PoolError> { let current = get_admin(&env)?; current.require_auth(); @@ -389,7 +400,8 @@ impl FarmingPool { if whitelist_enabled(&env) && !is_user_whitelisted(&env, &user) { return Err(PoolError::NotWhitelisted); - let min_stake = Self::get_min_stake_amount(env.clone()).unwrap(); + } + let min_stake = Self::get_min_stake_amount(env.clone())?; if amount < min_stake { return Err(PoolError::BelowMinimumStake); } @@ -492,9 +504,6 @@ impl FarmingPool { let elapsed = env .ledger() .sequence() - .saturating_sub(pos.checkpoint_ledger); - pos.total_credits + pos.amount * rate * elapsed as i128; - Ok(pos.total_credits + pos.amount * rate * elapsed as i128) .saturating_sub(position.checkpoint_ledger); Ok(position.total_credits + position.amount * position.credit_rate * elapsed as i128) } @@ -532,7 +541,6 @@ impl FarmingPool { } pub fn emergency_withdraw(env: Env, user: Address) -> Result { - get_admin(&env).unwrap().require_auth(); require_initialized(&env)?; let admin = get_admin(&env)?; admin.require_auth(); @@ -541,9 +549,6 @@ impl FarmingPool { } bump_instance(&env); - let mut total_returned: i128 = 0; - let mut banked_credits: i128 = 0; - let token = token::TokenClient::new(&env, &get_stake_token(&env).unwrap()); let mut total_returned = 0i128; let mut banked_credits = 0i128; let stake_token = get_stake_token(&env)?; @@ -595,7 +600,9 @@ impl FarmingPool { require_initialized(&env)?; get_admin(&env)?.require_auth(); bump_instance(&env); - env.storage().instance().set(&DataKey::WhitelistEnabled, &true); + env.storage() + .instance() + .set(&DataKey::WhitelistEnabled, &true); Ok(()) } @@ -604,7 +611,9 @@ impl FarmingPool { require_initialized(&env)?; get_admin(&env)?.require_auth(); bump_instance(&env); - env.storage().instance().set(&DataKey::WhitelistEnabled, &false); + env.storage() + .instance() + .set(&DataKey::WhitelistEnabled, &false); Ok(()) } @@ -664,7 +673,8 @@ impl FarmingPool { if whitelist_enabled(&env) && !is_user_whitelisted(&env, &from) { return Err(PoolError::NotWhitelisted); - let min_stake = Self::get_min_stake_amount(env.clone()).unwrap(); + } + let min_stake = Self::get_min_stake_amount(env.clone())?; if amount < min_stake { return Err(PoolError::BelowMinimumStake); } @@ -850,8 +860,6 @@ impl FarmingPool { let allocation_pct = get_user_boost(&env, &user).unwrap_or(0); let multiplier = read_global_multiplier(&env); let elapsed = env.ledger().sequence().saturating_sub(stake.start_ledger); - stake.credits_banked - + compute_credits(stake.amount, allocation_pct, multiplier, rate, elapsed); Ok(stake.credits_banked + compute_credits( stake.amount, @@ -874,16 +882,17 @@ impl FarmingPool { Ok(()) } /// Return the current min stake amount , or `None` if not staked. - pub fn get_min_stake_amount(env: Env) -> Result { + pub fn get_min_stake_amount(env: Env) -> Result { require_initialized(&env)?; - let min_stake = env.storage().instance() + let min_stake = env + .storage() + .instance() .get::(&DataKey::MinStakeAmount) .unwrap_or(1); Ok(min_stake) } - /// Return the current stake record for `user`, or `None` if not staked. pub fn get_stake(env: Env, user: Address) -> Result, PoolError> { require_initialized(&env)?; diff --git a/soroban/contracts/farming-pool/src/test.rs b/soroban/contracts/farming-pool/src/test.rs index f197faa..14d42d9 100644 --- a/soroban/contracts/farming-pool/src/test.rs +++ b/soroban/contracts/farming-pool/src/test.rs @@ -103,7 +103,7 @@ fn setup_with_lock_period( &global_multiplier, &credit_rate, &min_lock_period, - &min_stake_amount + &min_stake_amount, ); let token = TokenClient::new(&env, &asset.address()); @@ -530,9 +530,9 @@ fn test_credit_rate_change_does_not_retroactively_alter_staked_credits() { t.client.set_credit_rate(&3i128); assert_eq!(t.client.get_credits(&t.user), 10_000); - t.client.stake(&t.user, &1); // checkpoints under the old rate + t.client.stake(&t.user, &100); // checkpoints under the old rate (top-up must clear min_stake_amount) advance_ledgers(&t.env, 5); - assert_eq!(t.client.get_credits(&t.user), 25_015); + assert_eq!(t.client.get_credits(&t.user), 26_500); } #[test] @@ -544,9 +544,9 @@ fn test_credit_rate_change_does_not_retroactively_alter_locked_credits() { t.client.set_credit_rate(&3i128); assert_eq!(t.client.calculate_credits(&t.user), 10_000); - t.client.lock_assets(&t.user, &1); // checkpoints under the old rate + t.client.lock_assets(&t.user, &100); // checkpoints under the old rate (top-up must clear min_stake_amount) advance_ledgers(&t.env, 5); - assert_eq!(t.client.calculate_credits(&t.user), 25_015); + assert_eq!(t.client.calculate_credits(&t.user), 26_500); } #[test] @@ -618,6 +618,7 @@ fn test_initialize_rejects_global_multiplier_above_ceiling() { &(MAX_GLOBAL_MULTIPLIER + 1), &1i128, &0u32, + &1i128, ); assert!(matches!( result, @@ -641,6 +642,7 @@ fn test_initialize_rejects_credit_rate_above_ceiling() { &2u32, &(MAX_CREDIT_RATE + 1), &0u32, + &1i128, ); assert!(matches!(result, Err(Ok(PoolError::InvalidCreditRate)))); } @@ -679,6 +681,7 @@ fn test_compute_credits_no_overflow_at_ceilings() { &MAX_GLOBAL_MULTIPLIER, &MAX_CREDIT_RATE, &0u32, + &1i128, ); client.stake(&user, &AMOUNT_MAX); @@ -942,7 +945,7 @@ fn test_unlock_assets_full_returns_tokens_and_credits() { assert_eq!(t.token.balance(&t.user), initial_balance); assert_eq!(t.token.balance(&t.contract_id), 0); assert!(t.client.get_user_position(&t.user).is_none()); - assert_eq!(t.client.calculate_credits(&t.user).unwrap(), 0); + assert_eq!(t.client.calculate_credits(&t.user), 0); } #[test] @@ -951,8 +954,6 @@ fn test_unlock_assets_partial_keeps_remaining_position() { let initial_balance = t.token.balance(&t.user); t.client.lock_assets(&t.user, &500); advance_ledgers(&t.env, 10); - t.client.unlock_assets(&t.user, &400); // partial unlock - t.client.unlock_assets(&t.user, &200); // partial unlock let pos = t @@ -1025,7 +1026,7 @@ fn test_unlock_allowed_after_min_lock_period() { let t = setup_with_lock_period(1, 1, 100); t.client.lock_assets(&t.user, &1_000); advance_ledgers(&t.env, 100); // Should succeed at exactly the boundary. - // Should succeed — no panic. + // Should succeed — no panic. t.client.unlock_assets(&t.user, &1_000); assert!(t.client.get_user_position(&t.user).is_none()); } @@ -1070,7 +1071,7 @@ fn test_new_positions_use_updated_min_lock_period() { #[test] fn test_calculate_credits_zero_without_position() { let t = setup(1, 1); - assert_eq!(t.client.calculate_credits(&t.user).unwrap(), 0); + assert_eq!(t.client.calculate_credits(&t.user), 0); } #[test] @@ -1079,7 +1080,7 @@ fn test_calculate_credits_accrues_over_time() { let t = setup(1, 2); t.client.lock_assets(&t.user, &500); advance_ledgers(&t.env, 20); - assert_eq!(t.client.calculate_credits(&t.user).unwrap(), 20_000); + assert_eq!(t.client.calculate_credits(&t.user), 20_000); } #[test] @@ -1091,7 +1092,7 @@ fn test_calculate_credits_includes_banked_plus_accruing() { advance_ledgers(&t.env, 10); t.client.lock_assets(&t.user, &500); // banks 10000 advance_ledgers(&t.env, 10); - assert_eq!(t.client.calculate_credits(&t.user).unwrap(), 25_000); + assert_eq!(t.client.calculate_credits(&t.user), 25_000); } #[test] @@ -1103,7 +1104,7 @@ fn test_calculate_credits_reflects_partial_unlock_checkpoint() { advance_ledgers(&t.env, 10); t.client.unlock_assets(&t.user, &400); // banks 10000 into pos.total_credits advance_ledgers(&t.env, 5); - assert_eq!(t.client.calculate_credits(&t.user).unwrap(), 13_000); + assert_eq!(t.client.calculate_credits(&t.user), 13_000); } // ── get_user_position tests ─────────────────────────────────────────────────── @@ -1111,7 +1112,7 @@ fn test_calculate_credits_reflects_partial_unlock_checkpoint() { #[test] fn test_get_user_position_none_before_lock() { let t = setup(1, 1); - assert!(t.client.get_user_position(&t.user).unwrap().is_none()); + assert!(t.client.get_user_position(&t.user).is_none()); } #[test] @@ -1119,7 +1120,7 @@ fn test_get_user_position_returns_correct_fields() { let t = setup(1, 1); let start = t.env.ledger().sequence(); t.client.lock_assets(&t.user, &750); - let pos = t.client.get_user_position(&t.user).unwrap().unwrap(); + let pos = t.client.get_user_position(&t.user).unwrap(); assert_eq!(pos.amount, 750); assert_eq!(pos.lock_ledger, start); assert_eq!(pos.unlock_ledger, start); @@ -1134,7 +1135,7 @@ fn test_get_user_position_none_after_full_unlock() { t.client.lock_assets(&t.user, &1_000); advance_ledgers(&t.env, 5); t.client.unlock_assets(&t.user, &1_000); - assert!(t.client.get_user_position(&t.user).unwrap().is_none()); + assert!(t.client.get_user_position(&t.user).is_none()); } // ── pause / unpause tests ───────────────────────────────────────────────────── @@ -1142,14 +1143,14 @@ fn test_get_user_position_none_after_full_unlock() { #[test] fn test_pool_not_paused_initially() { let t = setup(1, 1); - assert!(!t.client.is_paused().unwrap()); + assert!(!t.client.is_paused()); } #[test] fn test_pause_blocks_lock_assets() { let t = setup(1, 1); t.client.pause(); - assert!(t.client.is_paused().unwrap()); + assert!(t.client.is_paused()); assert!(t.client.try_lock_assets(&t.user, &100i128).is_err()); assert!(t.client.is_paused()); @@ -1176,7 +1177,7 @@ fn test_unpause_restores_operations() { let t = setup(1, 1); t.client.pause(); t.client.unpause(); - assert!(!t.client.is_paused().unwrap()); + assert!(!t.client.is_paused()); // Lock and unlock should work again. t.client.lock_assets(&t.user, &500); t.client.unlock_assets(&t.user, &500); @@ -1220,7 +1221,7 @@ fn test_unpause_restores_stake() { t.client.pause(); t.client.unpause(); t.client.stake(&t.user, &500); - assert_eq!(t.client.get_stake(&t.user).unwrap().unwrap().amount, 500); + assert_eq!(t.client.get_stake(&t.user).unwrap().amount, 500); } #[test] @@ -1238,7 +1239,7 @@ fn test_unpause_restores_unstake() { t.client.pause(); t.client.unpause(); t.client.unstake(&t.user); - assert!(t.client.get_stake(&t.user).unwrap().is_none()); + assert!(t.client.get_stake(&t.user).is_none()); } #[test] @@ -1257,7 +1258,7 @@ fn test_unpause_restores_set_boost() { t.client.unpause(); t.client.set_boost(&t.user, &50u32); assert_eq!( - t.client.get_boost_config(&t.user).unwrap().unwrap().allocation_pct, + t.client.get_boost_config(&t.user).unwrap().allocation_pct, 50 ); } @@ -1269,7 +1270,7 @@ fn test_set_global_multiplier_callable_while_paused() { t.client.set_boost(&t.user, &50u32); t.client.pause(); t.client.set_global_multiplier(&3u32); - assert_eq!(t.client.get_boost_config(&t.user).unwrap().unwrap().multiplier, 3); + assert_eq!(t.client.get_boost_config(&t.user).unwrap().multiplier, 3); } // ── multi-user isolation ────────────────────────────────────────────────────── @@ -1283,8 +1284,8 @@ fn test_multiple_users_independent_positions() { t.client.lock_assets(&user2, &2_000); advance_ledgers(&t.env, 10); // Each user's credits are independent. - assert_eq!(t.client.calculate_credits(&t.user).unwrap(), 10_000); // 1000 * 10 - assert_eq!(t.client.calculate_credits(&user2).unwrap(), 20_000); // 2000 * 10 + assert_eq!(t.client.calculate_credits(&t.user), 10_000); // 1000 * 10 + assert_eq!(t.client.calculate_credits(&user2), 20_000); // 2000 * 10 } #[test] @@ -1300,7 +1301,6 @@ fn test_one_user_unlock_does_not_affect_another() { let pos2 = t .client .get_user_position(&user2) - .unwrap() .expect("user2 position should exist"); assert_eq!(pos2.amount, 2_000); } @@ -1351,13 +1351,13 @@ fn test_whitelist_disabled_by_default_allows_all() { let t = setup(2, 1); // User can stake when whitelist is not enabled (disabled by default) t.client.stake(&t.user, &1_000); - assert_eq!(t.client.get_stake(&t.user).unwrap().unwrap().amount, 1_000); + assert_eq!(t.client.get_stake(&t.user).unwrap().amount, 1_000); // User can lock when whitelist is not enabled let user2 = Address::generate(&t.env); t.token_sac.mint(&user2, &500_000i128); t.client.lock_assets(&user2, &500); - assert_eq!(t.client.get_user_position(&user2).unwrap().unwrap().amount, 500); + assert_eq!(t.client.get_user_position(&user2).unwrap().amount, 500); } #[test] @@ -1389,8 +1389,8 @@ fn test_user_added_and_removed_from_whitelist() { // Now user can stake and lock t.client.stake(&t.user, &1_000); t.client.lock_assets(&t.user, &500); - assert_eq!(t.client.get_stake(&t.user).unwrap().unwrap().amount, 1_000); - assert_eq!(t.client.get_user_position(&t.user).unwrap().unwrap().amount, 500); + assert_eq!(t.client.get_stake(&t.user).unwrap().amount, 1_000); + assert_eq!(t.client.get_user_position(&t.user).unwrap().amount, 500); // Remove user from whitelist t.client.remove_from_whitelist(&t.user); @@ -1414,7 +1414,7 @@ fn test_disable_whitelist_restores_open_access() { // Stake succeeds now t.client.stake(&t.user, &1_000); - assert_eq!(t.client.get_stake(&t.user).unwrap().unwrap().amount, 1_000); + assert_eq!(t.client.get_stake(&t.user).unwrap().amount, 1_000); } #[test] @@ -1446,9 +1446,9 @@ fn test_batch_add_to_whitelist() { t.client.stake(&user2, &100); t.client.stake(&user3, &100); - assert_eq!(t.client.get_stake(&user1).unwrap().unwrap().amount, 100); - assert_eq!(t.client.get_stake(&user2).unwrap().unwrap().amount, 100); - assert_eq!(t.client.get_stake(&user3).unwrap().unwrap().amount, 100); + assert_eq!(t.client.get_stake(&user1).unwrap().amount, 100); + assert_eq!(t.client.get_stake(&user2).unwrap().amount, 100); + assert_eq!(t.client.get_stake(&user3).unwrap().amount, 100); } #[test] @@ -1460,9 +1460,10 @@ fn test_batch_add_to_whitelist_exceeds_limit() { users.push_back(Address::generate(&t.env)); } t.client.batch_add_to_whitelist(&users); +} #[test] -#[should_panic(expected = "Error(Contract, #15)")] +#[should_panic(expected = "Error(Contract, #6)")] fn test_set_min_stake_amount_lock_assets() { let t = setup(1, 1); @@ -1470,7 +1471,7 @@ fn test_set_min_stake_amount_lock_assets() { } #[test] -#[should_panic(expected = "Error(Contract, #15)")] +#[should_panic(expected = "Error(Contract, #6)")] fn test_set_min_stake_amount_stake() { let t = setup(1, 1); @@ -1484,7 +1485,6 @@ fn test_set_min_stake_amount_lock_assets_pass() { t.client.lock_assets(&t.user, &100i128); } - #[test] fn test_set_min_stake_amount() { let t = setup(1, 1); @@ -1533,7 +1533,7 @@ fn test_lock_assets_reentrant_transfer_is_rejected_and_final_state_is_correct() let token_client = MockReentrantTokenClient::new(&env, &token_id); token_client.configure(&farming_pool_id, &user); - client.initialize(&admin, &token_id, &2u32, &100i128, &0u32); + client.initialize(&admin, &token_id, &2u32, &100i128, &0u32, &0i128); // Succeeds fully: the mock token catches the rejected reentry gracefully // (via try_invoke_contract) rather than trapping the whole call. @@ -1564,7 +1564,7 @@ fn test_lock_assets_reverts_entirely_if_stake_token_naively_reenters() { let token_client = MockNaiveReentrantTokenClient::new(&env, &token_id); token_client.configure(&farming_pool_id, &user); - client.initialize(&admin, &token_id, &2u32, &100i128, &0u32); + client.initialize(&admin, &token_id, &2u32, &100i128, &0u32, &0i128); // The naive mock token doesn't catch the host's rejection, so the // reentrant call traps — and with it, the entire lock_assets invocation, diff --git a/soroban/contracts/farming-pool/src/types.rs b/soroban/contracts/farming-pool/src/types.rs index c119cfe..0dc0a89 100644 --- a/soroban/contracts/farming-pool/src/types.rs +++ b/soroban/contracts/farming-pool/src/types.rs @@ -7,19 +7,16 @@ use soroban_sdk::{contracterror, contracttype, Address}; pub enum PoolError { AlreadyInitialized = 1, NotInitialized = 2, - /// Returned by `emergency_withdraw` when the pool is not currently paused. - NotPaused = 13, - /// Returned by `emergency_withdraw` when the user has no stake or locked position. - NoActiveStake = 14, - BelowMinimumStake = 15 InvalidCreditRate = 3, - NotPaused = 13, - Paused = 20, - NoActiveStake = 14, - NotWhitelisted = 15, /// `global_multiplier` was 0 or exceeded `MAX_GLOBAL_MULTIPLIER`. See #89. - InvalidGlobalMultiplier = 15, - + InvalidGlobalMultiplier = 4, + NotWhitelisted = 5, + BelowMinimumStake = 6, + /// Returned by `emergency_withdraw` when the pool is not currently paused. + NotPaused = 7, + /// Returned by `emergency_withdraw` when the user has no stake or locked position. + NoActiveStake = 8, + Paused = 9, } /// Per-user boost configuration returned by `get_boost_config`.