diff --git a/chain-extensions/src/tests.rs b/chain-extensions/src/tests.rs index 16619389bf..886f722aa7 100644 --- a/chain-extensions/src/tests.rs +++ b/chain-extensions/src/tests.rs @@ -1297,15 +1297,26 @@ fn add_stake_recycle_rollback_on_recycle_failure() { let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); - // Set up very low reserves so recycle will fail with InsufficientLiquidity + mock::register_ok_neuron(netuid, hotkey, coldkey, 0); + pallet_subtensor::Pallet::::insert_lock_state( + &coldkey, + netuid, + &hotkey, + pallet_subtensor::staking::lock::LockState { + locked_mass: AlphaBalance::from(u64::MAX / 4), + conviction: U64F64::saturating_from_num(0), + last_update: pallet_subtensor::Pallet::::get_current_block_as_u64(), + }, + ); + + // Leave enough input-side liquidity for add_stake to pass the 1000x swap input cap. + // The lock above makes the recycle leg fail, exercising atomic rollback. mock::setup_reserves( netuid, - TaoBalance::from(1_000_u64), + TaoBalance::from(tao_amount_raw / 1000 + 1), AlphaBalance::from(1_000_u64), ); - mock::register_ok_neuron(netuid, hotkey, coldkey, 0); - add_balance_to_coldkey_account( &coldkey, TaoBalance::from(tao_amount_raw.saturating_add(1_000_000_000)), @@ -1368,15 +1379,26 @@ fn add_stake_burn_rollback_on_burn_failure() { let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); - // Set up very low reserves so burn will fail with InsufficientLiquidity + mock::register_ok_neuron(netuid, hotkey, coldkey, 0); + pallet_subtensor::Pallet::::insert_lock_state( + &coldkey, + netuid, + &hotkey, + pallet_subtensor::staking::lock::LockState { + locked_mass: AlphaBalance::from(u64::MAX / 4), + conviction: U64F64::saturating_from_num(0), + last_update: pallet_subtensor::Pallet::::get_current_block_as_u64(), + }, + ); + + // Leave enough input-side liquidity for add_stake to pass the 1000x swap input cap. + // The lock above makes the burn leg fail, exercising atomic rollback. mock::setup_reserves( netuid, - TaoBalance::from(1_000_u64), + TaoBalance::from(tao_amount_raw / 1000 + 1), AlphaBalance::from(1_000_u64), ); - mock::register_ok_neuron(netuid, hotkey, coldkey, 0); - add_balance_to_coldkey_account( &coldkey, TaoBalance::from(tao_amount_raw.saturating_add(1_000_000_000)), diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index 33cadf241b..d2bad04787 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -48,6 +48,8 @@ impl Pallet { "do_add_stake( origin:{coldkey:?} hotkey:{hotkey:?}, netuid:{netuid:?}, stake_to_be_added:{stake_to_be_added:?} )" ); + Self::ensure_add_stake_input_within_swap_limit(netuid, stake_to_be_added)?; + // 2. Validate user input Self::validate_add_stake( &coldkey, @@ -124,6 +126,8 @@ impl Pallet { "do_add_stake( origin:{coldkey:?} hotkey:{hotkey:?}, netuid:{netuid:?}, stake_to_be_added:{stake_to_be_added:?} )" ); + Self::ensure_add_stake_input_within_swap_limit(netuid, stake_to_be_added)?; + // 2. Calculate the maximum amount that can be executed with price limit let max_amount: TaoBalance = Self::get_max_amount_add(netuid, limit_price)?.into(); let mut possible_stake = stake_to_be_added; @@ -175,11 +179,27 @@ impl Pallet { } } - // Use reverting swap to estimate max limit amount - let order = GetAlphaForTao::::with_amount(u64::MAX); + // Use the largest supported input instead of probing the swap path with u64::MAX. + let max_supported_input = SubnetTAO::::get(netuid).saturating_mul(1_000.into()); + let order = GetAlphaForTao::::with_amount(max_supported_input); let result = T::SwapInterface::swap(netuid.into(), order, limit_price, false, true) .map(|r| r.amount_paid_in.saturating_add(r.fee_paid))?; Ok(result.into()) } + + fn ensure_add_stake_input_within_swap_limit( + netuid: NetUid, + amount: TaoBalance, + ) -> Result<(), Error> { + if !netuid.is_root() && SubnetMechanism::::get(netuid) == 1 { + let max_supported_input = SubnetTAO::::get(netuid).saturating_mul(1_000.into()); + ensure!( + amount <= max_supported_input, + Error::::InsufficientLiquidity + ); + } + + Ok(()) + } } diff --git a/pallets/subtensor/src/staking/recycle_alpha.rs b/pallets/subtensor/src/staking/recycle_alpha.rs index 7152c48cdb..aa0eced405 100644 --- a/pallets/subtensor/src/staking/recycle_alpha.rs +++ b/pallets/subtensor/src/staking/recycle_alpha.rs @@ -1,5 +1,6 @@ use super::*; use crate::{Error, system::ensure_signed}; +use frame_support::storage::{TransactionOutcome, with_transaction}; use subtensor_runtime_common::{AlphaBalance, NetUid}; impl Pallet { @@ -132,22 +133,38 @@ impl Pallet { amount: TaoBalance, limit: Option, ) -> DispatchResult { - let alpha = if let Some(limit) = limit { - Self::do_add_stake_limit(origin.clone(), hotkey.clone(), netuid, amount, limit, false)? - } else { - Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)? - }; - - Self::do_burn_alpha(origin, hotkey.clone(), alpha, netuid)?; - - Self::deposit_event(Event::AddStakeBurn { - netuid, - hotkey, - amount, - alpha, - }); - - Ok(()) + with_transaction(|| { + let result = (|| { + let alpha = if let Some(limit) = limit { + Self::do_add_stake_limit( + origin.clone(), + hotkey.clone(), + netuid, + amount, + limit, + false, + )? + } else { + Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)? + }; + + Self::do_burn_alpha(origin, hotkey.clone(), alpha, netuid)?; + + Self::deposit_event(Event::AddStakeBurn { + netuid, + hotkey, + amount, + alpha, + }); + + Ok(()) + })(); + + match result { + Ok(()) => TransactionOutcome::Commit(Ok(())), + Err(err) => TransactionOutcome::Rollback(Err(err)), + } + }) } /// Atomically stakes TAO and recycles the resulting alpha. @@ -160,8 +177,17 @@ impl Pallet { netuid: NetUid, amount: TaoBalance, ) -> Result { - let alpha = Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)?; - Self::do_recycle_alpha(origin, hotkey, alpha, netuid) + with_transaction(|| { + let result = (|| { + let alpha = Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)?; + Self::do_recycle_alpha(origin, hotkey, alpha, netuid) + })(); + + match result { + Ok(alpha) => TransactionOutcome::Commit(Ok(alpha)), + Err(err) => TransactionOutcome::Rollback(Err(err)), + } + }) } /// Atomically stakes TAO and burns the resulting alpha. Permissionless @@ -173,7 +199,16 @@ impl Pallet { netuid: NetUid, amount: TaoBalance, ) -> Result { - let alpha = Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)?; - Self::do_burn_alpha(origin, hotkey, alpha, netuid) + with_transaction(|| { + let result = (|| { + let alpha = Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)?; + Self::do_burn_alpha(origin, hotkey, alpha, netuid) + })(); + + match result { + Ok(alpha) => TransactionOutcome::Commit(Ok(alpha)), + Err(err) => TransactionOutcome::Rollback(Err(err)), + } + }) } } diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index cf640dc661..0b460612b9 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -55,6 +55,7 @@ impl Pallet { let alpha_available = Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); let alpha_unstaked = alpha_unstaked.min(alpha_available); + Self::ensure_remove_stake_input_within_swap_limit(netuid, alpha_unstaked)?; // 2. Validate the user input Self::validate_remove_stake( @@ -336,6 +337,8 @@ impl Pallet { "do_remove_stake( origin:{coldkey:?} hotkey:{hotkey:?}, netuid: {netuid:?}, alpha_unstaked:{alpha_unstaked:?} )" ); + Self::ensure_remove_stake_input_within_swap_limit(netuid, alpha_unstaked)?; + // 2. Calculate the maximum amount that can be executed with price limit let max_amount = Self::get_max_amount_remove(netuid, limit_price)?; let mut possible_alpha = alpha_unstaked; @@ -394,14 +397,30 @@ impl Pallet { } } - // Use reverting swap to estimate max limit amount - let order = GetTaoForAlpha::::with_amount(u64::MAX); + // Use the largest supported input instead of probing the swap path with u64::MAX. + let max_supported_input = SubnetAlphaIn::::get(netuid).saturating_mul(1_000.into()); + let order = GetTaoForAlpha::::with_amount(max_supported_input); let result = T::SwapInterface::swap(netuid.into(), order, limit_price.into(), false, true) .map(|r| r.amount_paid_in.saturating_add(r.fee_paid))?; Ok(result) } + fn ensure_remove_stake_input_within_swap_limit( + netuid: NetUid, + amount: AlphaBalance, + ) -> Result<(), Error> { + if !netuid.is_root() && SubnetMechanism::::get(netuid) == 1 { + let max_supported_input = SubnetAlphaIn::::get(netuid).saturating_mul(1_000.into()); + ensure!( + amount <= max_supported_input, + Error::::InsufficientLiquidity + ); + } + + Ok(()) + } + pub fn do_remove_stake_full_limit( origin: OriginFor, hotkey: T::AccountId, diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index 660b6957d7..3d759e200c 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -776,9 +776,9 @@ fn test_add_stake_insufficient_liquidity() { }); } -/// cargo test --package pallet-subtensor --lib -- tests::staking::test_add_stake_insufficient_liquidity_one_side_ok --exact --show-output +/// cargo test --package pallet-subtensor --lib -- tests::staking::test_add_stake_input_reserve_too_low_fails --exact --show-output #[test] -fn test_add_stake_insufficient_liquidity_one_side_ok() { +fn test_add_stake_input_reserve_too_low_fails() { new_test_ext(1).execute_with(|| { let subnet_owner_coldkey = U256::from(1001); let subnet_owner_hotkey = U256::from(1002); @@ -795,13 +795,17 @@ fn test_add_stake_insufficient_liquidity_one_side_ok() { let reserve_tao = u64::from(mock::SwapMinimumReserve::get()) - 1; mock::setup_reserves(netuid, reserve_tao.into(), reserve_alpha.into()); - // Check the error - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(coldkey), - hotkey, - netuid, - amount_staked.into() - )); + // The output-side reserve is sufficient, but the input-side reserve is too small for the + // requested swap under the 1000x input-reserve cap. + assert_noop!( + SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + amount_staked.into() + ), + Error::::InsufficientLiquidity + ); }); } @@ -876,7 +880,7 @@ fn test_remove_stake_insufficient_liquidity() { // Mock more liquidity - remove becomes successful SubnetTAO::::insert(netuid, TaoBalance::from(amount_staked + 1)); - SubnetAlphaIn::::insert(netuid, AlphaBalance::from(1)); + SubnetAlphaIn::::insert(netuid, AlphaBalance::from(alpha.to_u64() / 1000 + 1)); assert_ok!(SubtensorModule::remove_stake( RuntimeOrigin::signed(coldkey), hotkey, @@ -3042,14 +3046,14 @@ fn test_max_amount_remove_dynamic() { pallet_subtensor_swap::Error::::PriceLimitExceeded, )), ), - (10_000_000_000, 10_000_000_000, 0, Ok(u64::MAX)), + (10_000_000_000, 10_000_000_000, 0, Ok(10_000_000_000_000)), // Low bounds (numbers are empirical, it is only important that result // is sharply decreasing when limit price increases) - (1_000, 1_000, 0, Ok(u64::MAX)), - (1_001, 1_001, 0, Ok(u64::MAX)), - (1_001, 1_001, 1, Ok(17_472)), - (1_001, 1_001, 2, Ok(17_472)), - (1_001, 1_001, 1_001, Ok(17_472)), + (1_000, 1_000, 0, Ok(1_000_000)), + (1_001, 1_001, 0, Ok(1_001_000)), + (1_001, 1_001, 1, Ok(1_001_000)), + (1_001, 1_001, 2, Ok(1_001_000)), + (1_001, 1_001, 1_001, Ok(1_001_000)), (1_001, 1_001, 10_000, Ok(17_472)), (1_001, 1_001, 100_000, Ok(17_472)), (1_001, 1_001, 1_000_000, Ok(17_472)), @@ -3065,7 +3069,7 @@ fn test_max_amount_remove_dynamic() { Ok(3_030_000_000_000), ), // Normal range values with edge cases and sanity checks - (200_000_000_000, 100_000_000_000, 0, Ok(u64::MAX)), + (200_000_000_000, 100_000_000_000, 0, Ok(100_000_000_000_000)), ( 200_000_000_000, 100_000_000_000, @@ -3153,10 +3157,13 @@ fn test_max_amount_remove_dynamic() { ), Ok(v) => { let v = AlphaBalance::from(v); - assert_abs_diff_eq!( - SubtensorModule::get_max_amount_remove(netuid, limit_price.into()).unwrap(), - v, - epsilon = v / 100.into() + let actual = + SubtensorModule::get_max_amount_remove(netuid, limit_price.into()).unwrap(); + let epsilon = v / 100.into(); + let diff = actual.max(v).saturating_sub(actual.min(v)); + assert!( + diff <= epsilon, + "max remove mismatch: tao_in={tao_in}, alpha_in={alpha_in:?}, limit_price={limit_price}, actual={actual:?}, expected={v:?}, epsilon={epsilon:?}", ); } } @@ -3413,10 +3420,10 @@ fn test_max_amount_move_dynamic_stable() { // The tests below just mimic the remove_stake_limit tests - // 0 price => max is u64::MAX + // 0 price => max is capped at 1000x input reserve assert_eq!( SubtensorModule::get_max_amount_move(dynamic_netuid, stable_netuid, TaoBalance::ZERO), - Ok(AlphaBalance::MAX) + Ok(alpha_in.saturating_mul(1_000.into())) ); // Low price values don't blow things up @@ -3872,6 +3879,33 @@ fn test_add_stake_limit_fill_or_kill() { }); } +#[test] +fn test_add_stake_limit_rejects_input_over_swap_reserve_cap() { + new_test_ext(1).execute_with(|| { + let hotkey_account_id = U256::from(533454); + let coldkey_account_id = U256::from(55454); + + let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + let tao_reserve = TaoBalance::from(1_000_u64); + mock::setup_reserves(netuid, tao_reserve, AlphaBalance::from(1_000_000_000_u64)); + + let amount = tao_reserve.saturating_mul(1_000.into()) + TaoBalance::from(1_u64); + add_balance_to_coldkey_account(&coldkey_account_id, amount); + + assert_noop!( + SubtensorModule::add_stake_limit( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + amount, + ::SwapInterface::max_price(), + true + ), + Error::::InsufficientLiquidity + ); + }); +} + #[test] fn test_add_stake_limit_partial_zero_max_stake_amount_error() { new_test_ext(1).execute_with(|| { @@ -5248,7 +5282,8 @@ fn test_large_swap() { // add network let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); add_balance_to_coldkey_account(&coldkey, 1_000_000_000_000_000_u64.into()); - let tao = TaoBalance::from(100_000_000u64); + let swap_amount = TaoBalance::from(100_000_000_000_000_u64); + let tao = TaoBalance::from(swap_amount.to_u64() / 1000); let alpha = AlphaBalance::from(1_000_000_000_000_000_u64); SubnetTAO::::insert(netuid, tao); SubnetAlphaIn::::insert(netuid, alpha); @@ -5256,7 +5291,6 @@ fn test_large_swap() { // Force the swap to initialize ::SwapInterface::init_swap(netuid, None); - let swap_amount = TaoBalance::from(100_000_000_000_000_u64); assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(coldkey), owner_hotkey, diff --git a/pallets/swap/src/pallet/balancer.rs b/pallets/swap/src/pallet/balancer.rs index 2ffd04fdba..244e4ff9eb 100644 --- a/pallets/swap/src/pallet/balancer.rs +++ b/pallets/swap/src/pallet/balancer.rs @@ -149,7 +149,7 @@ impl Balancer { let w1: u128 = self.get_base_weight().deconstruct() as u128; let w2: u128 = self.get_quote_weight().deconstruct() as u128; - let precision = 1024; + let precision = 256; let x_safe = SafeInt::from(x); let w1_safe = SafeInt::from(w1); let w2_safe = SafeInt::from(w2); @@ -187,8 +187,13 @@ impl Balancer { if let Some(result_safe_int) = maybe_result_safe_int && let Some(result_u64) = result_safe_int.to_u64() { - return U64F64::saturating_from_num(result_u64) + let result = U64F64::saturating_from_num(result_u64) .safe_div(U64F64::saturating_from_num(ACCURACY)); + return if dx >= 0 { + result.min(U64F64::from_num(1)) + } else { + result + }; } U64F64::saturating_from_num(0) } @@ -791,6 +796,12 @@ mod tests { let dy1 = y_fixed * (one - e1); let dy2 = y_fixed * (one - e2); + if dx > x.saturating_mul(1_000) { + assert!(e1 <= one); + assert!(e2 <= one); + return; + } + let w1 = perquintill_to_f64(bal.get_base_weight()); let w2 = perquintill_to_f64(bal.get_quote_weight()); let e1_expected = (x as f64 / (x as f64 + dx as f64)).powf(w1 / w2); @@ -928,6 +939,7 @@ mod tests { } } + // cargo test --package pallet-subtensor-swap --lib -- pallet::balancer::tests::test_exp_quote_fuzzy --include-ignored --exact --nocapture #[ignore] #[test] fn test_exp_quote_fuzzy() { @@ -993,7 +1005,7 @@ mod tests { // Print progress let done = counter.fetch_add(1, Ordering::Relaxed) + 1; - if done % 100_000_000 == 0 { + if done % 10_000_000 == 0 { let progress = done as f64 / ITERATIONS as f64 * 100.0; // Replace with println for real-time progress log::debug!("progress = {progress:.4}%"); diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index c3e0b2f1d3..c5b9b11a29 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -14,7 +14,7 @@ use subtensor_swap_interface::{ }; use super::pallet::*; -use super::swap_step::{BasicSwapStep, SwapStep}; +use super::swap_step::{BasicSwapStep, MAX_SWAP_INPUT_RESERVE_MULTIPLIER, SwapStep}; use crate::{pallet::Balancer, pallet::balancer::BalancerError}; impl Pallet { @@ -154,8 +154,13 @@ impl Pallet { transactional::with_transaction(|| { let reserve = Order::ReserveOut::reserve(netuid.into()); - let result = Self::swap_inner::(netuid, order, limit_price, drop_fees) - .map_err(Into::into); + let result = Self::ensure_swap_input_within_reserve_limit::( + netuid, + order.amount(), + drop_fees, + ) + .and_then(|_| Self::swap_inner::(netuid, order, limit_price, drop_fees)) + .map_err(Into::into); if simulate || result.is_err() { // Simulation only @@ -177,6 +182,23 @@ impl Pallet { }) } + fn ensure_swap_input_within_reserve_limit( + netuid: NetUid, + amount: Order::PaidIn, + drop_fees: bool, + ) -> Result<(), Error> + where + Order: OrderT, + { + let fee = Self::calculate_fee_amount(netuid, amount, drop_fees); + let net_amount = amount.saturating_sub(fee); + let input_reserve = Order::ReserveIn::reserve(netuid); + let max_amount = input_reserve.saturating_mul(MAX_SWAP_INPUT_RESERVE_MULTIPLIER.into()); + + ensure!(net_amount <= max_amount, Error::::SwapInputTooLarge); + Ok(()) + } + fn swap_inner( netuid: NetUid, order: Order, diff --git a/pallets/swap/src/pallet/mod.rs b/pallets/swap/src/pallet/mod.rs index 1d2fd07c59..b9044d4e82 100644 --- a/pallets/swap/src/pallet/mod.rs +++ b/pallets/swap/src/pallet/mod.rs @@ -165,6 +165,9 @@ mod pallet { /// Swap reserves are too imbalanced ReservesOutOfBalance, + /// Swap input is too large relative to input-side liquidity + SwapInputTooLarge, + /// The extrinsic is deprecated Deprecated, } diff --git a/pallets/swap/src/pallet/swap_step.rs b/pallets/swap/src/pallet/swap_step.rs index 7f10bff65a..3d4d516d1f 100644 --- a/pallets/swap/src/pallet/swap_step.rs +++ b/pallets/swap/src/pallet/swap_step.rs @@ -7,6 +7,8 @@ use subtensor_runtime_common::{AlphaBalance, NetUid, TaoBalance, Token, TokenRes use super::pallet::*; +pub(crate) const MAX_SWAP_INPUT_RESERVE_MULTIPLIER: u64 = 1_000; + /// A struct representing a single swap step with all its parameters and state pub(crate) struct BasicSwapStep where diff --git a/pallets/swap/src/pallet/tests.rs b/pallets/swap/src/pallet/tests.rs index b1071294d3..5e9712d63d 100644 --- a/pallets/swap/src/pallet/tests.rs +++ b/pallets/swap/src/pallet/tests.rs @@ -11,7 +11,7 @@ use sp_arithmetic::Perquintill; use sp_runtime::DispatchError; use substrate_fixed::types::U64F64; use subtensor_runtime_common::{NetUid, Token}; -use subtensor_swap_interface::Order as OrderT; +use subtensor_swap_interface::{Order as OrderT, SwapHandler}; use super::*; use crate::mock::*; @@ -721,6 +721,78 @@ fn test_rollback_works() { }) } +#[test] +fn test_swap_rejects_input_over_1000x_input_reserve() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + TaoReserve::set_mock_reserve(netuid, TaoBalance::from(1_000)); + AlphaReserve::set_mock_reserve(netuid, AlphaBalance::from(1_000)); + + assert_noop!( + Pallet::::do_swap( + netuid, + GetTaoForAlpha::with_amount(1_000_001), + get_min_price(), + true, + false, + ), + Error::::SwapInputTooLarge + ); + assert_noop!( + Pallet::::do_swap( + netuid, + GetAlphaForTao::with_amount(1_000_001), + get_max_price(), + true, + false, + ), + Error::::SwapInputTooLarge + ); + }); +} + +#[test] +fn test_sim_swap_rejects_input_over_1000x_input_reserve() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + TaoReserve::set_mock_reserve(netuid, TaoBalance::from(1_000)); + AlphaReserve::set_mock_reserve(netuid, AlphaBalance::from(1_000)); + + assert_noop!( + Pallet::::sim_swap(netuid, GetTaoForAlpha::with_amount(1_001_000)), + Error::::SwapInputTooLarge + ); + assert_noop!( + Pallet::::sim_swap(netuid, GetAlphaForTao::with_amount(1_001_000)), + Error::::SwapInputTooLarge + ); + }); +} + +#[test] +fn test_swap_allows_input_at_1000x_input_reserve() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + TaoReserve::set_mock_reserve(netuid, TaoBalance::from(1_000)); + AlphaReserve::set_mock_reserve(netuid, AlphaBalance::from(1_000)); + + assert_ok!(Pallet::::do_swap( + netuid, + GetTaoForAlpha::with_amount(1_000_000), + get_min_price(), + true, + true, + )); + assert_ok!(Pallet::::do_swap( + netuid, + GetAlphaForTao::with_amount(1_000_000), + get_max_price(), + true, + true, + )); + }); +} + #[allow(dead_code)] fn bbox(t: U64F64, a: U64F64, b: U64F64) -> U64F64 { if t < a { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a39c473880..52a517873a 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -234,7 +234,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 423, + spec_version: 424, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,