Skip to content

Commit 31578b0

Browse files
authored
Merge pull request #2160 from opentensor/feat/flow-emissions
Feat/flow emissions
2 parents f190dca + 3a475f3 commit 31578b0

File tree

12 files changed

+1302
-79
lines changed

12 files changed

+1302
-79
lines changed

pallets/admin-utils/src/lib.rs

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub mod pallet {
3131
utils::rate_limiting::{Hyperparameter, TransactionType},
3232
};
3333
use sp_runtime::BoundedVec;
34-
use substrate_fixed::types::I96F32;
34+
use substrate_fixed::types::{I64F64, I96F32, U64F64};
3535
use subtensor_runtime_common::{MechId, NetUid, TaoCurrency};
3636

3737
/// The main data structure of the module.
@@ -115,6 +115,8 @@ pub mod pallet {
115115
MaxAllowedUidsLessThanMinAllowedUids,
116116
/// The maximum allowed UIDs must be less than the default maximum allowed UIDs.
117117
MaxAllowedUidsGreaterThanDefaultMaxAllowedUids,
118+
/// Bad parameter value
119+
InvalidValue,
118120
}
119121
/// Enum for specifying the type of precompile operation.
120122
#[derive(
@@ -2102,6 +2104,71 @@ pub mod pallet {
21022104
);
21032105
Ok(())
21042106
}
2107+
2108+
/// Sets TAO flow cutoff value (A)
2109+
#[pallet::call_index(81)]
2110+
#[pallet::weight((
2111+
Weight::from_parts(7_343_000, 0)
2112+
.saturating_add(<T as frame_system::Config>::DbWeight::get().reads(0))
2113+
.saturating_add(<T as frame_system::Config>::DbWeight::get().writes(1)),
2114+
DispatchClass::Operational,
2115+
Pays::Yes
2116+
))]
2117+
pub fn sudo_set_tao_flow_cutoff(
2118+
origin: OriginFor<T>,
2119+
flow_cutoff: I64F64,
2120+
) -> DispatchResult {
2121+
ensure_root(origin)?;
2122+
pallet_subtensor::Pallet::<T>::set_tao_flow_cutoff(flow_cutoff);
2123+
log::debug!("set_tao_flow_cutoff( {flow_cutoff:?} ) ");
2124+
Ok(())
2125+
}
2126+
2127+
/// Sets TAO flow normalization exponent (p)
2128+
#[pallet::call_index(82)]
2129+
#[pallet::weight((
2130+
Weight::from_parts(7_343_000, 0)
2131+
.saturating_add(<T as frame_system::Config>::DbWeight::get().reads(0))
2132+
.saturating_add(<T as frame_system::Config>::DbWeight::get().writes(1)),
2133+
DispatchClass::Operational,
2134+
Pays::Yes
2135+
))]
2136+
pub fn sudo_set_tao_flow_normalization_exponent(
2137+
origin: OriginFor<T>,
2138+
exponent: U64F64,
2139+
) -> DispatchResult {
2140+
ensure_root(origin)?;
2141+
2142+
let one = U64F64::saturating_from_num(1);
2143+
let two = U64F64::saturating_from_num(2);
2144+
ensure!(
2145+
(one <= exponent) && (exponent <= two),
2146+
Error::<T>::InvalidValue
2147+
);
2148+
2149+
pallet_subtensor::Pallet::<T>::set_tao_flow_normalization_exponent(exponent);
2150+
log::debug!("set_tao_flow_normalization_exponent( {exponent:?} ) ");
2151+
Ok(())
2152+
}
2153+
2154+
/// Sets TAO flow smoothing factor (alpha)
2155+
#[pallet::call_index(83)]
2156+
#[pallet::weight((
2157+
Weight::from_parts(7_343_000, 0)
2158+
.saturating_add(<T as frame_system::Config>::DbWeight::get().reads(0))
2159+
.saturating_add(<T as frame_system::Config>::DbWeight::get().writes(1)),
2160+
DispatchClass::Operational,
2161+
Pays::Yes
2162+
))]
2163+
pub fn sudo_set_tao_flow_smoothing_factor(
2164+
origin: OriginFor<T>,
2165+
smoothing_factor: u64,
2166+
) -> DispatchResult {
2167+
ensure_root(origin)?;
2168+
pallet_subtensor::Pallet::<T>::set_tao_flow_smoothing_factor(smoothing_factor);
2169+
log::debug!("set_tao_flow_smoothing_factor( {smoothing_factor:?} ) ");
2170+
Ok(())
2171+
}
21052172
}
21062173
}
21072174

Lines changed: 259 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use super::*;
22
use crate::alloc::borrow::ToOwned;
33
use 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

68
impl<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

Comments
 (0)