Skip to content

Commit

Permalink
[Rikiddo] Implement Rikiddo trait and tests - part 1 (zeitgeistpm#218)
Browse files Browse the repository at this point in the history
* Chang trait generics to associated types

* Define RikkidoConfig and RikiddoSigmoidMV types

* Add missing file

* Add dummy Rikiddo implementations

* Prepare FeeSigmoid for incorporation of min_revenue

* Add min_revenue and type conversion to fee calculation

* Fix signed to unsigned conversion

* Implement RikiddoMV trait for RikiddoSigmoidMV

* Partially implement tests for RikiddoMV trait implementation

* Implement tests for RikiddoMV trait implementation

* Implement fee retrieval within RikiddoSigmoidMV

* Implement more tests for Rikiddo's get_fee()

* Complete Rikiddo's tests for get_fee()

* Extend RikiddoConfig and implement convert function

* Complete type conversion functions

* Start implementation of type conversion tests

* Implement fixed point type conversion tests

* Add min_revenue check to sigmoid fee test

* Partially implement Rikiddo's cost function

* Continue Rikiddos cost implementation

* Implement cost strategy selection and default strategy

* Implement Rikiddo's cost function

* Fix fee calculation: Add initial_fee to sigmoid result

* Implement Rikiddo default() and new() test

* Implement tests for default ln_sum_exp strategy

* Complete ln_sum_exp tests for both strategies

* Partially implement overflow tests for Rikiddo's cost function

* Partially implement overflow tests for Rikiddo's cost function

* Implement remaining Rikiddo cost tests

* Implement tx count per period estimation

* Simplify SigmoidFee implementation

* Improve code style
  • Loading branch information
sea212 authored and c410-f3r committed Sep 14, 2021
1 parent 20c2f91 commit 3aed86c
Show file tree
Hide file tree
Showing 10 changed files with 1,168 additions and 136 deletions.
17 changes: 9 additions & 8 deletions zrml/rikiddo/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,6 @@ pub const EMA_LONG: Timespan = Timespan::Hours(6);
pub const SMOOTHING: FixedU32<U24> = <FixedU32<U24>>::from_bits(0x0200_0000);

// --- Default configuration for FeeSigmoidConfig struct ---
/// Initial fee f
/// 0.005
pub const INITIAL_FEE: FixedU32<U32> = <FixedU32<U32>>::from_bits(0x0147_AE14);

/// Minimal revenue w (proportion of initial fee f)
/// f * β = 0.005 * 0.7 = 0.0035
pub const MINIMAL_REVENUE: FixedU32<U32> = <FixedU32<U32>>::from_bits(0x00E5_6042);

/// m value
/// 0.01
pub const M: FixedI32<U24> = <FixedI32<U24>>::from_bits(0x0002_8F5C);
Expand All @@ -37,3 +29,12 @@ pub const P: FixedI32<U24> = <FixedI32<U24>>::from_bits(0x0200_0000);
/// n value
/// 0.0
pub const N: FixedI32<U24> = <FixedI32<U24>>::from_bits(0x0000_0000);

// --- Default configuration for RikiddoConfig struct ---
/// Initial fee f
/// 0.005
pub const INITIAL_FEE: FixedU32<U32> = <FixedU32<U32>>::from_bits(0x0147_AE14);

/// Minimal revenue w (proportion of initial fee f)
/// f * β = 0.005 * 0.7 = 0.0035
pub const MINIMAL_REVENUE: FixedU32<U32> = <FixedU32<U32>>::from_bits(0x00E5_6042);
60 changes: 57 additions & 3 deletions zrml/rikiddo/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,66 @@
#![cfg(test)]

use crate::mock::*;
use frame_support::assert_err;
use substrate_fixed::{
types::extra::{U1, U2, U3, U7, U8},
FixedI8, FixedU8,
};

use crate::{
mock::*,
types::{convert_to_signed, convert_to_unsigned},
};

mod ema_market_volume;
mod rikiddo_sigmoid_mv;
mod sigmoid_fee;

fn max_allowed_error(fixed_point_bits: u8) -> f64 {
1.0 / (1u128 << (fixed_point_bits - 1)) as f64
fn max_allowed_error(fractional_bits: u8) -> f64 {
1.0 / (1u128 << (fractional_bits - 1)) as f64
}

#[test]
fn convert_signed_to_unsigned_fails() {
let num = <FixedI8<U8>>::from_num(-0.5f32);
assert_err!(
convert_to_unsigned::<FixedI8<U8>, FixedU8<U8>>(num),
"Cannot convert negative signed number into unsigned number"
);
}

#[test]
fn convert_number_does_not_fit_in_destination_type() {
let num = <FixedU8<U7>>::from_num(1);
assert_err!(
convert_to_signed::<FixedU8<U7>, FixedI8<U7>>(num),
"Fixed point conversion failed: FROM type does not fit in TO type"
);
}

#[test]
fn convert_unsigned_to_signed_returns_correct_result() -> Result<(), &'static str> {
// lossless - exact fit
let num1 = <FixedU8<U2>>::from_num(4.75);
let num1_converted: FixedI8<U3> = convert_to_signed(num1)?;
assert_eq!(num1_converted, num1);
// lossy - loses fractional bits
let num2 = <FixedU8<U2>>::from_num(4.75);
let num2_converted: FixedI8<U1> = convert_to_signed(num2)?;
assert_eq!(num2_converted.to_num::<f32>(), 4.5f32);
Ok(())
}

#[test]
fn convert_signed_to_unsigned_returns_correct_result() -> Result<(), &'static str> {
// lossless - exact fit
let num1 = <FixedI8<U2>>::from_num(4.75);
let num1_converted: FixedU8<U3> = convert_to_unsigned(num1)?;
assert_eq!(num1_converted, num1);
// lossy - loses fractional bits
let num2 = <FixedI8<U2>>::from_num(4.75);
let num2_converted: FixedU8<U1> = convert_to_unsigned(num2)?;
assert_eq!(num2_converted.to_num::<f32>(), 4.5f32);
Ok(())
}

