Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No Chk-take on same coldkey #1355

Merged
merged 2 commits into from
Mar 4, 2025
Merged
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
73 changes: 48 additions & 25 deletions pallets/subtensor/src/coinbase/run_coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@ impl<T: Config> Pallet<T> {

// Calculate the hotkey's share of the validator emission based on its childkey take
let validating_emission: I96F32 = I96F32::saturating_from_num(dividends);
let mut remaining_emission: I96F32 = validating_emission;
let childkey_take_proportion: I96F32 =
I96F32::saturating_from_num(Self::get_childkey_take(hotkey, netuid))
.safe_div(I96F32::saturating_from_num(u16::MAX));
Expand All @@ -507,23 +508,12 @@ impl<T: Config> Pallet<T> {
);
// NOTE: Only the validation emission should be split amongst parents.

// Reserve childkey take
let child_emission_take: I96F32 = childkey_take_proportion
.saturating_mul(I96F32::saturating_from_num(validating_emission));
let remaining_emission: I96F32 = validating_emission.saturating_sub(child_emission_take);
log::debug!(
"Child emission take: {:?} for hotkey {:?}",
child_emission_take,
hotkey
);
log::debug!(
"Remaining emission: {:?} for hotkey {:?}",
remaining_emission,
hotkey
);
// Grab the owner of the childkey.
let childkey_owner = Self::get_owning_coldkey_for_hotkey(hotkey);

// Initialize variables to track emission distribution
let mut to_parents: u64 = 0;
let mut total_child_emission_take: I96F32 = I96F32::saturating_from_num(0);

// Initialize variables to calculate total stakes from parents
let mut total_contribution: I96F32 = I96F32::saturating_from_num(0);
Expand Down Expand Up @@ -580,33 +570,66 @@ impl<T: Config> Pallet<T> {
// Distribute emission to parents based on their contributions.
// Deduct childkey take from parent contribution.
for (parent, contribution) in parent_contributions {
// Sum up the total emission for this parent
let parent_owner = Self::get_owning_coldkey_for_hotkey(&parent);

// Get the stake contribution of this parent key of the total stake.
let emission_factor: I96F32 = contribution
.checked_div(total_contribution)
.unwrap_or(I96F32::saturating_from_num(0));
let parent_emission: u64 =
(remaining_emission.saturating_mul(emission_factor)).saturating_to_num::<u64>();

// Get the parent's portion of the validating emission based on their contribution.
let mut parent_emission: I96F32 = validating_emission.saturating_mul(emission_factor);
// Remove this emission from the remaining emission.
remaining_emission = remaining_emission.saturating_sub(parent_emission);

// Get the childkey take for this parent.
let child_emission_take: I96F32 = if parent_owner == childkey_owner {
// The parent is from the same coldkey, so we don't remove any childkey take.
I96F32::saturating_from_num(0)
} else {
childkey_take_proportion
.saturating_mul(I96F32::saturating_from_num(parent_emission))
};

// Remove the childkey take from the parent's emission.
parent_emission = parent_emission.saturating_sub(child_emission_take);

// Add the childkey take to the total childkey take tracker.
total_child_emission_take =
total_child_emission_take.saturating_add(child_emission_take);

log::debug!(
"Child emission take: {:?} for hotkey {:?}",
child_emission_take,
hotkey
);
log::debug!(
"Parent emission: {:?} for hotkey {:?}",
parent_emission,
hotkey
);
log::debug!("remaining emission: {:?}", remaining_emission);

// Add the parent's emission to the distribution list
dividend_tuples.push((parent.clone(), parent_emission));
dividend_tuples.push((parent.clone(), parent_emission.saturating_to_num::<u64>()));

// Keep track of total emission distributed to parents
to_parents = to_parents.saturating_add(parent_emission);
to_parents = to_parents.saturating_add(parent_emission.saturating_to_num::<u64>());
log::debug!(
"Parent contribution for parent {:?} with contribution: {:?}, of total: {:?} of emission: {:?} gets: {:?}",
"Parent contribution for parent {:?} with contribution: {:?}, of total: {:?} ({:?}), of emission: {:?} gets: {:?}",
parent,
contribution,
total_contribution,
remaining_emission,
parent_emission
emission_factor,
validating_emission,
parent_emission,
);
}
// Calculate the final emission for the hotkey itself.
// This includes the take left from the parents and the self contribution.
let child_emission = remaining_emission
.saturating_add(child_emission_take)
.saturating_to_num::<u64>()
.saturating_sub(to_parents);
.saturating_add(total_child_emission_take)
.saturating_to_num::<u64>();

// Add the hotkey's own emission to the distribution list
dividend_tuples.push((hotkey.clone(), child_emission));
Expand Down
166 changes: 166 additions & 0 deletions pallets/subtensor/src/tests/children.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3906,3 +3906,169 @@ fn test_do_set_child_as_sn_owner_not_enough_stake() {
);
});
}

