diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index f791e5471..01fb985d9 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -931,136 +931,6 @@ mod sudo_set_nominator_min_required_stake { ); }); } - - #[test] - fn clears_staker_nominations_below_min() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - - // Create accounts. - let netuid = 1; - let hot1 = U256::from(1); - let hot2 = U256::from(2); - let cold1 = U256::from(3); - let cold2 = U256::from(4); - - // SubtensorModule::set_target_stakes_per_interval(10); - // Register network. - add_network(netuid, 0); - - // Register hot1. - register_ok_neuron(netuid, hot1, cold1, 0); - assert_eq!(SubtensorModule::get_owning_coldkey_for_hotkey(&hot1), cold1); - - // Register hot2. - register_ok_neuron(netuid, hot2, cold2, 0); - assert_eq!(SubtensorModule::get_owning_coldkey_for_hotkey(&hot2), cold2); - - // Add stake cold1 --> hot1 (non delegation.) - SubtensorModule::add_balance_to_coldkey_account(&cold1, 5); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(cold1), - hot1, - netuid, - 1 - )); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold1, netuid), - 1 - ); - assert_eq!(Balances::free_balance(cold1), 4); - - // Add stake cold2 --> hot1 (is delegation.) - SubtensorModule::add_balance_to_coldkey_account(&cold2, 5); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(cold2), - hot1, - netuid, - 1 - )); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold2, netuid), - 1 - ); - assert_eq!(Balances::free_balance(cold2), 4); - - // Add stake cold1 --> hot2 - SubtensorModule::add_balance_to_coldkey_account(&cold1, 5); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(cold1), - hot2, - netuid, - 1 - )); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold1, netuid), - 1 - ); - assert_eq!(Balances::free_balance(cold1), 8); - - // Add stake cold2 --> hot2 - SubtensorModule::add_balance_to_coldkey_account(&cold2, 5); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(cold2), - hot2, - netuid, - 1 - )); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold2, netuid), - 1 - ); - assert_eq!(Balances::free_balance(cold2), 8); - - // Set min stake to 0 (noop) - assert_ok!(AdminUtils::sudo_set_nominator_min_required_stake( - <::RuntimeOrigin>::root(), - 0u64 - )); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold1, netuid), - 1 - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold1, netuid), - 1 - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold2, netuid), - 1 - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold2, netuid), - 1 - ); - - // Set min nomination to 10: should clear (cold2, hot1) and (cold1, hot2). - assert_ok!(AdminUtils::sudo_set_nominator_min_required_stake( - <::RuntimeOrigin>::root(), - 10u64 - )); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold1, netuid), - 1 - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold1, netuid), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold2, netuid), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold2, netuid), - 1 - ); - - // Balances have been added back into accounts. - assert_eq!(Balances::free_balance(cold1), 9); - assert_eq!(Balances::free_balance(cold2), 9); - }); - } } #[test] diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index c895a7e1d..7f75568ba 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -696,9 +696,10 @@ pub mod pallet { } #[pallet::type_value] - /// Default minimum stake for setting childkeys. - pub fn DefaultChildkeysMinStake() -> u64 { - 1_000_000_000_000 + /// Default minimum stake. + /// 2M rao matches $1 at $500/TAO + pub fn DefaultMinStake() -> u64 { + 2_000_000 } #[pallet::type_value] diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index c101cff5c..7073a1413 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1555,5 +1555,46 @@ mod dispatches { pub fn unstake_all_alpha(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_unstake_all_alpha(origin, hotkey) } + + /// ---- The implementation for the extrinsic move_stake: Moves specified amount of stake from a hotkey to another across subnets. + /// + /// # Args: + /// * `origin` - (::Origin): + /// - The signature of the caller's coldkey. + /// + /// * `origin_hotkey` (T::AccountId): + /// - The hotkey account to move stake from. + /// + /// * `destination_hotkey` (T::AccountId): + /// - The hotkey account to move stake to. + /// + /// * `origin_netuid` (T::AccountId): + /// - The subnet ID to move stake from. + /// + /// * `destination_netuid` (T::AccountId): + /// - The subnet ID to move stake to. + /// + /// * `alpha_amount` (T::AccountId): + /// - The alpha stake amount to move. + /// + #[pallet::call_index(85)] + #[pallet::weight((Weight::from_parts(3_000_000, 0).saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Operational, Pays::No))] + pub fn move_stake( + origin: T::RuntimeOrigin, + origin_hotkey: T::AccountId, + destination_hotkey: T::AccountId, + origin_netuid: u16, + destination_netuid: u16, + alpha_amount: u64, + ) -> DispatchResult { + Self::do_move_stake( + origin, + origin_hotkey, + destination_hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + ) + } } } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index ee9aac2ef..649336674 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -183,5 +183,7 @@ mod errors { InputLengthsUnequal, /// A transactor exceeded the rate limit for setting weights. CommittingWeightsTooFast, + /// Stake amount is too low. + AmountTooLow, } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 7ef00b1e3..30566ac47 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -17,6 +17,8 @@ mod events { StakeAdded(T::AccountId, T::AccountId, u64, u64, u16), /// stake has been removed from the hotkey staking account onto the coldkey account. StakeRemoved(T::AccountId, T::AccountId, u64, u64, u16), + /// stake has been moved from origin (hotkey, subnet ID) to destination (hotkey, subnet ID) of this amount (in TAO). + StakeMoved(T::AccountId, T::AccountId, u16, T::AccountId, u16, u64), /// a caller successfully sets their weights on a subnetwork. WeightsSet(u16, u16), /// a new neuron account has been registered to the chain. diff --git a/pallets/subtensor/src/rpc_info/stake_info.rs b/pallets/subtensor/src/rpc_info/stake_info.rs index 2f87453f3..97afd5aa2 100644 --- a/pallets/subtensor/src/rpc_info/stake_info.rs +++ b/pallets/subtensor/src/rpc_info/stake_info.rs @@ -35,6 +35,9 @@ impl Pallet { let alpha: u64 = Self::get_stake_for_hotkey_and_coldkey_on_subnet( hotkey_i, coldkey_i, *netuid_i, ); + if alpha == 0 { + continue; + } let emission: u64 = AlphaDividendsPerSubnet::::get(*netuid_i, &hotkey_i); let is_registered: bool = Self::is_hotkey_registered_on_network(*netuid_i, hotkey_i); diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index 5b3e201cc..f63735cd3 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -1,4 +1,5 @@ use super::*; +use sp_core::Get; impl Pallet { /// ---- The implementation for the extrinsic add_stake: Adds stake to a hotkey account. @@ -61,6 +62,12 @@ impl Pallet { Error::::HotKeyAccountNotExists ); + // Ensure stake_to_be_added is at least DefaultMinStake + ensure!( + stake_to_be_added >= DefaultMinStake::::get(), + Error::::AmountTooLow + ); + // 5. Ensure the remove operation from the coldkey is a success. let tao_staked: u64 = Self::remove_balance_from_coldkey_account(&coldkey, stake_to_be_added)?; diff --git a/pallets/subtensor/src/staking/mod.rs b/pallets/subtensor/src/staking/mod.rs index 7508b9d2a..2b222036c 100644 --- a/pallets/subtensor/src/staking/mod.rs +++ b/pallets/subtensor/src/staking/mod.rs @@ -3,6 +3,7 @@ pub mod add_stake; pub mod decrease_take; pub mod helpers; pub mod increase_take; +pub mod move_stake; pub mod remove_stake; pub mod set_children; pub mod stake_utils; diff --git a/pallets/subtensor/src/staking/move_stake.rs b/pallets/subtensor/src/staking/move_stake.rs index a5794fe67..9628c5814 100644 --- a/pallets/subtensor/src/staking/move_stake.rs +++ b/pallets/subtensor/src/staking/move_stake.rs @@ -1,5 +1,5 @@ use super::*; -// use substrate_fixed::types::I96F32; +use sp_core::Get; impl Pallet { /// Moves stake from one hotkey to another across subnets. @@ -57,7 +57,11 @@ impl Pallet { ); // --- 6. Get the current alpha stake for the origin hotkey-coldkey pair in the origin subnet - let origin_alpha = Self::get_stake_for_hotkey_and_coldkey_on_subnet( &origin_hotkey, &coldkey, origin_netuid ); + let origin_alpha = Self::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + origin_netuid, + ); ensure!( alpha_amount <= origin_alpha, Error::::NotEnoughStakeToWithdraw @@ -71,6 +75,12 @@ impl Pallet { alpha_amount, ); + // Ensure origin_tao is at least DefaultMinStake + ensure!( + origin_tao >= DefaultMinStake::::get(), + Error::::AmountTooLow + ); + // --- 8. Stake the resulting TAO into the destination subnet for the destination hotkey Self::stake_into_subnet( &destination_hotkey.clone(), @@ -94,6 +104,7 @@ impl Pallet { origin_netuid, destination_hotkey, destination_netuid, + origin_tao, )); // -- 10. Ok and return. diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index 8b270c78d..eb299d88e 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -3058,28 +3058,34 @@ fn test_childkey_multiple_parents_emission() { // Register neurons and add initial stakes let initial_stakes: Vec<(bool, U256, U256, u64)> = vec![ - (false, coldkey_parent1, parent1, 200_000), - (true, coldkey_parent2, parent2, 150_000), - (true, coldkey_child, child, 20_000), - (true, coldkey_weight_setter, weight_setter, 100_000), + (false, coldkey_parent1, parent1, 200_000_000), + (true, coldkey_parent2, parent2, 150_000_000), + (true, coldkey_child, child, 20_000_000), + (true, coldkey_weight_setter, weight_setter, 100_000_000), ]; - for (register, coldkey, hotkey, stake) in initial_stakes.iter() { - SubtensorModule::add_balance_to_coldkey_account(coldkey, *stake); - if *register { - // Register a neuron - register_ok_neuron(netuid, *hotkey, *coldkey, 0); - } else { - // Just create hotkey account - SubtensorModule::create_account_if_non_existent(coldkey, hotkey); - } - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(*coldkey), - *hotkey, - netuid, - *stake - )); - } + let initial_actual_stakes: Vec = initial_stakes + .iter() + .map(|(register, coldkey, hotkey, stake)| { + SubtensorModule::add_balance_to_coldkey_account(coldkey, *stake); + if *register { + // Register a neuron + register_ok_neuron(netuid, *hotkey, *coldkey, 0); + } else { + // Just create hotkey account + SubtensorModule::create_account_if_non_existent(coldkey, hotkey); + } + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(*coldkey), + *hotkey, + netuid, + *stake + )); + + // Return actual stake + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, netuid) + }) + .collect(); SubtensorModule::set_weights_set_rate_limit(netuid, 0); step_block(2); @@ -3152,23 +3158,26 @@ fn test_childkey_multiple_parents_emission() { ); assert!( - parent1_stake > 200_000, + parent1_stake > initial_actual_stakes[0], "Parent1 should have received emission" ); assert!( - parent2_stake > 150_000, + parent2_stake > initial_actual_stakes[1], "Parent2 should have received emission" ); - assert!(child_stake > 20_000, "Child should have received emission"); assert!( - weight_setter_stake > 100_000, + child_stake > initial_actual_stakes[2], + "Child should have received emission" + ); + assert!( + weight_setter_stake > initial_actual_stakes[3], "Weight setter should have received emission" ); // Check individual stake increases - let parent1_stake_increase = parent1_stake - 200_000; - let parent2_stake_increase = parent2_stake - 150_000; - let child_stake_increase = child_stake - 20_000; + let parent1_stake_increase = parent1_stake - initial_actual_stakes[0]; + let parent2_stake_increase = parent2_stake - initial_actual_stakes[1]; + let child_stake_increase = child_stake - initial_actual_stakes[2]; log::debug!( "Stake increases - Parent1: {}, Parent2: {}, Child: {}", @@ -3192,12 +3201,13 @@ fn test_childkey_multiple_parents_emission() { ); // Check that the total stake has increased by the emission amount + // Allow 1% slippage let total_stake = parent1_stake + parent2_stake + child_stake + weight_setter_stake; - let initial_total_stake: u64 = initial_stakes.iter().map(|(_, _, _, stake)| stake).sum(); + let initial_total_stake: u64 = initial_actual_stakes.iter().sum::(); assert_abs_diff_eq!( total_stake, - initial_total_stake + total_emission - 2, - epsilon = total_emission / 10_000 + initial_total_stake + total_emission, + epsilon = total_emission / 100 ); }); } diff --git a/pallets/subtensor/src/tests/epoch.rs b/pallets/subtensor/src/tests/epoch.rs index 0bd5a18cb..43ac6b98c 100644 --- a/pallets/subtensor/src/tests/epoch.rs +++ b/pallets/subtensor/src/tests/epoch.rs @@ -12,7 +12,7 @@ use frame_support::{assert_err, assert_ok}; // use frame_system::Config; use rand::{distributions::Uniform, rngs::StdRng, seq::SliceRandom, thread_rng, Rng, SeedableRng}; -use sp_core::U256; +use sp_core::{Get, U256}; // use sp_runtime::DispatchError; use std::time::Instant; use substrate_fixed::types::I32F32; @@ -1505,7 +1505,7 @@ fn test_set_alpha_disabled() { signer.clone(), hotkey, netuid, - 1000 + DefaultMinStake::::get() )); // Only owner can set alpha values assert_ok!(SubtensorModule::register_network(signer.clone(), hotkey)); @@ -2600,7 +2600,7 @@ fn test_get_set_alpha() { signer.clone(), hotkey, netuid, - 1000 + DefaultMinStake::::get() )); assert_ok!(SubtensorModule::do_set_alpha_values( diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index e1b01ab81..e0fef9d55 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -7,6 +7,7 @@ mod epoch; mod math; mod migration; mod mock; +mod move_stake; mod networks; mod neuron_info; mod registration; diff --git a/pallets/subtensor/src/tests/move_stake.rs b/pallets/subtensor/src/tests/move_stake.rs new file mode 100644 index 000000000..f26a82432 --- /dev/null +++ b/pallets/subtensor/src/tests/move_stake.rs @@ -0,0 +1,775 @@ +use super::mock::*; +use crate::*; +use approx::assert_abs_diff_eq; +use frame_support::{assert_err, assert_noop, assert_ok}; +use sp_core::{Get, U256}; + +// 1. test_do_move_success +// Description: Test a successful move of stake between two hotkeys in the same subnet +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test move -- test_do_move_success --exact --nocapture +#[test] +fn test_do_move_success() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid: u16 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let coldkey = U256::from(1); + let origin_hotkey = U256::from(2); + let destination_hotkey = U256::from(3); + let stake_amount = DefaultMinStake::::get() * 10; + + // Set up initial stake + SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); + SubtensorModule::create_account_if_non_existent(&coldkey, &destination_hotkey); + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount); + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid, + ); + + // Perform the move + assert_ok!(SubtensorModule::do_move_stake( + RuntimeOrigin::signed(coldkey), + origin_hotkey, + destination_hotkey, + netuid, + netuid, + alpha, + )); + + // Check that the stake has been moved + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid + ), + 0 + ); + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &destination_hotkey, + &coldkey, + netuid + ), + stake_amount, + epsilon = stake_amount / 1000 + ); + }); +} + +// 2. test_do_move_different_subnets +// Description: Test moving stake between two hotkeys in different subnets +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test move -- test_do_move_different_subnets --exact --nocapture +#[test] +fn test_do_move_different_subnets() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let origin_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let destination_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let coldkey = U256::from(1); + let origin_hotkey = U256::from(2); + let destination_hotkey = U256::from(3); + let stake_amount = DefaultMinStake::::get() * 10; + + // Set up initial stake and subnets + SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); + SubtensorModule::create_account_if_non_existent(&coldkey, &destination_hotkey); + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, origin_netuid, stake_amount); + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + origin_netuid, + ); + + // Perform the move + assert_ok!(SubtensorModule::do_move_stake( + RuntimeOrigin::signed(coldkey), + origin_hotkey, + destination_hotkey, + origin_netuid, + destination_netuid, + alpha, + )); + + // Check that the stake has been moved + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + origin_netuid + ), + 0 + ); + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &destination_hotkey, + &coldkey, + destination_netuid + ), + stake_amount, + epsilon = stake_amount / 1000 + ); + }); +} + +// 4. test_do_move_nonexistent_subnet +// Description: Attempt to move stake to a non-existent subnet, which should fail +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test move -- test_do_move_nonexistent_subnet --exact --nocapture +#[test] +fn test_do_move_nonexistent_subnet() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let origin_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let coldkey = U256::from(1); + let origin_hotkey = U256::from(2); + let destination_hotkey = U256::from(3); + let nonexistent_netuid = 99; // Assuming this subnet doesn't exist + let stake_amount = 1_000_000; + + // Set up initial stake + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, origin_netuid, stake_amount); + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + origin_netuid, + ); + + // Attempt to move stake to a non-existent subnet + assert_noop!( + SubtensorModule::do_move_stake( + RuntimeOrigin::signed(coldkey), + origin_hotkey, + destination_hotkey, + origin_netuid, + nonexistent_netuid, + alpha, + ), + Error::::SubnetNotExists + ); + + // Check that the stake remains unchanged + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + origin_netuid + ), + stake_amount, + epsilon = 100 + ); + }); +} + +// 5. test_do_move_nonexistent_origin_hotkey +// Description: Attempt to move stake from a non-existent origin hotkey, which should fail +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test move -- test_do_move_nonexistent_origin_hotkey --exact --nocapture +#[test] +fn test_do_move_nonexistent_origin_hotkey() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let coldkey = U256::from(1); + let nonexistent_origin_hotkey = U256::from(99); // Assuming this hotkey doesn't exist + let destination_hotkey = U256::from(3); + + // Attempt to move stake from a non-existent origin hotkey + assert_noop!( + SubtensorModule::do_move_stake( + RuntimeOrigin::signed(coldkey), + nonexistent_origin_hotkey, + destination_hotkey, + netuid, + netuid, + 123 + ), + Error::::HotKeyAccountNotExists + ); + + // Check that no stake was moved + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &nonexistent_origin_hotkey, + &coldkey, + netuid + ), + 0 + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &destination_hotkey, + &coldkey, + netuid + ), + 0 + ); + }); +} + +// 6. test_do_move_nonexistent_destination_hotkey +// Description: Attempt to move stake to a non-existent destination hotkey, which should fail +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test move -- test_do_move_nonexistent_destination_hotkey --exact --nocapture +#[test] +fn test_do_move_nonexistent_destination_hotkey() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let origin_hotkey = U256::from(2); + let nonexistent_destination_hotkey = U256::from(99); // Assuming this hotkey doesn't exist + let netuid = 1; + let stake_amount = 1_000_000; + + // Set up initial stake + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount); + + // Attempt to move stake from a non-existent origin hotkey + add_network(netuid, 0, 0); + assert_noop!( + SubtensorModule::do_move_stake( + RuntimeOrigin::signed(coldkey), + origin_hotkey, + nonexistent_destination_hotkey, + netuid, + netuid, + 1234 + ), + Error::::HotKeyAccountNotExists + ); + + // Check that the stake was moved successfully + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid + ), + stake_amount + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &nonexistent_destination_hotkey, + &coldkey, + netuid + ), + 0 + ); + }); +} + +// 8. test_do_move_all_stake +// Description: Test moving all stake from one hotkey to another +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test move -- test_do_move_all_stake --exact --nocapture +#[test] +fn test_do_move_all_stake() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let coldkey = U256::from(1); + let origin_hotkey = U256::from(2); + let destination_hotkey = U256::from(3); + let stake_amount = DefaultMinStake::::get() * 10; + + // Set up initial stake + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount); + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid, + ); + + // Move all stake + SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); + SubtensorModule::create_account_if_non_existent(&coldkey, &destination_hotkey); + assert_ok!(SubtensorModule::do_move_stake( + RuntimeOrigin::signed(coldkey), + origin_hotkey, + destination_hotkey, + netuid, + netuid, + alpha, + )); + + // Check that all stake was moved + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid + ), + 0 + ); + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &destination_hotkey, + &coldkey, + netuid + ), + stake_amount, + epsilon = stake_amount / 1000 + ); + }); +} + +#[test] +fn test_do_move_half_stake() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let coldkey = U256::from(1); + let origin_hotkey = U256::from(2); + let destination_hotkey = U256::from(3); + let stake_amount = DefaultMinStake::::get() * 10; + + // Set up initial stake + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount); + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid, + ); + + // Move all stake + SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); + SubtensorModule::create_account_if_non_existent(&coldkey, &destination_hotkey); + assert_ok!(SubtensorModule::do_move_stake( + RuntimeOrigin::signed(coldkey), + origin_hotkey, + destination_hotkey, + netuid, + netuid, + alpha / 2, + )); + + // Check that all stake was moved + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid + ), + stake_amount / 2, + epsilon = stake_amount / 1000 + ); + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &destination_hotkey, + &coldkey, + netuid + ), + stake_amount / 2, + epsilon = stake_amount / 1000 + ); + }); +} + +// 9. test_do_move_partial_stake +// Description: Test moving a portion of stake from one hotkey to another +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test move -- test_do_move_partial_stake --exact --nocapture +#[test] +fn test_do_move_partial_stake() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let coldkey = U256::from(1); + let origin_hotkey = U256::from(2); + let destination_hotkey = U256::from(3); + let total_stake = DefaultMinStake::::get() * 10; + + // Set up initial stake + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, total_stake); + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid, + ); + + // Move partial stake + SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); + SubtensorModule::create_account_if_non_existent(&coldkey, &destination_hotkey); + assert_ok!(SubtensorModule::do_move_stake( + RuntimeOrigin::signed(coldkey), + origin_hotkey, + destination_hotkey, + netuid, + netuid, + alpha, + )); + + // Check that the correct amount of stake was moved + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid + ), + 0 + ); + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &destination_hotkey, + &coldkey, + netuid + ), + total_stake, + epsilon = total_stake / 1000 + ); + }); +} + +// 10. test_do_move_multiple_times +// Description: Test moving stake multiple times between the same hotkeys +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test move -- test_do_move_multiple_times --exact --nocapture +#[test] +fn test_do_move_multiple_times() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let coldkey = U256::from(1); + let hotkey1 = U256::from(2); + let hotkey2 = U256::from(3); + let initial_stake = DefaultMinStake::::get() * 10; + + // Set up initial stake + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey1); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey2); + SubtensorModule::stake_into_subnet(&hotkey1, &coldkey, netuid, initial_stake); + + // Move stake multiple times + for _ in 0..3 { + let alpha1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey1, &coldkey, netuid, + ); + assert_ok!(SubtensorModule::do_move_stake( + RuntimeOrigin::signed(coldkey), + hotkey1, + hotkey2, + netuid, + netuid, + alpha1, + )); + let alpha2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey2, &coldkey, netuid, + ); + assert_ok!(SubtensorModule::do_move_stake( + RuntimeOrigin::signed(coldkey), + hotkey2, + hotkey1, + netuid, + netuid, + alpha2, + )); + } + + // Check final stake distribution + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey1, &coldkey, netuid), + initial_stake, + epsilon = initial_stake / 1000 + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey2, &coldkey, netuid), + 0 + ); + }); +} + +// 13. test_do_move_wrong_origin +// Description: Attempt to move stake with a different origin than the coldkey, which should fail +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test move -- test_do_move_wrong_origin --exact --nocapture +#[test] +fn test_do_move_wrong_origin() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let wrong_coldkey = U256::from(99); + let origin_hotkey = U256::from(2); + let destination_hotkey = U256::from(3); + let netuid = 1; + let stake_amount = 1000; + + // Set up initial stake + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount); + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid, + ); + + // Attempt to move stake with wrong origin + add_network(netuid, 0, 0); + SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); + SubtensorModule::create_account_if_non_existent(&coldkey, &destination_hotkey); + assert_err!( + SubtensorModule::do_move_stake( + RuntimeOrigin::signed(wrong_coldkey), + origin_hotkey, + destination_hotkey, + netuid, + netuid, + alpha, + ), + Error::::NotEnoughStakeToWithdraw + ); + + // Check that no stake was moved + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid + ), + stake_amount + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &destination_hotkey, + &coldkey, + netuid + ), + 0 + ); + }); +} + +// 14. test_do_move_same_hotkey +// Description: Attempt to move stake to the same hotkey, which should fail or have no effect +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test move -- test_do_move_same_hotkey --exact --nocapture +#[test] +fn test_do_move_same_hotkey() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let stake_amount = DefaultMinStake::::get() * 10; + + // Set up initial stake + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid, stake_amount); + let alpha = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + + // Attempt to move stake to the same hotkey + assert_ok!(SubtensorModule::do_move_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + hotkey, + netuid, + netuid, + alpha, + )); + + // Check that stake remains unchanged + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid), + alpha, + epsilon = 5 + ); + }); +} + +// 15. test_do_move_event_emission +// Description: Verify that the correct event is emitted after a successful move +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test move -- test_do_move_event_emission --exact --nocapture +#[test] +fn test_do_move_event_emission() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let coldkey = U256::from(1); + let origin_hotkey = U256::from(2); + let destination_hotkey = U256::from(3); + let stake_amount = DefaultMinStake::::get() * 10; + + // Set up initial stake + SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); + SubtensorModule::create_account_if_non_existent(&coldkey, &destination_hotkey); + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount); + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid, + ); + + // Move stake and capture events + System::reset_events(); + assert_ok!(SubtensorModule::do_move_stake( + RuntimeOrigin::signed(coldkey), + origin_hotkey, + destination_hotkey, + netuid, + netuid, + alpha, + )); + + // Check for the correct event emission + System::assert_last_event( + Event::StakeMoved( + coldkey, + origin_hotkey, + netuid, + destination_hotkey, + netuid, + 19999999, // Should be TAO equivalent + ) + .into(), + ); + }); +} + +// 16. test_do_move_storage_updates +// Description: Verify that all relevant storage items are correctly updated after a move +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test move -- test_do_move_storage_updates --exact --nocapture +#[test] +fn test_do_move_storage_updates() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let origin_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let destination_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let coldkey = U256::from(1); + let origin_hotkey = U256::from(2); + let destination_hotkey = U256::from(3); + let stake_amount = DefaultMinStake::::get() * 10; + + // Set up initial stake + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, origin_netuid, stake_amount); + + // Move stake + SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); + SubtensorModule::create_account_if_non_existent(&coldkey, &destination_hotkey); + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + origin_netuid, + ); + + assert_ok!(SubtensorModule::do_move_stake( + RuntimeOrigin::signed(coldkey), + origin_hotkey, + destination_hotkey, + origin_netuid, + destination_netuid, + alpha, + )); + + // Verify storage updates + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + origin_netuid + ), + 0 + ); + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &destination_hotkey, + &coldkey, + destination_netuid + ), + alpha, + epsilon = 5 + ); + }); +} + +// 18. test_do_move_max_values +// Description: Test moving the maximum possible stake values to check for overflows +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test move -- test_do_move_max_values --exact --nocapture +#[test] +fn test_do_move_max_values() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let coldkey = U256::from(1); + let origin_hotkey = U256::from(2); + let destination_hotkey = U256::from(3); + let max_stake = u64::MAX; + let netuid: u16 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + // Set up initial stake with maximum value + SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); + SubtensorModule::create_account_if_non_existent(&coldkey, &destination_hotkey); + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, max_stake); + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid, + ); + + // Move maximum stake + assert_ok!(SubtensorModule::do_move_stake( + RuntimeOrigin::signed(coldkey), + origin_hotkey, + destination_hotkey, + netuid, + netuid, + alpha, + )); + + // Verify stake movement without overflow + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid + ), + 0 + ); + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &destination_hotkey, + &coldkey, + netuid + ), + alpha, + epsilon = 5 + ); + }); +} + +// Verify moving too low amount is impossible +#[test] +fn test_moving_too_little_fails() { + new_test_ext(1).execute_with(|| { + let hotkey_account_id = U256::from(533453); + let coldkey_account_id = U256::from(55453); + let amount = DefaultMinStake::::get(); + + //add network + let netuid: u16 = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + let netuid2: u16 = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + + // Give it some $$$ in his coldkey balance + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount); + + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + amount + )); + + // Coldkey / hotkey 0 decreases take to 5%. This should fail as the minimum take is 9% + assert_err!( + SubtensorModule::move_stake( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + hotkey_account_id, + netuid, + netuid2, + 1 + ), + Error::::AmountTooLow + ); + }); +} diff --git a/pallets/subtensor/src/tests/senate.rs b/pallets/subtensor/src/tests/senate.rs index 7fb86c5c6..00a1b897f 100644 --- a/pallets/subtensor/src/tests/senate.rs +++ b/pallets/subtensor/src/tests/senate.rs @@ -1,11 +1,12 @@ #![allow(clippy::unwrap_used)] use super::mock::*; - +use crate::*; +use approx::assert_abs_diff_eq; use codec::Encode; use frame_support::{assert_noop, assert_ok}; use frame_system::{EventRecord, Phase}; -use sp_core::{bounded_vec, H256, U256}; +use sp_core::{bounded_vec, Get, H256, U256}; use sp_runtime::{ traits::{BlakeTwo256, Hash}, BuildStorage, @@ -65,6 +66,7 @@ fn test_senate_join_works() { let hotkey_account_id = U256::from(6); let burn_cost = 1000; let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har + let stake = DefaultMinStake::::get() * 100; //add network SubtensorModule::set_burn(netuid, burn_cost); @@ -95,26 +97,28 @@ fn test_senate_join_works() { Delegates::::insert(hotkey_account_id, u16::MAX / 10); let staker_coldkey = U256::from(7); - SubtensorModule::add_balance_to_coldkey_account(&staker_coldkey, 100_000); + SubtensorModule::add_balance_to_coldkey_account(&staker_coldkey, stake); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(staker_coldkey), hotkey_account_id, netuid, - 100_000 + stake )); - assert_eq!( + assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey_account_id, &staker_coldkey, netuid ), - 99_999 + stake, + epsilon = 10 ); - assert_eq!( + assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey_account_id, netuid), - 99_999 + stake, + epsilon = 10 ); assert_ok!(SubtensorModule::root_register( @@ -166,25 +170,28 @@ fn test_senate_vote_works() { Delegates::::insert(hotkey_account_id, u16::MAX / 10); let staker_coldkey = U256::from(7); - SubtensorModule::add_balance_to_coldkey_account(&staker_coldkey, 100_000); + let stake = DefaultMinStake::::get() * 10; + SubtensorModule::add_balance_to_coldkey_account(&staker_coldkey, stake); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(staker_coldkey), hotkey_account_id, netuid, - 100_000 + stake )); - assert_eq!( + assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey_account_id, &staker_coldkey, netuid ), - 99_999 + stake, + epsilon = stake / 1000 ); - assert_eq!( + assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey_account_id, netuid), - 99_999 + stake, + epsilon = stake / 1000 ); assert_ok!(SubtensorModule::root_register( @@ -305,6 +312,7 @@ fn test_senate_leave_works() { let hotkey_account_id = U256::from(6); let burn_cost = 1000; let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har + let stake = DefaultMinStake::::get() * 10; //add network SubtensorModule::set_burn(netuid, burn_cost); @@ -335,25 +343,27 @@ fn test_senate_leave_works() { Delegates::::insert(hotkey_account_id, u16::MAX / 10); let staker_coldkey = U256::from(7); - SubtensorModule::add_balance_to_coldkey_account(&staker_coldkey, 100_000); + SubtensorModule::add_balance_to_coldkey_account(&staker_coldkey, stake); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(staker_coldkey), hotkey_account_id, netuid, - 100_000 + stake )); - assert_eq!( + assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey_account_id, &staker_coldkey, netuid ), - 99_999 + stake, + epsilon = stake / 1000 ); - assert_eq!( + assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey_account_id, netuid), - 99_999 + stake, + epsilon = stake / 1000 ); assert_ok!(SubtensorModule::root_register( @@ -376,12 +386,13 @@ fn test_senate_leave_vote_removal() { let burn_cost = 1000; let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har let coldkey_origin = <::RuntimeOrigin>::signed(coldkey_account_id); + let stake = DefaultMinStake::::get() * 10; //add network SubtensorModule::set_burn(netuid, burn_cost); add_network(netuid, tempo, 0); // Give it some $$$ in his coldkey balance - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, stake); // Subscribe and check extrinsic output assert_ok!(SubtensorModule::burned_register( @@ -392,7 +403,7 @@ fn test_senate_leave_vote_removal() { // Check if balance has decreased to pay for the burn. assert_eq!( SubtensorModule::get_coldkey_balance(&coldkey_account_id), - (10000 - burn_cost) + (stake - burn_cost) ); // funds drained on reg. // Check if neuron has added to the specified network(netuid) assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 1); @@ -406,25 +417,27 @@ fn test_senate_leave_vote_removal() { Delegates::::insert(hotkey_account_id, u16::MAX / 10); let staker_coldkey = U256::from(7); - SubtensorModule::add_balance_to_coldkey_account(&staker_coldkey, 100_000); + SubtensorModule::add_balance_to_coldkey_account(&staker_coldkey, stake); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(staker_coldkey), hotkey_account_id, netuid, - 100_000 + stake )); - assert_eq!( + assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey_account_id, &staker_coldkey, netuid ), - 99_999 + stake, + epsilon = 10 ); - assert_eq!( + assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey_account_id, netuid), - 99_999 + stake, + epsilon = 10 ); assert_ok!(SubtensorModule::root_register( @@ -545,7 +558,7 @@ fn test_senate_not_leave_when_stake_removed() { Delegates::::insert(hotkey_account_id, u16::MAX / 10); let staker_coldkey = U256::from(7); - let stake_amount: u64 = 100_000; + let stake_amount: u64 = DefaultMinStake::::get() * 10; SubtensorModule::add_balance_to_coldkey_account(&staker_coldkey, stake_amount); assert_ok!(SubtensorModule::add_stake( @@ -554,17 +567,19 @@ fn test_senate_not_leave_when_stake_removed() { netuid, stake_amount )); - assert_eq!( + assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey_account_id, &staker_coldkey, netuid ), - stake_amount - 1 // Need to account for ED + stake_amount, + epsilon = stake_amount / 1000 ); - assert_eq!( + assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey_account_id, netuid), - stake_amount - 1 // Need to account for ED + stake_amount, + epsilon = stake_amount / 1000 ); assert_ok!(SubtensorModule::root_register( @@ -673,10 +688,12 @@ fn test_adjust_senate_events() { let root_netuid = SubtensorModule::get_root_netuid(); let max_senate_size: u16 = SenateMaxMembers::get() as u16; - let stake_threshold: u64 = 100_000; // Give this much to every senator + let stake_threshold: u64 = DefaultMinStake::::get(); // Give this much to every senator // We will be registering MaxMembers hotkeys and two more to try a replace - let balance_to_add = 50_000 + (stake_threshold + burn_cost) * (max_senate_size + 2) as u64; + let balance_to_add = DefaultMinStake::::get() * 10 + + 50_000 + + (stake_threshold + burn_cost) * (max_senate_size + 2) as u64; let replacement_hotkey_account_id = U256::from(7); // Will be added to the senate to replace hotkey_account_id @@ -776,26 +793,29 @@ fn test_adjust_senate_events() { // as they have no stake assert!(!Senate::is_member(&replacement_hotkey_account_id)); // Add/delegate enough stake to join the senate + let stake = DefaultMinStake::::get() * 10; assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(coldkey_account_id), replacement_hotkey_account_id, root_netuid, - 1 // Will be more than the last one in the senate by stake (has 0 stake) + stake // Will be more than the last one in the senate by stake (has 0 stake) )); - assert_eq!( + assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &replacement_hotkey_account_id, &coldkey_account_id, root_netuid ), - 1 + stake, + epsilon = stake / 1000 ); - assert_eq!( + assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_on_subnet( &replacement_hotkey_account_id, root_netuid ), - 1 + stake, + epsilon = stake / 1000 ); System::reset_events(); diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index a712993e2..6594887b9 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -9,7 +9,7 @@ use crate::*; use approx::assert_abs_diff_eq; use frame_support::dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}; use frame_support::sp_runtime::DispatchError; -use sp_core::{H256, U256}; +use sp_core::{Get, H256, U256}; /*********************************************************** staking::add_stake() tests @@ -41,7 +41,7 @@ fn test_add_stake_ok_no_emission() { new_test_ext(1).execute_with(|| { let hotkey_account_id = U256::from(533453); let coldkey_account_id = U256::from(55453); - let amount = 10_000; + let amount = DefaultMinStake::::get() * 10; //add network let netuid: u16 = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); @@ -70,14 +70,14 @@ fn test_add_stake_ok_no_emission() { assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), amount, - epsilon = 1, + epsilon = amount / 1000, ); // Check if balance has decreased assert_eq!(SubtensorModule::get_coldkey_balance(&coldkey_account_id), 1); // Check if total stake has increased accordingly. - assert_eq!(SubtensorModule::get_total_stake(), 10000); + assert_eq!(SubtensorModule::get_total_stake(), amount); assert_abs_diff_eq!(SubtensorModule::get_total_stake(), amount, epsilon = 1,); }); } @@ -179,16 +179,17 @@ fn test_add_stake_ok_neuron_does_not_belong_to_coldkey() { let hotkey_id = U256::from(54544); let other_cold_key = U256::from(99498); let netuid: u16 = add_dynamic_network(&hotkey_id, &coldkey_id); + let stake = DefaultMinStake::::get() * 10; // Give it some $$$ in his coldkey balance - SubtensorModule::add_balance_to_coldkey_account(&other_cold_key, 100000); + SubtensorModule::add_balance_to_coldkey_account(&other_cold_key, stake); // Perform the request which is signed by a different cold key assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(other_cold_key), hotkey_id, netuid, - 1000, + stake, )); }); } @@ -198,7 +199,7 @@ fn test_add_stake_err_not_enough_belance() { new_test_ext(1).execute_with(|| { let coldkey_id = U256::from(544); let hotkey_id = U256::from(54544); - let stake = 60_000; + let stake = DefaultMinStake::::get() * 10; let netuid: u16 = add_dynamic_network(&hotkey_id, &coldkey_id); // Lets try to stake with 0 balance in cold key account @@ -561,7 +562,7 @@ fn test_remove_stake_total_issuance_no_change() { let subnet_owner_hotkey = U256::from(2); let hotkey_account_id = U256::from(581337); let coldkey_account_id = U256::from(81337); - let amount = 10_000; + let amount = DefaultMinStake::::get() * 10; let netuid: u16 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); @@ -593,7 +594,11 @@ fn test_remove_stake_total_issuance_no_change() { let total_issuance_after_stake = Balances::total_issuance(); // Remove all stake - let stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id); + let stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid, + ); assert_ok!(SubtensorModule::remove_stake( RuntimeOrigin::signed(coldkey_account_id), hotkey_account_id, @@ -1102,6 +1107,7 @@ fn test_clear_small_nominations() { let cold1 = U256::from(3); let cold2 = U256::from(4); let netuid: u16 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let amount = DefaultMinStake::::get() * 10; // Register hot1. register_ok_neuron(netuid, hot1, cold1, 0); @@ -1114,60 +1120,82 @@ fn test_clear_small_nominations() { assert_eq!(SubtensorModule::get_owning_coldkey_for_hotkey(&hot2), cold2); // Add stake cold1 --> hot1 (non delegation.) - SubtensorModule::add_balance_to_coldkey_account(&cold1, 5); + SubtensorModule::add_balance_to_coldkey_account(&cold1, amount); assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(cold1), hot1, netuid, - 1 + amount + )); + assert_ok!(SubtensorModule::remove_stake( + RuntimeOrigin::signed(cold1), + hot1, + netuid, + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold1, netuid) - 1 )); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold1, netuid), 1 ); - assert_eq!(Balances::free_balance(cold1), 4); // Add stake cold2 --> hot1 (is delegation.) - SubtensorModule::add_balance_to_coldkey_account(&cold2, 5); + SubtensorModule::add_balance_to_coldkey_account(&cold2, amount); assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(cold2), hot1, netuid, - 1 + amount + )); + assert_ok!(SubtensorModule::remove_stake( + RuntimeOrigin::signed(cold2), + hot1, + netuid, + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold2, netuid) - 1 )); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold2, netuid), 1 ); - assert_eq!(Balances::free_balance(cold2), 4); // Add stake cold1 --> hot2 (non delegation.) - SubtensorModule::add_balance_to_coldkey_account(&cold1, 5); + SubtensorModule::add_balance_to_coldkey_account(&cold1, amount); assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(cold1), hot2, netuid, - 1 + amount + )); + assert_ok!(SubtensorModule::remove_stake( + RuntimeOrigin::signed(cold1), + hot2, + netuid, + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold1, netuid) - 1 )); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold1, netuid), 1 ); - assert_eq!(Balances::free_balance(cold1), 8); + let balance1_before_cleaning = Balances::free_balance(cold1); // Add stake cold2 --> hot2 (is delegation.) - SubtensorModule::add_balance_to_coldkey_account(&cold2, 5); + SubtensorModule::add_balance_to_coldkey_account(&cold2, amount); assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(cold2), hot2, netuid, - 1 + amount + )); + assert_ok!(SubtensorModule::remove_stake( + RuntimeOrigin::signed(cold2), + hot2, + netuid, + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold2, netuid) - 1 )); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold2, netuid), 1 ); - assert_eq!(Balances::free_balance(cold2), 8); + let balance2_before_cleaning = Balances::free_balance(cold2); // Run clear all small nominations when min stake is zero (noop) SubtensorModule::set_nominator_min_required_stake(0); @@ -1218,8 +1246,10 @@ fn test_clear_small_nominations() { ); // Balances have been added back into accounts. - assert_eq!(Balances::free_balance(cold1), 9); - assert_eq!(Balances::free_balance(cold2), 9); + let balance1_after_cleaning = Balances::free_balance(cold1); + let balance2_after_cleaning = Balances::free_balance(cold2); + assert_eq!(balance1_before_cleaning + 1, balance1_after_cleaning); + assert_eq!(balance2_before_cleaning + 1, balance2_after_cleaning); assert_eq!( TotalHotkeyAlpha::::get(hot2, netuid), @@ -1556,8 +1586,8 @@ fn test_get_total_delegated_stake_after_unstaking() { let delegate_coldkey = U256::from(1); let delegate_hotkey = U256::from(2); let delegator = U256::from(3); - let initial_stake = 2_000; - let unstake_amount = 500; + let initial_stake = DefaultMinStake::::get() * 10; + let unstake_amount = DefaultMinStake::::get() * 5; let existential_deposit = ExistentialDeposit::get(); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); @@ -1578,12 +1608,12 @@ fn test_get_total_delegated_stake_after_unstaking() { assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_coldkey(&delegator), initial_stake - existential_deposit, - epsilon = 1, + epsilon = initial_stake / 1000, ); assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate_hotkey), initial_stake - existential_deposit, - epsilon = 1, + epsilon = initial_stake / 1000, ); // Unstake part of the delegation @@ -1611,12 +1641,12 @@ fn test_get_total_delegated_stake_after_unstaking() { assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_coldkey(&delegator), expected_delegated_stake, - epsilon = 1, + epsilon = expected_delegated_stake / 1000, ); assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate_hotkey), expected_delegated_stake, - epsilon = 1, + epsilon = expected_delegated_stake / 1000, ); }); } @@ -1644,7 +1674,7 @@ fn test_get_total_delegated_stake_single_delegator() { let delegate_coldkey = U256::from(1); let delegate_hotkey = U256::from(2); let delegator = U256::from(3); - let stake_amount = 999; + let stake_amount = DefaultMinStake::::get() * 10 - 1; let existential_deposit = ExistentialDeposit::get(); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); @@ -1682,12 +1712,12 @@ fn test_get_total_delegated_stake_single_delegator() { assert_abs_diff_eq!( actual_delegated_stake, expected_delegated_stake, - epsilon = 1, + epsilon = expected_delegated_stake / 1000, ); assert_abs_diff_eq!( actual_delegator_stake, expected_delegated_stake, - epsilon = 1, + epsilon = expected_delegated_stake / 1000, ); }); } @@ -1702,8 +1732,8 @@ fn test_get_alpha_share_stake_multiple_delegators() { let coldkey1 = U256::from(3); let coldkey2 = U256::from(4); let existential_deposit = 2; - let stake1 = 1000; - let stake2 = 1999; + let stake1 = DefaultMinStake::::get() * 10; + let stake2 = DefaultMinStake::::get() * 10 - 1; let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey1, coldkey1, 0); @@ -1730,19 +1760,26 @@ fn test_get_alpha_share_stake_multiple_delegators() { // Debug prints println!("Delegator1 stake: {}", stake1); println!("Delegator2 stake: {}", stake2); - println!("Alpha share for for 1: {}", SubtensorModule::get_alpha_share_pool(hotkey1, netuid).get_value(&coldkey1)); - println!("Alpha share for for 2: {}", SubtensorModule::get_alpha_share_pool(hotkey2, netuid).get_value(&coldkey2)); + println!( + "Alpha share for for 1: {}", + SubtensorModule::get_alpha_share_pool(hotkey1, netuid).get_value(&coldkey1) + ); + println!( + "Alpha share for for 2: {}", + SubtensorModule::get_alpha_share_pool(hotkey2, netuid).get_value(&coldkey2) + ); // Calculate expected total delegated stake let expected_total_stake = stake1 + stake2 - existential_deposit * 2; - let actual_total_stake = SubtensorModule::get_alpha_share_pool(hotkey1, netuid).get_value(&coldkey1) + SubtensorModule::get_alpha_share_pool(hotkey2, netuid).get_value(&coldkey2); + let actual_total_stake = SubtensorModule::get_alpha_share_pool(hotkey1, netuid) + .get_value(&coldkey1) + + SubtensorModule::get_alpha_share_pool(hotkey2, netuid).get_value(&coldkey2); - assert_eq!( + // Total subnet stake should match the sum of delegators' stakes minus existential deposits. + assert_abs_diff_eq!( actual_total_stake, expected_total_stake, - "Total subnet stake should match the sum of delegators' stakes minus existential deposits. Expected: {}, Actual: {}", - expected_total_stake, - actual_total_stake + epsilon = expected_total_stake / 1000 ); }); } @@ -1753,8 +1790,8 @@ fn test_get_total_delegated_stake_exclude_owner_stake() { let delegate_coldkey = U256::from(1); let delegate_hotkey = U256::from(2); let delegator = U256::from(3); - let owner_stake = 1000; - let delegator_stake = 999; + let owner_stake = DefaultMinStake::::get() * 10; + let delegator_stake = DefaultMinStake::::get() * 10 - 1; let netuid = add_dynamic_network(&delegate_hotkey, &delegate_coldkey); @@ -1792,10 +1829,10 @@ fn test_get_total_delegated_stake_exclude_owner_stake() { let actual_delegated_stake = SubtensorModule::get_total_stake_for_coldkey(&delegate_coldkey); - assert_eq!( - actual_delegated_stake, expected_delegated_stake, - "Delegated stake should exclude owner's stake. Expected: {}, Actual: {}", - expected_delegated_stake, actual_delegated_stake + assert_abs_diff_eq!( + actual_delegated_stake, + expected_delegated_stake, + epsilon = expected_delegated_stake / 1000 ); }); } @@ -1877,3 +1914,30 @@ fn test_mining_emission_distribution_validator_valiminer_miner() { assert_eq!(miner_emission, total_emission / 4); }); } + +// Verify staking too low amount is impossible +#[test] +fn test_staking_too_little_fails() { + new_test_ext(1).execute_with(|| { + let hotkey_account_id = U256::from(533453); + let coldkey_account_id = U256::from(55453); + let amount = 10_000; + + //add network + let netuid: u16 = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + + // Give it some $$$ in his coldkey balance + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount); + + // Coldkey / hotkey 0 decreases take to 5%. This should fail as the minimum take is 9% + assert_err!( + SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + 1 + ), + Error::::AmountTooLow + ); + }); +} diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index a4ec2d499..0cbcecfaf 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -7,12 +7,12 @@ use frame_system::{Config, RawOrigin}; use super::mock::*; use crate::*; use crate::{Call, ColdkeySwapScheduleDuration, Error}; +use approx::assert_abs_diff_eq; use frame_support::error::BadOrigin; use frame_support::traits::schedule::v3::Named as ScheduleNamed; use frame_support::traits::schedule::DispatchTime; use frame_support::traits::OnInitialize; -use sp_core::H256; -use sp_core::U256; +use sp_core::{Get, H256, U256}; use sp_runtime::DispatchError; // // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_total_hotkey_coldkey_stakes_this_interval --exact --nocapture @@ -100,7 +100,7 @@ fn test_swap_total_coldkey_stake() { let other_coldkey = U256::from(3); let hotkey = U256::from(4); let other_hotkey = U256::from(5); - let stake = 100; + let stake = DefaultMinStake::::get() * 10; let netuid = 1u16; add_network(netuid, 1, 0); @@ -311,7 +311,7 @@ fn test_swap_idempotency() { let new_coldkey = U256::from(2); let hotkey = U256::from(3); let netuid = 1u16; - let stake = 100; + let stake = DefaultMinStake::::get() * 10; // Add a network add_network(netuid, 1, 0); @@ -364,7 +364,7 @@ fn test_swap_with_max_values() { let other_coldkey = U256::from(7); let netuid = 1u16; let netuid2 = 2u16; - let stake = 100; + let stake = 10_000; let max_stake = 21_000_000_000_000_000; // 21 Million TAO; max possible balance. // Add a network @@ -432,7 +432,7 @@ fn test_swap_with_non_existent_new_coldkey() { let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); let hotkey = U256::from(3); - let stake = 100; + let stake = DefaultMinStake::::get() * 10; let netuid = 1u16; add_network(netuid, 1, 0); register_ok_neuron(netuid, hotkey, old_coldkey, 1001000); @@ -495,7 +495,7 @@ fn test_swap_effect_on_delegated_stake() { let new_coldkey = U256::from(2); let delegator = U256::from(3); let hotkey = U256::from(4); - let stake = 100; + let stake = 10_000; StakingHotkeys::::insert(old_coldkey, vec![hotkey]); StakingHotkeys::::insert(delegator, vec![hotkey]); @@ -624,8 +624,8 @@ fn test_do_swap_coldkey_success() { let hotkey1 = U256::from(3); let hotkey2 = U256::from(4); let netuid = 1u16; - let stake_amount1 = 1000u64; - let stake_amount2 = 2000u64; + let stake_amount1 = DefaultMinStake::::get() * 10; + let stake_amount2 = DefaultMinStake::::get() * 20; let swap_cost = SubtensorModule::get_key_swap_cost(); let free_balance_old = 12345u64 + swap_cost; @@ -796,9 +796,9 @@ fn test_swap_stake_for_coldkey() { let new_coldkey = U256::from(2); let hotkey1 = U256::from(3); let hotkey2 = U256::from(4); - let stake_amount1 = 1000u64; - let stake_amount2 = 2000u64; - let stake_amount3 = 3000u64; + let stake_amount1 = DefaultMinStake::::get() * 10; + let stake_amount2 = DefaultMinStake::::get() * 20; + let stake_amount3 = DefaultMinStake::::get() * 30; let total_stake = stake_amount1 + stake_amount2; let mut weight = Weight::zero(); @@ -952,8 +952,8 @@ fn test_swap_staking_hotkeys_for_coldkey() { let other_coldkey = U256::from(3); let hotkey1 = U256::from(4); let hotkey2 = U256::from(5); - let stake_amount1 = 1000u64; - let stake_amount2 = 2000u64; + let stake_amount1 = DefaultMinStake::::get() * 10; + let stake_amount2 = DefaultMinStake::::get() * 20; let total_stake = stake_amount1 + stake_amount2; let mut weight = Weight::zero(); @@ -1023,8 +1023,8 @@ fn test_swap_delegated_stake_for_coldkey() { let other_coldkey = U256::from(3); let hotkey1 = U256::from(4); let hotkey2 = U256::from(5); - let stake_amount1 = 1000u64; - let stake_amount2 = 2000u64; + let stake_amount1 = DefaultMinStake::::get() * 10; + let stake_amount2 = DefaultMinStake::::get() * 20; let mut weight = Weight::zero(); let netuid = 1u16; @@ -1233,13 +1233,14 @@ fn test_coldkey_swap_total() { let netuid1 = 1u16; let netuid2 = 2u16; let netuid3 = 3u16; - SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); - SubtensorModule::add_balance_to_coldkey_account(&delegate1, 1000); - SubtensorModule::add_balance_to_coldkey_account(&delegate2, 1000); - SubtensorModule::add_balance_to_coldkey_account(&delegate3, 1000); - SubtensorModule::add_balance_to_coldkey_account(&nominator1, 1000); - SubtensorModule::add_balance_to_coldkey_account(&nominator2, 1000); - SubtensorModule::add_balance_to_coldkey_account(&nominator3, 1000); + let stake = DefaultMinStake::::get() * 10; + SubtensorModule::add_balance_to_coldkey_account(&coldkey, stake * 6); + SubtensorModule::add_balance_to_coldkey_account(&delegate1, stake * 2); + SubtensorModule::add_balance_to_coldkey_account(&delegate2, stake * 2); + SubtensorModule::add_balance_to_coldkey_account(&delegate3, stake * 2); + SubtensorModule::add_balance_to_coldkey_account(&nominator1, stake * 2); + SubtensorModule::add_balance_to_coldkey_account(&nominator2, stake * 2); + SubtensorModule::add_balance_to_coldkey_account(&nominator3, stake * 2); // Setup initial state add_network(netuid1, 13, 0); @@ -1262,113 +1263,113 @@ fn test_coldkey_swap_total() { <::RuntimeOrigin>::signed(coldkey), hotkey1, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(coldkey), hotkey2, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(coldkey), hotkey3, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(coldkey), delegate1, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(coldkey), delegate2, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(coldkey), delegate3, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(delegate1), hotkey1, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(delegate2), hotkey2, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(delegate3), hotkey3, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(delegate1), delegate1, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(delegate2), delegate2, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(delegate3), delegate3, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(nominator1), hotkey1, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(nominator2), hotkey2, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(nominator3), hotkey3, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(nominator1), delegate1, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(nominator2), delegate2, netuid1, - 100 + stake )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(nominator3), delegate3, netuid1, - 100 + stake )); assert_eq!( @@ -1581,6 +1582,7 @@ fn test_coldkey_delegations() { let delegate = U256::from(2); let netuid = 0u16; // Stake to 0 let netuid2 = 1u16; // Stake to 1 + let stake = DefaultMinStake::::get() * 10; add_network(netuid, 13, 0); // root add_network(netuid2, 13, 0); @@ -1590,13 +1592,13 @@ fn test_coldkey_delegations() { delegate )); // register on root register_ok_neuron(netuid2, delegate, owner, 0); - SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, stake * 10); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(coldkey), delegate, netuid, - 100_u64 + stake )); // Add stake to netuid2 @@ -1604,7 +1606,7 @@ fn test_coldkey_delegations() { <::RuntimeOrigin>::signed(coldkey), delegate, netuid2, - 100_u64 + stake )); // Perform the swap @@ -1616,19 +1618,29 @@ fn test_coldkey_delegations() { )); // Verify stake was moved for the delegate - assert_eq!( + assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate), - 100 * 2 + stake * 2, + epsilon = stake / 1000 ); assert_eq!(SubtensorModule::get_total_stake_for_coldkey(&coldkey), 0); - assert_eq!( + assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), - 100 * 2 + stake * 2, + epsilon = stake / 1000 + ); + assert_abs_diff_eq!( + Alpha::::get((delegate, new_coldkey, netuid)).to_num::(), + stake, + epsilon = stake / 1000 ); - assert_eq!(Alpha::::get((delegate, new_coldkey, netuid)), 100); assert_eq!(Alpha::::get((delegate, coldkey, netuid)), 0); - assert_eq!(Alpha::::get((delegate, new_coldkey, netuid2)), 100); + assert_abs_diff_eq!( + Alpha::::get((delegate, new_coldkey, netuid2)).to_num::(), + stake, + epsilon = stake / 1000 + ); assert_eq!(Alpha::::get((delegate, coldkey, netuid2)), 0); }); } @@ -1703,7 +1715,7 @@ fn test_schedule_swap_coldkey_execution() { let new_coldkey = U256::from(2); let hotkey = U256::from(3); let netuid = 1u16; - let stake_amount = 100; + let stake_amount = DefaultMinStake::::get() * 10; add_network(netuid, 13, 0); register_ok_neuron(netuid, hotkey, old_coldkey, 0); diff --git a/pallets/subtensor/src/tests/swap_hotkey.rs b/pallets/subtensor/src/tests/swap_hotkey.rs index 596571a73..0f11dfa98 100644 --- a/pallets/subtensor/src/tests/swap_hotkey.rs +++ b/pallets/subtensor/src/tests/swap_hotkey.rs @@ -64,7 +64,7 @@ fn test_swap_total_hotkey_stake() { let old_hotkey = U256::from(1); let new_hotkey = U256::from(2); let coldkey = U256::from(3); - let amount = 10_000; + let amount = DefaultMinStake::::get() * 10; let mut weight = Weight::zero(); //add network @@ -85,7 +85,7 @@ fn test_swap_total_hotkey_stake() { assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&old_hotkey), amount, - epsilon = 1, + epsilon = amount / 1000, ); assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&new_hotkey), @@ -110,7 +110,7 @@ fn test_swap_total_hotkey_stake() { assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&new_hotkey), amount, - epsilon = 1, + epsilon = amount / 1000, ); }); } @@ -634,7 +634,7 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { let coldkey2 = U256::from(4); let netuid1 = 1; let netuid2 = 2; - let stake = 1_000_000_u64; + let stake = DefaultMinStake::::get() * 10; let mut weight = Weight::zero(); // Set up initial state diff --git a/pallets/subtensor/src/tests/weights.rs b/pallets/subtensor/src/tests/weights.rs index 50c228dc8..b71093776 100644 --- a/pallets/subtensor/src/tests/weights.rs +++ b/pallets/subtensor/src/tests/weights.rs @@ -1,25 +1,17 @@ #![allow(clippy::indexing_slicing)] use super::mock::*; -use crate::{ - // coinbase::run_coinbase::WeightsTlockPayload, CRV3WeightCommits, Error, Owner, - coinbase::run_coinbase::WeightsTlockPayload, - CRV3WeightCommits, - Error, - MAX_CRV3_COMMIT_SIZE_BYTES, -}; +use crate::coinbase::run_coinbase::WeightsTlockPayload; +use crate::*; use ark_serialize::CanonicalDeserialize; use frame_support::{ - assert_err, - assert_ok, - // dispatch::{DispatchClass, DispatchInfo, DispatchResult, GetDispatchInfo, Pays}, + assert_err, assert_ok, dispatch::{DispatchClass, DispatchResult, GetDispatchInfo, Pays}, - // pallet_prelude::{InvalidTransaction, TransactionValidityError}, }; use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; use scale_info::prelude::collections::HashMap; use sha2::Digest; -use sp_core::{H256, U256}; +use sp_core::{Get, H256, U256}; use sp_runtime::{ traits::{BlakeTwo256, ConstU32, Hash, SignedExtension}, BoundedVec, DispatchError, @@ -139,7 +131,7 @@ fn test_set_rootweights_validate() { RuntimeOrigin::signed(hotkey), hotkey, netuid, - 1 + DefaultMinStake::::get() * 10 )); // Verify stake is more than minimum @@ -253,7 +245,7 @@ fn test_commit_weights_validate() { RuntimeOrigin::signed(hotkey), hotkey, netuid, - 1 + DefaultMinStake::::get() * 10 )); // Verify stake is more than minimum @@ -433,7 +425,7 @@ fn test_reveal_weights_validate() { RuntimeOrigin::signed(hotkey), hotkey, netuid, - 1 + DefaultMinStake::::get() * 10 )); // Verify stake is more than minimum diff --git a/runtime/src/precompiles/solidity/staking.abi b/runtime/src/precompiles/solidity/staking.abi index af814335b..795b29baa 100644 --- a/runtime/src/precompiles/solidity/staking.abi +++ b/runtime/src/precompiles/solidity/staking.abi @@ -1,4 +1,17 @@ [ + { + "inputs": [ + { + "internalType": "bytes32", + "name": "delegate", + "type": "bytes32" + } + ], + "name": "addProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -17,6 +30,19 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "delegate", + "type": "bytes32" + } + ], + "name": "removeProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { diff --git a/runtime/src/precompiles/solidity/staking.sol b/runtime/src/precompiles/solidity/staking.sol index 614300218..596443c04 100644 --- a/runtime/src/precompiles/solidity/staking.sol +++ b/runtime/src/precompiles/solidity/staking.sol @@ -54,6 +54,20 @@ interface IStaking { */ function getStakeColdkey(bytes32 coldkey) external view returns (uint256); + /** + * @dev Delegates staking to a proxy account. + * + * @param delegate The public key (32 bytes) of the delegate. + */ + function addProxy(bytes32 delegate) external; + + /** + * @dev Removes staking proxy account. + * + * @param delegate The public key (32 bytes) of the delegate. + */ + function removeProxy(bytes32 delegate) external; + /** * @dev Returns the stake amount associated with the specified `hotkey` and `coldkey`. * diff --git a/runtime/src/precompiles/staking.rs b/runtime/src/precompiles/staking.rs index 55fd8a3a7..193c4bbc6 100644 --- a/runtime/src/precompiles/staking.rs +++ b/runtime/src/precompiles/staking.rs @@ -33,10 +33,13 @@ use pallet_evm::{ use sp_core::crypto::Ss58Codec; use sp_core::U256; use sp_runtime::traits::Dispatchable; -use sp_runtime::traits::{BlakeTwo256, UniqueSaturatedInto}; +use sp_runtime::traits::{BlakeTwo256, StaticLookup, UniqueSaturatedInto}; use sp_runtime::AccountId32; -use crate::precompiles::{get_method_id, get_slice}; +use crate::{ + precompiles::{get_method_id, get_slice}, + ProxyType, +}; use sp_std::vec; use crate::{Runtime, RuntimeCall}; @@ -52,27 +55,27 @@ impl StakingPrecompile { .get(4..) .map_or_else(vec::Vec::new, |slice| slice.to_vec()); // Avoiding borrowing conflicts - match method_id { - id if id == get_method_id("addStake(bytes32,uint256)") => { - Self::add_stake(handle, &method_input) - } - id if id == get_method_id("removeStake(bytes32,uint256,uint256)") => { - Self::remove_stake(handle, &method_input) - } - id if id == get_method_id("getStakeColdkey(bytes32)") => { - Self::get_stake_coldkey(&method_input) - } - id if id == get_method_id("getStake(bytes32,bytes32,uint256)") => { - Self::get_stake(&method_input) - } - _ => Err(PrecompileFailure::Error { + if method_id == get_method_id("addStake(bytes32,uint256)") { + Self::add_stake(handle, &method_input) + } else if method_id == get_method_id("removeStake(bytes32,uint256,uint256)") { + Self::remove_stake(handle, &method_input) + } else if method_id == get_method_id("getStake(bytes32,bytes32,uint256)") { + Self::get_stake(&method_input) + } else if method_id == get_method_id("getStakeColdkey(bytes32)") { + Self::get_stake_coldkey(&method_input) + } else if method_id == get_method_id("addProxy(bytes32)") { + Self::add_proxy(handle, &method_input) + } else if method_id == get_method_id("removeProxy(bytes32)") { + Self::remove_proxy(handle, &method_input) + } else { + Err(PrecompileFailure::Error { exit_status: ExitError::InvalidRange, - }), + }) } } fn add_stake(handle: &mut impl PrecompileHandle, data: &[u8]) -> PrecompileResult { - let hotkey = Self::parse_key(data)?.into(); + let hotkey = Self::parse_pub_key(data)?.into(); let amount: U256 = handle.context().apparent_value; let netuid = Self::parse_netuid(data, 0x3E)?; @@ -91,7 +94,7 @@ impl StakingPrecompile { } fn remove_stake(handle: &mut impl PrecompileHandle, data: &[u8]) -> PrecompileResult { - let hotkey = Self::parse_key(data)?.into(); + let hotkey = Self::parse_pub_key(data)?.into(); let netuid = Self::parse_netuid(data, 0x5E)?; // We have to treat this as uint256 (because of Solidity ABI encoding rules, it pads uint64), @@ -113,8 +116,8 @@ impl StakingPrecompile { Self::dispatch(handle, call) } - fn get_stake_coldkey(data: &[u8]) -> PrecompileResult { - let coldkey: AccountId32 = Self::parse_key(data)?.into(); + fn get_stake_coldkey(data: &[u8]) -> PrecompileResult { + let coldkey: AccountId32 = Self::parse_pub_key(data)?.into(); // get total stake of coldkey let total_stake = pallet_subtensor::Pallet::::get_total_stake_for_coldkey(&coldkey); @@ -128,6 +131,30 @@ impl StakingPrecompile { }) } + fn add_proxy(handle: &mut impl PrecompileHandle, data: &[u8]) -> PrecompileResult { + let delegate = AccountId32::from(Self::parse_pub_key(data)?); + let delegate = ::Lookup::unlookup(delegate); + let call = RuntimeCall::Proxy(pallet_proxy::Call::::add_proxy { + delegate, + proxy_type: ProxyType::Staking, + delay: 0, + }); + + Self::dispatch(handle, call) + } + + fn remove_proxy(handle: &mut impl PrecompileHandle, data: &[u8]) -> PrecompileResult { + let delegate = AccountId32::from(Self::parse_pub_key(data)?); + let delegate = ::Lookup::unlookup(delegate); + let call = RuntimeCall::Proxy(pallet_proxy::Call::::remove_proxy { + delegate, + proxy_type: ProxyType::Staking, + delay: 0, + }); + + Self::dispatch(handle, call) + } + fn get_stake(data: &[u8]) -> PrecompileResult { let (hotkey, coldkey) = Self::parse_hotkey_coldkey(data)?; let netuid: u16 = Self::parse_netuid(data, 0x5E)?; @@ -167,15 +194,15 @@ impl StakingPrecompile { Ok((hotkey, coldkey)) } - fn parse_key(data: &[u8]) -> Result<[u8; 32], PrecompileFailure> { + fn parse_pub_key(data: &[u8]) -> Result<[u8; 32], PrecompileFailure> { if data.len() < 32 { return Err(PrecompileFailure::Error { exit_status: ExitError::InvalidRange, }); } - let mut key = [0u8; 32]; - key.copy_from_slice(get_slice(data, 0, 32)?); - Ok(key) + let mut pubkey = [0u8; 32]; + pubkey.copy_from_slice(get_slice(data, 0, 32)?); + Ok(pubkey) } fn parse_netuid(data: &[u8], offset: usize) -> Result { diff --git a/scripts/fix_rust.sh b/scripts/fix_rust.sh new file mode 100755 index 000000000..9d2af2904 --- /dev/null +++ b/scripts/fix_rust.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +set -e # Exit immediately if a command exits with a non-zero status. + +# Function to check for git changes and commit if necessary. +commit_if_changes() { + if [ -n "$(git status --porcelain)" ]; then + echo "changes detected, committing..." + git commit -am "$1" + echo "commit created." + fi +} + +# Step 1: Run cargo check and commit changes to Cargo.lock if any. +cargo check --workspace +commit_if_changes "commit Cargo.lock" + +# Step 2: Run cargo clippy with fixes and commit changes if any. +cargo clippy --fix --workspace --all-features +commit_if_changes "cargo clippy" + +# Step 3: Run cargo fix and commit changes if any. +cargo fix --workspace --all-features --all-targets +commit_if_changes "cargo fix" + +# Step 4: Run cargo fmt and commit changes if any. +cargo fmt +commit_if_changes "cargo fmt"