diff --git a/primitives/src/constants.rs b/primitives/src/constants.rs index dbcee8ed9..a733a2566 100644 --- a/primitives/src/constants.rs +++ b/primitives/src/constants.rs @@ -26,6 +26,7 @@ parameter_types! { // Court parameter_types! { pub const CourtCaseDuration: u64 = BLOCKS_PER_DAY; + pub const CourtPalletId: PalletId = PalletId(*b"zge/cout"); pub const StakeWeight: u128 = 2 * BASE; } @@ -36,7 +37,7 @@ parameter_types! { // Liquidity Mining parameters parameter_types! { - pub const LiquidityMiningPalletId: PalletId = PalletId(*b"zrml/lmg"); + pub const LiquidityMiningPalletId: PalletId = PalletId(*b"zge/lymg"); } // Prediction Market parameters diff --git a/primitives/src/outcome_report.rs b/primitives/src/outcome_report.rs index f261621a8..1a8decb8f 100644 --- a/primitives/src/outcome_report.rs +++ b/primitives/src/outcome_report.rs @@ -1,7 +1,16 @@ use crate::types::CategoryIndex; /// The reported outcome of a market -#[derive(Clone, Debug, Eq, PartialEq, parity_scale_codec::Decode, parity_scale_codec::Encode)] +#[derive( + Clone, + Debug, + Eq, + Ord, + PartialEq, + PartialOrd, + parity_scale_codec::Decode, + parity_scale_codec::Encode, +)] pub enum OutcomeReport { Categorical(CategoryIndex), Scalar(u128), diff --git a/primitives/src/traits/dispute_api.rs b/primitives/src/traits/dispute_api.rs index c5fed5f6c..9383c8bd6 100644 --- a/primitives/src/traits/dispute_api.rs +++ b/primitives/src/traits/dispute_api.rs @@ -13,13 +13,15 @@ pub trait DisputeApi { /// Disputes a reported outcome. fn on_dispute( + bond: Self::Balance, disputes: &[MarketDispute], - market_id: Self::MarketId, + market_id: &Self::MarketId, + who: &Self::AccountId, ) -> DispatchResult; /// Manages markets resolutions moving all reported markets to resolved. fn on_resolution( - dispute_bound: &D, + bound: &D, disputes: &[MarketDispute], market_id: &Self::MarketId, market: &Market, diff --git a/zrml/court/src/lib.rs b/zrml/court/src/lib.rs index 96cfe8c57..9f1952f45 100644 --- a/zrml/court/src/lib.rs +++ b/zrml/court/src/lib.rs @@ -16,18 +16,26 @@ pub use pallet::*; #[frame_support::pallet] mod pallet { use crate::{Juror, JurorStatus}; + use alloc::collections::BTreeMap; use arrayvec::ArrayVec; use core::marker::PhantomData; use frame_support::{ dispatch::DispatchResult, pallet_prelude::{StorageDoubleMap, StorageMap, StorageValue, ValueQuery}, - traits::{Currency, Get, Hooks, IsType, Randomness, ReservableCurrency}, - Blake2_128Concat, + 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::Saturating, ArithmeticError, DispatchError, SaturatedConversion}; + use sp_runtime::{ + traits::{AccountIdConversion, Saturating}, + ArithmeticError, DispatchError, SaturatedConversion, + }; use zeitgeist_primitives::{ + constants::CourtPalletId, traits::DisputeApi, types::{Market, MarketDispute, OutcomeReport}, }; @@ -36,9 +44,13 @@ mod pallet { // 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; + // Divides the reserved juror balance to calculate the slash amount. `5` here + // means that the output value will be 20% of the dividend. + const TARDY_PUNISHMENT_DIVISOR: u8 = 5; pub(crate) type BalanceOf = as Currency<::AccountId>>::Balance; @@ -55,7 +67,7 @@ mod pallet { let who = ensure_signed(origin)?; let juror = Self::juror(&who)?; Jurors::::remove(&who); - CurrencyOf::::unreserve(&who, juror.staked); + CurrencyOf::::unreserve_named(&RESERVE_ID, &who, juror.staked); Ok(()) } @@ -70,7 +82,7 @@ mod pallet { let jurors_num = Jurors::::iter().count(); 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(&who, stake)?; + CurrencyOf::::reserve_named(&RESERVE_ID, &who, stake)?; Jurors::::insert(&who, Juror { staked: stake, status: JurorStatus::Ok }); Ok(()) } @@ -88,8 +100,8 @@ mod pallet { return Err(Error::::OnlyJurorsCanVote.into()); } Votes::::insert( - who, market_id, + who, (>::block_number(), outcome), ); Ok(()) @@ -110,11 +122,17 @@ mod pallet { BlockNumber = Self::BlockNumber, >; + /// Identifier of this pallet + type PalletId: Get; + /// Randomness source type Random: Randomness; /// Weight used to calculate the necessary staking amount to become a juror type StakeWeight: Get>; + + /// Slashed funds are send to the treasury + type TreasuryId: Get; } #[pallet::error] @@ -123,6 +141,8 @@ mod pallet { JurorAlreadyExists, /// An account id does not exist on the jurors storage. JurorDoesNotExists, + /// No-one voted on an outcome to resolve a market + NoVotes, /// Forbids voting of unknown accounts OnlyJurorsCanVote, } @@ -174,6 +194,17 @@ mod pallet { StdRng::from_seed(seed) } + pub(crate) fn set_juror_as_tardy(account_id: &T::AccountId) -> DispatchResult { + Self::mutate_juror(account_id, |juror| { + juror.status = JurorStatus::Tardy; + Ok(()) + }) + } + + pub(crate) fn treasury_account_id() -> T::AccountId { + T::TreasuryId::get().into_account() + } + // No-one can stake more than BalanceOf::::max(), therefore, this function saturates // arithmetic operations. fn current_required_stake(jurors_num: usize) -> BalanceOf { @@ -181,6 +212,11 @@ mod pallet { T::StakeWeight::get().saturating_mul(jurors_len) } + // Retrieves a juror from the storage + fn juror(account_id: &T::AccountId) -> Result>, DispatchError> { + 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 @@ -190,9 +226,121 @@ mod pallet { 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)], + votes: &[(T::AccountId, (T::BlockNumber, 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)?; + } + } 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)?; + } + } + } + + Ok(()) + } + // Retrieves a juror from the storage - fn juror(account_id: &T::AccountId) -> Result>, DispatchError> { - Jurors::::get(account_id).ok_or_else(|| Error::::JurorDoesNotExists.into()) + fn mutate_juror(account_id: &T::AccountId, mut cb: F) -> DispatchResult + where + F: FnMut(&mut Juror>) -> DispatchResult, + { + Jurors::::try_mutate(account_id, |opt| { + if let Some(el) = opt { + cb(el)?; + } else { + return Err(Error::::JurorDoesNotExists.into()); + } + Ok(()) + }) + } + + // 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, + votes: &[(T::AccountId, (T::BlockNumber, 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)?; + } + } + } + Ok(()) + } + + // For market resolution based on the votes of a market + fn two_best_outcomes( + votes: &[(T::AccountId, (T::BlockNumber, OutcomeReport))], + ) -> Result<(OutcomeReport, Option), DispatchError> { + let mut scores = BTreeMap::::new(); + + for (_, (_, outcome_report)) in votes { + if let Some(el) = scores.get_mut(outcome_report) { + *el = el.saturating_add(1); + } else { + scores.insert(outcome_report.clone(), 1); + } + } + + let mut best_score; + let mut iter = scores.iter(); + + if let Some(first) = iter.next() { + best_score = first; + } else { + return Err(Error::::NoVotes.into()); + } + + let mut second_best_score = if let Some(second) = iter.next() { + if second.1 > best_score.1 { + best_score = second; + best_score + } else { + second + } + } else { + return Ok((best_score.0.clone(), None)); + }; + + for el in iter { + if el.1 > best_score.1 { + best_score = el; + second_best_score = best_score; + } else if el.1 > second_best_score.1 { + second_best_score = el; + } + } + + Ok((best_score.0.clone(), Some(second_best_score.0.clone()))) } } @@ -208,9 +356,12 @@ mod pallet { type Origin = T::Origin; fn on_dispute( + bond: Self::Balance, disputes: &[MarketDispute], - market_id: Self::MarketId, + market_id: &Self::MarketId, + who: &Self::AccountId, ) -> DispatchResult { + CurrencyOf::::reserve(who, bond)?; let jurors: Vec<_> = Jurors::::iter().collect(); let necessary_jurors_num = Self::necessary_jurors_num(disputes); let mut rng = Self::rng(); @@ -218,7 +369,7 @@ mod pallet { let curr_block_num = >::block_number(); let block_limit = curr_block_num.saturating_add(T::CourtCaseDuration::get()); for (ai, _) in random_jurors { - RequestedJurors::::insert(ai, market_id, block_limit); + RequestedJurors::::insert(market_id, ai, block_limit); } Ok(()) } @@ -226,13 +377,20 @@ mod pallet { fn on_resolution( _: &D, _: &[MarketDispute], - _: &Self::MarketId, + market_id: &Self::MarketId, _: &Market, ) -> Result where D: Fn(usize) -> Self::Balance, { - Ok(OutcomeReport::Scalar(Default::default())) + let requested_jurors: Vec<_> = RequestedJurors::::iter_prefix(market_id).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)?; + Votes::::remove_prefix(market_id, None); + RequestedJurors::::remove_prefix(market_id, None); + Ok(first) } } @@ -249,20 +407,22 @@ mod pallet { pub type RequestedJurors = StorageDoubleMap< _, Blake2_128Concat, - T::AccountId, - Blake2_128Concat, MarketIdOf, + Blake2_128Concat, + T::AccountId, T::BlockNumber, >; /// Votes of market outcomes for disputes + /// + /// Stores the vote block number and the submitted outcome. #[pallet::storage] pub type Votes = StorageDoubleMap< _, Blake2_128Concat, - T::AccountId, - Blake2_128Concat, MarketIdOf, + Blake2_128Concat, + T::AccountId, (T::BlockNumber, OutcomeReport), >; } diff --git a/zrml/court/src/mock.rs b/zrml/court/src/mock.rs index 05edc2417..03a0398d2 100644 --- a/zrml/court/src/mock.rs +++ b/zrml/court/src/mock.rs @@ -1,13 +1,16 @@ #![cfg(test)] use crate as zrml_court; -use frame_support::{construct_runtime, parameter_types, PalletId}; +use frame_support::construct_runtime; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, }; use zeitgeist_primitives::{ - constants::{BlockHashCount, CourtCaseDuration, MaxReserves, MinimumPeriod, StakeWeight, BASE}, + constants::{ + BlockHashCount, CourtCaseDuration, CourtPalletId, MaxReserves, MinimumPeriod, StakeWeight, + TreasuryPalletId, BASE, + }, types::{ AccountIdTest, Balance, BlockNumber, BlockTest, Hash, Index, MarketId, Moment, UncheckedExtrinsicTest, @@ -16,14 +19,11 @@ use zeitgeist_primitives::{ pub const ALICE: AccountIdTest = 0; pub const BOB: AccountIdTest = 1; +pub const CHARLIE: AccountIdTest = 2; type Block = BlockTest; type UncheckedExtrinsic = UncheckedExtrinsicTest; -parameter_types! { - pub const LmPalletId: PalletId = PalletId(*b"test/lmg"); -} - construct_runtime!( pub enum Runtime where @@ -44,8 +44,10 @@ impl crate::Config for Runtime { type CourtCaseDuration = CourtCaseDuration; type Event = (); type MarketCommons = MarketCommons; + type PalletId = CourtPalletId; type Random = RandomnessCollectiveFlip; type StakeWeight = StakeWeight; + type TreasuryId = TreasuryPalletId; } impl frame_system::Config for Runtime { @@ -107,7 +109,14 @@ pub struct ExtBuilder { impl Default for ExtBuilder { fn default() -> Self { - Self { balances: vec![(ALICE, 1_000 * BASE), (BOB, 1_000 * BASE)] } + Self { + balances: vec![ + (ALICE, 1_000 * BASE), + (BOB, 1_000 * BASE), + (CHARLIE, 1_000 * BASE), + (Court::treasury_account_id(), 1_000 * BASE), + ], + } } } diff --git a/zrml/court/src/tests.rs b/zrml/court/src/tests.rs index 0b6a30376..5faa3df40 100644 --- a/zrml/court/src/tests.rs +++ b/zrml/court/src/tests.rs @@ -3,14 +3,35 @@ use crate::{ mock::{ Balances, Court, ExtBuilder, Origin, RandomnessCollectiveFlip, Runtime, System, ALICE, BOB, + CHARLIE, }, Error, Juror, JurorStatus, Jurors, RequestedJurors, Votes, }; use core::ops::Range; use frame_support::{assert_noop, assert_ok, traits::Hooks}; use sp_runtime::traits::Header; -use zeitgeist_primitives::{constants::BASE, traits::DisputeApi, types::OutcomeReport}; +use zeitgeist_primitives::{ + constants::BASE, + traits::DisputeApi, + types::{ + Market, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, MarketType, + OutcomeReport, + }, +}; +const DEFAULT_MARKET: Market = Market { + creation: MarketCreation::Permissionless, + creator_fee: 0, + creator: 0, + market_type: MarketType::Scalar(0..=100), + mdm: MarketDisputeMechanism::SimpleDisputes, + metadata: vec![], + oracle: 0, + period: MarketPeriod::Block(0..100), + report: None, + 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 }), @@ -84,7 +105,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)); - let _ = Court::on_dispute(&[], 0); + Court::on_dispute(BASE, &[], &0, &ALICE).unwrap(); assert_noop!( Court::join_court(Origin::signed(ALICE)), Error::::JurorAlreadyExists @@ -93,6 +114,86 @@ fn on_dispute_stores_jurors_that_should_vote() { }); } +#[test] +fn on_resolution_decides_market_outcome_based_on_the_majority() { + 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(BASE, &[], &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(); + let outcome = Court::on_resolution(&|_| 0, &[], &0, &DEFAULT_MARKET).unwrap(); + assert_eq!(outcome, OutcomeReport::Scalar(1)) + }); +} + +#[test] +fn on_resolution_sets_late_jurors_as_tardy() { + ExtBuilder::default().build().execute_with(|| { + setup_blocks(1..2); + 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(); + 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); + }); +} + +#[test] +fn on_resolution_sets_jurors_that_voted_on_the_second_most_voted_outcome_as_tardy() { + 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(BASE, &[], &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(); + let _ = Court::on_resolution(&|_| 0, &[], &0, &DEFAULT_MARKET).unwrap(); + assert_eq!(Jurors::::get(CHARLIE).unwrap().status, JurorStatus::Tardy); + }); +} + +#[test] +fn on_resolution_punishes_tardy_jurors_that_failed_to_vote_a_second_time() { + ExtBuilder::default().build().execute_with(|| { + 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::vote(Origin::signed(ALICE), 0, OutcomeReport::Scalar(1)).unwrap(); + Court::on_dispute(BASE, &[], &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); + }); +} + +#[test] +fn on_resolution_removes_requested_jurors_and_votes() { + 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(BASE, &[], &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(); + let _ = Court::on_resolution(&|_| 0, &[], &0, &DEFAULT_MARKET).unwrap(); + assert_eq!(RequestedJurors::::iter().count(), 0); + assert_eq!(Votes::::iter().count(), 0); + }); +} + #[test] fn random_jurors_returns_an_unique_different_subset_of_jurors() { ExtBuilder::default().build().execute_with(|| { @@ -115,7 +216,6 @@ fn random_jurors_returns_an_unique_different_subset_of_jurors() { if let Some(juror) = iter.next() { at_least_one_set_is_different = random_jurors.iter().all(|el| el != juror); } else { - at_least_one_set_is_different = false; continue; } for juror in iter { @@ -126,6 +226,7 @@ fn random_jurors_returns_an_unique_different_subset_of_jurors() { break; } } + assert_eq!(at_least_one_set_is_different, true); }); } diff --git a/zrml/liquidity-mining/src/mock.rs b/zrml/liquidity-mining/src/mock.rs index 91073de16..2505725ce 100644 --- a/zrml/liquidity-mining/src/mock.rs +++ b/zrml/liquidity-mining/src/mock.rs @@ -1,13 +1,16 @@ #![cfg(test)] use crate as zrml_liquidity_mining; -use frame_support::{construct_runtime, parameter_types, traits::GenesisBuild, PalletId}; +use frame_support::{construct_runtime, traits::GenesisBuild}; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, }; use zeitgeist_primitives::{ - constants::{BlockHashCount, ExistentialDeposit, MaxLocks, MaxReserves, MinimumPeriod, BASE}, + constants::{ + BlockHashCount, ExistentialDeposit, LiquidityMiningPalletId, MaxLocks, MaxReserves, + MinimumPeriod, BASE, + }, types::{ AccountIdTest, Balance, BlockNumber, BlockTest, Hash, Index, MarketId, Moment, UncheckedExtrinsicTest, @@ -20,10 +23,6 @@ pub const BOB: AccountIdTest = 1; type Block = BlockTest; type UncheckedExtrinsic = UncheckedExtrinsicTest; -parameter_types! { - pub const LmPalletId: PalletId = PalletId(*b"test/lmg"); -} - construct_runtime!( pub enum Runtime where @@ -44,7 +43,7 @@ impl crate::Config for Runtime { type Event = (); type MarketCommons = MarketCommons; type MarketId = MarketId; - type PalletId = LmPalletId; + type PalletId = LiquidityMiningPalletId; type WeightInfo = crate::weights::WeightInfo; } diff --git a/zrml/market-commons/src/lib.rs b/zrml/market-commons/src/lib.rs index b33ee904d..ad5521bc1 100644 --- a/zrml/market-commons/src/lib.rs +++ b/zrml/market-commons/src/lib.rs @@ -19,7 +19,7 @@ mod pallet { use frame_support::{ dispatch::DispatchResult, pallet_prelude::{StorageMap, StorageValue, ValueQuery}, - traits::{Hooks, ReservableCurrency, Time}, + traits::{Hooks, NamedReservableCurrency, Time}, Blake2_128Concat, Parameter, }; use sp_runtime::{ @@ -36,7 +36,9 @@ mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// Native token - type Currency: ReservableCurrency; + // + // Reserve identifiers can be pallet ids or any other sequence of bytes. + type Currency: NamedReservableCurrency; /// The identifier of individual markets. type MarketId: AtLeast32Bit diff --git a/zrml/market-commons/src/market_commons_pallet_api.rs b/zrml/market-commons/src/market_commons_pallet_api.rs index eaaa4c3d7..201af76e4 100644 --- a/zrml/market-commons/src/market_commons_pallet_api.rs +++ b/zrml/market-commons/src/market_commons_pallet_api.rs @@ -1,7 +1,7 @@ use frame_support::{ dispatch::{DispatchError, DispatchResult}, pallet_prelude::{MaybeSerializeDeserialize, Member}, - traits::ReservableCurrency, + traits::NamedReservableCurrency, Parameter, }; use sp_runtime::traits::AtLeast32Bit; @@ -11,7 +11,7 @@ use zeitgeist_primitives::types::{Market, PoolId}; pub trait MarketCommonsPalletApi { type AccountId; type BlockNumber: AtLeast32Bit; - type Currency: ReservableCurrency; + type Currency: NamedReservableCurrency; type MarketId: AtLeast32Bit + Copy + Default + MaybeSerializeDeserialize + Member + Parameter; type Moment: AtLeast32Bit + Copy + Default + Parameter; diff --git a/zrml/orderbook-v1/src/mock.rs b/zrml/orderbook-v1/src/mock.rs index 4eb1bda1c..9c2eb3d7e 100644 --- a/zrml/orderbook-v1/src/mock.rs +++ b/zrml/orderbook-v1/src/mock.rs @@ -25,7 +25,6 @@ pub type UncheckedExtrinsic = UncheckedExtrinsicTest; parameter_types! { pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); pub const ExistentialDeposit: Balance = 1; - pub const SharesPalletId: PalletId = PalletId(*b"test/sha"); pub DustAccount: AccountIdTest = PalletId(*b"orml/dst").into_account(); } diff --git a/zrml/prediction-markets/fuzz/pm_full_workflow.rs b/zrml/prediction-markets/fuzz/pm_full_workflow.rs index 2ba971588..1830b9156 100644 --- a/zrml/prediction-markets/fuzz/pm_full_workflow.rs +++ b/zrml/prediction-markets/fuzz/pm_full_workflow.rs @@ -50,8 +50,10 @@ fuzz_target!(|data: Data| { let dispute_market_id = data.dispute_market_id.into(); let _ = SimpleDisputes::on_dispute( + data.dispute_bond.into(), &zrml_prediction_markets::Disputes::::get(&dispute_market_id), - dispute_market_id, + &dispute_market_id, + &data.dispute_origin.into(), ); let _ = PredictionMarkets::on_initialize(5); @@ -85,6 +87,7 @@ struct Data { report_market_id: u8, report_outcome: u128, + dispute_bond: u8, dispute_origin: u8, dispute_market_id: u8, dispute_outcome: u128, diff --git a/zrml/prediction-markets/src/benchmarks.rs b/zrml/prediction-markets/src/benchmarks.rs index 7f4e9cb75..62d95668e 100644 --- a/zrml/prediction-markets/src/benchmarks.rs +++ b/zrml/prediction-markets/src/benchmarks.rs @@ -211,7 +211,7 @@ benchmarks! { for i in 0..c.min(T::MaxDisputes::get() as u32) { let origin = caller.clone(); let disputes = crate::Disputes::::get(&marketid); - let _ = T::SimpleDisputes::on_dispute(&disputes, marketid)?; + let _ = T::SimpleDisputes::on_dispute(Default::default(), &disputes, &marketid, &origin)?; } let approval_origin = T::ApprovalOrigin::successful_origin(); @@ -315,7 +315,7 @@ benchmarks! { }: { let origin = caller.clone(); let disputes = crate::Disputes::::get(&marketid); - let _ = T::SimpleDisputes::on_dispute(&disputes, marketid)?; + let _ = T::SimpleDisputes::on_dispute(Default::default(), &disputes, &marketid, &origin)?; } internal_resolve_categorical_reported { @@ -331,7 +331,7 @@ benchmarks! { }: { let market = T::MarketCommons::market(&marketid)?; let disputes = crate::Disputes::::get(&marketid); - T::SimpleDisputes::on_resolution(&default_dispute_bound::, &disputes, &marketid, &market)? + T::SimpleDisputes::on_resolution(&default_dispute_bond::, &disputes, &marketid, &market)? } internal_resolve_categorical_disputed { @@ -350,12 +350,12 @@ benchmarks! { for i in 0..c.min(d) { let origin = caller.clone(); let disputes = crate::Disputes::::get(&marketid); - let _ = T::SimpleDisputes::on_dispute(&disputes, marketid)?; + let _ = T::SimpleDisputes::on_dispute(Default::default(), &disputes, &marketid, &origin)?; } }: { let market = T::MarketCommons::market(&marketid)?; let disputes = crate::Disputes::::get(&marketid); - T::SimpleDisputes::on_resolution(&default_dispute_bound::, &disputes, &marketid, &market)? + T::SimpleDisputes::on_resolution(&default_dispute_bond::, &disputes, &marketid, &market)? } internal_resolve_scalar_reported { @@ -365,7 +365,7 @@ benchmarks! { }: { let market = T::MarketCommons::market(&marketid)?; let disputes = crate::Disputes::::get(&marketid); - T::SimpleDisputes::on_resolution(&default_dispute_bound::, &disputes, &marketid, &market)? + T::SimpleDisputes::on_resolution(&default_dispute_bond::, &disputes, &marketid, &market)? } internal_resolve_scalar_disputed { @@ -378,12 +378,12 @@ benchmarks! { for i in 0..d { let disputes = crate::Disputes::::get(&marketid); let origin = caller.clone(); - let _ = T::SimpleDisputes::on_dispute(&disputes, marketid)?; + let _ = T::SimpleDisputes::on_dispute(Default::default(), &disputes, &marketid, &origin)?; } }: { let market = T::MarketCommons::market(&marketid)?; let disputes = crate::Disputes::::get(&marketid); - T::SimpleDisputes::on_resolution(&default_dispute_bound::, &disputes, &marketid, &market)? + T::SimpleDisputes::on_resolution(&default_dispute_bond::, &disputes, &marketid, &market)? } // This benchmark measures the cost of fn `on_initialize` minus the resolution. diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index b4e763f72..9681ed783 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -295,8 +295,12 @@ mod pallet { let num_disputes: u32 = disputes.len().saturated_into(); let outcome_clone = outcome.clone(); Self::validate_dispute(&disputes, &market, num_disputes, &outcome)?; - CurrencyOf::::reserve(&who, default_dispute_bound::(disputes.len()))?; - T::SimpleDisputes::on_dispute(&disputes, market_id)?; + T::SimpleDisputes::on_dispute( + default_dispute_bond::(disputes.len()), + &disputes, + &market_id, + &who, + )?; Self::remove_last_dispute_from_market_ids_per_dispute_block(&disputes, &market_id)?; Self::set_market_as_disputed(&market, &market_id)?; >::mutate(market_id, |disputes| { @@ -1292,19 +1296,19 @@ mod pallet { let disputes = Disputes::::get(market_id); let resolved_outcome = match market.mdm { MarketDisputeMechanism::Authorized(_) => T::SimpleDisputes::on_resolution( - &default_dispute_bound::, + &default_dispute_bond::, &disputes, market_id, market, )?, MarketDisputeMechanism::Court => T::SimpleDisputes::on_resolution( - &default_dispute_bound::, + &default_dispute_bond::, &disputes, market_id, market, )?, MarketDisputeMechanism::SimpleDisputes => T::SimpleDisputes::on_resolution( - &default_dispute_bound::, + &default_dispute_bond::, &disputes, market_id, market, @@ -1431,7 +1435,7 @@ mod pallet { } // No-one can bound more than BalanceOf, therefore, this functions saturates - pub fn default_dispute_bound(n: usize) -> BalanceOf + pub fn default_dispute_bond(n: usize) -> BalanceOf where T: Config, { diff --git a/zrml/prediction-markets/src/mock.rs b/zrml/prediction-markets/src/mock.rs index 21a52b4ea..ae4985825 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -16,9 +16,9 @@ use sp_runtime::{ }; use zeitgeist_primitives::{ constants::{ - BlockHashCount, ExitFee, MaxAssets, MaxCategories, MaxDisputes, MaxInRatio, MaxOutRatio, - MaxReserves, MaxTotalWeight, MaxWeight, MinCategories, MinLiquidity, MinWeight, PmPalletId, - SimpleDisputesPalletId, SwapsPalletId, BASE, + BlockHashCount, ExitFee, LiquidityMiningPalletId, MaxAssets, MaxCategories, MaxDisputes, + MaxInRatio, MaxOutRatio, MaxReserves, MaxTotalWeight, MaxWeight, MinCategories, + MinLiquidity, MinWeight, PmPalletId, SimpleDisputesPalletId, SwapsPalletId, BASE, }, types::{ AccountIdTest, Amount, Asset, Balance, BlockNumber, BlockTest, CurrencyId, Hash, Index, @@ -51,7 +51,6 @@ parameter_types! { pub const DisputePeriod: BlockNumber = 10; pub const ExistentialDeposit: u64 = 1; pub const GetNativeCurrencyId: Asset = Asset::Ztg; - pub const LiquidityMiningPalletId: PalletId = PalletId(*b"test/lmp"); pub const MaximumBlockLength: u32 = 2 * 1024; pub const MaximumBlockWeight: Weight = 1024; pub const MinimumPeriod: u64 = 0; diff --git a/zrml/simple-disputes/src/lib.rs b/zrml/simple-disputes/src/lib.rs index 982937a08..90c8de7aa 100644 --- a/zrml/simple-disputes/src/lib.rs +++ b/zrml/simple-disputes/src/lib.rs @@ -108,14 +108,17 @@ mod pallet { type Origin = T::Origin; fn on_dispute( + bond: Self::Balance, _: &[MarketDispute], - _: Self::MarketId, + _: &Self::MarketId, + who: &Self::AccountId, ) -> DispatchResult { + CurrencyOf::::reserve(who, bond)?; Ok(()) } fn on_resolution( - dispute_bound: &D, + bond: &D, disputes: &[MarketDispute], _: &Self::MarketId, market: &Market>, @@ -181,14 +184,14 @@ mod pallet { } for (i, dispute) in disputes.iter().enumerate() { - let actual_dispute_bond = dispute_bound(i); + let actual_bond = bond(i); if dispute.outcome == resolved_outcome { - CurrencyOf::::unreserve(&dispute.by, actual_dispute_bond); + CurrencyOf::::unreserve(&dispute.by, actual_bond); correct_reporters.push(dispute.by.clone()); } else { let (imbalance, _) = - CurrencyOf::::slash_reserved(&dispute.by, actual_dispute_bond); + CurrencyOf::::slash_reserved(&dispute.by, actual_bond); overall_imbalance.subsume(imbalance); } }