From 87264f8130b746880dfdbc6f30bad97e662c4f95 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 14 Jul 2022 09:37:17 +0200 Subject: [PATCH] Fix zero division error in `on_resolution` (#701) * Fix zero division error in `on_resolution` * Update zrml/prediction-markets/src/tests.rs Co-authored-by: Harald Heckmann * Fix typo and future-proof the test * Update zrml/prediction-markets/src/lib.rs Co-authored-by: Chralt * Update zrml/prediction-markets/src/tests.rs Co-authored-by: Chralt * fix unused import Co-authored-by: Harald Heckmann Co-authored-by: Chralt --- zrml/prediction-markets/src/lib.rs | 37 ++++++++++--------- zrml/prediction-markets/src/tests.rs | 54 ++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 3b872ba49..248a227ac 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -92,7 +92,7 @@ mod pallet { use sp_arithmetic::per_things::Perbill; use sp_runtime::{ traits::{AccountIdConversion, CheckedDiv, Saturating, Zero}, - ArithmeticError, DispatchError, DispatchResult, SaturatedConversion, + DispatchError, DispatchResult, SaturatedConversion, }; use zeitgeist_primitives::{ constants::{PmPalletId, MILLISECS_PER_BLOCK}, @@ -1775,22 +1775,25 @@ mod pallet { } } - // fold all the imbalances into one and reward the correct reporters. - let reward_per_each = overall_imbalance - .checked_div(&correct_reporters.len().saturated_into()) - .ok_or(ArithmeticError::DivisionByZero)?; - for correct_reporter in &correct_reporters { - let reward = overall_imbalance.min(reward_per_each); // *Should* always be equal to `reward_per_each` - overall_imbalance = overall_imbalance.saturating_sub(reward); - - if let Err(err) = - T::AssetManager::deposit(Asset::Ztg, correct_reporter, reward) - { - log::warn!( - "[PredictionMarkets] Cannot deposit to the correct reporter. \ - error: {:?}", - err - ); + // Fold all the imbalances into one and reward the correct reporters. The + // number of correct reporters might be zero if the market defaults to the + // report after abandoned dispute. In that case, the rewards remain slashed. + if let Some(reward_per_each) = + overall_imbalance.checked_div(&correct_reporters.len().saturated_into()) + { + for correct_reporter in &correct_reporters { + let reward = overall_imbalance.min(reward_per_each); // *Should* always be equal to `reward_per_each` + overall_imbalance = overall_imbalance.saturating_sub(reward); + + if let Err(err) = + T::AssetManager::deposit(Asset::Ztg, correct_reporter, reward) + { + log::warn!( + "[PredictionMarkets] Cannot deposit to the correct reporter. \ + error: {:?}", + err + ); + } } } diff --git a/zrml/prediction-markets/src/tests.rs b/zrml/prediction-markets/src/tests.rs index d25cd9deb..a8c7d199b 100644 --- a/zrml/prediction-markets/src/tests.rs +++ b/zrml/prediction-markets/src/tests.rs @@ -2261,6 +2261,60 @@ fn authorized_correctly_resolves_disputed_market() { }); } +#[test] +fn on_resolution_defaults_to_oracle_report_in_case_of_unresolved_dispute() { + ExtBuilder::default().build().execute_with(|| { + let end = 1; + assert_ok!(PredictionMarkets::create_market( + Origin::signed(ALICE), + BOB, + MarketPeriod::Block(0..end), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + MarketDisputeMechanism::Authorized(FRED), + ScoringRule::CPMM, + )); + assert_ok!(PredictionMarkets::buy_complete_set(Origin::signed(CHARLIE), 0, CENT)); + + run_to_block(end); + assert_ok!(PredictionMarkets::report( + Origin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + run_to_block(>::block_number() + 1); + assert_ok!(PredictionMarkets::dispute( + Origin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(0) + )); + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Disputed); + + let charlie_reserved = Balances::reserved_balance(&CHARLIE); + assert_eq!(charlie_reserved, DisputeBond::get()); + + run_to_block(>::block_number() + DisputePeriod::get()); + let market_after = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after.status, MarketStatus::Resolved); + let disputes = crate::Disputes::::get(&0); + assert_eq!(disputes.len(), 0); + assert_ok!(PredictionMarkets::redeem_shares(Origin::signed(CHARLIE), 0)); + + // Make sure rewards are right: + // + // - Bob reported "correctly" and in time, so Alice and Bob don't get slashed + // - Charlie started a dispute which was abandoned, hence he's slashed + let charlie_balance = Balances::free_balance(&CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE - charlie_reserved); + let alice_balance = Balances::free_balance(&ALICE); + assert_eq!(alice_balance, 1_000 * BASE); + let bob_balance = Balances::free_balance(&BOB); + assert_eq!(bob_balance, 1_000 * BASE); + }); +} + #[test] fn approve_market_correctly_unreserves_advisory_bond() { ExtBuilder::default().build().execute_with(|| {