@@ -3906,3 +3906,169 @@ fn test_do_set_child_as_sn_owner_not_enough_stake() {
3906
3906
) ;
3907
3907
} ) ;
3908
3908
}
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