diff --git a/Cargo.lock b/Cargo.lock index e86ef3d698..1d5fad7da2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6456,6 +6456,7 @@ dependencies = [ "parity-util-mem", "rand", "rand_chacha", + "safe-math", "scale-info", "serde", "serde-tuple-vec-map", @@ -8032,6 +8033,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "safe-math" +version = "0.1.0" +dependencies = [ + "num-traits", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", + "substrate-fixed", +] + [[package]] name = "safe-mix" version = "1.0.1" @@ -9568,6 +9578,7 @@ dependencies = [ name = "share-pool" version = "0.1.0" dependencies = [ + "safe-math", "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", "substrate-fixed", ] diff --git a/pallets/collective/src/lib.rs b/pallets/collective/src/lib.rs index 823e926634..707f49deda 100644 --- a/pallets/collective/src/lib.rs +++ b/pallets/collective/src/lib.rs @@ -549,7 +549,8 @@ pub mod pallet { ); let threshold = T::GetVotingMembers::get_count() - .saturating_div(2) + .checked_div(2) + .unwrap_or(0) .saturating_add(1); let members = Self::members(); diff --git a/pallets/subtensor/Cargo.toml b/pallets/subtensor/Cargo.toml index fe1bb7e2dd..f22f855fc1 100644 --- a/pallets/subtensor/Cargo.toml +++ b/pallets/subtensor/Cargo.toml @@ -41,6 +41,7 @@ pallet-utility = { workspace = true } ndarray = { workspace = true } hex = { workspace = true } share-pool = { default-features = false, path = "../../primitives/share-pool" } +safe-math = { default-features = false, path = "../../primitives/safe-math" } approx = { workspace = true } pallet-collective = { version = "4.0.0-dev", default-features = false, path = "../collective" } @@ -104,6 +105,7 @@ std = [ "ark-serialize/std", "w3f-bls/std", "rand_chacha/std", + "safe-math/std", "sha2/std", "share-pool/std" ] diff --git a/pallets/subtensor/src/coinbase/block_emission.rs b/pallets/subtensor/src/coinbase/block_emission.rs index ab570c19e7..6d2c57b742 100644 --- a/pallets/subtensor/src/coinbase/block_emission.rs +++ b/pallets/subtensor/src/coinbase/block_emission.rs @@ -1,5 +1,6 @@ use super::*; use frame_support::traits::Get; +use safe_math::*; use substrate_fixed::{transcendental::log2, types::I96F32}; impl Pallet { @@ -30,15 +31,15 @@ impl Pallet { alpha_block_emission: u64, ) -> (u64, u64, u64) { // Init terms. - let mut tao_in_emission: I96F32 = I96F32::from_num(tao_emission); - let float_alpha_block_emission: I96F32 = I96F32::from_num(alpha_block_emission); + let mut tao_in_emission: I96F32 = I96F32::saturating_from_num(tao_emission); + let float_alpha_block_emission: I96F32 = I96F32::saturating_from_num(alpha_block_emission); // Get alpha price for subnet. let alpha_price: I96F32 = Self::get_alpha_price(netuid); log::debug!("{:?} - alpha_price: {:?}", netuid, alpha_price); // Get initial alpha_in - let mut alpha_in_emission: I96F32 = I96F32::from_num(tao_emission) + let mut alpha_in_emission: I96F32 = I96F32::saturating_from_num(tao_emission) .checked_div(alpha_price) .unwrap_or(float_alpha_block_emission); @@ -59,15 +60,15 @@ impl Pallet { } // Avoid rounding errors. - if tao_in_emission < I96F32::from_num(1) || alpha_in_emission < I96F32::from_num(1) { - alpha_in_emission = I96F32::from_num(0); - tao_in_emission = I96F32::from_num(0); + if tao_in_emission < I96F32::saturating_from_num(1) + || alpha_in_emission < I96F32::saturating_from_num(1) + { + alpha_in_emission = I96F32::saturating_from_num(0); + tao_in_emission = I96F32::saturating_from_num(0); } // Set Alpha in emission. - let alpha_out_emission = I96F32::from_num(2) - .saturating_mul(float_alpha_block_emission) - .saturating_sub(alpha_in_emission); + let alpha_out_emission = float_alpha_block_emission; // Log results. log::debug!("{:?} - tao_in_emission: {:?}", netuid, tao_in_emission); @@ -80,9 +81,9 @@ impl Pallet { // Return result. ( - tao_in_emission.to_num::(), - alpha_in_emission.to_num::(), - alpha_out_emission.to_num::(), + tao_in_emission.saturating_to_num::(), + alpha_in_emission.saturating_to_num::(), + alpha_out_emission.saturating_to_num::(), ) } @@ -105,23 +106,22 @@ impl Pallet { /// Returns the block emission for an issuance value. pub fn get_block_emission_for_issuance(issuance: u64) -> Result { // Convert issuance to a float for calculations below. - let total_issuance: I96F32 = I96F32::from_num(issuance); + let total_issuance: I96F32 = I96F32::saturating_from_num(issuance); // Check to prevent division by zero when the total supply is reached // and creating an issuance greater than the total supply. - if total_issuance >= I96F32::from_num(TotalSupply::::get()) { + if total_issuance >= I96F32::saturating_from_num(TotalSupply::::get()) { return Ok(0); } // Calculate the logarithmic residual of the issuance against half the total supply. let residual: I96F32 = log2( - I96F32::from_num(1.0) + I96F32::saturating_from_num(1.0) .checked_div( - I96F32::from_num(1.0) + I96F32::saturating_from_num(1.0) .checked_sub( total_issuance - .checked_div( - I96F32::from_num(2.0) - .saturating_mul(I96F32::from_num(10_500_000_000_000_000.0)), - ) + .checked_div(I96F32::saturating_from_num(2.0).saturating_mul( + I96F32::saturating_from_num(10_500_000_000_000_000.0), + )) .ok_or("Logarithm calculation failed")?, ) .ok_or("Logarithm calculation failed")?, @@ -133,18 +133,19 @@ impl Pallet { let floored_residual: I96F32 = residual.floor(); // Calculate the final emission rate using the floored residual. // Convert floored_residual to an integer - let floored_residual_int: u64 = floored_residual.to_num::(); + let floored_residual_int: u64 = floored_residual.saturating_to_num::(); // Multiply 2.0 by itself floored_residual times to calculate the power of 2. - let mut multiplier: I96F32 = I96F32::from_num(1.0); + let mut multiplier: I96F32 = I96F32::saturating_from_num(1.0); for _ in 0..floored_residual_int { - multiplier = multiplier.saturating_mul(I96F32::from_num(2.0)); + multiplier = multiplier.saturating_mul(I96F32::saturating_from_num(2.0)); } - let block_emission_percentage: I96F32 = I96F32::from_num(1.0).saturating_div(multiplier); + let block_emission_percentage: I96F32 = + I96F32::saturating_from_num(1.0).safe_div(multiplier); // Calculate the actual emission based on the emission rate let block_emission: I96F32 = block_emission_percentage - .saturating_mul(I96F32::from_num(DefaultBlockEmission::::get())); + .saturating_mul(I96F32::saturating_from_num(DefaultBlockEmission::::get())); // Convert to u64 - let block_emission_u64: u64 = block_emission.to_num::(); + let block_emission_u64: u64 = block_emission.saturating_to_num::(); if BlockEmission::::get() != block_emission_u64 { BlockEmission::::put(block_emission_u64); } diff --git a/pallets/subtensor/src/coinbase/block_step.rs b/pallets/subtensor/src/coinbase/block_step.rs index c3abf8dabf..bcfd1a37bc 100644 --- a/pallets/subtensor/src/coinbase/block_step.rs +++ b/pallets/subtensor/src/coinbase/block_step.rs @@ -1,5 +1,6 @@ use super::*; use frame_support::storage::IterableStorageMap; +use safe_math::*; use substrate_fixed::types::{I110F18, I96F32}; impl Pallet { @@ -10,14 +11,29 @@ impl Pallet { // --- 1. Adjust difficulties. Self::adjust_registration_terms_for_networks(); // --- 2. Get the current coinbase emission. - let block_emission: I96F32 = I96F32::from_num(Self::get_block_emission().unwrap_or(0)); + let block_emission: I96F32 = + I96F32::saturating_from_num(Self::get_block_emission().unwrap_or(0)); log::debug!("Block emission: {:?}", block_emission); // --- 3. Run emission through network. Self::run_coinbase(block_emission); + + // --- 4. Set pending children on the epoch; but only after the coinbase has been run. + Self::try_set_pending_children(block_number); + // Return ok. Ok(()) } + fn try_set_pending_children(block_number: u64) { + let subnets: Vec = Self::get_all_subnet_netuids(); + for &netuid in subnets.iter() { + if Self::should_run_epoch(netuid, block_number) { + // Set pending children on the epoch. + Self::do_set_pending_children(netuid); + } + } + } + /// Adjusts the network difficulties/burns of every active network. Resetting state parameters. /// pub fn adjust_registration_terms_for_networks() { @@ -184,28 +200,28 @@ impl Pallet { registrations_this_interval: u16, target_registrations_per_interval: u16, ) -> u64 { - let updated_difficulty: I110F18 = I110F18::from_num(current_difficulty) - .saturating_mul(I110F18::from_num( + let updated_difficulty: I110F18 = I110F18::saturating_from_num(current_difficulty) + .saturating_mul(I110F18::saturating_from_num( registrations_this_interval.saturating_add(target_registrations_per_interval), )) - .saturating_div(I110F18::from_num( + .safe_div(I110F18::saturating_from_num( target_registrations_per_interval.saturating_add(target_registrations_per_interval), )); - let alpha: I110F18 = I110F18::from_num(Self::get_adjustment_alpha(netuid)) - .saturating_div(I110F18::from_num(u64::MAX)); + let alpha: I110F18 = I110F18::saturating_from_num(Self::get_adjustment_alpha(netuid)) + .safe_div(I110F18::saturating_from_num(u64::MAX)); let next_value: I110F18 = alpha - .saturating_mul(I110F18::from_num(current_difficulty)) + .saturating_mul(I110F18::saturating_from_num(current_difficulty)) .saturating_add( - I110F18::from_num(1.0) + I110F18::saturating_from_num(1.0) .saturating_sub(alpha) .saturating_mul(updated_difficulty), ); - if next_value >= I110F18::from_num(Self::get_max_difficulty(netuid)) { + if next_value >= I110F18::saturating_from_num(Self::get_max_difficulty(netuid)) { Self::get_max_difficulty(netuid) - } else if next_value <= I110F18::from_num(Self::get_min_difficulty(netuid)) { + } else if next_value <= I110F18::saturating_from_num(Self::get_min_difficulty(netuid)) { return Self::get_min_difficulty(netuid); } else { - return next_value.to_num::(); + return next_value.saturating_to_num::(); } } @@ -218,28 +234,28 @@ impl Pallet { registrations_this_interval: u16, target_registrations_per_interval: u16, ) -> u64 { - let updated_burn: I110F18 = I110F18::from_num(current_burn) - .saturating_mul(I110F18::from_num( + let updated_burn: I110F18 = I110F18::saturating_from_num(current_burn) + .saturating_mul(I110F18::saturating_from_num( registrations_this_interval.saturating_add(target_registrations_per_interval), )) - .saturating_div(I110F18::from_num( + .safe_div(I110F18::saturating_from_num( target_registrations_per_interval.saturating_add(target_registrations_per_interval), )); - let alpha: I110F18 = I110F18::from_num(Self::get_adjustment_alpha(netuid)) - .saturating_div(I110F18::from_num(u64::MAX)); + let alpha: I110F18 = I110F18::saturating_from_num(Self::get_adjustment_alpha(netuid)) + .safe_div(I110F18::saturating_from_num(u64::MAX)); let next_value: I110F18 = alpha - .saturating_mul(I110F18::from_num(current_burn)) + .saturating_mul(I110F18::saturating_from_num(current_burn)) .saturating_add( - I110F18::from_num(1.0) + I110F18::saturating_from_num(1.0) .saturating_sub(alpha) .saturating_mul(updated_burn), ); - if next_value >= I110F18::from_num(Self::get_max_burn_as_u64(netuid)) { + if next_value >= I110F18::saturating_from_num(Self::get_max_burn_as_u64(netuid)) { Self::get_max_burn_as_u64(netuid) - } else if next_value <= I110F18::from_num(Self::get_min_burn_as_u64(netuid)) { + } else if next_value <= I110F18::saturating_from_num(Self::get_min_burn_as_u64(netuid)) { return Self::get_min_burn_as_u64(netuid); } else { - return next_value.to_num::(); + return next_value.saturating_to_num::(); } } } diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 112c887853..3f83f934f1 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -19,6 +19,7 @@ use super::*; use frame_support::dispatch::Pays; use frame_support::storage::IterableStorageDoubleMap; use frame_support::weights::Weight; +use safe_math::*; use sp_core::Get; use sp_std::vec; use substrate_fixed::types::I64F64; @@ -112,7 +113,7 @@ impl Pallet { // --- 2. Initialize a 2D vector with zeros to store the weights. The dimensions are determined // by `n` (number of validators) and `k` (total number of subnets). - let mut weights: Vec> = vec![vec![I64F64::from_num(0.0); k]; n]; + let mut weights: Vec> = vec![vec![I64F64::saturating_from_num(0.0); k]; n]; log::debug!("weights:\n{:?}\n", weights); let subnet_list = Self::get_all_subnet_netuids(); @@ -134,7 +135,7 @@ impl Pallet { .zip(&subnet_list) .find(|(_, subnet)| *subnet == netuid) { - *w = I64F64::from_num(*weight_ij); + *w = I64F64::saturating_from_num(*weight_ij); } } } @@ -624,7 +625,7 @@ impl Pallet { let mut lock_cost = last_lock.saturating_mul(mult).saturating_sub( last_lock - .saturating_div(lock_reduction_interval) + .safe_div(lock_reduction_interval) .saturating_mul(current_block.saturating_sub(last_lock_block)), ); diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index e86c07c242..7516857b49 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -1,5 +1,6 @@ use super::*; use alloc::collections::BTreeMap; +use safe_math::*; use substrate_fixed::types::I96F32; use tle::stream_ciphers::AESGCMStreamCipherProvider; use tle::tlock::tld; @@ -24,15 +25,16 @@ impl Pallet { validator_proportion: I96F32, ) -> I96F32 { // Get total TAO on root. - let total_root_tao: I96F32 = I96F32::from_num(SubnetTAO::::get(0)); + let total_root_tao: I96F32 = I96F32::saturating_from_num(SubnetTAO::::get(0)); // Get total ALPHA on subnet. - let total_alpha_issuance: I96F32 = I96F32::from_num(Self::get_alpha_issuance(netuid)); + let total_alpha_issuance: I96F32 = + I96F32::saturating_from_num(Self::get_alpha_issuance(netuid)); // Get tao_weight let tao_weight: I96F32 = total_root_tao.saturating_mul(Self::get_tao_weight()); // Get root proportional dividends. let root_proportion: I96F32 = tao_weight .checked_div(tao_weight.saturating_add(total_alpha_issuance)) - .unwrap_or(I96F32::from_num(0.0)); + .unwrap_or(I96F32::saturating_from_num(0.0)); // Get root proportion of alpha_out dividends. let root_divs_in_alpha: I96F32 = root_proportion .saturating_mul(alpha_out_emission) @@ -53,17 +55,22 @@ impl Pallet { // --- 2. Sum all the SubnetTAO associated with the same mechanism. // Mechanisms get emission based on the proportion of TAO across all their subnets - let mut total_active_tao: I96F32 = I96F32::from_num(0); + let mut total_active_tao: I96F32 = I96F32::saturating_from_num(0); let mut mechanism_tao: BTreeMap = BTreeMap::new(); for netuid in subnets.iter() { if *netuid == 0 { continue; } // Skip root network let mechid = SubnetMechanism::::get(*netuid); - let subnet_tao = I96F32::from_num(SubnetTAO::::get(*netuid)); - let new_subnet_tao = subnet_tao - .saturating_add(*mechanism_tao.entry(mechid).or_insert(I96F32::from_num(0))); - *mechanism_tao.entry(mechid).or_insert(I96F32::from_num(0)) = new_subnet_tao; + let subnet_tao = I96F32::saturating_from_num(SubnetTAO::::get(*netuid)); + let new_subnet_tao = subnet_tao.saturating_add( + *mechanism_tao + .entry(mechid) + .or_insert(I96F32::saturating_from_num(0)), + ); + *mechanism_tao + .entry(mechid) + .or_insert(I96F32::saturating_from_num(0)) = new_subnet_tao; total_active_tao = total_active_tao.saturating_add(subnet_tao); } log::debug!("Mechanism TAO sums: {:?}", mechanism_tao); @@ -79,10 +86,12 @@ impl Pallet { let mechid: u16 = SubnetMechanism::::get(*netuid); log::debug!("Netuid: {:?}, Mechanism ID: {:?}", netuid, mechid); // 3.2: Get subnet TAO (T_s) - let subnet_tao: I96F32 = I96F32::from_num(SubnetTAO::::get(*netuid)); + let subnet_tao: I96F32 = I96F32::saturating_from_num(SubnetTAO::::get(*netuid)); log::debug!("Subnet TAO (T_s) for netuid {:?}: {:?}", netuid, subnet_tao); // 3.3: Get the denominator as the sum of all TAO associated with a specific mechanism (T_m) - let mech_tao: I96F32 = *mechanism_tao.get(&mechid).unwrap_or(&I96F32::from_num(0)); + let mech_tao: I96F32 = *mechanism_tao + .get(&mechid) + .unwrap_or(&I96F32::saturating_from_num(0)); log::debug!( "Mechanism TAO (T_m) for mechanism ID {:?}: {:?}", mechid, @@ -91,7 +100,7 @@ impl Pallet { // 3.4: Compute the mechanism emission proportion: P_m = T_m / T_total let mech_proportion: I96F32 = mech_tao .checked_div(total_active_tao) - .unwrap_or(I96F32::from_num(0)); + .unwrap_or(I96F32::saturating_from_num(0)); log::debug!( "Mechanism proportion (P_m) for mechanism ID {:?}: {:?}", mechid, @@ -107,7 +116,7 @@ impl Pallet { // 3.6: Calculate subnet's proportion of mechanism TAO: P_s = T_s / T_m let subnet_proportion: I96F32 = subnet_tao .checked_div(mech_tao) - .unwrap_or(I96F32::from_num(0)); + .unwrap_or(I96F32::saturating_from_num(0)); log::debug!( "Subnet proportion (P_s) for netuid {:?}: {:?}", netuid, @@ -116,8 +125,8 @@ impl Pallet { // 3.7: Calculate subnet's TAO emission: E_s = P_s * E_m let tao_in: u64 = mech_emission .checked_mul(subnet_proportion) - .unwrap_or(I96F32::from_num(0)) - .to_num::(); + .unwrap_or(I96F32::saturating_from_num(0)) + .saturating_to_num::(); log::debug!( "Subnet TAO emission (E_s) for netuid {:?}: {:?}", netuid, @@ -198,9 +207,9 @@ impl Pallet { }); // Calculate the owner cut. - let owner_cut: u64 = I96F32::from_num(alpha_out_emission) + let owner_cut: u64 = I96F32::saturating_from_num(alpha_out_emission) .saturating_mul(Self::get_float_subnet_owner_cut()) - .to_num::(); + .saturating_to_num::(); log::debug!("Owner cut for netuid {:?}: {:?}", netuid, owner_cut); // Store the owner cut for this subnet. *owner_cuts.entry(*netuid).or_insert(0) = owner_cut; @@ -213,31 +222,36 @@ impl Pallet { ); // Validators get 50% of remaining emission. - let validator_proportion: I96F32 = I96F32::from_num(0.5); + let validator_proportion: I96F32 = I96F32::saturating_from_num(0.5); // Get proportion of alpha out emission as root divs. let root_emission_in_alpha: I96F32 = Self::get_root_divs_in_alpha( *netuid, - I96F32::from_num(remaining_emission), + I96F32::saturating_from_num(remaining_emission), validator_proportion, ); // Subtract root divs from alpha divs. - let pending_alpha_emission: I96F32 = - I96F32::from_num(remaining_emission).saturating_sub(root_emission_in_alpha); + let pending_alpha_emission: I96F32 = I96F32::saturating_from_num(remaining_emission) + .saturating_sub(root_emission_in_alpha); // Sell root emission through the pool. - let root_emission_in_tao: u64 = - Self::swap_alpha_for_tao(*netuid, root_emission_in_alpha.to_num::()); - SubnetAlphaEmissionSell::::insert(*netuid, root_emission_in_alpha.to_num::()); + let root_emission_in_tao: u64 = Self::swap_alpha_for_tao( + *netuid, + root_emission_in_alpha.saturating_to_num::(), + ); + SubnetAlphaEmissionSell::::insert( + *netuid, + root_emission_in_alpha.saturating_to_num::(), + ); // Accumulate root divs for subnet. PendingRootDivs::::mutate(*netuid, |total| { *total = total.saturating_add(root_emission_in_tao); }); // Accumulate alpha that was swapped for the pending root divs. PendingAlphaSwapped::::mutate(*netuid, |total| { - *total = total.saturating_add(root_emission_in_alpha.to_num::()); + *total = total.saturating_add(root_emission_in_alpha.saturating_to_num::()); }); // Accumulate alpha emission in pending. PendingEmission::::mutate(*netuid, |total| { - *total = total.saturating_add(pending_alpha_emission.to_num::()); + *total = total.saturating_add(pending_alpha_emission.saturating_to_num::()); }); // Accumulate the owner cut in pending. PendingOwnerCut::::mutate(*netuid, |total| { @@ -308,7 +322,7 @@ impl Pallet { owner_cut ); - // Run the epoch() --> hotkey emission. + // === 1. Run the epoch() --> hotkey emission. // Needs to run on the full emission to the subnet. let hotkey_emission: Vec<(T::AccountId, u64, u64)> = Self::epoch( netuid, @@ -320,15 +334,16 @@ impl Pallet { hotkey_emission ); - // Pay out the hotkey alpha dividends. - // First clear the netuid from HotkeyDividends - let mut total_root_alpha_divs: u64 = 0; - let mut root_alpha_divs: BTreeMap = BTreeMap::new(); + // === 2. Calculate the dividend distributions using the current stake. + // Clear maps let _ = AlphaDividendsPerSubnet::::clear_prefix(netuid, u32::MAX, None); + let _ = TaoDividendsPerSubnet::::clear_prefix(netuid, u32::MAX, None); + // Initialize maps for parent-child OR miner dividend distributions. let mut dividends_to_distribute: Vec<(T::AccountId, Vec<(T::AccountId, u64)>)> = Vec::new(); let mut mining_incentive_to_distribute: Vec<(T::AccountId, u64)> = Vec::new(); + // 2.1 --- Get dividend distribution from parent-child and miner distributions. for (hotkey, incentive, dividends) in hotkey_emission { log::debug!( "Processing hotkey {:?} with incentive {:?} and dividends {:?}", @@ -354,77 +369,134 @@ impl Pallet { dividends_to_distribute.push((hotkey.clone(), dividend_tuples)); } - // Calculate the validator take and root alpha divs using the alpha divs. + // Initialize maps for dividend calculations. + let mut root_alpha_divs: BTreeMap = BTreeMap::new(); + let mut alpha_divs: BTreeMap = BTreeMap::new(); + let mut validator_alpha_takes: BTreeMap = BTreeMap::new(); + let mut validator_root_alpha_takes: BTreeMap = BTreeMap::new(); + // 2.2 --- Calculate the validator_take, alpha_divs, and root_alpha_divs using above dividend tuples. for (hotkey, dividend_tuples) in dividends_to_distribute.iter() { - // Get the local alpha and root alpha. - let hotkey_tao: I96F32 = I96F32::from_num(Self::get_stake_for_hotkey_on_subnet( - hotkey, - Self::get_root_netuid(), - )); - let hotkey_tao_as_alpha: I96F32 = hotkey_tao.saturating_mul(Self::get_tao_weight()); - let hotkey_alpha = - I96F32::from_num(Self::get_stake_for_hotkey_on_subnet(hotkey, netuid)); - log::debug!("Hotkey tao for hotkey {:?} on root netuid: {:?}, hotkey tao as alpha: {:?}, hotkey alpha: {:?}", hotkey, hotkey_tao, hotkey_tao_as_alpha, hotkey_alpha); - - // Compute alpha and root proportions. - let alpha_prop: I96F32 = hotkey_alpha - .checked_div(hotkey_alpha.saturating_add(hotkey_tao_as_alpha)) - .unwrap_or(I96F32::from_num(0.0)); - let root_prop: I96F32 = hotkey_tao_as_alpha - .checked_div(hotkey_alpha.saturating_add(hotkey_tao_as_alpha)) - .unwrap_or(I96F32::from_num(0.0)); - log::debug!( - "Alpha proportion: {:?}, root proportion: {:?}", - alpha_prop, - root_prop - ); - - // Calculate the dividends to hotkeys based on the local vs root proportion. + // Calculate the proportion of root vs alpha divs for each hotkey using their stake. for (hotkey_j, divs_j) in dividend_tuples.iter() { log::debug!( - "Processing dividend for hotkey {:?} to hotkey {:?}: {:?}", + "Processing dividend for child-hotkey {:?} to parent-hotkey {:?}: {:?}", hotkey, hotkey_j, *divs_j ); - // Remove the hotkey take straight off the top. - let take_prop: I96F32 = I96F32::from_num(Self::get_hotkey_take(hotkey_j)) - .checked_div(I96F32::from_num(u16::MAX)) - .unwrap_or(I96F32::from_num(0.0)); - let validator_take: I96F32 = take_prop.saturating_mul(I96F32::from_num(*divs_j)); - let rem_divs_j: I96F32 = I96F32::from_num(*divs_j).saturating_sub(validator_take); + // 2.1 --- Get the local alpha and root alpha. + let hotkey_tao: I96F32 = I96F32::saturating_from_num( + Self::get_stake_for_hotkey_on_subnet(hotkey_j, Self::get_root_netuid()), + ); + let hotkey_tao_as_alpha: I96F32 = hotkey_tao.saturating_mul(Self::get_tao_weight()); + let hotkey_alpha = I96F32::saturating_from_num( + Self::get_stake_for_hotkey_on_subnet(hotkey_j, netuid), + ); + log::debug!("Hotkey tao for hotkey {:?} on root netuid: {:?}, hotkey tao as alpha: {:?}, hotkey alpha: {:?}", hotkey_j, hotkey_tao, hotkey_tao_as_alpha, hotkey_alpha); + + // 2.2 --- Compute alpha and root proportions. + let alpha_prop: I96F32 = hotkey_alpha + .checked_div(hotkey_alpha.saturating_add(hotkey_tao_as_alpha)) + .unwrap_or(I96F32::saturating_from_num(0.0)); + let root_prop: I96F32 = hotkey_tao_as_alpha + .checked_div(hotkey_alpha.saturating_add(hotkey_tao_as_alpha)) + .unwrap_or(I96F32::saturating_from_num(0.0)); + log::debug!( + "Alpha proportion: {:?}, root proportion: {:?}", + alpha_prop, + root_prop + ); + + let divs_j: I96F32 = I96F32::saturating_from_num(*divs_j); + // 2.3.1 --- Compute root dividends + let root_alpha_divs_j: I96F32 = divs_j.saturating_mul(root_prop); + // 2.3.2 --- Compute alpha dividends + let alpha_divs_j: I96F32 = divs_j.saturating_sub(root_alpha_divs_j); log::debug!( - "Validator take for hotkey {:?}: {:?}, remaining dividends: {:?}", + "Alpha dividends: {:?}, Root alpha-dividends: {:?}", + alpha_divs_j, + root_alpha_divs_j + ); + + // 2.4.1 --- Remove the hotkey take from both alpha and root divs. + let take_prop: I96F32 = + I96F32::saturating_from_num(Self::get_hotkey_take(hotkey_j)) + .checked_div(I96F32::saturating_from_num(u16::MAX)) + .unwrap_or(I96F32::saturating_from_num(0.0)); + + let validator_alpha_take: I96F32 = take_prop.saturating_mul(alpha_divs_j); + let validator_root_alpha_take: I96F32 = take_prop.saturating_mul(root_alpha_divs_j); + + let rem_alpha_divs_j: I96F32 = alpha_divs_j.saturating_sub(validator_alpha_take); + let rem_root_alpha_divs_j: I96F32 = + root_alpha_divs_j.saturating_sub(validator_root_alpha_take); + log::debug!( + "Validator take for hotkey {:?}: alpha take: {:?}, remaining alpha: {:?}, root-alpha take: {:?}, remaining root-alpha: {:?}", hotkey_j, - validator_take, - rem_divs_j + validator_alpha_take, + rem_alpha_divs_j, + validator_root_alpha_take, + rem_root_alpha_divs_j ); - // Compute root dividends - let root_divs: I96F32 = rem_divs_j.saturating_mul(root_prop); + // 2.4.2 --- Store the validator takes. + validator_alpha_takes + .entry(hotkey_j.clone()) + .and_modify(|e| { + *e = e.saturating_add(validator_alpha_take.saturating_to_num::()) + }) + .or_insert(validator_alpha_take.saturating_to_num::()); + validator_root_alpha_takes + .entry(hotkey_j.clone()) + .and_modify(|e| { + *e = e.saturating_add(validator_root_alpha_take.saturating_to_num::()) + }) + .or_insert(validator_root_alpha_take.saturating_to_num::()); log::debug!( - "Alpha dividends: {:?}, root dividends: {:?}", - rem_divs_j, - root_divs + "Stored validator take for hotkey {:?}: alpha take: {:?}, root-alpha take: {:?}", + hotkey_j, + validator_alpha_take.saturating_to_num::(), + validator_root_alpha_take.saturating_to_num::() ); - // Store the root-alpha divs under hotkey_j + // 2.5.1 --- Store the root divs under hotkey_j root_alpha_divs .entry(hotkey_j.clone()) - .and_modify(|e| *e = e.saturating_add(root_divs.to_num::())) - .or_insert(root_divs.to_num::()); - total_root_alpha_divs = - total_root_alpha_divs.saturating_add(root_divs.to_num::()); + .and_modify(|e| { + *e = e.saturating_add(rem_root_alpha_divs_j.saturating_to_num::()) + }) + .or_insert(rem_root_alpha_divs_j.saturating_to_num::()); log::debug!( "Stored root alpha dividends for hotkey {:?}: {:?}", hotkey_j, - root_divs.to_num::() + rem_root_alpha_divs_j.saturating_to_num::() + ); + + // 2.5.2 --- Store the alpha dividends + alpha_divs + .entry(hotkey_j.clone()) + .and_modify(|e| { + *e = e.saturating_add(rem_alpha_divs_j.saturating_to_num::()) + }) + .or_insert(rem_alpha_divs_j.saturating_to_num::()); + log::debug!( + "Stored alpha dividends for hotkey {:?}: {:?}", + hotkey_j, + rem_alpha_divs_j.saturating_to_num::() ); } } - // Check for existence of owner cold/hot pair and distribute emission directly to them. + let total_root_alpha_divs: u64 = root_alpha_divs + .values() + .sum::() + .saturating_add(validator_root_alpha_takes.values().sum::()); + + // === 3. Distribute the dividends to the hotkeys. + + // 3.1 --- Distribute owner cut. + // Check for existence of subnet owner cold/hot pair and distribute emission directly to them. if let Ok(owner_coldkey) = SubnetOwner::::try_get(netuid) { if let Ok(owner_hotkey) = SubnetOwnerHotkey::::try_get(netuid) { // Increase stake for both coldkey and hotkey on the subnet @@ -438,90 +510,128 @@ impl Pallet { } } - // Distribute mining incentive. - for (hotkey, incentive) in mining_incentive_to_distribute { + // 3.2 --- Distribute mining incentive. + for (miner_j, incentive) in mining_incentive_to_distribute { // Distribute mining incentive immediately. Self::increase_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey.clone(), - &Owner::::get(hotkey.clone()), + &miner_j.clone(), + &Owner::::get(miner_j.clone()), netuid, incentive, ); log::debug!( "Distributed mining incentive for hotkey {:?} on netuid {:?}: {:?}", - hotkey, + miner_j, netuid, incentive ); } - // Distribute validator take and alpha-dividends. - for (_hotkey, dividend_tuples) in dividends_to_distribute.iter() { - // Pay out dividends to hotkeys based on the local vs root proportion. - for (hotkey_j, divs_j) in dividend_tuples.iter() { - // Remove the hotkey take straight off the top. - let take_prop: I96F32 = I96F32::from_num(Self::get_hotkey_take(hotkey_j)) - .checked_div(I96F32::from_num(u16::MAX)) - .unwrap_or(I96F32::from_num(0.0)); - let validator_take: I96F32 = take_prop.saturating_mul(I96F32::from_num(*divs_j)); - let rem_divs_j: I96F32 = I96F32::from_num(*divs_j).saturating_sub(validator_take); - log::debug!( - "Validator take for hotkey {:?}: {:?}, remaining dividends: {:?}", - hotkey_j, - validator_take, - rem_divs_j - ); + // 3.3.1 --- Distribute validator alpha takes + for (validator_j, validator_take) in validator_alpha_takes { + // 3.3.1a --- Distribute validator take to the validator. + Self::increase_stake_for_hotkey_and_coldkey_on_subnet( + &validator_j.clone(), + &Owner::::get(validator_j.clone()), + netuid, + validator_take, + ); + log::debug!( + "Distributed validator take for hotkey {:?} on netuid {:?}: {:?}", + validator_j, + netuid, + validator_take + ); - // Distribute validator take. - Self::increase_stake_for_hotkey_and_coldkey_on_subnet( - hotkey_j, - &Owner::::get(hotkey_j.clone()), - netuid, - validator_take.to_num::(), - ); - log::debug!( - "Distributed validator take for hotkey {:?} on netuid {:?}: {:?}", - hotkey_j, - netuid, - validator_take.to_num::() - ); + // 3.3.1b --- Record dividends for this validator on this subnet. + AlphaDividendsPerSubnet::::mutate(netuid, validator_j.clone(), |divs| { + *divs = divs.saturating_add(validator_take); + }); + log::debug!( + "Recorded dividends for validator {:?} on netuid {:?}: {:?}", + validator_j, + netuid, + validator_take + ); + } - // Distribute the alpha divs to the hotkey. - Self::increase_stake_for_hotkey_on_subnet( - hotkey_j, - netuid, - rem_divs_j.to_num::(), - ); - log::debug!( - "Distributed alpha dividends for hotkey {:?} on netuid {:?}: {:?}", - hotkey_j, - netuid, - rem_divs_j.to_num::() - ); + // 3.3.2 --- Distribute validator root-alpha takes + for (validator_j, validator_take) in validator_root_alpha_takes { + // 3.3.2a --- Calculate the proportion of root divs to pay out to this validator's take. + let proportion: I96F32 = I96F32::saturating_from_num(validator_take) + .checked_div(I96F32::saturating_from_num(total_root_alpha_divs)) + .unwrap_or(I96F32::saturating_from_num(0)); + // 3.3.2b --- Get the proportion of root divs from the pending root divs. + let take_as_root_divs: u64 = proportion + .saturating_mul(I96F32::saturating_from_num(pending_root_divs)) + .saturating_to_num::(); + log::debug!( + "Root div proportion for validator take {:?}: {:?}, take_as_root_divs: {:?}", + validator_take, + proportion, + take_as_root_divs + ); - // Record dividends for this hotkey on this subnet. - AlphaDividendsPerSubnet::::mutate(netuid, hotkey_j.clone(), |divs| { - *divs = divs.saturating_add(*divs_j); - }); - log::debug!( - "Recorded dividends for hotkey {:?} on netuid {:?}: {:?}", - hotkey_j, - netuid, - *divs_j - ); - } + // 3.3.2c --- Distribute validator take to the validator. + Self::increase_stake_for_hotkey_and_coldkey_on_subnet( + &validator_j.clone(), + &Owner::::get(validator_j.clone()), + Self::get_root_netuid(), + take_as_root_divs, + ); + log::debug!( + "Distributed validator take for hotkey {:?} on root netuid {:?}: {:?}", + validator_j, + Self::get_root_netuid(), + take_as_root_divs + ); + + // 3.3.2d --- Record dividends for this validator on this subnet. + TaoDividendsPerSubnet::::mutate(netuid, validator_j.clone(), |divs| { + *divs = divs.saturating_add(take_as_root_divs); + }); + log::debug!( + "Recorded dividends for validator {:?} on netuid {:?}: {:?}", + validator_j, + netuid, + take_as_root_divs + ); } - // For all the root-alpha divs give this proportion of the swapped tao to the root participants. - let _ = TaoDividendsPerSubnet::::clear_prefix(netuid, u32::MAX, None); + // 3.4 --- Distribute alpha divs + for (hotkey_j, alpha_divs_j) in alpha_divs { + // 3.4.1 --- Distribute alpha divs to the hotkey. + Self::increase_stake_for_hotkey_on_subnet(&hotkey_j.clone(), netuid, alpha_divs_j); + log::debug!( + "Distributed alpha dividends for hotkey {:?} on netuid {:?}: {:?}", + hotkey_j, + netuid, + alpha_divs_j + ); + + // 3.4.2 --- Record dividends for this hotkey on this subnet. + AlphaDividendsPerSubnet::::mutate(netuid, hotkey_j.clone(), |divs| { + *divs = divs.saturating_add(alpha_divs_j); + }); + log::debug!( + "Recorded dividends for hotkey {:?} on netuid {:?}: {:?}", + hotkey_j, + netuid, + alpha_divs_j + ); + } - for (hotkey_j, root_divs) in root_alpha_divs.iter() { - let proportion: I96F32 = I96F32::from_num(*root_divs) - .checked_div(I96F32::from_num(total_root_alpha_divs)) - .unwrap_or(I96F32::from_num(0)); + // 3.5 --- Distribute root divs + // For all the root-alpha divs give this proportion of the swapped tao to the root participants. + for (hotkey_j, root_alpha_divs_j) in root_alpha_divs.iter() { + // 3.5.1 --- Calculate the proportion of root divs to pay out to this hotkey. + let proportion: I96F32 = I96F32::saturating_from_num(*root_alpha_divs_j) + .checked_div(I96F32::saturating_from_num(total_root_alpha_divs)) + .unwrap_or(I96F32::saturating_from_num(0)); + // 3.5.2 --- Get the proportion of root divs from the pending root divs. let root_divs_to_pay: u64 = proportion - .saturating_mul(I96F32::from_num(pending_root_divs)) - .to_num::(); + .saturating_mul(I96F32::saturating_from_num(pending_root_divs)) + .saturating_to_num::(); log::debug!( "Proportion for hotkey {:?}: {:?}, root_divs_to_pay: {:?}", hotkey_j, @@ -529,22 +639,29 @@ impl Pallet { root_divs_to_pay ); - // Pay the tao to the hotkey on netuid 0 + // 3.5.3 --- Distribute the root divs to the hotkey on the root subnet. Self::increase_stake_for_hotkey_on_subnet( hotkey_j, Self::get_root_netuid(), root_divs_to_pay, ); log::debug!( - "Paid tao to hotkey {:?} on root netuid: {:?}", + "Paid tao to hotkey {:?} on root netuid from netuid {:?}: {:?}", hotkey_j, + netuid, root_divs_to_pay ); - // Record dividends for this hotkey on this subnet. + // 3.5.4 --- Record dividends for this hotkey on this subnet. TaoDividendsPerSubnet::::mutate(netuid, hotkey_j.clone(), |divs| { *divs = divs.saturating_add(root_divs_to_pay); }); + log::debug!( + "Recorded dividends for hotkey {:?} on netuid {:?}: {:?}", + hotkey_j, + netuid, + root_divs_to_pay + ); } } @@ -553,11 +670,11 @@ impl Pallet { pub fn get_self_contribution(hotkey: &T::AccountId, netuid: u16) -> u64 { // Get all childkeys for this hotkey. let childkeys = Self::get_children(hotkey, netuid); - let mut remaining_proportion: I96F32 = I96F32::from_num(1.0); + let mut remaining_proportion: I96F32 = I96F32::saturating_from_num(1.0); for (proportion, _) in childkeys { remaining_proportion = remaining_proportion.saturating_sub( - I96F32::from_num(proportion) // Normalize - .saturating_div(I96F32::from_num(u64::MAX)), + I96F32::saturating_from_num(proportion) // Normalize + .safe_div(I96F32::saturating_from_num(u64::MAX)), ); } @@ -565,12 +682,12 @@ impl Pallet { let tao_weight: I96F32 = Self::get_tao_weight(); // Get the hotkey's stake including weight - let root_stake: I96F32 = I96F32::from_num(Self::get_stake_for_hotkey_on_subnet( + let root_stake: I96F32 = I96F32::saturating_from_num(Self::get_stake_for_hotkey_on_subnet( hotkey, Self::get_root_netuid(), )); let alpha_stake: I96F32 = - I96F32::from_num(Self::get_stake_for_hotkey_on_subnet(hotkey, netuid)); + I96F32::saturating_from_num(Self::get_stake_for_hotkey_on_subnet(hotkey, netuid)); // Calculate the let alpha_contribution: I96F32 = alpha_stake.saturating_mul(remaining_proportion); @@ -580,7 +697,7 @@ impl Pallet { let combined_contribution: I96F32 = alpha_contribution.saturating_add(root_contribution); // Return the combined contribution as a u64 - combined_contribution.to_num::() + combined_contribution.saturating_to_num::() } /// Returns a list of tuples for each parent associated with this hotkey including self @@ -605,10 +722,10 @@ impl Pallet { let mut dividend_tuples: Vec<(T::AccountId, u64)> = vec![]; // Calculate the hotkey's share of the validator emission based on its childkey take - let validating_emission: I96F32 = I96F32::from_num(dividends); + let validating_emission: I96F32 = I96F32::saturating_from_num(dividends); let childkey_take_proportion: I96F32 = - I96F32::from_num(Self::get_childkey_take(hotkey, netuid)) - .saturating_div(I96F32::from_num(u16::MAX)); + I96F32::saturating_from_num(Self::get_childkey_take(hotkey, netuid)) + .safe_div(I96F32::saturating_from_num(u16::MAX)); log::debug!( "Childkey take proportion: {:?} for hotkey {:?}", childkey_take_proportion, @@ -617,8 +734,8 @@ impl Pallet { // NOTE: Only the validation emission should be split amongst parents. // Reserve childkey take - let child_emission_take: I96F32 = - childkey_take_proportion.saturating_mul(I96F32::from_num(validating_emission)); + let child_emission_take: I96F32 = childkey_take_proportion + .saturating_mul(I96F32::saturating_from_num(validating_emission)); let remaining_emission: I96F32 = validating_emission.saturating_sub(child_emission_take); log::debug!( "Child emission take: {:?} for hotkey {:?}", @@ -635,7 +752,7 @@ impl Pallet { let mut to_parents: u64 = 0; // Initialize variables to calculate total stakes from parents - let mut total_contribution: I96F32 = I96F32::from_num(0); + let mut total_contribution: I96F32 = I96F32::saturating_from_num(0); let mut parent_contributions: Vec<(T::AccountId, I96F32)> = Vec::new(); // Get the weights for root and alpha stakes in emission distribution @@ -650,21 +767,21 @@ impl Pallet { self_contribution ); // Add self contribution to total contribution but not to the parent contributions. - total_contribution = total_contribution.saturating_add(I96F32::from_num(self_contribution)); + total_contribution = + total_contribution.saturating_add(I96F32::saturating_from_num(self_contribution)); // Calculate total root and alpha (subnet-specific) stakes from all parents for (proportion, parent) in Self::get_parents(hotkey, netuid) { // Convert the parent's stake proportion to a fractional value - let parent_proportion: I96F32 = - I96F32::from_num(proportion).saturating_div(I96F32::from_num(u64::MAX)); + let parent_proportion: I96F32 = I96F32::saturating_from_num(proportion) + .safe_div(I96F32::saturating_from_num(u64::MAX)); // Get the parent's root and subnet-specific (alpha) stakes - let parent_root: I96F32 = I96F32::from_num(Self::get_stake_for_hotkey_on_subnet( - &parent, - Self::get_root_netuid(), - )); + let parent_root: I96F32 = I96F32::saturating_from_num( + Self::get_stake_for_hotkey_on_subnet(&parent, Self::get_root_netuid()), + ); let parent_alpha: I96F32 = - I96F32::from_num(Self::get_stake_for_hotkey_on_subnet(&parent, netuid)); + I96F32::saturating_from_num(Self::get_stake_for_hotkey_on_subnet(&parent, netuid)); // Calculate the parent's contribution to the hotkey's stakes let parent_alpha_contribution: I96F32 = parent_alpha.saturating_mul(parent_proportion); @@ -692,9 +809,9 @@ impl Pallet { // Sum up the total emission for this parent let emission_factor: I96F32 = contribution .checked_div(total_contribution) - .unwrap_or(I96F32::from_num(0)); + .unwrap_or(I96F32::saturating_from_num(0)); let parent_emission: u64 = - (remaining_emission.saturating_mul(emission_factor)).to_num::(); + (remaining_emission.saturating_mul(emission_factor)).saturating_to_num::(); // Add the parent's emission to the distribution list dividend_tuples.push((parent, parent_emission)); @@ -706,7 +823,7 @@ impl Pallet { // This includes the take left from the parents and the self contribution. let child_emission = remaining_emission .saturating_add(child_emission_take) - .to_num::() + .saturating_to_num::() .saturating_sub(to_parents); // Add the hotkey's own emission to the distribution list diff --git a/pallets/subtensor/src/epoch/math.rs b/pallets/subtensor/src/epoch/math.rs index 616a9b78be..9818b06a48 100644 --- a/pallets/subtensor/src/epoch/math.rs +++ b/pallets/subtensor/src/epoch/math.rs @@ -3,6 +3,7 @@ use crate::alloc::borrow::ToOwned; #[allow(unused)] use num_traits::float::Float; +use safe_math::*; use sp_runtime::traits::{CheckedAdd, Saturating}; use sp_std::cmp::Ordering; @@ -16,47 +17,47 @@ use sp_std::vec::Vec; #[allow(dead_code)] pub fn fixed(val: f32) -> I32F32 { - I32F32::from_num(val) + I32F32::saturating_from_num(val) } #[allow(dead_code)] pub fn fixed_to_u16(x: I32F32) -> u16 { - x.to_num::() + x.saturating_to_num::() } #[allow(dead_code)] pub fn fixed_to_u64(x: I32F32) -> u64 { - x.to_num::() + x.saturating_to_num::() } #[allow(dead_code)] pub fn fixed64_to_u64(x: I64F64) -> u64 { - x.to_num::() + x.saturating_to_num::() } #[allow(dead_code)] pub fn fixed64_to_fixed32(x: I64F64) -> I32F32 { - I32F32::from_num(x) + I32F32::saturating_from_num(x) } #[allow(dead_code)] pub fn fixed32_to_fixed64(x: I32F32) -> I64F64 { - I64F64::from_num(x) + I64F64::saturating_from_num(x) } #[allow(dead_code)] pub fn u16_to_fixed(x: u16) -> I32F32 { - I32F32::from_num(x) + I32F32::saturating_from_num(x) } #[allow(dead_code)] pub fn u16_proportion_to_fixed(x: u16) -> I32F32 { - I32F32::from_num(x).saturating_div(I32F32::from_num(u16::MAX)) + I32F32::saturating_from_num(x).safe_div(I32F32::saturating_from_num(u16::MAX)) } #[allow(dead_code)] pub fn fixed_proportion_to_u16(x: I32F32) -> u16 { - fixed_to_u16(x.saturating_mul(I32F32::from_num(u16::MAX))) + fixed_to_u16(x.saturating_mul(I32F32::saturating_from_num(u16::MAX))) } #[allow(dead_code)] @@ -92,33 +93,33 @@ pub fn vec_fixed_proportions_to_u16(vec: Vec) -> Vec { #[allow(dead_code)] // Max-upscale vector and convert to u16 so max_value = u16::MAX. Assumes non-negative normalized input. pub fn vec_max_upscale_to_u16(vec: &[I32F32]) -> Vec { - let u16_max: I32F32 = I32F32::from_num(u16::MAX); - let threshold: I32F32 = I32F32::from_num(32768); + let u16_max: I32F32 = I32F32::saturating_from_num(u16::MAX); + let threshold: I32F32 = I32F32::saturating_from_num(32768); let max_value: Option<&I32F32> = vec.iter().max(); match max_value { Some(val) => { - if *val == I32F32::from_num(0) { + if *val == I32F32::saturating_from_num(0) { return vec .iter() - .map(|e: &I32F32| e.saturating_mul(u16_max).to_num::()) + .map(|e: &I32F32| e.saturating_mul(u16_max).saturating_to_num::()) .collect(); } if *val > threshold { return vec .iter() .map(|e: &I32F32| { - e.saturating_mul(u16_max.saturating_div(*val)) + e.saturating_mul(u16_max.safe_div(*val)) .round() - .to_num::() + .saturating_to_num::() }) .collect(); } vec.iter() .map(|e: &I32F32| { e.saturating_mul(u16_max) - .saturating_div(*val) + .safe_div(*val) .round() - .to_num::() + .saturating_to_num::() }) .collect() } @@ -127,8 +128,8 @@ pub fn vec_max_upscale_to_u16(vec: &[I32F32]) -> Vec { vec.iter() .map(|e: &I32F32| { e.saturating_mul(u16_max) - .saturating_div(sum) - .to_num::() + .safe_div(sum) + .saturating_to_num::() }) .collect() } @@ -138,7 +139,10 @@ pub fn vec_max_upscale_to_u16(vec: &[I32F32]) -> Vec { #[allow(dead_code)] // Max-upscale u16 vector and convert to u16 so max_value = u16::MAX. Assumes u16 vector input. pub fn vec_u16_max_upscale_to_u16(vec: &[u16]) -> Vec { - let vec_fixed: Vec = vec.iter().map(|e: &u16| I32F32::from_num(*e)).collect(); + let vec_fixed: Vec = vec + .iter() + .map(|e: &u16| I32F32::saturating_from_num(*e)) + .collect(); vec_max_upscale_to_u16(&vec_fixed) } @@ -146,8 +150,11 @@ pub fn vec_u16_max_upscale_to_u16(vec: &[u16]) -> Vec { // Checks if u16 vector, when normalized, has a max value not greater than a u16 ratio max_limit. pub fn check_vec_max_limited(vec: &[u16], max_limit: u16) -> bool { let max_limit_fixed: I32F32 = - I32F32::from_num(max_limit).saturating_div(I32F32::from_num(u16::MAX)); - let mut vec_fixed: Vec = vec.iter().map(|e: &u16| I32F32::from_num(*e)).collect(); + I32F32::saturating_from_num(max_limit).safe_div(I32F32::saturating_from_num(u16::MAX)); + let mut vec_fixed: Vec = vec + .iter() + .map(|e: &u16| I32F32::saturating_from_num(*e)) + .collect(); inplace_normalize(&mut vec_fixed); let max_value: Option<&I32F32> = vec_fixed.iter().max(); max_value.is_none_or(|v| *v <= max_limit_fixed) @@ -180,14 +187,14 @@ where #[allow(dead_code)] pub fn is_zero(vector: &[I32F32]) -> bool { let vector_sum: I32F32 = sum(vector); - vector_sum == I32F32::from_num(0) + vector_sum == I32F32::saturating_from_num(0) } // Exp safe function with I32F32 output of I32F32 input. #[allow(dead_code)] pub fn exp_safe(input: I32F32) -> I32F32 { - let min_input: I32F32 = I32F32::from_num(-20); // <= 1/exp(-20) = 485 165 195,4097903 - let max_input: I32F32 = I32F32::from_num(20); // <= exp(20) = 485 165 195,4097903 + let min_input: I32F32 = I32F32::saturating_from_num(-20); // <= 1/exp(-20) = 485 165 195,4097903 + let max_input: I32F32 = I32F32::saturating_from_num(20); // <= exp(20) = 485 165 195,4097903 let mut safe_input: I32F32 = input; if input < min_input { safe_input = min_input; @@ -201,7 +208,7 @@ pub fn exp_safe(input: I32F32) -> I32F32 { } Err(_err) => { if safe_input <= 0 { - output = I32F32::from_num(0); + output = I32F32::saturating_from_num(0); } else { output = I32F32::max_value(); } @@ -213,13 +220,13 @@ pub fn exp_safe(input: I32F32) -> I32F32 { // Sigmoid safe function with I32F32 output of I32F32 input with offset kappa and (recommended) scaling 0 < rho <= 40. #[allow(dead_code)] pub fn sigmoid_safe(input: I32F32, rho: I32F32, kappa: I32F32) -> I32F32 { - let one: I32F32 = I32F32::from_num(1); + let one: I32F32 = I32F32::saturating_from_num(1); let offset: I32F32 = input.saturating_sub(kappa); // (input - kappa) let neg_rho: I32F32 = rho.saturating_mul(one.saturating_neg()); // -rho let exp_input: I32F32 = neg_rho.saturating_mul(offset); // -rho*(input-kappa) let exp_output: I32F32 = exp_safe(exp_input); // exp(-rho*(input-kappa)) let denominator: I32F32 = exp_output.saturating_add(one); // 1 + exp(-rho*(input-kappa)) - let sigmoid_output: I32F32 = one.saturating_div(denominator); // 1 / (1 + exp(-rho*(input-kappa))) + let sigmoid_output: I32F32 = one.safe_div(denominator); // 1 / (1 + exp(-rho*(input-kappa))) sigmoid_output } @@ -243,8 +250,8 @@ pub fn is_topk(vector: &[I32F32], k: usize) -> Vec { #[allow(dead_code)] pub fn normalize(x: &[I32F32]) -> Vec { let x_sum: I32F32 = sum(x); - if x_sum != I32F32::from_num(0.0_f32) { - x.iter().map(|xi| xi.saturating_div(x_sum)).collect() + if x_sum != I32F32::saturating_from_num(0.0_f32) { + x.iter().map(|xi| xi.safe_div(x_sum)).collect() } else { x.to_vec() } @@ -254,32 +261,32 @@ pub fn normalize(x: &[I32F32]) -> Vec { #[allow(dead_code)] pub fn inplace_normalize(x: &mut [I32F32]) { let x_sum: I32F32 = x.iter().sum(); - if x_sum == I32F32::from_num(0.0_f32) { + if x_sum == I32F32::saturating_from_num(0.0_f32) { return; } x.iter_mut() - .for_each(|value| *value = value.saturating_div(x_sum)); + .for_each(|value| *value = value.safe_div(x_sum)); } // Normalizes (sum to 1 except 0) the input vector directly in-place, using the sum arg. #[allow(dead_code)] pub fn inplace_normalize_using_sum(x: &mut [I32F32], x_sum: I32F32) { - if x_sum == I32F32::from_num(0.0_f32) { + if x_sum == I32F32::saturating_from_num(0.0_f32) { return; } x.iter_mut() - .for_each(|value| *value = value.saturating_div(x_sum)); + .for_each(|value| *value = value.safe_div(x_sum)); } // Normalizes (sum to 1 except 0) the I64F64 input vector directly in-place. #[allow(dead_code)] pub fn inplace_normalize_64(x: &mut [I64F64]) { let x_sum: I64F64 = x.iter().sum(); - if x_sum == I64F64::from_num(0) { + if x_sum == I64F64::saturating_from_num(0) { return; } x.iter_mut() - .for_each(|value| *value = value.saturating_div(x_sum)); + .for_each(|value| *value = value.safe_div(x_sum)); } /// Normalizes (sum to 1 except 0) each row (dim=0) of a I64F64 matrix in-place. @@ -287,9 +294,9 @@ pub fn inplace_normalize_64(x: &mut [I64F64]) { pub fn inplace_row_normalize_64(x: &mut [Vec]) { for row in x { let row_sum: I64F64 = row.iter().sum(); - if row_sum > I64F64::from_num(0.0_f64) { + if row_sum > I64F64::saturating_from_num(0.0_f64) { row.iter_mut() - .for_each(|x_ij: &mut I64F64| *x_ij = x_ij.saturating_div(row_sum)); + .for_each(|x_ij: &mut I64F64| *x_ij = x_ij.safe_div(row_sum)); } } } @@ -302,9 +309,9 @@ pub fn vecdiv(x: &[I32F32], y: &[I32F32]) -> Vec { .zip(y) .map(|(x_i, y_i)| { if *y_i != 0 { - x_i.saturating_div(*y_i) + x_i.safe_div(*y_i) } else { - I32F32::from_num(0) + I32F32::saturating_from_num(0) } }) .collect() @@ -315,9 +322,9 @@ pub fn vecdiv(x: &[I32F32], y: &[I32F32]) -> Vec { pub fn inplace_row_normalize(x: &mut [Vec]) { for row in x { let row_sum: I32F32 = row.iter().sum(); - if row_sum > I32F32::from_num(0.0_f32) { + if row_sum > I32F32::saturating_from_num(0.0_f32) { row.iter_mut() - .for_each(|x_ij: &mut I32F32| *x_ij = x_ij.saturating_div(row_sum)); + .for_each(|x_ij: &mut I32F32| *x_ij = x_ij.safe_div(row_sum)); } } } @@ -327,10 +334,10 @@ pub fn inplace_row_normalize(x: &mut [Vec]) { pub fn inplace_row_normalize_sparse(sparse_matrix: &mut [Vec<(u16, I32F32)>]) { for sparse_row in sparse_matrix.iter_mut() { let row_sum: I32F32 = sparse_row.iter().map(|(_j, value)| *value).sum(); - if row_sum > I32F32::from_num(0.0) { + if row_sum > I32F32::saturating_from_num(0.0) { sparse_row .iter_mut() - .for_each(|(_j, value)| *value = value.saturating_div(row_sum)); + .for_each(|(_j, value)| *value = value.safe_div(row_sum)); } } } @@ -365,19 +372,21 @@ pub fn col_sum(x: &[Vec]) -> Vec { if cols == 0 { return vec![]; } - x.iter() - .fold(vec![I32F32::from_num(0); cols], |acc, next_row| { + x.iter().fold( + vec![I32F32::saturating_from_num(0); cols], + |acc, next_row| { acc.into_iter() .zip(next_row) .map(|(acc_elem, next_elem)| acc_elem.saturating_add(*next_elem)) .collect() - }) + }, + ) } // Sum across each column (dim=1) of a sparse matrix. #[allow(dead_code, clippy::indexing_slicing)] pub fn col_sum_sparse(sparse_matrix: &[Vec<(u16, I32F32)>], columns: u16) -> Vec { - let mut result: Vec = vec![I32F32::from_num(0); columns as usize]; + let mut result: Vec = vec![I32F32::saturating_from_num(0); columns as usize]; for sparse_row in sparse_matrix { for (j, value) in sparse_row { result[*j as usize] = result[*j as usize].saturating_add(*value); @@ -389,7 +398,7 @@ pub fn col_sum_sparse(sparse_matrix: &[Vec<(u16, I32F32)>], columns: u16) -> Vec // Normalizes (sum to 1 except 0) each column (dim=1) of a sparse matrix in-place. #[allow(dead_code, clippy::indexing_slicing)] pub fn inplace_col_normalize_sparse(sparse_matrix: &mut [Vec<(u16, I32F32)>], columns: u16) { - let mut col_sum: Vec = vec![I32F32::from_num(0.0); columns as usize]; // assume square matrix, rows=cols + let mut col_sum: Vec = vec![I32F32::saturating_from_num(0.0); columns as usize]; // assume square matrix, rows=cols for sparse_row in sparse_matrix.iter() { for (j, value) in sparse_row.iter() { col_sum[*j as usize] = col_sum[*j as usize].saturating_add(*value); @@ -397,10 +406,10 @@ pub fn inplace_col_normalize_sparse(sparse_matrix: &mut [Vec<(u16, I32F32)>], co } for sparse_row in sparse_matrix { for (j, value) in sparse_row { - if col_sum[*j as usize] == I32F32::from_num(0.0_f32) { + if col_sum[*j as usize] == I32F32::saturating_from_num(0.0_f32) { continue; } - *value = value.saturating_div(col_sum[*j as usize]); + *value = value.safe_div(col_sum[*j as usize]); } } } @@ -417,7 +426,7 @@ pub fn inplace_col_normalize(x: &mut [Vec]) { let cols = first_row.len(); let col_sums = x .iter_mut() - .fold(vec![I32F32::from_num(0.0); cols], |acc, row| { + .fold(vec![I32F32::saturating_from_num(0.0); cols], |acc, row| { row.iter_mut() .zip(acc) .map(|(&mut m_val, acc_val)| acc_val.saturating_add(m_val)) @@ -426,9 +435,9 @@ pub fn inplace_col_normalize(x: &mut [Vec]) { x.iter_mut().for_each(|row| { row.iter_mut() .zip(&col_sums) - .filter(|(_, col_sum)| **col_sum != I32F32::from_num(0_f32)) + .filter(|(_, col_sum)| **col_sum != I32F32::saturating_from_num(0_f32)) .for_each(|(m_val, col_sum)| { - *m_val = m_val.saturating_div(*col_sum); + *m_val = m_val.safe_div(*col_sum); }); }); } @@ -436,7 +445,7 @@ pub fn inplace_col_normalize(x: &mut [Vec]) { // Max-upscale each column (dim=1) of a sparse matrix in-place. #[allow(dead_code, clippy::indexing_slicing)] pub fn inplace_col_max_upscale_sparse(sparse_matrix: &mut [Vec<(u16, I32F32)>], columns: u16) { - let mut col_max: Vec = vec![I32F32::from_num(0.0); columns as usize]; // assume square matrix, rows=cols + let mut col_max: Vec = vec![I32F32::saturating_from_num(0.0); columns as usize]; // assume square matrix, rows=cols for sparse_row in sparse_matrix.iter() { for (j, value) in sparse_row.iter() { if col_max[*j as usize] < *value { @@ -446,10 +455,10 @@ pub fn inplace_col_max_upscale_sparse(sparse_matrix: &mut [Vec<(u16, I32F32)>], } for sparse_row in sparse_matrix { for (j, value) in sparse_row { - if col_max[*j as usize] == I32F32::from_num(0.0_f32) { + if col_max[*j as usize] == I32F32::saturating_from_num(0.0_f32) { continue; } - *value = value.saturating_div(col_max[*j as usize]); + *value = value.safe_div(col_max[*j as usize]); } } } @@ -464,20 +473,21 @@ pub fn inplace_col_max_upscale(x: &mut [Vec]) { return; } let cols = first_row.len(); - let col_maxes = x - .iter_mut() - .fold(vec![I32F32::from_num(0_f32); cols], |acc, row| { + let col_maxes = x.iter_mut().fold( + vec![I32F32::saturating_from_num(0_f32); cols], + |acc, row| { row.iter_mut() .zip(acc) .map(|(m_val, acc_val)| acc_val.max(*m_val)) .collect() - }); + }, + ); x.iter_mut().for_each(|row| { row.iter_mut() .zip(&col_maxes) - .filter(|(_, col_max)| **col_max != I32F32::from_num(0)) + .filter(|(_, col_max)| **col_max != I32F32::saturating_from_num(0)) .for_each(|(m_val, col_max)| { - *m_val = m_val.saturating_div(*col_max); + *m_val = m_val.safe_div(*col_max); }); }); } @@ -489,7 +499,7 @@ pub fn inplace_mask_vector(mask: &[bool], vector: &mut [I32F32]) { return; } assert_eq!(mask.len(), vector.len()); - let zero: I32F32 = I32F32::from_num(0.0); + let zero: I32F32 = I32F32::saturating_from_num(0.0); mask.iter() .zip(vector) .filter(|(m, _)| **m) @@ -508,7 +518,7 @@ pub fn inplace_mask_matrix(mask: &[Vec], matrix: &mut Vec>) { return; } assert_eq!(mask.len(), matrix.len()); - let zero: I32F32 = I32F32::from_num(0.0); + let zero: I32F32 = I32F32::saturating_from_num(0.0); mask.iter().zip(matrix).for_each(|(mask_row, matrix_row)| { mask_row .iter() @@ -528,7 +538,7 @@ pub fn inplace_mask_rows(mask: &[bool], matrix: &mut [Vec]) { }; let cols = first_row.len(); assert_eq!(mask.len(), matrix.len()); - let zero: I32F32 = I32F32::from_num(0); + let zero: I32F32 = I32F32::saturating_from_num(0); matrix .iter_mut() .zip(mask) @@ -549,7 +559,7 @@ pub fn inplace_mask_diag(matrix: &mut [Vec]) { return; } assert_eq!(matrix.len(), first_row.len()); - let zero: I32F32 = I32F32::from_num(0.0); + let zero: I32F32 = I32F32::saturating_from_num(0.0); matrix.iter_mut().enumerate().for_each(|(idx, row)| { let Some(elem) = row.get_mut(idx) else { // Should not happen since matrix is square @@ -664,7 +674,7 @@ pub fn matmul(matrix: &[Vec], vector: &[I32F32]) -> Vec { } assert!(matrix.len() == vector.len()); matrix.iter().zip(vector).fold( - vec![I32F32::from_num(0_f32); cols], + vec![I32F32::saturating_from_num(0_f32); cols], |acc, (row, vec_val)| { row.iter() .zip(acc) @@ -690,10 +700,9 @@ pub fn matmul_64(matrix: &[Vec], vector: &[I64F64]) -> Vec { return vec![]; } assert!(matrix.len() == vector.len()); - matrix - .iter() - .zip(vector) - .fold(vec![I64F64::from_num(0.0); cols], |acc, (row, vec_val)| { + matrix.iter().zip(vector).fold( + vec![I64F64::saturating_from_num(0.0); cols], + |acc, (row, vec_val)| { row.iter() .zip(acc) .map(|(m_val, acc_val)| { @@ -703,7 +712,8 @@ pub fn matmul_64(matrix: &[Vec], vector: &[I64F64]) -> Vec { acc_val.saturating_add(vec_val.saturating_mul(*m_val)) }) .collect() - }) + }, + ) } // Column-wise matrix-vector product, row-wise sum: result_i = SUM(j) vector_j * matrix_ij. @@ -721,7 +731,7 @@ pub fn matmul_transpose(matrix: &[Vec], vector: &[I32F32]) -> Vec Vec { - let mut result: Vec = vec![I32F32::from_num(0.0); columns as usize]; + let mut result: Vec = vec![I32F32::saturating_from_num(0.0); columns as usize]; for (i, sparse_row) in sparse_matrix.iter().enumerate() { for (j, value) in sparse_row.iter() { // Compute ranks: r_j = SUM(i) w_ij * s_i @@ -757,7 +767,7 @@ pub fn matmul_transpose_sparse( sparse_matrix: &[Vec<(u16, I32F32)>], vector: &[I32F32], ) -> Vec { - let mut result: Vec = vec![I32F32::from_num(0.0); sparse_matrix.len()]; + let mut result: Vec = vec![I32F32::saturating_from_num(0.0); sparse_matrix.len()]; for (i, sparse_row) in sparse_matrix.iter().enumerate() { for (j, value) in sparse_row.iter() { // Compute dividends: d_j = SUM(i) b_ji * inc_i @@ -892,16 +902,16 @@ pub fn weighted_median( ) -> I32F32 { let n = partition_idx.len(); if n == 0 { - return I32F32::from_num(0); + return I32F32::saturating_from_num(0); } if n == 1 { return score[partition_idx[0]]; } assert!(stake.len() == score.len()); - let mid_idx: usize = n.saturating_div(2); + let mid_idx: usize = n.safe_div(2); let pivot: I32F32 = score[partition_idx[mid_idx]]; - let mut lo_stake: I32F32 = I32F32::from_num(0); - let mut hi_stake: I32F32 = I32F32::from_num(0); + let mut lo_stake: I32F32 = I32F32::saturating_from_num(0); + let mut hi_stake: I32F32 = I32F32::saturating_from_num(0); let mut lower: Vec = vec![]; let mut upper: Vec = vec![]; for &idx in partition_idx { @@ -951,7 +961,7 @@ pub fn weighted_median_col( ) -> Vec { let rows = stake.len(); let columns = score[0].len(); - let zero: I32F32 = I32F32::from_num(0); + let zero: I32F32 = I32F32::saturating_from_num(0); let mut median: Vec = vec![zero; columns]; #[allow(clippy::needless_range_loop)] @@ -991,7 +1001,7 @@ pub fn weighted_median_col_sparse( majority: I32F32, ) -> Vec { let rows = stake.len(); - let zero: I32F32 = I32F32::from_num(0); + let zero: I32F32 = I32F32::saturating_from_num(0); let mut use_stake: Vec = stake.iter().copied().filter(|&s| s > zero).collect(); inplace_normalize(&mut use_stake); let stake_sum: I32F32 = use_stake.iter().sum(); @@ -1028,10 +1038,10 @@ pub fn weighted_median_col_sparse( // ratio=1: Result = B #[allow(dead_code)] pub fn interpolate(mat1: &[Vec], mat2: &[Vec], ratio: I32F32) -> Vec> { - if ratio == I32F32::from_num(0) { + if ratio == I32F32::saturating_from_num(0) { return mat1.to_owned(); } - if ratio == I32F32::from_num(1) { + if ratio == I32F32::saturating_from_num(1) { return mat2.to_owned(); } assert!(mat1.len() == mat2.len()); @@ -1042,7 +1052,10 @@ pub fn interpolate(mat1: &[Vec], mat2: &[Vec], ratio: I32F32) -> return vec![vec![]; 1]; } let mut result: Vec> = - vec![vec![I32F32::from_num(0); mat1.first().unwrap_or(&vec![]).len()]; mat1.len()]; + vec![ + vec![I32F32::saturating_from_num(0); mat1.first().unwrap_or(&vec![]).len()]; + mat1.len() + ]; for (i, (row1, row2)) in mat1.iter().zip(mat2.iter()).enumerate() { assert!(row1.len() == row2.len()); for (j, (&v1, &v2)) in row1.iter().zip(row2.iter()).enumerate() { @@ -1065,15 +1078,15 @@ pub fn interpolate_sparse( columns: u16, ratio: I32F32, ) -> Vec> { - if ratio == I32F32::from_num(0) { + if ratio == I32F32::saturating_from_num(0) { return mat1.to_owned(); } - if ratio == I32F32::from_num(1) { + if ratio == I32F32::saturating_from_num(1) { return mat2.to_owned(); } assert!(mat1.len() == mat2.len()); let rows = mat1.len(); - let zero: I32F32 = I32F32::from_num(0); + let zero: I32F32 = I32F32::saturating_from_num(0); let mut result: Vec> = vec![vec![]; rows]; for i in 0..rows { let mut row1: Vec = vec![zero; columns as usize]; @@ -1137,7 +1150,7 @@ pub fn hadamard_sparse( ) -> Vec> { assert!(mat1.len() == mat2.len()); let rows = mat1.len(); - let zero: I32F32 = I32F32::from_num(0); + let zero: I32F32 = I32F32::saturating_from_num(0); let mut result: Vec> = vec![vec![]; rows]; for i in 0..rows { let mut row1: Vec = vec![zero; columns as usize]; @@ -1169,7 +1182,7 @@ pub fn mat_ema(new: &[Vec], old: &[Vec], alpha: I32F32) -> Vec Vec> { assert!(new.len() == old.len()); let n = new.len(); // assume square matrix, rows=cols - let zero: I32F32 = I32F32::from_num(0.0); - let one_minus_alpha: I32F32 = I32F32::from_num(1.0).saturating_sub(alpha); + let zero: I32F32 = I32F32::saturating_from_num(0.0); + let one_minus_alpha: I32F32 = I32F32::saturating_from_num(1.0).saturating_sub(alpha); let mut result: Vec> = vec![vec![]; n]; for i in 0..new.len() { let mut row: Vec = vec![zero; n]; @@ -1241,7 +1254,7 @@ pub fn mat_ema_alpha_vec_sparse( // Ensure the new and old matrices have the same number of rows. assert!(new.len() == old.len()); let n = new.len(); // Assume square matrix, rows=cols - let zero: I32F32 = I32F32::from_num(0.0); + let zero: I32F32 = I32F32::saturating_from_num(0.0); let mut result: Vec> = vec![vec![]; n]; // Iterate over each row of the matrices. @@ -1273,7 +1286,8 @@ pub fn mat_ema_alpha_vec_sparse( // Retrieve the alpha value for the current column. let alpha_val: I32F32 = alpha.get(*j as usize).copied().unwrap_or(zero); // Calculate the complement of the alpha value using saturating subtraction. - let one_minus_alpha: I32F32 = I32F32::from_num(1.0).saturating_sub(alpha_val); + let one_minus_alpha: I32F32 = + I32F32::saturating_from_num(1.0).saturating_sub(alpha_val); // Compute the EMA component for the old value and add it to the row using saturating operations. if let Some(row_val) = row.get_mut(*j as usize) { *row_val = row_val.saturating_add(one_minus_alpha.saturating_mul(*value)); @@ -1323,7 +1337,10 @@ pub fn mat_ema_alpha_vec( // Initialize the result matrix with zeros, having the same dimensions as the new matrix. let mut result: Vec> = - vec![vec![I32F32::from_num(0.0); new.first().map_or(0, |row| row.len())]; new.len()]; + vec![ + vec![I32F32::saturating_from_num(0.0); new.first().map_or(0, |row| row.len())]; + new.len() + ]; // Iterate over each row of the matrices. for (i, (new_row, old_row)) in new.iter().zip(old).enumerate() { @@ -1333,7 +1350,7 @@ pub fn mat_ema_alpha_vec( // Iterate over each column of the current row. for (j, &alpha_val) in alpha.iter().enumerate().take(new_row.len()) { // Calculate the complement of the alpha value using saturating subtraction. - let one_minus_alpha = I32F32::from_num(1.0).saturating_sub(alpha_val); + let one_minus_alpha = I32F32::saturating_from_num(1.0).saturating_sub(alpha_val); // Compute the EMA for the current element using saturating operations. if let (Some(new_val), Some(old_val), Some(result_val)) = ( @@ -1365,7 +1382,7 @@ pub fn quantile(data: &[I32F32], quantile: f64) -> I32F32 { // If the data is empty, return 0 as the quantile value. if len == 0 { - return I32F32::from_num(0); + return I32F32::saturating_from_num(0); } // Calculate the position in the sorted array corresponding to the quantile. @@ -1382,20 +1399,20 @@ pub fn quantile(data: &[I32F32], quantile: f64) -> I32F32 { sorted_data .get(low) .copied() - .unwrap_or_else(|| I32F32::from_num(0)) + .unwrap_or_else(|| I32F32::saturating_from_num(0)) } else { // Otherwise, perform linear interpolation between the low and high values. let low_value = sorted_data .get(low) .copied() - .unwrap_or_else(|| I32F32::from_num(0)); + .unwrap_or_else(|| I32F32::saturating_from_num(0)); let high_value = sorted_data .get(high) .copied() - .unwrap_or_else(|| I32F32::from_num(0)); + .unwrap_or_else(|| I32F32::saturating_from_num(0)); // Calculate the weight for interpolation. - let weight = I32F32::from_num(pos - low as f64); + let weight = I32F32::saturating_from_num(pos - low as f64); // Return the interpolated value using saturating operations. low_value.saturating_add((high_value.saturating_sub(low_value)).saturating_mul(weight)) @@ -1404,10 +1421,10 @@ pub fn quantile(data: &[I32F32], quantile: f64) -> I32F32 { /// Safe ln function, returns 0 if value is 0. pub fn safe_ln(value: I32F32) -> I32F32 { - ln(value).unwrap_or(I32F32::from_num(0.0)) + ln(value).unwrap_or(I32F32::saturating_from_num(0.0)) } /// Safe exp function, returns 0 if value is 0. pub fn safe_exp(value: I32F32) -> I32F32 { - exp(value).unwrap_or(I32F32::from_num(0.0)) + exp(value).unwrap_or(I32F32::saturating_from_num(0.0)) } diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index e6edd25857..9a7e4f6abd 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -1,6 +1,7 @@ use super::*; use crate::epoch::math::*; use frame_support::IterableStorageDoubleMap; +use safe_math::*; use sp_std::vec; use substrate_fixed::types::{I32F32, I64F64, I96F32}; @@ -230,34 +231,34 @@ impl Pallet { } // Compute rao based emission scores. range: I96F32(0, rao_emission) - let float_rao_emission: I96F32 = I96F32::from_num(rao_emission); + let float_rao_emission: I96F32 = I96F32::saturating_from_num(rao_emission); let server_emission: Vec = normalized_server_emission .iter() - .map(|se: &I32F32| I96F32::from_num(*se).saturating_mul(float_rao_emission)) + .map(|se: &I32F32| I96F32::saturating_from_num(*se).saturating_mul(float_rao_emission)) .collect(); let server_emission: Vec = server_emission .iter() - .map(|e: &I96F32| e.to_num::()) + .map(|e: &I96F32| e.saturating_to_num::()) .collect(); let validator_emission: Vec = normalized_validator_emission .iter() - .map(|ve: &I32F32| I96F32::from_num(*ve).saturating_mul(float_rao_emission)) + .map(|ve: &I32F32| I96F32::saturating_from_num(*ve).saturating_mul(float_rao_emission)) .collect(); let validator_emission: Vec = validator_emission .iter() - .map(|e: &I96F32| e.to_num::()) + .map(|e: &I96F32| e.saturating_to_num::()) .collect(); // Used only to track combined emission in the storage. let combined_emission: Vec = normalized_combined_emission .iter() - .map(|ce: &I32F32| I96F32::from_num(*ce).saturating_mul(float_rao_emission)) + .map(|ce: &I32F32| I96F32::saturating_from_num(*ce).saturating_mul(float_rao_emission)) .collect(); let combined_emission: Vec = combined_emission .iter() - .map(|e: &I96F32| e.to_num::()) + .map(|e: &I96F32| e.saturating_to_num::()) .collect(); log::trace!("nSE: {:?}", &normalized_server_emission); @@ -601,34 +602,34 @@ impl Pallet { } // Compute rao based emission scores. range: I96F32(0, rao_emission) - let float_rao_emission: I96F32 = I96F32::from_num(rao_emission); + let float_rao_emission: I96F32 = I96F32::saturating_from_num(rao_emission); let server_emission: Vec = normalized_server_emission .iter() - .map(|se: &I32F32| I96F32::from_num(*se).saturating_mul(float_rao_emission)) + .map(|se: &I32F32| I96F32::saturating_from_num(*se).saturating_mul(float_rao_emission)) .collect(); let server_emission: Vec = server_emission .iter() - .map(|e: &I96F32| e.to_num::()) + .map(|e: &I96F32| e.saturating_to_num::()) .collect(); let validator_emission: Vec = normalized_validator_emission .iter() - .map(|ve: &I32F32| I96F32::from_num(*ve).saturating_mul(float_rao_emission)) + .map(|ve: &I32F32| I96F32::saturating_from_num(*ve).saturating_mul(float_rao_emission)) .collect(); let validator_emission: Vec = validator_emission .iter() - .map(|e: &I96F32| e.to_num::()) + .map(|e: &I96F32| e.saturating_to_num::()) .collect(); // Only used to track emission in storage. let combined_emission: Vec = normalized_combined_emission .iter() - .map(|ce: &I32F32| I96F32::from_num(*ce).saturating_mul(float_rao_emission)) + .map(|ce: &I32F32| I96F32::saturating_from_num(*ce).saturating_mul(float_rao_emission)) .collect(); let combined_emission: Vec = combined_emission .iter() - .map(|e: &I96F32| e.to_num::()) + .map(|e: &I96F32| e.saturating_to_num::()) .collect(); log::trace!( @@ -732,13 +733,15 @@ impl Pallet { } pub fn get_float_rho(netuid: u16) -> I32F32 { - I32F32::from_num(Self::get_rho(netuid)) + I32F32::saturating_from_num(Self::get_rho(netuid)) } pub fn get_float_kappa(netuid: u16) -> I32F32 { - I32F32::from_num(Self::get_kappa(netuid)).saturating_div(I32F32::from_num(u16::MAX)) + I32F32::saturating_from_num(Self::get_kappa(netuid)) + .safe_div(I32F32::saturating_from_num(u16::MAX)) } pub fn get_float_bonds_penalty(netuid: u16) -> I32F32 { - I32F32::from_num(Self::get_bonds_penalty(netuid)).saturating_div(I32F32::from_num(u16::MAX)) + I32F32::saturating_from_num(Self::get_bonds_penalty(netuid)) + .safe_div(I32F32::saturating_from_num(u16::MAX)) } pub fn get_block_at_registration(netuid: u16) -> Vec { @@ -767,7 +770,7 @@ impl Pallet { weights .get_mut(uid_i as usize) .expect("uid_i is filtered to be less than n; qed") - .push((*uid_j, I32F32::from_num(*weight_ij))); + .push((*uid_j, I32F32::saturating_from_num(*weight_ij))); } } weights @@ -776,7 +779,7 @@ impl Pallet { /// Output unnormalized weights in [n, n] matrix, input weights are assumed to be row max-upscaled in u16. pub fn get_weights(netuid: u16) -> Vec> { let n: usize = Self::get_subnetwork_n(netuid) as usize; - let mut weights: Vec> = vec![vec![I32F32::from_num(0.0); n]; n]; + let mut weights: Vec> = vec![vec![I32F32::saturating_from_num(0.0); n]; n]; for (uid_i, weights_vec) in as IterableStorageDoubleMap>>::iter_prefix(netuid) .filter(|(uid_i, _)| *uid_i < n as u16) @@ -790,7 +793,7 @@ impl Pallet { .expect("uid_i is filtered to be less than n; qed") .get_mut(uid_j as usize) .expect("uid_j is filtered to be less than n; qed") = - I32F32::from_num(weight_ij); + I32F32::saturating_from_num(weight_ij); } } weights @@ -808,7 +811,7 @@ impl Pallet { bonds .get_mut(uid_i as usize) .expect("uid_i is filtered to be less than n; qed") - .push((uid_j, I32F32::from_num(bonds_ij))); + .push((uid_j, I32F32::saturating_from_num(bonds_ij))); } } bonds @@ -817,7 +820,7 @@ impl Pallet { /// Output unnormalized bonds in [n, n] matrix, input bonds are assumed to be column max-upscaled in u16. pub fn get_bonds(netuid: u16) -> Vec> { let n: usize = Self::get_subnetwork_n(netuid) as usize; - let mut bonds: Vec> = vec![vec![I32F32::from_num(0.0); n]; n]; + let mut bonds: Vec> = vec![vec![I32F32::saturating_from_num(0.0); n]; n]; for (uid_i, bonds_vec) in as IterableStorageDoubleMap>>::iter_prefix(netuid) .filter(|(uid_i, _)| *uid_i < n as u16) @@ -828,7 +831,7 @@ impl Pallet { .expect("uid_i has been filtered to be less than n; qed") .get_mut(uid_j as usize) .expect("uid_j has been filtered to be less than n; qed") = - I32F32::from_num(bonds_ij); + I32F32::saturating_from_num(bonds_ij); } } bonds @@ -858,25 +861,30 @@ impl Pallet { // extra caution to ensure we never divide by zero if consensus_high <= consensus_low || alpha_low == 0 || alpha_high == 0 { // Return 0 for both 'a' and 'b' when consensus values are equal - return (I32F32::from_num(0.0), I32F32::from_num(0.0)); + return ( + I32F32::saturating_from_num(0.0), + I32F32::saturating_from_num(0.0), + ); } // Calculate the slope 'a' of the logistic function. // a = (ln((1 / alpha_high - 1)) - ln((1 / alpha_low - 1))) / (consensus_low - consensus_high) let a = (safe_ln( - (I32F32::from_num(1.0).saturating_div(alpha_high)) - .saturating_sub(I32F32::from_num(1.0)), + (I32F32::saturating_from_num(1.0).safe_div(alpha_high)) + .saturating_sub(I32F32::saturating_from_num(1.0)), ) .saturating_sub(safe_ln( - (I32F32::from_num(1.0).saturating_div(alpha_low)).saturating_sub(I32F32::from_num(1.0)), + (I32F32::saturating_from_num(1.0).safe_div(alpha_low)) + .saturating_sub(I32F32::saturating_from_num(1.0)), ))) - .saturating_div(consensus_low.saturating_sub(consensus_high)); + .safe_div(consensus_low.saturating_sub(consensus_high)); log::trace!("a: {:?}", a); // Calculate the intercept 'b' of the logistic function. // b = ln((1 / alpha_low - 1)) + a * consensus_low let b = safe_ln( - (I32F32::from_num(1.0).saturating_div(alpha_low)).saturating_sub(I32F32::from_num(1.0)), + (I32F32::saturating_from_num(1.0).safe_div(alpha_low)) + .saturating_sub(I32F32::saturating_from_num(1.0)), ) .saturating_add(a.saturating_mul(consensus_low)); log::trace!("b: {:?}", b); @@ -905,7 +913,8 @@ impl Pallet { // Compute the alpha value using the logistic function formula. // alpha = 1 / (1 + exp_val) - I32F32::from_num(1.0).saturating_div(I32F32::from_num(1.0).saturating_add(exp_val)) + I32F32::saturating_from_num(1.0) + .safe_div(I32F32::saturating_from_num(1.0).saturating_add(exp_val)) }) .collect(); @@ -1019,13 +1028,14 @@ impl Pallet { netuid: u16, ) -> Vec> { // Retrieve the bonds moving average for the given network ID and scale it down. - let bonds_moving_average: I64F64 = I64F64::from_num(Self::get_bonds_moving_average(netuid)) - .saturating_div(I64F64::from_num(1_000_000)); + let bonds_moving_average: I64F64 = + I64F64::saturating_from_num(Self::get_bonds_moving_average(netuid)) + .safe_div(I64F64::saturating_from_num(1_000_000)); // Calculate the alpha value for the EMA calculation. // Alpha is derived by subtracting the scaled bonds moving average from 1. - let alpha: I32F32 = - I32F32::from_num(1).saturating_sub(I32F32::from_num(bonds_moving_average)); + let alpha: I32F32 = I32F32::saturating_from_num(1) + .saturating_sub(I32F32::saturating_from_num(bonds_moving_average)); // Compute the Exponential Moving Average (EMA) of bonds using the calculated alpha value. let ema_bonds = mat_ema_sparse(bonds_delta, bonds, alpha); @@ -1052,13 +1062,14 @@ impl Pallet { netuid: u16, ) -> Vec> { // Retrieve the bonds moving average for the given network ID and scale it down. - let bonds_moving_average: I64F64 = I64F64::from_num(Self::get_bonds_moving_average(netuid)) - .saturating_div(I64F64::from_num(1_000_000)); + let bonds_moving_average: I64F64 = + I64F64::saturating_from_num(Self::get_bonds_moving_average(netuid)) + .safe_div(I64F64::saturating_from_num(1_000_000)); // Calculate the alpha value for the EMA calculation. // Alpha is derived by subtracting the scaled bonds moving average from 1. - let alpha: I32F32 = - I32F32::from_num(1).saturating_sub(I32F32::from_num(bonds_moving_average)); + let alpha: I32F32 = I32F32::saturating_from_num(1) + .saturating_sub(I32F32::saturating_from_num(bonds_moving_average)); // Compute the Exponential Moving Average (EMA) of bonds using the calculated alpha value. let ema_bonds = mat_ema(bonds_delta, bonds, alpha); @@ -1090,7 +1101,9 @@ impl Pallet { // This way we avoid the quantil function panic. if LiquidAlphaOn::::get(netuid) && !consensus.is_empty() - && consensus.iter().any(|&c| c != I32F32::from_num(0)) + && consensus + .iter() + .any(|&c| c != I32F32::saturating_from_num(0)) { // Calculate the 75th percentile (high) and 25th percentile (low) of the consensus values. let consensus_high = quantile(&consensus, 0.75); @@ -1158,7 +1171,9 @@ impl Pallet { // Check if Liquid Alpha is enabled, consensus is not empty, and contains non-zero values. if LiquidAlphaOn::::get(netuid) && !consensus.is_empty() - && consensus.iter().any(|&c| c != I32F32::from_num(0)) + && consensus + .iter() + .any(|&c| c != I32F32::saturating_from_num(0)) { // Calculate the 75th percentile (high) and 25th percentile (low) of the consensus values. let consensus_high = quantile(&consensus, 0.75); @@ -1221,7 +1236,7 @@ impl Pallet { ); let max_u16: u32 = u16::MAX as u32; // 65535 - let min_alpha_high: u16 = (max_u16.saturating_mul(4).saturating_div(5)) as u16; // 52428 + let min_alpha_high: u16 = (max_u16.saturating_mul(4).safe_div(5)) as u16; // 52428 // --- 4. Ensure alpha high is greater than the minimum ensure!(alpha_high >= min_alpha_high, Error::::AlphaHighTooLow); diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 9c4c5d0d4d..3fd67e0d8e 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -12,10 +12,7 @@ use frame_support::{ dispatch::{self, DispatchInfo, DispatchResult, DispatchResultWithPostInfo, PostDispatchInfo}, ensure, pallet_macros::import_section, - traits::{ - tokens::{fungible, Fortitude, Preservation}, - IsSubType, - }, + traits::{tokens::fungible, IsSubType}, }; use codec::{Decode, Encode}; @@ -707,11 +704,17 @@ pub mod pallet { #[pallet::type_value] /// Default minimum stake. /// 500k rao matches $0.25 at $500/TAO - /// Also used as staking fee pub fn DefaultMinStake() -> u64 { 500_000 } + #[pallet::type_value] + /// Default staking fee. + /// 500k rao matches $0.25 at $500/TAO + pub fn DefaultStakingFee() -> u64 { + 500_000 + } + #[pallet::type_value] /// Default unicode vector for tau symbol. pub fn DefaultUnicodeVecU8() -> Vec { @@ -727,7 +730,7 @@ pub mod pallet { #[pallet::type_value] /// Default value for Share Pool variables pub fn DefaultSharePoolZero() -> U64F64 { - U64F64::from_num(0) + U64F64::saturating_from_num(0) } #[pallet::storage] @@ -1554,6 +1557,12 @@ pub enum CallType { pub enum CustomTransactionError { ColdkeyInSwapSchedule, StakeAmountTooLow, + BalanceTooLow, + SubnetDoesntExist, + HotkeyAccountDoesntExist, + NotEnoughStakeToWithdraw, + RateLimitExceeded, + BadRequest, } impl From for u8 { @@ -1561,6 +1570,12 @@ impl From for u8 { match variant { CustomTransactionError::ColdkeyInSwapSchedule => 0, CustomTransactionError::StakeAmountTooLow => 1, + CustomTransactionError::BalanceTooLow => 2, + CustomTransactionError::SubnetDoesntExist => 3, + CustomTransactionError::HotkeyAccountDoesntExist => 4, + CustomTransactionError::NotEnoughStakeToWithdraw => 5, + CustomTransactionError::RateLimitExceeded => 6, + CustomTransactionError::BadRequest => 255, } } } @@ -1603,6 +1618,41 @@ where pub fn check_weights_min_stake(who: &T::AccountId, netuid: u16) -> bool { Pallet::::check_weights_min_stake(who, netuid) } + + pub fn result_to_validity(result: Result<(), Error>) -> TransactionValidity { + if let Err(err) = result { + match err { + Error::::AmountTooLow => Err(InvalidTransaction::Custom( + CustomTransactionError::StakeAmountTooLow.into(), + ) + .into()), + Error::::SubnetNotExists => Err(InvalidTransaction::Custom( + CustomTransactionError::SubnetDoesntExist.into(), + ) + .into()), + Error::::NotEnoughBalanceToStake => Err(InvalidTransaction::Custom( + CustomTransactionError::BalanceTooLow.into(), + ) + .into()), + Error::::HotKeyAccountNotExists => Err(InvalidTransaction::Custom( + CustomTransactionError::HotkeyAccountDoesntExist.into(), + ) + .into()), + Error::::NotEnoughStakeToWithdraw => Err(InvalidTransaction::Custom( + CustomTransactionError::NotEnoughStakeToWithdraw.into(), + ) + .into()), + _ => Err( + InvalidTransaction::Custom(CustomTransactionError::BadRequest.into()).into(), + ), + } + } else { + Ok(ValidTransaction { + priority: Self::get_priority_vanilla(), + ..Default::default() + }) + } + } } impl sp_std::fmt::Debug for SubtensorSignedExtension { @@ -1647,7 +1697,10 @@ where ..Default::default() }) } else { - Err(InvalidTransaction::Custom(1).into()) + Err(InvalidTransaction::Custom( + CustomTransactionError::StakeAmountTooLow.into(), + ) + .into()) } } Some(Call::reveal_weights { netuid, .. }) => { @@ -1659,7 +1712,10 @@ where ..Default::default() }) } else { - Err(InvalidTransaction::Custom(2).into()) + Err(InvalidTransaction::Custom( + CustomTransactionError::StakeAmountTooLow.into(), + ) + .into()) } } Some(Call::batch_reveal_weights { netuid, .. }) => { @@ -1671,7 +1727,10 @@ where ..Default::default() }) } else { - Err(InvalidTransaction::Custom(6).into()) + Err(InvalidTransaction::Custom( + CustomTransactionError::StakeAmountTooLow.into(), + ) + .into()) } } Some(Call::set_weights { netuid, .. }) => { @@ -1683,7 +1742,10 @@ where ..Default::default() }) } else { - Err(InvalidTransaction::Custom(3).into()) + Err(InvalidTransaction::Custom( + CustomTransactionError::StakeAmountTooLow.into(), + ) + .into()) } } Some(Call::set_tao_weights { netuid, hotkey, .. }) => { @@ -1695,7 +1757,10 @@ where ..Default::default() }) } else { - Err(InvalidTransaction::Custom(4).into()) + Err(InvalidTransaction::Custom( + CustomTransactionError::StakeAmountTooLow.into(), + ) + .into()) } } Some(Call::commit_crv3_weights { netuid, .. }) => { @@ -1707,38 +1772,91 @@ where ..Default::default() }) } else { - Err(InvalidTransaction::Custom(7).into()) + Err(InvalidTransaction::Custom( + CustomTransactionError::StakeAmountTooLow.into(), + ) + .into()) } } Some(Call::add_stake { - hotkey: _, - netuid: _, + hotkey, + netuid, amount_staked, }) => { - // Check that amount parameter is at least the min stake - // also check the coldkey balance - let coldkey_balance = <::Currency as fungible::Inspect< - ::AccountId, - >>::reducible_balance( - who, Preservation::Expendable, Fortitude::Polite - ); - - if (*amount_staked < DefaultMinStake::::get()) - || (coldkey_balance < DefaultMinStake::::get()) - { - InvalidTransaction::Custom(CustomTransactionError::StakeAmountTooLow.into()) - .into() - } else { - Ok(ValidTransaction { - priority: Self::get_priority_vanilla(), - ..Default::default() - }) - } + // Fully validate the user input + Self::result_to_validity(Pallet::::validate_add_stake( + who, + hotkey, + *netuid, + *amount_staked, + )) + } + Some(Call::remove_stake { + hotkey, + netuid, + amount_unstaked, + }) => { + // Fully validate the user input + Self::result_to_validity(Pallet::::validate_remove_stake( + who, + hotkey, + *netuid, + *amount_unstaked, + )) + } + Some(Call::move_stake { + origin_hotkey, + destination_hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + }) => { + // Fully validate the user input + Self::result_to_validity(Pallet::::validate_stake_transition( + who, + who, + origin_hotkey, + destination_hotkey, + *origin_netuid, + *destination_netuid, + *alpha_amount, + )) + } + Some(Call::transfer_stake { + destination_coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + }) => { + // Fully validate the user input + Self::result_to_validity(Pallet::::validate_stake_transition( + who, + destination_coldkey, + hotkey, + hotkey, + *origin_netuid, + *destination_netuid, + *alpha_amount, + )) + } + Some(Call::swap_stake { + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + }) => { + // Fully validate the user input + Self::result_to_validity(Pallet::::validate_stake_transition( + who, + who, + hotkey, + hotkey, + *origin_netuid, + *destination_netuid, + *alpha_amount, + )) } - Some(Call::remove_stake { .. }) => Ok(ValidTransaction { - priority: Self::get_priority_vanilla(), - ..Default::default() - }), Some(Call::register { netuid, .. } | Call::burned_register { netuid, .. }) => { let registrations_this_interval = Pallet::::get_registrations_this_interval(*netuid); @@ -1747,7 +1865,10 @@ where if registrations_this_interval >= (max_registrations_per_interval.saturating_mul(3)) { // If the registration limit for the interval is exceeded, reject the transaction - return Err(InvalidTransaction::Custom(5).into()); + return Err(InvalidTransaction::Custom( + CustomTransactionError::RateLimitExceeded.into(), + ) + .into()); } Ok(ValidTransaction { priority: Self::get_priority_vanilla(), diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 6493366749..dd5e97b78a 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -25,8 +25,8 @@ mod errors { /// Request to stake, unstake or subscribe is made by a coldkey that is not associated with /// the hotkey account. NonAssociatedColdKey, - /// Stake amount to withdraw is zero. - StakeToWithdrawIsZero, + /// DEPRECATED: Stake amount to withdraw is zero. + // StakeToWithdrawIsZero, /// The caller does not have enought stake to perform this action. NotEnoughStake, /// The caller is requesting removing more stake than there exists in the staking account. diff --git a/pallets/subtensor/src/macros/genesis.rs b/pallets/subtensor/src/macros/genesis.rs index 96c4db62e6..f1288c5a63 100644 --- a/pallets/subtensor/src/macros/genesis.rs +++ b/pallets/subtensor/src/macros/genesis.rs @@ -66,13 +66,13 @@ mod genesis { Alpha::::insert( // Lock the initial funds making this key the owner. (hotkey.clone(), hotkey.clone(), netuid), - U64F64::from_num(1_000_000_000), + U64F64::saturating_from_num(1_000_000_000), ); TotalHotkeyAlpha::::insert(hotkey.clone(), netuid, 1_000_000_000); TotalHotkeyShares::::insert( hotkey.clone(), netuid, - U64F64::from_num(1_000_000_000), + U64F64::saturating_from_num(1_000_000_000), ); // TotalColdkeyAlpha::::insert(hotkey.clone(), netuid, 1_000_000_000); SubnetAlphaOut::::insert(netuid, 1_000_000_000); diff --git a/pallets/subtensor/src/migrations/migrate_rao.rs b/pallets/subtensor/src/migrations/migrate_rao.rs index d575c07f56..3c034b7dad 100644 --- a/pallets/subtensor/src/migrations/migrate_rao.rs +++ b/pallets/subtensor/src/migrations/migrate_rao.rs @@ -45,10 +45,10 @@ pub fn migrate_rao() -> Weight { }); // Set all the stake on root 0 subnet. Alpha::::mutate((hotkey.clone(), coldkey.clone(), 0), |total| { - *total = total.saturating_add(U64F64::from_num(stake)) + *total = total.saturating_add(U64F64::saturating_from_num(stake)) }); TotalHotkeyShares::::mutate(hotkey.clone(), 0, |total| { - *total = total.saturating_add(U64F64::from_num(stake)) + *total = total.saturating_add(U64F64::saturating_from_num(stake)) }); // Set the total stake on the hotkey TotalHotkeyAlpha::::mutate(hotkey.clone(), 0, |total| { @@ -71,17 +71,26 @@ pub fn migrate_rao() -> Weight { } let owner: T::AccountId = SubnetOwner::::get(netuid); let lock: u64 = SubnetLocked::::get(netuid); - let initial_liquidity: u64 = 100_000_000_000; // 100 TAO. - let remaining_lock: u64 = lock.saturating_sub(initial_liquidity); + + // Put initial TAO from lock into subnet TAO and produce numerically equal amount of Alpha + // The initial TAO is the locked amount, with a minimum of 1 RAO and a cap of 100 TAO. + let pool_initial_tao = 100_000_000_000.min(lock.max(1)); + + let remaining_lock = lock.saturating_sub(pool_initial_tao); + // Refund the owner for the remaining lock. Pallet::::add_balance_to_coldkey_account(&owner, remaining_lock); - SubnetTAO::::insert(netuid, initial_liquidity); // Set TAO to the lock. - SubnetAlphaIn::::insert(netuid, initial_liquidity); // Set AlphaIn to the initial alpha distribution. + SubnetTAO::::insert(netuid, pool_initial_tao); // Set TAO to the lock. + + SubnetAlphaIn::::insert( + netuid, + pool_initial_tao.saturating_mul(netuids.len() as u64), + ); // Set AlphaIn to the initial alpha distribution. + SubnetAlphaOut::::insert(netuid, 0); // Set zero subnet alpha out. SubnetMechanism::::insert(netuid, 1); // Convert to dynamic immediately with initialization. Tempo::::insert(netuid, DefaultTempo::::get()); // Set the token symbol for this subnet using Self instead of Pallet:: TokenSymbol::::insert(netuid, Pallet::::get_symbol_for_subnet(*netuid)); - SubnetTAO::::insert(netuid, initial_liquidity); // Set TAO to the lock. TotalStakeAtDynamic::::insert(netuid, 0); if let Ok(owner_coldkey) = SubnetOwner::::try_get(netuid) { @@ -89,8 +98,12 @@ pub fn migrate_rao() -> Weight { SubnetOwnerHotkey::::insert(netuid, owner_coldkey.clone()); // Associate the coldkey to coldkey. Pallet::::create_account_if_non_existent(&owner_coldkey, &owner_coldkey); - // Register the owner_coldkey as neuron to the network. - let _neuron_uid: u16 = Pallet::::register_neuron(*netuid, &owner_coldkey); + + // Only register the owner coldkey if it's not already a hotkey on the subnet. + if !Uids::::contains_key(*netuid, &owner_coldkey) { + // Register the owner_coldkey as neuron to the network. + let _neuron_uid: u16 = Pallet::::register_neuron(*netuid, &owner_coldkey); + } // Register the neuron immediately. if !Identities::::contains_key(owner_coldkey.clone()) { // Set the identitiy for the Owner coldkey if non existent. diff --git a/pallets/subtensor/src/rpc_info/delegate_info.rs b/pallets/subtensor/src/rpc_info/delegate_info.rs index 5776c48af4..f8d6418232 100644 --- a/pallets/subtensor/src/rpc_info/delegate_info.rs +++ b/pallets/subtensor/src/rpc_info/delegate_info.rs @@ -2,6 +2,7 @@ use super::*; use frame_support::pallet_prelude::{Decode, Encode}; use frame_support::storage::IterableStorageMap; use frame_support::IterableStorageDoubleMap; +use safe_math::*; use substrate_fixed::types::U64F64; extern crate alloc; use codec::Compact; @@ -27,16 +28,16 @@ impl Pallet { emissions_per_day: U64F64, ) -> U64F64 { // Get the take as a percentage and subtract it from 1 for remainder. - let without_take: U64F64 = U64F64::from_num(1) - .saturating_sub(U64F64::from_num(take.0).saturating_div(u16::MAX.into())); + let without_take: U64F64 = U64F64::saturating_from_num(1) + .saturating_sub(U64F64::saturating_from_num(take.0).safe_div(u16::MAX.into())); - if total_stake > U64F64::from_num(0) { + if total_stake > U64F64::saturating_from_num(0) { emissions_per_day .saturating_mul(without_take) // Divide by 1000 TAO for return per 1k - .saturating_div(total_stake.saturating_div(U64F64::from_num(1000.0 * 1e9))) + .safe_div(total_stake.safe_div(U64F64::saturating_from_num(1000.0 * 1e9))) } else { - U64F64::from_num(0) + U64F64::saturating_from_num(0) } } @@ -66,7 +67,7 @@ impl Pallet { let registrations = Self::get_registered_networks_for_hotkey(&delegate.clone()); let mut validator_permits = Vec::>::new(); - let mut emissions_per_day: U64F64 = U64F64::from_num(0); + let mut emissions_per_day: U64F64 = U64F64::saturating_from_num(0); for netuid in registrations.iter() { if let Ok(uid) = Self::get_uid_for_net_and_hotkey(*netuid, &delegate.clone()) { @@ -77,8 +78,8 @@ impl Pallet { let emission: U64F64 = Self::get_emission_for_uid(*netuid, uid).into(); let tempo: U64F64 = Self::get_tempo(*netuid).into(); - if tempo > U64F64::from_num(0) { - let epochs_per_day: U64F64 = U64F64::from_num(7200).saturating_div(tempo); + if tempo > U64F64::saturating_from_num(0) { + let epochs_per_day: U64F64 = U64F64::saturating_from_num(7200).safe_div(tempo); emissions_per_day = emissions_per_day.saturating_add(emission.saturating_mul(epochs_per_day)); } @@ -101,8 +102,8 @@ impl Pallet { owner_ss58: owner.clone(), registrations: registrations.iter().map(|x| x.into()).collect(), validator_permits, - return_per_1000: U64F64::to_num::(return_per_1000).into(), - total_daily_return: U64F64::to_num::(emissions_per_day).into(), + return_per_1000: return_per_1000.saturating_to_num::().into(), + total_daily_return: emissions_per_day.saturating_to_num::().into(), } } diff --git a/pallets/subtensor/src/rpc_info/metagraph.rs b/pallets/subtensor/src/rpc_info/metagraph.rs index 091c7e259c..1e77e5fe6a 100644 --- a/pallets/subtensor/src/rpc_info/metagraph.rs +++ b/pallets/subtensor/src/rpc_info/metagraph.rs @@ -6,7 +6,7 @@ use frame_support::pallet_prelude::{Decode, Encode}; use substrate_fixed::types::I64F64; use subtensor_macros::freeze_struct; -#[freeze_struct("eff674535ea437ae")] +#[freeze_struct("bce2310daa502e48")] #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] pub struct Metagraph { // Subnet index @@ -82,7 +82,7 @@ pub struct Metagraph { // Metagraph info. hotkeys: Vec, // hotkey per UID coldkeys: Vec, // coldkey per UID - identities: Vec, // coldkeys identities + identities: Vec>, // coldkeys identities axons: Vec, // UID axons. active: Vec, // Avtive per UID validator_permit: Vec, // Val permit per UID @@ -114,7 +114,7 @@ impl Pallet { let mut hotkeys: Vec = vec![]; let mut coldkeys: Vec = vec![]; let mut block_at_registration: Vec> = vec![]; - let mut identities: Vec = vec![]; + let mut identities: Vec> = vec![]; let mut axons: Vec = vec![]; for uid in 0..n { let hotkey = Keys::::get(netuid, uid); @@ -122,7 +122,7 @@ impl Pallet { hotkeys.push(hotkey.clone()); coldkeys.push(coldkey.clone()); block_at_registration.push(BlockAtRegistration::::get(netuid, uid).into()); - identities.push(Identities::::get(coldkey.clone())?); + identities.push(Identities::::get(coldkey.clone())); axons.push(Self::get_axon_info(netuid, &hotkey)); } let mut tao_dividends_per_hotkey: Vec<(T::AccountId, Compact)> = vec![]; diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index 66b337b3db..326eca1dce 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -47,34 +47,16 @@ impl Pallet { stake_to_be_added ); - // 2. Ensure that the subnet exists. - ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + // 2. Validate user input + Self::validate_add_stake(&coldkey, &hotkey, netuid, stake_to_be_added)?; - // 3. Ensure the callers coldkey has enough stake to perform the transaction. - ensure!( - Self::can_remove_balance_from_coldkey_account(&coldkey, stake_to_be_added), - Error::::NotEnoughBalanceToStake - ); - - // 4. Ensure that the hotkey account exists this is only possible through registration. - ensure!( - Self::hotkey_account_exists(&hotkey), - 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. + // 3. 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)?; - // 6. Swap the stake into alpha on the subnet and increase counters. + // 4. Swap the stake into alpha on the subnet and increase counters. // Emit the staking event. - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); Self::stake_into_subnet(&hotkey, &coldkey, netuid, tao_staked, fee); // Ok and return. diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index 1a1a2d00dd..0cbe3bccf4 100644 --- a/pallets/subtensor/src/staking/helpers.rs +++ b/pallets/subtensor/src/staking/helpers.rs @@ -46,10 +46,11 @@ impl Pallet { Self::get_all_subnet_netuids() .iter() .map(|netuid| { - let alpha: I96F32 = - I96F32::from_num(Self::get_stake_for_hotkey_on_subnet(hotkey, *netuid)); + let alpha: I96F32 = I96F32::saturating_from_num( + Self::get_stake_for_hotkey_on_subnet(hotkey, *netuid), + ); let tao_price: I96F32 = Self::get_alpha_price(*netuid); - alpha.saturating_mul(tao_price).to_num::() + alpha.saturating_mul(tao_price).saturating_to_num::() }) .sum() } @@ -65,9 +66,9 @@ impl Pallet { for (netuid, alpha) in Alpha::::iter_prefix((hotkey, coldkey)) { let tao_price: I96F32 = Self::get_alpha_price(netuid); total_stake = total_stake.saturating_add( - I96F32::from_num(alpha) + I96F32::saturating_from_num(alpha) .saturating_mul(tao_price) - .to_num::(), + .saturating_to_num::(), ); } total_stake diff --git a/pallets/subtensor/src/staking/move_stake.rs b/pallets/subtensor/src/staking/move_stake.rs index fff688084f..75f9331356 100644 --- a/pallets/subtensor/src/staking/move_stake.rs +++ b/pallets/subtensor/src/staking/move_stake.rs @@ -1,4 +1,5 @@ use super::*; +use safe_math::*; use sp_core::Get; impl Pallet { @@ -31,68 +32,21 @@ impl Pallet { destination_netuid: u16, alpha_amount: u64, ) -> dispatch::DispatchResult { - // --- 1. Check that the origin is signed by the origin_hotkey. + // Check that the origin is signed by the origin_hotkey. let coldkey = ensure_signed(origin)?; - // --- 2. Check that the subnet exists. - ensure!( - Self::if_subnet_exist(origin_netuid), - Error::::SubnetNotExists - ); - ensure!( - Self::if_subnet_exist(destination_netuid), - Error::::SubnetNotExists - ); - - // --- 3. Check that the origin_hotkey exists. - ensure!( - Self::hotkey_account_exists(&origin_hotkey), - Error::::HotKeyAccountNotExists - ); - - // --- 4. Check that the destination_hotkey exists. - ensure!( - Self::hotkey_account_exists(&destination_hotkey), - Error::::HotKeyAccountNotExists - ); - - // --- 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, + // Validate input and move stake + let tao_moved = Self::transition_stake_internal( &coldkey, + &coldkey, + &origin_hotkey, + &destination_hotkey, origin_netuid, - ); - ensure!( - alpha_amount <= origin_alpha, - Error::::NotEnoughStakeToWithdraw - ); - - // --- 7. Unstake the amount of alpha from the origin subnet, converting it to TAO - let fee = DefaultMinStake::::get().saturating_div(2); // fee is half of min stake because it is applied twice - let origin_tao = Self::unstake_from_subnet( - &origin_hotkey.clone(), - &coldkey.clone(), - origin_netuid, - alpha_amount, - fee, - ); - - // 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(), - &coldkey.clone(), destination_netuid, - origin_tao, - fee, - ); + alpha_amount, + )?; - // --- 9. Log the event. + // Log the event. log::info!( "StakeMoved( coldkey:{:?}, origin_hotkey:{:?}, origin_netuid:{:?}, destination_hotkey:{:?}, destination_netuid:{:?} )", coldkey.clone(), @@ -107,10 +61,10 @@ impl Pallet { origin_netuid, destination_hotkey, destination_netuid, - origin_tao.saturating_sub(fee), + tao_moved, )); - // -- 10. Ok and return. + // Ok and return. Ok(()) } @@ -147,59 +101,19 @@ impl Pallet { destination_netuid: u16, alpha_amount: u64, ) -> dispatch::DispatchResult { - // 1. Ensure the extrinsic is signed by the origin_coldkey. + // Ensure the extrinsic is signed by the origin_coldkey. let coldkey = ensure_signed(origin)?; - // 2. Ensure both subnets exist. - ensure!( - Self::if_subnet_exist(origin_netuid), - Error::::SubnetNotExists - ); - ensure!( - Self::if_subnet_exist(destination_netuid), - Error::::SubnetNotExists - ); - - // 3. Check that the hotkey exists. - ensure!( - Self::hotkey_account_exists(&hotkey), - Error::::HotKeyAccountNotExists - ); - - // 4. Check that the signed coldkey actually owns the given hotkey. - ensure!( - Self::coldkey_owns_hotkey(&coldkey, &hotkey), - Error::::NonAssociatedColdKey - ); - - // 5. Get current stake. - let origin_alpha = - Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, origin_netuid); - ensure!( - alpha_amount <= origin_alpha, - Error::::NotEnoughStakeToWithdraw - ); - - // 6. Unstake from the origin coldkey; this returns an amount of TAO. - let fee = DefaultMinStake::::get().saturating_div(2); - let origin_tao = - Self::unstake_from_subnet(&hotkey, &coldkey, origin_netuid, alpha_amount, fee); - - // 7. Ensure the returned TAO meets a minimum stake requirement (if required). - ensure!( - origin_tao >= DefaultMinStake::::get(), - Error::::AmountTooLow - ); - - // 8. Stake the TAO into `(destination_coldkey, hotkey)` on the destination subnet. - // Create the account if it does not exist. - Self::stake_into_subnet( - &hotkey, + // Validate input and move stake + let tao_moved = Self::transition_stake_internal( + &coldkey, &destination_coldkey, + &hotkey, + &hotkey, + origin_netuid, destination_netuid, - origin_tao, - fee, - ); + alpha_amount, + )?; // 9. Emit an event for logging/monitoring. log::info!( @@ -209,7 +123,7 @@ impl Pallet { hotkey, origin_netuid, destination_netuid, - origin_tao + tao_moved ); Self::deposit_event(Event::StakeTransferred( coldkey, @@ -217,7 +131,7 @@ impl Pallet { hotkey, origin_netuid, destination_netuid, - origin_tao, + tao_moved, )); // 10. Return success. @@ -254,71 +168,84 @@ impl Pallet { destination_netuid: u16, alpha_amount: u64, ) -> dispatch::DispatchResult { - // 1. Ensure the extrinsic is signed by the coldkey. + // Ensure the extrinsic is signed by the coldkey. let coldkey = ensure_signed(origin)?; - // 2. Check that both subnets exist. - ensure!( - Self::if_subnet_exist(origin_netuid), - Error::::SubnetNotExists - ); - ensure!( - Self::if_subnet_exist(destination_netuid), - Error::::SubnetNotExists - ); - - // 3. Check that the hotkey exists. - ensure!( - Self::hotkey_account_exists(&hotkey), - Error::::HotKeyAccountNotExists - ); - - // 4. Ensure this coldkey actually owns the hotkey. - ensure!( - Self::coldkey_owns_hotkey(&coldkey, &hotkey), - Error::::NonAssociatedColdKey - ); - - // 5. Ensure there is enough stake in the origin subnet. - let origin_alpha = - Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, origin_netuid); - ensure!( - alpha_amount <= origin_alpha, - Error::::NotEnoughStakeToWithdraw - ); - - // 6. Unstake from the origin subnet, returning TAO (or a 1:1 equivalent). - let fee = DefaultMinStake::::get().saturating_div(2); - let tao_unstaked = - Self::unstake_from_subnet(&hotkey, &coldkey, origin_netuid, alpha_amount, fee); - - // 7. Check that the unstaked amount is above the minimum stake threshold. - ensure!( - tao_unstaked >= DefaultMinStake::::get(), - Error::::AmountTooLow - ); - - // 8. Stake the unstaked amount into the destination subnet, using the same coldkey/hotkey. - Self::stake_into_subnet(&hotkey, &coldkey, destination_netuid, tao_unstaked, fee); + // Validate input and move stake + let tao_moved = Self::transition_stake_internal( + &coldkey, + &coldkey, + &hotkey, + &hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + )?; - // 9. Emit an event for logging. + // Emit an event for logging. log::info!( "StakeSwapped(coldkey: {:?}, hotkey: {:?}, origin_netuid: {:?}, destination_netuid: {:?}, amount: {:?})", coldkey, hotkey, origin_netuid, destination_netuid, - tao_unstaked + tao_moved ); Self::deposit_event(Event::StakeSwapped( coldkey, hotkey, origin_netuid, destination_netuid, - tao_unstaked, + tao_moved, )); - // 10. Return success. + // 6. Return success. Ok(()) } + + fn transition_stake_internal( + origin_coldkey: &T::AccountId, + destination_coldkey: &T::AccountId, + origin_hotkey: &T::AccountId, + destination_hotkey: &T::AccountId, + origin_netuid: u16, + destination_netuid: u16, + alpha_amount: u64, + ) -> Result> { + // Validate user input + Self::validate_stake_transition( + origin_coldkey, + destination_coldkey, + origin_hotkey, + destination_hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + )?; + + // Unstake from the origin subnet, returning TAO (or a 1:1 equivalent). + let fee = DefaultStakingFee::::get().safe_div(2); + let tao_unstaked = Self::unstake_from_subnet( + origin_hotkey, + origin_coldkey, + origin_netuid, + alpha_amount, + fee, + ); + + // Stake the unstaked amount into the destination. + // Because of the fee, the tao_unstaked may be too low if initial stake is low. In that case, + // do not restake. + if tao_unstaked >= DefaultMinStake::::get().saturating_add(fee) { + Self::stake_into_subnet( + destination_hotkey, + destination_coldkey, + destination_netuid, + tao_unstaked, + fee, + ); + } + + Ok(tao_unstaked.saturating_sub(fee)) + } } diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index 5b3ed33902..bab8c9a294 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -47,36 +47,21 @@ impl Pallet { alpha_unstaked ); - // 2. Ensure that the stake amount to be removed is above zero. - ensure!(alpha_unstaked > 0, Error::::StakeToWithdrawIsZero); + // 2. Validate the user input + Self::validate_remove_stake(&coldkey, &hotkey, netuid, alpha_unstaked)?; - // 3. Ensure that the subnet exists. - ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); - - // 4. Ensure that the hotkey account exists this is only possible through registration. - ensure!( - Self::hotkey_account_exists(&hotkey), - Error::::HotKeyAccountNotExists - ); - - // 5. Ensure that the hotkey has enough stake to withdraw. - ensure!( - Self::has_enough_stake_on_subnet(&hotkey, &coldkey, netuid, alpha_unstaked), - Error::::NotEnoughStakeToWithdraw - ); - - // 6. Swap the alpba to tao and update counters for this subnet. - let fee = DefaultMinStake::::get(); + // 3. Swap the alpba to tao and update counters for this subnet. + let fee = DefaultStakingFee::::get(); let tao_unstaked: u64 = Self::unstake_from_subnet(&hotkey, &coldkey, netuid, alpha_unstaked, fee); - // 7. We add the balance to the coldkey. If the above fails we will not credit this coldkey. + // 4. We add the balance to the coldkey. If the above fails we will not credit this coldkey. Self::add_balance_to_coldkey_account(&coldkey, tao_unstaked); - // 8. If the stake is below the minimum, we clear the nomination from storage. + // 5. If the stake is below the minimum, we clear the nomination from storage. Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid); - // 9. Check if stake lowered below MinStake and remove Pending children if it did + // 6. Check if stake lowered below MinStake and remove Pending children if it did if Self::get_total_stake_for_hotkey(&hotkey) < StakeThreshold::::get() { Self::get_all_subnet_netuids().iter().for_each(|netuid| { PendingChildKeys::::remove(netuid, &hotkey); @@ -117,7 +102,7 @@ impl Pallet { origin: T::RuntimeOrigin, hotkey: T::AccountId, ) -> dispatch::DispatchResult { - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // 1. We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information. let coldkey = ensure_signed(origin)?; @@ -185,7 +170,7 @@ impl Pallet { origin: T::RuntimeOrigin, hotkey: T::AccountId, ) -> dispatch::DispatchResult { - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // 1. We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information. let coldkey = ensure_signed(origin)?; diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index d6901eec21..1bd6aa1af5 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -1,4 +1,5 @@ use super::*; +use safe_math::*; use share_pool::{SharePool, SharePoolDataOperations}; use sp_std::ops::Neg; use substrate_fixed::types::{I64F64, I96F32, U64F64}; @@ -31,17 +32,17 @@ impl Pallet { /// * `I96F32` - The price of alpha for the specified subnet. pub fn get_alpha_price(netuid: u16) -> I96F32 { if netuid == Self::get_root_netuid() { - return I96F32::from_num(1.0); // Root. + return I96F32::saturating_from_num(1.0); // Root. } if SubnetMechanism::::get(netuid) == 0 { - return I96F32::from_num(1.0); // Stable + return I96F32::saturating_from_num(1.0); // Stable } if SubnetAlphaIn::::get(netuid) == 0 { - I96F32::from_num(0) + I96F32::saturating_from_num(0) } else { - I96F32::from_num(SubnetTAO::::get(netuid)) - .checked_div(I96F32::from_num(SubnetAlphaIn::::get(netuid))) - .unwrap_or(I96F32::from_num(0)) + I96F32::saturating_from_num(SubnetTAO::::get(netuid)) + .checked_div(I96F32::saturating_from_num(SubnetAlphaIn::::get(netuid))) + .unwrap_or(I96F32::saturating_from_num(0)) } } @@ -66,11 +67,11 @@ impl Pallet { let stored_weight = TaoWeight::::get(); // Step 2: Convert the u64 weight to I96F32 - let weight_fixed = I96F32::from_num(stored_weight); + let weight_fixed = I96F32::saturating_from_num(stored_weight); // Step 3: Normalize the weight by dividing by u64::MAX // This ensures the result is always between 0 and 1 - weight_fixed.saturating_div(I96F32::from_num(u64::MAX)) + weight_fixed.safe_div(I96F32::saturating_from_num(u64::MAX)) } /// Sets the global global weight in storage. @@ -101,16 +102,17 @@ impl Pallet { netuid: u16, ) -> (I64F64, I64F64, I64F64) { // Retrieve the global tao weight. - let tao_weight = I64F64::from_num(Self::get_tao_weight()); + let tao_weight = I64F64::saturating_from_num(Self::get_tao_weight()); log::debug!("tao_weight: {:?}", tao_weight); // Step 1: Get stake of hotkey (neuron) let alpha_stake = - I64F64::from_num(Self::get_inherited_for_hotkey_on_subnet(hotkey, netuid)); + I64F64::saturating_from_num(Self::get_inherited_for_hotkey_on_subnet(hotkey, netuid)); log::trace!("alpha_stake: {:?}", alpha_stake); // Step 2: Get the global tao stake for the hotkey - let tao_stake = I64F64::from_num(Self::get_inherited_for_hotkey_on_subnet(hotkey, 0)); + let tao_stake = + I64F64::saturating_from_num(Self::get_inherited_for_hotkey_on_subnet(hotkey, 0)); log::trace!("tao_stake: {:?}", tao_stake); // Step 3: Combine alpha and tao stakes @@ -124,7 +126,7 @@ impl Pallet { /// pub fn get_stake_weights_for_network(netuid: u16) -> (Vec, Vec, Vec) { // Retrieve the global tao weight. - let tao_weight: I64F64 = I64F64::from_num(Self::get_tao_weight()); + let tao_weight: I64F64 = I64F64::saturating_from_num(Self::get_tao_weight()); log::debug!("tao_weight: {:?}", tao_weight); // Step 1: Get subnetwork size @@ -135,9 +137,11 @@ impl Pallet { .map(|uid| { if Keys::::contains_key(netuid, uid) { let hotkey: T::AccountId = Keys::::get(netuid, uid); - I64F64::from_num(Self::get_inherited_for_hotkey_on_subnet(&hotkey, netuid)) + I64F64::saturating_from_num(Self::get_inherited_for_hotkey_on_subnet( + &hotkey, netuid, + )) } else { - I64F64::from_num(0) + I64F64::saturating_from_num(0) } }) .collect(); @@ -149,9 +153,11 @@ impl Pallet { .map(|uid| { if Keys::::contains_key(netuid, uid) { let hotkey: T::AccountId = Keys::::get(netuid, uid); - I64F64::from_num(Self::get_inherited_for_hotkey_on_subnet(&hotkey, 0)) + I64F64::saturating_from_num(Self::get_inherited_for_hotkey_on_subnet( + &hotkey, 0, + )) } else { - I64F64::from_num(0) + I64F64::saturating_from_num(0) } }) .collect(); @@ -199,7 +205,7 @@ impl Pallet { pub fn get_inherited_for_hotkey_on_subnet(hotkey: &T::AccountId, netuid: u16) -> u64 { // Step 1: Retrieve the initial total stake (alpha) for the hotkey on the specified subnet. let initial_alpha: I96F32 = - I96F32::from_num(Self::get_stake_for_hotkey_on_subnet(hotkey, netuid)); + I96F32::saturating_from_num(Self::get_stake_for_hotkey_on_subnet(hotkey, netuid)); log::trace!( "Initial alpha for hotkey {:?} on subnet {}: {:?}", hotkey, @@ -207,12 +213,12 @@ impl Pallet { initial_alpha ); if netuid == 0 { - return initial_alpha.to_num::(); + return initial_alpha.saturating_to_num::(); } // Initialize variables to track alpha allocated to children and inherited from parents. - let mut alpha_to_children: I96F32 = I96F32::from_num(0); - let mut alpha_from_parents: I96F32 = I96F32::from_num(0); + let mut alpha_to_children: I96F32 = I96F32::saturating_from_num(0); + let mut alpha_from_parents: I96F32 = I96F32::saturating_from_num(0); // Step 2: Retrieve the lists of parents and children for the hotkey on the subnet. let parents: Vec<(u64, T::AccountId)> = Self::get_parents(hotkey, netuid); @@ -233,8 +239,8 @@ impl Pallet { // Step 3: Calculate the total alpha allocated to children. for (proportion, _) in children { // Convert the proportion to a normalized value between 0 and 1. - let normalized_proportion: I96F32 = - I96F32::from_num(proportion).saturating_div(I96F32::from_num(u64::MAX)); + let normalized_proportion: I96F32 = I96F32::saturating_from_num(proportion) + .safe_div(I96F32::saturating_from_num(u64::MAX)); log::trace!( "Normalized proportion for child: {:?}", normalized_proportion @@ -242,7 +248,7 @@ impl Pallet { // Calculate the amount of alpha to be allocated to this child. let alpha_proportion_to_child: I96F32 = - I96F32::from_num(initial_alpha).saturating_mul(normalized_proportion); + I96F32::saturating_from_num(initial_alpha).saturating_mul(normalized_proportion); log::trace!("Alpha proportion to child: {:?}", alpha_proportion_to_child); // Add this child's allocation to the total alpha allocated to children. @@ -254,7 +260,7 @@ impl Pallet { for (proportion, parent) in parents { // Retrieve the parent's total stake on this subnet. let parent_alpha: I96F32 = - I96F32::from_num(Self::get_stake_for_hotkey_on_subnet(&parent, netuid)); + I96F32::saturating_from_num(Self::get_stake_for_hotkey_on_subnet(&parent, netuid)); log::trace!( "Parent alpha for parent {:?} on subnet {}: {:?}", parent, @@ -263,8 +269,8 @@ impl Pallet { ); // Convert the proportion to a normalized value between 0 and 1. - let normalized_proportion: I96F32 = - I96F32::from_num(proportion).saturating_div(I96F32::from_num(u64::MAX)); + let normalized_proportion: I96F32 = I96F32::saturating_from_num(proportion) + .safe_div(I96F32::saturating_from_num(u64::MAX)); log::trace!( "Normalized proportion from parent: {:?}", normalized_proportion @@ -272,7 +278,7 @@ impl Pallet { // Calculate the amount of alpha to be inherited from this parent. let alpha_proportion_from_parent: I96F32 = - I96F32::from_num(parent_alpha).saturating_mul(normalized_proportion); + I96F32::saturating_from_num(parent_alpha).saturating_mul(normalized_proportion); log::trace!( "Alpha proportion from parent: {:?}", alpha_proportion_from_parent @@ -298,7 +304,7 @@ impl Pallet { ); // Step 6: Return the final inherited alpha value. - finalized_alpha.to_num::() + finalized_alpha.saturating_to_num::() } /// Checks if a specific hotkey-coldkey pair has enough stake on a subnet to fulfill a given decrement. @@ -460,22 +466,23 @@ impl Pallet { // Step 2: Initialized vars. let alpha: I96F32 = if mechanism_id == 1 { // Step 3.a.1: Dynamic mechanism calculations - let tao_reserves: I96F32 = I96F32::from_num(SubnetTAO::::get(netuid)); - let alpha_reserves: I96F32 = I96F32::from_num(SubnetAlphaIn::::get(netuid)); + let tao_reserves: I96F32 = I96F32::saturating_from_num(SubnetTAO::::get(netuid)); + let alpha_reserves: I96F32 = + I96F32::saturating_from_num(SubnetAlphaIn::::get(netuid)); // Step 3.a.2: Compute constant product k = alpha * tao let k: I96F32 = alpha_reserves.saturating_mul(tao_reserves); // Step 3.a.3: Calculate alpha staked using the constant product formula // alpha_stake_recieved = current_alpha - (k / (current_tao + new_tao)) alpha_reserves.saturating_sub( - k.checked_div(tao_reserves.saturating_add(I96F32::from_num(tao))) - .unwrap_or(I96F32::from_num(0)), + k.checked_div(tao_reserves.saturating_add(I96F32::saturating_from_num(tao))) + .unwrap_or(I96F32::saturating_from_num(0)), ) } else { // Step 3.b.1: Stable mechanism, just return the value 1:1 - I96F32::from_num(tao) + I96F32::saturating_from_num(tao) }; // Return simulated amount. - alpha.to_num::() + alpha.saturating_to_num::() } /// Swaps a subnet's Alpba token for TAO. @@ -487,21 +494,22 @@ impl Pallet { // Step 2: Swap alpha and attain tao let tao: I96F32 = if mechanism_id == 1 { // Step 3.a.1: Dynamic mechanism calculations - let tao_reserves: I96F32 = I96F32::from_num(SubnetTAO::::get(netuid)); - let alpha_reserves: I96F32 = I96F32::from_num(SubnetAlphaIn::::get(netuid)); + let tao_reserves: I96F32 = I96F32::saturating_from_num(SubnetTAO::::get(netuid)); + let alpha_reserves: I96F32 = + I96F32::saturating_from_num(SubnetAlphaIn::::get(netuid)); // Step 3.a.2: Compute constant product k = alpha * tao let k: I96F32 = alpha_reserves.saturating_mul(tao_reserves); // Step 3.a.3: Calculate alpha staked using the constant product formula // tao_recieved = tao_reserves - (k / (alpha_reserves + new_tao)) tao_reserves.saturating_sub( - k.checked_div(alpha_reserves.saturating_add(I96F32::from_num(alpha))) - .unwrap_or(I96F32::from_num(0)), + k.checked_div(alpha_reserves.saturating_add(I96F32::saturating_from_num(alpha))) + .unwrap_or(I96F32::saturating_from_num(0)), ) } else { // Step 3.b.1: Stable mechanism, just return the value 1:1 - I96F32::from_num(alpha) + I96F32::saturating_from_num(alpha) }; - tao.to_num::() + tao.saturating_to_num::() } /// Swaps TAO for the alpha token on the subnet. @@ -513,27 +521,28 @@ impl Pallet { // Step 2: Initialized vars. let alpha: I96F32 = if mechanism_id == 1 { // Step 3.a.1: Dynamic mechanism calculations - let tao_reserves: I96F32 = I96F32::from_num(SubnetTAO::::get(netuid)); - let alpha_reserves: I96F32 = I96F32::from_num(SubnetAlphaIn::::get(netuid)); + let tao_reserves: I96F32 = I96F32::saturating_from_num(SubnetTAO::::get(netuid)); + let alpha_reserves: I96F32 = + I96F32::saturating_from_num(SubnetAlphaIn::::get(netuid)); // Step 3.a.2: Compute constant product k = alpha * tao let k: I96F32 = alpha_reserves.saturating_mul(tao_reserves); // Step 3.a.3: Calculate alpha staked using the constant product formula // alpha_stake_recieved = current_alpha - (k / (current_tao + new_tao)) alpha_reserves.saturating_sub( - k.checked_div(tao_reserves.saturating_add(I96F32::from_num(tao))) - .unwrap_or(I96F32::from_num(0)), + k.checked_div(tao_reserves.saturating_add(I96F32::saturating_from_num(tao))) + .unwrap_or(I96F32::saturating_from_num(0)), ) } else { // Step 3.b.1: Stable mechanism, just return the value 1:1 - I96F32::from_num(tao) + I96F32::saturating_from_num(tao) }; // Step 4. Decrease Alpha reserves. SubnetAlphaIn::::mutate(netuid, |total| { - *total = total.saturating_sub(alpha.to_num::()); + *total = total.saturating_sub(alpha.saturating_to_num::()); }); // Step 5: Increase Alpha outstanding. SubnetAlphaOut::::mutate(netuid, |total| { - *total = total.saturating_add(alpha.to_num::()); + *total = total.saturating_add(alpha.saturating_to_num::()); }); // Step 6: Increase Tao reserves. SubnetTAO::::mutate(netuid, |total| { @@ -548,7 +557,7 @@ impl Pallet { *total = total.saturating_sub(tao); }); // Step 9. Return the alpha received. - alpha.to_num::() + alpha.saturating_to_num::() } /// Swaps a subnet's Alpba token for TAO. @@ -560,19 +569,20 @@ impl Pallet { // Step 2: Swap alpha and attain tao let tao: I96F32 = if mechanism_id == 1 { // Step 3.a.1: Dynamic mechanism calculations - let tao_reserves: I96F32 = I96F32::from_num(SubnetTAO::::get(netuid)); - let alpha_reserves: I96F32 = I96F32::from_num(SubnetAlphaIn::::get(netuid)); + let tao_reserves: I96F32 = I96F32::saturating_from_num(SubnetTAO::::get(netuid)); + let alpha_reserves: I96F32 = + I96F32::saturating_from_num(SubnetAlphaIn::::get(netuid)); // Step 3.a.2: Compute constant product k = alpha * tao let k: I96F32 = alpha_reserves.saturating_mul(tao_reserves); // Step 3.a.3: Calculate alpha staked using the constant product formula // tao_recieved = tao_reserves - (k / (alpha_reserves + new_tao)) tao_reserves.saturating_sub( - k.checked_div(alpha_reserves.saturating_add(I96F32::from_num(alpha))) - .unwrap_or(I96F32::from_num(0)), + k.checked_div(alpha_reserves.saturating_add(I96F32::saturating_from_num(alpha))) + .unwrap_or(I96F32::saturating_from_num(0)), ) } else { // Step 3.b.1: Stable mechanism, just return the value 1:1 - I96F32::from_num(alpha) + I96F32::saturating_from_num(alpha) }; // Step 4: Increase Alpha reserves. SubnetAlphaIn::::mutate(netuid, |total| { @@ -584,18 +594,18 @@ impl Pallet { }); // Step 6: Decrease tao reserves. SubnetTAO::::mutate(netuid, |total| { - *total = total.saturating_sub(tao.to_num::()); + *total = total.saturating_sub(tao.saturating_to_num::()); }); // Step 7: Reduce total TAO reserves. TotalStake::::mutate(|total| { - *total = total.saturating_sub(tao.to_num::()); + *total = total.saturating_sub(tao.saturating_to_num::()); }); // Step 8. Decrease Alpha reserves. SubnetVolume::::mutate(netuid, |total| { - *total = total.saturating_sub(tao.to_num::()); + *total = total.saturating_sub(tao.saturating_to_num::()); }); // Step 9. Return the tao received. - tao.to_num::() + tao.saturating_to_num::() } /// Unstakes alpha from a subnet for a given hotkey and coldkey pair. @@ -719,6 +729,128 @@ impl Pallet { let ops = HotkeyAlphaSharePoolDataOperations::new(hotkey, netuid); SharePool::, HotkeyAlphaSharePoolDataOperations>::new(ops) } + + /// Validate add_stake user input + /// + pub fn validate_add_stake( + coldkey: &T::AccountId, + hotkey: &T::AccountId, + netuid: u16, + stake_to_be_added: u64, + ) -> Result<(), Error> { + // Ensure that the subnet exists. + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + + // Get the minimum balance (and amount) that satisfies the transaction + let min_amount = DefaultMinStake::::get().saturating_add(DefaultStakingFee::::get()); + + // Ensure that the stake_to_be_added is at least the min_amount + ensure!(stake_to_be_added >= min_amount, Error::::AmountTooLow); + + // Ensure the callers coldkey has enough stake to perform the transaction. + ensure!( + Self::can_remove_balance_from_coldkey_account(coldkey, stake_to_be_added), + Error::::NotEnoughBalanceToStake + ); + + // Ensure that the hotkey account exists this is only possible through registration. + ensure!( + Self::hotkey_account_exists(hotkey), + Error::::HotKeyAccountNotExists + ); + + Ok(()) + } + + /// Validate remove_stake user input + /// + pub fn validate_remove_stake( + coldkey: &T::AccountId, + hotkey: &T::AccountId, + netuid: u16, + alpha_unstaked: u64, + ) -> Result<(), Error> { + // Ensure that the subnet exists. + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + + // Ensure that the stake amount to be removed is above the minimum in tao equivalent. + let tao_equivalent = Self::sim_swap_alpha_for_tao(netuid, alpha_unstaked); + ensure!( + tao_equivalent > DefaultMinStake::::get(), + Error::::AmountTooLow + ); + + // Ensure that the hotkey account exists this is only possible through registration. + ensure!( + Self::hotkey_account_exists(hotkey), + Error::::HotKeyAccountNotExists + ); + + // Ensure that the hotkey has enough stake to withdraw. + ensure!( + Self::has_enough_stake_on_subnet(hotkey, coldkey, netuid, alpha_unstaked), + Error::::NotEnoughStakeToWithdraw + ); + + Ok(()) + } + + /// Validate stake transition user input + /// That works for move_stake, transfer_stake, and swap_stake + /// + pub fn validate_stake_transition( + origin_coldkey: &T::AccountId, + _destination_coldkey: &T::AccountId, + origin_hotkey: &T::AccountId, + _destination_hotkey: &T::AccountId, + origin_netuid: u16, + destination_netuid: u16, + alpha_amount: u64, + ) -> Result<(), Error> { + // Ensure that both subnets exist. + ensure!( + Self::if_subnet_exist(origin_netuid), + Error::::SubnetNotExists + ); + if origin_netuid != destination_netuid { + ensure!( + Self::if_subnet_exist(destination_netuid), + Error::::SubnetNotExists + ); + } + + // Ensure that the origin hotkey account exists + ensure!( + Self::hotkey_account_exists(origin_hotkey), + Error::::HotKeyAccountNotExists + ); + + // Ensure origin coldkey owns the origin hotkey. + ensure!( + Self::coldkey_owns_hotkey(origin_coldkey, origin_hotkey), + Error::::NonAssociatedColdKey + ); + + // Ensure there is enough stake in the origin subnet. + let origin_alpha = Self::get_stake_for_hotkey_and_coldkey_on_subnet( + origin_hotkey, + origin_coldkey, + origin_netuid, + ); + ensure!( + alpha_amount <= origin_alpha, + Error::::NotEnoughStakeToWithdraw + ); + + // Ensure that the stake amount to be removed is above the minimum in tao equivalent. + let tao_equivalent = Self::sim_swap_alpha_for_tao(origin_netuid, alpha_amount); + ensure!( + tao_equivalent > DefaultMinStake::::get(), + Error::::AmountTooLow + ); + + Ok(()) + } } /////////////////////////////////////////// @@ -748,7 +880,7 @@ impl SharePoolDataOperations> for HotkeyAlphaSharePoolDataOperations { fn get_shared_value(&self) -> U64F64 { - U64F64::from_num(crate::TotalHotkeyAlpha::::get(&self.hotkey, self.netuid)) + U64F64::saturating_from_num(crate::TotalHotkeyAlpha::::get(&self.hotkey, self.netuid)) } fn get_share(&self, key: &AlphaShareKey) -> U64F64 { @@ -768,7 +900,7 @@ impl SharePoolDataOperations> crate::TotalHotkeyAlpha::::insert( &(self.hotkey), self.netuid, - value.to_num::(), + value.saturating_to_num::(), ); } else { crate::TotalHotkeyAlpha::::remove(&(self.hotkey), self.netuid); diff --git a/pallets/subtensor/src/subnets/registration.rs b/pallets/subtensor/src/subnets/registration.rs index 342c5c4087..bce9ea25c2 100644 --- a/pallets/subtensor/src/subnets/registration.rs +++ b/pallets/subtensor/src/subnets/registration.rs @@ -119,29 +119,29 @@ impl Pallet { Error::::NotEnoughBalanceToStake ); - // --- 8. Ensure the remove operation from the coldkey is a success. - let actual_burn_amount = - Self::remove_balance_from_coldkey_account(&coldkey, registration_cost)?; - - // Tokens are swapped and then burned. - let burned_alpha: u64 = Self::swap_tao_for_alpha(netuid, actual_burn_amount); - SubnetAlphaOut::::mutate(netuid, |total| *total = total.saturating_sub(burned_alpha)); - - // --- 9. If the network account does not exist we will create it here. + // If the network account does not exist we will create it here. Self::create_account_if_non_existent(&coldkey, &hotkey); - // --- 10. Ensure that the pairing is correct. + // --- 8. Ensure that the pairing is correct. ensure!( Self::coldkey_owns_hotkey(&coldkey, &hotkey), Error::::NonAssociatedColdKey ); - // Possibly there is no neuron slots at all. + // --- 9. Possibly there are no neuron slots at all. ensure!( Self::get_max_allowed_uids(netuid) != 0, Error::::NoNeuronIdAvailable ); + // --- 10. Ensure the remove operation from the coldkey is a success. + let actual_burn_amount = + Self::remove_balance_from_coldkey_account(&coldkey, registration_cost)?; + + // Tokens are swapped and then burned. + let burned_alpha: u64 = Self::swap_tao_for_alpha(netuid, actual_burn_amount); + SubnetAlphaOut::::mutate(netuid, |total| *total = total.saturating_sub(burned_alpha)); + // Actually perform the registration. let neuron_uid: u16 = Self::register_neuron(netuid, &hotkey); diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 7b7de6aeb8..942f6fe8ba 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -233,18 +233,18 @@ impl Pallet { Self::get_symbol_for_subnet(netuid_to_register), ); // Set subnet token symbol. - // Put 100 TAO from lock into subnet TAO and produce numerically equal amount of Alpha - let mut pool_initial_tao = 100_000_000_000; - if pool_initial_tao > actual_tao_lock_amount { - pool_initial_tao = actual_tao_lock_amount; - } - if pool_initial_tao < 1 { - pool_initial_tao = 1; - } + // Put initial TAO from lock into subnet TAO and produce numerically equal amount of Alpha + // The initial TAO is the locked amount, with a minimum of 1 RAO and a cap of 100 TAO. + let pool_initial_tao = 100_000_000_000.min(actual_tao_lock_amount.max(1)); + let actual_tao_lock_amount_less_pool_tao = actual_tao_lock_amount.saturating_sub(pool_initial_tao); SubnetTAO::::insert(netuid_to_register, pool_initial_tao); - SubnetAlphaIn::::insert(netuid_to_register, pool_initial_tao); + SubnetAlphaIn::::insert( + netuid_to_register, + pool_initial_tao.saturating_mul(Self::get_all_subnet_netuids().len() as u64), + ); // Set AlphaIn to the initial alpha distribution. + SubnetOwner::::insert(netuid_to_register, coldkey.clone()); SubnetOwnerHotkey::::insert(netuid_to_register, hotkey.clone()); TotalStakeAtDynamic::::insert(netuid_to_register, TotalStake::::get()); diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index afdbc0371e..6b1fa5c6bf 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -8,6 +8,23 @@ impl Pallet { SubnetworkN::::get(netuid) } + /// Sets value for the element at the given position if it exists. + pub fn set_element_at(vec: &mut [N], position: usize, value: N) { + if let Some(element) = vec.get_mut(position) { + *element = value; + } + } + + /// Resets the trust, emission, consensus, incentive, dividends of the neuron to default + pub fn clear_neuron(netuid: u16, neuron_uid: u16) { + let neuron_index: usize = neuron_uid.into(); + Emission::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Trust::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Consensus::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Incentive::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Dividends::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + } + /// Replace the neuron under this uid. pub fn replace_neuron( netuid: u16, @@ -40,6 +57,12 @@ impl Pallet { // 4. Clear neuron certificates NeuronCertificates::::remove(netuid, old_hotkey.clone()); + + // 5. Reset new neuron's values. + Self::clear_neuron(netuid, uid_to_replace); + + // 5a. reset axon info for the new uid. + Axons::::remove(netuid, old_hotkey); } /// Appends the uid to the network. diff --git a/pallets/subtensor/src/subnets/weights.rs b/pallets/subtensor/src/subnets/weights.rs index fca0e5999e..ad868c52dc 100644 --- a/pallets/subtensor/src/subnets/weights.rs +++ b/pallets/subtensor/src/subnets/weights.rs @@ -1,6 +1,7 @@ use super::*; use crate::epoch::math::*; use codec::Compact; +use safe_math::*; use sp_core::{ConstU32, H256}; use sp_runtime::{ traits::{BlakeTwo256, Hash}, @@ -1001,9 +1002,7 @@ impl Pallet { return weights; } weights.iter_mut().for_each(|x| { - *x = (*x as u64) - .saturating_mul(u16::MAX as u64) - .saturating_div(sum) as u16; + *x = (*x as u64).saturating_mul(u16::MAX as u64).safe_div(sum) as u16; }); weights } diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index eb1babe82d..f8bd979df8 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -364,6 +364,7 @@ impl Pallet { ChildKeys::::remove(old_hotkey, netuid); // Insert the same child entries for the new hotkey ChildKeys::::insert(new_hotkey, netuid, my_children.clone()); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); for (_, child_key_i) in my_children { // For each child, update their parent list let mut child_parents: Vec<(u64, T::AccountId)> = @@ -376,6 +377,7 @@ impl Pallet { } // Update the child's parent list ParentKeys::::insert(child_key_i, netuid, child_parents); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); } } @@ -388,6 +390,7 @@ impl Pallet { ParentKeys::::remove(old_hotkey, netuid); // Insert the same parent entries for the new hotkey ParentKeys::::insert(new_hotkey, netuid, parents.clone()); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); for (_, parent_key_i) in parents { // For each parent, update their children list let mut parent_children: Vec<(u64, T::AccountId)> = @@ -400,11 +403,39 @@ impl Pallet { } // Update the parent's children list ChildKeys::::insert(parent_key_i, netuid, parent_children); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); } } - // 14. Swap Stake Delta for all coldkeys. - // DEPRECATED + // 14. Swap PendingChildKeys. + // PendingChildKeys( netuid, parent ) --> Vec<(proportion,child), cool_down_block> + for netuid in Self::get_all_subnet_netuids() { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + if PendingChildKeys::::contains_key(netuid, old_hotkey) { + let (children, cool_down_block) = PendingChildKeys::::get(netuid, old_hotkey); + PendingChildKeys::::remove(netuid, old_hotkey); + PendingChildKeys::::insert(netuid, new_hotkey, (children, cool_down_block)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + + // Also check for others with our hotkey as a child + for (hotkey, (children, cool_down_block)) in PendingChildKeys::::iter_prefix(netuid) + { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + + if let Some(potential_idx) = + children.iter().position(|(_, child)| *child == *old_hotkey) + { + let mut new_children = children.clone(); + let entry_to_remove = new_children.remove(potential_idx); + new_children.push((entry_to_remove.0, new_hotkey.clone())); // Keep the proportion. + + PendingChildKeys::::remove(netuid, hotkey.clone()); + PendingChildKeys::::insert(netuid, hotkey, (new_children, cool_down_block)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + } + } // Return successful after swapping all the relevant terms. Ok(()) diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index eb299d88e0..7e30a22514 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -331,7 +331,7 @@ fn test_add_singular_child() { ), Err(Error::::SubNetworkDoesNotExist.into()) ); - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); step_rate_limit(&TransactionType::SetChildren, netuid); assert_eq!( SubtensorModule::do_schedule_children( @@ -376,7 +376,7 @@ fn test_get_stake_for_hotkey_on_subnet() { let child = U256::from(2); let coldkey1 = U256::from(3); let coldkey2 = U256::from(4); - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, parent, coldkey1, 0); register_ok_neuron(netuid, child, coldkey2, 0); // Set parent-child relationship with 100% stake allocation @@ -1664,7 +1664,7 @@ fn test_get_parents_chain() { // Set up parent-child relationships for i in 0..num_keys - 1 { - mock_set_children( + mock_schedule_children( &coldkey, &hotkeys[i], netuid, @@ -1677,6 +1677,8 @@ fn test_get_parents_chain() { proportion ); } + // Wait for children to be set + wait_and_set_pending_children(netuid); // Test get_parents for each hotkey for i in 1..num_keys { @@ -1715,7 +1717,9 @@ fn test_get_parents_chain() { // Test multiple parents let last_hotkey = hotkeys[num_keys - 1]; let new_parent = U256::from(num_keys as u64 + 2); - register_ok_neuron(netuid, new_parent, coldkey, 0); + // Set reg diff back down (adjusted from last block steps) + SubtensorModule::set_difficulty(netuid, 1); + register_ok_neuron(netuid, new_parent, coldkey, 99 * 2); log::info!( "Registered new parent neuron: new_parent={}, coldkey={}, netuid={}", new_parent, @@ -1772,7 +1776,7 @@ fn test_get_stake_for_hotkey_on_subnet_basic() { let hotkey = U256::from(1); let coldkey = U256::from(2); - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, hotkey, coldkey, 0); SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &coldkey, netuid, 1000, @@ -1798,7 +1802,7 @@ fn test_get_stake_for_hotkey_on_subnet_multiple_coldkeys() { let coldkey1 = U256::from(2); let coldkey2 = U256::from(3); - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, hotkey, coldkey1, 0); SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( @@ -1831,7 +1835,7 @@ fn test_get_stake_for_hotkey_on_subnet_single_parent_child() { let child = U256::from(2); let coldkey = U256::from(3); - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, parent, coldkey, 0); register_ok_neuron(netuid, child, coldkey, 0); @@ -1869,7 +1873,7 @@ fn test_get_stake_for_hotkey_on_subnet_multiple_parents_single_child() { let child = U256::from(3); let coldkey = U256::from(4); - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, parent1, coldkey, 0); register_ok_neuron(netuid, parent2, coldkey, 0); register_ok_neuron(netuid, child, coldkey, 0); @@ -1922,7 +1926,7 @@ fn test_get_stake_for_hotkey_on_subnet_single_parent_multiple_children() { let child2 = U256::from(3); let coldkey = U256::from(4); - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, parent, coldkey, 0); register_ok_neuron(netuid, child1, coldkey, 0); register_ok_neuron(netuid, child2, coldkey, 0); @@ -1985,7 +1989,7 @@ fn test_get_stake_for_hotkey_on_subnet_edge_cases() { let child2 = U256::from(3); let coldkey = U256::from(4); - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, parent, coldkey, 0); register_ok_neuron(netuid, child1, coldkey, 0); register_ok_neuron(netuid, child2, coldkey, 0); @@ -2057,7 +2061,7 @@ fn test_get_stake_for_hotkey_on_subnet_complex_hierarchy() { let coldkey_child2 = U256::from(7); let coldkey_grandchild = U256::from(8); - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); SubtensorModule::set_max_registrations_per_block(netuid, 1000); SubtensorModule::set_target_registrations_per_interval(netuid, 1000); register_ok_neuron(netuid, parent, coldkey_parent, 0); @@ -2247,8 +2251,8 @@ fn test_get_stake_for_hotkey_on_subnet_multiple_networks() { let hotkey = U256::from(1); let coldkey = U256::from(2); - add_network(netuid1, 0, 0); - add_network(netuid2, 0, 0); + add_network(netuid1, 1, 0); + add_network(netuid2, 1, 0); register_ok_neuron(netuid1, hotkey, coldkey, 0); register_ok_neuron(netuid2, hotkey, coldkey, 0); @@ -2439,6 +2443,73 @@ fn test_do_set_child_cooldown_period() { }); } +// Test that pending childkeys get set during the epoch after the cooldown period. +// +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::children::test_do_set_pending_children_runs_in_epoch --exact --show-output --nocapture +#[cfg(test)] +#[test] +fn test_do_set_pending_children_runs_in_epoch() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let parent = U256::from(2); + let child = U256::from(3); + let netuid: u16 = 1; + let proportion: u64 = 1000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, parent, coldkey, 0); + + // Set minimum stake for setting children + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &parent, + &coldkey, + netuid, + StakeThreshold::::get(), + ); + + // Schedule parent-child relationship + assert_ok!(SubtensorModule::do_schedule_children( + RuntimeOrigin::signed(coldkey), + parent, + netuid, + vec![(proportion, child)], + )); + + // Ensure the childkeys are not yet applied + let children_before = SubtensorModule::get_children(&parent, netuid); + close( + children_before.len() as u64, + 0, + 0, + "Children vector should be empty before cooldown", + ); + + wait_set_pending_children_cooldown(netuid); + + // Verify child assignment + let children_after = SubtensorModule::get_children(&parent, netuid); + close( + children_after.len() as u64, + 1, + 0, + "Children vector should have one entry after cooldown", + ); + close( + children_after[0].0, + proportion, + 0, + "Child proportion should match", + ); + close( + children_after[0].1.try_into().unwrap(), + child.try_into().unwrap(), + 0, + "Child key should match", + ); + }); +} + // Test that revoking childkeys does not require minimum stake // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::children::test_revoke_child_no_min_stake_check --exact --show-output --nocapture #[test] @@ -3033,14 +3104,17 @@ fn test_childkey_take_drain_validator_take() { // - Runs an epoch // - Checks the emission distribution among parents, child, and weight setter // - Verifies that all parties received emissions and the total stake increased correctly -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test coinbase test_childkey_multiple_parents_emission -- --nocapture +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::children::test_childkey_multiple_parents_emission --exact --nocapture #[test] fn test_childkey_multiple_parents_emission() { new_test_ext(1).execute_with(|| { let subnet_owner_coldkey = U256::from(1001); - let subnet_owner_hotkey = U256::from(1002); + let subnet_owner_hotkey: U256 = U256::from(1002); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); Tempo::::insert(netuid, 10); // run epoch every 10 blocks + // Set subnet owner cut to 0 + SubtensorModule::set_subnet_owner_cut(0); + SubtensorModule::set_tao_weight(0); // No TAO weight // Set registration parameters and emission tempo SubtensorModule::set_max_registrations_per_block(netuid, 1000); @@ -3064,9 +3138,9 @@ fn test_childkey_multiple_parents_emission() { (true, coldkey_weight_setter, weight_setter, 100_000_000), ]; - let initial_actual_stakes: Vec = initial_stakes + initial_stakes .iter() - .map(|(register, coldkey, hotkey, stake)| { + .for_each(|(register, coldkey, hotkey, stake)| { SubtensorModule::add_balance_to_coldkey_account(coldkey, *stake); if *register { // Register a neuron @@ -3081,20 +3155,28 @@ fn test_childkey_multiple_parents_emission() { 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); // Set parent-child relationships - mock_set_children(&coldkey_parent1, &parent1, netuid, &[(u64::MAX, child)]); - mock_set_children(&coldkey_parent2, &parent2, netuid, &[(u64::MAX / 2, child)]); + mock_schedule_children(&coldkey_parent1, &parent1, netuid, &[(u64::MAX, child)]); + mock_schedule_children(&coldkey_parent2, &parent2, netuid, &[(u64::MAX / 2, child)]); + wait_and_set_pending_children(netuid); ChildkeyTake::::insert(child, netuid, u16::MAX / 5); + // Set pending emission to 0 + PendingEmission::::insert(netuid, 0); + + let initial_actual_stakes: Vec = initial_stakes + .iter() + .map(|(_register, coldkey, hotkey, _stake)| { + // Return actual stake + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, netuid) + }) + .collect(); + // Set weights (subnet owner is uid 0, ignore him) let uids: Vec = vec![1, 2]; let values: Vec = vec![65354, 65354]; @@ -3107,7 +3189,7 @@ fn test_childkey_multiple_parents_emission() { values, version_key )); - + log::info!("Running an epoch"); // Wait until epoch let start_block = SubtensorModule::get_current_block_as_u64(); loop { @@ -3118,8 +3200,19 @@ fn test_childkey_multiple_parents_emission() { } step_block(1); } - let total_emission = SubtensorModule::get_block_emission().unwrap_or(0) - * (SubtensorModule::get_current_block_as_u64() - start_block + 1); + // We substract one because we are running it *after* the epoch, so we don't expect it to effect the emission. + let blocks_passed = SubtensorModule::get_current_block_as_u64() - start_block - 1; + log::info!("blocks_passed: {:?}", blocks_passed); + let alpha_block_emission: u64 = SubtensorModule::get_block_emission_for_issuance( + SubtensorModule::get_alpha_issuance(netuid), + ) + .unwrap_or(0); + let (_, _, per_block_emission) = SubtensorModule::get_dynamic_tao_emission( + netuid, + SubtensorModule::get_block_emission().unwrap_or(0), + alpha_block_emission, + ); + let total_emission = per_block_emission * blocks_passed; // Check emission distribution let stakes: Vec<(U256, U256, &str)> = vec![ @@ -3200,6 +3293,19 @@ fn test_childkey_multiple_parents_emission() { "Child should have received some emission" ); + let mut total_stake_on_subnet = 0; + let hks = [parent1, parent2, child, weight_setter]; + for (hk, net, alpha) in TotalHotkeyAlpha::::iter() { + if hks.contains(&hk) && net == netuid { + total_stake_on_subnet += alpha; + } else { + log::info!("hk: {:?}, net: {:?}, alpha: {:?}", hk, net, alpha); + } + } + + log::info!("total_stake_on_subnet: {:?}", total_stake_on_subnet); + log::info!("total_stake: {:?}", TotalStake::::get()); + log::info!("total_emission: {:?}", total_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; @@ -3207,7 +3313,7 @@ fn test_childkey_multiple_parents_emission() { assert_abs_diff_eq!( total_stake, initial_total_stake + total_emission, - epsilon = total_emission / 100 + epsilon = total_emission / 100, ); }); } @@ -3251,7 +3357,7 @@ fn test_parent_child_chain_emission() { let total_tao: I96F32 = I96F32::from_num(300_000 + 100_000 + 50_000); let total_alpha: I96F32 = I96F32::from_num(SubtensorModule::swap_tao_for_alpha( netuid, - total_tao.saturating_to_num::(), + total_tao.to_num::(), )); // Set the stakes directly @@ -3280,7 +3386,7 @@ fn test_parent_child_chain_emission() { let stake_b: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_b); let stake_c: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_c); - let total_stake: I96F32 = I96F32::from_num(stake_a + stake_b + stake_c); + let _total_stake: I96F32 = I96F32::from_num(stake_a + stake_b + stake_c); // Assert initial stake is correct let rel_stake_a = I96F32::from_num(stake_a) / total_tao; @@ -3296,10 +3402,18 @@ fn test_parent_child_chain_emission() { // Set parent-child relationships // A -> B (50% of A's stake) - mock_set_children(&coldkey_a, &hotkey_a, netuid, &[(u64::MAX / 2, hotkey_b)]); + mock_schedule_children(&coldkey_a, &hotkey_a, netuid, &[(u64::MAX / 2, hotkey_b)]); // B -> C (50% of B's stake) - mock_set_children(&coldkey_b, &hotkey_b, netuid, &[(u64::MAX / 2, hotkey_c)]); + mock_schedule_children(&coldkey_b, &hotkey_b, netuid, &[(u64::MAX / 2, hotkey_c)]); + wait_and_set_pending_children(netuid); // Don't want to run blocks before both children are scheduled + + // Get old stakes after children are scheduled + let stake_a_old: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_a); + let stake_b_old: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_b); + let stake_c_old: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_c); + + let total_stake_old: I96F32 = I96F32::from_num(stake_a_old + stake_b_old + stake_c_old); // Set CHK take rate to 1/9 let chk_take: I96F32 = I96F32::from_num(1_f64 / 9_f64); @@ -3311,38 +3425,11 @@ fn test_parent_child_chain_emission() { SubtensorModule::set_tao_weight(0); let hardcoded_emission: I96F32 = I96F32::from_num(1_000_000); // 1 million (adjust as needed) + let emission_as_alpha = + I96F32::from_num(hardcoded_emission) / SubtensorModule::get_alpha_price(netuid); - let hotkey_emission: Vec<(U256, u64, u64)> = - SubtensorModule::epoch(netuid, hardcoded_emission.saturating_to_num::()); - log::info!("hotkey_emission: {:?}", hotkey_emission); - let total_emission: I96F32 = hotkey_emission - .iter() - .map(|(_, _, emission)| I96F32::from_num(*emission)) - .sum(); - - // Verify emissions match expected from CHK arrangements - let em_eps: I96F32 = I96F32::from_num(1e-4); // 4 decimal places - // A's pending emission: - assert!( - ((I96F32::from_num(hotkey_emission[0].2) / total_emission) - - I96F32::from_num(2_f64 / 3_f64 * 1_f64 / 2_f64)).abs() // 2/3 * 1/2 = 1/3; 50% -> B - <= em_eps, - "A should have pending emission of 1/3 of total emission" - ); - // B's pending emission: - assert!( - ((I96F32::from_num(hotkey_emission[1].2) / total_emission) - - (I96F32::from_num(2_f64 / 9_f64 * 1_f64 / 2_f64 + 2_f64 / 3_f64 * 1_f64 / 2_f64))).abs() // 2/9 * 1/2 + 2/3 * 1/2; 50% -> C + 50% from A - <= em_eps, - "B should have pending emission of 4/9 of total emission" - ); - // C's pending emission: - assert!( - ((I96F32::from_num(hotkey_emission[2].2) / total_emission) - - (I96F32::from_num(1_f64 / 9_f64 + 1_f64 / 2_f64 * 2_f64 / 9_f64))).abs() // 1/9 + 2/9 * 1/2; 50% from B - <= em_eps, - "C should have pending emission of 1/9 of total emission" - ); + // Set pending emission to 0 + PendingEmission::::insert(netuid, 0); // Run epoch with a hardcoded emission value SubtensorModule::run_coinbase(hardcoded_emission); @@ -3356,10 +3443,10 @@ fn test_parent_child_chain_emission() { log::info!("Stake for hotkey B: {:?}", stake_b_new); log::info!("Stake for hotkey C: {:?}", stake_c_new); - let stake_inc_a: u64 = stake_a_new - stake_a; - let stake_inc_b: u64 = stake_b_new - stake_b; - let stake_inc_c: u64 = stake_c_new - stake_c; - let total_stake_inc: I96F32 = total_stake_new - total_stake; + let stake_inc_a: u64 = stake_a_new - stake_a_old; + let stake_inc_b: u64 = stake_b_new - stake_b_old; + let stake_inc_c: u64 = stake_c_new - stake_c_old; + let total_stake_inc: I96F32 = total_stake_new - total_stake_old; log::info!("Stake increase for hotkey A: {:?}", stake_inc_a); log::info!("Stake increase for hotkey B: {:?}", stake_inc_b); log::info!("Stake increase for hotkey C: {:?}", stake_inc_c); @@ -3404,16 +3491,390 @@ fn test_parent_child_chain_emission() { rel_stake_inc_c ); + let hotkeys = [hotkey_a, hotkey_b, hotkey_c]; + let mut total_stake_now = 0; + for (hotkey, netuid, stake) in TotalHotkeyAlpha::::iter() { + if hotkeys.contains(&hotkey) { + total_stake_now += stake; + } else { + log::info!( + "hotkey: {:?}, netuid: {:?}, stake: {:?}", + hotkey, + netuid, + stake + ); + } + } + log::info!( + "total_stake_now: {:?}, total_stake_new: {:?}", + total_stake_now, + total_stake_new + ); + let eps: I96F32 = I96F32::from_num(10_000); assert!( - (total_stake_new - (total_stake + hardcoded_emission)).abs() <= eps, + (total_stake_new - (total_stake_old + emission_as_alpha)).abs() <= eps, "Total stake should have increased by the hardcoded emission amount {:?}", - total_stake_new - (total_stake + hardcoded_emission) + total_stake_new - (total_stake_old + emission_as_alpha) + ); + }); +} + +// 45: Test *epoch* with a chain of parent-child relationships (e.g., A -> B -> C) +// This test verifies the correct distribution of emissions in a chain of parent-child relationships: +// - Sets up a network with three neurons A, B, and C in a chain (A -> B -> C) +// - Establishes parent-child relationships with different stake proportions +// - Sets weights for all neurons +// - Runs an epoch with a hardcoded emission value +// - Checks the emission distribution among A, B, and C +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::children::test_parent_child_chain_epoch --exact --show-output +#[test] +fn test_parent_child_chain_epoch() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + add_network(netuid, 1, 0); + // Set owner cut to 0 + SubtensorModule::set_subnet_owner_cut(0_u16); + + // Define hotkeys and coldkeys + let hotkey_a: U256 = U256::from(1); + let hotkey_b: U256 = U256::from(2); + let hotkey_c: U256 = U256::from(3); + let coldkey_a: U256 = U256::from(100); + let coldkey_b: U256 = U256::from(101); + let coldkey_c: U256 = U256::from(102); + + // Register neurons with decreasing stakes + register_ok_neuron(netuid, hotkey_a, coldkey_a, 0); + register_ok_neuron(netuid, hotkey_b, coldkey_b, 0); + register_ok_neuron(netuid, hotkey_c, coldkey_c, 0); + + // Add initial stakes + SubtensorModule::add_balance_to_coldkey_account(&coldkey_a, 1_000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_b, 1_000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_c, 1_000); + + // Swap to alpha + let total_tao: I96F32 = I96F32::from_num(300_000 + 100_000 + 50_000); + let total_alpha: I96F32 = I96F32::from_num(SubtensorModule::swap_tao_for_alpha( + netuid, + total_tao.saturating_to_num::(), + )); + + // Set the stakes directly + // This avoids needing to swap tao to alpha, impacting the initial stake distribution. + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_a, + &coldkey_a, + netuid, + (total_alpha * I96F32::from_num(300_000) / total_tao).saturating_to_num::(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_b, + &coldkey_b, + netuid, + (total_alpha * I96F32::from_num(100_000) / total_tao).saturating_to_num::(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_c, + &coldkey_c, + netuid, + (total_alpha * I96F32::from_num(50_000) / total_tao).saturating_to_num::(), + ); + + // Get old stakes + let stake_a: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_a); + let stake_b: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_b); + let stake_c: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_c); + + // Assert initial stake is correct + let rel_stake_a = I96F32::from_num(stake_a) / total_tao; + let rel_stake_b = I96F32::from_num(stake_b) / total_tao; + let rel_stake_c = I96F32::from_num(stake_c) / total_tao; + + log::info!("rel_stake_a: {:?}", rel_stake_a); // 0.6666 -> 2/3 + log::info!("rel_stake_b: {:?}", rel_stake_b); // 0.2222 -> 2/9 + log::info!("rel_stake_c: {:?}", rel_stake_c); // 0.1111 -> 1/9 + assert_eq!(rel_stake_a, I96F32::from_num(300_000) / total_tao); + assert_eq!(rel_stake_b, I96F32::from_num(100_000) / total_tao); + assert_eq!(rel_stake_c, I96F32::from_num(50_000) / total_tao); + + // Set parent-child relationships + // A -> B (50% of A's stake) + mock_set_children(&coldkey_a, &hotkey_a, netuid, &[(u64::MAX / 2, hotkey_b)]); + + // B -> C (50% of B's stake) + mock_set_children(&coldkey_b, &hotkey_b, netuid, &[(u64::MAX / 2, hotkey_c)]); + + // Set CHK take rate to 1/9 + let chk_take: I96F32 = I96F32::from_num(1_f64 / 9_f64); + let chk_take_u16: u16 = (chk_take * I96F32::from_num(u16::MAX)).saturating_to_num::(); + ChildkeyTake::::insert(hotkey_b, netuid, chk_take_u16); + ChildkeyTake::::insert(hotkey_c, netuid, chk_take_u16); + + // Set the weight of root TAO to be 0%, so only alpha is effective. + SubtensorModule::set_tao_weight(0); + + let hardcoded_emission: I96F32 = I96F32::from_num(1_000_000); // 1 million (adjust as needed) + + let hotkey_emission: Vec<(U256, u64, u64)> = + SubtensorModule::epoch(netuid, hardcoded_emission.saturating_to_num::()); + log::info!("hotkey_emission: {:?}", hotkey_emission); + let total_emission: I96F32 = hotkey_emission + .iter() + .map(|(_, _, emission)| I96F32::from_num(*emission)) + .sum(); + + // Verify emissions match expected from CHK arrangements + let em_eps: I96F32 = I96F32::from_num(1e-4); // 4 decimal places + // A's pending emission: + assert!( + ((I96F32::from_num(hotkey_emission[0].2) / total_emission) - + I96F32::from_num(2_f64 / 3_f64 * 1_f64 / 2_f64)).abs() // 2/3 * 1/2 = 1/3; 50% -> B + <= em_eps, + "A should have pending emission of 1/3 of total emission" + ); + // B's pending emission: + assert!( + ((I96F32::from_num(hotkey_emission[1].2) / total_emission) - + (I96F32::from_num(2_f64 / 9_f64 * 1_f64 / 2_f64 + 2_f64 / 3_f64 * 1_f64 / 2_f64))).abs() // 2/9 * 1/2 + 2/3 * 1/2; 50% -> C + 50% from A + <= em_eps, + "B should have pending emission of 4/9 of total emission" + ); + // C's pending emission: + assert!( + ((I96F32::from_num(hotkey_emission[2].2) / total_emission) - + (I96F32::from_num(1_f64 / 9_f64 + 1_f64 / 2_f64 * 2_f64 / 9_f64))).abs() // 1/9 + 2/9 * 1/2; 50% from B + <= em_eps, + "C should have pending emission of 1/9 of total emission" + ); + }); +} + +// 46: Test dividend distribution with children +// This test verifies the correct distribution of emissions in a chain of parent-child relationships: +// - Sets up a network with three neurons A, B, and C in a chain (A -> B -> C) +// - Establishes parent-child relationships with different stake proportions +// - Adds a childkey take for both B and C +// - Distributes emission across each hotkey using a the helper +// - Checks the emission distribution among A, B, and C +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::children::test_dividend_distribution_with_children --exact --show-output +#[test] +fn test_dividend_distribution_with_children() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + add_network(netuid, 1, 0); + // Set owner cut to 0 + SubtensorModule::set_subnet_owner_cut(0_u16); + + // Define hotkeys and coldkeys + let hotkey_a: U256 = U256::from(1); + let hotkey_b: U256 = U256::from(2); + let hotkey_c: U256 = U256::from(3); + let coldkey_a: U256 = U256::from(100); + let coldkey_b: U256 = U256::from(101); + let coldkey_c: U256 = U256::from(102); + + // Register neurons with decreasing stakes + register_ok_neuron(netuid, hotkey_a, coldkey_a, 0); + register_ok_neuron(netuid, hotkey_b, coldkey_b, 0); + register_ok_neuron(netuid, hotkey_c, coldkey_c, 0); + + // Add initial stakes + SubtensorModule::add_balance_to_coldkey_account(&coldkey_a, 1_000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_b, 1_000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_c, 1_000); + + // Swap to alpha + let total_tao: I96F32 = I96F32::from_num(300_000 + 100_000 + 50_000); + let total_alpha: I96F32 = I96F32::from_num(SubtensorModule::swap_tao_for_alpha( + netuid, + total_tao.saturating_to_num::(), + )); + + // Set the stakes directly + // This avoids needing to swap tao to alpha, impacting the initial stake distribution. + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_a, + &coldkey_a, + netuid, + (total_alpha * I96F32::from_num(300_000) / total_tao).saturating_to_num::(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_b, + &coldkey_b, + netuid, + (total_alpha * I96F32::from_num(100_000) / total_tao).saturating_to_num::(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_c, + &coldkey_c, + netuid, + (total_alpha * I96F32::from_num(50_000) / total_tao).saturating_to_num::(), + ); + + // Get old stakes + let stake_a: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_a); + let stake_b: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_b); + let stake_c: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_c); + + // Assert initial stake is correct + let rel_stake_a = I96F32::from_num(stake_a) / total_tao; + let rel_stake_b = I96F32::from_num(stake_b) / total_tao; + let rel_stake_c = I96F32::from_num(stake_c) / total_tao; + + log::info!("rel_stake_a: {:?}", rel_stake_a); // 0.6666 -> 2/3 + log::info!("rel_stake_b: {:?}", rel_stake_b); // 0.2222 -> 2/9 + log::info!("rel_stake_c: {:?}", rel_stake_c); // 0.1111 -> 1/9 + assert_eq!(rel_stake_a, I96F32::from_num(300_000) / total_tao); + assert_eq!(rel_stake_b, I96F32::from_num(100_000) / total_tao); + assert_eq!(rel_stake_c, I96F32::from_num(50_000) / total_tao); + + // Set parent-child relationships + // A -> B (50% of A's stake) + mock_set_children(&coldkey_a, &hotkey_a, netuid, &[(u64::MAX / 2, hotkey_b)]); + + // B -> C (50% of B's stake) + mock_set_children(&coldkey_b, &hotkey_b, netuid, &[(u64::MAX / 2, hotkey_c)]); + + // Set CHK take rate to 1/9 + let chk_take: I96F32 = I96F32::from_num(1_f64 / 9_f64); + let chk_take_u16: u16 = (chk_take * I96F32::from_num(u16::MAX)).saturating_to_num::(); + ChildkeyTake::::insert(hotkey_b, netuid, chk_take_u16); + ChildkeyTake::::insert(hotkey_c, netuid, chk_take_u16); + + // Set the weight of root TAO to be 0%, so only alpha is effective. + SubtensorModule::set_tao_weight(0); + + let hardcoded_emission: I96F32 = I96F32::from_num(1_000_000); // 1 million (adjust as needed) + + let hotkey_emission: Vec<(U256, u64, u64)> = + SubtensorModule::epoch(netuid, hardcoded_emission.saturating_to_num::()); + log::info!("hotkey_emission: {:?}", hotkey_emission); + let total_emission: I96F32 = hotkey_emission + .iter() + .map(|(_, _, emission)| I96F32::from_num(*emission)) + .sum(); + + // Verify emissions match expected from CHK arrangements + let em_eps: I96F32 = I96F32::from_num(1e-4); // 4 decimal places + // A's pending emission: + assert!( + ((I96F32::from_num(hotkey_emission[0].2) / total_emission) - + I96F32::from_num(2_f64 / 3_f64 * 1_f64 / 2_f64)).abs() // 2/3 * 1/2 = 1/3; 50% -> B + <= em_eps, + "A should have pending emission of 1/3 of total emission" + ); + // B's pending emission: + assert!( + ((I96F32::from_num(hotkey_emission[1].2) / total_emission) - + (I96F32::from_num(2_f64 / 9_f64 * 1_f64 / 2_f64 + 2_f64 / 3_f64 * 1_f64 / 2_f64))).abs() // 2/9 * 1/2 + 2/3 * 1/2; 50% -> C + 50% from A + <= em_eps, + "B should have pending emission of 4/9 of total emission" + ); + // C's pending emission: + assert!( + ((I96F32::from_num(hotkey_emission[2].2) / total_emission) - + (I96F32::from_num(1_f64 / 9_f64 + 1_f64 / 2_f64 * 2_f64 / 9_f64))).abs() // 1/9 + 2/9 * 1/2; 50% from B + <= em_eps, + "C should have pending emission of 1/9 of total emission" + ); + + let dividends_a = SubtensorModule::get_dividends_distribution( + &hotkey_a, + netuid, + hardcoded_emission.saturating_to_num::(), + ); + let dividends_b = SubtensorModule::get_dividends_distribution( + &hotkey_b, + netuid, + hardcoded_emission.saturating_to_num::(), + ); + let dividends_c = SubtensorModule::get_dividends_distribution( + &hotkey_c, + netuid, + hardcoded_emission.saturating_to_num::(), + ); + log::info!("dividends_a: {:?}", dividends_a); + log::info!("dividends_b: {:?}", dividends_b); + log::info!("dividends_c: {:?}", dividends_c); + + // We expect A to get all of its own emission, as it has no parents. + assert_eq!(dividends_a.len(), 1); + assert_eq!(dividends_a[0].0, hotkey_a); + assert_eq!( + dividends_a[0].1, + hardcoded_emission.saturating_to_num::() + ); + assert_abs_diff_eq!( + dividends_a + .iter() + .map(|(_, emission)| *emission) + .sum::(), + hardcoded_emission.saturating_to_num::(), + epsilon = (hardcoded_emission / 1000).saturating_to_num::() + ); + + // We expect B to get a portion of its own emission, and some comission from A, where A gets the rest. + // B re-delegates 0.5 of its stake to C; And A re-delegates 0.5 of its stake to B. + let total_stake_b = rel_stake_b * 1 / 2 + rel_stake_a * 1 / 2; + let expected_b_b: u64 = ((rel_stake_b * 1 / 2) / total_stake_b * hardcoded_emission + + (rel_stake_a * 1 / 2) / total_stake_b * hardcoded_emission * chk_take) + .saturating_to_num::(); + assert_eq!(dividends_b.len(), 2); // A and B + assert_eq!(dividends_b[1].0, hotkey_b); + assert_abs_diff_eq!( + dividends_b[1].1, + expected_b_b, + epsilon = (hardcoded_emission / 1000).saturating_to_num::() + ); + let expected_b_a: u64 = hardcoded_emission.saturating_to_num::() - expected_b_b; + assert_eq!(dividends_b[0].0, hotkey_a); + assert_abs_diff_eq!( + dividends_b[0].1, + expected_b_a, + epsilon = (hardcoded_emission / 1000).saturating_to_num::() + ); + assert_abs_diff_eq!( + dividends_b + .iter() + .map(|(_, emission)| *emission) + .sum::(), + hardcoded_emission.saturating_to_num::(), + epsilon = (hardcoded_emission / 1000).saturating_to_num::() + ); + + // We expect C to get a portion of its own emission, and some comission from B, where B gets the rest. + let total_stake_c = rel_stake_c + rel_stake_b * 1 / 2; + let expected_c_c: u64 = (rel_stake_c / total_stake_c * hardcoded_emission + + (rel_stake_b * 1 / 2) / total_stake_c * hardcoded_emission * chk_take) + .saturating_to_num::(); + assert_eq!(dividends_c.len(), 2); // B and C + assert_eq!(dividends_c[1].0, hotkey_c); + assert_abs_diff_eq!( + dividends_c[1].1, + expected_c_c, + epsilon = (hardcoded_emission / 1000).saturating_to_num::() + ); + let expected_c_b: u64 = hardcoded_emission.saturating_to_num::() - expected_c_c; + assert_eq!(dividends_c[0].0, hotkey_b); + assert_abs_diff_eq!( + dividends_c[0].1, + expected_c_b, + epsilon = (hardcoded_emission / 1000).saturating_to_num::() + ); + assert_abs_diff_eq!( + dividends_c + .iter() + .map(|(_, emission)| *emission) + .sum::(), + hardcoded_emission.saturating_to_num::(), + epsilon = (hardcoded_emission / 1000).saturating_to_num::() ); }); } -// 46: Test emission distribution when adding/removing parent-child relationships mid-epoch +// 47: Test emission distribution when adding/removing parent-child relationships mid-epoch // This test verifies the correct distribution of emissions when parent-child relationships change: // - Sets up a network with three neurons: parent, child1, and child2 // - Establishes initial parent-child relationship between parent and child1 @@ -3585,7 +4046,7 @@ fn test_dynamic_parent_child_relationships() { let expected_parent_stake = ((I96F32::from_num(stake_parent_0) + total_emission * rel_stake_parent_0) * I96F32::from_num(5)) - .saturating_div(I96F32::from_num(12)); + / I96F32::from_num(12); assert!( (I96F32::from_num(stake_parent_2) - expected_parent_stake).abs() / expected_parent_stake diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 257ea075ff..ff4908172e 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -2,6 +2,7 @@ use super::mock::*; use crate::*; +use approx::assert_abs_diff_eq; use frame_support::assert_ok; use sp_core::U256; use substrate_fixed::types::I64F64; @@ -38,7 +39,7 @@ fn test_dynamic_function_various_values() { assert!(alpha_in_emission <= alpha_emission, "alpha_in_emission is greater than alpha_emission"); assert!(alpha_out_emission <= 2 * alpha_emission, "alpha_out_emission is greater than 2 * alpha_emission"); assert!((alpha_in_emission + alpha_out_emission) <= 2 * alpha_emission, "Sum of alpha_in_emission and alpha_out_emission is less than or equal to. 2 * alpha_emission"); - close( alpha_in_emission + alpha_out_emission, 2 * alpha_emission, 10 ); + close( alpha_in_emission + alpha_out_emission, alpha_in_emission + alpha_emission, 10 ); if alpha_in_emission > 0 || tao_in_emission > 0 { assert!((tao_in_emission as f64 / alpha_in_emission as f64 - price).abs() < 1e-1, "Ratio of tao_in_emission to alpha_in_emission is not equal to price"); } @@ -69,6 +70,322 @@ fn test_dynamic_function_price_equal_emission() { let expected_alpha_in: u64 = (alpha_block_emission * tao_subnet_emission) / tao_block_emission; close(alpha_in, expected_alpha_in, 10); - close(alpha_out, 2 * alpha_block_emission - expected_alpha_in, 10); + close(alpha_out, alpha_block_emission, 10); + }); +} + +// Verifies that the total stake after the coinbase is only increased by the coinbase emission. +// Avoids TAO weight. +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_total_stake_after_coinbase_no_tao_weight --exact --show-output --nocapture +#[test] +fn test_total_stake_after_coinbase_no_tao_weight() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + add_network(netuid, 1, 0); + // Set TAO weight to 0 + SubtensorModule::set_tao_weight(0); + // Set owner cut to ~11.11% + SubtensorModule::set_subnet_owner_cut(u16::MAX / 9); + let total_coinbase_emission: I96F32 = I96F32::from_num(1_123_456_789); + let epsilon: u64 = 100; + + // Define hotkeys and coldkeys + let hotkey_a: U256 = U256::from(1); + let hotkey_b: U256 = U256::from(2); + let hotkey_c: U256 = U256::from(3); + let coldkey_a: U256 = U256::from(100); + let coldkey_b: U256 = U256::from(101); + let coldkey_c: U256 = U256::from(102); + + // Register neurons with decreasing stakes + register_ok_neuron(netuid, hotkey_a, coldkey_a, 0); + register_ok_neuron(netuid, hotkey_b, coldkey_b, 0); + register_ok_neuron(netuid, hotkey_c, coldkey_c, 0); + + // Add initial stakes + SubtensorModule::add_balance_to_coldkey_account(&coldkey_a, 1_000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_b, 1_000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_c, 1_000); + + // Swap to alpha + let total_tao: I96F32 = I96F32::from_num(300_000 + 100_000 + 50_000); + let total_alpha: I96F32 = I96F32::from_num(SubtensorModule::swap_tao_for_alpha( + netuid, + total_tao.saturating_to_num::(), + )); + + // Set the stakes directly + // This avoids needing to swap tao to alpha, impacting the initial stake distribution. + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_a, + &coldkey_a, + netuid, + (total_alpha * I96F32::from_num(300_000) / total_tao).saturating_to_num::(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_b, + &coldkey_b, + netuid, + (total_alpha * I96F32::from_num(100_000) / total_tao).saturating_to_num::(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_c, + &coldkey_c, + netuid, + (total_alpha * I96F32::from_num(50_000) / total_tao).saturating_to_num::(), + ); + + // Get the total stake on the network + let mut total_stake_before = 0; + for (hotkey, netuid_i, alpha) in TotalHotkeyAlpha::::iter() { + if netuid_i == netuid { + total_stake_before += alpha; + } else { + assert!( + alpha == 0, + "Alpha should be 0 for non-subnet hotkeys, but is {:?} on netuid {:?}", + alpha, + netuid_i + ); + } + } + + log::info!("total_stake_before: {:?}", total_stake_before); + + // Run the coinbase + SubtensorModule::run_coinbase(total_coinbase_emission); + + // Get the total stake on the network + let mut total_stake_after = 0; + for (hotkey, netuid_i, alpha) in TotalHotkeyAlpha::::iter() { + if netuid_i == netuid { + total_stake_after += alpha; + } else { + assert!( + alpha == 0, + "Alpha should be 0 for non-subnet hotkeys, but is {:?} on netuid {:?}", + alpha, + netuid_i + ); + } + } + assert_abs_diff_eq!( + total_stake_after, + total_stake_before + total_coinbase_emission.saturating_to_num::(), + epsilon = epsilon + ); + }); +} + +// Verifies that the total stake after the coinbase is only increased by the coinbase emission. +// Includes TAO weight. +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_total_stake_after_coinbase --exact --show-output --nocapture +#[test] +fn test_total_stake_after_coinbase() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + add_network(netuid, 1, 0); + // Set TAO weight to 18% + SubtensorModule::set_tao_weight(I96F32::from_num(0.18).saturating_to_num::()); + // Set owner cut to ~11.11% + SubtensorModule::set_subnet_owner_cut(u16::MAX / 9); + let total_coinbase_emission: I96F32 = I96F32::from_num(1_123_456_789); + let epsilon: u64 = 100; + + // Define hotkeys and coldkeys + let hotkey_a: U256 = U256::from(1); + let hotkey_b: U256 = U256::from(2); + let hotkey_c: U256 = U256::from(3); + let coldkey_a: U256 = U256::from(100); + let coldkey_b: U256 = U256::from(101); + let coldkey_c: U256 = U256::from(102); + + // Register neurons with decreasing stakes + register_ok_neuron(netuid, hotkey_a, coldkey_a, 0); + register_ok_neuron(netuid, hotkey_b, coldkey_b, 0); + register_ok_neuron(netuid, hotkey_c, coldkey_c, 0); + + // Add initial stakes + SubtensorModule::add_balance_to_coldkey_account(&coldkey_a, 1_000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_b, 1_000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_c, 1_000); + + // Swap to alpha + let total_tao: I96F32 = I96F32::from_num(300_000 + 100_000 + 50_000); + let total_alpha: I96F32 = I96F32::from_num(SubtensorModule::swap_tao_for_alpha( + netuid, + total_tao.saturating_to_num::(), + )); + + // Set the stakes directly + // This avoids needing to swap tao to alpha, impacting the initial stake distribution. + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_a, + &coldkey_a, + netuid, + (total_alpha * I96F32::from_num(300_000) / total_tao).saturating_to_num::(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_b, + &coldkey_b, + netuid, + (total_alpha * I96F32::from_num(100_000) / total_tao).saturating_to_num::(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_c, + &coldkey_c, + netuid, + (total_alpha * I96F32::from_num(50_000) / total_tao).saturating_to_num::(), + ); + + // Stake some to root + let stake_to_root: u64 = 10_000_000; + SubtensorModule::add_balance_to_coldkey_account(&coldkey_a, stake_to_root); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_a, + &coldkey_a, + netuid, + stake_to_root, + ); + + let alpha_price = SubtensorModule::get_alpha_price(netuid); + log::info!("alpha_price: {:?}", alpha_price); + + // Get the total stake on the network + let mut total_stake_before = 0; + for (hotkey, netuid_i, alpha) in TotalHotkeyAlpha::::iter() { + if netuid_i == netuid { + total_stake_before += alpha; + } else if netuid == SubtensorModule::get_root_netuid() { + let as_alpha: I96F32 = I96F32::from_num(alpha) / alpha_price; + total_stake_before += as_alpha.saturating_to_num::(); + } else { + assert!( + alpha == 0, + "Alpha should be 0 for non-subnet hotkeys, but is {:?} on netuid {:?}", + alpha, + netuid_i + ); + } + } + + log::info!("total_stake_before: {:?}", total_stake_before); + + // Run the coinbase + SubtensorModule::run_coinbase(total_coinbase_emission); + + // Get the total stake on the network + let mut total_stake_after = 0; + for (hotkey, netuid_i, alpha) in TotalHotkeyAlpha::::iter() { + if netuid_i == netuid { + total_stake_after += alpha; + } else if netuid == SubtensorModule::get_root_netuid() { + let as_alpha: I96F32 = I96F32::from_num(alpha) / alpha_price; + total_stake_after += as_alpha.saturating_to_num::(); + } else { + assert!( + alpha == 0, + "Alpha should be 0 for non-subnet hotkeys, but is {:?} on netuid {:?}", + alpha, + netuid_i + ); + } + } + assert_abs_diff_eq!( + total_stake_after, + total_stake_before + total_coinbase_emission.saturating_to_num::(), + epsilon = epsilon + ); + }); +} + +// Verifies that the total issuance after the coinbase is only increased by the coinbase emission. +// Includes TAO weight. +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_total_issuance_after_coinbase --exact --show-output --nocapture +#[test] +fn test_total_issuance_after_coinbase() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + add_network(netuid, 1, 0); + // Set TAO weight to 18% + SubtensorModule::set_tao_weight(I96F32::from_num(0.18).saturating_to_num::()); + // Set owner cut to ~11.11% + SubtensorModule::set_subnet_owner_cut(u16::MAX / 9); + let total_coinbase_emission: I96F32 = I96F32::from_num(1_123_456_789); + let epsilon: u64 = 100; + + // Define hotkeys and coldkeys + let hotkey_a: U256 = U256::from(1); + let hotkey_b: U256 = U256::from(2); + let hotkey_c: U256 = U256::from(3); + let coldkey_a: U256 = U256::from(100); + let coldkey_b: U256 = U256::from(101); + let coldkey_c: U256 = U256::from(102); + + // Register neurons with decreasing stakes + register_ok_neuron(netuid, hotkey_a, coldkey_a, 0); + register_ok_neuron(netuid, hotkey_b, coldkey_b, 0); + register_ok_neuron(netuid, hotkey_c, coldkey_c, 0); + + // Add initial stakes + SubtensorModule::add_balance_to_coldkey_account(&coldkey_a, 1_000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_b, 1_000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_c, 1_000); + + // Swap to alpha + let total_tao: I96F32 = I96F32::from_num(300_000 + 100_000 + 50_000); + let total_alpha: I96F32 = I96F32::from_num(SubtensorModule::swap_tao_for_alpha( + netuid, + total_tao.saturating_to_num::(), + )); + + // Set the stakes directly + // This avoids needing to swap tao to alpha, impacting the initial stake distribution. + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_a, + &coldkey_a, + netuid, + (total_alpha * I96F32::from_num(300_000) / total_tao).saturating_to_num::(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_b, + &coldkey_b, + netuid, + (total_alpha * I96F32::from_num(100_000) / total_tao).saturating_to_num::(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_c, + &coldkey_c, + netuid, + (total_alpha * I96F32::from_num(50_000) / total_tao).saturating_to_num::(), + ); + + // Stake some to root + let stake_to_root: u64 = 10_000_000; + SubtensorModule::add_balance_to_coldkey_account(&coldkey_a, stake_to_root); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_a, + &coldkey_a, + netuid, + stake_to_root, + ); + + let alpha_price = SubtensorModule::get_alpha_price(netuid); + log::info!("alpha_price: {:?}", alpha_price); + + // Get the total issuance + let mut total_issuance_before = TotalIssuance::::get(); + log::info!("total_issuance_before: {:?}", total_issuance_before); + + // Run the coinbase + SubtensorModule::run_coinbase(total_coinbase_emission); + + // Compare + let total_issuance_after = TotalIssuance::::get(); + assert_abs_diff_eq!( + total_issuance_after, + total_issuance_before + total_coinbase_emission.saturating_to_num::(), + epsilon = epsilon + ); }); } diff --git a/pallets/subtensor/src/tests/delegate_info.rs b/pallets/subtensor/src/tests/delegate_info.rs index 78ea484829..0dcb69d78d 100644 --- a/pallets/subtensor/src/tests/delegate_info.rs +++ b/pallets/subtensor/src/tests/delegate_info.rs @@ -6,7 +6,7 @@ use super::mock::*; #[test] fn test_return_per_1000_tao() { let take = // 18% take to the Validator - Compact::::from((U64F64::from_num(0.18 * u16::MAX as f64)).saturating_to_num::()); + Compact::::from((U64F64::from_num(0.18 * u16::MAX as f64)).to_num::()); // 10_000 TAO total validator stake let total_stake = U64F64::from_num(10_000.0 * 1e9); diff --git a/pallets/subtensor/src/tests/epoch.rs b/pallets/subtensor/src/tests/epoch.rs index 7973a44e1d..a353ee5b92 100644 --- a/pallets/subtensor/src/tests/epoch.rs +++ b/pallets/subtensor/src/tests/epoch.rs @@ -493,7 +493,7 @@ fn init_run_epochs( // new_test_ext(1).execute_with(|| { // log::info!("test_overflow:"); // let netuid: u16 = 1; -// add_network(netuid, 0, 0); +// add_network(netuid, 1, 0); // SubtensorModule::set_max_allowed_uids(netuid, 3); // SubtensorModule::increase_stake_on_coldkey_hotkey_account( // &U256::from(0), @@ -1522,7 +1522,7 @@ fn test_set_alpha_disabled() { signer.clone(), hotkey, netuid, - DefaultMinStake::::get() + DefaultMinStake::::get() + DefaultStakingFee::::get() )); // Only owner can set alpha values assert_ok!(SubtensorModule::register_network(signer.clone(), hotkey)); @@ -2285,22 +2285,19 @@ fn test_compute_alpha_values() { // exp_val = exp(0.0 - 1.0 * 0.1) = exp(-0.1) // alpha[0] = 1 / (1 + exp(-0.1)) ~ 0.9048374180359595 let exp_val_0 = I32F32::from_num(0.9048374180359595); - let expected_alpha_0 = - I32F32::from_num(1.0).saturating_div(I32F32::from_num(1.0).saturating_add(exp_val_0)); + let expected_alpha_0 = I32F32::from_num(1.0) / I32F32::from_num(1.0).saturating_add(exp_val_0); // For consensus[1] = 0.5: // exp_val = exp(0.0 - 1.0 * 0.5) = exp(-0.5) // alpha[1] = 1 / (1 + exp(-0.5)) ~ 0.6065306597126334 let exp_val_1 = I32F32::from_num(0.6065306597126334); - let expected_alpha_1 = - I32F32::from_num(1.0).saturating_div(I32F32::from_num(1.0).saturating_add(exp_val_1)); + let expected_alpha_1 = I32F32::from_num(1.0) / I32F32::from_num(1.0).saturating_add(exp_val_1); // For consensus[2] = 0.9: // exp_val = exp(0.0 - 1.0 * 0.9) = exp(-0.9) // alpha[2] = 1 / (1 + exp(-0.9)) ~ 0.4065696597405991 let exp_val_2 = I32F32::from_num(0.4065696597405991); - let expected_alpha_2 = - I32F32::from_num(1.0).saturating_div(I32F32::from_num(1.0).saturating_add(exp_val_2)); + let expected_alpha_2 = I32F32::from_num(1.0) / I32F32::from_num(1.0).saturating_add(exp_val_2); // Define an epsilon for approximate equality checks. let epsilon = I32F32::from_num(1e-6); @@ -2338,8 +2335,7 @@ fn test_compute_alpha_values_256_miners() { let exp_val = safe_exp(exponent); // Use saturating addition and division - let expected_alpha = - I32F32::from_num(1.0).saturating_div(I32F32::from_num(1.0).saturating_add(exp_val)); + let expected_alpha = I32F32::from_num(1.0) / I32F32::from_num(1.0).saturating_add(exp_val); // Assert that the computed alpha values match the expected values within the epsilon. assert_approx_eq(alpha[i], expected_alpha, epsilon); @@ -2618,7 +2614,7 @@ fn test_get_set_alpha() { signer.clone(), hotkey, netuid, - DefaultMinStake::::get() + DefaultMinStake::::get() + DefaultStakingFee::::get() )); assert_ok!(SubtensorModule::do_set_alpha_values( diff --git a/pallets/subtensor/src/tests/math.rs b/pallets/subtensor/src/tests/math.rs index 84fcc4aac1..59d826ab33 100644 --- a/pallets/subtensor/src/tests/math.rs +++ b/pallets/subtensor/src/tests/math.rs @@ -2264,16 +2264,17 @@ fn test_math_fixed_to_u64() { } #[test] -#[should_panic(expected = "-1 overflows")] -fn test_math_fixed_to_u64_panics() { +fn test_math_fixed_to_u64_saturates() { let bad_input = I32F32::from_num(-1); - fixed_to_u64(bad_input); + let expected = 0; + assert_eq!(fixed_to_u64(bad_input), expected); } #[test] fn test_math_fixed64_to_u64() { let expected = u64::MIN; - assert_eq!(fixed64_to_u64(I64F64::from_num(expected)), expected); + let input = I64F64::from_num(expected); + assert_eq!(fixed64_to_u64(input), expected); let input = i64::MAX / 2; let expected = u64::try_from(input).unwrap(); @@ -2285,10 +2286,10 @@ fn test_math_fixed64_to_u64() { } #[test] -#[should_panic(expected = "-1 overflows")] -fn test_math_fixed64_to_u64_panics() { +fn test_math_fixed64_to_u64_saturates() { let bad_input = I64F64::from_num(-1); - fixed64_to_u64(bad_input); + let expected = 0; + assert_eq!(fixed64_to_u64(bad_input), expected); } /* @TODO: find the _true_ max, and half, input values */ @@ -2304,10 +2305,9 @@ fn test_math_fixed64_to_fixed32() { } #[test] -#[should_panic(expected = "overflow")] -fn test_math_fixed64_to_fixed32_panics() { +fn test_math_fixed64_to_fixed32_saturates() { let bad_input = I64F64::from_num(u32::MAX); - fixed64_to_fixed32(bad_input); + assert_eq!(fixed64_to_fixed32(bad_input), I32F32::max_value()); } #[test] @@ -2340,14 +2340,15 @@ fn test_fixed_proportion_to_u16() { } #[test] -#[should_panic(expected = "overflow")] -fn test_fixed_proportion_to_u16_panics() { +fn test_fixed_proportion_to_u16_saturates() { let expected = u16::MAX; let input = I32F32::from_num(expected); log::trace!("Testing with input: {:?}", input); // Debug output let result = fixed_proportion_to_u16(input); log::trace!("Testing with result: {:?}", result); // Debug output + assert_eq!(result, expected); } + #[test] fn test_vec_fixed64_to_fixed32() { let input = vec![I64F64::from_num(i32::MIN)]; @@ -2360,10 +2361,9 @@ fn test_vec_fixed64_to_fixed32() { } #[test] -#[should_panic(expected = "overflow")] -fn test_vec_fixed64_to_fixed32_panics() { +fn test_vec_fixed64_to_fixed32_saturates() { let bad_input = vec![I64F64::from_num(i64::MAX)]; - vec_fixed64_to_fixed32(bad_input); + assert_eq!(vec_fixed64_to_fixed32(bad_input), [I32F32::max_value()]); } #[test] diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index 56cbe6cdca..7939083362 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -598,9 +598,9 @@ fn test_migrate_rao() { // Verify root subnet (netuid 0) state after migration assert_eq!(SubnetTAO::::get(netuid_0), 4 * stake_amount); // Root has everything - assert_eq!(SubnetTAO::::get(netuid_1), 100_000_000_000); // Initial Rao amount. + assert_eq!(SubnetTAO::::get(netuid_1), lock_amount); // Initial Rao amount. assert_eq!(SubnetAlphaIn::::get(netuid_0), 1); // No Alpha in pool on root. - assert_eq!(SubnetAlphaIn::::get(netuid_1), 100_000_000_000); // Initial Rao amount. + assert_eq!(SubnetAlphaIn::::get(netuid_1), 2 * lock_amount); // Initial Rao amount == num_subnets * lock_amount assert_eq!(SubnetAlphaOut::::get(netuid_0), 4 * stake_amount); // All stake is outstanding. assert_eq!(SubnetAlphaOut::::get(netuid_1), 0); // No stake outstanding. diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 9d5f939f6e..b0d7c461ad 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -601,6 +601,7 @@ pub(crate) fn step_epochs(count: u16, netuid: u16) { SubtensorModule::get_tempo(netuid), SubtensorModule::get_current_block_as_u64(), ); + log::info!("Blocks to next epoch: {:?}", blocks_to_next_epoch); step_block(blocks_to_next_epoch as u16); assert!(SubtensorModule::should_run_epoch( @@ -686,16 +687,28 @@ pub fn setup_neuron_with_stake(netuid: u16, hotkey: U256, coldkey: U256, stake: increase_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, stake, netuid); } +#[allow(dead_code)] +pub fn wait_set_pending_children_cooldown(netuid: u16) { + let cooldown = DefaultPendingCooldown::::get(); + step_block(cooldown as u16); // Wait for cooldown to pass + step_epochs(1, netuid); // Run next epoch +} + #[allow(dead_code)] pub fn wait_and_set_pending_children(netuid: u16) { let original_block = System::block_number(); - System::set_block_number(System::block_number() + 7300); + wait_set_pending_children_cooldown(netuid); SubtensorModule::do_set_pending_children(netuid); System::set_block_number(original_block); } #[allow(dead_code)] -pub fn mock_set_children(coldkey: &U256, parent: &U256, netuid: u16, child_vec: &[(u64, U256)]) { +pub fn mock_schedule_children( + coldkey: &U256, + parent: &U256, + netuid: u16, + child_vec: &[(u64, U256)], +) { // Set minimum stake for setting children StakeThreshold::::put(0); @@ -706,6 +719,11 @@ pub fn mock_set_children(coldkey: &U256, parent: &U256, netuid: u16, child_vec: netuid, child_vec.to_vec() )); +} + +#[allow(dead_code)] +pub fn mock_set_children(coldkey: &U256, parent: &U256, netuid: u16, child_vec: &[(u64, U256)]) { + mock_schedule_children(coldkey, parent, netuid, child_vec); wait_and_set_pending_children(netuid); } diff --git a/pallets/subtensor/src/tests/move_stake.rs b/pallets/subtensor/src/tests/move_stake.rs index 96d54ed77e..f299832ed2 100644 --- a/pallets/subtensor/src/tests/move_stake.rs +++ b/pallets/subtensor/src/tests/move_stake.rs @@ -3,6 +3,7 @@ use crate::*; use approx::assert_abs_diff_eq; use frame_support::{assert_err, assert_noop, assert_ok}; use sp_core::{Get, U256}; +use substrate_fixed::types::I96F32; // 1. test_do_move_success // Description: Test a successful move of stake between two hotkeys in the same subnet @@ -17,7 +18,7 @@ fn test_do_move_success() { let origin_hotkey = U256::from(2); let destination_hotkey = U256::from(3); let stake_amount = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // Set up initial stake SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); @@ -74,7 +75,7 @@ fn test_do_move_different_subnets() { let origin_hotkey = U256::from(2); let destination_hotkey = U256::from(3); let stake_amount = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // Set up initial stake and subnets SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); @@ -111,14 +112,19 @@ fn test_do_move_different_subnets() { ), 0 ); + let alpha_fee: I96F32 = + I96F32::from_num(fee) / SubtensorModule::get_alpha_price(destination_netuid); + let expected_value = I96F32::from_num(alpha) + * SubtensorModule::get_alpha_price(origin_netuid) + / SubtensorModule::get_alpha_price(destination_netuid); assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &destination_hotkey, &coldkey, destination_netuid ), - stake_amount - 2 * fee, - epsilon = stake_amount / 1000 + (expected_value - alpha_fee).to_num::(), + epsilon = (expected_value / 1000).to_num::() ); }); } @@ -242,7 +248,7 @@ fn test_do_move_nonexistent_destination_hotkey() { SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount, fee); // Attempt to move stake from a non-existent origin hotkey - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); assert_noop!( SubtensorModule::do_move_stake( RuntimeOrigin::signed(coldkey), @@ -288,7 +294,7 @@ fn test_do_move_all_stake() { let origin_hotkey = U256::from(2); let destination_hotkey = U256::from(3); let stake_amount = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // Set up initial stake SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount, fee); @@ -341,7 +347,7 @@ fn test_do_move_half_stake() { let origin_hotkey = U256::from(2); let destination_hotkey = U256::from(3); let stake_amount = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // Set up initial stake SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount, fee); @@ -398,7 +404,7 @@ fn test_do_move_partial_stake() { let origin_hotkey = U256::from(2); let destination_hotkey = U256::from(3); let total_stake = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // Set up initial stake SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, total_stake, fee); @@ -454,7 +460,7 @@ fn test_do_move_multiple_times() { let hotkey1 = U256::from(2); let hotkey2 = U256::from(3); let initial_stake = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // Set up initial stake SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey1); @@ -511,7 +517,7 @@ fn test_do_move_wrong_origin() { let origin_hotkey = U256::from(2); let destination_hotkey = U256::from(3); let netuid = 1; - let stake_amount = 1000; + let stake_amount = DefaultMinStake::::get() * 10; let fee = 0; // Set up initial stake @@ -523,7 +529,7 @@ fn test_do_move_wrong_origin() { ); // Attempt to move stake with wrong origin - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); SubtensorModule::create_account_if_non_existent(&coldkey, &destination_hotkey); assert_err!( @@ -535,7 +541,7 @@ fn test_do_move_wrong_origin() { netuid, alpha, ), - Error::::NotEnoughStakeToWithdraw + Error::::NonAssociatedColdKey ); // Check that no stake was moved @@ -570,7 +576,7 @@ fn test_do_move_same_hotkey() { let coldkey = U256::from(1); let hotkey = U256::from(2); let stake_amount = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // Set up initial stake SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); @@ -610,7 +616,7 @@ fn test_do_move_event_emission() { let origin_hotkey = U256::from(2); let destination_hotkey = U256::from(3); let stake_amount = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // Set up initial stake SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); @@ -662,7 +668,7 @@ fn test_do_move_storage_updates() { let origin_hotkey = U256::from(2); let destination_hotkey = U256::from(3); let stake_amount = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // Set up initial stake SubtensorModule::stake_into_subnet( @@ -700,13 +706,17 @@ fn test_do_move_storage_updates() { ), 0 ); + let alpha_fee = + I96F32::from_num(fee) / SubtensorModule::get_alpha_price(destination_netuid); + let alpha2 = I96F32::from_num(alpha) * SubtensorModule::get_alpha_price(origin_netuid) + / SubtensorModule::get_alpha_price(destination_netuid); assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &destination_hotkey, &coldkey, destination_netuid ), - alpha - fee, + (alpha2 - alpha_fee).to_num::(), epsilon = alpha / 1000 ); }); @@ -770,12 +780,12 @@ fn test_do_move_max_values() { // Verify moving too low amount is impossible #[test] -fn test_moving_too_little_fails() { +fn test_moving_too_little_unstakes() { new_test_ext(1).execute_with(|| { let hotkey_account_id = U256::from(533453); let coldkey_account_id = U256::from(55453); let amount = DefaultMinStake::::get(); - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); //add network let netuid: u16 = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); @@ -791,7 +801,6 @@ fn test_moving_too_little_fails() { amount + fee )); - // 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), @@ -813,7 +822,7 @@ fn test_do_transfer_success() { 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 fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // 2. Define the origin coldkey, destination coldkey, and hotkey to be used. let origin_coldkey = U256::from(1); @@ -952,7 +961,7 @@ fn test_do_transfer_wrong_origin() { let destination_coldkey = U256::from(2); let hotkey = U256::from(3); let stake_amount = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); SubtensorModule::create_account_if_non_existent(&origin_coldkey, &hotkey); SubtensorModule::add_balance_to_coldkey_account(&origin_coldkey, stake_amount + fee); @@ -1015,7 +1024,7 @@ fn test_do_transfer_different_subnets() { let destination_coldkey = U256::from(2); let hotkey = U256::from(3); let stake_amount = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // 3. Create accounts if needed. SubtensorModule::create_account_if_non_existent(&origin_coldkey, &hotkey); @@ -1064,10 +1073,12 @@ fn test_do_transfer_different_subnets() { &destination_coldkey, destination_netuid, ); + let expected_value = I96F32::from_num(stake_amount - fee) + / SubtensorModule::get_alpha_price(destination_netuid); assert_abs_diff_eq!( dest_stake, - stake_amount - fee, - epsilon = stake_amount / 1000 + expected_value.to_num::(), + epsilon = (expected_value / 1000).to_num::() ); }); } @@ -1080,11 +1091,10 @@ fn test_do_swap_success() { 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 fee = DefaultMinStake::::get(); - let coldkey = U256::from(1); let hotkey = U256::from(2); let stake_amount = DefaultMinStake::::get() * 10; + let fee = DefaultStakingFee::::get(); SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); SubtensorModule::stake_into_subnet(&hotkey, &coldkey, origin_netuid, stake_amount, 0); @@ -1116,10 +1126,15 @@ fn test_do_swap_success() { &coldkey, destination_netuid, ); + let alpha_fee = + I96F32::from_num(fee) / SubtensorModule::get_alpha_price(destination_netuid); + let expected_value = I96F32::from_num(alpha_before) + * SubtensorModule::get_alpha_price(origin_netuid) + / SubtensorModule::get_alpha_price(destination_netuid); assert_abs_diff_eq!( alpha_after, - stake_amount - fee, - epsilon = stake_amount / 1000 + (expected_value - alpha_fee).to_num::(), + epsilon = (expected_value / 1000).to_num::() ); }); } @@ -1265,11 +1280,10 @@ fn test_do_swap_same_subnet() { let subnet_owner_hotkey = U256::from(1101); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); - let fee = DefaultMinStake::::get(); - let coldkey = U256::from(1); let hotkey = U256::from(2); let stake_amount = DefaultMinStake::::get() * 10; + let fee = DefaultStakingFee::::get(); SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid, stake_amount, 0); @@ -1288,8 +1302,11 @@ fn test_do_swap_same_subnet() { let alpha_after = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); - assert_abs_diff_eq!(alpha_after, alpha_before - fee_as_alpha, epsilon = 15); - // Some slippage due to fee on same subnet. + assert_abs_diff_eq!( + alpha_after, + alpha_before - fee_as_alpha, + epsilon = alpha_after / 10000 + ); }); } @@ -1301,16 +1318,14 @@ fn test_do_swap_partial_stake() { 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 fee = DefaultMinStake::::get(); - let coldkey = U256::from(1); let hotkey = U256::from(2); let total_stake = DefaultMinStake::::get() * 10; + let fee = DefaultStakingFee::::get(); SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); SubtensorModule::stake_into_subnet(&hotkey, &coldkey, origin_netuid, total_stake, 0); - let fee_as_alpha2 = SubtensorModule::swap_tao_for_alpha(destination_netuid, fee); let swap_amount = total_stake / 2; assert_ok!(SubtensorModule::do_swap_stake( RuntimeOrigin::signed(coldkey), @@ -1329,14 +1344,20 @@ fn test_do_swap_partial_stake() { total_stake - swap_amount, epsilon = total_stake / 1000 ); + + let alpha_fee = + I96F32::from_num(fee) / SubtensorModule::get_alpha_price(destination_netuid); + let expected_value = I96F32::from_num(swap_amount) + * SubtensorModule::get_alpha_price(origin_netuid) + / SubtensorModule::get_alpha_price(destination_netuid); assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &coldkey, destination_netuid ), - swap_amount - fee_as_alpha2, - epsilon = total_stake / 1000 + (expected_value - alpha_fee).to_num::(), + epsilon = (expected_value / 1000).to_num::() ); }); } @@ -1349,11 +1370,10 @@ fn test_do_swap_storage_updates() { 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 fee = DefaultMinStake::::get(); - let coldkey = U256::from(1); let hotkey = U256::from(2); let stake_amount = DefaultMinStake::::get() * 10; + let fee = DefaultStakingFee::::get(); SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); SubtensorModule::stake_into_subnet(&hotkey, &coldkey, origin_netuid, stake_amount, 0); @@ -1380,16 +1400,19 @@ fn test_do_swap_storage_updates() { 0 ); - let fee_as_alpha = SubtensorModule::swap_tao_for_alpha(destination_netuid, fee); - + let alpha_fee = + I96F32::from_num(fee) / SubtensorModule::get_alpha_price(destination_netuid); + let expected_value = I96F32::from_num(alpha) + * SubtensorModule::get_alpha_price(origin_netuid) + / SubtensorModule::get_alpha_price(destination_netuid); assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &coldkey, destination_netuid ), - alpha - fee_as_alpha, - epsilon = 5 + (expected_value - alpha_fee).to_num::(), + epsilon = (expected_value / 1000).to_num::() ); }); } @@ -1402,11 +1425,10 @@ fn test_do_swap_multiple_times() { let netuid1 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); let netuid2 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); - let fee = DefaultMinStake::::get(); - let coldkey = U256::from(1); let hotkey = U256::from(2); let initial_stake = DefaultMinStake::::get() * 10; + let fee = DefaultStakingFee::::get(); SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid1, initial_stake, 0); @@ -1453,7 +1475,7 @@ fn test_do_swap_multiple_times() { assert_abs_diff_eq!( final_stake_netuid1, expected_stake, - epsilon = expected_stake / 1000 + epsilon = initial_stake / 10000 ); assert_eq!(final_stake_netuid2, 0); }); diff --git a/pallets/subtensor/src/tests/registration.rs b/pallets/subtensor/src/tests/registration.rs index 07a456c83b..768d923d05 100644 --- a/pallets/subtensor/src/tests/registration.rs +++ b/pallets/subtensor/src/tests/registration.rs @@ -3,7 +3,7 @@ use frame_support::traits::Currency; use super::mock::*; -use crate::{AxonInfoOf, Error, SubtensorSignedExtension}; +use crate::{AxonInfoOf, CustomTransactionError, Error, SubtensorSignedExtension}; use frame_support::dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}; use frame_support::sp_runtime::{transaction_validity::InvalidTransaction, DispatchError}; use frame_support::{assert_err, assert_noop, assert_ok}; @@ -274,7 +274,10 @@ fn test_registration_rate_limit_exceeded() { let result = extension.validate(&who, &call.into(), &info, 10); // Expectation: The transaction should be rejected - assert_err!(result, InvalidTransaction::Custom(5)); + assert_err!( + result, + InvalidTransaction::Custom(CustomTransactionError::RateLimitExceeded.into()) + ); let current_registrants = SubtensorModule::get_registrations_this_interval(netuid); assert!(current_registrants <= max_registrants); @@ -356,7 +359,10 @@ fn test_burned_registration_rate_limit_exceeded() { extension.validate(&who, &call_burned_register.into(), &info, 10); // Expectation: The transaction should be rejected - assert_err!(burned_register_result, InvalidTransaction::Custom(5)); + assert_err!( + burned_register_result, + InvalidTransaction::Custom(CustomTransactionError::RateLimitExceeded.into()) + ); let current_registrants = SubtensorModule::get_registrations_this_interval(netuid); assert!(current_registrants <= max_registrants); @@ -484,6 +490,46 @@ fn test_burn_registration_without_neuron_slot() { }); } +#[test] +fn test_burn_registration_doesnt_write_on_failure() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let tempo: u16 = 13; + let hotkey_account_id = U256::from(1); + let burn_cost = 1000; + let initial_balance = burn_cost * 10; + let coldkey_account_id = U256::from(987); + + // Add network and set burn cost + add_network(netuid, tempo, 0); + SubtensorModule::set_burn(netuid, burn_cost); + // Give coldkey balance to pay for registration + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, initial_balance); + // Set max allowed uids to 0 so registration will fail, but only on last check. + SubtensorModule::set_max_allowed_uids(netuid, 0); + + // We expect this to fail at the last ensure check. + assert_err!( + SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(coldkey_account_id), + netuid, + hotkey_account_id + ), + Error::::NoNeuronIdAvailable + ); + + // Make sure the coldkey balance is unchanged. + assert_eq!( + SubtensorModule::get_coldkey_balance(&coldkey_account_id), + initial_balance + ); + // Make sure the neuron is not registered. + assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 0); + // Make sure the hotkey is not registered. + assert!(SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id).is_err()); + }); +} + #[test] fn test_burn_adjustment() { new_test_ext(1).execute_with(|| { @@ -1211,7 +1257,7 @@ fn test_registration_get_uid_to_prune_all_in_immunity_period() { new_test_ext(1).execute_with(|| { System::set_block_number(0); let netuid: u16 = 1; - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); log::info!("add network"); register_ok_neuron(netuid, U256::from(0), U256::from(0), 39420842); register_ok_neuron(netuid, U256::from(1), U256::from(1), 12412392); @@ -1235,7 +1281,7 @@ fn test_registration_get_uid_to_prune_none_in_immunity_period() { new_test_ext(1).execute_with(|| { System::set_block_number(0); let netuid: u16 = 1; - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); log::info!("add network"); register_ok_neuron(netuid, U256::from(0), U256::from(0), 39420842); register_ok_neuron(netuid, U256::from(1), U256::from(1), 12412392); diff --git a/pallets/subtensor/src/tests/senate.rs b/pallets/subtensor/src/tests/senate.rs index 01dfc17d71..70ad5bb524 100644 --- a/pallets/subtensor/src/tests/senate.rs +++ b/pallets/subtensor/src/tests/senate.rs @@ -67,7 +67,7 @@ fn test_senate_join_works() { let burn_cost = 1000; let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har let stake = DefaultMinStake::::get() * 100; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); //add network SubtensorModule::set_burn(netuid, burn_cost); @@ -141,7 +141,7 @@ fn test_senate_vote_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 fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); //add network SubtensorModule::set_burn(netuid, burn_cost); @@ -315,7 +315,7 @@ fn test_senate_leave_works() { let burn_cost = 1000; let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har let stake = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); //add network SubtensorModule::set_burn(netuid, burn_cost); @@ -390,7 +390,7 @@ fn test_senate_leave_vote_removal() { 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; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); //add network SubtensorModule::set_burn(netuid, burn_cost); @@ -473,7 +473,7 @@ fn test_senate_leave_vote_removal() { // Add two networks. let root_netuid: u16 = 0; let other_netuid: u16 = 5; - add_network(other_netuid, 0, 0); + add_network(other_netuid, 1, 0); SubtensorModule::set_burn(other_netuid, 0); SubtensorModule::set_max_registrations_per_block(other_netuid, 1000); SubtensorModule::set_target_registrations_per_interval(other_netuid, 1000); @@ -532,7 +532,7 @@ fn test_senate_not_leave_when_stake_removed() { 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 fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); //add network SubtensorModule::set_burn(netuid, burn_cost); @@ -691,10 +691,11 @@ fn test_adjust_senate_events() { let burn_cost = 1000; let coldkey_account_id = U256::from(667); let root_netuid = SubtensorModule::get_root_netuid(); - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); let max_senate_size: u16 = SenateMaxMembers::get() as u16; - let stake_threshold: u64 = DefaultMinStake::::get(); // Give this much to every senator + let stake_threshold: u64 = + DefaultMinStake::::get() + DefaultStakingFee::::get(); // Give this much to every senator // We will be registering MaxMembers hotkeys and two more to try a replace let balance_to_add = DefaultMinStake::::get() * 10 @@ -761,13 +762,14 @@ fn test_adjust_senate_events() { coldkey_account_id ); // Add/delegate enough stake to join the senate + // +1 to be above hotkey_account_id assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(coldkey_account_id), new_hotkey_account_id, netuid, stake_threshold + 1 + i as u64 // Increasing with i to make them ordered - )); // +1 to be above hotkey_account_id - // Join senate + )); + // Join senate assert_ok!(SubtensorModule::root_register( <::RuntimeOrigin>::signed(coldkey_account_id), new_hotkey_account_id diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index ef5ac5f4a7..54413fdc0d 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -42,7 +42,7 @@ fn test_add_stake_ok_no_emission() { let hotkey_account_id = U256::from(533453); let coldkey_account_id = U256::from(55453); let amount = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); //add network let netuid: u16 = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); @@ -158,8 +158,9 @@ fn test_add_stake_not_registered_key_pair() { let coldkey_account_id = U256::from(435445); let hotkey_account_id = U256::from(54544); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); - let amount = 1337; - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 1800); + let amount = DefaultMinStake::::get() * 10; + let fee = DefaultStakingFee::::get(); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount + fee); assert_err!( SubtensorModule::add_stake( RuntimeOrigin::signed(coldkey_account_id), @@ -351,7 +352,7 @@ fn test_remove_stake_ok_no_emission() { let coldkey_account_id = U256::from(4343); let hotkey_account_id = U256::from(4968585); let amount = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); let netuid: u16 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); @@ -390,7 +391,7 @@ fn test_remove_stake_ok_no_emission() { } #[test] -fn test_remove_stake_amount_zero() { +fn test_remove_stake_amount_too_low() { new_test_ext(1).execute_with(|| { let subnet_owner_coldkey = U256::from(1); let subnet_owner_hotkey = U256::from(2); @@ -424,7 +425,7 @@ fn test_remove_stake_amount_zero() { netuid, 0 ), - Error::::StakeToWithdrawIsZero + Error::::AmountTooLow ); }); } @@ -454,7 +455,7 @@ fn test_remove_stake_ok_hotkey_does_not_belong_to_coldkey() { let coldkey_id = U256::from(544); let hotkey_id = U256::from(54544); let other_cold_key = U256::from(99498); - let amount = 1000; + let amount = DefaultMinStake::::get() * 10; let netuid: u16 = add_dynamic_network(&hotkey_id, &coldkey_id); // Give the neuron some stake to remove @@ -479,7 +480,7 @@ fn test_remove_stake_no_enough_stake() { new_test_ext(1).execute_with(|| { let coldkey_id = U256::from(544); let hotkey_id = U256::from(54544); - let amount = 10000; + let amount = DefaultMinStake::::get() * 10; let netuid = add_dynamic_network(&hotkey_id, &coldkey_id); assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey_id), 0); @@ -508,7 +509,7 @@ fn test_remove_stake_total_balance_no_change() { let hotkey_account_id = U256::from(571337); let coldkey_account_id = U256::from(71337); let amount = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); let netuid: u16 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); @@ -567,7 +568,7 @@ fn test_remove_stake_total_issuance_no_change() { let coldkey_account_id = U256::from(81337); let amount = DefaultMinStake::::get() * 10; let netuid: u16 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); // Give it some $$$ in his coldkey balance @@ -1295,7 +1296,7 @@ fn test_delegate_take_can_be_decreased() { // Register the neuron to a new network let netuid = 1; - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, hotkey0, coldkey0, 124124); // Coldkey / hotkey 0 become delegates with 9% take @@ -1330,7 +1331,7 @@ fn test_can_set_min_take_ok() { // Register the neuron to a new network let netuid = 1; - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, hotkey0, coldkey0, 124124); // Coldkey / hotkey 0 become delegates @@ -1362,7 +1363,7 @@ fn test_delegate_take_can_not_be_increased_with_decrease_take() { // Register the neuron to a new network let netuid = 1; - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, hotkey0, coldkey0, 124124); // Set min take @@ -1397,7 +1398,7 @@ fn test_delegate_take_can_be_increased() { // Register the neuron to a new network let netuid = 1; - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, hotkey0, coldkey0, 124124); // Coldkey / hotkey 0 become delegates with 9% take @@ -1432,7 +1433,7 @@ fn test_delegate_take_can_not_be_decreased_with_increase_take() { // Register the neuron to a new network let netuid = 1; - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, hotkey0, coldkey0, 124124); // Coldkey / hotkey 0 become delegates with 9% take @@ -1471,7 +1472,7 @@ fn test_delegate_take_can_be_increased_to_limit() { // Register the neuron to a new network let netuid = 1; - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, hotkey0, coldkey0, 124124); // Coldkey / hotkey 0 become delegates with 9% take @@ -1509,7 +1510,7 @@ fn test_delegate_take_can_not_be_increased_beyond_limit() { // Register the neuron to a new network let netuid = 1; - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, hotkey0, coldkey0, 124124); // Coldkey / hotkey 0 become delegates with 9% take @@ -1551,7 +1552,7 @@ fn test_rate_limits_enforced_on_increase_take() { // Register the neuron to a new network let netuid = 1; - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, hotkey0, coldkey0, 124124); // Coldkey / hotkey 0 become delegates with 9% take @@ -1609,7 +1610,7 @@ fn test_get_total_delegated_stake_after_unstaking() { let unstake_amount = DefaultMinStake::::get() * 5; let existential_deposit = ExistentialDeposit::get(); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); register_ok_neuron(netuid, delegate_hotkey, delegate_coldkey, 0); @@ -1678,7 +1679,7 @@ fn test_get_total_delegated_stake_no_delegations() { let coldkey = U256::from(2); let netuid = 1u16; - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, delegate, coldkey, 0); // Check that there's no delegated stake @@ -1697,7 +1698,7 @@ fn test_get_total_delegated_stake_single_delegator() { let stake_amount = DefaultMinStake::::get() * 10 - 1; let existential_deposit = ExistentialDeposit::get(); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); register_ok_neuron(netuid, delegate_hotkey, delegate_coldkey, 0); @@ -1755,7 +1756,7 @@ fn test_get_alpha_share_stake_multiple_delegators() { let existential_deposit = 2; let stake1 = DefaultMinStake::::get() * 10; let stake2 = DefaultMinStake::::get() * 10 - 1; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey1, coldkey1, 0); @@ -1814,7 +1815,7 @@ fn test_get_total_delegated_stake_exclude_owner_stake() { let delegator = U256::from(3); let owner_stake = DefaultMinStake::::get() * 10; let delegator_stake = DefaultMinStake::::get() * 10 - 1; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); let netuid = add_dynamic_network(&delegate_hotkey, &delegate_coldkey); @@ -1975,7 +1976,7 @@ fn test_add_stake_fee_goes_to_subnet_tao() { let coldkey = U256::from(3); let existential_deposit = ExistentialDeposit::get(); let tao_to_stake = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); @@ -2021,7 +2022,7 @@ fn test_remove_stake_fee_goes_to_subnet_tao() { let hotkey = U256::from(2); let coldkey = U256::from(3); let tao_to_stake = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); @@ -2074,7 +2075,7 @@ fn test_stake_below_min_validate() { let subnet_owner_hotkey = U256::from(1002); let hotkey = U256::from(2); let coldkey = U256::from(3); - let amount_staked = DefaultMinStake::::get() - 1; + let amount_staked = DefaultMinStake::::get() + DefaultStakingFee::::get() - 1; let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); @@ -2097,11 +2098,13 @@ fn test_stake_below_min_validate() { // Should fail due to insufficient stake assert_err!( result_no_stake, - crate::TransactionValidityError::Invalid(crate::InvalidTransaction::Custom(1)) + crate::TransactionValidityError::Invalid(crate::InvalidTransaction::Custom( + CustomTransactionError::StakeAmountTooLow.into() + )) ); // Increase the stake to be equal to the minimum, but leave the balance low - let amount_staked = DefaultMinStake::::get(); + let amount_staked = DefaultMinStake::::get() + DefaultStakingFee::::get(); let call_2 = RuntimeCall::SubtensorModule(SubtensorCall::add_stake { hotkey, netuid, @@ -2111,10 +2114,12 @@ fn test_stake_below_min_validate() { // Submit to the signed extension validate function let result_low_balance = extension.validate(&coldkey, &call_2.clone(), &info, 10); - // Still doesn't pass + // Still doesn't pass, but with a different reason (balance too low) assert_err!( result_low_balance, - crate::TransactionValidityError::Invalid(crate::InvalidTransaction::Custom(1)) + crate::TransactionValidityError::Invalid(crate::InvalidTransaction::Custom( + CustomTransactionError::BalanceTooLow.into() + )) ); // Increase the coldkey balance to match the minimum diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 45b3ba6e67..f21af56cf9 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -366,7 +366,7 @@ fn test_swap_with_max_values() { let netuid2 = 2u16; let stake = 10_000; let max_stake = 21_000_000_000_000_000; // 21 Million TAO; max possible balance. - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // Add a network add_network(netuid, 1, 0); @@ -435,7 +435,7 @@ fn test_swap_with_non_existent_new_coldkey() { let hotkey = U256::from(3); let stake = DefaultMinStake::::get() * 10; let netuid = 1u16; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); add_network(netuid, 1, 0); register_ok_neuron(netuid, hotkey, old_coldkey, 1001000); @@ -528,7 +528,7 @@ fn test_swap_concurrent_modifications() { let netuid: u16 = 1; let initial_stake = 1_000_000_000_000; let additional_stake = 500_000_000_000; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // Setup initial state add_network(netuid, 1, 1); @@ -806,7 +806,7 @@ fn test_swap_stake_for_coldkey() { let stake_amount3 = DefaultMinStake::::get() * 30; let total_stake = stake_amount1 + stake_amount2; let mut weight = Weight::zero(); - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // Setup initial state // Add a network @@ -962,7 +962,7 @@ fn test_swap_staking_hotkeys_for_coldkey() { let stake_amount2 = DefaultMinStake::::get() * 20; let total_stake = stake_amount1 + stake_amount2; let mut weight = Weight::zero(); - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // Setup initial state // Add a network @@ -1034,7 +1034,7 @@ fn test_swap_delegated_stake_for_coldkey() { let stake_amount2 = DefaultMinStake::::get() * 20; let mut weight = Weight::zero(); let netuid = 1u16; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); // Setup initial state add_network(netuid, 1, 0); @@ -1595,7 +1595,7 @@ fn test_coldkey_delegations() { let netuid = 0u16; // Stake to 0 let netuid2 = 1u16; // Stake to 1 let stake = DefaultMinStake::::get() * 10; - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); add_network(netuid, 13, 0); // root add_network(netuid2, 13, 0); diff --git a/pallets/subtensor/src/tests/swap_hotkey.rs b/pallets/subtensor/src/tests/swap_hotkey.rs index d35dd6d568..8cf04d2855 100644 --- a/pallets/subtensor/src/tests/swap_hotkey.rs +++ b/pallets/subtensor/src/tests/swap_hotkey.rs @@ -66,7 +66,7 @@ fn test_swap_total_hotkey_stake() { let coldkey = U256::from(3); let amount = DefaultMinStake::::get() * 10; let mut weight = Weight::zero(); - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); //add network let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); @@ -222,7 +222,7 @@ fn test_swap_subnet_membership() { let netuid = 0u16; let mut weight = Weight::zero(); - add_network(netuid, 0, 1); + add_network(netuid, 1, 1); IsNetworkMember::::insert(old_hotkey, netuid, true); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -247,7 +247,7 @@ fn test_swap_uids_and_keys() { let uid = 5u16; let mut weight = Weight::zero(); - add_network(netuid, 0, 1); + add_network(netuid, 1, 1); IsNetworkMember::::insert(old_hotkey, netuid, true); Uids::::insert(netuid, old_hotkey, uid); Keys::::insert(netuid, uid, old_hotkey); @@ -276,7 +276,7 @@ fn test_swap_prometheus() { let prometheus_info = PrometheusInfo::default(); let mut weight = Weight::zero(); - add_network(netuid, 0, 1); + add_network(netuid, 1, 1); IsNetworkMember::::insert(old_hotkey, netuid, true); Prometheus::::insert(netuid, old_hotkey, prometheus_info.clone()); @@ -306,7 +306,7 @@ fn test_swap_axons() { let axon_info = AxonInfo::default(); let mut weight = Weight::zero(); - add_network(netuid, 0, 1); + add_network(netuid, 1, 1); IsNetworkMember::::insert(old_hotkey, netuid, true); Axons::::insert(netuid, old_hotkey, axon_info.clone()); @@ -333,7 +333,7 @@ fn test_swap_certificates() { let certificate = NeuronCertificate::try_from(vec![1, 2, 3]).unwrap(); let mut weight = Weight::zero(); - add_network(netuid, 0, 1); + add_network(netuid, 1, 1); IsNetworkMember::::insert(old_hotkey, netuid, true); NeuronCertificates::::insert(netuid, old_hotkey, certificate.clone()); @@ -366,7 +366,7 @@ fn test_swap_weight_commits() { weight_commits.push_back((H256::from_low_u64_be(100), 200, 1, 1)); let mut weight = Weight::zero(); - add_network(netuid, 0, 1); + add_network(netuid, 1, 1); IsNetworkMember::::insert(old_hotkey, netuid, true); WeightCommits::::insert(netuid, old_hotkey, weight_commits.clone()); @@ -397,7 +397,7 @@ fn test_swap_loaded_emission() { let validator_emission = 1000u64; let mut weight = Weight::zero(); - add_network(netuid, 0, 1); + add_network(netuid, 1, 1); IsNetworkMember::::insert(old_hotkey, netuid, true); LoadedEmission::::insert( netuid, @@ -537,8 +537,8 @@ fn test_swap_hotkey_with_multiple_subnets() { let netuid2 = 1; let mut weight = Weight::zero(); - add_network(netuid1, 0, 1); - add_network(netuid2, 0, 1); + add_network(netuid1, 1, 1); + add_network(netuid2, 1, 1); IsNetworkMember::::insert(old_hotkey, netuid1, true); IsNetworkMember::::insert(old_hotkey, netuid2, true); @@ -639,8 +639,8 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { let mut weight = Weight::zero(); // Set up initial state - add_network(netuid1, 0, 1); - add_network(netuid2, 0, 1); + add_network(netuid1, 1, 1); + add_network(netuid2, 1, 1); register_ok_neuron(netuid1, old_hotkey, coldkey1, 1234); register_ok_neuron(netuid2, old_hotkey, coldkey1, 1234); @@ -1257,12 +1257,18 @@ fn test_swap_parent_hotkey_childkey_maps() { let parent_old = U256::from(1); let coldkey = U256::from(2); let child = U256::from(3); + let child_other = U256::from(4); let parent_new = U256::from(4); - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); SubtensorModule::create_account_if_non_existent(&coldkey, &parent_old); // Set child and verify state maps mock_set_children(&coldkey, &parent_old, netuid, &[(u64::MAX, child)]); + // Wait rate limit + step_rate_limit(&TransactionType::SetChildren, netuid); + // Schedule some pending child keys. + mock_schedule_children(&coldkey, &parent_old, netuid, &[(u64::MAX, child_other)]); + assert_eq!( ParentKeys::::get(child, netuid), vec![(u64::MAX, parent_old)] @@ -1271,6 +1277,8 @@ fn test_swap_parent_hotkey_childkey_maps() { ChildKeys::::get(parent_old, netuid), vec![(u64::MAX, child)] ); + let existing_pending_child_keys = PendingChildKeys::::get(netuid, parent_old); + assert_eq!(existing_pending_child_keys.0, vec![(u64::MAX, child_other)]); // Swap let mut weight = Weight::zero(); @@ -1290,6 +1298,10 @@ fn test_swap_parent_hotkey_childkey_maps() { ChildKeys::::get(parent_new, netuid), vec![(u64::MAX, child)] ); + assert_eq!( + PendingChildKeys::::get(netuid, parent_new), + existing_pending_child_keys // Entry under new hotkey. + ); }) } @@ -1301,12 +1313,16 @@ fn test_swap_child_hotkey_childkey_maps() { let coldkey = U256::from(2); let child_old = U256::from(3); let child_new = U256::from(4); - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); SubtensorModule::create_account_if_non_existent(&coldkey, &child_old); SubtensorModule::create_account_if_non_existent(&coldkey, &parent); // Set child and verify state maps mock_set_children(&coldkey, &parent, netuid, &[(u64::MAX, child_old)]); + // Wait rate limit + step_rate_limit(&TransactionType::SetChildren, netuid); + // Schedule some pending child keys. + mock_schedule_children(&coldkey, &parent, netuid, &[(u64::MAX, child_old)]); assert_eq!( ParentKeys::::get(child_old, netuid), @@ -1316,6 +1332,8 @@ fn test_swap_child_hotkey_childkey_maps() { ChildKeys::::get(parent, netuid), vec![(u64::MAX, child_old)] ); + let existing_pending_child_keys = PendingChildKeys::::get(netuid, parent); + assert_eq!(existing_pending_child_keys.0, vec![(u64::MAX, child_old)]); // Swap let mut weight = Weight::zero(); @@ -1335,5 +1353,9 @@ fn test_swap_child_hotkey_childkey_maps() { ChildKeys::::get(parent, netuid), vec![(u64::MAX, child_new)] ); + assert_eq!( + PendingChildKeys::::get(netuid, parent), + (vec![(u64::MAX, child_new)], existing_pending_child_keys.1) // Same cooldown block. + ); }) } diff --git a/pallets/subtensor/src/tests/uids.rs b/pallets/subtensor/src/tests/uids.rs index 9fdeca0416..b45a156a98 100644 --- a/pallets/subtensor/src/tests/uids.rs +++ b/pallets/subtensor/src/tests/uids.rs @@ -50,17 +50,47 @@ fn test_replace_neuron() { // Get UID let neuron_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id); assert_ok!(neuron_uid); + let neuron_uid = neuron_uid.unwrap(); + + // set non-default values + Trust::::mutate(netuid, |v| { + SubtensorModule::set_element_at(v, neuron_uid as usize, 5u16) + }); + Emission::::mutate(netuid, |v| { + SubtensorModule::set_element_at(v, neuron_uid as usize, 5u64) + }); + Consensus::::mutate(netuid, |v| { + SubtensorModule::set_element_at(v, neuron_uid as usize, 5u16) + }); + Incentive::::mutate(netuid, |v| { + SubtensorModule::set_element_at(v, neuron_uid as usize, 5u16) + }); + Dividends::::mutate(netuid, |v| { + SubtensorModule::set_element_at(v, neuron_uid as usize, 5u16) + }); + + // serve axon mock address + let ip: u128 = 1676056785; + let port: u16 = 9999; + let ip_type: u8 = 4; + assert!(SubtensorModule::serve_axon( + <::RuntimeOrigin>::signed(hotkey_account_id), + netuid, + 0, + ip, + port, + ip_type, + 0, + 0, + 0 + ) + .is_ok()); // Set a neuron certificate for it NeuronCertificates::::insert(netuid, hotkey_account_id, certificate); // Replace the neuron. - SubtensorModule::replace_neuron( - netuid, - neuron_uid.unwrap(), - &new_hotkey_account_id, - block_number, - ); + SubtensorModule::replace_neuron(netuid, neuron_uid, &new_hotkey_account_id, block_number); // Check old hotkey is not registered on any network. assert!(SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id).is_err()); @@ -68,7 +98,7 @@ fn test_replace_neuron() { &hotkey_account_id )); - let curr_hotkey = SubtensorModule::get_hotkey_for_net_and_uid(netuid, neuron_uid.unwrap()); + let curr_hotkey = SubtensorModule::get_hotkey_for_net_and_uid(netuid, neuron_uid); assert_ok!(curr_hotkey); assert_ne!(curr_hotkey.unwrap(), hotkey_account_id); @@ -84,6 +114,28 @@ fn test_replace_neuron() { // Check neuron certificate was reset let certificate = NeuronCertificates::::get(netuid, hotkey_account_id); assert_eq!(certificate, None); + + // Check trust, emission, consensus, incentive, dividends have been reset to 0. + assert_eq!(SubtensorModule::get_trust_for_uid(netuid, neuron_uid), 0); + assert_eq!(SubtensorModule::get_emission_for_uid(netuid, neuron_uid), 0); + assert_eq!( + SubtensorModule::get_consensus_for_uid(netuid, neuron_uid), + 0 + ); + assert_eq!( + SubtensorModule::get_incentive_for_uid(netuid, neuron_uid), + 0 + ); + assert_eq!( + SubtensorModule::get_dividends_for_uid(netuid, neuron_uid), + 0 + ); + + // Check axon info is reset. + let axon_info = SubtensorModule::get_axon_info(netuid, &curr_hotkey.unwrap()); + assert_eq!(axon_info.ip, 0); + assert_eq!(axon_info.port, 0); + assert_eq!(axon_info.ip_type, 0); }); } diff --git a/pallets/subtensor/src/tests/weights.rs b/pallets/subtensor/src/tests/weights.rs index 10c829870c..57d51598c7 100644 --- a/pallets/subtensor/src/tests/weights.rs +++ b/pallets/subtensor/src/tests/weights.rs @@ -69,7 +69,7 @@ fn test_set_rootweights_validate() { let coldkey = U256::from(0); let hotkey: U256 = U256::from(1); // Add the hotkey field assert_ne!(hotkey, coldkey); // Ensure hotkey is NOT the same as coldkey !!! - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); let who = coldkey; // The coldkey signs this transaction @@ -82,7 +82,7 @@ fn test_set_rootweights_validate() { }); // Create netuid - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); // Register the hotkey SubtensorModule::append_neuron(netuid, &hotkey, 0); crate::Owner::::insert(hotkey, coldkey); @@ -105,7 +105,9 @@ fn test_set_rootweights_validate() { assert_err!( // Should get an invalid transaction error result_no_stake, - crate::TransactionValidityError::Invalid(crate::InvalidTransaction::Custom(4)) + crate::TransactionValidityError::Invalid(crate::InvalidTransaction::Custom( + CustomTransactionError::StakeAmountTooLow.into() + )) ); // Increase the stake to be equal to the minimum @@ -184,7 +186,7 @@ fn test_commit_weights_validate() { let coldkey = U256::from(0); let hotkey: U256 = U256::from(1); // Add the hotkey field assert_ne!(hotkey, coldkey); // Ensure hotkey is NOT the same as coldkey !!! - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); let who = hotkey; // The hotkey signs this transaction @@ -197,7 +199,7 @@ fn test_commit_weights_validate() { }); // Create netuid - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); // Register the hotkey SubtensorModule::append_neuron(netuid, &hotkey, 0); crate::Owner::::insert(hotkey, coldkey); @@ -294,7 +296,7 @@ fn test_set_weights_validate() { let coldkey = U256::from(0); let hotkey: U256 = U256::from(1); assert_ne!(hotkey, coldkey); - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); let who = hotkey; // The hotkey signs this transaction @@ -306,7 +308,7 @@ fn test_set_weights_validate() { }); // Create netuid - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); // Register the hotkey SubtensorModule::append_neuron(netuid, &hotkey, 0); crate::Owner::::insert(hotkey, coldkey); @@ -328,7 +330,9 @@ fn test_set_weights_validate() { // Should fail due to insufficient stake assert_err!( result_no_stake, - crate::TransactionValidityError::Invalid(crate::InvalidTransaction::Custom(3)) + crate::TransactionValidityError::Invalid(crate::InvalidTransaction::Custom( + CustomTransactionError::StakeAmountTooLow.into() + )) ); // Increase the stake to be equal to the minimum @@ -367,7 +371,7 @@ fn test_reveal_weights_validate() { let coldkey = U256::from(0); let hotkey: U256 = U256::from(1); // Add the hotkey field assert_ne!(hotkey, coldkey); // Ensure hotkey is NOT the same as coldkey !!! - let fee = DefaultMinStake::::get(); + let fee = DefaultStakingFee::::get(); let who = hotkey; // The hotkey signs this transaction @@ -380,7 +384,7 @@ fn test_reveal_weights_validate() { }); // Create netuid - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); // Register the hotkey SubtensorModule::append_neuron(netuid, &hotkey, 0); crate::Owner::::insert(hotkey, coldkey); @@ -402,7 +406,9 @@ fn test_reveal_weights_validate() { assert_err!( // Should get an invalid transaction error result_no_stake, - crate::TransactionValidityError::Invalid(crate::InvalidTransaction::Custom(2)) + crate::TransactionValidityError::Invalid(crate::InvalidTransaction::Custom( + CustomTransactionError::StakeAmountTooLow.into() + )) ); // Increase the stake to be equal to the minimum @@ -521,7 +527,7 @@ fn test_set_stake_threshold_failed() { let hotkey = U256::from(0); let coldkey = U256::from(0); - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, hotkey, coldkey, 2143124); SubtensorModule::set_stake_threshold(20_000_000_000_000); SubtensorModule::add_balance_to_coldkey_account(&hotkey, u64::MAX); @@ -583,8 +589,8 @@ fn test_weights_version_key() { let netuid0: u16 = 1; let netuid1: u16 = 2; - add_network(netuid0, 0, 0); - add_network(netuid1, 0, 0); + add_network(netuid0, 1, 0); + add_network(netuid1, 1, 0); register_ok_neuron(netuid0, hotkey, coldkey, 2143124); register_ok_neuron(netuid1, hotkey, coldkey, 3124124); @@ -1421,7 +1427,7 @@ fn test_check_len_uids_within_allowed_not_within_network_pool() { fn test_set_weights_commit_reveal_enabled_error() { new_test_ext(0).execute_with(|| { let netuid: u16 = 1; - add_network(netuid, 0, 0); + add_network(netuid, 1, 0); register_ok_neuron(netuid, U256::from(1), U256::from(2), 10); let uids = vec![0]; diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index f1e0c7eb40..b27921f27c 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -3,6 +3,7 @@ use crate::{ system::{ensure_root, ensure_signed_or_root, pallet_prelude::BlockNumberFor}, Error, }; +use safe_math::*; use sp_core::Get; use sp_core::U256; use sp_runtime::Saturating; @@ -592,7 +593,8 @@ impl Pallet { SubnetOwnerCut::::get() } pub fn get_float_subnet_owner_cut() -> I96F32 { - I96F32::from_num(SubnetOwnerCut::::get()).saturating_div(I96F32::from_num(u16::MAX)) + I96F32::saturating_from_num(SubnetOwnerCut::::get()) + .safe_div(I96F32::saturating_from_num(u16::MAX)) } pub fn set_subnet_owner_cut(subnet_owner_cut: u16) { SubnetOwnerCut::::set(subnet_owner_cut); @@ -664,9 +666,10 @@ impl Pallet { pub fn get_alpha_values_32(netuid: u16) -> (I32F32, I32F32) { let (alpha_low, alpha_high): (u16, u16) = AlphaValues::::get(netuid); - let converted_low = I32F32::from_num(alpha_low).saturating_div(I32F32::from_num(u16::MAX)); + let converted_low = + I32F32::saturating_from_num(alpha_low).safe_div(I32F32::saturating_from_num(u16::MAX)); let converted_high = - I32F32::from_num(alpha_high).saturating_div(I32F32::from_num(u16::MAX)); + I32F32::saturating_from_num(alpha_high).safe_div(I32F32::saturating_from_num(u16::MAX)); (converted_low, converted_high) } diff --git a/primitives/safe-math/Cargo.toml b/primitives/safe-math/Cargo.toml new file mode 100644 index 0000000000..f67d53c5df --- /dev/null +++ b/primitives/safe-math/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "safe-math" +version = "0.1.0" +edition = "2021" + +[dependencies] +substrate-fixed = { workspace = true } +sp-std = { workspace = true } +num-traits = { version = "0.2.19", default-features = false, features = ["libm"] } + +[lints] +workspace = true + +[features] +default = ["std"] +std = [ + "substrate-fixed/std", + "sp-std/std", + "num-traits/std", +] diff --git a/primitives/safe-math/src/lib.rs b/primitives/safe-math/src/lib.rs new file mode 100644 index 0000000000..83c27b07c4 --- /dev/null +++ b/primitives/safe-math/src/lib.rs @@ -0,0 +1,71 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::result_unit_err)] + +use substrate_fixed::types::{I110F18, I32F32, I64F64, I96F32, U64F64}; + +/// Safe division trait +pub trait SafeDiv { + /// Safe division that returns supplied default value for division by zero + fn safe_div_or(self, rhs: Self, def: Self) -> Self; + /// Safe division that returns default value for division by zero + fn safe_div(self, rhs: Self) -> Self; +} + +/// Implementation of safe division trait for primitive types +macro_rules! impl_safe_div_for_primitive { + ($($t:ty),*) => { + $( + impl SafeDiv for $t { + fn safe_div_or(self, rhs: Self, def: Self) -> Self { + self.checked_div(rhs).unwrap_or(def) + } + + fn safe_div(self, rhs: Self) -> Self { + self.checked_div(rhs).unwrap_or_default() + } + } + )* + }; +} +impl_safe_div_for_primitive!(u8, u16, u32, u64, i8, i16, i32, i64, usize); + +/// Implementation of safe division trait for substrate fixed types +macro_rules! impl_safe_div_for_fixed { + ($($t:ty),*) => { + $( + impl SafeDiv for $t { + fn safe_div_or(self, rhs: Self, def: Self) -> Self { + self.checked_div(rhs).unwrap_or(def) + } + + fn safe_div(self, rhs: Self) -> Self { + self.checked_div(rhs).unwrap_or_default() + } + } + )* + }; +} +impl_safe_div_for_fixed!(I96F32, I32F32, I64F64, I110F18, U64F64); + +// /// Trait for safe conversion to primitive type P +// pub trait SafeToNum { +// /// Safe conversion to primitive type P +// fn safe_to_num

(self) -> P +// where +// P: num_traits::Bounded + substrate_fixed::prelude::ToFixed + substrate_fixed::prelude::FromFixed; +// } + +// impl SafeToNum for T +// where +// T: substrate_fixed::traits::Fixed, +// { +// fn safe_to_num

(self) -> P +// where +// P: num_traits::Bounded + substrate_fixed::prelude::ToFixed + substrate_fixed::prelude::FromFixed +// { +// match self.try_into() { +// Ok(value) => value, +// Err(_) => P::max_value(), +// } +// } +// } diff --git a/primitives/share-pool/Cargo.toml b/primitives/share-pool/Cargo.toml index 2191232699..e0696ee306 100644 --- a/primitives/share-pool/Cargo.toml +++ b/primitives/share-pool/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] substrate-fixed = { workspace = true } sp-std = { workspace = true } +safe-math = { default-features = false, path = "../safe-math" } [lints] workspace = true @@ -15,4 +16,5 @@ default = ["std"] std = [ "substrate-fixed/std", "sp-std/std", + "safe-math/std", ] diff --git a/primitives/share-pool/src/lib.rs b/primitives/share-pool/src/lib.rs index 2f4d25ef9f..f3e00fca9a 100644 --- a/primitives/share-pool/src/lib.rs +++ b/primitives/share-pool/src/lib.rs @@ -52,9 +52,9 @@ where shared_value .checked_div(denominator) - .unwrap_or(U64F64::from_num(0)) + .unwrap_or(U64F64::saturating_from_num(0)) .saturating_mul(current_share) - .to_num::() + .saturating_to_num::() } pub fn try_get_value(&self, key: &K) -> Result { @@ -69,9 +69,9 @@ where pub fn update_value_for_all(&mut self, update: i64) { let shared_value: U64F64 = self.state_ops.get_shared_value(); self.state_ops.set_shared_value(if update >= 0 { - shared_value.saturating_add(U64F64::from_num(update)) + shared_value.saturating_add(U64F64::saturating_from_num(update)) } else { - shared_value.saturating_sub(U64F64::from_num(update.neg())) + shared_value.saturating_sub(U64F64::saturating_from_num(update.neg())) }); } @@ -92,31 +92,33 @@ where self.state_ops.set_share(key, new_shared_value); } else { // There are already keys in the pool, set or update this key - let value_per_share: I64F64 = I64F64::from_num( + let value_per_share: I64F64 = I64F64::saturating_from_num( shared_value .checked_div(denominator) // denominator is never 0 here - .unwrap_or(U64F64::from_num(0)), + .unwrap_or(U64F64::saturating_from_num(0)), ); - let shares_per_update: I64F64 = I64F64::from_num(update) + let shares_per_update: I64F64 = I64F64::saturating_from_num(update) .checked_div(value_per_share) - .unwrap_or(I64F64::from_num(0)); + .unwrap_or(I64F64::saturating_from_num(0)); if shares_per_update >= 0 { self.state_ops.set_denominator( - denominator.saturating_add(U64F64::from_num(shares_per_update)), + denominator.saturating_add(U64F64::saturating_from_num(shares_per_update)), ); self.state_ops.set_share( key, - current_share.saturating_add(U64F64::from_num(shares_per_update)), + current_share.saturating_add(U64F64::saturating_from_num(shares_per_update)), ); } else { self.state_ops.set_denominator( - denominator.saturating_sub(U64F64::from_num(shares_per_update.neg())), + denominator + .saturating_sub(U64F64::saturating_from_num(shares_per_update.neg())), ); self.state_ops.set_share( key, - current_share.saturating_sub(U64F64::from_num(shares_per_update.neg())), + current_share + .saturating_sub(U64F64::saturating_from_num(shares_per_update.neg())), ); } } @@ -137,9 +139,9 @@ mod tests { impl MockSharePoolDataOperations { fn new() -> Self { MockSharePoolDataOperations { - shared_value: U64F64::from_num(0), + shared_value: U64F64::saturating_from_num(0), share: BTreeMap::new(), - denominator: U64F64::from_num(0), + denominator: U64F64::saturating_from_num(0), } } } @@ -150,7 +152,10 @@ mod tests { } fn get_share(&self, key: &u16) -> U64F64 { - *self.share.get(key).unwrap_or(&U64F64::from_num(0)) + *self + .share + .get(key) + .unwrap_or(&U64F64::saturating_from_num(0)) } fn try_get_share(&self, key: &u16) -> Result { @@ -180,10 +185,10 @@ mod tests { #[test] fn test_get_value() { let mut mock_ops = MockSharePoolDataOperations::new(); - mock_ops.set_denominator(U64F64::from_num(10)); - mock_ops.set_share(&1_u16, U64F64::from_num(3)); - mock_ops.set_share(&2_u16, U64F64::from_num(7)); - mock_ops.set_shared_value(U64F64::from_num(100)); + mock_ops.set_denominator(U64F64::saturating_from_num(10)); + mock_ops.set_share(&1_u16, U64F64::saturating_from_num(3)); + mock_ops.set_share(&2_u16, U64F64::saturating_from_num(7)); + mock_ops.set_shared_value(U64F64::saturating_from_num(100)); let share_pool = SharePool::new(mock_ops); let result1 = share_pool.get_value(&1); let result2 = share_pool.get_value(&2); @@ -194,7 +199,7 @@ mod tests { #[test] fn test_division_by_zero() { let mut mock_ops = MockSharePoolDataOperations::new(); - mock_ops.set_denominator(U64F64::from_num(0)); // Zero denominator + mock_ops.set_denominator(U64F64::saturating_from_num(0)); // Zero denominator let pool = SharePool::::new(mock_ops); let value = pool.get_value(&1); @@ -204,10 +209,10 @@ mod tests { #[test] fn test_max_shared_value() { let mut mock_ops = MockSharePoolDataOperations::new(); - mock_ops.set_shared_value(U64F64::from_num(u64::MAX)); - mock_ops.set_share(&1, U64F64::from_num(3)); // Use a neutral value for share - mock_ops.set_share(&2, U64F64::from_num(7)); // Use a neutral value for share - mock_ops.set_denominator(U64F64::from_num(10)); // Neutral value to see max effect + mock_ops.set_shared_value(U64F64::saturating_from_num(u64::MAX)); + mock_ops.set_share(&1, U64F64::saturating_from_num(3)); // Use a neutral value for share + mock_ops.set_share(&2, U64F64::saturating_from_num(7)); // Use a neutral value for share + mock_ops.set_denominator(U64F64::saturating_from_num(10)); // Neutral value to see max effect let pool = SharePool::::new(mock_ops); let max_value = pool.get_value(&1) + pool.get_value(&2); @@ -217,10 +222,10 @@ mod tests { #[test] fn test_max_share_value() { let mut mock_ops = MockSharePoolDataOperations::new(); - mock_ops.set_shared_value(U64F64::from_num(1_000_000_000)); // Use a neutral value for shared value - mock_ops.set_share(&1, U64F64::from_num(u64::MAX / 2)); - mock_ops.set_share(&2, U64F64::from_num(u64::MAX / 2)); - mock_ops.set_denominator(U64F64::from_num(u64::MAX)); + mock_ops.set_shared_value(U64F64::saturating_from_num(1_000_000_000)); // Use a neutral value for shared value + mock_ops.set_share(&1, U64F64::saturating_from_num(u64::MAX / 2)); + mock_ops.set_share(&2, U64F64::saturating_from_num(u64::MAX / 2)); + mock_ops.set_denominator(U64F64::saturating_from_num(u64::MAX)); let pool = SharePool::::new(mock_ops); let value1 = pool.get_value(&1) as i128; @@ -268,6 +273,9 @@ mod tests { let mut pool = SharePool::::new(mock_ops); pool.update_value_for_all(1000); - assert_eq!(pool.state_ops.shared_value, U64F64::from_num(1000)); + assert_eq!( + pool.state_ops.shared_value, + U64F64::saturating_from_num(1000) + ); } } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index e0ea639877..372c9a9815 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -220,7 +220,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: 223, + spec_version: 224, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -1179,7 +1179,9 @@ const BLOCK_GAS_LIMIT: u64 = 75_000_000; /// `WeightPerGas` is an approximate ratio of the amount of Weight per Gas. /// fn weight_per_gas() -> Weight { - (NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT).saturating_div(BLOCK_GAS_LIMIT) + (NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT) + .checked_div(BLOCK_GAS_LIMIT) + .unwrap_or_default() } parameter_types! {