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
185 changes: 157 additions & 28 deletions pallets/subtensor/src/coinbase/run_coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,19 +510,39 @@ impl<T: Config> Pallet<T> {
) -> (
BTreeMap<T::AccountId, AlphaBalance>,
BTreeMap<T::AccountId, U96F32>,
) {
let (incentives, dividends, _) =
Self::calculate_dividends_incentives_and_child_takes(netuid, hotkey_emission);
(incentives, dividends)
}

fn calculate_dividends_incentives_and_child_takes(
netuid: NetUid,
hotkey_emission: Vec<(T::AccountId, AlphaBalance, AlphaBalance)>,
) -> (
BTreeMap<T::AccountId, AlphaBalance>,
BTreeMap<T::AccountId, U96F32>,
BTreeMap<T::AccountId, U96F32>,
) {
// Accumulate emission of dividends and incentive per hotkey.
let mut incentives: BTreeMap<T::AccountId, AlphaBalance> = BTreeMap::new();
let mut dividends: BTreeMap<T::AccountId, U96F32> = BTreeMap::new();
let mut child_takes: BTreeMap<T::AccountId, U96F32> = BTreeMap::new();
for (hotkey, incentive, dividend) in hotkey_emission {
// Accumulate incentives to miners.
incentives
.entry(hotkey.clone())
.and_modify(|e| *e = e.saturating_add(incentive))
.or_insert(incentive);
// Accumulate dividends to parents.
let div_tuples: Vec<(T::AccountId, AlphaBalance)> =
Self::get_parent_child_dividends_distribution(&hotkey, netuid, dividend);
let (div_tuples, child_take) =
Self::get_parent_child_dividends_distribution_with_child_take(
&hotkey, netuid, dividend,
);
child_takes
.entry(hotkey.clone())
.and_modify(|e| *e = e.saturating_add(asfloat!(child_take)))
.or_insert(asfloat!(child_take));
// Accumulate dividends per hotkey.
for (parent, parent_div) in div_tuples {
dividends
Expand All @@ -533,8 +553,9 @@ impl<T: Config> Pallet<T> {
}
log::debug!("incentives: {incentives:?}");
log::debug!("dividends: {dividends:?}");
log::debug!("child_takes: {child_takes:?}");

(incentives, dividends)
(incentives, dividends, child_takes)
}

pub fn calculate_dividend_distribution(
Expand Down Expand Up @@ -679,6 +700,24 @@ impl<T: Config> Pallet<T> {
incentives: BTreeMap<T::AccountId, AlphaBalance>,
alpha_dividends: BTreeMap<T::AccountId, U96F32>,
root_alpha_dividends: BTreeMap<T::AccountId, U96F32>,
) {
Self::distribute_dividends_and_incentives_with_child_takes(
netuid,
owner_cut,
incentives,
alpha_dividends,
root_alpha_dividends,
BTreeMap::new(),
);
}

fn distribute_dividends_and_incentives_with_child_takes(
netuid: NetUid,
owner_cut: AlphaBalance,
incentives: BTreeMap<T::AccountId, AlphaBalance>,
alpha_dividends: BTreeMap<T::AccountId, U96F32>,
root_alpha_dividends: BTreeMap<T::AccountId, U96F32>,
child_take_proportions: BTreeMap<T::AccountId, U96F32>,
) {
// Distribute the owner cut.
if let Ok(owner_coldkey) = SubnetOwner::<T>::try_get(netuid)
Expand Down Expand Up @@ -793,19 +832,29 @@ impl<T: Config> Pallet<T> {
for (hotkey, alpha_divs) in alpha_dividends {
let owner: T::AccountId = Owner::<T>::get(&hotkey);
let total: AlphaBalance = tou64!(alpha_divs).into();
let alpha_take: U96F32 =
Self::get_hotkey_take_float(&hotkey).saturating_mul(alpha_divs);
let nominator_divs: U96F32 = alpha_divs.saturating_sub(alpha_take);
let take: AlphaBalance = tou64!(alpha_take).into();
let captured = Self::settle_miner_collateral(netuid, &hotkey, &owner, total, take);
let liquid_take = take.saturating_sub(captured);
if !liquid_take.is_zero() {
log::debug!("hotkey: {hotkey:?} alpha_take: {liquid_take:?}");
let child_take = child_take_proportions
.get(&hotkey)
.copied()
.unwrap_or_else(|| asfloat!(0))
.saturating_mul(alpha_divs);
let shared_dividends = alpha_divs.saturating_sub(child_take);
let delegate_take =
Self::get_hotkey_take_float(&hotkey).saturating_mul(shared_dividends);
let nominator_divs = shared_dividends.saturating_sub(delegate_take);
let shared_total: AlphaBalance = tou64!(shared_dividends).into();
let delegate_take: AlphaBalance = tou64!(delegate_take).into();
let child_take = total.saturating_sub(shared_total);
let owner_take = delegate_take.saturating_add(child_take);
Self::settle_miner_collateral_without_stake_credit(
netuid, &hotkey, &owner, total, owner_take,
);
if !delegate_take.is_zero() {
log::debug!("hotkey: {hotkey:?} delegate_take: {delegate_take:?}");
Self::increase_stake_for_hotkey_and_coldkey_on_subnet(
&hotkey,
&owner,
netuid,
liquid_take,
delegate_take,
);
}
let nominator_alpha: AlphaBalance = tou64!(nominator_divs).into();
Expand All @@ -816,6 +865,14 @@ impl<T: Config> Pallet<T> {
*divs = divs.saturating_add(nominator_alpha);
});
}
// Credit childkey take after shared dividends so the newly created
// owner stake cannot participate in the same dividend distribution.
if !child_take.is_zero() {
log::debug!("hotkey: {hotkey:?} child_take: {child_take:?}");
Self::increase_stake_for_hotkey_and_coldkey_on_subnet(
&hotkey, &owner, netuid, child_take,
);
}
let total_hotkey_alpha = TotalHotkeyAlpha::<T>::get(&hotkey, netuid);
TotalHotkeyAlphaLastEpoch::<T>::insert(hotkey, netuid, total_hotkey_alpha);
}
Expand All @@ -826,19 +883,29 @@ impl<T: Config> Pallet<T> {
for (hotkey, root_alpha) in root_alpha_dividends {
let owner: T::AccountId = Owner::<T>::get(&hotkey);
let total: AlphaBalance = tou64!(root_alpha).into();
let alpha_take: U96F32 =
Self::get_hotkey_take_float(&hotkey).saturating_mul(root_alpha);
let root_claimable: U96F32 = root_alpha.saturating_sub(alpha_take);
let take: AlphaBalance = tou64!(alpha_take).into();
let captured = Self::settle_miner_collateral(netuid, &hotkey, &owner, total, take);
let liquid_take = take.saturating_sub(captured);
if !liquid_take.is_zero() {
log::debug!("hotkey: {hotkey:?} alpha_take: {liquid_take:?}");
let child_take = child_take_proportions
.get(&hotkey)
.copied()
.unwrap_or_else(|| asfloat!(0))
.saturating_mul(root_alpha);
let shared_dividends = root_alpha.saturating_sub(child_take);
let delegate_take =
Self::get_hotkey_take_float(&hotkey).saturating_mul(shared_dividends);
let root_claimable = shared_dividends.saturating_sub(delegate_take);
let shared_total: AlphaBalance = tou64!(shared_dividends).into();
let delegate_take: AlphaBalance = tou64!(delegate_take).into();
let child_take = total.saturating_sub(shared_total);
let owner_take = delegate_take.saturating_add(child_take);
Self::settle_miner_collateral_without_stake_credit(
netuid, &hotkey, &owner, total, owner_take,
);
if !delegate_take.is_zero() {
log::debug!("hotkey: {hotkey:?} delegate_take: {delegate_take:?}");
Self::increase_stake_for_hotkey_and_coldkey_on_subnet(
&hotkey,
&owner,
netuid,
liquid_take,
delegate_take,
);
}

Expand All @@ -854,6 +921,14 @@ impl<T: Config> Pallet<T> {
*divs = divs.saturating_add(root_claimable_alpha);
});
}
// Root-derived childkey take is owner-only as well; unlike the
// shared remainder it must not become root-claimable.
if !child_take.is_zero() {
log::debug!("hotkey: {hotkey:?} child_take: {child_take:?}");
Self::increase_stake_for_hotkey_and_coldkey_on_subnet(
&hotkey, &owner, netuid, child_take,
);
}
}
}

Expand Down Expand Up @@ -885,8 +960,46 @@ impl<T: Config> Pallet<T> {
BTreeMap<T::AccountId, U96F32>,
),
) {
let (incentives, dividends) =
Self::calculate_dividends_and_incentives(netuid, hotkey_emission);
let (incentives, dividends, _) =
Self::calculate_dividend_and_incentive_distribution_with_child_takes(
netuid,
pending_root_alpha,
pending_validator_alpha,
hotkey_emission,
tao_weight,
);
(incentives, dividends)
}

fn calculate_dividend_and_incentive_distribution_with_child_takes(
netuid: NetUid,
pending_root_alpha: AlphaBalance,
pending_validator_alpha: AlphaBalance,
hotkey_emission: Vec<(T::AccountId, AlphaBalance, AlphaBalance)>,
tao_weight: U96F32,
) -> (
BTreeMap<T::AccountId, AlphaBalance>,
(
BTreeMap<T::AccountId, U96F32>,
BTreeMap<T::AccountId, U96F32>,
),
BTreeMap<T::AccountId, U96F32>,
) {
let (incentives, dividends, child_takes) =
Self::calculate_dividends_incentives_and_child_takes(netuid, hotkey_emission);

// Preserve the child take's share of each hotkey's dividend score through
// normalization into the subnet and root dividend pools.
let zero = asfloat!(0);
let one = asfloat!(1);
let child_take_proportions = child_takes
.into_iter()
.filter_map(|(hotkey, child_take)| {
let total_dividends = dividends.get(&hotkey).copied()?;
let proportion = child_take.checked_div(total_dividends).unwrap_or(zero);
Some((hotkey, if proportion > one { one } else { proportion }))
})
.collect();

let stake_map = Self::get_stake_map(netuid, dividends.keys().collect::<Vec<_>>());

Expand All @@ -898,7 +1011,11 @@ impl<T: Config> Pallet<T> {
dividends,
);

(incentives, (alpha_dividends, root_alpha_dividends))
(
incentives,
(alpha_dividends, root_alpha_dividends),
child_take_proportions,
)
}

pub fn distribute_emission(
Expand Down Expand Up @@ -943,21 +1060,22 @@ impl<T: Config> Pallet<T> {
let root_alpha = pending_root_alpha;
let owner_cut = pending_owner_cut;

let (incentives, (alpha_dividends, root_alpha_dividends)) =
Self::calculate_dividend_and_incentive_distribution(
let (incentives, (alpha_dividends, root_alpha_dividends), child_take_proportions) =
Self::calculate_dividend_and_incentive_distribution_with_child_takes(
netuid,
root_alpha,
validator_alpha,
hotkey_emission,
tao_weight,
);

Self::distribute_dividends_and_incentives(
Self::distribute_dividends_and_incentives_with_child_takes(
netuid,
owner_cut,
incentives,
alpha_dividends,
root_alpha_dividends,
child_take_proportions,
);
}

Expand Down Expand Up @@ -1012,6 +1130,14 @@ impl<T: Config> Pallet<T> {
netuid: NetUid,
dividends: AlphaBalance,
) -> Vec<(T::AccountId, AlphaBalance)> {
Self::get_parent_child_dividends_distribution_with_child_take(hotkey, netuid, dividends).0
}

fn get_parent_child_dividends_distribution_with_child_take(
hotkey: &T::AccountId,
netuid: NetUid,
dividends: AlphaBalance,
) -> (Vec<(T::AccountId, AlphaBalance)>, AlphaBalance) {
// hotkey dividends.
let mut dividend_tuples: Vec<(T::AccountId, AlphaBalance)> = vec![];

Expand Down Expand Up @@ -1138,7 +1264,10 @@ impl<T: Config> Pallet<T> {
// Add the hotkey's own emission to the distribution list
dividend_tuples.push((hotkey.clone(), child_emission));

dividend_tuples
(
dividend_tuples,
total_child_take.saturating_to_num::<u64>().into(),
)
}

/// Checks if the epoch should run for a given subnet based on the current block.
Expand Down
34 changes: 31 additions & 3 deletions pallets/subtensor/src/subnets/collateral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,32 @@ impl<T: Config> Pallet<T> {
owner: &T::AccountId,
emission: AlphaBalance,
capturable: AlphaBalance,
) -> AlphaBalance {
Self::settle_miner_collateral_inner(netuid, hotkey, owner, emission, capturable, true)
}

/// Settle collateral without crediting captured stake. The caller must
/// credit the full `capturable` amount to the owner after this returns.
///
/// This allows emission distribution to control payout ordering while
/// preserving the same collateral accounting as [`Self::settle_miner_collateral`].
pub(crate) fn settle_miner_collateral_without_stake_credit(
netuid: NetUid,
hotkey: &T::AccountId,
owner: &T::AccountId,
emission: AlphaBalance,
capturable: AlphaBalance,
) -> AlphaBalance {
Self::settle_miner_collateral_inner(netuid, hotkey, owner, emission, capturable, false)
}

fn settle_miner_collateral_inner(
netuid: NetUid,
hotkey: &T::AccountId,
owner: &T::AccountId,
emission: AlphaBalance,
capturable: AlphaBalance,
credit_captured_stake: bool,
) -> AlphaBalance {
if emission.is_zero() {
return AlphaBalance::ZERO;
Expand All @@ -508,9 +534,11 @@ impl<T: Config> Pallet<T> {
if captured.is_zero() {
return AlphaBalance::ZERO;
}
Self::increase_stake_for_hotkey_and_coldkey_on_subnet(
hotkey, owner, netuid, captured,
);
if credit_captured_stake {
Self::increase_stake_for_hotkey_and_coldkey_on_subnet(
hotkey, owner, netuid, captured,
);
}
state.locked = state.locked.saturating_add(captured);
return captured;
}
Expand Down
Loading
Loading