Skip to content

Commit

Permalink
Seventh iteration of the Court pallet
Browse files Browse the repository at this point in the history
  • Loading branch information
c410-f3r committed Aug 1, 2021
1 parent 3dd7c5c commit 6c9bb00
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 25 deletions.
11 changes: 10 additions & 1 deletion primitives/src/outcome_report.rs
Original file line number Diff line number Diff line change
@@ -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),
Expand Down
2 changes: 1 addition & 1 deletion primitives/src/traits/dispute_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub trait DisputeApi {
/// Disputes a reported outcome.
fn on_dispute(
disputes: &[MarketDispute<Self::AccountId, Self::BlockNumber>],
market_id: Self::MarketId,
market_id: &Self::MarketId,
) -> DispatchResult;

/// Manages markets resolutions moving all reported markets to resolved.
Expand Down
173 changes: 161 additions & 12 deletions zrml/court/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ 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},
traits::{BalanceStatus, Currency, Get, Hooks, IsType, Randomness, ReservableCurrency},
Blake2_128Concat,
};
use frame_system::{ensure_signed, pallet_prelude::OriginFor};
Expand Down Expand Up @@ -85,8 +86,8 @@ mod pallet {
return Err(Error::<T>::OnlyJurorsCanVote.into());
}
Votes::<T>::insert(
who,
market_id,
who,
(<frame_system::Pallet<T>>::block_number(), outcome),
);
Ok(())
Expand All @@ -112,6 +113,9 @@ mod pallet {

/// Weight used to calculate the necessary staking amount to become a juror
type StakeWeight: Get<BalanceOf<Self>>;

/// Slashed funds are send to the treasury
type TreasuryAccount: Get<Self::AccountId>;
}

#[pallet::error]
Expand All @@ -120,6 +124,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,
}
Expand Down Expand Up @@ -171,13 +177,25 @@ 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(())
})
}

// No-one can stake more than BalanceOf::<T>::max(), therefore, this function saturates
// arithmetic operations.
fn current_required_stake(jurors_num: usize) -> BalanceOf<T> {
let jurors_len: BalanceOf<T> = jurors_num.saturated_into();
T::StakeWeight::get().saturating_mul(jurors_len)
}

