Skip to content

Commit 0e2eccb

Browse files
committed
fix test and impl
1 parent 318bd48 commit 0e2eccb

File tree

2 files changed

+214
-25
lines changed

2 files changed

+214
-25
lines changed

pallets/subtensor/src/coinbase/run_coinbase.rs

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,7 @@ impl<T: Config> Pallet<T> {
497497

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

510-
// Reserve childkey take
511-
let child_emission_take: I96F32 = childkey_take_proportion
512-
.saturating_mul(I96F32::saturating_from_num(validating_emission));
513-
let remaining_emission: I96F32 = validating_emission.saturating_sub(child_emission_take);
514-
log::debug!(
515-
"Child emission take: {:?} for hotkey {:?}",
516-
child_emission_take,
517-
hotkey
518-
);
519-
log::debug!(
520-
"Remaining emission: {:?} for hotkey {:?}",
521-
remaining_emission,
522-
hotkey
523-
);
511+
// Grab the owner of the childkey.
512+
let childkey_owner = Self::get_owning_coldkey_for_hotkey(hotkey);
524513

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

528518
// Initialize variables to calculate total stakes from parents
529519
let mut total_contribution: I96F32 = I96F32::saturating_from_num(0);
@@ -580,33 +570,66 @@ impl<T: Config> Pallet<T> {
580570
// Distribute emission to parents based on their contributions.
581571
// Deduct childkey take from parent contribution.
582572
for (parent, contribution) in parent_contributions {
583-
// Sum up the total emission for this parent
573+
let parent_owner = Self::get_owning_coldkey_for_hotkey(&parent);
574+
575+
// Get the stake contribution of this parent key of the total stake.
584576
let emission_factor: I96F32 = contribution
585577
.checked_div(total_contribution)
586578
.unwrap_or(I96F32::saturating_from_num(0));
587-
let parent_emission: u64 =
588-
(remaining_emission.saturating_mul(emission_factor)).saturating_to_num::<u64>();
579+
580+
// Get the parent's portion of the validating emission based on their contribution.
581+
let mut parent_emission: I96F32 = validating_emission.saturating_mul(emission_factor);
582+
// Remove this emission from the remaining emission.
583+
remaining_emission = remaining_emission.saturating_sub(parent_emission);
584+
585+
// Get the childkey take for this parent.
586+
let child_emission_take: I96F32 = if parent_owner == childkey_owner {
587+
// The parent is from the same coldkey, so we don't remove any childkey take.
588+
I96F32::saturating_from_num(0)
589+
} else {
590+
childkey_take_proportion
591+
.saturating_mul(I96F32::saturating_from_num(parent_emission))
592+
};
593+
594+
// Remove the childkey take from the parent's emission.
595+
parent_emission = parent_emission.saturating_sub(child_emission_take);
596+
597+
// Add the childkey take to the total childkey take tracker.
598+
total_child_emission_take =
599+
total_child_emission_take.saturating_add(child_emission_take);
600+
601+
log::debug!(
602+
"Child emission take: {:?} for hotkey {:?}",
603+
child_emission_take,
604+
hotkey
605+
);
606+
log::debug!(
607+
"Parent emission: {:?} for hotkey {:?}",
608+
parent_emission,
609+
hotkey
610+
);
611+
log::debug!("remaining emission: {:?}", remaining_emission);
589612

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

593616
// Keep track of total emission distributed to parents
594-
to_parents = to_parents.saturating_add(parent_emission);
617+
to_parents = to_parents.saturating_add(parent_emission.saturating_to_num::<u64>());
595618
log::debug!(
596-
"Parent contribution for parent {:?} with contribution: {:?}, of total: {:?} of emission: {:?} gets: {:?}",
619+
"Parent contribution for parent {:?} with contribution: {:?}, of total: {:?} ({:?}), of emission: {:?} gets: {:?}",
597620
parent,
598621
contribution,
599622
total_contribution,
600-
remaining_emission,
601-
parent_emission
623+
emission_factor,
624+
validating_emission,
625+
parent_emission,
602626
);
603627
}
604628
// Calculate the final emission for the hotkey itself.
605629
// This includes the take left from the parents and the self contribution.
606630
let child_emission = remaining_emission
607-
.saturating_add(child_emission_take)
608-
.saturating_to_num::<u64>()
609-
.saturating_sub(to_parents);
631+
.saturating_add(total_child_emission_take)
632+
.saturating_to_num::<u64>();
610633

611634
// Add the hotkey's own emission to the distribution list
612635
dividend_tuples.push((hotkey.clone(), child_emission));

pallets/subtensor/src/tests/children.rs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3906,3 +3906,169 @@ fn test_do_set_child_as_sn_owner_not_enough_stake() {
39063906
);
39073907
});
39083908
}
3909+
3910+
// Test dividend distribution for children with same coldkey Owner
3911+
// 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
3912+
#[test]
3913+
fn test_dividend_distribution_with_children_same_coldkey_owner() {
3914+
new_test_ext(1).execute_with(|| {
3915+
let netuid: u16 = 1;
3916+
add_network(netuid, 1, 0);
3917+
// Set SN owner cut to 0
3918+
SubtensorModule::set_subnet_owner_cut(0_u16);
3919+
3920+
// Define hotkeys and coldkeys
3921+
let hotkey_a: U256 = U256::from(1);
3922+
let hotkey_b: U256 = U256::from(2);
3923+
let coldkey_a: U256 = U256::from(100); // Only one coldkey
3924+
3925+
// Register neurons with decreasing stakes
3926+
register_ok_neuron(netuid, hotkey_a, coldkey_a, 0);
3927+
register_ok_neuron(netuid, hotkey_b, coldkey_a, 0);
3928+
3929+
// Add initial stakes
3930+
SubtensorModule::add_balance_to_coldkey_account(&coldkey_a, 1_000);
3931+
SubtensorModule::add_balance_to_coldkey_account(&coldkey_a, 1_000);
3932+
3933+
// Swap to alpha
3934+
let total_tao: I96F32 = I96F32::from_num(300_000 + 100_000);
3935+
let total_alpha: I96F32 = I96F32::from_num(SubtensorModule::swap_tao_for_alpha(
3936+
netuid,
3937+
total_tao.saturating_to_num::<u64>(),
3938+
));
3939+
3940+
// Set the stakes directly
3941+
// This avoids needing to swap tao to alpha, impacting the initial stake distribution.
3942+
SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet(
3943+
&hotkey_a,
3944+
&coldkey_a,
3945+
netuid,
3946+
(total_alpha * I96F32::from_num(300_000) / total_tao).saturating_to_num::<u64>(),
3947+
);
3948+
SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet(
3949+
&hotkey_b,
3950+
&coldkey_a,
3951+
netuid,
3952+
(total_alpha * I96F32::from_num(100_000) / total_tao).saturating_to_num::<u64>(),
3953+
);
3954+
3955+
// Get old stakes
3956+
let stake_a: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_a);
3957+
let stake_b: u64 = SubtensorModule::get_total_stake_for_hotkey(&hotkey_b);
3958+
3959+
// Assert initial stake is correct
3960+
let rel_stake_a = I96F32::from_num(stake_a) / total_tao;
3961+
let rel_stake_b = I96F32::from_num(stake_b) / total_tao;
3962+
3963+
log::info!("rel_stake_a: {:?}", rel_stake_a); // 0.75 -> 3/4
3964+
log::info!("rel_stake_b: {:?}", rel_stake_b); // 0.25 -> 1/4
3965+
assert_eq!(rel_stake_a, I96F32::from_num(300_000) / total_tao);
3966+
assert_eq!(rel_stake_b, I96F32::from_num(100_000) / total_tao);
3967+
3968+
// Set parent-child relationships
3969+
// A -> B (50% of A's stake)
3970+
mock_set_children(&coldkey_a, &hotkey_a, netuid, &[(u64::MAX / 2, hotkey_b)]);
3971+
3972+
// Set CHK take rate to 1/9
3973+
let chk_take: I96F32 = I96F32::from_num(1_f64 / 9_f64);
3974+
let chk_take_u16: u16 = (chk_take * I96F32::from_num(u16::MAX)).saturating_to_num::<u16>();
3975+
ChildkeyTake::<Test>::insert(hotkey_b, netuid, chk_take_u16);
3976+
3977+
// Set the weight of root TAO to be 0%, so only alpha is effective.
3978+
SubtensorModule::set_tao_weight(0);
3979+
3980+
let hardcoded_emission: I96F32 = I96F32::from_num(1_000_000); // 1 million (adjust as needed)
3981+
3982+
let hotkey_emission: Vec<(U256, u64, u64)> =
3983+
SubtensorModule::epoch(netuid, hardcoded_emission.saturating_to_num::<u64>());
3984+
log::info!("hotkey_emission: {:?}", hotkey_emission);
3985+
let total_emission: I96F32 = hotkey_emission
3986+
.iter()
3987+
.map(|(_, _, emission)| I96F32::from_num(*emission))
3988+
.sum();
3989+
3990+
// Verify emissions match expected from CHK arrangements
3991+
let em_eps: I96F32 = I96F32::from_num(1e-4); // 4 decimal places
3992+
// A's pending emission:
3993+
assert!(
3994+
((I96F32::from_num(hotkey_emission[0].2) / total_emission) -
3995+
I96F32::from_num(3_f64 / 4_f64 * 1_f64 / 2_f64)).abs() // 3/4 * 1/2 = 3/8; 50% -> B
3996+
<= em_eps,
3997+
"A should have pending emission of 3/8 of total emission"
3998+
);
3999+
// B's pending emission:
4000+
assert!(
4001+
((I96F32::from_num(hotkey_emission[1].2) / total_emission) -
4002+
(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
4003+
<= em_eps,
4004+
"B should have pending emission of 5/8 of total emission: {:?}",
4005+
I96F32::from_num(hotkey_emission[1].2) / total_emission
4006+
);
4007+
4008+
// Get the distribution of dividends including the Parent/Child relationship.
4009+
let dividends_a = SubtensorModule::get_dividends_distribution(
4010+
&hotkey_a,
4011+
netuid,
4012+
hardcoded_emission.saturating_to_num::<u64>(),
4013+
);
4014+
let dividends_b = SubtensorModule::get_dividends_distribution(
4015+
&hotkey_b,
4016+
netuid,
4017+
hardcoded_emission.saturating_to_num::<u64>(),
4018+
);
4019+
log::info!("dividends_a: {:?}", dividends_a);
4020+
log::info!("dividends_b: {:?}", dividends_b);
4021+
4022+
// We expect A should have no impact from B, as they have the same owner.
4023+
assert_eq!(dividends_a.len(), 1);
4024+
assert_eq!(dividends_a[0].0, hotkey_a);
4025+
assert_eq!(
4026+
dividends_a[0].1,
4027+
hardcoded_emission.saturating_to_num::<u64>()
4028+
);
4029+
assert_abs_diff_eq!(
4030+
dividends_a
4031+
.iter()
4032+
.map(|(_, emission)| *emission)
4033+
.sum::<u64>(),
4034+
hardcoded_emission.saturating_to_num::<u64>(),
4035+
epsilon = (hardcoded_emission / 1000).saturating_to_num::<u64>()
4036+
);
4037+
4038+
// Expect only 2 dividends. Parent key A and child key B.
4039+
assert_eq!(dividends_b.len(), 2); // A and B
4040+
assert_eq!(dividends_b[0].0, hotkey_a);
4041+
assert_eq!(dividends_b[1].0, hotkey_b);
4042+
4043+
// We expect B's coldkey to have no increase in dividends from A, as they have the same owner.
4044+
// And therefore, B should get no CHK_TAKE.
4045+
4046+
// A should also have no decrease because there is no CHK_TAKE.
4047+
let total_stake_b = rel_stake_b + rel_stake_a * 1 / 2;
4048+
let expected_b_b: u64 =
4049+
(rel_stake_b / total_stake_b * hardcoded_emission).saturating_to_num::<u64>();
4050+
4051+
assert_abs_diff_eq!(
4052+
dividends_b[1].1,
4053+
expected_b_b,
4054+
epsilon = (hardcoded_emission / 1000).saturating_to_num::<u64>(),
4055+
);
4056+
4057+
let expected_b_a: u64 =
4058+
((rel_stake_a * 1 / 2) / total_stake_b * hardcoded_emission).saturating_to_num::<u64>();
4059+
assert_eq!(dividends_b[0].0, hotkey_a);
4060+
assert_abs_diff_eq!(
4061+
dividends_b[0].1,
4062+
expected_b_a,
4063+
epsilon = (hardcoded_emission / 1000).saturating_to_num::<u64>()
4064+
);
4065+
assert_abs_diff_eq!(
4066+
dividends_b
4067+
.iter()
4068+
.map(|(_, emission)| *emission)
4069+
.sum::<u64>(),
4070+
hardcoded_emission.saturating_to_num::<u64>(),
4071+
epsilon = (hardcoded_emission / 1000).saturating_to_num::<u64>()
4072+
);
4073+
});
4074+
}

0 commit comments

Comments
 (0)