#[test]
Expand Down
108 changes: 80 additions & 28 deletions zrml/rikiddo/src/tests/ema_market_volume.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
use super::max_allowed_error;
use crate::{
traits::MarketAverage,
types::{EmaMarketVolume, EmaVolumeConfig, MarketVolumeState, Timespan, TimestampedVolume},
types::{EmaConfig, EmaMarketVolume, MarketVolumeState, Timespan, TimestampedVolume},
};
use frame_support::assert_err;
use substrate_fixed::{types::extra::U64, FixedU128};
use substrate_fixed::{
types::extra::{U64, U96},
FixedU128,
};

fn ema_create_test_struct(period: u32, smoothing: f64) -> EmaMarketVolume<FixedU128<U64>> {
let emv_cfg = EmaVolumeConfig::<FixedU128<U64>> {
pub(super) fn ema_create_test_struct(
period: u32,
smoothing: f64,
) -> EmaMarketVolume<FixedU128<U64>> {
let emv_cfg = EmaConfig::<FixedU128<U64>> {
ema_period: Timespan::Seconds(period),
ema_period_estimate_after: None,
smoothing: <FixedU128<U64>>::from_num(smoothing),
};

Expand All @@ -27,33 +34,33 @@ fn ema_calculate(old_ema: f64, multiplier: f64, volume: f64) -> f64 {
fn ema_state_transitions_work() {
let mut emv = ema_create_test_struct(2, 2.0);
assert_eq!(emv.state(), &MarketVolumeState::Uninitialized);
let _ = emv.update(TimestampedVolume { timestamp: 0, volume: 1u32.into() }).unwrap();
let _ = emv.update(&TimestampedVolume { timestamp: 0, volume: 1u32.into() }).unwrap();
assert_eq!(emv.state(), &MarketVolumeState::DataCollectionStarted);
let _ = emv.update(TimestampedVolume { timestamp: 3, volume: 1u32.into() }).unwrap();
let _ = emv.update(&TimestampedVolume { timestamp: 3, volume: 1u32.into() }).unwrap();
assert_eq!(emv.state(), &MarketVolumeState::DataCollected);
}

#[test]
fn ema_returns_none_before_final_state() {
let mut emv = ema_create_test_struct(2, 2.0);
assert_eq!(emv.get(), None);
let _ = emv.update(TimestampedVolume { timestamp: 0, volume: 1u32.into() }).unwrap();
let _ = emv.update(&TimestampedVolume { timestamp: 0, volume: 1u32.into() }).unwrap();
assert_eq!(emv.get(), None);
let _ = emv.update(TimestampedVolume { timestamp: 3, volume: 1u32.into() }).unwrap();
let _ = emv.update(&TimestampedVolume { timestamp: 3, volume: 1u32.into() }).unwrap();
assert_ne!(emv.get(), None);
}

#[test]
fn ema_returns_correct_ema() {
let mut emv = ema_create_test_struct(2, 2.0);
let _ = emv.update(TimestampedVolume { timestamp: 0, volume: 2u32.into() }).unwrap();
let _ = emv.update(TimestampedVolume { timestamp: 1, volume: 6u32.into() }).unwrap();
let _ = emv.update(TimestampedVolume { timestamp: 2, volume: 4u32.into() }).unwrap();
let _ = emv.update(&TimestampedVolume { timestamp: 0, volume: 2u32.into() }).unwrap();
let _ = emv.update(&TimestampedVolume { timestamp: 1, volume: 6u32.into() }).unwrap();
let _ = emv.update(&TimestampedVolume { timestamp: 2, volume: 4u32.into() }).unwrap();
// Currently it's a sma
let ema = emv.ema.to_num::<f64>();
assert_eq!(ema, (2.0 + 6.0 + 4.0) / 3.0);

let _ = emv.update(TimestampedVolume { timestamp: 3, volume: 20u32.into() }).unwrap();
let _ = emv.update(&TimestampedVolume { timestamp: 3, volume: 20u32.into() }).unwrap();
// Now it's an ema
let ema_fixed_f64: f64 = emv.ema.to_num();
let multiplier = ema_get_multiplier(3, emv.config.smoothing.to_num());
Expand Down Expand Up @@ -81,11 +88,46 @@ fn ema_returns_correct_ema() {
);
}

#[test]
fn ema_returns_correct_ema_after_estimated_period() {
let mut emv = EmaMarketVolume::new(EmaConfig::<FixedU128<U64>> {
ema_period: Timespan::Seconds(8),
ema_period_estimate_after: Some(Timespan::Seconds(2)),
smoothing: <FixedU128<U64>>::from_num(2.0),
});
let _ = emv.update(&TimestampedVolume { timestamp: 0, volume: 2u32.into() }).unwrap();
let _ = emv.update(&TimestampedVolume { timestamp: 1, volume: 6u32.into() }).unwrap();
let _ = emv.update(&TimestampedVolume { timestamp: 2, volume: 4u32.into() }).unwrap();
// Currently it's a sma
let ema = emv.ema.to_num::<f64>();
assert_eq!(ema, (2.0 + 6.0 + 4.0) / 3.0);

let _ = emv.update(&TimestampedVolume { timestamp: 3, volume: 20u32.into() }).unwrap();
// Now it's an ema (using estimated transaction count per period)
let ema_fixed_f64: f64 = emv.ema.to_num();
let extrapolation_factor = emv.config.ema_period.to_seconds() as f64
/ emv.config.ema_period_estimate_after.unwrap().to_seconds() as f64;
let multiplier = ema_get_multiplier(
(3f64 * extrapolation_factor).ceil() as u64,
emv.config.smoothing.to_num(),
);
let ema_f64 = ema_calculate(ema, multiplier, 20f64);
let difference_abs = (ema_fixed_f64 - ema_f64).abs();
assert!(
difference_abs <= max_allowed_error(64),
"\nFixed result: {}\nFloat result: {}\nDifference: {}\nMax_Allowed_Difference: {}",
ema_fixed_f64,
ema_f64,
difference_abs,
max_allowed_error(64)
);
}

#[test]
fn ema_clear_ereases_data() {
let mut emv = ema_create_test_struct(2, 2.0);
let _ = emv.update(TimestampedVolume { timestamp: 0, volume: 2u32.into() }).unwrap();
let _ = emv.update(TimestampedVolume { timestamp: 3, volume: 6u32.into() }).unwrap();
let _ = emv.update(&TimestampedVolume { timestamp: 0, volume: 2u32.into() }).unwrap();
let _ = emv.update(&TimestampedVolume { timestamp: 3, volume: 6u32.into() }).unwrap();
emv.clear();
assert_eq!(emv.ema, <FixedU128<U64>>::from_num(0));
assert_eq!(emv.multiplier(), &<FixedU128<U64>>::from_num(0));
Expand All @@ -98,37 +140,47 @@ fn ema_clear_ereases_data() {
#[test]
fn ema_added_volume_is_older_than_previous() {
let mut emv = ema_create_test_struct(2, 2.0);
let _ = emv.update(TimestampedVolume { timestamp: 2, volume: 2u32.into() }).unwrap();
let _ = emv.update(&TimestampedVolume { timestamp: 2, volume: 2u32.into() }).unwrap();
assert_err!(
emv.update(TimestampedVolume { timestamp: 1, volume: 2u32.into() }),
emv.update(&TimestampedVolume { timestamp: 1, volume: 2u32.into() }),
"[EmaMarketVolume] Incoming volume timestamp is older than previous timestamp"
);
}

#[test]
fn ema_overflow_sma_times_vpp() {
let emv_cfg = EmaVolumeConfig::<FixedU128<U64>> {
ema_period: Timespan::Seconds(3),
smoothing: <FixedU128<U64>>::from_num(2),
};
// TODO
let mut emv = <EmaMarketVolume<FixedU128<U64>>>::new(emv_cfg);
let _ = emv.update(TimestampedVolume { timestamp: 0, volume: 2u32.into() }).unwrap();
let _ = emv.update(TimestampedVolume { timestamp: 1, volume: 6u32.into() }).unwrap();
let mut emv = ema_create_test_struct(3, 2.0);
let _ = emv.update(&TimestampedVolume { timestamp: 0, volume: 2u32.into() }).unwrap();
let _ = emv.update(&TimestampedVolume { timestamp: 1, volume: 6u32.into() }).unwrap();
emv.ema = <FixedU128<U64>>::from_num(u64::MAX);
assert_err!(
emv.update(TimestampedVolume { timestamp: 3, volume: 6u32.into() }),
emv.update(&TimestampedVolume { timestamp: 3, volume: 6u32.into() }),
"[EmaMarketVolume] Overflow during calculation: sma * volumes_per_period"
);
}

#[test]
fn ema_overflow_sma_times_vpp_plus_volume() {
let mut emv = ema_create_test_struct(2, -1.0001);
let _ = emv.update(TimestampedVolume { timestamp: 0, volume: 2u32.into() }).unwrap();
let mut emv = ema_create_test_struct(2, 2.0);
let _ = emv.update(&TimestampedVolume { timestamp: 0, volume: 2u32.into() }).unwrap();
let max_u64_fixed = <FixedU128<U64>>::from_num(u64::MAX);
assert_err!(
emv.update(TimestampedVolume { timestamp: 2, volume: max_u64_fixed }),
emv.update(&TimestampedVolume { timestamp: 2, volume: max_u64_fixed }),
"[EmaMarketVolume] Overflow during calculation: sma * volumes_per_period + volume"
);
}

#[test]
fn ema_overflow_estimated_tx_per_period_does_not_fit() {
let mut emv = EmaMarketVolume::new(EmaConfig::<FixedU128<U96>> {
ema_period: Timespan::Hours(2_386_093),
ema_period_estimate_after: Some(Timespan::Seconds(0)),
smoothing: <FixedU128<U96>>::from_num(2.0),
});
let _ = emv.update(&TimestampedVolume { timestamp: 0, volume: 2u32.into() }).unwrap();
let _ = emv.update(&TimestampedVolume { timestamp: 0, volume: 2u32.into() }).unwrap();
assert_err!(
emv.update(&TimestampedVolume { timestamp: 1, volume: 6u32.into() }),
"[EmaMarketVolume] Overflow during estimation of transactions per period"
);
}
Loading

0 comments on commit 3aed86c

Please sign in to comment.