Skip to content

Commit

Permalink
Implement and test bmul_bdiv_*; use in zrml-orderbook and zrml-pari…
Browse files Browse the repository at this point in the history
…mutuel (zeitgeistpm#1223)

* Implement and test `bmul_bdiv_*`

* Use `bmul_bdiv_*` in pallets

* Update copyright
  • Loading branch information
maltekliemann authored Jan 22, 2024
1 parent ca684ac commit 1918416
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 39 deletions.
180 changes: 169 additions & 11 deletions primitives/src/math/fixed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,17 +196,13 @@ where
bmul_bdiv_common(self, multiplier, divisor, adjustment)
}

fn bmul_bdiv_floor(&self, _multiplier: Self, _divisor: Self) -> Result<Self, DispatchError> {
// TODO(#1217): Commented mplementation below should work, but remains untested!
// bmul_bdiv_common(self, multiplier, divisor, Zero::zero())
Err(DispatchError::Other("not implemented"))
fn bmul_bdiv_floor(&self, multiplier: Self, divisor: Self) -> Result<Self, DispatchError> {
bmul_bdiv_common(self, multiplier, divisor, Zero::zero())
}

fn bmul_bdiv_ceil(&self, _multiplier: Self, _divisor: Self) -> Result<Self, DispatchError> {
// TODO(#1217): Commented mplementation below should work, but remains untested!
// let adjustment = ZeitgeistBase::<T>::get()?.checked_sub_res(&1u8.into())?;
// bmul_bdiv_common(self, multiplier, divisor, adjustment)
Err(DispatchError::Other("not implemented"))
fn bmul_bdiv_ceil(&self, multiplier: Self, divisor: Self) -> Result<Self, DispatchError> {
let adjustment = ZeitgeistBase::<T>::get()?.checked_sub_res(&1u8.into())?;
bmul_bdiv_common(self, multiplier, divisor, adjustment)
}
}

Expand Down Expand Up @@ -647,16 +643,178 @@ mod tests {
#[test_case(1_234_567 * _1, 9_876_543 * _1, 123_456, 9876599000357212286158412534)]
#[test_case(1_000_000 * _1, 9_876_543 * _1, 1_000_000 * _1, 9_876_543 * _1)]

fn fixed_mul_div_works(lhs: u128, multiplier: u128, divisor: u128, expected: u128) {
fn bmul_bdiv_works(lhs: u128, multiplier: u128, divisor: u128, expected: u128) {
assert_eq!(lhs.bmul_bdiv(multiplier, divisor).unwrap(), expected);
}

#[test_case(_1, u128::MAX, u128::MAX, DispatchError::Arithmetic(ArithmeticError::Overflow))]
#[test_case(_1, _2, 0, DispatchError::Arithmetic(ArithmeticError::DivisionByZero))]
fn fixed_mul_div_fails(lhs: u128, multiplier: u128, divisor: u128, expected: DispatchError) {
fn bmul_bdiv_fails(lhs: u128, multiplier: u128, divisor: u128, expected: DispatchError) {
assert_eq!(lhs.bmul_bdiv(multiplier, divisor), Err(expected));
}

// bmul tests
#[test_case(0, 0, _1, 0)]
#[test_case(0, _1, _1, 0)]
#[test_case(0, _2, _1, 0)]
#[test_case(0, _3, _1, 0)]
#[test_case(_1, 0, _1, 0)]
#[test_case(_1, _1, _1, _1)]
#[test_case(_1, _2, _1, _2)]
#[test_case(_1, _3, _1, _3)]
#[test_case(_2, 0, _1, 0)]
#[test_case(_2, _1, _1, _2)]
#[test_case(_2, _2, _1, _4)]
#[test_case(_2, _3, _1, _6)]
#[test_case(_3, 0, _1, 0)]
#[test_case(_3, _1, _1, _3)]
#[test_case(_3, _2, _1, _6)]
#[test_case(_3, _3, _1, _9)]
#[test_case(_4, _1_2, _1, _2)]
#[test_case(_5, _1 + _1_2, _1, _7 + _1_2)]
#[test_case(_1 + 1, _2, _1, _2 + 2)]
#[test_case(9_999_999_999, _2, _1, 19_999_999_998)]
#[test_case(9_999_999_999, _10, _1, 99_999_999_990)]
// Rounding behavior when multiplying with small numbers
#[test_case(9_999_999_999, _1_2, _1, _1_2 - 1)] // 4999999999.5
#[test_case(9_999_999_997, _1_4, _1, 2_499_999_999)] // 2499999999.25
#[test_case(9_999_999_996, _1_3, _1, 3_333_333_331)] // 3333333331.666...
#[test_case(10_000_000_001, _1_10, _1, _1_10)]
#[test_case(10_000_000_005, _1_10, _1, _1_10)]
#[test_case(10_000_000_009, _1_10, _1, _1_10)] //

// bdiv tests
#[test_case(0, _1, _3, 0)]
#[test_case(_1, _1, _2, _1_2)]
#[test_case(_2, _1, _2, _1)]
#[test_case(_3,_1, _2, _1 + _1_2)]
#[test_case(_3, _1, _3, _1)]
#[test_case(_3 + _1_2, _1, _1_2, _7)]
#[test_case(99_999_999_999, _1, 1, 99_999_999_999 * _1)]
// Rounding behavior
#[test_case(_2, _1, _3, _2_3)] // 0.6666...
#[test_case(99_999_999_999, _1, _10, 9_999_999_999)]
#[test_case(99_999_999_994, _1, _10, 9_999_999_999)]
#[test_case(9, _1, _10, 0)] // 0.0...09 (less than precision)
#[test_case(4, _1, _10, 0)] // 0.0...04 (less than precision)

// Normal Cases
#[test_case(_2, _2, _2, _2)]
#[test_case(_1, _2, _3, _2_3)] // 0.6666...
#[test_case(_2, _3, _4, _1 + _1_2)]
#[test_case(_1 + 1, _2, _3, (_2 + 2) / 3)]
#[test_case(_5, _6, _7, _5 * _6 / _7)]
#[test_case(_100, _101, _20, _100 * _101 / _20)] //

// Boundary cases
#[test_case(u128::MAX / _1, _1, _2, u128::MAX / _2)]
#[test_case(0, _1, _2, 0)]
#[test_case(_1, u128::MAX / _1, u128::MAX / _1, _1)] //

// Special rounding cases
#[test_case(_1, _1_2, _1, _1_2)]
#[test_case(_1, _1_3, _1, _1_3)]
#[test_case(_1, _2_3, _1, _2_3)]
#[test_case(_9, _1_2, _1, _9 / 2)]
#[test_case(_9, _1_3, _1, 29_999_999_997)]
#[test_case(_9, _2_3, _1, 59_999_999_994)] //

// Divide-first value
#[test_case(1_000_000 * _1, 1_000_000 * _1, _10, 100000000000 * _1)]
#[test_case(1_234_567 * _1, 9_876_543 * _1, 123_456, 9876599000357212286158412534)]
#[test_case(1_000_000 * _1, 9_876_543 * _1, 1_000_000 * _1, 9_876_543 * _1)]

fn bmul_bdiv_floor_works(lhs: u128, multiplier: u128, divisor: u128, expected: u128) {
assert_eq!(lhs.bmul_bdiv_floor(multiplier, divisor).unwrap(), expected);
}

#[test_case(_1, u128::MAX, u128::MAX, DispatchError::Arithmetic(ArithmeticError::Overflow))]
#[test_case(_1, _2, 0, DispatchError::Arithmetic(ArithmeticError::DivisionByZero))]
fn bmul_bdiv_floor_fails(lhs: u128, multiplier: u128, divisor: u128, expected: DispatchError) {
assert_eq!(lhs.bmul_bdiv_floor(multiplier, divisor), Err(expected));
}

// bmul tests
#[test_case(0, 0, _1, 0)]
#[test_case(0, _1, _1, 0)]
#[test_case(0, _2, _1, 0)]
#[test_case(0, _3, _1, 0)]
#[test_case(_1, 0, _1, 0)]
#[test_case(_1, _1, _1, _1)]
#[test_case(_1, _2, _1, _2)]
#[test_case(_1, _3, _1, _3)]
#[test_case(_2, 0, _1, 0)]
#[test_case(_2, _1, _1, _2)]
#[test_case(_2, _2, _1, _4)]
#[test_case(_2, _3, _1, _6)]
#[test_case(_3, 0, _1, 0)]
#[test_case(_3, _1, _1, _3)]
#[test_case(_3, _2, _1, _6)]
#[test_case(_3, _3, _1, _9)]
#[test_case(_4, _1_2, _1, _2)]
#[test_case(_5, _1 + _1_2, _1, _7 + _1_2)]
#[test_case(_1 + 1, _2, _1, _2 + 2)]
#[test_case(9_999_999_999, _2, _1, 19_999_999_998)]
#[test_case(9_999_999_999, _10, _1, 99_999_999_990)]
// Rounding behavior when multiplying with small numbers
#[test_case(9_999_999_999, _1_2, _1, _1_2)] // 4999999999.5
#[test_case(9_999_999_997, _1_4, _1, 2_500_000_000)] // 2499999999.25
#[test_case(9_999_999_996, _1_3, _1, 3_333_333_332)] // 3333333331.666...
#[test_case(10_000_000_001, _1_10, _1, _1_10 + 1)]
#[test_case(10_000_000_005, _1_10, _1, _1_10 + 1)]
#[test_case(10_000_000_009, _1_10, _1, _1_10 + 1)] //

// bdiv tests
#[test_case(0, _1, _3, 0)]
#[test_case(_1, _1, _2, _1_2)]
#[test_case(_2, _1, _2, _1)]
#[test_case(_3,_1, _2, _1 + _1_2)]
#[test_case(_3, _1, _3, _1)]
#[test_case(_3 + _1_2, _1, _1_2, _7)]
#[test_case(99_999_999_999, _1, 1, 99_999_999_999 * _1)]
// Rounding behavior
#[test_case(_2, _1, _3, _2_3 + 1)] // 0.6666...
#[test_case(99_999_999_999, _1, _10, _1)]
#[test_case(99_999_999_991, _1, _10, _1)]
#[test_case(9, _1, _10, 1)] // 0.0...09 (less than precision)
#[test_case(4, _1, _10, 1)] // 0.0...04 (less than precision)

// Normal Cases
#[test_case(_2, _2, _2, _2)]
#[test_case(_1, _2, _3, _2_3 + 1)] // 0.6666...
#[test_case(_2, _3, _4, _1 + _1_2)]
#[test_case(_1 + 1, _2, _3, 6_666_666_668)] // 0.6666666667333333
#[test_case(_5, _6, _7, 42_857_142_858)]
#[test_case(_100, _101, _20, _100 * _101 / _20)] //

// Boundary cases
#[test_case(u128::MAX / _1, _1, _2, u128::MAX / _2)]
#[test_case(0, _1, _2, 0)]
#[test_case(_1, u128::MAX / _1, u128::MAX / _1, _1)] //

// Special rounding cases
#[test_case(_1, _1_2, _1, _1_2)]
#[test_case(_1, _1_3, _1, _1_3)]
#[test_case(_1, _2_3, _1, _2_3)]
#[test_case(_9, _1_2, _1, _9 / 2)]
#[test_case(_9, _1_3, _1, 29_999_999_997)]
#[test_case(_9, _2_3, _1, 59_999_999_994)] //

// Divide-first value
#[test_case(1_000_000 * _1, 1_000_000 * _1, _10, 100000000000 * _1)]
#[test_case(1_234_567 * _1, 9_876_543 * _1, 123_456, 9876599000357212286158412534)]
#[test_case(1_000_000 * _1, 9_876_543 * _1, 1_000_000 * _1, 9_876_543 * _1)]

fn bmul_bdiv_ceil_works(lhs: u128, multiplier: u128, divisor: u128, expected: u128) {
assert_eq!(lhs.bmul_bdiv_ceil(multiplier, divisor).unwrap(), expected);
}

#[test_case(_1, u128::MAX, u128::MAX, DispatchError::Arithmetic(ArithmeticError::Overflow))]
#[test_case(_1, _2, 0, DispatchError::Arithmetic(ArithmeticError::DivisionByZero))]
fn bmul_bdiv_ceil_fails(lhs: u128, multiplier: u128, divisor: u128, expected: DispatchError) {
assert_eq!(lhs.bmul_bdiv_ceil(multiplier, divisor), Err(expected));
}

#[test_case(0, 10, 0.0)]
#[test_case(1, 10, 0.0000000001)]
#[test_case(9, 10, 0.0000000009)]
Expand Down
9 changes: 3 additions & 6 deletions zrml/orderbook/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2022-2023 Forecasting Technologies LTD.
// Copyright 2022-2024 Forecasting Technologies LTD.
// Copyright 2021-2022 Zeitgeist PM LLC.
//
// This file is part of Zeitgeist.
Expand Down Expand Up @@ -39,7 +39,7 @@ use sp_runtime::traits::{Get, Zero};
use zeitgeist_primitives::{
math::{
checked_ops_res::{CheckedAddRes, CheckedSubRes},
fixed::{FixedDiv, FixedMul},
fixed::FixedMulDiv,
},
traits::{DistributeFees, MarketCommonsPalletApi},
types::{Asset, Market, MarketStatus, MarketType, ScalarPosition, ScoringRule},
Expand Down Expand Up @@ -295,10 +295,7 @@ mod pallet {
// This ensures that the reserve of the maker
// is always enough to repatriate successfully!
// `maker_full_fill` is ensured to be never zero in `ensure_ratio_quotient_valid`
let ratio = maker_fill.bdiv_floor(maker_full_fill)?;
// returns the (partial) amount of what the taker gets from the maker's amount
// respected the partial fill from the taker of what the maker wants to get filled
ratio.bmul_floor(taker_full_fill)
maker_fill.bmul_bdiv_floor(taker_full_fill, maker_full_fill)
}

fn ensure_ratio_quotient_valid(order_data: &OrderOf<T>) -> DispatchResult {
Expand Down
28 changes: 6 additions & 22 deletions zrml/parimutuel/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023 Forecasting Technologies LTD.
// Copyright 2023-2024 Forecasting Technologies LTD.
//
// This file is part of Zeitgeist.
//
Expand Down Expand Up @@ -44,11 +44,10 @@ mod pallet {
use orml_traits::MultiCurrency;
use sp_runtime::{
traits::{AccountIdConversion, CheckedSub, Zero},
DispatchResult, SaturatedConversion,
DispatchResult,
};
use zeitgeist_primitives::{
constants::BASE,
math::fixed::{FixedDiv, FixedMul},
math::fixed::FixedMulDiv,
traits::DistributeFees,
types::{Asset, Market, MarketStatus, MarketType, OutcomeReport, ScoringRule},
};
Expand Down Expand Up @@ -260,7 +259,6 @@ mod pallet {
winning_balance: BalanceOf<T>,
pot_total: BalanceOf<T>,
outcome_total: BalanceOf<T>,
payoff_ratio_mul_base: BalanceOf<T>,
payoff: BalanceOf<T>,
) -> DispatchResult {
ensure!(
Expand All @@ -275,13 +273,6 @@ mod pallet {
InconsistentStateError::OutcomeIssuanceGreaterCollateral
)
);
if payoff_ratio_mul_base < BASE.saturated_into() {
log::debug!(
target: LOG_TARGET,
"The payoff ratio should be greater than or equal to BASE!"
);
debug_assert!(false);
}
if payoff < winning_balance {
log::debug!(
target: LOG_TARGET,
Expand Down Expand Up @@ -406,16 +397,9 @@ mod pallet {

let pot_account = Self::pot_account(market_id);
let pot_total = T::AssetManager::free_balance(market.base_asset, &pot_account);
let payoff_ratio_mul_base = pot_total.bdiv_floor(outcome_total)?;
let payoff = payoff_ratio_mul_base.bmul_floor(winning_balance)?;

Self::check_values(
winning_balance,
pot_total,
outcome_total,
payoff_ratio_mul_base,
payoff,
)?;
let payoff = pot_total.bmul_bdiv(winning_balance, outcome_total)?;

Self::check_values(winning_balance, pot_total, outcome_total, payoff)?;

let withdrawn_asset_balance = winning_balance;

Expand Down

0 comments on commit 1918416

Please sign in to comment.