use std::collections::BTreeMap;
use neuron_id::NeuronId;
use tru::Fx;
#[derive(Clone, Copy, Debug)]
pub struct ServedQuery {
pub servicer: NeuronId,
pub fee: Fx,
}
pub fn service_fee_shares(served: &[ServedQuery], gamma: Fx, beta: Fx) -> Vec<(NeuronId, Fx)> {
let rate = clip_unit(gamma) * (Fx::ONE - clip_unit(beta));
let mut totals: BTreeMap<NeuronId, Fx> = BTreeMap::new();
let mut order: Vec<NeuronId> = Vec::new();
for q in served {
let share = rate * q.fee;
totals
.entry(q.servicer)
.and_modify(|s| *s = *s + share)
.or_insert_with(|| {
order.push(q.servicer);
share
});
}
order.into_iter().map(|n| (n, totals[&n])).collect()
}
fn clip_unit(x: Fx) -> Fx {
if x < Fx::ZERO {
Fx::ZERO
} else if x > Fx::ONE {
Fx::ONE
} else {
x
}
}
#[cfg(test)]
mod tests {
use super::*;
fn n(b: u8) -> NeuronId {
let mut x = [0u8; 32];
x[0] = b;
x
}
fn q(servicer: NeuronId, fee: i64) -> ServedQuery {
ServedQuery {
servicer,
fee: Fx::from_int(fee),
}
}
#[test]
fn single_query_pays_gamma_times_one_minus_beta() {
let gamma = Fx::from_ratio(1, 2);
let beta = Fx::from_ratio(1, 5);
let shares = service_fee_shares(&[q(n(1), 10)], gamma, beta);
assert_eq!(shares.len(), 1);
assert_eq!(shares[0].0, n(1));
assert!((shares[0].1.to_f64() - 4.0).abs() < 1e-6);
}
#[test]
fn multiple_queries_from_one_servicer_sum() {
let gamma = Fx::ONE;
let beta = Fx::ZERO;
let shares = service_fee_shares(&[q(n(1), 3), q(n(1), 5)], gamma, beta);
assert_eq!(shares, vec![(n(1), Fx::from_int(8))]);
}
#[test]
fn distinct_servicers_stay_separate() {
let gamma = Fx::ONE;
let beta = Fx::ZERO;
let shares = service_fee_shares(&[q(n(1), 3), q(n(2), 5)], gamma, beta);
assert_eq!(shares, vec![(n(1), Fx::from_int(3)), (n(2), Fx::from_int(5))]);
}
#[test]
fn gamma_zero_pays_nothing() {
let shares = service_fee_shares(&[q(n(1), 10)], Fx::ZERO, Fx::ZERO);
assert_eq!(shares, vec![(n(1), Fx::ZERO)]);
}
#[test]
fn beta_one_burns_the_whole_fee() {
let shares = service_fee_shares(&[q(n(1), 10)], Fx::ONE, Fx::ONE);
assert_eq!(shares, vec![(n(1), Fx::ZERO)]);
}
#[test]
fn beta_above_one_clips_to_zero_payout_not_negative() {
let shares = service_fee_shares(&[q(n(1), 10)], Fx::ONE, Fx::from_int(2));
assert_eq!(shares, vec![(n(1), Fx::ZERO)]);
}
#[test]
fn gamma_above_one_clips_to_one() {
let a = service_fee_shares(&[q(n(1), 10)], Fx::from_int(2), Fx::ZERO);
let b = service_fee_shares(&[q(n(1), 10)], Fx::ONE, Fx::ZERO);
assert_eq!(a, b);
}
#[test]
fn negative_beta_clips_to_zero_burn() {
let a = service_fee_shares(&[q(n(1), 10)], Fx::ONE, Fx::ZERO - Fx::from_int(1));
let b = service_fee_shares(&[q(n(1), 10)], Fx::ONE, Fx::ZERO);
assert_eq!(a, b);
}
#[test]
fn empty_served_list_returns_empty() {
assert!(service_fee_shares(&[], Fx::ONE, Fx::ZERO).is_empty());
}
#[test]
fn sum_of_shares_never_exceeds_sum_of_fees() {
let gamma = Fx::from_ratio(7, 10);
let beta = Fx::from_ratio(3, 10);
let served = [q(n(1), 4), q(n(2), 9), q(n(1), 2)];
let total_fee: f64 = served.iter().map(|q| q.fee.to_f64()).sum();
let shares = service_fee_shares(&served, gamma, beta);
let total_share: f64 = shares.iter().map(|(_, s)| s.to_f64()).sum();
assert!(total_share <= total_fee + 1e-9);
}
}