// Retrieves a juror from the storage
fn juror(account_id: &T::AccountId) -> Result<Juror<BalanceOf<T>>, DispatchError> {
Jurors::<T>::get(account_id).ok_or_else(|| Error::<T>::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
Expand All @@ -188,8 +206,130 @@ mod pallet {
}

// Retrieves a juror from the storage
fn juror(account_id: &T::AccountId) -> Result<Juror<BalanceOf<T>>, DispatchError> {
Jurors::<T>::get(account_id).ok_or_else(|| Error::<T>::JurorDoesNotExists.into())
fn mutate_juror<F>(account_id: &T::AccountId, mut cb: F) -> DispatchResult
where
F: FnMut(&mut Juror<BalanceOf<T>>) -> DispatchResult,
{
Jurors::<T>::try_mutate(account_id, |opt| {
if let Some(el) = opt {
cb(el)?;
} else {
return Err(Error::<T>::JurorDoesNotExists.into());
}
Ok(())
})
}

// Slashes 20% of staked funds and removes them from the list of jurors
fn punish_tardy_jurors(
requested_jurors: &mut Vec<(T::AccountId, T::BlockNumber)>,
) -> DispatchResult {
let mut i = 0;
while i < requested_jurors.len() {
let should_remove = {
// `get` will never panic
let (ai, _) = requested_jurors.get(i).unwrap();
let juror = Self::juror(ai)?;
matches!(juror.status, JurorStatus::Tardy)
};
if should_remove {
// `remove` will never panic
let (ai, _) = requested_jurors.remove(i);
let reserved = CurrencyOf::<T>::reserved_balance(&ai);
// Division will never overflow
let slash = reserved / BalanceOf::<T>::from(5u8);
CurrencyOf::<T>::repatriate_reserved(
&ai,
&T::TreasuryAccount::get(),
slash,
BalanceStatus::Free,
)?;
Jurors::<T>::remove(ai);
} else {
// `i` will never overflow
i += 1;
}
}
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<OutcomeReport>,
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(())
}

// Jurors that didn't vote within `CourtCaseDuration` or didn't vote at all.
fn set_late_jurors_as_tardy(
requested_jurors: &[(T::AccountId, T::BlockNumber)],
votes: &[(T::AccountId, (T::BlockNumber, OutcomeReport))],
) -> DispatchResult {
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 {
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<OutcomeReport>), DispatchError> {
let mut scores = BTreeMap::<OutcomeReport, u32>::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(el) = iter.next() {
best_score = el;
} else {
return Err(Error::<T>::NoVotes.into());
}
for el in iter {
if el.1 > best_score.1 {
best_score = el;
}
}
let best_outcome = best_score.0.clone();

let _ = scores.remove(&best_outcome);

let mut iter = scores.iter();
let mut second_best_score_opt = None;
if let Some(el) = iter.next() {
let mut second_best_score = el;
for el in iter {
if el.1 > second_best_score.1 {
second_best_score = el;
}
}
second_best_score_opt = Some(second_best_score);
}
let second_best_outcome = second_best_score_opt.map(|el| el.0.clone());

Ok((best_outcome, second_best_outcome))
}
}

Expand All @@ -205,7 +345,7 @@ mod pallet {

fn on_dispute(
disputes: &[MarketDispute<Self::AccountId, Self::BlockNumber>],
market_id: Self::MarketId,
market_id: &Self::MarketId,
) -> DispatchResult {
let jurors: Vec<_> = Jurors::<T>::iter().collect();
let necessary_jurors_num = Self::necessary_jurors_num(disputes);
Expand All @@ -214,21 +354,30 @@ mod pallet {
let curr_block_num = <frame_system::Pallet<T>>::block_number();
let block_limit = curr_block_num.saturating_add(T::CourtCaseDuration::get());
for (ai, _) in random_jurors {
RequestedJurors::<T>::insert(ai, market_id, block_limit);
RequestedJurors::<T>::insert(market_id, ai, block_limit);
}
Ok(())
}

fn on_resolution<D>(
_dispute_bound: &D,
_disputes: &[MarketDispute<Self::AccountId, Self::BlockNumber>],
_market_id: &Self::MarketId,
market_id: &Self::MarketId,
_market: &Market<Self::AccountId, Self::BlockNumber>,
) -> Result<OutcomeReport, DispatchError>
where
D: Fn(usize) -> Self::Balance,
{
todo!()
let mut requested_jurors: Vec<_> =
RequestedJurors::<T>::iter_prefix(market_id).collect();
let votes: Vec<_> = Votes::<T>::iter_prefix(market_id).collect();
Self::punish_tardy_jurors(&mut requested_jurors)?;
let (first, second) = Self::two_best_outcomes(&votes)?;
Self::set_jurors_that_sided_on_the_second_most_voted_outcome_as_tardy(&second, &votes)?;
Self::set_late_jurors_as_tardy(&requested_jurors, &votes)?;
Votes::<T>::remove_prefix(market_id, None);
RequestedJurors::<T>::remove_prefix(market_id, None);
Ok(first)
}
}

Expand All @@ -237,9 +386,9 @@ mod pallet {
pub type RequestedJurors<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AccountId,
Blake2_128Concat,
MarketIdOf<T>,
Blake2_128Concat,
T::AccountId,
T::BlockNumber,
>;

Expand All @@ -256,9 +405,9 @@ mod pallet {
pub type Votes<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AccountId,
Blake2_128Concat,
MarketIdOf<T>,
Blake2_128Concat,
T::AccountId,
(T::BlockNumber, OutcomeReport),
>;
}
13 changes: 12 additions & 1 deletion zrml/court/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use zeitgeist_primitives::{

pub const ALICE: AccountIdTest = 0;
pub const BOB: AccountIdTest = 1;
pub const CHARLIE: AccountIdTest = 2;
pub const TREASURY: AccountIdTest = 99;

type Block = BlockTest<Runtime>;
type UncheckedExtrinsic = UncheckedExtrinsicTest<Runtime>;
Expand All @@ -24,6 +26,7 @@ parameter_types! {
pub const BlockHashCount: u64 = BLOCK_HASH_COUNT;
pub const LmPalletId: PalletId = PalletId(*b"test/lmg");
pub const StakeWeight: u128 = 2 * BASE;
pub const TreasuryAccount: u128 = TREASURY;
}

construct_runtime!(
Expand All @@ -47,6 +50,7 @@ impl crate::Config for Runtime {
type MarketCommons = MarketCommons;
type Random = RandomnessCollectiveFlip;
type StakeWeight = StakeWeight;
type TreasuryAccount = TreasuryAccount;
}

impl frame_system::Config for Runtime {
Expand Down Expand Up @@ -100,7 +104,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),
(TREASURY, 1_000 * BASE),
],
}
}
}

Expand Down
Loading

0 comments on commit 6c9bb00

Please sign in to comment.