code/pallets/subtensor/src/staking/stake_utils.rs
use super::*;
use safe_math::*;
use share_pool::{SafeFloat, SharePool, SharePoolDataOperations};
use sp_std::{collections::btree_map::BTreeMap, ops::Neg};
use substrate_fixed::types::{I64F64, I96F32, U64F64, U96F32};
use subtensor_runtime_common::{AlphaBalance, AuthorshipInfo, NetUid, TaoBalance, Token};
use subtensor_swap_interface::{Order, SwapHandler, SwapResult};
impl<T: Config> Pallet<T> {
/// Retrieves the total alpha issuance for a given subnet.
///
/// This function calculates the total alpha issuance by summing the alpha
/// values from `SubnetAlphaIn` and `SubnetAlphaOut` for the specified subnet.
///
/// # Arguments
/// * `netuid`: The unique identifier of the subnet.
///
/// # Returns
/// * `u64`: The total alpha issuance for the specified subnet.
pub fn get_alpha_issuance(netuid: NetUid) -> AlphaBalance {
SubnetAlphaIn::<T>::get(netuid)
.saturating_add(SubnetAlphaOut::<T>::get(netuid))
.saturating_add(T::SwapInterface::protocol_alpha_reservoir(netuid))
}
pub fn get_moving_alpha_price(netuid: NetUid) -> U64F64 {
let one = U64F64::saturating_from_num(1.0);
if netuid.is_root() {
// Root.
one
} else if SubnetMechanism::<T>::get(netuid) == 0 {
// Stable
one
} else {
U64F64::saturating_from_num(SubnetMovingPrice::<T>::get(netuid))
}
}
pub fn update_moving_price(netuid: NetUid) {
let blocks_since_start_call = U64F64::saturating_from_num({
// We expect FirstEmissionBlockNumber to be set earlier, and we take the block when
// `start_call` was called (first block before FirstEmissionBlockNumber).
let start_call_block = FirstEmissionBlockNumber::<T>::get(netuid)
.unwrap_or_default()
.saturating_sub(1);
Self::get_current_block_as_u64().saturating_sub(start_call_block)
});
// Use halving time hyperparameter. The meaning of this parameter can be best explained under
// the assumption of a constant price and SubnetMovingAlpha == 0.5: It is how many blocks it
// will take in order for the distance between current EMA of price and current price to shorten
// by half.
let halving_time = EMAPriceHalvingBlocks::<T>::get(netuid);
let current_ma_unsigned = U64F64::saturating_from_num(SubnetMovingAlpha::<T>::get());
let alpha: U64F64 = current_ma_unsigned.saturating_mul(blocks_since_start_call.safe_div(
blocks_since_start_call.saturating_add(U64F64::saturating_from_num(halving_time)),
));
// Because alpha = b / (b + h), where b and h > 0, alpha < 1, so 1 - alpha > 0.
// We can use unsigned type here: U96F32
let one_minus_alpha: U64F64 = U64F64::saturating_from_num(1.0).saturating_sub(alpha);
let current_price: U64F64 = alpha.saturating_mul(U64F64::saturating_from_num(
T::SwapInterface::current_alpha_price(netuid.into())
.min(U64F64::saturating_from_num(1.0)),
));
let current_moving: U64F64 = one_minus_alpha.saturating_mul(U64F64::saturating_from_num(
Self::get_moving_alpha_price(netuid),
));
// Convert batch to signed I96F32 to avoid migration of SubnetMovingPrice for now``
let new_moving: I96F32 =
I96F32::saturating_from_num(current_price.saturating_add(current_moving));
SubnetMovingPrice::<T>::insert(netuid, new_moving);
}
/// Gets the Median Subnet Alpha Price
pub fn get_median_subnet_alpha_price() -> U64F64 {
let default_price = U64F64::saturating_from_num(1_u64);
let zero_price = U64F64::saturating_from_num(0_u64);
let two = U64F64::saturating_from_num(2_u64);
let mut price_counts: BTreeMap<U64F64, usize> = BTreeMap::new();
let mut total_prices: usize = 0;
for (netuid, added) in NetworksAdded::<T>::iter() {
if !added || netuid == NetUid::ROOT {
continue;
}
let price = T::SwapInterface::current_alpha_price(netuid);
if price <= zero_price {
continue;
}
total_prices = total_prices.saturating_add(1);
if let Some(count) = price_counts.get_mut(&price) {
*count = count.saturating_add(1);
} else {
price_counts.insert(price, 1usize);
}
}
if total_prices == 0 {
return default_price;
}
let Some(last_index) = total_prices.checked_sub(1) else {
return default_price;
};
let Some(lower_target) = last_index.checked_div(2) else {
return default_price;
};
let Some(upper_target) = total_prices.checked_div(2) else {
return default_price;
};
let mut cumulative: usize = 0;
let mut lower_price: Option<U64F64> = None;
let mut upper_price: Option<U64F64> = None;
for (price, count) in price_counts.into_iter() {
let next_cumulative = cumulative.saturating_add(count);
if lower_price.is_none() && lower_target < next_cumulative {
lower_price = Some(price);
}
if upper_price.is_none() && upper_target < next_cumulative {
upper_price = Some(price);
}
if lower_price.is_some() && upper_price.is_some() {
break;
}
cumulative = next_cumulative;
}
match (lower_price, upper_price) {
(Some(_), Some(upper)) if lower_target == upper_target => upper,
(Some(lower), Some(upper)) => lower.saturating_add(upper).safe_div(two),
_ => default_price,
}
}
/// Retrieves the TAO weight as a normalized value between 0 and 1.
///
/// This function performs the following steps:
/// 1. Fetches the TAO weight from storage using the TaoWeight storage item.
/// 2. Converts the retrieved u64 value to a fixed-point number (U96F32).
/// 3. Normalizes the weight by dividing it by the maximum possible u64 value.
/// 4. Returns the normalized weight as an U96F32 fixed-point number.
///
/// The normalization ensures that the returned value is always between 0 and 1,
/// regardless of the actual stored weight value.
///
/// # Returns
/// * `U96F32`: The normalized TAO weight as a fixed-point number between 0 and 1.
///
/// # Note
/// This function uses saturating division to prevent potential overflow errors.
pub fn get_tao_weight() -> U96F32 {
// Step 1: Fetch the TAO weight from storage
let stored_weight = TaoWeight::<T>::get();
// Step 2: Convert the u64 weight to U96F32
let weight_fixed = U96F32::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.safe_div(U96F32::saturating_from_num(u64::MAX))
}
pub fn get_ck_burn() -> U96F32 {
let stored_weight = CKBurn::<T>::get();
let weight_fixed = U96F32::saturating_from_num(stored_weight);
weight_fixed.safe_div(U96F32::saturating_from_num(u64::MAX))
}
/// Sets the TAO weight in storage.
///
/// This function performs the following steps:
/// 1. Takes the provided weight value as a u64.
/// 2. Updates the TaoWeight storage item with the new value.
///
/// # Arguments
/// * `weight`: The new TAO weight value to be set, as a u64.
///
/// # Effects
/// This function modifies the following storage item:
/// * `TaoWeight`: Updates it with the new weight value.
///
/// # Note
/// The weight is stored as a raw u64 value. To get the normalized weight between 0 and 1,
/// use the `get_tao_weight()` function.
pub fn set_tao_weight(weight: u64) {
// Update the TaoWeight storage with the new weight value
TaoWeight::<T>::set(weight);
}
// Set the amount burned on non owned CK
pub fn set_ck_burn(weight: u64) {
// Update the ck burn value.
CKBurn::<T>::set(weight);
}
/// Calculates the weighted combination of alpha and TAO stake for a single hotkey on a subnet.
///
pub fn get_stake_weights_for_hotkey_on_subnet(
hotkey: &T::AccountId,
netuid: NetUid,
) -> (I64F64, I64F64, I64F64) {
// Retrieve the 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::saturating_from_num(Self::get_inherited_for_hotkey_on_subnet(hotkey, netuid));
log::debug!("alpha_stake: {alpha_stake:?}");
// Step 2: Get the TAO stake for the hotkey
let tao_stake = I64F64::saturating_from_num(Self::get_tao_inherited_for_hotkey_on_subnet(
hotkey, netuid,
));
log::debug!("tao_stake: {tao_stake:?}");
// Step 3: Combine alpha and tao stakes
let total_stake = alpha_stake.saturating_add(tao_stake.saturating_mul(tao_weight));
log::debug!("total_stake: {total_stake:?}");
(total_stake, alpha_stake, tao_stake)
}
/// Calculates the weighted combination of alpha and TAO stake for hotkeys on a subnet.
///
pub fn get_stake_weights_for_network(
netuid: NetUid,
) -> (Vec<I64F64>, Vec<I64F64>, Vec<I64F64>) {
// Retrieve the 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
let n: u16 = Self::get_subnetwork_n(netuid);
// Step 2: Get stake of all hotkeys (neurons) ordered by uid
let alpha_stake: Vec<I64F64> = (0..n)
.map(|uid| {
if Keys::<T>::contains_key(netuid, uid) {
let hotkey: T::AccountId = Keys::<T>::get(netuid, uid);
I64F64::saturating_from_num(Self::get_inherited_for_hotkey_on_subnet(
&hotkey, netuid,
))
} else {
I64F64::saturating_from_num(0)
}
})
.collect();
log::debug!("alpha_stake: {alpha_stake:?}");
// Step 3: Calculate the TAO stake vector.
// Initialize a vector to store TAO stakes for each neuron.
let tao_stake: Vec<I64F64> = (0..n)
.map(|uid| {
if Keys::<T>::contains_key(netuid, uid) {
let hotkey: T::AccountId = Keys::<T>::get(netuid, uid);
I64F64::saturating_from_num(Self::get_tao_inherited_for_hotkey_on_subnet(
&hotkey, netuid,
))
} else {
I64F64::saturating_from_num(0)
}
})
.collect();
log::trace!("tao_stake: {tao_stake:?}");
// Step 4: Combine alpha and TAO stakes.
// Calculate the weighted average of alpha and TAO stakes for each neuron.
let total_stake: Vec<I64F64> = alpha_stake
.iter()
.zip(tao_stake.iter())
.map(|(alpha_i, tao_i)| alpha_i.saturating_add(tao_i.saturating_mul(tao_weight)))
.collect();
log::trace!("total_stake: {total_stake:?}");
(total_stake, alpha_stake, tao_stake)
}
/// Calculates the total inherited stake (alpha) held by a hotkey on a network, considering child/parent relationships.
///
/// This function performs the following steps:
/// 1. Retrieves the initial alpha (stake) for the hotkey on the specified subnet.
/// 2. Retrieves the list of children and parents for the hotkey on the subnet.
/// 3. Calculates the alpha allocated to children:
/// a. For each child, computes the proportion of alpha to be allocated.
/// b. Accumulates the total alpha allocated to all children.
/// 4. Calculates the alpha received from parents:
/// a. For each parent, retrieves the parent's stake on the subnet.
/// b. Computes the proportion of the parent's stake to be inherited.
/// c. Accumulates the total alpha inherited from all parents.
/// 5. Computes the final inherited alpha by adjusting the initial alpha:
/// a. Subtracts the alpha allocated to children.
/// b. Adds the alpha inherited from parents.
/// 6. Returns the final inherited alpha value.
///
/// # Arguments
/// * `hotkey`: AccountId of the hotkey whose total inherited stake is to be calculated.
/// * `netuid`: Network unique identifier specifying the subnet context.
///
/// # Returns
/// * `u64`: The total inherited alpha for the hotkey on the subnet after considering the
/// stakes allocated to children and inherited from parents.
///
/// # Note
/// This function uses saturating arithmetic to prevent overflows.
pub fn get_tao_inherited_for_hotkey_on_subnet(
hotkey: &T::AccountId,
netuid: NetUid,
) -> TaoBalance {
let initial_tao: U96F32 =
U96F32::saturating_from_num(Self::get_stake_for_hotkey_on_subnet(hotkey, NetUid::ROOT));
// Initialize variables to track alpha allocated to children and inherited from parents.
let mut tao_to_children: U96F32 = U96F32::saturating_from_num(0);
let mut tao_from_parents: U96F32 = U96F32::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);
let children: Vec<(u64, T::AccountId)> = Self::get_children(hotkey, netuid);
log::trace!("Parents for hotkey {hotkey:?} on subnet {netuid}: {parents:?}");
log::trace!("Children for hotkey {hotkey:?} on subnet {netuid}: {children:?}");
// Step 3: Calculate the total tao allocated to children.
for (proportion, _) in children {
// Convert the proportion to a normalized value between 0 and 1.
let normalized_proportion: U96F32 = U96F32::saturating_from_num(proportion)
.safe_div(U96F32::saturating_from_num(u64::MAX));
log::trace!("Normalized proportion for child: {normalized_proportion:?}");
// Calculate the amount of tao to be allocated to this child.
let tao_proportion_to_child: U96F32 =
U96F32::saturating_from_num(initial_tao).saturating_mul(normalized_proportion);
log::trace!("Tao proportion to child: {tao_proportion_to_child:?}");
// Add this child's allocation to the total tao allocated to children.
tao_to_children = tao_to_children.saturating_add(tao_proportion_to_child);
}
log::trace!("Total tao allocated to children: {tao_to_children:?}");
// Step 4: Calculate the total tao inherited from parents.
for (proportion, parent) in parents {
// Retrieve the parent's total stake on this subnet.
let parent_tao = U96F32::saturating_from_num(Self::get_stake_for_hotkey_on_subnet(
&parent,
NetUid::ROOT,
));
log::trace!("Parent tao for parent {parent:?} on subnet {netuid}: {parent_tao:?}");
// Convert the proportion to a normalized value between 0 and 1.
let normalized_proportion = U96F32::saturating_from_num(proportion)
.safe_div(U96F32::saturating_from_num(u64::MAX));
log::trace!("Normalized proportion from parent: {normalized_proportion:?}");
// Calculate the amount of tao to be inherited from this parent.
let tao_proportion_from_parent: U96F32 =
U96F32::saturating_from_num(parent_tao).saturating_mul(normalized_proportion);
log::trace!("Tao proportion from parent: {tao_proportion_from_parent:?}");
// Add this parent's contribution to the total tao inherited from parents.
tao_from_parents = tao_from_parents.saturating_add(tao_proportion_from_parent);
}
log::trace!("Total tao inherited from parents: {tao_from_parents:?}");
// Step 5: Calculate the final inherited tao for the hotkey.
let finalized_tao: U96F32 = initial_tao
.saturating_sub(tao_to_children) // Subtract tao allocated to children
.saturating_add(tao_from_parents); // Add tao inherited from parents
log::trace!("Finalized tao for hotkey {hotkey:?} on subnet {netuid}: {finalized_tao:?}");
// Step 6: Return the final inherited tao value.
finalized_tao.saturating_to_num::<u64>().into()
}
pub fn get_inherited_for_hotkey_on_subnet(
hotkey: &T::AccountId,
netuid: NetUid,
) -> AlphaBalance {
// Step 1: Retrieve the initial total stake (alpha) for the hotkey on the specified subnet.
let initial_alpha: U96F32 =
U96F32::saturating_from_num(Self::get_stake_for_hotkey_on_subnet(hotkey, netuid));
log::debug!("Initial alpha for hotkey {hotkey:?} on subnet {netuid}: {initial_alpha:?}");
if netuid.is_root() {
return initial_alpha.saturating_to_num::<u64>().into();
}
// Initialize variables to track alpha allocated to children and inherited from parents.
let mut alpha_to_children: U96F32 = U96F32::saturating_from_num(0);
let mut alpha_from_parents: U96F32 = U96F32::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);
let children: Vec<(u64, T::AccountId)> = Self::get_children(hotkey, netuid);
log::debug!("Parents for hotkey {hotkey:?} on subnet {netuid}: {parents:?}");
log::debug!("Children for hotkey {hotkey:?} on subnet {netuid}: {children:?}");
// 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: U96F32 = U96F32::saturating_from_num(proportion)
.safe_div(U96F32::saturating_from_num(u64::MAX));
log::trace!("Normalized proportion for child: {normalized_proportion:?}");
// Calculate the amount of alpha to be allocated to this child.
let alpha_proportion_to_child: U96F32 =
U96F32::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.
alpha_to_children = alpha_to_children.saturating_add(alpha_proportion_to_child);
}
log::debug!("Total alpha allocated to children: {alpha_to_children:?}");
// Step 4: Calculate the total alpha inherited from parents.
for (proportion, parent) in parents {
// Retrieve the parent's total stake on this subnet.
let parent_alpha: U96F32 =
U96F32::saturating_from_num(Self::get_stake_for_hotkey_on_subnet(&parent, netuid));
log::trace!("Parent alpha for parent {parent:?} on subnet {netuid}: {parent_alpha:?}");
// Convert the proportion to a normalized value between 0 and 1.
let normalized_proportion: U96F32 = U96F32::saturating_from_num(proportion)
.safe_div(U96F32::saturating_from_num(u64::MAX));
log::trace!("Normalized proportion from parent: {normalized_proportion:?}");
// Calculate the amount of alpha to be inherited from this parent.
let alpha_proportion_from_parent: U96F32 =
U96F32::saturating_from_num(parent_alpha).saturating_mul(normalized_proportion);
log::trace!("Alpha proportion from parent: {alpha_proportion_from_parent:?}");
// Add this parent's contribution to the total alpha inherited from parents.
alpha_from_parents = alpha_from_parents.saturating_add(alpha_proportion_from_parent);
}
log::debug!("Total alpha inherited from parents: {alpha_from_parents:?}");
// Step 5: Calculate the final inherited alpha for the hotkey.
let finalized_alpha: U96F32 = initial_alpha
.saturating_sub(alpha_to_children) // Subtract alpha allocated to children
.saturating_add(alpha_from_parents); // Add alpha inherited from parents
log::trace!(
"Finalized alpha for hotkey {hotkey:?} on subnet {netuid}: {finalized_alpha:?}"
);
// Step 6: Return the final inherited alpha value.
finalized_alpha.saturating_to_num::<u64>().into()
}
/// Checks if a specific hotkey-coldkey pair has enough stake on a subnet to fulfill a given decrement.
///
/// This function performs the following steps:
/// 1. Retrieves the current stake for the hotkey-coldkey pair on the specified subnet.
/// 2. Compares this stake with the requested decrement amount.
///
/// # Arguments
/// * `hotkey`: The account ID of the hotkey.
/// * `coldkey`: The account ID of the coldkey.
/// * `netuid`: The unique identifier of the subnet.
/// * `decrement`: The amount of stake to be potentially decremented.
///
/// # Returns
/// * `bool`: True if the account has enough stake to fulfill the decrement, false otherwise.
///
/// # Note
/// This function only checks the stake for the specific hotkey-coldkey pair, not the total stake of the hotkey or coldkey individually.
pub fn calculate_reduced_stake_on_subnet(
hotkey: &T::AccountId,
coldkey: &T::AccountId,
netuid: NetUid,
decrement: AlphaBalance,
) -> Result<AlphaBalance, Error<T>> {
// Retrieve the current stake for this hotkey-coldkey pair on the subnet
let current_stake =
Self::get_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, netuid);
// Compare the current stake with the requested decrement
// Return true if the current stake is greater than or equal to the decrement
if current_stake >= decrement {
Ok(current_stake.saturating_sub(decrement))
} else {
Err(Error::<T>::NotEnoughStakeToWithdraw)
}
}
/// Retrieves the alpha (stake) value for a given hotkey and coldkey pair on a specific subnet.
///
/// This function performs the following steps:
/// 1. Takes the hotkey, coldkey, and subnet ID as input parameters.
/// 2. Accesses the Alpha storage map to retrieve the stake value.
/// 3. Returns the retrieved stake value as a u64.
///
/// # Arguments
/// * `hotkey`: The account ID of the hotkey (neuron).
/// * `coldkey`: The account ID of the coldkey (owner).
/// * `netuid`: The unique identifier of the subnet.
///
/// # Returns
/// * `u64`: The alpha (stake) value for the specified hotkey-coldkey pair on the given subnet.
///
/// # Note
/// This function retrieves the stake specific to the hotkey-coldkey pair, not the total stake of the hotkey or coldkey individually.
pub fn get_stake_for_hotkey_and_coldkey_on_subnet(
hotkey: &T::AccountId,
coldkey: &T::AccountId,
netuid: NetUid,
) -> AlphaBalance {
let alpha_share_pool = Self::get_alpha_share_pool(hotkey.clone(), netuid);
alpha_share_pool.try_get_value(coldkey).unwrap_or(0).into()
}
/// Retrieves the total stake (alpha) for a given hotkey on a specific subnet.
///
/// This function performs the following step:
/// 1. Retrieves and returns the total alpha value associated with the hotkey on the specified subnet.
///
/// # Arguments
/// * `hotkey`: The account ID of the hotkey.
/// * `netuid`: The unique identifier of the subnet.
///
/// # Returns
/// * `u64`: The total alpha value for the hotkey on the specified subnet.
///
/// # Note
/// This function returns the cumulative stake across all coldkeys associated with this hotkey on the subnet.
pub fn get_stake_for_hotkey_on_subnet(hotkey: &T::AccountId, netuid: NetUid) -> AlphaBalance {
// Retrieve and return the total alpha this hotkey owns on this subnet.
// This value represents the sum of stakes from all coldkeys associated with this hotkey.
TotalHotkeyAlpha::<T>::get(hotkey, netuid)
}
/// Increase hotkey stake on a subnet.
///
/// The function updates share totals given current prices.
///
/// # Arguments
/// * `hotkey`: The account ID of the hotkey.
/// * `netuid`: The unique identifier of the subnet.
/// * `amount`: The amount of alpha to be added.
///
pub fn increase_stake_for_hotkey_on_subnet(
hotkey: &T::AccountId,
netuid: NetUid,
amount: AlphaBalance,
) {
let mut alpha_share_pool = Self::get_alpha_share_pool(hotkey.clone(), netuid);
alpha_share_pool.update_value_for_all(amount.to_u64() as i64);
}
/// Decrease hotkey stake on a subnet.
///
/// The function updates share totals given current prices.
///
/// # Arguments
/// * `hotkey`: The account ID of the hotkey.
/// * `netuid`: The unique identifier of the subnet.
/// * `amount`: The amount of alpha to be added.
///
pub fn decrease_stake_for_hotkey_on_subnet(hotkey: &T::AccountId, netuid: NetUid, amount: u64) {
let mut alpha_share_pool = Self::get_alpha_share_pool(hotkey.clone(), netuid);
alpha_share_pool.update_value_for_all((amount as i64).neg());
}
/// Buys shares in the hotkey on a given subnet
///
/// The function updates share totals given current prices.
///
/// # Arguments
/// * `hotkey`: The account ID of the hotkey.
/// * `coldkey`: The account ID of the coldkey (owner).
/// * `netuid`: The unique identifier of the subnet.
/// * `amount`: The amount of alpha to be added.
///
pub fn increase_stake_for_hotkey_and_coldkey_on_subnet(
hotkey: &T::AccountId,
coldkey: &T::AccountId,
netuid: NetUid,
amount: AlphaBalance,
) {
if !amount.is_zero() {
let mut staking_hotkeys = StakingHotkeys::<T>::get(coldkey);
if !staking_hotkeys.contains(hotkey) {
staking_hotkeys.push(hotkey.clone());
StakingHotkeys::<T>::insert(coldkey, staking_hotkeys.clone());
}
}
let mut alpha_share_pool = Self::get_alpha_share_pool(hotkey.clone(), netuid);
// We expect to add a positive amount here.
let amount = amount.to_u64() as i64;
alpha_share_pool.update_value_for_one(coldkey, amount);
}
pub fn try_increase_stake_for_hotkey_and_coldkey_on_subnet(
hotkey: &T::AccountId,
netuid: NetUid,
amount: AlphaBalance,
) -> bool {
let mut alpha_share_pool = Self::get_alpha_share_pool(hotkey.clone(), netuid);
let amount = amount.to_u64() as i64;
alpha_share_pool.sim_update_value_for_one(amount)
}
/// Sell shares in the hotkey on a given subnet
///
/// The function updates share totals given current prices.
///
/// # Arguments
/// * `hotkey`: The account ID of the hotkey.
/// * `coldkey`: The account ID of the coldkey (owner).
/// * `netuid`: The unique identifier of the subnet.
/// * `amount`: The amount of alpha to be added.
///
pub fn decrease_stake_for_hotkey_and_coldkey_on_subnet(
hotkey: &T::AccountId,
coldkey: &T::AccountId,
netuid: NetUid,
amount: AlphaBalance,
) {
let mut alpha_share_pool = Self::get_alpha_share_pool(hotkey.clone(), netuid);
let amount = amount.to_u64();
// We expect a negative value here
if let Ok(value) = alpha_share_pool.try_get_value(coldkey)
&& value >= amount
{
alpha_share_pool.update_value_for_one(coldkey, (amount as i64).neg());
}
}
/// Swaps TAO for the alpha token on the subnet.
///
/// Updates TaoIn, AlphaIn, and AlphaOut
pub fn swap_tao_for_alpha(
netuid: NetUid,
tao: TaoBalance,
price_limit: TaoBalance,
drop_fees: bool,
) -> Result<SwapResult<TaoBalance, AlphaBalance>, DispatchError> {
// Step 1: Get the mechanism type for the subnet (0 for Stable, 1 for Dynamic)
let mechanism_id: u16 = SubnetMechanism::<T>::get(netuid);
let swap_result = if mechanism_id == 1 {
let order = GetAlphaForTao::<T>::with_amount(tao);
T::SwapInterface::swap(netuid.into(), order, price_limit.into(), drop_fees, false)?
} else {
// Step 3.b.1: Stable mechanism, just return the value 1:1
SwapResult {
amount_paid_in: tao,
amount_paid_out: tao.to_u64().into(),
fee_paid: TaoBalance::ZERO,
fee_to_block_author: TaoBalance::ZERO,
}
};
let alpha_decrease = swap_result.paid_out_reserve_delta_i64().unsigned_abs();
// Decrease Alpha reserves.
Self::decrease_provided_alpha_reserve(netuid.into(), alpha_decrease.into());
// Increase Alpha outstanding.
SubnetAlphaOut::<T>::mutate(netuid, |total| {
*total = total.saturating_add(swap_result.amount_paid_out.into());
});
// Increase the protocol TAO reserve
SubnetTAO::<T>::mutate(netuid, |total| {
let delta = swap_result.paid_in_reserve_delta_i64().unsigned_abs();
*total = total.saturating_add(delta.into());
});
// Increase Total Tao reserves.
TotalStake::<T>::mutate(|total| *total = total.saturating_add(tao));
// Increase total subnet TAO volume.
SubnetVolume::<T>::mutate(netuid, |total| {
*total = total.saturating_add(tao.to_u64() as u128);
});
Ok(swap_result)
}
/// Swaps a subnet's Alpha token for TAO.
///
/// Updates TaoIn, AlphaIn, and AlphaOut
pub fn swap_alpha_for_tao(
netuid: NetUid,
alpha: AlphaBalance,
price_limit: TaoBalance,
drop_fees: bool,
) -> Result<SwapResult<AlphaBalance, TaoBalance>, DispatchError> {
// Step 1: Get the mechanism type for the subnet (0 for Stable, 1 for Dynamic)
let mechanism_id: u16 = SubnetMechanism::<T>::get(netuid);
// Step 2: Swap alpha and attain tao
let swap_result = if mechanism_id == 1 {
let order = GetTaoForAlpha::<T>::with_amount(alpha);
T::SwapInterface::swap(netuid.into(), order, price_limit.into(), drop_fees, false)?
} else {
// Step 3.b.1: Stable mechanism, just return the value 1:1
SwapResult {
amount_paid_in: alpha,
amount_paid_out: alpha.to_u64().into(),
fee_paid: AlphaBalance::ZERO,
fee_to_block_author: AlphaBalance::ZERO,
}
};
// Increase only the protocol Alpha reserve
let alpha_delta = swap_result.paid_in_reserve_delta_i64().unsigned_abs();
SubnetAlphaIn::<T>::mutate(netuid, |total| {
*total = total.saturating_add(alpha_delta.into());
});
// Decrease Alpha outstanding.
SubnetAlphaOut::<T>::mutate(netuid, |total| {
*total = total.saturating_sub(alpha_delta.into());
});
// Decrease tao reserves.
let tao_delta = swap_result.paid_out_reserve_delta_i64().unsigned_abs();
Self::decrease_provided_tao_reserve(netuid.into(), tao_delta.into());
// Reduce total TAO reserves.
TotalStake::<T>::mutate(|total| *total = total.saturating_sub(swap_result.amount_paid_out));
// Increase total subnet TAO volume.
SubnetVolume::<T>::mutate(netuid, |total| {
*total = total.saturating_add(swap_result.amount_paid_out.to_u64() as u128)
});
// Return the tao received.
Ok(swap_result)
}
/// Unstakes alpha from a subnet for a given hotkey and coldkey pair.
///
/// We update the pools associated with a subnet as well as update hotkey alpha shares.
/// Credits the unstaked TAO to the beneficiary account
pub fn unstake_from_subnet(
hotkey: &T::AccountId,
coldkey: &T::AccountId,
beneficiary: &T::AccountId,
netuid: NetUid,
alpha: AlphaBalance,
price_limit: TaoBalance,
drop_fees: bool,
) -> Result<TaoBalance, DispatchError> {
// Decrease alpha on subnet
Self::decrease_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, netuid, alpha);
// Swap the alpha for TAO.
let swap_result = Self::swap_alpha_for_tao(netuid, alpha, price_limit, drop_fees)?;
// Refund the unused alpha (in case if limit price is hit)
let refund = alpha.saturating_sub(
swap_result
.amount_paid_in
.saturating_add(swap_result.fee_paid)
.into(),
);
if !refund.is_zero() {
Self::increase_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, netuid, refund);
}
// Transfer unstaked TAO from subnet account to the coldkey.
Self::transfer_tao_from_subnet(netuid, beneficiary, swap_result.amount_paid_out.into())?;
// Swap (in a fee-less way) the block builder alpha fee
let mut fee_outflow = 0_u64;
let maybe_block_author_coldkey = T::AuthorshipProvider::author();
if let Some(block_author_coldkey) = maybe_block_author_coldkey {
let bb_swap_result = Self::swap_alpha_for_tao(
netuid,
swap_result.fee_to_block_author,
T::SwapInterface::min_price::<TaoBalance>(),
true,
)?;
Self::transfer_tao_from_subnet(
netuid,
&block_author_coldkey,
bb_swap_result.amount_paid_out.into(),
)?;
fee_outflow = bb_swap_result.amount_paid_out.into();
} else {
// block author is not found, burn this alpha
Self::burn_subnet_alpha(netuid, swap_result.fee_to_block_author);
}
// If this is a root-stake
if netuid == NetUid::ROOT {
// Adjust root claimed value for this hotkey and coldkey.
Self::remove_stake_adjust_root_claimed_for_hotkey_and_coldkey(hotkey, coldkey, alpha);
// If the coldkey no longer holds any root stake, remove it from the
// auto-claim staking-coldkey index so dead entries do not accumulate.
if !Self::coldkey_has_root_stake(coldkey) {
Self::maybe_remove_coldkey_index(coldkey);
}
}
// Step 3: Update StakingHotkeys if the hotkey's total alpha, across all subnets, is zero
// TODO const: fix.
// if Self::get_stake(hotkey, coldkey) == 0 {
// StakingHotkeys::<T>::mutate(coldkey, |hotkeys| {
// hotkeys.retain(|k| k != hotkey);
// });
// }
// Record TAO outflow
Self::record_tao_outflow(
netuid,
swap_result
.amount_paid_out
.saturating_add(fee_outflow.into()),
);
// Cleanup locks if needed
Self::cleanup_lock_if_zero(coldkey, netuid);
LastColdkeyHotkeyStakeBlock::<T>::insert(coldkey, hotkey, Self::get_current_block_as_u64());
// Deposit and log the unstaking event.
Self::deposit_event(Event::StakeRemoved(
coldkey.clone(),
hotkey.clone(),
swap_result.amount_paid_out.into(),
swap_result.amount_paid_in.into(),
netuid,
swap_result.fee_paid.to_u64(),
));
log::debug!(
"StakeRemoved( coldkey: {:?}, hotkey:{:?}, tao: {:?}, alpha:{:?}, netuid: {:?}, fee {} )",
coldkey.clone(),
hotkey.clone(),
swap_result.amount_paid_out,
swap_result.amount_paid_in,
netuid,
swap_result.fee_paid
);
Ok(swap_result.amount_paid_out.into())
}
/// Stakes TAO into a subnet for a given hotkey and coldkey pair.
///
/// We update the pools associated with a subnet as well as update hotkey alpha shares.
pub(crate) fn stake_into_subnet(
hotkey: &T::AccountId,
coldkey: &T::AccountId,
netuid: NetUid,
tao: TaoBalance,
price_limit: TaoBalance,
drop_fees: bool,
) -> Result<AlphaBalance, DispatchError> {
// Transfer TAO from coldkey to the subnet account.
// Actual transfered may be different within ED amount.
let tao_staked = Self::transfer_tao_to_subnet(netuid, coldkey, tao)?;
// Swap the tao to alpha.
let swap_result = Self::swap_tao_for_alpha(netuid, tao_staked, price_limit, drop_fees)?;
ensure!(
!swap_result.amount_paid_out.is_zero(),
Error::<T>::AmountTooLow
);
ensure!(
Self::try_increase_stake_for_hotkey_and_coldkey_on_subnet(
hotkey,
netuid,
swap_result.amount_paid_out.into(),
),
Error::<T>::InsufficientLiquidity
);
// Increase the alpha on the hotkey account.
Self::increase_stake_for_hotkey_and_coldkey_on_subnet(
hotkey,
coldkey,
netuid,
swap_result.amount_paid_out.into(),
);
// Step 4: Update the list of hotkeys staking for this coldkey
let mut staking_hotkeys = StakingHotkeys::<T>::get(coldkey);
if !staking_hotkeys.contains(hotkey) {
staking_hotkeys.push(hotkey.clone());
StakingHotkeys::<T>::insert(coldkey, staking_hotkeys.clone());
}
// Increase the balance of the block author
let maybe_block_author_coldkey = T::AuthorshipProvider::author();
if let Some(block_author_coldkey) = maybe_block_author_coldkey {
// TAO was transferred to subnet account in the beginning of this fn
// swap_tao_for_alpha guarantees that input amount of TAO was split into
// reserve delta + fee_to_block_author.
// Now transfer the fee from subnet account to block builder.
Self::transfer_tao_from_subnet(
netuid,
&block_author_coldkey,
swap_result.fee_to_block_author.into(),
)?;
} else {
// Block author is not found - burn this TAO
if let Some(subnet_account_id) = Self::get_subnet_account_id(netuid) {
let _ = Self::burn_tao(&subnet_account_id, swap_result.fee_to_block_author.into());
}
}
// Refund the TAO the AMM could not consume (e.g. when the user-supplied
// price limit is hit before the full `tao_staked` is swapped). Without
// this, the unswapped remainder is stranded on the subnet PalletId
// account. Mirrors the alpha refund in `unstake_from_subnet`.
let consumed_tao = swap_result
.amount_paid_in
.saturating_add(swap_result.fee_paid);
let refund_tao = tao_staked.saturating_sub(consumed_tao);
if !refund_tao.is_zero() {
Self::transfer_tao_from_subnet(netuid, coldkey, refund_tao)?;
// `swap_tao_for_alpha` bumped `TotalStake` by the full `tao_staked`;
// only `consumed_tao` actually became stake, so back out the refund.
TotalStake::<T>::mutate(|total| *total = total.saturating_sub(refund_tao));
}
// Record TAO inflow
Self::record_tao_inflow(netuid, swap_result.amount_paid_in.into());
// Cleanup locks if needed
Self::cleanup_lock_if_zero(coldkey, netuid);
LastColdkeyHotkeyStakeBlock::<T>::insert(coldkey, hotkey, Self::get_current_block_as_u64());
// If this is a root-stake
if netuid == NetUid::ROOT {
// Adjust root claimed for this hotkey and coldkey.
let alpha = swap_result.amount_paid_out.into();
Self::add_stake_adjust_root_claimed_for_hotkey_and_coldkey(hotkey, coldkey, alpha);
Self::maybe_add_coldkey_index(coldkey);
}
// Deposit and log the staking event.
Self::deposit_event(Event::StakeAdded(
coldkey.clone(),
hotkey.clone(),
tao_staked,
swap_result.amount_paid_out.into(),
netuid,
swap_result.fee_paid.to_u64(),
));
log::debug!(
"StakeAdded( coldkey: {:?}, hotkey:{:?}, tao: {:?}, alpha:{:?}, netuid: {:?}, fee {} )",
coldkey.clone(),
hotkey.clone(),
tao_staked,
swap_result.amount_paid_out,
netuid,
swap_result.fee_paid,
);
Ok(swap_result.amount_paid_out.into())
}
/// Transfers stake between coldkeys and/or hotkey within one subnet without running it
/// through swap.
///
/// Does not incur any swapping nor fees
pub fn transfer_stake_within_subnet(
origin_coldkey: &T::AccountId,
origin_hotkey: &T::AccountId,
destination_coldkey: &T::AccountId,
destination_hotkey: &T::AccountId,
netuid: NetUid,
alpha: AlphaBalance,
) -> Result<TaoBalance, DispatchError> {
// Transfer lock (may fail if destination coldkey has a conflicting lock)
Self::transfer_lock(origin_coldkey, destination_coldkey, netuid, alpha)?;
// Decrease alpha on origin keys
Self::decrease_stake_for_hotkey_and_coldkey_on_subnet(
origin_hotkey,
origin_coldkey,
netuid,
alpha,
);
if netuid == NetUid::ROOT {
Self::remove_stake_adjust_root_claimed_for_hotkey_and_coldkey(
origin_hotkey,
origin_coldkey,
alpha,
);
}
// Increase alpha on destination keys
Self::increase_stake_for_hotkey_and_coldkey_on_subnet(
destination_hotkey,
destination_coldkey,
netuid,
alpha,
);
if netuid == NetUid::ROOT {
Self::add_stake_adjust_root_claimed_for_hotkey_and_coldkey(
destination_hotkey,
destination_coldkey,
u64::from(alpha).into(),
);
}
// Calculate TAO equivalent based on current price (it is accurate because
// there's no slippage in this move)
let current_price =
<T as pallet::Config>::SwapInterface::current_alpha_price(netuid.into());
let tao_equivalent: TaoBalance = current_price
.saturating_mul(U64F64::saturating_from_num(alpha))
.saturating_to_num::<u64>()
.into();
// Ensure tao_equivalent is above DefaultMinStake
ensure!(
tao_equivalent >= DefaultMinStake::<T>::get(),
Error::<T>::AmountTooLow
);
// Step 3: Update StakingHotkeys if the hotkey's total alpha, across all subnets, is zero
// TODO: fix.
// if Self::get_stake(hotkey, coldkey) == 0 {
// StakingHotkeys::<T>::mutate(coldkey, |hotkeys| {
// hotkeys.retain(|k| k != hotkey);
// });
// }
LastColdkeyHotkeyStakeBlock::<T>::insert(
destination_coldkey,
destination_hotkey,
Self::get_current_block_as_u64(),
);
// Deposit and log the unstaking event.
Self::deposit_event(Event::StakeRemoved(
origin_coldkey.clone(),
origin_hotkey.clone(),
tao_equivalent,
alpha,
netuid,
0_u64, // 0 fee
));
Self::deposit_event(Event::StakeAdded(
destination_coldkey.clone(),
destination_hotkey.clone(),
tao_equivalent,
alpha,
netuid,
0_u64, // 0 fee
));
Ok(tao_equivalent)
}
pub fn get_alpha_share_pool(
hotkey: <T as frame_system::Config>::AccountId,
netuid: NetUid,
) -> SharePool<AlphaShareKey<T>, HotkeyAlphaSharePoolDataOperations<T>> {
let ops = HotkeyAlphaSharePoolDataOperations::new(hotkey, netuid);
SharePool::<AlphaShareKey<T>, HotkeyAlphaSharePoolDataOperations<T>>::new(ops)
}
/// Validate add_stake user input
pub fn validate_add_stake(
coldkey: &T::AccountId,
hotkey: &T::AccountId,
netuid: NetUid,
mut stake_to_be_added: TaoBalance,
max_amount: TaoBalance,
allow_partial: bool,
) -> Result<(), Error<T>> {
// Ensure that the subnet exists.
ensure!(Self::if_subnet_exist(netuid), Error::<T>::SubnetNotExists);
// Ensure that the subnet is enabled.
Self::ensure_subtoken_enabled(netuid)?;
// Get the minimum balance (and amount) that satisfies the transaction
let min_stake = DefaultMinStake::<T>::get();
let min_amount = {
let order = GetAlphaForTao::<T>::with_amount(min_stake);
let fee = T::SwapInterface::sim_swap(netuid.into(), order)
.map(|res| res.fee_paid)
.unwrap_or(T::SwapInterface::approx_fee_amount(
netuid.into(),
min_stake.into(),
));
min_stake.saturating_add(fee.into())
};
// Ensure that the stake_to_be_added is at least the min_amount
ensure!(stake_to_be_added >= min_amount, Error::<T>::AmountTooLow);
// Ensure that if partial execution is not allowed, the amount will not cause
// slippage over desired
if !allow_partial {
ensure!(stake_to_be_added <= max_amount, Error::<T>::SlippageTooHigh);
} else {
stake_to_be_added = max_amount.min(stake_to_be_added);
}
// Ensure the callers coldkey has enough stake to perform the transaction.
ensure!(
Self::can_remove_balance_from_coldkey_account(coldkey, stake_to_be_added.into()),
Error::<T>::NotEnoughBalanceToStake
);
// Ensure that the hotkey account exists this is only possible through registration.
ensure!(
Self::hotkey_account_exists(hotkey),
Error::<T>::HotKeyAccountNotExists
);
let order = GetAlphaForTao::<T>::with_amount(stake_to_be_added);
let swap_result = T::SwapInterface::sim_swap(netuid.into(), order)
.map_err(|_| Error::<T>::InsufficientLiquidity)?;
// Check that actual withdrawn TAO amount is not lower than the minimum stake
ensure!(
swap_result.amount_paid_in >= min_stake,
Error::<T>::AmountTooLow
);
ensure!(
!swap_result.amount_paid_out.is_zero(),
Error::<T>::InsufficientLiquidity
);
// Ensure hotkey pool is precise enough
let try_stake_result = Self::try_increase_stake_for_hotkey_and_coldkey_on_subnet(
hotkey,
netuid,
swap_result.amount_paid_out.into(),
);
ensure!(try_stake_result, Error::<T>::InsufficientLiquidity);
Ok(())
}
/// Validate remove_stake user input
///
pub fn validate_remove_stake(
coldkey: &T::AccountId,
hotkey: &T::AccountId,
netuid: NetUid,
alpha_unstaked: AlphaBalance,
max_amount: AlphaBalance,
allow_partial: bool,
) -> Result<(), Error<T>> {
// Ensure that the subnet exists.
ensure!(Self::if_subnet_exist(netuid), Error::<T>::SubnetNotExists);
// Ensure that the subnet is enabled.
// Self::ensure_subtoken_enabled(netuid)?;
// Do not allow zero unstake amount
ensure!(!alpha_unstaked.is_zero(), Error::<T>::AmountTooLow);
// Ensure that the stake amount to be removed is above the minimum in tao equivalent.
// Bypass this check if the user unstakes full amount
let remaining_alpha_stake =
Self::calculate_reduced_stake_on_subnet(hotkey, coldkey, netuid, alpha_unstaked)?;
let order = GetTaoForAlpha::<T>::with_amount(alpha_unstaked);
match T::SwapInterface::sim_swap(netuid.into(), order) {
Ok(res) => {
if !remaining_alpha_stake.is_zero() {
ensure!(
res.amount_paid_out >= DefaultMinStake::<T>::get(),
Error::<T>::AmountTooLow
);
}
}
Err(_) => return Err(Error::<T>::InsufficientLiquidity),
}
// Ensure that if partial execution is not allowed, the amount will not cause
// slippage over desired
if !allow_partial {
ensure!(alpha_unstaked <= max_amount, Error::<T>::SlippageTooHigh);
}
// Ensure that the hotkey account exists this is only possible through registration.
ensure!(
Self::hotkey_account_exists(hotkey),
Error::<T>::HotKeyAccountNotExists
);
// Ensure that unstaked amount is not greater than available to unstake (due to locks)
Self::ensure_available_to_unstake(coldkey, netuid, alpha_unstaked)?;
Ok(())
}
/// Validate if unstake_all can be executed
///
pub fn validate_unstake_all(
coldkey: &T::AccountId,
hotkey: &T::AccountId,
only_alpha: bool,
) -> Result<(), Error<T>> {
// Get all netuids (filter out root)
let subnets = Self::get_all_subnet_netuids();
// Ensure that the hotkey account exists this is only possible through registration.
ensure!(
Self::hotkey_account_exists(hotkey),
Error::<T>::HotKeyAccountNotExists
);
let mut unstaking_any = false;
for netuid in subnets.iter() {
if only_alpha && netuid.is_root() {
continue;
}
// Get user's stake in this subnet
let alpha = Self::get_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, *netuid);
// Ensure that unstaked amount is not greater than available to unstake (due to locks)
// for this subnet.
Self::ensure_available_to_unstake(coldkey, *netuid, alpha)?;
if Self::validate_remove_stake(coldkey, hotkey, *netuid, alpha, alpha, false).is_ok() {
unstaking_any = true;
}
}
// If no unstaking happens, return error
ensure!(unstaking_any, Error::<T>::AmountTooLow);
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: NetUid,
destination_netuid: NetUid,
alpha_amount: AlphaBalance,
max_amount: AlphaBalance,
maybe_allow_partial: Option<bool>,
check_transfer_toggle: bool,
) -> Result<(), Error<T>> {
// Ensure stake transition is actually happening
if origin_coldkey == destination_coldkey && origin_hotkey == destination_hotkey {
ensure!(origin_netuid != destination_netuid, Error::<T>::SameNetuid);
}
// Ensure that both subnets exist.
ensure!(
Self::if_subnet_exist(origin_netuid),
Error::<T>::SubnetNotExists
);
if origin_netuid != destination_netuid {
ensure!(
Self::if_subnet_exist(destination_netuid),
Error::<T>::SubnetNotExists
);
}
ensure!(
SubtokenEnabled::<T>::get(origin_netuid),
Error::<T>::SubtokenDisabled
);
ensure!(
SubtokenEnabled::<T>::get(destination_netuid),
Error::<T>::SubtokenDisabled
);
// Ensure that the origin hotkey account exists
ensure!(
Self::hotkey_account_exists(origin_hotkey),
Error::<T>::HotKeyAccountNotExists
);
// Ensure that the destination hotkey account exists
ensure!(
Self::hotkey_account_exists(destination_hotkey),
Error::<T>::HotKeyAccountNotExists
);
// 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::<T>::NotEnoughStakeToWithdraw
);
// If origin and destination netuid are different, do the swap-related checks
if origin_netuid != destination_netuid {
// Ensure that the stake amount to be removed is above the minimum in tao equivalent.
let order = GetTaoForAlpha::<T>::with_amount(alpha_amount);
let tao_equivalent = T::SwapInterface::sim_swap(origin_netuid.into(), order)
.map(|res| res.amount_paid_out)
.map_err(|_| Error::<T>::InsufficientLiquidity)?;
ensure!(
tao_equivalent > DefaultMinStake::<T>::get(),
Error::<T>::AmountTooLow
);
// Ensure that if partial execution is not allowed, the amount will not cause
// slippage over desired
if let Some(allow_partial) = maybe_allow_partial
&& !allow_partial
{
ensure!(alpha_amount <= max_amount, Error::<T>::SlippageTooHigh);
}
}
if check_transfer_toggle {
// Ensure transfer is toggled.
ensure!(
TransferToggle::<T>::get(origin_netuid),
Error::<T>::TransferDisallowed
);
if origin_netuid != destination_netuid {
ensure!(
TransferToggle::<T>::get(destination_netuid),
Error::<T>::TransferDisallowed
);
}
}
// Enforce lock invariant: if the is cross-subnet move, the remaining amount must
// cover the lock.
if origin_netuid != destination_netuid {
Self::ensure_available_to_unstake(origin_coldkey, origin_netuid, alpha_amount)?;
}
Ok(())
}
pub fn increase_provided_tao_reserve(netuid: NetUid, tao: TaoBalance) {
if !tao.is_zero() {
SubnetTAO::<T>::mutate(netuid, |total| {
*total = total.saturating_add(tao);
});
}
}
pub fn decrease_provided_tao_reserve(netuid: NetUid, tao: TaoBalance) {
if !tao.is_zero() {
SubnetTAO::<T>::mutate(netuid, |total| {
*total = total.saturating_sub(tao);
});
}
}
pub fn increase_provided_alpha_reserve(netuid: NetUid, alpha: AlphaBalance) {
if !alpha.is_zero() {
SubnetAlphaIn::<T>::mutate(netuid, |total| {
*total = total.saturating_add(alpha);
});
}
}
pub fn decrease_provided_alpha_reserve(netuid: NetUid, alpha: AlphaBalance) {
if !alpha.is_zero() {
SubnetAlphaIn::<T>::mutate(netuid, |total| {
*total = total.saturating_sub(alpha);
});
}
}
}
///////////////////////////////////////////
// Alpha share pool chain data layer
#[derive(Debug)]
pub struct HotkeyAlphaSharePoolDataOperations<T: frame_system::Config> {
netuid: NetUid,
hotkey: <T as frame_system::Config>::AccountId,
_marker: sp_std::marker::PhantomData<T>,
}
impl<T: Config> HotkeyAlphaSharePoolDataOperations<T> {
fn new(hotkey: <T as frame_system::Config>::AccountId, netuid: NetUid) -> Self {
HotkeyAlphaSharePoolDataOperations {
netuid,
hotkey,
_marker: sp_std::marker::PhantomData,
}
}
}
// Alpha share key is coldkey because the HotkeyAlphaSharePoolDataOperations struct already has hotkey and netuid
type AlphaShareKey<T> = <T as frame_system::Config>::AccountId;
impl<T: Config> SharePoolDataOperations<AlphaShareKey<T>>
for HotkeyAlphaSharePoolDataOperations<T>
{
fn get_shared_value(&self) -> u64 {
u64::from(TotalHotkeyAlpha::<T>::get(&self.hotkey, self.netuid))
}
fn get_share(&self, key: &AlphaShareKey<T>) -> SafeFloat {
// Read the deprecated Alpha map first and, if value is not available, try new AlphaV2
let maybe_share_v1 = Alpha::<T>::try_get((&(self.hotkey), key, self.netuid));
if let Ok(share_v1) = maybe_share_v1 {
return SafeFloat::from(share_v1);
}
AlphaV2::<T>::get((&(self.hotkey), key, self.netuid))
}
fn try_get_share(&self, key: &AlphaShareKey<T>) -> Result<SafeFloat, ()> {
// Read the deprecated Alpha map first and, if value is not available, try new AlphaV2
let maybe_share_v1 = Alpha::<T>::try_get((&(self.hotkey), key, self.netuid));
if let Ok(share_v1) = maybe_share_v1 {
return Ok(SafeFloat::from(share_v1));
}
let maybe_share = AlphaV2::<T>::try_get((&(self.hotkey), key, self.netuid));
if let Ok(share) = maybe_share {
Ok(share)
} else {
Err(())
}
}
fn get_denominator(&self) -> SafeFloat {
// Read the deprecated TotalHotkeyShares map first and, if value is not available, try new TotalHotkeySharesV2
let maybe_denomnator_v1 = TotalHotkeyShares::<T>::try_get(&(self.hotkey), self.netuid);
if let Ok(denomnator_v1) = maybe_denomnator_v1 {
return SafeFloat::from(denomnator_v1);
}
TotalHotkeySharesV2::<T>::get(&(self.hotkey), self.netuid)
}
fn set_shared_value(&mut self, value: u64) {
if value != 0 {
TotalHotkeyAlpha::<T>::insert(&(self.hotkey), self.netuid, AlphaBalance::from(value));
} else {
TotalHotkeyAlpha::<T>::remove(&(self.hotkey), self.netuid);
}
}
fn set_share(&mut self, key: &AlphaShareKey<T>, share: SafeFloat) {
// Lazy Alpha -> AlphaV2 migration happens right here
// Delete the Alpha entry, insert into AlphaV2
let maybe_share_v1 = Alpha::<T>::try_get((&(self.hotkey), key, self.netuid));
if maybe_share_v1.is_ok() {
Alpha::<T>::remove((&self.hotkey, key, self.netuid));
}
if !share.is_zero() {
AlphaV2::<T>::insert((&self.hotkey, key, self.netuid), share);
} else {
AlphaV2::<T>::remove((&self.hotkey, key, self.netuid));
}
}
fn set_denominator(&mut self, update: SafeFloat) {
// Lazy TotalHotkeyShares -> TotalHotkeySharesV2 migration happens right here
// Delete the TotalHotkeyShares entry, insert into TotalHotkeySharesV2
let maybe_denominator_v1 = TotalHotkeyShares::<T>::try_get(&(self.hotkey), self.netuid);
if maybe_denominator_v1.is_ok() {
TotalHotkeyShares::<T>::remove(&self.hotkey, self.netuid);
}
if !update.is_zero() {
TotalHotkeySharesV2::<T>::insert(&self.hotkey, self.netuid, update);
} else {
TotalHotkeySharesV2::<T>::remove(&self.hotkey, self.netuid);
}
}
}