Skip to content
44 changes: 22 additions & 22 deletions pallets/subtensor/src/macros/dispatches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ mod dispatches {
#[pallet::call_index(1)]
#[pallet::weight((Weight::from_parts(3_657_000, 0)
.saturating_add(T::DbWeight::get().reads(0))
.saturating_add(T::DbWeight::get().writes(0)), DispatchClass::Normal, Pays::No))]
.saturating_add(T::DbWeight::get().writes(0)), DispatchClass::Normal, Pays::Yes))]
pub fn become_delegate(_origin: OriginFor<T>, _hotkey: T::AccountId) -> DispatchResult {
// DEPRECATED
// Self::do_become_delegate(origin, hotkey, Self::get_default_delegate_take())
Expand Down Expand Up @@ -587,7 +587,7 @@ mod dispatches {
#[pallet::call_index(2)]
#[pallet::weight((Weight::from_parts(345_500_000, 0)
.saturating_add(T::DbWeight::get().reads(26))
.saturating_add(T::DbWeight::get().writes(15)), DispatchClass::Normal, Pays::No))]
.saturating_add(T::DbWeight::get().writes(15)), DispatchClass::Normal, Pays::Yes))]
pub fn add_stake(
origin: OriginFor<T>,
hotkey: T::AccountId,
Expand Down Expand Up @@ -631,7 +631,7 @@ mod dispatches {
#[pallet::call_index(3)]
#[pallet::weight((Weight::from_parts(196_800_000, 0)
.saturating_add(T::DbWeight::get().reads(19))
.saturating_add(T::DbWeight::get().writes(10)), DispatchClass::Normal, Pays::No))]
.saturating_add(T::DbWeight::get().writes(10)), DispatchClass::Normal, Pays::Yes))]
pub fn remove_stake(
origin: OriginFor<T>,
hotkey: T::AccountId,
Expand Down Expand Up @@ -1046,11 +1046,11 @@ mod dispatches {
#[pallet::call_index(69)]
#[pallet::weight((
Weight::from_parts(5_760_000, 0)
.saturating_add(T::DbWeight::get().reads(0))
.saturating_add(T::DbWeight::get().writes(1)),
DispatchClass::Operational,
Pays::No
))]
.saturating_add(T::DbWeight::get().reads(0))
.saturating_add(T::DbWeight::get().writes(1)),
DispatchClass::Operational,
Pays::No
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this Pays:No when add is yes?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then unstake all is Yes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sudo_set_tx_childkey_take_rate_limit is a root-only transaction. No spam risk.

))]
pub fn sudo_set_tx_childkey_take_rate_limit(
origin: OriginFor<T>,
tx_rate_limit: u64,
Expand Down Expand Up @@ -1577,7 +1577,7 @@ mod dispatches {
#[pallet::call_index(83)]
#[pallet::weight((Weight::from_parts(30_190_000, 0)
.saturating_add(T::DbWeight::get().reads(6))
.saturating_add(T::DbWeight::get().writes(0)), DispatchClass::Operational, Pays::No))]
.saturating_add(T::DbWeight::get().writes(0)), DispatchClass::Operational, Pays::Yes))]
pub fn unstake_all(origin: OriginFor<T>, hotkey: T::AccountId) -> DispatchResult {
Self::do_unstake_all(origin, hotkey)
}
Expand Down Expand Up @@ -1610,7 +1610,7 @@ mod dispatches {
#[pallet::call_index(84)]
#[pallet::weight((Weight::from_parts(369_500_000, 0)
.saturating_add(T::DbWeight::get().reads(33))
.saturating_add(T::DbWeight::get().writes(16)), DispatchClass::Operational, Pays::No))]
.saturating_add(T::DbWeight::get().writes(16)), DispatchClass::Operational, Pays::Yes))]
pub fn unstake_all_alpha(origin: OriginFor<T>, hotkey: T::AccountId) -> DispatchResult {
Self::do_unstake_all_alpha(origin, hotkey)
}
Expand All @@ -1637,9 +1637,9 @@ mod dispatches {
/// - The alpha stake amount to move.
///
#[pallet::call_index(85)]
#[pallet::weight((Weight::from_parts(419_500_000, 0)
.saturating_add(T::DbWeight::get().reads(32))
.saturating_add(T::DbWeight::get().writes(20)), DispatchClass::Operational, Pays::No))]
#[pallet::weight((Weight::from_parts(157_100_000, 0)
.saturating_add(T::DbWeight::get().reads(15_u64))
.saturating_add(T::DbWeight::get().writes(7_u64)), DispatchClass::Operational, Pays::Yes))]
pub fn move_stake(
origin: T::RuntimeOrigin,
origin_hotkey: T::AccountId,
Expand Down Expand Up @@ -1680,9 +1680,9 @@ mod dispatches {
/// # Events
/// May emit a `StakeTransferred` event on success.
#[pallet::call_index(86)]
#[pallet::weight((Weight::from_parts(432_600_000, 0)
.saturating_add(T::DbWeight::get().reads(31))
.saturating_add(T::DbWeight::get().writes(19)), DispatchClass::Operational, Pays::No))]
#[pallet::weight((Weight::from_parts(154_800_000, 0)
.saturating_add(T::DbWeight::get().reads(13_u64))
.saturating_add(T::DbWeight::get().writes(6_u64)), DispatchClass::Operational, Pays::Yes))]
pub fn transfer_stake(
origin: T::RuntimeOrigin,
destination_coldkey: T::AccountId,
Expand Down Expand Up @@ -1726,7 +1726,7 @@ mod dispatches {
.saturating_add(T::DbWeight::get().reads(32))
.saturating_add(T::DbWeight::get().writes(17)),
DispatchClass::Operational,
Pays::No
Pays::Yes
))]
pub fn swap_stake(
origin: T::RuntimeOrigin,
Expand Down Expand Up @@ -1789,7 +1789,7 @@ mod dispatches {
#[pallet::call_index(88)]
#[pallet::weight((Weight::from_parts(402_800_000, 0)
.saturating_add(T::DbWeight::get().reads(26))
.saturating_add(T::DbWeight::get().writes(15)), DispatchClass::Normal, Pays::No))]
.saturating_add(T::DbWeight::get().writes(15)), DispatchClass::Normal, Pays::Yes))]
pub fn add_stake_limit(
origin: OriginFor<T>,
hotkey: T::AccountId,
Expand Down Expand Up @@ -1853,7 +1853,7 @@ mod dispatches {
#[pallet::call_index(89)]
#[pallet::weight((Weight::from_parts(403_800_000, 0)
.saturating_add(T::DbWeight::get().reads(30))
.saturating_add(T::DbWeight::get().writes(14)), DispatchClass::Normal, Pays::No))]
.saturating_add(T::DbWeight::get().writes(14)), DispatchClass::Normal, Pays::Yes))]
pub fn remove_stake_limit(
origin: OriginFor<T>,
hotkey: T::AccountId,
Expand Down Expand Up @@ -1899,7 +1899,7 @@ mod dispatches {
.saturating_add(T::DbWeight::get().reads(32))
.saturating_add(T::DbWeight::get().writes(17)),
DispatchClass::Operational,
Pays::No
Pays::Yes
))]
pub fn swap_stake_limit(
origin: T::RuntimeOrigin,
Expand Down Expand Up @@ -1996,7 +1996,7 @@ mod dispatches {
#[pallet::weight((
Weight::from_parts(3_000_000, 0).saturating_add(T::DbWeight::get().reads_writes(2, 1)),
DispatchClass::Operational,
Pays::No
Pays::Yes
))]
pub fn associate_evm_key(
origin: T::RuntimeOrigin,
Expand Down Expand Up @@ -2077,7 +2077,7 @@ mod dispatches {
#[pallet::call_index(103)]
#[pallet::weight((Weight::from_parts(398_000_000, 10142)
.saturating_add(T::DbWeight::get().reads(30_u64))
.saturating_add(T::DbWeight::get().writes(14_u64)), DispatchClass::Normal, Pays::No))]
.saturating_add(T::DbWeight::get().writes(14_u64)), DispatchClass::Normal, Pays::Yes))]
pub fn remove_stake_full_limit(
origin: T::RuntimeOrigin,
hotkey: T::AccountId,
Expand Down
8 changes: 6 additions & 2 deletions pallets/subtensor/src/rpc_info/stake_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,11 @@ impl<T: Config> Pallet<T> {
_destination_coldkey_account: T::AccountId,
amount: u64,
) -> u64 {
let netuid = destination.or(origin).map(|v| v.1).unwrap_or_default();
T::SwapInterface::approx_fee_amount(netuid.into(), amount)
if destination == origin {
0_u64
} else {
let netuid = destination.or(origin).map(|v| v.1).unwrap_or_default();
T::SwapInterface::approx_fee_amount(netuid.into(), amount)
}
}
}
69 changes: 42 additions & 27 deletions pallets/subtensor/src/staking/move_stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,12 @@ impl<T: Config> Pallet<T> {
set_limit: bool,
) -> Result<u64, DispatchError> {
// Calculate the maximum amount that can be executed
let max_amount = if let Some(limit_price) = maybe_limit_price {
Self::get_max_amount_move(origin_netuid, destination_netuid, limit_price)?
let max_amount = if origin_netuid != destination_netuid {
if let Some(limit_price) = maybe_limit_price {
Self::get_max_amount_move(origin_netuid, destination_netuid, limit_price)?
} else {
alpha_amount
}
} else {
alpha_amount
};
Expand All @@ -356,36 +360,47 @@ impl<T: Config> Pallet<T> {
max_amount
};

// do not pay fees to avoid double fees in moves transactions
let tao_unstaked = Self::unstake_from_subnet(
origin_hotkey,
origin_coldkey,
origin_netuid,
move_amount,
T::SwapInterface::min_price(),
true,
)?;
if origin_netuid != destination_netuid {
// do not pay remove fees to avoid double fees in moves transactions
let tao_unstaked = Self::unstake_from_subnet(
origin_hotkey,
origin_coldkey,
origin_netuid,
move_amount,
T::SwapInterface::min_price(),
true,
)?;

// Stake the unstaked amount into the destination.
// Because of the fee, the tao_unstaked may be too low if initial stake is low. In that case,
// do not restake.
if tao_unstaked >= DefaultMinStake::<T>::get() {
// If the coldkey is not the owner, make the hotkey a delegate.
if Self::get_owning_coldkey_for_hotkey(destination_hotkey) != *destination_coldkey {
Self::maybe_become_delegate(destination_hotkey);
// Stake the unstaked amount into the destination.
// Because of the fee, the tao_unstaked may be too low if initial stake is low. In that case,
// do not restake.
if tao_unstaked >= DefaultMinStake::<T>::get() {
// If the coldkey is not the owner, make the hotkey a delegate.
if Self::get_owning_coldkey_for_hotkey(destination_hotkey) != *destination_coldkey {
Self::maybe_become_delegate(destination_hotkey);
}

Self::stake_into_subnet(
destination_hotkey,
destination_coldkey,
destination_netuid,
tao_unstaked,
T::SwapInterface::max_price(),
set_limit,
)?;
}

Self::stake_into_subnet(
destination_hotkey,
Ok(tao_unstaked)
} else {
Self::transfer_stake_within_subnet(
origin_coldkey,
origin_hotkey,
destination_coldkey,
destination_netuid,
tao_unstaked,
T::SwapInterface::max_price(),
set_limit,
)?;
destination_hotkey,
origin_netuid,
move_amount,
)
}

Ok(tao_unstaked)
}

/// Returns the maximum amount of origin netuid Alpha that can be executed before we cross
Expand Down
118 changes: 100 additions & 18 deletions pallets/subtensor/src/staking/stake_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,83 @@ impl<T: Config> Pallet<T> {
Ok(swap_result.amount_paid_out)
}

/// 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: u64,
) -> Result<u64, DispatchError> {
// Decrease alpha on origin keys
let actual_alpha_decrease = Self::decrease_stake_for_hotkey_and_coldkey_on_subnet(
origin_hotkey,
origin_coldkey,
netuid,
alpha,
);

// Increase alpha on destination keys
let actual_alpha_moved = Self::increase_stake_for_hotkey_and_coldkey_on_subnet(
destination_hotkey,
destination_coldkey,
netuid,
actual_alpha_decrease,
);

// 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 = current_price
.saturating_mul(U96F32::saturating_from_num(actual_alpha_moved))
.saturating_to_num::<u64>();

// 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have this snippet commented?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs a better fix than just O(n) iteration over all all subnets for this hotkey. Probably we should store aggregate alpha for each hotkey or some king of bitmap that shows whether the hotkey has any alpha in a subnet. This is an older tech debt.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it valid without the update? Can we get away with the postponed solution?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya we can clean up the map later if needed. There's a call for it also

// 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,
actual_alpha_decrease,
netuid,
0_u64, // 0 fee
));
Self::deposit_event(Event::StakeAdded(
destination_coldkey.clone(),
destination_hotkey.clone(),
tao_equivalent,
actual_alpha_moved,
netuid,
0_u64, // 0 fee
));

Ok(tao_equivalent)
}