// Test dividend distribution for children with same coldkey Owner
// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::children::test_dividend_distribution_with_children_same_coldkey_owner --exact --show-output
#[test]
fn test_dividend_distribution_with_children_same_coldkey_owner() {
new_test_ext(1).execute_with(|| {
let netuid: u16 = 1;
add_network(netuid, 1, 0);
// Set SN owner cut to 0
SubtensorModule::set_subnet_owner_cut(0_u16);

// Define hotkeys and coldkeys
let hotkey_a: U256 = U256::from(1);
let hotkey_b: U256 = U256::from(2);
let coldkey_a: U256 = U256::from(100); // Only one coldkey

// Register neurons with decreasing stakes
register_ok_neuron(netuid, hotkey_a, coldkey_a, 0);
register_ok_neuron(netuid, hotkey_b, coldkey_a, 0);

// Add initial stakes
SubtensorModule::add_balance_to_coldkey_account(&coldkey_a, 1_000);
SubtensorModule::add_balance_to_coldkey_account(&coldkey_a, 1_000);

// Swap to alpha
let total_tao: I96F32 = I96F32::from_num(300_000 + 100_000);
let total_alpha: I96F32 = I96F32::from_num(SubtensorModule::swap_tao_for_alpha(
netuid,
total_tao.saturating_to_num::<u64>(),
));

// Set the stakes directly
// This avoids needing to swap tao to alpha, impacting the initial stake distribution.
SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet(
&hotkey_a,
&coldkey_a,
netuid,
(total_alpha * I96F32::from_num(300_000) / total_tao).saturating_to_num::<u64>(),
);
SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet(
&hotkey_b,
&coldkey_a,
netuid,
(total_alpha * I96F32::from_num(100_000) / total_tao).saturating_to_num::<u64>(),
);

// Get old stakes
let stake_a: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_a);
let stake_b: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_b);

// Assert initial stake is correct
let rel_stake_a = I96F32::from_num(stake_a) / total_tao;
let rel_stake_b = I96F32::from_num(stake_b) / total_tao;

log::info!("rel_stake_a: {:?}", rel_stake_a); // 0.75 -> 3/4
log::info!("rel_stake_b: {:?}", rel_stake_b); // 0.25 -> 1/4
assert_eq!(rel_stake_a, I96F32::from_num(300_000) / total_tao);
assert_eq!(rel_stake_b, I96F32::from_num(100_000) / total_tao);

// Set parent-child relationships
// A -> B (50% of A's stake)
mock_set_children(&coldkey_a, &hotkey_a, netuid, &[(u64::MAX / 2, hotkey_b)]);

// Set CHK take rate to 1/9
let chk_take: I96F32 = I96F32::from_num(1_f64 / 9_f64);
let chk_take_u16: u16 = (chk_take * I96F32::from_num(u16::MAX)).saturating_to_num::<u16>();
ChildkeyTake::<Test>::insert(hotkey_b, netuid, chk_take_u16);

// Set the weight of root TAO to be 0%, so only alpha is effective.
SubtensorModule::set_tao_weight(0);

let hardcoded_emission: I96F32 = I96F32::from_num(1_000_000); // 1 million (adjust as needed)

let hotkey_emission: Vec<(U256, u64, u64)> =
SubtensorModule::epoch(netuid, hardcoded_emission.saturating_to_num::<u64>());
log::info!("hotkey_emission: {:?}", hotkey_emission);
let total_emission: I96F32 = hotkey_emission
.iter()
.map(|(_, _, emission)| I96F32::from_num(*emission))
.sum();

// Verify emissions match expected from CHK arrangements
let em_eps: I96F32 = I96F32::from_num(1e-4); // 4 decimal places
// A's pending emission:
assert!(
((I96F32::from_num(hotkey_emission[0].2) / total_emission) -
I96F32::from_num(3_f64 / 4_f64 * 1_f64 / 2_f64)).abs() // 3/4 * 1/2 = 3/8; 50% -> B
<= em_eps,
"A should have pending emission of 3/8 of total emission"
);
// B's pending emission:
assert!(
((I96F32::from_num(hotkey_emission[1].2) / total_emission) -
(I96F32::from_num(1_f64 / 4_f64 + 3_f64 / 4_f64 * 1_f64 / 2_f64))).abs() // 1/4 + 3/4 * 1/2 = 5/8; 50% from A
<= em_eps,
"B should have pending emission of 5/8 of total emission: {:?}",
I96F32::from_num(hotkey_emission[1].2) / total_emission
);

// Get the distribution of dividends including the Parent/Child relationship.
let dividends_a = SubtensorModule::get_dividends_distribution(
&hotkey_a,
netuid,
hardcoded_emission.saturating_to_num::<u64>(),
);
let dividends_b = SubtensorModule::get_dividends_distribution(
&hotkey_b,
netuid,
hardcoded_emission.saturating_to_num::<u64>(),
);
log::info!("dividends_a: {:?}", dividends_a);
log::info!("dividends_b: {:?}", dividends_b);

// We expect A should have no impact from B, as they have the same owner.
assert_eq!(dividends_a.len(), 1);
assert_eq!(dividends_a[0].0, hotkey_a);
assert_eq!(
dividends_a[0].1,
hardcoded_emission.saturating_to_num::<u64>()
);
assert_abs_diff_eq!(
dividends_a
.iter()
.map(|(_, emission)| *emission)
.sum::<u64>(),
hardcoded_emission.saturating_to_num::<u64>(),
epsilon = (hardcoded_emission / 1000).saturating_to_num::<u64>()
);

// Expect only 2 dividends. Parent key A and child key B.
assert_eq!(dividends_b.len(), 2); // A and B
assert_eq!(dividends_b[0].0, hotkey_a);
assert_eq!(dividends_b[1].0, hotkey_b);

// We expect B's coldkey to have no increase in dividends from A, as they have the same owner.
// And therefore, B should get no CHK_TAKE.

// A should also have no decrease because there is no CHK_TAKE.
let total_stake_b = rel_stake_b + rel_stake_a * 1 / 2;
let expected_b_b: u64 =
(rel_stake_b / total_stake_b * hardcoded_emission).saturating_to_num::<u64>();

assert_abs_diff_eq!(
dividends_b[1].1,
expected_b_b,
epsilon = (hardcoded_emission / 1000).saturating_to_num::<u64>(),
);

let expected_b_a: u64 =
((rel_stake_a * 1 / 2) / total_stake_b * hardcoded_emission).saturating_to_num::<u64>();
assert_eq!(dividends_b[0].0, hotkey_a);
assert_abs_diff_eq!(
dividends_b[0].1,
expected_b_a,
epsilon = (hardcoded_emission / 1000).saturating_to_num::<u64>()
);
assert_abs_diff_eq!(
dividends_b
.iter()
.map(|(_, emission)| *emission)
.sum::<u64>(),
hardcoded_emission.saturating_to_num::<u64>(),
epsilon = (hardcoded_emission / 1000).saturating_to_num::<u64>()
);
});
}
Loading
Loading