11use super :: * ;
22use crate :: alloc:: borrow:: ToOwned ;
33use alloc:: collections:: BTreeMap ;
4- use substrate_fixed:: types:: U96F32 ;
4+ use safe_math:: FixedExt ;
5+ use substrate_fixed:: transcendental:: { exp, ln} ;
6+ use substrate_fixed:: types:: { I32F32 , I64F64 , U64F64 , U96F32 } ;
57
68impl < T : Config > Pallet < T > {
79 pub fn get_subnet_block_emissions (
@@ -17,29 +19,275 @@ impl<T: Config> Pallet<T> {
1719 . collect ( ) ;
1820 log:: debug!( "Subnets to emit to: {subnets_to_emit_to:?}" ) ;
1921
22+ // Get subnet TAO emissions.
23+ let shares = Self :: get_shares ( & subnets_to_emit_to) ;
24+ log:: debug!( "Subnet emission shares = {shares:?}" ) ;
25+
26+ shares
27+ . into_iter ( )
28+ . map ( |( netuid, share) | {
29+ let emission = U64F64 :: saturating_from_num ( block_emission) . saturating_mul ( share) ;
30+ ( netuid, U96F32 :: saturating_from_num ( emission) )
31+ } )
32+ . collect :: < BTreeMap < NetUid , U96F32 > > ( )
33+ }
34+
35+ pub fn record_tao_inflow ( netuid : NetUid , tao : TaoCurrency ) {
36+ SubnetTaoFlow :: < T > :: mutate ( netuid, |flow| {
37+ * flow = flow. saturating_add ( u64:: from ( tao) as i64 ) ;
38+ } ) ;
39+ }
40+
41+ pub fn record_tao_outflow ( netuid : NetUid , tao : TaoCurrency ) {
42+ SubnetTaoFlow :: < T > :: mutate ( netuid, |flow| {
43+ * flow = flow. saturating_sub ( u64:: from ( tao) as i64 )
44+ } ) ;
45+ }
46+
47+ pub fn reset_tao_outflow ( netuid : NetUid ) {
48+ SubnetTaoFlow :: < T > :: remove ( netuid) ;
49+ }
50+
51+ // Update SubnetEmaTaoFlow if needed and return its value for
52+ // the current block
53+ fn get_ema_flow ( netuid : NetUid ) -> I64F64 {
54+ let current_block: u64 = Self :: get_current_block_as_u64 ( ) ;
55+
56+ // Calculate net ema flow for the next block
57+ let block_flow = I64F64 :: saturating_from_num ( SubnetTaoFlow :: < T > :: get ( netuid) ) ;
58+ if let Some ( ( last_block, last_block_ema) ) = SubnetEmaTaoFlow :: < T > :: get ( netuid) {
59+ // EMA flow already initialized
60+ if last_block != current_block {
61+ let flow_alpha = I64F64 :: saturating_from_num ( FlowEmaSmoothingFactor :: < T > :: get ( ) )
62+ . safe_div ( I64F64 :: saturating_from_num ( u16:: MAX ) ) ;
63+ let one = I64F64 :: saturating_from_num ( 1 ) ;
64+ let ema_flow = ( one. saturating_sub ( flow_alpha) )
65+ . saturating_mul ( last_block_ema)
66+ . saturating_add ( flow_alpha. saturating_mul ( block_flow) ) ;
67+ SubnetEmaTaoFlow :: < T > :: insert ( netuid, ( current_block, ema_flow) ) ;
68+
69+ // Drop the accumulated flow in the last block
70+ Self :: reset_tao_outflow ( netuid) ;
71+ ema_flow
72+ } else {
73+ last_block_ema
74+ }
75+ } else {
76+ // Initialize EMA flow, set S(current_block) = 0
77+ let ema_flow = I64F64 :: saturating_from_num ( 0 ) ;
78+ SubnetEmaTaoFlow :: < T > :: insert ( netuid, ( current_block, ema_flow) ) ;
79+ ema_flow
80+ }
81+ }
82+
83+ // Either the minimal EMA flow L = min{Si}, or an artificial
84+ // cut off at some higher value A (TaoFlowCutoff)
85+ // L = max {A, min{min{S[i], 0}}}
86+ fn get_lower_limit ( ema_flows : & BTreeMap < NetUid , I64F64 > ) -> I64F64 {
87+ let zero = I64F64 :: saturating_from_num ( 0 ) ;
88+ let min_flow = ema_flows
89+ . values ( )
90+ . map ( |flow| flow. min ( & zero) )
91+ . min ( )
92+ . unwrap_or ( & zero) ;
93+ let flow_cutoff = TaoFlowCutoff :: < T > :: get ( ) ;
94+ flow_cutoff. max ( * min_flow)
95+ }
96+
97+ // Estimate the upper value of pow with hardcoded p = 2
98+ fn pow_estimate ( val : U64F64 ) -> U64F64 {
99+ val. saturating_mul ( val)
100+ }
101+
102+ fn safe_pow ( val : U64F64 , p : U64F64 ) -> U64F64 {
103+ // If val is too low so that ln(val) doesn't fit I32F32::MIN,
104+ // return 0 from the function
105+ let zero = U64F64 :: saturating_from_num ( 0 ) ;
106+ let i32f32_max = I32F32 :: saturating_from_num ( i32:: MAX ) ;
107+ if let Ok ( val_ln) = ln ( I32F32 :: saturating_from_num ( val) ) {
108+ // If exp doesn't fit, do the best we can - max out on I32F32::MAX
109+ U64F64 :: saturating_from_num ( I32F32 :: saturating_from_num (
110+ exp ( I32F32 :: saturating_from_num ( p) . saturating_mul ( val_ln) ) . unwrap_or ( i32f32_max) ,
111+ ) )
112+ } else {
113+ zero
114+ }
115+ }
116+
117+ fn inplace_scale ( offset_flows : & mut BTreeMap < NetUid , U64F64 > ) {
118+ let zero = U64F64 :: saturating_from_num ( 0 ) ;
119+ let flow_max = offset_flows. values ( ) . copied ( ) . max ( ) . unwrap_or ( zero) ;
120+
121+ // Calculate scale factor so that max becomes 1.0
122+ let flow_factor = U64F64 :: saturating_from_num ( 1 ) . safe_div ( flow_max) ;
123+
124+ // Upscale/downscale in-place
125+ for flow in offset_flows. values_mut ( ) {
126+ * flow = flow_factor. saturating_mul ( * flow) ;
127+ }
128+ }
129+
130+ pub ( crate ) fn inplace_pow_normalize ( offset_flows : & mut BTreeMap < NetUid , U64F64 > , p : U64F64 ) {
131+ // Scale offset flows so that that are no overflows and underflows when we use safe_pow:
132+ // flow_factor * subnet_count * (flow_max ^ p) <= I32F32::MAX
133+ let zero = U64F64 :: saturating_from_num ( 0 ) ;
134+ let subnet_count = offset_flows. len ( ) ;
135+
136+ // Pre-scale to max 1.0
137+ Self :: inplace_scale ( offset_flows) ;
138+
139+ // Scale to maximize precision
140+ let flow_max = offset_flows. values ( ) . copied ( ) . max ( ) . unwrap_or ( zero) ;
141+ log:: debug!( "Offset flow max: {flow_max:?}" ) ;
142+ let flow_max_pow_est = Self :: pow_estimate ( flow_max) ;
143+ log:: debug!( "flow_max_pow_est: {flow_max_pow_est:?}" ) ;
144+
145+ let max_times_count =
146+ U64F64 :: saturating_from_num ( subnet_count) . saturating_mul ( flow_max_pow_est) ;
147+ let i32f32_max = U64F64 :: saturating_from_num ( i32:: MAX ) ;
148+ let precision_min = i32f32_max. safe_div ( U64F64 :: saturating_from_num ( u64:: MAX ) ) ;
149+
150+ // If max_times_count < precision_min, all flow values are too low to fit I32F32.
151+ if max_times_count >= precision_min {
152+ let epsilon =
153+ U64F64 :: saturating_from_num ( 1 ) . safe_div ( U64F64 :: saturating_from_num ( 1_000 ) ) ;
154+ let flow_factor = i32f32_max
155+ . safe_div ( max_times_count)
156+ . checked_sqrt ( epsilon)
157+ . unwrap_or ( zero) ;
158+
159+ // Calculate sum
160+ let sum = offset_flows
161+ . clone ( )
162+ . into_values ( )
163+ . map ( |flow| flow_factor. saturating_mul ( flow) )
164+ . map ( |scaled_flow| Self :: safe_pow ( scaled_flow, p) )
165+ . sum ( ) ;
166+ log:: debug!( "Scaled offset flow sum: {sum:?}" ) ;
167+
168+ // Normalize in-place
169+ for flow in offset_flows. values_mut ( ) {
170+ let scaled_flow = flow_factor. saturating_mul ( * flow) ;
171+ * flow = Self :: safe_pow ( scaled_flow, p) . safe_div ( sum) ;
172+ }
173+ }
174+ }
175+
176+ // Implementation of shares that uses TAO flow
177+ fn get_shares_flow ( subnets_to_emit_to : & [ NetUid ] ) -> BTreeMap < NetUid , U64F64 > {
178+ // Get raw flows
179+ let ema_flows = subnets_to_emit_to
180+ . iter ( )
181+ . map ( |netuid| ( * netuid, Self :: get_ema_flow ( * netuid) ) )
182+ . collect ( ) ;
183+ log:: debug!( "EMA flows: {ema_flows:?}" ) ;
184+
185+ // Clip the EMA flow with lower limit L
186+ // z[i] = max{S[i] − L, 0}
187+ let lower_limit = Self :: get_lower_limit ( & ema_flows) ;
188+ log:: debug!( "Lower flow limit: {lower_limit:?}" ) ;
189+ let mut offset_flows = ema_flows
190+ . iter ( )
191+ . map ( |( netuid, flow) | {
192+ (
193+ * netuid,
194+ if * flow > lower_limit {
195+ U64F64 :: saturating_from_num ( flow. saturating_sub ( lower_limit) )
196+ } else {
197+ U64F64 :: saturating_from_num ( 0 )
198+ } ,
199+ )
200+ } )
201+ . collect :: < BTreeMap < NetUid , U64F64 > > ( ) ;
202+
203+ // Normalize the set {z[i]}, using an exponent parameter (p ≥ 1)
204+ let p = FlowNormExponent :: < T > :: get ( ) ;
205+ Self :: inplace_pow_normalize ( & mut offset_flows, p) ;
206+ offset_flows
207+ }
208+
209+ // DEPRECATED: Implementation of shares that uses EMA prices will be gradually deprecated
210+ fn get_shares_price_ema ( subnets_to_emit_to : & [ NetUid ] ) -> BTreeMap < NetUid , U64F64 > {
20211 // Get sum of alpha moving prices
21212 let total_moving_prices = subnets_to_emit_to
22213 . iter ( )
23- . map ( |netuid| Self :: get_moving_alpha_price ( * netuid) )
24- . fold ( U96F32 :: saturating_from_num ( 0.0 ) , |acc, ema| {
214+ . map ( |netuid| U64F64 :: saturating_from_num ( Self :: get_moving_alpha_price ( * netuid) ) )
215+ . fold ( U64F64 :: saturating_from_num ( 0.0 ) , |acc, ema| {
25216 acc. saturating_add ( ema)
26217 } ) ;
27218 log:: debug!( "total_moving_prices: {total_moving_prices:?}" ) ;
28219
29- // Get subnet TAO emissions .
220+ // Calculate shares .
30221 subnets_to_emit_to
31- . into_iter ( )
222+ . iter ( )
32223 . map ( |netuid| {
33- let moving_price = Self :: get_moving_alpha_price ( netuid) ;
224+ let moving_price =
225+ U64F64 :: saturating_from_num ( Self :: get_moving_alpha_price ( * netuid) ) ;
34226 log:: debug!( "moving_price_i: {moving_price:?}" ) ;
35227
36- let share = block_emission
37- . saturating_mul ( moving_price)
228+ let share = moving_price
38229 . checked_div ( total_moving_prices)
39- . unwrap_or ( U96F32 :: from_num ( 0 ) ) ;
230+ . unwrap_or ( U64F64 :: saturating_from_num ( 0 ) ) ;
40231
41- ( netuid, share)
232+ ( * netuid, share)
42233 } )
43- . collect :: < BTreeMap < NetUid , U96F32 > > ( )
234+ . collect :: < BTreeMap < NetUid , U64F64 > > ( )
235+ }
236+
237+ // Combines ema price method and tao flow method linearly over FlowHalfLife blocks
238+ pub ( crate ) fn get_shares ( subnets_to_emit_to : & [ NetUid ] ) -> BTreeMap < NetUid , U64F64 > {
239+ let current_block: u64 = Self :: get_current_block_as_u64 ( ) ;
240+
241+ // Weight of tao flow method
242+ let period = FlowHalfLife :: < T > :: get ( ) ;
243+ let one = U64F64 :: saturating_from_num ( 1 ) ;
244+ let zero = U64F64 :: saturating_from_num ( 0 ) ;
245+ let tao_flow_weight = if let Some ( start_block) = FlowFirstBlock :: < T > :: get ( ) {
246+ if ( current_block > start_block) && ( current_block < start_block. saturating_add ( period) )
247+ {
248+ // Combination period in progress
249+ let start_fixed = U64F64 :: saturating_from_num ( start_block) ;
250+ let current_fixed = U64F64 :: saturating_from_num ( current_block) ;
251+ let period_fixed = U64F64 :: saturating_from_num ( period) ;
252+ current_fixed
253+ . saturating_sub ( start_fixed)
254+ . safe_div ( period_fixed)
255+ } else if current_block >= start_block. saturating_add ( period) {
256+ // Over combination period
257+ one
258+ } else {
259+ // Not yet in combination period
260+ zero
261+ }
262+ } else {
263+ zero
264+ } ;
265+
266+ // Get shares for each method as needed
267+ let shares_flow = if tao_flow_weight > zero {
268+ Self :: get_shares_flow ( subnets_to_emit_to)
269+ } else {
270+ BTreeMap :: new ( )
271+ } ;
272+
273+ let shares_prices = if tao_flow_weight < one {
274+ Self :: get_shares_price_ema ( subnets_to_emit_to)
275+ } else {
276+ BTreeMap :: new ( )
277+ } ;
278+
279+ // Combine
280+ let mut shares_combined = BTreeMap :: new ( ) ;
281+ for netuid in subnets_to_emit_to. iter ( ) {
282+ let share_flow = shares_flow. get ( netuid) . unwrap_or ( & zero) ;
283+ let share_prices = shares_prices. get ( netuid) . unwrap_or ( & zero) ;
284+ shares_combined. insert (
285+ * netuid,
286+ share_flow. saturating_mul ( tao_flow_weight) . saturating_add (
287+ share_prices. saturating_mul ( one. saturating_sub ( tao_flow_weight) ) ,
288+ ) ,
289+ ) ;
290+ }
291+ shares_combined
44292 }
45293}
0 commit comments