Skip to content

Commit

Permalink
[Rikiddo] Implement Rikiddo helper functions (#199)
Browse files Browse the repository at this point in the history
* Add LSD-LSMR skeleton

* Add types and trait functions

* Rename to rikiddo and bump version

* Rename rikiddo to zrml-rikiddo

* Start implementation of FeeSigmoid

* Add z(r) fee calculation

Also updated traits and constants to use different types

* Change constant types and adjust default functions

* Add "valid sigmoid fee result" test

* Add FeeSigmoid overflow tests

* Add self to EmaVolume trait functions

* Update EmaMarketVolume to support multiple ema chains

* Implement default, clear and update for EmaMarketVolume

* Change fee calculation type to generic type

* Implement EmaMarketVolume

* Rename remaining old names to Rikiddo

* Prepare library for better error handling

* Implement arithmetic checks for EmaMarketVolume

* Add generic type for FeeSigmoidConfig

* Add EmaMarketVolume test scaffold

* Implement EmaMarketVolume state transit test

* Fix off-by-one error and add test

* Partially implement EmaMarketVolume tests

* Revert constant types back to 32bit numbers

This decision was made, because the fee and ema structures are limited by the length of the constants. To allow any fixed point numbers with at least 32 bit width, the constants were reverted back to 32 bit length

* Fix missing assignment and implement some tests

* Implement last timestamp check and test

* Implement some overflow tests for ema calculation

* Improve module hierarchy

* Add additional (probably unnecessary) overflow checks

* Remove rebase artifacts

* Clippy

* Use integer sign only when needed
  • Loading branch information
sea212 authored Jul 19, 2021
1 parent eeceba3 commit 80936e4
Show file tree
Hide file tree
Showing 11 changed files with 614 additions and 122 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"runtime",
"zrml/court",
"zrml/market-commons",
"zrml/rikiddo",
"zrml/orderbook-v1",
"zrml/prediction-markets",
"zrml/prediction-markets/fuzz",
Expand Down
8 changes: 4 additions & 4 deletions zrml/rikiddo/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::types::Timespan;
use substrate_fixed::{
types::extra::{U24, U32},
FixedU32,
FixedI32, FixedU32,
};

// --- Default configuration for EmaConfig struct ---
Expand All @@ -28,12 +28,12 @@ pub const MINIMAL_REVENUE: FixedU32<U32> = <FixedU32<U32>>::from_bits(0x00E5_604

/// m value
/// 0.01
pub const M: FixedU32<U24> = <FixedU32<U24>>::from_bits(0x0002_8F5C);
pub const M: FixedI32<U24> = <FixedI32<U24>>::from_bits(0x0002_8F5C);

/// p value
/// 2.0
pub const P: FixedU32<U24> = <FixedU32<U24>>::from_bits(0x0200_0000);
pub const P: FixedI32<U24> = <FixedI32<U24>>::from_bits(0x0200_0000);

/// n value
/// 0.0
pub const N: FixedU32<U24> = <FixedU32<U24>>::from_bits(0x0000_0000);
pub const N: FixedI32<U24> = <FixedI32<U24>>::from_bits(0x0000_0000);
4 changes: 2 additions & 2 deletions zrml/rikiddo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ mod pallet {

impl<T: Config> Pallet<T> {}

// This is the storage containing the LsdLmsr instances per pool.
// This is the storage containing the Rikiddo instances per pool.
/*
#[pallet::storage]
pub type LmsrPerPool<T: Config> = StorageMap<
_,
Twox64Concat,
u128,
LsdLmsrSigmoidMV<
RikiddoMV<
FixedU128<T::FractionalType>,
FeeSigmoid,
EmaMarketVolume<FixedU128<T::FractionalType>>,
Expand Down
2 changes: 1 addition & 1 deletion zrml/rikiddo/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ construct_runtime!(
UncheckedExtrinsic = UncheckedExtrinsic,
{
Balances: pallet_balances::{Call, Config<T>, Event<T>, Pallet, Storage},
LsdLmsr: zrml_rikiddo::{Pallet, Storage},
Rikiddo: zrml_rikiddo::{Pallet, Storage},
System: frame_system::{Config, Event<T>, Pallet, Storage},
Timestamp: pallet_timestamp::{Call, Pallet, Storage, Inherent},
}
Expand Down
7 changes: 7 additions & 0 deletions zrml/rikiddo/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

use crate::mock::*;

mod ema_market_volume;
mod sigmoid_fee;

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

#[test]
fn it_is_a_dummy_test() {
ExtBuilder::default().build().execute_with(|| {
Expand Down
134 changes: 134 additions & 0 deletions zrml/rikiddo/src/tests/ema_market_volume.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use super::max_allowed_error;
use crate::{
traits::MarketAverage,
types::{EmaMarketVolume, EmaVolumeConfig, MarketVolumeState, Timespan, TimestampedVolume},
};
use frame_support::assert_err;
use substrate_fixed::{types::extra::U64, FixedU128};

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

<EmaMarketVolume<FixedU128<U64>>>::new(emv_cfg)
}

fn ema_get_multiplier(volumes_per_period: u64, smoothing: f64) -> f64 {
smoothing / (1 + volumes_per_period) as f64
}

fn ema_calculate(old_ema: f64, multiplier: f64, volume: f64) -> f64 {
volume * multiplier + old_ema * (1.0 - multiplier)
}

#[test]
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();
assert_eq!(emv.state(), &MarketVolumeState::DataCollectionStarted);
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();
assert_eq!(emv.get(), None);
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();
// 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
let ema_fixed_f64: f64 = emv.ema.to_num();
let multiplier = ema_get_multiplier(3, 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)
);

// Repeat check using the get() function
let ema_fixed_f64: f64 = emv.get().unwrap().to_num();
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();
emv.clear();
assert_eq!(emv.ema, <FixedU128<U64>>::from_num(0));
assert_eq!(emv.multiplier(), &<FixedU128<U64>>::from_num(0));
assert_eq!(emv.state(), &MarketVolumeState::Uninitialized);
assert_eq!(emv.start_time(), &0);
assert_eq!(emv.last_time(), &0);
assert_eq!(emv.volumes_per_period(), &0);
}

#[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();
assert_err!(
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();
emv.ema = <FixedU128<U64>>::from_num(u64::MAX);
assert_err!(
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 max_u64_fixed = <FixedU128<U64>>::from_num(u64::MAX);
assert_err!(
emv.update(TimestampedVolume { timestamp: 2, volume: max_u64_fixed }),
"[EmaMarketVolume] Overflow during calculation: sma * volumes_per_period + volume"
);
}
95 changes: 95 additions & 0 deletions zrml/rikiddo/src/tests/sigmoid_fee.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use super::max_allowed_error;
use crate::{
traits::Sigmoid,
types::{FeeSigmoid, FeeSigmoidConfig},
};
use frame_support::assert_err;
use substrate_fixed::{types::extra::U64, FixedI128};

fn sigmoid_fee(m: f64, n: f64, p: f64, r: f64) -> f64 {
(m * (r - n)) / (p + (r - n).powi(2)).sqrt()
}

fn init_default_sigmoid_fee_struct() -> (FeeSigmoid<FixedI128<U64>>, f64, f64, f64) {
let m = 0.01f64;
let n = 0f64;
let p = 2.0f64;

let config = FeeSigmoidConfig {
m: <FixedI128<U64>>::from_num(m),
n: <FixedI128<U64>>::from_num(n),
p: <FixedI128<U64>>::from_num(p),
};

let fee = FeeSigmoid { config };
(fee, m, n, p)
}

#[test]
fn fee_sigmoid_overflow_r_minus_n() {
let (mut fee, _, _, _) = init_default_sigmoid_fee_struct();
let r = <FixedI128<U64>>::from_num(i64::MIN);
fee.config.n = <FixedI128<U64>>::from_num(i64::MAX);
assert_err!(fee.calculate(r), "[FeeSigmoid] Overflow during calculation: r - n");
}

#[test]
fn fee_sigmoid_overflow_m_times_r_minus_n() {
let (mut fee, _, _, _) = init_default_sigmoid_fee_struct();
let r = <FixedI128<U64>>::from_num(i64::MIN);
fee.config.n = <FixedI128<U64>>::from_num(0);
fee.config.m = <FixedI128<U64>>::from_num(i64::MAX);
assert_err!(fee.calculate(r), "[FeeSigmoid] Overflow during calculation: m * (r-n)");
}

#[test]
fn fee_sigmoid_overflow_r_minus_n_squared() {
let (mut fee, _, _, _) = init_default_sigmoid_fee_struct();
let r = <FixedI128<U64>>::from_num(i64::MIN);
fee.config.n = <FixedI128<U64>>::from_num(0);
assert_err!(fee.calculate(r), "[FeeSigmoid] Overflow during calculation: (r-n)^2");
}

#[test]
fn fee_sigmoid_overflow_p_plus_r_minus_n_squared() {
let (mut fee, _, _, _) = init_default_sigmoid_fee_struct();
let r = <FixedI128<U64>>::from_num(0);
fee.config.n = <FixedI128<U64>>::from_num(1);
fee.config.m = <FixedI128<U64>>::from_num(0);
fee.config.p = <FixedI128<U64>>::from_num(i64::MAX);
assert_err!(fee.calculate(r), "[FeeSigmoid] Overflow during calculation: p + (r-n)^2");
}

#[test]
fn fee_sigmoid_overflow_numerator_div_denominator() {
let (mut fee, _, _, _) = init_default_sigmoid_fee_struct();
let r = <FixedI128<U64>>::from_num(0.1);
fee.config.n = <FixedI128<U64>>::from_num(0);
fee.config.m = <FixedI128<U64>>::from_num(i64::MAX);
fee.config.p = <FixedI128<U64>>::from_num(-0.0099);
assert_err!(
fee.calculate(r),
"[FeeSigmoid] Overflow during calculation: numerator / denominator"
);
}

#[test]
fn fee_sigmoid_correct_result() -> Result<(), &'static str> {
let r = 1.5f64;
let (fee, m, n, p) = init_default_sigmoid_fee_struct();
let fee_f64 = sigmoid_fee(m, n, p, r);
let fee_fixed = fee.calculate(<FixedI128<U64>>::from_num(r))?;
let fee_fixed_f64: f64 = fee_fixed.to_num();
let difference_abs = (fee_f64 - fee_fixed_f64).abs();

assert!(
difference_abs <= max_allowed_error(64),
"\nFixed result: {}\nFloat result: {}\nDifference: {}\nMax_Allowed_Difference: {}",
fee_f64,
fee_fixed_f64,
difference_abs,
max_allowed_error(64)
);

Ok(())
}
Loading

0 comments on commit 80936e4

Please sign in to comment.