Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pallets/admin-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ substrate-fixed = { workspace = true }
pallet-evm-chain-id = { workspace = true }
pallet-drand = { workspace = true, default-features = false }
sp-consensus-grandpa = { workspace = true }
subtensor-swap-interface = { workspace = true }

[dev-dependencies]
sp-core = { workspace = true }
Expand Down
4 changes: 2 additions & 2 deletions pallets/admin-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1016,8 +1016,8 @@ pub mod pallet {
log::trace!("Setting minimum stake to: {}", min_stake);
pallet_subtensor::Pallet::<T>::set_nominator_min_required_stake(min_stake);
if min_stake > prev_min_stake {
log::trace!("Clearing small nominations");
pallet_subtensor::Pallet::<T>::clear_small_nominations()?;
log::trace!("Clearing small nominations if possible");
pallet_subtensor::Pallet::<T>::clear_small_nominations();
log::trace!("Small nominations cleared");
}
Ok(())
Expand Down
1 change: 1 addition & 0 deletions pallets/admin-utils/src/tests/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ impl pallet_subtensor_swap::Config for Test {
type RuntimeEvent = RuntimeEvent;
type AdminOrigin = EnsureRoot<AccountId>;
type LiquidityDataProvider = SubtensorModule;
type BalanceOps = SubtensorModule;
type ProtocolId = SwapProtocolId;
type MaxFeeRate = SwapMaxFeeRate;
type MaxPositions = SwapMaxPositions;
Expand Down
2 changes: 1 addition & 1 deletion pallets/subtensor/src/coinbase/block_step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ impl<T: Config + pallet_drand::Config> Pallet<T> {
U96F32::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).map_err(Into::<&'static str>::into)?;
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.
Expand Down
42 changes: 22 additions & 20 deletions pallets/subtensor/src/coinbase/run_coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ macro_rules! tou64 {
}

impl<T: Config> Pallet<T> {
pub fn run_coinbase(block_emission: U96F32) -> DispatchResult {
pub fn run_coinbase(block_emission: U96F32) {
// --- 0. Get current block.
let current_block: u64 = Self::get_current_block_as_u64();
log::debug!("Current block: {:?}", current_block);
Expand Down Expand Up @@ -192,25 +192,29 @@ impl<T: Config> Pallet<T> {
let pending_alpha: U96F32 = alpha_out_i.saturating_sub(root_alpha);
log::debug!("pending_alpha: {:?}", pending_alpha);
// Sell root emission through the pool.
let root_tao: u64 = Self::swap_alpha_for_tao(

let swap_result = Self::swap_alpha_for_tao(
*netuid_i,
tou64!(root_alpha),
T::SwapInterface::max_price(),
)?
.amount_paid_out;
log::debug!("root_tao: {:?}", root_tao);
// Accumulate alpha emission in pending.
PendingAlphaSwapped::<T>::mutate(*netuid_i, |total| {
*total = total.saturating_add(tou64!(root_alpha));
});
// Accumulate alpha emission in pending.
PendingEmission::<T>::mutate(*netuid_i, |total| {
*total = total.saturating_add(tou64!(pending_alpha));
});
// Accumulate root divs for subnet.
PendingRootDivs::<T>::mutate(*netuid_i, |total| {
*total = total.saturating_add(root_tao);
});
T::SwapInterface::min_price(),
);
if let Ok(ok_result) = swap_result {
let root_tao: u64 = ok_result.amount_paid_out;

log::debug!("root_tao: {:?}", root_tao);
// Accumulate alpha emission in pending.
PendingAlphaSwapped::<T>::mutate(*netuid_i, |total| {
*total = total.saturating_add(tou64!(root_alpha));
});
// Accumulate alpha emission in pending.
PendingEmission::<T>::mutate(*netuid_i, |total| {
*total = total.saturating_add(tou64!(pending_alpha));
});
// Accumulate root divs for subnet.
PendingRootDivs::<T>::mutate(*netuid_i, |total| {
*total = total.saturating_add(root_tao);
});
}
}

// --- 7 Update moving prices after using them in the emission calculation.
Expand Down Expand Up @@ -266,8 +270,6 @@ impl<T: Config> Pallet<T> {
BlocksSinceLastStep::<T>::mutate(netuid, |total| *total = total.saturating_add(1));
}
}

Ok(())
}

pub fn calculate_dividends_and_incentives(
Expand Down
6 changes: 5 additions & 1 deletion pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2435,6 +2435,10 @@ impl<T: Config + pallet_balances::Config<Balance = u64>>
fn subnet_exist(netuid: u16) -> bool {
Self::if_subnet_exist(netuid)
}

fn subnet_mechanism(netuid: u16) -> u16 {
SubnetMechanism::<T>::get(netuid)
}
}

impl<T: Config + pallet_balances::Config<Balance = u64>>
Expand Down Expand Up @@ -2486,5 +2490,5 @@ impl<T: Config + pallet_balances::Config<Balance = u64>>
Ok(Self::decrease_stake_for_hotkey_and_coldkey_on_subnet(
&hotkey, &coldkey, netuid, alpha,
))
}
}
}
44 changes: 6 additions & 38 deletions pallets/subtensor/src/staking/add_stake.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::*;
use substrate_fixed::types::I96F32;
use subtensor_swap_interface::SwapHandler;
use subtensor_swap_interface::{SwapHandler, OrderType};

impl<T: Config> Pallet<T> {
/// ---- The implementation for the extrinsic add_stake: Adds stake to a hotkey account.
Expand Down Expand Up @@ -184,44 +184,12 @@ impl<T: Config> Pallet<T> {
}
}

// Corner case: SubnetAlphaIn is zero. Staking can't happen, so max amount is zero.
let alpha_in = SubnetAlphaIn::<T>::get(netuid);
if alpha_in == 0 {
return 0;
}
let alpha_in_u128 = alpha_in as u128;

// Corner case: SubnetTAO is zero. Staking can't happen, so max amount is zero.
let tao_reserve = SubnetTAO::<T>::get(netuid);
if tao_reserve == 0 {
return 0;
}
let tao_reserve_u128 = tao_reserve as u128;

// Corner case: limit_price < current_price (price cannot decrease with staking)
let tao = 1_000_000_000_u128;
let limit_price_u128 = limit_price as u128;
if (limit_price_u128
< T::SwapInterface::current_alpha_price(netuid)
.saturating_to_num::<u128>()
.saturating_mul(tao))
|| (limit_price == 0u64)
{
return 0;
}

// Main case: return limit_price * SubnetAlphaIn - SubnetTAO
// Non overflowing calculation: limit_price * alpha_in <= u64::MAX * u64::MAX <= u128::MAX
// May overflow result, then it will be capped at u64::MAX, which is OK because that matches balance u64 size.
let result = limit_price_u128
.saturating_mul(alpha_in_u128)
.checked_div(tao)
.unwrap_or(0)
.saturating_sub(tao_reserve_u128);
if result < u64::MAX as u128 {
result as u64
// Use reverting swap to estimate max limit amount
if let Ok(swap_result) = T::SwapInterface::swap(netuid, OrderType::Buy, u64::MAX, limit_price, true) {
swap_result.amount_paid_in.saturating_add(swap_result.fee_paid)
} else {
u64::MAX
0
}
}
}

