@@ -4,7 +4,7 @@ use crate::polkadot_test_net::*;
44use frame_support:: { assert_ok, traits:: Hooks } ;
55use frame_system:: RawOrigin ;
66use hydradx_runtime:: {
7- Currencies , FeeProcessor , Omnipool , Referrals , Router , Runtime , RuntimeOrigin , Staking , System , Tokens , Treasury ,
7+ Currencies , FeeProcessor , Omnipool , Referrals , Router , Runtime , RuntimeOrigin , Staking , System , Tokens ,
88} ;
99use orml_traits:: MultiCurrency ;
1010use pallet_fee_processor:: WeightInfo ;
@@ -103,7 +103,6 @@ fn hdx_fee_distributes_to_hdx_receivers() {
103103
104104 let staking_before = Currencies :: free_balance ( HDX , & staking_pot ( ) ) ;
105105 let referrals_before = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) ;
106- let treasury_before = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) ;
107106
108107 // Sell DAI for HDX — the trade fee on the HDX side will be in HDX
109108 assert_ok ! ( Omnipool :: sell(
@@ -116,22 +115,16 @@ fn hdx_fee_distributes_to_hdx_receivers() {
116115
117116 let staking_increase = Currencies :: free_balance ( HDX , & staking_pot ( ) ) . saturating_sub ( staking_before) ;
118117 let referrals_increase = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) . saturating_sub ( referrals_before) ;
119- let treasury_increase = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) . saturating_sub ( treasury_before) ;
120118
121- // Staking receives its HDX-path slice directly.
119+ // Referrals participates in the HDX fee path (ReferralsFeeReceiver is in HdxFeeReceivers),
120+ // so both pots receive their identical 5%/50% slice directly, without any conversion step.
122121 assert_eq ! (
123122 staking_increase, 188932 ,
124123 "Staking pot should receive its HDX-path slice"
125124 ) ;
126- // No referral shares exist, so the referrals slice is routed to the treasury rather than
127- // stranded in the (unclaimable) referrals pot.
128125 assert_eq ! (
129- referrals_increase, 0 ,
130- "Referrals pot must not be funded while TotalShares == 0"
131- ) ;
132- assert_eq ! (
133- treasury_increase, 188932 ,
134- "Referrals slice should be routed to the treasury while TotalShares == 0"
126+ referrals_increase, 188932 ,
127+ "Referrals pot should receive its HDX-path slice"
135128 ) ;
136129 } ) ;
137130}
@@ -234,7 +227,6 @@ fn non_hdx_fee_conversion_distributes_to_all_receivers() {
234227
235228 let staking_before = Currencies :: free_balance ( HDX , & staking_pot ( ) ) ;
236229 let referrals_before = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) ;
237- let treasury_before = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) ;
238230
239231 // Manually trigger conversion
240232 assert_ok ! ( FeeProcessor :: convert( RuntimeOrigin :: signed( ALICE . into( ) ) , DAI ) ) ;
@@ -254,27 +246,21 @@ fn non_hdx_fee_conversion_distributes_to_all_receivers() {
254246 // HDX should be distributed to all configured receiver pots
255247 // FeeReceivers: StakingFeeReceiver 10%, ReferralsFeeReceiver 10%
256248 let staking_increase = Currencies :: free_balance ( HDX , & staking_pot ( ) ) . saturating_sub ( staking_before) ;
257- let referrals_pot_increase = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) . saturating_sub ( referrals_before) ;
258- // No referral shares exist, so the referrals slice is routed to the treasury, not the pot.
259- let referrals_increase = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) . saturating_sub ( treasury_before) ;
249+ let referrals_increase = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) . saturating_sub ( referrals_before) ;
260250
261251 assert ! ( staking_increase > 0 , "Staking pot should receive HDX from conversion" ) ;
262- assert_eq ! (
263- referrals_pot_increase, 0 ,
264- "Referrals pot must not be funded while TotalShares == 0"
265- ) ;
266252 assert ! (
267253 referrals_increase > 0 ,
268- "Referrals slice should reach the treasury from conversion"
254+ "Referrals pot should receive HDX from conversion"
269255 ) ;
270256
271- // Staking and referrals both get the same 5/50 slice, so the treasury-routed referrals
272- // amount should roughly match the staking pot's increase .
257+ // Staking and referrals both get 10%, but referrals on_fee_received callback
258+ // redistributes some HDX to referrers, so the pot balance may be slightly less .
273259 let diff = staking_increase. abs_diff ( referrals_increase) ;
274260 let tolerance = staking_increase / 10 ; // 10% tolerance
275261 assert ! (
276262 diff <= tolerance,
277- "Staking and referrals should receive roughly equal amounts: {staking_increase} vs {referrals_increase} (diff: {diff})" ,
263+ "Staking (10%) and referrals (10%) should receive roughly equal amounts: {staking_increase} vs {referrals_increase} (diff: {diff})" ,
278264 ) ;
279265 } ) ;
280266}
@@ -304,7 +290,6 @@ fn non_hdx_conversion_distributes_exact_proportional_amounts_to_each_receiver()
304290
305291 let staking_before = Currencies :: free_balance ( HDX , & staking_pot ( ) ) ;
306292 let referrals_before = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) ;
307- let treasury_before = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) ;
308293 let gigahdx_before = Currencies :: free_balance ( HDX , & gigahdx_pot ( ) ) ;
309294 let rewards_before = Currencies :: free_balance ( HDX , & gigahdx_rewards_pot ( ) ) ;
310295
@@ -351,13 +336,7 @@ fn non_hdx_conversion_distributes_exact_proportional_amounts_to_each_receiver()
351336 let gigahdx_increase = Currencies :: free_balance ( HDX , & gigahdx_pot ( ) ) - gigahdx_before;
352337 let rewards_increase = Currencies :: free_balance ( HDX , & gigahdx_rewards_pot ( ) ) - rewards_before;
353338 let staking_increase = Currencies :: free_balance ( HDX , & staking_pot ( ) ) - staking_before;
354- // No referral shares exist, so the referrals slice is routed to the treasury, not the pot.
355- let referrals_pot_increase = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) - referrals_before;
356- let referrals_increase = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) - treasury_before;
357- assert_eq ! (
358- referrals_pot_increase, 0 ,
359- "Referrals pot must not be funded while TotalShares == 0"
360- ) ;
339+ let referrals_increase = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) - referrals_before;
361340
362341 // Every receiver gets at least its primary `hdx_out` share, plus a strictly positive
363342 // secondary slice — so each strictly exceeds the primary share.
@@ -535,7 +514,6 @@ fn multiple_hdx_trades_accumulate_in_all_hdx_receivers() {
535514
536515 let staking_initial = Currencies :: free_balance ( HDX , & staking_pot ( ) ) ;
537516 let referrals_initial = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) ;
538- let treasury_initial = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) ;
539517
540518 // Multiple HDX-generating trades
541519 for _ in 0 ..3 {
@@ -549,23 +527,17 @@ fn multiple_hdx_trades_accumulate_in_all_hdx_receivers() {
549527 }
550528
551529 let staking_total = Currencies :: free_balance ( HDX , & staking_pot ( ) ) . saturating_sub ( staking_initial) ;
552- let referrals_pot_total = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) . saturating_sub ( referrals_initial) ;
553- // No referral shares exist, so the referrals slice is routed to the treasury, not the pot.
554- let referrals_total = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) . saturating_sub ( treasury_initial) ;
530+ let referrals_total = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) . saturating_sub ( referrals_initial) ;
555531
556532 // Both staking and referrals are in HdxFeeReceivers, so both accumulate across the 3 trades
557533 // (3 × the single-trade slice of 188932).
558534 assert_eq ! (
559535 staking_total, 566796 ,
560536 "Staking pot should accumulate from multiple HDX trades"
561537 ) ;
562- assert_eq ! (
563- referrals_pot_total, 0 ,
564- "Referrals pot must not be funded while TotalShares == 0"
565- ) ;
566538 assert_eq ! (
567539 referrals_total, 566796 ,
568- "Referrals slice should accumulate in the treasury from multiple HDX trades"
540+ "Referrals pot should accumulate from multiple HDX trades"
569541 ) ;
570542 } ) ;
571543}
@@ -678,7 +650,6 @@ fn tiny_hdx_fee_distributes_to_configured_receivers() {
678650 let rewards_before = Currencies :: free_balance ( HDX , & gigahdx_rewards_pot ( ) ) ;
679651 let staking_before = Currencies :: free_balance ( HDX , & staking_pot ( ) ) ;
680652 let referrals_before = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) ;
681- let treasury_before = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) ;
682653
683654 //Act: HdxFeeReceivers = GigaHdx 15% + GigaHdxRewards 25% + HdxStaking 5% + Referrals 5%
684655 // (total 50%). fee = 100 → take = 50, distributed as 15 / 25 / 5 / 5.
@@ -691,12 +662,7 @@ fn tiny_hdx_fee_distributes_to_configured_receivers() {
691662 25
692663 ) ;
693664 assert_eq ! ( Currencies :: free_balance( HDX , & staking_pot( ) ) - staking_before, 5 ) ;
694- // No referral shares exist, so the referrals slice (5) is routed to the treasury, not the pot.
695- assert_eq ! ( Currencies :: free_balance( HDX , & referrals_pot( ) ) - referrals_before, 0 ) ;
696- assert_eq ! (
697- Currencies :: free_balance( HDX , & Treasury :: account_id( ) ) - treasury_before,
698- 5
699- ) ;
665+ assert_eq ! ( Currencies :: free_balance( HDX , & referrals_pot( ) ) - referrals_before, 5 ) ;
700666 } ) ;
701667}
702668
@@ -935,26 +901,19 @@ fn buy_trade_distributes_fees_same_as_sell() {
935901 // Convert and verify configured receiver pots get HDX
936902 let staking_pre = Currencies :: free_balance ( HDX , & staking_pot ( ) ) ;
937903 let referrals_pre = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) ;
938- let treasury_pre = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) ;
939904
940905 assert_ok ! ( FeeProcessor :: convert( RuntimeOrigin :: signed( ALICE . into( ) ) , DAI ) ) ;
941906
942907 let staking_increase = Currencies :: free_balance ( HDX , & staking_pot ( ) ) . saturating_sub ( staking_pre) ;
943- let referrals_pot_increase = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) . saturating_sub ( referrals_pre) ;
944- // No referral shares exist, so the referrals slice is routed to the treasury, not the pot.
945- let referrals_increase = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) . saturating_sub ( treasury_pre) ;
908+ let referrals_increase = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) . saturating_sub ( referrals_pre) ;
946909
947910 assert ! (
948911 staking_increase > 0 ,
949912 "Staking should receive HDX from buy trade conversion"
950913 ) ;
951- assert_eq ! (
952- referrals_pot_increase, 0 ,
953- "Referrals pot must not be funded while TotalShares == 0"
954- ) ;
955914 assert ! (
956915 referrals_increase > 0 ,
957- "Referrals slice should reach the treasury from buy trade conversion"
916+ "Referrals should receive HDX from buy trade conversion"
958917 ) ;
959918 } ) ;
960919}
@@ -1378,26 +1337,19 @@ fn router_trade_fee_distribution_matches_direct_trade() {
13781337 // Convert and verify configured receiver pots get HDX
13791338 let staking_pre = Currencies :: free_balance ( HDX , & staking_pot ( ) ) ;
13801339 let referrals_pre = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) ;
1381- let treasury_pre = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) ;
13821340
13831341 assert_ok ! ( FeeProcessor :: convert( RuntimeOrigin :: signed( ALICE . into( ) ) , DAI ) ) ;
13841342
13851343 let staking_increase = Currencies :: free_balance ( HDX , & staking_pot ( ) ) . saturating_sub ( staking_pre) ;
1386- let referrals_pot_increase = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) . saturating_sub ( referrals_pre) ;
1387- // No referral shares exist, so the referrals slice is routed to the treasury, not the pot.
1388- let referrals_increase = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) . saturating_sub ( treasury_pre) ;
1344+ let referrals_increase = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) . saturating_sub ( referrals_pre) ;
13891345
13901346 assert ! (
13911347 staking_increase > 0 ,
13921348 "Staking should receive HDX from router trade conversion"
13931349 ) ;
1394- assert_eq ! (
1395- referrals_pot_increase, 0 ,
1396- "Referrals pot must not be funded while TotalShares == 0"
1397- ) ;
13981350 assert ! (
13991351 referrals_increase > 0 ,
1400- "Referrals slice should reach the treasury from router trade conversion"
1352+ "Referrals should receive HDX from router trade conversion"
14011353 ) ;
14021354 } ) ;
14031355}
@@ -1418,7 +1370,6 @@ fn buying_hdx_from_omnipool_credits_all_four_hdx_fee_pots() {
14181370 let giga_rewards_before = Currencies :: free_balance ( HDX , & gigahdx_rewards_pot ( ) ) ;
14191371 let staking_before = Currencies :: free_balance ( HDX , & staking_pot ( ) ) ;
14201372 let referrals_before = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) ;
1421- let treasury_before = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) ;
14221373
14231374 // Buy 100 HDX with DAI — fee leg is on `asset_out` (HDX), so this hits
14241375 // the HDX path of process_trade_fee and distributes synchronously.
@@ -1467,13 +1418,7 @@ fn buying_hdx_from_omnipool_credits_all_four_hdx_fee_pots() {
14671418 let giga_increase = Currencies :: free_balance ( HDX , & gigahdx_pot ( ) ) - giga_before;
14681419 let giga_rewards_increase = Currencies :: free_balance ( HDX , & gigahdx_rewards_pot ( ) ) - giga_rewards_before;
14691420 let staking_increase = Currencies :: free_balance ( HDX , & staking_pot ( ) ) - staking_before;
1470- // No referral shares exist, so the referrals slice is routed to the treasury, not the pot.
1471- let referrals_pot_increase = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) - referrals_before;
1472- let referrals_increase = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) - treasury_before;
1473- assert_eq ! (
1474- referrals_pot_increase, 0 ,
1475- "Referrals pot must not be funded while TotalShares == 0"
1476- ) ;
1421+ let referrals_increase = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) - referrals_before;
14771422
14781423 assert_eq ! ( giga_increase, share( 15 ) , "gigaHDX pot must receive 15/50 share" ) ;
14791424 assert_eq ! (
@@ -1482,11 +1427,7 @@ fn buying_hdx_from_omnipool_credits_all_four_hdx_fee_pots() {
14821427 "gigaHDX rewards pot must receive 25/50 share"
14831428 ) ;
14841429 assert_eq ! ( staking_increase, share( 5 ) , "legacy staking pot must receive 5/50 share" ) ;
1485- assert_eq ! (
1486- referrals_increase,
1487- share( 5 ) ,
1488- "referrals 5/50 share must be routed to the treasury"
1489- ) ;
1430+ assert_eq ! ( referrals_increase, share( 5 ) , "referrals pot must receive 5/50 share" ) ;
14901431
14911432 // Conservation: the four receiver shares plus any rounding dust account
14921433 // for the full take. With three 5% slices and one 15%/25%, distinct
@@ -1535,9 +1476,6 @@ fn selling_for_dai_then_advancing_block_distributes_converted_hdx_to_all_four_po
15351476 // so we call it explicitly with generous weight. This is the path the chain
15361477 // will take in production once the next block fires.
15371478 hydradx_run_to_next_block ( ) ;
1538- // Snapshot the treasury right before conversion: with no referral shares the referrals
1539- // slice is routed here, and this isolates it from any block-production effects above.
1540- let treasury_before = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) ;
15411479 let weight = frame_support:: weights:: Weight :: from_parts ( 1_000_000_000_000 , u64:: MAX ) ;
15421480 pallet_fee_processor:: Pallet :: < Runtime > :: on_idle ( System :: block_number ( ) , weight) ;
15431481
@@ -1600,14 +1538,7 @@ fn selling_for_dai_then_advancing_block_distributes_converted_hdx_to_all_four_po
16001538 let giga_increase = Currencies :: free_balance ( HDX , & gigahdx_pot ( ) ) - giga_before;
16011539 let giga_rewards_increase = Currencies :: free_balance ( HDX , & gigahdx_rewards_pot ( ) ) - giga_rewards_before;
16021540 let staking_increase = Currencies :: free_balance ( HDX , & staking_pot ( ) ) - staking_before;
1603- // No referral shares exist, so the referrals slice (primary + nested HDX) is routed to the
1604- // treasury, not the pot.
1605- let referrals_pot_increase = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) - referrals_before;
1606- let referrals_increase = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) - treasury_before;
1607- assert_eq ! (
1608- referrals_pot_increase, 0 ,
1609- "Referrals pot must not be funded while TotalShares == 0"
1610- ) ;
1541+ let referrals_increase = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) - referrals_before;
16111542
16121543 assert_eq ! (
16131544 giga_increase,
@@ -1627,7 +1558,7 @@ fn selling_for_dai_then_advancing_block_distributes_converted_hdx_to_all_four_po
16271558 assert_eq ! (
16281559 referrals_increase,
16291560 expected( 5 ) ,
1630- "referrals 5/50 of both inflows must be routed to the treasury "
1561+ "referrals pot must receive 5/50 of both inflows"
16311562 ) ;
16321563 } ) ;
16331564}
@@ -1834,35 +1765,28 @@ fn referrer_may_claim_a_bounded_cut_during_the_conversion_gap() {
18341765 } ) ;
18351766}
18361767
1837- /// FINDING 5 — referral fee slice must not strand in the pot when `TotalShares == 0`.
1838- ///
1839- /// The referrals accumulator (`RewardPerShare`) divides by `TotalShares`, so it cannot
1840- /// record a deposit while no shares exist (genesis/pre-adoption, or after every holder has
1841- /// claimed and burned). Previously the slice was still transferred into the referrals pot,
1842- /// where — never reflected in `RewardPerShare` — it was permanently unclaimable.
1843- ///
1844- /// Fix: while `TotalShares == 0` the slice is routed to the treasury instead. The referrals
1845- /// pot is left untouched and the accumulator correctly stays zero (no shareholder to
1846- /// attribute the deposit to); the value is preserved in the treasury.
1768+ /// FINDING 5 (accepted limitation) — while `TotalShares == 0` the referrals slice is still
1769+ /// deposited into the referrals pot, but the accumulator (`RewardPerShare`) divides by
1770+ /// `TotalShares` and so cannot record it yet. This is accepted for the genesis/pre-adoption
1771+ /// window; once any referral shares exist, deposits accrue normally. (A treasury-redirect fix
1772+ /// was reverted because it polluted the shared treasury fee accounting.)
18471773#[ test]
1848- fn referral_fee_with_zero_shares_should_be_routed_to_treasury ( ) {
1774+ fn referral_fee_with_zero_shares_is_deposited_to_pot_but_not_yet_accrued ( ) {
18491775 TestNet :: reset ( ) ;
18501776
18511777 Hydra :: execute_with ( || {
18521778 init_omnipool_with_oracle_for_block_24 ( ) ;
18531779
1854- // No referral codes registered/linked → no referral shares exist.
18551780 assert_eq ! ( Referrals :: total_shares( ) , 0 , "no referral shares in the system" ) ;
18561781 assert ! (
18571782 pallet_referrals:: RewardPerShare :: <Runtime >:: get( ) . is_zero( ) ,
18581783 "accumulator starts at zero"
18591784 ) ;
18601785
18611786 let referrals_pot_before = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) ;
1862- let treasury_before = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) ;
18631787
1864- // An unlinked trader generates a non-HDX (DAI) fee, then it is converted to HDX —
1865- // the referrals slice would normally be transferred into the referrals pot.
1788+ // An unlinked trader generates a non-HDX (DAI) fee that is converted to HDX; the
1789+ // referrals slice is transferred into the referrals pot.
18661790 assert_ok ! ( Omnipool :: sell(
18671791 RuntimeOrigin :: signed( BOB . into( ) ) ,
18681792 HDX ,
@@ -1872,24 +1796,15 @@ fn referral_fee_with_zero_shares_should_be_routed_to_treasury() {
18721796 ) ) ;
18731797 assert_ok ! ( FeeProcessor :: convert( RuntimeOrigin :: signed( ALICE . into( ) ) , DAI ) ) ;
18741798
1875- let referrals_pot_increase =
1876- Currencies :: free_balance ( HDX , & referrals_pot ( ) ) . saturating_sub ( referrals_pot_before) ;
1877- let treasury_increase = Currencies :: free_balance ( HDX , & Treasury :: account_id ( ) ) . saturating_sub ( treasury_before) ;
1799+ let deposited = Currencies :: free_balance ( HDX , & referrals_pot ( ) ) . saturating_sub ( referrals_pot_before) ;
18781800
1879- // The slice was generated and routed to the treasury, not stranded in the pot...
1880- assert ! (
1881- treasury_increase > 0 ,
1882- "the un-attributable referrals slice must be routed to the treasury"
1883- ) ;
1884- assert_eq ! (
1885- referrals_pot_increase, 0 ,
1886- "the referrals pot must not retain un-attributable HDX"
1887- ) ;
1888- // ...and the accumulator correctly stays zero (no shares to attribute to).
1801+ // The slice lands in the referrals pot...
1802+ assert ! ( deposited > 0 , "the referrals slice is deposited into the referrals pot" ) ;
1803+ // ...but with zero shares the accumulator cannot record it (accepted limitation).
18891804 assert_eq ! ( Referrals :: total_shares( ) , 0 , "still no referral shares" ) ;
18901805 assert ! (
18911806 pallet_referrals:: RewardPerShare :: <Runtime >:: get( ) . is_zero( ) ,
1892- "RewardPerShare must stay zero — there is no shareholder to attribute the deposit to "
1807+ "with zero shares the deposit is not (yet) reflected in the accumulator "
18931808 ) ;
18941809 } ) ;
18951810}
0 commit comments