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
14 changes: 13 additions & 1 deletion pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ pub mod pallet {
use sp_std::collections::vec_deque::VecDeque;
use sp_std::vec;
use sp_std::vec::Vec;
use substrate_fixed::types::U64F64;
use substrate_fixed::types::{I96F32, U64F64};
use subtensor_macros::freeze_struct;

#[cfg(not(feature = "std"))]
Expand Down Expand Up @@ -733,6 +733,12 @@ pub mod pallet {
U64F64::saturating_from_num(0)
}

#[pallet::type_value]
/// Default value for minimum liquidity in pool
pub fn DefaultMinimumPoolLiquidity<T: Config>() -> I96F32 {
I96F32::saturating_from_num(1_000_000)
}

#[pallet::storage]
pub type ColdkeySwapScheduleDuration<T: Config> =
StorageValue<_, BlockNumberFor<T>, ValueQuery, DefaultColdkeySwapScheduleDuration<T>>;
Expand Down Expand Up @@ -1562,6 +1568,7 @@ pub enum CustomTransactionError {
HotkeyAccountDoesntExist,
NotEnoughStakeToWithdraw,
RateLimitExceeded,
InsufficientLiquidity,
BadRequest,
}

Expand All @@ -1575,6 +1582,7 @@ impl From<CustomTransactionError> for u8 {
CustomTransactionError::HotkeyAccountDoesntExist => 4,
CustomTransactionError::NotEnoughStakeToWithdraw => 5,
CustomTransactionError::RateLimitExceeded => 6,
CustomTransactionError::InsufficientLiquidity => 7,
CustomTransactionError::BadRequest => 255,
}
}
Expand Down Expand Up @@ -1642,6 +1650,10 @@ where
CustomTransactionError::NotEnoughStakeToWithdraw.into(),
)
.into()),
Error::<T>::InsufficientLiquidity => Err(InvalidTransaction::Custom(
CustomTransactionError::InsufficientLiquidity.into(),
)
.into()),
_ => Err(
InvalidTransaction::Custom(CustomTransactionError::BadRequest.into()).into(),
),
Expand Down
2 changes: 2 additions & 0 deletions pallets/subtensor/src/macros/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,5 +185,7 @@ mod errors {
CommittingWeightsTooFast,
/// Stake amount is too low.
AmountTooLow,
/// Not enough liquidity.
InsufficientLiquidity,
}
}
235 changes: 120 additions & 115 deletions pallets/subtensor/src/staking/stake_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,155 +457,148 @@ impl<T: Config> Pallet<T> {
}
}

