From 9de7cb6cc588855bd03fcc73f9c5d9685c27b7ee Mon Sep 17 00:00:00 2001 From: Caio Date: Tue, 7 Sep 2021 15:27:31 -0300 Subject: [PATCH] Integrate Court into the Runtime --- Cargo.lock | 4 + runtime/Cargo.toml | 7 +- runtime/src/lib.rs | 24 +- zrml/court/Cargo.toml | 7 + zrml/court/src/benchmarks.rs | 52 ++++ zrml/court/src/court_pallet_api.rs | 5 +- zrml/court/src/juror.rs | 3 +- zrml/court/src/lib.rs | 271 +++++++++++++----- zrml/court/src/mock.rs | 6 +- zrml/court/src/tests.rs | 82 ++++-- zrml/court/src/weights.rs | 61 ++++ zrml/prediction-markets/Cargo.toml | 4 + zrml/prediction-markets/src/lib.rs | 23 +- zrml/prediction-markets/src/mock.rs | 27 +- zrml/simple-disputes/src/lib.rs | 6 + .../src/simple_disputes_pallet_api.rs | 3 + 16 files changed, 461 insertions(+), 124 deletions(-) create mode 100644 zrml/court/src/benchmarks.rs create mode 100644 zrml/court/src/weights.rs create mode 100644 zrml/simple-disputes/src/simple_disputes_pallet_api.rs diff --git a/Cargo.lock b/Cargo.lock index 802bbbe6c..051307b50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11985,6 +11985,7 @@ dependencies = [ "xcm-builder", "xcm-executor", "zeitgeist-primitives", + "zrml-court", "zrml-liquidity-mining", "zrml-market-commons", "zrml-orderbook-v1", @@ -12020,6 +12021,7 @@ name = "zrml-court" version = "0.1.0" dependencies = [ "arrayvec 0.7.1", + "frame-benchmarking", "frame-support", "frame-system", "pallet-balances", @@ -12100,6 +12102,7 @@ dependencies = [ "orml-tokens", "orml-traits", "pallet-balances", + "pallet-randomness-collective-flip", "pallet-timestamp", "parity-scale-codec", "sp-api", @@ -12107,6 +12110,7 @@ dependencies = [ "sp-io", "sp-runtime", "zeitgeist-primitives", + "zrml-court", "zrml-liquidity-mining", "zrml-market-commons", "zrml-prediction-markets", diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index ca769b4b6..440a89a0b 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -72,6 +72,7 @@ sp-finality-grandpa = { branch = "polkadot-v0.9.8", default-features = false, gi # Zeitgeist zeitgeist-primitives = { default-features = false, path = "../primitives" } +zrml-court = { default-features = false, path = "../zrml/court" } zrml-liquidity-mining = { default-features = false, path = "../zrml/liquidity-mining" } zrml-market-commons = { default-features = false, path = "../zrml/market-commons" } zrml-orderbook-v1 = { default-features = false, path = "../zrml/orderbook-v1" } @@ -112,6 +113,8 @@ parachain = [ "xcm", ] runtime-benchmarks = [ + "pallet-utility/runtime-benchmarks", + "zrml-swaps/runtime-benchmarks", "frame-benchmarking", "frame-support/runtime-benchmarks", "frame-system-benchmarking", @@ -119,15 +122,14 @@ runtime-benchmarks = [ "hex-literal", "pallet-balances/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", - "pallet-utility/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", + "zrml-court/runtime-benchmarks", "zrml-liquidity-mining/runtime-benchmarks", "zrml-orderbook-v1/runtime-benchmarks", "zrml-prediction-markets/runtime-benchmarks", "zrml-simple-disputes/runtime-benchmarks", - "zrml-swaps/runtime-benchmarks", ] std = [ "frame-executive/std", @@ -199,6 +201,7 @@ std = [ # Zeitgeist "zeitgeist-primitives/std", + "zrml-court/std", "zrml-liquidity-mining/std", "zrml-market-commons/std", "zrml-orderbook-v1/std", diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index fad51f098..8a5701b79 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -117,9 +117,10 @@ macro_rules! create_zeitgeist_runtime { Orderbook: zrml_orderbook_v1::{Call, Event, Pallet, Storage} = 41, MarketCommons: zrml_market_commons::{Pallet, Storage} = 42, - Swaps: zrml_swaps::{Call, Event, Pallet, Storage} = 43, - SimpleDisputes: zrml_simple_disputes::{Event, Pallet, Storage} = 44, - PredictionMarkets: zrml_prediction_markets::{Call, Event, Pallet, Storage} = 45, + Court: zrml_court::{Event, Pallet, Storage} = 43, + Swaps: zrml_swaps::{Call, Event, Pallet, Storage} = 44, + SimpleDisputes: zrml_simple_disputes::{Event, Pallet, Storage} = 45, + PredictionMarkets: zrml_prediction_markets::{Call, Event, Pallet, Storage} = 46, $($additional_pallets)* } @@ -323,7 +324,7 @@ impl orml_tokens::Config for Runtime { type Event = Event; type ExistentialDeposits = ExistentialDeposits; type MaxLocks = MaxLocks; - type OnDust = (); + type OnDust = orml_tokens::TransferDust; type WeightInfo = (); } @@ -389,6 +390,17 @@ impl pallet_utility::Config for Runtime { #[cfg(feature = "parachain")] impl parachain_info::Config for Runtime {} +impl zrml_court::Config for Runtime { + type CourtCaseDuration = CourtCaseDuration; + type Event = Event; + type MarketCommons = MarketCommons; + type PalletId = CourtPalletId; + type Random = RandomnessCollectiveFlip; + type StakeWeight = StakeWeight; + type TreasuryPalletId = TreasuryPalletId; + type WeightInfo = zrml_court::weights::WeightInfo; +} + impl zrml_liquidity_mining::Config for Runtime { type Currency = Balances; type Event = Event; @@ -415,12 +427,13 @@ impl zrml_orderbook_v1::Config for Runtime { impl zrml_prediction_markets::Config for Runtime { type AdvisoryBond = AdvisoryBond; type ApprovalOrigin = EnsureRoot; + type Court = Court; type DisputeBond = DisputeBond; type DisputeFactor = DisputeFactor; type DisputePeriod = DisputePeriod; type Event = Event; - type MarketCommons = MarketCommons; type LiquidityMining = LiquidityMining; + type MarketCommons = MarketCommons; type MaxCategories = MaxCategories; type MaxDisputes = MaxDisputes; type MinCategories = MinCategories; @@ -528,6 +541,7 @@ impl_runtime_apis! { add_benchmark!(params, batches, pallet_timestamp, Timestamp); add_benchmark!(params, batches, pallet_utility, Utility); add_benchmark!(params, batches, zrml_swaps, Swaps); + add_benchmark!(params, batches, zrml_court, Court); add_benchmark!(params, batches, zrml_prediction_markets, PredictionMarkets); add_benchmark!(params, batches, zrml_liquidity_mining, LiquidityMining); add_benchmark!(params, batches, zrml_orderbook_v1, Orderbook); diff --git a/zrml/court/Cargo.toml b/zrml/court/Cargo.toml index 113457223..689ec7c18 100644 --- a/zrml/court/Cargo.toml +++ b/zrml/court/Cargo.toml @@ -1,5 +1,6 @@ [dependencies] arrayvec = { default-features = false, version = "0.7" } +frame-benchmarking = { branch = "polkadot-v0.9.8", default-features = false, optional = true, git = "https://github.com/paritytech/substrate" } frame-support = { branch = "polkadot-v0.9.8", default-features = false, git = "https://github.com/paritytech/substrate" } frame-system = { branch = "polkadot-v0.9.8", default-features = false, git = "https://github.com/paritytech/substrate" } parity-scale-codec = { default-features = false, features = ["derive"], version = "2.0" } @@ -16,7 +17,13 @@ sp-io = { branch = "polkadot-v0.9.8", git = "https://github.com/paritytech/subst [features] default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks" +] std = [ + 'frame-benchmarking?/std', 'frame-support/std', 'frame-system/std', 'parity-scale-codec/std', diff --git a/zrml/court/src/benchmarks.rs b/zrml/court/src/benchmarks.rs new file mode 100644 index 000000000..89f15f6be --- /dev/null +++ b/zrml/court/src/benchmarks.rs @@ -0,0 +1,52 @@ +#![allow( + // Auto-generated code is a no man's land + clippy::integer_arithmetic +)] +#![cfg(feature = "runtime-benchmarks")] + +#[cfg(test)] +use crate::Pallet as Court; +use crate::{BalanceOf, Call, Config, CurrencyOf, Pallet}; +use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, whitelisted_caller}; +use frame_support::{dispatch::UnfilteredDispatchable, traits::Currency}; +use frame_system::RawOrigin; +use sp_runtime::traits::Bounded; +use zeitgeist_primitives::types::OutcomeReport; + +fn deposit(caller: &T::AccountId) +where + T: Config, +{ + let _ = CurrencyOf::::deposit_creating(caller, BalanceOf::::max_value()); +} + +fn deposit_and_join_court(caller: &T::AccountId) +where + T: Config, +{ + deposit::(caller); + Call::::join_court() + .dispatch_bypass_filter(RawOrigin::Signed(caller.clone()).into()) + .unwrap(); +} + +benchmarks! { + exit_court { + let caller: T::AccountId = whitelisted_caller(); + deposit_and_join_court::(&caller); + }: _(RawOrigin::Signed(caller)) + + join_court { + let caller: T::AccountId = whitelisted_caller(); + deposit::(&caller); + }: _(RawOrigin::Signed(caller)) + + vote { + let caller: T::AccountId = whitelisted_caller(); + let market_id = Default::default(); + let outcome = OutcomeReport::Scalar(u128::MAX); + deposit_and_join_court::(&caller); + }: _(RawOrigin::Signed(caller), market_id, outcome) +} + +impl_benchmark_test_suite!(Court, crate::mock::ExtBuilder::default().build(), crate::mock::Runtime); diff --git a/zrml/court/src/court_pallet_api.rs b/zrml/court/src/court_pallet_api.rs index 49ab2c8d1..67d701d20 100644 --- a/zrml/court/src/court_pallet_api.rs +++ b/zrml/court/src/court_pallet_api.rs @@ -1,2 +1,3 @@ -/// Court - Pallet Api -pub trait CourtPalletApi {} +use zeitgeist_primitives::traits::DisputeApi; + +pub trait CourtPalletApi: DisputeApi {} diff --git a/zrml/court/src/juror.rs b/zrml/court/src/juror.rs index 0af1a9bbb..fa1b4177a 100644 --- a/zrml/court/src/juror.rs +++ b/zrml/court/src/juror.rs @@ -4,7 +4,6 @@ use crate::JurorStatus; /// /// * `B`: Balance #[derive(Debug, PartialEq, parity_scale_codec::Decode, parity_scale_codec::Encode)] -pub struct Juror { - pub(crate) staked: B, +pub struct Juror { pub(crate) status: JurorStatus, } diff --git a/zrml/court/src/lib.rs b/zrml/court/src/lib.rs index 9f1952f45..9a12c82ca 100644 --- a/zrml/court/src/lib.rs +++ b/zrml/court/src/lib.rs @@ -1,22 +1,33 @@ //! # Court +// It is important to note that if a categorical market has only two outcomes, then winners +// won't receive any rewards because accounts of the most voted outcome on the loser side are +// simply registered as `JurorStatus::Tardy`. + #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; +mod benchmarks; +mod court_pallet_api; mod juror; mod juror_status; mod mock; mod tests; +pub mod weights; +pub use court_pallet_api::CourtPalletApi; pub use juror::Juror; pub use juror_status::JurorStatus; pub use pallet::*; #[frame_support::pallet] mod pallet { - use crate::{Juror, JurorStatus}; - use alloc::collections::BTreeMap; + use crate::{weights::WeightInfoZeitgeist, CourtPalletApi, Juror, JurorStatus}; + use alloc::{ + collections::{BTreeMap, BTreeSet}, + vec::Vec, + }; use arrayvec::ArrayVec; use core::marker::PhantomData; use frame_support::{ @@ -24,14 +35,13 @@ mod pallet { pallet_prelude::{StorageDoubleMap, StorageMap, StorageValue, ValueQuery}, traits::{ BalanceStatus, Currency, Get, Hooks, IsType, NamedReservableCurrency, Randomness, - ReservableCurrency, }, Blake2_128Concat, PalletId, }; use frame_system::{ensure_signed, pallet_prelude::OriginFor}; use rand::{rngs::StdRng, seq::SliceRandom, RngCore, SeedableRng}; use sp_runtime::{ - traits::{AccountIdConversion, Saturating}, + traits::{AccountIdConversion, CheckedDiv, Saturating}, ArithmeticError, DispatchError, SaturatedConversion, }; use zeitgeist_primitives::{ @@ -41,10 +51,11 @@ mod pallet { }; use zrml_market_commons::MarketCommonsPalletApi; + pub(crate) const RESERVE_ID: [u8; 8] = CourtPalletId::get().0; + // Number of jurors for an initial market dispute const INITIAL_JURORS_NUM: usize = 3; const MAX_RANDOM_JURORS: usize = 13; - const RESERVE_ID: [u8; 8] = CourtPalletId::get().0; // Weight used to increase the number of jurors for subsequent disputes // of the same market const SUBSEQUENT_JURORS_FACTOR: usize = 2; @@ -62,18 +73,20 @@ mod pallet { #[pallet::call] impl Pallet { - #[pallet::weight(0)] + // `transactional` attribute is not used here because `remove` and `unreserve_named` are + // infallible. + #[pallet::weight(T::WeightInfo::exit_court())] pub fn exit_court(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let juror = Self::juror(&who)?; + let _ = Self::juror(&who)?; Jurors::::remove(&who); - CurrencyOf::::unreserve_named(&RESERVE_ID, &who, juror.staked); + CurrencyOf::::unreserve_all_named(&RESERVE_ID, &who); Ok(()) } - // `transactional` attribute is not used here because once `reserve` is successful, `insert` - // won't fail. - #[pallet::weight(0)] + // `transactional` attribute is not used here because once `reserve_named` is + // successful, `insert` won't fail. + #[pallet::weight(T::WeightInfo::join_court())] pub fn join_court(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; if Jurors::::get(&who).is_some() { @@ -83,13 +96,13 @@ mod pallet { let jurors_num_plus_one = jurors_num.checked_add(1).ok_or(ArithmeticError::Overflow)?; let stake = Self::current_required_stake(jurors_num_plus_one); CurrencyOf::::reserve_named(&RESERVE_ID, &who, stake)?; - Jurors::::insert(&who, Juror { staked: stake, status: JurorStatus::Ok }); + Jurors::::insert(&who, Juror { status: JurorStatus::Ok }); Ok(()) } // `transactional` attribute is not used here because no fallible storage operation - // is performed - #[pallet::weight(0)] + // is performed. + #[pallet::weight(T::WeightInfo::vote())] pub fn vote( origin: OriginFor, market_id: MarketIdOf, @@ -132,7 +145,10 @@ mod pallet { type StakeWeight: Get>; /// Slashed funds are send to the treasury - type TreasuryId: Get; + type TreasuryPalletId: Get; + + /// Weights generated by benchmarks + type WeightInfo: WeightInfoZeitgeist; } #[pallet::error] @@ -166,10 +182,10 @@ mod pallet { // // If `len` is greater than the length of `jurors`, then `len` will be capped. pub(crate) fn random_jurors<'a, 'b, R>( - jurors: &'a [(T::AccountId, Juror>)], + jurors: &'a [(T::AccountId, Juror)], len: usize, rng: &mut R, - ) -> ArrayVec<&'b (T::AccountId, Juror>), MAX_RANDOM_JURORS> + ) -> ArrayVec<&'b (T::AccountId, Juror), MAX_RANDOM_JURORS> where 'a: 'b, R: RngCore, @@ -194,7 +210,8 @@ mod pallet { StdRng::from_seed(seed) } - pub(crate) fn set_juror_as_tardy(account_id: &T::AccountId) -> DispatchResult { + // Used to avoid code duplications. + pub(crate) fn set_stored_juror_as_tardy(account_id: &T::AccountId) -> DispatchResult { Self::mutate_juror(account_id, |juror| { juror.status = JurorStatus::Tardy; Ok(()) @@ -202,7 +219,7 @@ mod pallet { } pub(crate) fn treasury_account_id() -> T::AccountId { - T::TreasuryId::get().into_account() + T::TreasuryPalletId::get().into_account() } // No-one can stake more than BalanceOf::::max(), therefore, this function saturates @@ -213,62 +230,69 @@ mod pallet { } // Retrieves a juror from the storage - fn juror(account_id: &T::AccountId) -> Result>, DispatchError> { + fn juror(account_id: &T::AccountId) -> Result { Jurors::::get(account_id).ok_or_else(|| Error::::JurorDoesNotExists.into()) } - // Calculates the necessary number of jurors depending on the number of market disputes. - // - // Result is capped to `usize::MAX` or in other words, capped to a very, very, very - // high number of jurors. - fn necessary_jurors_num(disputes: &[MarketDispute]) -> usize { - let len = disputes.len(); - INITIAL_JURORS_NUM.saturating_add(SUBSEQUENT_JURORS_FACTOR.saturating_mul(len)) - } - // * Jurors that didn't vote within `CourtCaseDuration` or didn't vote at all are // placed as tardy. // - // * Slashes 20% of staked funds and removes tardy jurors that didn't vote a second time. - fn manage_tardy_jurors( - requested_jurors: &[(T::AccountId, T::BlockNumber)], + // * Slashes 20% of staked funds and removes tardy jurors that didn't vote or voted + // after the maximum allowed block. + fn manage_tardy_jurors( + requested_jurors: &mut [(T::AccountId, Juror, T::BlockNumber)], votes: &[(T::AccountId, (T::BlockNumber, OutcomeReport))], - ) -> DispatchResult { + mut cb: F, + ) -> DispatchResult + where + F: FnMut(&T::AccountId, &mut Juror, &OutcomeReport) -> DispatchResult, + { let treasury_account_id = Self::treasury_account_id(); - for (ai, max_block) in requested_jurors { - if let Some((_, (block, _))) = votes.iter().find(|el| &el.0 == ai) { - if block > max_block { - Self::set_juror_as_tardy(ai)?; + let slash_and_remove_juror = |ai: &T::AccountId| { + let all_reserved = CurrencyOf::::reserved_balance_named(&RESERVE_ID, ai); + // Division will never overflow + let slash = all_reserved / BalanceOf::::from(TARDY_PUNISHMENT_DIVISOR); + let _ = CurrencyOf::::repatriate_reserved_named( + &RESERVE_ID, + ai, + &treasury_account_id, + slash, + BalanceStatus::Free, + )?; + CurrencyOf::::unreserve_all_named(&RESERVE_ID, ai); + Jurors::::remove(ai); + Self::remove_juror_from_all_courts_of_all_markets(ai); + Ok::<_, DispatchError>(()) + }; + + for (ai, juror, max_block) in requested_jurors { + if let Some((_, (block, outcome))) = votes.iter().find(|el| &el.0 == ai) { + let vote_is_expired = block > max_block; + if vote_is_expired { + if let JurorStatus::Tardy = juror.status { + slash_and_remove_juror(ai)?; + } else { + Self::set_stored_and_in_memory_juror_as_tardy(ai, juror)?; + } + continue; } + + cb(ai, juror, outcome)?; + } else if let JurorStatus::Tardy = juror.status { + slash_and_remove_juror(ai)?; } else { - let juror = Self::juror(ai)?; - if let JurorStatus::Tardy = juror.status { - let reserved = CurrencyOf::::reserved_balance_named(&RESERVE_ID, ai); - // Division will never overflow - let slash = reserved / BalanceOf::::from(TARDY_PUNISHMENT_DIVISOR); - CurrencyOf::::repatriate_reserved_named( - &RESERVE_ID, - ai, - &treasury_account_id, - slash, - BalanceStatus::Free, - )?; - CurrencyOf::::unreserve_named(&RESERVE_ID, ai, reserved); - Jurors::::remove(ai); - } else { - Self::set_juror_as_tardy(ai)?; - } + Self::set_stored_and_in_memory_juror_as_tardy(ai, juror)?; } } Ok(()) } - // Retrieves a juror from the storage + // Modifies a stored juror. fn mutate_juror(account_id: &T::AccountId, mut cb: F) -> DispatchResult where - F: FnMut(&mut Juror>) -> DispatchResult, + F: FnMut(&mut Juror) -> DispatchResult, { Jurors::::try_mutate(account_id, |opt| { if let Some(el) = opt { @@ -280,20 +304,89 @@ mod pallet { }) } - // Jurors are only rewarded if sided on the most voted outcome but jurors that voted - // second most voted outcome (winner of the losing majority) are placed as tardy instead - // of being slashed - fn set_jurors_that_sided_on_the_second_most_voted_outcome_as_tardy( - second_most_voted_outcome: &Option, + // Calculates the necessary number of jurors depending on the number of market disputes. + // + // Result is capped to `usize::MAX` or in other words, capped to a very, very, very + // high number of jurors. + fn necessary_jurors_num(disputes: &[MarketDispute]) -> usize { + let len = disputes.len(); + INITIAL_JURORS_NUM.saturating_add(SUBSEQUENT_JURORS_FACTOR.saturating_mul(len)) + } + + // Handy iterator 👍 + // + // Used by `slash_losers_to_award_winners`. Tardy jurors include previously removed + // accounts, winners of the losing side, accounts that didn't vote and accounts that voted + // after the maximum allowed block, therefore, this function only yields valid winners + // and valid jurors that voted on the third or worse outcome. + fn non_tardy_jurors_with_votes<'a, 'b>( + requested_jurors: &'a mut [(T::AccountId, Juror, T::BlockNumber)], + votes: &'b [(T::AccountId, (T::BlockNumber, OutcomeReport))], + ) -> impl Iterator< + Item = ( + &'a mut T::AccountId, + &'a mut Juror, + &'a mut T::BlockNumber, + &'b (T::BlockNumber, OutcomeReport), + ), + > { + requested_jurors.iter_mut().filter_map(move |(jai, j, max_block)| { + if matches!(j.status, JurorStatus::Tardy) { + return None; + } + let vote_opt = votes.iter().find(|el| &el.0 == jai).map(|el| &el.1); + Some((jai, j, max_block, vote_opt?)) + }) + } + + // Useful when dealing with both structures at the same time + fn set_stored_and_in_memory_juror_as_tardy( + account_id: &T::AccountId, + juror: &mut Juror, + ) -> DispatchResult { + juror.status = JurorStatus::Tardy; + Self::set_stored_juror_as_tardy(account_id) + } + + // Every juror that not voted on the first or second most voted outcome are slashed. + fn slash_losers_to_award_winners( + requested_jurors: &mut [(T::AccountId, Juror, T::BlockNumber)], votes: &[(T::AccountId, (T::BlockNumber, OutcomeReport))], + winner_outcome: &OutcomeReport, ) -> DispatchResult { - if let Some(el) = second_most_voted_outcome { - for (ai, (_, outcome_report)) in votes { - if outcome_report == el { - Self::set_juror_as_tardy(ai)?; - } + let mut total_incentives = BalanceOf::::from(0u8); + let mut total_winners = BalanceOf::::from(0u8); + + for (jai, _, _, (_, outcome)) in + Self::non_tardy_jurors_with_votes(requested_jurors, votes) + { + if outcome == winner_outcome { + total_winners = total_winners.saturating_add(BalanceOf::::from(1u8)); + } else { + let all_reserved = CurrencyOf::::reserved_balance_named(&RESERVE_ID, jai); + // Division will never overflow + let slash = all_reserved / BalanceOf::::from(2u8); + CurrencyOf::::slash_reserved_named(&RESERVE_ID, jai, slash); + total_incentives = total_incentives.saturating_add(slash); } } + + let individual_winner_incentive = + if let Some(e) = total_incentives.checked_div(&total_winners) { + e + } else { + // No winners + return Ok(()); + }; + + for (jai, _, _, (_, outcome)) in + Self::non_tardy_jurors_with_votes(requested_jurors, votes) + { + if outcome == winner_outcome { + CurrencyOf::::deposit_into_existing(jai, individual_winner_incentive)?; + } + } + Ok(()) } @@ -342,6 +435,20 @@ mod pallet { Ok((best_score.0.clone(), Some(second_best_score.0.clone()))) } + + // Obliterates all stored references of a juror + fn remove_juror_from_all_courts_of_all_markets(ai: &T::AccountId) { + let mut market_ids = BTreeSet::new(); + market_ids.extend(RequestedJurors::::iter().map(|el| el.0)); + for market_id in &market_ids { + RequestedJurors::::remove(market_id, ai); + } + market_ids.clear(); + market_ids.extend(Votes::::iter().map(|el| el.0)); + for market_id in &market_ids { + Votes::::remove(market_id, ai); + } + } } impl DisputeApi for Pallet @@ -361,7 +468,7 @@ mod pallet { market_id: &Self::MarketId, who: &Self::AccountId, ) -> DispatchResult { - CurrencyOf::::reserve(who, bond)?; + CurrencyOf::::reserve_named(&RESERVE_ID, who, bond)?; let jurors: Vec<_> = Jurors::::iter().collect(); let necessary_jurors_num = Self::necessary_jurors_num(disputes); let mut rng = Self::rng(); @@ -374,6 +481,10 @@ mod pallet { Ok(()) } + // Set jurors that sided on the second most voted outcome as tardy. Jurors are only + // rewarded if sided on the most voted outcome but jurors that voted second most + // voted outcome (winner of the losing majority) are placed as tardy instead of + // being slashed. fn on_resolution( _: &D, _: &[MarketDispute], @@ -383,20 +494,36 @@ mod pallet { where D: Fn(usize) -> Self::Balance, { - let requested_jurors: Vec<_> = RequestedJurors::::iter_prefix(market_id).collect(); + let mut requested_jurors: Vec<_> = RequestedJurors::::iter_prefix(market_id) + .filter_map(|el| { + let j = Self::juror(&el.0).ok()?; + Some((el.0, j, el.1)) + }) + .collect(); let votes: Vec<_> = Votes::::iter_prefix(market_id).collect(); - let (first, second) = Self::two_best_outcomes(&votes)?; - Self::manage_tardy_jurors(&requested_jurors, &votes)?; - Self::set_jurors_that_sided_on_the_second_most_voted_outcome_as_tardy(&second, &votes)?; + let (first, second_opt) = Self::two_best_outcomes(&votes)?; + if let Some(second) = second_opt { + Self::manage_tardy_jurors(&mut requested_jurors, &votes, |ai, juror, outcome| { + if outcome == &second { + Self::set_stored_and_in_memory_juror_as_tardy(ai, juror)?; + } + Ok(()) + })?; + } else { + Self::manage_tardy_jurors(&mut requested_jurors, &votes, |_, _, _| Ok(()))?; + } + Self::slash_losers_to_award_winners(&mut requested_jurors, &votes, &first)?; Votes::::remove_prefix(market_id, None); RequestedJurors::::remove_prefix(market_id, None); Ok(first) } } + impl CourtPalletApi for Pallet where T: Config {} + /// Accounts that stake funds to decide outcomes. #[pallet::storage] - pub type Jurors = StorageMap<_, Blake2_128Concat, T::AccountId, Juror>>; + pub type Jurors = StorageMap<_, Blake2_128Concat, T::AccountId, Juror>; /// An extra layer of pseudo randomness. #[pallet::storage] diff --git a/zrml/court/src/mock.rs b/zrml/court/src/mock.rs index 03a0398d2..90252c2da 100644 --- a/zrml/court/src/mock.rs +++ b/zrml/court/src/mock.rs @@ -1,6 +1,6 @@ #![cfg(test)] -use crate as zrml_court; +use crate::{self as zrml_court}; use frame_support::construct_runtime; use sp_runtime::{ testing::Header, @@ -20,6 +20,7 @@ use zeitgeist_primitives::{ pub const ALICE: AccountIdTest = 0; pub const BOB: AccountIdTest = 1; pub const CHARLIE: AccountIdTest = 2; +pub const INITIAL_BALANCE: u128 = 1000 * BASE; type Block = BlockTest; type UncheckedExtrinsic = UncheckedExtrinsicTest; @@ -47,7 +48,8 @@ impl crate::Config for Runtime { type PalletId = CourtPalletId; type Random = RandomnessCollectiveFlip; type StakeWeight = StakeWeight; - type TreasuryId = TreasuryPalletId; + type TreasuryPalletId = TreasuryPalletId; + type WeightInfo = crate::weights::WeightInfo; } impl frame_system::Config for Runtime { diff --git a/zrml/court/src/tests.rs b/zrml/court/src/tests.rs index 5faa3df40..b3c3e2be0 100644 --- a/zrml/court/src/tests.rs +++ b/zrml/court/src/tests.rs @@ -3,12 +3,15 @@ use crate::{ mock::{ Balances, Court, ExtBuilder, Origin, RandomnessCollectiveFlip, Runtime, System, ALICE, BOB, - CHARLIE, + CHARLIE, INITIAL_BALANCE, }, - Error, Juror, JurorStatus, Jurors, RequestedJurors, Votes, + Error, Juror, JurorStatus, Jurors, RequestedJurors, Votes, RESERVE_ID, }; use core::ops::Range; -use frame_support::{assert_noop, assert_ok, traits::Hooks}; +use frame_support::{ + assert_noop, assert_ok, + traits::{Hooks, NamedReservableCurrency}, +}; use sp_runtime::traits::Header; use zeitgeist_primitives::{ constants::BASE, @@ -32,23 +35,27 @@ const DEFAULT_MARKET: Market = Market { resolved_outcome: None, status: MarketStatus::Closed, }; -const DEFAULT_SET_OF_JURORS: &[(u128, Juror)] = &[ - (7, Juror { staked: 1, status: JurorStatus::Ok }), - (6, Juror { staked: 2, status: JurorStatus::Tardy }), - (5, Juror { staked: 3, status: JurorStatus::Ok }), - (4, Juror { staked: 4, status: JurorStatus::Tardy }), - (3, Juror { staked: 5, status: JurorStatus::Ok }), - (2, Juror { staked: 6, status: JurorStatus::Ok }), - (1, Juror { staked: 7, status: JurorStatus::Ok }), +const DEFAULT_SET_OF_JURORS: &[(u128, Juror)] = &[ + (7, Juror { status: JurorStatus::Ok }), + (6, Juror { status: JurorStatus::Tardy }), + (5, Juror { status: JurorStatus::Ok }), + (4, Juror { status: JurorStatus::Tardy }), + (3, Juror { status: JurorStatus::Ok }), + (2, Juror { status: JurorStatus::Ok }), + (1, Juror { status: JurorStatus::Ok }), ]; #[test] -fn exit_court_successfully_removes_a_juror() { +fn exit_court_successfully_removes_a_juror_and_frees_balances() { ExtBuilder::default().build().execute_with(|| { assert_ok!(Court::join_court(Origin::signed(ALICE))); assert_eq!(Jurors::::iter().count(), 1); + assert_eq!(Balances::free_balance(ALICE), 998 * BASE); + assert_eq!(Balances::reserved_balance_named(&RESERVE_ID, &ALICE), 2 * BASE); assert_ok!(Court::exit_court(Origin::signed(ALICE))); assert_eq!(Jurors::::iter().count(), 0); + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + assert_eq!(Balances::reserved_balance_named(&RESERVE_ID, &ALICE), 0); }); } @@ -68,12 +75,12 @@ fn join_court_reserves_balance_according_to_the_number_of_jurors() { assert_eq!(Balances::free_balance(ALICE), 1000 * BASE); assert_ok!(Court::join_court(Origin::signed(ALICE))); assert_eq!(Balances::free_balance(ALICE), 998 * BASE); - assert_eq!(Balances::reserved_balance(ALICE), 2 * BASE); + assert_eq!(Balances::reserved_balance_named(&RESERVE_ID, &ALICE), 2 * BASE); assert_eq!(Balances::free_balance(BOB), 1000 * BASE); assert_ok!(Court::join_court(Origin::signed(BOB))); assert_eq!(Balances::free_balance(BOB), 996 * BASE); - assert_eq!(Balances::reserved_balance(BOB), 4 * BASE); + assert_eq!(Balances::reserved_balance_named(&RESERVE_ID, &BOB), 4 * BASE); }); } @@ -83,7 +90,7 @@ fn join_court_successfully_stores_a_juror() { assert_ok!(Court::join_court(Origin::signed(ALICE))); assert_eq!( Jurors::::iter().next().unwrap(), - (ALICE, Juror { staked: 2 * BASE, status: JurorStatus::Ok }) + (ALICE, Juror { status: JurorStatus::Ok }) ); }); } @@ -105,7 +112,7 @@ fn on_dispute_stores_jurors_that_should_vote() { setup_blocks(1..123); let _ = Court::join_court(Origin::signed(ALICE)); let _ = Court::join_court(Origin::signed(BOB)); - Court::on_dispute(BASE, &[], &0, &ALICE).unwrap(); + Court::on_dispute(0, &[], &0, &ALICE).unwrap(); assert_noop!( Court::join_court(Origin::signed(ALICE)), Error::::JurorAlreadyExists @@ -114,6 +121,28 @@ fn on_dispute_stores_jurors_that_should_vote() { }); } +// Alice is the winner, Bob is tardy and Charlie is the loser +#[test] +fn on_resolution_awards_winners_and_slashes_losers() { + ExtBuilder::default().build().execute_with(|| { + setup_blocks(1..2); + Court::join_court(Origin::signed(ALICE)).unwrap(); + Court::join_court(Origin::signed(BOB)).unwrap(); + Court::join_court(Origin::signed(CHARLIE)).unwrap(); + Court::on_dispute(0, &[], &0, &ALICE).unwrap(); + Court::vote(Origin::signed(ALICE), 0, OutcomeReport::Scalar(1)).unwrap(); + Court::vote(Origin::signed(BOB), 0, OutcomeReport::Scalar(2)).unwrap(); + Court::vote(Origin::signed(CHARLIE), 0, OutcomeReport::Scalar(3)).unwrap(); + let _ = Court::on_resolution(&|_| 0, &[], &0, &DEFAULT_MARKET).unwrap(); + assert_eq!(Balances::free_balance(ALICE), 998 * BASE + 3 * BASE); + assert_eq!(Balances::reserved_balance_named(&RESERVE_ID, &ALICE), 2 * BASE); + assert_eq!(Balances::free_balance(BOB), 996 * BASE); + assert_eq!(Balances::reserved_balance_named(&RESERVE_ID, &BOB), 4 * BASE); + assert_eq!(Balances::free_balance(CHARLIE), 994 * BASE); + assert_eq!(Balances::reserved_balance_named(&RESERVE_ID, &CHARLIE), 3 * BASE); + }); +} + #[test] fn on_resolution_decides_market_outcome_based_on_the_majority() { ExtBuilder::default().build().execute_with(|| { @@ -121,7 +150,7 @@ fn on_resolution_decides_market_outcome_based_on_the_majority() { Court::join_court(Origin::signed(ALICE)).unwrap(); Court::join_court(Origin::signed(BOB)).unwrap(); Court::join_court(Origin::signed(CHARLIE)).unwrap(); - Court::on_dispute(BASE, &[], &0, &ALICE).unwrap(); + Court::on_dispute(0, &[], &0, &ALICE).unwrap(); Court::vote(Origin::signed(ALICE), 0, OutcomeReport::Scalar(1)).unwrap(); Court::vote(Origin::signed(BOB), 0, OutcomeReport::Scalar(1)).unwrap(); Court::vote(Origin::signed(CHARLIE), 0, OutcomeReport::Scalar(2)).unwrap(); @@ -137,7 +166,7 @@ fn on_resolution_sets_late_jurors_as_tardy() { Court::join_court(Origin::signed(ALICE)).unwrap(); Court::join_court(Origin::signed(BOB)).unwrap(); Court::vote(Origin::signed(ALICE), 0, OutcomeReport::Scalar(1)).unwrap(); - Court::on_dispute(BASE, &[], &0, &ALICE).unwrap(); + Court::on_dispute(0, &[], &0, &ALICE).unwrap(); let _ = Court::on_resolution(&|_| 0, &[], &0, &DEFAULT_MARKET).unwrap(); assert_eq!(Jurors::::get(ALICE).unwrap().status, JurorStatus::Ok); assert_eq!(Jurors::::get(BOB).unwrap().status, JurorStatus::Tardy); @@ -151,7 +180,7 @@ fn on_resolution_sets_jurors_that_voted_on_the_second_most_voted_outcome_as_tard Court::join_court(Origin::signed(ALICE)).unwrap(); Court::join_court(Origin::signed(BOB)).unwrap(); Court::join_court(Origin::signed(CHARLIE)).unwrap(); - Court::on_dispute(BASE, &[], &0, &ALICE).unwrap(); + Court::on_dispute(0, &[], &0, &ALICE).unwrap(); Court::vote(Origin::signed(ALICE), 0, OutcomeReport::Scalar(1)).unwrap(); Court::vote(Origin::signed(BOB), 0, OutcomeReport::Scalar(1)).unwrap(); Court::vote(Origin::signed(CHARLIE), 0, OutcomeReport::Scalar(2)).unwrap(); @@ -166,14 +195,15 @@ fn on_resolution_punishes_tardy_jurors_that_failed_to_vote_a_second_time() { setup_blocks(1..2); Court::join_court(Origin::signed(ALICE)).unwrap(); Court::join_court(Origin::signed(BOB)).unwrap(); - Court::set_juror_as_tardy(&BOB).unwrap(); + Court::set_stored_juror_as_tardy(&BOB).unwrap(); Court::vote(Origin::signed(ALICE), 0, OutcomeReport::Scalar(1)).unwrap(); - Court::on_dispute(BASE, &[], &0, &ALICE).unwrap(); + Court::on_dispute(0, &[], &0, &ALICE).unwrap(); let _ = Court::on_resolution(&|_| 0, &[], &0, &DEFAULT_MARKET).unwrap(); - let slash = 8000000000; - assert_eq!(Balances::free_balance(Court::treasury_account_id()), 1_000 * BASE + slash); - assert_eq!(Balances::free_balance(BOB), 1_000 * BASE - slash); - assert_eq!(Balances::reserved_balance(BOB), 0); + let join_court_stake = 40000000000; + let slash = join_court_stake / 5; + assert_eq!(Balances::free_balance(Court::treasury_account_id()), INITIAL_BALANCE + slash); + assert_eq!(Balances::free_balance(BOB), INITIAL_BALANCE - slash); + assert_eq!(Balances::reserved_balance_named(&RESERVE_ID, &BOB), 0); }); } @@ -184,7 +214,7 @@ fn on_resolution_removes_requested_jurors_and_votes() { Court::join_court(Origin::signed(ALICE)).unwrap(); Court::join_court(Origin::signed(BOB)).unwrap(); Court::join_court(Origin::signed(CHARLIE)).unwrap(); - Court::on_dispute(BASE, &[], &0, &ALICE).unwrap(); + Court::on_dispute(0, &[], &0, &ALICE).unwrap(); Court::vote(Origin::signed(ALICE), 0, OutcomeReport::Scalar(1)).unwrap(); Court::vote(Origin::signed(BOB), 0, OutcomeReport::Scalar(1)).unwrap(); Court::vote(Origin::signed(CHARLIE), 0, OutcomeReport::Scalar(2)).unwrap(); diff --git a/zrml/court/src/weights.rs b/zrml/court/src/weights.rs new file mode 100644 index 000000000..c753595e1 --- /dev/null +++ b/zrml/court/src/weights.rs @@ -0,0 +1,61 @@ +//! Autogenerated weights for zrml_court +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 +//! DATE: 2021-09-07, STEPS: `[10, ]`, REPEAT: 2000, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/zeitgeist +// benchmark +// --chain +// dev +// --execution +// wasm +// --extrinsic +// * +// --output +// ./zrml/court/src/weights.rs +// --pallet +// zrml-court +// --repeat +// 2000 +// --steps +// 10 +// --template +// ./misc/weight_template.hbs +// --wasm-execution +// compiled + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use core::marker::PhantomData; +use frame_support::{traits::Get, weights::Weight}; + +/// Trait containing the required functions for weight retrival within +/// zrml_court (automatically generated) +pub trait WeightInfoZeitgeist { + fn exit_court() -> Weight; + fn join_court() -> Weight; + fn vote() -> Weight; +} + +/// Weight functions for zrml_court (automatically generated) +pub struct WeightInfo(PhantomData); +impl WeightInfoZeitgeist for WeightInfo { + fn exit_court() -> Weight { + (30_157_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn join_court() -> Weight { + (32_872_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn vote() -> Weight { + (9_839_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/prediction-markets/Cargo.toml b/zrml/prediction-markets/Cargo.toml index f125b67df..57dd6d8e2 100644 --- a/zrml/prediction-markets/Cargo.toml +++ b/zrml/prediction-markets/Cargo.toml @@ -9,6 +9,7 @@ sp-runtime = { branch = "polkadot-v0.9.8", default-features = false, git = "http zeitgeist-primitives = { default-features = false, path = "../../primitives" } zrml-liquidity-mining = { default-features = false, path = "../liquidity-mining" } zrml-market-commons = { default-features = false, path = "../market-commons" } +zrml-court = { default-features = false, path = "../court" } zrml-simple-disputes = { default-features = false, path = "../simple-disputes" } # Mock @@ -16,6 +17,7 @@ zrml-simple-disputes = { default-features = false, path = "../simple-disputes" } orml-currencies = { branch = "master", git = "https://github.com/open-web3-stack/open-runtime-module-library", optional = true } orml-tokens = { branch = "master", git = "https://github.com/open-web3-stack/open-runtime-module-library", optional = true } pallet-balances = { branch = "polkadot-v0.9.8", git = "https://github.com/paritytech/substrate", optional = true } +pallet-randomness-collective-flip = { branch = "polkadot-v0.9.8", git = "https://github.com/paritytech/substrate", optional = true } pallet-timestamp = { branch = "polkadot-v0.9.8", git = "https://github.com/paritytech/substrate", optional = true } sp-api = { branch = "polkadot-v0.9.8", git = "https://github.com/paritytech/substrate", optional = true } sp-io = { branch = "polkadot-v0.9.8", git = "https://github.com/paritytech/substrate", optional = true } @@ -31,6 +33,7 @@ mock = [ "orml-currencies", "orml-tokens", "pallet-balances", + "pallet-randomness-collective-flip", "pallet-timestamp", "sp-api", "sp-io", @@ -51,6 +54,7 @@ std = [ 'sp-arithmetic/std', 'sp-runtime/std', 'zeitgeist-primitives/std', + 'zrml-court/std', 'zrml-liquidity-mining/std', 'zrml-market-commons/std', 'zrml-simple-disputes/std', diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 6a779fca3..b39cf678f 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -853,6 +853,16 @@ mod pallet { type ApprovalOrigin: EnsureOrigin<::Origin>; + /// See [`CourtPalletApi`]. + type Court: zrml_court::CourtPalletApi< + AccountId = Self::AccountId, + Balance = BalanceOf, + BlockNumber = Self::BlockNumber, + MarketId = MarketIdOf, + Moment = MomentOf, + Origin = Self::Origin, + >; + /// The base amount of currency that must be bonded in order to create a dispute. type DisputeBond: Get>; @@ -925,7 +935,7 @@ mod pallet { /// guaranteeing that it will resolve as anything but `Invalid`. type ValidityBond: Get>; - ///Weights generated by benchmarks + /// Weights generated by benchmarks type WeightInfo: WeightInfoZeitgeist; } @@ -1303,13 +1313,10 @@ mod pallet { ) -> Result { let disputes = Disputes::::get(market_id); let resolved_outcome = match market.mdm { - MarketDisputeMechanism::Authorized(_) => T::SimpleDisputes::on_resolution( - &default_dispute_bond::, - &disputes, - market_id, - market, - )?, - MarketDisputeMechanism::Court => T::SimpleDisputes::on_resolution( + MarketDisputeMechanism::Authorized(_) => { + return Err(DispatchError::Other("Authorized dispute is not yet implemented")); + } + MarketDisputeMechanism::Court => T::Court::on_resolution( &default_dispute_bond::, &disputes, market_id, diff --git a/zrml/prediction-markets/src/mock.rs b/zrml/prediction-markets/src/mock.rs index 355a4ead3..13c44b79f 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -19,10 +19,11 @@ use sp_runtime::{ }; use zeitgeist_primitives::{ constants::{ - BlockHashCount, DustAccountTest, ExitFee, GetNativeCurrencyId, LiquidityMiningPalletId, - MaxAssets, MaxCategories, MaxDisputes, MaxInRatio, MaxOutRatio, MaxReserves, - MaxTotalWeight, MaxWeight, MinCategories, MinLiquidity, MinWeight, PmPalletId, - SimpleDisputesPalletId, SwapsPalletId, BASE, + BlockHashCount, CourtCaseDuration, CourtPalletId, DustAccountTest, ExitFee, + GetNativeCurrencyId, LiquidityMiningPalletId, MaxAssets, MaxCategories, MaxDisputes, + MaxInRatio, MaxOutRatio, MaxReserves, MaxTotalWeight, MaxWeight, MinCategories, + MinLiquidity, MinWeight, PmPalletId, SimpleDisputesPalletId, StakeWeight, SwapsPalletId, + TreasuryPalletId, BASE, }, types::{ AccountIdTest, Amount, Asset, Balance, BlockNumber, BlockTest, CurrencyId, Hash, Index, @@ -37,6 +38,7 @@ pub const DAVE: AccountIdTest = 3; pub const EVE: AccountIdTest = 4; pub const FRED: AccountIdTest = 5; pub const SUDO: AccountIdTest = 69; +pub const TREASURY: AccountIdTest = 99; pub type Block = BlockTest; pub type UncheckedExtrinsic = UncheckedExtrinsicTest; @@ -46,7 +48,6 @@ pub type AdaptedBasicCurrency = ord_parameter_types! { pub const Sudo: AccountIdTest = SUDO; } - parameter_types! { pub const AdvisoryBond: Balance = 50; pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); @@ -76,10 +77,12 @@ construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { Balances: pallet_balances::{Call, Config, Event, Pallet, Storage}, + Court: zrml_court::{Event, Pallet, Storage}, Currency: orml_currencies::{Call, Event, Pallet, Storage}, LiquidityMining: zrml_liquidity_mining::{Config, Event, Pallet}, MarketCommons: zrml_market_commons::{Pallet, Storage}, PredictionMarkets: prediction_markets::{Event, Pallet, Storage}, + RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage}, SimpleDisputes: zrml_simple_disputes::{Event, Pallet, Storage}, Swaps: zrml_swaps::{Call, Event, Pallet}, System: frame_system::{Config, Event, Pallet, Storage}, @@ -91,6 +94,7 @@ construct_runtime!( impl crate::Config for Runtime { type AdvisoryBond = AdvisoryBond; type ApprovalOrigin = EnsureSignedBy; + type Court = Court; type DisputeBond = DisputeBond; type DisputeFactor = DisputeFactor; type DisputePeriod = DisputePeriod; @@ -168,6 +172,8 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); } +impl pallet_randomness_collective_flip::Config for Runtime {} + impl pallet_timestamp::Config for Runtime { type MinimumPeriod = MinimumPeriod; type Moment = Moment; @@ -175,6 +181,17 @@ impl pallet_timestamp::Config for Runtime { type WeightInfo = (); } +impl zrml_court::Config for Runtime { + type CourtCaseDuration = CourtCaseDuration; + type Event = Event; + type MarketCommons = MarketCommons; + type PalletId = CourtPalletId; + type Random = RandomnessCollectiveFlip; + type StakeWeight = StakeWeight; + type TreasuryPalletId = TreasuryPalletId; + type WeightInfo = zrml_court::weights::WeightInfo; +} + impl zrml_liquidity_mining::Config for Runtime { type Currency = Balances; type Event = Event; diff --git a/zrml/simple-disputes/src/lib.rs b/zrml/simple-disputes/src/lib.rs index 7b9ce6c2c..1ef018e30 100644 --- a/zrml/simple-disputes/src/lib.rs +++ b/zrml/simple-disputes/src/lib.rs @@ -6,10 +6,14 @@ extern crate alloc; +mod simple_disputes_pallet_api; + pub use pallet::*; +pub use simple_disputes_pallet_api::SimpleDisputesPalletApi; #[frame_support::pallet] mod pallet { + use crate::SimpleDisputesPalletApi; use alloc::vec::Vec; use core::marker::PhantomData; use frame_support::{ @@ -214,6 +218,8 @@ mod pallet { } } + impl SimpleDisputesPalletApi for Pallet where T: Config {} + #[pallet::pallet] pub struct Pallet(PhantomData); } diff --git a/zrml/simple-disputes/src/simple_disputes_pallet_api.rs b/zrml/simple-disputes/src/simple_disputes_pallet_api.rs new file mode 100644 index 000000000..e17631440 --- /dev/null +++ b/zrml/simple-disputes/src/simple_disputes_pallet_api.rs @@ -0,0 +1,3 @@ +use zeitgeist_primitives::traits::DisputeApi; + +pub trait SimpleDisputesPalletApi: DisputeApi {}