code/pallets/subtensor/src/staking/add_stake.rs

add_stake.rs

185 lines · 6,736 bytes · 19a6485969RawGitHub
use subtensor_runtime_common::{NetUid, TaoBalance};
use subtensor_swap_interface::{Order, SwapHandler};

use super::*;

impl<T: Config> Pallet<T> {
    /// The implementation for the extrinsic add_stake: Adds stake to a hotkey account.
    ///
    /// # Arguments
    /// * `origin`: The signature of the caller's coldkey.
    ///
    /// * `hotkey`: The associated hotkey account.
    ///
    /// * `netuid`: Subnetwork UID.
    ///
    /// * `stake_to_be_added`: The amount of stake to be added to the hotkey staking account.
    ///
    /// # Events
    /// * `StakeAdded`: On the successfully adding stake to a global account.
    ///
    /// # Errors
    /// * `NotEnoughBalanceToStake`: Not enough balance on the coldkey to add onto the global account.
    ///
    /// * `NonAssociatedColdKey`: The calling coldkey is not associated with this hotkey.
    ///
    /// * `BalanceWithdrawalError`: Errors stemming from transaction pallet.
    ///
    /// * `TxRateLimitExceeded`: Thrown if key has hit transaction rate limit.
    ///
    pub fn do_add_stake(
        origin: OriginFor<T>,
        hotkey: T::AccountId,
        netuid: NetUid,
        stake_to_be_added: TaoBalance,
    ) -> Result<AlphaBalance, DispatchError> {
        // 1. We check that the transaction is signed by the caller and retrieve the T::AccountId coldkey information.
        let coldkey = ensure_signed(origin)?;
        log::debug!(
            "do_add_stake( origin:{coldkey:?} hotkey:{hotkey:?}, netuid:{netuid:?}, stake_to_be_added:{stake_to_be_added:?} )"
        );

        Self::ensure_add_stake_input_within_swap_limit(netuid, stake_to_be_added)?;

        // 2. Validate user input
        Self::validate_add_stake(
            &coldkey,
            &hotkey,
            netuid,
            stake_to_be_added,
            stake_to_be_added,
            false,
        )?;

        // 3. Swap the stake into alpha on the subnet and increase counters.
        // Emit the staking event.
        Self::stake_into_subnet(
            &hotkey,
            &coldkey,
            netuid,
            stake_to_be_added,
            T::SwapInterface::max_price(),
            false,
        )
    }

    /// The implementation for the extrinsic add_stake_limit: Adds stake to a hotkey
    /// account on a subnet with price limit.
    ///
    /// # Arguments
    /// * `origin`: The signature of the caller's coldkey.
    ///
    /// * `hotkey`: The associated hotkey account.
    ///
    /// * `netuid`: Subnetwork UID.
    ///
    /// * `stake_to_be_added`: The amount of stake to be added to the hotkey staking account.
    ///
    /// * `limit_price`: The limit price expressed in units of RAO per one Alpha.
    ///
    /// * `allow_partial`: Allows partial execution of the amount. If set to false, this becomes
    ///   fill or kill type of order.
    ///
    /// # Events
    /// * `StakeAdded`: On the successfully adding stake to a global account.
    ///
    /// # Errors
    /// * `NotEnoughBalanceToStake`: Not enough balance on the coldkey to add onto the global account.
    ///
    /// * `NonAssociatedColdKey`: The calling coldkey is not associated with this hotkey.
    ///
    /// * `BalanceWithdrawalError`: Errors stemming from transaction pallet.
    ///
    /// * `TxRateLimitExceeded`: Thrown if key has hit transaction rate limit.
    ///
    pub fn do_add_stake_limit(
        origin: OriginFor<T>,
        hotkey: T::AccountId,
        netuid: NetUid,
        stake_to_be_added: TaoBalance,
        limit_price: TaoBalance,
        allow_partial: bool,
    ) -> Result<AlphaBalance, DispatchError> {
        // 1. We check that the transaction is signed by the caller and retrieve the T::AccountId coldkey information.
        let coldkey = ensure_signed(origin)?;
        log::debug!(
            "do_add_stake( origin:{coldkey:?} hotkey:{hotkey:?}, netuid:{netuid:?}, stake_to_be_added:{stake_to_be_added:?} )"
        );

        Self::ensure_add_stake_input_within_swap_limit(netuid, stake_to_be_added)?;

        // 2. Calculate the maximum amount that can be executed with price limit
        let max_amount: TaoBalance = Self::get_max_amount_add(netuid, limit_price)?.into();
        let mut possible_stake = stake_to_be_added;
        if possible_stake > max_amount {
            possible_stake = max_amount;
        }

        // 3. Validate user input
        Self::validate_add_stake(
            &coldkey,
            &hotkey,
            netuid,
            stake_to_be_added,
            max_amount.into(),
            allow_partial,
        )?;

        // 4. If the coldkey is not the owner, make the hotkey a delegate.
        if Self::get_owning_coldkey_for_hotkey(&hotkey) != coldkey {
            Self::maybe_become_delegate(&hotkey);
        }

        // 5. Swap the stake into alpha on the subnet and increase counters.
        // Emit the staking event.
        Self::stake_into_subnet(
            &hotkey,
            &coldkey,
            netuid,
            possible_stake,
            limit_price,
            false,
        )
    }

    // Returns the maximum amount of RAO that can be executed with price limit
    pub fn get_max_amount_add(
        netuid: NetUid,
        limit_price: TaoBalance,
    ) -> Result<u64, DispatchError> {
        // Corner case: root and stao
        // There's no slippage for root or stable subnets, so if limit price is 1e9 rao or
        // higher, then max_amount equals u64::MAX, otherwise it is 0.
        if netuid.is_root() || SubnetMechanism::<T>::get(netuid) == 0 {
            if limit_price >= 1_000_000_000.into() {
                return Ok(u64::MAX);
            } else {
                // Price will never move down, so maximum amount that can be staked is zero
                return Ok(0_u64);
            }
        }

        // Use the largest supported input instead of probing the swap path with u64::MAX.
        let max_supported_input = SubnetTAO::<T>::get(netuid).saturating_mul(1_000.into());
        let order = GetAlphaForTao::<T>::with_amount(max_supported_input);
        let result = T::SwapInterface::swap(netuid.into(), order, limit_price, false, true)
            .map(|r| r.amount_paid_in.saturating_add(r.fee_paid))?;

        Ok(result.into())
    }

    fn ensure_add_stake_input_within_swap_limit(
        netuid: NetUid,
        amount: TaoBalance,
    ) -> Result<(), Error<T>> {
        if !netuid.is_root() && SubnetMechanism::<T>::get(netuid) == 1 {
            let max_supported_input = SubnetTAO::<T>::get(netuid).saturating_mul(1_000.into());
            ensure!(
                amount <= max_supported_input,
                Error::<T>::InsufficientLiquidity
            );
        }

        Ok(())
    }
}