pub fn get_alpha_share_pool(
hotkey: <T as frame_system::Config>::AccountId,
netuid: NetUid,
Expand Down Expand Up @@ -1119,21 +1196,24 @@ impl<T: Config> Pallet<T> {
Error::<T>::NotEnoughStakeToWithdraw
);

// Ensure that the stake amount to be removed is above the minimum in tao equivalent.
let tao_equivalent =
T::SwapInterface::sim_swap(origin_netuid.into(), OrderType::Sell, alpha_amount)
.map(|res| res.amount_paid_out)
.map_err(|_| Error::<T>::InsufficientLiquidity)?;
ensure!(
tao_equivalent > DefaultMinStake::<T>::get(),
Error::<T>::AmountTooLow
);
// 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 tao_equivalent =
T::SwapInterface::sim_swap(origin_netuid.into(), OrderType::Sell, alpha_amount)
.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 {
if !allow_partial {
ensure!(alpha_amount <= max_amount, Error::<T>::SlippageTooHigh);
// Ensure that if partial execution is not allowed, the amount will not cause
// slippage over desired
if let Some(allow_partial) = maybe_allow_partial {
if !allow_partial {
ensure!(alpha_amount <= max_amount, Error::<T>::SlippageTooHigh);
}
}
}

Expand All @@ -1143,10 +1223,12 @@ impl<T: Config> Pallet<T> {
TransferToggle::<T>::get(origin_netuid),
Error::<T>::TransferDisallowed
);
ensure!(
TransferToggle::<T>::get(destination_netuid),
Error::<T>::TransferDisallowed
);
if origin_netuid != destination_netuid {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saves one read

We need to make sure we check only for coldkey dest != coldkey origin

ensure!(
TransferToggle::<T>::get(destination_netuid),
Error::<T>::TransferDisallowed
);
}
}

Ok(())
Expand Down
Loading
Loading