diff --git a/docs/changelog_for_devs.md b/docs/changelog_for_devs.md index 9f113d413..fde20e06c 100644 --- a/docs/changelog_for_devs.md +++ b/docs/changelog_for_devs.md @@ -5,6 +5,12 @@ which has three fields: `who` (the account that reserved the bond), `value` (the amount reserved), `is_settled` (a flag which determines if the bond was already unreserved and/or (partially) slashed). +- The market dispute mechanisms are now able to control their resolution. The + `CorrectionPeriod` parameter determines how long the authorized pallet can + call `authorize_market_outcome` again after the first call to it (fat-finger + protection). The authority report now includes its resolution block number. + This is the time of the first call to `authorize_market_outcome` plus the + `CorrectionPeriod`. # v0.3.7 diff --git a/primitives/src/constants/mock.rs b/primitives/src/constants/mock.rs index 5ceec13d6..515924b50 100644 --- a/primitives/src/constants/mock.rs +++ b/primitives/src/constants/mock.rs @@ -11,6 +11,7 @@ use orml_traits::parameter_type_with_key; // Authorized parameter_types! { pub const AuthorizedPalletId: PalletId = PalletId(*b"zge/atzd"); + pub const CorrectionPeriod: BlockNumber = 4; } // Court diff --git a/primitives/src/market.rs b/primitives/src/market.rs index a2ccf417a..9584fd274 100644 --- a/primitives/src/market.rs +++ b/primitives/src/market.rs @@ -264,6 +264,12 @@ pub struct Report { pub outcome: OutcomeReport, } +#[derive(Clone, Decode, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] +pub struct AuthorityReport { + pub resolve_at: BlockNumber, + pub outcome: OutcomeReport, +} + /// Contains a market id and the market period. /// /// * `BN`: Block Number diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index ee21643ac..1fc14cba1 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -21,7 +21,7 @@ mod market_id; mod swaps; mod zeitgeist_multi_reservable_currency; -pub use dispute_api::DisputeApi; +pub use dispute_api::{DisputeApi, 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 41733edd8..17d062692 100644 --- a/primitives/src/traits/dispute_api.rs +++ b/primitives/src/traits/dispute_api.rs @@ -16,7 +16,7 @@ // along with Zeitgeist. If not, see . use crate::{market::MarketDispute, outcome_report::OutcomeReport, types::Market}; -use frame_support::dispatch::DispatchResult; +use frame_support::{dispatch::DispatchResult, pallet_prelude::Weight, BoundedVec}; use sp_runtime::DispatchError; // Abstraction of the market type, which is not a part of `DisputeApi` because Rust doesn't support @@ -60,4 +60,83 @@ pub trait DisputeApi { market_id: &Self::MarketId, market: &MarketOfDisputeApi, ) -> Result, DispatchError>; + + /// Query the future resolution block of a disputed market. + /// **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>; + + /// 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; +} + +type MarketOfDisputeResolutionApi = Market< + ::AccountId, + ::Balance, + ::BlockNumber, + ::Moment, +>; + +pub trait DisputeResolutionApi { + type AccountId; + type Balance; + type BlockNumber; + type MarketId; + type MaxDisputes; + type Moment; + + /// Resolve a market. + /// + /// **Should** only be called if the market dispute + /// mechanism is ready for the resolution ([`DisputeApi::on_resolution`]). + /// + /// # Returns + /// + /// Returns the consumed weight. + fn resolve( + market_id: &Self::MarketId, + market: &MarketOfDisputeResolutionApi, + ) -> Result; + + /// Add a future block resolution of a disputed market. + /// + /// # Returns + /// + /// Returns the number of elements in the storage structure. + fn add_auto_resolve( + market_id: &Self::MarketId, + resolve_at: Self::BlockNumber, + ) -> Result; + + /// Check if a future block resolution of a disputed market exists. + /// + /// # Returns + /// + /// Returns `true` if the future block resolution exists, otherwise `false`. + fn auto_resolve_exists(market_id: &Self::MarketId, resolve_at: Self::BlockNumber) -> bool; + + /// Remove a future block resolution of a disputed market. + /// + /// # Returns + /// + /// 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 68bf1c76d..ed4fd4d2d 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -51,6 +51,7 @@ pub(crate) const FEES_AND_TIPS_BURN_PERCENTAGE: u32 = 0; parameter_types! { // Authorized pub const AuthorizedPalletId: PalletId = AUTHORIZED_PALLET_ID; + pub const CorrectionPeriod: BlockNumber = BLOCKS_PER_DAY; // Authority pub const MaxAuthorities: u32 = 32; diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 09466e035..70389df19 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::RecordBonds, + ( + zrml_prediction_markets::migrations::RecordBonds, + zrml_prediction_markets::migrations::AddFieldToAuthorityReport, + ), >; #[cfg(not(feature = "parachain"))] @@ -68,7 +71,10 @@ macro_rules! decl_common_types { frame_system::ChainContext, Runtime, AllPalletsWithSystem, - zrml_prediction_markets::migrations::RecordBonds, + ( + zrml_prediction_markets::migrations::RecordBonds, + zrml_prediction_markets::migrations::AddFieldToAuthorityReport, + ), >; pub type Header = generic::Header; @@ -912,15 +918,18 @@ macro_rules! impl_config_traits { impl parachain_info::Config for Runtime {} impl zrml_authorized::Config for Runtime { + type AuthorizedDisputeResolutionOrigin = EnsureRootOrHalfAdvisoryCommittee; + type CorrectionPeriod = CorrectionPeriod; + type DisputeResolution = zrml_prediction_markets::Pallet; type Event = Event; type MarketCommons = MarketCommons; - type AuthorizedDisputeResolutionOrigin = EnsureRootOrHalfAdvisoryCommittee; type PalletId = AuthorizedPalletId; type WeightInfo = zrml_authorized::weights::WeightInfo; } impl zrml_court::Config for Runtime { type CourtCaseDuration = CourtCaseDuration; + type DisputeResolution = zrml_prediction_markets::Pallet; type Event = Event; type MarketCommons = MarketCommons; type PalletId = CourtPalletId; @@ -1033,6 +1042,7 @@ macro_rules! impl_config_traits { } impl zrml_simple_disputes::Config for Runtime { + type DisputeResolution = zrml_prediction_markets::Pallet; type Event = Event; type MarketCommons = MarketCommons; type PalletId = SimpleDisputesPalletId; diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 062f42f7e..060e26a5b 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -51,6 +51,7 @@ pub(crate) const FEES_AND_TIPS_BURN_PERCENTAGE: u32 = 0; parameter_types! { // Authorized pub const AuthorizedPalletId: PalletId = AUTHORIZED_PALLET_ID; + pub const CorrectionPeriod: BlockNumber = BLOCKS_PER_DAY; // Authority pub const MaxAuthorities: u32 = 32; diff --git a/zrml/authorized/src/benchmarks.rs b/zrml/authorized/src/benchmarks.rs index e19beaf35..b2f590903 100644 --- a/zrml/authorized/src/benchmarks.rs +++ b/zrml/authorized/src/benchmarks.rs @@ -23,19 +23,77 @@ #[cfg(test)] use crate::Pallet as Authorized; -use crate::{market_mock, Call, Config, Pallet}; +use crate::{market_mock, AuthorizedOutcomeReports, Call, Config, Pallet}; use frame_benchmarking::benchmarks; -use frame_support::{dispatch::UnfilteredDispatchable, traits::EnsureOrigin}; -use zeitgeist_primitives::types::OutcomeReport; +use frame_support::{ + dispatch::UnfilteredDispatchable, + traits::{EnsureOrigin, Get}, +}; +use sp_runtime::traits::Saturating; +use zeitgeist_primitives::{ + traits::DisputeResolutionApi, + types::{AuthorityReport, OutcomeReport}, +}; use zrml_market_commons::MarketCommonsPalletApi; benchmarks! { - authorize_market_outcome { + authorize_market_outcome_first_report { + let m in 1..63; + + let origin = T::AuthorizedDisputeResolutionOrigin::successful_origin(); + let market_id = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market).unwrap(); + + frame_system::Pallet::::set_block_number(42u32.into()); + let now = frame_system::Pallet::::block_number(); + let correction_period_ends_at = now.saturating_add(T::CorrectionPeriod::get()); + for _ in 1..=m { + let id = T::MarketCommons::push_market(market_mock::()).unwrap(); + T::DisputeResolution::add_auto_resolve(&id, correction_period_ends_at).unwrap(); + } + + let call = Call::::authorize_market_outcome { + market_id, + outcome: OutcomeReport::Scalar(1), + }; + }: { + call.dispatch_bypass_filter(origin)? + } verify { + let report = AuthorityReport { + resolve_at: correction_period_ends_at, + outcome: OutcomeReport::Scalar(1) + }; + assert_eq!(AuthorizedOutcomeReports::::get(market_id).unwrap(), report); + } + + authorize_market_outcome_existing_report { let origin = T::AuthorizedDisputeResolutionOrigin::successful_origin(); + let market_id = 0u32.into(); let market = market_mock::(); T::MarketCommons::push_market(market).unwrap(); - let call = Call::::authorize_market_outcome { market_id: 0_u32.into(), outcome: OutcomeReport::Scalar(1) }; - }: { call.dispatch_bypass_filter(origin)? } + + frame_system::Pallet::::set_block_number(42u32.into()); + + let now = frame_system::Pallet::::block_number(); + let resolve_at = now.saturating_add(T::CorrectionPeriod::get()); + + let report = AuthorityReport { resolve_at, outcome: OutcomeReport::Scalar(0) }; + AuthorizedOutcomeReports::::insert(market_id, report); + + let now = frame_system::Pallet::::block_number(); + frame_system::Pallet::::set_block_number(now + 42u32.into()); + + let call = Call::::authorize_market_outcome { + market_id, + outcome: OutcomeReport::Scalar(1), + }; + }: { + call.dispatch_bypass_filter(origin)? + } verify { + let report = AuthorityReport { resolve_at, outcome: OutcomeReport::Scalar(1) }; + assert_eq!(AuthorizedOutcomeReports::::get(market_id).unwrap(), report); + } impl_benchmark_test_suite!( Authorized, diff --git a/zrml/authorized/src/lib.rs b/zrml/authorized/src/lib.rs index 23bd36d92..d33cc2789 100644 --- a/zrml/authorized/src/lib.rs +++ b/zrml/authorized/src/lib.rs @@ -24,6 +24,7 @@ mod authorized_pallet_api; mod benchmarks; pub mod migrations; mod mock; +mod mock_storage; mod tests; pub mod weights; @@ -35,22 +36,25 @@ mod pallet { use crate::{weights::WeightInfoZeitgeist, AuthorizedPalletApi}; use core::marker::PhantomData; use frame_support::{ - dispatch::DispatchResult, + dispatch::{DispatchResult, DispatchResultWithPostInfo}, ensure, - pallet_prelude::{EnsureOrigin, OptionQuery, StorageMap}, + pallet_prelude::{ConstU32, EnsureOrigin, OptionQuery, StorageMap}, traits::{Currency, Get, Hooks, IsType, StorageVersion}, PalletId, Twox64Concat, }; use frame_system::pallet_prelude::OriginFor; - use sp_runtime::DispatchError; + use sp_runtime::{traits::Saturating, DispatchError}; use zeitgeist_primitives::{ - traits::DisputeApi, - types::{Market, MarketDispute, MarketDisputeMechanism, MarketStatus, OutcomeReport}, + traits::{DisputeApi, DisputeResolutionApi}, + types::{ + AuthorityReport, Market, MarketDispute, MarketDisputeMechanism, MarketStatus, + OutcomeReport, + }, }; use zrml_market_commons::MarketCommonsPalletApi; /// The current storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(3); pub(crate) type BalanceOf = as Currency<::AccountId>>::Balance; @@ -59,7 +63,9 @@ mod pallet { pub(crate) type MarketIdOf = <::MarketCommons as MarketCommonsPalletApi>::MarketId; pub(crate) type MomentOf = <::MarketCommons as MarketCommonsPalletApi>::Moment; - type MarketOf = Market< + + pub type CacheSize = ConstU32<64>; + pub(crate) type MarketOf = Market< ::AccountId, BalanceOf, ::BlockNumber, @@ -70,22 +76,43 @@ mod pallet { impl Pallet { /// Overwrites already provided outcomes for the same market and account. #[frame_support::transactional] - #[pallet::weight(T::WeightInfo::authorize_market_outcome())] + #[pallet::weight( + T::WeightInfo::authorize_market_outcome_first_report(CacheSize::get()).max( + T::WeightInfo::authorize_market_outcome_existing_report(), + ) + )] pub fn authorize_market_outcome( origin: OriginFor, market_id: MarketIdOf, outcome: OutcomeReport, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { T::AuthorizedDisputeResolutionOrigin::ensure_origin(origin)?; let market = T::MarketCommons::market(&market_id)?; ensure!(market.status == MarketStatus::Disputed, Error::::MarketIsNotDisputed); ensure!(market.matches_outcome_report(&outcome), Error::::OutcomeMismatch); - match market.dispute_mechanism { - MarketDisputeMechanism::Authorized => { - AuthorizedOutcomeReports::::insert(market_id, outcome); - Ok(()) + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::Authorized, + Error::::MarketDoesNotHaveDisputeMechanismAuthorized + ); + + let now = frame_system::Pallet::::block_number(); + + let report_opt = AuthorizedOutcomeReports::::get(market_id); + let (report, ids_len) = match &report_opt { + Some(report) => (AuthorityReport { resolve_at: report.resolve_at, outcome }, 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) } - _ => Err(Error::::MarketDoesNotHaveDisputeMechanismAuthorized.into()), + }; + + AuthorizedOutcomeReports::::insert(market_id, report); + + if report_opt.is_none() { + Ok(Some(T::WeightInfo::authorize_market_outcome_first_report(ids_len)).into()) + } else { + Ok(Some(T::WeightInfo::authorize_market_outcome_existing_report()).into()) } } } @@ -95,7 +122,18 @@ mod pallet { /// Event type Event: From> + IsType<::Event>; - /// Market commons + /// The period, in which the authority can correct the outcome of a market. + /// This value must not be zero. + #[pallet::constant] + type CorrectionPeriod: Get; + + type DisputeResolution: DisputeResolutionApi< + AccountId = Self::AccountId, + BlockNumber = Self::BlockNumber, + MarketId = MarketIdOf, + Moment = MomentOf, + >; + type MarketCommons: MarketCommonsPalletApi< AccountId = Self::AccountId, BlockNumber = Self::BlockNumber, @@ -118,6 +156,8 @@ 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, } @@ -134,7 +174,15 @@ mod pallet { #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData); - impl Pallet where T: Config {} + impl Pallet + where + T: Config, + { + /// Return the resolution block number for the given market. + fn get_auto_resolve(market_id: &MarketIdOf) -> Option { + AuthorizedOutcomeReports::::get(market_id).map(|report| report.resolve_at) + } + } impl DisputeApi for Pallet where @@ -148,19 +196,54 @@ mod pallet { type Origin = T::Origin; fn on_dispute( - _: &[MarketDispute], + disputes: &[MarketDispute], _: &Self::MarketId, - _: &MarketOf, + market: &MarketOf, ) -> DispatchResult { + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::Authorized, + Error::::MarketDoesNotHaveDisputeMechanismAuthorized + ); + ensure!(disputes.is_empty(), Error::::OnlyOneDisputeAllowed); Ok(()) } fn on_resolution( _: &[MarketDispute], market_id: &Self::MarketId, - _: &MarketOf, + market: &MarketOf, ) -> Result, DispatchError> { - Ok(AuthorizedOutcomeReports::::take(market_id)) + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::Authorized, + Error::::MarketDoesNotHaveDisputeMechanismAuthorized + ); + let report = AuthorizedOutcomeReports::::take(market_id); + Ok(report.map(|r| r.outcome)) + } + + fn get_auto_resolve( + _: &[MarketDispute], + market_id: &Self::MarketId, + market: &MarketOf, + ) -> Result, DispatchError> { + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::Authorized, + Error::::MarketDoesNotHaveDisputeMechanismAuthorized + ); + Ok(Self::get_auto_resolve(market_id)) + } + + fn has_failed( + _: &[MarketDispute], + _: &Self::MarketId, + market: &MarketOf, + ) -> Result { + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::Authorized, + Error::::MarketDoesNotHaveDisputeMechanismAuthorized + ); + + Ok(false) } } @@ -170,7 +253,7 @@ mod pallet { #[pallet::storage] #[pallet::getter(fn outcomes)] pub type AuthorizedOutcomeReports = - StorageMap<_, Twox64Concat, MarketIdOf, OutcomeReport, OptionQuery>; + StorageMap<_, Twox64Concat, MarketIdOf, AuthorityReport, OptionQuery>; } #[cfg(any(feature = "runtime-benchmarks", test))] diff --git a/zrml/authorized/src/migrations.rs b/zrml/authorized/src/migrations.rs index 16887b73b..55d6e03fb 100644 --- a/zrml/authorized/src/migrations.rs +++ b/zrml/authorized/src/migrations.rs @@ -14,3 +14,59 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . + +// 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 +// and previous migrations. +mod utility { + use crate::{BalanceOf, Config, MarketIdOf}; + use alloc::vec::Vec; + use frame_support::{ + migration::{get_storage_value, put_storage_value}, + storage::{storage_prefix, unhashed}, + traits::StorageVersion, + Blake2_128Concat, StorageHasher, + }; + use parity_scale_codec::Encode; + use zeitgeist_primitives::types::{Pool, PoolId}; + + #[allow(unused)] + const SWAPS: &[u8] = b"Swaps"; + #[allow(unused)] + const POOLS: &[u8] = b"Pools"; + #[allow(unused)] + fn storage_prefix_of_swaps_pallet() -> [u8; 32] { + storage_prefix(b"Swaps", b":__STORAGE_VERSION__:") + } + #[allow(unused)] + pub fn key_to_hash(key: K) -> Vec + where + H: StorageHasher, + K: Encode, + { + key.using_encoded(H::hash).as_ref().to_vec() + } + #[allow(unused)] + pub fn get_on_chain_storage_version_of_swaps_pallet() -> StorageVersion { + let key = storage_prefix_of_swaps_pallet(); + unhashed::get_or_default(&key) + } + #[allow(unused)] + pub fn put_storage_version_of_swaps_pallet(value: u16) { + let key = storage_prefix_of_swaps_pallet(); + unhashed::put(&key, &StorageVersion::new(value)); + } + #[allow(unused)] + pub fn get_pool(pool_id: PoolId) -> Option, MarketIdOf>> { + let hash = key_to_hash::(pool_id); + let pool_maybe = + get_storage_value::, MarketIdOf>>>(SWAPS, POOLS, &hash); + pool_maybe.unwrap_or(None) + } + #[allow(unused)] + pub fn set_pool(pool_id: PoolId, pool: Pool, MarketIdOf>) { + let hash = key_to_hash::(pool_id); + put_storage_value(SWAPS, POOLS, &hash, Some(pool)); + } +} diff --git a/zrml/authorized/src/mock.rs b/zrml/authorized/src/mock.rs index 7dece7580..056e59f1e 100644 --- a/zrml/authorized/src/mock.rs +++ b/zrml/authorized/src/mock.rs @@ -17,8 +17,16 @@ #![cfg(test)] -use crate::{self as zrml_authorized}; -use frame_support::{construct_runtime, ord_parameter_types, traits::Everything}; +extern crate alloc; + +use crate::{self as zrml_authorized, mock_storage::pallet as mock_storage}; +use alloc::{vec, vec::Vec}; +use frame_support::{ + construct_runtime, ord_parameter_types, + pallet_prelude::{DispatchError, Weight}, + traits::Everything, + BoundedVec, +}; use frame_system::EnsureSignedBy; use sp_runtime::{ testing::Header, @@ -26,11 +34,13 @@ use sp_runtime::{ }; use zeitgeist_primitives::{ constants::mock::{ - AuthorizedPalletId, BlockHashCount, MaxReserves, MinimumPeriod, PmPalletId, BASE, + AuthorizedPalletId, BlockHashCount, CorrectionPeriod, MaxReserves, MinimumPeriod, + PmPalletId, BASE, }, + traits::DisputeResolutionApi, types::{ - AccountIdTest, Balance, BlockNumber, BlockTest, Hash, Index, MarketId, Moment, - UncheckedExtrinsicTest, + AccountIdTest, Balance, BlockNumber, BlockTest, Hash, Index, Market, MarketDispute, + MarketId, Moment, OutcomeReport, UncheckedExtrinsicTest, }, }; @@ -50,15 +60,75 @@ construct_runtime!( MarketCommons: zrml_market_commons::{Pallet, Storage}, System: frame_system::{Call, Config, Event, Pallet, Storage}, Timestamp: pallet_timestamp::{Pallet}, + // Just a mock storage for testing. + MockStorage: mock_storage::{Storage}, } ); ord_parameter_types! { pub const AuthorizedDisputeResolutionUser: AccountIdTest = ALICE; + pub const MaxDisputes: u32 = 64; +} + +// MockResolution implements DisputeResolutionApi with no-ops. +pub struct MockResolution; + +impl DisputeResolutionApi for MockResolution { + type AccountId = AccountIdTest; + type Balance = Balance; + type BlockNumber = BlockNumber; + type MarketId = MarketId; + type MaxDisputes = MaxDisputes; + type Moment = Moment; + + fn resolve( + _market_id: &Self::MarketId, + _market: &Market, + ) -> Result { + Ok(0) + } + + fn add_auto_resolve( + market_id: &Self::MarketId, + resolve_at: Self::BlockNumber, + ) -> Result { + let ids_len = >::try_mutate( + resolve_at, + |ids| -> Result { + ids.try_push(*market_id).map_err(|_| DispatchError::Other("Storage Overflow"))?; + Ok(ids.len() as u32) + }, + )?; + Ok(ids_len) + } + + fn auto_resolve_exists(market_id: &Self::MarketId, resolve_at: Self::BlockNumber) -> bool { + >::get(resolve_at).contains(market_id) + } + + fn remove_auto_resolve(market_id: &Self::MarketId, resolve_at: Self::BlockNumber) -> u32 { + >::mutate(resolve_at, |ids| -> u32 { + ids.retain(|id| id != market_id); + 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 { type Event = (); + type CorrectionPeriod = CorrectionPeriod; + type DisputeResolution = MockResolution; type MarketCommons = MarketCommons; type PalletId = AuthorizedPalletId; type AuthorizedDisputeResolutionOrigin = @@ -66,6 +136,10 @@ impl crate::Config for Runtime { type WeightInfo = crate::weights::WeightInfo; } +impl mock_storage::Config for Runtime { + type MarketCommons = MarketCommons; +} + impl frame_system::Config for Runtime { type AccountData = pallet_balances::AccountData; type AccountId = AccountIdTest; diff --git a/zrml/authorized/src/mock_storage.rs b/zrml/authorized/src/mock_storage.rs new file mode 100644 index 000000000..c7908fa55 --- /dev/null +++ b/zrml/authorized/src/mock_storage.rs @@ -0,0 +1,52 @@ +// Copyright 2022 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(test)] +#![allow(dead_code)] + +#[frame_support::pallet] +pub(crate) mod pallet { + use core::marker::PhantomData; + use frame_support::pallet_prelude::*; + use zrml_market_commons::MarketCommonsPalletApi; + + pub(crate) type MarketIdOf = + <::MarketCommons as MarketCommonsPalletApi>::MarketId; + + pub type CacheSize = ConstU32<64>; + + #[pallet::config] + pub trait Config: frame_system::Config { + type MarketCommons: MarketCommonsPalletApi< + AccountId = Self::AccountId, + BlockNumber = Self::BlockNumber, + >; + } + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + /// Only used for testing the dispute resolution API to prediction-markets + #[pallet::storage] + pub(crate) type MarketIdsPerDisputeBlock = StorageMap< + _, + Twox64Concat, + T::BlockNumber, + BoundedVec, CacheSize>, + ValueQuery, + >; +} diff --git a/zrml/authorized/src/tests.rs b/zrml/authorized/src/tests.rs index c74af01d8..7cd33883c 100644 --- a/zrml/authorized/src/tests.rs +++ b/zrml/authorized/src/tests.rs @@ -20,12 +20,13 @@ use crate::{ market_mock, mock::{Authorized, AuthorizedDisputeResolutionUser, ExtBuilder, Origin, Runtime, BOB}, + mock_storage::pallet as mock_storage, AuthorizedOutcomeReports, Error, }; use frame_support::{assert_noop, assert_ok, dispatch::DispatchError}; use zeitgeist_primitives::{ traits::DisputeApi, - types::{MarketDisputeMechanism, MarketStatus, OutcomeReport}, + types::{AuthorityReport, MarketDispute, MarketDisputeMechanism, MarketStatus, OutcomeReport}, }; use zrml_market_commons::Markets; @@ -38,7 +39,46 @@ fn authorize_market_outcome_inserts_a_new_outcome() { 0, OutcomeReport::Scalar(1) )); - assert_eq!(AuthorizedOutcomeReports::::get(0).unwrap(), OutcomeReport::Scalar(1)); + let now = frame_system::Pallet::::block_number(); + let resolve_at = now + ::CorrectionPeriod::get(); + assert_eq!( + AuthorizedOutcomeReports::::get(0).unwrap(), + AuthorityReport { outcome: OutcomeReport::Scalar(1), resolve_at } + ); + }); +} + +#[test] +fn authorize_market_outcome_does_not_reset_dispute_resolution() { + ExtBuilder::default().build().execute_with(|| { + Markets::::insert(0, market_mock::()); + + assert_ok!(Authorized::authorize_market_outcome( + Origin::signed(AuthorizedDisputeResolutionUser::get()), + 0, + OutcomeReport::Scalar(1), + )); + let now = frame_system::Pallet::::block_number(); + let resolve_at_0 = now + ::CorrectionPeriod::get(); + assert_eq!( + AuthorizedOutcomeReports::::get(0).unwrap(), + AuthorityReport { outcome: OutcomeReport::Scalar(1), resolve_at: resolve_at_0 } + ); + + assert_eq!(mock_storage::MarketIdsPerDisputeBlock::::get(resolve_at_0), vec![0]); + + assert_ok!(Authorized::authorize_market_outcome( + Origin::signed(AuthorizedDisputeResolutionUser::get()), + 0, + OutcomeReport::Scalar(2) + )); + + assert_eq!( + AuthorizedOutcomeReports::::get(0).unwrap(), + AuthorityReport { outcome: OutcomeReport::Scalar(2), resolve_at: resolve_at_0 } + ); + + assert_eq!(mock_storage::MarketIdsPerDisputeBlock::::get(resolve_at_0), vec![0]); }); } @@ -116,6 +156,18 @@ 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(|| { @@ -177,13 +229,40 @@ fn authorized_market_outcome_can_handle_multiple_markets() { 1, OutcomeReport::Scalar(456) )); + let now = frame_system::Pallet::::block_number(); + let resolve_at = now + ::CorrectionPeriod::get(); assert_eq!( AuthorizedOutcomeReports::::get(0).unwrap(), - OutcomeReport::Scalar(123) + AuthorityReport { outcome: OutcomeReport::Scalar(123), resolve_at } ); assert_eq!( AuthorizedOutcomeReports::::get(1).unwrap(), - OutcomeReport::Scalar(456) + AuthorityReport { outcome: OutcomeReport::Scalar(456), resolve_at } ); }); } + +#[test] +fn get_auto_resolve_works() { + ExtBuilder::default().build().execute_with(|| { + frame_system::Pallet::::set_block_number(42); + let market = market_mock::(); + Markets::::insert(0, &market); + assert_ok!(Authorized::authorize_market_outcome( + Origin::signed(AuthorizedDisputeResolutionUser::get()), + 0, + OutcomeReport::Scalar(1) + )); + 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),); + }); +} + +#[test] +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,); + }); +} diff --git a/zrml/authorized/src/weights.rs b/zrml/authorized/src/weights.rs index fb76eadbb..b2cb712bd 100644 --- a/zrml/authorized/src/weights.rs +++ b/zrml/authorized/src/weights.rs @@ -18,11 +18,11 @@ //! Autogenerated weights for zrml_authorized //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-25, STEPS: `10`, REPEAT: 1000, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-01-06, STEPS: `10`, REPEAT: 1000, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/zeitgeist +// ./target/release/zeitgeist // benchmark // pallet // --chain=dev @@ -33,8 +33,8 @@ // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --template=./misc/weight_template.hbs // --output=./zrml/authorized/src/weights.rs +// --template=./misc/weight_template.hbs #![allow(unused_parens)] #![allow(unused_imports)] @@ -45,17 +45,28 @@ use frame_support::{traits::Get, weights::Weight}; /// Trait containing the required functions for weight retrival within /// zrml_authorized (automatically generated) pub trait WeightInfoZeitgeist { - fn authorize_market_outcome() -> Weight; + fn authorize_market_outcome_first_report(m: u32) -> Weight; + fn authorize_market_outcome_existing_report() -> Weight; } /// Weight functions for zrml_authorized (automatically generated) pub struct WeightInfo(PhantomData); impl WeightInfoZeitgeist for WeightInfo { // Storage: MarketCommons Markets (r:1 w:0) - // Storage: Authorized AuthorizedOutcomeReports (r:0 w:1) - fn authorize_market_outcome() -> Weight { - (16_170_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) + // Storage: Authorized AuthorizedOutcomeReports (r:1 w:1) + // Storage: PredictionMarkets MarketIdsPerDisputeBlock (r:1 w:1) + fn authorize_market_outcome_first_report(m: u32) -> Weight { + (31_031_000 as Weight) + // Standard Error: 0 + .saturating_add((85_000 as Weight).saturating_mul(m as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: MarketCommons Markets (r:1 w:0) + // Storage: Authorized AuthorizedOutcomeReports (r:1 w:1) + fn authorize_market_outcome_existing_report() -> Weight { + (24_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } } diff --git a/zrml/court/src/lib.rs b/zrml/court/src/lib.rs index ce0d064c4..c1d08183b 100644 --- a/zrml/court/src/lib.rs +++ b/zrml/court/src/lib.rs @@ -50,6 +50,7 @@ mod pallet { use core::marker::PhantomData; use frame_support::{ dispatch::DispatchResult, + ensure, pallet_prelude::{CountedStorageMap, StorageDoubleMap, StorageValue, ValueQuery}, traits::{ BalanceStatus, Currency, Get, Hooks, IsType, NamedReservableCurrency, Randomness, @@ -64,7 +65,7 @@ mod pallet { ArithmeticError, DispatchError, SaturatedConversion, }; use zeitgeist_primitives::{ - traits::DisputeApi, + traits::{DisputeApi, DisputeResolutionApi}, types::{Market, MarketDispute, MarketDisputeMechanism, OutcomeReport}, }; use zrml_market_commons::MarketCommonsPalletApi; @@ -150,6 +151,13 @@ mod pallet { #[pallet::constant] type CourtCaseDuration: Get; + type DisputeResolution: DisputeResolutionApi< + AccountId = Self::AccountId, + BlockNumber = Self::BlockNumber, + MarketId = MarketIdOf, + Moment = MomentOf, + >; + /// Event type Event: From> + IsType<::Event>; @@ -506,9 +514,10 @@ mod pallet { market_id: &Self::MarketId, market: &MarketOf, ) -> DispatchResult { - if market.dispute_mechanism != MarketDisputeMechanism::Court { - return Err(Error::::MarketDoesNotHaveCourtMechanism.into()); - } + 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(); @@ -530,9 +539,10 @@ mod pallet { market_id: &Self::MarketId, market: &MarketOf, ) -> Result, DispatchError> { - if market.dispute_mechanism != MarketDisputeMechanism::Court { - return Err(Error::::MarketDoesNotHaveCourtMechanism.into()); - } + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::Court, + Error::::MarketDoesNotHaveCourtMechanism + ); let votes: Vec<_> = Votes::::iter_prefix(market_id).collect(); let requested_jurors: Vec<_> = RequestedJurors::::iter_prefix(market_id) .map(|(juror_id, max_allowed_block)| { @@ -552,6 +562,30 @@ mod pallet { let _ = RequestedJurors::::clear_prefix(market_id, u32::max_value(), None); Ok(Some(first)) } + + fn get_auto_resolve( + _: &[MarketDispute], + _: &Self::MarketId, + market: &MarketOf, + ) -> Result, DispatchError> { + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::Court, + Error::::MarketDoesNotHaveCourtMechanism + ); + Ok(None) + } + + fn has_failed( + _: &[MarketDispute], + _: &Self::MarketId, + market: &MarketOf, + ) -> Result { + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::Court, + Error::::MarketDoesNotHaveCourtMechanism + ); + Ok(false) + } } impl CourtPalletApi for Pallet where T: Config {} diff --git a/zrml/court/src/mock.rs b/zrml/court/src/mock.rs index 6d14af846..11cfe03ee 100644 --- a/zrml/court/src/mock.rs +++ b/zrml/court/src/mock.rs @@ -18,7 +18,13 @@ #![cfg(test)] use crate::{self as zrml_court}; -use frame_support::{construct_runtime, parameter_types, traits::Everything, PalletId}; +use frame_support::{ + construct_runtime, + pallet_prelude::{DispatchError, Weight}, + parameter_types, + traits::Everything, + BoundedVec, PalletId, +}; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, @@ -28,9 +34,10 @@ use zeitgeist_primitives::{ BlockHashCount, CourtCaseDuration, CourtPalletId, MaxReserves, MinimumPeriod, PmPalletId, StakeWeight, BASE, }, + traits::DisputeResolutionApi, types::{ - AccountIdTest, Balance, BlockNumber, BlockTest, Hash, Index, MarketId, Moment, - UncheckedExtrinsicTest, + AccountIdTest, Balance, BlockNumber, BlockTest, Hash, Index, Market, MarketDispute, + MarketId, Moment, UncheckedExtrinsicTest, }, }; @@ -59,8 +66,49 @@ construct_runtime!( } ); +// NoopResolution implements DisputeResolutionApi with no-ops. +pub struct NoopResolution; + +impl DisputeResolutionApi for NoopResolution { + type AccountId = AccountIdTest; + type Balance = Balance; + type BlockNumber = BlockNumber; + type MarketId = MarketId; + type MaxDisputes = u32; + type Moment = Moment; + + fn resolve( + _market_id: &Self::MarketId, + _market: &Market, + ) -> Result { + Ok(0) + } + + fn add_auto_resolve( + _market_id: &Self::MarketId, + _resolve_at: Self::BlockNumber, + ) -> Result { + Ok(0u32) + } + + fn auto_resolve_exists(_market_id: &Self::MarketId, _resolve_at: Self::BlockNumber) -> bool { + false + } + + 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 CourtCaseDuration = CourtCaseDuration; + type DisputeResolution = NoopResolution; type Event = (); type MarketCommons = MarketCommons; type PalletId = CourtPalletId; diff --git a/zrml/prediction-markets/src/benchmarks.rs b/zrml/prediction-markets/src/benchmarks.rs index 1a383d395..efac65a47 100644 --- a/zrml/prediction-markets/src/benchmarks.rs +++ b/zrml/prediction-markets/src/benchmarks.rs @@ -77,12 +77,13 @@ fn create_market_common_parameters( } // Create a market based on common parameters -fn create_market_common( +fn create_market_common( permission: MarketCreation, options: MarketType, scoring_rule: ScoringRule, period: Option>>, ) -> Result<(T::AccountId, MarketIdOf), &'static str> { + pallet_timestamp::Pallet::::set_timestamp(0u32.into()); let range_start: MomentOf = 100_000u64.saturated_into(); let range_end: MomentOf = 1_000_000u64.saturated_into(); let period = period.unwrap_or(MarketPeriod::Timestamp(range_start..range_end)); @@ -147,7 +148,7 @@ fn setup_redeem_shares_common( } else if let MarketType::Scalar(range) = market_type { outcome = OutcomeReport::Scalar(*range.end()); } else { - panic!("setup_redeem_shares_common: Unsupported market type: {:?}", market_type); + panic!("setup_redeem_shares_common: Unsupported market type: {market_type:?}"); } Pallet::::do_buy_complete_set( @@ -450,7 +451,6 @@ benchmarks! { admin_move_market_to_resolved_scalar_disputed { let r in 0..63; - let d in 1..T::MaxDisputes::get(); let (_, market_id) = create_close_and_report_market::( MarketCreation::Permissionless, @@ -464,23 +464,16 @@ benchmarks! { })?; let market = >::market(&market_id)?; - if let MarketType::Scalar(range) = market.market_type { - assert!((d as u128) < *range.end()); - } else { - panic!("Must create scalar market"); - } - for i in 0..d { - let outcome = OutcomeReport::Scalar((i % 2).into()); - let disputor = account("disputor", i + 1, 0); - let dispute_bond = crate::pallet::default_dispute_bond::(i as usize); - T::AssetManager::deposit( - Asset::Ztg, - &disputor, - dispute_bond, - )?; - Pallet::::dispute(RawOrigin::Signed(disputor).into(), market_id, outcome)?; - } + let outcome = OutcomeReport::Scalar(0); + let disputor = account("disputor", 1, 0); + let dispute_bond = crate::pallet::default_dispute_bond::(0_usize); + T::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). @@ -513,7 +506,6 @@ benchmarks! { admin_move_market_to_resolved_categorical_disputed { let r in 0..63; - let d in 1..T::MaxDisputes::get(); let categories = T::MaxCategories::get(); let (caller, market_id) = @@ -527,17 +519,16 @@ benchmarks! { Ok(()) })?; - for i in 0..d { - let outcome = OutcomeReport::Categorical((i % 2).saturated_into::()); - let disputor = account("disputor", i + 1, 0); - let dispute_bond = crate::pallet::default_dispute_bond::(i as usize); - T::AssetManager::deposit( - Asset::Ztg, - &disputor, - dispute_bond, - )?; - Pallet::::dispute(RawOrigin::Signed(disputor).into(), market_id, outcome)?; - } + let outcome = OutcomeReport::Categorical(0u16); + let disputor = account("disputor", 1, 0); + let dispute_bond = crate::pallet::default_dispute_bond::(0_usize); + T::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). @@ -785,6 +776,7 @@ benchmarks! { start_global_dispute { let m in 1..CacheSize::get(); + let n in 1..CacheSize::get(); // no benchmarking component for max disputes here, // because MaxDisputes is enforced for the extrinsic @@ -794,11 +786,16 @@ benchmarks! { OutcomeReport::Scalar(u128::MAX), )?; + >::mutate_market(&market_id, |market| { + market.dispute_mechanism = MarketDisputeMechanism::SimpleDisputes; + Ok(()) + })?; + // first element is the market id from above - let mut market_ids = BoundedVec::try_from(vec![market_id]).unwrap(); + let mut market_ids_1: BoundedVec, CacheSize> = Default::default(); assert_eq!(market_id, 0u128.saturated_into()); for i in 1..m { - market_ids.try_push(i.saturated_into()).unwrap(); + market_ids_1.try_push(i.saturated_into()).unwrap(); } let max_dispute_len = T::MaxDisputes::get(); @@ -815,11 +812,28 @@ benchmarks! { .dispatch_bypass_filter(RawOrigin::Signed(disputor.clone()).into())?; } + let market = >::market(&market_id.saturated_into()).unwrap(); + let disputes = Disputes::::get(market_id); + 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( + vec![market_id], + ).unwrap(); + for i in 1..n { + market_ids_2.try_push(i.saturated_into()).unwrap(); + } + MarketIdsPerDisputeBlock::::insert(dispute_duration_ends_at_block, market_ids_2); + let current_block: T::BlockNumber = (max_dispute_len + 1).saturated_into(); >::set_block_number(current_block); - // the complexity depends on MarketIdsPerDisputeBlock at the current block - // this is because a variable number of market ids need to be decoded from the storage - MarketIdsPerDisputeBlock::::insert(current_block, market_ids); + + #[cfg(feature = "with-global-disputes")] + { + let global_dispute_end = current_block + T::GlobalDisputePeriod::get(); + // the complexity depends on MarketIdsPerDisputeBlock at the current block + // this is because a variable number of market ids need to be decoded from the storage + MarketIdsPerDisputeBlock::::insert(global_dispute_end, market_ids_1); + } let call = Call::::start_global_dispute { market_id }; }: { @@ -830,9 +844,6 @@ benchmarks! { } dispute_authorized { - let d in 0..(T::MaxDisputes::get() - 1); - let b in 0..63; - let report_outcome = OutcomeReport::Scalar(u128::MAX); let (caller, market_id) = create_close_and_report_market::( MarketCreation::Permissionless, @@ -846,28 +857,9 @@ benchmarks! { })?; let market = >::market(&market_id)?; - if let MarketType::Scalar(range) = market.market_type { - assert!((d as u128) < *range.end()); - } else { - panic!("Must create scalar market"); - } - for i in 0..d { - let outcome = OutcomeReport::Scalar(i.into()); - let disputor = account("disputor", i, 0); - T::AssetManager::deposit(Asset::Ztg, &disputor, (u128::MAX).saturated_into())?; - Pallet::::dispute(RawOrigin::Signed(disputor).into(), market_id, outcome)?; - } - let now = frame_system::Pallet::::block_number(); - let resolves_at = now.saturating_add(market.deadlines.dispute_duration); - for i in 0..b { - MarketIdsPerDisputeBlock::::try_mutate( - resolves_at, - |ids| ids.try_push(i.into()), - ).unwrap(); - } - - let dispute_outcome = OutcomeReport::Scalar((d + 1).into()); + // only one dispute allowed for authorized mdm + let dispute_outcome = OutcomeReport::Scalar(1u128); let call = Call::::dispute { market_id, outcome: dispute_outcome }; }: { call.dispatch_bypass_filter(RawOrigin::Signed(caller).into())?; @@ -902,9 +894,6 @@ benchmarks! { } internal_resolve_categorical_disputed { - // d = num. disputes - let d in 1..T::MaxDisputes::get(); - let categories = T::MaxCategories::get(); let (caller, market_id) = setup_reported_categorical_market_with_pool::( @@ -916,14 +905,11 @@ benchmarks! { Ok(()) })?; - for i in 0..d { - let origin = caller.clone(); - Pallet::::dispute( - RawOrigin::Signed(origin).into(), - market_id, - OutcomeReport::Categorical((i % 2).saturated_into::()), - )?; - } + 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( @@ -954,8 +940,6 @@ benchmarks! { } internal_resolve_scalar_disputed { - let d in 1..T::MaxDisputes::get(); - let (caller, market_id) = create_close_and_report_market::( MarketCreation::Permissionless, MarketType::Scalar(0u128..=u128::MAX), @@ -966,19 +950,11 @@ benchmarks! { Ok(()) })?; let market = >::market(&market_id)?; - if let MarketType::Scalar(range) = market.market_type { - assert!((d as u128) < *range.end()); - } else { - panic!("Must create scalar market"); - } - for i in 0..d { - let origin = caller.clone(); - Pallet::::dispute( - RawOrigin::Signed(origin).into(), - market_id, - OutcomeReport::Scalar((i % 2).into()) - )?; - } + 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( diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 045ec7e0c..9695db7c9 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -56,7 +56,7 @@ mod pallet { }; use zeitgeist_primitives::{ constants::MILLISECS_PER_BLOCK, - traits::{DisputeApi, Swaps, ZeitgeistAssetManager}, + traits::{DisputeApi, DisputeResolutionApi, Swaps, ZeitgeistAssetManager}, types::{ Asset, Bond, Deadlines, Market, MarketBonds, MarketCreation, MarketDispute, MarketDisputeMechanism, MarketPeriod, MarketStatus, MarketType, MultiHash, @@ -70,7 +70,7 @@ mod pallet { use zrml_market_commons::MarketCommonsPalletApi; /// The current storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(6); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(7); pub(crate) type BalanceOf = <::AssetManager as MultiCurrency< ::AccountId, @@ -346,15 +346,9 @@ mod pallet { .max( T::WeightInfo::admin_move_market_to_resolved_categorical_reported(CacheSize::get()) ).max( - T::WeightInfo::admin_move_market_to_resolved_scalar_disputed( - CacheSize::get(), - T::MaxDisputes::get() - ) + T::WeightInfo::admin_move_market_to_resolved_scalar_disputed(CacheSize::get()) ).max( - T::WeightInfo::admin_move_market_to_resolved_categorical_disputed( - CacheSize::get(), - T::MaxDisputes::get() - ) + T::WeightInfo::admin_move_market_to_resolved_categorical_disputed(CacheSize::get()) ), Pays::No, ))] @@ -370,7 +364,7 @@ mod pallet { market.status == MarketStatus::Reported || market.status == MarketStatus::Disputed, Error::::InvalidMarketStatus, ); - let (ids_len, disputes_len) = Self::clear_auto_resolve(&market_id)?; + let (ids_len, _) = Self::clear_auto_resolve(&market_id)?; let market = >::market(&market_id)?; let _ = Self::on_resolution(&market_id, &market)?; let weight = match market.market_type { @@ -379,10 +373,7 @@ mod pallet { T::WeightInfo::admin_move_market_to_resolved_scalar_reported(ids_len) } MarketStatus::Disputed => { - T::WeightInfo::admin_move_market_to_resolved_scalar_disputed( - ids_len, - disputes_len, - ) + T::WeightInfo::admin_move_market_to_resolved_scalar_disputed(ids_len) } _ => return Err(Error::::InvalidMarketStatus.into()), }, @@ -391,10 +382,7 @@ mod pallet { T::WeightInfo::admin_move_market_to_resolved_categorical_reported(ids_len) } MarketStatus::Disputed => { - T::WeightInfo::admin_move_market_to_resolved_categorical_disputed( - ids_len, - disputes_len, - ) + T::WeightInfo::admin_move_market_to_resolved_categorical_disputed(ids_len) } _ => return Err(Error::::InvalidMarketStatus.into()), }, @@ -526,10 +514,7 @@ mod pallet { /// # Weight /// /// Complexity: `O(n)`, where `n` is the number of outstanding disputes. - #[pallet::weight(T::WeightInfo::dispute_authorized( - T::MaxDisputes::get(), - CacheSize::get() - ))] + #[pallet::weight(T::WeightInfo::dispute_authorized())] #[transactional] pub fn dispute( origin: OriginFor, @@ -552,6 +537,7 @@ mod pallet { &who, default_dispute_bond::(disputes.len()), )?; + // TODO(#782): use multiple benchmarks paths for different dispute mechanisms match market.dispute_mechanism { MarketDisputeMechanism::Authorized => { T::Authorized::on_dispute(&disputes, &market_id, &market)? @@ -563,26 +549,18 @@ mod pallet { T::SimpleDisputes::on_dispute(&disputes, &market_id, &market)? } } - Self::remove_last_dispute_from_market_ids_per_dispute_block(&disputes, &market_id)?; + 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) })?; - // each dispute resets dispute_duration - let dispute_duration_ends_at_block = - curr_block_num.saturating_add(market.deadlines.dispute_duration); - >::try_mutate(dispute_duration_ends_at_block, |ids| { - ids.try_push(market_id).map_err(|_| >::StorageOverflow) - })?; - Self::deposit_event(Event::MarketDisputed( market_id, MarketStatus::Disputed, market_dispute, )); - // TODO(#782): add court benchmark - Ok((Some(T::WeightInfo::dispute_authorized(num_disputes, CacheSize::get()))).into()) + Ok((Some(T::WeightInfo::dispute_authorized())).into()) } /// Create a permissionless market, buy complete sets and deploy a pool with specified @@ -1318,7 +1296,7 @@ mod pallet { /// The outcomes of the disputes and the report outcome /// are added to the global dispute voting outcomes. /// The bond of each dispute is the initial vote amount. - #[pallet::weight(T::WeightInfo::start_global_dispute(CacheSize::get()))] + #[pallet::weight(T::WeightInfo::start_global_dispute(CacheSize::get(), CacheSize::get()))] #[transactional] pub fn start_global_dispute( origin: OriginFor, @@ -1350,10 +1328,10 @@ mod pallet { ); // add report outcome to voting choices - if let Some(report) = market.report { + if let Some(report) = &market.report { T::GlobalDisputes::push_voting_outcome( &market_id, - report.outcome, + report.outcome.clone(), &report.by, >::zero(), )?; @@ -1369,9 +1347,12 @@ mod pallet { )?; } + // TODO(#372): Allow court with global disputes. // ensure, that global disputes controls the resolution now // it does not end after the dispute period now, but after the global dispute end - Self::remove_last_dispute_from_market_ids_per_dispute_block(&disputes, &market_id)?; + + // ignore first of tuple because we always have max disputes + let (_, ids_len_2) = Self::clear_auto_resolve(&market_id)?; let now = >::block_number(); let global_dispute_end = now.saturating_add(T::GlobalDisputePeriod::get()); @@ -1385,7 +1366,7 @@ mod pallet { Self::deposit_event(Event::GlobalDisputeStarted(market_id)); - Ok(Some(T::WeightInfo::start_global_dispute(market_ids_len)).into()) + Ok(Some(T::WeightInfo::start_global_dispute(market_ids_len, ids_len_2)).into()) } #[cfg(not(feature = "with-global-disputes"))] @@ -2054,17 +2035,24 @@ mod pallet { } MarketStatus::Disputed => { let disputes = Disputes::::get(market_id); - let last_dispute = disputes.last().ok_or(Error::::MarketIsNotDisputed)?; - let dispute_duration_ends_at_block = - last_dispute.at.saturating_add(market.deadlines.dispute_duration); - MarketIdsPerDisputeBlock::::mutate( - dispute_duration_ends_at_block, - |ids| -> (u32, u32) { - let ids_len = ids.len() as u32; - remove_item::, _>(ids, market_id); - (ids_len, disputes.len() as u32) - }, - ) + // 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)? + } + }; + 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) + } else { + (0u32, disputes.len() as u32) + } } _ => (0u32, 0u32), }; @@ -2135,17 +2123,17 @@ mod pallet { time.saturated_into::().saturating_div(MILLISECS_PER_BLOCK.into()) } - fn calculate_internal_resolve_weight(market: &MarketOf, total_disputes: u32) -> Weight { + fn calculate_internal_resolve_weight(market: &MarketOf) -> Weight { if let MarketType::Categorical(_) = market.market_type { if let MarketStatus::Reported = market.status { T::WeightInfo::internal_resolve_categorical_reported() } else { - T::WeightInfo::internal_resolve_categorical_disputed(total_disputes) + T::WeightInfo::internal_resolve_categorical_disputed() } } else if let MarketStatus::Reported = market.status { T::WeightInfo::internal_resolve_scalar_reported() } else { - T::WeightInfo::internal_resolve_scalar_disputed(total_disputes) + T::WeightInfo::internal_resolve_scalar_disputed() } } @@ -2366,6 +2354,8 @@ mod pallet { 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() { @@ -2438,13 +2428,12 @@ mod pallet { pub fn on_resolution( market_id: &MarketIdOf, market: &MarketOf, - ) -> Result { + ) -> Result { if market.creation == MarketCreation::Permissionless { Self::unreserve_creation_bond(market_id)?; } let mut total_weight = 0; - let disputes = Disputes::::get(market_id); let resolved_outcome = match market.status { MarketStatus::Reported => Self::resolve_reported_market(market_id, market)?, @@ -2470,10 +2459,7 @@ mod pallet { MarketStatus::Resolved, resolved_outcome, )); - Ok(total_weight.saturating_add(Self::calculate_internal_resolve_weight( - market, - disputes.len().saturated_into(), - ))) + Ok(total_weight.saturating_add(Self::calculate_internal_resolve_weight(market))) } pub(crate) fn process_subsidy_collecting_markets( @@ -2636,21 +2622,6 @@ mod pallet { weight_basis.saturating_add(total_weight) } - fn remove_last_dispute_from_market_ids_per_dispute_block( - disputes: &[MarketDispute], - market_id: &MarketIdOf, - ) -> DispatchResult { - if let Some(last_dispute) = disputes.last() { - let market = >::market(market_id)?; - let dispute_duration_ends_at_block = - last_dispute.at.saturating_add(market.deadlines.dispute_duration); - MarketIdsPerDisputeBlock::::mutate(dispute_duration_ends_at_block, |ids| { - remove_item::, _>(ids, market_id); - }); - } - Ok(()) - } - /// The reserve ID of the prediction-markets pallet. #[inline] pub fn reserve_id() -> [u8; 8] { @@ -2884,4 +2855,63 @@ mod pallet { items.swap_remove(pos); } } + + fn remove_auto_resolve( + market_id: &MarketIdOf, + resolve_at: T::BlockNumber, + ) -> u32 { + MarketIdsPerDisputeBlock::::mutate(resolve_at, |ids| -> u32 { + let ids_len = ids.len() as u32; + remove_item::, _>(ids, market_id); + ids_len + }) + } + + impl DisputeResolutionApi for Pallet + where + T: Config, + { + type AccountId = T::AccountId; + type Balance = BalanceOf; + type BlockNumber = T::BlockNumber; + type MarketId = MarketIdOf; + type MaxDisputes = T::MaxDisputes; + type Moment = MomentOf; + + fn resolve( + market_id: &Self::MarketId, + market: &Market, + ) -> Result { + Self::on_resolution(market_id, market) + } + + fn add_auto_resolve( + market_id: &Self::MarketId, + resolve_at: Self::BlockNumber, + ) -> Result { + let ids_len = >::try_mutate( + resolve_at, + |ids| -> Result { + ids.try_push(*market_id).map_err(|_| >::StorageOverflow)?; + Ok(ids.len() as u32) + }, + )?; + Ok(ids_len) + } + + fn auto_resolve_exists(market_id: &Self::MarketId, resolve_at: Self::BlockNumber) -> bool { + >::get(resolve_at).contains(market_id) + } + + 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 08b190343..618ebcf94 100644 --- a/zrml/prediction-markets/src/migrations.rs +++ b/zrml/prediction-markets/src/migrations.rs @@ -19,10 +19,8 @@ use crate::MarketIdOf; use crate::{Config, MarketOf, MomentOf}; #[cfg(feature = "try-runtime")] -use alloc::collections::BTreeMap; -#[cfg(feature = "try-runtime")] use alloc::format; -use alloc::vec::Vec; +use alloc::{collections::BTreeMap, vec::Vec}; #[cfg(feature = "try-runtime")] use frame_support::traits::OnRuntimeUpgradeHelpersExt; use frame_support::{ @@ -39,9 +37,7 @@ use zeitgeist_primitives::types::{ 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}; const MARKET_COMMONS: &[u8] = b"MarketCommons"; const MARKETS: &[u8] = b"Markets"; @@ -146,7 +142,7 @@ impl OnRuntimeUpgrade for RecordBonds Result<(), &'static str> { - use frame_support::{migration::storage_key_iter, pallet_prelude::Blake2_128Concat}; + use frame_support::pallet_prelude::Blake2_128Concat; let old_markets = storage_key_iter::, OldMarketOf, Blake2_128Concat>( MARKET_COMMONS, @@ -424,10 +420,289 @@ mod tests { } } +#[cfg(feature = "try-runtime")] +use alloc::string::ToString; +use frame_support::{migration::storage_key_iter, Twox64Concat}; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_runtime::traits::Saturating; +use zeitgeist_primitives::types::AuthorityReport; +use zrml_authorized::Pallet as AuthorizedPallet; + +const AUTHORIZED: &[u8] = b"Authorized"; +const AUTHORIZED_OUTCOME_REPORTS: &[u8] = b"AuthorizedOutcomeReports"; + +const AUTHORIZED_REQUIRED_STORAGE_VERSION: u16 = 2; +const AUTHORIZED_NEXT_STORAGE_VERSION: u16 = 3; + +pub struct AddFieldToAuthorityReport(PhantomData); + +// Add resolve_at block number value field to `AuthorizedOutcomeReports` map. +impl OnRuntimeUpgrade + for AddFieldToAuthorityReport +{ + fn on_runtime_upgrade() -> Weight + where + T: Config, + { + let mut total_weight = T::DbWeight::get().reads(1); + let authorized_version = StorageVersion::get::>(); + if authorized_version != AUTHORIZED_REQUIRED_STORAGE_VERSION { + log::info!( + "AddFieldToAuthorityReport: authorized version is {:?}, require {:?};", + authorized_version, + AUTHORIZED_REQUIRED_STORAGE_VERSION, + ); + return total_weight; + } + log::info!("AddFieldToAuthorityReport: Starting..."); + + let mut authorized_resolutions = + BTreeMap::<::MarketId, BlockNumberFor>::new(); + for (resolve_at, bounded_vec) in crate::MarketIdsPerDisputeBlock::::iter() { + total_weight = total_weight.saturating_add(T::DbWeight::get().reads(1)); + + for id in bounded_vec.into_inner().iter() { + if let Ok(market) = >::market(id) { + if market.dispute_mechanism == MarketDisputeMechanism::Authorized { + authorized_resolutions.insert(*id, resolve_at); + } + } else { + log::warn!("AddFieldToAuthorityReport: Could not find market with id {:?}", id); + } + } + } + + let mut new_storage_map: Vec<( + ::MarketId, + AuthorityReport>, + )> = Vec::new(); + + let now = frame_system::Pallet::::block_number(); + total_weight = total_weight.saturating_add(T::DbWeight::get().reads(1)); + + for (market_id, old_value) in storage_key_iter::< + ::MarketId, + OutcomeReport, + Twox64Concat, + >(AUTHORIZED, AUTHORIZED_OUTCOME_REPORTS) + { + total_weight = total_weight.saturating_add(T::DbWeight::get().reads(1)); + + let resolve_at: Option> = + authorized_resolutions.get(&market_id).cloned(); + + match resolve_at { + Some(block) if now <= block => { + new_storage_map.push(( + market_id, + AuthorityReport { resolve_at: block, outcome: old_value }, + )); + } + _ => { + log::warn!( + "AddFieldToAuthorityReport: Market was not found in \ + MarketIdsPerDisputeBlock; market id: {:?}", + market_id + ); + // example case market id 432 + // https://github.com/zeitgeistpm/zeitgeist/pull/701 market id 432 is invalid, because of zero-division error in the past + // we have to handle manually here, because MarketIdsPerDisputeBlock does not contain 432 + let mut resolve_at = now.saturating_add(T::CorrectionPeriod::get()); + total_weight = total_weight.saturating_add(T::DbWeight::get().reads(1)); + + let mut bounded_vec = >::get(resolve_at); + while bounded_vec.is_full() { + // roll the dice until we find a block that is not full + total_weight = total_weight.saturating_add(T::DbWeight::get().reads(1)); + resolve_at = resolve_at.saturating_add(1u32.into()); + bounded_vec = >::get(resolve_at); + } + // is not full, so we can push + bounded_vec.force_push(market_id); + >::insert(resolve_at, bounded_vec); + total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); + + new_storage_map + .push((market_id, AuthorityReport { resolve_at, outcome: old_value })); + } + } + } + + for (market_id, new_value) in new_storage_map { + let hash = utility::key_to_hash::< + Twox64Concat, + ::MarketId, + >(market_id); + put_storage_value::>( + AUTHORIZED, + AUTHORIZED_OUTCOME_REPORTS, + &hash, + new_value, + ); + total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); + } + + StorageVersion::new(AUTHORIZED_NEXT_STORAGE_VERSION).put::>(); + total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); + log::info!("AddFieldToAuthorityReport: Done!"); + total_weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + let mut counter = 0_u32; + for (key, value) in storage_iter::(AUTHORIZED, AUTHORIZED_OUTCOME_REPORTS) { + Self::set_temp_storage(value, &format!("{:?}", key.as_slice())); + + counter = counter.saturating_add(1_u32); + } + let counter_key = "counter_key".to_string(); + Self::set_temp_storage(counter, &counter_key); + Ok(()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + let mut markets_count = 0_u32; + let old_counter_key = "counter_key".to_string(); + for (key, new_value) in + storage_iter::>(AUTHORIZED, AUTHORIZED_OUTCOME_REPORTS) + { + let key_str = format!("{:?}", key.as_slice()); + + let AuthorityReport { resolve_at: _, outcome } = new_value; + let old_value: OutcomeReport = Self::get_temp_storage(&key_str) + .unwrap_or_else(|| panic!("old value not found for market id {:?}", key_str)); + + assert_eq!(old_value, outcome); + + markets_count += 1_u32; + } + let old_markets_count: u32 = + Self::get_temp_storage(&old_counter_key).expect("old counter key storage not found"); + assert_eq!(markets_count, old_markets_count); + Ok(()) + } +} + +#[cfg(test)] +mod tests_authorized { + use super::*; + use crate::{ + mock::{ExtBuilder, MarketCommons, Runtime, ALICE, BOB}, + CacheSize, MarketIdOf, + }; + use frame_support::{BoundedVec, Twox64Concat}; + use zeitgeist_primitives::types::{MarketId, OutcomeReport}; + use zrml_market_commons::MarketCommonsPalletApi; + + #[test] + fn on_runtime_upgrade_increments_the_storage_versions() { + ExtBuilder::default().build().execute_with(|| { + set_up_chain(); + AddFieldToAuthorityReport::::on_runtime_upgrade(); + let authorized_version = StorageVersion::get::>(); + assert_eq!(authorized_version, AUTHORIZED_NEXT_STORAGE_VERSION); + }); + } + + #[test] + fn on_runtime_sets_new_struct_with_resolve_at() { + ExtBuilder::default().build().execute_with(|| { + set_up_chain(); + + >::set_block_number(10_000); + + let hash = crate::migrations::utility::key_to_hash::(0); + let outcome = OutcomeReport::Categorical(42u16); + put_storage_value::( + AUTHORIZED, + AUTHORIZED_OUTCOME_REPORTS, + &hash, + outcome.clone(), + ); + + let resolve_at = 42_000; + + let sample_market = get_sample_market(); + let market_id: MarketId = MarketCommons::push_market(sample_market).unwrap(); + let bounded_vec = + BoundedVec::, CacheSize>::try_from(vec![market_id]) + .expect("BoundedVec should be created"); + crate::MarketIdsPerDisputeBlock::::insert(resolve_at, bounded_vec); + + AddFieldToAuthorityReport::::on_runtime_upgrade(); + + let expected = AuthorityReport { resolve_at, outcome }; + + let actual = frame_support::migration::get_storage_value::< + AuthorityReport<::BlockNumber>, + >(AUTHORIZED, AUTHORIZED_OUTCOME_REPORTS, &hash) + .unwrap(); + assert_eq!(expected, actual); + }); + } + + #[test] + fn on_runtime_is_noop_if_versions_are_not_correct() { + ExtBuilder::default().build().execute_with(|| { + // storage migration already executed (storage version is incremented already) + StorageVersion::new(AUTHORIZED_NEXT_STORAGE_VERSION).put::>(); + + let hash = crate::migrations::utility::key_to_hash::(0); + let outcome = OutcomeReport::Categorical(42u16); + + let report = AuthorityReport { resolve_at: 42, outcome }; + put_storage_value::::BlockNumber>>( + AUTHORIZED, + AUTHORIZED_OUTCOME_REPORTS, + &hash, + report.clone(), + ); + + AddFieldToAuthorityReport::::on_runtime_upgrade(); + + let actual = frame_support::migration::get_storage_value::< + AuthorityReport<::BlockNumber>, + >(AUTHORIZED, AUTHORIZED_OUTCOME_REPORTS, &hash) + .unwrap(); + assert_eq!(report, actual); + }); + } + + fn set_up_chain() { + StorageVersion::new(AUTHORIZED_REQUIRED_STORAGE_VERSION).put::>(); + } + + fn get_sample_market() -> zeitgeist_primitives::types::Market { + zeitgeist_primitives::types::Market { + creation: zeitgeist_primitives::types::MarketCreation::Permissionless, + creator_fee: 0, + creator: ALICE, + market_type: zeitgeist_primitives::types::MarketType::Scalar(0..=100), + dispute_mechanism: zeitgeist_primitives::types::MarketDisputeMechanism::Authorized, + metadata: Default::default(), + oracle: BOB, + 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: zeitgeist_primitives::types::ScoringRule::CPMM, + status: zeitgeist_primitives::types::MarketStatus::Disputed, + bonds: Default::default(), + } + } +} + // 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 // and previous migrations. + mod utility { use crate::{BalanceOf, Config, MarketIdOf}; use alloc::vec::Vec; diff --git a/zrml/prediction-markets/src/mock.rs b/zrml/prediction-markets/src/mock.rs index 709f75167..f44b7b8ba 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -35,15 +35,15 @@ use sp_runtime::{ use substrate_fixed::{types::extra::U33, FixedI128, FixedU128}; use zeitgeist_primitives::{ constants::mock::{ - AuthorizedPalletId, BalanceFractionalDecimals, BlockHashCount, CourtCaseDuration, - CourtPalletId, DisputeFactor, ExistentialDeposit, ExistentialDeposits, ExitFee, - GetNativeCurrencyId, LiquidityMiningPalletId, MaxApprovals, MaxAssets, MaxCategories, - MaxDisputeDuration, MaxDisputes, MaxEditReasonLen, MaxGracePeriod, MaxInRatio, - MaxMarketPeriod, MaxOracleDuration, MaxOutRatio, MaxRejectReasonLen, MaxReserves, - MaxSubsidyPeriod, MaxSwapFee, MaxTotalWeight, MaxWeight, MinAssets, MinCategories, - MinDisputeDuration, MinLiquidity, MinOracleDuration, MinSubsidy, MinSubsidyPeriod, - MinWeight, MinimumPeriod, PmPalletId, SimpleDisputesPalletId, StakeWeight, SwapsPalletId, - TreasuryPalletId, BASE, CENT, MILLISECS_PER_BLOCK, + AuthorizedPalletId, BalanceFractionalDecimals, BlockHashCount, CorrectionPeriod, + CourtCaseDuration, CourtPalletId, DisputeFactor, ExistentialDeposit, ExistentialDeposits, + ExitFee, GetNativeCurrencyId, LiquidityMiningPalletId, MaxApprovals, MaxAssets, + MaxCategories, MaxDisputeDuration, MaxDisputes, MaxEditReasonLen, MaxGracePeriod, + MaxInRatio, MaxMarketPeriod, MaxOracleDuration, MaxOutRatio, MaxRejectReasonLen, + MaxReserves, MaxSubsidyPeriod, MaxSwapFee, MaxTotalWeight, MaxWeight, MinAssets, + MinCategories, MinDisputeDuration, MinLiquidity, MinOracleDuration, MinSubsidy, + MinSubsidyPeriod, MinWeight, MinimumPeriod, PmPalletId, SimpleDisputesPalletId, + StakeWeight, SwapsPalletId, TreasuryPalletId, BASE, CENT, MILLISECS_PER_BLOCK, }, types::{ AccountIdTest, Amount, Asset, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, @@ -252,16 +252,19 @@ ord_parameter_types! { } impl zrml_authorized::Config for Runtime { - type Event = Event; - type MarketCommons = MarketCommons; type AuthorizedDisputeResolutionOrigin = EnsureSignedBy; + type CorrectionPeriod = CorrectionPeriod; + type Event = Event; + type DisputeResolution = prediction_markets::Pallet; + type MarketCommons = MarketCommons; type PalletId = AuthorizedPalletId; type WeightInfo = zrml_authorized::weights::WeightInfo; } impl zrml_court::Config for Runtime { type CourtCaseDuration = CourtCaseDuration; + type DisputeResolution = prediction_markets::Pallet; type Event = Event; type MarketCommons = MarketCommons; type PalletId = CourtPalletId; @@ -303,6 +306,7 @@ impl zrml_rikiddo::Config for Runtime { impl zrml_simple_disputes::Config for Runtime { type Event = Event; + type DisputeResolution = prediction_markets::Pallet; type MarketCommons = MarketCommons; type PalletId = SimpleDisputesPalletId; } diff --git a/zrml/prediction-markets/src/tests.rs b/zrml/prediction-markets/src/tests.rs index 7848524dc..8e9cb2bae 100644 --- a/zrml/prediction-markets/src/tests.rs +++ b/zrml/prediction-markets/src/tests.rs @@ -41,6 +41,7 @@ use zeitgeist_primitives::{ MultiHash, OutcomeReport, PoolStatus, ScalarPosition, ScoringRule, }, }; +use zrml_authorized::Error as AuthorizedError; use zrml_market_commons::MarketCommonsPalletApi; use zrml_swaps::Pools; @@ -2014,6 +2015,49 @@ fn it_allows_to_dispute_the_outcome_of_a_market() { }); } +#[test] +fn dispute_fails_authority_reported_already() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + assert_ok!(PredictionMarkets::create_market( + Origin::signed(ALICE), + 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, + OutcomeReport::Categorical(0) + )); + + assert_noop!( + PredictionMarkets::dispute(Origin::signed(CHARLIE), 0, OutcomeReport::Categorical(1)), + AuthorizedError::::OnlyOneDisputeAllowed + ); + }); +} + #[test] fn it_allows_anyone_to_report_an_unreported_market() { ExtBuilder::default().build().execute_with(|| { @@ -2244,87 +2288,6 @@ fn dispute_fails_unless_reported_or_disputed_market(status: MarketStatus) { }); } -#[test] -fn it_resolves_a_disputed_market_to_default_if_dispute_mechanism_failed() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - assert_ok!(PredictionMarkets::create_market( - Origin::signed(ALICE), - BOB, - MarketPeriod::Block(0..2), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::Authorized, - ScoringRule::CPMM, - )); - assert_ok!(PredictionMarkets::buy_complete_set(Origin::signed(CHARLIE), 0, CENT)); - - let market = MarketCommons::market(&0).unwrap(); - let grace_period = market.deadlines.grace_period; - run_to_block(end + grace_period + 1); - assert_ok!(PredictionMarkets::report( - Origin::signed(BOB), - 0, - OutcomeReport::Categorical(0) - )); - let dispute_at_0 = end + grace_period + 2; - run_to_block(dispute_at_0); - assert_ok!(PredictionMarkets::dispute( - Origin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(1) - )); - let dispute_at_1 = dispute_at_0 + 1; - run_to_block(dispute_at_1); - assert_ok!(PredictionMarkets::dispute( - Origin::signed(DAVE), - 0, - OutcomeReport::Categorical(0) - )); - let dispute_at_2 = dispute_at_1 + 1; - run_to_block(dispute_at_2); - assert_ok!(PredictionMarkets::dispute( - Origin::signed(EVE), - 0, - OutcomeReport::Categorical(1) - )); - - let charlie_reserved = Balances::reserved_balance(&CHARLIE); - let eve_reserved = Balances::reserved_balance(&EVE); - let disputes = crate::Disputes::::get(0); - assert_eq!(disputes.len(), 3); - - run_blocks(market.deadlines.dispute_duration); - let market_after = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after.status, MarketStatus::Resolved); - let disputes = crate::Disputes::::get(0); - assert_eq!(disputes.len(), 0); - assert_ok!(PredictionMarkets::redeem_shares(Origin::signed(CHARLIE), 0)); - - // make sure rewards are right - // - // slashed amounts - // --------------------------- - // - Charlie's reserve: DisputeBond::get() - // - Eve's reserve: DisputeBond::get() + 2 * DisputeFactor::get() - // - // All goes to Dave (because Bob is - strictly speaking - not a disputor). - assert_eq!(Balances::free_balance(&CHARLIE), 1_000 * BASE - charlie_reserved); - assert_eq!(Balances::free_balance(&EVE), 1_000 * BASE - eve_reserved); - let total_slashed = charlie_reserved + eve_reserved; - assert_eq!(Balances::free_balance(&DAVE), 1_000 * BASE + total_slashed); - - // The oracle report was accepted, so Alice is not slashed. - assert_eq!(Balances::free_balance(&ALICE), 1_000 * BASE); - assert_eq!(Balances::free_balance(&BOB), 1_000 * BASE); - - assert!(market_after.bonds.creation.unwrap().is_settled); - assert!(market_after.bonds.oracle.unwrap().is_settled); - }); -} - #[test] fn start_global_dispute_works() { ExtBuilder::default().build().execute_with(|| { @@ -2454,16 +2417,15 @@ fn start_global_dispute_fails_on_wrong_mdm() { let dispute_at_0 = end + grace_period + 2; run_to_block(dispute_at_0); - for i in 1..=::MaxDisputes::get() { - assert_ok!(PredictionMarkets::dispute( - Origin::signed(CHARLIE), - market_id, - OutcomeReport::Categorical(i.saturated_into()) - )); - run_blocks(1); - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Disputed); - } + // only one dispute allowed for authorized mdm + assert_ok!(PredictionMarkets::dispute( + Origin::signed(CHARLIE), + market_id, + OutcomeReport::Categorical(1u32.saturated_into()) + )); + run_blocks(1); + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Disputed); #[cfg(feature = "with-global-disputes")] assert_noop!( @@ -3035,14 +2997,21 @@ fn authorized_correctly_resolves_disputed_market() { 0, OutcomeReport::Categorical(0) )); - let dispute_at_0 = grace_period + 1 + 1; - run_to_block(dispute_at_0); + + let charlie_balance = Balances::free_balance(&CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE - CENT); + + let dispute_at = grace_period + 1 + 1; + run_to_block(dispute_at); assert_ok!(PredictionMarkets::dispute( Origin::signed(CHARLIE), 0, OutcomeReport::Categorical(1) )); + let charlie_balance = Balances::free_balance(&CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE - CENT - DisputeBond::get()); + // Fred authorizses an outcome, but fat-fingers it on the first try. assert_ok!(Authorized::authorize_market_outcome( Origin::signed(AuthorizedDisputeResolutionUser::get()), @@ -3055,21 +3024,6 @@ fn authorized_correctly_resolves_disputed_market() { OutcomeReport::Categorical(1) )); - let dispute_at_1 = dispute_at_0 + 1; - run_to_block(dispute_at_1); - assert_ok!(PredictionMarkets::dispute( - Origin::signed(DAVE), - 0, - OutcomeReport::Categorical(0) - )); - let dispute_at_2 = dispute_at_1 + 1; - run_to_block(dispute_at_2); - assert_ok!(PredictionMarkets::dispute( - Origin::signed(EVE), - 0, - OutcomeReport::Categorical(1) - )); - let market = MarketCommons::market(&0).unwrap(); assert_eq!(market.status, MarketStatus::Disputed); @@ -3077,33 +3031,30 @@ fn authorized_correctly_resolves_disputed_market() { let charlie_reserved = Balances::reserved_balance(&CHARLIE); assert_eq!(charlie_reserved, DisputeBond::get()); - let dave_reserved = Balances::reserved_balance(&DAVE); - assert_eq!(dave_reserved, DisputeBond::get() + DisputeFactor::get()); - - let eve_reserved = Balances::reserved_balance(&EVE); - assert_eq!(eve_reserved, DisputeBond::get() + 2 * DisputeFactor::get()); - // check disputes length let disputes = crate::Disputes::::get(0); - assert_eq!(disputes.len(), 3); + assert_eq!(disputes.len(), 1); - // make sure the old mappings of market id per dispute block are erased let market_ids_1 = MarketIdsPerDisputeBlock::::get( - dispute_at_0 + market.deadlines.dispute_duration, + dispute_at + ::CorrectionPeriod::get(), ); - assert_eq!(market_ids_1.len(), 0); + assert_eq!(market_ids_1.len(), 1); - let market_ids_2 = MarketIdsPerDisputeBlock::::get( - dispute_at_1 + market.deadlines.dispute_duration, - ); - assert_eq!(market_ids_2.len(), 0); + let charlie_balance = Balances::free_balance(&CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE - CENT - DisputeBond::get()); - let market_ids_3 = MarketIdsPerDisputeBlock::::get( - dispute_at_2 + market.deadlines.dispute_duration, - ); - assert_eq!(market_ids_3.len(), 1); + run_blocks(::CorrectionPeriod::get() - 1); - run_blocks(market.deadlines.dispute_duration); + let market_after = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after.status, MarketStatus::Disputed); + + let charlie_balance = Balances::free_balance(&CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE - CENT - DisputeBond::get()); + + run_blocks(1); + + let charlie_balance = Balances::free_balance(&CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE - CENT + OracleBond::get()); let market_after = MarketCommons::market(&0).unwrap(); assert_eq!(market_after.status, MarketStatus::Resolved); @@ -3112,26 +3063,10 @@ fn authorized_correctly_resolves_disputed_market() { assert_ok!(PredictionMarkets::redeem_shares(Origin::signed(CHARLIE), 0)); - // Make sure rewards are right: - // - // Slashed amounts: - // - Dave's reserve: DisputeBond::get() + DisputeFactor::get() - // - Alice's oracle bond: OracleBond::get() - // Total: OracleBond::get() + DisputeBond::get() + DisputeFactor::get() - // - // 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; - 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()); let charlie_reserved_2 = Balances::reserved_balance(&CHARLIE); assert_eq!(charlie_reserved_2, 0); - let eve_balance = Balances::free_balance(&EVE); - assert_eq!(eve_balance, 1_000 * BASE + total_slashed / 2); - - let dave_balance = Balances::free_balance(&DAVE); - assert_eq!(dave_balance, 1_000 * BASE - dave_reserved); let alice_balance = Balances::free_balance(&ALICE); assert_eq!(alice_balance, 1_000 * BASE - OracleBond::get()); @@ -3146,69 +3081,6 @@ fn authorized_correctly_resolves_disputed_market() { }); } -#[test] -fn on_resolution_defaults_to_oracle_report_in_case_of_unresolved_dispute() { - ExtBuilder::default().build().execute_with(|| { - assert!(Balances::free_balance(Treasury::account_id()).is_zero()); - let end = 2; - let market_id = 0; - assert_ok!(PredictionMarkets::create_market( - Origin::signed(ALICE), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::Authorized, - ScoringRule::CPMM, - )); - assert_ok!(PredictionMarkets::buy_complete_set(Origin::signed(CHARLIE), market_id, CENT)); - - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - assert_ok!(PredictionMarkets::report( - Origin::signed(BOB), - market_id, - OutcomeReport::Categorical(1) - )); - assert_ok!(PredictionMarkets::dispute( - Origin::signed(CHARLIE), - market_id, - OutcomeReport::Categorical(0) - )); - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Disputed); - - let charlie_reserved = Balances::reserved_balance(&CHARLIE); - assert_eq!(charlie_reserved, DisputeBond::get()); - - run_blocks(market.deadlines.dispute_duration); - let market_after = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market_after.status, MarketStatus::Resolved); - let disputes = crate::Disputes::::get(0); - assert_eq!(disputes.len(), 0); - assert_ok!(PredictionMarkets::redeem_shares(Origin::signed(CHARLIE), market_id)); - - // Make sure rewards are right: - // - // - Bob reported "correctly" and in time, so Alice and Bob don't get slashed - // - Charlie started a dispute which was abandoned, hence he's slashed and his rewards are - // moved to the treasury - let alice_balance = Balances::free_balance(&ALICE); - assert_eq!(alice_balance, 1_000 * BASE); - let bob_balance = Balances::free_balance(&BOB); - assert_eq!(bob_balance, 1_000 * BASE); - let charlie_balance = Balances::free_balance(&CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - charlie_reserved); - assert_eq!(Balances::free_balance(Treasury::account_id()), charlie_reserved); - - assert!(market_after.bonds.creation.unwrap().is_settled); - assert!(market_after.bonds.oracle.unwrap().is_settled); - }); -} - #[test] fn approve_market_correctly_unreserves_advisory_bond() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/prediction-markets/src/weights.rs b/zrml/prediction-markets/src/weights.rs index 22884a3f0..496d959a4 100644 --- a/zrml/prediction-markets/src/weights.rs +++ b/zrml/prediction-markets/src/weights.rs @@ -18,11 +18,11 @@ //! Autogenerated weights for zrml_prediction_markets //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-25, STEPS: `10`, REPEAT: 1000, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-01-09, STEPS: `10`, REPEAT: 1000, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/zeitgeist +// ./target/release/zeitgeist // benchmark // pallet // --chain=dev @@ -33,8 +33,8 @@ // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --template=./misc/weight_template.hbs // --output=./zrml/prediction-markets/src/weights.rs +// --template=./misc/weight_template.hbs #![allow(unused_parens)] #![allow(unused_imports)] @@ -50,8 +50,8 @@ pub trait WeightInfoZeitgeist { fn admin_move_market_to_closed(o: u32, c: u32) -> Weight; fn admin_move_market_to_resolved_scalar_reported(r: u32) -> Weight; fn admin_move_market_to_resolved_categorical_reported(r: u32) -> Weight; - fn admin_move_market_to_resolved_scalar_disputed(r: u32, d: u32) -> Weight; - fn admin_move_market_to_resolved_categorical_disputed(r: u32, d: u32) -> Weight; + fn admin_move_market_to_resolved_scalar_disputed(r: u32) -> Weight; + fn admin_move_market_to_resolved_categorical_disputed(r: u32) -> Weight; fn approve_market() -> Weight; fn request_edit(r: u32) -> Weight; fn buy_complete_set(a: u32) -> Weight; @@ -59,13 +59,13 @@ pub trait WeightInfoZeitgeist { fn edit_market(m: u32) -> Weight; fn deploy_swap_pool_for_market_future_pool(a: u32, o: u32) -> Weight; fn deploy_swap_pool_for_market_open_pool(a: u32) -> Weight; - fn start_global_dispute(m: u32) -> Weight; - fn dispute_authorized(d: u32, b: u32) -> Weight; + fn start_global_dispute(m: u32, n: u32) -> Weight; + fn dispute_authorized() -> Weight; fn handle_expired_advised_market() -> Weight; fn internal_resolve_categorical_reported() -> Weight; - fn internal_resolve_categorical_disputed(d: u32) -> Weight; + fn internal_resolve_categorical_disputed() -> Weight; fn internal_resolve_scalar_reported() -> Weight; - fn internal_resolve_scalar_disputed(d: u32) -> Weight; + fn internal_resolve_scalar_disputed() -> Weight; fn on_initialize_resolve_overhead() -> Weight; fn process_subsidy_collecting_markets_raw(a: u32) -> Weight; fn redeem_shares_categorical() -> Weight; @@ -91,12 +91,14 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: Tokens TotalIssuance (r:2 w:2) // Storage: PredictionMarkets Disputes (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 { - (176_251_000 as Weight) - // Standard Error: 14_000 - .saturating_add((37_179_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 218_000 - .saturating_add((6_261_000 as Weight).saturating_mul(d as Weight)) + fn admin_destroy_disputed_market(a: u32, d: u32, o: u32, _c: u32, _r: u32) -> Weight { + (131_391_000 as Weight) + // Standard Error: 3_000 + .saturating_add((22_410_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 56_000 + .saturating_add((1_219_000 as Weight).saturating_mul(d as Weight)) + // Standard Error: 3_000 + .saturating_add((66_000 as Weight).saturating_mul(o as Weight)) .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(a as Weight))) .saturating_add(T::DbWeight::get().writes(8 as Weight)) @@ -111,12 +113,10 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: Tokens TotalIssuance (r:2 w:2) // Storage: PredictionMarkets MarketIdsPerReportBlock (r:1 w:1) // Storage: PredictionMarkets Disputes (r:0 w:1) - fn admin_destroy_reported_market(a: u32, _o: u32, c: u32, _r: u32) -> Weight { - (198_500_000 as Weight) - // Standard Error: 16_000 - .saturating_add((37_042_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 15_000 - .saturating_add((218_000 as Weight).saturating_mul(c as Weight)) + fn admin_destroy_reported_market(a: u32, _o: u32, _c: u32, _r: u32) -> Weight { + (148_923_000 as Weight) + // Standard Error: 4_000 + .saturating_add((22_300_000 as Weight).saturating_mul(a as Weight)) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(a as Weight))) .saturating_add(T::DbWeight::get().writes(8 as Weight)) @@ -126,10 +126,12 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: PredictionMarkets MarketIdsPerOpenTimeFrame (r:1 w:1) // Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) // Storage: MarketCommons MarketPool (r:1 w:0) - fn admin_move_market_to_closed(_o: u32, c: u32) -> Weight { - (62_877_000 as Weight) + fn admin_move_market_to_closed(o: u32, c: u32) -> Weight { + (38_298_000 as Weight) + // Standard Error: 0 + .saturating_add((21_000 as Weight).saturating_mul(o as Weight)) // Standard Error: 0 - .saturating_add((71_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((7_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -139,9 +141,9 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: PredictionMarkets Disputes (r:1 w:1) // Storage: MarketCommons MarketPool (r:1 w:0) fn admin_move_market_to_resolved_scalar_reported(r: u32) -> Weight { - (100_354_000 as Weight) + (72_815_000 as Weight) // Standard Error: 0 - .saturating_add((80_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((58_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -152,65 +154,55 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: MarketCommons MarketPool (r:1 w:0) // Storage: Swaps Pools (r:1 w:1) fn admin_move_market_to_resolved_categorical_reported(r: u32) -> Weight { - (201_548_000 as Weight) - // Standard Error: 3_000 - .saturating_add((100_000 as Weight).saturating_mul(r as Weight)) + (101_641_000 as Weight) + // Standard Error: 1_000 + .saturating_add((57_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: MarketCommons Markets (r:1 w:1) // Storage: PredictionMarkets Disputes (r:1 w:1) + // Storage: Authorized AuthorizedOutcomeReports (r:1 w:1) // Storage: PredictionMarkets MarketIdsPerDisputeBlock (r:1 w:1) - // Storage: Balances Reserves (r:7 w:7) + // Storage: Balances Reserves (r:2 w:2) // Storage: GlobalDisputes Winners (r:1 w:0) - // Storage: Authorized AuthorizedOutcomeReports (r:1 w:0) - // Storage: System Account (r:7 w:7) + // Storage: System Account (r:1 w:1) // Storage: MarketCommons MarketPool (r:1 w:0) - fn admin_move_market_to_resolved_scalar_disputed(r: u32, d: u32) -> Weight { - (132_066_000 as Weight) + fn admin_move_market_to_resolved_scalar_disputed(r: u32) -> Weight { + (119_331_000 as Weight) // Standard Error: 1_000 - .saturating_add((120_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 21_000 - .saturating_add((30_950_000 as Weight).saturating_mul(d as Weight)) - .saturating_add(T::DbWeight::get().reads(8 as Weight)) - .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(d as Weight))) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) - .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(d as Weight))) + .saturating_add((23_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(9 as Weight)) + .saturating_add(T::DbWeight::get().writes(7 as Weight)) } // Storage: MarketCommons Markets (r:1 w:1) // Storage: PredictionMarkets Disputes (r:1 w:1) + // Storage: Authorized AuthorizedOutcomeReports (r:1 w:1) // Storage: PredictionMarkets MarketIdsPerDisputeBlock (r:1 w:1) - // Storage: Balances Reserves (r:7 w:7) + // Storage: Balances Reserves (r:2 w:2) // Storage: GlobalDisputes Winners (r:1 w:0) - // Storage: Authorized AuthorizedOutcomeReports (r:1 w:0) - // Storage: System Account (r:6 w:6) + // Storage: System Account (r:1 w:1) // Storage: MarketCommons MarketPool (r:1 w:0) // Storage: Swaps Pools (r:1 w:1) - fn admin_move_market_to_resolved_categorical_disputed(r: u32, d: u32) -> Weight { - (227_185_000 as Weight) - // Standard Error: 6_000 - .saturating_add((69_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 90_000 - .saturating_add((33_824_000 as Weight).saturating_mul(d as Weight)) - .saturating_add(T::DbWeight::get().reads(9 as Weight)) - .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(d as Weight))) - .saturating_add(T::DbWeight::get().writes(6 as Weight)) - .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(d as Weight))) + fn admin_move_market_to_resolved_categorical_disputed(r: u32) -> Weight { + (148_478_000 as Weight) + // Standard Error: 1_000 + .saturating_add((35_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(10 as Weight)) + .saturating_add(T::DbWeight::get().writes(8 as Weight)) } // Storage: MarketCommons Markets (r:1 w:1) // Storage: PredictionMarkets MarketIdsForEdit (r:1 w:0) // Storage: Balances Reserves (r:1 w:1) fn approve_market() -> Weight { - (62_900_000 as Weight) + (46_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: MarketCommons Markets (r:1 w:0) // Storage: PredictionMarkets MarketIdsForEdit (r:1 w:1) - fn request_edit(r: u32) -> Weight { - (37_307_000 as Weight) - // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(r as Weight)) + fn request_edit(_r: u32) -> Weight { + (24_170_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -219,9 +211,9 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: Tokens Accounts (r:2 w:2) // Storage: Tokens TotalIssuance (r:2 w:2) fn buy_complete_set(a: u32) -> Weight { - (63_624_000 as Weight) - // Standard Error: 12_000 - .saturating_add((26_565_000 as Weight).saturating_mul(a as Weight)) + (52_875_000 as Weight) + // Standard Error: 4_000 + .saturating_add((17_255_000 as Weight).saturating_mul(a as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(a as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -233,9 +225,9 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) // Storage: MarketCommons Markets (r:0 w:1) fn create_market(m: u32) -> Weight { - (75_202_000 as Weight) - // Standard Error: 3_000 - .saturating_add((53_000 as Weight).saturating_mul(m as Weight)) + (46_289_000 as Weight) + // Standard Error: 0 + .saturating_add((24_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -244,9 +236,9 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) fn edit_market(m: u32) -> Weight { - (64_080_000 as Weight) + (40_535_000 as Weight) // Standard Error: 0 - .saturating_add((117_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((30_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -260,11 +252,11 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: MarketCommons MarketPool (r:1 w:1) // Storage: Swaps Pools (r:0 w:1) fn deploy_swap_pool_for_market_future_pool(a: u32, o: u32) -> Weight { - (108_283_000 as Weight) - // Standard Error: 14_000 - .saturating_add((42_613_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 13_000 - .saturating_add((322_000 as Weight).saturating_mul(o as Weight)) + (91_979_000 as Weight) + // Standard Error: 6_000 + .saturating_add((26_628_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 6_000 + .saturating_add((2_000 as Weight).saturating_mul(o as Weight)) .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(a as Weight))) .saturating_add(T::DbWeight::get().writes(7 as Weight)) @@ -279,9 +271,9 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: MarketCommons MarketPool (r:1 w:1) // Storage: Swaps Pools (r:0 w:1) fn deploy_swap_pool_for_market_open_pool(a: u32) -> Weight { - (136_673_000 as Weight) - // Standard Error: 16_000 - .saturating_add((42_795_000 as Weight).saturating_mul(a as Weight)) + (94_216_000 as Weight) + // Standard Error: 4_000 + .saturating_add((26_749_000 as Weight).saturating_mul(a as Weight)) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(a as Weight))) .saturating_add(T::DbWeight::get().writes(6 as Weight)) @@ -292,79 +284,73 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: GlobalDisputes Winners (r:1 w:1) // Storage: GlobalDisputes Outcomes (r:7 w:7) // Storage: PredictionMarkets MarketIdsPerDisputeBlock (r:2 w:2) - fn start_global_dispute(_m: u32) -> Weight { - (143_932_000 as Weight) + fn start_global_dispute(m: u32, n: u32) -> Weight { + (94_106_000 as Weight) + // Standard Error: 0 + .saturating_add((15_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 0 + .saturating_add((20_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(12 as Weight)) .saturating_add(T::DbWeight::get().writes(10 as Weight)) } // Storage: PredictionMarkets Disputes (r:1 w:1) // Storage: MarketCommons Markets (r:1 w:1) // Storage: Balances Reserves (r:1 w:1) - // Storage: PredictionMarkets MarketIdsPerDisputeBlock (r:1 w:1) - fn dispute_authorized(d: u32, b: u32) -> Weight { - (79_841_000 as Weight) - // Standard Error: 17_000 - .saturating_add((1_894_000 as Weight).saturating_mul(d as Weight)) - // Standard Error: 1_000 - .saturating_add((82_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + fn dispute_authorized() -> Weight { + (46_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) } - // Storage: Balances Reserves (r:1 w:1) // Storage: MarketCommons Markets (r:1 w:1) + // Storage: Balances Reserves (r:1 w:1) // Storage: PredictionMarkets MarketIdsForEdit (r:0 w:1) fn handle_expired_advised_market() -> Weight { - (61_850_000 as Weight) + (49_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } + // Storage: MarketCommons Markets (r:1 w:1) // Storage: Balances Reserves (r:1 w:1) // Storage: PredictionMarkets Disputes (r:1 w:1) // Storage: MarketCommons MarketPool (r:1 w:0) // Storage: Swaps Pools (r:1 w:1) - // Storage: MarketCommons Markets (r:1 w:1) fn internal_resolve_categorical_reported() -> Weight { - (172_370_000 as Weight) + (85_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } + // Storage: MarketCommons Markets (r:1 w:1) // Storage: Balances Reserves (r:1 w:1) // Storage: PredictionMarkets Disputes (r:1 w:1) + // Storage: GlobalDisputes Winners (r:1 w:0) + // Storage: Authorized AuthorizedOutcomeReports (r:1 w:1) // Storage: MarketCommons MarketPool (r:1 w:0) // Storage: Swaps Pools (r:1 w:1) - // Storage: MarketCommons Markets (r:1 w:1) - // Storage: GlobalDisputes Winners (r:1 w:0) - // Storage: Authorized AuthorizedOutcomeReports (r:1 w:0) - // Storage: System Account (r:1 w:1) - fn internal_resolve_categorical_disputed(d: u32) -> Weight { - (183_852_000 as Weight) - // Standard Error: 82_000 - .saturating_add((24_286_000 as Weight).saturating_mul(d as Weight)) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + fn internal_resolve_categorical_disputed() -> Weight { + (118_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) } + // Storage: MarketCommons Markets (r:1 w:1) // Storage: Balances Reserves (r:1 w:1) // Storage: PredictionMarkets Disputes (r:1 w:1) // Storage: MarketCommons MarketPool (r:1 w:0) - // Storage: MarketCommons Markets (r:1 w:1) fn internal_resolve_scalar_reported() -> Weight { - (73_231_000 as Weight) + (56_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } + // Storage: MarketCommons Markets (r:1 w:1) // Storage: Balances Reserves (r:1 w:1) // Storage: PredictionMarkets Disputes (r:1 w:1) - // Storage: MarketCommons MarketPool (r:1 w:0) - // Storage: MarketCommons Markets (r:1 w:1) // Storage: GlobalDisputes Winners (r:1 w:0) - // Storage: Authorized AuthorizedOutcomeReports (r:1 w:0) + // Storage: Authorized AuthorizedOutcomeReports (r:1 w:1) // Storage: System Account (r:1 w:1) - fn internal_resolve_scalar_disputed(d: u32) -> Weight { - (90_052_000 as Weight) - // Standard Error: 74_000 - .saturating_add((21_265_000 as Weight).saturating_mul(d as Weight)) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(4 as Weight)) + // Storage: MarketCommons MarketPool (r:1 w:0) + fn internal_resolve_scalar_disputed() -> Weight { + (100_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: Timestamp Now (r:1 w:0) // Storage: PredictionMarkets MarketsCollectingSubsidy (r:1 w:1) @@ -376,15 +362,15 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: PredictionMarkets MarketIdsPerReportBlock (r:1 w:1) // Storage: PredictionMarkets MarketIdsPerDisputeBlock (r:1 w:1) fn on_initialize_resolve_overhead() -> Weight { - (45_170_000 as Weight) + (30_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(9 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) } // Storage: PredictionMarkets MarketsCollectingSubsidy (r:1 w:1) fn process_subsidy_collecting_markets_raw(a: u32) -> Weight { - (5_577_000 as Weight) - // Standard Error: 8_000 - .saturating_add((507_000 as Weight).saturating_mul(a as Weight)) + (3_580_000 as Weight) + // Standard Error: 1_000 + .saturating_add((172_000 as Weight).saturating_mul(a as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -393,7 +379,7 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: System Account (r:1 w:1) // Storage: Tokens TotalIssuance (r:1 w:1) fn redeem_shares_categorical() -> Weight { - (130_241_000 as Weight) + (73_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -402,7 +388,7 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: System Account (r:1 w:1) // Storage: Tokens TotalIssuance (r:2 w:2) fn redeem_shares_scalar() -> Weight { - (144_640_000 as Weight) + (95_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -412,23 +398,21 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: Balances Reserves (r:1 w:1) // Storage: PredictionMarkets MarketIdsForEdit (r:0 w:1) fn reject_market(c: u32, o: u32, r: u32) -> Weight { - (89_918_000 as Weight) + (78_886_000 as Weight) // Standard Error: 0 - .saturating_add((76_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((16_000 as Weight).saturating_mul(c as Weight)) // Standard Error: 0 - .saturating_add((81_000 as Weight).saturating_mul(o as Weight)) + .saturating_add((21_000 as Weight).saturating_mul(o as Weight)) // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((2_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: MarketCommons Markets (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) // Storage: PredictionMarkets MarketIdsPerReportBlock (r:1 w:1) - fn report(m: u32) -> Weight { - (50_223_000 as Weight) - // Standard Error: 0 - .saturating_add((16_000 as Weight).saturating_mul(m as Weight)) + fn report(_m: u32) -> Weight { + (31_024_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -437,9 +421,9 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: Tokens Accounts (r:2 w:2) // Storage: Tokens TotalIssuance (r:2 w:2) fn sell_complete_set(a: u32) -> Weight { - (48_611_000 as Weight) - // Standard Error: 12_000 - .saturating_add((33_331_000 as Weight).saturating_mul(a as Weight)) + (40_368_000 as Weight) + // Standard Error: 4_000 + .saturating_add((21_523_000 as Weight).saturating_mul(a as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(a as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -452,9 +436,9 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: PredictionMarkets MarketsCollectingSubsidy (r:1 w:1) // Storage: Swaps Pools (r:0 w:1) fn start_subsidy(a: u32) -> Weight { - (57_592_000 as Weight) - // Standard Error: 2_000 - .saturating_add((53_000 as Weight).saturating_mul(a as Weight)) + (33_451_000 as Weight) + // Standard Error: 0 + .saturating_add((52_000 as Weight).saturating_mul(a as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -462,11 +446,11 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: MarketCommons Markets (r:32 w:0) // Storage: PredictionMarkets MarketIdsPerOpenTimeFrame (r:1 w:1) fn market_status_manager(b: u32, f: u32) -> Weight { - (27_651_000 as Weight) - // Standard Error: 3_000 - .saturating_add((6_365_000 as Weight).saturating_mul(b as Weight)) - // Standard Error: 3_000 - .saturating_add((6_378_000 as Weight).saturating_mul(f as Weight)) + (15_414_000 as Weight) + // Standard Error: 2_000 + .saturating_add((4_284_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 2_000 + .saturating_add((4_380_000 as Weight).saturating_mul(f as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight))) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(f as Weight))) @@ -476,11 +460,11 @@ impl WeightInfoZeitgeist for WeightInfo { // Storage: MarketCommons Markets (r:32 w:0) // Storage: PredictionMarkets MarketIdsPerDisputeBlock (r:1 w:1) fn market_resolution_manager(r: u32, d: u32) -> Weight { - (29_736_000 as Weight) - // Standard Error: 3_000 - .saturating_add((6_353_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 3_000 - .saturating_add((6_344_000 as Weight).saturating_mul(d as Weight)) + (17_183_000 as Weight) + // Standard Error: 2_000 + .saturating_add((4_209_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 2_000 + .saturating_add((4_285_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(d as Weight))) @@ -488,7 +472,7 @@ impl WeightInfoZeitgeist for WeightInfo { } // Storage: PredictionMarkets MarketsCollectingSubsidy (r:1 w:1) fn process_subsidy_collecting_markets_dummy() -> Weight { - (5_470_000 as Weight) + (3_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } diff --git a/zrml/simple-disputes/src/lib.rs b/zrml/simple-disputes/src/lib.rs index 2609fe844..193a3f716 100644 --- a/zrml/simple-disputes/src/lib.rs +++ b/zrml/simple-disputes/src/lib.rs @@ -33,12 +33,13 @@ mod pallet { use core::marker::PhantomData; use frame_support::{ dispatch::DispatchResult, + ensure, traits::{Currency, Get, Hooks, IsType}, PalletId, }; - use sp_runtime::DispatchError; + use sp_runtime::{traits::Saturating, DispatchError}; use zeitgeist_primitives::{ - traits::DisputeApi, + traits::{DisputeApi, DisputeResolutionApi}, types::{Market, MarketDispute, MarketDisputeMechanism, MarketStatus, OutcomeReport}, }; use zrml_market_commons::MarketCommonsPalletApi; @@ -65,6 +66,13 @@ mod pallet { /// Event type Event: From> + IsType<::Event>; + type DisputeResolution: DisputeResolutionApi< + AccountId = Self::AccountId, + BlockNumber = Self::BlockNumber, + MarketId = MarketIdOf, + Moment = MomentOf, + >; + /// The identifier of individual markets. type MarketCommons: MarketCommonsPalletApi< AccountId = Self::AccountId, @@ -93,6 +101,33 @@ mod pallet { #[pallet::hooks] impl Hooks for Pallet {} + impl Pallet + where + T: Config, + { + fn get_auto_resolve( + disputes: &[MarketDispute], + market: &MarketOf, + ) -> Option { + disputes.last().map(|last_dispute| { + last_dispute.at.saturating_add(market.deadlines.dispute_duration) + }) + } + + fn remove_auto_resolve( + disputes: &[MarketDispute], + market_id: &MarketIdOf, + market: &MarketOf, + ) { + if let Some(dispute_duration_ends_at_block) = Self::get_auto_resolve(disputes, market) { + T::DisputeResolution::remove_auto_resolve( + market_id, + dispute_duration_ends_at_block, + ); + } + } + } + impl DisputeApi for Pallet where T: Config, @@ -105,13 +140,20 @@ mod pallet { type Origin = T::Origin; fn on_dispute( - _: &[MarketDispute], - _: &Self::MarketId, + disputes: &[MarketDispute], + market_id: &Self::MarketId, market: &MarketOf, ) -> DispatchResult { - if market.dispute_mechanism != MarketDisputeMechanism::SimpleDisputes { - return Err(Error::::MarketDoesNotHaveSimpleDisputesMechanism.into()); - } + 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(()) } @@ -120,12 +162,11 @@ mod pallet { _: &Self::MarketId, market: &MarketOf, ) -> Result, DispatchError> { - if market.dispute_mechanism != MarketDisputeMechanism::SimpleDisputes { - return Err(Error::::MarketDoesNotHaveSimpleDisputesMechanism.into()); - } - if market.status != MarketStatus::Disputed { - return Err(Error::::InvalidMarketStatus.into()); - } + 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())) @@ -133,6 +174,31 @@ mod pallet { Err(Error::::InvalidMarketStatus.into()) } } + + fn get_auto_resolve( + disputes: &[MarketDispute], + _: &Self::MarketId, + market: &MarketOf, + ) -> Result, DispatchError> { + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, + Error::::MarketDoesNotHaveSimpleDisputesMechanism + ); + Ok(Self::get_auto_resolve(disputes, market)) + } + + fn has_failed( + _: &[MarketDispute], + _: &Self::MarketId, + market: &MarketOf, + ) -> Result { + ensure!( + market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, + Error::::MarketDoesNotHaveSimpleDisputesMechanism + ); + // TODO when does simple disputes fail? + Ok(false) + } } impl SimpleDisputesPalletApi for Pallet where T: Config {} diff --git a/zrml/simple-disputes/src/mock.rs b/zrml/simple-disputes/src/mock.rs index bc03f9baa..5e6afbcd6 100644 --- a/zrml/simple-disputes/src/mock.rs +++ b/zrml/simple-disputes/src/mock.rs @@ -18,7 +18,12 @@ #![cfg(test)] use crate::{self as zrml_simple_disputes}; -use frame_support::{construct_runtime, traits::Everything}; +use frame_support::{ + construct_runtime, + pallet_prelude::{DispatchError, Weight}, + traits::Everything, + BoundedVec, +}; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, @@ -27,9 +32,10 @@ use zeitgeist_primitives::{ constants::mock::{ BlockHashCount, MaxReserves, MinimumPeriod, PmPalletId, SimpleDisputesPalletId, }, + traits::DisputeResolutionApi, types::{ - AccountIdTest, Balance, BlockNumber, BlockTest, Hash, Index, MarketId, Moment, - UncheckedExtrinsicTest, + AccountIdTest, Balance, BlockNumber, BlockTest, Hash, Index, Market, MarketDispute, + MarketId, Moment, UncheckedExtrinsicTest, }, }; @@ -48,8 +54,49 @@ construct_runtime!( } ); +// NoopResolution implements DisputeResolutionApi with no-ops. +pub struct NoopResolution; + +impl DisputeResolutionApi for NoopResolution { + type AccountId = AccountIdTest; + type Balance = Balance; + type BlockNumber = BlockNumber; + type MarketId = MarketId; + type MaxDisputes = u32; + type Moment = Moment; + + fn resolve( + _market_id: &Self::MarketId, + _market: &Market, + ) -> Result { + Ok(0) + } + + fn add_auto_resolve( + _market_id: &Self::MarketId, + _resolve_at: Self::BlockNumber, + ) -> Result { + Ok(0u32) + } + + fn auto_resolve_exists(_market_id: &Self::MarketId, _resolve_at: Self::BlockNumber) -> bool { + false + } + + 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 Event = (); + type DisputeResolution = NoopResolution; type MarketCommons = MarketCommons; type PalletId = SimpleDisputesPalletId; }