diff --git a/Cargo.lock b/Cargo.lock index bb4ae6a7a..d0f56c7af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13108,6 +13108,9 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "orml-currencies", + "orml-tokens", + "orml-traits", "pallet-balances", "pallet-timestamp", "parity-scale-codec", diff --git a/primitives/src/constants/mock.rs b/primitives/src/constants/mock.rs index 3c0f64294..6d367004e 100644 --- a/primitives/src/constants/mock.rs +++ b/primitives/src/constants/mock.rs @@ -58,8 +58,7 @@ parameter_types! { // Prediction Market parameters parameter_types! { pub const AdvisoryBond: Balance = 25 * CENT; - pub const DisputeBond: Balance = 5 * BASE; - pub const DisputeFactor: Balance = 2 * BASE; + pub const DisputeBond: Balance = 20 * BASE; pub const GlobalDisputePeriod: BlockNumber = 7 * BLOCKS_PER_DAY; pub const MaxCategories: u16 = 10; pub const MaxDisputeDuration: BlockNumber = 50; @@ -85,6 +84,8 @@ parameter_types! { // Simple disputes parameters parameter_types! { pub const SimpleDisputesPalletId: PalletId = PalletId(*b"zge/sedp"); + pub const OutcomeBond: Balance = 5 * BASE; + pub const OutcomeFactor: Balance = 2 * BASE; } // Swaps parameters diff --git a/primitives/src/market.rs b/primitives/src/market.rs index 7a2cc3127..6eeb4e9ca 100644 --- a/primitives/src/market.rs +++ b/primitives/src/market.rs @@ -88,6 +88,7 @@ pub struct MarketBonds { pub creation: Option>, pub oracle: Option>, pub outsider: Option>, + pub dispute: Option>, } impl MarketBonds { @@ -100,13 +101,14 @@ impl MarketBonds { value_or_default(&self.creation) .saturating_add(value_or_default(&self.oracle)) .saturating_add(value_or_default(&self.outsider)) + .saturating_add(value_or_default(&self.dispute)) } } // Used primarily for testing purposes. impl Default for MarketBonds { fn default() -> Self { - MarketBonds { creation: None, oracle: None, outsider: None } + MarketBonds { creation: None, oracle: None, outsider: None, dispute: None } } } @@ -175,11 +177,31 @@ pub enum MarketCreation { Advised, } +/// Defines a global dispute item for the initialisation of a global dispute. +pub struct GlobalDisputeItem { + /// The account that already paid somehow for the outcome. + pub owner: AccountId, + /// The outcome that was already paid for + /// and should be added as vote outcome inside global disputes. + pub outcome: OutcomeReport, + /// The initial amount added in the global dispute vote system initially for the outcome. + pub initial_vote_amount: Balance, +} + +// TODO to remove, when Disputes storage item is removed +#[derive(Clone, Decode, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] +pub struct OldMarketDispute { + pub at: BlockNumber, + pub by: AccountId, + pub outcome: OutcomeReport, +} + #[derive(Clone, Decode, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] -pub struct MarketDispute { +pub struct MarketDispute { pub at: BlockNumber, pub by: AccountId, pub outcome: OutcomeReport, + pub bond: Balance, } /// How a market should resolve disputes diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index fe7ec219f..a1be3cd57 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -22,7 +22,7 @@ mod market_id; mod swaps; mod zeitgeist_multi_reservable_currency; -pub use dispute_api::{DisputeApi, DisputeResolutionApi}; +pub use dispute_api::{DisputeApi, DisputeMaxWeightApi, DisputeResolutionApi}; pub use market_commons_pallet_api::MarketCommonsPalletApi; pub use market_id::MarketId; pub use swaps::Swaps; diff --git a/primitives/src/traits/dispute_api.rs b/primitives/src/traits/dispute_api.rs index caa7b94f3..385bbd7de 100644 --- a/primitives/src/traits/dispute_api.rs +++ b/primitives/src/traits/dispute_api.rs @@ -16,12 +16,14 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +extern crate alloc; + use crate::{ - market::MarketDispute, outcome_report::OutcomeReport, - types::{Asset, Market}, + types::{Asset, GlobalDisputeItem, Market, ResultWithWeightInfo}, }; -use frame_support::{dispatch::DispatchResult, pallet_prelude::Weight, BoundedVec}; +use alloc::vec::Vec; +use frame_support::pallet_prelude::Weight; use parity_scale_codec::MaxEncodedLen; use sp_runtime::DispatchError; @@ -35,9 +37,13 @@ type MarketOfDisputeApi = Market< Asset<::MarketId>, >; +type GlobalDisputeItemOfDisputeApi = + GlobalDisputeItem<::AccountId, ::Balance>; + pub trait DisputeApi { type AccountId; type Balance; + type NegativeImbalance; type BlockNumber; type MarketId: MaxEncodedLen; type Moment; @@ -48,10 +54,9 @@ pub trait DisputeApi { /// Further interaction with the dispute API (if necessary) **should** happen through an /// associated pallet. **May** assume that `market.dispute_mechanism` refers to the calling dispute API. fn on_dispute( - previous_disputes: &[MarketDispute], market_id: &Self::MarketId, market: &MarketOfDisputeApi, - ) -> DispatchResult; + ) -> Result, DispatchError>; /// Manage market resolution of a disputed market. /// @@ -63,31 +68,81 @@ pub trait DisputeApi { /// Returns the dispute mechanism's report if available, otherwise `None`. If `None` is /// returned, this means that the dispute could not be resolved. fn on_resolution( - disputes: &[MarketDispute], market_id: &Self::MarketId, market: &MarketOfDisputeApi, - ) -> Result, DispatchError>; + ) -> Result>, DispatchError>; - /// Query the future resolution block of a disputed market. + /// Allow the transfer of funds from the API caller to the API consumer and back. + /// This can be based on the final resolution outcome of the market. + /// **May** assume that `market.dispute_mechanism` refers to the calling dispute API. + /// + /// # Arguments + /// * `market_id` - The identifier of the market. + /// * `market` - The market data. + /// * `resolved_outcome` - The final resolution outcome of the market. + /// * `amount` - The amount of funds transferred to the dispute mechanism. + /// + /// # Returns + /// Returns a negative imbalance in order to transfer funds back to the caller. + fn exchange( + market_id: &Self::MarketId, + market: &MarketOfDisputeApi, + resolved_outcome: &OutcomeReport, + amount: Self::NegativeImbalance, + ) -> Result, DispatchError>; /// **May** assume that `market.dispute_mechanism` refers to the calling dispute API. /// /// # Returns /// /// Returns the future resolution block if available, otherwise `None`. fn get_auto_resolve( - disputes: &[MarketDispute], market_id: &Self::MarketId, market: &MarketOfDisputeApi, - ) -> Result, DispatchError>; + ) -> Result>, DispatchError>; /// Returns `true` if the market dispute mechanism /// was unable to come to a conclusion. /// **May** assume that `market.dispute_mechanism` refers to the calling dispute API. fn has_failed( - disputes: &[MarketDispute], market_id: &Self::MarketId, market: &MarketOfDisputeApi, - ) -> Result; + ) -> Result, DispatchError>; + + /// Called, when a global dispute is started. + /// **May** assume that `market.dispute_mechanism` refers to the calling dispute API. + /// + /// # Returns + /// Returns the initial vote outcomes with initial vote value and owner of the vote. + fn on_global_dispute( + market_id: &Self::MarketId, + market: &MarketOfDisputeApi, + ) -> Result>>, DispatchError>; + + /// Allow the API consumer to clear storage items of the dispute mechanism. + /// This may be called, when the dispute mechanism is no longer needed. + /// **May** assume that `market.dispute_mechanism` refers to the calling dispute API. + fn clear( + market_id: &Self::MarketId, + market: &MarketOfDisputeApi, + ) -> Result, DispatchError>; +} + +pub trait DisputeMaxWeightApi { + /// Return the max weight of the `on_dispute` function. + fn on_dispute_max_weight() -> Weight; + /// Return the max weight of the `on_resolution` function. + fn on_resolution_max_weight() -> Weight; + /// Return the max weight of the `exchange` function. + fn exchange_max_weight() -> Weight; + /// Query the future resolution block of a disputed market. + /// Return the max weight of the `get_auto_resolve` function. + fn get_auto_resolve_max_weight() -> Weight; + /// Return the max weight of the `has_failed` function. + fn has_failed_max_weight() -> Weight; + /// Return the max weight of the `on_global_dispute` function. + fn on_global_dispute_max_weight() -> Weight; + /// Return the max weight of the `clear` function. + fn clear_max_weight() -> Weight; } type MarketOfDisputeResolutionApi = Market< @@ -103,7 +158,6 @@ pub trait DisputeResolutionApi { type Balance; type BlockNumber; type MarketId: MaxEncodedLen; - type MaxDisputes; type Moment; /// Resolve a market. @@ -142,9 +196,4 @@ pub trait DisputeResolutionApi { /// /// Returns the number of elements in the storage structure. fn remove_auto_resolve(market_id: &Self::MarketId, resolve_at: Self::BlockNumber) -> u32; - - /// Get the disputes of a market. - fn get_disputes( - market_id: &Self::MarketId, - ) -> BoundedVec, Self::MaxDisputes>; } diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index 4b37e26c1..3e1d6eaa9 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -153,11 +153,9 @@ parameter_types! { /// The percentage of the advisory bond that gets slashed when a market is rejected. pub const AdvisoryBondSlashPercentage: Percent = Percent::from_percent(0); /// (Slashable) Bond that is provided for disputing the outcome. - /// Slashed in case the final outcome does not match the dispute for which the `DisputeBond` - /// was deposited. - pub const DisputeBond: Balance = 5 * BASE; - /// `DisputeBond` is increased by this factor after every dispute. - pub const DisputeFactor: Balance = 2 * BASE; + /// Unreserved in case the dispute was justified otherwise slashed. + /// This is when the resolved outcome is different to the default (reported) outcome. + pub const DisputeBond: Balance = 25 * BASE; /// Maximum Categories a prediciton market can have (excluding base asset). pub const MaxCategories: u16 = MAX_CATEGORIES; /// Maximum block period for a dispute. @@ -224,6 +222,12 @@ parameter_types! { // Simple disputes parameters /// Pallet identifier, mainly used for named balance reserves. pub const SimpleDisputesPalletId: PalletId = SD_PALLET_ID; + /// (Slashable) Bond that is provided for overriding the last outcome addition. + /// Slashed in case the final outcome does not match the dispute for which the `OutcomeBond` + /// was deposited. + pub const OutcomeBond: Balance = 5 * BASE; + /// `OutcomeBond` is increased by this factor after every new outcome addition. + pub const OutcomeFactor: Balance = 2 * BASE; // Swaps parameters /// A precentage from the withdrawal amount a liquidity provider wants to withdraw diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 0151f932d..bd2346a4d 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -58,7 +58,10 @@ macro_rules! decl_common_types { frame_system::ChainContext, Runtime, AllPalletsWithSystem, - zrml_prediction_markets::migrations::AddOutsiderBond, + ( + zrml_prediction_markets::migrations::AddOutsiderAndDisputeBond, + zrml_prediction_markets::migrations::MoveDataToSimpleDisputes, + ), >; pub type Header = generic::Header; @@ -300,7 +303,7 @@ macro_rules! create_runtime { Court: zrml_court::{Call, Event, Pallet, Storage} = 52, LiquidityMining: zrml_liquidity_mining::{Call, Config, Event, Pallet, Storage} = 53, RikiddoSigmoidFeeMarketEma: zrml_rikiddo::::{Pallet, Storage} = 54, - SimpleDisputes: zrml_simple_disputes::{Event, Pallet, Storage} = 55, + SimpleDisputes: zrml_simple_disputes::{Call, Event, Pallet, Storage} = 55, Swaps: zrml_swaps::{Call, Event, Pallet, Storage} = 56, PredictionMarkets: zrml_prediction_markets::{Call, Event, Pallet, Storage} = 57, Styx: zrml_styx::{Call, Event, Pallet, Storage} = 58, @@ -986,7 +989,6 @@ macro_rules! impl_config_traits { type CloseOrigin = EnsureRoot; type DestroyOrigin = EnsureRootOrAllAdvisoryCommittee; type DisputeBond = DisputeBond; - type DisputeFactor = DisputeFactor; type Event = Event; #[cfg(feature = "with-global-disputes")] type GlobalDisputes = GlobalDisputes; @@ -1041,10 +1043,15 @@ macro_rules! impl_config_traits { } impl zrml_simple_disputes::Config for Runtime { + type AssetManager = AssetManager; + type OutcomeBond = OutcomeBond; + type OutcomeFactor = OutcomeFactor; type DisputeResolution = zrml_prediction_markets::Pallet; type Event = Event; type MarketCommons = MarketCommons; + type MaxDisputes = MaxDisputes; type PalletId = SimpleDisputesPalletId; + type WeightInfo = zrml_simple_disputes::weights::WeightInfo; } #[cfg(feature = "with-global-disputes")] @@ -1195,6 +1202,7 @@ macro_rules! create_runtime_api { list_benchmark!(list, extra, zrml_swaps, Swaps); list_benchmark!(list, extra, zrml_authorized, Authorized); list_benchmark!(list, extra, zrml_court, Court); + list_benchmark!(list, extra, zrml_simple_disputes, SimpleDisputes); #[cfg(feature = "with-global-disputes")] list_benchmark!(list, extra, zrml_global_disputes, GlobalDisputes); #[cfg(not(feature = "parachain"))] @@ -1273,6 +1281,7 @@ macro_rules! create_runtime_api { add_benchmark!(params, batches, zrml_swaps, Swaps); add_benchmark!(params, batches, zrml_authorized, Authorized); add_benchmark!(params, batches, zrml_court, Court); + add_benchmark!(params, batches, zrml_simple_disputes, SimpleDisputes); #[cfg(feature = "with-global-disputes")] add_benchmark!(params, batches, zrml_global_disputes, GlobalDisputes); #[cfg(not(feature = "parachain"))] diff --git a/runtime/zeitgeist/src/lib.rs b/runtime/zeitgeist/src/lib.rs index 71477fd01..8df1cc377 100644 --- a/runtime/zeitgeist/src/lib.rs +++ b/runtime/zeitgeist/src/lib.rs @@ -163,6 +163,7 @@ impl Contains for IsCallable { _ => true, } } + Call::SimpleDisputes(_) => false, Call::System(inner_call) => { match inner_call { // Some "waste" storage will never impact proper operation. diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 794afd2a5..853373eed 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -153,11 +153,9 @@ parameter_types! { /// The percentage of the advisory bond that gets slashed when a market is rejected. pub const AdvisoryBondSlashPercentage: Percent = Percent::from_percent(0); /// (Slashable) Bond that is provided for disputing the outcome. - /// Slashed in case the final outcome does not match the dispute for which the `DisputeBond` - /// was deposited. + /// Unreserved in case the dispute was justified otherwise slashed. + /// This is when the resolved outcome is different to the default (reported) outcome. pub const DisputeBond: Balance = 2_000 * BASE; - /// `DisputeBond` is increased by this factor after every dispute. - pub const DisputeFactor: Balance = 2 * BASE; /// Maximum Categories a prediciton market can have (excluding base asset). pub const MaxCategories: u16 = MAX_CATEGORIES; /// Maximum block period for a dispute. @@ -224,6 +222,12 @@ parameter_types! { // Simple disputes parameters /// Pallet identifier, mainly used for named balance reserves. DO NOT CHANGE. pub const SimpleDisputesPalletId: PalletId = SD_PALLET_ID; + /// (Slashable) Bond that is provided for overriding the last outcome addition. + /// Slashed in case the final outcome does not match the dispute for which the `OutcomeBond` + /// was deposited. + pub const OutcomeBond: Balance = 2_000 * BASE; + /// `OutcomeBond` is increased by this factor after every new outcome addition. + pub const OutcomeFactor: Balance = 2 * BASE; // Swaps parameters /// A precentage from the withdrawal amount a liquidity provider wants to withdraw diff --git a/zrml/authorized/src/authorized_pallet_api.rs b/zrml/authorized/src/authorized_pallet_api.rs index b9a1f4185..387fa6034 100644 --- a/zrml/authorized/src/authorized_pallet_api.rs +++ b/zrml/authorized/src/authorized_pallet_api.rs @@ -1,3 +1,4 @@ +// Copyright 2023 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -15,6 +16,6 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use zeitgeist_primitives::traits::DisputeApi; +use zeitgeist_primitives::traits::{DisputeApi, DisputeMaxWeightApi}; -pub trait AuthorizedPalletApi: DisputeApi {} +pub trait AuthorizedPalletApi: DisputeApi + DisputeMaxWeightApi {} diff --git a/zrml/authorized/src/benchmarks.rs b/zrml/authorized/src/benchmarks.rs index d4f349161..1ddf58cf8 100644 --- a/zrml/authorized/src/benchmarks.rs +++ b/zrml/authorized/src/benchmarks.rs @@ -22,17 +22,18 @@ )] #![cfg(feature = "runtime-benchmarks")] -#[cfg(test)] -use crate::Pallet as Authorized; -use crate::{market_mock, AuthorizedOutcomeReports, Call, Config, Pallet}; +use crate::{ + market_mock, AuthorizedOutcomeReports, Call, Config, NegativeImbalanceOf, Pallet as Authorized, + Pallet, +}; use frame_benchmarking::benchmarks; use frame_support::{ dispatch::UnfilteredDispatchable, - traits::{EnsureOrigin, Get}, + traits::{EnsureOrigin, Get, Imbalance}, }; use sp_runtime::traits::Saturating; use zeitgeist_primitives::{ - traits::DisputeResolutionApi, + traits::{DisputeApi, DisputeResolutionApi}, types::{AuthorityReport, OutcomeReport}, }; use zrml_market_commons::MarketCommonsPalletApi; @@ -96,6 +97,72 @@ benchmarks! { assert_eq!(AuthorizedOutcomeReports::::get(market_id).unwrap(), report); } + on_dispute_weight { + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + }: { + Authorized::::on_dispute(&market_id, &market).unwrap(); + } + + on_resolution_weight { + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + + let report = AuthorityReport { resolve_at: 0u32.into(), outcome: OutcomeReport::Scalar(0) }; + AuthorizedOutcomeReports::::insert(market_id, report); + }: { + Authorized::::on_resolution(&market_id, &market).unwrap(); + } + + exchange_weight { + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + + let outcome = OutcomeReport::Scalar(0); + let imb = NegativeImbalanceOf::::zero(); + }: { + Authorized::::exchange(&market_id, &market, &outcome, imb).unwrap(); + } + + get_auto_resolve_weight { + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + let report = AuthorityReport { resolve_at: 0u32.into(), outcome: OutcomeReport::Scalar(0) }; + AuthorizedOutcomeReports::::insert(market_id, report); + }: { + Authorized::::get_auto_resolve(&market_id, &market).unwrap(); + } + + has_failed_weight { + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + }: { + Authorized::::has_failed(&market_id, &market).unwrap(); + } + + on_global_dispute_weight { + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + }: { + Authorized::::on_global_dispute(&market_id, &market).unwrap(); + } + + clear_weight { + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + let report = AuthorityReport { resolve_at: 0u32.into(), outcome: OutcomeReport::Scalar(0) }; + AuthorizedOutcomeReports::::insert(market_id, report); + }: { + Authorized::::clear(&market_id, &market).unwrap(); + } + impl_benchmark_test_suite!( Authorized, crate::mock::ExtBuilder::default().build(), diff --git a/zrml/authorized/src/lib.rs b/zrml/authorized/src/lib.rs index 64619a232..675061058 100644 --- a/zrml/authorized/src/lib.rs +++ b/zrml/authorized/src/lib.rs @@ -35,21 +35,22 @@ pub use pallet::*; #[frame_support::pallet] mod pallet { use crate::{weights::WeightInfoZeitgeist, AuthorizedPalletApi}; + use alloc::vec::Vec; use core::marker::PhantomData; use frame_support::{ - dispatch::{DispatchResult, DispatchResultWithPostInfo}, + dispatch::DispatchResultWithPostInfo, ensure, - pallet_prelude::{ConstU32, EnsureOrigin, OptionQuery, StorageMap}, + pallet_prelude::{ConstU32, EnsureOrigin, OptionQuery, StorageMap, Weight}, traits::{Currency, Get, Hooks, IsType, StorageVersion}, PalletId, Twox64Concat, }; use frame_system::pallet_prelude::OriginFor; use sp_runtime::{traits::Saturating, DispatchError}; use zeitgeist_primitives::{ - traits::{DisputeApi, DisputeResolutionApi}, + traits::{DisputeApi, DisputeMaxWeightApi, DisputeResolutionApi}, types::{ - Asset, AuthorityReport, Market, MarketDispute, MarketDisputeMechanism, MarketStatus, - OutcomeReport, + Asset, AuthorityReport, GlobalDisputeItem, Market, MarketDisputeMechanism, + MarketStatus, OutcomeReport, ResultWithWeightInfo, }, }; use zrml_market_commons::MarketCommonsPalletApi; @@ -61,6 +62,8 @@ mod pallet { as Currency<::AccountId>>::Balance; pub(crate) type CurrencyOf = <::MarketCommons as MarketCommonsPalletApi>::Currency; + pub(crate) type NegativeImbalanceOf = + as Currency<::AccountId>>::NegativeImbalance; pub(crate) type MarketIdOf = <::MarketCommons as MarketCommonsPalletApi>::MarketId; pub(crate) type MomentOf = <::MarketCommons as MarketCommonsPalletApi>::Moment; @@ -100,16 +103,21 @@ mod pallet { let report_opt = AuthorizedOutcomeReports::::get(market_id); let (report, ids_len) = match &report_opt { - Some(report) => (AuthorityReport { resolve_at: report.resolve_at, outcome }, 0u32), + Some(report) => ( + AuthorityReport { resolve_at: report.resolve_at, outcome: outcome.clone() }, + 0u32, + ), None => { let resolve_at = now.saturating_add(T::CorrectionPeriod::get()); let ids_len = T::DisputeResolution::add_auto_resolve(&market_id, resolve_at)?; - (AuthorityReport { resolve_at, outcome }, ids_len) + (AuthorityReport { resolve_at, outcome: outcome.clone() }, ids_len) } }; AuthorizedOutcomeReports::::insert(market_id, report); + Self::deposit_event(Event::AuthorityReported { market_id, outcome }); + if report_opt.is_none() { Ok(Some(T::WeightInfo::authorize_market_outcome_first_report(ids_len)).into()) } else { @@ -157,16 +165,19 @@ mod pallet { MarketDoesNotHaveDisputeMechanismAuthorized, /// An account attempts to submit a report to an undisputed market. MarketIsNotDisputed, - /// Only one dispute is allowed. - OnlyOneDisputeAllowed, /// The report does not match the market's type. OutcomeMismatch, } #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event where - T: Config, {} + T: Config, + { + /// The Authority reported. + AuthorityReported { market_id: MarketIdOf, outcome: OutcomeReport }, + } #[pallet::hooks] impl Hooks for Pallet {} @@ -185,66 +196,169 @@ mod pallet { } } + impl DisputeMaxWeightApi for Pallet + where + T: Config, + { + fn on_dispute_max_weight() -> Weight { + T::WeightInfo::on_dispute_weight() + } + + fn on_resolution_max_weight() -> Weight { + T::WeightInfo::on_resolution_weight() + } + + fn exchange_max_weight() -> Weight { + T::WeightInfo::exchange_weight() + } + + fn get_auto_resolve_max_weight() -> Weight { + T::WeightInfo::get_auto_resolve_weight() + } + + fn has_failed_max_weight() -> Weight { + T::WeightInfo::has_failed_weight() + } + + fn on_global_dispute_max_weight() -> Weight { + T::WeightInfo::on_global_dispute_weight() + } + + fn clear_max_weight() -> Weight { + T::WeightInfo::clear_weight() + } + } + impl DisputeApi for Pallet where T: Config, { type AccountId = T::AccountId; type Balance = BalanceOf; + type NegativeImbalance = NegativeImbalanceOf; type BlockNumber = T::BlockNumber; type MarketId = MarketIdOf; type Moment = MomentOf; type Origin = T::Origin; fn on_dispute( - disputes: &[MarketDispute], _: &Self::MarketId, market: &MarketOf, - ) -> DispatchResult { + ) -> Result, DispatchError> { ensure!( market.dispute_mechanism == MarketDisputeMechanism::Authorized, Error::::MarketDoesNotHaveDisputeMechanismAuthorized ); - ensure!(disputes.is_empty(), Error::::OnlyOneDisputeAllowed); - Ok(()) + + let res = + ResultWithWeightInfo { result: (), weight: T::WeightInfo::on_dispute_weight() }; + + Ok(res) } fn on_resolution( - _: &[MarketDispute], market_id: &Self::MarketId, market: &MarketOf, - ) -> Result, DispatchError> { + ) -> Result>, DispatchError> { ensure!( market.dispute_mechanism == MarketDisputeMechanism::Authorized, Error::::MarketDoesNotHaveDisputeMechanismAuthorized ); let report = AuthorizedOutcomeReports::::take(market_id); - Ok(report.map(|r| r.outcome)) + + let res = ResultWithWeightInfo { + result: report.map(|r| r.outcome), + weight: T::WeightInfo::on_resolution_weight(), + }; + + Ok(res) + } + + fn exchange( + _: &Self::MarketId, + market: &MarketOf, + _: &OutcomeReport, + overall_imbalance: NegativeImbalanceOf, + ) -> Result>, DispatchError> { + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::Authorized, + Error::::MarketDoesNotHaveDisputeMechanismAuthorized + ); + // all funds to treasury + let res = ResultWithWeightInfo { + result: overall_imbalance, + weight: T::WeightInfo::exchange_weight(), + }; + + Ok(res) } fn get_auto_resolve( - _: &[MarketDispute], market_id: &Self::MarketId, market: &MarketOf, - ) -> Result, DispatchError> { + ) -> Result>, DispatchError> { ensure!( market.dispute_mechanism == MarketDisputeMechanism::Authorized, Error::::MarketDoesNotHaveDisputeMechanismAuthorized ); - Ok(Self::get_auto_resolve(market_id)) + + let res = ResultWithWeightInfo { + result: Self::get_auto_resolve(market_id), + weight: T::WeightInfo::get_auto_resolve_weight(), + }; + + Ok(res) } fn has_failed( - _: &[MarketDispute], _: &Self::MarketId, market: &MarketOf, - ) -> Result { + ) -> Result, DispatchError> { ensure!( market.dispute_mechanism == MarketDisputeMechanism::Authorized, Error::::MarketDoesNotHaveDisputeMechanismAuthorized ); - Ok(false) + let res = + ResultWithWeightInfo { result: false, weight: T::WeightInfo::has_failed_weight() }; + + Ok(res) + } + + fn on_global_dispute( + _: &Self::MarketId, + market: &MarketOf, + ) -> Result< + ResultWithWeightInfo>>, + DispatchError, + > { + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::Authorized, + Error::::MarketDoesNotHaveDisputeMechanismAuthorized + ); + + let res = ResultWithWeightInfo { + result: Vec::new(), + weight: T::WeightInfo::on_global_dispute_weight(), + }; + + Ok(res) + } + + fn clear( + market_id: &Self::MarketId, + market: &MarketOf, + ) -> Result, DispatchError> { + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::Authorized, + Error::::MarketDoesNotHaveDisputeMechanismAuthorized + ); + + AuthorizedOutcomeReports::::remove(market_id); + + let res = ResultWithWeightInfo { result: (), weight: T::WeightInfo::clear_weight() }; + + Ok(res) } } diff --git a/zrml/authorized/src/mock.rs b/zrml/authorized/src/mock.rs index 53e3a07eb..6f9193323 100644 --- a/zrml/authorized/src/mock.rs +++ b/zrml/authorized/src/mock.rs @@ -26,7 +26,6 @@ use frame_support::{ construct_runtime, ord_parameter_types, pallet_prelude::{DispatchError, Weight}, traits::Everything, - BoundedVec, }; use frame_system::EnsureSignedBy; use sp_runtime::{ @@ -40,8 +39,8 @@ use zeitgeist_primitives::{ }, traits::DisputeResolutionApi, types::{ - AccountIdTest, Asset, Balance, BlockNumber, BlockTest, Hash, Index, Market, MarketDispute, - MarketId, Moment, OutcomeReport, UncheckedExtrinsicTest, + AccountIdTest, Asset, Balance, BlockNumber, BlockTest, Hash, Index, Market, MarketId, + Moment, UncheckedExtrinsicTest, }, }; @@ -68,7 +67,6 @@ construct_runtime!( ord_parameter_types! { pub const AuthorizedDisputeResolutionUser: AccountIdTest = ALICE; - pub const MaxDisputes: u32 = 64; } // MockResolution implements DisputeResolutionApi with no-ops. @@ -79,7 +77,6 @@ impl DisputeResolutionApi for MockResolution { type Balance = Balance; type BlockNumber = BlockNumber; type MarketId = MarketId; - type MaxDisputes = MaxDisputes; type Moment = Moment; fn resolve( @@ -119,17 +116,6 @@ impl DisputeResolutionApi for MockResolution { ids.len() as u32 }) } - - fn get_disputes( - _market_id: &Self::MarketId, - ) -> BoundedVec, Self::MaxDisputes> { - BoundedVec::try_from(vec![MarketDispute { - at: 42u64, - by: BOB, - outcome: OutcomeReport::Scalar(42), - }]) - .unwrap() - } } impl crate::Config for Runtime { diff --git a/zrml/authorized/src/tests.rs b/zrml/authorized/src/tests.rs index f1a0901d1..515e656e2 100644 --- a/zrml/authorized/src/tests.rs +++ b/zrml/authorized/src/tests.rs @@ -27,7 +27,7 @@ use crate::{ use frame_support::{assert_noop, assert_ok, dispatch::DispatchError}; use zeitgeist_primitives::{ traits::DisputeApi, - types::{AuthorityReport, MarketDispute, MarketDisputeMechanism, MarketStatus, OutcomeReport}, + types::{AuthorityReport, MarketDisputeMechanism, MarketStatus, OutcomeReport}, }; use zrml_market_commons::Markets; @@ -157,22 +157,10 @@ fn authorize_market_outcome_fails_on_unauthorized_account() { }); } -#[test] -fn on_dispute_fails_if_disputes_is_not_empty() { - ExtBuilder::default().build().execute_with(|| { - let dispute = - MarketDispute { by: crate::mock::ALICE, at: 0, outcome: OutcomeReport::Scalar(1) }; - assert_noop!( - Authorized::on_dispute(&[dispute], &0, &market_mock::()), - Error::::OnlyOneDisputeAllowed - ); - }); -} - #[test] fn on_resolution_fails_if_no_report_was_submitted() { ExtBuilder::default().build().execute_with(|| { - let report = Authorized::on_resolution(&[], &0, &market_mock::()).unwrap(); + let report = Authorized::on_resolution(&0, &market_mock::()).unwrap().result; assert!(report.is_none()); }); } @@ -187,7 +175,7 @@ fn on_resolution_removes_stored_outcomes() { 0, OutcomeReport::Scalar(2) )); - assert_ok!(Authorized::on_resolution(&[], &0, &market)); + assert_ok!(Authorized::on_resolution(&0, &market)); assert_eq!(AuthorizedOutcomeReports::::get(0), None); }); } @@ -209,7 +197,7 @@ fn on_resolution_returns_the_reported_outcome() { OutcomeReport::Scalar(2) )); assert_eq!( - Authorized::on_resolution(&[], &0, &market).unwrap(), + Authorized::on_resolution(&0, &market).unwrap().result, Some(OutcomeReport::Scalar(2)) ); }); @@ -256,7 +244,7 @@ fn get_auto_resolve_works() { )); let now = frame_system::Pallet::::block_number(); let resolve_at = now + ::CorrectionPeriod::get(); - assert_eq!(Authorized::get_auto_resolve(&[], &0, &market).unwrap(), Some(resolve_at),); + assert_eq!(Authorized::get_auto_resolve(&0, &market).unwrap().result, Some(resolve_at),); }); } @@ -264,6 +252,6 @@ fn get_auto_resolve_works() { fn get_auto_resolve_returns_none_without_market_storage() { ExtBuilder::default().build().execute_with(|| { let market = market_mock::(); - assert_eq!(Authorized::get_auto_resolve(&[], &0, &market).unwrap(), None,); + assert_eq!(Authorized::get_auto_resolve(&0, &market).unwrap().result, None,); }); } diff --git a/zrml/authorized/src/weights.rs b/zrml/authorized/src/weights.rs index 685066985..1754ea7c7 100644 --- a/zrml/authorized/src/weights.rs +++ b/zrml/authorized/src/weights.rs @@ -48,6 +48,13 @@ use frame_support::{traits::Get, weights::Weight}; pub trait WeightInfoZeitgeist { fn authorize_market_outcome_first_report(m: u32) -> Weight; fn authorize_market_outcome_existing_report() -> Weight; + fn on_dispute_weight() -> Weight; + fn on_resolution_weight() -> Weight; + fn exchange_weight() -> Weight; + fn get_auto_resolve_weight() -> Weight; + fn has_failed_weight() -> Weight; + fn on_global_dispute_weight() -> Weight; + fn clear_weight() -> Weight; } /// Weight functions for zrml_authorized (automatically generated) @@ -70,4 +77,25 @@ impl WeightInfoZeitgeist for WeightInfo { .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } + fn on_dispute_weight() -> Weight { + Weight::from_ref_time(0) + } + fn on_resolution_weight() -> Weight { + Weight::from_ref_time(0) + } + fn exchange_weight() -> Weight { + Weight::from_ref_time(0) + } + fn get_auto_resolve_weight() -> Weight { + Weight::from_ref_time(0) + } + fn has_failed_weight() -> Weight { + Weight::from_ref_time(0) + } + fn on_global_dispute_weight() -> Weight { + Weight::from_ref_time(0) + } + fn clear_weight() -> Weight { + Weight::from_ref_time(0) + } } diff --git a/zrml/court/src/benchmarks.rs b/zrml/court/src/benchmarks.rs index d78f614d1..994ac8c2e 100644 --- a/zrml/court/src/benchmarks.rs +++ b/zrml/court/src/benchmarks.rs @@ -22,14 +22,13 @@ )] #![cfg(feature = "runtime-benchmarks")] -#[cfg(test)] -use crate::Pallet as Court; -use crate::{BalanceOf, Call, Config, CurrencyOf, Pallet}; +use crate::{market_mock, BalanceOf, Call, Config, CurrencyOf, Pallet as Court, Pallet}; use frame_benchmarking::{benchmarks, whitelisted_caller}; use frame_support::{dispatch::UnfilteredDispatchable, traits::Currency}; use frame_system::RawOrigin; use sp_runtime::traits::Bounded; -use zeitgeist_primitives::types::OutcomeReport; +use zeitgeist_primitives::{traits::DisputeApi, types::OutcomeReport}; +use zrml_market_commons::MarketCommonsPalletApi; fn deposit(caller: &T::AccountId) where @@ -66,6 +65,14 @@ benchmarks! { deposit_and_join_court::(&caller); }: _(RawOrigin::Signed(caller), market_id, outcome) + on_dispute_weight { + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + }: { + Court::::on_dispute(&market_id, &market).unwrap(); + } + impl_benchmark_test_suite!( Court, crate::mock::ExtBuilder::default().build(), diff --git a/zrml/court/src/court_pallet_api.rs b/zrml/court/src/court_pallet_api.rs index 73b9e2829..feebd7c35 100644 --- a/zrml/court/src/court_pallet_api.rs +++ b/zrml/court/src/court_pallet_api.rs @@ -1,3 +1,4 @@ +// Copyright 2023 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -15,6 +16,6 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use zeitgeist_primitives::traits::DisputeApi; +use zeitgeist_primitives::traits::{DisputeApi, DisputeMaxWeightApi}; -pub trait CourtPalletApi: DisputeApi {} +pub trait CourtPalletApi: DisputeApi + DisputeMaxWeightApi {} diff --git a/zrml/court/src/lib.rs b/zrml/court/src/lib.rs index cc258e378..7b7f049dc 100644 --- a/zrml/court/src/lib.rs +++ b/zrml/court/src/lib.rs @@ -52,7 +52,7 @@ mod pallet { use frame_support::{ dispatch::DispatchResult, ensure, - pallet_prelude::{CountedStorageMap, StorageDoubleMap, StorageValue, ValueQuery}, + pallet_prelude::{CountedStorageMap, StorageDoubleMap, StorageValue, ValueQuery, Weight}, traits::{ BalanceStatus, Currency, Get, Hooks, IsType, NamedReservableCurrency, Randomness, StorageVersion, @@ -66,8 +66,11 @@ mod pallet { ArithmeticError, DispatchError, SaturatedConversion, }; use zeitgeist_primitives::{ - traits::{DisputeApi, DisputeResolutionApi}, - types::{Asset, Market, MarketDispute, MarketDisputeMechanism, OutcomeReport}, + traits::{DisputeApi, DisputeMaxWeightApi, DisputeResolutionApi}, + types::{ + Asset, GlobalDisputeItem, Market, MarketDispute, MarketDisputeMechanism, MarketStatus, + OutcomeReport, ResultWithWeightInfo, + }, }; use zrml_market_commons::MarketCommonsPalletApi; @@ -87,6 +90,8 @@ mod pallet { as Currency<::AccountId>>::Balance; pub(crate) type CurrencyOf = <::MarketCommons as MarketCommonsPalletApi>::Currency; + pub(crate) type NegativeImbalanceOf = + as Currency<::AccountId>>::NegativeImbalance; pub(crate) type MarketIdOf = <::MarketCommons as MarketCommonsPalletApi>::MarketId; pub(crate) type MomentOf = <::MarketCommons as MarketCommonsPalletApi>::Moment; @@ -100,6 +105,33 @@ mod pallet { #[pallet::call] impl Pallet { + #[pallet::weight(1_000_000_000_000)] + pub fn appeal(origin: OriginFor, market_id: MarketIdOf) -> DispatchResult { + // TODO take a bond from the caller + ensure_signed(origin)?; + let market = T::MarketCommons::market(&market_id)?; + ensure!(market.status == MarketStatus::Disputed, Error::::MarketIsNotDisputed); + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::Court, + Error::::MarketDoesNotHaveCourtMechanism + ); + + let jurors: Vec<_> = Jurors::::iter().collect(); + // TODO &[] was disputes list before: how to handle it now without disputes from pm? + let necessary_jurors_num = Self::necessary_jurors_num(&[]); + let mut rng = Self::rng(); + let random_jurors = Self::random_jurors(&jurors, necessary_jurors_num, &mut rng); + let curr_block_num = >::block_number(); + let block_limit = curr_block_num.saturating_add(T::CourtCaseDuration::get()); + for (ai, _) in random_jurors { + RequestedJurors::::insert(market_id, ai, block_limit); + } + + Self::deposit_event(Event::MarketAppealed { market_id }); + + Ok(()) + } + // MARK(non-transactional): `remove_juror_from_all_courts_of_all_markets` is infallible. #[pallet::weight(T::WeightInfo::exit_court())] pub fn exit_court(origin: OriginFor) -> DispatchResult { @@ -200,6 +232,8 @@ mod pallet { NoVotes, /// Forbids voting of unknown accounts OnlyJurorsCanVote, + /// The market is not in a state where it can be disputed. + MarketIsNotDisputed, } #[pallet::event] @@ -210,6 +244,10 @@ mod pallet { { ExitedJuror(T::AccountId, Juror), JoinedJuror(T::AccountId, Juror), + /// A market has been appealed. + MarketAppealed { + market_id: MarketIdOf, + }, } #[pallet::hooks] @@ -393,7 +431,9 @@ mod pallet { // // Result is capped to `usize::MAX` or in other words, capped to a very, very, very // high number of jurors. - fn necessary_jurors_num(disputes: &[MarketDispute]) -> usize { + fn necessary_jurors_num( + disputes: &[MarketDispute>], + ) -> usize { let len = disputes.len(); INITIAL_JURORS_NUM.saturating_add(SUBSEQUENT_JURORS_FACTOR.saturating_mul(len)) } @@ -500,36 +540,64 @@ mod pallet { } } + impl DisputeMaxWeightApi for Pallet + where + T: Config, + { + fn on_dispute_max_weight() -> Weight { + T::WeightInfo::on_dispute_weight() + } + + fn on_resolution_max_weight() -> Weight { + T::WeightInfo::on_resolution_weight() + } + + fn exchange_max_weight() -> Weight { + T::WeightInfo::exchange_weight() + } + + fn get_auto_resolve_max_weight() -> Weight { + T::WeightInfo::get_auto_resolve_weight() + } + + fn has_failed_max_weight() -> Weight { + T::WeightInfo::has_failed_weight() + } + + fn on_global_dispute_max_weight() -> Weight { + T::WeightInfo::on_global_dispute_weight() + } + + fn clear_max_weight() -> Weight { + T::WeightInfo::clear_weight() + } + } + impl DisputeApi for Pallet where T: Config, { type AccountId = T::AccountId; type Balance = BalanceOf; + type NegativeImbalance = NegativeImbalanceOf; type BlockNumber = T::BlockNumber; type MarketId = MarketIdOf; type Moment = MomentOf; type Origin = T::Origin; fn on_dispute( - disputes: &[MarketDispute], - market_id: &Self::MarketId, + _: &Self::MarketId, market: &MarketOf, - ) -> DispatchResult { + ) -> Result, DispatchError> { ensure!( market.dispute_mechanism == MarketDisputeMechanism::Court, Error::::MarketDoesNotHaveCourtMechanism ); - let jurors: Vec<_> = Jurors::::iter().collect(); - let necessary_jurors_num = Self::necessary_jurors_num(disputes); - let mut rng = Self::rng(); - let random_jurors = Self::random_jurors(&jurors, necessary_jurors_num, &mut rng); - let curr_block_num = >::block_number(); - let block_limit = curr_block_num.saturating_add(T::CourtCaseDuration::get()); - for (ai, _) in random_jurors { - RequestedJurors::::insert(market_id, ai, block_limit); - } - Ok(()) + + let res = + ResultWithWeightInfo { result: (), weight: T::WeightInfo::on_dispute_weight() }; + + Ok(res) } // Set jurors that sided on the second most voted outcome as tardy. Jurors are only @@ -537,10 +605,9 @@ mod pallet { // voted outcome (winner of the losing majority) are placed as tardy instead of // being slashed. fn on_resolution( - _: &[MarketDispute], market_id: &Self::MarketId, market: &MarketOf, - ) -> Result, DispatchError> { + ) -> Result>, DispatchError> { ensure!( market.dispute_mechanism == MarketDisputeMechanism::Court, Error::::MarketDoesNotHaveCourtMechanism @@ -562,31 +629,100 @@ mod pallet { Self::slash_losers_to_award_winners(&valid_winners_and_losers, &first)?; let _ = Votes::::clear_prefix(market_id, u32::max_value(), None); let _ = RequestedJurors::::clear_prefix(market_id, u32::max_value(), None); - Ok(Some(first)) + + let res = ResultWithWeightInfo { + result: Some(first), + weight: T::WeightInfo::on_resolution_weight(), + }; + + Ok(res) + } + + fn exchange( + _: &Self::MarketId, + market: &MarketOf, + _: &OutcomeReport, + overall_imbalance: NegativeImbalanceOf, + ) -> Result>, DispatchError> { + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::Court, + Error::::MarketDoesNotHaveCourtMechanism + ); + // TODO all funds to treasury? + + let res = ResultWithWeightInfo { + result: overall_imbalance, + weight: T::WeightInfo::exchange_weight(), + }; + Ok(res) } fn get_auto_resolve( - _: &[MarketDispute], _: &Self::MarketId, market: &MarketOf, - ) -> Result, DispatchError> { + ) -> Result>, DispatchError> { ensure!( market.dispute_mechanism == MarketDisputeMechanism::Court, Error::::MarketDoesNotHaveCourtMechanism ); - Ok(None) + + let res = ResultWithWeightInfo { + result: None, + weight: T::WeightInfo::get_auto_resolve_weight(), + }; + + Ok(res) } fn has_failed( - _: &[MarketDispute], _: &Self::MarketId, market: &MarketOf, - ) -> Result { + ) -> Result, DispatchError> { + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::Court, + Error::::MarketDoesNotHaveCourtMechanism + ); + + let res = + ResultWithWeightInfo { result: false, weight: T::WeightInfo::has_failed_weight() }; + + Ok(res) + } + + fn on_global_dispute( + _: &Self::MarketId, + market: &MarketOf, + ) -> Result< + ResultWithWeightInfo>>, + DispatchError, + > { + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::Court, + Error::::MarketDoesNotHaveCourtMechanism + ); + + let res = ResultWithWeightInfo { + result: Vec::new(), + weight: T::WeightInfo::on_global_dispute_weight(), + }; + + Ok(res) + } + + fn clear( + market_id: &Self::MarketId, + market: &MarketOf, + ) -> Result, DispatchError> { ensure!( market.dispute_mechanism == MarketDisputeMechanism::Court, Error::::MarketDoesNotHaveCourtMechanism ); - Ok(false) + let _ = Votes::::clear_prefix(market_id, u32::max_value(), None); + let _ = RequestedJurors::::clear_prefix(market_id, u32::max_value(), None); + + let res = ResultWithWeightInfo { result: (), weight: T::WeightInfo::clear_weight() }; + + Ok(res) } } @@ -624,3 +760,35 @@ mod pallet { (T::BlockNumber, OutcomeReport), >; } + +#[cfg(any(feature = "runtime-benchmarks", test))] +pub(crate) fn market_mock() -> MarketOf +where + T: crate::Config, +{ + use frame_support::traits::Get; + use sp_runtime::traits::AccountIdConversion; + use zeitgeist_primitives::types::{Asset, MarketBonds, ScoringRule}; + + zeitgeist_primitives::types::Market { + base_asset: Asset::Ztg, + creation: zeitgeist_primitives::types::MarketCreation::Permissionless, + creator_fee: 0, + creator: T::PalletId::get().into_account_truncating(), + market_type: zeitgeist_primitives::types::MarketType::Scalar(0..=100), + dispute_mechanism: zeitgeist_primitives::types::MarketDisputeMechanism::Court, + metadata: Default::default(), + oracle: T::PalletId::get().into_account_truncating(), + period: zeitgeist_primitives::types::MarketPeriod::Block(Default::default()), + deadlines: zeitgeist_primitives::types::Deadlines { + grace_period: 1_u32.into(), + oracle_duration: 1_u32.into(), + dispute_duration: 1_u32.into(), + }, + report: None, + resolved_outcome: None, + scoring_rule: ScoringRule::CPMM, + status: zeitgeist_primitives::types::MarketStatus::Disputed, + bonds: MarketBonds::default(), + } +} diff --git a/zrml/court/src/mock.rs b/zrml/court/src/mock.rs index 8152b44b7..c3e73da85 100644 --- a/zrml/court/src/mock.rs +++ b/zrml/court/src/mock.rs @@ -24,7 +24,7 @@ use frame_support::{ pallet_prelude::{DispatchError, Weight}, parameter_types, traits::Everything, - BoundedVec, PalletId, + PalletId, }; use sp_runtime::{ testing::Header, @@ -37,8 +37,8 @@ use zeitgeist_primitives::{ }, traits::DisputeResolutionApi, types::{ - AccountIdTest, Asset, Balance, BlockNumber, BlockTest, Hash, Index, Market, MarketDispute, - MarketId, Moment, UncheckedExtrinsicTest, + AccountIdTest, Asset, Balance, BlockNumber, BlockTest, Hash, Index, Market, MarketId, + Moment, UncheckedExtrinsicTest, }, }; @@ -75,7 +75,6 @@ impl DisputeResolutionApi for NoopResolution { type Balance = Balance; type BlockNumber = BlockNumber; type MarketId = MarketId; - type MaxDisputes = u32; type Moment = Moment; fn resolve( @@ -105,12 +104,6 @@ impl DisputeResolutionApi for NoopResolution { fn remove_auto_resolve(_market_id: &Self::MarketId, _resolve_at: Self::BlockNumber) -> u32 { 0u32 } - - fn get_disputes( - _market_id: &Self::MarketId, - ) -> BoundedVec, Self::MaxDisputes> { - Default::default() - } } impl crate::Config for Runtime { diff --git a/zrml/court/src/tests.rs b/zrml/court/src/tests.rs index 46624e6a5..0932af74b 100644 --- a/zrml/court/src/tests.rs +++ b/zrml/court/src/tests.rs @@ -20,8 +20,8 @@ use crate::{ mock::{ - Balances, Court, ExtBuilder, Origin, RandomnessCollectiveFlip, Runtime, System, ALICE, BOB, - CHARLIE, INITIAL_BALANCE, + Balances, Court, ExtBuilder, MarketCommons, Origin, RandomnessCollectiveFlip, Runtime, + System, ALICE, BOB, CHARLIE, INITIAL_BALANCE, }, Error, Juror, JurorStatus, Jurors, MarketOf, RequestedJurors, Votes, }; @@ -37,6 +37,7 @@ use zeitgeist_primitives::{ MarketPeriod, MarketStatus, MarketType, OutcomeReport, ScoringRule, }, }; +use zrml_market_commons::MarketCommonsPalletApi; const DEFAULT_MARKET: MarketOf = Market { base_asset: Asset::Ztg, @@ -51,9 +52,9 @@ const DEFAULT_MARKET: MarketOf = Market { deadlines: Deadlines { grace_period: 1_u64, oracle_duration: 1_u64, dispute_duration: 1_u64 }, report: None, resolved_outcome: None, - status: MarketStatus::Closed, + status: MarketStatus::Disputed, scoring_rule: ScoringRule::CPMM, - bonds: MarketBonds { creation: None, oracle: None, outsider: None }, + bonds: MarketBonds { creation: None, oracle: None, outsider: None, dispute: None }, }; const DEFAULT_SET_OF_JURORS: &[(u128, Juror)] = &[ (7, Juror { status: JurorStatus::Ok }), @@ -132,7 +133,7 @@ fn on_dispute_denies_non_court_markets() { let mut market = DEFAULT_MARKET; market.dispute_mechanism = MarketDisputeMechanism::SimpleDisputes; assert_noop!( - Court::on_dispute(&[], &0, &market), + Court::on_dispute(&0, &market), Error::::MarketDoesNotHaveCourtMechanism ); }); @@ -144,19 +145,20 @@ fn on_resolution_denies_non_court_markets() { let mut market = DEFAULT_MARKET; market.dispute_mechanism = MarketDisputeMechanism::SimpleDisputes; assert_noop!( - Court::on_resolution(&[], &0, &market), + Court::on_resolution(&0, &market), Error::::MarketDoesNotHaveCourtMechanism ); }); } #[test] -fn on_dispute_stores_jurors_that_should_vote() { +fn appeal_stores_jurors_that_should_vote() { ExtBuilder::default().build().execute_with(|| { setup_blocks(123); let _ = Court::join_court(Origin::signed(ALICE)); let _ = Court::join_court(Origin::signed(BOB)); - Court::on_dispute(&[], &0, &DEFAULT_MARKET).unwrap(); + MarketCommons::push_market(DEFAULT_MARKET).unwrap(); + Court::appeal(Origin::signed(ALICE), 0).unwrap(); assert_noop!( Court::join_court(Origin::signed(ALICE)), Error::::JurorAlreadyExists @@ -173,11 +175,12 @@ fn on_resolution_awards_winners_and_slashes_losers() { Court::join_court(Origin::signed(ALICE)).unwrap(); Court::join_court(Origin::signed(BOB)).unwrap(); Court::join_court(Origin::signed(CHARLIE)).unwrap(); - Court::on_dispute(&[], &0, &DEFAULT_MARKET).unwrap(); + MarketCommons::push_market(DEFAULT_MARKET).unwrap(); + Court::appeal(Origin::signed(ALICE), 0).unwrap(); Court::vote(Origin::signed(ALICE), 0, OutcomeReport::Scalar(1)).unwrap(); Court::vote(Origin::signed(BOB), 0, OutcomeReport::Scalar(2)).unwrap(); Court::vote(Origin::signed(CHARLIE), 0, OutcomeReport::Scalar(3)).unwrap(); - let _ = Court::on_resolution(&[], &0, &DEFAULT_MARKET).unwrap(); + let _ = Court::on_resolution(&0, &DEFAULT_MARKET).unwrap(); assert_eq!(Balances::free_balance(ALICE), 998 * BASE + 3 * BASE); assert_eq!(Balances::reserved_balance_named(&Court::reserve_id(), &ALICE), 2 * BASE); assert_eq!(Balances::free_balance(BOB), 996 * BASE); @@ -194,11 +197,12 @@ fn on_resolution_decides_market_outcome_based_on_the_majority() { Court::join_court(Origin::signed(ALICE)).unwrap(); Court::join_court(Origin::signed(BOB)).unwrap(); Court::join_court(Origin::signed(CHARLIE)).unwrap(); - Court::on_dispute(&[], &0, &DEFAULT_MARKET).unwrap(); + MarketCommons::push_market(DEFAULT_MARKET).unwrap(); + Court::appeal(Origin::signed(ALICE), 0).unwrap(); Court::vote(Origin::signed(ALICE), 0, OutcomeReport::Scalar(1)).unwrap(); Court::vote(Origin::signed(BOB), 0, OutcomeReport::Scalar(1)).unwrap(); Court::vote(Origin::signed(CHARLIE), 0, OutcomeReport::Scalar(2)).unwrap(); - let outcome = Court::on_resolution(&[], &0, &DEFAULT_MARKET).unwrap(); + let outcome = Court::on_resolution(&0, &DEFAULT_MARKET).unwrap().result; assert_eq!(outcome, Some(OutcomeReport::Scalar(1))); }); } @@ -210,8 +214,9 @@ fn on_resolution_sets_late_jurors_as_tardy() { Court::join_court(Origin::signed(ALICE)).unwrap(); Court::join_court(Origin::signed(BOB)).unwrap(); Court::vote(Origin::signed(ALICE), 0, OutcomeReport::Scalar(1)).unwrap(); - Court::on_dispute(&[], &0, &DEFAULT_MARKET).unwrap(); - let _ = Court::on_resolution(&[], &0, &DEFAULT_MARKET).unwrap(); + MarketCommons::push_market(DEFAULT_MARKET).unwrap(); + Court::appeal(Origin::signed(ALICE), 0).unwrap(); + let _ = Court::on_resolution(&0, &DEFAULT_MARKET).unwrap(); assert_eq!(Jurors::::get(ALICE).unwrap().status, JurorStatus::Ok); assert_eq!(Jurors::::get(BOB).unwrap().status, JurorStatus::Tardy); }); @@ -224,11 +229,12 @@ fn on_resolution_sets_jurors_that_voted_on_the_second_most_voted_outcome_as_tard Court::join_court(Origin::signed(ALICE)).unwrap(); Court::join_court(Origin::signed(BOB)).unwrap(); Court::join_court(Origin::signed(CHARLIE)).unwrap(); - Court::on_dispute(&[], &0, &DEFAULT_MARKET).unwrap(); + MarketCommons::push_market(DEFAULT_MARKET).unwrap(); + Court::appeal(Origin::signed(ALICE), 0).unwrap(); Court::vote(Origin::signed(ALICE), 0, OutcomeReport::Scalar(1)).unwrap(); Court::vote(Origin::signed(BOB), 0, OutcomeReport::Scalar(1)).unwrap(); Court::vote(Origin::signed(CHARLIE), 0, OutcomeReport::Scalar(2)).unwrap(); - let _ = Court::on_resolution(&[], &0, &DEFAULT_MARKET).unwrap(); + let _ = Court::on_resolution(&0, &DEFAULT_MARKET).unwrap(); assert_eq!(Jurors::::get(CHARLIE).unwrap().status, JurorStatus::Tardy); }); } @@ -241,8 +247,9 @@ fn on_resolution_punishes_tardy_jurors_that_failed_to_vote_a_second_time() { Court::join_court(Origin::signed(BOB)).unwrap(); Court::set_stored_juror_as_tardy(&BOB).unwrap(); Court::vote(Origin::signed(ALICE), 0, OutcomeReport::Scalar(1)).unwrap(); - Court::on_dispute(&[], &0, &DEFAULT_MARKET).unwrap(); - let _ = Court::on_resolution(&[], &0, &DEFAULT_MARKET).unwrap(); + MarketCommons::push_market(DEFAULT_MARKET).unwrap(); + Court::appeal(Origin::signed(ALICE), 0).unwrap(); + let _ = Court::on_resolution(&0, &DEFAULT_MARKET).unwrap(); let join_court_stake = 40000000000; let slash = join_court_stake / 5; assert_eq!(Balances::free_balance(Court::treasury_account_id()), INITIAL_BALANCE + slash); @@ -258,11 +265,12 @@ fn on_resolution_removes_requested_jurors_and_votes() { Court::join_court(Origin::signed(ALICE)).unwrap(); Court::join_court(Origin::signed(BOB)).unwrap(); Court::join_court(Origin::signed(CHARLIE)).unwrap(); - Court::on_dispute(&[], &0, &DEFAULT_MARKET).unwrap(); + MarketCommons::push_market(DEFAULT_MARKET).unwrap(); + Court::appeal(Origin::signed(ALICE), 0).unwrap(); Court::vote(Origin::signed(ALICE), 0, OutcomeReport::Scalar(1)).unwrap(); Court::vote(Origin::signed(BOB), 0, OutcomeReport::Scalar(1)).unwrap(); Court::vote(Origin::signed(CHARLIE), 0, OutcomeReport::Scalar(2)).unwrap(); - let _ = Court::on_resolution(&[], &0, &DEFAULT_MARKET).unwrap(); + let _ = Court::on_resolution(&0, &DEFAULT_MARKET).unwrap(); assert_eq!(RequestedJurors::::iter().count(), 0); assert_eq!(Votes::::iter().count(), 0); }); diff --git a/zrml/court/src/weights.rs b/zrml/court/src/weights.rs index 96b5e68a9..f50651eee 100644 --- a/zrml/court/src/weights.rs +++ b/zrml/court/src/weights.rs @@ -49,6 +49,13 @@ pub trait WeightInfoZeitgeist { fn exit_court() -> Weight; fn join_court() -> Weight; fn vote() -> Weight; + fn on_dispute_weight() -> Weight; + fn on_resolution_weight() -> Weight; + fn exchange_weight() -> Weight; + fn get_auto_resolve_weight() -> Weight; + fn has_failed_weight() -> Weight; + fn on_global_dispute_weight() -> Weight; + fn clear_weight() -> Weight; } /// Weight functions for zrml_court (automatically generated) @@ -79,4 +86,25 @@ impl WeightInfoZeitgeist for WeightInfo { .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + fn on_dispute_weight() -> Weight { + Weight::from_ref_time(0) + } + fn on_resolution_weight() -> Weight { + Weight::from_ref_time(0) + } + fn exchange_weight() -> Weight { + Weight::from_ref_time(0) + } + fn get_auto_resolve_weight() -> Weight { + Weight::from_ref_time(0) + } + fn has_failed_weight() -> Weight { + Weight::from_ref_time(0) + } + fn on_global_dispute_weight() -> Weight { + Weight::from_ref_time(0) + } + fn clear_weight() -> Weight { + Weight::from_ref_time(0) + } } diff --git a/zrml/market-commons/src/tests.rs b/zrml/market-commons/src/tests.rs index 2d027bb95..61c5feb5e 100644 --- a/zrml/market-commons/src/tests.rs +++ b/zrml/market-commons/src/tests.rs @@ -48,7 +48,7 @@ const MARKET_DUMMY: Market::MarketCommons as MarketCommonsPalletApi>::MarketId: From<::MarketId>, } - admin_destroy_disputed_market{ + admin_destroy_disputed_market { // The number of assets. let a in (T::MinCategories::get().into())..T::MaxCategories::get().into(); - // The number of disputes. - let d in 1..T::MaxDisputes::get(); // The number of market ids per open time frame. let o in 0..63; // The number of market ids per close time frame. @@ -243,15 +241,20 @@ benchmarks! { OutcomeReport::Categorical(0u16), )?; + >::mutate_market(&market_id, |market| { + market.dispute_mechanism = MarketDisputeMechanism::Authorized; + Ok(()) + })?; + let pool_id = >::market_pool(&market_id)?; - for i in 1..=d { - let outcome = OutcomeReport::Categorical((i % a).saturated_into()); - let disputor = account("disputor", i, 0); - let dispute_bond = crate::pallet::default_dispute_bond::(i as usize); - T::AssetManager::deposit(Asset::Ztg, &disputor, dispute_bond)?; - let _ = Pallet::::dispute(RawOrigin::Signed(disputor).into(), market_id, outcome)?; - } + let disputor = account("disputor", 1, 0); + ::AssetManager::deposit( + Asset::Ztg, + &disputor, + u128::MAX.saturated_into(), + ).unwrap(); + let _ = Pallet::::dispute(RawOrigin::Signed(disputor).into(), market_id)?; let market = >::market(&market_id)?; @@ -261,26 +264,34 @@ benchmarks! { }; for i in 0..o { + // shift of 1 to avoid collisions with first market id 0 MarketIdsPerOpenTimeFrame::::try_mutate( Pallet::::calculate_time_frame_of_moment(range_start), - |ids| ids.try_push(i.into()), + |ids| ids.try_push((i + 1).into()), ).unwrap(); } for i in 0..c { + // shift of 65 to avoid collisions with `o` MarketIdsPerCloseTimeFrame::::try_mutate( Pallet::::calculate_time_frame_of_moment(range_end), - |ids| ids.try_push(i.into()), + |ids| ids.try_push((i + 65).into()), ).unwrap(); } - let disputes = Disputes::::get(market_id); - let last_dispute = disputes.last().unwrap(); - let resolves_at = last_dispute.at.saturating_add(market.deadlines.dispute_duration); + AuthorizedPallet::::authorize_market_outcome( + T::AuthorizedDisputeResolutionOrigin::successful_origin(), + market_id.into(), + OutcomeReport::Categorical(0u16), + )?; + + let now = >::block_number(); + let resolves_at = now.saturating_add(::CorrectionPeriod::get()); for i in 0..r { + // shift of 129 to avoid collisions with `o` and `c` MarketIdsPerDisputeBlock::::try_mutate( resolves_at, - |ids| ids.try_push(i.into()), + |ids| ids.try_push((i + 129).into()), ).unwrap(); } @@ -317,25 +328,28 @@ benchmarks! { }; for i in 0..o { + // shift of 1 to avoid collisions with first market id 0 MarketIdsPerOpenTimeFrame::::try_mutate( Pallet::::calculate_time_frame_of_moment(range_start), - |ids| ids.try_push(i.into()), + |ids| ids.try_push((i + 1).into()), ).unwrap(); } for i in 0..c { + // shift of 65 to avoid collisions with `o` MarketIdsPerCloseTimeFrame::::try_mutate( Pallet::::calculate_time_frame_of_moment(range_end), - |ids| ids.try_push(i.into()), + |ids| ids.try_push((i + 65).into()), ).unwrap(); } let report_at = market.report.unwrap().at; let resolves_at = report_at.saturating_add(market.deadlines.dispute_duration); for i in 0..r { + // shift of 129 to avoid collisions with `o` and `c` MarketIdsPerReportBlock::::try_mutate( resolves_at, - |ids| ids.try_push(i.into()), + |ids| ids.try_push((i + 129).into()), ).unwrap(); } @@ -466,24 +480,21 @@ benchmarks! { let outcome = OutcomeReport::Scalar(0); let disputor = account("disputor", 1, 0); - let dispute_bond = crate::pallet::default_dispute_bond::(0_usize); - T::AssetManager::deposit( + ::AssetManager::deposit( Asset::Ztg, &disputor, - dispute_bond, - )?; - Pallet::::dispute(RawOrigin::Signed(disputor).into(), market_id, outcome)?; - let disputes = Disputes::::get(market_id); - // Authorize the outcome with the highest number of correct reporters to maximize the - // number of transfers required (0 has (d+1)//2 reports, 1 has d//2 reports). + u128::MAX.saturated_into(), + ).unwrap(); + Pallet::::dispute(RawOrigin::Signed(disputor).into(), market_id)?; + + let now = >::block_number(); AuthorizedPallet::::authorize_market_outcome( T::AuthorizedDisputeResolutionOrigin::successful_origin(), market_id.into(), OutcomeReport::Scalar(0), )?; - let last_dispute = disputes.last().unwrap(); - let resolves_at = last_dispute.at.saturating_add(market.deadlines.dispute_duration); + let resolves_at = now.saturating_add(::CorrectionPeriod::get()); for i in 0..r { MarketIdsPerDisputeBlock::::try_mutate( resolves_at, @@ -518,17 +529,14 @@ benchmarks! { Ok(()) })?; - let outcome = OutcomeReport::Categorical(0u16); let disputor = account("disputor", 1, 0); - let dispute_bond = crate::pallet::default_dispute_bond::(0_usize); - T::AssetManager::deposit( + ::AssetManager::deposit( Asset::Ztg, &disputor, - dispute_bond, - )?; - Pallet::::dispute(RawOrigin::Signed(disputor).into(), market_id, outcome)?; + u128::MAX.saturated_into(), + ).unwrap(); + Pallet::::dispute(RawOrigin::Signed(disputor).into(), market_id)?; - let disputes = Disputes::::get(market_id); // Authorize the outcome with the highest number of correct reporters to maximize the // number of transfers required (0 has (d+1)//2 reports, 1 has d//2 reports). AuthorizedPallet::::authorize_market_outcome( @@ -537,9 +545,9 @@ benchmarks! { OutcomeReport::Categorical(0), )?; - let last_dispute = disputes.last().unwrap(); let market = >::market(&market_id)?; - let resolves_at = last_dispute.at.saturating_add(market.deadlines.dispute_duration); + let now = >::block_number(); + let resolves_at = now.saturating_add(::CorrectionPeriod::get()); for i in 0..r { MarketIdsPerDisputeBlock::::try_mutate( resolves_at, @@ -801,22 +809,35 @@ benchmarks! { market_ids_1.try_push(i.saturated_into()).unwrap(); } - let max_dispute_len = T::MaxDisputes::get(); + let disputor: T::AccountId = account("Disputor", 1, 0); + ::AssetManager::deposit( + Asset::Ztg, + &disputor, + u128::MAX.saturated_into(), + ).unwrap(); + let _ = Call::::dispute { + market_id, + } + .dispatch_bypass_filter(RawOrigin::Signed(disputor).into())?; + + let max_dispute_len = ::MaxDisputes::get(); for i in 0..max_dispute_len { // ensure that the MarketIdsPerDisputeBlock does not interfere // with the start_global_dispute execution block >::set_block_number(i.saturated_into()); - let disputor: T::AccountId = account("Disputor", i, 0); - T::AssetManager::deposit(Asset::Ztg, &disputor, (u128::MAX).saturated_into())?; - let _ = Call::::dispute { - market_id, - outcome: OutcomeReport::Scalar(i.into()), - } - .dispatch_bypass_filter(RawOrigin::Signed(disputor.clone()).into())?; + let reserver: T::AccountId = account("Reserver", i, 0); + ::AssetManager::deposit(Asset::Ztg, &reserver, (u128::MAX).saturated_into())?; + let market_id_number: u128 = market_id.saturated_into::(); + let _ = zrml_simple_disputes::Call::::suggest_outcome { + market_id: market_id_number.saturated_into(), + outcome: OutcomeReport::Scalar(i.saturated_into()), + }.dispatch_bypass_filter(RawOrigin::Signed(reserver.clone()).into())?; } let market = >::market(&market_id.saturated_into()).unwrap(); - let disputes = Disputes::::get(market_id); + let market_id_number: u128 = market_id.saturated_into::(); + let market_id_simple: zrml_simple_disputes::MarketIdOf = market_id_number.saturated_into(); + let disputes = zrml_simple_disputes::Disputes::::get(market_id_simple); let last_dispute = disputes.last().unwrap(); let dispute_duration_ends_at_block = last_dispute.at + market.deadlines.dispute_duration; let mut market_ids_2: BoundedVec, CacheSize> = BoundedVec::try_from( @@ -861,9 +882,7 @@ benchmarks! { let market = >::market(&market_id)?; - // only one dispute allowed for authorized mdm - let dispute_outcome = OutcomeReport::Scalar(1u128); - let call = Call::::dispute { market_id, outcome: dispute_outcome }; + let call = Call::::dispute { market_id }; }: { call.dispatch_bypass_filter(RawOrigin::Signed(caller).into())?; } @@ -911,10 +930,8 @@ benchmarks! { Pallet::::dispute( RawOrigin::Signed(caller).into(), market_id, - OutcomeReport::Categorical(0), )?; - // Authorize the outcome with the highest number of correct reporters to maximize the - // number of transfers required (0 has (d+1)//2 reports, 1 has d//2 reports). + AuthorizedPallet::::authorize_market_outcome( T::AuthorizedDisputeResolutionOrigin::successful_origin(), market_id.into(), @@ -956,10 +973,8 @@ benchmarks! { Pallet::::dispute( RawOrigin::Signed(caller).into(), market_id, - OutcomeReport::Scalar(1) )?; - // Authorize the outcome with the highest number of correct reporters to maximize the - // number of transfers required (0 has (d+1)//2 reports, 1 has d//2 reports). + AuthorizedPallet::::authorize_market_outcome( T::AuthorizedDisputeResolutionOrigin::successful_origin(), market_id.into(), diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 7fda9ce26..399f73b07 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -57,21 +57,25 @@ mod pallet { use orml_traits::{MultiCurrency, NamedMultiReservableCurrency}; use sp_arithmetic::per_things::{Perbill, Percent}; use sp_runtime::{ - traits::{CheckedDiv, Saturating, Zero}, + traits::{Saturating, Zero}, DispatchError, DispatchResult, SaturatedConversion, }; use zeitgeist_primitives::{ constants::MILLISECS_PER_BLOCK, traits::{DisputeApi, DisputeResolutionApi, Swaps, ZeitgeistAssetManager}, types::{ - Asset, Bond, Deadlines, Market, MarketBonds, MarketCreation, MarketDispute, - MarketDisputeMechanism, MarketPeriod, MarketStatus, MarketType, MultiHash, - OutcomeReport, Report, ScalarPosition, ScoringRule, SubsidyUntil, + Asset, Bond, Deadlines, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, + MarketPeriod, MarketStatus, MarketType, MultiHash, OldMarketDispute, OutcomeReport, + Report, ResultWithWeightInfo, ScalarPosition, ScoringRule, SubsidyUntil, }, }; #[cfg(feature = "with-global-disputes")] - use zrml_global_disputes::GlobalDisputesPalletApi; + use { + zeitgeist_primitives::types::GlobalDisputeItem, + zrml_global_disputes::GlobalDisputesPalletApi, + }; + use zeitgeist_primitives::traits::DisputeMaxWeightApi; use zrml_liquidity_mining::LiquidityMiningPalletApi; use zrml_market_commons::MarketCommonsPalletApi; @@ -314,7 +318,6 @@ mod pallet { ) .max(T::WeightInfo::admin_destroy_disputed_market( T::MaxCategories::get().into(), - T::MaxDisputes::get(), CacheSize::get(), CacheSize::get(), CacheSize::get(), @@ -360,21 +363,9 @@ mod pallet { let open_ids_len = Self::clear_auto_open(&market_id)?; let close_ids_len = Self::clear_auto_close(&market_id)?; - let (ids_len, disputes_len) = Self::clear_auto_resolve(&market_id)?; - // `Disputes` is emtpy unless the market is disputed, so this is just a defensive - // check. - if market.status == MarketStatus::Disputed { - for (index, dispute) in Disputes::::take(market_id).iter().enumerate() { - T::AssetManager::unreserve_named( - &Self::reserve_id(), - Asset::Ztg, - &dispute.by, - default_dispute_bond::(index), - ); - } - } + let (ids_len, _) = Self::clear_auto_resolve(&market_id)?; + Self::clear_dispute_mechanism(&market_id)?; >::remove_market(&market_id)?; - Disputes::::remove(market_id); Self::deposit_event(Event::MarketDestroyed(market_id)); @@ -395,7 +386,6 @@ mod pallet { Ok(( Some(T::WeightInfo::admin_destroy_disputed_market( category_count, - disputes_len, open_ids_len, close_ids_len, ids_len, @@ -627,53 +617,53 @@ mod pallet { /// # Weight /// /// Complexity: `O(n)`, where `n` is the number of outstanding disputes. - #[pallet::weight(T::WeightInfo::dispute_authorized())] + #[pallet::weight( + T::WeightInfo::dispute_authorized().saturating_add( + T::Court::on_dispute_max_weight().saturating_add( + T::SimpleDisputes::on_dispute_max_weight() + ) + ) + )] #[transactional] pub fn dispute( origin: OriginFor, #[pallet::compact] market_id: MarketIdOf, - outcome: OutcomeReport, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let disputes = Disputes::::get(market_id); - let curr_block_num = >::block_number(); + let market = >::market(&market_id)?; - ensure!( - matches!(market.status, MarketStatus::Reported | MarketStatus::Disputed), - Error::::InvalidMarketStatus - ); - let num_disputes: u32 = disputes.len().saturated_into(); - Self::validate_dispute(&disputes, &market, num_disputes, &outcome)?; - T::AssetManager::reserve_named( - &Self::reserve_id(), - Asset::Ztg, - &who, - default_dispute_bond::(disputes.len()), - )?; - // TODO(#782): use multiple benchmarks paths for different dispute mechanisms - match market.dispute_mechanism { + ensure!(market.status == MarketStatus::Reported, Error::::InvalidMarketStatus); + + let weight = match market.dispute_mechanism { MarketDisputeMechanism::Authorized => { - T::Authorized::on_dispute(&disputes, &market_id, &market)? + T::Authorized::on_dispute(&market_id, &market)?; + T::WeightInfo::dispute_authorized() } MarketDisputeMechanism::Court => { - T::Court::on_dispute(&disputes, &market_id, &market)? + let court_weight = T::Court::on_dispute(&market_id, &market)?.weight; + T::WeightInfo::dispute_authorized() + .saturating_sub(T::Authorized::on_dispute_max_weight()) + .saturating_add(court_weight) } MarketDisputeMechanism::SimpleDisputes => { - T::SimpleDisputes::on_dispute(&disputes, &market_id, &market)? + let sd_weight = T::SimpleDisputes::on_dispute(&market_id, &market)?.weight; + T::WeightInfo::dispute_authorized() + .saturating_sub(T::Authorized::on_dispute_max_weight()) + .saturating_add(sd_weight) } - } + }; + + let dispute_bond = T::DisputeBond::get(); + T::AssetManager::reserve_named(&Self::reserve_id(), Asset::Ztg, &who, dispute_bond)?; - Self::set_market_as_disputed(&market, &market_id)?; - let market_dispute = MarketDispute { at: curr_block_num, by: who, outcome }; - >::try_mutate(market_id, |disputes| { - disputes.try_push(market_dispute.clone()).map_err(|_| >::StorageOverflow) + >::mutate_market(&market_id, |m| { + m.status = MarketStatus::Disputed; + m.bonds.dispute = Some(Bond::new(who.clone(), dispute_bond)); + Ok(()) })?; - Self::deposit_event(Event::MarketDisputed( - market_id, - MarketStatus::Disputed, - market_dispute, - )); - Ok((Some(T::WeightInfo::dispute_authorized())).into()) + + Self::deposit_event(Event::MarketDisputed(market_id, MarketStatus::Disputed)); + Ok((Some(weight)).into()) } /// Create a permissionless market, buy complete sets and deploy a pool with specified @@ -1456,34 +1446,55 @@ mod pallet { Error::::InvalidDisputeMechanism ); - let disputes = >::get(market_id); - ensure!( - disputes.len() == T::MaxDisputes::get() as usize, - Error::::MaxDisputesNeeded - ); - ensure!( T::GlobalDisputes::is_not_started(&market_id), Error::::GlobalDisputeAlreadyStarted ); - // add report outcome to voting choices - if let Some(report) = &market.report { - T::GlobalDisputes::push_voting_outcome( - &market_id, - report.outcome.clone(), - &report.by, - >::zero(), - )?; - } + let report = market.report.as_ref().ok_or(Error::::MarketIsNotReported)?; + + // TODO(#782): use multiple benchmarks paths for different dispute mechanisms - for (index, MarketDispute { at: _, by, outcome }) in disputes.iter().enumerate() { - let dispute_bond = default_dispute_bond::(index); + let res_0 = match market.dispute_mechanism { + MarketDisputeMechanism::Authorized => { + T::Authorized::has_failed(&market_id, &market)? + } + MarketDisputeMechanism::Court => T::Court::has_failed(&market_id, &market)?, + MarketDisputeMechanism::SimpleDisputes => { + T::SimpleDisputes::has_failed(&market_id, &market)? + } + }; + let has_failed = res_0.result; + ensure!(has_failed, Error::::MarketDisputeMechanismNotFailed); + + let res_1 = match market.dispute_mechanism { + MarketDisputeMechanism::Authorized => { + T::Authorized::on_global_dispute(&market_id, &market)? + } + MarketDisputeMechanism::Court => { + T::Court::on_global_dispute(&market_id, &market)? + } + MarketDisputeMechanism::SimpleDisputes => { + T::SimpleDisputes::on_global_dispute(&market_id, &market)? + } + }; + + let gd_items = res_1.result; + + T::GlobalDisputes::push_voting_outcome( + &market_id, + report.outcome.clone(), + &report.by, + >::zero(), + )?; + + // push vote outcomes other than the report outcome + for GlobalDisputeItem { outcome, owner, initial_vote_amount } in gd_items { T::GlobalDisputes::push_voting_outcome( &market_id, - outcome.clone(), - by, - dispute_bond, + outcome, + &owner, + initial_vote_amount, )?; } @@ -1547,6 +1558,7 @@ mod pallet { type Authorized: zrml_authorized::AuthorizedPalletApi< AccountId = Self::AccountId, Balance = BalanceOf, + NegativeImbalance = NegativeImbalanceOf, BlockNumber = Self::BlockNumber, MarketId = MarketIdOf, Moment = MomentOf, @@ -1560,6 +1572,7 @@ mod pallet { type Court: zrml_court::CourtPalletApi< AccountId = Self::AccountId, Balance = BalanceOf, + NegativeImbalance = NegativeImbalanceOf, BlockNumber = Self::BlockNumber, MarketId = MarketIdOf, Moment = MomentOf, @@ -1573,11 +1586,6 @@ mod pallet { #[pallet::constant] type DisputeBond: Get>; - /// The additional amount of currency that must be bonded when creating a subsequent - /// dispute. - #[pallet::constant] - type DisputeFactor: Get>; - /// Event type Event: From> + IsType<::Event>; @@ -1678,9 +1686,10 @@ mod pallet { type ResolveOrigin: EnsureOrigin; /// See [`DisputeApi`]. - type SimpleDisputes: DisputeApi< + type SimpleDisputes: zrml_simple_disputes::SimpleDisputesPalletApi< AccountId = Self::AccountId, Balance = BalanceOf, + NegativeImbalance = NegativeImbalanceOf, BlockNumber = Self::BlockNumber, MarketId = MarketIdOf, Moment = MomentOf, @@ -1751,10 +1760,8 @@ mod pallet { MarketStartTooSoon, /// The point in time when the market becomes active is too late. MarketStartTooLate, - /// The maximum number of disputes has been reached. - MaxDisputesReached, - /// The maximum number of disputes is needed for this operation. - MaxDisputesNeeded, + /// The market dispute mechanism has not failed. + MarketDisputeMechanismNotFailed, /// Tried to settle missing bond. MissingBond, /// The number of categories for a categorical market is too low. @@ -1828,8 +1835,8 @@ mod pallet { MarketInsufficientSubsidy(MarketIdOf, MarketStatus), /// A market has been closed \[market_id\] MarketClosed(MarketIdOf), - /// A market has been disputed \[market_id, new_market_status, new_outcome\] - MarketDisputed(MarketIdOf, MarketStatus, MarketDispute), + /// A market has been disputed \[market_id, new_market_status\] + MarketDisputed(MarketIdOf, MarketStatus), /// An advised market has ended before it was approved or rejected. \[market_id\] MarketExpired(MarketIdOf), /// A pending market has been rejected as invalid with a reason. \[market_id, reject_reason\] @@ -1981,7 +1988,7 @@ mod pallet { _, Blake2_128Concat, MarketIdOf, - BoundedVec, T::MaxDisputes>, + BoundedVec, T::MaxDisputes>, ValueQuery, >; @@ -2068,13 +2075,16 @@ mod pallet { impl_unreserve_bond!(unreserve_creation_bond, creation); impl_unreserve_bond!(unreserve_oracle_bond, oracle); impl_unreserve_bond!(unreserve_outsider_bond, outsider); + impl_unreserve_bond!(unreserve_dispute_bond, dispute); impl_slash_bond!(slash_creation_bond, creation); impl_slash_bond!(slash_oracle_bond, oracle); impl_slash_bond!(slash_outsider_bond, outsider); + impl_slash_bond!(slash_dispute_bond, dispute); impl_repatriate_bond!(repatriate_oracle_bond, oracle); impl_is_bond_pending!(is_creation_bond_pending, creation); impl_is_bond_pending!(is_oracle_bond_pending, oracle); impl_is_bond_pending!(is_outsider_bond_pending, outsider); + impl_is_bond_pending!(is_dispute_bond_pending, dispute); fn slash_pending_bonds(market_id: &MarketIdOf, market: &MarketOf) -> DispatchResult { if Self::is_creation_bond_pending(market_id, market, false) { @@ -2086,6 +2096,9 @@ mod pallet { if Self::is_outsider_bond_pending(market_id, market, false) { Self::slash_outsider_bond(market_id, None)?; } + if Self::is_dispute_bond_pending(market_id, market, false) { + Self::slash_dispute_bond(market_id, None)?; + } Ok(()) } @@ -2194,7 +2207,7 @@ mod pallet { /// Clears this market from being stored for automatic resolution. fn clear_auto_resolve(market_id: &MarketIdOf) -> Result<(u32, u32), DispatchError> { let market = >::market(market_id)?; - let (ids_len, disputes_len) = match market.status { + let (ids_len, mdm_len) = match market.status { MarketStatus::Reported => { let report = market.report.ok_or(Error::::MarketIsNotReported)?; let dispute_duration_ends_at_block = @@ -2209,30 +2222,45 @@ mod pallet { ) } MarketStatus::Disputed => { - let disputes = Disputes::::get(market_id); // TODO(#782): use multiple benchmarks paths for different dispute mechanisms - let auto_resolve_block_opt = match market.dispute_mechanism { - MarketDisputeMechanism::Authorized => { - T::Authorized::get_auto_resolve(&disputes, market_id, &market)? - } - MarketDisputeMechanism::Court => { - T::Court::get_auto_resolve(&disputes, market_id, &market)? - } - MarketDisputeMechanism::SimpleDisputes => { - T::SimpleDisputes::get_auto_resolve(&disputes, market_id, &market)? - } - }; + let ResultWithWeightInfo { result: auto_resolve_block_opt, weight: _ } = + match market.dispute_mechanism { + MarketDisputeMechanism::Authorized => { + T::Authorized::get_auto_resolve(market_id, &market)? + } + MarketDisputeMechanism::Court => { + T::Court::get_auto_resolve(market_id, &market)? + } + MarketDisputeMechanism::SimpleDisputes => { + T::SimpleDisputes::get_auto_resolve(market_id, &market)? + } + }; if let Some(auto_resolve_block) = auto_resolve_block_opt { let ids_len = remove_auto_resolve::(market_id, auto_resolve_block); - (ids_len, disputes.len() as u32) + (ids_len, 0u32) } else { - (0u32, disputes.len() as u32) + (0u32, 0u32) } } _ => (0u32, 0u32), }; - Ok((ids_len, disputes_len)) + Ok((ids_len, mdm_len)) + } + + /// The dispute mechanism is intended to clear its own storage here. + fn clear_dispute_mechanism(market_id: &MarketIdOf) -> DispatchResult { + let market = >::market(market_id)?; + + // TODO(#782): use multiple benchmarks paths for different dispute mechanisms + match market.dispute_mechanism { + MarketDisputeMechanism::Authorized => T::Authorized::clear(market_id, &market)?, + MarketDisputeMechanism::Court => T::Court::clear(market_id, &market)?, + MarketDisputeMechanism::SimpleDisputes => { + T::SimpleDisputes::clear(market_id, &market)? + } + }; + Ok(()) } pub(crate) fn do_buy_complete_set( @@ -2311,26 +2339,6 @@ mod pallet { } } - fn ensure_can_not_dispute_the_same_outcome( - disputes: &[MarketDispute], - report: &Report, - outcome: &OutcomeReport, - ) -> DispatchResult { - if let Some(last_dispute) = disputes.last() { - ensure!(&last_dispute.outcome != outcome, Error::::CannotDisputeSameOutcome); - } else { - ensure!(&report.outcome != outcome, Error::::CannotDisputeSameOutcome); - } - - Ok(()) - } - - #[inline] - fn ensure_disputes_does_not_exceed_max_disputes(num_disputes: u32) -> DispatchResult { - ensure!(num_disputes < T::MaxDisputes::get(), Error::::MaxDisputesReached); - Ok(()) - } - fn ensure_market_is_active(market: &MarketOf) -> DispatchResult { ensure!(market.status == MarketStatus::Active, Error::::MarketIsNotActive); Ok(()) @@ -2507,6 +2515,8 @@ mod pallet { } } + /// Handle a market resolution, which is currently in the reported state. + /// Returns the resolved outcome of a market, which is the reported outcome. fn resolve_reported_market( market_id: &MarketIdOf, market: &MarketOf, @@ -2527,49 +2537,116 @@ mod pallet { Ok(report.outcome.clone()) } + /// Handle a market resolution, which is currently in the disputed state. + /// Returns the resolved outcome of a market. fn resolve_disputed_market( market_id: &MarketIdOf, market: &MarketOf, - ) -> Result { + ) -> Result, DispatchError> { let report = market.report.as_ref().ok_or(Error::::MarketIsNotReported)?; - let disputes = Disputes::::get(market_id); + let mut weight = Weight::zero(); + + let res: ResultWithWeightInfo = + Self::get_resolved_outcome(market_id, market, &report.outcome)?; + let resolved_outcome = res.result; + weight = weight.saturating_add(res.weight); + let imbalance_left = Self::settle_bonds(market_id, market, &resolved_outcome, report)?; + + let remainder = match market.dispute_mechanism { + MarketDisputeMechanism::Authorized => { + let res = T::Authorized::exchange( + market_id, + market, + &resolved_outcome, + imbalance_left, + )?; + let remainder = res.result; + weight = weight.saturating_add(res.weight); + remainder + } + MarketDisputeMechanism::Court => { + let res = + T::Court::exchange(market_id, market, &resolved_outcome, imbalance_left)?; + let remainder = res.result; + weight = weight.saturating_add(res.weight); + remainder + } + MarketDisputeMechanism::SimpleDisputes => { + let res = T::SimpleDisputes::exchange( + market_id, + market, + &resolved_outcome, + imbalance_left, + )?; + let remainder = res.result; + weight = weight.saturating_add(res.weight); + remainder + } + }; + + T::Slash::on_unbalanced(remainder); + + let res = ResultWithWeightInfo { result: resolved_outcome, weight }; + + Ok(res) + } + + /// Get the outcome the market should resolve to. + pub(crate) fn get_resolved_outcome( + market_id: &MarketIdOf, + market: &MarketOf, + reported_outcome: &OutcomeReport, + ) -> Result, DispatchError> { let mut resolved_outcome_option = None; + let mut weight = Weight::zero(); #[cfg(feature = "with-global-disputes")] if let Some(o) = T::GlobalDisputes::determine_voting_winner(market_id) { resolved_outcome_option = Some(o); } - // TODO(#782): use multiple benchmarks paths for different dispute mechanisms - // Try to get the outcome of the MDM. If the MDM failed to resolve, default to // the oracle's report. if resolved_outcome_option.is_none() { resolved_outcome_option = match market.dispute_mechanism { MarketDisputeMechanism::Authorized => { - T::Authorized::on_resolution(&disputes, market_id, market)? + let res = T::Authorized::on_resolution(market_id, market)?; + weight = weight.saturating_add(res.weight); + res.result } MarketDisputeMechanism::Court => { - T::Court::on_resolution(&disputes, market_id, market)? + let res = T::Court::on_resolution(market_id, market)?; + weight = weight.saturating_add(res.weight); + res.result } MarketDisputeMechanism::SimpleDisputes => { - T::SimpleDisputes::on_resolution(&disputes, market_id, market)? + let res = T::SimpleDisputes::on_resolution(market_id, market)?; + weight = weight.saturating_add(res.weight); + res.result } }; } let resolved_outcome = - resolved_outcome_option.unwrap_or_else(|| report.outcome.clone()); + resolved_outcome_option.unwrap_or_else(|| reported_outcome.clone()); + + let res = ResultWithWeightInfo { result: resolved_outcome, weight }; - let mut correct_reporters: Vec = Vec::new(); + Ok(res) + } - // If the oracle reported right, return the OracleBond, otherwise slash it to - // pay the correct reporters. + /// Manage the outstanding bonds (oracle, outsider, dispute) of the market. + fn settle_bonds( + market_id: &MarketIdOf, + market: &MarketOf, + resolved_outcome: &OutcomeReport, + report: &Report, + ) -> Result, DispatchError> { let mut overall_imbalance = NegativeImbalanceOf::::zero(); let report_by_oracle = report.by == market.oracle; - let is_correct = report.outcome == resolved_outcome; + let is_correct = &report.outcome == resolved_outcome; let unreserve_outsider = || -> DispatchResult { if Self::is_outsider_bond_pending(market_id, market, true) { @@ -2607,42 +2684,21 @@ mod pallet { } } - for (i, dispute) in disputes.iter().enumerate() { - let actual_bond = default_dispute_bond::(i); - if dispute.outcome == resolved_outcome { - T::AssetManager::unreserve_named( - &Self::reserve_id(), - Asset::Ztg, - &dispute.by, - actual_bond, - ); - - correct_reporters.push(dispute.by.clone()); - } else { - let (imbalance, _) = CurrencyOf::::slash_reserved_named( - &Self::reserve_id(), - &dispute.by, - actual_bond.saturated_into::().saturated_into(), - ); - overall_imbalance.subsume(imbalance); - } - } - - // Fold all the imbalances into one and reward the correct reporters. The - // number of correct reporters might be zero if the market defaults to the - // report after abandoned dispute. In that case, the rewards remain slashed. - if let Some(reward_per_each) = - overall_imbalance.peek().checked_div(&correct_reporters.len().saturated_into()) - { - for correct_reporter in &correct_reporters { - let (actual_reward, leftover) = overall_imbalance.split(reward_per_each); - overall_imbalance = leftover; - CurrencyOf::::resolve_creating(correct_reporter, actual_reward); + if let Some(bond) = &market.bonds.dispute { + if !bond.is_settled { + if is_correct { + let imb = Self::slash_dispute_bond(market_id, None)?; + overall_imbalance.subsume(imb); + } else { + // If the report outcome was wrong, the dispute was justified + Self::unreserve_dispute_bond(market_id)?; + CurrencyOf::::resolve_creating(&bond.who, overall_imbalance); + overall_imbalance = NegativeImbalanceOf::::zero(); + } } } - T::Slash::on_unbalanced(overall_imbalance); - Ok(resolved_outcome) + Ok(overall_imbalance) } pub fn on_resolution( @@ -2657,7 +2713,11 @@ mod pallet { let resolved_outcome = match market.status { MarketStatus::Reported => Self::resolve_reported_market(market_id, market)?, - MarketStatus::Disputed => Self::resolve_disputed_market(market_id, market)?, + MarketStatus::Disputed => { + let res = Self::resolve_disputed_market(market_id, market)?; + total_weight = total_weight.saturating_add(res.weight); + res.result + } _ => return Err(Error::::InvalidMarketStatus.into()), }; let clean_up_weight = Self::clean_up_pool(market, market_id, &resolved_outcome)?; @@ -2673,7 +2733,7 @@ mod pallet { m.resolved_outcome = Some(resolved_outcome.clone()); Ok(()) })?; - Disputes::::remove(market_id); + Self::deposit_event(Event::MarketResolved( *market_id, MarketStatus::Resolved, @@ -2923,20 +2983,6 @@ mod pallet { )) } - // If the market is already disputed, does nothing. - fn set_market_as_disputed( - market: &MarketOf, - market_id: &MarketIdOf, - ) -> DispatchResult { - if market.status != MarketStatus::Disputed { - >::mutate_market(market_id, |m| { - m.status = MarketStatus::Disputed; - Ok(()) - })?; - } - Ok(()) - } - // If a market has a pool that is `Active`, then changes from `Active` to `Clean`. If // the market does not exist or the market does not have a pool, does nothing. fn clean_up_pool( @@ -2996,19 +3042,6 @@ mod pallet { Ok(T::WeightInfo::start_subsidy(total_assets.saturated_into())) } - fn validate_dispute( - disputes: &[MarketDispute], - market: &MarketOf, - num_disputes: u32, - outcome_report: &OutcomeReport, - ) -> DispatchResult { - let report = market.report.as_ref().ok_or(Error::::MarketIsNotReported)?; - ensure!(market.matches_outcome_report(outcome_report), Error::::OutcomeMismatch); - Self::ensure_can_not_dispute_the_same_outcome(disputes, report, outcome_report)?; - Self::ensure_disputes_does_not_exceed_max_disputes(num_disputes)?; - Ok(()) - } - fn construct_market( base_asset: Asset>, creator: T::AccountId, @@ -3075,16 +3108,6 @@ mod pallet { } } - // No-one can bound more than BalanceOf, therefore, this functions saturates - pub(crate) fn default_dispute_bond(n: usize) -> BalanceOf - where - T: Config, - { - T::DisputeBond::get().saturating_add( - T::DisputeFactor::get().saturating_mul(n.saturated_into::().into()), - ) - } - fn remove_item(items: &mut BoundedVec, item: &I) { if let Some(pos) = items.iter().position(|i| i == item) { items.swap_remove(pos); @@ -3110,7 +3133,6 @@ mod pallet { type Balance = BalanceOf; type BlockNumber = T::BlockNumber; type MarketId = MarketIdOf; - type MaxDisputes = T::MaxDisputes; type Moment = MomentOf; fn resolve( @@ -3141,12 +3163,5 @@ mod pallet { fn remove_auto_resolve(market_id: &Self::MarketId, resolve_at: Self::BlockNumber) -> u32 { remove_auto_resolve::(market_id, resolve_at) } - - fn get_disputes( - market_id: &Self::MarketId, - ) -> BoundedVec, Self::MaxDisputes> - { - Disputes::::get(market_id) - } } } diff --git a/zrml/prediction-markets/src/migrations.rs b/zrml/prediction-markets/src/migrations.rs index 15c18bf56..65950184c 100644 --- a/zrml/prediction-markets/src/migrations.rs +++ b/zrml/prediction-markets/src/migrations.rs @@ -42,9 +42,7 @@ use zeitgeist_primitives::types::{ Asset, Bond, Deadlines, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, MarketType, OutcomeReport, Report, ScoringRule, }; -#[cfg(feature = "try-runtime")] -use zrml_market_commons::MarketCommonsPalletApi; -use zrml_market_commons::Pallet as MarketCommonsPallet; +use zrml_market_commons::{MarketCommonsPalletApi, Pallet as MarketCommonsPallet}; #[cfg(any(feature = "try-runtime", test))] const MARKET_COMMONS: &[u8] = b"MarketCommons"; @@ -95,57 +93,71 @@ pub(crate) type Markets = StorageMap< OldMarketOf, >; -pub struct AddOutsiderBond(PhantomData); +pub struct AddOutsiderAndDisputeBond(PhantomData); -impl OnRuntimeUpgrade for AddOutsiderBond { +impl OnRuntimeUpgrade for AddOutsiderAndDisputeBond { fn on_runtime_upgrade() -> Weight { let mut total_weight = T::DbWeight::get().reads(1); let market_commons_version = StorageVersion::get::>(); if market_commons_version != MARKET_COMMONS_REQUIRED_STORAGE_VERSION { log::info!( - "AddOutsiderBond: market-commons version is {:?}, but {:?} is required", + "AddOutsiderAndDisputeBond: market-commons version is {:?}, but {:?} is required", market_commons_version, MARKET_COMMONS_REQUIRED_STORAGE_VERSION, ); return total_weight; } - log::info!("AddOutsiderBond: Starting..."); + log::info!("AddOutsiderAndDisputeBond: Starting..."); let mut translated = 0u64; - zrml_market_commons::Markets::::translate::, _>(|_key, old_market| { - translated.saturating_inc(); - - let new_market = Market { - base_asset: old_market.base_asset, - creator: old_market.creator, - creation: old_market.creation, - creator_fee: old_market.creator_fee, - oracle: old_market.oracle, - metadata: old_market.metadata, - market_type: old_market.market_type, - period: old_market.period, - scoring_rule: old_market.scoring_rule, - status: old_market.status, - report: old_market.report, - resolved_outcome: old_market.resolved_outcome, - dispute_mechanism: old_market.dispute_mechanism, - deadlines: old_market.deadlines, - bonds: MarketBonds { - creation: old_market.bonds.creation, - oracle: old_market.bonds.oracle, - outsider: None, - }, - }; + zrml_market_commons::Markets::::translate::, _>( + |market_id, old_market| { + translated.saturating_inc(); - Some(new_market) - }); - log::info!("AddOutsiderBond: Upgraded {} markets.", translated); + let mut dispute_bond = None; + // SimpleDisputes is regarded in the following migration `MoveDataToSimpleDisputes` + if let MarketDisputeMechanism::Authorized = old_market.dispute_mechanism { + total_weight = total_weight.saturating_add(T::DbWeight::get().reads(1)); + let old_disputes = crate::Disputes::::get(market_id); + if let Some(first_dispute) = old_disputes.first() { + let OldMarketDispute { at: _, by, outcome: _ } = first_dispute; + dispute_bond = Some(Bond::new(by.clone(), T::DisputeBond::get())); + } + } + + let new_market = Market { + base_asset: old_market.base_asset, + creator: old_market.creator, + creation: old_market.creation, + creator_fee: old_market.creator_fee, + oracle: old_market.oracle, + metadata: old_market.metadata, + market_type: old_market.market_type, + period: old_market.period, + scoring_rule: old_market.scoring_rule, + status: old_market.status, + report: old_market.report, + resolved_outcome: old_market.resolved_outcome, + dispute_mechanism: old_market.dispute_mechanism, + deadlines: old_market.deadlines, + bonds: MarketBonds { + creation: old_market.bonds.creation, + oracle: old_market.bonds.oracle, + outsider: None, + dispute: dispute_bond, + }, + }; + + Some(new_market) + }, + ); + log::info!("AddOutsiderAndDisputeBond: Upgraded {} markets.", translated); total_weight = total_weight.saturating_add(T::DbWeight::get().reads_writes(translated, translated)); StorageVersion::new(MARKET_COMMONS_NEXT_STORAGE_VERSION).put::>(); total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); - log::info!("AddOutsiderBond: Done!"); + log::info!("AddOutsiderAndDisputeBond: Done!"); total_weight } @@ -201,10 +213,31 @@ impl OnRuntimeUpgrade for AddOutsiderBo assert_eq!(new_market.dispute_mechanism, old_market.dispute_mechanism); assert_eq!(new_market.bonds.oracle, old_market.bonds.oracle); assert_eq!(new_market.bonds.creation, old_market.bonds.creation); - // new field + // new fields assert_eq!(new_market.bonds.outsider, None); + // other dispute mechanisms are regarded in the migration after this migration + if let MarketDisputeMechanism::Authorized = new_market.dispute_mechanism { + let old_disputes = crate::Disputes::::get(market_id); + if let Some(first_dispute) = old_disputes.first() { + let OldMarketDispute { at: _, by, outcome: _ } = first_dispute; + assert_eq!( + new_market.bonds.dispute, + Some(Bond { + who: by.clone(), + value: T::DisputeBond::get(), + is_settled: false + }) + ); + } + } else { + assert_eq!(new_market.bonds.dispute, None); + } } - log::info!("AddOutsiderBond: Market Counter post-upgrade is {}!", new_market_count); + + log::info!( + "AddOutsiderAndDisputeBond: Market Counter post-upgrade is {}!", + new_market_count + ); assert!(new_market_count > 0); Ok(()) } @@ -214,7 +247,7 @@ impl OnRuntimeUpgrade for AddOutsiderBo mod tests { use super::*; use crate::{ - mock::{ExtBuilder, Runtime}, + mock::{DisputeBond, ExtBuilder, Runtime}, MarketIdOf, MarketOf, }; use frame_support::{ @@ -226,7 +259,7 @@ mod tests { fn on_runtime_upgrade_increments_the_storage_version() { ExtBuilder::default().build().execute_with(|| { set_up_version(); - AddOutsiderBond::::on_runtime_upgrade(); + AddOutsiderAndDisputeBond::::on_runtime_upgrade(); assert_eq!( StorageVersion::get::>(), MARKET_COMMONS_NEXT_STORAGE_VERSION @@ -238,29 +271,51 @@ mod tests { fn on_runtime_upgrade_is_noop_if_versions_are_not_correct() { ExtBuilder::default().build().execute_with(|| { // Don't set up chain to signal that storage is already up to date. - let (_, new_markets) = construct_old_new_tuple(); + let (_, new_markets) = construct_old_new_tuple(None); populate_test_data::, MarketOf>( MARKET_COMMONS, MARKETS, new_markets.clone(), ); - AddOutsiderBond::::on_runtime_upgrade(); + AddOutsiderAndDisputeBond::::on_runtime_upgrade(); + let actual = >::market(&0u128).unwrap(); + assert_eq!(actual, new_markets[0]); + }); + } + + #[test] + fn on_runtime_upgrade_correctly_updates_markets_with_none_disputor() { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + let (old_markets, new_markets) = construct_old_new_tuple(None); + populate_test_data::, OldMarketOf>( + MARKET_COMMONS, + MARKETS, + old_markets, + ); + AddOutsiderAndDisputeBond::::on_runtime_upgrade(); let actual = >::market(&0u128).unwrap(); assert_eq!(actual, new_markets[0]); }); } #[test] - fn on_runtime_upgrade_correctly_updates_markets() { + fn on_runtime_upgrade_correctly_updates_markets_with_some_disputor() { ExtBuilder::default().build().execute_with(|| { set_up_version(); - let (old_markets, new_markets) = construct_old_new_tuple(); + let mut disputes = crate::Disputes::::get(0); + let disputor = crate::mock::EVE; + let dispute = + OldMarketDispute { at: 0, by: disputor, outcome: OutcomeReport::Categorical(0u16) }; + disputes.try_push(dispute).unwrap(); + crate::Disputes::::insert(0, disputes); + let (old_markets, new_markets) = construct_old_new_tuple(Some(disputor)); populate_test_data::, OldMarketOf>( MARKET_COMMONS, MARKETS, old_markets, ); - AddOutsiderBond::::on_runtime_upgrade(); + AddOutsiderAndDisputeBond::::on_runtime_upgrade(); let actual = >::market(&0u128).unwrap(); assert_eq!(actual, new_markets[0]); }); @@ -271,7 +326,9 @@ mod tests { .put::>(); } - fn construct_old_new_tuple() -> (Vec>, Vec>) { + fn construct_old_new_tuple( + disputor: Option, + ) -> (Vec>, Vec>) { let base_asset = Asset::Ztg; let creator = 999; let creator_fee = 1; @@ -290,10 +347,12 @@ mod tests { creation: Some(Bond::new(creator, ::ValidityBond::get())), oracle: Some(Bond::new(creator, ::OracleBond::get())), }; + let dispute_bond = disputor.map(|disputor| Bond::new(disputor, DisputeBond::get())); let new_bonds = MarketBonds { creation: Some(Bond::new(creator, ::ValidityBond::get())), oracle: Some(Bond::new(creator, ::OracleBond::get())), outsider: None, + dispute: dispute_bond, }; let old_market = OldMarket { @@ -348,6 +407,404 @@ mod tests { } } +use frame_support::dispatch::EncodeLike; +use sp_runtime::SaturatedConversion; +use zeitgeist_primitives::types::{MarketDispute, OldMarketDispute}; + +const PREDICTION_MARKETS_REQUIRED_STORAGE_VERSION: u16 = 6; +const PREDICTION_MARKETS_NEXT_STORAGE_VERSION: u16 = 7; + +#[cfg(feature = "try-runtime")] +type OldDisputesOf = frame_support::BoundedVec< + OldMarketDispute< + ::AccountId, + ::BlockNumber, + >, + ::MaxDisputes, +>; + +pub struct MoveDataToSimpleDisputes(PhantomData); + +impl OnRuntimeUpgrade + for MoveDataToSimpleDisputes +where + ::MarketId: EncodeLike< + <::MarketCommons as MarketCommonsPalletApi>::MarketId, + >, +{ + fn on_runtime_upgrade() -> Weight { + use orml_traits::NamedMultiReservableCurrency; + + let mut total_weight = T::DbWeight::get().reads(1); + let pm_version = StorageVersion::get::>(); + if pm_version != PREDICTION_MARKETS_REQUIRED_STORAGE_VERSION { + log::info!( + "MoveDataToSimpleDisputes: prediction-markets version is {:?}, but {:?} is required", + pm_version, + PREDICTION_MARKETS_REQUIRED_STORAGE_VERSION, + ); + return total_weight; + } + log::info!("MoveDataToSimpleDisputes: Starting..."); + + total_weight = total_weight.saturating_add(T::DbWeight::get().reads(1)); + + // important drain disputes storage item from prediction markets pallet + for (market_id, old_disputes) in crate::Disputes::::drain() { + total_weight = total_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + if let Ok(market) = >::market(&market_id) { + match market.dispute_mechanism { + MarketDisputeMechanism::Authorized => continue, + // just transform SimpleDispute disputes + MarketDisputeMechanism::SimpleDisputes => (), + MarketDisputeMechanism::Court => continue, + } + } else { + log::warn!( + "MoveDataToSimpleDisputes: Could not find market with market id {:?}", + market_id + ); + } + + total_weight = total_weight.saturating_add(T::DbWeight::get().reads(1)); + let mut new_disputes = zrml_simple_disputes::Disputes::::get(market_id); + for (i, old_dispute) in old_disputes.iter().enumerate() { + let bond = zrml_simple_disputes::default_outcome_bond::(i); + let new_dispute = MarketDispute { + at: old_dispute.at, + by: old_dispute.by.clone(), + outcome: old_dispute.outcome.clone(), + bond, + }; + let res = new_disputes.try_push(new_dispute); + if res.is_err() { + log::error!( + "MoveDataToSimpleDisputes: Could not push dispute for market id {:?}", + market_id + ); + } + + // switch to new reserve identifier for simple disputes + let sd_reserve_id = >::reserve_id(); + let pm_reserve_id = >::reserve_id(); + + // charge weight defensivly for unreserve_named + // https://github.com/open-web3-stack/open-runtime-module-library/blob/24f0a8b6e04e1078f70d0437fb816337cdf4f64c/tokens/src/lib.rs#L1516-L1547 + total_weight = total_weight.saturating_add(T::DbWeight::get().reads_writes(4, 3)); + let reserved_balance = ::AssetManager::reserved_balance_named( + &pm_reserve_id, + Asset::Ztg, + &old_dispute.by, + ); + if reserved_balance < bond.saturated_into::().saturated_into() { + // warns for battery station market id 386 + // https://discord.com/channels/737780518313000960/817041223201587230/958682619413934151 + log::warn!( + "MoveDataToSimpleDisputes: Could not unreserve {:?} for {:?} because \ + reserved balance is only {:?}. Market id: {:?}", + bond, + old_dispute.by, + reserved_balance, + market_id, + ); + } + ::AssetManager::unreserve_named( + &pm_reserve_id, + Asset::Ztg, + &old_dispute.by, + bond.saturated_into::().saturated_into(), + ); + + // charge weight defensivly for reserve_named + // https://github.com/open-web3-stack/open-runtime-module-library/blob/24f0a8b6e04e1078f70d0437fb816337cdf4f64c/tokens/src/lib.rs#L1486-L1499 + total_weight = total_weight.saturating_add(T::DbWeight::get().reads_writes(3, 3)); + let res = ::AssetManager::reserve_named( + &sd_reserve_id, + Asset::Ztg, + &old_dispute.by, + bond.saturated_into::().saturated_into(), + ); + if res.is_err() { + log::error!( + "MoveDataToSimpleDisputes: Could not reserve bond for dispute caller {:?} \ + and market id {:?}", + old_dispute.by, + market_id + ); + } + } + + total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); + zrml_simple_disputes::Disputes::::insert(market_id, new_disputes); + } + + StorageVersion::new(PREDICTION_MARKETS_NEXT_STORAGE_VERSION).put::>(); + total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); + log::info!("MoveDataToSimpleDisputes: Done!"); + total_weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + log::info!("MoveDataToSimpleDisputes: Start pre_upgrade!"); + + let old_disputes = crate::Disputes::::iter().collect::>(); + Self::set_temp_storage(old_disputes, "old_disputes"); + + Ok(()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + let old_disputes: BTreeMap, OldDisputesOf> = + Self::get_temp_storage("old_disputes").unwrap(); + + log::info!("MoveDataToSimpleDisputes: (post_upgrade) Start first try-runtime part!"); + + for (market_id, o) in old_disputes.iter() { + let market = >::market(market_id) + .expect(&format!("Market for market id {:?} not found", market_id)[..]); + + // market id is a reference, but we need the raw value to encode with the where clause + let disputes = zrml_simple_disputes::Disputes::::get(*market_id); + + match market.dispute_mechanism { + MarketDisputeMechanism::Authorized => { + let simple_disputes_count = disputes.iter().count(); + assert_eq!(simple_disputes_count, 0); + continue; + } + MarketDisputeMechanism::SimpleDisputes => { + let new_count = disputes.iter().count(); + let old_count = o.iter().count(); + assert_eq!(new_count, old_count); + } + MarketDisputeMechanism::Court => { + panic!("Court should not be contained at all.") + } + } + } + + log::info!("MoveDataToSimpleDisputes: (post_upgrade) Start second try-runtime part!"); + + assert!(crate::Disputes::::iter().count() == 0); + + for (market_id, new_disputes) in zrml_simple_disputes::Disputes::::iter() { + let old_disputes = old_disputes + .get(&market_id.saturated_into::().saturated_into()) + .expect(&format!("Disputes for market {:?} not found", market_id)[..]); + + let market = ::MarketCommons::market(&market_id) + .expect(&format!("Market for market id {:?} not found", market_id)[..]); + match market.dispute_mechanism { + MarketDisputeMechanism::Authorized => { + panic!("Authorized should not be contained in simple disputes."); + } + MarketDisputeMechanism::SimpleDisputes => (), + MarketDisputeMechanism::Court => { + panic!("Court should not be contained in simple disputes."); + } + } + + for (i, new_dispute) in new_disputes.iter().enumerate() { + let old_dispute = + old_disputes.get(i).expect(&format!("Dispute at index {} not found", i)[..]); + assert_eq!(new_dispute.at, old_dispute.at); + assert_eq!(new_dispute.by, old_dispute.by); + assert_eq!(new_dispute.outcome, old_dispute.outcome); + assert_eq!(new_dispute.bond, zrml_simple_disputes::default_outcome_bond::(i)); + } + } + + log::info!("MoveDataToSimpleDisputes: Done! (post_upgrade)"); + Ok(()) + } +} + +#[cfg(test)] +mod tests_simple_disputes_migration { + use super::*; + use crate::{ + mock::{DisputeBond, ExtBuilder, Runtime}, + MarketOf, + }; + use orml_traits::NamedMultiReservableCurrency; + use zrml_market_commons::MarketCommonsPalletApi; + + #[test] + fn on_runtime_upgrade_increments_the_storage_version() { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + MoveDataToSimpleDisputes::::on_runtime_upgrade(); + assert_eq!( + StorageVersion::get::>(), + PREDICTION_MARKETS_NEXT_STORAGE_VERSION + ); + }); + } + + #[test] + fn on_runtime_upgrade_is_noop_if_versions_are_not_correct() { + ExtBuilder::default().build().execute_with(|| { + // Don't set up chain to signal that storage is already up to date. + let market_id = 0u128; + let mut disputes = zrml_simple_disputes::Disputes::::get(market_id); + let dispute = MarketDispute { + at: 42u64, + by: 0u128, + outcome: OutcomeReport::Categorical(0u16), + bond: DisputeBond::get(), + }; + disputes.try_push(dispute.clone()).unwrap(); + zrml_simple_disputes::Disputes::::insert(market_id, disputes); + let market = get_market(MarketDisputeMechanism::SimpleDisputes); + >::push_market(market).unwrap(); + + MoveDataToSimpleDisputes::::on_runtime_upgrade(); + + let actual = zrml_simple_disputes::Disputes::::get(0); + assert_eq!(actual, vec![dispute]); + }); + } + + #[test] + fn on_runtime_upgrade_correctly_updates_simple_disputes() { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + let market_id = 0u128; + + let mut disputes = crate::Disputes::::get(0); + for i in 0..::MaxDisputes::get() { + let dispute = OldMarketDispute { + at: i as u64 + 42u64, + by: i as u128, + outcome: OutcomeReport::Categorical(i as u16), + }; + disputes.try_push(dispute).unwrap(); + } + crate::Disputes::::insert(market_id, disputes); + let market = get_market(MarketDisputeMechanism::SimpleDisputes); + >::push_market(market).unwrap(); + + MoveDataToSimpleDisputes::::on_runtime_upgrade(); + + let mut disputes = zrml_simple_disputes::Disputes::::get(market_id); + for i in 0..::MaxDisputes::get() { + let dispute = disputes.get_mut(i as usize).unwrap(); + + assert_eq!(dispute.at, i as u64 + 42u64); + assert_eq!(dispute.by, i as u128); + assert_eq!(dispute.outcome, OutcomeReport::Categorical(i as u16)); + + let bond = zrml_simple_disputes::default_outcome_bond::(i as usize); + assert_eq!(dispute.bond, bond); + } + }); + } + + #[test] + fn on_runtime_upgrade_correctly_updates_reserve_ids() { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + let market_id = 0u128; + + let mut disputes = crate::Disputes::::get(0); + for i in 0..::MaxDisputes::get() { + let dispute = OldMarketDispute { + at: i as u64 + 42u64, + by: i as u128, + outcome: OutcomeReport::Categorical(i as u16), + }; + let bond = zrml_simple_disputes::default_outcome_bond::(i.into()); + let pm_reserve_id = crate::Pallet::::reserve_id(); + let res = ::AssetManager::reserve_named( + &pm_reserve_id, + Asset::Ztg, + &dispute.by, + bond.saturated_into::().saturated_into(), + ); + assert!(res.is_ok()); + disputes.try_push(dispute).unwrap(); + } + crate::Disputes::::insert(market_id, disputes); + let market = get_market(MarketDisputeMechanism::SimpleDisputes); + >::push_market(market).unwrap(); + + MoveDataToSimpleDisputes::::on_runtime_upgrade(); + + let mut disputes = zrml_simple_disputes::Disputes::::get(market_id); + for i in 0..::MaxDisputes::get() { + let dispute = disputes.get_mut(i as usize).unwrap(); + + let sd_reserve_id = zrml_simple_disputes::Pallet::::reserve_id(); + let reserved_balance = + ::AssetManager::reserved_balance_named( + &sd_reserve_id, + Asset::Ztg, + &dispute.by, + ); + let bond = zrml_simple_disputes::default_outcome_bond::(i.into()); + assert_eq!(reserved_balance, bond); + assert!(reserved_balance > 0); + + let pm_reserve_id = crate::Pallet::::reserve_id(); + let reserved_balance = + ::AssetManager::reserved_balance_named( + &pm_reserve_id, + Asset::Ztg, + &dispute.by, + ); + assert_eq!(reserved_balance, 0); + } + }); + } + + fn set_up_version() { + StorageVersion::new(PREDICTION_MARKETS_REQUIRED_STORAGE_VERSION) + .put::>(); + } + + fn get_market(dispute_mechanism: MarketDisputeMechanism) -> MarketOf { + let base_asset = Asset::Ztg; + let creator = 999; + let creator_fee = 1; + let oracle = 2; + let metadata = vec![3, 4, 5]; + let market_type = MarketType::Categorical(6); + let period = MarketPeriod::Block(7..8); + let scoring_rule = ScoringRule::CPMM; + let status = MarketStatus::Disputed; + let creation = MarketCreation::Permissionless; + let report = None; + let resolved_outcome = None; + let deadlines = Deadlines::default(); + let bonds = MarketBonds { + creation: Some(Bond::new(creator, ::ValidityBond::get())), + oracle: Some(Bond::new(creator, ::OracleBond::get())), + outsider: None, + dispute: None, + }; + + Market { + base_asset, + creator, + creation, + creator_fee, + oracle, + metadata, + market_type, + period, + scoring_rule, + status, + report, + resolved_outcome, + dispute_mechanism, + deadlines, + bonds, + } + } +} + // We use these utilities to prevent having to make the swaps pallet a dependency of // prediciton-markets. The calls are based on the implementation of `StorageVersion`, found here: // https://github.com/paritytech/substrate/blob/bc7a1e6c19aec92bfa247d8ca68ec63e07061032/frame/support/src/traits/metadata.rs#L168-L230 diff --git a/zrml/prediction-markets/src/mock.rs b/zrml/prediction-markets/src/mock.rs index 04b4d8c95..4f0ef9f02 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -39,14 +39,15 @@ use substrate_fixed::{types::extra::U33, FixedI128, FixedU128}; use zeitgeist_primitives::{ constants::mock::{ AuthorizedPalletId, BalanceFractionalDecimals, BlockHashCount, CorrectionPeriod, - CourtCaseDuration, CourtPalletId, DisputeFactor, ExistentialDeposit, ExistentialDeposits, - ExitFee, GetNativeCurrencyId, LiquidityMiningPalletId, MaxApprovals, MaxAssets, - MaxCategories, MaxDisputeDuration, MaxDisputes, MaxEditReasonLen, MaxGracePeriod, - MaxInRatio, MaxMarketLifetime, MaxOracleDuration, MaxOutRatio, MaxRejectReasonLen, - MaxReserves, MaxSubsidyPeriod, MaxSwapFee, MaxTotalWeight, MaxWeight, MinAssets, - MinCategories, MinDisputeDuration, MinOracleDuration, MinSubsidy, MinSubsidyPeriod, - MinWeight, MinimumPeriod, OutsiderBond, PmPalletId, SimpleDisputesPalletId, StakeWeight, - SwapsPalletId, TreasuryPalletId, BASE, CENT, MILLISECS_PER_BLOCK, + CourtCaseDuration, CourtPalletId, ExistentialDeposit, ExistentialDeposits, ExitFee, + GetNativeCurrencyId, LiquidityMiningPalletId, MaxApprovals, MaxAssets, MaxCategories, + MaxDisputeDuration, MaxDisputes, MaxEditReasonLen, MaxGracePeriod, MaxInRatio, + MaxMarketLifetime, MaxOracleDuration, MaxOutRatio, MaxRejectReasonLen, MaxReserves, + MaxSubsidyPeriod, MaxSwapFee, MaxTotalWeight, MaxWeight, MinAssets, MinCategories, + MinDisputeDuration, MinOracleDuration, MinSubsidy, MinSubsidyPeriod, MinWeight, + MinimumPeriod, OutcomeBond, OutcomeFactor, OutsiderBond, PmPalletId, + SimpleDisputesPalletId, StakeWeight, SwapsPalletId, TreasuryPalletId, BASE, CENT, + MILLISECS_PER_BLOCK, }, types::{ AccountIdTest, Amount, Asset, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, @@ -148,7 +149,6 @@ impl crate::Config for Runtime { type Court = Court; type DestroyOrigin = EnsureSignedBy; type DisputeBond = DisputeBond; - type DisputeFactor = DisputeFactor; type Event = Event; #[cfg(feature = "with-global-disputes")] type GlobalDisputes = GlobalDisputes; @@ -319,10 +319,15 @@ impl zrml_rikiddo::Config for Runtime { } impl zrml_simple_disputes::Config for Runtime { + type AssetManager = AssetManager; type Event = Event; + type OutcomeBond = OutcomeBond; + type OutcomeFactor = OutcomeFactor; type DisputeResolution = prediction_markets::Pallet; type MarketCommons = MarketCommons; + type MaxDisputes = MaxDisputes; type PalletId = SimpleDisputesPalletId; + type WeightInfo = zrml_simple_disputes::weights::WeightInfo; } #[cfg(feature = "with-global-disputes")] diff --git a/zrml/prediction-markets/src/tests.rs b/zrml/prediction-markets/src/tests.rs index 1dc1a7955..e5c3ceb05 100644 --- a/zrml/prediction-markets/src/tests.rs +++ b/zrml/prediction-markets/src/tests.rs @@ -20,9 +20,8 @@ #![allow(clippy::reversed_empty_ranges)] use crate::{ - default_dispute_bond, mock::*, Config, Disputes, Error, Event, LastTimeFrame, MarketIdsForEdit, - MarketIdsPerCloseBlock, MarketIdsPerDisputeBlock, MarketIdsPerOpenBlock, - MarketIdsPerReportBlock, TimeFrame, + mock::*, Config, Error, Event, LastTimeFrame, MarketIdsForEdit, MarketIdsPerCloseBlock, + MarketIdsPerDisputeBlock, MarketIdsPerOpenBlock, MarketIdsPerReportBlock, TimeFrame, }; use core::ops::{Range, RangeInclusive}; use frame_support::{ @@ -35,7 +34,7 @@ use test_case::test_case; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use sp_runtime::traits::{AccountIdConversion, SaturatedConversion, Zero}; use zeitgeist_primitives::{ - constants::mock::{DisputeFactor, OutsiderBond, BASE, CENT, MILLISECS_PER_BLOCK}, + constants::mock::{OutcomeBond, OutcomeFactor, OutsiderBond, BASE, CENT, MILLISECS_PER_BLOCK}, traits::Swaps as SwapsPalletApi, types::{ AccountIdTest, Asset, Balance, BlockNumber, Bond, Deadlines, Market, MarketBonds, @@ -43,7 +42,6 @@ use zeitgeist_primitives::{ Moment, MultiHash, OutcomeReport, PoolStatus, ScalarPosition, ScoringRule, }, }; -use zrml_authorized::Error as AuthorizedError; use zrml_market_commons::MarketCommonsPalletApi; use zrml_swaps::Pools; @@ -596,11 +594,7 @@ fn admin_destroy_market_correctly_slashes_permissionless_market_disputed() { OutcomeReport::Categorical(1) )); run_to_block(grace_period + 2); - assert_ok!(PredictionMarkets::dispute( - Origin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(0) - )); + assert_ok!(PredictionMarkets::dispute(Origin::signed(CHARLIE), 0,)); assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); assert_ok!(Balances::reserve_named( &PredictionMarkets::reserve_id(), @@ -626,7 +620,7 @@ fn admin_destroy_market_correctly_slashes_permissionless_market_disputed() { } #[test] -fn admin_destroy_market_correctly_unreserves_dispute_bonds() { +fn admin_destroy_market_correctly_slashes_dispute_bonds() { ExtBuilder::default().build().execute_with(|| { let end = 2; simple_create_categorical_market( @@ -646,12 +640,13 @@ fn admin_destroy_market_correctly_unreserves_dispute_bonds() { OutcomeReport::Categorical(1) )); run_to_block(grace_period + 2); - assert_ok!(PredictionMarkets::dispute( + assert_ok!(PredictionMarkets::dispute(Origin::signed(CHARLIE), 0,)); + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(CHARLIE), 0, OutcomeReport::Categorical(0) )); - assert_ok!(PredictionMarkets::dispute( + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(DAVE), 0, OutcomeReport::Categorical(1) @@ -680,13 +675,13 @@ fn admin_destroy_market_correctly_unreserves_dispute_bonds() { ); assert_eq!( Balances::free_balance(CHARLIE), - balance_free_before_charlie + default_dispute_bond::(0) + balance_free_before_charlie + zrml_simple_disputes::default_outcome_bond::(0) ); assert_eq!( Balances::free_balance(DAVE), - balance_free_before_dave + default_dispute_bond::(1), + balance_free_before_dave + zrml_simple_disputes::default_outcome_bond::(1), ); - assert!(Disputes::::get(market_id).is_empty()); + assert!(zrml_simple_disputes::Disputes::::get(market_id).is_empty()); }); } @@ -910,11 +905,7 @@ fn admin_destroy_market_correctly_slashes_advised_market_disputed() { 0, OutcomeReport::Categorical(1) )); - assert_ok!(PredictionMarkets::dispute( - Origin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(0) - )); + assert_ok!(PredictionMarkets::dispute(Origin::signed(CHARLIE), 0,)); assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); assert_ok!(Balances::reserve_named( &PredictionMarkets::reserve_id(), @@ -2642,7 +2633,8 @@ fn it_allows_to_dispute_the_outcome_of_a_market() { let dispute_at = grace_period + 2; run_to_block(dispute_at); - assert_ok!(PredictionMarkets::dispute( + assert_ok!(PredictionMarkets::dispute(Origin::signed(CHARLIE), 0,)); + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(CHARLIE), 0, OutcomeReport::Categorical(0) @@ -2651,7 +2643,7 @@ fn it_allows_to_dispute_the_outcome_of_a_market() { let market = MarketCommons::market(&0).unwrap(); assert_eq!(market.status, MarketStatus::Disputed); - let disputes = crate::Disputes::::get(0); + let disputes = zrml_simple_disputes::Disputes::::get(0); assert_eq!(disputes.len(), 1); let dispute = &disputes[0]; assert_eq!(dispute.at, dispute_at); @@ -2666,7 +2658,7 @@ fn it_allows_to_dispute_the_outcome_of_a_market() { } #[test] -fn dispute_fails_authority_reported_already() { +fn dispute_fails_disputed_already() { ExtBuilder::default().build().execute_with(|| { let end = 2; assert_ok!(PredictionMarkets::create_market( @@ -2696,15 +2688,175 @@ fn dispute_fails_authority_reported_already() { let dispute_at = grace_period + 2; run_to_block(dispute_at); - assert_ok!(PredictionMarkets::dispute( - Origin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(0) + assert_ok!(PredictionMarkets::dispute(Origin::signed(CHARLIE), 0,)); + + assert_noop!( + PredictionMarkets::dispute(Origin::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( + Origin::signed(ALICE), + Asset::Ztg, + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + MarketDisputeMechanism::Authorized, + ScoringRule::CPMM, )); + // 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(Origin::signed(CHARLIE), 0, OutcomeReport::Categorical(1)), - AuthorizedError::::OnlyOneDisputeAllowed + PredictionMarkets::dispute(Origin::signed(CHARLIE), 0), + Error::::InvalidMarketStatus, + ); + }); +} + +#[test] +fn dispute_reserves_dispute_bond() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + assert_ok!(PredictionMarkets::create_market( + Origin::signed(ALICE), + Asset::Ztg, + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + MarketDisputeMechanism::Authorized, + ScoringRule::CPMM, + )); + + // 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( + Origin::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(Origin::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 dispute_updates_market() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + assert_ok!(PredictionMarkets::create_market( + Origin::signed(ALICE), + Asset::Ztg, + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + MarketDisputeMechanism::Authorized, + ScoringRule::CPMM, + )); + + // 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( + Origin::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(Origin::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( + Origin::signed(ALICE), + Asset::Ztg, + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + MarketDisputeMechanism::Authorized, + ScoringRule::CPMM, + )); + + // 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( + Origin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + + let dispute_at = grace_period + 2; + run_to_block(dispute_at); + + assert_ok!(PredictionMarkets::dispute(Origin::signed(CHARLIE), 0,)); + + System::assert_last_event( + Event::MarketDisputed(0u32.into(), MarketStatus::Disputed).into(), ); }); } @@ -2828,10 +2980,18 @@ fn it_resolves_a_disputed_market() { OutcomeReport::Categorical(0) )); + assert_ok!(PredictionMarkets::dispute(Origin::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!(PredictionMarkets::dispute( + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(CHARLIE), 0, OutcomeReport::Categorical(1) @@ -2840,7 +3000,7 @@ fn it_resolves_a_disputed_market() { let dispute_at_1 = report_at + 2; run_to_block(dispute_at_1); - assert_ok!(PredictionMarkets::dispute( + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(DAVE), 0, OutcomeReport::Categorical(0) @@ -2849,7 +3009,7 @@ fn it_resolves_a_disputed_market() { let dispute_at_2 = report_at + 3; run_to_block(dispute_at_2); - assert_ok!(PredictionMarkets::dispute( + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(EVE), 0, OutcomeReport::Categorical(1) @@ -2860,16 +3020,16 @@ fn it_resolves_a_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() + OutcomeBond::get()); let dave_reserved = Balances::reserved_balance(&DAVE); - assert_eq!(dave_reserved, DisputeBond::get() + DisputeFactor::get()); + assert_eq!(dave_reserved, OutcomeBond::get() + OutcomeFactor::get()); let eve_reserved = Balances::reserved_balance(&EVE); - assert_eq!(eve_reserved, DisputeBond::get() + 2 * DisputeFactor::get()); + assert_eq!(eve_reserved, OutcomeBond::get() + 2 * OutcomeFactor::get()); // check disputes length - let disputes = crate::Disputes::::get(0); + 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 @@ -2892,7 +3052,7 @@ fn it_resolves_a_disputed_market() { let market_after = MarketCommons::market(&0).unwrap(); assert_eq!(market_after.status, MarketStatus::Resolved); - let disputes = crate::Disputes::::get(0); + let disputes = zrml_simple_disputes::Disputes::::get(0); assert_eq!(disputes.len(), 0); assert_ok!(PredictionMarkets::redeem_shares(Origin::signed(CHARLIE), 0)); @@ -2900,16 +3060,18 @@ fn it_resolves_a_disputed_market() { // Make sure rewards are right: // // Slashed amounts: - // - Dave's reserve: DisputeBond::get() + DisputeFactor::get() + // - Dave's reserve: OutcomeBond::get() + OutcomeFactor::get() // - Alice's oracle bond: OracleBond::get() - // Total: OracleBond::get() + DisputeBond::get() + DisputeFactor::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 total slashed amount as bounty. - let dave_reserved = DisputeBond::get() + DisputeFactor::get(); - let total_slashed = OracleBond::get() + dave_reserved; + // 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 + total_slashed / 2); + 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); @@ -2928,6 +3090,7 @@ fn it_resolves_a_disputed_market() { 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); @@ -2960,7 +3123,7 @@ fn dispute_fails_unless_reported_or_disputed_market(status: MarketStatus) { })); assert_noop!( - PredictionMarkets::dispute(Origin::signed(EVE), 0, OutcomeReport::Categorical(1)), + PredictionMarkets::dispute(Origin::signed(EVE), 0), Error::::InvalidMarketStatus ); }); @@ -2994,31 +3157,25 @@ fn start_global_dispute_works() { )); let dispute_at_0 = end + grace_period + 2; run_to_block(dispute_at_0); - for i in 1..=::MaxDisputes::get() { - if i == 1 { - #[cfg(feature = "with-global-disputes")] - assert_noop!( - PredictionMarkets::start_global_dispute(Origin::signed(CHARLIE), market_id), - Error::::InvalidMarketStatus - ); - } else { - #[cfg(feature = "with-global-disputes")] - assert_noop!( - PredictionMarkets::start_global_dispute(Origin::signed(CHARLIE), market_id), - Error::::MaxDisputesNeeded - ); - } - assert_ok!(PredictionMarkets::dispute( + assert_ok!(PredictionMarkets::dispute(Origin::signed(CHARLIE), market_id,)); + for i in 1..=::MaxDisputes::get() { + #[cfg(feature = "with-global-disputes")] + assert_noop!( + PredictionMarkets::start_global_dispute(Origin::signed(CHARLIE), market_id), + Error::::MarketDisputeMechanismNotFailed + ); + + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(CHARLIE), market_id, - OutcomeReport::Categorical(i.saturated_into()) + OutcomeReport::Categorical(i.saturated_into()), )); run_blocks(1); let market = MarketCommons::market(&market_id).unwrap(); assert_eq!(market.status, MarketStatus::Disputed); } - let disputes = crate::Disputes::::get(market_id); + let disputes = zrml_simple_disputes::Disputes::::get(market_id); assert_eq!(disputes.len(), ::MaxDisputes::get() as usize); let last_dispute = disputes.last().unwrap(); @@ -3039,7 +3196,8 @@ fn start_global_dispute_works() { Some((Zero::zero(), vec![BOB])), ); for i in 1..=::MaxDisputes::get() { - let dispute_bond = crate::default_dispute_bond::((i - 1).into()); + let dispute_bond = + zrml_simple_disputes::default_outcome_bond::((i - 1).into()); assert_eq!( GlobalDisputes::get_voting_outcome_info( &market_id, @@ -3098,11 +3256,7 @@ fn start_global_dispute_fails_on_wrong_mdm() { run_to_block(dispute_at_0); // only one dispute allowed for authorized mdm - assert_ok!(PredictionMarkets::dispute( - Origin::signed(CHARLIE), - market_id, - OutcomeReport::Categorical(1u32.saturated_into()) - )); + assert_ok!(PredictionMarkets::dispute(Origin::signed(CHARLIE), market_id,)); run_blocks(1); let market = MarketCommons::market(&market_id).unwrap(); assert_eq!(market.status, MarketStatus::Disputed); @@ -3637,15 +3791,20 @@ fn full_scalar_market_lifecycle() { assert_eq!(report.outcome, OutcomeReport::Scalar(100)); // dispute - assert_ok!(PredictionMarkets::dispute(Origin::signed(DAVE), 0, OutcomeReport::Scalar(25))); - let disputes = crate::Disputes::::get(0); + assert_ok!(PredictionMarkets::dispute(Origin::signed(DAVE), 0)); + assert_ok!(SimpleDisputes::suggest_outcome( + Origin::signed(DAVE), + 0, + OutcomeReport::Scalar(25) + )); + let disputes = zrml_simple_disputes::Disputes::::get(0); assert_eq!(disputes.len(), 1); run_blocks(market.deadlines.dispute_duration); let market_after_resolve = MarketCommons::market(&0).unwrap(); assert_eq!(market_after_resolve.status, MarketStatus::Resolved); - let disputes = crate::Disputes::::get(0); + let disputes = zrml_simple_disputes::Disputes::::get(0); assert_eq!(disputes.len(), 0); // give EVE some shares @@ -3856,11 +4015,7 @@ fn authorized_correctly_resolves_disputed_market() { let dispute_at = grace_period + 1 + 1; run_to_block(dispute_at); - assert_ok!(PredictionMarkets::dispute( - Origin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(1) - )); + assert_ok!(PredictionMarkets::dispute(Origin::signed(CHARLIE), 0,)); if base_asset == Asset::Ztg { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); @@ -3891,10 +4046,6 @@ fn authorized_correctly_resolves_disputed_market() { let charlie_reserved = Balances::reserved_balance(&CHARLIE); assert_eq!(charlie_reserved, DisputeBond::get()); - // check disputes length - let disputes = crate::Disputes::::get(0); - assert_eq!(disputes.len(), 1); - let market_ids_1 = MarketIdsPerDisputeBlock::::get( dispute_at + ::CorrectionPeriod::get(), ); @@ -3939,7 +4090,7 @@ fn authorized_correctly_resolves_disputed_market() { let market_after = MarketCommons::market(&0).unwrap(); assert_eq!(market_after.status, MarketStatus::Resolved); - let disputes = crate::Disputes::::get(0); + let disputes = zrml_simple_disputes::Disputes::::get(0); assert_eq!(disputes.len(), 0); assert_ok!(PredictionMarkets::redeem_shares(Origin::signed(CHARLIE), 0)); @@ -4262,13 +4413,21 @@ fn outsider_reports_wrong_outcome() { let dispute_at_0 = report_at + 1; run_to_block(dispute_at_0); - assert_ok!(PredictionMarkets::dispute( - Origin::signed(EVE), + assert_ok!(PredictionMarkets::dispute(Origin::signed(EVE), 0,)); + check_reserve(&EVE, DisputeBond::get()); + + assert_ok!(SimpleDisputes::suggest_outcome( + Origin::signed(DAVE), 0, OutcomeReport::Categorical(0) )); + let outcome_bond = zrml_simple_disputes::default_outcome_bond::(0); + + check_reserve(&DAVE, outcome_bond); + let eve_balance_before = Balances::free_balance(&EVE); + let dave_balance_before = Balances::free_balance(&DAVE); // on_resolution called run_blocks(market.deadlines.dispute_duration); @@ -4278,12 +4437,13 @@ fn outsider_reports_wrong_outcome() { check_reserve(&outsider, 0); assert_eq!(Balances::free_balance(&outsider), outsider_balance_before); - let dispute_bond = crate::default_dispute_bond::(0usize); - // disputor EVE gets the OracleBond and OutsiderBond and dispute bond + // disputor EVE gets the OracleBond and OutsiderBond and DisputeBond assert_eq!( Balances::free_balance(&EVE), - eve_balance_before + dispute_bond + 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); }; ExtBuilder::default().build().execute_with(|| { test(Asset::Ztg); @@ -4414,7 +4574,8 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark 0, OutcomeReport::Categorical(0) )); - assert_ok!(PredictionMarkets::dispute( + assert_ok!(PredictionMarkets::dispute(Origin::signed(CHARLIE), 0,)); + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(CHARLIE), 0, OutcomeReport::Categorical(1) @@ -4464,7 +4625,8 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_ma 0, OutcomeReport::Categorical(0) )); - assert_ok!(PredictionMarkets::dispute( + assert_ok!(PredictionMarkets::dispute(Origin::signed(CHARLIE), 0,)); + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(CHARLIE), 0, OutcomeReport::Categorical(1) @@ -4513,13 +4675,14 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark 0, OutcomeReport::Categorical(0) )); + assert_ok!(PredictionMarkets::dispute(Origin::signed(EVE), 0,)); // EVE disputes with wrong outcome - assert_ok!(PredictionMarkets::dispute( + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(EVE), 0, OutcomeReport::Categorical(1) )); - assert_ok!(PredictionMarkets::dispute( + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(CHARLIE), 0, OutcomeReport::Categorical(0) @@ -4572,13 +4735,14 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_ma 0, OutcomeReport::Categorical(0) )); + assert_ok!(PredictionMarkets::dispute(Origin::signed(EVE), 0,)); // EVE disputes with wrong outcome - assert_ok!(PredictionMarkets::dispute( + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(EVE), 0, OutcomeReport::Categorical(1) )); - assert_ok!(PredictionMarkets::dispute( + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(CHARLIE), 0, OutcomeReport::Categorical(0) @@ -4633,17 +4797,17 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark 0, OutcomeReport::Categorical(0) )); - let outsider_balance_before = Balances::free_balance(&outsider); check_reserve(&outsider, OutsiderBond::get()); + assert_ok!(PredictionMarkets::dispute(Origin::signed(EVE), 0,)); // EVE disputes with wrong outcome - assert_ok!(PredictionMarkets::dispute( + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(EVE), 0, OutcomeReport::Categorical(1) )); - assert_ok!(PredictionMarkets::dispute( + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(FRED), 0, OutcomeReport::Categorical(0) @@ -4704,17 +4868,17 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_ma 0, OutcomeReport::Categorical(0) )); - let outsider_balance_before = Balances::free_balance(&outsider); check_reserve(&outsider, OutsiderBond::get()); + assert_ok!(PredictionMarkets::dispute(Origin::signed(EVE), 0,)); // EVE disputes with wrong outcome - assert_ok!(PredictionMarkets::dispute( + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(EVE), 0, OutcomeReport::Categorical(1) )); - assert_ok!(PredictionMarkets::dispute( + assert_ok!(SimpleDisputes::suggest_outcome( Origin::signed(FRED), 0, OutcomeReport::Categorical(0) @@ -5053,6 +5217,7 @@ fn create_market_fails_if_market_duration_is_too_long_in_moments() { creation: Some(Bond::new(ALICE, ::AdvisoryBond::get())), oracle: Some(Bond::new(ALICE, ::OracleBond::get())), outsider: None, + dispute: None, } )] #[test_case( @@ -5063,6 +5228,7 @@ fn create_market_fails_if_market_duration_is_too_long_in_moments() { creation: Some(Bond::new(ALICE, ::ValidityBond::get())), oracle: Some(Bond::new(ALICE, ::OracleBond::get())), outsider: None, + dispute: None, } )] fn create_market_sets_the_correct_market_parameters_and_reserves_the_correct_amount( diff --git a/zrml/prediction-markets/src/weights.rs b/zrml/prediction-markets/src/weights.rs index 4480c5ea7..b82f6f825 100644 --- a/zrml/prediction-markets/src/weights.rs +++ b/zrml/prediction-markets/src/weights.rs @@ -46,7 +46,7 @@ use frame_support::{traits::Get, weights::Weight}; /// Trait containing the required functions for weight retrival within /// zrml_prediction_markets (automatically generated) pub trait WeightInfoZeitgeist { - fn admin_destroy_disputed_market(a: u32, d: u32, o: u32, c: u32, r: u32) -> Weight; + fn admin_destroy_disputed_market(a: u32, o: u32, c: u32, r: u32) -> Weight; fn admin_destroy_reported_market(a: u32, o: u32, c: u32, r: u32) -> Weight; fn admin_move_market_to_closed(o: u32, c: u32) -> Weight; fn admin_move_market_to_resolved_scalar_reported(r: u32) -> Weight; @@ -84,30 +84,29 @@ pub trait WeightInfoZeitgeist { pub struct WeightInfo(PhantomData); impl WeightInfoZeitgeist for WeightInfo { // Storage: MarketCommons Markets (r:1 w:1) - // Storage: Balances Reserves (r:7 w:7) - // Storage: System Account (r:8 w:8) + // Storage: Balances Reserves (r:2 w:2) + // Storage: System Account (r:3 w:3) // Storage: MarketCommons MarketPool (r:1 w:1) // Storage: Swaps Pools (r:1 w:1) // Storage: Tokens Accounts (r:2 w:2) // Storage: Tokens TotalIssuance (r:2 w:2) - // Storage: PredictionMarkets Disputes (r:1 w:1) + // Storage: Authorized AuthorizedOutcomeReports (r:1 w:1) // Storage: PredictionMarkets MarketIdsPerDisputeBlock (r:1 w:1) - fn admin_destroy_disputed_market(a: u32, d: u32, _o: u32, c: u32, r: u32) -> Weight { - Weight::from_ref_time(36_524_000) - // Standard Error: 41_000 - .saturating_add(Weight::from_ref_time(28_980_000).saturating_mul(a.into())) - // Standard Error: 473_000 - .saturating_add(Weight::from_ref_time(40_066_000).saturating_mul(d.into())) - // Standard Error: 41_000 - .saturating_add(Weight::from_ref_time(425_000).saturating_mul(c.into())) - // Standard Error: 41_000 - .saturating_add(Weight::from_ref_time(438_000).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(8)) + fn admin_destroy_disputed_market(a: u32, o: u32, c: u32, r: u32) -> Weight { + Weight::from_ref_time(138_848_000) + // Standard Error: 35_000 + .saturating_add(Weight::from_ref_time(20_922_000)) + .saturating_mul(a.into()) + // Standard Error: 34_000 + .saturating_add(Weight::from_ref_time(1_091_000).saturating_mul(o.into())) + // Standard Error: 34_000 + .saturating_add(Weight::from_ref_time(984_000).saturating_mul(c.into())) + // Standard Error: 34_000 + .saturating_add(Weight::from_ref_time(1_026_000).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(d.into()))) - .saturating_add(T::DbWeight::get().writes(8)) + .saturating_add(T::DbWeight::get().writes(10)) .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(d.into()))) } // Storage: MarketCommons Markets (r:1 w:1) // Storage: Balances Reserves (r:1 w:1) diff --git a/zrml/simple-disputes/Cargo.toml b/zrml/simple-disputes/Cargo.toml index d6b6ede2b..e947cc9a8 100644 --- a/zrml/simple-disputes/Cargo.toml +++ b/zrml/simple-disputes/Cargo.toml @@ -2,6 +2,7 @@ frame-benchmarking = { branch = "moonbeam-polkadot-v0.9.29", default-features = false, optional = true, git = "https://github.com/zeitgeistpm/substrate" } frame-support = { branch = "moonbeam-polkadot-v0.9.29", default-features = false, git = "https://github.com/zeitgeistpm/substrate" } frame-system = { branch = "moonbeam-polkadot-v0.9.29", default-features = false, git = "https://github.com/zeitgeistpm/substrate" } +orml-traits = { branch = "moonbeam-polkadot-v0.9.29", default-features = false, git = "https://github.com/zeitgeistpm/open-runtime-module-library" } parity-scale-codec = { default-features = false, features = ["derive", "max-encoded-len"], version = "3.0.0" } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-runtime = { branch = "moonbeam-polkadot-v0.9.29", default-features = false, git = "https://github.com/zeitgeistpm/substrate" } @@ -9,6 +10,8 @@ zeitgeist-primitives = { default-features = false, path = "../../primitives" } zrml-market-commons = { default-features = false, path = "../market-commons" } [dev-dependencies] +orml-currencies = { branch = "moonbeam-polkadot-v0.9.29", git = "https://github.com/zeitgeistpm/open-runtime-module-library" } +orml-tokens = { branch = "moonbeam-polkadot-v0.9.29", git = "https://github.com/zeitgeistpm/open-runtime-module-library" } pallet-balances = { branch = "moonbeam-polkadot-v0.9.29", git = "https://github.com/zeitgeistpm/substrate" } pallet-timestamp = { branch = "moonbeam-polkadot-v0.9.29", git = "https://github.com/zeitgeistpm/substrate" } sp-io = { branch = "moonbeam-polkadot-v0.9.29", git = "https://github.com/zeitgeistpm/substrate" } diff --git a/zrml/simple-disputes/src/benchmarks.rs b/zrml/simple-disputes/src/benchmarks.rs new file mode 100644 index 000000000..d4b324630 --- /dev/null +++ b/zrml/simple-disputes/src/benchmarks.rs @@ -0,0 +1,177 @@ +// Copyright 2023 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 . + +#![allow( + // Auto-generated code is a no man's land + clippy::integer_arithmetic +)] +#![allow(clippy::type_complexity)] +#![cfg(feature = "runtime-benchmarks")] + +use crate::Pallet as SimpleDisputes; + +use super::*; +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_support::{ + dispatch::RawOrigin, + traits::{Get, Imbalance}, +}; +use orml_traits::MultiCurrency; +use sp_runtime::traits::{One, Saturating}; +use zrml_market_commons::MarketCommonsPalletApi; + +fn fill_disputes(market_id: MarketIdOf, d: u32) { + for i in 0..d { + let now = >::block_number(); + let disputor = account("disputor", i, 0); + let bond = default_outcome_bond::(i as usize); + T::AssetManager::deposit(Asset::Ztg, &disputor, bond).unwrap(); + let outcome = OutcomeReport::Scalar((2 + i).into()); + SimpleDisputes::::suggest_outcome( + RawOrigin::Signed(disputor).into(), + market_id, + outcome, + ) + .unwrap(); + >::set_block_number(now.saturating_add(T::BlockNumber::one())); + } +} + +benchmarks! { + suggest_outcome { + let d in 1..(T::MaxDisputes::get() - 1); + let r in 1..63; + let e in 1..63; + + let caller: T::AccountId = whitelisted_caller(); + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + + fill_disputes::(market_id, d); + let disputes = Disputes::::get(market_id); + let last_dispute = disputes.last().unwrap(); + let auto_resolve = last_dispute.at.saturating_add(market.deadlines.dispute_duration); + for i in 0..r { + let id = T::MarketCommons::push_market(market_mock::()).unwrap(); + T::DisputeResolution::add_auto_resolve(&id, auto_resolve).unwrap(); + } + + let now = >::block_number(); + + let dispute_duration_ends_at_block = + now.saturating_add(market.deadlines.dispute_duration); + for i in 0..e { + let id = T::MarketCommons::push_market(market_mock::()).unwrap(); + T::DisputeResolution::add_auto_resolve(&id, dispute_duration_ends_at_block).unwrap(); + } + + let outcome = OutcomeReport::Scalar(1); + let bond = default_outcome_bond::(T::MaxDisputes::get() as usize); + T::AssetManager::deposit(Asset::Ztg, &caller, bond).unwrap(); + }: _(RawOrigin::Signed(caller.clone()), market_id, outcome) + + on_dispute_weight { + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + }: { + SimpleDisputes::::on_dispute(&market_id, &market).unwrap(); + } + + on_resolution_weight { + let d in 1..T::MaxDisputes::get(); + + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + + fill_disputes::(market_id, d); + }: { + SimpleDisputes::::on_resolution(&market_id, &market).unwrap(); + } + + exchange_weight { + let d in 1..T::MaxDisputes::get(); + + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + + fill_disputes::(market_id, d); + + let outcome = OutcomeReport::Scalar(1); + let imb = NegativeImbalanceOf::::zero(); + }: { + SimpleDisputes::::exchange(&market_id, &market, &outcome, imb).unwrap(); + } + + get_auto_resolve_weight { + let d in 1..T::MaxDisputes::get(); + + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + + fill_disputes::(market_id, d); + }: { + SimpleDisputes::::get_auto_resolve(&market_id, &market).unwrap(); + } + + has_failed_weight { + let d in 1..T::MaxDisputes::get(); + + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + + fill_disputes::(market_id, d); + }: { + SimpleDisputes::::has_failed(&market_id, &market).unwrap(); + } + + on_global_dispute_weight { + let d in 1..T::MaxDisputes::get(); + + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + + fill_disputes::(market_id, d); + }: { + SimpleDisputes::::on_global_dispute(&market_id, &market).unwrap(); + } + + clear_weight { + let d in 1..T::MaxDisputes::get(); + + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + + fill_disputes::(market_id, d); + }: { + SimpleDisputes::::clear(&market_id, &market).unwrap(); + } + + impl_benchmark_test_suite!( + SimpleDisputes, + crate::mock::ExtBuilder::default().build(), + crate::mock::Runtime, + ); +} diff --git a/zrml/simple-disputes/src/lib.rs b/zrml/simple-disputes/src/lib.rs index fb4308b53..aa60ff84b 100644 --- a/zrml/simple-disputes/src/lib.rs +++ b/zrml/simple-disputes/src/lib.rs @@ -21,55 +21,64 @@ extern crate alloc; +#[cfg(feature = "runtime-benchmarks")] +mod benchmarks; mod mock; mod simple_disputes_pallet_api; mod tests; +pub mod weights; pub use pallet::*; pub use simple_disputes_pallet_api::SimpleDisputesPalletApi; +use zeitgeist_primitives::{ + traits::{DisputeApi, DisputeMaxWeightApi, DisputeResolutionApi, ZeitgeistAssetManager}, + types::{ + Asset, GlobalDisputeItem, Market, MarketDispute, MarketDisputeMechanism, MarketStatus, + OutcomeReport, Report, ResultWithWeightInfo, + }, +}; #[frame_support::pallet] mod pallet { - use crate::SimpleDisputesPalletApi; + use super::*; + use crate::{weights::WeightInfoZeitgeist, SimpleDisputesPalletApi}; + use alloc::vec::Vec; use core::marker::PhantomData; use frame_support::{ dispatch::DispatchResult, ensure, - traits::{Currency, Get, Hooks, IsType}, - PalletId, - }; - use sp_runtime::{traits::Saturating, DispatchError}; - use zeitgeist_primitives::{ - traits::{DisputeApi, DisputeResolutionApi}, - types::{ - Asset, Market, MarketDispute, MarketDisputeMechanism, MarketStatus, OutcomeReport, + pallet_prelude::{ + Blake2_128Concat, ConstU32, DispatchResultWithPostInfo, StorageMap, ValueQuery, Weight, }, + traits::{Currency, Get, Hooks, Imbalance, IsType, NamedReservableCurrency}, + transactional, BoundedVec, PalletId, + }; + use frame_system::pallet_prelude::*; + use orml_traits::currency::NamedMultiReservableCurrency; + use sp_runtime::{ + traits::{CheckedDiv, Saturating}, + DispatchError, SaturatedConversion, }; - use zrml_market_commons::MarketCommonsPalletApi; - - type BalanceOf = - as Currency<::AccountId>>::Balance; - pub(crate) type CurrencyOf = - <::MarketCommons as MarketCommonsPalletApi>::Currency; - pub(crate) type MarketIdOf = - <::MarketCommons as MarketCommonsPalletApi>::MarketId; - pub(crate) type MomentOf = <::MarketCommons as MarketCommonsPalletApi>::Moment; - pub(crate) type MarketOf = Market< - ::AccountId, - BalanceOf, - ::BlockNumber, - MomentOf, - Asset>, - >; - #[pallet::call] - impl Pallet {} + use zrml_market_commons::MarketCommonsPalletApi; #[pallet::config] pub trait Config: frame_system::Config { + /// Shares of outcome assets and native currency + type AssetManager: ZeitgeistAssetManager< + Self::AccountId, + Balance = as Currency>::Balance, + CurrencyId = Asset>, + ReserveIdentifier = [u8; 8], + >; + /// Event type Event: From> + IsType<::Event>; + /// The base amount of currency that must be bonded in order to create a dispute. + #[pallet::constant] + type OutcomeBond: Get>; + type DisputeResolution: DisputeResolutionApi< AccountId = Self::AccountId, BlockNumber = Self::BlockNumber, @@ -77,15 +86,73 @@ mod pallet { Moment = MomentOf, >; + /// The additional amount of currency that must be bonded when creating a subsequent + /// dispute. + #[pallet::constant] + type OutcomeFactor: Get>; + /// The identifier of individual markets. type MarketCommons: MarketCommonsPalletApi< AccountId = Self::AccountId, BlockNumber = Self::BlockNumber, >; + /// The maximum number of disputes allowed on any single market. + #[pallet::constant] + type MaxDisputes: Get; + /// The pallet identifier. #[pallet::constant] type PalletId: Get; + + /// Weights generated by benchmarks + type WeightInfo: WeightInfoZeitgeist; + } + + pub(crate) type BalanceOf = + as Currency<::AccountId>>::Balance; + pub(crate) type CurrencyOf = + <::MarketCommons as MarketCommonsPalletApi>::Currency; + pub(crate) type NegativeImbalanceOf = + as Currency<::AccountId>>::NegativeImbalance; + pub type MarketIdOf = <::MarketCommons as MarketCommonsPalletApi>::MarketId; + pub(crate) type MomentOf = <::MarketCommons as MarketCommonsPalletApi>::Moment; + pub(crate) type MarketOf = Market< + ::AccountId, + BalanceOf, + ::BlockNumber, + MomentOf, + Asset>, + >; + pub(crate) type DisputesOf = BoundedVec< + MarketDispute< + ::AccountId, + ::BlockNumber, + BalanceOf, + >, + ::MaxDisputes, + >; + pub type CacheSize = ConstU32<64>; + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + /// For each market, this holds the dispute information for each dispute that's + /// been issued. + #[pallet::storage] + pub type Disputes = + StorageMap<_, Blake2_128Concat, MarketIdOf, DisputesOf, ValueQuery>; + + #[pallet::event] + #[pallet::generate_deposit(fn deposit_event)] + pub enum Event + where + T: Config, + { + OutcomeReserved { + market_id: MarketIdOf, + dispute: MarketDispute>, + }, } #[pallet::error] @@ -95,22 +162,97 @@ mod pallet { InvalidMarketStatus, /// On dispute or resolution, someone tried to pass a non-simple-disputes market type MarketDoesNotHaveSimpleDisputesMechanism, + StorageOverflow, + OutcomeMismatch, + CannotDisputeSameOutcome, + MarketIsNotReported, + /// The maximum number of disputes has been reached. + MaxDisputesReached, } - #[pallet::event] - pub enum Event - where - T: Config, {} - #[pallet::hooks] impl Hooks for Pallet {} - impl Pallet - where - T: Config, - { + #[pallet::call] + impl Pallet { + #[pallet::weight(T::WeightInfo::suggest_outcome( + T::MaxDisputes::get(), + CacheSize::get(), + CacheSize::get(), + ))] + #[transactional] + pub fn suggest_outcome( + origin: OriginFor, + #[pallet::compact] market_id: MarketIdOf, + outcome: OutcomeReport, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let market = T::MarketCommons::market(&market_id)?; + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, + Error::::MarketDoesNotHaveSimpleDisputesMechanism + ); + ensure!(market.status == MarketStatus::Disputed, Error::::InvalidMarketStatus); + ensure!(market.matches_outcome_report(&outcome), Error::::OutcomeMismatch); + let report = market.report.as_ref().ok_or(Error::::MarketIsNotReported)?; + + let now = >::block_number(); + let disputes = Disputes::::get(market_id); + let num_disputes: u32 = disputes.len().saturated_into(); + + Self::ensure_can_not_dispute_the_same_outcome(&disputes, report, &outcome)?; + Self::ensure_disputes_does_not_exceed_max_disputes(num_disputes)?; + + let bond = default_outcome_bond::(disputes.len()); + + T::AssetManager::reserve_named(&Self::reserve_id(), Asset::Ztg, &who, bond)?; + + let market_dispute = MarketDispute { at: now, by: who, outcome, bond }; + >::try_mutate(market_id, |disputes| { + disputes.try_push(market_dispute.clone()).map_err(|_| >::StorageOverflow) + })?; + + // each dispute resets dispute_duration + let r = Self::remove_auto_resolve(disputes.as_slice(), &market_id, &market); + let dispute_duration_ends_at_block = + now.saturating_add(market.deadlines.dispute_duration); + let e = + T::DisputeResolution::add_auto_resolve(&market_id, dispute_duration_ends_at_block)?; + + Self::deposit_event(Event::OutcomeReserved { market_id, dispute: market_dispute }); + + Ok((Some(T::WeightInfo::suggest_outcome(num_disputes, r, e))).into()) + } + } + + impl Pallet { + #[inline] + pub fn reserve_id() -> [u8; 8] { + T::PalletId::get().0 + } + + fn ensure_can_not_dispute_the_same_outcome( + disputes: &[MarketDispute>], + report: &Report, + outcome: &OutcomeReport, + ) -> DispatchResult { + if let Some(last_dispute) = disputes.last() { + ensure!(&last_dispute.outcome != outcome, Error::::CannotDisputeSameOutcome); + } else { + ensure!(&report.outcome != outcome, Error::::CannotDisputeSameOutcome); + } + + Ok(()) + } + + #[inline] + fn ensure_disputes_does_not_exceed_max_disputes(num_disputes: u32) -> DispatchResult { + ensure!(num_disputes < T::MaxDisputes::get(), Error::::MaxDisputesReached); + Ok(()) + } + fn get_auto_resolve( - disputes: &[MarketDispute], + disputes: &[MarketDispute>], market: &MarketOf, ) -> Option { disputes.last().map(|last_dispute| { @@ -119,16 +261,50 @@ mod pallet { } fn remove_auto_resolve( - disputes: &[MarketDispute], + disputes: &[MarketDispute>], market_id: &MarketIdOf, market: &MarketOf, - ) { + ) -> u32 { if let Some(dispute_duration_ends_at_block) = Self::get_auto_resolve(disputes, market) { - T::DisputeResolution::remove_auto_resolve( + return T::DisputeResolution::remove_auto_resolve( market_id, dispute_duration_ends_at_block, ); } + 0u32 + } + } + + impl DisputeMaxWeightApi for Pallet + where + T: Config, + { + fn on_dispute_max_weight() -> Weight { + T::WeightInfo::on_dispute_weight() + } + + fn on_resolution_max_weight() -> Weight { + T::WeightInfo::on_resolution_weight(T::MaxDisputes::get()) + } + + fn exchange_max_weight() -> Weight { + T::WeightInfo::exchange_weight(T::MaxDisputes::get()) + } + + fn get_auto_resolve_max_weight() -> Weight { + T::WeightInfo::get_auto_resolve_weight(T::MaxDisputes::get()) + } + + fn has_failed_max_weight() -> Weight { + T::WeightInfo::has_failed_weight(T::MaxDisputes::get()) + } + + fn on_global_dispute_max_weight() -> Weight { + T::WeightInfo::on_global_dispute_weight(T::MaxDisputes::get()) + } + + fn clear_max_weight() -> Weight { + T::WeightInfo::clear_weight(T::MaxDisputes::get()) } } @@ -138,75 +314,261 @@ mod pallet { { type AccountId = T::AccountId; type Balance = BalanceOf; + type NegativeImbalance = NegativeImbalanceOf; type BlockNumber = T::BlockNumber; type MarketId = MarketIdOf; type Moment = MomentOf; type Origin = T::Origin; fn on_dispute( - disputes: &[MarketDispute], - market_id: &Self::MarketId, + _: &Self::MarketId, market: &MarketOf, - ) -> DispatchResult { + ) -> Result, DispatchError> { ensure!( market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, Error::::MarketDoesNotHaveSimpleDisputesMechanism ); - Self::remove_auto_resolve(disputes, market_id, market); - let curr_block_num = >::block_number(); - // each dispute resets dispute_duration - let dispute_duration_ends_at_block = - curr_block_num.saturating_add(market.deadlines.dispute_duration); - T::DisputeResolution::add_auto_resolve(market_id, dispute_duration_ends_at_block)?; - Ok(()) + + let res = + ResultWithWeightInfo { result: (), weight: T::WeightInfo::on_dispute_weight() }; + + Ok(res) } fn on_resolution( - disputes: &[MarketDispute], - _: &Self::MarketId, + market_id: &Self::MarketId, market: &MarketOf, - ) -> Result, DispatchError> { + ) -> Result>, DispatchError> { ensure!( market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, Error::::MarketDoesNotHaveSimpleDisputesMechanism ); ensure!(market.status == MarketStatus::Disputed, Error::::InvalidMarketStatus); - if let Some(last_dispute) = disputes.last() { - Ok(Some(last_dispute.outcome.clone())) - } else { - Err(Error::::InvalidMarketStatus.into()) + let disputes = Disputes::::get(market_id); + + let last_dispute = match disputes.last() { + Some(l) => l, + // if there are no disputes, then the market is resolved with the default report + None => { + return Ok(ResultWithWeightInfo { + result: None, + weight: T::WeightInfo::on_resolution_weight(disputes.len() as u32), + }); + } + }; + + let res = ResultWithWeightInfo { + result: Some(last_dispute.outcome.clone()), + weight: T::WeightInfo::on_resolution_weight(disputes.len() as u32), + }; + + Ok(res) + } + + fn exchange( + market_id: &Self::MarketId, + market: &MarketOf, + resolved_outcome: &OutcomeReport, + mut overall_imbalance: NegativeImbalanceOf, + ) -> Result>, DispatchError> { + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, + Error::::MarketDoesNotHaveSimpleDisputesMechanism + ); + ensure!(market.status == MarketStatus::Disputed, Error::::InvalidMarketStatus); + + let disputes = Disputes::::get(market_id); + + let mut correct_reporters: Vec = Vec::new(); + + for dispute in disputes.iter() { + if &dispute.outcome == resolved_outcome { + T::AssetManager::unreserve_named( + &Self::reserve_id(), + Asset::Ztg, + &dispute.by, + dispute.bond.saturated_into::().saturated_into(), + ); + + correct_reporters.push(dispute.by.clone()); + } else { + let (imbalance, _) = CurrencyOf::::slash_reserved_named( + &Self::reserve_id(), + &dispute.by, + dispute.bond.saturated_into::().saturated_into(), + ); + overall_imbalance.subsume(imbalance); + } + } + + // Fold all the imbalances into one and reward the correct reporters. The + // number of correct reporters might be zero if the market defaults to the + // report after abandoned dispute. In that case, the rewards remain slashed. + if let Some(reward_per_each) = + overall_imbalance.peek().checked_div(&correct_reporters.len().saturated_into()) + { + for correct_reporter in &correct_reporters { + let (actual_reward, leftover) = overall_imbalance.split(reward_per_each); + overall_imbalance = leftover; + CurrencyOf::::resolve_creating(correct_reporter, actual_reward); + } } + + Disputes::::remove(market_id); + + let res = ResultWithWeightInfo { + result: overall_imbalance, + weight: T::WeightInfo::exchange_weight(disputes.len() as u32), + }; + + Ok(res) } fn get_auto_resolve( - disputes: &[MarketDispute], - _: &Self::MarketId, + market_id: &Self::MarketId, market: &MarketOf, - ) -> Result, DispatchError> { + ) -> Result>, DispatchError> { ensure!( market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, Error::::MarketDoesNotHaveSimpleDisputesMechanism ); - Ok(Self::get_auto_resolve(disputes, market)) + let disputes = Disputes::::get(market_id); + + let res = ResultWithWeightInfo { + result: Self::get_auto_resolve(disputes.as_slice(), market), + weight: T::WeightInfo::get_auto_resolve_weight(disputes.len() as u32), + }; + + Ok(res) } fn has_failed( - _: &[MarketDispute], - _: &Self::MarketId, + market_id: &Self::MarketId, market: &MarketOf, - ) -> Result { + ) -> Result, DispatchError> { ensure!( market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, Error::::MarketDoesNotHaveSimpleDisputesMechanism ); - // TODO when does simple disputes fail? - Ok(false) + let disputes = >::get(market_id); + let disputes_len = disputes.len() as u32; + + let res = ResultWithWeightInfo { + result: disputes_len == T::MaxDisputes::get(), + weight: T::WeightInfo::has_failed_weight(disputes_len), + }; + + Ok(res) + } + + fn on_global_dispute( + market_id: &Self::MarketId, + market: &MarketOf, + ) -> Result< + ResultWithWeightInfo>>, + DispatchError, + > { + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, + Error::::MarketDoesNotHaveSimpleDisputesMechanism + ); + + let disputes_len = >::decode_len(market_id).unwrap_or(0) as u32; + + let res = ResultWithWeightInfo { + result: >::get(market_id) + .iter() + .map(|dispute| GlobalDisputeItem { + outcome: dispute.outcome.clone(), + owner: dispute.by.clone(), + initial_vote_amount: dispute.bond, + }) + .collect(), + weight: T::WeightInfo::on_global_dispute_weight(disputes_len), + }; + + Ok(res) + } + + fn clear( + market_id: &Self::MarketId, + market: &MarketOf, + ) -> Result, DispatchError> { + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, + Error::::MarketDoesNotHaveSimpleDisputesMechanism + ); + + let mut disputes_len = 0u32; + // `Disputes` is emtpy unless the market is disputed, so this is just a defensive + // check. + if market.status == MarketStatus::Disputed { + disputes_len = Disputes::::decode_len(market_id).unwrap_or(0) as u32; + for dispute in Disputes::::take(market_id).iter() { + T::AssetManager::unreserve_named( + &Self::reserve_id(), + Asset::Ztg, + &dispute.by, + dispute.bond.saturated_into::().saturated_into(), + ); + } + } + + let res = ResultWithWeightInfo { + result: (), + weight: T::WeightInfo::clear_weight(disputes_len), + }; + + Ok(res) } } impl SimpleDisputesPalletApi for Pallet where T: Config {} - #[pallet::pallet] - pub struct Pallet(PhantomData); + // No-one can bound more than BalanceOf, therefore, this functions saturates + pub fn default_outcome_bond(n: usize) -> BalanceOf + where + T: Config, + { + T::OutcomeBond::get().saturating_add( + T::OutcomeFactor::get().saturating_mul(n.saturated_into::().into()), + ) + } +} + +#[cfg(any(feature = "runtime-benchmarks", test))] +pub(crate) fn market_mock() -> MarketOf +where + T: crate::Config, +{ + use frame_support::traits::Get; + use sp_runtime::{traits::AccountIdConversion, SaturatedConversion}; + use zeitgeist_primitives::types::{MarketBonds, ScoringRule}; + + zeitgeist_primitives::types::Market { + base_asset: Asset::Ztg, + creation: zeitgeist_primitives::types::MarketCreation::Permissionless, + creator_fee: 0, + creator: T::PalletId::get().into_account_truncating(), + market_type: zeitgeist_primitives::types::MarketType::Scalar(0..=100), + dispute_mechanism: zeitgeist_primitives::types::MarketDisputeMechanism::SimpleDisputes, + metadata: Default::default(), + oracle: T::PalletId::get().into_account_truncating(), + period: zeitgeist_primitives::types::MarketPeriod::Block(Default::default()), + deadlines: zeitgeist_primitives::types::Deadlines { + grace_period: 1_u32.into(), + oracle_duration: 1_u32.into(), + dispute_duration: 42_u32.into(), + }, + report: Some(zeitgeist_primitives::types::Report { + outcome: OutcomeReport::Scalar(0), + at: 0u64.saturated_into(), + by: T::PalletId::get().into_account_truncating(), + }), + resolved_outcome: None, + scoring_rule: ScoringRule::CPMM, + status: zeitgeist_primitives::types::MarketStatus::Disputed, + bonds: MarketBonds::default(), + } } diff --git a/zrml/simple-disputes/src/mock.rs b/zrml/simple-disputes/src/mock.rs index af96ecece..5119f1343 100644 --- a/zrml/simple-disputes/src/mock.rs +++ b/zrml/simple-disputes/src/mock.rs @@ -20,10 +20,9 @@ use crate::{self as zrml_simple_disputes}; use frame_support::{ - construct_runtime, + construct_runtime, ord_parameter_types, pallet_prelude::{DispatchError, Weight}, traits::Everything, - BoundedVec, }; use sp_runtime::{ testing::Header, @@ -31,15 +30,30 @@ use sp_runtime::{ }; use zeitgeist_primitives::{ constants::mock::{ - BlockHashCount, MaxReserves, MinimumPeriod, PmPalletId, SimpleDisputesPalletId, + BlockHashCount, ExistentialDeposits, GetNativeCurrencyId, MaxDisputes, MaxReserves, + MinimumPeriod, OutcomeBond, OutcomeFactor, PmPalletId, SimpleDisputesPalletId, BASE, }, traits::DisputeResolutionApi, types::{ - AccountIdTest, Asset, Balance, BlockNumber, BlockTest, Hash, Index, Market, MarketDispute, - MarketId, Moment, UncheckedExtrinsicTest, + AccountIdTest, Amount, Asset, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, + CurrencyId, Hash, Index, Market, MarketId, Moment, UncheckedExtrinsicTest, }, }; +pub const ALICE: AccountIdTest = 0; +pub const BOB: AccountIdTest = 1; +pub const CHARLIE: AccountIdTest = 2; +pub const DAVE: AccountIdTest = 3; +pub const EVE: AccountIdTest = 4; +pub const FRED: AccountIdTest = 5; +pub const SUDO: AccountIdTest = 69; + +pub const INITIAL_BALANCE: u128 = 1_000 * BASE; + +ord_parameter_types! { + pub const Sudo: AccountIdTest = SUDO; +} + construct_runtime!( pub enum Runtime where @@ -47,11 +61,13 @@ construct_runtime!( NodeBlock = BlockTest, UncheckedExtrinsic = UncheckedExtrinsicTest, { + AssetManager: orml_currencies::{Call, Pallet, Storage}, Balances: pallet_balances::{Call, Config, Event, Pallet, Storage}, MarketCommons: zrml_market_commons::{Pallet, Storage}, SimpleDisputes: zrml_simple_disputes::{Event, Pallet, Storage}, System: frame_system::{Call, Config, Event, Pallet, Storage}, Timestamp: pallet_timestamp::{Pallet}, + Tokens: orml_tokens::{Config, Event, Pallet, Storage}, } ); @@ -63,7 +79,6 @@ impl DisputeResolutionApi for NoopResolution { type Balance = Balance; type BlockNumber = BlockNumber; type MarketId = MarketId; - type MaxDisputes = u32; type Moment = Moment; fn resolve( @@ -93,19 +108,18 @@ impl DisputeResolutionApi for NoopResolution { fn remove_auto_resolve(_market_id: &Self::MarketId, _resolve_at: Self::BlockNumber) -> u32 { 0u32 } - - fn get_disputes( - _market_id: &Self::MarketId, - ) -> BoundedVec, Self::MaxDisputes> { - Default::default() - } } impl crate::Config for Runtime { + type AssetManager = AssetManager; type Event = (); type DisputeResolution = NoopResolution; type MarketCommons = MarketCommons; + type MaxDisputes = MaxDisputes; + type OutcomeBond = OutcomeBond; + type OutcomeFactor = OutcomeFactor; type PalletId = SimpleDisputesPalletId; + type WeightInfo = zrml_simple_disputes::weights::WeightInfo; } impl frame_system::Config for Runtime { @@ -147,6 +161,29 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); } +impl orml_currencies::Config for Runtime { + type GetNativeCurrencyId = GetNativeCurrencyId; + type MultiCurrency = Tokens; + type NativeCurrency = BasicCurrencyAdapter; + type WeightInfo = (); +} + +impl orml_tokens::Config for Runtime { + type Amount = Amount; + type Balance = Balance; + type CurrencyId = CurrencyId; + type DustRemovalWhitelist = Everything; + type Event = (); + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = (); + type MaxReserves = MaxReserves; + type OnDust = (); + type OnKilledTokenAccount = (); + type OnNewTokenAccount = (); + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); +} + impl zrml_market_commons::Config for Runtime { type Currency = Balances; type MarketId = MarketId; @@ -161,10 +198,34 @@ impl pallet_timestamp::Config for Runtime { type WeightInfo = (); } -pub struct ExtBuilder; +pub struct ExtBuilder { + balances: Vec<(AccountIdTest, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + balances: vec![ + (ALICE, INITIAL_BALANCE), + (BOB, INITIAL_BALANCE), + (CHARLIE, INITIAL_BALANCE), + (DAVE, INITIAL_BALANCE), + (EVE, INITIAL_BALANCE), + (FRED, INITIAL_BALANCE), + (SUDO, INITIAL_BALANCE), + ], + } + } +} impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { - frame_system::GenesisConfig::default().build_storage::().unwrap().into() + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + pallet_balances::GenesisConfig:: { balances: self.balances } + .assimilate_storage(&mut t) + .unwrap(); + + t.into() } } diff --git a/zrml/simple-disputes/src/simple_disputes_pallet_api.rs b/zrml/simple-disputes/src/simple_disputes_pallet_api.rs index 3c723b624..f01588aaa 100644 --- a/zrml/simple-disputes/src/simple_disputes_pallet_api.rs +++ b/zrml/simple-disputes/src/simple_disputes_pallet_api.rs @@ -1,3 +1,4 @@ +// Copyright 2023 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -15,6 +16,6 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use zeitgeist_primitives::traits::DisputeApi; +use zeitgeist_primitives::traits::{DisputeApi, DisputeMaxWeightApi}; -pub trait SimpleDisputesPalletApi: DisputeApi {} +pub trait SimpleDisputesPalletApi: DisputeApi + DisputeMaxWeightApi {} diff --git a/zrml/simple-disputes/src/tests.rs b/zrml/simple-disputes/src/tests.rs index f9e9d87af..f5f35ba89 100644 --- a/zrml/simple-disputes/src/tests.rs +++ b/zrml/simple-disputes/src/tests.rs @@ -20,10 +20,11 @@ use crate::{ mock::{ExtBuilder, Runtime, SimpleDisputes}, - Error, MarketOf, + Disputes, Error, MarketOf, }; -use frame_support::assert_noop; +use frame_support::{assert_noop, BoundedVec}; use zeitgeist_primitives::{ + constants::mock::{OutcomeBond, OutcomeFactor}, traits::DisputeApi, types::{ Asset, Deadlines, Market, MarketBonds, MarketCreation, MarketDispute, @@ -46,16 +47,16 @@ const DEFAULT_MARKET: MarketOf = Market { resolved_outcome: None, scoring_rule: ScoringRule::CPMM, status: MarketStatus::Disputed, - bonds: MarketBonds { creation: None, oracle: None, outsider: None }, + bonds: MarketBonds { creation: None, oracle: None, outsider: None, dispute: None }, }; #[test] fn on_dispute_denies_non_simple_disputes_markets() { - ExtBuilder.build().execute_with(|| { + ExtBuilder::default().build().execute_with(|| { let mut market = DEFAULT_MARKET; market.dispute_mechanism = MarketDisputeMechanism::Court; assert_noop!( - SimpleDisputes::on_dispute(&[], &0, &market), + SimpleDisputes::on_dispute(&0, &market), Error::::MarketDoesNotHaveSimpleDisputesMechanism ); }); @@ -63,11 +64,11 @@ fn on_dispute_denies_non_simple_disputes_markets() { #[test] fn on_resolution_denies_non_simple_disputes_markets() { - ExtBuilder.build().execute_with(|| { + ExtBuilder::default().build().execute_with(|| { let mut market = DEFAULT_MARKET; market.dispute_mechanism = MarketDisputeMechanism::Court; assert_noop!( - SimpleDisputes::on_resolution(&[], &0, &market), + SimpleDisputes::on_resolution(&0, &market), Error::::MarketDoesNotHaveSimpleDisputesMechanism ); }); @@ -75,16 +76,33 @@ fn on_resolution_denies_non_simple_disputes_markets() { #[test] fn on_resolution_sets_the_last_dispute_of_disputed_markets_as_the_canonical_outcome() { - ExtBuilder.build().execute_with(|| { + ExtBuilder::default().build().execute_with(|| { let mut market = DEFAULT_MARKET; market.status = MarketStatus::Disputed; - let disputes = [ - MarketDispute { at: 0, by: 0, outcome: OutcomeReport::Scalar(0) }, - MarketDispute { at: 0, by: 0, outcome: OutcomeReport::Scalar(20) }, - ]; + let disputes = BoundedVec::try_from( + [ + MarketDispute { + at: 0, + by: 0, + outcome: OutcomeReport::Scalar(0), + bond: OutcomeBond::get(), + }, + MarketDispute { + at: 0, + by: 0, + outcome: OutcomeReport::Scalar(20), + bond: OutcomeFactor::get() * OutcomeBond::get(), + }, + ] + .to_vec(), + ) + .unwrap(); + Disputes::::insert(0, &disputes); assert_eq!( - &SimpleDisputes::on_resolution(&disputes, &0, &market).unwrap().unwrap(), + &SimpleDisputes::on_resolution(&0, &market).unwrap().result.unwrap(), &disputes.last().unwrap().outcome ) }); } + +// TODO test `suggest_outcome` functionality and API functionality diff --git a/zrml/simple-disputes/src/weights.rs b/zrml/simple-disputes/src/weights.rs new file mode 100644 index 000000000..bfbc6cc8c --- /dev/null +++ b/zrml/simple-disputes/src/weights.rs @@ -0,0 +1,133 @@ +// Copyright 2023 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 . + +//! Autogenerated weights for zrml_simple_disputes +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-01-19, STEPS: `10`, REPEAT: 10, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Native), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/debug/zeitgeist +// benchmark +// pallet +// --chain=dev +// --steps=10 +// --repeat=10 +// --pallet=zrml_simple_disputes +// --extrinsic=* +// --execution=Native +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./zrml/simple-disputes/src/weights2.rs +// --template=./misc/weight_template.hbs + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use core::marker::PhantomData; +use frame_support::{traits::Get, weights::Weight}; + +/// Trait containing the required functions for weight retrival within +/// zrml_simple_disputes (automatically generated) +pub trait WeightInfoZeitgeist { + fn suggest_outcome(d: u32, r: u32, e: u32) -> Weight; + fn on_dispute_weight() -> Weight; + fn on_resolution_weight(d: u32) -> Weight; + fn exchange_weight(d: u32) -> Weight; + fn get_auto_resolve_weight(d: u32) -> Weight; + fn has_failed_weight(d: u32) -> Weight; + fn on_global_dispute_weight(d: u32) -> Weight; + fn clear_weight(d: u32) -> Weight; +} + +/// Weight functions for zrml_simple_disputes (automatically generated) +pub struct WeightInfo(PhantomData); +impl WeightInfoZeitgeist for WeightInfo { + // Storage: MarketCommons Markets (r:1 w:0) + // Storage: SimpleDisputes Disputes (r:1 w:1) + // Storage: Balances Reserves (r:1 w:1) + // Storage: PredictionMarkets MarketIdsPerDisputeBlock (r:2 w:2) + fn suggest_outcome(d: u32, r: u32, e: u32) -> Weight { + Weight::from_ref_time(400_160_000) + // Standard Error: 1_302_000 + .saturating_add(Weight::from_ref_time(3_511_000).saturating_mul(d.into())) + // Standard Error: 69_000 + .saturating_add(Weight::from_ref_time(324_000).saturating_mul(r.into())) + // Standard Error: 69_000 + .saturating_add(Weight::from_ref_time(311_000).saturating_mul(e.into())) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + + fn on_dispute_weight() -> Weight { + Weight::from_ref_time(0) + } + // Storage: SimpleDisputes Disputes (r:1 w:0) + fn on_resolution_weight(d: u32) -> Weight { + Weight::from_ref_time(5_464_000) + // Standard Error: 3_000 + .saturating_add(Weight::from_ref_time(210_000).saturating_mul(d.into())) + .saturating_add(T::DbWeight::get().reads(1)) + } + // Storage: SimpleDisputes Disputes (r:1 w:1) + // Storage: Balances Reserves (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn exchange_weight(d: u32) -> Weight { + Weight::from_ref_time(18_573_000) + // Standard Error: 14_000 + .saturating_add(Weight::from_ref_time(19_710_000).saturating_mul(d.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(d.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(d.into()))) + } + // Storage: SimpleDisputes Disputes (r:1 w:0) + fn get_auto_resolve_weight(d: u32) -> Weight { + Weight::from_ref_time(5_535_000) + // Standard Error: 3_000 + .saturating_add(Weight::from_ref_time(145_000).saturating_mul(d.into())) + .saturating_add(T::DbWeight::get().reads(1)) + } + // Storage: SimpleDisputes Disputes (r:1 w:0) + fn has_failed_weight(d: u32) -> Weight { + Weight::from_ref_time(5_685_000) + // Standard Error: 2_000 + .saturating_add(Weight::from_ref_time(117_000).saturating_mul(d.into())) + .saturating_add(T::DbWeight::get().reads(1)) + } + // Storage: SimpleDisputes Disputes (r:1 w:0) + fn on_global_dispute_weight(d: u32) -> Weight { + Weight::from_ref_time(5_815_000) + // Standard Error: 2_000 + .saturating_add(Weight::from_ref_time(66_000).saturating_mul(d.into())) + .saturating_add(T::DbWeight::get().reads(1)) + } + // Storage: SimpleDisputes Disputes (r:1 w:1) + // Storage: Balances Reserves (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn clear_weight(d: u32) -> Weight { + Weight::from_ref_time(15_958_000) + // Standard Error: 17_000 + .saturating_add(Weight::from_ref_time(13_085_000).saturating_mul(d.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(d.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(d.into()))) + } +}