/// Swaps TAO for the alpha token on the subnet.
/// Calculates Some(Alpha) returned from pool by staking operation
/// if liquidity allows that. If not, returns None.
///
/// Updates TaoIn, AlphaIn, and AlphaOut
pub fn sim_swap_tao_for_alpha(netuid: u16, tao: u64) -> u64 {
/// If new alpha_reserve is about to drop below DefaultMinimumPoolLiquidity,
/// then don't do it.
///
pub fn sim_swap_tao_for_alpha(netuid: u16, tao: u64) -> Option<u64> {
// 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: Initialized vars.
let alpha: I96F32 = if mechanism_id == 1 {
if mechanism_id == 1 {
// Step 3.a.1: Dynamic mechanism calculations
let tao_reserves: I96F32 = I96F32::saturating_from_num(SubnetTAO::<T>::get(netuid));
let alpha_reserves: I96F32 =
I96F32::saturating_from_num(SubnetAlphaIn::<T>::get(netuid));
// Step 3.a.2: Compute constant product k = alpha * tao
let k: I96F32 = alpha_reserves.saturating_mul(tao_reserves);

// Calculate new alpha reserve
let new_alpha_reserves: I96F32 = k
.checked_div(tao_reserves.saturating_add(I96F32::saturating_from_num(tao)))
.unwrap_or(I96F32::saturating_from_num(0));

// Step 3.a.3: Calculate alpha staked using the constant product formula
// alpha_stake_recieved = current_alpha - (k / (current_tao + new_tao))
alpha_reserves.saturating_sub(
k.checked_div(tao_reserves.saturating_add(I96F32::saturating_from_num(tao)))
.unwrap_or(I96F32::saturating_from_num(0)),
)
if new_alpha_reserves >= DefaultMinimumPoolLiquidity::<T>::get() {
Some(
alpha_reserves
.saturating_sub(new_alpha_reserves)
.saturating_to_num::<u64>(),
)
} else {
None
}
} else {
// Step 3.b.1: Stable mechanism, just return the value 1:1
I96F32::saturating_from_num(tao)
};
// Return simulated amount.
alpha.saturating_to_num::<u64>()
Some(tao)
}
}

/// Swaps a subnet's Alpba token for TAO.
/// Calculates Some(Tao) returned from pool by unstaking operation
/// if liquidity allows that. If not, returns None.
///
/// Updates TaoIn, AlphaIn, and AlphaOut
pub fn sim_swap_alpha_for_tao(netuid: u16, alpha: u64) -> u64 {
/// If new tao_reserve is about to drop below DefaultMinimumPoolLiquidity,
/// then don't do it.
///
pub fn sim_swap_alpha_for_tao(netuid: u16, alpha: u64) -> Option<u64> {
// 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 tao: I96F32 = if mechanism_id == 1 {
if mechanism_id == 1 {
// Step 3.a.1: Dynamic mechanism calculations
let tao_reserves: I96F32 = I96F32::saturating_from_num(SubnetTAO::<T>::get(netuid));
let alpha_reserves: I96F32 =
I96F32::saturating_from_num(SubnetAlphaIn::<T>::get(netuid));
// Step 3.a.2: Compute constant product k = alpha * tao
let k: I96F32 = alpha_reserves.saturating_mul(tao_reserves);

// Calculate new tao reserve
let new_tao_reserves: I96F32 = k
.checked_div(alpha_reserves.saturating_add(I96F32::saturating_from_num(alpha)))
.unwrap_or(I96F32::saturating_from_num(0));

// Step 3.a.3: Calculate alpha staked using the constant product formula
// tao_recieved = tao_reserves - (k / (alpha_reserves + new_tao))
tao_reserves.saturating_sub(
k.checked_div(alpha_reserves.saturating_add(I96F32::saturating_from_num(alpha)))
.unwrap_or(I96F32::saturating_from_num(0)),
)
if new_tao_reserves >= DefaultMinimumPoolLiquidity::<T>::get() {
Some(
tao_reserves
.saturating_sub(new_tao_reserves)
.saturating_to_num::<u64>(),
)
} else {
None
}
} else {
// Step 3.b.1: Stable mechanism, just return the value 1:1
I96F32::saturating_from_num(alpha)
};
tao.saturating_to_num::<u64>()
Some(alpha)
}
}

/// Swaps TAO for the alpha token on the subnet.
///
/// Updates TaoIn, AlphaIn, and AlphaOut
pub fn swap_tao_for_alpha(netuid: u16, tao: u64) -> u64 {
// 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: Initialized vars.
let alpha: I96F32 = if mechanism_id == 1 {
// Step 3.a.1: Dynamic mechanism calculations
let tao_reserves: I96F32 = I96F32::saturating_from_num(SubnetTAO::<T>::get(netuid));
let alpha_reserves: I96F32 =
I96F32::saturating_from_num(SubnetAlphaIn::<T>::get(netuid));
// Step 3.a.2: Compute constant product k = alpha * tao
let k: I96F32 = alpha_reserves.saturating_mul(tao_reserves);
// Step 3.a.3: Calculate alpha staked using the constant product formula
// alpha_stake_recieved = current_alpha - (k / (current_tao + new_tao))
alpha_reserves.saturating_sub(
k.checked_div(tao_reserves.saturating_add(I96F32::saturating_from_num(tao)))
.unwrap_or(I96F32::saturating_from_num(0)),
)
if let Some(alpha) = Self::sim_swap_tao_for_alpha(netuid, tao) {
// Step 4. Decrease Alpha reserves.
SubnetAlphaIn::<T>::mutate(netuid, |total| {
*total = total.saturating_sub(alpha);
});
// Step 5: Increase Alpha outstanding.
SubnetAlphaOut::<T>::mutate(netuid, |total| {
*total = total.saturating_add(alpha);
});
// Step 6: Increase Tao reserves.
SubnetTAO::<T>::mutate(netuid, |total| {
*total = total.saturating_add(tao);
});
// Step 7: Increase Total Tao reserves.
TotalStake::<T>::mutate(|total| {
*total = total.saturating_add(tao);
});
// Step 8. Decrease Alpha reserves.
SubnetVolume::<T>::mutate(netuid, |total| {
*total = total.saturating_add(tao);
});
// Step 9. Return the alpha received.
alpha
} else {
// Step 3.b.1: Stable mechanism, just return the value 1:1
I96F32::saturating_from_num(tao)
};
// Step 4. Decrease Alpha reserves.
SubnetAlphaIn::<T>::mutate(netuid, |total| {
*total = total.saturating_sub(alpha.saturating_to_num::<u64>());
});
// Step 5: Increase Alpha outstanding.
SubnetAlphaOut::<T>::mutate(netuid, |total| {
*total = total.saturating_add(alpha.saturating_to_num::<u64>());
});
// Step 6: Increase Tao reserves.
SubnetTAO::<T>::mutate(netuid, |total| {
*total = total.saturating_add(tao);
});
// Step 7: Increase Total Tao reserves.
TotalStake::<T>::mutate(|total| {
*total = total.saturating_add(tao);
});
// Step 8. Decrease Alpha reserves.
SubnetVolume::<T>::mutate(netuid, |total| {
*total = total.saturating_sub(tao);
});
// Step 9. Return the alpha received.
alpha.saturating_to_num::<u64>()
0
}
}

/// Swaps a subnet's Alpba token for TAO.
///
/// Updates TaoIn, AlphaIn, and AlphaOut
pub fn swap_alpha_for_tao(netuid: u16, alpha: u64) -> u64 {
// 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 tao: I96F32 = if mechanism_id == 1 {
// Step 3.a.1: Dynamic mechanism calculations
let tao_reserves: I96F32 = I96F32::saturating_from_num(SubnetTAO::<T>::get(netuid));
let alpha_reserves: I96F32 =
I96F32::saturating_from_num(SubnetAlphaIn::<T>::get(netuid));
// Step 3.a.2: Compute constant product k = alpha * tao
let k: I96F32 = alpha_reserves.saturating_mul(tao_reserves);
// Step 3.a.3: Calculate alpha staked using the constant product formula
// tao_recieved = tao_reserves - (k / (alpha_reserves + new_tao))
tao_reserves.saturating_sub(
k.checked_div(alpha_reserves.saturating_add(I96F32::saturating_from_num(alpha)))
.unwrap_or(I96F32::saturating_from_num(0)),
)
if let Some(tao) = Self::sim_swap_alpha_for_tao(netuid, alpha) {
// Step 4: Increase Alpha reserves.
SubnetAlphaIn::<T>::mutate(netuid, |total| {
*total = total.saturating_add(alpha);
});
// Step 5: Decrease Alpha outstanding.
SubnetAlphaOut::<T>::mutate(netuid, |total| {
*total = total.saturating_sub(alpha);
});
// Step 6: Decrease tao reserves.
SubnetTAO::<T>::mutate(netuid, |total| {
*total = total.saturating_sub(tao);
});
// Step 7: Reduce total TAO reserves.
TotalStake::<T>::mutate(|total| {
*total = total.saturating_sub(tao);
});
// Step 8. Decrease Alpha reserves.
SubnetVolume::<T>::mutate(netuid, |total| {
*total = total.saturating_add(tao);
});
// Step 9. Return the tao received.
tao
} else {
// Step 3.b.1: Stable mechanism, just return the value 1:1
I96F32::saturating_from_num(alpha)
};
// Step 4: Increase Alpha reserves.
SubnetAlphaIn::<T>::mutate(netuid, |total| {
*total = total.saturating_add(alpha);
});
// Step 5: Decrease Alpha outstanding.
SubnetAlphaOut::<T>::mutate(netuid, |total| {
*total = total.saturating_sub(alpha);
});
// Step 6: Decrease tao reserves.
SubnetTAO::<T>::mutate(netuid, |total| {
*total = total.saturating_sub(tao.saturating_to_num::<u64>());
});
// Step 7: Reduce total TAO reserves.
TotalStake::<T>::mutate(|total| {
*total = total.saturating_sub(tao.saturating_to_num::<u64>());
});
// Step 8. Decrease Alpha reserves.
SubnetVolume::<T>::mutate(netuid, |total| {
*total = total.saturating_sub(tao.saturating_to_num::<u64>());
});
// Step 9. Return the tao received.
tao.saturating_to_num::<u64>()
0
}
}