34 changes: 17 additions & 17 deletions pallets/subtensor/src/staking/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::*;
use safe_math::*;
use substrate_fixed::types::U96F32;
use subtensor_swap_interface::{OrderType, SwapHandler};
use subtensor_swap_interface::SwapHandler;

use frame_support::traits::{
Imbalance,
Expand Down Expand Up @@ -70,12 +70,9 @@ impl<T: Config> Pallet<T> {
let alpha_stake = Self::get_stake_for_hotkey_and_coldkey_on_subnet(
hotkey, coldkey, netuid,
);
T::SwapInterface::swap(
Self::sim_swap_alpha_for_tao(
netuid,
OrderType::Sell,
alpha_stake,
T::SwapInterface::max_price(),
true,
)
.map(|r| {
let fee: u64 = U96F32::saturating_from_num(r.fee_paid)
Expand Down Expand Up @@ -177,7 +174,7 @@ impl<T: Config> Pallet<T> {
hotkey: &T::AccountId,
coldkey: &T::AccountId,
netuid: u16,
) -> DispatchResult {
) {
// Verify if the account is a nominator account by checking ownership of the hotkey by the coldkey.
if !Self::coldkey_owns_hotkey(coldkey, hotkey) {
// If the stake is below the minimum required, it's considered a small nomination and needs to be cleared.
Expand All @@ -189,32 +186,35 @@ impl<T: Config> Pallet<T> {
// Remove the stake from the nominator account. (this is a more forceful unstake operation which )
// Actually deletes the staking account.
// Do not apply any fees
let cleared_stake = Self::unstake_from_subnet(
let maybe_cleared_stake = Self::unstake_from_subnet(
hotkey,
coldkey,
netuid,
stake,
T::SwapInterface::max_price(),
)?;
// Add the stake to the coldkey account.
Self::add_balance_to_coldkey_account(coldkey, cleared_stake);
T::SwapInterface::min_price(),
);

if let Ok(cleared_stake) = maybe_cleared_stake {
// Add the stake to the coldkey account.
Self::add_balance_to_coldkey_account(coldkey, cleared_stake);
} else {
// Just clear small alpha
let alpha = Self::get_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, netuid);
Self::decrease_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, netuid, alpha);
}
}
}

Ok(())
}

/// Clears small nominations for all accounts.
///
/// WARN: This is an O(N) operation, where N is the number of staking accounts. It should be
/// used with caution.
pub fn clear_small_nominations() -> DispatchResult {
pub fn clear_small_nominations() {
// Loop through all staking accounts to identify and clear nominations below the minimum stake.
for ((hotkey, coldkey, netuid), _) in Alpha::<T>::iter() {
Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid)?;
Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid);
}

Ok(())
}

pub fn add_balance_to_coldkey_account(
Expand Down
2 changes: 1 addition & 1 deletion pallets/subtensor/src/staking/move_stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ impl<T: Config> Pallet<T> {
origin_coldkey,
origin_netuid,
move_amount,
T::SwapInterface::max_price(),
T::SwapInterface::min_price(),
)?;

// Stake the unstaked amount into the destination.
Expand Down
66 changes: 14 additions & 52 deletions pallets/subtensor/src/staking/remove_stake.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::*;
use subtensor_swap_interface::SwapHandler;
use subtensor_swap_interface::{SwapHandler, OrderType};

impl<T: Config> Pallet<T> {
/// ---- The implementation for the extrinsic remove_stake: Removes stake from a hotkey account and adds it onto a coldkey.
Expand Down Expand Up @@ -47,6 +47,8 @@ impl<T: Config> Pallet<T> {
alpha_unstaked
);

// println!("alpha to be unstaked = {:?}", alpha_unstaked);

// 2. Validate the user input
Self::validate_remove_stake(
&coldkey,
Expand All @@ -63,14 +65,16 @@ impl<T: Config> Pallet<T> {
&coldkey,
netuid,
alpha_unstaked,
T::SwapInterface::max_price(),
T::SwapInterface::min_price(),
)?;

// println!("tao_unstaked = {:?}, alpha_unstaked = {:?}", tao_unstaked, alpha_unstaked);

// 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);

// 5. If the stake is below the minimum, we clear the nomination from storage.
Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid)?;
Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid);

// 6. Check if stake lowered below MinStake and remove Pending children if it did
if Self::get_total_stake_for_hotkey(&hotkey) < StakeThreshold::<T>::get() {
Expand Down Expand Up @@ -161,7 +165,7 @@ impl<T: Config> Pallet<T> {
Self::add_balance_to_coldkey_account(&coldkey, tao_unstaked);

// If the stake is below the minimum, we clear the nomination from storage.
Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid)?;
Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid);
}
}

Expand Down Expand Up @@ -250,7 +254,7 @@ impl<T: Config> Pallet<T> {
total_tao_unstaked = total_tao_unstaked.saturating_add(tao_unstaked);

// If the stake is below the minimum, we clear the nomination from storage.
Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid)?;
Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid);
}
}
}
Expand Down Expand Up @@ -354,7 +358,7 @@ impl<T: Config> Pallet<T> {
Self::add_balance_to_coldkey_account(&coldkey, tao_unstaked);

// 6. If the stake is below the minimum, we clear the nomination from storage.
Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid)?;
Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid);

