From c5620f92881761bf8c7fb20a8075758ec12eace7 Mon Sep 17 00:00:00 2001 From: Keith Date: Fri, 24 May 2024 23:47:51 +0800 Subject: [PATCH] Ban unsafe arithmetic operations --- Cargo.toml | 3 + node/src/chain_spec.rs | 5 +- pallets/collective/src/lib.rs | 27 +++-- pallets/commitments/src/lib.rs | 13 +-- pallets/commitments/src/types.rs | 6 +- pallets/registry/src/lib.rs | 15 +-- pallets/registry/src/types.rs | 6 +- pallets/subtensor/src/block_step.rs | 107 ++++++++++++------- pallets/subtensor/src/delegate_info.rs | 10 +- pallets/subtensor/src/epoch.rs | 36 ++++--- pallets/subtensor/src/lib.rs | 29 ++++-- pallets/subtensor/src/math.rs | 137 +++++++++++++++---------- pallets/subtensor/src/migration.rs | 11 +- pallets/subtensor/src/registration.rs | 33 +++--- pallets/subtensor/src/root.rs | 90 +++++++++------- pallets/subtensor/src/serving.rs | 4 +- pallets/subtensor/src/staking.rs | 6 +- pallets/subtensor/src/subnet_info.rs | 2 +- pallets/subtensor/src/uids.rs | 8 +- pallets/subtensor/src/utils.rs | 4 +- pallets/subtensor/src/weights.rs | 10 +- runtime/src/check_nonce.rs | 128 +++++++++++++++++++++++ runtime/src/lib.rs | 12 ++- 23 files changed, 468 insertions(+), 234 deletions(-) create mode 100644 runtime/src/check_nonce.rs diff --git a/Cargo.toml b/Cargo.toml index a05b3c9e5..41e0db26a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,10 @@ members = [ resolver = "2" [workspace.lints.clippy] +indexing-slicing = "deny" +arithmetic-side-effects = "deny" type_complexity = "allow" +unwrap-used = "deny" [profile.release] panic = "unwind" diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index 6dbb68b02..c2d22da54 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -12,6 +12,7 @@ use sp_consensus_grandpa::AuthorityId as GrandpaId; use sp_core::crypto::Ss58Codec; use sp_core::{bounded_vec, sr25519, Pair, Public}; use sp_runtime::traits::{IdentifyAccount, Verify}; +use sp_runtime::Saturating; use std::env; // The URL for the telemetry server. @@ -119,7 +120,7 @@ pub fn finney_mainnet_config() -> Result { let key_account = sp_runtime::AccountId32::from(key); processed_balances.push((key_account, *amount)); - balances_issuance += *amount; + balances_issuance.saturating_accrue(*amount); } // Give front-ends necessary data to present to users @@ -298,7 +299,7 @@ pub fn finney_testnet_config() -> Result { let key_account = sp_runtime::AccountId32::from(key); processed_balances.push((key_account, *amount)); - balances_issuance += *amount; + balances_issuance.saturating_accrue(*amount); } // Give front-ends necessary data to present to users diff --git a/pallets/collective/src/lib.rs b/pallets/collective/src/lib.rs index bcd88cc31..f67712717 100644 --- a/pallets/collective/src/lib.rs +++ b/pallets/collective/src/lib.rs @@ -44,7 +44,7 @@ use scale_info::TypeInfo; use sp_io::storage; -use sp_runtime::{traits::Hash, RuntimeDebug}; +use sp_runtime::{traits::Hash, RuntimeDebug, Saturating}; use sp_std::{marker::PhantomData, prelude::*, result}; use frame_support::{ @@ -122,7 +122,7 @@ impl DefaultVote for MoreThanMajorityThenPrimeDefaultVote { _no_votes: MemberCount, len: MemberCount, ) -> bool { - let more_than_majority = yes_votes * 2 > len; + let more_than_majority = yes_votes.saturating_mul(2) > len; more_than_majority || prime_vote.unwrap_or(false) } } @@ -527,7 +527,9 @@ pub mod pallet { Error::::WrongDuration ); - let threshold = (T::GetVotingMembers::get_count() / 2) + 1; + let threshold = T::GetVotingMembers::get_count() + .saturating_div(2) + .saturating_add(1); let members = Self::members(); let (proposal_len, active_proposals) = @@ -724,10 +726,13 @@ impl, I: 'static> Pallet { })?; let index = Self::proposal_count(); - >::mutate(|i| *i += 1); + >::try_mutate(|i| { + *i = i.checked_add(1).ok_or(Error::::TooManyProposals)?; + Ok::<(), Error>(()) + })?; >::insert(proposal_hash, proposal); let votes = { - let end = frame_system::Pallet::::block_number() + duration; + let end = frame_system::Pallet::::block_number().saturating_add(duration); Votes { index, threshold, @@ -864,10 +869,10 @@ impl, I: 'static> Pallet { // default voting strategy. let default = T::DefaultVote::default_vote(prime_vote, yes_votes, no_votes, seats); - let abstentions = seats - (yes_votes + no_votes); + let abstentions = seats.saturating_sub(yes_votes.saturating_add(no_votes)); match default { - true => yes_votes += abstentions, - false => no_votes += abstentions, + true => yes_votes = yes_votes.saturating_add(abstentions), + false => no_votes = no_votes.saturating_add(abstentions), } let approved = yes_votes >= voting.threshold; @@ -983,7 +988,7 @@ impl, I: 'static> Pallet { Voting::::remove(proposal_hash); let num_proposals = Proposals::::mutate(|proposals| { proposals.retain(|h| h != &proposal_hash); - proposals.len() + 1 // calculate weight based on original length + proposals.len().saturating_add(1) // calculate weight based on original length }); num_proposals as u32 } @@ -1153,7 +1158,7 @@ impl< type Success = (); fn try_origin(o: O) -> Result { o.into().and_then(|o| match o { - RawOrigin::Members(n, m) if n * D > N * m => Ok(()), + RawOrigin::Members(n, m) if n.saturating_mul(D) > N.saturating_mul(m) => Ok(()), r => Err(O::from(r)), }) } @@ -1178,7 +1183,7 @@ impl< type Success = (); fn try_origin(o: O) -> Result { o.into().and_then(|o| match o { - RawOrigin::Members(n, m) if n * D >= N * m => Ok(()), + RawOrigin::Members(n, m) if n.saturating_mul(D) >= N.saturating_mul(m) => Ok(()), r => Err(O::from(r)), }) } diff --git a/pallets/commitments/src/lib.rs b/pallets/commitments/src/lib.rs index fd9b0bbb3..61a421d7c 100644 --- a/pallets/commitments/src/lib.rs +++ b/pallets/commitments/src/lib.rs @@ -14,7 +14,7 @@ pub use types::*; pub use weights::WeightInfo; use frame_support::traits::Currency; -use sp_runtime::traits::Zero; +use sp_runtime::{traits::Zero, Saturating}; use sp_std::boxed::Box; type BalanceOf = @@ -129,12 +129,12 @@ pub mod pallet { let cur_block = >::block_number(); if let Some(last_commit) = >::get(netuid, &who) { ensure!( - cur_block >= last_commit + T::RateLimit::get(), + cur_block >= last_commit.saturating_add(T::RateLimit::get()), Error::::RateLimitExceeded ); } - let fd = >::from(extra_fields) * T::FieldDeposit::get(); + let fd = >::from(extra_fields).saturating_mul(T::FieldDeposit::get()); let mut id = match >::get(netuid, &who) { Some(mut id) => { id.info = *info; @@ -149,12 +149,13 @@ pub mod pallet { }; let old_deposit = id.deposit; - id.deposit = T::InitialDeposit::get() + fd; + id.deposit = T::InitialDeposit::get().saturating_add(fd); if id.deposit > old_deposit { - T::Currency::reserve(&who, id.deposit - old_deposit)?; + T::Currency::reserve(&who, id.deposit.saturating_sub(old_deposit))?; } if old_deposit > id.deposit { - let err_amount = T::Currency::unreserve(&who, old_deposit - id.deposit); + let err_amount = + T::Currency::unreserve(&who, old_deposit.saturating_sub(id.deposit)); debug_assert!(err_amount.is_zero()); } diff --git a/pallets/commitments/src/types.rs b/pallets/commitments/src/types.rs index 6ca9ee603..dca9fee49 100644 --- a/pallets/commitments/src/types.rs +++ b/pallets/commitments/src/types.rs @@ -66,7 +66,7 @@ impl Decode for Data { Ok(match b { 0 => Data::None, n @ 1..=129 => { - let mut r: BoundedVec<_, _> = vec![0u8; n as usize - 1] + let mut r: BoundedVec<_, _> = vec![0u8; (n as usize).saturating_sub(1)] .try_into() .expect("bound checked in match arm condition; qed"); input.read(&mut r[..])?; @@ -86,8 +86,8 @@ impl Encode for Data { match self { Data::None => vec![0u8; 1], Data::Raw(ref x) => { - let l = x.len().min(128); - let mut r = vec![l as u8 + 1]; + let l = x.len().min(128) as u8; + let mut r = vec![l.saturating_add(1)]; r.extend_from_slice(&x[..]); r } diff --git a/pallets/registry/src/lib.rs b/pallets/registry/src/lib.rs index e54386350..e880deec7 100644 --- a/pallets/registry/src/lib.rs +++ b/pallets/registry/src/lib.rs @@ -16,7 +16,7 @@ use frame_support::traits::tokens::{ fungible::{self, MutateHold as _}, Precision, }; -use sp_runtime::traits::Zero; +use sp_runtime::{traits::Zero, Saturating}; use sp_std::boxed::Box; type BalanceOf = @@ -122,7 +122,7 @@ pub mod pallet { Error::::TooManyFields ); - let fd = >::from(extra_fields) * T::FieldDeposit::get(); + let fd = >::from(extra_fields).saturating_mul(T::FieldDeposit::get()); let mut id = match >::get(&identified) { Some(mut id) => { id.info = *info; @@ -135,23 +135,24 @@ pub mod pallet { }; let old_deposit = id.deposit; - id.deposit = T::InitialDeposit::get() + fd; + id.deposit = T::InitialDeposit::get().saturating_add(fd); if id.deposit > old_deposit { T::Currency::hold( &HoldReason::RegistryIdentity.into(), &who, - id.deposit - old_deposit, + id.deposit.saturating_sub(old_deposit), )?; } if old_deposit > id.deposit { let release_res = T::Currency::release( &HoldReason::RegistryIdentity.into(), &who, - old_deposit - id.deposit, + old_deposit.saturating_sub(id.deposit), Precision::BestEffort, ); - debug_assert!(release_res - .is_ok_and(|released_amount| released_amount == (old_deposit - id.deposit))); + debug_assert!(release_res.is_ok_and( + |released_amount| released_amount == old_deposit.saturating_sub(id.deposit) + )); } >::insert(&identified, id); diff --git a/pallets/registry/src/types.rs b/pallets/registry/src/types.rs index 0573392cd..81826319d 100644 --- a/pallets/registry/src/types.rs +++ b/pallets/registry/src/types.rs @@ -67,7 +67,7 @@ impl Decode for Data { Ok(match b { 0 => Data::None, n @ 1..=65 => { - let mut r: BoundedVec<_, _> = vec![0u8; n as usize - 1] + let mut r: BoundedVec<_, _> = vec![0u8; (n as usize).saturating_sub(1)] .try_into() .expect("bound checked in match arm condition; qed"); input.read(&mut r[..])?; @@ -87,8 +87,8 @@ impl Encode for Data { match self { Data::None => vec![0u8; 1], Data::Raw(ref x) => { - let l = x.len().min(64); - let mut r = vec![l as u8 + 1]; + let l = x.len().min(64) as u8; + let mut r = vec![l.saturating_add(1)]; r.extend_from_slice(&x[..]); r } diff --git a/pallets/subtensor/src/block_step.rs b/pallets/subtensor/src/block_step.rs index 22ded6324..19c235673 100644 --- a/pallets/subtensor/src/block_step.rs +++ b/pallets/subtensor/src/block_step.rs @@ -1,6 +1,7 @@ use super::*; use frame_support::storage::IterableStorageDoubleMap; use frame_support::storage::IterableStorageMap; +use sp_runtime::Saturating; use substrate_fixed::types::I110F18; use substrate_fixed::types::I64F64; use substrate_fixed::types::I96F32; @@ -27,9 +28,10 @@ impl Pallet { Ok(()) } - // Helper function which returns the number of blocks remaining before we will run the epoch on this - // network. Networks run their epoch when (block_number + netuid + 1 ) % (tempo + 1) = 0 - // + #[allow(clippy::arithmetic_side_effects)] + /// Helper function which returns the number of blocks remaining before we will run the epoch on this + /// network. Networks run their epoch when (block_number + netuid + 1 ) % (tempo + 1) = 0 + /// pub fn blocks_until_next_epoch(netuid: u16, tempo: u16, block_number: u64) -> u64 { // tempo | netuid | # first epoch block // 1 0 0 @@ -42,12 +44,16 @@ impl Pallet { if tempo == 0 { return 1000; } - tempo as u64 - (block_number + netuid as u64 + 1) % (tempo as u64 + 1) + (tempo as u64).saturating_sub( + block_number.saturating_add(netuid as u64).saturating_add(1) + % (tempo as u64).saturating_add(1), + ) } - // Helper function returns the number of tuples to drain on a particular step based on - // the remaining tuples to sink and the block number - // + #[allow(clippy::arithmetic_side_effects)] + /// Helper function returns the number of tuples to drain on a particular step based on + /// the remaining tuples to sink and the block number + /// pub fn tuples_to_drain_this_block( netuid: u16, tempo: u16, @@ -55,18 +61,20 @@ impl Pallet { n_remaining: usize, ) -> usize { let blocks_until_epoch: u64 = Self::blocks_until_next_epoch(netuid, tempo, block_number); - if blocks_until_epoch / 2 == 0 { + if blocks_until_epoch.saturating_div(2) == 0 { return n_remaining; } // drain all. - if tempo / 2 == 0 { + if tempo.saturating_div(2) == 0 { return n_remaining; } // drain all if n_remaining == 0 { return 0; } // nothing to drain at all. // Else return enough tuples to drain all within half the epoch length. - let to_sink_via_tempo: usize = n_remaining / (tempo as usize / 2); - let to_sink_via_blocks_until_epoch: usize = n_remaining / (blocks_until_epoch as usize / 2); + let to_sink_via_tempo: usize = + n_remaining.saturating_div((tempo as usize).saturating_div(2)); + let to_sink_via_blocks_until_epoch: usize = + n_remaining.saturating_div((blocks_until_epoch as usize).saturating_div(2)); if to_sink_via_tempo > to_sink_via_blocks_until_epoch { to_sink_via_tempo } else { @@ -95,7 +103,7 @@ impl Pallet { *server_amount, *validator_amount, ); - total_emitted += *server_amount + *validator_amount; + total_emitted.saturating_accrue((*server_amount).saturating_add(*validator_amount)); } LoadedEmission::::remove(netuid); TotalIssuance::::put(TotalIssuance::::get().saturating_add(total_emitted)); @@ -142,7 +150,9 @@ impl Pallet { Self::coinbase(cut.to_num::()); } // --- 5. Add remaining amount to the network's pending emission. - PendingEmission::::mutate(netuid, |queued| *queued += remaining.to_num::()); + PendingEmission::::mutate(netuid, |queued| { + queued.saturating_accrue(remaining.to_num::()) + }); log::debug!( "netuid_i: {:?} queued_emission: +{:?} ", netuid, @@ -154,7 +164,7 @@ impl Pallet { // --- 3.1 No epoch, increase blocks since last step and continue, Self::set_blocks_since_last_step( netuid, - Self::get_blocks_since_last_step(netuid) + 1, + Self::get_blocks_since_last_step(netuid).saturating_add(1), ); continue; } @@ -176,7 +186,7 @@ impl Pallet { // --- 9. Check that the emission does not exceed the allowed total. let emission_sum: u128 = emission_tuples_this_block .iter() - .map(|(_account_id, ve, se)| *ve as u128 + *se as u128) + .map(|(_account_id, ve, se)| (*ve as u128).saturating_add(*se as u128)) .sum(); if emission_sum > emission_to_drain as u128 { continue; @@ -208,7 +218,10 @@ impl Pallet { // --- 1. Check if the hotkey is a delegate. If not, we simply pass the stake through to the // coldkey - hotkey account as normal. if !Self::hotkey_is_delegate(hotkey) { - Self::increase_stake_on_hotkey_account(hotkey, server_emission + validator_emission); + Self::increase_stake_on_hotkey_account( + hotkey, + server_emission.saturating_add(validator_emission), + ); return; } // Then this is a delegate, we distribute validator_emission, then server_emission. @@ -218,7 +231,7 @@ impl Pallet { let total_hotkey_stake: u64 = Self::get_total_stake_for_hotkey(hotkey); let delegate_take: u64 = Self::calculate_delegate_proportional_take(hotkey, validator_emission); - let validator_emission_minus_take: u64 = validator_emission - delegate_take; + let validator_emission_minus_take: u64 = validator_emission.saturating_sub(delegate_take); let mut remaining_validator_emission: u64 = validator_emission_minus_take; // 3. -- The remaining emission goes to the owners in proportion to the stake delegated. @@ -244,14 +257,14 @@ impl Pallet { hotkey, stake_proportion ); - remaining_validator_emission -= stake_proportion; + remaining_validator_emission.saturating_reduce(stake_proportion); } // --- 5. Last increase final account balance of delegate after 4, since 5 will change the stake proportion of // the delegate and effect calculation in 4. Self::increase_stake_on_hotkey_account( hotkey, - delegate_take + remaining_validator_emission, + delegate_take.saturating_add(remaining_validator_emission), ); log::debug!("delkey: {:?} delegate_take: +{:?} ", hotkey, delegate_take); // Also emit the server_emission to the hotkey @@ -311,8 +324,10 @@ impl Pallet { if total_stake == 0 { return 0; }; - let stake_proportion: I64F64 = I64F64::from_num(stake) / I64F64::from_num(total_stake); - let proportional_emission: I64F64 = I64F64::from_num(emission) * stake_proportion; + let stake_proportion: I64F64 = + I64F64::from_num(stake).saturating_div(I64F64::from_num(total_stake)); + let proportional_emission: I64F64 = + I64F64::from_num(emission).saturating_mul(stake_proportion); proportional_emission.to_num::() } @@ -320,9 +335,9 @@ impl Pallet { // pub fn calculate_delegate_proportional_take(hotkey: &T::AccountId, emission: u64) -> u64 { if Self::hotkey_is_delegate(hotkey) { - let take_proportion: I64F64 = - I64F64::from_num(Delegates::::get(hotkey)) / I64F64::from_num(u16::MAX); - let take_emission: I64F64 = take_proportion * I64F64::from_num(emission); + let take_proportion: I64F64 = I64F64::from_num(Delegates::::get(hotkey)) + .saturating_div(I64F64::from_num(u16::MAX)); + let take_emission: I64F64 = take_proportion.saturating_mul(I64F64::from_num(emission)); take_emission.to_num::() } else { 0 @@ -349,7 +364,7 @@ impl Pallet { // --- 3. Check if we are at the adjustment interval for this network. // If so, we need to adjust the registration difficulty based on target and actual registrations. - if (current_block - last_adjustment_block) >= adjustment_interval as u64 { + if current_block.saturating_sub(last_adjustment_block) >= adjustment_interval as u64 { log::debug!("interval reached."); // --- 4. Get the current counters for this network w.r.t burn and difficulty values. @@ -496,14 +511,21 @@ impl Pallet { target_registrations_per_interval: u16, ) -> u64 { let updated_difficulty: I110F18 = I110F18::from_num(current_difficulty) - * I110F18::from_num(registrations_this_interval + target_registrations_per_interval) - / I110F18::from_num( - target_registrations_per_interval + target_registrations_per_interval, + .saturating_mul(I110F18::from_num( + registrations_this_interval.saturating_add(target_registrations_per_interval), + )) + .saturating_div(I110F18::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 next_value: I110F18 = alpha + .saturating_mul(I110F18::from_num(current_difficulty)) + .saturating_add( + I110F18::from_num(1.0) + .saturating_sub(alpha) + .saturating_mul(updated_difficulty), ); - let alpha: I110F18 = - I110F18::from_num(Self::get_adjustment_alpha(netuid)) / I110F18::from_num(u64::MAX); - let next_value: I110F18 = alpha * I110F18::from_num(current_difficulty) - + (I110F18::from_num(1.0) - alpha) * updated_difficulty; if next_value >= I110F18::from_num(Self::get_max_difficulty(netuid)) { Self::get_max_difficulty(netuid) } else if next_value <= I110F18::from_num(Self::get_min_difficulty(netuid)) { @@ -523,14 +545,21 @@ impl Pallet { target_registrations_per_interval: u16, ) -> u64 { let updated_burn: I110F18 = I110F18::from_num(current_burn) - * I110F18::from_num(registrations_this_interval + target_registrations_per_interval) - / I110F18::from_num( - target_registrations_per_interval + target_registrations_per_interval, + .saturating_mul(I110F18::from_num( + registrations_this_interval.saturating_add(target_registrations_per_interval), + )) + .saturating_div(I110F18::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 next_value: I110F18 = alpha + .saturating_mul(I110F18::from_num(current_burn)) + .saturating_add( + I110F18::from_num(1.0) + .saturating_sub(alpha) + .saturating_mul(updated_burn), ); - let alpha: I110F18 = - I110F18::from_num(Self::get_adjustment_alpha(netuid)) / I110F18::from_num(u64::MAX); - let next_value: I110F18 = alpha * I110F18::from_num(current_burn) - + (I110F18::from_num(1.0) - alpha) * updated_burn; if next_value >= I110F18::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)) { diff --git a/pallets/subtensor/src/delegate_info.rs b/pallets/subtensor/src/delegate_info.rs index afd540eba..3227c54b7 100644 --- a/pallets/subtensor/src/delegate_info.rs +++ b/pallets/subtensor/src/delegate_info.rs @@ -52,8 +52,9 @@ impl Pallet { let emission: U64F64 = Self::get_emission_for_uid(*netuid, uid).into(); let tempo: U64F64 = Self::get_tempo(*netuid).into(); - let epochs_per_day: U64F64 = U64F64::from_num(7200) / tempo; - emissions_per_day += emission * epochs_per_day; + let epochs_per_day: U64F64 = U64F64::from_num(7200).saturating_div(tempo); + emissions_per_day = + emissions_per_day.saturating_add(emission.saturating_mul(epochs_per_day)); } } @@ -65,8 +66,9 @@ impl Pallet { let mut return_per_1000: U64F64 = U64F64::from_num(0); if total_stake > U64F64::from_num(0) { - return_per_1000 = (emissions_per_day * U64F64::from_num(0.82)) - / (total_stake / U64F64::from_num(1000)); + return_per_1000 = emissions_per_day + .saturating_mul(U64F64::from_num(0.82)) + .saturating_div(total_stake.saturating_div(U64F64::from_num(1000))); } return DelegateInfo { diff --git a/pallets/subtensor/src/epoch.rs b/pallets/subtensor/src/epoch.rs index 52e81355b..1c1264f8f 100644 --- a/pallets/subtensor/src/epoch.rs +++ b/pallets/subtensor/src/epoch.rs @@ -32,7 +32,7 @@ impl Pallet { // Inactive mask. let inactive: Vec = last_update .iter() - .map(|updated| *updated + activity_cutoff < current_block) + .map(|updated| updated.saturating_add(activity_cutoff) < current_block) .collect(); log::trace!("Inactive:\n{:?}\n", inactive.clone()); @@ -175,9 +175,10 @@ impl Pallet { // log::trace!( "ΔB:\n{:?}\n", &bonds_delta ); // Compute bonds moving average. - let bonds_moving_average: I64F64 = - I64F64::from_num(Self::get_bonds_moving_average(netuid)) / I64F64::from_num(1_000_000); - let alpha: I32F32 = I32F32::from_num(1) - I32F32::from_num(bonds_moving_average); + let bonds_moving_average: I64F64 = I64F64::from_num(Self::get_bonds_moving_average(netuid)) + .saturating_div(I64F64::from_num(1_000_000)); + let alpha: I32F32 = + I32F32::from_num(1).saturating_sub(I32F32::from_num(bonds_moving_average)); let mut ema_bonds: Vec> = mat_ema(&bonds_delta, &bonds, alpha); inplace_col_normalize(&mut ema_bonds); // sum_i b_ij = 1 // log::trace!( "emaB:\n{:?}\n", &ema_bonds ); @@ -198,7 +199,7 @@ impl Pallet { let combined_emission: Vec = incentive .iter() .zip(dividends.clone()) - .map(|(ii, di)| ii + di) + .map(|(ii, di)| ii.saturating_add(di)) .collect(); let emission_sum: I32F32 = combined_emission.iter().sum(); @@ -228,7 +229,7 @@ impl Pallet { let server_emission: Vec = normalized_server_emission .iter() - .map(|se: &I32F32| I96F32::from_num(*se) * float_rao_emission) + .map(|se: &I32F32| I96F32::from_num(*se).saturating_mul(float_rao_emission)) .collect(); let server_emission: Vec = server_emission .iter() @@ -237,7 +238,7 @@ impl Pallet { let validator_emission: Vec = normalized_validator_emission .iter() - .map(|ve: &I32F32| I96F32::from_num(*ve) * float_rao_emission) + .map(|ve: &I32F32| I96F32::from_num(*ve).saturating_mul(float_rao_emission)) .collect(); let validator_emission: Vec = validator_emission .iter() @@ -247,7 +248,7 @@ impl Pallet { // Used only to track combined emission in the storage. let combined_emission: Vec = normalized_combined_emission .iter() - .map(|ce: &I32F32| I96F32::from_num(*ce) * float_rao_emission) + .map(|ce: &I32F32| I96F32::from_num(*ce).saturating_mul(float_rao_emission)) .collect(); let combined_emission: Vec = combined_emission .iter() @@ -376,7 +377,7 @@ impl Pallet { // Inactive mask. let inactive: Vec = last_update .iter() - .map(|updated| *updated + activity_cutoff < current_block) + .map(|updated| updated.saturating_add(activity_cutoff) < current_block) .collect(); log::trace!("Inactive: {:?}", inactive.clone()); @@ -535,9 +536,10 @@ impl Pallet { // log::trace!( "ΔB (norm): {:?}", &bonds_delta ); // Compute bonds moving average. - let bonds_moving_average: I64F64 = - I64F64::from_num(Self::get_bonds_moving_average(netuid)) / I64F64::from_num(1_000_000); - let alpha: I32F32 = I32F32::from_num(1) - I32F32::from_num(bonds_moving_average); + let bonds_moving_average: I64F64 = I64F64::from_num(Self::get_bonds_moving_average(netuid)) + .saturating_div(I64F64::from_num(1_000_000)); + let alpha: I32F32 = + I32F32::from_num(1).saturating_sub(I32F32::from_num(bonds_moving_average)); let mut ema_bonds: Vec> = mat_ema_sparse(&bonds_delta, &bonds, alpha); // Normalize EMA bonds. @@ -558,7 +560,7 @@ impl Pallet { let combined_emission: Vec = incentive .iter() .zip(dividends.clone()) - .map(|(ii, di)| ii + di) + .map(|(ii, di)| ii.saturating_add(di)) .collect(); let emission_sum: I32F32 = combined_emission.iter().sum(); @@ -588,7 +590,7 @@ impl Pallet { let server_emission: Vec = normalized_server_emission .iter() - .map(|se: &I32F32| I96F32::from_num(*se) * float_rao_emission) + .map(|se: &I32F32| I96F32::from_num(*se).saturating_mul(float_rao_emission)) .collect(); let server_emission: Vec = server_emission .iter() @@ -597,7 +599,7 @@ impl Pallet { let validator_emission: Vec = normalized_validator_emission .iter() - .map(|ve: &I32F32| I96F32::from_num(*ve) * float_rao_emission) + .map(|ve: &I32F32| I96F32::from_num(*ve).saturating_mul(float_rao_emission)) .collect(); let validator_emission: Vec = validator_emission .iter() @@ -607,7 +609,7 @@ impl Pallet { // Only used to track emission in storage. let combined_emission: Vec = normalized_combined_emission .iter() - .map(|ce: &I32F32| I96F32::from_num(*ce) * float_rao_emission) + .map(|ce: &I32F32| I96F32::from_num(*ce).saturating_mul(float_rao_emission)) .collect(); let combined_emission: Vec = combined_emission .iter() @@ -704,7 +706,7 @@ impl Pallet { I32F32::from_num(Self::get_rho(netuid)) } pub fn get_float_kappa(netuid: u16) -> I32F32 { - I32F32::from_num(Self::get_kappa(netuid)) / I32F32::from_num(u16::MAX) + I32F32::from_num(Self::get_kappa(netuid)).saturating_div(I32F32::from_num(u16::MAX)) } pub fn get_normalized_stake(netuid: u16) -> Vec { diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index ae403b30f..7f6ae8ee0 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1108,7 +1108,7 @@ pub mod pallet { // Set max allowed uids MaxAllowedUids::::insert(netuid, max_uids); - let mut next_uid = 0; + let mut next_uid = 0u16; for (coldkey, hotkeys) in self.stakes.iter() { for (hotkey, stake_uid) in hotkeys.iter() { @@ -1147,7 +1147,9 @@ pub mod pallet { Stake::::insert(hotkey.clone(), coldkey.clone(), stake); - next_uid += 1; + next_uid = next_uid.checked_add(1).expect( + "should not have total number of hotkey accounts larger than u16::MAX", + ); } } @@ -1155,7 +1157,11 @@ pub mod pallet { SubnetworkN::::insert(netuid, next_uid); // --- Increase total network count. - TotalNetworks::::mutate(|n| *n += 1); + TotalNetworks::::mutate(|n| { + *n = n + .checked_add(1) + .expect("should not have total number of networks larger than u16::MAX") + }); // Get the root network uid. let root_netuid: u16 = 0; @@ -1164,8 +1170,11 @@ pub mod pallet { NetworksAdded::::insert(root_netuid, true); // Increment the number of total networks. - TotalNetworks::::mutate(|n| *n += 1); - + TotalNetworks::::mutate(|n| { + *n = n + .checked_add(1) + .expect("should not have total number of networks larger than u16::MAX") + }); // Set the number of validators to 1. SubnetworkN::::insert(root_netuid, 0); @@ -1178,7 +1187,7 @@ pub mod pallet { // Set the min allowed weights to zero, no weights restrictions. MinAllowedWeights::::insert(root_netuid, 0); - // Set the max weight limit to infitiy, no weight restrictions. + // Set the max weight limit to infinity, no weight restrictions. MaxWeightsLimit::::insert(root_netuid, u16::MAX); // Add default root tempo. @@ -1843,8 +1852,8 @@ pub mod pallet { let _stake = Self::get_total_stake_for_hotkey(hotkey); let current_block_number: u64 = Self::get_current_block_as_u64(); let default_priority: u64 = - current_block_number - Self::get_last_update_for_uid(netuid, uid); - return default_priority + u32::max_value() as u64; + current_block_number.saturating_sub(Self::get_last_update_for_uid(netuid, uid)); + return default_priority.saturating_add(u32::max_value() as u64); } 0 } @@ -1871,7 +1880,7 @@ pub mod pallet { return false; } if Self::get_registrations_this_interval(netuid) - >= Self::get_target_registrations_per_interval(netuid) * 3 + >= Self::get_target_registrations_per_interval(netuid).saturating_mul(3) { return false; } @@ -1988,7 +1997,7 @@ where Pallet::::get_registrations_this_interval(*netuid); let max_registrations_per_interval = Pallet::::get_target_registrations_per_interval(*netuid); - if registrations_this_interval >= (max_registrations_per_interval * 3) { + if registrations_this_interval >= (max_registrations_per_interval.saturating_mul(3)) { // If the registration limit for the interval is exceeded, reject the transaction return InvalidTransaction::ExhaustsResources.into(); } diff --git a/pallets/subtensor/src/math.rs b/pallets/subtensor/src/math.rs index 9256206cd..efea009bd 100644 --- a/pallets/subtensor/src/math.rs +++ b/pallets/subtensor/src/math.rs @@ -1,5 +1,5 @@ use frame_support::sp_std::vec; -use sp_runtime::traits::CheckedAdd; +use sp_runtime::{traits::CheckedAdd, Saturating}; use substrate_fixed::transcendental::exp; use substrate_fixed::types::{I32F32, I64F64}; @@ -44,12 +44,12 @@ pub fn u16_to_fixed(x: u16) -> I32F32 { #[allow(dead_code)] pub fn u16_proportion_to_fixed(x: u16) -> I32F32 { - I32F32::from_num(x) / I32F32::from_num(u16::MAX) + I32F32::from_num(x).saturating_div(I32F32::from_num(u16::MAX)) } #[allow(dead_code)] pub fn fixed_proportion_to_u16(x: I32F32) -> u16 { - fixed_to_u16(x * I32F32::from_num(u16::MAX)) + fixed_to_u16(x.saturating_mul(I32F32::from_num(u16::MAX))) } #[allow(dead_code)] @@ -93,25 +93,38 @@ pub fn vec_max_upscale_to_u16(vec: &[I32F32]) -> Vec { if *val == I32F32::from_num(0) { return vec .iter() - .map(|e: &I32F32| (e * u16_max).to_num::()) + .map(|e: &I32F32| e.saturating_mul(u16_max).to_num::()) .collect(); } if *val > threshold { return vec .iter() - .map(|e: &I32F32| (e * (u16_max / *val)).round().to_num::()) + .map(|e: &I32F32| { + e.saturating_mul(u16_max.saturating_div(*val)) + .round() + .to_num::() + }) .collect(); } return vec .iter() - .map(|e: &I32F32| ((e * u16_max) / *val).round().to_num::()) + .map(|e: &I32F32| { + e.saturating_mul(u16_max) + .saturating_div(*val) + .round() + .to_num::() + }) .collect(); } None => { let sum: I32F32 = vec.iter().sum(); return vec .iter() - .map(|e: &I32F32| ((e * u16_max) / sum).to_num::()) + .map(|e: &I32F32| { + e.saturating_mul(u16_max) + .saturating_div(sum) + .to_num::() + }) .collect(); } } @@ -127,7 +140,8 @@ pub fn vec_u16_max_upscale_to_u16(vec: &[u16]) -> Vec { #[allow(dead_code)] // 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) / I32F32::from_num(u16::MAX); + 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(); inplace_normalize(&mut vec_fixed); let max_value: Option<&I32F32> = vec_fixed.iter().max(); @@ -196,7 +210,7 @@ pub fn exp_safe(input: I32F32) -> I32F32 { pub fn sigmoid_safe(input: I32F32, rho: I32F32, kappa: I32F32) -> I32F32 { let one: I32F32 = I32F32::from_num(1); let offset: I32F32 = input.saturating_sub(kappa); // (input - kappa) - let neg_rho: I32F32 = rho.saturating_mul(-one); // -rho + 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)) @@ -214,7 +228,7 @@ pub fn is_topk(vector: &[I32F32], k: usize) -> Vec { } let mut idxs: Vec = (0..n).collect(); idxs.sort_by_key(|&idx| &vector[idx]); // ascending stable sort - for &idx in idxs.iter().take(n - k) { + for &idx in idxs.iter().take(n.saturating_sub(k)) { result[idx] = false; } result @@ -225,7 +239,7 @@ pub fn is_topk(vector: &[I32F32], k: usize) -> Vec { pub fn normalize(x: &[I32F32]) -> Vec { let x_sum: I32F32 = sum(x); if x_sum != I32F32::from_num(0.0_f32) { - return x.iter().map(|xi| xi / x_sum).collect(); + return x.iter().map(|xi| xi.saturating_div(x_sum)).collect(); } else { x.to_vec() } @@ -238,7 +252,8 @@ pub fn inplace_normalize(x: &mut [I32F32]) { if x_sum == I32F32::from_num(0.0_f32) { return; } - x.into_iter().for_each(|value| *value /= x_sum); + x.into_iter() + .for_each(|value| *value = value.saturating_div(x_sum)); } // Normalizes (sum to 1 except 0) the input vector directly in-place, using the sum arg. @@ -247,7 +262,8 @@ pub fn inplace_normalize_using_sum(x: &mut [I32F32], x_sum: I32F32) { if x_sum == I32F32::from_num(0.0_f32) { return; } - x.into_iter().for_each(|value| *value /= x_sum); + x.into_iter() + .for_each(|value| *value = value.saturating_div(x_sum)); } // Normalizes (sum to 1 except 0) the I64F64 input vector directly in-place. @@ -257,7 +273,8 @@ pub fn inplace_normalize_64(x: &mut [I64F64]) { if x_sum == I64F64::from_num(0) { return; } - x.into_iter().for_each(|value| *value /= x_sum); + x.into_iter() + .for_each(|value| *value = value.saturating_div(x_sum)); } /// Returns x / y for input vectors x and y, if y == 0 return 0. @@ -268,7 +285,7 @@ pub fn vecdiv(x: &[I32F32], y: &[I32F32]) -> Vec { .zip(y) .map(|(x_i, y_i)| { if *y_i != 0 { - x_i / y_i + x_i.saturating_div(*y_i) } else { I32F32::from_num(0) } @@ -281,9 +298,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 as f32) { + if row_sum > I32F32::from_num(0.0_f32) { row.into_iter() - .for_each(|x_ij: &mut I32F32| *x_ij /= row_sum); + .for_each(|x_ij: &mut I32F32| *x_ij = x_ij.saturating_div(row_sum)); } } } @@ -296,7 +313,7 @@ pub fn inplace_row_normalize_sparse(sparse_matrix: &mut [Vec<(u16, I32F32)>]) { if row_sum > I32F32::from_num(0.0) { sparse_row .iter_mut() - .for_each(|(_j, value)| *value /= row_sum); + .for_each(|(_j, value)| *value = value.saturating_div(row_sum)); } } } @@ -335,7 +352,7 @@ pub fn col_sum(x: &[Vec]) -> Vec { .fold(vec![I32F32::from_num(0); cols], |acc, next_row| { acc.into_iter() .zip(next_row) - .map(|(acc_elem, next_elem)| acc_elem + next_elem) + .map(|(acc_elem, next_elem)| acc_elem.saturating_add(*next_elem)) .collect() }) } @@ -346,7 +363,7 @@ 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]; for sparse_row in sparse_matrix { for (j, value) in sparse_row { - result[*j as usize] += value; + result[*j as usize] = result[*j as usize].saturating_add(*value); } } result @@ -358,7 +375,7 @@ pub fn inplace_col_normalize_sparse(sparse_matrix: &mut [Vec<(u16, I32F32)>], co let mut col_sum: Vec = vec![I32F32::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] += value; + col_sum[*j as usize] = col_sum[*j as usize].saturating_add(*value); } } for sparse_row in sparse_matrix { @@ -366,7 +383,7 @@ pub fn inplace_col_normalize_sparse(sparse_matrix: &mut [Vec<(u16, I32F32)>], co if col_sum[*j as usize] == I32F32::from_num(0.0_f32) { continue; } - *value /= col_sum[*j as usize]; + *value = value.saturating_div(col_sum[*j as usize]); } } } @@ -386,7 +403,7 @@ pub fn inplace_col_normalize(x: &mut [Vec]) { .fold(vec![I32F32::from_num(0.0); cols], |acc, row| { row.into_iter() .zip(acc) - .map(|(&mut m_val, acc_val)| acc_val + m_val) + .map(|(&mut m_val, acc_val)| acc_val.saturating_add(m_val)) .collect() }); x.into_iter().for_each(|row| { @@ -394,7 +411,7 @@ pub fn inplace_col_normalize(x: &mut [Vec]) { .zip(&col_sums) .filter(|(_, col_sum)| **col_sum != I32F32::from_num(0_f32)) .for_each(|(m_val, col_sum)| { - *m_val /= col_sum; + *m_val = m_val.saturating_div(*col_sum); }); }); } @@ -415,7 +432,7 @@ pub fn inplace_col_max_upscale_sparse(sparse_matrix: &mut [Vec<(u16, I32F32)>], if col_max[*j as usize] == I32F32::from_num(0.0_f32) { continue; } - *value /= col_max[*j as usize]; + *value = value.saturating_div(col_max[*j as usize]); } } } @@ -443,7 +460,7 @@ pub fn inplace_col_max_upscale(x: &mut [Vec]) { .zip(&col_maxes) .filter(|(_, col_max)| **col_max != I32F32::from_num(0)) .for_each(|(m_val, col_max)| { - *m_val /= col_max; + *m_val = m_val.saturating_div(*col_max); }); }); } @@ -594,7 +611,11 @@ pub fn row_hadamard(matrix: &[Vec], vector: &[I32F32]) -> Vec], vector: &[I32F32]) -> Vec { // Compute ranks: r_j = SUM(i) w_ij * s_i // Compute trust scores: t_j = SUM(i) w_ij * s_i // result_j = SUM(i) vector_i * matrix_ij - acc_val + vec_val * m_val + acc_val.saturating_add(vec_val.saturating_mul(*m_val)) }) .collect() }, @@ -664,7 +685,7 @@ pub fn matmul_64(matrix: &[Vec], vector: &[I64F64]) -> Vec { // Compute ranks: r_j = SUM(i) w_ij * s_i // Compute trust scores: t_j = SUM(i) w_ij * s_i // result_j = SUM(i) vector_i * matrix_ij - acc_val + vec_val * m_val + acc_val.saturating_add(vec_val.saturating_mul(*m_val)) }) .collect() }) @@ -689,7 +710,7 @@ pub fn matmul_transpose(matrix: &[Vec], vector: &[I32F32]) -> Vec = (0..use_stake.len()).collect(); - let minority: I32F32 = stake_sum - majority; + let minority: I32F32 = stake_sum.saturating_sub(majority); let mut use_score: Vec> = vec![vec![zero; use_stake.len()]; columns as usize]; let mut median: Vec = vec![zero; columns as usize]; let mut k: usize = 0; @@ -968,7 +992,7 @@ pub fn weighted_median_col_sparse( for (c, val) in score[r].iter() { use_score[*c as usize][k] = *val; } - k += 1; + k.saturating_inc(); } for c in 0..columns as usize { median[c] = weighted_median( @@ -999,7 +1023,7 @@ pub fn hadamard(mat1: &[Vec], mat2: &[Vec]) -> Vec> assert!(row1.len() == row2.len()); row1.iter() .zip(row2) - .map(|(elem1, elem2)| elem1 * elem2) + .map(|(elem1, elem2)| elem1.saturating_mul(*elem2)) .collect() }) .collect() @@ -1019,14 +1043,14 @@ pub fn hadamard_sparse( for i in 0..rows { let mut row1: Vec = vec![zero; columns as usize]; for (j, value) in mat1[i].iter() { - row1[*j as usize] += value; + row1[*j as usize] = row1[*j as usize].saturating_add(*value); } let mut row2: Vec = vec![zero; columns as usize]; for (j, value) in mat2[i].iter() { - row2[*j as usize] += value; + row2[*j as usize] = row2[*j as usize].saturating_add(*value); } for j in 0..columns as usize { - let prod: I32F32 = row1[j] * row2[j]; + let prod: I32F32 = row1[j].saturating_mul(row2[j]); if zero < prod { result[i].push((j as u16, prod)) } @@ -1046,14 +1070,18 @@ pub fn mat_ema(new: &[Vec], old: &[Vec], alpha: I32F32) -> Vec> = vec![vec![]; n]; for i in 0..new.len() { let mut row: Vec = vec![zero; n]; for (j, value) in new[i].iter() { - row[*j as usize] += alpha * value; + row[*j as usize] = row[*j as usize].saturating_add(alpha.saturating_mul(*value)); } for (j, value) in old[i].iter() { - row[*j as usize] += one_minus_alpha * value; + row[*j as usize] = + row[*j as usize].saturating_add(one_minus_alpha.saturating_mul(*value)); } for (j, value) in row.iter().enumerate() { if *value > zero { @@ -1104,7 +1133,7 @@ pub fn sparse_threshold(w: &[Vec<(u16, I32F32)>], threshold: I32F32) -> Vec(test: bool) -> Weight { weight = weight.saturating_add(T::DbWeight::get().reads(1)); // Compute the total issuance value - let total_issuance_value: u64 = stake_sum + total_balance_sum + locked_sum; + let total_issuance_value: u64 = stake_sum + .saturating_add(total_balance_sum) + .saturating_add(locked_sum); // Update the total issuance in storage TotalIssuance::::put(total_issuance_value); @@ -134,7 +137,7 @@ pub fn migrate_create_root_network() -> Weight { NetworksAdded::::insert(root_netuid, true); // Increment the number of total networks. - TotalNetworks::::mutate(|n| *n += 1); + TotalNetworks::::mutate(|n| n.saturating_inc()); // Set the maximum number to the number of senate members. MaxAllowedUids::::insert(root_netuid, 64); @@ -201,7 +204,7 @@ pub fn migrate_delete_subnet_3() -> Weight { NetworksAdded::::remove(netuid); // --- 6. Decrement the network counter. - TotalNetworks::::mutate(|n| *n -= 1); + TotalNetworks::::mutate(|n| n.saturating_dec()); // --- 7. Remove various network-related storages. NetworkRegisteredAt::::remove(netuid); @@ -285,7 +288,7 @@ pub fn migrate_delete_subnet_21() -> Weight { NetworksAdded::::remove(netuid); // --- 6. Decrement the network counter. - TotalNetworks::::mutate(|n| *n -= 1); + TotalNetworks::::mutate(|n| n.saturating_dec()); // --- 7. Remove various network-related storages. NetworkRegisteredAt::::remove(netuid); diff --git a/pallets/subtensor/src/registration.rs b/pallets/subtensor/src/registration.rs index 8b44b3e5f..355b67253 100644 --- a/pallets/subtensor/src/registration.rs +++ b/pallets/subtensor/src/registration.rs @@ -3,7 +3,7 @@ use frame_support::pallet_prelude::{DispatchResult, DispatchResultWithPostInfo}; use frame_support::storage::IterableStorageDoubleMap; use sp_core::{Get, H256, U256}; use sp_io::hashing::{keccak_256, sha2_256}; -use sp_runtime::MultiAddress; +use sp_runtime::{MultiAddress, Saturating}; use system::pallet_prelude::BlockNumberFor; const LOG_TARGET: &str = "runtime::subtensor::registration"; @@ -76,7 +76,7 @@ impl Pallet { // --- 4. Ensure we are not exceeding the max allowed registrations per interval. ensure!( Self::get_registrations_this_interval(netuid) - < Self::get_target_registrations_per_interval(netuid) * 3, + < Self::get_target_registrations_per_interval(netuid).saturating_mul(3), Error::::TooManyRegistrationsThisInterval ); @@ -145,9 +145,9 @@ impl Pallet { } // --- 14. Record the registration and increment block and interval counters. - BurnRegistrationsThisInterval::::mutate(netuid, |val| *val += 1); - RegistrationsThisInterval::::mutate(netuid, |val| *val += 1); - RegistrationsThisBlock::::mutate(netuid, |val| *val += 1); + BurnRegistrationsThisInterval::::mutate(netuid, |val| val.saturating_inc()); + RegistrationsThisInterval::::mutate(netuid, |val| val.saturating_inc()); + RegistrationsThisBlock::::mutate(netuid, |val| val.saturating_inc()); Self::increase_rao_recycled(netuid, Self::get_burn_as_u64(netuid)); // --- 15. Deposit successful event. @@ -258,7 +258,7 @@ impl Pallet { // --- 5. Ensure we are not exceeding the max allowed registrations per interval. ensure!( Self::get_registrations_this_interval(netuid) - < Self::get_target_registrations_per_interval(netuid) * 3, + < Self::get_target_registrations_per_interval(netuid).saturating_mul(3), Error::::TooManyRegistrationsThisInterval ); @@ -276,7 +276,7 @@ impl Pallet { Error::::InvalidWorkBlock ); ensure!( - current_block_number - block_number < 3, + current_block_number.saturating_sub(block_number) < 3, Error::::InvalidWorkBlock ); @@ -337,9 +337,9 @@ impl Pallet { } // --- 12. Record the registration and increment block and interval counters. - POWRegistrationsThisInterval::::mutate(netuid, |val| *val += 1); - RegistrationsThisInterval::::mutate(netuid, |val| *val += 1); - RegistrationsThisBlock::::mutate(netuid, |val| *val += 1); + POWRegistrationsThisInterval::::mutate(netuid, |val| val.saturating_inc()); + RegistrationsThisInterval::::mutate(netuid, |val| val.saturating_inc()); + RegistrationsThisBlock::::mutate(netuid, |val| val.saturating_inc()); // --- 13. Deposit successful event. log::info!( @@ -375,7 +375,7 @@ impl Pallet { Error::::InvalidWorkBlock ); ensure!( - current_block_number - block_number < 3, + current_block_number.saturating_sub(block_number) < 3, Error::::InvalidWorkBlock ); @@ -439,7 +439,7 @@ impl Pallet { Self::get_neuron_block_at_registration(netuid, neuron_uid_i); #[allow(clippy::comparison_chain)] if min_score == pruning_score { - if current_block - block_at_registration < immunity_period { + if current_block.saturating_sub(block_at_registration) < immunity_period { //neuron is in immunity period if min_score_in_immunity_period > pruning_score { min_score_in_immunity_period = pruning_score; @@ -451,7 +451,7 @@ impl Pallet { } // Find min pruning score. else if min_score > pruning_score { - if current_block - block_at_registration < immunity_period { + if current_block.saturating_sub(block_at_registration) < immunity_period { //neuron is in immunity period if min_score_in_immunity_period > pruning_score { min_score_in_immunity_period = pruning_score; @@ -588,7 +588,7 @@ impl Pallet { let mut nonce: u64 = start_nonce; let mut work: H256 = Self::create_seal_hash(block_number, nonce, hotkey); while !Self::hash_meets_difficulty(&work, difficulty) { - nonce += 1; + nonce.saturating_inc(); work = Self::create_seal_hash(block_number, nonce, hotkey); } let vec_work: Vec = Self::hash_to_vec(work); @@ -622,8 +622,9 @@ impl Pallet { Error::::AlreadyRegistered ); - weight - .saturating_accrue(T::DbWeight::get().reads((TotalNetworks::::get() + 1u16) as u64)); + weight.saturating_accrue( + T::DbWeight::get().reads((TotalNetworks::::get().saturating_add(1)) as u64), + ); let swap_cost = 1_000_000_000u64; ensure!( diff --git a/pallets/subtensor/src/root.rs b/pallets/subtensor/src/root.rs index 196311ce3..197fc4549 100644 --- a/pallets/subtensor/src/root.rs +++ b/pallets/subtensor/src/root.rs @@ -22,6 +22,7 @@ use frame_support::sp_std::vec; use frame_support::storage::{IterableStorageDoubleMap, IterableStorageMap}; use frame_support::traits::Get; use frame_support::weights::Weight; +use sp_runtime::Saturating; use substrate_fixed::{ transcendental::log2, types::{I64F64, I96F32}, @@ -152,9 +153,17 @@ impl Pallet { // Calculate the logarithmic residual of the issuance against half the total supply. let residual: I96F32 = log2( I96F32::from_num(1.0) - / (I96F32::from_num(1.0) - - total_issuance - / (I96F32::from_num(2.0) * I96F32::from_num(10_500_000_000_000_000.0))), + .checked_div( + I96F32::from_num(1.0) + .checked_sub(total_issuance) + .ok_or("Logarithm calculation failed")? + .checked_div( + I96F32::from_num(2.0) + .saturating_mul(I96F32::from_num(10_500_000_000_000_000.0)), + ) + .ok_or("Logarithm calculation failed")?, + ) + .ok_or("Logarithm calculation failed")?, ) .map_err(|_| "Logarithm calculation failed")?; // Floor the residual to smooth out the emission rate. @@ -165,12 +174,12 @@ impl Pallet { // Multiply 2.0 by itself floored_residual times to calculate the power of 2. let mut multiplier: I96F32 = I96F32::from_num(1.0); for _ in 0..floored_residual_int { - multiplier *= I96F32::from_num(2.0); + multiplier = multiplier.saturating_mul(I96F32::from_num(2.0)); } - let block_emission_percentage: I96F32 = I96F32::from_num(1.0) / multiplier; + let block_emission_percentage: I96F32 = I96F32::from_num(1.0).saturating_div(multiplier); // Calculate the actual emission based on the emission rate - let block_emission: I96F32 = - block_emission_percentage * I96F32::from_num(DefaultBlockEmission::::get()); + let block_emission: I96F32 = block_emission_percentage + .saturating_mul(I96F32::from_num(DefaultBlockEmission::::get())); // Convert to u64 let block_emission_u64: u64 = block_emission.to_num::(); if BlockEmission::::get() != block_emission_u64 { @@ -378,10 +387,10 @@ impl Pallet { let mut trust = vec![I64F64::from_num(0); total_networks as usize]; let mut total_stake: I64F64 = I64F64::from_num(0); for (weights, hotkey_stake) in weights.iter().zip(stake_i64) { - total_stake += hotkey_stake; + total_stake = total_stake.saturating_add(hotkey_stake); for (weight, trust_score) in weights.iter().zip(&mut trust) { if *weight > 0 { - *trust_score += hotkey_stake; + *trust_score = trust_score.saturating_add(hotkey_stake); } } } @@ -405,13 +414,15 @@ impl Pallet { let one = I64F64::from_num(1); let mut consensus = vec![I64F64::from_num(0); total_networks as usize]; for (trust_score, consensus_i) in trust.iter_mut().zip(&mut consensus) { - let shifted_trust = *trust_score - I64F64::from_num(Self::get_float_kappa(0)); // Range( -kappa, 1 - kappa ) - let temperatured_trust = shifted_trust * I64F64::from_num(Self::get_rho(0)); // Range( -rho * kappa, rho ( 1 - kappa ) ) + let shifted_trust = + trust_score.saturating_sub(I64F64::from_num(Self::get_float_kappa(0))); // Range( -kappa, 1 - kappa ) + let temperatured_trust = + shifted_trust.saturating_mul(I64F64::from_num(Self::get_rho(0))); // Range( -rho * kappa, rho ( 1 - kappa ) ) let exponentiated_trust: I64F64 = - substrate_fixed::transcendental::exp(-temperatured_trust) + substrate_fixed::transcendental::exp(temperatured_trust.saturating_neg()) .expect("temperatured_trust is on range( -rho * kappa, rho ( 1 - kappa ) )"); - *consensus_i = one / (one + exponentiated_trust); + *consensus_i = one.saturating_div(one.saturating_add(exponentiated_trust)); } log::debug!("C:\n{:?}\n", &consensus); @@ -419,7 +430,7 @@ impl Pallet { for ((emission, consensus_i), rank) in weighted_emission.iter_mut().zip(&consensus).zip(&ranks) { - *emission = *consensus_i * (*rank); + *emission = consensus_i.saturating_mul(*rank); } inplace_normalize_64(&mut weighted_emission); log::debug!("Ei64:\n{:?}\n", &weighted_emission); @@ -427,7 +438,7 @@ impl Pallet { // -- 11. Converts the normalized 64-bit fixed point rank values to u64 for the final emission calculation. let emission_as_tao: Vec = weighted_emission .iter() - .map(|v: &I64F64| *v * block_emission) + .map(|v: &I64F64| v.saturating_mul(block_emission)) .collect(); // --- 12. Converts the normalized 64-bit fixed point rank values to u64 for the final emission calculation. @@ -480,7 +491,7 @@ impl Pallet { // --- 3. Ensure that the number of registrations in this interval doesn't exceed thrice the target limit. ensure!( Self::get_registrations_this_interval(root_netuid) - < Self::get_target_registrations_per_interval(root_netuid) * 3, + < Self::get_target_registrations_per_interval(root_netuid).saturating_mul(3), Error::::TooManyRegistrationsThisInterval ); @@ -578,8 +589,8 @@ impl Pallet { } // --- 14. Update the registration counters for both the block and interval. - RegistrationsThisInterval::::mutate(root_netuid, |val| *val += 1); - RegistrationsThisBlock::::mutate(root_netuid, |val| *val += 1); + RegistrationsThisInterval::::mutate(root_netuid, |val| val.saturating_inc()); + RegistrationsThisBlock::::mutate(root_netuid, |val| val.saturating_inc()); // --- 15. Log and announce the successful registration. log::info!( @@ -684,7 +695,7 @@ impl Pallet { // We subtract one because we don't want root subnet to count towards total let mut next_available_netuid = 0; loop { - next_available_netuid += 1; + next_available_netuid.saturating_inc(); if !Self::if_subnet_exist(next_available_netuid) { log::debug!("got subnet id: {:?}", next_available_netuid); break next_available_netuid; @@ -783,7 +794,7 @@ impl Pallet { NetworkModality::::insert(netuid, 0); // --- 5. Increase total network count. - TotalNetworks::::mutate(|n| *n += 1); + TotalNetworks::::mutate(|n| n.saturating_dec()); // --- 6. Set all default values **explicitly**. Self::set_network_registration_allowed(netuid, true); @@ -875,7 +886,7 @@ impl Pallet { NetworksAdded::::remove(netuid); // --- 6. Decrement the network counter. - TotalNetworks::::mutate(|n| *n -= 1); + TotalNetworks::::mutate(|n| n.saturating_dec()); // --- 7. Remove various network-related storages. NetworkRegisteredAt::::remove(netuid); @@ -939,24 +950,25 @@ impl Pallet { SubnetOwner::::remove(netuid); } - // This function calculates the lock cost for a network based on the last lock amount, minimum lock cost, last lock block, and current block. - // The lock cost is calculated using the formula: - // lock_cost = (last_lock * mult) - (last_lock / lock_reduction_interval) * (current_block - last_lock_block) - // where: - // - last_lock is the last lock amount for the network - // - mult is the multiplier which increases lock cost each time a registration occurs - // - last_lock_block is the block number at which the last lock occurred - // - lock_reduction_interval the number of blocks before the lock returns to previous value. - // - current_block is the current block number - // - DAYS is the number of blocks in a day - // - min_lock is the minimum lock cost for the network - // - // If the calculated lock cost is less than the minimum lock cost, the minimum lock cost is returned. - // - // # Returns: - // * 'u64': - // - The lock cost for the network. - // + #[allow(clippy::arithmetic_side_effects)] + /// This function calculates the lock cost for a network based on the last lock amount, minimum lock cost, last lock block, and current block. + /// The lock cost is calculated using the formula: + /// lock_cost = (last_lock * mult) - (last_lock / lock_reduction_interval) * (current_block - last_lock_block) + /// where: + /// - last_lock is the last lock amount for the network + /// - mult is the multiplier which increases lock cost each time a registration occurs + /// - last_lock_block is the block number at which the last lock occurred + /// - lock_reduction_interval the number of blocks before the lock returns to previous value. + /// - current_block is the current block number + /// - DAYS is the number of blocks in a day + /// - min_lock is the minimum lock cost for the network + /// + /// If the calculated lock cost is less than the minimum lock cost, the minimum lock cost is returned. + /// + /// # Returns: + /// * 'u64': + /// - The lock cost for the network. + /// pub fn get_network_lock_cost() -> u64 { let last_lock = Self::get_network_last_lock(); let min_lock = Self::get_network_min_lock(); diff --git a/pallets/subtensor/src/serving.rs b/pallets/subtensor/src/serving.rs index 84546bd19..4b34f4576 100644 --- a/pallets/subtensor/src/serving.rs +++ b/pallets/subtensor/src/serving.rs @@ -222,7 +222,7 @@ impl Pallet { ) -> bool { let rate_limit: u64 = Self::get_serving_rate_limit(netuid); let last_serve = prev_axon_info.block; - rate_limit == 0 || last_serve == 0 || current_block - last_serve >= rate_limit + rate_limit == 0 || last_serve == 0 || current_block.saturating_sub(last_serve) >= rate_limit } pub fn prometheus_passes_rate_limit( @@ -232,7 +232,7 @@ impl Pallet { ) -> bool { let rate_limit: u64 = Self::get_serving_rate_limit(netuid); let last_serve = prev_prometheus_info.block; - rate_limit == 0 || last_serve == 0 || current_block - last_serve >= rate_limit + rate_limit == 0 || last_serve == 0 || current_block.saturating_sub(last_serve) >= rate_limit } pub fn has_axon_info(netuid: u16, hotkey: &T::AccountId) -> bool { diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index dd7f78367..ba54bf926 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -343,7 +343,7 @@ impl Pallet { Self::set_stakes_this_interval_for_coldkey_hotkey( &coldkey, &hotkey, - stakes_this_interval + 1, + stakes_this_interval.saturating_add(1), block, ); log::info!( @@ -456,7 +456,7 @@ impl Pallet { Self::set_stakes_this_interval_for_coldkey_hotkey( &coldkey, &hotkey, - unstakes_this_interval + 1, + unstakes_this_interval.saturating_add(1), block, ); log::info!( @@ -534,7 +534,7 @@ impl Pallet { TotalHotkeyColdkeyStakesThisInterval::::get(coldkey, hotkey); // Calculate the block number after which the stakes for the hotkey should be reset. - let block_to_reset_after = block_last_staked_at + stake_interval; + let block_to_reset_after = block_last_staked_at.saturating_add(stake_interval); // If the current block number is beyond the reset point, // it indicates the end of the staking interval for the hotkey. diff --git a/pallets/subtensor/src/subnet_info.rs b/pallets/subtensor/src/subnet_info.rs index df768b0cd..6985047c8 100644 --- a/pallets/subtensor/src/subnet_info.rs +++ b/pallets/subtensor/src/subnet_info.rs @@ -115,7 +115,7 @@ impl Pallet { } let mut subnets_info = Vec::>>::new(); - for netuid_ in 0..(max_netuid + 1) { + for netuid_ in 0..=max_netuid { if subnet_netuids.contains(&netuid_) { subnets_info.push(Self::get_subnet_info(netuid_)); } diff --git a/pallets/subtensor/src/uids.rs b/pallets/subtensor/src/uids.rs index d0628a044..3ffe998f9 100644 --- a/pallets/subtensor/src/uids.rs +++ b/pallets/subtensor/src/uids.rs @@ -60,7 +60,7 @@ impl Pallet { ); // 2. Get and increase the uid count. - SubnetworkN::::insert(netuid, next_uid + 1); + SubnetworkN::::insert(netuid, next_uid.saturating_add(1)); // 3. Expand Yuma Consensus with new position. Rank::::mutate(netuid, |v| v.push(0)); @@ -125,11 +125,7 @@ impl Pallet { // Return the total number of subnetworks available on the chain. // pub fn get_number_of_subnets() -> u16 { - let mut number_of_subnets: u16 = 0; - for (_, _) in as IterableStorageMap>::iter() { - number_of_subnets += 1; - } - number_of_subnets + as IterableStorageMap>::iter().count() as u16 } // Return a list of all networks a hotkey is registered on. diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index cc126512b..7ee97bc6f 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -288,7 +288,7 @@ impl Pallet { return false; } - current_block - prev_tx_block <= rate_limit + current_block.saturating_sub(prev_tx_block) <= rate_limit } pub fn exceeds_tx_delegate_take_rate_limit(prev_tx_block: u64, current_block: u64) -> bool { let rate_limit: u64 = Self::get_tx_delegate_take_rate_limit(); @@ -296,7 +296,7 @@ impl Pallet { return false; } - return current_block - prev_tx_block <= rate_limit; + current_block.saturating_sub(prev_tx_block) <= rate_limit } // ======================== diff --git a/pallets/subtensor/src/weights.rs b/pallets/subtensor/src/weights.rs index c912af61d..032bffd08 100644 --- a/pallets/subtensor/src/weights.rs +++ b/pallets/subtensor/src/weights.rs @@ -215,7 +215,8 @@ impl Pallet { if last_set_weights == 0 { return true; } // (Storage default) Never set weights. - return (current_block - last_set_weights) >= Self::get_weights_set_rate_limit(netuid); + return current_block.saturating_sub(last_set_weights) + >= Self::get_weights_set_rate_limit(netuid); } // --- 3. Non registered peers cant pass. false @@ -288,14 +289,17 @@ impl Pallet { false } - // Implace normalizes the passed positive integer weights so that they sum to u16 max value. + #[allow(clippy::arithmetic_side_effects)] + /// Returns normalized the passed positive integer weights so that they sum to u16 max value. pub fn normalize_weights(mut weights: Vec) -> Vec { let sum: u64 = weights.iter().map(|x| *x as u64).sum(); if sum == 0 { return weights; } weights.iter_mut().for_each(|x| { - *x = (*x as u64 * u16::max_value() as u64 / sum) as u16; + *x = (*x as u64) + .saturating_mul(u16::max_value() as u64) + .saturating_div(sum) as u16; }); weights } diff --git a/runtime/src/check_nonce.rs b/runtime/src/check_nonce.rs new file mode 100644 index 000000000..4000e2192 --- /dev/null +++ b/runtime/src/check_nonce.rs @@ -0,0 +1,128 @@ +use codec::{Decode, Encode}; +use frame_support::dispatch::{DispatchInfo, Pays}; +use frame_system::Config; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{DispatchInfoOf, Dispatchable, One, SignedExtension, Zero}, + transaction_validity::{ + InvalidTransaction, TransactionLongevity, TransactionValidity, TransactionValidityError, + ValidTransaction, + }, + Saturating, +}; +use sp_std::vec; + +/// Nonce check and increment to give replay protection for transactions. +/// +/// # Transaction Validity +/// +/// This extension affects `requires` and `provides` tags of validity, but DOES NOT +/// set the `priority` field. Make sure that AT LEAST one of the signed extension sets +/// some kind of priority upon validating transactions. +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct CheckNonce(#[codec(compact)] pub T::Nonce); + +impl CheckNonce { + /// utility constructor. Used only in client/factory code. + pub fn from(nonce: T::Nonce) -> Self { + Self(nonce) + } +} + +impl sp_std::fmt::Debug for CheckNonce { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "CheckNonce({})", self.0) + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +impl SignedExtension for CheckNonce +where + T::RuntimeCall: Dispatchable, +{ + type AccountId = T::AccountId; + type Call = T::RuntimeCall; + type AdditionalSigned = (); + type Pre = (); + const IDENTIFIER: &'static str = "CheckNonce"; + + fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { + Ok(()) + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + _call: &Self::Call, + info: &DispatchInfoOf, + _len: usize, + ) -> Result<(), TransactionValidityError> { + let mut account = frame_system::Account::::get(who); + match info.pays_fee { + Pays::Yes => { + if account.providers.is_zero() && account.sufficients.is_zero() { + // Nonce storage not paid for + return Err(InvalidTransaction::Payment.into()); + } + } + // not check providers and sufficients for Pays::No extrinsic + Pays::No => {} + } + + if self.0 != account.nonce { + return Err(if self.0 < account.nonce { + InvalidTransaction::Stale + } else { + InvalidTransaction::Future + } + .into()); + } + account.nonce.saturating_inc(); + frame_system::Account::::insert(who, account); + Ok(()) + } + + fn validate( + &self, + who: &Self::AccountId, + _call: &Self::Call, + info: &DispatchInfoOf, + _len: usize, + ) -> TransactionValidity { + let account = frame_system::Account::::get(who); + match info.pays_fee { + Pays::Yes => { + if account.providers.is_zero() && account.sufficients.is_zero() { + // Nonce storage not paid for + return Err(InvalidTransaction::Payment.into()); + } + } + // not check providers and sufficients for Pays::No extrinsic + Pays::No => {} + } + if self.0 < account.nonce { + return InvalidTransaction::Stale.into(); + } + + let provides = vec![Encode::encode(&(who, self.0))]; + let requires = if account.nonce < self.0 { + vec![Encode::encode(&(who, self.0.saturating_sub(One::one())))] + } else { + vec![] + }; + + Ok(ValidTransaction { + priority: 0, + requires, + provides, + longevity: TransactionLongevity::max_value(), + propagate: true, + }) + } +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 0c09e2145..afe21a13b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1,6 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std)] // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. #![recursion_limit = "256"] +// Some arithmetic operations can't use the saturating equivalent, such as the PerThing types +#![allow(clippy::arithmetic_side_effects)] // Make the WASM binary available. #[cfg(feature = "std")] @@ -94,7 +96,9 @@ pub type Nonce = u32; pub const fn deposit(items: u32, bytes: u32) -> Balance { pub const ITEMS_FEE: Balance = 2_000 * 10_000; pub const BYTES_FEE: Balance = 100 * 10_000; - items as Balance * ITEMS_FEE + bytes as Balance * BYTES_FEE + (items as Balance) + .saturating_mul(ITEMS_FEE) + .saturating_add((bytes as Balance).saturating_mul(BYTES_FEE)) } // Opaque types. These are used by the CLI to instantiate machinery that don't need to know @@ -646,7 +650,11 @@ impl PrivilegeCmp for OriginPrivilegeCmp { r_yes_votes, r_count, )), // Equivalent to (l_yes_votes / l_count).cmp(&(r_yes_votes / r_count)) - ) => Some((l_yes_votes * r_count).cmp(&(r_yes_votes * l_count))), + ) => Some( + l_yes_votes + .saturating_mul(*r_count) + .cmp(&r_yes_votes.saturating_mul(*l_count)), + ), // For every other origin we don't care, as they are not used for `ScheduleOrigin`. _ => None, }