From 15bee2cdcef6bd5f1e8d82834e595b3e4f56ff38 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 11 Jan 2024 11:45:27 +0100 Subject: [PATCH 01/32] Rename `tests.rs` to `old_tests.rs` to avoid naming conflicts --- zrml/prediction-markets/src/lib.rs | 2 +- zrml/prediction-markets/src/{tests.rs => old_tests.rs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename zrml/prediction-markets/src/{tests.rs => old_tests.rs} (100%) diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index f20e3df11..d71100b56 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -26,8 +26,8 @@ extern crate alloc; mod benchmarks; pub mod migrations; pub mod mock; +mod old_tests; pub mod orml_asset_registry; -mod tests; pub mod weights; pub use pallet::*; diff --git a/zrml/prediction-markets/src/tests.rs b/zrml/prediction-markets/src/old_tests.rs similarity index 100% rename from zrml/prediction-markets/src/tests.rs rename to zrml/prediction-markets/src/old_tests.rs From 1b343020e545246817ab1e491facdc30a4708d73 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 11 Jan 2024 11:51:04 +0100 Subject: [PATCH 02/32] Move `buy_complete_set` tests to new tests --- zrml/prediction-markets/src/lib.rs | 1 + zrml/prediction-markets/src/old_tests.rs | 139 --------------- .../src/tests/buy_complete_set.rs | 159 ++++++++++++++++++ zrml/prediction-markets/src/tests/mod.rs | 71 ++++++++ 4 files changed, 231 insertions(+), 139 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/buy_complete_set.rs create mode 100644 zrml/prediction-markets/src/tests/mod.rs diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index d71100b56..603542fb7 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -28,6 +28,7 @@ pub mod migrations; pub mod mock; mod old_tests; pub mod orml_asset_registry; +mod tests; pub mod weights; pub use pallet::*; diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index 942820374..abfaa6398 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -141,48 +141,6 @@ fn simple_create_scalar_market( )); } -#[test_case(MarketStatus::Proposed)] -#[test_case(MarketStatus::Closed)] -#[test_case(MarketStatus::Reported)] -#[test_case(MarketStatus::Disputed)] -#[test_case(MarketStatus::Resolved)] -fn buy_complete_set_fails_if_market_is_not_active(status: MarketStatus) { - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..2, - ScoringRule::Lmsr, - ); - let market_id = 0; - assert_ok!(MarketCommons::mutate_market(&market_id, |market| { - market.status = status; - Ok(()) - })); - assert_noop!( - PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(FRED), market_id, 1), - Error::::MarketIsNotActive, - ); - }); -} - -#[test_case(ScoringRule::Parimutuel)] -fn buy_complete_set_fails_if_market_has_wrong_scoring_rule(scoring_rule: ScoringRule) { - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..2, - scoring_rule, - ); - let market_id = 0; - assert_noop!( - PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(FRED), market_id, 1), - Error::::InvalidScoringRule, - ); - }); -} - #[test] fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_blocknumber() { ExtBuilder::default().build().execute_with(|| { @@ -1349,64 +1307,6 @@ fn on_initialize_skips_the_genesis_block() { }); } -#[test] -fn it_allows_to_buy_a_complete_set() { - let test = |base_asset: Asset| { - frame_system::Pallet::::set_block_number(1); - // Creates a permissionless market. - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..2, - ScoringRule::Lmsr, - ); - - // Allows someone to generate a complete set - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); - - let market = MarketCommons::market(&0).unwrap(); - - // Check the outcome balances - let assets = PredictionMarkets::outcome_assets(0, &market); - for asset in assets.iter() { - let bal = Tokens::free_balance(*asset, &BOB); - assert_eq!(bal, CENT); - } - - let market_account = PredictionMarkets::market_account(0); - let bal = AssetManager::free_balance(base_asset, &BOB); - assert_eq!(bal, 1_000 * BASE - CENT); - - let market_bal = AssetManager::free_balance(base_asset, &market_account); - assert_eq!(market_bal, CENT); - System::assert_last_event(Event::BoughtCompleteSet(0, CENT, BOB).into()); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn it_does_not_allow_to_buy_a_complete_set_on_pending_advised_market() { - ExtBuilder::default().build().execute_with(|| { - // Creates a permissionless market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - assert_noop!( - PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT), - Error::::MarketIsNotActive, - ); - }); -} - #[test] fn create_categorical_market_fails_if_market_begin_is_equal_to_end() { ExtBuilder::default().build().execute_with(|| { @@ -1502,45 +1402,6 @@ fn create_categorical_market_fails_if_end_is_not_far_enough_ahead() { }); } -#[test] -fn it_does_not_allow_zero_amounts_in_buy_complete_set() { - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..1, - ScoringRule::Lmsr, - ); - assert_noop!( - PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 0), - Error::::ZeroAmount - ); - }); -} - -#[test] -fn it_does_not_allow_buying_complete_sets_with_insufficient_balance() { - let test = |base_asset: Asset| { - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..1, - ScoringRule::Lmsr, - ); - assert_noop!( - PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 10000 * BASE), - Error::::NotEnoughBalance - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - #[test] fn it_allows_to_sell_a_complete_set() { let test = |base_asset: Asset| { diff --git a/zrml/prediction-markets/src/tests/buy_complete_set.rs b/zrml/prediction-markets/src/tests/buy_complete_set.rs new file mode 100644 index 000000000..081a12d54 --- /dev/null +++ b/zrml/prediction-markets/src/tests/buy_complete_set.rs @@ -0,0 +1,159 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; +use test_case::test_case; + +#[test] +fn it_allows_to_buy_a_complete_set() { + let test = |base_asset: Asset| { + frame_system::Pallet::::set_block_number(1); + // Creates a permissionless market. + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..2, + ScoringRule::Lmsr, + ); + + // Allows someone to generate a complete set + assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); + + let market = MarketCommons::market(&0).unwrap(); + + // Check the outcome balances + let assets = PredictionMarkets::outcome_assets(0, &market); + for asset in assets.iter() { + let bal = Tokens::free_balance(*asset, &BOB); + assert_eq!(bal, CENT); + } + + let market_account = PredictionMarkets::market_account(0); + let bal = AssetManager::free_balance(base_asset, &BOB); + assert_eq!(bal, 1_000 * BASE - CENT); + + let market_bal = AssetManager::free_balance(base_asset, &market_account); + assert_eq!(market_bal, CENT); + System::assert_last_event(Event::BoughtCompleteSet(0, CENT, BOB).into()); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn it_does_not_allow_to_buy_a_complete_set_on_pending_advised_market() { + ExtBuilder::default().build().execute_with(|| { + // Creates a permissionless market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..1, + ScoringRule::Lmsr, + ); + assert_noop!( + PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT), + Error::::MarketIsNotActive, + ); + }); +} + +#[test] +fn it_does_not_allow_zero_amounts_in_buy_complete_set() { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..1, + ScoringRule::Lmsr, + ); + assert_noop!( + PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 0), + Error::::ZeroAmount + ); + }); +} + +#[test] +fn it_does_not_allow_buying_complete_sets_with_insufficient_balance() { + let test = |base_asset: Asset| { + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..1, + ScoringRule::Lmsr, + ); + assert_noop!( + PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 10000 * BASE), + Error::::NotEnoughBalance + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test_case(MarketStatus::Proposed)] +#[test_case(MarketStatus::Closed)] +#[test_case(MarketStatus::Reported)] +#[test_case(MarketStatus::Disputed)] +#[test_case(MarketStatus::Resolved)] +fn buy_complete_set_fails_if_market_is_not_active(status: MarketStatus) { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..2, + ScoringRule::Lmsr, + ); + let market_id = 0; + assert_ok!(MarketCommons::mutate_market(&market_id, |market| { + market.status = status; + Ok(()) + })); + assert_noop!( + PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(FRED), market_id, 1), + Error::::MarketIsNotActive, + ); + }); +} + +#[test_case(ScoringRule::Parimutuel)] +fn buy_complete_set_fails_if_market_has_wrong_scoring_rule(scoring_rule: ScoringRule) { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..2, + scoring_rule, + ); + let market_id = 0; + assert_noop!( + PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(FRED), market_id, 1), + Error::::InvalidScoringRule, + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs new file mode 100644 index 000000000..202dd6ec2 --- /dev/null +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -0,0 +1,71 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(all(feature = "mock", test))] + +mod buy_complete_set; + +use crate::{mock::*, Config, Error, Event}; +use core::ops::Range; +use frame_support::{assert_noop, assert_ok}; +use orml_traits::MultiCurrency; +use sp_arithmetic::Perbill; +use zeitgeist_primitives::{ + constants::mock::{BASE, CENT}, + types::{ + Asset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketId, MarketPeriod, + MarketStatus, MarketType, MultiHash, ScoringRule, + }, +}; +use zrml_market_commons::MarketCommonsPalletApi; + +fn get_deadlines() -> Deadlines<::BlockNumber> { + Deadlines { + grace_period: 1_u32.into(), + oracle_duration: ::MinOracleDuration::get(), + dispute_duration: ::MinDisputeDuration::get(), + } +} + +fn gen_metadata(byte: u8) -> MultiHash { + let mut metadata = [byte; 50]; + metadata[0] = 0x15; + metadata[1] = 0x30; + MultiHash::Sha3_384(metadata) +} + +fn simple_create_categorical_market( + base_asset: Asset, + creation: MarketCreation, + period: Range, + scoring_rule: ScoringRule, +) { + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(period), + get_deadlines(), + gen_metadata(2), + creation, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::SimpleDisputes), + scoring_rule + )); +} From 8908952f637faac2db8d640cc89baae73a7fe3b9 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 11 Jan 2024 14:10:06 +0100 Subject: [PATCH 03/32] Clean up `buy_complete_set` tests --- zrml/prediction-markets/src/lib.rs | 48 +++++++--------- .../src/tests/buy_complete_set.rs | 57 +++++++------------ zrml/prediction-markets/src/tests/mod.rs | 8 +-- 3 files changed, 46 insertions(+), 67 deletions(-) diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 603542fb7..f38d5b00a 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -84,26 +84,27 @@ mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(8); const LOG_TARGET: &str = "runtime::zrml-prediction-markets"; - pub(crate) type BalanceOf = ::Balance; pub(crate) type AccountIdOf = ::AccountId; - pub(crate) type NegativeImbalanceOf = - <::Currency as Currency>>::NegativeImbalance; - pub(crate) type TimeFrame = u64; + pub(crate) type AssetOf = Asset>; + pub(crate) type BalanceOf = ::Balance; + pub(crate) type CacheSize = ConstU32<64>; + pub(crate) type EditReason = BoundedVec::MaxEditReasonLen>; + pub(crate) type InitialItemOf = InitialItem, BalanceOf>; pub(crate) type MarketIdOf = ::MarketId; - pub(crate) type MomentOf = - <::Timestamp as frame_support::traits::Time>::Moment; - pub type MarketOf = Market< + pub(crate) type MarketOf = Market< AccountIdOf, BalanceOf, ::BlockNumber, MomentOf, - Asset>, + AssetOf, >; + pub(crate) type MomentOf = + <::Timestamp as frame_support::traits::Time>::Moment; + pub(crate) type NegativeImbalanceOf = + <::Currency as Currency>>::NegativeImbalance; + pub(crate) type RejectReason = BoundedVec::MaxRejectReasonLen>; pub(crate) type ReportOf = Report, ::BlockNumber>; - pub type CacheSize = ConstU32<64>; - pub type EditReason = BoundedVec::MaxEditReasonLen>; - pub type RejectReason = BoundedVec::MaxRejectReasonLen>; - type InitialItemOf = InitialItem, BalanceOf>; + pub(crate) type TimeFrame = u64; macro_rules! impl_unreserve_bond { ($fn_name:ident, $bond_type:ident) => { @@ -588,7 +589,7 @@ mod pallet { #[transactional] pub fn create_market( origin: OriginFor, - base_asset: Asset>, + base_asset: AssetOf, creator_fee: Perbill, oracle: T::AccountId, period: MarketPeriod>, @@ -640,7 +641,7 @@ mod pallet { #[transactional] pub fn edit_market( origin: OriginFor, - base_asset: Asset>, + base_asset: AssetOf, market_id: MarketIdOf, oracle: T::AccountId, period: MarketPeriod>, @@ -1059,7 +1060,7 @@ mod pallet { #[pallet::call_index(17)] pub fn create_market_and_deploy_pool( origin: OriginFor, - base_asset: Asset>, + base_asset: AssetOf, creator_fee: Perbill, oracle: T::AccountId, period: MarketPeriod>, @@ -1842,13 +1843,7 @@ mod pallet { SoldCompleteSet(MarketIdOf, BalanceOf, AccountIdOf), /// An amount of winning outcomes have been redeemed. /// \[market_id, currency_id, amount_redeemed, payout, who\] - TokensRedeemed( - MarketIdOf, - Asset>, - BalanceOf, - BalanceOf, - AccountIdOf, - ), + TokensRedeemed(MarketIdOf, AssetOf, BalanceOf, BalanceOf, AccountIdOf), /// The global dispute was started. \[market_id\] GlobalDisputeStarted(MarketIdOf), } @@ -2059,7 +2054,7 @@ mod pallet { #[require_transactional] fn do_create_market( who: T::AccountId, - base_asset: Asset>, + base_asset: AssetOf, creator_fee: Perbill, oracle: T::AccountId, period: MarketPeriod>, @@ -2117,10 +2112,7 @@ mod pallet { Ok((ids_amount, market_id)) } - pub fn outcome_assets( - market_id: MarketIdOf, - market: &MarketOf, - ) -> Vec>> { + pub fn outcome_assets(market_id: MarketIdOf, market: &MarketOf) -> Vec> { match market.market_type { MarketType::Categorical(categories) => { let mut assets = Vec::new(); @@ -2837,7 +2829,7 @@ mod pallet { } fn construct_market( - base_asset: Asset>, + base_asset: AssetOf, creator: T::AccountId, creator_fee: Perbill, oracle: T::AccountId, diff --git a/zrml/prediction-markets/src/tests/buy_complete_set.rs b/zrml/prediction-markets/src/tests/buy_complete_set.rs index 081a12d54..36b302230 100644 --- a/zrml/prediction-markets/src/tests/buy_complete_set.rs +++ b/zrml/prediction-markets/src/tests/buy_complete_set.rs @@ -20,10 +20,9 @@ use super::*; use test_case::test_case; #[test] -fn it_allows_to_buy_a_complete_set() { - let test = |base_asset: Asset| { +fn buy_complete_set_works() { + let test = |base_asset: AssetOf| { frame_system::Pallet::::set_block_number(1); - // Creates a permissionless market. simple_create_categorical_market( base_asset, MarketCreation::Permissionless, @@ -31,25 +30,30 @@ fn it_allows_to_buy_a_complete_set() { ScoringRule::Lmsr, ); - // Allows someone to generate a complete set - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); + let market_id = 0; + let who = BOB; + let amount = CENT; + assert_ok!(PredictionMarkets::buy_complete_set( + RuntimeOrigin::signed(who), + market_id, + amount + )); - let market = MarketCommons::market(&0).unwrap(); + let market = MarketCommons::market(&market_id).unwrap(); - // Check the outcome balances - let assets = PredictionMarkets::outcome_assets(0, &market); + let assets = PredictionMarkets::outcome_assets(market_id, &market); for asset in assets.iter() { - let bal = Tokens::free_balance(*asset, &BOB); - assert_eq!(bal, CENT); + let bal = Tokens::free_balance(*asset, &who); + assert_eq!(bal, amount); } - let market_account = PredictionMarkets::market_account(0); - let bal = AssetManager::free_balance(base_asset, &BOB); - assert_eq!(bal, 1_000 * BASE - CENT); + let bal = AssetManager::free_balance(base_asset, &who); + assert_eq!(bal, 1_000 * BASE - amount); + let market_account = PredictionMarkets::market_account(market_id); let market_bal = AssetManager::free_balance(base_asset, &market_account); - assert_eq!(market_bal, CENT); - System::assert_last_event(Event::BoughtCompleteSet(0, CENT, BOB).into()); + assert_eq!(market_bal, amount); + System::assert_last_event(Event::BoughtCompleteSet(market_id, amount, who).into()); }; ExtBuilder::default().build().execute_with(|| { test(Asset::Ztg); @@ -61,24 +65,7 @@ fn it_allows_to_buy_a_complete_set() { } #[test] -fn it_does_not_allow_to_buy_a_complete_set_on_pending_advised_market() { - ExtBuilder::default().build().execute_with(|| { - // Creates a permissionless market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - assert_noop!( - PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT), - Error::::MarketIsNotActive, - ); - }); -} - -#[test] -fn it_does_not_allow_zero_amounts_in_buy_complete_set() { +fn buy_complete_fails_on_zero_amount() { ExtBuilder::default().build().execute_with(|| { simple_create_categorical_market( Asset::Ztg, @@ -94,8 +81,8 @@ fn it_does_not_allow_zero_amounts_in_buy_complete_set() { } #[test] -fn it_does_not_allow_buying_complete_sets_with_insufficient_balance() { - let test = |base_asset: Asset| { +fn buy_complete_set_fails_on_insufficient_balance() { + let test = |base_asset: AssetOf| { simple_create_categorical_market( base_asset, MarketCreation::Permissionless, diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 202dd6ec2..b62b7602a 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -20,7 +20,7 @@ mod buy_complete_set; -use crate::{mock::*, Config, Error, Event}; +use crate::{mock::*, AssetOf, Config, Error, Event}; use core::ops::Range; use frame_support::{assert_noop, assert_ok}; use orml_traits::MultiCurrency; @@ -28,8 +28,8 @@ use sp_arithmetic::Perbill; use zeitgeist_primitives::{ constants::mock::{BASE, CENT}, types::{ - Asset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketId, MarketPeriod, - MarketStatus, MarketType, MultiHash, ScoringRule, + Asset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, + MarketType, MultiHash, ScoringRule, }, }; use zrml_market_commons::MarketCommonsPalletApi; @@ -50,7 +50,7 @@ fn gen_metadata(byte: u8) -> MultiHash { } fn simple_create_categorical_market( - base_asset: Asset, + base_asset: AssetOf, creation: MarketCreation, period: Range, scoring_rule: ScoringRule, From 3bd0f73a9ab7e2473f55c127f0c4bd92ddf58f4e Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 11 Jan 2024 14:12:24 +0100 Subject: [PATCH 04/32] Extract `sell_complete_set_tests` --- zrml/prediction-markets/src/old_tests.rs | 104 --------------- zrml/prediction-markets/src/tests/mod.rs | 1 + .../src/tests/sell_complete_set.rs | 124 ++++++++++++++++++ 3 files changed, 125 insertions(+), 104 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/sell_complete_set.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index abfaa6398..4273e6c5a 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -1402,110 +1402,6 @@ fn create_categorical_market_fails_if_end_is_not_far_enough_ahead() { }); } -#[test] -fn it_allows_to_sell_a_complete_set() { - let test = |base_asset: Asset| { - frame_system::Pallet::::set_block_number(1); - // Creates a permissionless market. - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..2, - ScoringRule::Lmsr, - ); - - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); - - assert_ok!(PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); - - let market = MarketCommons::market(&0).unwrap(); - - // Check the outcome balances - let assets = PredictionMarkets::outcome_assets(0, &market); - for asset in assets.iter() { - let bal = Tokens::free_balance(*asset, &BOB); - assert_eq!(bal, 0); - } - - // also check native balance - let bal = Balances::free_balance(BOB); - assert_eq!(bal, 1_000 * BASE); - - System::assert_last_event(Event::SoldCompleteSet(0, CENT, BOB).into()); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn it_does_not_allow_zero_amounts_in_sell_complete_set() { - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..1, - ScoringRule::Lmsr, - ); - assert_noop!( - PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, 0), - Error::::ZeroAmount - ); - }); -} - -#[test] -fn it_does_not_allow_to_sell_complete_sets_with_insufficient_balance() { - let test = |base_asset: Asset| { - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..1, - ScoringRule::Lmsr, - ); - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 2 * CENT)); - assert_eq!(AssetManager::slash(Asset::CategoricalOutcome(0, 1), &BOB, CENT), 0); - assert_noop!( - PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, 2 * CENT), - Error::::InsufficientShareBalance - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test_case(ScoringRule::Parimutuel; "parimutuel")] -fn sell_complete_set_fails_if_market_has_wrong_scoring_rule(scoring_rule: ScoringRule) { - let test = |base_asset: Asset| { - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..1, - scoring_rule, - ); - assert_noop!( - PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, 2 * CENT), - Error::::InvalidScoringRule - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - #[test] fn it_allows_to_report_the_outcome_of_a_market() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index b62b7602a..bee5807f1 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -19,6 +19,7 @@ #![cfg(all(feature = "mock", test))] mod buy_complete_set; +mod sell_complete_set; use crate::{mock::*, AssetOf, Config, Error, Event}; use core::ops::Range; diff --git a/zrml/prediction-markets/src/tests/sell_complete_set.rs b/zrml/prediction-markets/src/tests/sell_complete_set.rs new file mode 100644 index 000000000..78574fe9c --- /dev/null +++ b/zrml/prediction-markets/src/tests/sell_complete_set.rs @@ -0,0 +1,124 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; +use test_case::test_case; + +#[test] +fn it_allows_to_sell_a_complete_set() { + let test = |base_asset: AssetOf| { + frame_system::Pallet::::set_block_number(1); + // Creates a permissionless market. + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..2, + ScoringRule::Lmsr, + ); + + assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); + + assert_ok!(PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); + + let market = MarketCommons::market(&0).unwrap(); + + // Check the outcome balances + let assets = PredictionMarkets::outcome_assets(0, &market); + for asset in assets.iter() { + let bal = Tokens::free_balance(*asset, &BOB); + assert_eq!(bal, 0); + } + + // also check native balance + let bal = Balances::free_balance(BOB); + assert_eq!(bal, 1_000 * BASE); + + System::assert_last_event(Event::SoldCompleteSet(0, CENT, BOB).into()); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn it_does_not_allow_zero_amounts_in_sell_complete_set() { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..1, + ScoringRule::Lmsr, + ); + assert_noop!( + PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, 0), + Error::::ZeroAmount + ); + }); +} + +#[test] +fn it_does_not_allow_to_sell_complete_sets_with_insufficient_balance() { + let test = |base_asset: AssetOf| { + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..1, + ScoringRule::Lmsr, + ); + assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 2 * CENT)); + assert_eq!(AssetManager::slash(Asset::CategoricalOutcome(0, 1), &BOB, CENT), 0); + assert_noop!( + PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, 2 * CENT), + Error::::InsufficientShareBalance + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test_case(ScoringRule::Parimutuel; "parimutuel")] +fn sell_complete_set_fails_if_market_has_wrong_scoring_rule(scoring_rule: ScoringRule) { + let test = |base_asset: AssetOf| { + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..1, + scoring_rule, + ); + assert_noop!( + PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, 2 * CENT), + Error::::InvalidScoringRule + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} From 4be04841275ccd4b439a7c3f2e045f5a4637af13 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 11 Jan 2024 18:16:11 +0100 Subject: [PATCH 05/32] Clean up `sell_complete_set` tests --- .../src/tests/sell_complete_set.rs | 59 ++++++++++++------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/zrml/prediction-markets/src/tests/sell_complete_set.rs b/zrml/prediction-markets/src/tests/sell_complete_set.rs index 78574fe9c..9b080fd4e 100644 --- a/zrml/prediction-markets/src/tests/sell_complete_set.rs +++ b/zrml/prediction-markets/src/tests/sell_complete_set.rs @@ -19,36 +19,46 @@ use super::*; use test_case::test_case; -#[test] -fn it_allows_to_sell_a_complete_set() { +#[test_case(ScoringRule::Lmsr)] +#[test_case(ScoringRule::Orderbook)] +fn sell_complete_set_works(scoring_rule: ScoringRule) { let test = |base_asset: AssetOf| { frame_system::Pallet::::set_block_number(1); - // Creates a permissionless market. simple_create_categorical_market( base_asset, MarketCreation::Permissionless, 0..2, - ScoringRule::Lmsr, + scoring_rule, ); + let market_id = 0; + let buy_amount = 5 * CENT; + let sell_amount = 3 * CENT; + let expected_amount = 2 * CENT; + let who = BOB; - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); + assert_ok!(PredictionMarkets::buy_complete_set( + RuntimeOrigin::signed(who), + market_id, + buy_amount + )); - assert_ok!(PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); + assert_ok!(PredictionMarkets::sell_complete_set( + RuntimeOrigin::signed(who), + market_id, + sell_amount + )); - let market = MarketCommons::market(&0).unwrap(); - - // Check the outcome balances - let assets = PredictionMarkets::outcome_assets(0, &market); + let market = MarketCommons::market(&market_id).unwrap(); + let assets = PredictionMarkets::outcome_assets(market_id, &market); for asset in assets.iter() { - let bal = Tokens::free_balance(*asset, &BOB); - assert_eq!(bal, 0); + let bal = AssetManager::free_balance(*asset, &who); + assert_eq!(bal, expected_amount); } - // also check native balance - let bal = Balances::free_balance(BOB); - assert_eq!(bal, 1_000 * BASE); + let bal = AssetManager::free_balance(base_asset, &who); + assert_eq!(bal, 1_000 * BASE - expected_amount); - System::assert_last_event(Event::SoldCompleteSet(0, CENT, BOB).into()); + System::assert_last_event(Event::SoldCompleteSet(market_id, sell_amount, who).into()); }; ExtBuilder::default().build().execute_with(|| { test(Asset::Ztg); @@ -60,7 +70,7 @@ fn it_allows_to_sell_a_complete_set() { } #[test] -fn it_does_not_allow_zero_amounts_in_sell_complete_set() { +fn sell_complete_set_fails_on_zero_amount() { ExtBuilder::default().build().execute_with(|| { simple_create_categorical_market( Asset::Ztg, @@ -76,7 +86,7 @@ fn it_does_not_allow_zero_amounts_in_sell_complete_set() { } #[test] -fn it_does_not_allow_to_sell_complete_sets_with_insufficient_balance() { +fn sell_complete_set_fails_on_insufficient_share_balance() { let test = |base_asset: AssetOf| { simple_create_categorical_market( base_asset, @@ -84,10 +94,17 @@ fn it_does_not_allow_to_sell_complete_sets_with_insufficient_balance() { 0..1, ScoringRule::Lmsr, ); - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 2 * CENT)); - assert_eq!(AssetManager::slash(Asset::CategoricalOutcome(0, 1), &BOB, CENT), 0); + let market_id = 0; + let amount = 2 * CENT; + let who = BOB; + assert_ok!(PredictionMarkets::buy_complete_set( + RuntimeOrigin::signed(who), + market_id, + amount + )); + assert_eq!(AssetManager::slash(Asset::CategoricalOutcome(market_id, 1), &who, 1), 0); assert_noop!( - PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, 2 * CENT), + PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(who), market_id, amount), Error::::InsufficientShareBalance ); }; From 62c591550f16f4358be511e065160b32d177536d Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 11 Jan 2024 18:30:07 +0100 Subject: [PATCH 06/32] Extract `admin_move_market_to_closed` tests --- zrml/prediction-markets/src/old_tests.rs | 144 --------------- .../src/tests/admin_move_market_to_closed.rs | 166 ++++++++++++++++++ zrml/prediction-markets/src/tests/mod.rs | 1 + 3 files changed, 167 insertions(+), 144 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index 4273e6c5a..ce6441f7e 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -141,150 +141,6 @@ fn simple_create_scalar_market( )); } -#[test] -fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_blocknumber() { - ExtBuilder::default().build().execute_with(|| { - run_blocks(7); - let now = frame_system::Pallet::::block_number(); - let end = 42; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - now..end, - ScoringRule::Lmsr, - ); - run_blocks(3); - let market_id = 0; - assert_ok!(PredictionMarkets::admin_move_market_to_closed( - RuntimeOrigin::signed(SUDO), - market_id - )); - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - let new_end = now + 3; - assert_eq!(market.period, MarketPeriod::Block(now..new_end)); - assert_ne!(new_end, end); - System::assert_last_event(Event::MarketClosed(market_id).into()); - }); -} - -#[test] -fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_timestamp() { - ExtBuilder::default().build().execute_with(|| { - let start_block = 7; - set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as u64); - run_blocks(start_block); - let start = >::now(); - - let end = start + 42.saturated_into::() * MILLISECS_PER_BLOCK as u64; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(start..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - )); - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.period, MarketPeriod::Timestamp(start..end)); - - let shift_blocks = 3; - let shift = shift_blocks * MILLISECS_PER_BLOCK as u64; - // millisecs per block is substracted inside the function - set_timestamp_for_on_initialize(start + shift + MILLISECS_PER_BLOCK as u64); - run_blocks(shift_blocks); - - assert_ok!(PredictionMarkets::admin_move_market_to_closed( - RuntimeOrigin::signed(SUDO), - market_id - )); - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - let new_end = start + shift; - assert_eq!(market.period, MarketPeriod::Timestamp(start..new_end)); - assert_ne!(new_end, end); - System::assert_last_event(Event::MarketClosed(market_id).into()); - }); -} - -#[test] -fn admin_move_market_to_closed_fails_if_market_does_not_exist() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - PredictionMarkets::admin_move_market_to_closed(RuntimeOrigin::signed(SUDO), 0), - zrml_market_commons::Error::::MarketDoesNotExist - ); - }); -} - -#[test_case(MarketStatus::Closed; "closed")] -#[test_case(MarketStatus::Reported; "reported")] -#[test_case(MarketStatus::Disputed; "disputed")] -#[test_case(MarketStatus::Resolved; "resolved")] -#[test_case(MarketStatus::Proposed; "proposed")] -fn admin_move_market_to_closed_fails_if_market_is_not_active(market_status: MarketStatus) { - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..1, - ScoringRule::Lmsr, - ); - let market_id = 0; - assert_ok!(MarketCommons::mutate_market(&market_id, |market| { - market.status = market_status; - Ok(()) - })); - assert_noop!( - PredictionMarkets::admin_move_market_to_closed(RuntimeOrigin::signed(SUDO), market_id), - Error::::MarketIsNotActive, - ); - }); -} - -#[test] -fn admin_move_market_to_closed_correctly_clears_auto_close_blocks() { - ExtBuilder::default().build().execute_with(|| { - let category_count = 3; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Block(22..66), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Block(33..66), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - assert_ok!(PredictionMarkets::admin_move_market_to_closed(RuntimeOrigin::signed(SUDO), 0)); - - let auto_close = MarketIdsPerCloseBlock::::get(66).into_inner(); - assert_eq!(auto_close, vec![1]); - }); -} - #[test_case(654..=321; "empty range")] #[test_case(555..=555; "one element as range")] fn create_scalar_market_fails_on_invalid_range(range: RangeInclusive) { diff --git a/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs b/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs new file mode 100644 index 000000000..cff423974 --- /dev/null +++ b/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs @@ -0,0 +1,166 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; +use crate::MarketIdsPerCloseBlock; +use test_case::test_case; +use zeitgeist_primitives::constants::MILLISECS_PER_BLOCK; + +#[test] +fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_blocknumber() { + ExtBuilder::default().build().execute_with(|| { + run_blocks(7); + let now = frame_system::Pallet::::block_number(); + let end = 42; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + now..end, + ScoringRule::Lmsr, + ); + run_blocks(3); + let market_id = 0; + assert_ok!(PredictionMarkets::admin_move_market_to_closed( + RuntimeOrigin::signed(SUDO), + market_id + )); + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + let new_end = now + 3; + assert_eq!(market.period, MarketPeriod::Block(now..new_end)); + assert_ne!(new_end, end); + System::assert_last_event(Event::MarketClosed(market_id).into()); + }); +} + +#[test] +fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_timestamp() { + ExtBuilder::default().build().execute_with(|| { + let start_block = 7; + set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as u64); + run_blocks(start_block); + let start = >::now(); + + let end = start + 42u64 * (MILLISECS_PER_BLOCK as u64); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(start..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + )); + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.period, MarketPeriod::Timestamp(start..end)); + + let shift_blocks = 3; + let shift = shift_blocks * MILLISECS_PER_BLOCK as u64; + // millisecs per block is substracted inside the function + set_timestamp_for_on_initialize(start + shift + MILLISECS_PER_BLOCK as u64); + run_blocks(shift_blocks); + + assert_ok!(PredictionMarkets::admin_move_market_to_closed( + RuntimeOrigin::signed(SUDO), + market_id + )); + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + let new_end = start + shift; + assert_eq!(market.period, MarketPeriod::Timestamp(start..new_end)); + assert_ne!(new_end, end); + System::assert_last_event(Event::MarketClosed(market_id).into()); + }); +} + +#[test] +fn admin_move_market_to_closed_fails_if_market_does_not_exist() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + PredictionMarkets::admin_move_market_to_closed(RuntimeOrigin::signed(SUDO), 0), + zrml_market_commons::Error::::MarketDoesNotExist + ); + }); +} + +#[test_case(MarketStatus::Closed; "closed")] +#[test_case(MarketStatus::Reported; "reported")] +#[test_case(MarketStatus::Disputed; "disputed")] +#[test_case(MarketStatus::Resolved; "resolved")] +#[test_case(MarketStatus::Proposed; "proposed")] +fn admin_move_market_to_closed_fails_if_market_is_not_active(market_status: MarketStatus) { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..1, + ScoringRule::Lmsr, + ); + let market_id = 0; + assert_ok!(MarketCommons::mutate_market(&market_id, |market| { + market.status = market_status; + Ok(()) + })); + assert_noop!( + PredictionMarkets::admin_move_market_to_closed(RuntimeOrigin::signed(SUDO), market_id), + Error::::MarketIsNotActive, + ); + }); +} + +#[test] +fn admin_move_market_to_closed_correctly_clears_auto_close_blocks() { + ExtBuilder::default().build().execute_with(|| { + let category_count = 3; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Block(22..66), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Block(33..66), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::admin_move_market_to_closed(RuntimeOrigin::signed(SUDO), 0)); + + let auto_close = MarketIdsPerCloseBlock::::get(66).into_inner(); + assert_eq!(auto_close, vec![1]); + }); +} diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index bee5807f1..f2cae52c1 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -18,6 +18,7 @@ #![cfg(all(feature = "mock", test))] +mod admin_move_market_to_closed; mod buy_complete_set; mod sell_complete_set; From edf6aeab2644bf465627b615a729cc55c89e4b51 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 11 Jan 2024 22:09:10 +0100 Subject: [PATCH 07/32] Extract `create_market` tests --- zrml/prediction-markets/src/old_tests.rs | 584 +---------------- .../src/tests/create_market.rs | 604 ++++++++++++++++++ zrml/prediction-markets/src/tests/mod.rs | 1 + 3 files changed, 607 insertions(+), 582 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/create_market.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index ce6441f7e..d0d815b49 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -26,7 +26,7 @@ use crate::{ MarketIdsPerCloseTimeFrame, MarketIdsPerDisputeBlock, MarketIdsPerReportBlock, TimeFrame, }; use alloc::collections::BTreeMap; -use core::ops::{Range, RangeInclusive}; +use core::ops::Range; use frame_support::{ assert_noop, assert_ok, dispatch::DispatchError, @@ -47,7 +47,7 @@ use zeitgeist_primitives::{ MinJurorStake, OutcomeBond, OutcomeFactor, OutsiderBond, BASE, CENT, MILLISECS_PER_BLOCK, }, types::{ - AccountIdTest, Asset, Balance, BlockNumber, Bond, Deadlines, MarketBonds, MarketCreation, + AccountIdTest, Asset, Balance, Bond, Deadlines, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketId, MarketPeriod, MarketStatus, MarketType, Moment, MultiHash, OutcomeReport, Report, ScalarPosition, ScoringRule, }, @@ -141,228 +141,6 @@ fn simple_create_scalar_market( )); } -#[test_case(654..=321; "empty range")] -#[test_case(555..=555; "one element as range")] -fn create_scalar_market_fails_on_invalid_range(range: RangeInclusive) { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Scalar(range), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - ), - Error::::InvalidOutcomeRange - ); - }); -} - -#[test] -fn create_market_fails_on_min_dispute_period() { - ExtBuilder::default().build().execute_with(|| { - let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get(), - oracle_duration: ::MaxOracleDuration::get(), - dispute_duration: ::MinDisputeDuration::get() - 1, - }; - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - deadlines, - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - ), - Error::::DisputeDurationSmallerThanMinDisputeDuration - ); - }); -} - -#[test] -fn create_market_fails_on_min_oracle_duration() { - ExtBuilder::default().build().execute_with(|| { - let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get(), - oracle_duration: ::MinOracleDuration::get() - 1, - dispute_duration: ::MinDisputeDuration::get(), - }; - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - deadlines, - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - ), - Error::::OracleDurationSmallerThanMinOracleDuration - ); - }); -} - -#[test] -fn create_market_fails_on_max_dispute_period() { - ExtBuilder::default().build().execute_with(|| { - let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get(), - oracle_duration: ::MaxOracleDuration::get(), - dispute_duration: ::MaxDisputeDuration::get() + 1, - }; - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - deadlines, - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - ), - Error::::DisputeDurationGreaterThanMaxDisputeDuration - ); - }); -} - -#[test] -fn create_market_fails_on_max_grace_period() { - ExtBuilder::default().build().execute_with(|| { - let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get() + 1, - oracle_duration: ::MaxOracleDuration::get(), - dispute_duration: ::MaxDisputeDuration::get(), - }; - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - deadlines, - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - ), - Error::::GracePeriodGreaterThanMaxGracePeriod - ); - }); -} - -#[test] -fn create_market_fails_on_max_oracle_duration() { - ExtBuilder::default().build().execute_with(|| { - let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get(), - oracle_duration: ::MaxOracleDuration::get() + 1, - dispute_duration: ::MaxDisputeDuration::get(), - }; - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - deadlines, - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - ), - Error::::OracleDurationGreaterThanMaxOracleDuration - ); - }); -} - -#[cfg(feature = "parachain")] -#[test] -fn create_market_with_foreign_assets() { - ExtBuilder::default().build().execute_with(|| { - let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get(), - oracle_duration: ::MaxOracleDuration::get(), - dispute_duration: ::MaxDisputeDuration::get(), - }; - - // As per Mock asset_registry genesis ForeignAsset(420) has allow_as_base_asset set to false. - - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(420), - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - deadlines, - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - ), - Error::::InvalidBaseAsset, - ); - // As per Mock asset_registry genesis ForeignAsset(50) is not registered in asset_registry. - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(50), - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - deadlines, - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - ), - Error::::UnregisteredForeignAsset, - ); - // As per Mock asset_registry genesis ForeignAsset(100) has allow_as_base_asset set to true. - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(100), - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - deadlines, - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.base_asset, Asset::ForeignAsset(100)); - }); -} - #[test] fn admin_move_market_to_resolved_resolves_reported_market() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. @@ -526,50 +304,6 @@ fn create_scalar_market_deposits_the_correct_event() { }); } -#[test] -fn it_does_not_create_market_with_too_few_categories() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..100), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(::MinCategories::get() - 1), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - ), - Error::::NotEnoughCategories - ); - }); -} - -#[test] -fn it_does_not_create_market_with_too_many_categories() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..100), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(::MaxCategories::get() + 1), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - ), - Error::::TooManyCategories - ); - }); -} - #[test] fn it_allows_advisory_origin_to_approve_markets() { ExtBuilder::default().build().execute_with(|| { @@ -1163,101 +897,6 @@ fn on_initialize_skips_the_genesis_block() { }); } -#[test] -fn create_categorical_market_fails_if_market_begin_is_equal_to_end() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(3..3), - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - ), - Error::::InvalidMarketPeriod, - ); - }); -} - -#[test_case(MarketPeriod::Block(2..1); "block start greater than end")] -#[test_case(MarketPeriod::Block(3..3); "block start equal to end")] -#[test_case(MarketPeriod::Timestamp(2..1); "timestamp start greater than end")] -#[test_case(MarketPeriod::Timestamp(3..3); "timestamp start equal to end")] -#[test_case( - MarketPeriod::Timestamp(0..(MILLISECS_PER_BLOCK - 1).into()); - "range shorter than block time" -)] -fn create_categorical_market_fails_if_market_period_is_invalid( - period: MarketPeriod, -) { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - period, - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - ), - Error::::InvalidMarketPeriod, - ); - }); -} - -#[test] -fn create_categorical_market_fails_if_end_is_not_far_enough_ahead() { - ExtBuilder::default().build().execute_with(|| { - let end_block = 33; - run_to_block(end_block); - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end_block), - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - ), - Error::::InvalidMarketPeriod, - ); - - let end_time = MILLISECS_PER_BLOCK as u64 / 2; - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(0..end_time), - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - ), - Error::::InvalidMarketPeriod, - ); - }); -} - #[test] fn it_allows_to_report_the_outcome_of_a_market() { ExtBuilder::default().build().execute_with(|| { @@ -4727,199 +4366,6 @@ fn report_fails_if_reporter_is_not_the_oracle() { }); } -#[test] -fn create_market_succeeds_if_market_duration_is_maximal_in_blocks() { - ExtBuilder::default().build().execute_with(|| { - let now = 1; - frame_system::Pallet::::set_block_number(now); - let start = 5; - let end = now + ::MaxMarketLifetime::get(); - assert!( - end > start, - "Test failed due to misconfiguration: `MaxMarketLifetime` is too small" - ); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(start..end), - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - }); -} - -#[test] -fn create_market_suceeds_if_market_duration_is_maximal_in_moments() { - ExtBuilder::default().build().execute_with(|| { - let now = 12_001u64; - Timestamp::set_timestamp(now); - let start = 5 * MILLISECS_PER_BLOCK as u64; - let end = - now + ::MaxMarketLifetime::get() * (MILLISECS_PER_BLOCK as u64); - assert!( - end > start, - "Test failed due to misconfiguration: `MaxMarketLifetime` is too small" - ); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(start..end), - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - }); -} - -#[test] -fn create_market_fails_if_market_duration_is_too_long_in_blocks() { - ExtBuilder::default().build().execute_with(|| { - let now = 1; - frame_system::Pallet::::set_block_number(now); - let start = 5; - let end = now + ::MaxMarketLifetime::get() + 1; - assert!( - end > start, - "Test failed due to misconfiguration: `MaxMarketLifetime` is too small" - ); - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(start..end), - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - ), - crate::Error::::MarketDurationTooLong, - ); - }); -} - -#[test] -fn create_market_fails_if_market_duration_is_too_long_in_moments() { - ExtBuilder::default().build().execute_with(|| { - let now = 12_001u64; - Timestamp::set_timestamp(now); - let start = 5 * MILLISECS_PER_BLOCK as u64; - let end = now - + (::MaxMarketLifetime::get() + 1) * (MILLISECS_PER_BLOCK as u64); - assert!( - end > start, - "Test failed due to misconfiguration: `MaxMarketLifetime` is too small" - ); - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(start..end), - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - ), - crate::Error::::MarketDurationTooLong, - ); - }); -} - -#[test_case( - MarketCreation::Advised, - ScoringRule::Lmsr, - MarketStatus::Proposed, - MarketBonds { - creation: Some(Bond::new(ALICE, ::AdvisoryBond::get())), - oracle: Some(Bond::new(ALICE, ::OracleBond::get())), - outsider: None, - dispute: None, - close_dispute: None, - close_request: None, - } -)] -#[test_case( - MarketCreation::Permissionless, - ScoringRule::Lmsr, - MarketStatus::Active, - MarketBonds { - creation: Some(Bond::new(ALICE, ::ValidityBond::get())), - oracle: Some(Bond::new(ALICE, ::OracleBond::get())), - outsider: None, - dispute: None, - close_dispute: None, - close_request: None, - } -)] -fn create_market_sets_the_correct_market_parameters_and_reserves_the_correct_amount( - creation: MarketCreation, - scoring_rule: ScoringRule, - status: MarketStatus, - bonds: MarketBonds, -) { - ExtBuilder::default().build().execute_with(|| { - let creator = ALICE; - let oracle = BOB; - let period = MarketPeriod::Block(1..2); - let deadlines = Deadlines { - grace_period: 1, - oracle_duration: ::MinOracleDuration::get() + 2, - dispute_duration: ::MinDisputeDuration::get() + 3, - }; - let metadata = gen_metadata(0x99); - let MultiHash::Sha3_384(multihash) = metadata; - let market_type = MarketType::Categorical(7); - let dispute_mechanism = Some(MarketDisputeMechanism::Authorized); - let creator_fee = Perbill::from_parts(1); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(creator), - Asset::Ztg, - creator_fee, - oracle, - period.clone(), - deadlines, - metadata, - creation.clone(), - market_type.clone(), - dispute_mechanism.clone(), - scoring_rule, - )); - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.creator, creator); - assert_eq!(market.creation, creation); - assert_eq!(market.creator_fee, creator_fee); - assert_eq!(market.oracle, oracle); - assert_eq!(market.metadata, multihash); - assert_eq!(market.market_type, market_type); - assert_eq!(market.period, period); - assert_eq!(market.deadlines, deadlines); - assert_eq!(market.scoring_rule, scoring_rule); - assert_eq!(market.status, status); - assert_eq!(market.report, None); - assert_eq!(market.resolved_outcome, None); - assert_eq!(market.dispute_mechanism, dispute_mechanism); - assert_eq!(market.bonds, bonds); - }); -} - #[test] fn create_market_and_deploy_pool_works() { ExtBuilder::default().build().execute_with(|| { @@ -4992,32 +4438,6 @@ fn create_market_and_deploy_pool_works() { }); } -#[test] -fn create_market_fails_on_trusted_market_with_non_zero_dispute_period() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(1..2), - Deadlines { - grace_period: 1, - oracle_duration: ::MinOracleDuration::get() + 2, - dispute_duration: ::MinDisputeDuration::get() + 3, - }, - gen_metadata(0x99), - MarketCreation::Permissionless, - MarketType::Categorical(3), - None, - ScoringRule::Lmsr, - ), - Error::::NonZeroDisputePeriodOnTrustedMarket - ); - }); -} - #[test] fn trusted_market_complete_lifecycle() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/create_market.rs b/zrml/prediction-markets/src/tests/create_market.rs new file mode 100644 index 000000000..de1f6f340 --- /dev/null +++ b/zrml/prediction-markets/src/tests/create_market.rs @@ -0,0 +1,604 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; +use test_case::test_case; + +use core::ops::RangeInclusive; +use zeitgeist_primitives::{ + constants::MILLISECS_PER_BLOCK, + types::{AccountIdTest, Balance, BlockNumber, Bond, Moment, MarketBonds}, +}; + +#[test_case(std::ops::RangeInclusive::new(7, 6); "empty range")] +#[test_case(555..=555; "one element as range")] +fn create_scalar_market_fails_on_invalid_range(range: RangeInclusive) { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Scalar(range), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + ), + Error::::InvalidOutcomeRange + ); + }); +} + +#[test] +fn create_market_fails_on_min_dispute_period() { + ExtBuilder::default().build().execute_with(|| { + let deadlines = Deadlines { + grace_period: ::MaxGracePeriod::get(), + oracle_duration: ::MaxOracleDuration::get(), + dispute_duration: ::MinDisputeDuration::get() - 1, + }; + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + deadlines, + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + ), + Error::::DisputeDurationSmallerThanMinDisputeDuration + ); + }); +} + +#[test] +fn create_market_fails_on_min_oracle_duration() { + ExtBuilder::default().build().execute_with(|| { + let deadlines = Deadlines { + grace_period: ::MaxGracePeriod::get(), + oracle_duration: ::MinOracleDuration::get() - 1, + dispute_duration: ::MinDisputeDuration::get(), + }; + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + deadlines, + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + ), + Error::::OracleDurationSmallerThanMinOracleDuration + ); + }); +} + +#[test] +fn create_market_fails_on_max_dispute_period() { + ExtBuilder::default().build().execute_with(|| { + let deadlines = Deadlines { + grace_period: ::MaxGracePeriod::get(), + oracle_duration: ::MaxOracleDuration::get(), + dispute_duration: ::MaxDisputeDuration::get() + 1, + }; + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + deadlines, + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + ), + Error::::DisputeDurationGreaterThanMaxDisputeDuration + ); + }); +} + +#[test] +fn create_market_fails_on_max_grace_period() { + ExtBuilder::default().build().execute_with(|| { + let deadlines = Deadlines { + grace_period: ::MaxGracePeriod::get() + 1, + oracle_duration: ::MaxOracleDuration::get(), + dispute_duration: ::MaxDisputeDuration::get(), + }; + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + deadlines, + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + ), + Error::::GracePeriodGreaterThanMaxGracePeriod + ); + }); +} + +#[test] +fn create_market_fails_on_max_oracle_duration() { + ExtBuilder::default().build().execute_with(|| { + let deadlines = Deadlines { + grace_period: ::MaxGracePeriod::get(), + oracle_duration: ::MaxOracleDuration::get() + 1, + dispute_duration: ::MaxDisputeDuration::get(), + }; + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + deadlines, + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + ), + Error::::OracleDurationGreaterThanMaxOracleDuration + ); + }); +} + +#[cfg(feature = "parachain")] +#[test] +fn create_market_with_foreign_assets() { + ExtBuilder::default().build().execute_with(|| { + let deadlines = Deadlines { + grace_period: ::MaxGracePeriod::get(), + oracle_duration: ::MaxOracleDuration::get(), + dispute_duration: ::MaxDisputeDuration::get(), + }; + + // As per Mock asset_registry genesis ForeignAsset(420) has allow_as_base_asset set to false. + + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::ForeignAsset(420), + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + deadlines, + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + ), + Error::::InvalidBaseAsset, + ); + // As per Mock asset_registry genesis ForeignAsset(50) is not registered in asset_registry. + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::ForeignAsset(50), + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + deadlines, + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + ), + Error::::UnregisteredForeignAsset, + ); + // As per Mock asset_registry genesis ForeignAsset(100) has allow_as_base_asset set to true. + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::ForeignAsset(100), + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + deadlines, + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.base_asset, Asset::ForeignAsset(100)); + }); +} + +#[test] +fn it_does_not_create_market_with_too_few_categories() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..100), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(::MinCategories::get() - 1), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + ), + Error::::NotEnoughCategories + ); + }); +} + +#[test] +fn it_does_not_create_market_with_too_many_categories() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..100), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(::MaxCategories::get() + 1), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + ), + Error::::TooManyCategories + ); + }); +} + +#[test] +fn create_categorical_market_fails_if_market_begin_is_equal_to_end() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(3..3), + get_deadlines(), + gen_metadata(0), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + ), + Error::::InvalidMarketPeriod, + ); + }); +} + +#[test_case(MarketPeriod::Block(3..3); "empty range blocks")] +#[test_case(MarketPeriod::Timestamp(3..3); "empty range timestamp")] +#[test_case( + MarketPeriod::Timestamp(0..(MILLISECS_PER_BLOCK - 1).into()); + "range shorter than block time" +)] +fn create_categorical_market_fails_if_market_period_is_invalid( + period: MarketPeriod, +) { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + period, + get_deadlines(), + gen_metadata(0), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + ), + Error::::InvalidMarketPeriod, + ); + }); +} + +#[test] +fn create_categorical_market_fails_if_end_is_not_far_enough_ahead() { + ExtBuilder::default().build().execute_with(|| { + let end_block = 33; + run_to_block(end_block); + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end_block), + get_deadlines(), + gen_metadata(0), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + ), + Error::::InvalidMarketPeriod, + ); + + let end_time = MILLISECS_PER_BLOCK as u64 / 2; + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(0..end_time), + get_deadlines(), + gen_metadata(0), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + ), + Error::::InvalidMarketPeriod, + ); + }); +} + +#[test] +fn create_market_succeeds_if_market_duration_is_maximal_in_blocks() { + ExtBuilder::default().build().execute_with(|| { + let now = 1; + frame_system::Pallet::::set_block_number(now); + let start = 5; + let end = now + ::MaxMarketLifetime::get(); + assert!( + end > start, + "Test failed due to misconfiguration: `MaxMarketLifetime` is too small" + ); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(start..end), + get_deadlines(), + gen_metadata(0), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + }); +} + +#[test] +fn create_market_suceeds_if_market_duration_is_maximal_in_moments() { + ExtBuilder::default().build().execute_with(|| { + let now = 12_001u64; + Timestamp::set_timestamp(now); + let start = 5 * MILLISECS_PER_BLOCK as u64; + let end = + now + ::MaxMarketLifetime::get() * (MILLISECS_PER_BLOCK as u64); + assert!( + end > start, + "Test failed due to misconfiguration: `MaxMarketLifetime` is too small" + ); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(start..end), + get_deadlines(), + gen_metadata(0), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + }); +} + +#[test] +fn create_market_fails_if_market_duration_is_too_long_in_blocks() { + ExtBuilder::default().build().execute_with(|| { + let now = 1; + frame_system::Pallet::::set_block_number(now); + let start = 5; + let end = now + ::MaxMarketLifetime::get() + 1; + assert!( + end > start, + "Test failed due to misconfiguration: `MaxMarketLifetime` is too small" + ); + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(start..end), + get_deadlines(), + gen_metadata(0), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + ), + crate::Error::::MarketDurationTooLong, + ); + }); +} + +#[test] +fn create_market_fails_if_market_duration_is_too_long_in_moments() { + ExtBuilder::default().build().execute_with(|| { + let now = 12_001u64; + Timestamp::set_timestamp(now); + let start = 5 * MILLISECS_PER_BLOCK as u64; + let end = now + + (::MaxMarketLifetime::get() + 1) * (MILLISECS_PER_BLOCK as u64); + assert!( + end > start, + "Test failed due to misconfiguration: `MaxMarketLifetime` is too small" + ); + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(start..end), + get_deadlines(), + gen_metadata(0), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + ), + crate::Error::::MarketDurationTooLong, + ); + }); +} + +#[test_case( + MarketCreation::Advised, + ScoringRule::Lmsr, + MarketStatus::Proposed, + MarketBonds { + creation: Some(Bond::new(ALICE, ::AdvisoryBond::get())), + oracle: Some(Bond::new(ALICE, ::OracleBond::get())), + outsider: None, + dispute: None, + close_dispute: None, + close_request: None, + } +)] +#[test_case( + MarketCreation::Permissionless, + ScoringRule::Lmsr, + MarketStatus::Active, + MarketBonds { + creation: Some(Bond::new(ALICE, ::ValidityBond::get())), + oracle: Some(Bond::new(ALICE, ::OracleBond::get())), + outsider: None, + dispute: None, + close_dispute: None, + close_request: None, + } +)] +fn create_market_sets_the_correct_market_parameters_and_reserves_the_correct_amount( + creation: MarketCreation, + scoring_rule: ScoringRule, + status: MarketStatus, + bonds: MarketBonds, +) { + ExtBuilder::default().build().execute_with(|| { + let creator = ALICE; + let oracle = BOB; + let period = MarketPeriod::Block(1..2); + let deadlines = Deadlines { + grace_period: 1, + oracle_duration: ::MinOracleDuration::get() + 2, + dispute_duration: ::MinDisputeDuration::get() + 3, + }; + let metadata = gen_metadata(0x99); + let MultiHash::Sha3_384(multihash) = metadata; + let market_type = MarketType::Categorical(7); + let dispute_mechanism = Some(MarketDisputeMechanism::Authorized); + let creator_fee = Perbill::from_parts(1); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(creator), + Asset::Ztg, + creator_fee, + oracle, + period.clone(), + deadlines, + metadata, + creation.clone(), + market_type.clone(), + dispute_mechanism.clone(), + scoring_rule, + )); + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.creator, creator); + assert_eq!(market.creation, creation); + assert_eq!(market.creator_fee, creator_fee); + assert_eq!(market.oracle, oracle); + assert_eq!(market.metadata, multihash); + assert_eq!(market.market_type, market_type); + assert_eq!(market.period, period); + assert_eq!(market.deadlines, deadlines); + assert_eq!(market.scoring_rule, scoring_rule); + assert_eq!(market.status, status); + assert_eq!(market.report, None); + assert_eq!(market.resolved_outcome, None); + assert_eq!(market.dispute_mechanism, dispute_mechanism); + assert_eq!(market.bonds, bonds); + }); +} + +#[test] +fn create_market_fails_on_trusted_market_with_non_zero_dispute_period() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(1..2), + Deadlines { + grace_period: 1, + oracle_duration: ::MinOracleDuration::get() + 2, + dispute_duration: ::MinDisputeDuration::get() + 3, + }, + gen_metadata(0x99), + MarketCreation::Permissionless, + MarketType::Categorical(3), + None, + ScoringRule::Lmsr, + ), + Error::::NonZeroDisputePeriodOnTrustedMarket + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index f2cae52c1..8d6742ec8 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -20,6 +20,7 @@ mod admin_move_market_to_closed; mod buy_complete_set; +mod create_market; mod sell_complete_set; use crate::{mock::*, AssetOf, Config, Error, Event}; From cbf78f6e781a73e6bce98ecd946aa708ea63adb1 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 11 Jan 2024 22:20:02 +0100 Subject: [PATCH 08/32] Extract `admin_move_market_to_resolved` tests --- zrml/prediction-markets/src/old_tests.rs | 98 -------------- .../tests/admin_move_market_to_resolved.rs | 120 ++++++++++++++++++ zrml/prediction-markets/src/tests/mod.rs | 5 +- 3 files changed, 124 insertions(+), 99 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/admin_move_market_to_resolved.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index d0d815b49..bf0f0bcb1 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -141,104 +141,6 @@ fn simple_create_scalar_market( )); } -#[test] -fn admin_move_market_to_resolved_resolves_reported_market() { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - let end = 33; - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - let market_id = 0; - - // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check - // that the correct bonds are unreserved! - assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); - assert_ok!(Balances::reserve_named( - &PredictionMarkets::reserve_id(), - &ALICE, - SENTINEL_AMOUNT - )); - let balance_free_before = Balances::free_balance(ALICE); - let balance_reserved_before = - Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); - - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - let category = 1; - let outcome_report = OutcomeReport::Categorical(category); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - market_id, - outcome_report.clone() - )); - assert_ok!(PredictionMarkets::admin_move_market_to_resolved( - RuntimeOrigin::signed(SUDO), - market_id - )); - - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Resolved); - assert_eq!(market.report.unwrap().outcome, outcome_report); - assert_eq!(market.resolved_outcome.unwrap(), outcome_report); - System::assert_last_event( - Event::MarketResolved(market_id, MarketStatus::Resolved, outcome_report).into(), - ); - - assert_eq!( - Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE), - balance_reserved_before - - ::OracleBond::get() - - ::ValidityBond::get() - ); - assert_eq!( - Balances::free_balance(ALICE), - balance_free_before - + ::OracleBond::get() - + ::ValidityBond::get() - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test_case(MarketStatus::Active)] -#[test_case(MarketStatus::Closed)] -#[test_case(MarketStatus::Resolved)] -fn admin_move_market_to_resolved_fails_if_market_is_not_reported_or_disputed( - market_status: MarketStatus, -) { - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..33, - ScoringRule::Lmsr, - ); - let market_id = 0; - assert_ok!(MarketCommons::mutate_market(&market_id, |market| { - market.status = market_status; - Ok(()) - })); - assert_noop!( - PredictionMarkets::admin_move_market_to_resolved( - RuntimeOrigin::signed(SUDO), - market_id, - ), - Error::::InvalidMarketStatus, - ); - }); -} - #[test] fn it_creates_binary_markets() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/admin_move_market_to_resolved.rs b/zrml/prediction-markets/src/tests/admin_move_market_to_resolved.rs new file mode 100644 index 000000000..6b84bd4a8 --- /dev/null +++ b/zrml/prediction-markets/src/tests/admin_move_market_to_resolved.rs @@ -0,0 +1,120 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; +use test_case::test_case; + +use zeitgeist_primitives::types::OutcomeReport; + +#[test] +fn admin_move_market_to_resolved_resolves_reported_market() { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + let end = 33; + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + let market_id = 0; + + // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check + // that the correct bonds are unreserved! + assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); + assert_ok!(Balances::reserve_named( + &PredictionMarkets::reserve_id(), + &ALICE, + SENTINEL_AMOUNT + )); + let balance_free_before = Balances::free_balance(ALICE); + let balance_reserved_before = + Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); + + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + let category = 1; + let outcome_report = OutcomeReport::Categorical(category); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + market_id, + outcome_report.clone() + )); + assert_ok!(PredictionMarkets::admin_move_market_to_resolved( + RuntimeOrigin::signed(SUDO), + market_id + )); + + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Resolved); + assert_eq!(market.report.unwrap().outcome, outcome_report); + assert_eq!(market.resolved_outcome.unwrap(), outcome_report); + System::assert_last_event( + Event::MarketResolved(market_id, MarketStatus::Resolved, outcome_report).into(), + ); + + assert_eq!( + Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE), + balance_reserved_before + - ::OracleBond::get() + - ::ValidityBond::get() + ); + assert_eq!( + Balances::free_balance(ALICE), + balance_free_before + + ::OracleBond::get() + + ::ValidityBond::get() + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test_case(MarketStatus::Active)] +#[test_case(MarketStatus::Closed)] +#[test_case(MarketStatus::Resolved)] +fn admin_move_market_to_resolved_fails_if_market_is_not_reported_or_disputed( + market_status: MarketStatus, +) { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..33, + ScoringRule::Lmsr, + ); + let market_id = 0; + assert_ok!(MarketCommons::mutate_market(&market_id, |market| { + market.status = market_status; + Ok(()) + })); + assert_noop!( + PredictionMarkets::admin_move_market_to_resolved( + RuntimeOrigin::signed(SUDO), + market_id, + ), + Error::::InvalidMarketStatus, + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 8d6742ec8..e825fd573 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -19,13 +19,14 @@ #![cfg(all(feature = "mock", test))] mod admin_move_market_to_closed; +mod admin_move_market_to_resolved; mod buy_complete_set; mod create_market; mod sell_complete_set; use crate::{mock::*, AssetOf, Config, Error, Event}; use core::ops::Range; -use frame_support::{assert_noop, assert_ok}; +use frame_support::{assert_noop, assert_ok, traits::NamedReservableCurrency}; use orml_traits::MultiCurrency; use sp_arithmetic::Perbill; use zeitgeist_primitives::{ @@ -37,6 +38,8 @@ use zeitgeist_primitives::{ }; use zrml_market_commons::MarketCommonsPalletApi; +const SENTINEL_AMOUNT: u128 = BASE; + fn get_deadlines() -> Deadlines<::BlockNumber> { Deadlines { grace_period: 1_u32.into(), From 8df142fd5d3649aabed961af696ada429117a6ac Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 11 Jan 2024 22:24:37 +0100 Subject: [PATCH 09/32] Remove superfluous test --- zrml/prediction-markets/src/old_tests.rs | 31 ------------------------ 1 file changed, 31 deletions(-) diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index bf0f0bcb1..a19f691f8 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -141,37 +141,6 @@ fn simple_create_scalar_market( )); } -#[test] -fn it_creates_binary_markets() { - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..2, - ScoringRule::Lmsr, - ); - - // check the correct amount was reserved - let alice_reserved = Balances::reserved_balance(ALICE); - assert_eq!(alice_reserved, ValidityBond::get() + OracleBond::get()); - - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..2, - ScoringRule::Lmsr, - ); - - let new_alice_reserved = Balances::reserved_balance(ALICE); - assert_eq!(new_alice_reserved, AdvisoryBond::get() + OracleBond::get() + alice_reserved); - - // Make sure that the market id has been incrementing - let market_id = MarketCommons::latest_market_id().unwrap(); - assert_eq!(market_id, 1); - }); -} - #[test] fn create_categorical_market_deposits_the_correct_event() { ExtBuilder::default().build().execute_with(|| { From 8a73e993dab5da2d4a697607ffae4718297802bd Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 11 Jan 2024 22:49:14 +0100 Subject: [PATCH 10/32] Extract more `create_market` tests --- zrml/prediction-markets/src/old_tests.rs | 34 ------------------- .../src/tests/create_market.rs | 34 +++++++++++++++++++ zrml/prediction-markets/src/tests/mod.rs | 21 ++++++++++++ 3 files changed, 55 insertions(+), 34 deletions(-) diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index a19f691f8..5206b3bc1 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -141,40 +141,6 @@ fn simple_create_scalar_market( )); } -#[test] -fn create_categorical_market_deposits_the_correct_event() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 1..2, - ScoringRule::Lmsr, - ); - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - let market_account = PredictionMarkets::market_account(market_id); - System::assert_last_event(Event::MarketCreated(0, market_account, market).into()); - }); -} - -#[test] -fn create_scalar_market_deposits_the_correct_event() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - simple_create_scalar_market( - Asset::Ztg, - MarketCreation::Permissionless, - 1..2, - ScoringRule::Lmsr, - ); - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - let market_account = PredictionMarkets::market_account(market_id); - System::assert_last_event(Event::MarketCreated(0, market_account, market).into()); - }); -} - #[test] fn it_allows_advisory_origin_to_approve_markets() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/create_market.rs b/zrml/prediction-markets/src/tests/create_market.rs index de1f6f340..9d485390a 100644 --- a/zrml/prediction-markets/src/tests/create_market.rs +++ b/zrml/prediction-markets/src/tests/create_market.rs @@ -602,3 +602,37 @@ fn create_market_fails_on_trusted_market_with_non_zero_dispute_period() { ); }); } + +#[test] +fn create_categorical_market_deposits_the_correct_event() { + ExtBuilder::default().build().execute_with(|| { + frame_system::Pallet::::set_block_number(1); + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 1..2, + ScoringRule::Lmsr, + ); + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + let market_account = PredictionMarkets::market_account(market_id); + System::assert_last_event(Event::MarketCreated(0, market_account, market).into()); + }); +} + +#[test] +fn create_scalar_market_deposits_the_correct_event() { + ExtBuilder::default().build().execute_with(|| { + frame_system::Pallet::::set_block_number(1); + simple_create_scalar_market( + Asset::Ztg, + MarketCreation::Permissionless, + 1..2, + ScoringRule::Lmsr, + ); + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + let market_account = PredictionMarkets::market_account(market_id); + System::assert_last_event(Event::MarketCreated(0, market_account, market).into()); + }); +} diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index e825fd573..59ed3d69e 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -75,3 +75,24 @@ fn simple_create_categorical_market( scoring_rule )); } + +fn simple_create_scalar_market( + base_asset: AssetOf, + creation: MarketCreation, + period: Range, + scoring_rule: ScoringRule, +) { + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(period), + get_deadlines(), + gen_metadata(2), + creation, + MarketType::Scalar(100..=200), + Some(MarketDisputeMechanism::SimpleDisputes), + scoring_rule + )); +} From 4d04fdb4de999b9a32e805aed8894ed00a1a3e0a Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 11 Jan 2024 22:56:41 +0100 Subject: [PATCH 11/32] Extract `approve_market` tests --- zrml/prediction-markets/src/old_tests.rs | 92 -------------- .../src/tests/approve_market.rs | 113 ++++++++++++++++++ zrml/prediction-markets/src/tests/mod.rs | 27 ++++- 3 files changed, 139 insertions(+), 93 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/approve_market.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index 5206b3bc1..f8c0914e7 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -141,35 +141,6 @@ fn simple_create_scalar_market( )); } -#[test] -fn it_allows_advisory_origin_to_approve_markets() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - // Make sure it fails from the random joe - assert_noop!( - PredictionMarkets::approve_market(RuntimeOrigin::signed(BOB), 0), - DispatchError::BadOrigin - ); - - // Now it should work from SUDO - assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); - - let after_market = MarketCommons::market(&0); - assert_eq!(after_market.unwrap().status, MarketStatus::Active); - }); -} - #[test] fn it_allows_request_edit_origin_to_request_edits_for_markets() { ExtBuilder::default().build().execute_with(|| { @@ -260,33 +231,6 @@ fn edit_request_fails_if_edit_reason_is_too_long() { }); } -#[test] -fn market_with_edit_request_cannot_be_approved() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - - assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); - - assert!(MarketIdsForEdit::::contains_key(0)); - assert_noop!( - PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0), - Error::::MarketEditRequestAlreadyInProgress - ); - }); -} - #[test] fn it_allows_the_advisory_origin_to_reject_markets() { ExtBuilder::default().build().execute_with(|| { @@ -3390,42 +3334,6 @@ fn authorized_correctly_resolves_disputed_market() { }); } -#[test] -fn approve_market_correctly_unreserves_advisory_bond() { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..100), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - let market_id = 0; - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, AdvisoryBond::get() + OracleBond::get()); - assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), market_id)); - check_reserve(&ALICE, OracleBond::get()); - assert_eq!(Balances::free_balance(ALICE), alice_balance_before + AdvisoryBond::get()); - let market = MarketCommons::market(&market_id).unwrap(); - assert!(market.bonds.creation.unwrap().is_settled); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - #[test] fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_on_oracle_report() { diff --git a/zrml/prediction-markets/src/tests/approve_market.rs b/zrml/prediction-markets/src/tests/approve_market.rs new file mode 100644 index 000000000..e18bd7a02 --- /dev/null +++ b/zrml/prediction-markets/src/tests/approve_market.rs @@ -0,0 +1,113 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +use sp_runtime::DispatchError;use crate::MarketIdsForEdit; + +#[test] +fn it_allows_advisory_origin_to_approve_markets() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..1, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + // Make sure it fails from the random joe + assert_noop!( + PredictionMarkets::approve_market(RuntimeOrigin::signed(BOB), 0), + DispatchError::BadOrigin + ); + + // Now it should work from SUDO + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); + + let after_market = MarketCommons::market(&0); + assert_eq!(after_market.unwrap().status, MarketStatus::Active); + }); +} + +#[test] +fn market_with_edit_request_cannot_be_approved() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..1, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + + assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); + + assert!(MarketIdsForEdit::::contains_key(0)); + assert_noop!( + PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0), + Error::::MarketEditRequestAlreadyInProgress + ); + }); +} + +#[test] +fn approve_market_correctly_unreserves_advisory_bond() { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..100), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + let market_id = 0; + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, AdvisoryBond::get() + OracleBond::get()); + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), market_id)); + check_reserve(&ALICE, OracleBond::get()); + assert_eq!(Balances::free_balance(ALICE), alice_balance_before + AdvisoryBond::get()); + let market = MarketCommons::market(&market_id).unwrap(); + assert!(market.bonds.creation.unwrap().is_settled); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 59ed3d69e..aefc82e65 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -20,11 +20,12 @@ mod admin_move_market_to_closed; mod admin_move_market_to_resolved; +mod approve_market; mod buy_complete_set; mod create_market; mod sell_complete_set; -use crate::{mock::*, AssetOf, Config, Error, Event}; +use crate::{mock::*, AssetOf, BalanceOf, AccountIdOf, Config, Error, Event}; use core::ops::Range; use frame_support::{assert_noop, assert_ok, traits::NamedReservableCurrency}; use orml_traits::MultiCurrency; @@ -96,3 +97,27 @@ fn simple_create_scalar_market( scoring_rule )); } + +fn check_reserve(account: &AccountIdOf, expected: BalanceOf) { + assert_eq!(Balances::reserved_balance(account), SENTINEL_AMOUNT + expected); +} + +fn reserve_sentinel_amounts() { + // Reserve a sentinel amount to check that we don't unreserve too much. + assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &ALICE, SENTINEL_AMOUNT)); + assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &BOB, SENTINEL_AMOUNT)); + assert_ok!(Balances::reserve_named( + &PredictionMarkets::reserve_id(), + &CHARLIE, + SENTINEL_AMOUNT + )); + assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &DAVE, SENTINEL_AMOUNT)); + assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &EVE, SENTINEL_AMOUNT)); + assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &FRED, SENTINEL_AMOUNT)); + assert_eq!(Balances::reserved_balance(ALICE), SENTINEL_AMOUNT); + assert_eq!(Balances::reserved_balance(BOB), SENTINEL_AMOUNT); + assert_eq!(Balances::reserved_balance(CHARLIE), SENTINEL_AMOUNT); + assert_eq!(Balances::reserved_balance(DAVE), SENTINEL_AMOUNT); + assert_eq!(Balances::reserved_balance(EVE), SENTINEL_AMOUNT); + assert_eq!(Balances::reserved_balance(FRED), SENTINEL_AMOUNT); +} From 29bafe417a6c1cdccb3347f7e0490507d29c7d5a Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 11 Jan 2024 23:09:12 +0100 Subject: [PATCH 12/32] Extract `edit_market` tests --- zrml/prediction-markets/src/old_tests.rs | 252 ---------------- .../src/tests/edit_market.rs | 273 ++++++++++++++++++ zrml/prediction-markets/src/tests/mod.rs | 1 + 3 files changed, 274 insertions(+), 252 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/edit_market.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index f8c0914e7..7f34b7b34 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -29,7 +29,6 @@ use alloc::collections::BTreeMap; use core::ops::Range; use frame_support::{ assert_noop, assert_ok, - dispatch::DispatchError, traits::{NamedReservableCurrency, OnInitialize}, }; use sp_runtime::{traits::BlakeTwo256, Perquintill}; @@ -141,96 +140,6 @@ fn simple_create_scalar_market( )); } -#[test] -fn it_allows_request_edit_origin_to_request_edits_for_markets() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 2..4, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - // Make sure it fails from the random joe - assert_noop!( - PredictionMarkets::request_edit(RuntimeOrigin::signed(BOB), 0, edit_reason.clone()), - DispatchError::BadOrigin - ); - - // Now it should work from SUDO - assert_ok!(PredictionMarkets::request_edit( - RuntimeOrigin::signed(SUDO), - 0, - edit_reason.clone() - )); - System::assert_last_event( - Event::MarketRequestedEdit( - 0, - edit_reason.try_into().expect("Conversion to BoundedVec failed"), - ) - .into(), - ); - - assert!(MarketIdsForEdit::::contains_key(0)); - }); -} - -#[test] -fn request_edit_fails_on_bad_origin() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 2..4, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - // Make sure it fails from the random joe - assert_noop!( - PredictionMarkets::request_edit(RuntimeOrigin::signed(BOB), 0, edit_reason), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn edit_request_fails_if_edit_reason_is_too_long() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize + 1]; - - assert_noop!( - PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason), - Error::::EditReasonLengthExceedsMaxEditReasonLen - ); - }); -} - #[test] fn it_allows_the_advisory_origin_to_reject_markets() { ExtBuilder::default().build().execute_with(|| { @@ -2778,167 +2687,6 @@ fn redeem_shares_fails_if_invalid_resolution_mechanism(scoring_rule: ScoringRule }); } -#[test] -fn only_creator_can_edit_market() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - - // Now it should work from SUDO - assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); - - assert!(MarketIdsForEdit::::contains_key(0)); - - // ALICE is market creator through simple_create_categorical_market - assert_noop!( - PredictionMarkets::edit_market( - RuntimeOrigin::signed(BOB), - Asset::Ztg, - 0, - CHARLIE, - MarketPeriod::Block(0..1), - get_deadlines(), - gen_metadata(2), - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - ), - Error::::EditorNotCreator - ); - }); -} - -#[test] -fn edit_cycle_for_proposed_markets() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - run_to_block(1); - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 2..4, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - - // Now it should work from SUDO - assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); - - assert!(MarketIdsForEdit::::contains_key(0)); - - // BOB was the oracle before through simple_create_categorical_market - // After this edit its changed to ALICE - assert_ok!(PredictionMarkets::edit_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - 0, - CHARLIE, - MarketPeriod::Block(2..4), - get_deadlines(), - gen_metadata(2), - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - )); - let edited_market = MarketCommons::market(&0).expect("Market not found"); - System::assert_last_event(Event::MarketEdited(0, edited_market).into()); - assert!(!MarketIdsForEdit::::contains_key(0)); - // verify oracle is CHARLIE - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().oracle, CHARLIE); - }); -} - -#[cfg(feature = "parachain")] -#[test] -fn edit_market_with_foreign_asset() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - - // Now it should work from SUDO - assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); - - assert!(MarketIdsForEdit::::contains_key(0)); - - // ALICE is market creator through simple_create_categorical_market - // As per Mock asset_registry genesis ForeignAsset(50) is not registered in asset_registry. - assert_noop!( - PredictionMarkets::edit_market( - RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(50), - 0, - CHARLIE, - MarketPeriod::Block(0..1), - get_deadlines(), - gen_metadata(2), - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - ), - Error::::UnregisteredForeignAsset - ); - // As per Mock asset_registry genesis ForeignAsset(420) has allow_as_base_asset set to false. - assert_noop!( - PredictionMarkets::edit_market( - RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(420), - 0, - CHARLIE, - MarketPeriod::Block(0..1), - get_deadlines(), - gen_metadata(2), - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - ), - Error::::InvalidBaseAsset, - ); - // As per Mock asset_registry genesis ForeignAsset(100) has allow_as_base_asset set to true. - assert_ok!(PredictionMarkets::edit_market( - RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(100), - 0, - CHARLIE, - MarketPeriod::Block(0..1), - get_deadlines(), - gen_metadata(2), - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - )); - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.base_asset, Asset::ForeignAsset(100)); - }); -} - #[test] fn the_entire_market_lifecycle_works_with_timestamps() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/edit_market.rs b/zrml/prediction-markets/src/tests/edit_market.rs new file mode 100644 index 000000000..be5a81733 --- /dev/null +++ b/zrml/prediction-markets/src/tests/edit_market.rs @@ -0,0 +1,273 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +use crate::MarketIdsForEdit; +use sp_runtime::DispatchError; + +#[test] +fn it_allows_request_edit_origin_to_request_edits_for_markets() { + ExtBuilder::default().build().execute_with(|| { + frame_system::Pallet::::set_block_number(1); + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 2..4, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + // Make sure it fails from the random joe + assert_noop!( + PredictionMarkets::request_edit(RuntimeOrigin::signed(BOB), 0, edit_reason.clone()), + DispatchError::BadOrigin + ); + + // Now it should work from SUDO + assert_ok!(PredictionMarkets::request_edit( + RuntimeOrigin::signed(SUDO), + 0, + edit_reason.clone() + )); + System::assert_last_event( + Event::MarketRequestedEdit( + 0, + edit_reason.try_into().expect("Conversion to BoundedVec failed"), + ) + .into(), + ); + + assert!(MarketIdsForEdit::::contains_key(0)); + }); +} + +#[test] +fn request_edit_fails_on_bad_origin() { + ExtBuilder::default().build().execute_with(|| { + frame_system::Pallet::::set_block_number(1); + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 2..4, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + // Make sure it fails from the random joe + assert_noop!( + PredictionMarkets::request_edit(RuntimeOrigin::signed(BOB), 0, edit_reason), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn edit_request_fails_if_edit_reason_is_too_long() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..1, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize + 1]; + + assert_noop!( + PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason), + Error::::EditReasonLengthExceedsMaxEditReasonLen + ); + }); +} + +#[test] +fn only_creator_can_edit_market() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..1, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + + // Now it should work from SUDO + assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); + + assert!(MarketIdsForEdit::::contains_key(0)); + + // ALICE is market creator through simple_create_categorical_market + assert_noop!( + PredictionMarkets::edit_market( + RuntimeOrigin::signed(BOB), + Asset::Ztg, + 0, + CHARLIE, + MarketPeriod::Block(0..1), + get_deadlines(), + gen_metadata(2), + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + ), + Error::::EditorNotCreator + ); + }); +} + +#[test] +fn edit_cycle_for_proposed_markets() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + run_to_block(1); + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 2..4, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + + // Now it should work from SUDO + assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); + + assert!(MarketIdsForEdit::::contains_key(0)); + + // BOB was the oracle before through simple_create_categorical_market + // After this edit its changed to ALICE + assert_ok!(PredictionMarkets::edit_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + 0, + CHARLIE, + MarketPeriod::Block(2..4), + get_deadlines(), + gen_metadata(2), + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + )); + let edited_market = MarketCommons::market(&0).expect("Market not found"); + System::assert_last_event(Event::MarketEdited(0, edited_market).into()); + assert!(!MarketIdsForEdit::::contains_key(0)); + // verify oracle is CHARLIE + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().oracle, CHARLIE); + }); +} + +#[cfg(feature = "parachain")] +#[test] +fn edit_market_with_foreign_asset() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..1, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + + // Now it should work from SUDO + assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); + + assert!(MarketIdsForEdit::::contains_key(0)); + + // ALICE is market creator through simple_create_categorical_market + // As per Mock asset_registry genesis ForeignAsset(50) is not registered in asset_registry. + assert_noop!( + PredictionMarkets::edit_market( + RuntimeOrigin::signed(ALICE), + Asset::ForeignAsset(50), + 0, + CHARLIE, + MarketPeriod::Block(0..1), + get_deadlines(), + gen_metadata(2), + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + ), + Error::::UnregisteredForeignAsset + ); + // As per Mock asset_registry genesis ForeignAsset(420) has allow_as_base_asset set to false. + assert_noop!( + PredictionMarkets::edit_market( + RuntimeOrigin::signed(ALICE), + Asset::ForeignAsset(420), + 0, + CHARLIE, + MarketPeriod::Block(0..1), + get_deadlines(), + gen_metadata(2), + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + ), + Error::::InvalidBaseAsset, + ); + // As per Mock asset_registry genesis ForeignAsset(100) has allow_as_base_asset set to true. + assert_ok!(PredictionMarkets::edit_market( + RuntimeOrigin::signed(ALICE), + Asset::ForeignAsset(100), + 0, + CHARLIE, + MarketPeriod::Block(0..1), + get_deadlines(), + gen_metadata(2), + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + )); + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.base_asset, Asset::ForeignAsset(100)); + }); +} diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index aefc82e65..966be5a8b 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -23,6 +23,7 @@ mod admin_move_market_to_resolved; mod approve_market; mod buy_complete_set; mod create_market; +mod edit_market; mod sell_complete_set; use crate::{mock::*, AssetOf, BalanceOf, AccountIdOf, Config, Error, Event}; From dd688426da649829f6efb4e1ab57064fce14b91a Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 11 Jan 2024 23:16:11 +0100 Subject: [PATCH 13/32] Extract `edit_market` tests --- zrml/prediction-markets/src/old_tests.rs | 224 ---------------- .../src/tests/approve_market.rs | 3 +- .../src/tests/create_market.rs | 2 +- zrml/prediction-markets/src/tests/mod.rs | 3 +- .../src/tests/reject_market.rs | 245 ++++++++++++++++++ 5 files changed, 250 insertions(+), 227 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/reject_market.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index 7f34b7b34..c22c8511b 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -140,191 +140,6 @@ fn simple_create_scalar_market( )); } -#[test] -fn it_allows_the_advisory_origin_to_reject_markets() { - ExtBuilder::default().build().execute_with(|| { - run_to_block(2); - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 4..6, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let reject_reason: Vec = - vec![0; ::MaxRejectReasonLen::get() as usize]; - // Now it should work from SUDO - assert_ok!(PredictionMarkets::reject_market( - RuntimeOrigin::signed(SUDO), - 0, - reject_reason.clone() - )); - let reject_reason = reject_reason.try_into().expect("BoundedVec conversion failed"); - System::assert_has_event(Event::MarketRejected(0, reject_reason).into()); - - assert_noop!( - MarketCommons::market(&0), - zrml_market_commons::Error::::MarketDoesNotExist - ); - }); -} - -#[test] -fn reject_errors_if_reject_reason_is_too_long() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let reject_reason: Vec = - vec![0; ::MaxRejectReasonLen::get() as usize + 1]; - assert_noop!( - PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason), - Error::::RejectReasonLengthExceedsMaxRejectReasonLen - ); - }); -} - -#[test] -fn it_allows_the_advisory_origin_to_reject_markets_with_edit_request() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - - let reject_reason = vec![0_u8; ::MaxRejectReasonLen::get() as usize]; - assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); - assert!(MarketIdsForEdit::::contains_key(0)); - assert_ok!(PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason)); - assert!(!MarketIdsForEdit::::contains_key(0)); - - assert_noop!( - MarketCommons::market(&0), - zrml_market_commons::Error::::MarketDoesNotExist - ); - }); -} - -#[test] -fn reject_market_unreserves_oracle_bond_and_slashes_advisory_bond() { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - simple_create_categorical_market( - base_asset, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - - // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check - // that the AdvisoryBond gets slashed but the OracleBond gets unreserved. - assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); - assert_ok!(Balances::reserve_named( - &PredictionMarkets::reserve_id(), - &ALICE, - SENTINEL_AMOUNT, - )); - assert!(Balances::free_balance(Treasury::account_id()).is_zero()); - - let balance_free_before_alice = Balances::free_balance(ALICE); - let balance_reserved_before_alice = - Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); - - let reject_reason: Vec = - vec![0; ::MaxRejectReasonLen::get() as usize]; - assert_ok!(PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason)); - - // AdvisoryBond gets slashed after reject_market - // OracleBond gets unreserved after reject_market - let balance_reserved_after_alice = - Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); - assert_eq!( - balance_reserved_after_alice, - balance_reserved_before_alice - - ::OracleBond::get() - - ::AdvisoryBond::get(), - ); - let balance_free_after_alice = Balances::free_balance(ALICE); - let slash_amount_advisory_bond = ::AdvisoryBondSlashPercentage::get() - .mul_floor(::AdvisoryBond::get()); - let advisory_bond_remains = - ::AdvisoryBond::get() - slash_amount_advisory_bond; - assert_eq!( - balance_free_after_alice, - balance_free_before_alice - + ::OracleBond::get() - + advisory_bond_remains, - ); - - // AdvisoryBond is transferred to the treasury - let balance_treasury_after = Balances::free_balance(Treasury::account_id()); - assert_eq!(balance_treasury_after, slash_amount_advisory_bond); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn reject_market_clears_auto_close_blocks() { - // We don't have to check that reject market clears the cache for opening pools, since Cpmm pools - // can not be deployed on pending advised pools. - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 33..66, - ScoringRule::Lmsr, - ); - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 22..66, - ScoringRule::Lmsr, - ); - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 22..33, - ScoringRule::Lmsr, - ); - let reject_reason: Vec = - vec![0; ::MaxRejectReasonLen::get() as usize]; - assert_ok!(PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason)); - - let auto_close = MarketIdsPerCloseBlock::::get(66); - assert_eq!(auto_close.len(), 1); - assert_eq!(auto_close[0], 1); - }); -} - #[test] fn on_market_close_auto_rejects_expired_advised_market() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. @@ -2897,45 +2712,6 @@ fn scalar_market_correctly_resolves_on_out_of_range_outcomes_above_threshold() { }); } -#[test] -fn reject_market_fails_on_permissionless_market() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..1, - ScoringRule::Lmsr, - ); - let reject_reason: Vec = - vec![0; ::MaxRejectReasonLen::get() as usize]; - assert_noop!( - PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason), - Error::::InvalidMarketStatus - ); - }); -} - -#[test] -fn reject_market_fails_on_approved_market() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); - let reject_reason: Vec = - vec![0; ::MaxRejectReasonLen::get() as usize]; - assert_noop!( - PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason), - Error::::InvalidMarketStatus - ); - }); -} - #[test] fn authorized_correctly_resolves_disputed_market() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. diff --git a/zrml/prediction-markets/src/tests/approve_market.rs b/zrml/prediction-markets/src/tests/approve_market.rs index e18bd7a02..8235bb107 100644 --- a/zrml/prediction-markets/src/tests/approve_market.rs +++ b/zrml/prediction-markets/src/tests/approve_market.rs @@ -18,7 +18,8 @@ use super::*; -use sp_runtime::DispatchError;use crate::MarketIdsForEdit; +use crate::MarketIdsForEdit; +use sp_runtime::DispatchError; #[test] fn it_allows_advisory_origin_to_approve_markets() { diff --git a/zrml/prediction-markets/src/tests/create_market.rs b/zrml/prediction-markets/src/tests/create_market.rs index 9d485390a..fc65f04a0 100644 --- a/zrml/prediction-markets/src/tests/create_market.rs +++ b/zrml/prediction-markets/src/tests/create_market.rs @@ -22,7 +22,7 @@ use test_case::test_case; use core::ops::RangeInclusive; use zeitgeist_primitives::{ constants::MILLISECS_PER_BLOCK, - types::{AccountIdTest, Balance, BlockNumber, Bond, Moment, MarketBonds}, + types::{AccountIdTest, Balance, BlockNumber, Bond, MarketBonds, Moment}, }; #[test_case(std::ops::RangeInclusive::new(7, 6); "empty range")] diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 966be5a8b..8eb419d0a 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -24,9 +24,10 @@ mod approve_market; mod buy_complete_set; mod create_market; mod edit_market; +mod reject_market; mod sell_complete_set; -use crate::{mock::*, AssetOf, BalanceOf, AccountIdOf, Config, Error, Event}; +use crate::{mock::*, AccountIdOf, AssetOf, BalanceOf, Config, Error, Event}; use core::ops::Range; use frame_support::{assert_noop, assert_ok, traits::NamedReservableCurrency}; use orml_traits::MultiCurrency; diff --git a/zrml/prediction-markets/src/tests/reject_market.rs b/zrml/prediction-markets/src/tests/reject_market.rs new file mode 100644 index 000000000..28684717f --- /dev/null +++ b/zrml/prediction-markets/src/tests/reject_market.rs @@ -0,0 +1,245 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +use crate::{MarketIdsForEdit, MarketIdsPerCloseBlock}; + +#[test] +fn it_allows_the_advisory_origin_to_reject_markets() { + ExtBuilder::default().build().execute_with(|| { + run_to_block(2); + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 4..6, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let reject_reason: Vec = + vec![0; ::MaxRejectReasonLen::get() as usize]; + // Now it should work from SUDO + assert_ok!(PredictionMarkets::reject_market( + RuntimeOrigin::signed(SUDO), + 0, + reject_reason.clone() + )); + let reject_reason = reject_reason.try_into().expect("BoundedVec conversion failed"); + System::assert_has_event(Event::MarketRejected(0, reject_reason).into()); + + assert_noop!( + MarketCommons::market(&0), + zrml_market_commons::Error::::MarketDoesNotExist + ); + }); +} + +#[test] +fn reject_errors_if_reject_reason_is_too_long() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..1, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let reject_reason: Vec = + vec![0; ::MaxRejectReasonLen::get() as usize + 1]; + assert_noop!( + PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason), + Error::::RejectReasonLengthExceedsMaxRejectReasonLen + ); + }); +} + +#[test] +fn it_allows_the_advisory_origin_to_reject_markets_with_edit_request() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..1, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + + let reject_reason = vec![0_u8; ::MaxRejectReasonLen::get() as usize]; + assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); + assert!(MarketIdsForEdit::::contains_key(0)); + assert_ok!(PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason)); + assert!(!MarketIdsForEdit::::contains_key(0)); + + assert_noop!( + MarketCommons::market(&0), + zrml_market_commons::Error::::MarketDoesNotExist + ); + }); +} + +#[test] +fn reject_market_unreserves_oracle_bond_and_slashes_advisory_bond() { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + simple_create_categorical_market( + base_asset, + MarketCreation::Advised, + 0..1, + ScoringRule::Lmsr, + ); + + // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check + // that the AdvisoryBond gets slashed but the OracleBond gets unreserved. + assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); + assert_ok!(Balances::reserve_named( + &PredictionMarkets::reserve_id(), + &ALICE, + SENTINEL_AMOUNT, + )); + assert_eq!(Balances::free_balance(Treasury::account_id()), 0); + + let balance_free_before_alice = Balances::free_balance(ALICE); + let balance_reserved_before_alice = + Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); + + let reject_reason: Vec = + vec![0; ::MaxRejectReasonLen::get() as usize]; + assert_ok!(PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason)); + + // AdvisoryBond gets slashed after reject_market + // OracleBond gets unreserved after reject_market + let balance_reserved_after_alice = + Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); + assert_eq!( + balance_reserved_after_alice, + balance_reserved_before_alice + - ::OracleBond::get() + - ::AdvisoryBond::get(), + ); + let balance_free_after_alice = Balances::free_balance(ALICE); + let slash_amount_advisory_bond = ::AdvisoryBondSlashPercentage::get() + .mul_floor(::AdvisoryBond::get()); + let advisory_bond_remains = + ::AdvisoryBond::get() - slash_amount_advisory_bond; + assert_eq!( + balance_free_after_alice, + balance_free_before_alice + + ::OracleBond::get() + + advisory_bond_remains, + ); + + // AdvisoryBond is transferred to the treasury + let balance_treasury_after = Balances::free_balance(Treasury::account_id()); + assert_eq!(balance_treasury_after, slash_amount_advisory_bond); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn reject_market_clears_auto_close_blocks() { + // We don't have to check that reject market clears the cache for opening pools, since Cpmm pools + // can not be deployed on pending advised pools. + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 33..66, + ScoringRule::Lmsr, + ); + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 22..66, + ScoringRule::Lmsr, + ); + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 22..33, + ScoringRule::Lmsr, + ); + let reject_reason: Vec = + vec![0; ::MaxRejectReasonLen::get() as usize]; + assert_ok!(PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason)); + + let auto_close = MarketIdsPerCloseBlock::::get(66); + assert_eq!(auto_close.len(), 1); + assert_eq!(auto_close[0], 1); + }); +} + +#[test] +fn reject_market_fails_on_permissionless_market() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..1, + ScoringRule::Lmsr, + ); + let reject_reason: Vec = + vec![0; ::MaxRejectReasonLen::get() as usize]; + assert_noop!( + PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason), + Error::::InvalidMarketStatus + ); + }); +} + +#[test] +fn reject_market_fails_on_approved_market() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..1, + ScoringRule::Lmsr, + ); + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); + let reject_reason: Vec = + vec![0; ::MaxRejectReasonLen::get() as usize]; + assert_noop!( + PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason), + Error::::InvalidMarketStatus + ); + }); +} From a3827ad8a0afdb4e9a7bfb69108d38b221ab8d06 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 11 Jan 2024 23:31:03 +0100 Subject: [PATCH 14/32] Add `on_market_close` tests --- zrml/prediction-markets/src/old_tests.rs | 281 +--------------- zrml/prediction-markets/src/tests/mod.rs | 1 + .../src/tests/on_market_close.rs | 301 ++++++++++++++++++ 3 files changed, 303 insertions(+), 280 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/on_market_close.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index b9525bc4d..5ee277ee7 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -22,7 +22,7 @@ extern crate alloc; use crate::{ - mock::*, Config, Error, Event, LastTimeFrame, MarketIdsForEdit, MarketIdsPerCloseBlock, + mock::*, Config, Error, Event, LastTimeFrame, MarketIdsPerCloseBlock, MarketIdsPerCloseTimeFrame, MarketIdsPerDisputeBlock, MarketIdsPerReportBlock, TimeFrame, }; use alloc::collections::BTreeMap; @@ -140,285 +140,6 @@ fn simple_create_scalar_market( )); } -#[test] -fn on_market_close_auto_rejects_expired_advised_market() { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check - // that the AdvisoryBond and the OracleBond gets unreserved, when the advised market expires. - assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); - assert_ok!(Balances::reserve_named( - &PredictionMarkets::reserve_id(), - &ALICE, - SENTINEL_AMOUNT - )); - let balance_free_before_alice = Balances::free_balance(ALICE); - let balance_reserved_before_alice = - Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); - - let end = 33; - simple_create_categorical_market( - base_asset, - MarketCreation::Advised, - 0..end, - ScoringRule::Lmsr, - ); - let market_id = 0; - - run_to_block(end); - - assert_eq!( - Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE), - balance_reserved_before_alice - ); - assert_eq!(Balances::free_balance(ALICE), balance_free_before_alice); - assert_noop!( - MarketCommons::market(&market_id), - zrml_market_commons::Error::::MarketDoesNotExist, - ); - System::assert_has_event(Event::MarketExpired(market_id).into()); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_market_close_auto_rejects_expired_advised_market_with_edit_request() { - let test = |base_asset: Asset| { - // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check - // that the AdvisoryBond and the OracleBond gets unreserved, when the advised market expires. - assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); - assert_ok!(Balances::reserve_named( - &PredictionMarkets::reserve_id(), - &ALICE, - SENTINEL_AMOUNT - )); - let balance_free_before_alice = Balances::free_balance(ALICE); - let balance_reserved_before_alice = - Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); - - let end = 33; - simple_create_categorical_market( - base_asset, - MarketCreation::Advised, - 0..end, - ScoringRule::Lmsr, - ); - run_to_block(2); - let market_id = 0; - let market = MarketCommons::market(&market_id); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - - assert_ok!(PredictionMarkets::request_edit( - RuntimeOrigin::signed(SUDO), - market_id, - edit_reason - )); - - assert!(MarketIdsForEdit::::contains_key(0)); - run_blocks(end); - assert!(!MarketIdsForEdit::::contains_key(0)); - - assert_eq!( - Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE), - balance_reserved_before_alice - ); - assert_eq!(Balances::free_balance(ALICE), balance_free_before_alice); - assert_noop!( - MarketCommons::market(&market_id), - zrml_market_commons::Error::::MarketDoesNotExist, - ); - System::assert_has_event(Event::MarketExpired(market_id).into()); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_market_close_successfully_auto_closes_market_with_blocks() { - ExtBuilder::default().build().execute_with(|| { - let end = 33; - let category_count = 3; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - let market_id = 0; - - run_to_block(end - 1); - let market_before_close = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market_before_close.status, MarketStatus::Active); - - run_to_block(end); - let market_after_close = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market_after_close.status, MarketStatus::Closed); - - System::assert_last_event(Event::MarketClosed(market_id).into()); - }); -} - -#[test] -fn on_market_close_successfully_auto_closes_market_with_timestamps() { - ExtBuilder::default().build().execute_with(|| { - let end: Moment = (2 * MILLISECS_PER_BLOCK).into(); - let category_count = 3; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Timestamp(0..end), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - let market_id = 0; - - // (Check that the market doesn't close too soon) - set_timestamp_for_on_initialize(end - 1); - run_to_block(2); // Trigger `on_initialize`; must be at least block #2! - let market_before_close = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market_before_close.status, MarketStatus::Active); - - set_timestamp_for_on_initialize(end); - run_blocks(1); - let market_after_close = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market_after_close.status, MarketStatus::Closed); - - System::assert_last_event(Event::MarketClosed(market_id).into()); - }); -} - -#[test] -fn on_market_close_successfully_auto_closes_multiple_markets_after_stall() { - // We check that `on_market_close` works correctly even if a block takes much longer than 12sec - // to be produced and multiple markets are involved. - ExtBuilder::default().build().execute_with(|| { - // Mock last time frame to prevent it from defaulting. - LastTimeFrame::::set(Some(0)); - - let end: Moment = (5 * MILLISECS_PER_BLOCK).into(); - let category_count = 3; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Timestamp(0..end), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Timestamp(0..end), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - - // This block takes much longer than 12sec, but markets and pools still close correctly. - set_timestamp_for_on_initialize(9 * MILLISECS_PER_BLOCK as u64); - run_to_block(2); // Trigger `on_initialize`; must be at least block #2! - - let market_after_close = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after_close.status, MarketStatus::Closed); - System::assert_has_event(Event::MarketClosed(0).into()); - - let market_after_close = MarketCommons::market(&1).unwrap(); - assert_eq!(market_after_close.status, MarketStatus::Closed); - System::assert_has_event(Event::MarketClosed(1).into()); - }); -} - -#[test] -fn on_market_close_market_status_manager_exceeds_max_recovery_time_frames_after_stall() { - // We check that `on_market_close` works correctly even if a block takes much longer than 12sec - // to be produced and multiple markets are involved. - ExtBuilder::default().build().execute_with(|| { - // Mock last time frame to prevent it from defaulting. - LastTimeFrame::::set(Some(0)); - - let end: Moment = (5 * MILLISECS_PER_BLOCK).into(); - let category_count = 3; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Timestamp(0..end), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Timestamp(0..end), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - - set_timestamp_for_on_initialize( - end + (crate::MAX_RECOVERY_TIME_FRAMES + 1) * MILLISECS_PER_BLOCK as u64, - ); - run_to_block(2); // Trigger `on_initialize`; must be at least block #2! - - System::assert_last_event( - Event::RecoveryLimitReached { last_time_frame: 0, limit_time_frame: 6 }.into(), - ); - - // still active, not closed, because recovery limit reached - let market_after_close = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after_close.status, MarketStatus::Active); - - let market_after_close = MarketCommons::market(&1).unwrap(); - assert_eq!(market_after_close.status, MarketStatus::Active); - }); -} - #[test] fn manually_close_market_after_long_stall() { // We check that `on_market_close` works correctly even if a block takes much longer than 12sec diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 8eb419d0a..8765656f9 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -24,6 +24,7 @@ mod approve_market; mod buy_complete_set; mod create_market; mod edit_market; +mod on_market_close; mod reject_market; mod sell_complete_set; diff --git a/zrml/prediction-markets/src/tests/on_market_close.rs b/zrml/prediction-markets/src/tests/on_market_close.rs new file mode 100644 index 000000000..23ce18632 --- /dev/null +++ b/zrml/prediction-markets/src/tests/on_market_close.rs @@ -0,0 +1,301 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +use crate::{LastTimeFrame, MarketIdsForEdit}; +use zeitgeist_primitives::constants::MILLISECS_PER_BLOCK; + +#[test] +fn on_market_close_auto_rejects_expired_advised_market() { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check + // that the AdvisoryBond and the OracleBond gets unreserved, when the advised market expires. + assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); + assert_ok!(Balances::reserve_named( + &PredictionMarkets::reserve_id(), + &ALICE, + SENTINEL_AMOUNT + )); + let balance_free_before_alice = Balances::free_balance(ALICE); + let balance_reserved_before_alice = + Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); + + let end = 33; + simple_create_categorical_market( + base_asset, + MarketCreation::Advised, + 0..end, + ScoringRule::Lmsr, + ); + let market_id = 0; + + run_to_block(end); + + assert_eq!( + Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE), + balance_reserved_before_alice + ); + assert_eq!(Balances::free_balance(ALICE), balance_free_before_alice); + assert_noop!( + MarketCommons::market(&market_id), + zrml_market_commons::Error::::MarketDoesNotExist, + ); + System::assert_has_event(Event::MarketExpired(market_id).into()); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_market_close_auto_rejects_expired_advised_market_with_edit_request() { + let test = |base_asset: AssetOf| { + // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check + // that the AdvisoryBond and the OracleBond gets unreserved, when the advised market expires. + assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); + assert_ok!(Balances::reserve_named( + &PredictionMarkets::reserve_id(), + &ALICE, + SENTINEL_AMOUNT + )); + let balance_free_before_alice = Balances::free_balance(ALICE); + let balance_reserved_before_alice = + Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); + + let end = 33; + simple_create_categorical_market( + base_asset, + MarketCreation::Advised, + 0..end, + ScoringRule::Lmsr, + ); + run_to_block(2); + let market_id = 0; + let market = MarketCommons::market(&market_id); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + + assert_ok!(PredictionMarkets::request_edit( + RuntimeOrigin::signed(SUDO), + market_id, + edit_reason + )); + + assert!(MarketIdsForEdit::::contains_key(0)); + run_blocks(end); + assert!(!MarketIdsForEdit::::contains_key(0)); + + assert_eq!( + Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE), + balance_reserved_before_alice + ); + assert_eq!(Balances::free_balance(ALICE), balance_free_before_alice); + assert_noop!( + MarketCommons::market(&market_id), + zrml_market_commons::Error::::MarketDoesNotExist, + ); + System::assert_has_event(Event::MarketExpired(market_id).into()); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_market_close_successfully_auto_closes_market_with_blocks() { + ExtBuilder::default().build().execute_with(|| { + let end = 33; + let category_count = 3; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + let market_id = 0; + + run_to_block(end - 1); + let market_before_close = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market_before_close.status, MarketStatus::Active); + + run_to_block(end); + let market_after_close = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market_after_close.status, MarketStatus::Closed); + + System::assert_last_event(Event::MarketClosed(market_id).into()); + }); +} + +#[test] +fn on_market_close_successfully_auto_closes_market_with_timestamps() { + ExtBuilder::default().build().execute_with(|| { + let end = (2 * MILLISECS_PER_BLOCK) as u64; + let category_count = 3; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Timestamp(0..end), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + let market_id = 0; + + // (Check that the market doesn't close too soon) + set_timestamp_for_on_initialize(end - 1); + run_to_block(2); // Trigger `on_initialize`; must be at least block #2! + let market_before_close = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market_before_close.status, MarketStatus::Active); + + set_timestamp_for_on_initialize(end); + run_blocks(1); + let market_after_close = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market_after_close.status, MarketStatus::Closed); + + System::assert_last_event(Event::MarketClosed(market_id).into()); + }); +} + +#[test] +fn on_market_close_successfully_auto_closes_multiple_markets_after_stall() { + // We check that `on_market_close` works correctly even if a block takes much longer than 12sec + // to be produced and multiple markets are involved. + ExtBuilder::default().build().execute_with(|| { + // Mock last time frame to prevent it from defaulting. + LastTimeFrame::::set(Some(0)); + + let end = (5 * MILLISECS_PER_BLOCK) as u64; + let category_count = 3; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Timestamp(0..end), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Timestamp(0..end), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + + // This block takes much longer than 12sec, but markets and pools still close correctly. + set_timestamp_for_on_initialize(9 * MILLISECS_PER_BLOCK as u64); + run_to_block(2); // Trigger `on_initialize`; must be at least block #2! + + let market_after_close = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after_close.status, MarketStatus::Closed); + System::assert_has_event(Event::MarketClosed(0).into()); + + let market_after_close = MarketCommons::market(&1).unwrap(); + assert_eq!(market_after_close.status, MarketStatus::Closed); + System::assert_has_event(Event::MarketClosed(1).into()); + }); +} + +#[test] +fn on_market_close_market_status_manager_exceeds_max_recovery_time_frames_after_stall() { + // We check that `on_market_close` works correctly even if a block takes much longer than 12sec + // to be produced and multiple markets are involved. + ExtBuilder::default().build().execute_with(|| { + // Mock last time frame to prevent it from defaulting. + LastTimeFrame::::set(Some(0)); + + let end = (5 * MILLISECS_PER_BLOCK) as u64; + let category_count = 3; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Timestamp(0..end), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Timestamp(0..end), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + + set_timestamp_for_on_initialize( + end + (crate::MAX_RECOVERY_TIME_FRAMES + 1) * MILLISECS_PER_BLOCK as u64, + ); + run_to_block(2); // Trigger `on_initialize`; must be at least block #2! + + System::assert_last_event( + Event::RecoveryLimitReached { last_time_frame: 0, limit_time_frame: 6 }.into(), + ); + + // still active, not closed, because recovery limit reached + let market_after_close = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after_close.status, MarketStatus::Active); + + let market_after_close = MarketCommons::market(&1).unwrap(); + assert_eq!(market_after_close.status, MarketStatus::Active); + }); +} From 4352aa2d6dc436904b6c330c8975a4eb1d4b0117 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 11 Jan 2024 23:38:18 +0100 Subject: [PATCH 15/32] Extract `manually_close_market` tests --- zrml/prediction-markets/src/old_tests.rs | 147 --------------- .../src/tests/manually_close_market.rs | 169 ++++++++++++++++++ zrml/prediction-markets/src/tests/mod.rs | 1 + 3 files changed, 170 insertions(+), 147 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/manually_close_market.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index 5ee277ee7..fb77e6e9a 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -140,153 +140,6 @@ fn simple_create_scalar_market( )); } -#[test] -fn manually_close_market_after_long_stall() { - // We check that `on_market_close` works correctly even if a block takes much longer than 12sec - // to be produced and multiple markets are involved. - ExtBuilder::default().build().execute_with(|| { - // Mock last time frame to prevent it from defaulting. - LastTimeFrame::::set(Some(0)); - - let end: Moment = (5 * MILLISECS_PER_BLOCK).into(); - let category_count = 3; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Timestamp(0..end), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Timestamp(0..end), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - - // This block takes much longer than 12sec, but markets and pools still close correctly. - set_timestamp_for_on_initialize( - end + (crate::MAX_RECOVERY_TIME_FRAMES + 1) * MILLISECS_PER_BLOCK as u64, - ); - run_to_block(2); // Trigger `on_initialize`; must be at least block #2! - let new_end = >::now(); - assert_ne!(end, new_end); - - // still active, not closed, because recovery limit reached - let market_after_close = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after_close.status, MarketStatus::Active); - - let market_after_close = MarketCommons::market(&1).unwrap(); - assert_eq!(market_after_close.status, MarketStatus::Active); - - let range_end_time_frame = crate::Pallet::::calculate_time_frame_of_moment(end); - assert_eq!(MarketIdsPerCloseTimeFrame::::get(range_end_time_frame), vec![0, 1]); - - assert_ok!(PredictionMarkets::manually_close_market(RuntimeOrigin::signed(ALICE), 0)); - assert_eq!(MarketIdsPerCloseTimeFrame::::get(range_end_time_frame), vec![1]); - let market_after_manual_close = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after_manual_close.status, MarketStatus::Closed); - - assert_eq!(market_after_manual_close.period, MarketPeriod::Timestamp(0..new_end)); - assert_ok!(PredictionMarkets::manually_close_market(RuntimeOrigin::signed(ALICE), 1)); - assert_eq!(MarketIdsPerCloseTimeFrame::::get(range_end_time_frame), vec![]); - let market_after_manual_close = MarketCommons::market(&1).unwrap(); - assert_eq!(market_after_manual_close.status, MarketStatus::Closed); - assert_eq!(market_after_manual_close.period, MarketPeriod::Timestamp(0..new_end)); - }); -} - -#[test] -fn manually_close_market_fails_if_market_not_in_close_time_frame_list() { - // We check that `on_market_close` works correctly even if a block takes much longer than 12sec - // to be produced and multiple markets are involved. - ExtBuilder::default().build().execute_with(|| { - // Mock last time frame to prevent it from defaulting. - LastTimeFrame::::set(Some(0)); - - let end: Moment = (5 * MILLISECS_PER_BLOCK).into(); - let category_count = 3; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Timestamp(0..end), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - - // remove market from open time frame list - let range_end_time_frame = crate::Pallet::::calculate_time_frame_of_moment(end); - crate::MarketIdsPerCloseTimeFrame::::remove(range_end_time_frame); - - // This block takes much longer than 12sec, but markets and pools still close correctly. - set_timestamp_for_on_initialize( - end + (crate::MAX_RECOVERY_TIME_FRAMES + 1) * MILLISECS_PER_BLOCK as u64, - ); - run_to_block(2); // Trigger `on_initialize`; must be at least block #2! - - assert_noop!( - PredictionMarkets::manually_close_market(RuntimeOrigin::signed(ALICE), 0), - Error::::MarketNotInCloseTimeFrameList - ); - }); -} - -#[test] -fn manually_close_market_fails_if_not_allowed_for_block_based_markets() { - // We check that `on_market_close` works correctly even if a block takes much longer than 12sec - // to be produced and multiple markets are involved. - ExtBuilder::default().build().execute_with(|| { - // Mock last time frame to prevent it from defaulting. - LastTimeFrame::::set(Some(0)); - - let category_count = 3; - let end = 5; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - - // This block takes much longer than 12sec, but markets and pools still close correctly. - set_timestamp_for_on_initialize( - end + (crate::MAX_RECOVERY_TIME_FRAMES + 1) * MILLISECS_PER_BLOCK as u64, - ); - run_to_block(2); // Trigger `on_initialize`; must be at least block #2! - - assert_noop!( - PredictionMarkets::manually_close_market(RuntimeOrigin::signed(ALICE), 0), - Error::::NotAllowedForBlockBasedMarkets - ); - }); -} - #[test] fn on_initialize_skips_the_genesis_block() { // We ensure that a timestamp of zero will not be stored at genesis into LastTimeFrame storage. diff --git a/zrml/prediction-markets/src/tests/manually_close_market.rs b/zrml/prediction-markets/src/tests/manually_close_market.rs new file mode 100644 index 000000000..0d39626cd --- /dev/null +++ b/zrml/prediction-markets/src/tests/manually_close_market.rs @@ -0,0 +1,169 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +use crate::{LastTimeFrame, MarketIdsPerCloseTimeFrame}; +use zeitgeist_primitives::constants::MILLISECS_PER_BLOCK; + +#[test] +fn manually_close_market_after_long_stall() { + // We check that `on_market_close` works correctly even if a block takes much longer than 12sec + // to be produced and multiple markets are involved. + ExtBuilder::default().build().execute_with(|| { + // Mock last time frame to prevent it from defaulting. + LastTimeFrame::::set(Some(0)); + + let end = (5 * MILLISECS_PER_BLOCK) as u64; + let category_count = 3; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Timestamp(0..end), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Timestamp(0..end), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + + // This block takes much longer than 12sec, but markets and pools still close correctly. + set_timestamp_for_on_initialize( + end + (crate::MAX_RECOVERY_TIME_FRAMES + 1) * MILLISECS_PER_BLOCK as u64, + ); + run_to_block(2); // Trigger `on_initialize`; must be at least block #2! + let new_end = >::now(); + assert_ne!(end, new_end); + + // still active, not closed, because recovery limit reached + let market_after_close = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after_close.status, MarketStatus::Active); + + let market_after_close = MarketCommons::market(&1).unwrap(); + assert_eq!(market_after_close.status, MarketStatus::Active); + + let range_end_time_frame = crate::Pallet::::calculate_time_frame_of_moment(end); + assert_eq!(MarketIdsPerCloseTimeFrame::::get(range_end_time_frame), vec![0, 1]); + + assert_ok!(PredictionMarkets::manually_close_market(RuntimeOrigin::signed(ALICE), 0)); + assert_eq!(MarketIdsPerCloseTimeFrame::::get(range_end_time_frame), vec![1]); + let market_after_manual_close = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after_manual_close.status, MarketStatus::Closed); + + assert_eq!(market_after_manual_close.period, MarketPeriod::Timestamp(0..new_end)); + assert_ok!(PredictionMarkets::manually_close_market(RuntimeOrigin::signed(ALICE), 1)); + assert_eq!(MarketIdsPerCloseTimeFrame::::get(range_end_time_frame), vec![]); + let market_after_manual_close = MarketCommons::market(&1).unwrap(); + assert_eq!(market_after_manual_close.status, MarketStatus::Closed); + assert_eq!(market_after_manual_close.period, MarketPeriod::Timestamp(0..new_end)); + }); +} + +#[test] +fn manually_close_market_fails_if_market_not_in_close_time_frame_list() { + // We check that `on_market_close` works correctly even if a block takes much longer than 12sec + // to be produced and multiple markets are involved. + ExtBuilder::default().build().execute_with(|| { + // Mock last time frame to prevent it from defaulting. + LastTimeFrame::::set(Some(0)); + + let end = (5 * MILLISECS_PER_BLOCK) as u64; + let category_count = 3; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Timestamp(0..end), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + + // remove market from open time frame list + let range_end_time_frame = crate::Pallet::::calculate_time_frame_of_moment(end); + crate::MarketIdsPerCloseTimeFrame::::remove(range_end_time_frame); + + // This block takes much longer than 12sec, but markets and pools still close correctly. + set_timestamp_for_on_initialize( + end + (crate::MAX_RECOVERY_TIME_FRAMES + 1) * MILLISECS_PER_BLOCK as u64, + ); + run_to_block(2); // Trigger `on_initialize`; must be at least block #2! + + assert_noop!( + PredictionMarkets::manually_close_market(RuntimeOrigin::signed(ALICE), 0), + Error::::MarketNotInCloseTimeFrameList + ); + }); +} + +#[test] +fn manually_close_market_fails_if_not_allowed_for_block_based_markets() { + // We check that `on_market_close` works correctly even if a block takes much longer than 12sec + // to be produced and multiple markets are involved. + ExtBuilder::default().build().execute_with(|| { + // Mock last time frame to prevent it from defaulting. + LastTimeFrame::::set(Some(0)); + + let category_count = 3; + let end = 5; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + + // This block takes much longer than 12sec, but markets and pools still close correctly. + set_timestamp_for_on_initialize( + end + (crate::MAX_RECOVERY_TIME_FRAMES + 1) * MILLISECS_PER_BLOCK as u64, + ); + run_to_block(2); // Trigger `on_initialize`; must be at least block #2! + + assert_noop!( + PredictionMarkets::manually_close_market(RuntimeOrigin::signed(ALICE), 0), + Error::::NotAllowedForBlockBasedMarkets + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 8765656f9..df8435628 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -24,6 +24,7 @@ mod approve_market; mod buy_complete_set; mod create_market; mod edit_market; +mod manually_close_market; mod on_market_close; mod reject_market; mod sell_complete_set; From 9cb5dfbcf8ac362252428e0f5c8f17544bac276e Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Fri, 12 Jan 2024 13:33:04 +0100 Subject: [PATCH 16/32] Extract `on_initialize` tests --- zrml/prediction-markets/src/old_tests.rs | 51 ++-------------- zrml/prediction-markets/src/tests/mod.rs | 1 + .../src/tests/on_initialize.rs | 61 +++++++++++++++++++ 3 files changed, 67 insertions(+), 46 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/on_initialize.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index fb77e6e9a..15eebddac 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -22,15 +22,12 @@ extern crate alloc; use crate::{ - mock::*, Config, Error, Event, LastTimeFrame, MarketIdsPerCloseBlock, - MarketIdsPerCloseTimeFrame, MarketIdsPerDisputeBlock, MarketIdsPerReportBlock, TimeFrame, + mock::*, Config, Error, Event, MarketIdsPerCloseBlock, MarketIdsPerCloseTimeFrame, + MarketIdsPerDisputeBlock, MarketIdsPerReportBlock, TimeFrame, }; use alloc::collections::BTreeMap; use core::ops::Range; -use frame_support::{ - assert_noop, assert_ok, - traits::{NamedReservableCurrency, OnInitialize}, -}; +use frame_support::{assert_noop, assert_ok, traits::NamedReservableCurrency}; use sp_runtime::{traits::BlakeTwo256, Perquintill}; use test_case::test_case; use zeitgeist_primitives::types::{EarlyClose, EarlyCloseState}; @@ -47,8 +44,8 @@ use zeitgeist_primitives::{ }, types::{ AccountIdTest, Asset, Balance, Bond, Deadlines, MarketBonds, MarketCreation, - MarketDisputeMechanism, MarketId, MarketPeriod, MarketStatus, MarketType, Moment, - MultiHash, OutcomeReport, Report, ScalarPosition, ScoringRule, + MarketDisputeMechanism, MarketId, MarketPeriod, MarketStatus, MarketType, MultiHash, + OutcomeReport, Report, ScalarPosition, ScoringRule, }, }; use zrml_global_disputes::{ @@ -140,44 +137,6 @@ fn simple_create_scalar_market( )); } -#[test] -fn on_initialize_skips_the_genesis_block() { - // We ensure that a timestamp of zero will not be stored at genesis into LastTimeFrame storage. - let blocks = 5; - let end: Moment = (blocks * MILLISECS_PER_BLOCK).into(); - ExtBuilder::default().build().execute_with(|| { - let category_count = 3; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Timestamp(0..end), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - - // Blocknumber = 0 - assert_eq!(Timestamp::get(), 0); - PredictionMarkets::on_initialize(0); - assert_eq!(LastTimeFrame::::get(), None); - - // Blocknumber = 1 - assert_eq!(Timestamp::get(), 0); - PredictionMarkets::on_initialize(1); - assert_eq!(LastTimeFrame::::get(), None); - - // Blocknumer != 0, 1 - set_timestamp_for_on_initialize(end); - PredictionMarkets::on_initialize(2); - assert_eq!(LastTimeFrame::::get(), Some(blocks.into())); - }); -} - #[test] fn it_allows_to_report_the_outcome_of_a_market() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index df8435628..5007322e1 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -25,6 +25,7 @@ mod buy_complete_set; mod create_market; mod edit_market; mod manually_close_market; +mod on_initialize; mod on_market_close; mod reject_market; mod sell_complete_set; diff --git a/zrml/prediction-markets/src/tests/on_initialize.rs b/zrml/prediction-markets/src/tests/on_initialize.rs new file mode 100644 index 000000000..0d5d74427 --- /dev/null +++ b/zrml/prediction-markets/src/tests/on_initialize.rs @@ -0,0 +1,61 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +use crate::LastTimeFrame; +use frame_support::traits::Hooks; +use zeitgeist_primitives::constants::MILLISECS_PER_BLOCK; + +#[test] +fn on_initialize_skips_the_genesis_block() { + // We ensure that a timestamp of zero will not be stored at genesis into LastTimeFrame storage. + let blocks = 5; + let end = (blocks * MILLISECS_PER_BLOCK) as u64; + ExtBuilder::default().build().execute_with(|| { + let category_count = 3; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Timestamp(0..end), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + + // Blocknumber = 0 + assert_eq!(Timestamp::get(), 0); + PredictionMarkets::on_initialize(0); + assert_eq!(LastTimeFrame::::get(), None); + + // Blocknumber = 1 + assert_eq!(Timestamp::get(), 0); + PredictionMarkets::on_initialize(1); + assert_eq!(LastTimeFrame::::get(), None); + + // Blocknumer != 0, 1 + set_timestamp_for_on_initialize(end); + PredictionMarkets::on_initialize(2); + assert_eq!(LastTimeFrame::::get(), Some(blocks.into())); + }); +} From 0f4b1f9959d517cdb85fd980987dc4c94e73b53d Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Fri, 12 Jan 2024 13:48:47 +0100 Subject: [PATCH 17/32] Extract `report` tests --- zrml/prediction-markets/src/old_tests.rs | 387 ------------------- zrml/prediction-markets/src/tests/mod.rs | 1 + zrml/prediction-markets/src/tests/report.rs | 408 ++++++++++++++++++++ 3 files changed, 409 insertions(+), 387 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/report.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index 15eebddac..bd2d8b54c 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -137,227 +137,6 @@ fn simple_create_scalar_market( )); } -#[test] -fn it_allows_to_report_the_outcome_of_a_market() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - assert!(market.report.is_none()); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - - let market_after = MarketCommons::market(&0).unwrap(); - let report = market_after.report.unwrap(); - assert_eq!(market_after.status, MarketStatus::Reported); - assert_eq!(report.outcome, OutcomeReport::Categorical(1)); - assert_eq!(report.by, market_after.oracle); - - // Reset and report again as approval origin - let _ = MarketCommons::mutate_market(&0, |market| { - market.status = MarketStatus::Closed; - market.report = None; - Ok(()) - }); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(SUDO), - 0, - OutcomeReport::Categorical(1) - )); - }); -} - -#[test] -fn report_fails_before_grace_period_is_over() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - run_to_block(end); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - assert!(market.report.is_none()); - - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), - Error::::NotAllowedToReportYet - ); - }); -} - -#[test] -fn it_allows_only_oracle_to_report_the_outcome_of_a_market_during_oracle_duration_blocks() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - assert!(market.report.is_none()); - - assert_noop!( - PredictionMarkets::report( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(1) - ), - Error::::ReporterNotOracle - ); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - - let market_after = MarketCommons::market(&0).unwrap(); - let report = market_after.report.unwrap(); - assert_eq!(market_after.status, MarketStatus::Reported); - assert_eq!(report.outcome, OutcomeReport::Categorical(1)); - assert_eq!(report.by, market_after.oracle); - }); -} - -#[test] -fn it_allows_only_oracle_to_report_the_outcome_of_a_market_during_oracle_duration_moment() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(0..100_000_000), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - )); - - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); - - // set the timestamp - let market = MarketCommons::market(&0).unwrap(); - // set the timestamp - - set_timestamp_for_on_initialize(100_000_000); - run_to_block(2); // Trigger `on_initialize`; must be at least block #2. - let grace_period: u64 = market.deadlines.grace_period * MILLISECS_PER_BLOCK as u64; - Timestamp::set_timestamp(100_000_000 + grace_period); - - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(EVE), 0, OutcomeReport::Categorical(1)), - Error::::ReporterNotOracle - ); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - }); -} - -#[test] -fn report_fails_on_mismatched_outcome_for_categorical_market() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Scalar(123)), - Error::::OutcomeMismatch, - ); - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - assert!(market.report.is_none()); - }); -} - -#[test] -fn report_fails_on_out_of_range_outcome_for_categorical_market() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(2)), - Error::::OutcomeMismatch, - ); - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - assert!(market.report.is_none()); - }); -} - -#[test] -fn report_fails_on_mismatched_outcome_for_scalar_market() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_scalar_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(0)), - Error::::OutcomeMismatch, - ); - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - assert!(market.report.is_none()); - }); -} - #[test] fn it_allows_to_dispute_the_outcome_of_a_market() { ExtBuilder::default().build().execute_with(|| { @@ -1476,43 +1255,6 @@ fn dispute_emits_event() { }); } -#[test] -fn it_allows_anyone_to_report_an_unreported_market() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - let market = MarketCommons::market(&0).unwrap(); - // Just skip to waaaay overdue. - run_to_block(end + market.deadlines.grace_period + market.deadlines.oracle_duration + 1); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(ALICE), // alice reports her own market now - 0, - OutcomeReport::Categorical(1), - )); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Reported); - assert_eq!(market.report.unwrap().by, ALICE); - // but oracle was bob - assert_eq!(market.oracle, BOB); - - // make sure it still resolves - run_to_block( - frame_system::Pallet::::block_number() + market.deadlines.dispute_duration, - ); - - let market_after = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after.status, MarketStatus::Resolved); - }); -} - #[test] fn it_correctly_resolves_a_market_that_was_reported_on() { ExtBuilder::default().build().execute_with(|| { @@ -3241,135 +2983,6 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_ma }); } -#[test] -fn report_fails_on_market_state_proposed() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(0..100_000_000), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - )); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), - Error::::MarketIsNotClosed, - ); - }); -} - -#[test] -fn report_fails_on_market_state_closed_for_advised_market() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(0..100_000_000), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - )); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), - Error::::MarketIsNotClosed, - ); - }); -} - -#[test] -fn report_fails_on_market_state_active() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(0..100_000_000), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - )); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), - Error::::MarketIsNotClosed, - ); - }); -} - -#[test] -fn report_fails_on_market_state_resolved() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(0..100_000_000), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - )); - let _ = MarketCommons::mutate_market(&0, |market| { - market.status = MarketStatus::Resolved; - Ok(()) - }); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), - Error::::MarketIsNotClosed, - ); - }); -} - -#[test] -fn report_fails_if_reporter_is_not_the_oracle() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(0..100_000_000), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - )); - let market = MarketCommons::market(&0).unwrap(); - set_timestamp_for_on_initialize(100_000_000); - // Trigger hooks which close the market. - run_to_block(2); - let grace_period: u64 = market.deadlines.grace_period * MILLISECS_PER_BLOCK as u64; - set_timestamp_for_on_initialize(100_000_000 + grace_period + MILLISECS_PER_BLOCK as u64); - assert_noop!( - PredictionMarkets::report( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(1) - ), - Error::::ReporterNotOracle, - ); - }); -} - #[test] fn create_market_and_deploy_pool_works() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 5007322e1..aaf9bd5eb 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -28,6 +28,7 @@ mod manually_close_market; mod on_initialize; mod on_market_close; mod reject_market; +mod report; mod sell_complete_set; use crate::{mock::*, AccountIdOf, AssetOf, BalanceOf, Config, Error, Event}; diff --git a/zrml/prediction-markets/src/tests/report.rs b/zrml/prediction-markets/src/tests/report.rs new file mode 100644 index 000000000..13f0bbccd --- /dev/null +++ b/zrml/prediction-markets/src/tests/report.rs @@ -0,0 +1,408 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +use zeitgeist_primitives::{constants::MILLISECS_PER_BLOCK, types::OutcomeReport}; + +#[test] +fn it_allows_to_report_the_outcome_of_a_market() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + assert!(market.report.is_none()); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + + let market_after = MarketCommons::market(&0).unwrap(); + let report = market_after.report.unwrap(); + assert_eq!(market_after.status, MarketStatus::Reported); + assert_eq!(report.outcome, OutcomeReport::Categorical(1)); + assert_eq!(report.by, market_after.oracle); + + // Reset and report again as approval origin + let _ = MarketCommons::mutate_market(&0, |market| { + market.status = MarketStatus::Closed; + market.report = None; + Ok(()) + }); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(SUDO), + 0, + OutcomeReport::Categorical(1) + )); + }); +} + +#[test] +fn report_fails_before_grace_period_is_over() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + run_to_block(end); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + assert!(market.report.is_none()); + + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), + Error::::NotAllowedToReportYet + ); + }); +} + +#[test] +fn it_allows_only_oracle_to_report_the_outcome_of_a_market_during_oracle_duration_blocks() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + assert!(market.report.is_none()); + + assert_noop!( + PredictionMarkets::report( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(1) + ), + Error::::ReporterNotOracle + ); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + + let market_after = MarketCommons::market(&0).unwrap(); + let report = market_after.report.unwrap(); + assert_eq!(market_after.status, MarketStatus::Reported); + assert_eq!(report.outcome, OutcomeReport::Categorical(1)); + assert_eq!(report.by, market_after.oracle); + }); +} + +#[test] +fn it_allows_only_oracle_to_report_the_outcome_of_a_market_during_oracle_duration_moment() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(0..100_000_000), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + )); + + assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); + + // set the timestamp + let market = MarketCommons::market(&0).unwrap(); + // set the timestamp + + set_timestamp_for_on_initialize(100_000_000); + run_to_block(2); // Trigger `on_initialize`; must be at least block #2. + let grace_period: u64 = market.deadlines.grace_period * MILLISECS_PER_BLOCK as u64; + Timestamp::set_timestamp(100_000_000 + grace_period); + + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(EVE), 0, OutcomeReport::Categorical(1)), + Error::::ReporterNotOracle + ); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + }); +} + +#[test] +fn report_fails_on_mismatched_outcome_for_categorical_market() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Scalar(123)), + Error::::OutcomeMismatch, + ); + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + assert!(market.report.is_none()); + }); +} + +#[test] +fn report_fails_on_out_of_range_outcome_for_categorical_market() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(2)), + Error::::OutcomeMismatch, + ); + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + assert!(market.report.is_none()); + }); +} + +#[test] +fn report_fails_on_mismatched_outcome_for_scalar_market() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_scalar_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(0)), + Error::::OutcomeMismatch, + ); + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + assert!(market.report.is_none()); + }); +} + +#[test] +fn it_allows_anyone_to_report_an_unreported_market() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + let market = MarketCommons::market(&0).unwrap(); + // Just skip to waaaay overdue. + run_to_block(end + market.deadlines.grace_period + market.deadlines.oracle_duration + 1); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(ALICE), // alice reports her own market now + 0, + OutcomeReport::Categorical(1), + )); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Reported); + assert_eq!(market.report.unwrap().by, ALICE); + // but oracle was bob + assert_eq!(market.oracle, BOB); + + // make sure it still resolves + run_to_block( + frame_system::Pallet::::block_number() + market.deadlines.dispute_duration, + ); + + let market_after = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after.status, MarketStatus::Resolved); + }); +} + +#[test] +fn report_fails_on_market_state_proposed() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(0..100_000_000), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + )); + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), + Error::::MarketIsNotClosed, + ); + }); +} + +#[test] +fn report_fails_on_market_state_closed_for_advised_market() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(0..100_000_000), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + )); + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), + Error::::MarketIsNotClosed, + ); + }); +} + +#[test] +fn report_fails_on_market_state_active() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(0..100_000_000), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + )); + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), + Error::::MarketIsNotClosed, + ); + }); +} + +#[test] +fn report_fails_on_market_state_resolved() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(0..100_000_000), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + )); + let _ = MarketCommons::mutate_market(&0, |market| { + market.status = MarketStatus::Resolved; + Ok(()) + }); + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), + Error::::MarketIsNotClosed, + ); + }); +} + +#[test] +fn report_fails_if_reporter_is_not_the_oracle() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(0..100_000_000), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + )); + let market = MarketCommons::market(&0).unwrap(); + set_timestamp_for_on_initialize(100_000_000); + // Trigger hooks which close the market. + run_to_block(2); + let grace_period: u64 = market.deadlines.grace_period * MILLISECS_PER_BLOCK as u64; + set_timestamp_for_on_initialize(100_000_000 + grace_period + MILLISECS_PER_BLOCK as u64); + assert_noop!( + PredictionMarkets::report( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(1) + ), + Error::::ReporterNotOracle, + ); + }); +} From 7717b4e0de128ebe727c2b44b3f3c7e7d788768a Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Fri, 12 Jan 2024 13:50:26 +0100 Subject: [PATCH 18/32] Extract `dispute` tests --- zrml/prediction-markets/src/old_tests.rs | 171 ---------------- zrml/prediction-markets/src/tests/dispute.rs | 193 +++++++++++++++++++ zrml/prediction-markets/src/tests/mod.rs | 1 + 3 files changed, 194 insertions(+), 171 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/dispute.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index bd2d8b54c..34bf546d2 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -137,177 +137,6 @@ fn simple_create_scalar_market( )); } -#[test] -fn it_allows_to_dispute_the_outcome_of_a_market() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - // Run to the end of the trading phase. - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - - let dispute_at = grace_period + 2; - run_to_block(dispute_at); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(0) - )); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Disputed); - - let disputes = zrml_simple_disputes::Disputes::::get(0); - assert_eq!(disputes.len(), 1); - let dispute = &disputes[0]; - assert_eq!(dispute.at, dispute_at); - assert_eq!(dispute.by, CHARLIE); - assert_eq!(dispute.outcome, OutcomeReport::Categorical(0)); - - let dispute_ends_at = dispute_at + market.deadlines.dispute_duration; - let market_ids = MarketIdsPerDisputeBlock::::get(dispute_ends_at); - assert_eq!(market_ids.len(), 1); - assert_eq!(market_ids[0], 0); - }); -} - -#[test] -fn dispute_fails_disputed_already() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - - // Run to the end of the trading phase. - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - - let dispute_at = grace_period + 2; - run_to_block(dispute_at); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - - assert_noop!( - PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0), - Error::::InvalidMarketStatus, - ); - }); -} - -#[test] -fn dispute_fails_if_market_not_reported() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - - // Run to the end of the trading phase. - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - - // no report happening here... - - let dispute_at = grace_period + 2; - run_to_block(dispute_at); - - assert_noop!( - PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0), - Error::::InvalidMarketStatus, - ); - }); -} - -#[test] -fn dispute_reserves_dispute_bond() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - - // Run to the end of the trading phase. - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - - let dispute_at = grace_period + 2; - run_to_block(dispute_at); - - let free_charlie_before = Balances::free_balance(CHARLIE); - let reserved_charlie = Balances::reserved_balance(CHARLIE); - assert_eq!(reserved_charlie, 0); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - - let free_charlie_after = Balances::free_balance(CHARLIE); - assert_eq!(free_charlie_before - free_charlie_after, DisputeBond::get()); - - let reserved_charlie = Balances::reserved_balance(CHARLIE); - assert_eq!(reserved_charlie, DisputeBond::get()); - }); -} - #[test] fn schedule_early_close_emits_event() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/dispute.rs b/zrml/prediction-markets/src/tests/dispute.rs new file mode 100644 index 000000000..e34ff7a7a --- /dev/null +++ b/zrml/prediction-markets/src/tests/dispute.rs @@ -0,0 +1,193 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +use crate::MarketIdsPerDisputeBlock; +use zeitgeist_primitives::types::OutcomeReport; + +#[test] +fn it_allows_to_dispute_the_outcome_of_a_market() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + // Run to the end of the trading phase. + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + + let dispute_at = grace_period + 2; + run_to_block(dispute_at); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(0) + )); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Disputed); + + let disputes = zrml_simple_disputes::Disputes::::get(0); + assert_eq!(disputes.len(), 1); + let dispute = &disputes[0]; + assert_eq!(dispute.at, dispute_at); + assert_eq!(dispute.by, CHARLIE); + assert_eq!(dispute.outcome, OutcomeReport::Categorical(0)); + + let dispute_ends_at = dispute_at + market.deadlines.dispute_duration; + let market_ids = MarketIdsPerDisputeBlock::::get(dispute_ends_at); + assert_eq!(market_ids.len(), 1); + assert_eq!(market_ids[0], 0); + }); +} + +#[test] +fn dispute_fails_disputed_already() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + + // Run to the end of the trading phase. + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + + let dispute_at = grace_period + 2; + run_to_block(dispute_at); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); + + assert_noop!( + PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0), + Error::::InvalidMarketStatus, + ); + }); +} + +#[test] +fn dispute_fails_if_market_not_reported() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + + // Run to the end of the trading phase. + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + + // no report happening here... + + let dispute_at = grace_period + 2; + run_to_block(dispute_at); + + assert_noop!( + PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0), + Error::::InvalidMarketStatus, + ); + }); +} + +#[test] +fn dispute_reserves_dispute_bond() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + + // Run to the end of the trading phase. + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + + let dispute_at = grace_period + 2; + run_to_block(dispute_at); + + let free_charlie_before = Balances::free_balance(CHARLIE); + let reserved_charlie = Balances::reserved_balance(CHARLIE); + assert_eq!(reserved_charlie, 0); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); + + let free_charlie_after = Balances::free_balance(CHARLIE); + assert_eq!(free_charlie_before - free_charlie_after, DisputeBond::get()); + + let reserved_charlie = Balances::reserved_balance(CHARLIE); + assert_eq!(reserved_charlie, DisputeBond::get()); + }); +} diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index aaf9bd5eb..548c1220f 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -23,6 +23,7 @@ mod admin_move_market_to_resolved; mod approve_market; mod buy_complete_set; mod create_market; +mod dispute; mod edit_market; mod manually_close_market; mod on_initialize; From 87c1dc3270e03cdeb76944ca1f164a727d74d7e6 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Fri, 12 Jan 2024 14:06:37 +0100 Subject: [PATCH 19/32] Extract `schedule_early_close` tests --- zrml/prediction-markets/src/old_tests.rs | 318 +---------------- zrml/prediction-markets/src/tests/mod.rs | 1 + .../src/tests/schedule_early_close.rs | 335 ++++++++++++++++++ 3 files changed, 340 insertions(+), 314 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/schedule_early_close.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index 34bf546d2..e2a2c6b66 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -22,8 +22,8 @@ extern crate alloc; use crate::{ - mock::*, Config, Error, Event, MarketIdsPerCloseBlock, MarketIdsPerCloseTimeFrame, - MarketIdsPerDisputeBlock, MarketIdsPerReportBlock, TimeFrame, + mock::*, Config, Error, Event, MarketIdsPerCloseBlock, + MarketIdsPerDisputeBlock, MarketIdsPerReportBlock, }; use alloc::collections::BTreeMap; use core::ops::Range; @@ -35,11 +35,11 @@ use zrml_court::{types::*, Error as CError}; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use sp_arithmetic::Perbill; -use sp_runtime::traits::{Hash, SaturatedConversion, Zero}; +use sp_runtime::traits::{Hash, Zero}; use zeitgeist_primitives::{ constants::mock::{ CloseEarlyBlockPeriod, CloseEarlyDisputeBond, CloseEarlyProtectionBlockPeriod, - CloseEarlyProtectionTimeFramePeriod, CloseEarlyRequestBond, MaxAppeals, MaxSelectedDraws, + CloseEarlyRequestBond, MaxAppeals, MaxSelectedDraws, MinJurorStake, OutcomeBond, OutcomeFactor, OutsiderBond, BASE, CENT, MILLISECS_PER_BLOCK, }, types::{ @@ -137,40 +137,6 @@ fn simple_create_scalar_market( )); } -#[test] -fn schedule_early_close_emits_event() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - // just to ensure events are emitted - run_blocks(2); - - let market_id = 0; - - assert_ok!(PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id)); - - let now = >::block_number(); - let new_end = now + CloseEarlyProtectionBlockPeriod::get(); - assert!(new_end < end); - - let new_period = MarketPeriod::Block(0..new_end); - System::assert_last_event( - Event::MarketEarlyCloseScheduled { - market_id, - new_period: new_period.clone(), - state: EarlyCloseState::ScheduledAsOther, - } - .into(), - ); - }); -} - #[test] fn dispute_early_close_emits_event() { ExtBuilder::default().build().execute_with(|| { @@ -284,282 +250,6 @@ fn reject_early_close_fails_if_state_is_rejected() { }); } -#[test] -fn sudo_schedule_early_close_at_block_works() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - let old_market_period = market.period; - assert_eq!(market.status, MarketStatus::Active); - let market_ids_to_close = >::iter().next().unwrap(); - assert_eq!(market_ids_to_close.0, end); - assert_eq!(market_ids_to_close.1.into_inner(), vec![market_id]); - assert!(market.early_close.is_none()); - - assert_ok!( - PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) - ); - - let now = >::block_number(); - let new_end = now + CloseEarlyProtectionBlockPeriod::get(); - assert!(new_end < end); - - let market = MarketCommons::market(&market_id).unwrap(); - let new_period = MarketPeriod::Block(0..new_end); - assert_eq!( - market.early_close.unwrap(), - EarlyClose { - old: old_market_period, - new: new_period, - state: EarlyCloseState::ScheduledAsOther, - } - ); - - let market_ids_to_close = >::iter().collect::>(); - assert_eq!(market_ids_to_close.len(), 2); - - // The first entry is the old one without a market id inside. - let first = market_ids_to_close.first().unwrap(); - assert_eq!(first.0, end); - assert!(first.1.clone().into_inner().is_empty()); - - // The second entry is the new one with the market id inside. - let second = market_ids_to_close.last().unwrap(); - assert_eq!(second.0, new_end); - assert_eq!(second.1.clone().into_inner(), vec![market_id]); - - run_to_block(new_end + 1); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - }); -} - -#[test] -fn sudo_schedule_early_close_at_timeframe_works() { - ExtBuilder::default().build().execute_with(|| { - let start_block = 7; - set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as u64); - run_blocks(start_block); - let start = >::now(); - - let end = start + 42.saturated_into::() * MILLISECS_PER_BLOCK as u64; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(start..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - let old_market_period = market.period; - assert_eq!(market.status, MarketStatus::Active); - let market_ids_to_close = >::iter().collect::>(); - assert_eq!(market_ids_to_close.len(), 1); - let first = market_ids_to_close.first().unwrap(); - assert_eq!(first.0, end.saturating_div(MILLISECS_PER_BLOCK.into())); - assert_eq!(first.1.clone().into_inner(), vec![market_id]); - assert!(market.early_close.is_none()); - - assert_ok!( - PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) - ); - - let now = >::now(); - let new_end = now + CloseEarlyProtectionTimeFramePeriod::get(); - assert!(new_end < end); - - let market = MarketCommons::market(&market_id).unwrap(); - let new_period = MarketPeriod::Timestamp(start..new_end); - assert_eq!( - market.early_close.unwrap(), - EarlyClose { - old: old_market_period, - new: new_period, - state: EarlyCloseState::ScheduledAsOther, - } - ); - - let market_ids_to_close = >::iter().collect::>(); - assert_eq!(market_ids_to_close.len(), 2); - - // The first entry is the new one with the market id inside. - let first = market_ids_to_close.first().unwrap(); - assert_eq!(first.0, new_end.saturating_div(MILLISECS_PER_BLOCK.into())); - assert_eq!(first.1.clone().into_inner(), vec![market_id]); - - // The second entry is the old one without a market id inside. - let second = market_ids_to_close.last().unwrap(); - assert_eq!(second.0, end.saturating_div(MILLISECS_PER_BLOCK.into())); - assert!(second.1.clone().into_inner().is_empty()); - - set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as u64 + new_end); - run_to_block(start_block + new_end.saturating_div(MILLISECS_PER_BLOCK.into()) + 1); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - }); -} - -#[test] -fn schedule_early_close_block_fails_if_early_close_request_too_late() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - run_to_block(end - 1); - - let market_id = 0; - assert_noop!( - PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(ALICE), market_id,), - Error::::EarlyCloseRequestTooLate - ); - }); -} - -#[test] -fn schedule_early_close_timestamp_fails_if_early_close_request_too_late() { - ExtBuilder::default().build().execute_with(|| { - let start_block = 7; - set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as u64); - run_blocks(start_block); - let start = >::now(); - let end = start + 42.saturated_into::() * MILLISECS_PER_BLOCK as u64; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(start..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - run_to_block(end.saturating_div(MILLISECS_PER_BLOCK.into()) - 1); - set_timestamp_for_on_initialize(end - MILLISECS_PER_BLOCK as u64); - - let market_id = 0; - assert_noop!( - PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(ALICE), market_id,), - Error::::EarlyCloseRequestTooLate - ); - }); -} - -#[test] -fn schedule_early_close_as_market_creator_works() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - let old_market_period = market.period; - assert_eq!(market.status, MarketStatus::Active); - let market_ids_to_close = >::iter().next().unwrap(); - assert_eq!(market_ids_to_close.0, end); - assert_eq!(market_ids_to_close.1.into_inner(), vec![market_id]); - assert!(market.early_close.is_none()); - - let reserved_balance_alice = Balances::reserved_balance(ALICE); - - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - let reserved_balance_alice_after = Balances::reserved_balance(ALICE); - assert_eq!( - reserved_balance_alice_after - reserved_balance_alice, - CloseEarlyRequestBond::get() - ); - - let now = >::block_number(); - let new_end = now + CloseEarlyBlockPeriod::get(); - assert!(new_end < end); - - let market = MarketCommons::market(&market_id).unwrap(); - let new_period = MarketPeriod::Block(0..new_end); - assert_eq!( - market.early_close.unwrap(), - EarlyClose { - old: old_market_period, - new: new_period, - state: EarlyCloseState::ScheduledAsMarketCreator, - } - ); - - let market_ids_to_close = >::iter().collect::>(); - assert_eq!(market_ids_to_close.len(), 2); - - // The first entry is the old one without a market id inside. - let first = market_ids_to_close.first().unwrap(); - assert_eq!(first.0, end); - assert!(first.1.clone().into_inner().is_empty()); - - // The second entry is the new one with the market id inside. - let second = market_ids_to_close.last().unwrap(); - assert_eq!(second.0, new_end); - assert_eq!(second.1.clone().into_inner(), vec![market_id]); - - run_to_block(new_end + 1); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - }); -} - #[test] fn dispute_early_close_from_market_creator_works() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 548c1220f..f0fd71626 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -31,6 +31,7 @@ mod on_market_close; mod reject_market; mod report; mod sell_complete_set; +mod schedule_early_close; use crate::{mock::*, AccountIdOf, AssetOf, BalanceOf, Config, Error, Event}; use core::ops::Range; diff --git a/zrml/prediction-markets/src/tests/schedule_early_close.rs b/zrml/prediction-markets/src/tests/schedule_early_close.rs new file mode 100644 index 000000000..5ca8899f5 --- /dev/null +++ b/zrml/prediction-markets/src/tests/schedule_early_close.rs @@ -0,0 +1,335 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +use crate::{MarketIdsPerCloseBlock, MarketIdsPerCloseTimeFrame}; +use zeitgeist_primitives::{ + constants::MILLISECS_PER_BLOCK, + types::{EarlyClose, EarlyCloseState}, +}; + +#[test] +fn schedule_early_close_emits_event() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + // just to ensure events are emitted + run_blocks(2); + + let market_id = 0; + + assert_ok!(PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id)); + + let now = >::block_number(); + let new_end = now + ::CloseEarlyProtectionBlockPeriod::get(); + assert!(new_end < end); + + let new_period = MarketPeriod::Block(0..new_end); + System::assert_last_event( + Event::MarketEarlyCloseScheduled { + market_id, + new_period: new_period.clone(), + state: EarlyCloseState::ScheduledAsOther, + } + .into(), + ); + }); +} + +#[test] +fn sudo_schedule_early_close_at_block_works() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + let old_market_period = market.period; + assert_eq!(market.status, MarketStatus::Active); + let market_ids_to_close = >::iter().next().unwrap(); + assert_eq!(market_ids_to_close.0, end); + assert_eq!(market_ids_to_close.1.into_inner(), vec![market_id]); + assert!(market.early_close.is_none()); + + assert_ok!( + PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) + ); + + let now = >::block_number(); + let new_end = now + ::CloseEarlyProtectionBlockPeriod::get(); + assert!(new_end < end); + + let market = MarketCommons::market(&market_id).unwrap(); + let new_period = MarketPeriod::Block(0..new_end); + assert_eq!( + market.early_close.unwrap(), + EarlyClose { + old: old_market_period, + new: new_period, + state: EarlyCloseState::ScheduledAsOther, + } + ); + + let market_ids_to_close = >::iter().collect::>(); + assert_eq!(market_ids_to_close.len(), 2); + + // The first entry is the old one without a market id inside. + let first = market_ids_to_close.first().unwrap(); + assert_eq!(first.0, end); + assert!(first.1.clone().into_inner().is_empty()); + + // The second entry is the new one with the market id inside. + let second = market_ids_to_close.last().unwrap(); + assert_eq!(second.0, new_end); + assert_eq!(second.1.clone().into_inner(), vec![market_id]); + + run_to_block(new_end + 1); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + }); +} + +#[test] +fn sudo_schedule_early_close_at_timeframe_works() { + ExtBuilder::default().build().execute_with(|| { + let start_block = 7; + set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as u64); + run_blocks(start_block); + let start = >::now(); + + let end = start + (42 * MILLISECS_PER_BLOCK) as u64; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(start..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + let old_market_period = market.period; + assert_eq!(market.status, MarketStatus::Active); + let market_ids_to_close = >::iter().collect::>(); + assert_eq!(market_ids_to_close.len(), 1); + let first = market_ids_to_close.first().unwrap(); + assert_eq!(first.0, end.saturating_div(MILLISECS_PER_BLOCK.into())); + assert_eq!(first.1.clone().into_inner(), vec![market_id]); + assert!(market.early_close.is_none()); + + assert_ok!( + PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) + ); + + let now = >::now(); + let new_end = now + ::CloseEarlyProtectionTimeFramePeriod::get(); + assert!(new_end < end); + + let market = MarketCommons::market(&market_id).unwrap(); + let new_period = MarketPeriod::Timestamp(start..new_end); + assert_eq!( + market.early_close.unwrap(), + EarlyClose { + old: old_market_period, + new: new_period, + state: EarlyCloseState::ScheduledAsOther, + } + ); + + let market_ids_to_close = >::iter().collect::>(); + assert_eq!(market_ids_to_close.len(), 2); + + // The first entry is the new one with the market id inside. + let first = market_ids_to_close.first().unwrap(); + assert_eq!(first.0, new_end.saturating_div(MILLISECS_PER_BLOCK.into())); + assert_eq!(first.1.clone().into_inner(), vec![market_id]); + + // The second entry is the old one without a market id inside. + let second = market_ids_to_close.last().unwrap(); + assert_eq!(second.0, end.saturating_div(MILLISECS_PER_BLOCK.into())); + assert!(second.1.clone().into_inner().is_empty()); + + set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as u64 + new_end); + run_to_block(start_block + new_end.saturating_div(MILLISECS_PER_BLOCK.into()) + 1); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + }); +} + +#[test] +fn schedule_early_close_block_fails_if_early_close_request_too_late() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + run_to_block(end - 1); + + let market_id = 0; + assert_noop!( + PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(ALICE), market_id,), + Error::::EarlyCloseRequestTooLate + ); + }); +} + +#[test] +fn schedule_early_close_timestamp_fails_if_early_close_request_too_late() { + ExtBuilder::default().build().execute_with(|| { + let start_block = 7; + set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as u64); + run_blocks(start_block); + let start = >::now(); + let end = start + (42 * MILLISECS_PER_BLOCK) as u64; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(start..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + run_to_block(end.saturating_div(MILLISECS_PER_BLOCK.into()) - 1); + set_timestamp_for_on_initialize(end - MILLISECS_PER_BLOCK as u64); + + let market_id = 0; + assert_noop!( + PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(ALICE), market_id,), + Error::::EarlyCloseRequestTooLate + ); + }); +} + +#[test] +fn schedule_early_close_as_market_creator_works() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + let old_market_period = market.period; + assert_eq!(market.status, MarketStatus::Active); + let market_ids_to_close = >::iter().next().unwrap(); + assert_eq!(market_ids_to_close.0, end); + assert_eq!(market_ids_to_close.1.into_inner(), vec![market_id]); + assert!(market.early_close.is_none()); + + let reserved_balance_alice = Balances::reserved_balance(ALICE); + + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + let reserved_balance_alice_after = Balances::reserved_balance(ALICE); + assert_eq!( + reserved_balance_alice_after - reserved_balance_alice, + ::CloseEarlyRequestBond::get() + ); + + let now = >::block_number(); + let new_end = now + ::CloseEarlyBlockPeriod::get(); + assert!(new_end < end); + + let market = MarketCommons::market(&market_id).unwrap(); + let new_period = MarketPeriod::Block(0..new_end); + assert_eq!( + market.early_close.unwrap(), + EarlyClose { + old: old_market_period, + new: new_period, + state: EarlyCloseState::ScheduledAsMarketCreator, + } + ); + + let market_ids_to_close = >::iter().collect::>(); + assert_eq!(market_ids_to_close.len(), 2); + + // The first entry is the old one without a market id inside. + let first = market_ids_to_close.first().unwrap(); + assert_eq!(first.0, end); + assert!(first.1.clone().into_inner().is_empty()); + + // The second entry is the new one with the market id inside. + let second = market_ids_to_close.last().unwrap(); + assert_eq!(second.0, new_end); + assert_eq!(second.1.clone().into_inner(), vec![market_id]); + + run_to_block(new_end + 1); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + }); +} From 18e62b03d1c6edf40450f85af124fe46527115f1 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Fri, 12 Jan 2024 14:12:54 +0100 Subject: [PATCH 20/32] Extract `dispute_early_close` tests --- zrml/prediction-markets/src/old_tests.rs | 215 +--------------- .../src/tests/dispute_early_close.rs | 233 ++++++++++++++++++ zrml/prediction-markets/src/tests/mod.rs | 3 +- 3 files changed, 240 insertions(+), 211 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/dispute_early_close.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index e2a2c6b66..559caa505 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -22,8 +22,8 @@ extern crate alloc; use crate::{ - mock::*, Config, Error, Event, MarketIdsPerCloseBlock, - MarketIdsPerDisputeBlock, MarketIdsPerReportBlock, + mock::*, Config, Error, Event, MarketIdsPerCloseBlock, MarketIdsPerDisputeBlock, + MarketIdsPerReportBlock, }; use alloc::collections::BTreeMap; use core::ops::Range; @@ -38,9 +38,9 @@ use sp_arithmetic::Perbill; use sp_runtime::traits::{Hash, Zero}; use zeitgeist_primitives::{ constants::mock::{ - CloseEarlyBlockPeriod, CloseEarlyDisputeBond, CloseEarlyProtectionBlockPeriod, - CloseEarlyRequestBond, MaxAppeals, MaxSelectedDraws, - MinJurorStake, OutcomeBond, OutcomeFactor, OutsiderBond, BASE, CENT, MILLISECS_PER_BLOCK, + CloseEarlyDisputeBond, CloseEarlyProtectionBlockPeriod, CloseEarlyRequestBond, MaxAppeals, + MaxSelectedDraws, MinJurorStake, OutcomeBond, OutcomeFactor, OutsiderBond, BASE, CENT, + MILLISECS_PER_BLOCK, }, types::{ AccountIdTest, Asset, Balance, Bond, Deadlines, MarketBonds, MarketCreation, @@ -137,33 +137,6 @@ fn simple_create_scalar_market( )); } -#[test] -fn dispute_early_close_emits_event() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - // just to ensure events are emitted - run_blocks(2); - - let market_id = 0; - - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); - - System::assert_last_event(Event::MarketEarlyCloseDisputed { market_id }.into()); - }); -} - #[test] fn reject_early_close_emits_event() { ExtBuilder::default().build().execute_with(|| { @@ -250,74 +223,6 @@ fn reject_early_close_fails_if_state_is_rejected() { }); } -#[test] -fn dispute_early_close_from_market_creator_works() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - let old_market_period = market.period; - - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - let now = >::block_number(); - let new_end = now + CloseEarlyBlockPeriod::get(); - let market_ids_at_new_end = >::get(new_end); - assert_eq!(market_ids_at_new_end, vec![market_id]); - - run_blocks(1); - - let reserved_bob = Balances::reserved_balance(BOB); - - assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); - - let reserved_bob_after = Balances::reserved_balance(BOB); - assert_eq!(reserved_bob_after - reserved_bob, CloseEarlyDisputeBond::get()); - - let market_ids_at_new_end = >::get(new_end); - assert!(market_ids_at_new_end.is_empty()); - - let market_ids_at_old_end = >::get(end); - assert_eq!(market_ids_at_old_end, vec![market_id]); - - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.period, old_market_period); - assert_eq!(market.bonds.close_dispute, Some(Bond::new(BOB, CloseEarlyDisputeBond::get()))); - let new_period = MarketPeriod::Block(0..new_end); - assert_eq!( - market.early_close.unwrap(), - EarlyClose { - old: old_market_period, - new: new_period, - state: EarlyCloseState::Disputed, - } - ); - - run_to_block(new_end + 1); - - // verify the market doesn't close after proposed new market period end - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Active); - }); -} - #[test] fn settles_early_close_bonds_with_resolution_in_state_disputed() { ExtBuilder::default().build().execute_with(|| { @@ -413,76 +318,6 @@ fn settles_early_close_bonds_with_resolution_in_state_scheduled_as_market_creato }); } -#[test] -fn dispute_early_close_fails_if_scheduled_as_sudo() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - assert_ok!( - PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) - ); - - run_blocks(1); - - assert_noop!( - PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,), - Error::::InvalidEarlyCloseState - ); - }); -} - -#[test] -fn dispute_early_close_fails_if_already_disputed() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - run_blocks(1); - - assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); - - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.early_close.unwrap().state, EarlyCloseState::Disputed); - - assert_noop!( - PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,), - Error::::InvalidEarlyCloseState - ); - }); -} - #[test] fn reject_early_close_resets_to_old_market_period() { ExtBuilder::default().build().execute_with(|| { @@ -577,46 +412,6 @@ fn reject_early_close_settles_bonds() { }); } -#[test] -fn dispute_early_close_fails_if_already_rejected() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - run_blocks(1); - - assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); - - assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); - - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.early_close.unwrap().state, EarlyCloseState::Rejected); - - assert_noop!( - PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,), - Error::::InvalidEarlyCloseState - ); - }); -} - #[test] fn schedule_early_close_disputed_sudo_schedule_and_settle_bonds() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/dispute_early_close.rs b/zrml/prediction-markets/src/tests/dispute_early_close.rs new file mode 100644 index 000000000..879ca90af --- /dev/null +++ b/zrml/prediction-markets/src/tests/dispute_early_close.rs @@ -0,0 +1,233 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +use crate::MarketIdsPerCloseBlock; +use zeitgeist_primitives::types::{Bond, EarlyClose, EarlyCloseState}; + +#[test] +fn dispute_early_close_emits_event() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + // just to ensure events are emitted + run_blocks(2); + + let market_id = 0; + + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); + + System::assert_last_event(Event::MarketEarlyCloseDisputed { market_id }.into()); + }); +} + +#[test] +fn dispute_early_close_from_market_creator_works() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + let old_market_period = market.period; + + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + let now = >::block_number(); + let new_end = now + ::CloseEarlyBlockPeriod::get(); + let market_ids_at_new_end = >::get(new_end); + assert_eq!(market_ids_at_new_end, vec![market_id]); + + run_blocks(1); + + let reserved_bob = Balances::reserved_balance(BOB); + + assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); + + let reserved_bob_after = Balances::reserved_balance(BOB); + assert_eq!( + reserved_bob_after - reserved_bob, + ::CloseEarlyDisputeBond::get() + ); + + let market_ids_at_new_end = >::get(new_end); + assert!(market_ids_at_new_end.is_empty()); + + let market_ids_at_old_end = >::get(end); + assert_eq!(market_ids_at_old_end, vec![market_id]); + + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.period, old_market_period); + assert_eq!( + market.bonds.close_dispute, + Some(Bond::new(BOB, ::CloseEarlyDisputeBond::get())) + ); + let new_period = MarketPeriod::Block(0..new_end); + assert_eq!( + market.early_close.unwrap(), + EarlyClose { + old: old_market_period, + new: new_period, + state: EarlyCloseState::Disputed, + } + ); + + run_to_block(new_end + 1); + + // verify the market doesn't close after proposed new market period end + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Active); + }); +} + +#[test] +fn dispute_early_close_fails_if_scheduled_as_sudo() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + assert_ok!( + PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) + ); + + run_blocks(1); + + assert_noop!( + PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,), + Error::::InvalidEarlyCloseState + ); + }); +} + +#[test] +fn dispute_early_close_fails_if_already_disputed() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + run_blocks(1); + + assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); + + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.early_close.unwrap().state, EarlyCloseState::Disputed); + + assert_noop!( + PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,), + Error::::InvalidEarlyCloseState + ); + }); +} + +#[test] +fn dispute_early_close_fails_if_already_rejected() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + run_blocks(1); + + assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); + + assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); + + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.early_close.unwrap().state, EarlyCloseState::Rejected); + + assert_noop!( + PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,), + Error::::InvalidEarlyCloseState + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index f0fd71626..7c5e096e8 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -24,14 +24,15 @@ mod approve_market; mod buy_complete_set; mod create_market; mod dispute; +mod dispute_early_close; mod edit_market; mod manually_close_market; mod on_initialize; mod on_market_close; mod reject_market; mod report; -mod sell_complete_set; mod schedule_early_close; +mod sell_complete_set; use crate::{mock::*, AccountIdOf, AssetOf, BalanceOf, Config, Error, Event}; use core::ops::Range; From 5efef55d7a79fa8a3e07c9015e88a1369a5a5744 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 13 Jan 2024 17:07:43 +0100 Subject: [PATCH 21/32] Extract `reject_early_close` tests --- zrml/prediction-markets/src/old_tests.rs | 180 --------------- zrml/prediction-markets/src/tests/mod.rs | 1 + .../src/tests/reject_early_close.rs | 209 ++++++++++++++++++ 3 files changed, 210 insertions(+), 180 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/reject_early_close.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index 559caa505..6616c31b2 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -137,92 +137,6 @@ fn simple_create_scalar_market( )); } -#[test] -fn reject_early_close_emits_event() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - // just to ensure events are emitted - run_blocks(2); - - let market_id = 0; - - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); - - assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); - - System::assert_last_event(Event::MarketEarlyCloseRejected { market_id }.into()); - }); -} - -#[test] -fn reject_early_close_fails_if_state_is_scheduled_as_market_creator() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - // just to ensure events are emitted - run_blocks(2); - - let market_id = 0; - - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - assert_noop!( - PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,), - Error::::InvalidEarlyCloseState - ); - }); -} - -#[test] -fn reject_early_close_fails_if_state_is_rejected() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - // just to ensure events are emitted - run_blocks(2); - - let market_id = 0; - - assert_ok!( - PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) - ); - - assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); - - assert_noop!( - PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,), - Error::::InvalidEarlyCloseState - ); - }); -} - #[test] fn settles_early_close_bonds_with_resolution_in_state_disputed() { ExtBuilder::default().build().execute_with(|| { @@ -318,100 +232,6 @@ fn settles_early_close_bonds_with_resolution_in_state_scheduled_as_market_creato }); } -#[test] -fn reject_early_close_resets_to_old_market_period() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - assert_ok!( - PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) - ); - - let now = >::block_number(); - let new_end = now + CloseEarlyProtectionBlockPeriod::get(); - let market_ids_at_new_end = >::get(new_end); - assert_eq!(market_ids_at_new_end, vec![market_id]); - - run_blocks(1); - - assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); - - let market_ids_at_new_end = >::get(new_end); - assert!(market_ids_at_new_end.is_empty()); - - let market_ids_at_old_end = >::get(end); - assert_eq!(market_ids_at_old_end, vec![market_id]); - }); -} - -#[test] -fn reject_early_close_settles_bonds() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - run_blocks(1); - - assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); - - let reserved_bob = Balances::reserved_balance(BOB); - let reserved_alice = Balances::reserved_balance(ALICE); - let free_bob = Balances::free_balance(BOB); - let free_alice = Balances::free_balance(ALICE); - - assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); - - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.early_close.unwrap().state, EarlyCloseState::Rejected); - - let reserved_bob_after = Balances::reserved_balance(BOB); - let reserved_alice_after = Balances::reserved_balance(ALICE); - let free_bob_after = Balances::free_balance(BOB); - let free_alice_after = Balances::free_balance(ALICE); - - assert_eq!(reserved_alice - reserved_alice_after, CloseEarlyRequestBond::get()); - assert_eq!(reserved_bob - reserved_bob_after, CloseEarlyDisputeBond::get()); - // disputant Bob gets the bonds - assert_eq!( - free_bob_after - free_bob, - CloseEarlyRequestBond::get() + CloseEarlyDisputeBond::get() - ); - assert_eq!(free_alice_after - free_alice, 0); - }); -} - #[test] fn schedule_early_close_disputed_sudo_schedule_and_settle_bonds() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 7c5e096e8..2a741781d 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -29,6 +29,7 @@ mod edit_market; mod manually_close_market; mod on_initialize; mod on_market_close; +mod reject_early_close; mod reject_market; mod report; mod schedule_early_close; diff --git a/zrml/prediction-markets/src/tests/reject_early_close.rs b/zrml/prediction-markets/src/tests/reject_early_close.rs new file mode 100644 index 000000000..3bfc46bb3 --- /dev/null +++ b/zrml/prediction-markets/src/tests/reject_early_close.rs @@ -0,0 +1,209 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +use crate::MarketIdsPerCloseBlock; +use zeitgeist_primitives::types::EarlyCloseState; + +#[test] +fn reject_early_close_emits_event() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + // just to ensure events are emitted + run_blocks(2); + + let market_id = 0; + + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); + + assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); + + System::assert_last_event(Event::MarketEarlyCloseRejected { market_id }.into()); + }); +} + +#[test] +fn reject_early_close_fails_if_state_is_scheduled_as_market_creator() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + // just to ensure events are emitted + run_blocks(2); + + let market_id = 0; + + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + assert_noop!( + PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,), + Error::::InvalidEarlyCloseState + ); + }); +} + +#[test] +fn reject_early_close_fails_if_state_is_rejected() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + // just to ensure events are emitted + run_blocks(2); + + let market_id = 0; + + assert_ok!( + PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) + ); + + assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); + + assert_noop!( + PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,), + Error::::InvalidEarlyCloseState + ); + }); +} + +#[test] +fn reject_early_close_resets_to_old_market_period() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + assert_ok!( + PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) + ); + + let now = >::block_number(); + let new_end = now + ::CloseEarlyProtectionBlockPeriod::get(); + let market_ids_at_new_end = >::get(new_end); + assert_eq!(market_ids_at_new_end, vec![market_id]); + + run_blocks(1); + + assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); + + let market_ids_at_new_end = >::get(new_end); + assert!(market_ids_at_new_end.is_empty()); + + let market_ids_at_old_end = >::get(end); + assert_eq!(market_ids_at_old_end, vec![market_id]); + }); +} + +#[test] +fn reject_early_close_settles_bonds() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + run_blocks(1); + + assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); + + let reserved_bob = Balances::reserved_balance(BOB); + let reserved_alice = Balances::reserved_balance(ALICE); + let free_bob = Balances::free_balance(BOB); + let free_alice = Balances::free_balance(ALICE); + + assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); + + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.early_close.unwrap().state, EarlyCloseState::Rejected); + + let reserved_bob_after = Balances::reserved_balance(BOB); + let reserved_alice_after = Balances::reserved_balance(ALICE); + let free_bob_after = Balances::free_balance(BOB); + let free_alice_after = Balances::free_balance(ALICE); + + assert_eq!( + reserved_alice - reserved_alice_after, + ::CloseEarlyRequestBond::get() + ); + assert_eq!( + reserved_bob - reserved_bob_after, + ::CloseEarlyDisputeBond::get() + ); + // disputant Bob gets the bonds + assert_eq!( + free_bob_after - free_bob, + ::CloseEarlyRequestBond::get() + + ::CloseEarlyDisputeBond::get() + ); + assert_eq!(free_alice_after - free_alice, 0); + }); +} From cd7e813365e9ff2339e81ff89e3f7cedfc06ea9e Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 13 Jan 2024 17:09:26 +0100 Subject: [PATCH 22/32] Extract more `dispute` tests --- zrml/prediction-markets/src/old_tests.rs | 113 ------------------ zrml/prediction-markets/src/tests/dispute.rs | 116 ++++++++++++++++++- 2 files changed, 115 insertions(+), 114 deletions(-) diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index 6616c31b2..865a80741 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -302,93 +302,6 @@ fn schedule_early_close_disputed_sudo_schedule_and_settle_bonds() { }); } -#[test] -fn dispute_updates_market() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - - // Run to the end of the trading phase. - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - - let dispute_at = grace_period + 2; - run_to_block(dispute_at); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Reported); - assert_eq!(market.bonds.dispute, None); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Disputed); - assert_eq!( - market.bonds.dispute, - Some(Bond { who: CHARLIE, value: DisputeBond::get(), is_settled: false }) - ); - }); -} - -#[test] -fn dispute_emits_event() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - - // Run to the end of the trading phase. - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - - let dispute_at = grace_period + 2; - run_to_block(dispute_at); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - - System::assert_last_event( - Event::MarketDisputed(0u32.into(), MarketStatus::Disputed, CHARLIE).into(), - ); - }); -} - #[test] fn it_correctly_resolves_a_market_that_was_reported_on() { ExtBuilder::default().build().execute_with(|| { @@ -973,32 +886,6 @@ fn it_appeals_a_court_market_to_global_dispute() { }); } -#[test_case(MarketStatus::Active; "active")] -#[test_case(MarketStatus::Closed; "closed")] -#[test_case(MarketStatus::Proposed; "proposed")] -#[test_case(MarketStatus::Resolved; "resolved")] -fn dispute_fails_unless_reported_or_disputed_market(status: MarketStatus) { - ExtBuilder::default().build().execute_with(|| { - // Creates a permissionless market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..1, - ScoringRule::Lmsr, - ); - - assert_ok!(MarketCommons::mutate_market(&0, |market_inner| { - market_inner.status = status; - Ok(()) - })); - - assert_noop!( - PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0), - Error::::InvalidMarketStatus - ); - }); -} - #[test] fn start_global_dispute_fails_on_wrong_mdm() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/dispute.rs b/zrml/prediction-markets/src/tests/dispute.rs index e34ff7a7a..65d6e4077 100644 --- a/zrml/prediction-markets/src/tests/dispute.rs +++ b/zrml/prediction-markets/src/tests/dispute.rs @@ -17,9 +17,10 @@ // along with Zeitgeist. If not, see . use super::*; +use test_case::test_case; use crate::MarketIdsPerDisputeBlock; -use zeitgeist_primitives::types::OutcomeReport; +use zeitgeist_primitives::types::{Bond, OutcomeReport}; #[test] fn it_allows_to_dispute_the_outcome_of_a_market() { @@ -191,3 +192,116 @@ fn dispute_reserves_dispute_bond() { assert_eq!(reserved_charlie, DisputeBond::get()); }); } + +#[test] +fn dispute_updates_market() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + + // Run to the end of the trading phase. + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + + let dispute_at = grace_period + 2; + run_to_block(dispute_at); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Reported); + assert_eq!(market.bonds.dispute, None); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Disputed); + assert_eq!( + market.bonds.dispute, + Some(Bond { who: CHARLIE, value: DisputeBond::get(), is_settled: false }) + ); + }); +} + +#[test] +fn dispute_emits_event() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + + // Run to the end of the trading phase. + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + + let dispute_at = grace_period + 2; + run_to_block(dispute_at); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); + + System::assert_last_event( + Event::MarketDisputed(0u32.into(), MarketStatus::Disputed, CHARLIE).into(), + ); + }); +} + +#[test_case(MarketStatus::Active; "active")] +#[test_case(MarketStatus::Closed; "closed")] +#[test_case(MarketStatus::Proposed; "proposed")] +#[test_case(MarketStatus::Resolved; "resolved")] +fn dispute_fails_unless_reported_or_disputed_market(status: MarketStatus) { + ExtBuilder::default().build().execute_with(|| { + // Creates a permissionless market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..1, + ScoringRule::Lmsr, + ); + + assert_ok!(MarketCommons::mutate_market(&0, |market_inner| { + market_inner.status = status; + Ok(()) + })); + + assert_noop!( + PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0), + Error::::InvalidMarketStatus + ); + }); +} From fc577256a745f43bde8ddebb9bacac52dd48e12e Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 13 Jan 2024 22:28:25 +0100 Subject: [PATCH 23/32] Extract `close_trusted_market` tests --- zrml/prediction-markets/src/old_tests.rs | 140 --------------- .../src/tests/close_trusted_market.rs | 163 ++++++++++++++++++ zrml/prediction-markets/src/tests/mod.rs | 1 + 3 files changed, 164 insertions(+), 140 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/close_trusted_market.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index 865a80741..d75016097 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -2125,146 +2125,6 @@ fn trusted_market_complete_lifecycle() { }); } -#[test] -fn close_trusted_market_works() { - ExtBuilder::default().build().execute_with(|| { - let end = 10; - let market_creator = ALICE; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(market_creator), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - Deadlines { - grace_period: 0, - oracle_duration: ::MinOracleDuration::get(), - dispute_duration: Zero::zero(), - }, - gen_metadata(0x99), - MarketCreation::Permissionless, - MarketType::Categorical(3), - None, - ScoringRule::Lmsr, - )); - - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.dispute_mechanism, None); - - let new_end = end / 2; - assert_ne!(new_end, end); - run_to_block(new_end); - - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Active); - - let auto_closes = MarketIdsPerCloseBlock::::get(end); - assert_eq!(auto_closes.first().cloned().unwrap(), market_id); - - assert_noop!( - PredictionMarkets::close_trusted_market(RuntimeOrigin::signed(BOB), market_id), - Error::::CallerNotMarketCreator - ); - - assert_ok!(PredictionMarkets::close_trusted_market( - RuntimeOrigin::signed(market_creator), - market_id - )); - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.period, MarketPeriod::Block(0..new_end)); - assert_eq!(market.status, MarketStatus::Closed); - - let auto_closes = MarketIdsPerCloseBlock::::get(end); - assert_eq!(auto_closes.len(), 0); - }); -} - -#[test] -fn close_trusted_market_fails_if_not_trusted() { - ExtBuilder::default().build().execute_with(|| { - let end = 10; - let market_creator = ALICE; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(market_creator), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - Deadlines { - grace_period: 0, - oracle_duration: ::MinOracleDuration::get(), - dispute_duration: ::MinDisputeDuration::get(), - }, - gen_metadata(0x99), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr, - )); - - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.dispute_mechanism, Some(MarketDisputeMechanism::Court)); - - run_to_block(end / 2); - - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Active); - - assert_noop!( - PredictionMarkets::close_trusted_market( - RuntimeOrigin::signed(market_creator), - market_id - ), - Error::::MarketIsNotTrusted - ); - }); -} - -#[test_case(MarketStatus::Closed; "closed")] -#[test_case(MarketStatus::Proposed; "proposed")] -#[test_case(MarketStatus::Resolved; "resolved")] -#[test_case(MarketStatus::Disputed; "disputed")] -#[test_case(MarketStatus::Reported; "report")] -fn close_trusted_market_fails_if_invalid_market_state(status: MarketStatus) { - ExtBuilder::default().build().execute_with(|| { - let end = 10; - let market_creator = ALICE; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(market_creator), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - Deadlines { - grace_period: 0, - oracle_duration: ::MinOracleDuration::get(), - dispute_duration: Zero::zero(), - }, - gen_metadata(0x99), - MarketCreation::Permissionless, - MarketType::Categorical(3), - None, - ScoringRule::Lmsr, - )); - - let market_id = 0; - assert_ok!(MarketCommons::mutate_market(&market_id, |market| { - market.status = status; - Ok(()) - })); - - assert_noop!( - PredictionMarkets::close_trusted_market( - RuntimeOrigin::signed(market_creator), - market_id - ), - Error::::MarketIsNotActive - ); - }); -} - // Common code of `scalar_market_correctly_resolves_*` fn scalar_market_correctly_resolves_common(base_asset: Asset, reported_value: u128) { let end = 100; diff --git a/zrml/prediction-markets/src/tests/close_trusted_market.rs b/zrml/prediction-markets/src/tests/close_trusted_market.rs new file mode 100644 index 000000000..82a1c3d52 --- /dev/null +++ b/zrml/prediction-markets/src/tests/close_trusted_market.rs @@ -0,0 +1,163 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; +use test_case::test_case; + +use crate::MarketIdsPerCloseBlock; +use sp_runtime::traits::Zero; + +#[test] +fn close_trusted_market_works() { + ExtBuilder::default().build().execute_with(|| { + let end = 10; + let market_creator = ALICE; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(market_creator), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + Deadlines { + grace_period: 0, + oracle_duration: ::MinOracleDuration::get(), + dispute_duration: Zero::zero(), + }, + gen_metadata(0x99), + MarketCreation::Permissionless, + MarketType::Categorical(3), + None, + ScoringRule::Lmsr, + )); + + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.dispute_mechanism, None); + + let new_end = end / 2; + assert_ne!(new_end, end); + run_to_block(new_end); + + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Active); + + let auto_closes = MarketIdsPerCloseBlock::::get(end); + assert_eq!(auto_closes.first().cloned().unwrap(), market_id); + + assert_noop!( + PredictionMarkets::close_trusted_market(RuntimeOrigin::signed(BOB), market_id), + Error::::CallerNotMarketCreator + ); + + assert_ok!(PredictionMarkets::close_trusted_market( + RuntimeOrigin::signed(market_creator), + market_id + )); + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.period, MarketPeriod::Block(0..new_end)); + assert_eq!(market.status, MarketStatus::Closed); + + let auto_closes = MarketIdsPerCloseBlock::::get(end); + assert_eq!(auto_closes.len(), 0); + }); +} + +#[test] +fn close_trusted_market_fails_if_not_trusted() { + ExtBuilder::default().build().execute_with(|| { + let end = 10; + let market_creator = ALICE; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(market_creator), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + Deadlines { + grace_period: 0, + oracle_duration: ::MinOracleDuration::get(), + dispute_duration: ::MinDisputeDuration::get(), + }, + gen_metadata(0x99), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr, + )); + + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.dispute_mechanism, Some(MarketDisputeMechanism::Court)); + + run_to_block(end / 2); + + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Active); + + assert_noop!( + PredictionMarkets::close_trusted_market( + RuntimeOrigin::signed(market_creator), + market_id + ), + Error::::MarketIsNotTrusted + ); + }); +} + +#[test_case(MarketStatus::Closed; "closed")] +#[test_case(MarketStatus::Proposed; "proposed")] +#[test_case(MarketStatus::Resolved; "resolved")] +#[test_case(MarketStatus::Disputed; "disputed")] +#[test_case(MarketStatus::Reported; "report")] +fn close_trusted_market_fails_if_invalid_market_state(status: MarketStatus) { + ExtBuilder::default().build().execute_with(|| { + let end = 10; + let market_creator = ALICE; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(market_creator), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + Deadlines { + grace_period: 0, + oracle_duration: ::MinOracleDuration::get(), + dispute_duration: Zero::zero(), + }, + gen_metadata(0x99), + MarketCreation::Permissionless, + MarketType::Categorical(3), + None, + ScoringRule::Lmsr, + )); + + let market_id = 0; + assert_ok!(MarketCommons::mutate_market(&market_id, |market| { + market.status = status; + Ok(()) + })); + + assert_noop!( + PredictionMarkets::close_trusted_market( + RuntimeOrigin::signed(market_creator), + market_id + ), + Error::::MarketIsNotActive + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 2a741781d..daf8e02aa 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -23,6 +23,7 @@ mod admin_move_market_to_resolved; mod approve_market; mod buy_complete_set; mod create_market; +mod close_trusted_market; mod dispute; mod dispute_early_close; mod edit_market; From 0526b48c1f36d7b95c68f5ec02213c8e34256265 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 13 Jan 2024 22:31:14 +0100 Subject: [PATCH 24/32] Extract `start_global_dispute` tests --- zrml/prediction-markets/src/old_tests.rs | 43 ------------- zrml/prediction-markets/src/tests/mod.rs | 1 + .../src/tests/start_global_dispute.rs | 64 +++++++++++++++++++ 3 files changed, 65 insertions(+), 43 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/start_global_dispute.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index d75016097..9e5d0c3e1 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -886,49 +886,6 @@ fn it_appeals_a_court_market_to_global_dispute() { }); } -#[test] -fn start_global_dispute_fails_on_wrong_mdm() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..2), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MaxDisputes::get() + 1), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - let market_id = MarketCommons::latest_market_id().unwrap(); - - let market = MarketCommons::market(&market_id).unwrap(); - let grace_period = market.deadlines.grace_period; - run_to_block(end + grace_period + 1); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - market_id, - OutcomeReport::Categorical(0) - )); - let dispute_at_0 = end + grace_period + 2; - run_to_block(dispute_at_0); - - // only one dispute allowed for authorized mdm - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), market_id,)); - run_blocks(1); - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Disputed); - - assert_noop!( - PredictionMarkets::start_global_dispute(RuntimeOrigin::signed(CHARLIE), market_id), - Error::::InvalidDisputeMechanism - ); - }); -} - #[test] fn it_allows_to_redeem_shares() { let test = |base_asset: Asset| { diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index daf8e02aa..1137f6b63 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -35,6 +35,7 @@ mod reject_market; mod report; mod schedule_early_close; mod sell_complete_set; +mod start_global_dispute; use crate::{mock::*, AccountIdOf, AssetOf, BalanceOf, Config, Error, Event}; use core::ops::Range; diff --git a/zrml/prediction-markets/src/tests/start_global_dispute.rs b/zrml/prediction-markets/src/tests/start_global_dispute.rs new file mode 100644 index 000000000..573dcf8a3 --- /dev/null +++ b/zrml/prediction-markets/src/tests/start_global_dispute.rs @@ -0,0 +1,64 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +use zeitgeist_primitives::types::OutcomeReport; + +#[test] +fn start_global_dispute_fails_on_wrong_mdm() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..2), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MaxDisputes::get() + 1), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + let market_id = MarketCommons::latest_market_id().unwrap(); + + let market = MarketCommons::market(&market_id).unwrap(); + let grace_period = market.deadlines.grace_period; + run_to_block(end + grace_period + 1); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + market_id, + OutcomeReport::Categorical(0) + )); + let dispute_at_0 = end + grace_period + 2; + run_to_block(dispute_at_0); + + // only one dispute allowed for authorized mdm + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), market_id,)); + run_blocks(1); + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Disputed); + + assert_noop!( + PredictionMarkets::start_global_dispute(RuntimeOrigin::signed(CHARLIE), market_id), + Error::::InvalidDisputeMechanism + ); + }); +} From 27f420e6a94e09e7c3a0f87abea09383efc74730 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 13 Jan 2024 22:42:31 +0100 Subject: [PATCH 25/32] Extract `redeem_shares` tests and add missing tests --- zrml/prediction-markets/src/old_tests.rs | 248 +----------------- .../src/tests/dispute_early_close.rs | 187 +++++++++++++ zrml/prediction-markets/src/tests/mod.rs | 3 +- .../src/tests/redeem_shares.rs | 93 +++++++ 4 files changed, 285 insertions(+), 246 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/redeem_shares.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index 9e5d0c3e1..333d7c196 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -21,16 +21,11 @@ extern crate alloc; -use crate::{ - mock::*, Config, Error, Event, MarketIdsPerCloseBlock, MarketIdsPerDisputeBlock, - MarketIdsPerReportBlock, -}; +use crate::{mock::*, Config, Error, Event, MarketIdsPerDisputeBlock, MarketIdsPerReportBlock}; use alloc::collections::BTreeMap; use core::ops::Range; use frame_support::{assert_noop, assert_ok, traits::NamedReservableCurrency}; use sp_runtime::{traits::BlakeTwo256, Perquintill}; -use test_case::test_case; -use zeitgeist_primitives::types::{EarlyClose, EarlyCloseState}; use zrml_court::{types::*, Error as CError}; use orml_traits::{MultiCurrency, MultiReservableCurrency}; @@ -38,9 +33,8 @@ use sp_arithmetic::Perbill; use sp_runtime::traits::{Hash, Zero}; use zeitgeist_primitives::{ constants::mock::{ - CloseEarlyDisputeBond, CloseEarlyProtectionBlockPeriod, CloseEarlyRequestBond, MaxAppeals, - MaxSelectedDraws, MinJurorStake, OutcomeBond, OutcomeFactor, OutsiderBond, BASE, CENT, - MILLISECS_PER_BLOCK, + MaxAppeals, MaxSelectedDraws, MinJurorStake, OutcomeBond, OutcomeFactor, OutsiderBond, + BASE, CENT, MILLISECS_PER_BLOCK, }, types::{ AccountIdTest, Asset, Balance, Bond, Deadlines, MarketBonds, MarketCreation, @@ -137,171 +131,6 @@ fn simple_create_scalar_market( )); } -#[test] -fn settles_early_close_bonds_with_resolution_in_state_disputed() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - let alice_free = Balances::free_balance(ALICE); - let alice_reserved = Balances::reserved_balance(ALICE); - - run_blocks(1); - - assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); - - let bob_free = Balances::free_balance(BOB); - let bob_reserved = Balances::reserved_balance(BOB); - - run_to_block(end + 1); - - // verify the market doesn't close after proposed new market period end - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - - let alice_free_after = Balances::free_balance(ALICE); - let alice_reserved_after = Balances::reserved_balance(ALICE); - // moved CloseEarlyRequestBond from reserved to free - assert_eq!(alice_reserved - alice_reserved_after, CloseEarlyRequestBond::get()); - assert_eq!(alice_free_after - alice_free, CloseEarlyRequestBond::get()); - - let bob_free_after = Balances::free_balance(BOB); - let bob_reserved_after = Balances::reserved_balance(BOB); - // moved CloseEarlyDisputeBond from reserved to free - assert_eq!(bob_reserved - bob_reserved_after, CloseEarlyDisputeBond::get()); - assert_eq!(bob_free_after - bob_free, CloseEarlyDisputeBond::get()); - }); -} - -#[test] -fn settles_early_close_bonds_with_resolution_in_state_scheduled_as_market_creator() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - let alice_free = Balances::free_balance(ALICE); - let alice_reserved = Balances::reserved_balance(ALICE); - - run_to_block(end + 1); - - // verify the market doesn't close after proposed new market period end - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - - let alice_free_after = Balances::free_balance(ALICE); - let alice_reserved_after = Balances::reserved_balance(ALICE); - // moved CloseEarlyRequestBond from reserved to free - assert_eq!(alice_reserved - alice_reserved_after, CloseEarlyRequestBond::get()); - assert_eq!(alice_free_after - alice_free, CloseEarlyRequestBond::get()); - }); -} - -#[test] -fn schedule_early_close_disputed_sudo_schedule_and_settle_bonds() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - let old_period = MarketPeriod::Block(0..end); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - old_period.clone(), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - run_blocks(1); - - assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); - - let reserved_bob = Balances::reserved_balance(BOB); - let reserved_alice = Balances::reserved_balance(ALICE); - let free_bob = Balances::free_balance(BOB); - let free_alice = Balances::free_balance(ALICE); - - assert_ok!( - PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) - ); - - let reserved_bob_after = Balances::reserved_balance(BOB); - let reserved_alice_after = Balances::reserved_balance(ALICE); - let free_bob_after = Balances::free_balance(BOB); - let free_alice_after = Balances::free_balance(ALICE); - - assert_eq!(reserved_alice - reserved_alice_after, CloseEarlyRequestBond::get()); - assert_eq!(reserved_bob - reserved_bob_after, CloseEarlyDisputeBond::get()); - // market creator Alice gets the bonds - assert_eq!( - free_alice_after - free_alice, - CloseEarlyRequestBond::get() + CloseEarlyDisputeBond::get() - ); - assert_eq!(free_bob_after - free_bob, 0); - - let now = >::block_number(); - let new_end = now + CloseEarlyProtectionBlockPeriod::get(); - let market_ids_at_new_end = >::get(new_end); - assert_eq!(market_ids_at_new_end, vec![market_id]); - - let market = MarketCommons::market(&market_id).unwrap(); - let new_period = MarketPeriod::Block(0..new_end); - assert_eq!( - market.early_close.unwrap(), - EarlyClose { - old: old_period, - new: new_period, - state: EarlyCloseState::ScheduledAsOther, - } - ); - }); -} - #[test] fn it_correctly_resolves_a_market_that_was_reported_on() { ExtBuilder::default().build().execute_with(|| { @@ -886,77 +715,6 @@ fn it_appeals_a_court_market_to_global_dispute() { }); } -#[test] -fn it_allows_to_redeem_shares() { - let test = |base_asset: Asset| { - let end = 2; - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - run_blocks(market.deadlines.dispute_duration); - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Resolved); - - assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); - let bal = Balances::free_balance(CHARLIE); - assert_eq!(bal, 1_000 * BASE); - System::assert_last_event( - Event::TokensRedeemed(0, Asset::CategoricalOutcome(0, 1), CENT, CENT, CHARLIE).into(), - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test_case(ScoringRule::Parimutuel; "parimutuel")] -fn redeem_shares_fails_if_invalid_resolution_mechanism(scoring_rule: ScoringRule) { - let test = |base_asset: Asset| { - let end = 2; - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..end, - scoring_rule, - ); - - assert_ok!(MarketCommons::mutate_market(&0, |market_inner| { - market_inner.status = MarketStatus::Resolved; - Ok(()) - })); - - assert_noop!( - PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0), - Error::::InvalidResolutionMechanism - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - #[test] fn the_entire_market_lifecycle_works_with_timestamps() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/dispute_early_close.rs b/zrml/prediction-markets/src/tests/dispute_early_close.rs index 879ca90af..e72e34894 100644 --- a/zrml/prediction-markets/src/tests/dispute_early_close.rs +++ b/zrml/prediction-markets/src/tests/dispute_early_close.rs @@ -231,3 +231,190 @@ fn dispute_early_close_fails_if_already_rejected() { ); }); } + +#[test] +fn settles_early_close_bonds_with_resolution_in_state_disputed() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + let alice_free = Balances::free_balance(ALICE); + let alice_reserved = Balances::reserved_balance(ALICE); + + run_blocks(1); + + assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); + + let bob_free = Balances::free_balance(BOB); + let bob_reserved = Balances::reserved_balance(BOB); + + run_to_block(end + 1); + + // verify the market doesn't close after proposed new market period end + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + + let alice_free_after = Balances::free_balance(ALICE); + let alice_reserved_after = Balances::reserved_balance(ALICE); + // moved ::CloseEarlyRequestBond from reserved to free + assert_eq!( + alice_reserved - alice_reserved_after, + ::CloseEarlyRequestBond::get() + ); + assert_eq!( + alice_free_after - alice_free, + ::CloseEarlyRequestBond::get() + ); + + let bob_free_after = Balances::free_balance(BOB); + let bob_reserved_after = Balances::reserved_balance(BOB); + // moved ::CloseEarlyDisputeBond from reserved to free + assert_eq!( + bob_reserved - bob_reserved_after, + ::CloseEarlyDisputeBond::get() + ); + assert_eq!(bob_free_after - bob_free, ::CloseEarlyDisputeBond::get()); + }); +} + +#[test] +fn settles_early_close_bonds_with_resolution_in_state_scheduled_as_market_creator() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + let alice_free = Balances::free_balance(ALICE); + let alice_reserved = Balances::reserved_balance(ALICE); + + run_to_block(end + 1); + + // verify the market doesn't close after proposed new market period end + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + + let alice_free_after = Balances::free_balance(ALICE); + let alice_reserved_after = Balances::reserved_balance(ALICE); + // moved ::CloseEarlyRequestBond from reserved to free + assert_eq!( + alice_reserved - alice_reserved_after, + ::CloseEarlyRequestBond::get() + ); + assert_eq!( + alice_free_after - alice_free, + ::CloseEarlyRequestBond::get() + ); + }); +} + +#[test] +fn schedule_early_close_disputed_sudo_schedule_and_settle_bonds() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + let old_period = MarketPeriod::Block(0..end); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + old_period.clone(), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + run_blocks(1); + + assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); + + let reserved_bob = Balances::reserved_balance(BOB); + let reserved_alice = Balances::reserved_balance(ALICE); + let free_bob = Balances::free_balance(BOB); + let free_alice = Balances::free_balance(ALICE); + + assert_ok!( + PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) + ); + + let reserved_bob_after = Balances::reserved_balance(BOB); + let reserved_alice_after = Balances::reserved_balance(ALICE); + let free_bob_after = Balances::free_balance(BOB); + let free_alice_after = Balances::free_balance(ALICE); + + assert_eq!( + reserved_alice - reserved_alice_after, + ::CloseEarlyRequestBond::get() + ); + assert_eq!( + reserved_bob - reserved_bob_after, + ::CloseEarlyDisputeBond::get() + ); + // market creator Alice gets the bonds + assert_eq!( + free_alice_after - free_alice, + ::CloseEarlyRequestBond::get() + + ::CloseEarlyDisputeBond::get() + ); + assert_eq!(free_bob_after - free_bob, 0); + + let now = >::block_number(); + let new_end = now + ::CloseEarlyProtectionBlockPeriod::get(); + let market_ids_at_new_end = >::get(new_end); + assert_eq!(market_ids_at_new_end, vec![market_id]); + + let market = MarketCommons::market(&market_id).unwrap(); + let new_period = MarketPeriod::Block(0..new_end); + assert_eq!( + market.early_close.unwrap(), + EarlyClose { + old: old_period, + new: new_period, + state: EarlyCloseState::ScheduledAsOther, + } + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 1137f6b63..20249a20e 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -22,14 +22,15 @@ mod admin_move_market_to_closed; mod admin_move_market_to_resolved; mod approve_market; mod buy_complete_set; -mod create_market; mod close_trusted_market; +mod create_market; mod dispute; mod dispute_early_close; mod edit_market; mod manually_close_market; mod on_initialize; mod on_market_close; +mod redeem_shares; mod reject_early_close; mod reject_market; mod report; diff --git a/zrml/prediction-markets/src/tests/redeem_shares.rs b/zrml/prediction-markets/src/tests/redeem_shares.rs new file mode 100644 index 000000000..309247f78 --- /dev/null +++ b/zrml/prediction-markets/src/tests/redeem_shares.rs @@ -0,0 +1,93 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; +use test_case::test_case; + +use zeitgeist_primitives::types::OutcomeReport; + +#[test] +fn it_allows_to_redeem_shares() { + let test = |base_asset: AssetOf| { + let end = 2; + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + run_blocks(market.deadlines.dispute_duration); + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Resolved); + + assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); + let bal = Balances::free_balance(CHARLIE); + assert_eq!(bal, 1_000 * BASE); + System::assert_last_event( + Event::TokensRedeemed(0, Asset::CategoricalOutcome(0, 1), CENT, CENT, CHARLIE).into(), + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test_case(ScoringRule::Parimutuel; "parimutuel")] +fn redeem_shares_fails_if_invalid_resolution_mechanism(scoring_rule: ScoringRule) { + let test = |base_asset: AssetOf| { + let end = 2; + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..end, + scoring_rule, + ); + + assert_ok!(MarketCommons::mutate_market(&0, |market_inner| { + market_inner.status = MarketStatus::Resolved; + Ok(()) + })); + + assert_noop!( + PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0), + Error::::InvalidResolutionMechanism + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} From 2bc4b41e66b14fbd7bec6c896753648c0ef3c256 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 13 Jan 2024 23:11:13 +0100 Subject: [PATCH 26/32] Extract `on_resolution` and additional `redeem_shares` tests --- zrml/prediction-markets/src/old_tests.rs | 1287 +---------------- .../tests/create_market_and_deploy_pool.rs | 93 ++ zrml/prediction-markets/src/tests/mod.rs | 2 + .../src/tests/on_resolution.rs | 1101 ++++++++++++++ .../src/tests/redeem_shares.rs | 85 +- 5 files changed, 1296 insertions(+), 1272 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs create mode 100644 zrml/prediction-markets/src/tests/on_resolution.rs diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/old_tests.rs index 333d7c196..6c0c8eedd 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/old_tests.rs @@ -21,11 +21,10 @@ extern crate alloc; -use crate::{mock::*, Config, Error, Event, MarketIdsPerDisputeBlock, MarketIdsPerReportBlock}; +use crate::{mock::*, Config, Error, Event, MarketIdsPerDisputeBlock}; use alloc::collections::BTreeMap; -use core::ops::Range; use frame_support::{assert_noop, assert_ok, traits::NamedReservableCurrency}; -use sp_runtime::{traits::BlakeTwo256, Perquintill}; +use sp_runtime::traits::BlakeTwo256; use zrml_court::{types::*, Error as CError}; use orml_traits::{MultiCurrency, MultiReservableCurrency}; @@ -33,13 +32,12 @@ use sp_arithmetic::Perbill; use sp_runtime::traits::{Hash, Zero}; use zeitgeist_primitives::{ constants::mock::{ - MaxAppeals, MaxSelectedDraws, MinJurorStake, OutcomeBond, OutcomeFactor, OutsiderBond, - BASE, CENT, MILLISECS_PER_BLOCK, + MaxAppeals, MaxSelectedDraws, MinJurorStake, OutsiderBond, BASE, CENT, MILLISECS_PER_BLOCK, }, types::{ - AccountIdTest, Asset, Balance, Bond, Deadlines, MarketBonds, MarketCreation, - MarketDisputeMechanism, MarketId, MarketPeriod, MarketStatus, MarketType, MultiHash, - OutcomeReport, Report, ScalarPosition, ScoringRule, + AccountIdTest, Asset, Balance, Deadlines, MarketCreation, MarketDisputeMechanism, MarketId, + MarketPeriod, MarketStatus, MarketType, MultiHash, OutcomeReport, ScalarPosition, + ScoringRule, }, }; use zrml_global_disputes::{ @@ -89,487 +87,6 @@ fn check_reserve(account: &AccountIdTest, expected: Balance) { assert_eq!(Balances::reserved_balance(account), SENTINEL_AMOUNT + expected); } -fn simple_create_categorical_market( - base_asset: Asset, - creation: MarketCreation, - period: Range, - scoring_rule: ScoringRule, -) { - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(period), - get_deadlines(), - gen_metadata(2), - creation, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), - scoring_rule - )); -} - -fn simple_create_scalar_market( - base_asset: Asset, - creation: MarketCreation, - period: Range, - scoring_rule: ScoringRule, -) { - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(period), - get_deadlines(), - gen_metadata(2), - creation, - MarketType::Scalar(100..=200), - Some(MarketDisputeMechanism::SimpleDisputes), - scoring_rule - )); -} - -#[test] -fn it_correctly_resolves_a_market_that_was_reported_on() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); - - let market = MarketCommons::market(&0).unwrap(); - let report_at = end + market.deadlines.grace_period + 1; - run_to_block(report_at); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - - let reported_ids = - MarketIdsPerReportBlock::::get(report_at + market.deadlines.dispute_duration); - assert_eq!(reported_ids.len(), 1); - let id = reported_ids[0]; - assert_eq!(id, 0); - - run_blocks(market.deadlines.dispute_duration); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Resolved); - - // Check balance of winning outcome asset. - let share_b = Asset::CategoricalOutcome(0, 1); - let share_b_total = Tokens::total_issuance(share_b); - assert_eq!(share_b_total, CENT); - let share_b_bal = Tokens::free_balance(share_b, &CHARLIE); - assert_eq!(share_b_bal, CENT); - - // TODO(#792): Remove other assets. - let share_a = Asset::CategoricalOutcome(0, 0); - let share_a_total = Tokens::total_issuance(share_a); - assert_eq!(share_a_total, CENT); - let share_a_bal = Tokens::free_balance(share_a, &CHARLIE); - assert_eq!(share_a_bal, CENT); - - let share_c = Asset::CategoricalOutcome(0, 2); - let share_c_total = Tokens::total_issuance(share_c); - assert_eq!(share_c_total, 0); - let share_c_bal = Tokens::free_balance(share_c, &CHARLIE); - assert_eq!(share_c_bal, 0); - - assert!(market.bonds.creation.unwrap().is_settled); - assert!(market.bonds.oracle.unwrap().is_settled); - }); -} - -#[test] -fn it_resolves_a_disputed_market() { - let test = |base_asset: Asset| { - let end = 2; - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); - let market = MarketCommons::market(&0).unwrap(); - - let report_at = end + market.deadlines.grace_period + 1; - run_to_block(report_at); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(0) - )); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 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()); - - let dispute_at_0 = report_at + 1; - run_to_block(dispute_at_0); - - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(1) - )); - - let dispute_at_1 = report_at + 2; - run_to_block(dispute_at_1); - - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(DAVE), - 0, - OutcomeReport::Categorical(0) - )); - - let dispute_at_2 = report_at + 3; - run_to_block(dispute_at_2); - - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(EVE), - 0, - OutcomeReport::Categorical(1) - )); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Disputed); - - // check everyone's deposits - let charlie_reserved = Balances::reserved_balance(CHARLIE); - assert_eq!(charlie_reserved, DisputeBond::get() + OutcomeBond::get()); - - let dave_reserved = Balances::reserved_balance(DAVE); - assert_eq!(dave_reserved, OutcomeBond::get() + OutcomeFactor::get()); - - let eve_reserved = Balances::reserved_balance(EVE); - assert_eq!(eve_reserved, OutcomeBond::get() + 2 * OutcomeFactor::get()); - - // check disputes length - let disputes = zrml_simple_disputes::Disputes::::get(0); - assert_eq!(disputes.len(), 3); - - // make sure the old mappings of market id per dispute block are erased - let market_ids_1 = MarketIdsPerDisputeBlock::::get( - dispute_at_0 + market.deadlines.dispute_duration, - ); - assert_eq!(market_ids_1.len(), 0); - - let market_ids_2 = MarketIdsPerDisputeBlock::::get( - dispute_at_1 + market.deadlines.dispute_duration, - ); - assert_eq!(market_ids_2.len(), 0); - - let market_ids_3 = MarketIdsPerDisputeBlock::::get( - dispute_at_2 + market.deadlines.dispute_duration, - ); - assert_eq!(market_ids_3.len(), 1); - - run_blocks(market.deadlines.dispute_duration); - - let market_after = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after.status, MarketStatus::Resolved); - let disputes = zrml_simple_disputes::Disputes::::get(0); - assert_eq!(disputes.len(), 0); - - assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); - - // Make sure rewards are right: - // - // Slashed amounts: - // - Dave's reserve: OutcomeBond::get() + OutcomeFactor::get() - // - Alice's oracle bond: OracleBond::get() - // simple-disputes reward: OutcomeBond::get() + OutcomeFactor::get() - // Charlie gets OracleBond, because the dispute was justified. - // A dispute is justified if the oracle's report is different to the final outcome. - // - // Charlie and Eve each receive half of the simple-disputes reward as bounty. - let dave_reserved = OutcomeBond::get() + OutcomeFactor::get(); - let total_slashed = dave_reserved; - - let charlie_balance = Balances::free_balance(CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE + OracleBond::get() + total_slashed / 2); - let charlie_reserved_2 = Balances::reserved_balance(CHARLIE); - assert_eq!(charlie_reserved_2, 0); - let eve_balance = Balances::free_balance(EVE); - assert_eq!(eve_balance, 1_000 * BASE + total_slashed / 2); - - let dave_balance = Balances::free_balance(DAVE); - assert_eq!(dave_balance, 1_000 * BASE - dave_reserved); - - let alice_balance = Balances::free_balance(ALICE); - assert_eq!(alice_balance, 1_000 * BASE - OracleBond::get()); - - // bob kinda gets away scot-free since Alice is held responsible - // for her designated reporter - let bob_balance = Balances::free_balance(BOB); - assert_eq!(bob_balance, 1_000 * BASE); - - assert!(market_after.bonds.creation.unwrap().is_settled); - assert!(market_after.bonds.oracle.unwrap().is_settled); - assert!(market_after.bonds.dispute.unwrap().is_settled); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn it_resolves_a_disputed_court_market() { - let test = |base_asset: Asset| { - let juror_0 = 1000; - let juror_1 = 1001; - let juror_2 = 1002; - let juror_3 = 1003; - let juror_4 = 1004; - let juror_5 = 1005; - - for j in &[juror_0, juror_1, juror_2, juror_3, juror_4, juror_5] { - let amount = MinJurorStake::get() + *j; - assert_ok!(AssetManager::deposit(Asset::Ztg, j, amount + SENTINEL_AMOUNT)); - assert_ok!(Court::join_court(RuntimeOrigin::signed(*j), amount)); - } - - // just to have enough jurors for the dispute - for j in 1006..(1006 + Court::necessary_draws_weight(0usize) as u32) { - let juror = j as u128; - let amount = MinJurorStake::get() + juror; - assert_ok!(AssetManager::deposit(Asset::Ztg, &juror, amount + SENTINEL_AMOUNT)); - assert_ok!(Court::join_court(RuntimeOrigin::signed(juror), amount)); - } - - let end = 2; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr, - )); - - let market_id = 0; - let market = MarketCommons::market(&0).unwrap(); - - let report_at = end + market.deadlines.grace_period + 1; - run_to_block(report_at); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - market_id, - OutcomeReport::Categorical(0) - )); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), market_id,)); - - let court = zrml_court::Courts::::get(market_id).unwrap(); - let vote_start = court.round_ends.pre_vote + 1; - - run_to_block(vote_start); - - // overwrite draws to disregard randomness - zrml_court::SelectedDraws::::remove(market_id); - let mut draws = zrml_court::SelectedDraws::::get(market_id); - for juror in &[juror_0, juror_1, juror_2, juror_3, juror_4, juror_5] { - let draw = Draw { - court_participant: *juror, - weight: 1, - vote: Vote::Drawn, - slashable: MinJurorStake::get(), - }; - let index = draws - .binary_search_by_key(juror, |draw| draw.court_participant) - .unwrap_or_else(|j| j); - draws.try_insert(index, draw).unwrap(); - } - let old_draws = draws.clone(); - zrml_court::SelectedDraws::::insert(market_id, draws); - - let salt = ::Hash::default(); - - // outcome_0 is the plurality decision => right outcome - let outcome_0 = OutcomeReport::Categorical(0); - let vote_item_0 = VoteItem::Outcome(outcome_0.clone()); - // outcome_1 is the wrong outcome - let outcome_1 = OutcomeReport::Categorical(1); - let vote_item_1 = VoteItem::Outcome(outcome_1); - - let commitment_0 = BlakeTwo256::hash_of(&(juror_0, vote_item_0.clone(), salt)); - assert_ok!(Court::vote(RuntimeOrigin::signed(juror_0), market_id, commitment_0)); - - // juror_1 votes for non-plurality outcome => slashed later - let commitment_1 = BlakeTwo256::hash_of(&(juror_1, vote_item_1.clone(), salt)); - assert_ok!(Court::vote(RuntimeOrigin::signed(juror_1), market_id, commitment_1)); - - let commitment_2 = BlakeTwo256::hash_of(&(juror_2, vote_item_0.clone(), salt)); - assert_ok!(Court::vote(RuntimeOrigin::signed(juror_2), market_id, commitment_2)); - - let commitment_3 = BlakeTwo256::hash_of(&(juror_3, vote_item_0.clone(), salt)); - assert_ok!(Court::vote(RuntimeOrigin::signed(juror_3), market_id, commitment_3)); - - // juror_4 fails to vote in time - - let commitment_5 = BlakeTwo256::hash_of(&(juror_5, vote_item_0.clone(), salt)); - assert_ok!(Court::vote(RuntimeOrigin::signed(juror_5), market_id, commitment_5)); - - // juror_3 is denounced by juror_0 => slashed later - assert_ok!(Court::denounce_vote( - RuntimeOrigin::signed(juror_0), - market_id, - juror_3, - vote_item_0.clone(), - salt - )); - - let aggregation_start = court.round_ends.vote + 1; - run_to_block(aggregation_start); - - assert_ok!(Court::reveal_vote( - RuntimeOrigin::signed(juror_0), - market_id, - vote_item_0.clone(), - salt - )); - assert_ok!(Court::reveal_vote( - RuntimeOrigin::signed(juror_1), - market_id, - vote_item_1, - salt - )); - - let wrong_salt = BlakeTwo256::hash_of(&69); - assert_noop!( - Court::reveal_vote( - RuntimeOrigin::signed(juror_2), - market_id, - vote_item_0.clone(), - wrong_salt - ), - CError::::CommitmentHashMismatch - ); - assert_ok!(Court::reveal_vote( - RuntimeOrigin::signed(juror_2), - market_id, - vote_item_0.clone(), - salt - )); - - assert_noop!( - Court::reveal_vote( - RuntimeOrigin::signed(juror_3), - market_id, - vote_item_0.clone(), - salt - ), - CError::::VoteAlreadyDenounced - ); - - assert_noop!( - Court::reveal_vote( - RuntimeOrigin::signed(juror_4), - market_id, - vote_item_0.clone(), - salt - ), - CError::::JurorDidNotVote - ); - - // juror_5 fails to reveal in time - - let resolve_at = court.round_ends.appeal; - let market_ids = MarketIdsPerDisputeBlock::::get(resolve_at); - assert_eq!(market_ids.len(), 1); - - run_blocks(resolve_at); - - let market_after = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after.status, MarketStatus::Resolved); - assert_eq!(market_after.resolved_outcome, Some(outcome_0)); - let court_after = zrml_court::Courts::::get(market_id).unwrap(); - assert_eq!(court_after.status, CourtStatus::Closed { winner: vote_item_0 }); - - let free_juror_0_before = Balances::free_balance(juror_0); - let free_juror_1_before = Balances::free_balance(juror_1); - let free_juror_2_before = Balances::free_balance(juror_2); - let free_juror_3_before = Balances::free_balance(juror_3); - let free_juror_4_before = Balances::free_balance(juror_4); - let free_juror_5_before = Balances::free_balance(juror_5); - - assert_ok!(Court::reassign_court_stakes(RuntimeOrigin::signed(juror_0), market_id)); - - let free_juror_0_after = Balances::free_balance(juror_0); - let slashable_juror_0 = - old_draws.iter().find(|draw| draw.court_participant == juror_0).unwrap().slashable; - let free_juror_1_after = Balances::free_balance(juror_1); - let slashable_juror_1 = - old_draws.iter().find(|draw| draw.court_participant == juror_1).unwrap().slashable; - let free_juror_2_after = Balances::free_balance(juror_2); - let slashable_juror_2 = - old_draws.iter().find(|draw| draw.court_participant == juror_2).unwrap().slashable; - let free_juror_3_after = Balances::free_balance(juror_3); - let slashable_juror_3 = - old_draws.iter().find(|draw| draw.court_participant == juror_3).unwrap().slashable; - let free_juror_4_after = Balances::free_balance(juror_4); - let slashable_juror_4 = - old_draws.iter().find(|draw| draw.court_participant == juror_4).unwrap().slashable; - let free_juror_5_after = Balances::free_balance(juror_5); - let slashable_juror_5 = - old_draws.iter().find(|draw| draw.court_participant == juror_5).unwrap().slashable; - - let mut total_slashed = 0; - // juror_1 voted for the wrong outcome => slashed - assert_eq!(free_juror_1_before - free_juror_1_after, slashable_juror_1); - total_slashed += slashable_juror_1; - // juror_3 was denounced by juror_0 => slashed - assert_eq!(free_juror_3_before - free_juror_3_after, slashable_juror_3); - total_slashed += slashable_juror_3; - // juror_4 failed to vote => slashed - assert_eq!(free_juror_4_before - free_juror_4_after, slashable_juror_4); - total_slashed += slashable_juror_4; - // juror_5 failed to reveal => slashed - assert_eq!(free_juror_5_before - free_juror_5_after, slashable_juror_5); - total_slashed += slashable_juror_5; - // juror_0 and juror_2 voted for the right outcome => rewarded - let total_winner_stake = slashable_juror_0 + slashable_juror_2; - let juror_0_share = Perquintill::from_rational(slashable_juror_0, total_winner_stake); - assert_eq!(free_juror_0_after, free_juror_0_before + juror_0_share * total_slashed); - let juror_2_share = Perquintill::from_rational(slashable_juror_2, total_winner_stake); - assert_eq!(free_juror_2_after, free_juror_2_before + juror_2_share * total_slashed); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - fn simulate_appeal_cycle(market_id: MarketId) { let court = zrml_court::Courts::::get(market_id).unwrap(); let vote_start = court.round_ends.pre_vote + 1; @@ -893,38 +410,6 @@ fn full_scalar_market_lifecycle() { }); } -#[test] -fn scalar_market_correctly_resolves_on_out_of_range_outcomes_below_threshold() { - let test = |base_asset: Asset| { - scalar_market_correctly_resolves_common(base_asset, 50); - assert_eq!(AssetManager::free_balance(base_asset, &CHARLIE), 900 * BASE); - assert_eq!(AssetManager::free_balance(base_asset, &EVE), 1100 * BASE); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn scalar_market_correctly_resolves_on_out_of_range_outcomes_above_threshold() { - let test = |base_asset: Asset| { - scalar_market_correctly_resolves_common(base_asset, 250); - assert_eq!(AssetManager::free_balance(base_asset, &CHARLIE), 1000 * BASE); - assert_eq!(AssetManager::free_balance(base_asset, &EVE), 1000 * BASE); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - #[test] fn authorized_correctly_resolves_disputed_market() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. @@ -1072,12 +557,13 @@ fn authorized_correctly_resolves_disputed_market() { } #[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_on_oracle_report() - { +fn outsider_reports_wrong_outcome() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. let test = |base_asset: Asset| { reserve_sentinel_amounts(); + let end = 100; + let alice_balance_before = Balances::free_balance(ALICE); assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), base_asset, @@ -1091,129 +577,17 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::Lmsr, )); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); + + let outsider = CHARLIE; + let market = MarketCommons::market(&0).unwrap(); let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); + let report_at = grace_period + market.deadlines.oracle_duration + 1; + run_to_block(report_at); assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), + RuntimeOrigin::signed(outsider), 0, - OutcomeReport::Categorical(0) - )); - run_to_block(grace_period + market.deadlines.dispute_duration + 1); - check_reserve(&ALICE, 0); - assert_eq!( - Balances::free_balance(ALICE), - alice_balance_before + ValidityBond::get() + OracleBond::get() - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_on_outsider_report() - { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..100), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); - - let charlie_balance_before = Balances::free_balance(CHARLIE); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - let report_at = grace_period + market.deadlines.oracle_duration + 1; - run_to_block(report_at); - - assert!(market.bonds.outsider.is_none()); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(1) - )); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.bonds.outsider, Some(Bond::new(CHARLIE, OutsiderBond::get()))); - check_reserve(&CHARLIE, OutsiderBond::get()); - assert_eq!(Balances::free_balance(CHARLIE), charlie_balance_before - OutsiderBond::get()); - let charlie_balance_before = Balances::free_balance(CHARLIE); - - run_blocks(market.deadlines.dispute_duration); - check_reserve(&ALICE, 0); - // Check that validity bond didn't get slashed, but oracle bond did - assert_eq!(Balances::free_balance(ALICE), alice_balance_before + ValidityBond::get()); - - check_reserve(&CHARLIE, 0); - // Check that the outsider gets the OracleBond together with the OutsiderBond - assert_eq!( - Balances::free_balance(CHARLIE), - charlie_balance_before + OracleBond::get() + OutsiderBond::get() - ); - let market = MarketCommons::market(&0).unwrap(); - assert!(market.bonds.outsider.unwrap().is_settled); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn outsider_reports_wrong_outcome() { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - - let end = 100; - let alice_balance_before = Balances::free_balance(ALICE); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - - let outsider = CHARLIE; - - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - let report_at = grace_period + market.deadlines.oracle_duration + 1; - run_to_block(report_at); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(outsider), - 0, - OutcomeReport::Categorical(1) + OutcomeReport::Categorical(1) )); let outsider_balance_before = Balances::free_balance(outsider); @@ -1261,632 +635,3 @@ fn outsider_reports_wrong_outcome() { test(Asset::ForeignAsset(100)); }); } - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_market_on_oracle_report() - { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, OracleBond::get()); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - let report_at = grace_period + 1; - run_to_block(report_at); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - run_blocks(market.deadlines.dispute_duration); - check_reserve(&ALICE, 0); - // Check that nothing got slashed - assert_eq!(Balances::free_balance(ALICE), alice_balance_before + OracleBond::get()); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_market_on_outsider_report() - { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, OracleBond::get()); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - let report_at = grace_period + market.deadlines.oracle_duration + 1; - run_to_block(report_at); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(1) - )); - run_blocks(market.deadlines.dispute_duration); - // Check that oracle bond got slashed - check_reserve(&ALICE, 0); - assert_eq!(Balances::free_balance(ALICE), alice_balance_before); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_with_correct_disputed_outcome_with_oracle_report() - { - // Oracle reports in time but incorrect report, so OracleBond gets slashed on resolution - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(0) - )); - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(1) - )); - run_blocks(market.deadlines.dispute_duration); - check_reserve(&ALICE, 0); - // ValidityBond bond is returned but OracleBond is slashed - assert_eq!(Balances::free_balance(ALICE), alice_balance_before + ValidityBond::get()); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_market_with_correct_disputed_outcome_with_oracle_report() - { - // Oracle reports in time but incorrect report, so OracleBond gets slashed on resolution - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, OracleBond::get()); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(0) - )); - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(1) - )); - run_blocks(market.deadlines.dispute_duration); - check_reserve(&ALICE, 0); - // ValidityBond bond is returned but OracleBond is slashed - assert_eq!(Balances::free_balance(ALICE), alice_balance_before); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_with_wrong_disputed_outcome_with_oracle_report() - { - // Oracle reports in time and correct report, so OracleBond does not get slashed on resolution - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(0) - )); - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); - // EVE disputes with wrong outcome - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(EVE), - 0, - OutcomeReport::Categorical(1) - )); - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(0) - )); - run_blocks(market.deadlines.dispute_duration); - check_reserve(&ALICE, 0); - // ValidityBond bond is returned but OracleBond is not slashed - assert_eq!( - Balances::free_balance(ALICE), - alice_balance_before + ValidityBond::get() + OracleBond::get() - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_market_with_wrong_disputed_outcome_with_oracle_report() - { - // Oracle reports in time and correct report, so OracleBond does not get slashed on resolution - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, OracleBond::get()); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(0) - )); - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); - // EVE disputes with wrong outcome - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(EVE), - 0, - OutcomeReport::Categorical(1) - )); - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(0) - )); - run_blocks(market.deadlines.dispute_duration); - check_reserve(&ALICE, 0); - // ValidityBond bond is returned but OracleBond is not slashed - assert_eq!(Balances::free_balance(ALICE), alice_balance_before + OracleBond::get()); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_with_disputed_outcome_with_outsider_report() - { - // Oracle does not report in time, so OracleBond gets slashed on resolution - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); - - let outsider = CHARLIE; - - let market = MarketCommons::market(&0).unwrap(); - let after_oracle_duration = - end + market.deadlines.grace_period + market.deadlines.oracle_duration + 1; - run_to_block(after_oracle_duration); - // CHARLIE is not an Oracle - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(outsider), - 0, - OutcomeReport::Categorical(0) - )); - let outsider_balance_before = Balances::free_balance(outsider); - check_reserve(&outsider, OutsiderBond::get()); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); - // EVE disputes with wrong outcome - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(EVE), - 0, - OutcomeReport::Categorical(1) - )); - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(FRED), - 0, - OutcomeReport::Categorical(0) - )); - run_blocks(market.deadlines.dispute_duration); - check_reserve(&ALICE, 0); - // ValidityBond bond is returned but OracleBond is slashed - assert_eq!(Balances::free_balance(ALICE), alice_balance_before + ValidityBond::get()); - - check_reserve(&outsider, 0); - assert_eq!( - Balances::free_balance(outsider), - outsider_balance_before + OracleBond::get() + OutsiderBond::get() - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_market_with_disputed_outcome_with_outsider_report() - { - // Oracle does not report in time, so OracleBond gets slashed on resolution - // NOTE: Bonds are always in ZTG - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - - let outsider = CHARLIE; - - assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, OracleBond::get()); - let market = MarketCommons::market(&0).unwrap(); - let after_oracle_duration = - end + market.deadlines.grace_period + market.deadlines.oracle_duration + 1; - run_to_block(after_oracle_duration); - // CHARLIE is not an Oracle - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(outsider), - 0, - OutcomeReport::Categorical(0) - )); - let outsider_balance_before = Balances::free_balance(outsider); - check_reserve(&outsider, OutsiderBond::get()); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); - // EVE disputes with wrong outcome - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(EVE), - 0, - OutcomeReport::Categorical(1) - )); - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(FRED), - 0, - OutcomeReport::Categorical(0) - )); - run_blocks(market.deadlines.dispute_duration); - check_reserve(&ALICE, 0); - // ValidityBond bond is returned but OracleBond is slashed - assert_eq!(Balances::free_balance(ALICE), alice_balance_before); - - check_reserve(&outsider, 0); - assert_eq!( - Balances::free_balance(outsider), - outsider_balance_before + OracleBond::get() + OutsiderBond::get() - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn create_market_and_deploy_pool_works() { - ExtBuilder::default().build().execute_with(|| { - let creator = ALICE; - let creator_fee = Perbill::from_parts(1); - let oracle = BOB; - let period = MarketPeriod::Block(1..2); - let deadlines = Deadlines { - grace_period: 1, - oracle_duration: ::MinOracleDuration::get() + 2, - dispute_duration: ::MinDisputeDuration::get() + 3, - }; - let metadata = gen_metadata(0x99); - let MultiHash::Sha3_384(multihash) = metadata; - let market_type = MarketType::Categorical(7); - let dispute_mechanism = Some(MarketDisputeMechanism::Authorized); - let amount = 1234567890; - let swap_prices = vec![50 * CENT, 50 * CENT]; - let swap_fee = CENT; - let market_id = 0; - assert_ok!(PredictionMarkets::create_market_and_deploy_pool( - RuntimeOrigin::signed(creator), - Asset::Ztg, - creator_fee, - oracle, - period.clone(), - deadlines, - metadata, - market_type.clone(), - dispute_mechanism.clone(), - amount, - swap_prices.clone(), - swap_fee, - )); - let market = MarketCommons::market(&0).unwrap(); - let bonds = MarketBonds { - creation: Some(Bond::new(ALICE, ::ValidityBond::get())), - oracle: Some(Bond::new(ALICE, ::OracleBond::get())), - outsider: None, - dispute: None, - close_dispute: None, - close_request: None, - }; - assert_eq!(market.creator, creator); - assert_eq!(market.creation, MarketCreation::Permissionless); - assert_eq!(market.creator_fee, creator_fee); - assert_eq!(market.oracle, oracle); - assert_eq!(market.metadata, multihash); - assert_eq!(market.market_type, market_type); - assert_eq!(market.period, period); - assert_eq!(market.deadlines, deadlines); - assert_eq!(market.scoring_rule, ScoringRule::Lmsr); - assert_eq!(market.status, MarketStatus::Active); - assert_eq!(market.report, None); - assert_eq!(market.resolved_outcome, None); - assert_eq!(market.dispute_mechanism, dispute_mechanism); - assert_eq!(market.bonds, bonds); - // Check that the correct amount of full sets were bought. - assert_eq!( - AssetManager::free_balance(Asset::CategoricalOutcome(market_id, 0), &ALICE), - amount - ); - assert!(DeployPoolMock::called_once_with( - creator, - market_id, - amount, - swap_prices, - swap_fee - )); - }); -} - -#[test] -fn trusted_market_complete_lifecycle() { - ExtBuilder::default().build().execute_with(|| { - let end = 3; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - Deadlines { - grace_period: 0, - oracle_duration: ::MinOracleDuration::get(), - dispute_duration: Zero::zero(), - }, - gen_metadata(0x99), - MarketCreation::Permissionless, - MarketType::Categorical(3), - None, - ScoringRule::Lmsr, - )); - let market_id = 0; - assert_ok!(PredictionMarkets::buy_complete_set( - RuntimeOrigin::signed(FRED), - market_id, - BASE - )); - run_to_block(end); - let outcome = OutcomeReport::Categorical(1); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - market_id, - outcome.clone() - )); - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Resolved); - assert_eq!(market.report, Some(Report { at: end, by: BOB, outcome: outcome.clone() })); - assert_eq!(market.resolved_outcome, Some(outcome)); - assert_eq!(market.dispute_mechanism, None); - assert!(market.bonds.oracle.unwrap().is_settled); - assert_eq!(market.bonds.outsider, None); - assert_eq!(market.bonds.dispute, None); - assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(FRED), market_id)); - // Ensure that we don't accidentally leave any artifacts. - assert!(MarketIdsPerDisputeBlock::::iter().next().is_none()); - assert!(MarketIdsPerReportBlock::::iter().next().is_none()); - }); -} - -// Common code of `scalar_market_correctly_resolves_*` -fn scalar_market_correctly_resolves_common(base_asset: Asset, reported_value: u128) { - let end = 100; - simple_create_scalar_market( - base_asset, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, 100 * BASE)); - assert_ok!(Tokens::transfer( - RuntimeOrigin::signed(CHARLIE), - EVE, - Asset::ScalarOutcome(0, ScalarPosition::Short), - 100 * BASE - )); - // (Eve now has 100 SHORT, Charlie has 100 LONG) - - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Scalar(reported_value) - )); - let market_after_report = MarketCommons::market(&0).unwrap(); - assert!(market_after_report.report.is_some()); - let report = market_after_report.report.unwrap(); - assert_eq!(report.at, grace_period + 1); - assert_eq!(report.by, BOB); - assert_eq!(report.outcome, OutcomeReport::Scalar(reported_value)); - - run_blocks(market.deadlines.dispute_duration); - let market_after_resolve = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after_resolve.status, MarketStatus::Resolved); - - // Check balances before redeeming (just to make sure that our tests are based on correct - // assumptions)! - assert_eq!(AssetManager::free_balance(base_asset, &CHARLIE), 900 * BASE); - assert_eq!(AssetManager::free_balance(base_asset, &EVE), 1000 * BASE); - - assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); - assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(EVE), 0)); - let assets = PredictionMarkets::outcome_assets(0, &MarketCommons::market(&0).unwrap()); - for asset in assets.iter() { - assert_eq!(AssetManager::free_balance(*asset, &CHARLIE), 0); - assert_eq!(AssetManager::free_balance(*asset, &EVE), 0); - } -} diff --git a/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs b/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs new file mode 100644 index 000000000..6bd3f1fed --- /dev/null +++ b/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs @@ -0,0 +1,93 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +use zeitgeist_primitives::types::{Bond, MarketBonds}; + +#[test] +fn create_market_and_deploy_pool_works() { + ExtBuilder::default().build().execute_with(|| { + let creator = ALICE; + let creator_fee = Perbill::from_parts(1); + let oracle = BOB; + let period = MarketPeriod::Block(1..2); + let deadlines = Deadlines { + grace_period: 1, + oracle_duration: ::MinOracleDuration::get() + 2, + dispute_duration: ::MinDisputeDuration::get() + 3, + }; + let metadata = gen_metadata(0x99); + let MultiHash::Sha3_384(multihash) = metadata; + let market_type = MarketType::Categorical(7); + let dispute_mechanism = Some(MarketDisputeMechanism::Authorized); + let amount = 1234567890; + let swap_prices = vec![50 * CENT, 50 * CENT]; + let swap_fee = CENT; + let market_id = 0; + assert_ok!(PredictionMarkets::create_market_and_deploy_pool( + RuntimeOrigin::signed(creator), + Asset::Ztg, + creator_fee, + oracle, + period.clone(), + deadlines, + metadata, + market_type.clone(), + dispute_mechanism.clone(), + amount, + swap_prices.clone(), + swap_fee, + )); + let market = MarketCommons::market(&0).unwrap(); + let bonds = MarketBonds { + creation: Some(Bond::new(ALICE, ::ValidityBond::get())), + oracle: Some(Bond::new(ALICE, ::OracleBond::get())), + outsider: None, + dispute: None, + close_dispute: None, + close_request: None, + }; + assert_eq!(market.creator, creator); + assert_eq!(market.creation, MarketCreation::Permissionless); + assert_eq!(market.creator_fee, creator_fee); + assert_eq!(market.oracle, oracle); + assert_eq!(market.metadata, multihash); + assert_eq!(market.market_type, market_type); + assert_eq!(market.period, period); + assert_eq!(market.deadlines, deadlines); + assert_eq!(market.scoring_rule, ScoringRule::Lmsr); + assert_eq!(market.status, MarketStatus::Active); + assert_eq!(market.report, None); + assert_eq!(market.resolved_outcome, None); + assert_eq!(market.dispute_mechanism, dispute_mechanism); + assert_eq!(market.bonds, bonds); + // Check that the correct amount of full sets were bought. + assert_eq!( + AssetManager::free_balance(Asset::CategoricalOutcome(market_id, 0), &ALICE), + amount + ); + assert!(DeployPoolMock::called_once_with( + creator, + market_id, + amount, + swap_prices, + swap_fee + )); + }); +} diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 20249a20e..8ad8ea048 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -24,12 +24,14 @@ mod approve_market; mod buy_complete_set; mod close_trusted_market; mod create_market; +mod create_market_and_deploy_pool; mod dispute; mod dispute_early_close; mod edit_market; mod manually_close_market; mod on_initialize; mod on_market_close; +mod on_resolution; mod redeem_shares; mod reject_early_close; mod reject_market; diff --git a/zrml/prediction-markets/src/tests/on_resolution.rs b/zrml/prediction-markets/src/tests/on_resolution.rs new file mode 100644 index 000000000..e46d79ab5 --- /dev/null +++ b/zrml/prediction-markets/src/tests/on_resolution.rs @@ -0,0 +1,1101 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +use crate::{MarketIdsPerDisputeBlock, MarketIdsPerReportBlock}; +use sp_runtime::{ + traits::{BlakeTwo256, Hash, Zero}, + Perquintill, +}; +use zeitgeist_primitives::types::{Bond, OutcomeReport, Report}; +use zrml_court::types::{CourtStatus, Draw, Vote, VoteItem}; + +#[test] +fn it_correctly_resolves_a_market_that_was_reported_on() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); + + let market = MarketCommons::market(&0).unwrap(); + let report_at = end + market.deadlines.grace_period + 1; + run_to_block(report_at); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + + let reported_ids = + MarketIdsPerReportBlock::::get(report_at + market.deadlines.dispute_duration); + assert_eq!(reported_ids.len(), 1); + let id = reported_ids[0]; + assert_eq!(id, 0); + + run_blocks(market.deadlines.dispute_duration); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Resolved); + + // Check balance of winning outcome asset. + let share_b = Asset::CategoricalOutcome(0, 1); + let share_b_total = Tokens::total_issuance(share_b); + assert_eq!(share_b_total, CENT); + let share_b_bal = Tokens::free_balance(share_b, &CHARLIE); + assert_eq!(share_b_bal, CENT); + + // TODO(#792): Remove other assets. + let share_a = Asset::CategoricalOutcome(0, 0); + let share_a_total = Tokens::total_issuance(share_a); + assert_eq!(share_a_total, CENT); + let share_a_bal = Tokens::free_balance(share_a, &CHARLIE); + assert_eq!(share_a_bal, CENT); + + let share_c = Asset::CategoricalOutcome(0, 2); + let share_c_total = Tokens::total_issuance(share_c); + assert_eq!(share_c_total, 0); + let share_c_bal = Tokens::free_balance(share_c, &CHARLIE); + assert_eq!(share_c_bal, 0); + + assert!(market.bonds.creation.unwrap().is_settled); + assert!(market.bonds.oracle.unwrap().is_settled); + }); +} + +#[test] +fn it_resolves_a_disputed_market() { + let test = |base_asset: AssetOf| { + let end = 2; + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); + let market = MarketCommons::market(&0).unwrap(); + + let report_at = end + market.deadlines.grace_period + 1; + run_to_block(report_at); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(0) + )); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 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()); + + let dispute_at_0 = report_at + 1; + run_to_block(dispute_at_0); + + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(1) + )); + + let dispute_at_1 = report_at + 2; + run_to_block(dispute_at_1); + + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(DAVE), + 0, + OutcomeReport::Categorical(0) + )); + + let dispute_at_2 = report_at + 3; + run_to_block(dispute_at_2); + + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(EVE), + 0, + OutcomeReport::Categorical(1) + )); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Disputed); + + // check everyone's deposits + let charlie_reserved = Balances::reserved_balance(CHARLIE); + assert_eq!( + charlie_reserved, + DisputeBond::get() + ::OutcomeBond::get() + ); + + let dave_reserved = Balances::reserved_balance(DAVE); + assert_eq!( + dave_reserved, + ::OutcomeBond::get() + + ::OutcomeFactor::get() + ); + + let eve_reserved = Balances::reserved_balance(EVE); + assert_eq!( + eve_reserved, + ::OutcomeBond::get() + + 2 * ::OutcomeFactor::get() + ); + + // check disputes length + let disputes = zrml_simple_disputes::Disputes::::get(0); + assert_eq!(disputes.len(), 3); + + // make sure the old mappings of market id per dispute block are erased + let market_ids_1 = MarketIdsPerDisputeBlock::::get( + dispute_at_0 + market.deadlines.dispute_duration, + ); + assert_eq!(market_ids_1.len(), 0); + + let market_ids_2 = MarketIdsPerDisputeBlock::::get( + dispute_at_1 + market.deadlines.dispute_duration, + ); + assert_eq!(market_ids_2.len(), 0); + + let market_ids_3 = MarketIdsPerDisputeBlock::::get( + dispute_at_2 + market.deadlines.dispute_duration, + ); + assert_eq!(market_ids_3.len(), 1); + + run_blocks(market.deadlines.dispute_duration); + + let market_after = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after.status, MarketStatus::Resolved); + let disputes = zrml_simple_disputes::Disputes::::get(0); + assert_eq!(disputes.len(), 0); + + assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); + + // Make sure rewards are right: + // + // Slashed amounts: + // - Dave's reserve: ::OutcomeBond::get() + ::OutcomeFactor::get() + // - Alice's oracle bond: OracleBond::get() + // simple-disputes reward: ::OutcomeBond::get() + ::OutcomeFactor::get() + // Charlie gets OracleBond, because the dispute was justified. + // A dispute is justified if the oracle's report is different to the final outcome. + // + // Charlie and Eve each receive half of the simple-disputes reward as bounty. + let dave_reserved = ::OutcomeBond::get() + + ::OutcomeFactor::get(); + let total_slashed = dave_reserved; + + let charlie_balance = Balances::free_balance(CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE + OracleBond::get() + total_slashed / 2); + let charlie_reserved_2 = Balances::reserved_balance(CHARLIE); + assert_eq!(charlie_reserved_2, 0); + let eve_balance = Balances::free_balance(EVE); + assert_eq!(eve_balance, 1_000 * BASE + total_slashed / 2); + + let dave_balance = Balances::free_balance(DAVE); + assert_eq!(dave_balance, 1_000 * BASE - dave_reserved); + + let alice_balance = Balances::free_balance(ALICE); + assert_eq!(alice_balance, 1_000 * BASE - OracleBond::get()); + + // bob kinda gets away scot-free since Alice is held responsible + // for her designated reporter + let bob_balance = Balances::free_balance(BOB); + assert_eq!(bob_balance, 1_000 * BASE); + + assert!(market_after.bonds.creation.unwrap().is_settled); + assert!(market_after.bonds.oracle.unwrap().is_settled); + assert!(market_after.bonds.dispute.unwrap().is_settled); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn it_resolves_a_disputed_court_market() { + let test = |base_asset: AssetOf| { + let juror_0 = 1000; + let juror_1 = 1001; + let juror_2 = 1002; + let juror_3 = 1003; + let juror_4 = 1004; + let juror_5 = 1005; + + for j in &[juror_0, juror_1, juror_2, juror_3, juror_4, juror_5] { + let amount = ::MinJurorStake::get() + *j; + assert_ok!(AssetManager::deposit(Asset::Ztg, j, amount + SENTINEL_AMOUNT)); + assert_ok!(Court::join_court(RuntimeOrigin::signed(*j), amount)); + } + + // just to have enough jurors for the dispute + for j in 1006..(1006 + Court::necessary_draws_weight(0usize) as u32) { + let juror = j as u128; + let amount = ::MinJurorStake::get() + juror; + assert_ok!(AssetManager::deposit(Asset::Ztg, &juror, amount + SENTINEL_AMOUNT)); + assert_ok!(Court::join_court(RuntimeOrigin::signed(juror), amount)); + } + + let end = 2; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr, + )); + + let market_id = 0; + let market = MarketCommons::market(&0).unwrap(); + + let report_at = end + market.deadlines.grace_period + 1; + run_to_block(report_at); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + market_id, + OutcomeReport::Categorical(0) + )); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), market_id,)); + + let court = zrml_court::Courts::::get(market_id).unwrap(); + let vote_start = court.round_ends.pre_vote + 1; + + run_to_block(vote_start); + + // overwrite draws to disregard randomness + zrml_court::SelectedDraws::::remove(market_id); + let mut draws = zrml_court::SelectedDraws::::get(market_id); + for juror in &[juror_0, juror_1, juror_2, juror_3, juror_4, juror_5] { + let draw = Draw { + court_participant: *juror, + weight: 1, + vote: Vote::Drawn, + slashable: ::MinJurorStake::get(), + }; + let index = draws + .binary_search_by_key(juror, |draw| draw.court_participant) + .unwrap_or_else(|j| j); + draws.try_insert(index, draw).unwrap(); + } + let old_draws = draws.clone(); + zrml_court::SelectedDraws::::insert(market_id, draws); + + let salt = ::Hash::default(); + + // outcome_0 is the plurality decision => right outcome + let outcome_0 = OutcomeReport::Categorical(0); + let vote_item_0 = VoteItem::Outcome(outcome_0.clone()); + // outcome_1 is the wrong outcome + let outcome_1 = OutcomeReport::Categorical(1); + let vote_item_1 = VoteItem::Outcome(outcome_1); + + let commitment_0 = BlakeTwo256::hash_of(&(juror_0, vote_item_0.clone(), salt)); + assert_ok!(Court::vote(RuntimeOrigin::signed(juror_0), market_id, commitment_0)); + + // juror_1 votes for non-plurality outcome => slashed later + let commitment_1 = BlakeTwo256::hash_of(&(juror_1, vote_item_1.clone(), salt)); + assert_ok!(Court::vote(RuntimeOrigin::signed(juror_1), market_id, commitment_1)); + + let commitment_2 = BlakeTwo256::hash_of(&(juror_2, vote_item_0.clone(), salt)); + assert_ok!(Court::vote(RuntimeOrigin::signed(juror_2), market_id, commitment_2)); + + let commitment_3 = BlakeTwo256::hash_of(&(juror_3, vote_item_0.clone(), salt)); + assert_ok!(Court::vote(RuntimeOrigin::signed(juror_3), market_id, commitment_3)); + + // juror_4 fails to vote in time + + let commitment_5 = BlakeTwo256::hash_of(&(juror_5, vote_item_0.clone(), salt)); + assert_ok!(Court::vote(RuntimeOrigin::signed(juror_5), market_id, commitment_5)); + + // juror_3 is denounced by juror_0 => slashed later + assert_ok!(Court::denounce_vote( + RuntimeOrigin::signed(juror_0), + market_id, + juror_3, + vote_item_0.clone(), + salt + )); + + let aggregation_start = court.round_ends.vote + 1; + run_to_block(aggregation_start); + + assert_ok!(Court::reveal_vote( + RuntimeOrigin::signed(juror_0), + market_id, + vote_item_0.clone(), + salt + )); + assert_ok!(Court::reveal_vote( + RuntimeOrigin::signed(juror_1), + market_id, + vote_item_1, + salt + )); + + let wrong_salt = BlakeTwo256::hash_of(&69); + assert_noop!( + Court::reveal_vote( + RuntimeOrigin::signed(juror_2), + market_id, + vote_item_0.clone(), + wrong_salt + ), + zrml_court::Error::::CommitmentHashMismatch + ); + assert_ok!(Court::reveal_vote( + RuntimeOrigin::signed(juror_2), + market_id, + vote_item_0.clone(), + salt + )); + + assert_noop!( + Court::reveal_vote( + RuntimeOrigin::signed(juror_3), + market_id, + vote_item_0.clone(), + salt + ), + zrml_court::Error::::VoteAlreadyDenounced + ); + + assert_noop!( + Court::reveal_vote( + RuntimeOrigin::signed(juror_4), + market_id, + vote_item_0.clone(), + salt + ), + zrml_court::Error::::JurorDidNotVote + ); + + // juror_5 fails to reveal in time + + let resolve_at = court.round_ends.appeal; + let market_ids = MarketIdsPerDisputeBlock::::get(resolve_at); + assert_eq!(market_ids.len(), 1); + + run_blocks(resolve_at); + + let market_after = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after.status, MarketStatus::Resolved); + assert_eq!(market_after.resolved_outcome, Some(outcome_0)); + let court_after = zrml_court::Courts::::get(market_id).unwrap(); + assert_eq!(court_after.status, CourtStatus::Closed { winner: vote_item_0 }); + + let free_juror_0_before = Balances::free_balance(juror_0); + let free_juror_1_before = Balances::free_balance(juror_1); + let free_juror_2_before = Balances::free_balance(juror_2); + let free_juror_3_before = Balances::free_balance(juror_3); + let free_juror_4_before = Balances::free_balance(juror_4); + let free_juror_5_before = Balances::free_balance(juror_5); + + assert_ok!(Court::reassign_court_stakes(RuntimeOrigin::signed(juror_0), market_id)); + + let free_juror_0_after = Balances::free_balance(juror_0); + let slashable_juror_0 = + old_draws.iter().find(|draw| draw.court_participant == juror_0).unwrap().slashable; + let free_juror_1_after = Balances::free_balance(juror_1); + let slashable_juror_1 = + old_draws.iter().find(|draw| draw.court_participant == juror_1).unwrap().slashable; + let free_juror_2_after = Balances::free_balance(juror_2); + let slashable_juror_2 = + old_draws.iter().find(|draw| draw.court_participant == juror_2).unwrap().slashable; + let free_juror_3_after = Balances::free_balance(juror_3); + let slashable_juror_3 = + old_draws.iter().find(|draw| draw.court_participant == juror_3).unwrap().slashable; + let free_juror_4_after = Balances::free_balance(juror_4); + let slashable_juror_4 = + old_draws.iter().find(|draw| draw.court_participant == juror_4).unwrap().slashable; + let free_juror_5_after = Balances::free_balance(juror_5); + let slashable_juror_5 = + old_draws.iter().find(|draw| draw.court_participant == juror_5).unwrap().slashable; + + let mut total_slashed = 0; + // juror_1 voted for the wrong outcome => slashed + assert_eq!(free_juror_1_before - free_juror_1_after, slashable_juror_1); + total_slashed += slashable_juror_1; + // juror_3 was denounced by juror_0 => slashed + assert_eq!(free_juror_3_before - free_juror_3_after, slashable_juror_3); + total_slashed += slashable_juror_3; + // juror_4 failed to vote => slashed + assert_eq!(free_juror_4_before - free_juror_4_after, slashable_juror_4); + total_slashed += slashable_juror_4; + // juror_5 failed to reveal => slashed + assert_eq!(free_juror_5_before - free_juror_5_after, slashable_juror_5); + total_slashed += slashable_juror_5; + // juror_0 and juror_2 voted for the right outcome => rewarded + let total_winner_stake = slashable_juror_0 + slashable_juror_2; + let juror_0_share = Perquintill::from_rational(slashable_juror_0, total_winner_stake); + assert_eq!(free_juror_0_after, free_juror_0_before + juror_0_share * total_slashed); + let juror_2_share = Perquintill::from_rational(slashable_juror_2, total_winner_stake); + assert_eq!(free_juror_2_after, free_juror_2_before + juror_2_share * total_slashed); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_market_on_oracle_report() + { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, OracleBond::get()); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + let report_at = grace_period + 1; + run_to_block(report_at); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + run_blocks(market.deadlines.dispute_duration); + check_reserve(&ALICE, 0); + // Check that nothing got slashed + assert_eq!(Balances::free_balance(ALICE), alice_balance_before + OracleBond::get()); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_market_on_outsider_report() + { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, OracleBond::get()); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + let report_at = grace_period + market.deadlines.oracle_duration + 1; + run_to_block(report_at); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(1) + )); + run_blocks(market.deadlines.dispute_duration); + // Check that oracle bond got slashed + check_reserve(&ALICE, 0); + assert_eq!(Balances::free_balance(ALICE), alice_balance_before); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_with_correct_disputed_outcome_with_oracle_report() + { + // Oracle reports in time but incorrect report, so OracleBond gets slashed on resolution + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(0) + )); + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(1) + )); + run_blocks(market.deadlines.dispute_duration); + check_reserve(&ALICE, 0); + // ValidityBond bond is returned but OracleBond is slashed + assert_eq!(Balances::free_balance(ALICE), alice_balance_before + ValidityBond::get()); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_market_with_correct_disputed_outcome_with_oracle_report() + { + // Oracle reports in time but incorrect report, so OracleBond gets slashed on resolution + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, OracleBond::get()); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(0) + )); + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(1) + )); + run_blocks(market.deadlines.dispute_duration); + check_reserve(&ALICE, 0); + // ValidityBond bond is returned but OracleBond is slashed + assert_eq!(Balances::free_balance(ALICE), alice_balance_before); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_with_wrong_disputed_outcome_with_oracle_report() + { + // Oracle reports in time and correct report, so OracleBond does not get slashed on resolution + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(0) + )); + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); + // EVE disputes with wrong outcome + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(EVE), + 0, + OutcomeReport::Categorical(1) + )); + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(0) + )); + run_blocks(market.deadlines.dispute_duration); + check_reserve(&ALICE, 0); + // ValidityBond bond is returned but OracleBond is not slashed + assert_eq!( + Balances::free_balance(ALICE), + alice_balance_before + ValidityBond::get() + OracleBond::get() + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_market_with_wrong_disputed_outcome_with_oracle_report() + { + // Oracle reports in time and correct report, so OracleBond does not get slashed on resolution + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, OracleBond::get()); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(0) + )); + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); + // EVE disputes with wrong outcome + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(EVE), + 0, + OutcomeReport::Categorical(1) + )); + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(0) + )); + run_blocks(market.deadlines.dispute_duration); + check_reserve(&ALICE, 0); + // ValidityBond bond is returned but OracleBond is not slashed + assert_eq!(Balances::free_balance(ALICE), alice_balance_before + OracleBond::get()); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_with_disputed_outcome_with_outsider_report() + { + // Oracle does not report in time, so OracleBond gets slashed on resolution + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); + + let outsider = CHARLIE; + + let market = MarketCommons::market(&0).unwrap(); + let after_oracle_duration = + end + market.deadlines.grace_period + market.deadlines.oracle_duration + 1; + run_to_block(after_oracle_duration); + // CHARLIE is not an Oracle + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(outsider), + 0, + OutcomeReport::Categorical(0) + )); + let outsider_balance_before = Balances::free_balance(outsider); + check_reserve(&outsider, ::OutsiderBond::get()); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); + // EVE disputes with wrong outcome + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(EVE), + 0, + OutcomeReport::Categorical(1) + )); + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(FRED), + 0, + OutcomeReport::Categorical(0) + )); + run_blocks(market.deadlines.dispute_duration); + check_reserve(&ALICE, 0); + // ValidityBond bond is returned but OracleBond is slashed + assert_eq!(Balances::free_balance(ALICE), alice_balance_before + ValidityBond::get()); + + check_reserve(&outsider, 0); + assert_eq!( + Balances::free_balance(outsider), + outsider_balance_before + OracleBond::get() + ::OutsiderBond::get() + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_market_with_disputed_outcome_with_outsider_report() + { + // Oracle does not report in time, so OracleBond gets slashed on resolution + // NOTE: Bonds are always in ZTG + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + + let outsider = CHARLIE; + + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, OracleBond::get()); + let market = MarketCommons::market(&0).unwrap(); + let after_oracle_duration = + end + market.deadlines.grace_period + market.deadlines.oracle_duration + 1; + run_to_block(after_oracle_duration); + // CHARLIE is not an Oracle + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(outsider), + 0, + OutcomeReport::Categorical(0) + )); + let outsider_balance_before = Balances::free_balance(outsider); + check_reserve(&outsider, ::OutsiderBond::get()); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); + // EVE disputes with wrong outcome + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(EVE), + 0, + OutcomeReport::Categorical(1) + )); + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(FRED), + 0, + OutcomeReport::Categorical(0) + )); + run_blocks(market.deadlines.dispute_duration); + check_reserve(&ALICE, 0); + // ValidityBond bond is returned but OracleBond is slashed + assert_eq!(Balances::free_balance(ALICE), alice_balance_before); + + check_reserve(&outsider, 0); + assert_eq!( + Balances::free_balance(outsider), + outsider_balance_before + OracleBond::get() + ::OutsiderBond::get() + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn trusted_market_complete_lifecycle() { + ExtBuilder::default().build().execute_with(|| { + let end = 3; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + Deadlines { + grace_period: 0, + oracle_duration: ::MinOracleDuration::get(), + dispute_duration: Zero::zero(), + }, + gen_metadata(0x99), + MarketCreation::Permissionless, + MarketType::Categorical(3), + None, + ScoringRule::Lmsr, + )); + let market_id = 0; + assert_ok!(PredictionMarkets::buy_complete_set( + RuntimeOrigin::signed(FRED), + market_id, + BASE + )); + run_to_block(end); + let outcome = OutcomeReport::Categorical(1); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + market_id, + outcome.clone() + )); + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Resolved); + assert_eq!(market.report, Some(Report { at: end, by: BOB, outcome: outcome.clone() })); + assert_eq!(market.resolved_outcome, Some(outcome)); + assert_eq!(market.dispute_mechanism, None); + assert!(market.bonds.oracle.unwrap().is_settled); + assert_eq!(market.bonds.outsider, None); + assert_eq!(market.bonds.dispute, None); + assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(FRED), market_id)); + // Ensure that we don't accidentally leave any artifacts. + assert!(MarketIdsPerDisputeBlock::::iter().next().is_none()); + assert!(MarketIdsPerReportBlock::::iter().next().is_none()); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_on_oracle_report() + { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(0) + )); + run_to_block(grace_period + market.deadlines.dispute_duration + 1); + check_reserve(&ALICE, 0); + assert_eq!( + Balances::free_balance(ALICE), + alice_balance_before + ValidityBond::get() + OracleBond::get() + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_on_outsider_report() + { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..100), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); + + let charlie_balance_before = Balances::free_balance(CHARLIE); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + let report_at = grace_period + market.deadlines.oracle_duration + 1; + run_to_block(report_at); + + assert!(market.bonds.outsider.is_none()); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(1) + )); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!( + market.bonds.outsider, + Some(Bond::new(CHARLIE, ::OutsiderBond::get())) + ); + check_reserve(&CHARLIE, ::OutsiderBond::get()); + assert_eq!( + Balances::free_balance(CHARLIE), + charlie_balance_before - ::OutsiderBond::get() + ); + let charlie_balance_before = Balances::free_balance(CHARLIE); + + run_blocks(market.deadlines.dispute_duration); + check_reserve(&ALICE, 0); + // Check that validity bond didn't get slashed, but oracle bond did + assert_eq!(Balances::free_balance(ALICE), alice_balance_before + ValidityBond::get()); + + check_reserve(&CHARLIE, 0); + // Check that the outsider gets the OracleBond together with the OutsiderBond + assert_eq!( + Balances::free_balance(CHARLIE), + charlie_balance_before + OracleBond::get() + ::OutsiderBond::get() + ); + let market = MarketCommons::market(&0).unwrap(); + assert!(market.bonds.outsider.unwrap().is_settled); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} diff --git a/zrml/prediction-markets/src/tests/redeem_shares.rs b/zrml/prediction-markets/src/tests/redeem_shares.rs index 309247f78..3bb4cd650 100644 --- a/zrml/prediction-markets/src/tests/redeem_shares.rs +++ b/zrml/prediction-markets/src/tests/redeem_shares.rs @@ -19,7 +19,7 @@ use super::*; use test_case::test_case; -use zeitgeist_primitives::types::OutcomeReport; +use zeitgeist_primitives::types::{OutcomeReport, ScalarPosition}; #[test] fn it_allows_to_redeem_shares() { @@ -91,3 +91,86 @@ fn redeem_shares_fails_if_invalid_resolution_mechanism(scoring_rule: ScoringRule test(Asset::ForeignAsset(100)); }); } + +#[test] +fn scalar_market_correctly_resolves_on_out_of_range_outcomes_below_threshold() { + let test = |base_asset: AssetOf| { + scalar_market_correctly_resolves_common(base_asset, 50); + assert_eq!(AssetManager::free_balance(base_asset, &CHARLIE), 900 * BASE); + assert_eq!(AssetManager::free_balance(base_asset, &EVE), 1100 * BASE); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn scalar_market_correctly_resolves_on_out_of_range_outcomes_above_threshold() { + let test = |base_asset: AssetOf| { + scalar_market_correctly_resolves_common(base_asset, 250); + assert_eq!(AssetManager::free_balance(base_asset, &CHARLIE), 1000 * BASE); + assert_eq!(AssetManager::free_balance(base_asset, &EVE), 1000 * BASE); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +// Common code of `scalar_market_correctly_resolves_*` +fn scalar_market_correctly_resolves_common(base_asset: AssetOf, reported_value: u128) { + let end = 100; + simple_create_scalar_market( + base_asset, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, 100 * BASE)); + assert_ok!(Tokens::transfer( + RuntimeOrigin::signed(CHARLIE), + EVE, + Asset::ScalarOutcome(0, ScalarPosition::Short), + 100 * BASE + )); + // (Eve now has 100 SHORT, Charlie has 100 LONG) + + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Scalar(reported_value) + )); + let market_after_report = MarketCommons::market(&0).unwrap(); + assert!(market_after_report.report.is_some()); + let report = market_after_report.report.unwrap(); + assert_eq!(report.at, grace_period + 1); + assert_eq!(report.by, BOB); + assert_eq!(report.outcome, OutcomeReport::Scalar(reported_value)); + + run_blocks(market.deadlines.dispute_duration); + let market_after_resolve = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after_resolve.status, MarketStatus::Resolved); + + // Check balances before redeeming (just to make sure that our tests are based on correct + // assumptions)! + assert_eq!(AssetManager::free_balance(base_asset, &CHARLIE), 900 * BASE); + assert_eq!(AssetManager::free_balance(base_asset, &EVE), 1000 * BASE); + + assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); + assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(EVE), 0)); + let assets = PredictionMarkets::outcome_assets(0, &MarketCommons::market(&0).unwrap()); + for asset in assets.iter() { + assert_eq!(AssetManager::free_balance(*asset, &CHARLIE), 0); + assert_eq!(AssetManager::free_balance(*asset, &EVE), 0); + } +} From c0e8a3453fcfe2a7403308d0121f3580c7404a11 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 13 Jan 2024 23:38:46 +0100 Subject: [PATCH 27/32] Move integration tests into new test module --- zrml/prediction-markets/src/lib.rs | 1 - .../{old_tests.rs => tests/integration.rs} | 133 +++--------------- zrml/prediction-markets/src/tests/mod.rs | 55 +++++++- 3 files changed, 71 insertions(+), 118 deletions(-) rename zrml/prediction-markets/src/{old_tests.rs => tests/integration.rs} (81%) diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index e038e9b14..1bc745796 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -26,7 +26,6 @@ extern crate alloc; mod benchmarks; pub mod migrations; pub mod mock; -mod old_tests; pub mod orml_asset_registry; mod tests; pub mod weights; diff --git a/zrml/prediction-markets/src/old_tests.rs b/zrml/prediction-markets/src/tests/integration.rs similarity index 81% rename from zrml/prediction-markets/src/old_tests.rs rename to zrml/prediction-markets/src/tests/integration.rs index 6c0c8eedd..375e759e5 100644 --- a/zrml/prediction-markets/src/old_tests.rs +++ b/zrml/prediction-markets/src/tests/integration.rs @@ -16,128 +16,30 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -#![cfg(all(feature = "mock", test))] -#![allow(clippy::reversed_empty_ranges)] +use super::*; -extern crate alloc; - -use crate::{mock::*, Config, Error, Event, MarketIdsPerDisputeBlock}; use alloc::collections::BTreeMap; -use frame_support::{assert_noop, assert_ok, traits::NamedReservableCurrency}; -use sp_runtime::traits::BlakeTwo256; -use zrml_court::{types::*, Error as CError}; +use zeitgeist_primitives::types::OutcomeReport; -use orml_traits::{MultiCurrency, MultiReservableCurrency}; -use sp_arithmetic::Perbill; -use sp_runtime::traits::{Hash, Zero}; +use crate::MarketIdsPerDisputeBlock; +use orml_traits::MultiReservableCurrency; use zeitgeist_primitives::{ - constants::mock::{ - MaxAppeals, MaxSelectedDraws, MinJurorStake, OutsiderBond, BASE, CENT, MILLISECS_PER_BLOCK, - }, - types::{ - AccountIdTest, Asset, Balance, Deadlines, MarketCreation, MarketDisputeMechanism, MarketId, - MarketPeriod, MarketStatus, MarketType, MultiHash, OutcomeReport, ScalarPosition, - ScoringRule, - }, + constants::{mock::OutsiderBond, MILLISECS_PER_BLOCK}, + types::ScalarPosition, }; use zrml_global_disputes::{ types::{OutcomeInfo, Possession}, GlobalDisputesPalletApi, Outcomes, PossessionOf, }; -use zrml_market_commons::MarketCommonsPalletApi; - -const SENTINEL_AMOUNT: u128 = BASE; - -fn get_deadlines() -> Deadlines<::BlockNumber> { - Deadlines { - grace_period: 1_u32.into(), - oracle_duration: ::MinOracleDuration::get(), - dispute_duration: ::MinDisputeDuration::get(), - } -} - -fn gen_metadata(byte: u8) -> MultiHash { - let mut metadata = [byte; 50]; - metadata[0] = 0x15; - metadata[1] = 0x30; - MultiHash::Sha3_384(metadata) -} - -fn reserve_sentinel_amounts() { - // Reserve a sentinel amount to check that we don't unreserve too much. - assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &ALICE, SENTINEL_AMOUNT)); - assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &BOB, SENTINEL_AMOUNT)); - assert_ok!(Balances::reserve_named( - &PredictionMarkets::reserve_id(), - &CHARLIE, - SENTINEL_AMOUNT - )); - assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &DAVE, SENTINEL_AMOUNT)); - assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &EVE, SENTINEL_AMOUNT)); - assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &FRED, SENTINEL_AMOUNT)); - assert_eq!(Balances::reserved_balance(ALICE), SENTINEL_AMOUNT); - assert_eq!(Balances::reserved_balance(BOB), SENTINEL_AMOUNT); - assert_eq!(Balances::reserved_balance(CHARLIE), SENTINEL_AMOUNT); - assert_eq!(Balances::reserved_balance(DAVE), SENTINEL_AMOUNT); - assert_eq!(Balances::reserved_balance(EVE), SENTINEL_AMOUNT); - assert_eq!(Balances::reserved_balance(FRED), SENTINEL_AMOUNT); -} - -fn check_reserve(account: &AccountIdTest, expected: Balance) { - assert_eq!(Balances::reserved_balance(account), SENTINEL_AMOUNT + expected); -} - -fn simulate_appeal_cycle(market_id: MarketId) { - let court = zrml_court::Courts::::get(market_id).unwrap(); - let vote_start = court.round_ends.pre_vote + 1; - - run_to_block(vote_start); - - let salt = ::Hash::default(); - - let wrong_outcome = OutcomeReport::Categorical(1); - let wrong_vote_item = VoteItem::Outcome(wrong_outcome); - - let draws = zrml_court::SelectedDraws::::get(market_id); - for draw in &draws { - let commitment = - BlakeTwo256::hash_of(&(draw.court_participant, wrong_vote_item.clone(), salt)); - assert_ok!(Court::vote( - RuntimeOrigin::signed(draw.court_participant), - market_id, - commitment - )); - } - - let aggregation_start = court.round_ends.vote + 1; - run_to_block(aggregation_start); - - for draw in draws { - assert_ok!(Court::reveal_vote( - RuntimeOrigin::signed(draw.court_participant), - market_id, - wrong_vote_item.clone(), - salt, - )); - } - - let resolve_at = court.round_ends.appeal; - let market_ids = MarketIdsPerDisputeBlock::::get(resolve_at); - assert_eq!(market_ids.len(), 1); - - run_to_block(resolve_at - 1); - - let market_after = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after.status, MarketStatus::Disputed); -} #[test] fn it_appeals_a_court_market_to_global_dispute() { - let test = |base_asset: Asset| { + let test = |base_asset: AssetOf| { let mut free_before = BTreeMap::new(); - let jurors = 1000..(1000 + MaxSelectedDraws::get() as u128); + let jurors = + 1000..(1000 + ::MaxSelectedDraws::get() as u128); for j in jurors { - let amount = MinJurorStake::get() + j; + let amount = ::MinJurorStake::get() + j; assert_ok!(AssetManager::deposit(Asset::Ztg, &j, amount + SENTINEL_AMOUNT)); assert_ok!(Court::join_court(RuntimeOrigin::signed(j), amount)); free_before.insert(j, Balances::free_balance(j)); @@ -172,14 +74,17 @@ fn it_appeals_a_court_market_to_global_dispute() { assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), market_id,)); - for _ in 0..(MaxAppeals::get() - 1) { + for _ in 0..(::MaxAppeals::get() - 1) { simulate_appeal_cycle(market_id); assert_ok!(Court::appeal(RuntimeOrigin::signed(BOB), market_id)); } let court = zrml_court::Courts::::get(market_id).unwrap(); let appeals = court.appeals; - assert_eq!(appeals.len(), (MaxAppeals::get() - 1) as usize); + assert_eq!( + appeals.len(), + (::MaxAppeals::get() - 1) as usize + ); assert_noop!( PredictionMarkets::start_global_dispute(RuntimeOrigin::signed(BOB), market_id), @@ -191,7 +96,7 @@ fn it_appeals_a_court_market_to_global_dispute() { assert_noop!( Court::appeal(RuntimeOrigin::signed(BOB), market_id), - CError::::MaxAppealsReached + zrml_court::Error::::MaxAppealsReached ); assert!(!GlobalDisputes::does_exist(&market_id)); @@ -275,7 +180,7 @@ fn the_entire_market_lifecycle_works_with_timestamps() { #[test] fn full_scalar_market_lifecycle() { - let test = |base_asset: Asset| { + let test = |base_asset: AssetOf| { assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), base_asset, @@ -413,7 +318,7 @@ fn full_scalar_market_lifecycle() { #[test] fn authorized_correctly_resolves_disputed_market() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { + let test = |base_asset: AssetOf| { let end = 2; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), @@ -559,7 +464,7 @@ fn authorized_correctly_resolves_disputed_market() { #[test] fn outsider_reports_wrong_outcome() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { + let test = |base_asset: AssetOf| { reserve_sentinel_amounts(); let end = 100; diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 8ad8ea048..42d8c1f1e 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -28,6 +28,7 @@ mod create_market_and_deploy_pool; mod dispute; mod dispute_early_close; mod edit_market; +mod integration; mod manually_close_market; mod on_initialize; mod on_market_close; @@ -40,18 +41,22 @@ mod schedule_early_close; mod sell_complete_set; mod start_global_dispute; -use crate::{mock::*, AccountIdOf, AssetOf, BalanceOf, Config, Error, Event}; +use crate::{ + mock::*, AccountIdOf, AssetOf, BalanceOf, Config, Error, Event, MarketIdsPerDisputeBlock, +}; use core::ops::Range; use frame_support::{assert_noop, assert_ok, traits::NamedReservableCurrency}; use orml_traits::MultiCurrency; use sp_arithmetic::Perbill; +use sp_runtime::traits::{BlakeTwo256, Zero, Hash}; use zeitgeist_primitives::{ constants::mock::{BASE, CENT}, types::{ - Asset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, - MarketType, MultiHash, ScoringRule, + Asset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketId, MarketPeriod, + MarketStatus, MarketType, MultiHash, OutcomeReport, ScoringRule, }, }; +use zrml_court::types::VoteItem; use zrml_market_commons::MarketCommonsPalletApi; const SENTINEL_AMOUNT: u128 = BASE; @@ -136,3 +141,47 @@ fn reserve_sentinel_amounts() { assert_eq!(Balances::reserved_balance(EVE), SENTINEL_AMOUNT); assert_eq!(Balances::reserved_balance(FRED), SENTINEL_AMOUNT); } + +fn simulate_appeal_cycle(market_id: MarketId) { + let court = zrml_court::Courts::::get(market_id).unwrap(); + let vote_start = court.round_ends.pre_vote + 1; + + run_to_block(vote_start); + + let salt = ::Hash::default(); + + let wrong_outcome = OutcomeReport::Categorical(1); + let wrong_vote_item = VoteItem::Outcome(wrong_outcome); + + let draws = zrml_court::SelectedDraws::::get(market_id); + for draw in &draws { + let commitment = + BlakeTwo256::hash_of(&(draw.court_participant, wrong_vote_item.clone(), salt)); + assert_ok!(Court::vote( + RuntimeOrigin::signed(draw.court_participant), + market_id, + commitment + )); + } + + let aggregation_start = court.round_ends.vote + 1; + run_to_block(aggregation_start); + + for draw in draws { + assert_ok!(Court::reveal_vote( + RuntimeOrigin::signed(draw.court_participant), + market_id, + wrong_vote_item.clone(), + salt, + )); + } + + let resolve_at = court.round_ends.appeal; + let market_ids = MarketIdsPerDisputeBlock::::get(resolve_at); + assert_eq!(market_ids.len(), 1); + + run_to_block(resolve_at - 1); + + let market_after = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after.status, MarketStatus::Disputed); +} From fe30d802b61f41c9a61636e9688f75eb8734ecb8 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 13 Jan 2024 23:53:20 +0100 Subject: [PATCH 28/32] Automatically set block to `1` at the start of test --- zrml/prediction-markets/src/mock.rs | 5 ++++- .../src/tests/admin_move_market_to_closed.rs | 2 +- .../src/tests/approve_market.rs | 6 ++---- .../src/tests/buy_complete_set.rs | 5 ++--- .../src/tests/create_market.rs | 2 -- zrml/prediction-markets/src/tests/dispute.rs | 2 +- .../src/tests/dispute_early_close.rs | 3 --- .../src/tests/edit_market.rs | 21 +++++++------------ .../src/tests/reject_early_close.rs | 3 --- .../src/tests/reject_market.rs | 14 +++++-------- .../src/tests/schedule_early_close.rs | 3 --- .../src/tests/sell_complete_set.rs | 7 +++---- 12 files changed, 25 insertions(+), 48 deletions(-) diff --git a/zrml/prediction-markets/src/mock.rs b/zrml/prediction-markets/src/mock.rs index 40cad9a65..4eec9a8f7 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -482,7 +482,10 @@ impl ExtBuilder { } .assimilate_storage(&mut t) .unwrap(); - t.into() + + let mut test_ext: sp_io::TestExternalities = t.into(); + test_ext.execute_with(|| System::set_block_number(1)); + test_ext } } diff --git a/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs b/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs index cff423974..9133a6837 100644 --- a/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs +++ b/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs @@ -113,7 +113,7 @@ fn admin_move_market_to_closed_fails_if_market_is_not_active(market_status: Mark simple_create_categorical_market( Asset::Ztg, MarketCreation::Permissionless, - 0..1, + 0..2, ScoringRule::Lmsr, ); let market_id = 0; diff --git a/zrml/prediction-markets/src/tests/approve_market.rs b/zrml/prediction-markets/src/tests/approve_market.rs index 8235bb107..e2bcb14f3 100644 --- a/zrml/prediction-markets/src/tests/approve_market.rs +++ b/zrml/prediction-markets/src/tests/approve_market.rs @@ -24,15 +24,13 @@ use sp_runtime::DispatchError; #[test] fn it_allows_advisory_origin_to_approve_markets() { ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. simple_create_categorical_market( Asset::Ztg, MarketCreation::Advised, - 0..1, + 0..2, ScoringRule::Lmsr, ); - // make sure it's in status proposed let market = MarketCommons::market(&0); assert_eq!(market.unwrap().status, MarketStatus::Proposed); @@ -57,7 +55,7 @@ fn market_with_edit_request_cannot_be_approved() { simple_create_categorical_market( Asset::Ztg, MarketCreation::Advised, - 0..1, + 0..2, ScoringRule::Lmsr, ); diff --git a/zrml/prediction-markets/src/tests/buy_complete_set.rs b/zrml/prediction-markets/src/tests/buy_complete_set.rs index 36b302230..6a1035d3e 100644 --- a/zrml/prediction-markets/src/tests/buy_complete_set.rs +++ b/zrml/prediction-markets/src/tests/buy_complete_set.rs @@ -22,7 +22,6 @@ use test_case::test_case; #[test] fn buy_complete_set_works() { let test = |base_asset: AssetOf| { - frame_system::Pallet::::set_block_number(1); simple_create_categorical_market( base_asset, MarketCreation::Permissionless, @@ -70,7 +69,7 @@ fn buy_complete_fails_on_zero_amount() { simple_create_categorical_market( Asset::Ztg, MarketCreation::Permissionless, - 0..1, + 0..2, ScoringRule::Lmsr, ); assert_noop!( @@ -86,7 +85,7 @@ fn buy_complete_set_fails_on_insufficient_balance() { simple_create_categorical_market( base_asset, MarketCreation::Permissionless, - 0..1, + 0..2, ScoringRule::Lmsr, ); assert_noop!( diff --git a/zrml/prediction-markets/src/tests/create_market.rs b/zrml/prediction-markets/src/tests/create_market.rs index fc65f04a0..e52ed5a4e 100644 --- a/zrml/prediction-markets/src/tests/create_market.rs +++ b/zrml/prediction-markets/src/tests/create_market.rs @@ -606,7 +606,6 @@ fn create_market_fails_on_trusted_market_with_non_zero_dispute_period() { #[test] fn create_categorical_market_deposits_the_correct_event() { ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); simple_create_categorical_market( Asset::Ztg, MarketCreation::Permissionless, @@ -623,7 +622,6 @@ fn create_categorical_market_deposits_the_correct_event() { #[test] fn create_scalar_market_deposits_the_correct_event() { ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); simple_create_scalar_market( Asset::Ztg, MarketCreation::Permissionless, diff --git a/zrml/prediction-markets/src/tests/dispute.rs b/zrml/prediction-markets/src/tests/dispute.rs index 65d6e4077..c1f0f81d0 100644 --- a/zrml/prediction-markets/src/tests/dispute.rs +++ b/zrml/prediction-markets/src/tests/dispute.rs @@ -290,7 +290,7 @@ fn dispute_fails_unless_reported_or_disputed_market(status: MarketStatus) { simple_create_categorical_market( Asset::Ztg, MarketCreation::Permissionless, - 0..1, + 0..2, ScoringRule::Lmsr, ); diff --git a/zrml/prediction-markets/src/tests/dispute_early_close.rs b/zrml/prediction-markets/src/tests/dispute_early_close.rs index e72e34894..75b008257 100644 --- a/zrml/prediction-markets/src/tests/dispute_early_close.rs +++ b/zrml/prediction-markets/src/tests/dispute_early_close.rs @@ -32,9 +32,6 @@ fn dispute_early_close_emits_event() { ScoringRule::Lmsr, ); - // just to ensure events are emitted - run_blocks(2); - let market_id = 0; assert_ok!(PredictionMarkets::schedule_early_close( diff --git a/zrml/prediction-markets/src/tests/edit_market.rs b/zrml/prediction-markets/src/tests/edit_market.rs index be5a81733..2ac75d386 100644 --- a/zrml/prediction-markets/src/tests/edit_market.rs +++ b/zrml/prediction-markets/src/tests/edit_market.rs @@ -24,8 +24,6 @@ use sp_runtime::DispatchError; #[test] fn it_allows_request_edit_origin_to_request_edits_for_markets() { ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - // Creates an advised market. simple_create_categorical_market( Asset::Ztg, MarketCreation::Advised, @@ -94,11 +92,10 @@ fn edit_request_fails_if_edit_reason_is_too_long() { simple_create_categorical_market( Asset::Ztg, MarketCreation::Advised, - 0..1, + 0..2, ScoringRule::Lmsr, ); - // make sure it's in status proposed let market = MarketCommons::market(&0); assert_eq!(market.unwrap().status, MarketStatus::Proposed); @@ -114,11 +111,10 @@ fn edit_request_fails_if_edit_reason_is_too_long() { #[test] fn only_creator_can_edit_market() { ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. simple_create_categorical_market( Asset::Ztg, MarketCreation::Advised, - 0..1, + 0..2, ScoringRule::Lmsr, ); @@ -140,7 +136,7 @@ fn only_creator_can_edit_market() { Asset::Ztg, 0, CHARLIE, - MarketPeriod::Block(0..1), + MarketPeriod::Block(0..2), get_deadlines(), gen_metadata(2), MarketType::Categorical(::MinCategories::get()), @@ -155,8 +151,6 @@ fn only_creator_can_edit_market() { #[test] fn edit_cycle_for_proposed_markets() { ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - run_to_block(1); simple_create_categorical_market( Asset::Ztg, MarketCreation::Advised, @@ -164,7 +158,6 @@ fn edit_cycle_for_proposed_markets() { ScoringRule::Lmsr, ); - // make sure it's in status proposed let market = MarketCommons::market(&0); assert_eq!(market.unwrap().status, MarketStatus::Proposed); @@ -206,7 +199,7 @@ fn edit_market_with_foreign_asset() { simple_create_categorical_market( Asset::Ztg, MarketCreation::Advised, - 0..1, + 0..2, ScoringRule::Lmsr, ); @@ -229,7 +222,7 @@ fn edit_market_with_foreign_asset() { Asset::ForeignAsset(50), 0, CHARLIE, - MarketPeriod::Block(0..1), + MarketPeriod::Block(0..2), get_deadlines(), gen_metadata(2), MarketType::Categorical(::MinCategories::get()), @@ -245,7 +238,7 @@ fn edit_market_with_foreign_asset() { Asset::ForeignAsset(420), 0, CHARLIE, - MarketPeriod::Block(0..1), + MarketPeriod::Block(0..2), get_deadlines(), gen_metadata(2), MarketType::Categorical(::MinCategories::get()), @@ -260,7 +253,7 @@ fn edit_market_with_foreign_asset() { Asset::ForeignAsset(100), 0, CHARLIE, - MarketPeriod::Block(0..1), + MarketPeriod::Block(0..2), get_deadlines(), gen_metadata(2), MarketType::Categorical(::MinCategories::get()), diff --git a/zrml/prediction-markets/src/tests/reject_early_close.rs b/zrml/prediction-markets/src/tests/reject_early_close.rs index 3bfc46bb3..bdf235735 100644 --- a/zrml/prediction-markets/src/tests/reject_early_close.rs +++ b/zrml/prediction-markets/src/tests/reject_early_close.rs @@ -32,9 +32,6 @@ fn reject_early_close_emits_event() { ScoringRule::Lmsr, ); - // just to ensure events are emitted - run_blocks(2); - let market_id = 0; assert_ok!(PredictionMarkets::schedule_early_close( diff --git a/zrml/prediction-markets/src/tests/reject_market.rs b/zrml/prediction-markets/src/tests/reject_market.rs index 28684717f..d618465a7 100644 --- a/zrml/prediction-markets/src/tests/reject_market.rs +++ b/zrml/prediction-markets/src/tests/reject_market.rs @@ -23,8 +23,6 @@ use crate::{MarketIdsForEdit, MarketIdsPerCloseBlock}; #[test] fn it_allows_the_advisory_origin_to_reject_markets() { ExtBuilder::default().build().execute_with(|| { - run_to_block(2); - // Creates an advised market. simple_create_categorical_market( Asset::Ztg, MarketCreation::Advised, @@ -32,13 +30,11 @@ fn it_allows_the_advisory_origin_to_reject_markets() { ScoringRule::Lmsr, ); - // make sure it's in status proposed let market = MarketCommons::market(&0); assert_eq!(market.unwrap().status, MarketStatus::Proposed); let reject_reason: Vec = vec![0; ::MaxRejectReasonLen::get() as usize]; - // Now it should work from SUDO assert_ok!(PredictionMarkets::reject_market( RuntimeOrigin::signed(SUDO), 0, @@ -61,7 +57,7 @@ fn reject_errors_if_reject_reason_is_too_long() { simple_create_categorical_market( Asset::Ztg, MarketCreation::Advised, - 0..1, + 0..2, ScoringRule::Lmsr, ); @@ -85,7 +81,7 @@ fn it_allows_the_advisory_origin_to_reject_markets_with_edit_request() { simple_create_categorical_market( Asset::Ztg, MarketCreation::Advised, - 0..1, + 0..2, ScoringRule::Lmsr, ); @@ -115,7 +111,7 @@ fn reject_market_unreserves_oracle_bond_and_slashes_advisory_bond() { simple_create_categorical_market( base_asset, MarketCreation::Advised, - 0..1, + 0..2, ScoringRule::Lmsr, ); @@ -212,7 +208,7 @@ fn reject_market_fails_on_permissionless_market() { simple_create_categorical_market( Asset::Ztg, MarketCreation::Permissionless, - 0..1, + 0..2, ScoringRule::Lmsr, ); let reject_reason: Vec = @@ -231,7 +227,7 @@ fn reject_market_fails_on_approved_market() { simple_create_categorical_market( Asset::Ztg, MarketCreation::Advised, - 0..1, + 0..2, ScoringRule::Lmsr, ); assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); diff --git a/zrml/prediction-markets/src/tests/schedule_early_close.rs b/zrml/prediction-markets/src/tests/schedule_early_close.rs index 5ca8899f5..7c17fbb85 100644 --- a/zrml/prediction-markets/src/tests/schedule_early_close.rs +++ b/zrml/prediction-markets/src/tests/schedule_early_close.rs @@ -35,9 +35,6 @@ fn schedule_early_close_emits_event() { ScoringRule::Lmsr, ); - // just to ensure events are emitted - run_blocks(2); - let market_id = 0; assert_ok!(PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id)); diff --git a/zrml/prediction-markets/src/tests/sell_complete_set.rs b/zrml/prediction-markets/src/tests/sell_complete_set.rs index 9b080fd4e..23de1bd8f 100644 --- a/zrml/prediction-markets/src/tests/sell_complete_set.rs +++ b/zrml/prediction-markets/src/tests/sell_complete_set.rs @@ -23,7 +23,6 @@ use test_case::test_case; #[test_case(ScoringRule::Orderbook)] fn sell_complete_set_works(scoring_rule: ScoringRule) { let test = |base_asset: AssetOf| { - frame_system::Pallet::::set_block_number(1); simple_create_categorical_market( base_asset, MarketCreation::Permissionless, @@ -75,7 +74,7 @@ fn sell_complete_set_fails_on_zero_amount() { simple_create_categorical_market( Asset::Ztg, MarketCreation::Permissionless, - 0..1, + 0..2, ScoringRule::Lmsr, ); assert_noop!( @@ -91,7 +90,7 @@ fn sell_complete_set_fails_on_insufficient_share_balance() { simple_create_categorical_market( base_asset, MarketCreation::Permissionless, - 0..1, + 0..2, ScoringRule::Lmsr, ); let market_id = 0; @@ -123,7 +122,7 @@ fn sell_complete_set_fails_if_market_has_wrong_scoring_rule(scoring_rule: Scorin simple_create_categorical_market( base_asset, MarketCreation::Permissionless, - 0..1, + 0..2, scoring_rule, ); assert_noop!( From c7298b86226a0af1758b8e841aad5615c6427fbb Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 20 Jan 2024 23:02:39 +0100 Subject: [PATCH 29/32] Replace `crate::Config` with `Config` --- .../src/tests/admin_move_market_to_closed.rs | 2 +- .../src/tests/close_trusted_market.rs | 8 ++-- .../src/tests/create_market.rs | 44 +++++++++---------- .../tests/create_market_and_deploy_pool.rs | 4 +- .../src/tests/dispute_early_close.rs | 14 +++--- .../src/tests/edit_market.rs | 10 ++--- .../src/tests/integration.rs | 2 +- zrml/prediction-markets/src/tests/mod.rs | 2 +- .../src/tests/on_resolution.rs | 4 +- .../src/tests/reject_early_close.rs | 4 +- .../src/tests/schedule_early_close.rs | 10 ++--- 11 files changed, 52 insertions(+), 52 deletions(-) diff --git a/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs b/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs index 9133a6837..fc9fa8bd3 100644 --- a/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs +++ b/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs @@ -66,7 +66,7 @@ fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_timestamp get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::Lmsr )); diff --git a/zrml/prediction-markets/src/tests/close_trusted_market.rs b/zrml/prediction-markets/src/tests/close_trusted_market.rs index 82a1c3d52..8f8cb6de6 100644 --- a/zrml/prediction-markets/src/tests/close_trusted_market.rs +++ b/zrml/prediction-markets/src/tests/close_trusted_market.rs @@ -35,7 +35,7 @@ fn close_trusted_market_works() { MarketPeriod::Block(0..end), Deadlines { grace_period: 0, - oracle_duration: ::MinOracleDuration::get(), + oracle_duration: ::MinOracleDuration::get(), dispute_duration: Zero::zero(), }, gen_metadata(0x99), @@ -90,8 +90,8 @@ fn close_trusted_market_fails_if_not_trusted() { MarketPeriod::Block(0..end), Deadlines { grace_period: 0, - oracle_duration: ::MinOracleDuration::get(), - dispute_duration: ::MinDisputeDuration::get(), + oracle_duration: ::MinOracleDuration::get(), + dispute_duration: ::MinDisputeDuration::get(), }, gen_metadata(0x99), MarketCreation::Permissionless, @@ -136,7 +136,7 @@ fn close_trusted_market_fails_if_invalid_market_state(status: MarketStatus) { MarketPeriod::Block(0..end), Deadlines { grace_period: 0, - oracle_duration: ::MinOracleDuration::get(), + oracle_duration: ::MinOracleDuration::get(), dispute_duration: Zero::zero(), }, gen_metadata(0x99), diff --git a/zrml/prediction-markets/src/tests/create_market.rs b/zrml/prediction-markets/src/tests/create_market.rs index e52ed5a4e..028d1c896 100644 --- a/zrml/prediction-markets/src/tests/create_market.rs +++ b/zrml/prediction-markets/src/tests/create_market.rs @@ -52,9 +52,9 @@ fn create_scalar_market_fails_on_invalid_range(range: RangeInclusive) { fn create_market_fails_on_min_dispute_period() { ExtBuilder::default().build().execute_with(|| { let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get(), - oracle_duration: ::MaxOracleDuration::get(), - dispute_duration: ::MinDisputeDuration::get() - 1, + grace_period: ::MaxGracePeriod::get(), + oracle_duration: ::MaxOracleDuration::get(), + dispute_duration: ::MinDisputeDuration::get() - 1, }; assert_noop!( PredictionMarkets::create_market( @@ -79,9 +79,9 @@ fn create_market_fails_on_min_dispute_period() { fn create_market_fails_on_min_oracle_duration() { ExtBuilder::default().build().execute_with(|| { let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get(), - oracle_duration: ::MinOracleDuration::get() - 1, - dispute_duration: ::MinDisputeDuration::get(), + grace_period: ::MaxGracePeriod::get(), + oracle_duration: ::MinOracleDuration::get() - 1, + dispute_duration: ::MinDisputeDuration::get(), }; assert_noop!( PredictionMarkets::create_market( @@ -106,9 +106,9 @@ fn create_market_fails_on_min_oracle_duration() { fn create_market_fails_on_max_dispute_period() { ExtBuilder::default().build().execute_with(|| { let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get(), - oracle_duration: ::MaxOracleDuration::get(), - dispute_duration: ::MaxDisputeDuration::get() + 1, + grace_period: ::MaxGracePeriod::get(), + oracle_duration: ::MaxOracleDuration::get(), + dispute_duration: ::MaxDisputeDuration::get() + 1, }; assert_noop!( PredictionMarkets::create_market( @@ -133,9 +133,9 @@ fn create_market_fails_on_max_dispute_period() { fn create_market_fails_on_max_grace_period() { ExtBuilder::default().build().execute_with(|| { let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get() + 1, - oracle_duration: ::MaxOracleDuration::get(), - dispute_duration: ::MaxDisputeDuration::get(), + grace_period: ::MaxGracePeriod::get() + 1, + oracle_duration: ::MaxOracleDuration::get(), + dispute_duration: ::MaxDisputeDuration::get(), }; assert_noop!( PredictionMarkets::create_market( @@ -160,9 +160,9 @@ fn create_market_fails_on_max_grace_period() { fn create_market_fails_on_max_oracle_duration() { ExtBuilder::default().build().execute_with(|| { let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get(), - oracle_duration: ::MaxOracleDuration::get() + 1, - dispute_duration: ::MaxDisputeDuration::get(), + grace_period: ::MaxGracePeriod::get(), + oracle_duration: ::MaxOracleDuration::get() + 1, + dispute_duration: ::MaxDisputeDuration::get(), }; assert_noop!( PredictionMarkets::create_market( @@ -188,9 +188,9 @@ fn create_market_fails_on_max_oracle_duration() { fn create_market_with_foreign_assets() { ExtBuilder::default().build().execute_with(|| { let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get(), - oracle_duration: ::MaxOracleDuration::get(), - dispute_duration: ::MaxDisputeDuration::get(), + grace_period: ::MaxGracePeriod::get(), + oracle_duration: ::MaxOracleDuration::get(), + dispute_duration: ::MaxDisputeDuration::get(), }; // As per Mock asset_registry genesis ForeignAsset(420) has allow_as_base_asset set to false. @@ -538,8 +538,8 @@ fn create_market_sets_the_correct_market_parameters_and_reserves_the_correct_amo let period = MarketPeriod::Block(1..2); let deadlines = Deadlines { grace_period: 1, - oracle_duration: ::MinOracleDuration::get() + 2, - dispute_duration: ::MinDisputeDuration::get() + 3, + oracle_duration: ::MinOracleDuration::get() + 2, + dispute_duration: ::MinDisputeDuration::get() + 3, }; let metadata = gen_metadata(0x99); let MultiHash::Sha3_384(multihash) = metadata; @@ -589,8 +589,8 @@ fn create_market_fails_on_trusted_market_with_non_zero_dispute_period() { MarketPeriod::Block(1..2), Deadlines { grace_period: 1, - oracle_duration: ::MinOracleDuration::get() + 2, - dispute_duration: ::MinDisputeDuration::get() + 3, + oracle_duration: ::MinOracleDuration::get() + 2, + dispute_duration: ::MinDisputeDuration::get() + 3, }, gen_metadata(0x99), MarketCreation::Permissionless, diff --git a/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs b/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs index 6bd3f1fed..87824af68 100644 --- a/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs +++ b/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs @@ -29,8 +29,8 @@ fn create_market_and_deploy_pool_works() { let period = MarketPeriod::Block(1..2); let deadlines = Deadlines { grace_period: 1, - oracle_duration: ::MinOracleDuration::get() + 2, - dispute_duration: ::MinDisputeDuration::get() + 3, + oracle_duration: ::MinOracleDuration::get() + 2, + dispute_duration: ::MinDisputeDuration::get() + 3, }; let metadata = gen_metadata(0x99); let MultiHash::Sha3_384(multihash) = metadata; diff --git a/zrml/prediction-markets/src/tests/dispute_early_close.rs b/zrml/prediction-markets/src/tests/dispute_early_close.rs index 75b008257..2f7438eaf 100644 --- a/zrml/prediction-markets/src/tests/dispute_early_close.rs +++ b/zrml/prediction-markets/src/tests/dispute_early_close.rs @@ -58,7 +58,7 @@ fn dispute_early_close_from_market_creator_works() { get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), ScoringRule::Lmsr )); @@ -132,7 +132,7 @@ fn dispute_early_close_fails_if_scheduled_as_sudo() { get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), ScoringRule::Lmsr )); @@ -164,7 +164,7 @@ fn dispute_early_close_fails_if_already_disputed() { get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), ScoringRule::Lmsr )); @@ -202,7 +202,7 @@ fn dispute_early_close_fails_if_already_rejected() { get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), ScoringRule::Lmsr )); @@ -242,7 +242,7 @@ fn settles_early_close_bonds_with_resolution_in_state_disputed() { get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), ScoringRule::Lmsr )); @@ -305,7 +305,7 @@ fn settles_early_close_bonds_with_resolution_in_state_scheduled_as_market_creato get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), ScoringRule::Lmsr )); @@ -353,7 +353,7 @@ fn schedule_early_close_disputed_sudo_schedule_and_settle_bonds() { get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), ScoringRule::Lmsr )); diff --git a/zrml/prediction-markets/src/tests/edit_market.rs b/zrml/prediction-markets/src/tests/edit_market.rs index 2ac75d386..8a03bbc74 100644 --- a/zrml/prediction-markets/src/tests/edit_market.rs +++ b/zrml/prediction-markets/src/tests/edit_market.rs @@ -139,7 +139,7 @@ fn only_creator_can_edit_market() { MarketPeriod::Block(0..2), get_deadlines(), gen_metadata(2), - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::Lmsr ), @@ -178,7 +178,7 @@ fn edit_cycle_for_proposed_markets() { MarketPeriod::Block(2..4), get_deadlines(), gen_metadata(2), - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::Lmsr )); @@ -225,7 +225,7 @@ fn edit_market_with_foreign_asset() { MarketPeriod::Block(0..2), get_deadlines(), gen_metadata(2), - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::Lmsr ), @@ -241,7 +241,7 @@ fn edit_market_with_foreign_asset() { MarketPeriod::Block(0..2), get_deadlines(), gen_metadata(2), - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::Lmsr ), @@ -256,7 +256,7 @@ fn edit_market_with_foreign_asset() { MarketPeriod::Block(0..2), get_deadlines(), gen_metadata(2), - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::Lmsr )); diff --git a/zrml/prediction-markets/src/tests/integration.rs b/zrml/prediction-markets/src/tests/integration.rs index 375e759e5..e5f428931 100644 --- a/zrml/prediction-markets/src/tests/integration.rs +++ b/zrml/prediction-markets/src/tests/integration.rs @@ -55,7 +55,7 @@ fn it_appeals_a_court_market_to_global_dispute() { get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), ScoringRule::Lmsr, )); diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 42d8c1f1e..454cda81c 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -48,7 +48,7 @@ use core::ops::Range; use frame_support::{assert_noop, assert_ok, traits::NamedReservableCurrency}; use orml_traits::MultiCurrency; use sp_arithmetic::Perbill; -use sp_runtime::traits::{BlakeTwo256, Zero, Hash}; +use sp_runtime::traits::{BlakeTwo256, Hash, Zero}; use zeitgeist_primitives::{ constants::mock::{BASE, CENT}, types::{ diff --git a/zrml/prediction-markets/src/tests/on_resolution.rs b/zrml/prediction-markets/src/tests/on_resolution.rs index e46d79ab5..c9ec16c84 100644 --- a/zrml/prediction-markets/src/tests/on_resolution.rs +++ b/zrml/prediction-markets/src/tests/on_resolution.rs @@ -275,7 +275,7 @@ fn it_resolves_a_disputed_court_market() { get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), ScoringRule::Lmsr, )); @@ -946,7 +946,7 @@ fn trusted_market_complete_lifecycle() { MarketPeriod::Block(0..end), Deadlines { grace_period: 0, - oracle_duration: ::MinOracleDuration::get(), + oracle_duration: ::MinOracleDuration::get(), dispute_duration: Zero::zero(), }, gen_metadata(0x99), diff --git a/zrml/prediction-markets/src/tests/reject_early_close.rs b/zrml/prediction-markets/src/tests/reject_early_close.rs index bdf235735..22b518da9 100644 --- a/zrml/prediction-markets/src/tests/reject_early_close.rs +++ b/zrml/prediction-markets/src/tests/reject_early_close.rs @@ -117,7 +117,7 @@ fn reject_early_close_resets_to_old_market_period() { get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), ScoringRule::Lmsr )); @@ -157,7 +157,7 @@ fn reject_early_close_settles_bonds() { get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), ScoringRule::Lmsr )); diff --git a/zrml/prediction-markets/src/tests/schedule_early_close.rs b/zrml/prediction-markets/src/tests/schedule_early_close.rs index 7c17fbb85..d565d8bc7 100644 --- a/zrml/prediction-markets/src/tests/schedule_early_close.rs +++ b/zrml/prediction-markets/src/tests/schedule_early_close.rs @@ -68,7 +68,7 @@ fn sudo_schedule_early_close_at_block_works() { get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), ScoringRule::Lmsr )); @@ -139,7 +139,7 @@ fn sudo_schedule_early_close_at_timeframe_works() { get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), ScoringRule::Lmsr )); @@ -208,7 +208,7 @@ fn schedule_early_close_block_fails_if_early_close_request_too_late() { get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), ScoringRule::Lmsr )); @@ -240,7 +240,7 @@ fn schedule_early_close_timestamp_fails_if_early_close_request_too_late() { get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), ScoringRule::Lmsr )); @@ -269,7 +269,7 @@ fn schedule_early_close_as_market_creator_works() { get_deadlines(), gen_metadata(2), MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), + MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), ScoringRule::Lmsr )); From b7a7144953cfc359f2171b26bd998cffb5e52ae5 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 20 Jan 2024 23:10:59 +0100 Subject: [PATCH 30/32] Access constant through `Config` --- zrml/prediction-markets/src/lib.rs | 3 +- .../src/tests/create_market.rs | 5 +- .../src/tests/integration.rs | 57 ++++++++++++------- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 1bc745796..73c1a1085 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -94,6 +94,7 @@ mod pallet { pub(crate) type CacheSize = ConstU32<64>; pub(crate) type EditReason = BoundedVec::MaxEditReasonLen>; pub(crate) type InitialItemOf = InitialItem, BalanceOf>; + pub(crate) type MarketBondsOf = MarketBonds, BalanceOf>; pub(crate) type MarketIdOf = ::MarketId; pub(crate) type MarketOf = Market< AccountIdOf, @@ -2923,7 +2924,7 @@ mod pallet { scoring_rule: ScoringRule, report: Option>, resolved_outcome: Option, - bonds: MarketBonds>, + bonds: MarketBondsOf, ) -> Result, DispatchError> { let valid_base_asset = match base_asset { Asset::Ztg => true, diff --git a/zrml/prediction-markets/src/tests/create_market.rs b/zrml/prediction-markets/src/tests/create_market.rs index 028d1c896..1541a90f2 100644 --- a/zrml/prediction-markets/src/tests/create_market.rs +++ b/zrml/prediction-markets/src/tests/create_market.rs @@ -19,10 +19,11 @@ use super::*; use test_case::test_case; +use crate::MarketBondsOf; use core::ops::RangeInclusive; use zeitgeist_primitives::{ constants::MILLISECS_PER_BLOCK, - types::{AccountIdTest, Balance, BlockNumber, Bond, MarketBonds, Moment}, + types::{BlockNumber, Bond, MarketBonds, Moment}, }; #[test_case(std::ops::RangeInclusive::new(7, 6); "empty range")] @@ -530,7 +531,7 @@ fn create_market_sets_the_correct_market_parameters_and_reserves_the_correct_amo creation: MarketCreation, scoring_rule: ScoringRule, status: MarketStatus, - bonds: MarketBonds, + bonds: MarketBondsOf, ) { ExtBuilder::default().build().execute_with(|| { let creator = ALICE; diff --git a/zrml/prediction-markets/src/tests/integration.rs b/zrml/prediction-markets/src/tests/integration.rs index e5f428931..7640e4acb 100644 --- a/zrml/prediction-markets/src/tests/integration.rs +++ b/zrml/prediction-markets/src/tests/integration.rs @@ -23,10 +23,7 @@ use zeitgeist_primitives::types::OutcomeReport; use crate::MarketIdsPerDisputeBlock; use orml_traits::MultiReservableCurrency; -use zeitgeist_primitives::{ - constants::{mock::OutsiderBond, MILLISECS_PER_BLOCK}, - types::ScalarPosition, -}; +use zeitgeist_primitives::{constants::MILLISECS_PER_BLOCK, types::ScalarPosition}; use zrml_global_disputes::{ types::{OutcomeInfo, Possession}, GlobalDisputesPalletApi, Outcomes, PossessionOf, @@ -353,10 +350,13 @@ fn authorized_correctly_resolves_disputed_market() { if base_asset == Asset::Ztg { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - CENT - DisputeBond::get()); + assert_eq!( + charlie_balance, + 1_000 * BASE - CENT - ::DisputeBond::get() + ); } else { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - DisputeBond::get()); + assert_eq!(charlie_balance, 1_000 * BASE - ::DisputeBond::get()); let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE - CENT); } @@ -378,7 +378,7 @@ fn authorized_correctly_resolves_disputed_market() { // check everyone's deposits let charlie_reserved = Balances::reserved_balance(CHARLIE); - assert_eq!(charlie_reserved, DisputeBond::get()); + assert_eq!(charlie_reserved, ::DisputeBond::get()); let market_ids_1 = MarketIdsPerDisputeBlock::::get( dispute_at + ::CorrectionPeriod::get(), @@ -387,10 +387,13 @@ fn authorized_correctly_resolves_disputed_market() { if base_asset == Asset::Ztg { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - CENT - DisputeBond::get()); + assert_eq!( + charlie_balance, + 1_000 * BASE - CENT - ::DisputeBond::get() + ); } else { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - DisputeBond::get()); + assert_eq!(charlie_balance, 1_000 * BASE - ::DisputeBond::get()); let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE - CENT); } @@ -402,10 +405,13 @@ fn authorized_correctly_resolves_disputed_market() { if base_asset == Asset::Ztg { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - CENT - DisputeBond::get()); + assert_eq!( + charlie_balance, + 1_000 * BASE - CENT - ::DisputeBond::get() + ); } else { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - DisputeBond::get()); + assert_eq!(charlie_balance, 1_000 * BASE - ::DisputeBond::get()); let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE - CENT); } @@ -414,10 +420,13 @@ fn authorized_correctly_resolves_disputed_market() { if base_asset == Asset::Ztg { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - CENT + OracleBond::get()); + assert_eq!( + charlie_balance, + 1_000 * BASE - CENT + ::OracleBond::get() + ); } else { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE + OracleBond::get()); + assert_eq!(charlie_balance, 1_000 * BASE + ::OracleBond::get()); let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE - CENT); } @@ -431,10 +440,10 @@ fn authorized_correctly_resolves_disputed_market() { if base_asset == Asset::Ztg { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE + OracleBond::get()); + assert_eq!(charlie_balance, 1_000 * BASE + ::OracleBond::get()); } else { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE + OracleBond::get()); + assert_eq!(charlie_balance, 1_000 * BASE + ::OracleBond::get()); let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE); } @@ -442,7 +451,7 @@ fn authorized_correctly_resolves_disputed_market() { assert_eq!(charlie_reserved_2, 0); let alice_balance = AssetManager::free_balance(Asset::Ztg, &ALICE); - assert_eq!(alice_balance, 1_000 * BASE - OracleBond::get()); + assert_eq!(alice_balance, 1_000 * BASE - ::OracleBond::get()); // bob kinda gets away scot-free since Alice is held responsible // for her designated reporter @@ -496,12 +505,12 @@ fn outsider_reports_wrong_outcome() { )); let outsider_balance_before = Balances::free_balance(outsider); - check_reserve(&outsider, OutsiderBond::get()); + check_reserve(&outsider, ::OutsiderBond::get()); let dispute_at_0 = report_at + 1; run_to_block(dispute_at_0); assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); - check_reserve(&EVE, DisputeBond::get()); + check_reserve(&EVE, ::DisputeBond::get()); assert_ok!(SimpleDisputes::suggest_outcome( RuntimeOrigin::signed(DAVE), @@ -519,15 +528,21 @@ fn outsider_reports_wrong_outcome() { // on_resolution called run_blocks(market.deadlines.dispute_duration); - assert_eq!(Balances::free_balance(ALICE), alice_balance_before - OracleBond::get()); + assert_eq!( + Balances::free_balance(ALICE), + alice_balance_before - ::OracleBond::get() + ); check_reserve(&outsider, 0); assert_eq!(Balances::free_balance(outsider), outsider_balance_before); - // disputor EVE gets the OracleBond and OutsiderBond and DisputeBond + // disputor EVE gets the OracleBond and ::OutsiderBond and DisputeBond assert_eq!( Balances::free_balance(EVE), - eve_balance_before + DisputeBond::get() + OutsiderBond::get() + OracleBond::get() + eve_balance_before + + ::DisputeBond::get() + + ::OutsiderBond::get() + + ::OracleBond::get() ); // DAVE gets his outcome bond back assert_eq!(Balances::free_balance(DAVE), dave_balance_before + outcome_bond); From 6cf946fadb56e9d2d6b1d8efe749a4b00e6a54e0 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sun, 21 Jan 2024 18:49:07 +0100 Subject: [PATCH 31/32] Add TODOs for missing execution paths --- .../src/tests/admin_move_market_to_closed.rs | 4 +- .../tests/admin_move_market_to_resolved.rs | 2 + .../src/tests/approve_market.rs | 5 +- .../src/tests/buy_complete_set.rs | 2 + .../src/tests/close_trusted_market.rs | 3 + .../src/tests/create_market.rs | 27 +---- .../tests/create_market_and_deploy_pool.rs | 5 +- zrml/prediction-markets/src/tests/dispute.rs | 4 + .../src/tests/dispute_early_close.rs | 4 + .../src/tests/edit_market.rs | 91 +------------- .../src/tests/manually_close_market.rs | 3 + zrml/prediction-markets/src/tests/mod.rs | 1 + .../src/tests/redeem_shares.rs | 4 + .../src/tests/reject_early_close.rs | 3 + .../src/tests/reject_market.rs | 3 + zrml/prediction-markets/src/tests/report.rs | 11 ++ .../src/tests/request_edit.rs | 111 ++++++++++++++++++ .../src/tests/schedule_early_close.rs | 7 ++ .../src/tests/sell_complete_set.rs | 2 + .../src/tests/start_global_dispute.rs | 7 ++ 20 files changed, 186 insertions(+), 113 deletions(-) create mode 100644 zrml/prediction-markets/src/tests/request_edit.rs diff --git a/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs b/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs index fc9fa8bd3..b28662cfb 100644 --- a/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs +++ b/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs @@ -17,7 +17,7 @@ // along with Zeitgeist. If not, see . use super::*; -use crate::MarketIdsPerCloseBlock; +use crate::{MarketIdsPerCloseBlock, MomentOf}; use test_case::test_case; use zeitgeist_primitives::constants::MILLISECS_PER_BLOCK; @@ -52,7 +52,7 @@ fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_blocknumb fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_timestamp() { ExtBuilder::default().build().execute_with(|| { let start_block = 7; - set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as u64); + set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as MomentOf); run_blocks(start_block); let start = >::now(); diff --git a/zrml/prediction-markets/src/tests/admin_move_market_to_resolved.rs b/zrml/prediction-markets/src/tests/admin_move_market_to_resolved.rs index 6b84bd4a8..d4a8a8cca 100644 --- a/zrml/prediction-markets/src/tests/admin_move_market_to_resolved.rs +++ b/zrml/prediction-markets/src/tests/admin_move_market_to_resolved.rs @@ -91,6 +91,8 @@ fn admin_move_market_to_resolved_resolves_reported_market() { }); } +// TODO(#1239) resolves disputed market + #[test_case(MarketStatus::Active)] #[test_case(MarketStatus::Closed)] #[test_case(MarketStatus::Resolved)] diff --git a/zrml/prediction-markets/src/tests/approve_market.rs b/zrml/prediction-markets/src/tests/approve_market.rs index e2bcb14f3..d5a0db67c 100644 --- a/zrml/prediction-markets/src/tests/approve_market.rs +++ b/zrml/prediction-markets/src/tests/approve_market.rs @@ -21,6 +21,9 @@ use super::*; use crate::MarketIdsForEdit; use sp_runtime::DispatchError; +// TODO(#1239) Be more granular with regards to origins +// TODO(#1239) Approve fails if market status is not proposed + #[test] fn it_allows_advisory_origin_to_approve_markets() { ExtBuilder::default().build().execute_with(|| { @@ -34,7 +37,7 @@ fn it_allows_advisory_origin_to_approve_markets() { let market = MarketCommons::market(&0); assert_eq!(market.unwrap().status, MarketStatus::Proposed); - // Make sure it fails from the random joe + // Make sure it fails for the random joe assert_noop!( PredictionMarkets::approve_market(RuntimeOrigin::signed(BOB), 0), DispatchError::BadOrigin diff --git a/zrml/prediction-markets/src/tests/buy_complete_set.rs b/zrml/prediction-markets/src/tests/buy_complete_set.rs index 6a1035d3e..c9b2f3311 100644 --- a/zrml/prediction-markets/src/tests/buy_complete_set.rs +++ b/zrml/prediction-markets/src/tests/buy_complete_set.rs @@ -19,6 +19,8 @@ use super::*; use test_case::test_case; +// TODO(#1239) buy_complete_set fails if market doesn't exist + #[test] fn buy_complete_set_works() { let test = |base_asset: AssetOf| { diff --git a/zrml/prediction-markets/src/tests/close_trusted_market.rs b/zrml/prediction-markets/src/tests/close_trusted_market.rs index 8f8cb6de6..8fcdcb2f9 100644 --- a/zrml/prediction-markets/src/tests/close_trusted_market.rs +++ b/zrml/prediction-markets/src/tests/close_trusted_market.rs @@ -22,6 +22,9 @@ use test_case::test_case; use crate::MarketIdsPerCloseBlock; use sp_runtime::traits::Zero; +// TODO(#1239) MarketDoesNotExist + +// TODO(#1239) Split test #[test] fn close_trusted_market_works() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/create_market.rs b/zrml/prediction-markets/src/tests/create_market.rs index 1541a90f2..151ab1407 100644 --- a/zrml/prediction-markets/src/tests/create_market.rs +++ b/zrml/prediction-markets/src/tests/create_market.rs @@ -26,6 +26,10 @@ use zeitgeist_primitives::{ types::{BlockNumber, Bond, MarketBonds, Moment}, }; +// TODO(#1239) FeeTooHigh not verified +// TODO(#1239) InvalidMultihash not verified +// TODO(#1239) Creation fails if user can't afford the bonds + #[test_case(std::ops::RangeInclusive::new(7, 6); "empty range")] #[test_case(555..=555; "one element as range")] fn create_scalar_market_fails_on_invalid_range(range: RangeInclusive) { @@ -184,6 +188,7 @@ fn create_market_fails_on_max_oracle_duration() { }); } +// TODO(#1239) split this test #[cfg(feature = "parachain")] #[test] fn create_market_with_foreign_assets() { @@ -292,28 +297,6 @@ fn it_does_not_create_market_with_too_many_categories() { }); } -#[test] -fn create_categorical_market_fails_if_market_begin_is_equal_to_end() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(3..3), - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - ), - Error::::InvalidMarketPeriod, - ); - }); -} - #[test_case(MarketPeriod::Block(3..3); "empty range blocks")] #[test_case(MarketPeriod::Timestamp(3..3); "empty range timestamp")] #[test_case( diff --git a/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs b/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs index 87824af68..73264f96d 100644 --- a/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs +++ b/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs @@ -17,9 +17,12 @@ // along with Zeitgeist. If not, see . use super::*; - use zeitgeist_primitives::types::{Bond, MarketBonds}; +// TODO(#1239) Issue: Separate integration tests and other; use mocks for unit testing +// TODO(#1239) do_buy_complete_set failure +// TODO(#1239) deploy_pool failure + #[test] fn create_market_and_deploy_pool_works() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/dispute.rs b/zrml/prediction-markets/src/tests/dispute.rs index c1f0f81d0..947f2caee 100644 --- a/zrml/prediction-markets/src/tests/dispute.rs +++ b/zrml/prediction-markets/src/tests/dispute.rs @@ -22,6 +22,10 @@ use test_case::test_case; use crate::MarketIdsPerDisputeBlock; use zeitgeist_primitives::types::{Bond, OutcomeReport}; +// TODO(#1239) fails if market doesn't exist +// TODO(#1239) fails if market is trusted +// TODO(#1239) fails if user can't afford the bond + #[test] fn it_allows_to_dispute_the_outcome_of_a_market() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/dispute_early_close.rs b/zrml/prediction-markets/src/tests/dispute_early_close.rs index 2f7438eaf..8e2f7fdf1 100644 --- a/zrml/prediction-markets/src/tests/dispute_early_close.rs +++ b/zrml/prediction-markets/src/tests/dispute_early_close.rs @@ -21,6 +21,10 @@ use super::*; use crate::MarketIdsPerCloseBlock; use zeitgeist_primitives::types::{Bond, EarlyClose, EarlyCloseState}; +// TODO(#1239) MarketDoesNotExist +// TODO(#1239) MarketIsNotActive +// TODO(#1239) dispute bond failure + #[test] fn dispute_early_close_emits_event() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/edit_market.rs b/zrml/prediction-markets/src/tests/edit_market.rs index 8a03bbc74..731b93d2c 100644 --- a/zrml/prediction-markets/src/tests/edit_market.rs +++ b/zrml/prediction-markets/src/tests/edit_market.rs @@ -19,94 +19,11 @@ use super::*; use crate::MarketIdsForEdit; -use sp_runtime::DispatchError; -#[test] -fn it_allows_request_edit_origin_to_request_edits_for_markets() { - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 2..4, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - // Make sure it fails from the random joe - assert_noop!( - PredictionMarkets::request_edit(RuntimeOrigin::signed(BOB), 0, edit_reason.clone()), - DispatchError::BadOrigin - ); - - // Now it should work from SUDO - assert_ok!(PredictionMarkets::request_edit( - RuntimeOrigin::signed(SUDO), - 0, - edit_reason.clone() - )); - System::assert_last_event( - Event::MarketRequestedEdit( - 0, - edit_reason.try_into().expect("Conversion to BoundedVec failed"), - ) - .into(), - ); - - assert!(MarketIdsForEdit::::contains_key(0)); - }); -} - -#[test] -fn request_edit_fails_on_bad_origin() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 2..4, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - // Make sure it fails from the random joe - assert_noop!( - PredictionMarkets::request_edit(RuntimeOrigin::signed(BOB), 0, edit_reason), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn edit_request_fails_if_edit_reason_is_too_long() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..2, - ScoringRule::Lmsr, - ); - - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize + 1]; - - assert_noop!( - PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason), - Error::::EditReasonLengthExceedsMaxEditReasonLen - ); - }); -} +// TODO(#1239) MarketEditNotRequested +// TODO(#1239) MarketDoesNotExist +// TODO(#1239) InvalidMarketStatus +// TODO(#1239) All failures that need to be ensured for `create_market` #[test] fn only_creator_can_edit_market() { diff --git a/zrml/prediction-markets/src/tests/manually_close_market.rs b/zrml/prediction-markets/src/tests/manually_close_market.rs index 0d39626cd..9c61a8d33 100644 --- a/zrml/prediction-markets/src/tests/manually_close_market.rs +++ b/zrml/prediction-markets/src/tests/manually_close_market.rs @@ -21,6 +21,9 @@ use super::*; use crate::{LastTimeFrame, MarketIdsPerCloseTimeFrame}; use zeitgeist_primitives::constants::MILLISECS_PER_BLOCK; +// TODO(#1239) MarketDoesNotExist +// TODO(#1239) MarketPeriodEndNotAlreadyReachedYet + #[test] fn manually_close_market_after_long_stall() { // We check that `on_market_close` works correctly even if a block takes much longer than 12sec diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 454cda81c..0f17d3fe3 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -36,6 +36,7 @@ mod on_resolution; mod redeem_shares; mod reject_early_close; mod reject_market; +mod request_edit; mod report; mod schedule_early_close; mod sell_complete_set; diff --git a/zrml/prediction-markets/src/tests/redeem_shares.rs b/zrml/prediction-markets/src/tests/redeem_shares.rs index 3bb4cd650..a76618a4d 100644 --- a/zrml/prediction-markets/src/tests/redeem_shares.rs +++ b/zrml/prediction-markets/src/tests/redeem_shares.rs @@ -21,6 +21,10 @@ use test_case::test_case; use zeitgeist_primitives::types::{OutcomeReport, ScalarPosition}; +// TODO(#1239) MarketIsNotResolved +// TODO(#1239) NoWinningBalance +// TODO(#1239) MarketDoesNotExist + #[test] fn it_allows_to_redeem_shares() { let test = |base_asset: AssetOf| { diff --git a/zrml/prediction-markets/src/tests/reject_early_close.rs b/zrml/prediction-markets/src/tests/reject_early_close.rs index 22b518da9..77cd26a85 100644 --- a/zrml/prediction-markets/src/tests/reject_early_close.rs +++ b/zrml/prediction-markets/src/tests/reject_early_close.rs @@ -21,6 +21,9 @@ use super::*; use crate::MarketIdsPerCloseBlock; use zeitgeist_primitives::types::EarlyCloseState; +// TODO(#1239) MarketDoesNotExist +// TODO(#1239) NoEarlyCloseScheduled + #[test] fn reject_early_close_emits_event() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/reject_market.rs b/zrml/prediction-markets/src/tests/reject_market.rs index d618465a7..38eded944 100644 --- a/zrml/prediction-markets/src/tests/reject_market.rs +++ b/zrml/prediction-markets/src/tests/reject_market.rs @@ -20,6 +20,9 @@ use super::*; use crate::{MarketIdsForEdit, MarketIdsPerCloseBlock}; +// TODO(#1239) MarketDoesNotExist +// TODO(#1239) Fails if market is not proposed + #[test] fn it_allows_the_advisory_origin_to_reject_markets() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/report.rs b/zrml/prediction-markets/src/tests/report.rs index 13f0bbccd..4d28e8cea 100644 --- a/zrml/prediction-markets/src/tests/report.rs +++ b/zrml/prediction-markets/src/tests/report.rs @@ -20,6 +20,13 @@ use super::*; use zeitgeist_primitives::{constants::MILLISECS_PER_BLOCK, types::OutcomeReport}; +// TODO(#1239) MarketDoesNotExist +// TODO(#1239) MarketAlreadyReported +// TODO(#1239) Trusted markets resolve immediately +// TODO(#1239) NotAllowedToReport with timestamps +// TODO(#1239) Reports are allowed after the oracle duration +// TODO(#1239) Outsider can't report if they can't pay for the bond + #[test] fn it_allows_to_report_the_outcome_of_a_market() { ExtBuilder::default().build().execute_with(|| { @@ -90,6 +97,8 @@ fn report_fails_before_grace_period_is_over() { }); } +// TODO(#1239) This test is misnamed - this does NOT ensure that reports outside of the oracle duration are +// not allowed. #[test] fn it_allows_only_oracle_to_report_the_outcome_of_a_market_during_oracle_duration_blocks() { ExtBuilder::default().build().execute_with(|| { @@ -172,6 +181,7 @@ fn it_allows_only_oracle_to_report_the_outcome_of_a_market_during_oracle_duratio }); } +// TODO(#1239) Use test_case! #[test] fn report_fails_on_mismatched_outcome_for_categorical_market() { ExtBuilder::default().build().execute_with(|| { @@ -278,6 +288,7 @@ fn it_allows_anyone_to_report_an_unreported_market() { }); } +// TODO(#1239) Use `test_case` #[test] fn report_fails_on_market_state_proposed() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/request_edit.rs b/zrml/prediction-markets/src/tests/request_edit.rs new file mode 100644 index 000000000..8851e82de --- /dev/null +++ b/zrml/prediction-markets/src/tests/request_edit.rs @@ -0,0 +1,111 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; +use crate::MarketIdsForEdit; +use sp_runtime::DispatchError; + +// TODO(#1239) request_edit fails if market is not proposed +// TODO(#1239) request_edit fails if edit already in progress + +#[test] +fn it_allows_request_edit_origin_to_request_edits_for_markets() { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 2..4, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + // Make sure it fails from the random joe + assert_noop!( + PredictionMarkets::request_edit(RuntimeOrigin::signed(BOB), 0, edit_reason.clone()), + DispatchError::BadOrigin + ); + + // Now it should work from SUDO + assert_ok!(PredictionMarkets::request_edit( + RuntimeOrigin::signed(SUDO), + 0, + edit_reason.clone() + )); + System::assert_last_event( + Event::MarketRequestedEdit( + 0, + edit_reason.try_into().expect("Conversion to BoundedVec failed"), + ) + .into(), + ); + + assert!(MarketIdsForEdit::::contains_key(0)); + }); +} + +#[test] +fn request_edit_fails_on_bad_origin() { + ExtBuilder::default().build().execute_with(|| { + frame_system::Pallet::::set_block_number(1); + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 2..4, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + // Make sure it fails from the random joe + assert_noop!( + PredictionMarkets::request_edit(RuntimeOrigin::signed(BOB), 0, edit_reason), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn edit_request_fails_if_edit_reason_is_too_long() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..2, + ScoringRule::Lmsr, + ); + + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize + 1]; + + assert_noop!( + PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason), + Error::::EditReasonLengthExceedsMaxEditReasonLen + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/schedule_early_close.rs b/zrml/prediction-markets/src/tests/schedule_early_close.rs index d565d8bc7..cb8d83145 100644 --- a/zrml/prediction-markets/src/tests/schedule_early_close.rs +++ b/zrml/prediction-markets/src/tests/schedule_early_close.rs @@ -24,6 +24,13 @@ use zeitgeist_primitives::{ types::{EarlyClose, EarlyCloseState}, }; +// TODO(#1239) MarketDoesNotExist +// TODO(#1239) RequesterNotCreator +// TODO(#1239) MarketIsNotActive +// TODO(#1239) OnlyAuthorizedCanScheduleEarlyClose +// TODO(#1239) Correct repatriations +// TODO(#1239) reserve_named failure + #[test] fn schedule_early_close_emits_event() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/tests/sell_complete_set.rs b/zrml/prediction-markets/src/tests/sell_complete_set.rs index 23de1bd8f..6814481c9 100644 --- a/zrml/prediction-markets/src/tests/sell_complete_set.rs +++ b/zrml/prediction-markets/src/tests/sell_complete_set.rs @@ -19,6 +19,8 @@ use super::*; use test_case::test_case; +// TODO(#1239) MarketDoesNotExist + #[test_case(ScoringRule::Lmsr)] #[test_case(ScoringRule::Orderbook)] fn sell_complete_set_works(scoring_rule: ScoringRule) { diff --git a/zrml/prediction-markets/src/tests/start_global_dispute.rs b/zrml/prediction-markets/src/tests/start_global_dispute.rs index 573dcf8a3..2b84d8ed7 100644 --- a/zrml/prediction-markets/src/tests/start_global_dispute.rs +++ b/zrml/prediction-markets/src/tests/start_global_dispute.rs @@ -20,6 +20,13 @@ use super::*; use zeitgeist_primitives::types::OutcomeReport; +// TODO(#1239) MarketDoesNotExist +// TODO(#1239) NoDisputeMechanism +// TODO(#1239) InvalidMarketStatus +// TODO(#1239) GlobalDisputeExistsAlready +// TODO(#1239) MarketIsNotReported +// TODO(#1239) MarketDisputeMechanismNotFailed + #[test] fn start_global_dispute_fails_on_wrong_mdm() { ExtBuilder::default().build().execute_with(|| { From a5f24ef875ed093d1992f8f31e312c49982261ec Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sun, 21 Jan 2024 21:19:40 +0100 Subject: [PATCH 32/32] Fix formatting --- zrml/prediction-markets/src/tests/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index 0f17d3fe3..e11639a55 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -36,8 +36,8 @@ mod on_resolution; mod redeem_shares; mod reject_early_close; mod reject_market; -mod request_edit; mod report; +mod request_edit; mod schedule_early_close; mod sell_complete_set; mod start_global_dispute;