11use crate :: bundle_producer_election:: calculate_threshold;
22use crate :: offline_operators:: {
3- E_BASE , LN_1_OVER_TAU_1_PERCENT , chernoff_threshold_fp, compute_e_relevance,
4- is_throughput_relevant_fp, operator_expected_bundles_in_epoch, p_from_threshold,
3+ E_BASE , LN_1_OVER_TAU_0_5_PERCENT , LN_1_OVER_TAU_1_PERCENT , chernoff_threshold_fp,
4+ compute_e_relevance, is_throughput_relevant_fp, operator_expected_bundles_in_epoch,
5+ p_from_threshold,
56} ;
67use num_traits:: { One , Zero } ;
78use prop_test:: proptest:: prelude:: { ProptestConfig , Strategy } ;
@@ -11,10 +12,6 @@ use prop_test::proptest::{prop_assert, prop_assert_eq, prop_assume, proptest};
1112use sp_arithmetic:: traits:: { SaturatedConversion , Saturating } ;
1213use sp_arithmetic:: { FixedPointNumber , FixedU128 } ;
1314
14- // For τ = 0.5%: ln(1/τ) = ln(200) ≈ 5.298317366548036
15- // Represent in FixedU128 by multiplying by 1e18 and rounding.
16- const LN_1_OVER_TAU_0_5_PERCENT : FixedU128 = FixedU128 :: from_inner ( 5_298_317_366_548_036_000 ) ;
17-
1815// For τ = 0.1%: ln(1/τ) = ln(1000) ≈ 6.907755278982137
1916// Represent in FixedU128 by multiplying by 1e18 and rounding.
2017const LN_1_OVER_TAU_0_1_PERCENT : FixedU128 = FixedU128 :: from_inner ( 6_907_755_278_982_137_000 ) ;
@@ -29,12 +26,22 @@ fn test_ln_1_over_tau_1_value() {
2926 assert_eq ! ( LN_1_OVER_TAU_1_PERCENT , computed_ln_1_over_tau_1, ) ;
3027}
3128
29+ #[ test]
30+ fn test_ln_1_over_tau_0_5_value ( ) {
31+ // Dynamically compute ln(200) in floating point
32+ // Convert to FixedU128 representation (scaled by 1e18)
33+ let computed_ln_1_over_tau_0_5 = FixedU128 :: from_inner ( ( f64:: ln ( 200.0 ) * 1e18 ) . round ( ) as u128 ) ;
34+
35+ // Check equality of integer fixed-point representation
36+ assert_eq ! ( LN_1_OVER_TAU_0_5_PERCENT , computed_ln_1_over_tau_0_5, ) ;
37+ }
38+
3239#[ test]
3340fn test_chernoff_basic ( ) {
34- // S = 600, p = 0.05 (μ = 30). τ = 1 % -> ln(100 )
41+ // S = 600, p = 0.05 (μ = 30). τ = 0.5 % -> ln(200 )
3542 let s = 600u64 ;
3643 let p = FixedU128 :: saturating_from_rational ( 5u128 , 100u128 ) ;
37- let r = chernoff_threshold_fp ( s, p, LN_1_OVER_TAU_1_PERCENT ) . unwrap ( ) ;
44+ let r = chernoff_threshold_fp ( s, p, LN_1_OVER_TAU_0_5_PERCENT ) . unwrap ( ) ;
3845 // Chernoff is conservative; r will be noticeably below μ.
3946 // Expect r around low/mid-teens.
4047 assert ! ( r > 0 && r < 30 , "r should be between 1 and 29, got {r}" ) ;
@@ -66,13 +73,13 @@ fn test_p_from_threshold() {
6673}
6774
6875fn e_rel_1pct ( ) -> u64 {
69- compute_e_relevance ( LN_1_OVER_TAU_1_PERCENT , E_BASE )
76+ compute_e_relevance ( LN_1_OVER_TAU_0_5_PERCENT , E_BASE )
7077}
7178
7279#[ test]
73- fn e_relevance_is_10_for_tau_1pct ( ) {
74- // 2 * ln(100 ) ≈ 9.21034 -> ceil = 10 ; max(E_BASE=3, 10 ) = 10
75- assert_eq ! ( e_rel_1pct( ) , 10 ) ;
80+ fn e_relevance_is_11_for_tau_0_5_pct ( ) {
81+ // 2 * ln(200 ) ≈ 10.5966347331 -> ceil = 11 ; max(E_BASE=3, 11 ) = 11
82+ assert_eq ! ( e_rel_1pct( ) , 11 ) ;
7683}
7784
7885#[ test]
@@ -84,7 +91,7 @@ fn returns_none_when_no_slots_or_zero_threshold_inputs() {
8491 1_000 , // operator_stake
8592 1_000_000 , // total_domain_stake
8693 ( 1 , 1 ) , // theta
87- LN_1_OVER_TAU_1_PERCENT ,
94+ LN_1_OVER_TAU_0_5_PERCENT ,
8895 E_BASE ,
8996 )
9097 . is_none( )
@@ -97,7 +104,7 @@ fn returns_none_when_no_slots_or_zero_threshold_inputs() {
97104 1_000 , // operator_stake
98105 0 , // total_domain_stake
99106 ( 1 , 1 ) ,
100- LN_1_OVER_TAU_1_PERCENT ,
107+ LN_1_OVER_TAU_0_5_PERCENT ,
101108 E_BASE ,
102109 )
103110 . is_none( )
@@ -110,7 +117,7 @@ fn returns_none_when_no_slots_or_zero_threshold_inputs() {
110117 0 , // operator_stake
111118 1_000_000_000_000_000_000 , // total_domain_stake
112119 ( 1 , 1 ) ,
113- LN_1_OVER_TAU_1_PERCENT ,
120+ LN_1_OVER_TAU_0_5_PERCENT ,
114121 E_BASE ,
115122 )
116123 . is_none( )
@@ -128,7 +135,7 @@ fn small_mu_is_not_throughput_relevant() {
128135 operator,
129136 total,
130137 ( 1 , 1 ) ,
131- LN_1_OVER_TAU_1_PERCENT ,
138+ LN_1_OVER_TAU_0_5_PERCENT ,
132139 E_BASE ,
133140 )
134141 . is_none( )
@@ -137,19 +144,19 @@ fn small_mu_is_not_throughput_relevant() {
137144
138145#[ test]
139146fn relevant_operator_produces_some_expectations ( ) {
140- // S = 600, share ~ 2% => μ ≈ 12 >= 10 -> Some(...)
147+ // S = 600, share ~ 2% => μ ≈ 12 >= 11 -> Some(...)
141148 let total = 1_000_000_000_000_000_000u128 ;
142- let operator = total / 50 ; // 2 %
149+ let operator = total / 30 ; // 3.33 %
143150 let exp = operator_expected_bundles_in_epoch (
144151 600 ,
145152 operator,
146153 total,
147154 ( 1 , 1 ) ,
148- LN_1_OVER_TAU_1_PERCENT ,
155+ LN_1_OVER_TAU_0_5_PERCENT ,
149156 E_BASE ,
150157 )
151158 . expect ( "should be relevant" ) ;
152- // expected_bundles should be >= E_relevance (10 ) for τ=1 %
159+ // expected_bundles should be >= E_relevance (11 ) for τ=0.5 %
153160 assert ! ( exp. expected_bundles >= e_rel_1pct( ) ) ;
154161 // Chernoff threshold is a lower bound; must be <= expected_bundles
155162 assert ! ( exp. min_required_bundles <= exp. expected_bundles) ;
@@ -167,7 +174,7 @@ fn near_full_stake_behaves_sensibly() {
167174 operator,
168175 total,
169176 ( 1 , 1 ) ,
170- LN_1_OVER_TAU_1_PERCENT ,
177+ LN_1_OVER_TAU_0_5_PERCENT ,
171178 E_BASE ,
172179 )
173180 . expect ( "should be relevant" ) ;
@@ -187,9 +194,9 @@ fn monotonic_in_stake_when_relevant() {
187194 let tests = [
188195 ( total / 200 , false ) , // 0.5% -> not relevant (μ≈3)
189196 ( total / 100 , false ) , // 1% -> not relevant (μ≈6)
190- ( total / 59 , true ) , // ~1.695% -> μ_floor >= 10 -> relevant
191- ( total / 50 , true ) , // 2% -> μ≈12
192- ( total / 20 , true ) , // 5% -> μ≈30
197+ ( total / 59 , false ) , // ~1.695% -> μ_floor >= 10 -> not relevant
198+ ( total / 50 , true ) , // 2% -> μ≈12 relevant
199+ ( total / 20 , true ) , // 5% -> μ≈30 relevant
193200 ] ;
194201
195202 let mut last_exp_bundles = 0u64 ;
@@ -200,7 +207,7 @@ fn monotonic_in_stake_when_relevant() {
200207 stake,
201208 total,
202209 ( 1 , 1 ) ,
203- LN_1_OVER_TAU_1_PERCENT ,
210+ LN_1_OVER_TAU_0_5_PERCENT ,
204211 E_BASE ,
205212 ) ;
206213
@@ -255,16 +262,16 @@ fn chernoff_monotone_in_tau() {
255262fn chernoff_edges_p_zero_or_one ( ) {
256263 let s = 123u64 ;
257264 let r_zero =
258- chernoff_threshold_fp ( s, FixedU128 :: from_inner ( 0 ) , LN_1_OVER_TAU_1_PERCENT ) . unwrap ( ) ;
265+ chernoff_threshold_fp ( s, FixedU128 :: from_inner ( 0 ) , LN_1_OVER_TAU_0_5_PERCENT ) . unwrap ( ) ;
259266 assert_eq ! ( r_zero, 0 ) ;
260- let r_one = chernoff_threshold_fp ( s, FixedU128 :: one ( ) , LN_1_OVER_TAU_1_PERCENT ) . unwrap ( ) ;
267+ let r_one = chernoff_threshold_fp ( s, FixedU128 :: one ( ) , LN_1_OVER_TAU_0_5_PERCENT ) . unwrap ( ) ;
261268 assert_eq ! ( r_one, s) ;
262269}
263270
264271#[ test]
265272fn throughput_relevance_boundary ( ) {
266273 // Construct p so that floor(S * p) == E_relevance, should be relevant.
267- let e_rel = compute_e_relevance ( LN_1_OVER_TAU_1_PERCENT , E_BASE ) ; // 10
274+ let e_rel = compute_e_relevance ( LN_1_OVER_TAU_0_5_PERCENT , E_BASE ) ; // 11
268275 let s = 600u64 ;
269276 let p = FixedU128 :: saturating_from_rational ( e_rel as u128 , s as u128 ) ;
270277 // at boundary is not relevant
@@ -288,12 +295,12 @@ fn operator_expected_bundles_theta_one() {
288295 operator,
289296 total,
290297 ( 1 , 1 ) ,
291- LN_1_OVER_TAU_1_PERCENT ,
298+ LN_1_OVER_TAU_0_5_PERCENT ,
292299 E_BASE ,
293300 )
294301 . expect ( "relevant operator should produce expectations" ) ;
295302 // Expected bundles >= relevance floor
296- assert ! ( out. expected_bundles >= compute_e_relevance( LN_1_OVER_TAU_1_PERCENT , E_BASE ) ) ;
303+ assert ! ( out. expected_bundles >= compute_e_relevance( LN_1_OVER_TAU_0_5_PERCENT , E_BASE ) ) ;
297304 // r <= expected
298305 assert ! ( out. min_required_bundles <= out. expected_bundles) ;
299306 // monotone sanity: increasing S increases expected bundles
@@ -302,7 +309,7 @@ fn operator_expected_bundles_theta_one() {
302309 operator,
303310 total,
304311 ( 1 , 1 ) ,
305- LN_1_OVER_TAU_1_PERCENT ,
312+ LN_1_OVER_TAU_0_5_PERCENT ,
306313 E_BASE ,
307314 )
308315 . expect ( "still relevant at larger S" ) ;
@@ -315,13 +322,13 @@ fn operator_expected_bundles_theta_half_not_relevant() {
315322 let s = 600u64 ;
316323 let total = 1_000_000_000_000_000_000u128 ;
317324 let operator = total / 50 ; // 2% stake; μ would be ~12 at theta=1
318- // theta = 1/2 -> μ ≈ 6 -> below 10 => None
325+ // theta = 1/2 -> μ ≈ 6 -> below 11 => None
319326 let out = operator_expected_bundles_in_epoch (
320327 s,
321328 operator,
322329 total,
323330 ( 1 , 2 ) ,
324- LN_1_OVER_TAU_1_PERCENT ,
331+ LN_1_OVER_TAU_0_5_PERCENT ,
325332 E_BASE ,
326333 ) ;
327334 assert ! ( out. is_none( ) ) ;
@@ -338,11 +345,11 @@ fn operator_expected_bundles_handles_huge_total_and_small_op() {
338345 operator,
339346 total,
340347 ( 1 , 1 ) ,
341- LN_1_OVER_TAU_1_PERCENT ,
348+ LN_1_OVER_TAU_0_5_PERCENT ,
342349 E_BASE ,
343350 )
344351 . expect ( "should be relevant" ) ;
345- assert ! ( out. expected_bundles >= compute_e_relevance( LN_1_OVER_TAU_1_PERCENT , E_BASE ) ) ;
352+ assert ! ( out. expected_bundles >= compute_e_relevance( LN_1_OVER_TAU_0_5_PERCENT , E_BASE ) ) ;
346353 assert ! ( out. min_required_bundles <= out. expected_bundles) ;
347354}
348355
@@ -414,7 +421,7 @@ proptest! {
414421 let stake = operator_stake_strategy( total) . new_tree( & mut TestRunner :: default ( ) ) . unwrap( ) . current( ) ;
415422
416423 let out = operator_expected_bundles_in_epoch(
417- s, stake, total, theta, LN_1_OVER_TAU_1_PERCENT , E_BASE
424+ s, stake, total, theta, LN_1_OVER_TAU_0_5_PERCENT , E_BASE
418425 ) ;
419426
420427 let mu_floor = mu_floor_from_threshold( s, stake, total, theta) ;
@@ -460,10 +467,10 @@ proptest! {
460467 prop_assume!( stake_hi >= stake_lo) ;
461468
462469 let out_lo = operator_expected_bundles_in_epoch(
463- s, stake_lo, total, theta, LN_1_OVER_TAU_1_PERCENT , E_BASE
470+ s, stake_lo, total, theta, LN_1_OVER_TAU_0_5_PERCENT , E_BASE
464471 ) ;
465472 let out_hi = operator_expected_bundles_in_epoch(
466- s, stake_hi, total, theta, LN_1_OVER_TAU_1_PERCENT , E_BASE
473+ s, stake_hi, total, theta, LN_1_OVER_TAU_0_5_PERCENT , E_BASE
467474 ) ;
468475
469476 if let Some ( exp_lo) = out_lo {
@@ -495,10 +502,10 @@ proptest! {
495502 let ( s_min, s_max) = if s1 <= s2 { ( s1, s2) } else { ( s2, s1) } ;
496503
497504 let out_min = operator_expected_bundles_in_epoch(
498- s_min, stake, total, theta, LN_1_OVER_TAU_1_PERCENT , E_BASE
505+ s_min, stake, total, theta, LN_1_OVER_TAU_0_5_PERCENT , E_BASE
499506 ) ;
500507 let out_max = operator_expected_bundles_in_epoch(
501- s_max, stake, total, theta, LN_1_OVER_TAU_1_PERCENT , E_BASE
508+ s_max, stake, total, theta, LN_1_OVER_TAU_0_5_PERCENT , E_BASE
502509 ) ;
503510
504511 if let Some ( exp_min) = out_min {
@@ -533,10 +540,10 @@ proptest! {
533540 let theta_hi = ( num_hi, den) ;
534541
535542 let out_lo = operator_expected_bundles_in_epoch(
536- s, stake, total, theta_lo, LN_1_OVER_TAU_1_PERCENT , E_BASE
543+ s, stake, total, theta_lo, LN_1_OVER_TAU_0_5_PERCENT , E_BASE
537544 ) ;
538545 let out_hi = operator_expected_bundles_in_epoch(
539- s, stake, total, theta_hi, LN_1_OVER_TAU_1_PERCENT , E_BASE
546+ s, stake, total, theta_hi, LN_1_OVER_TAU_0_5_PERCENT , E_BASE
540547 ) ;
541548
542549 if let Some ( exp_lo) = out_lo {
0 commit comments