// 7. Check if stake lowered below MinStake and remove Pending children if it did
if Self::get_total_stake_for_hotkey(&hotkey) < StakeThreshold::<T>::get() {
Expand All @@ -380,53 +384,11 @@ impl<T: Config> Pallet<T> {
}
}

// Corner case: SubnetAlphaIn is zero. Staking can't happen, so max amount is zero.
let alpha_in = SubnetAlphaIn::<T>::get(netuid);
if alpha_in == 0 {
return 0;
}
let alpha_in_u128 = alpha_in as u128;

// Corner case: SubnetTAO is zero. Staking can't happen, so max amount is zero.
let tao_reserve = SubnetTAO::<T>::get(netuid);
if tao_reserve == 0 {
return 0;
}
let tao_reserve_u128 = tao_reserve as u128;

// Corner case: limit_price == 0 (because there's division by limit price)
// => can sell all
if limit_price == 0 {
return u64::MAX;
}

// Corner case: limit_price >= current_price (price cannot increase with unstaking)
// No overflows: alpha_price * tao <= u64::MAX * u64::MAX
// Alpha price is U96F32 size, but it is calculated as u64/u64, so it never uses all 96 bits.
let limit_price_u128 = limit_price as u128;
let tao = 1_000_000_000_u128;
if limit_price_u128
>= tao_reserve_u128
.saturating_mul(tao)
.checked_div(alpha_in_u128)
.unwrap_or(0)
{
return 0;
}

// Main case: SubnetTAO / limit_price - SubnetAlphaIn
// Non overflowing calculation: tao_reserve * tao <= u64::MAX * u64::MAX <= u128::MAX
// May overflow result, then it will be capped at u64::MAX, which is OK because that matches Alpha u64 size.
let result = tao_reserve_u128
.saturating_mul(tao)
.checked_div(limit_price_u128)
.unwrap_or(0)
.saturating_sub(alpha_in_u128);

if result < u64::MAX as u128 {
result as u64
// Use reverting swap to estimate max limit amount
if let Ok(swap_result) = T::SwapInterface::swap(netuid, OrderType::Sell, u64::MAX, limit_price, true) {
swap_result.amount_paid_in.saturating_add(swap_result.fee_paid)
} else {
u64::MAX
0
}
}
}
Loading
Loading