diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index 854fb07cb9..07be25171a 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -549,6 +549,15 @@ impl Pallet { alpha_share_pool.update_value_for_one(coldkey, amount as i64); } + pub fn try_increase_stake_for_hotkey_and_coldkey_on_subnet( + hotkey: &T::AccountId, + netuid: u16, + amount: u64, + ) -> bool { + let mut alpha_share_pool = Self::get_alpha_share_pool(hotkey.clone(), netuid); + alpha_share_pool.sim_update_value_for_one(amount as i64) + } + /// Sell shares in the hotkey on a given subnet /// /// The function updates share totals given current prices. @@ -877,11 +886,18 @@ impl Pallet { Error::::HotKeyAccountNotExists ); + let expected_alpha = Self::sim_swap_tao_for_alpha(netuid, stake_to_be_added); + // Ensure that we have adequate liquidity - ensure!( - Self::sim_swap_tao_for_alpha(netuid, stake_to_be_added).is_some(), - Error::::InsufficientLiquidity + ensure!(expected_alpha.is_some(), Error::::InsufficientLiquidity); + + // Ensure hotkey pool is precise enough + let try_stake_result = Self::try_increase_stake_for_hotkey_and_coldkey_on_subnet( + hotkey, + netuid, + expected_alpha.unwrap_or(0), ); + ensure!(try_stake_result, Error::::InsufficientLiquidity); Ok(()) } @@ -937,7 +953,7 @@ impl Pallet { origin_coldkey: &T::AccountId, _destination_coldkey: &T::AccountId, origin_hotkey: &T::AccountId, - _destination_hotkey: &T::AccountId, + destination_hotkey: &T::AccountId, origin_netuid: u16, destination_netuid: u16, alpha_amount: u64, @@ -975,7 +991,8 @@ impl Pallet { ); // Ensure that the stake amount to be removed is above the minimum in tao equivalent. - if let Some(tao_equivalent) = Self::sim_swap_alpha_for_tao(origin_netuid, alpha_amount) { + let tao_equivalent_result = Self::sim_swap_alpha_for_tao(origin_netuid, alpha_amount); + if let Some(tao_equivalent) = tao_equivalent_result { ensure!( tao_equivalent > DefaultMinStake::::get(), Error::::AmountTooLow @@ -992,6 +1009,18 @@ impl Pallet { } } + let expected_alpha = + Self::sim_swap_tao_for_alpha(destination_netuid, tao_equivalent_result.unwrap_or(0)) + .unwrap_or(0); + + // Ensure that the amount being staked to the new hotkey is precise enough + let try_stake_result = Self::try_increase_stake_for_hotkey_and_coldkey_on_subnet( + destination_hotkey, + destination_netuid, + expected_alpha, + ); + ensure!(try_stake_result, Error::::InsufficientLiquidity); + if check_transfer_toggle { // Ensure transfer is toggled. ensure!( diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index 5282c9941c..f97373b0b3 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -10,7 +10,7 @@ use approx::assert_abs_diff_eq; use frame_support::dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}; use frame_support::sp_runtime::DispatchError; use sp_core::{Get, H256, U256}; -use substrate_fixed::types::{I96F32, U96F32}; +use substrate_fixed::types::{I96F32, U64F64, U96F32}; /*********************************************************** staking::add_stake() tests @@ -3495,3 +3495,276 @@ fn test_remove_stake_limit_fill_or_kill() { ),); }); } + +// #[test] +// fn test_add_stake_specific() { +// new_test_ext(1).execute_with(|| { +// let sn_owner_coldkey = U256::from(55453); + +// let hotkey_account_id = U256::from(533453); +// let coldkey_account_id = U256::from(55454); +// let hotkey_owner_account_id = U256::from(533454); + +// let existing_shares: U64F64 = +// U64F64::from_num(161_986_254).saturating_div(U64F64::from_num(u64::MAX)); +// let existing_stake = 36_711_495_953; +// let amount_added = 1_274_280_132; + +// //add network +// let netuid: u16 = add_dynamic_network(&sn_owner_coldkey, &sn_owner_coldkey); + +// // Register hotkey on netuid +// register_ok_neuron(netuid, hotkey_account_id, hotkey_owner_account_id, 0); +// // Check we have zero staked +// assert_eq!( +// SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), +// 0 +// ); + +// // Set a hotkey pool for the hotkey +// let mut hotkey_pool = SubtensorModule::get_alpha_share_pool(hotkey_account_id, netuid); +// hotkey_pool.update_value_for_one(&hotkey_owner_account_id, 1234); // Doesn't matter, will be overridden + +// // Adjust the total hotkey stake and shares to match the existing values +// TotalHotkeyShares::::insert(hotkey_account_id, netuid, existing_shares); +// TotalHotkeyAlpha::::insert(hotkey_account_id, netuid, existing_stake); + +// // Make the hotkey a delegate +// Delegates::::insert(hotkey_account_id, 0); + +// // Add stake as new hotkey +// SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( +// &hotkey_account_id, +// &coldkey_account_id, +// netuid, +// amount_added, +// ); + +// // Check the stake and shares are correct +// assert!(Alpha::::get((&hotkey_account_id, &coldkey_account_id, netuid)) > 0); +// assert_eq!( +// TotalHotkeyAlpha::::get(hotkey_account_id, netuid), +// amount_added + existing_stake +// ); +// }); +// } + +// #[test] +// // RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::staking::test_add_stake_specific_stake_into_subnet --exact --show-output +// fn test_add_stake_specific_stake_into_subnet() { +// new_test_ext(1).execute_with(|| { +// let sn_owner_coldkey = U256::from(55453); + +// let hotkey_account_id = U256::from(533453); +// let coldkey_account_id = U256::from(55454); +// let hotkey_owner_account_id = U256::from(533454); + +// let existing_shares: U64F64 = +// U64F64::from_num(161_986_254).saturating_div(U64F64::from_num(u64::MAX)); +// let existing_stake = 36_711_495_953; + +// let tao_in = 2_409_892_148_947; +// let alpha_in = 15_358_708_513_716; + +// let tao_staked = 200_000_000; +// let fee = DefaultStakingFee::::get(); + +// //add network +// let netuid: u16 = add_dynamic_network(&sn_owner_coldkey, &sn_owner_coldkey); + +// // Register hotkey on netuid +// register_ok_neuron(netuid, hotkey_account_id, hotkey_owner_account_id, 0); +// // Check we have zero staked +// assert_eq!( +// SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), +// 0 +// ); + +// // Set a hotkey pool for the hotkey +// let mut hotkey_pool = SubtensorModule::get_alpha_share_pool(hotkey_account_id, netuid); +// hotkey_pool.update_value_for_one(&hotkey_owner_account_id, 1234); // Doesn't matter, will be overridden + +// // Adjust the total hotkey stake and shares to match the existing values +// TotalHotkeyShares::::insert(hotkey_account_id, netuid, existing_shares); +// TotalHotkeyAlpha::::insert(hotkey_account_id, netuid, existing_stake); + +// // Make the hotkey a delegate +// Delegates::::insert(hotkey_account_id, 0); + +// // Setup Subnet pool +// SubnetAlphaIn::::insert(netuid, alpha_in); +// SubnetTAO::::insert(netuid, tao_in); + +// // Add stake as new hotkey +// SubtensorModule::stake_into_subnet( +// &hotkey_account_id, +// &coldkey_account_id, +// netuid, +// tao_staked, +// fee, +// ); + +// // Check the stake and shares are correct +// assert!(Alpha::::get((&hotkey_account_id, &coldkey_account_id, netuid)) > 0); +// log::info!( +// "Alpha: {}", +// Alpha::::get((&hotkey_account_id, &coldkey_account_id, netuid)) +// ); +// log::info!( +// "TotalHotkeyAlpha: {}", +// TotalHotkeyAlpha::::get(hotkey_account_id, netuid) +// ); +// }); +// } + +#[test] +// RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::staking::test_add_stake_specific_stake_into_subnet_fail --exact --show-output +fn test_add_stake_specific_stake_into_subnet_fail() { + new_test_ext(1).execute_with(|| { + let sn_owner_coldkey = U256::from(55453); + + let hotkey_account_id = U256::from(533453); + let coldkey_account_id = U256::from(55454); + let hotkey_owner_account_id = U256::from(533454); + + let existing_shares: U64F64 = + U64F64::from_num(161_986_254).saturating_div(U64F64::from_num(u64::MAX)); + let existing_stake = 36_711_495_953; + + let tao_in = 2_409_892_148_947; + let alpha_in = 15_358_708_513_716; + + let tao_staked = 200_000_000; + + //add network + let netuid: u16 = add_dynamic_network(&sn_owner_coldkey, &sn_owner_coldkey); + + // Register hotkey on netuid + register_ok_neuron(netuid, hotkey_account_id, hotkey_owner_account_id, 0); + // Check we have zero staked + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), + 0 + ); + + // Set a hotkey pool for the hotkey + let mut hotkey_pool = SubtensorModule::get_alpha_share_pool(hotkey_account_id, netuid); + hotkey_pool.update_value_for_one(&hotkey_owner_account_id, 1234); // Doesn't matter, will be overridden + + // Adjust the total hotkey stake and shares to match the existing values + TotalHotkeyShares::::insert(hotkey_account_id, netuid, existing_shares); + TotalHotkeyAlpha::::insert(hotkey_account_id, netuid, existing_stake); + + // Make the hotkey a delegate + Delegates::::insert(hotkey_account_id, 0); + + // Setup Subnet pool + SubnetAlphaIn::::insert(netuid, alpha_in); + SubnetTAO::::insert(netuid, tao_in); + + // Give TAO balance to coldkey + SubtensorModule::add_balance_to_coldkey_account( + &coldkey_account_id, + tao_staked + 1_000_000_000, + ); + + // Add stake as new hotkey + assert_noop!( + SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + tao_staked, + ), + Error::::InsufficientLiquidity + ); + }); +} + +#[test] +// RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::staking::test_move_stake_specific_stake_into_subnet_fail --exact --show-output +fn test_move_stake_specific_stake_into_subnet_fail() { + new_test_ext(1).execute_with(|| { + let sn_owner_coldkey = U256::from(55453); + + let hotkey_account_id = U256::from(533453); + let coldkey_account_id = U256::from(55454); + let hotkey_owner_account_id = U256::from(533454); + + let existing_shares: U64F64 = + U64F64::from_num(161_986_254).saturating_div(U64F64::from_num(u64::MAX)); + let existing_stake = 36_711_495_953; + + let tao_in = 2_409_892_148_947; + let alpha_in = 15_358_708_513_716; + + let tao_staked = 200_000_000; + + //add network + let netuid: u16 = add_dynamic_network(&sn_owner_coldkey, &sn_owner_coldkey); + + let origin_netuid: u16 = add_dynamic_network(&sn_owner_coldkey, &sn_owner_coldkey); + + // Register hotkey on netuid + register_ok_neuron(netuid, hotkey_account_id, hotkey_owner_account_id, 0); + // Register hotkey on origin netuid + register_ok_neuron(origin_netuid, hotkey_account_id, hotkey_owner_account_id, 0); + + // Check we have zero staked + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), + 0 + ); + + // Set a hotkey pool for the hotkey on destination subnet + let mut hotkey_pool = SubtensorModule::get_alpha_share_pool(hotkey_account_id, netuid); + hotkey_pool.update_value_for_one(&hotkey_owner_account_id, 1234); // Doesn't matter, will be overridden + + // Adjust the total hotkey stake and shares to match the existing values + TotalHotkeyShares::::insert(hotkey_account_id, netuid, existing_shares); + TotalHotkeyAlpha::::insert(hotkey_account_id, netuid, existing_stake); + + // Make the hotkey a delegate + Delegates::::insert(hotkey_account_id, 0); + + // Setup Subnet pool + SubnetAlphaIn::::insert(netuid, alpha_in); + SubnetTAO::::insert(netuid, tao_in); + + // Give TAO balance to coldkey + SubtensorModule::add_balance_to_coldkey_account( + &coldkey_account_id, + tao_staked + 1_000_000_000, + ); + + // Setup Subnet pool for origin netuid + SubnetAlphaIn::::insert(origin_netuid, alpha_in + 10_000_000); + SubnetTAO::::insert(origin_netuid, tao_in + 10_000_000); + + // Add stake as new hotkey + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + origin_netuid, + tao_staked, + ),); + let alpha_to_move = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + origin_netuid, + ); + + // Move stake to destination subnet + assert_noop!( + SubtensorModule::move_stake( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + hotkey_account_id, + origin_netuid, + netuid, + alpha_to_move, + ), + Error::::InsufficientLiquidity + ); + }); +} diff --git a/primitives/share-pool/src/lib.rs b/primitives/share-pool/src/lib.rs index f3e00fca9a..8f963cfd36 100644 --- a/primitives/share-pool/src/lib.rs +++ b/primitives/share-pool/src/lib.rs @@ -75,6 +75,29 @@ where }); } + pub fn sim_update_value_for_one(&mut self, update: i64) -> bool { + let shared_value: U64F64 = self.state_ops.get_shared_value(); + let denominator: U64F64 = self.state_ops.get_denominator(); + + // Then, update this key's share + if denominator == 0 { + true + } else { + // There are already keys in the pool, set or update this key + let value_per_share: I64F64 = I64F64::saturating_from_num( + shared_value + .checked_div(denominator) // denominator is never 0 here + .unwrap_or(U64F64::saturating_from_num(0)), + ); + + let shares_per_update: I64F64 = I64F64::saturating_from_num(update) + .checked_div(value_per_share) + .unwrap_or(I64F64::saturating_from_num(0)); + + shares_per_update != 0 + } + } + /// Update the value associated with an item identified by the Key pub fn update_value_for_one(&mut self, key: &K, update: i64) { let shared_value: U64F64 = self.state_ops.get_shared_value(); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index bc8d74dd23..8dcce09b1e 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -229,7 +229,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: 238, + spec_version: 239, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,