Skip to content
Draft
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.

2 changes: 2 additions & 0 deletions pallets/subtensor/runtime-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ workspace = true
sp-api.workspace = true
sp-runtime.workspace = true
codec = { workspace = true, features = ["derive"] }
substrate-fixed.workspace = true
subtensor-runtime-common.workspace = true
# local
pallet-subtensor.workspace = true
Expand All @@ -26,6 +27,7 @@ std = [
"pallet-subtensor/std",
"sp-api/std",
"sp-runtime/std",
"substrate-fixed/std",
"subtensor-runtime-common/std",
]
pow-faucet = []
3 changes: 3 additions & 0 deletions pallets/subtensor/runtime-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use pallet_subtensor::rpc_info::{
subnet_info::{SubnetHyperparams, SubnetHyperparamsV2, SubnetInfo, SubnetInfov2},
};
use sp_runtime::AccountId32;
use substrate_fixed::types::U64F64;
use subtensor_runtime_common::{AlphaBalance, MechId, NetUid, TaoBalance};

// Here we declare the runtime API. It is implemented it the `impl` block in
Expand Down Expand Up @@ -55,6 +56,8 @@ sp_api::decl_runtime_apis! {
fn get_stake_info_for_coldkeys( coldkey_accounts: Vec<AccountId32> ) -> Vec<(AccountId32, Vec<StakeInfo<AccountId32>>)>;
fn get_stake_info_for_hotkey_coldkey_netuid( hotkey_account: AccountId32, coldkey_account: AccountId32, netuid: NetUid ) -> Option<StakeInfo<AccountId32>>;
fn get_stake_fee( origin: Option<(AccountId32, NetUid)>, origin_coldkey_account: AccountId32, destination: Option<(AccountId32, NetUid)>, destination_coldkey_account: AccountId32, amount: u64 ) -> u64;
fn get_hotkey_conviction(hotkey: AccountId32, netuid: NetUid) -> U64F64;
fn get_most_convicted_hotkey_on_subnet(netuid: NetUid) -> Option<AccountId32>;
}

pub trait SubnetRegistrationRuntimeApi {
Expand Down
60 changes: 60 additions & 0 deletions pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1492,6 +1492,66 @@ pub mod pallet {
ValueQuery,
>;

/// Exponential lock state for a coldkey on a subnet.
#[crate::freeze_struct("cfa10602e0577f6e")]
#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, Debug, TypeInfo)]
pub struct LockState<AccountId> {
/// The hotkey this stake is locked to.
pub hotkey: AccountId,
/// Exponentially decaying locked amount.
pub locked_mass: AlphaBalance,
/// Matured decaying score (integral of locked_mass over time).
pub conviction: U64F64,
/// Block number of last roll-forward.
pub last_update: u64,
}

/// --- DMAP ( coldkey, netuid ) --> LockState | Exponential lock per coldkey per subnet.
#[pallet::storage]
pub type Lock<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AccountId, // coldkey
Identity,
NetUid, // subnet
LockState<T::AccountId>,
OptionQuery,
>;

/// Exponential lock state for a hotkey on a subnet.
#[crate::freeze_struct("aba5b4d024b9837a")]
#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, Debug, TypeInfo)]
pub struct HotkeyLockState {
/// Exponentially decaying locked amount.
pub locked_mass: AlphaBalance,
/// Matured decaying score (integral of locked_mass over time).
pub conviction: U64F64,
/// Block number of last roll-forward.
pub last_update: u64,
}

/// --- DMAP ( netuid, hotkey ) --> LockState | Total lock per hotkey per subnet.
#[pallet::storage]
pub type HotkeyLock<T: Config> = StorageDoubleMap<
_,
Identity,
NetUid, // subnet
Blake2_128Concat,
T::AccountId, // hotkey
HotkeyLockState, // Total merged lock
OptionQuery,
>;

/// Default decay timescale: ~30 days at 12s blocks.
#[pallet::type_value]
pub fn DefaultTauBlocks<T: Config>() -> u64 {
7200 * 30
}

/// --- ITEM( tau_blocks ) | Decay timescale in blocks for exponential lock.
#[pallet::storage]
pub type TauBlocks<T: Config> = StorageValue<_, u64, ValueQuery, DefaultTauBlocks<T>>;

/// Contains last Alpha storage map key to iterate (check first)
#[pallet::storage]
pub type AlphaMapLastKey<T: Config> =
Expand Down
28 changes: 28 additions & 0 deletions pallets/subtensor/src/macros/dispatches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2533,5 +2533,33 @@ mod dispatches {
Self::deposit_event(Event::AutoParentDelegationEnabledSet { hotkey, enabled });
Ok(())
}

/// Locks stake on a subnet to a specific hotkey, building conviction over time.
///
/// If no lock exists for (coldkey, subnet), a new one is created.
/// If a lock exists, the destination hotkey must match the existing lock's hotkey.
/// Top-up adds to the locked amount after rolling the lock state forward.
///
/// # Arguments
/// * `origin` - Must be signed by the coldkey.
/// * `hotkey` - The hotkey to lock stake to.
/// * `netuid` - The subnet on which to lock.
/// * `amount` - The alpha amount to lock.
#[pallet::call_index(136)]
#[pallet::weight((Weight::from_parts(46_000_000, 0)
.saturating_add(T::DbWeight::get().reads(4))
.saturating_add(T::DbWeight::get().writes(1)),
DispatchClass::Normal,
Pays::Yes
))]
pub fn lock_stake(
origin: OriginFor<T>,
hotkey: T::AccountId,
netuid: NetUid,
amount: AlphaBalance,
) -> DispatchResult {
let coldkey = ensure_signed(origin)?;
Self::do_lock_stake(&coldkey, netuid, &hotkey, amount)
}
}
}
6 changes: 6 additions & 0 deletions pallets/subtensor/src/macros/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,5 +293,11 @@ mod errors {
DisabledTemporarily,
/// Registration Price Limit Exceeded
RegistrationPriceLimitExceeded,
/// Lock hotkey mismatch: existing lock is for a different hotkey.
LockHotkeyMismatch,
/// Insufficient stake on subnet to cover the lock amount.
InsufficientStakeForLock,
/// No existing lock found for the given coldkey and subnet.
NoExistingLock,
}
}
24 changes: 24 additions & 0 deletions pallets/subtensor/src/macros/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,5 +570,29 @@ mod events {
/// Whether delegation is now enabled.
enabled: bool,
},

/// Stake has been locked to a hotkey on a subnet.
StakeLocked {
/// The coldkey that locked the stake.
coldkey: T::AccountId,
/// The hotkey the stake is locked to.
hotkey: T::AccountId,
/// The subnet the stake is locked on.
netuid: NetUid,
/// The alpha amount locked.
amount: AlphaBalance,
},

/// Stake has been unlocked from a hotkey on a subnet.
LockMoved {
/// The coldkey that moved the lock.
coldkey: T::AccountId,
/// The hotkey the lock was moved from.
origin_hotkey: T::AccountId,
/// The hotkey the lock was moved to.
destination_hotkey: T::AccountId,
/// The subnet the lock is on.
netuid: NetUid,
},
}
}
3 changes: 3 additions & 0 deletions pallets/subtensor/src/staking/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@ impl<T: Config> Pallet<T> {
if let Ok(cleared_stake) = maybe_cleared_stake {
// Add the stake to the coldkey account.
Self::add_balance_to_coldkey_account(coldkey, cleared_stake.into());

// Clear the lock if exists
Self::maybe_cleanup_lock(coldkey, netuid);
} else {
// Just clear small alpha
let alpha =
Expand Down
Loading
Loading