/// Unstakes alpha from a subnet for a given hotkey and coldkey pair.
Expand Down Expand Up @@ -759,6 +752,12 @@ impl<T: Config> Pallet<T> {
Error::<T>::HotKeyAccountNotExists
);

// Ensure that we have adequate liquidity
ensure!(
Self::sim_swap_tao_for_alpha(netuid, stake_to_be_added).is_some(),
Error::<T>::InsufficientLiquidity
);

Ok(())
}

Expand All @@ -774,11 +773,14 @@ impl<T: Config> Pallet<T> {
ensure!(Self::if_subnet_exist(netuid), Error::<T>::SubnetNotExists);

// Ensure that the stake amount to be removed is above the minimum in tao equivalent.
let tao_equivalent = Self::sim_swap_alpha_for_tao(netuid, alpha_unstaked);
ensure!(
tao_equivalent > DefaultMinStake::<T>::get(),
Error::<T>::AmountTooLow
);
if let Some(tao_equivalent) = Self::sim_swap_alpha_for_tao(netuid, alpha_unstaked) {
ensure!(
tao_equivalent > DefaultMinStake::<T>::get(),
Error::<T>::AmountTooLow
);
} else {
return Err(Error::<T>::InsufficientLiquidity);
};

// Ensure that the hotkey account exists this is only possible through registration.
ensure!(
Expand Down Expand Up @@ -843,11 +845,14 @@ impl<T: Config> Pallet<T> {
);

// Ensure that the stake amount to be removed is above the minimum in tao equivalent.
let tao_equivalent = Self::sim_swap_alpha_for_tao(origin_netuid, alpha_amount);
ensure!(
tao_equivalent > DefaultMinStake::<T>::get(),
Error::<T>::AmountTooLow
);
if let Some(tao_equivalent) = Self::sim_swap_alpha_for_tao(origin_netuid, alpha_amount) {
ensure!(
tao_equivalent > DefaultMinStake::<T>::get(),
Error::<T>::AmountTooLow
);
} else {
return Err(Error::<T>::InsufficientLiquidity);
}

Ok(())
}
Expand Down
9 changes: 7 additions & 2 deletions pallets/subtensor/src/tests/move_stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -724,7 +724,7 @@ fn test_do_move_storage_updates() {

// 18. test_do_move_max_values
// Description: Test moving the maximum possible stake values to check for overflows
// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test move -- test_do_move_max_values --exact --nocapture
// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::move_stake::test_do_move_max_values --exact --show-output
#[test]
fn test_do_move_max_values() {
new_test_ext(1).execute_with(|| {
Expand All @@ -740,6 +740,11 @@ fn test_do_move_max_values() {
// Set up initial stake with maximum value
SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey);
SubtensorModule::create_account_if_non_existent(&coldkey, &destination_hotkey);

// Add lots of liquidity to bypass low liquidity check
SubnetTAO::<Test>::insert(netuid, u64::MAX / 1000);
SubnetAlphaIn::<Test>::insert(netuid, u64::MAX / 1000);

SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, max_stake, fee);
let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(
&origin_hotkey,
Expand Down Expand Up @@ -773,7 +778,7 @@ fn test_do_move_max_values() {
netuid
),
alpha,
epsilon = 5
epsilon = alpha / 1_000_000
);
});
}
Expand Down
Loading
Loading