Skip to content
Open
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
18 changes: 18 additions & 0 deletions pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2517,6 +2517,24 @@ pub mod pallet {
DefaultZeroAlpha<T>,
>;

/// --- MAP ( delegator root uid ) --> manager root uid | weight-vector management authorization.
///
/// A root validator can authorize another root validator (the manager) to set its beta-basket
/// weight vector via `set_root_weights_for`. The manager writes a *bespoke* vector into the
/// delegator's own `Weights[ROOT]` slot, so a single manager can run an independent vector per
/// delegator (not one shared vector everyone copies). This map only records the authorization
/// and routes the manager's `RootWeightTake`; distribution always reads the delegator's own
/// slot. uid-keyed (both sides) so it follows either validator through hotkey swaps with no
/// migration; the fee credit is guarded so a stale/reused uid is never paid.
#[pallet::storage]
pub type RootWeightManager<T: Config> = StorageMap<_, Identity, u16, u16, OptionQuery>;

/// --- MAP ( manager root uid ) --> take (bps, 0..=10000) | curation fee a manager charges its
/// delegators for setting their basket weight vector. Skimmed from each delegator's root
/// dividend (in TAO) and credited to the manager's own root stake during distribution.
#[pallet::storage]
pub type RootWeightTake<T: Config> = StorageMap<_, Identity, u16, u16, ValueQuery>;

#[pallet::storage] // -- MAP ( cold ) --> root_claim_type enum
pub type RootClaimType<T: Config> = StorageMap<
_,
Expand Down
50 changes: 50 additions & 0 deletions pallets/subtensor/src/macros/dispatches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,56 @@ mod dispatches {
Self::do_set_root_weights(origin, dests, weights, version_key)
}

/// --- Authorizes `manager` (another root validator) to set this validator's beta-basket
/// weight vector via `set_root_weights_for`, or clears the authorization by passing the
/// caller's own hotkey. The manager also earns its curation take on this validator's basket.
///
/// # Args:
/// * `origin`: the delegator root validator hotkey.
/// * `manager` (T::AccountId): the manager's root validator hotkey (self = clear).
#[pallet::call_index(140)]
#[pallet::weight((<T as crate::pallet::Config>::WeightInfo::set_childkey_take(), DispatchClass::Normal, Pays::Yes))]
pub fn set_root_weight_manager(
origin: OriginFor<T>,
manager: T::AccountId,
) -> DispatchResult {
Self::do_set_root_weight_manager(origin, manager)
}

/// --- Sets the curation take (basis points) this root validator charges its
/// weight-vector delegators. Bounded by the childkey-take ceiling; `0` curates for free.
///
/// # Args:
/// * `origin`: the manager root validator hotkey.
/// * `take` (u16): the take in basis points (0..=10000, capped at the childkey-take max).
#[pallet::call_index(141)]
#[pallet::weight((<T as crate::pallet::Config>::WeightInfo::set_childkey_take(), DispatchClass::Normal, Pays::Yes))]
pub fn set_root_weight_take(origin: OriginFor<T>, take: u16) -> DispatchResult {
Self::do_set_root_weight_take(origin, take)
}

/// --- Sets the beta-basket weight vector of a `delegator` root validator that has
/// authorized the caller as its manager. The vector is written into the delegator's own
/// slot, so one manager can curate an independent vector per delegator.
///
/// # Args:
/// * `origin`: the manager root validator hotkey.
/// * `delegator` (T::AccountId): the delegator whose vector is being set.
/// * `dests` (Vec<u16>): destination subnet netuids.
/// * `weights` (Vec<u16>): per-subnet weights (normalized on use).
/// * `version_key` (u64): the network version key.
#[pallet::call_index(142)]
#[pallet::weight((<T as crate::pallet::Config>::WeightInfo::set_weights(), DispatchClass::Normal, Pays::No))]
pub fn set_root_weights_for(
origin: OriginFor<T>,
delegator: T::AccountId,
dests: Vec<u16>,
weights: Vec<u16>,
version_key: u64,
) -> DispatchResult {
Self::do_set_root_weights_for(origin, delegator, dests, weights, version_key)
}

/// --- Sets the caller weights for the incentive mechanism for mechanisms. The call
/// can be made from the hotkey account so is potentially insecure, however, the damage
/// of changing weights is minimal if caught early. This function includes all the
Expand Down
6 changes: 6 additions & 0 deletions pallets/subtensor/src/macros/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@ mod errors {
NewColdKeyIsHotkey,
/// Childkey take is invalid.
InvalidChildkeyTake,
/// Root-weight delegation curation take is invalid (exceeds the max).
InvalidRootWeightTake,
/// Root-weight curation take increase rate limit exceeded.
TxRootWeightTakeRateLimitExceeded,
/// Caller is not the authorized weight-vector manager for this delegator.
NotRootWeightManager,
/// Childkey take rate limit exceeded.
TxChildkeyTakeRateLimitExceeded,
/// Invalid identity.
Expand Down
8 changes: 8 additions & 0 deletions pallets/subtensor/src/macros/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ mod events {
WeightsSet(NetUidStorageIndex, u16),
/// a root validator set its beta-basket distribution vector (uid on the root subnet).
RootWeightsSet(u16),
/// a root validator authorized a manager to set its basket weight vector (or cleared it by
/// authorizing itself). Fields: (delegator_uid, manager_uid); equal uids = cleared.
RootWeightManagerSet(u16, u16),
/// a manager set a delegator's basket weight vector on its behalf. Fields:
/// (delegator_uid, manager_uid).
RootWeightsSetByManager(u16, u16),
/// a manager set the curation take it charges delegators. Fields: (manager_uid, take_bps).
RootWeightTakeSet(u16, u16),
/// a new neuron account has been registered to the chain.
NeuronRegistered(NetUid, u16, T::AccountId),
/// multiple uids have been concurrently registered.
Expand Down
44 changes: 40 additions & 4 deletions pallets/subtensor/src/staking/claim_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,17 @@ impl<T: Config> Pallet<T> {
return;
}

// Resolve the validator's basket weight vector w = Weights[ROOT][uid]. The vector follows
// the validator's root uid (so it survives hotkey swaps automatically) and reuses the
// existing root weights plumbing.
// Resolve the validator's OWN basket weight vector w = Weights[ROOT][uid]. If a manager is
// authorized it writes a bespoke vector directly into this slot (see `set_root_weights_for`),
// so distribution reads the slot unconditionally. The vector follows the validator's root
// uid (surviving hotkey swaps) and reuses the existing root weights plumbing. The
// `RootWeightManager` pointer is read only to route the manager's `RootWeightTake` (skimmed
// as a curation fee below).
let maybe_uid = Uids::<T>::try_get(NetUid::ROOT, hotkey).ok();
let weights = maybe_uid
.map(|uid| Weights::<T>::get(NetUidStorageIndex::ROOT, uid))
.unwrap_or_default();
let manager_uid: Option<u16> = maybe_uid.and_then(|uid| RootWeightManager::<T>::get(uid));

// Keep weights that point at root (uid 0) or an existing subnet. Root is a valid
// destination: that slice is held as a root-stake (TAO) basket slot instead of being
Expand Down Expand Up @@ -145,7 +149,39 @@ impl<T: Config> Pallet<T> {
Self::record_protocol_outflow(origin_netuid, tao_total);

// 2. Split the TAO across subnets per w and buy each subnet's alpha.
let tao_total_u64: u64 = tao_total.to_u64();
let mut tao_total_u64: u64 = tao_total.to_u64();

// 2a. If an authorized manager curates this validator, skim the manager's curation take
// (bps) from the TAO and credit it to the manager's own root stake. Keeps the round
// trip TotalStake-neutral: sell - buys - fee == 0. The buy loop below redeploys only
// the post-fee remainder. Guarded by `Keys::try_get` so a stale/vacant manager uid is
// never credited (the remainder simply stays with the delegator's basket).
if let Some(m_uid) = manager_uid {
let take: u16 = RootWeightTake::<T>::get(m_uid);
if take > 0 {
if let Ok(manager_hotkey) = Keys::<T>::try_get(NetUid::ROOT, m_uid) {
let fee: u64 = U96F32::saturating_from_num(tao_total_u64)
.saturating_mul(U96F32::saturating_from_num(take))
.checked_div(U96F32::saturating_from_num(10_000u64))
.unwrap_or_default()
.saturating_to_num::<u64>();
if fee > 0 {
// Credit the fee as the manager's own root stake (mirrors the root-slot
// and claim paths: stake the TAO, then book the root reserves).
let manager_owner = Owner::<T>::get(&manager_hotkey);
Self::increase_stake_for_hotkey_and_coldkey_on_subnet(
&manager_hotkey,
&manager_owner,
NetUid::ROOT,
fee.into(),
);
Self::credit_root_reserves(fee.into());
tao_total_u64 = tao_total_u64.saturating_sub(fee);
}
}
}
}

let mut spent: u64 = 0;
let last_idx = valid.len().saturating_sub(1);
for (i, (dest_netuid, weight)) in valid.iter().enumerate() {
Expand Down
161 changes: 131 additions & 30 deletions pallets/subtensor/src/subnets/weights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -935,52 +935,46 @@ impl<T: Config> Pallet<T> {
/// values as the proportion of the validator's root dividends to deploy into each subnet's
/// alpha basket. Stored under `Weights[NetUidStorageIndex::ROOT][uid]` and consumed by
/// `distribute_root_alpha_to_basket` during emission.
pub fn do_set_root_weights(
origin: OriginFor<T>,
/// Shared inner: validate and store a beta-basket weight vector into `Weights[ROOT][target_uid]`
/// for `target_hotkey` (the slot owner). Used by both the self path (`do_set_root_weights`) and
/// the manager-on-behalf path (`do_set_root_weights_for`); each does its own
/// identity/authorization check first, then calls this with the resolved slot owner.
fn apply_root_weights(
target_hotkey: &T::AccountId,
target_uid: u16,
dests: Vec<u16>,
values: Vec<u16>,
version_key: u64,
) -> dispatch::DispatchResult {
// --- 1. Signed by the root validator hotkey.
let hotkey = ensure_signed(origin)?;
log::debug!("do_set_root_weights( hotkey:{hotkey:?}, dests:{dests:?}, values:{values:?} )");

// --- 2. Lengths match.
// --- Lengths match.
ensure!(
Self::uids_match_values(&dests, &values),
Error::<T>::WeightVecNotEqualSize
);

// --- 3. Caller must be a registered root validator.
ensure!(
Self::is_hotkey_registered_on_network(NetUid::ROOT, &hotkey),
Error::<T>::HotKeyNotRegisteredInSubNet
);

// --- 4. Must hold enough stake to set weights.
// --- Slot owner must hold enough stake to carry a weighted basket.
ensure!(
Self::check_weights_min_stake(&hotkey, NetUid::ROOT),
Self::check_weights_min_stake(target_hotkey, NetUid::ROOT),
Error::<T>::NotEnoughStakeToSetWeights
);

// --- 5. Version key must be current.
// --- Version key must be current.
ensure!(
Self::check_version_key(NetUid::ROOT, version_key),
Error::<T>::IncorrectWeightVersionKey
);

// --- 6. Rate limit on the root weights index.
let neuron_uid = Self::get_uid_for_net_and_hotkey(NetUid::ROOT, &hotkey)?;
// --- Rate limit on the slot owner's root weights index.
let current_block: u64 = Self::get_current_block_as_u64();
ensure!(
Self::check_rate_limit(NetUidStorageIndex::ROOT, neuron_uid, current_block),
Self::check_rate_limit(NetUidStorageIndex::ROOT, target_uid, current_block),
Error::<T>::SettingWeightsTooFast
);

// --- 7. No duplicate destination subnets.
// --- No duplicate destination subnets.
ensure!(!Self::has_duplicate_uids(&dests), Error::<T>::DuplicateUids);

// --- 8. Every destination must be root (uid 0) or an existing subnet. Root is a valid
// --- Every destination must be root (uid 0) or an existing subnet. Root is a valid
// basket destination: that weight slice is held as root stake (TAO) instead of being
// deployed into a subnet, letting a validator opt out of subnet exposure. This must mirror
// the consumer filter in `distribute_root_alpha_to_basket`.
Expand All @@ -992,23 +986,130 @@ impl<T: Config> Pallet<T> {
);
}

// --- 9. Max-upscale the weights.
// --- Max-upscale and store under the root weights index (reusing the root plumbing).
let max_upscaled_weights: Vec<u16> = vec_u16_max_upscale_to_u16(&values);

// --- 10. Zip and store under the root weights index (reusing the root weights plumbing).
let zipped_weights: Vec<(u16, u16)> = dests
.iter()
.copied()
.zip(max_upscaled_weights.iter().copied())
.collect();
Weights::<T>::insert(NetUidStorageIndex::ROOT, neuron_uid, zipped_weights);
Weights::<T>::insert(NetUidStorageIndex::ROOT, target_uid, zipped_weights);

// --- Record activity for the rate limit and emit.
Self::set_last_update_for_uid(NetUidStorageIndex::ROOT, target_uid, current_block);
log::debug!("RootWeightsSet( uid:{target_uid:?} )");
Self::deposit_event(Event::RootWeightsSet(target_uid));

Ok(())
}

/// Sets the caller's own beta-basket weight vector on the root subnet (netuid 0).
pub fn do_set_root_weights(
origin: OriginFor<T>,
dests: Vec<u16>,
values: Vec<u16>,
version_key: u64,
) -> dispatch::DispatchResult {
// --- Signed by, and registered as, a root validator.
let hotkey = ensure_signed(origin)?;
log::debug!("do_set_root_weights( hotkey:{hotkey:?}, dests:{dests:?}, values:{values:?} )");
ensure!(
Self::is_hotkey_registered_on_network(NetUid::ROOT, &hotkey),
Error::<T>::HotKeyNotRegisteredInSubNet
);
let neuron_uid = Self::get_uid_for_net_and_hotkey(NetUid::ROOT, &hotkey)?;

Self::apply_root_weights(&hotkey, neuron_uid, dests, values, version_key)
}

/// Sets the beta-basket weight vector of a `delegator` that has authorized the caller as its
/// manager (via `set_root_weight_manager`). The vector is written into the *delegator's own*
/// slot, so one manager can run an independent vector per delegator. The manager earns its
/// `RootWeightTake` on the delegator's distributions.
pub fn do_set_root_weights_for(
origin: OriginFor<T>,
delegator: T::AccountId,
dests: Vec<u16>,
values: Vec<u16>,
version_key: u64,
) -> dispatch::DispatchResult {
// --- Signed by a registered root validator (the manager).
let manager = ensure_signed(origin)?;
let manager_uid = Self::get_uid_for_net_and_hotkey(NetUid::ROOT, &manager)?;

// --- The delegator must have authorized this manager.
let delegator_uid = Self::get_uid_for_net_and_hotkey(NetUid::ROOT, &delegator)?;
ensure!(
RootWeightManager::<T>::get(delegator_uid) == Some(manager_uid),
Error::<T>::NotRootWeightManager
);

// --- 11. Record activity for the rate limit.
Self::set_last_update_for_uid(NetUidStorageIndex::ROOT, neuron_uid, current_block);
Self::apply_root_weights(&delegator, delegator_uid, dests, values, version_key)?;
Self::deposit_event(Event::RootWeightsSetByManager(delegator_uid, manager_uid));
Ok(())
}

// --- 12. Emit event.
log::debug!("RootWeightsSet( uid:{neuron_uid:?} )");
Self::deposit_event(Event::RootWeightsSet(neuron_uid));
/// Authorizes (or clears) a `manager` root validator to set the caller's beta-basket weight
/// vector via `set_root_weights_for`. Authorizing oneself clears the authorization
/// (self-manage). The manager additionally earns its `RootWeightTake` on the caller's
/// distributions.
pub fn do_set_root_weight_manager(
origin: OriginFor<T>,
manager: T::AccountId,
) -> dispatch::DispatchResult {
// --- Signed by the delegator root validator hotkey.
let hotkey = ensure_signed(origin)?;
let delegator_uid = Self::get_uid_for_net_and_hotkey(NetUid::ROOT, &hotkey)?;

// --- Authorizing oneself clears the manager; otherwise the manager must also be a
// registered root validator.
if manager == hotkey {
RootWeightManager::<T>::remove(delegator_uid);
Self::deposit_event(Event::RootWeightManagerSet(delegator_uid, delegator_uid));
} else {
let manager_uid = Self::get_uid_for_net_and_hotkey(NetUid::ROOT, &manager)?;
RootWeightManager::<T>::insert(delegator_uid, manager_uid);
Self::deposit_event(Event::RootWeightManagerSet(delegator_uid, manager_uid));
}

Ok(())
}

/// Sets the curation take (basis points, `0..=MaxChildkeyTake`) a manager root validator
/// charges its delegators. `0` lets a manager curate for free. Bounded by the same ceiling as
/// childkey take.
pub fn do_set_root_weight_take(origin: OriginFor<T>, take: u16) -> dispatch::DispatchResult {
// --- 1. Signed by the manager root validator hotkey.
let hotkey = ensure_signed(origin)?;

// --- 2. Manager must be a registered root validator.
let manager_uid = Self::get_uid_for_net_and_hotkey(NetUid::ROOT, &hotkey)?;

// --- 3. Take must be within the allowed ceiling.
ensure!(
take <= Self::get_max_childkey_take(),
Error::<T>::InvalidRootWeightTake
);

// --- 4. Rate-limit *increases* only (same window as childkey take), so a manager cannot
// spike its take on already-committed delegators between their per-block distributions.
// Lowering the take is always allowed.
let current_block = Self::get_current_block_as_u64();
if take > RootWeightTake::<T>::get(manager_uid) {
ensure!(
TransactionType::SetRootWeightTake
.passes_rate_limit_on_subnet::<T>(&hotkey, NetUid::ROOT),
Error::<T>::TxRootWeightTakeRateLimitExceeded
);
}

RootWeightTake::<T>::insert(manager_uid, take);
TransactionType::SetRootWeightTake.set_last_block_on_subnet::<T>(
&hotkey,
NetUid::ROOT,
current_block,
);
Self::deposit_event(Event::RootWeightTakeSet(manager_uid, take));

Ok(())
}
Expand Down
Loading