#![allow(clippy::string_lit_as_bytes)]
#![allow(clippy::redundant_closure_call)]
#![allow(clippy::type_complexity)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod tests;
use codec::Codec;
use frame_support::{
decl_error,
decl_event,
decl_module,
decl_storage,
ensure,
traits::{
Currency,
ExistenceRequirement,
Get,
ReservableCurrency,
},
Parameter,
};
use frame_system::{
self as system,
ensure_signed,
};
use sp_runtime::{
traits::{
AtLeast32Bit,
MaybeSerializeDeserialize,
Member,
Zero,
},
DispatchError,
DispatchResult,
};
use sp_std::{
fmt::Debug,
prelude::*,
};
use util::{
court::{
Dispute,
DisputeState,
ResolutionMetadata,
},
traits::{
GenerateUniqueID,
GetVoteOutcome,
IDIsAvailable,
OpenVote,
RegisterDisputeType,
},
vote::VoteOutcome,
};
type BalanceOf<T> = <<T as Trait>::Currency as Currency<
<T as frame_system::Trait>::AccountId,
>>::Balance;
type VoteId<T> = <T as vote::Trait>::VoteId;
type Signal<T> = <T as vote::Trait>::Signal;
pub trait Trait: frame_system::Trait + org::Trait + vote::Trait {
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
type Currency: Currency<Self::AccountId>
+ ReservableCurrency<Self::AccountId>;
type DisputeId: Parameter
+ Member
+ AtLeast32Bit
+ Codec
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ Debug
+ PartialOrd
+ PartialEq
+ Zero;
type MinimumDisputeAmount: Get<BalanceOf<Self>>;
}
decl_event!(
pub enum Event<T>
where
<T as frame_system::Trait>::AccountId,
<T as org::Trait>::OrgId,
<T as vote::Trait>::VoteId,
<T as Trait>::DisputeId,
Balance = BalanceOf<T>,
{
RegisteredDisputeWithResolutionPath(DisputeId, AccountId, Balance, AccountId, OrgId),
DisputeRaisedAndVoteTriggered(DisputeId, AccountId, Balance, AccountId, OrgId, VoteId),
DisputeAcceptedAndLockedFundsTransferred(DisputeId, AccountId, Balance, AccountId, OrgId, VoteId),
DisputeRejectedAndLockedFundsUnlocked(DisputeId, AccountId, Balance, AccountId, OrgId, VoteId),
}
);
decl_error! {
pub enum Error for Module<T: Trait> {
CannotRaiseDisputeIfDisputeStateDNE,
DisputeMustExceedModuleMinimum,
CannotPollDisputeIfDisputeStateDNE,
SignerNotAuthorizedToRaiseThisDispute,
ActiveDisputeCannotBeRaisedFromCurrentState,
ActiveDisputeCannotBePolledFromCurrentState,
VoteOutcomeInconclusiveSoPollCannotExecuteOutcome,
}
}
decl_storage! {
trait Store for Module<T: Trait> as Court {
DisputeIdCounter get(fn dispute_id_counter): T::DisputeId;
pub OpenDisputeCounter get(fn open_dispute_counter): u32;
pub DisputeStates get(fn dispute_states): map
hasher(blake2_128_concat) T::DisputeId => Option<
Dispute<
T::AccountId,
BalanceOf<T>,
T::BlockNumber,
ResolutionMetadata<
T::OrgId,
Signal<T>,
T::BlockNumber
>,
DisputeState<VoteId<T>>,
>
>;
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>;
fn deposit_event() = default;
#[weight = 0]
fn register_dispute_type_with_resolution_path(
origin,
amount_to_lock: BalanceOf<T>,
dispute_raiser: T::AccountId,
resolution_metadata: ResolutionMetadata<T::OrgId, Signal<T>, T::BlockNumber>,
expiry: Option<T::BlockNumber>,
) -> DispatchResult {
let locker = ensure_signed(origin)?;
let court_org = resolution_metadata.org();
let new_dispute_id = Self::register_dispute_type(
locker.clone(),
amount_to_lock,
dispute_raiser.clone(),
resolution_metadata,
expiry,
)?;
Self::deposit_event(RawEvent::RegisteredDisputeWithResolutionPath(new_dispute_id, locker, amount_to_lock, dispute_raiser, court_org));
Ok(())
}
#[weight = 0]
fn raise_dispute_to_trigger_vote(
origin,
dispute_id: T::DisputeId,
) -> DispatchResult {
let trigger = ensure_signed(origin)?;
let dispute = <DisputeStates<T>>::get(dispute_id).ok_or(Error::<T>::CannotRaiseDisputeIfDisputeStateDNE)?;
ensure!(dispute.can_raise_dispute(&trigger), Error::<T>::SignerNotAuthorizedToRaiseThisDispute);
let (new_dispute, dispatched_vote_id) = match dispute.state() {
DisputeState::DisputeNotRaised => {
let resolution_metadata = dispute.resolution_metadata();
let new_vote_id = <vote::Module<T>>::open_vote(None, resolution_metadata.org(), resolution_metadata.passage_threshold(), resolution_metadata.rejection_threshold(), resolution_metadata.duration())?;
let updated_dispute = dispute.set_state(DisputeState::DisputeRaisedAndVoteDispatched(new_vote_id));
(updated_dispute, new_vote_id)
},
_ => return Err(Error::<T>::ActiveDisputeCannotBeRaisedFromCurrentState.into()),
};
let (locker, amt_locked, court_org) = (
new_dispute.locker(),
new_dispute.locked_funds(),
new_dispute.resolution_metadata().org(),
);
<DisputeStates<T>>::insert(dispute_id, new_dispute);
Self::deposit_event(RawEvent::DisputeRaisedAndVoteTriggered(dispute_id, locker, amt_locked, trigger, court_org, dispatched_vote_id));
Ok(())
}
#[weight = 0]
fn poll_dispute_to_execute_outcome(
origin,
dispute_id: T::DisputeId,
) -> DispatchResult {
let _ = ensure_signed(origin)?;
let dispute = <DisputeStates<T>>::get(dispute_id).ok_or(Error::<T>::CannotPollDisputeIfDisputeStateDNE)?;
let new_dispute_state = match dispute.state() {
DisputeState::DisputeRaisedAndVoteDispatched(live_vote_id) => {
let outcome = <vote::Module<T>>::get_vote_outcome(live_vote_id)?;
match outcome {
VoteOutcome::Approved => {
let _ = T::Currency::unreserve(&dispute.locker(), dispute.locked_funds());
T::Currency::transfer(&dispute.locker(), &dispute.dispute_raiser(), dispute.locked_funds(), ExistenceRequirement::KeepAlive)?;
dispute.set_state(DisputeState::DisputeRaisedAndAccepted(live_vote_id))
}
VoteOutcome::Rejected => {
let _ = T::Currency::unreserve(&dispute.locker(), dispute.locked_funds());
dispute.set_state(DisputeState::DisputeRaisedAndRejected(live_vote_id))
}
_ => return Err(Error::<T>::VoteOutcomeInconclusiveSoPollCannotExecuteOutcome.into()),
}
}
_ => return Err(Error::<T>::ActiveDisputeCannotBePolledFromCurrentState.into()),
};
<DisputeStates<T>>::insert(dispute_id, new_dispute_state);
Ok(())
}
}
}
impl<T: Trait> IDIsAvailable<T::DisputeId> for Module<T> {
fn id_is_available(id: T::DisputeId) -> bool {
<DisputeStates<T>>::get(id).is_none()
}
}
impl<T: Trait> GenerateUniqueID<T::DisputeId> for Module<T> {
fn generate_unique_id() -> T::DisputeId {
let mut id_counter = <DisputeIdCounter<T>>::get() + 1u32.into();
while <DisputeStates<T>>::get(id_counter).is_some() {
id_counter += 1u32.into();
}
<DisputeIdCounter<T>>::put(id_counter);
id_counter
}
}
impl<T: Trait>
RegisterDisputeType<
T::AccountId,
BalanceOf<T>,
ResolutionMetadata<T::OrgId, Signal<T>, T::BlockNumber>,
T::BlockNumber,
> for Module<T>
{
type DisputeIdentifier = T::DisputeId;
fn register_dispute_type(
locker: T::AccountId,
amount_to_lock: BalanceOf<T>,
dispute_raiser: T::AccountId,
resolution_path: ResolutionMetadata<
T::OrgId,
Signal<T>,
T::BlockNumber,
>,
expiry: Option<T::BlockNumber>,
) -> Result<Self::DisputeIdentifier, DispatchError> {
ensure!(
amount_to_lock >= T::MinimumDisputeAmount::get(),
Error::<T>::DisputeMustExceedModuleMinimum
);
T::Currency::reserve(&locker, amount_to_lock)?;
let new_dispute_state = Dispute::new(
locker,
amount_to_lock,
dispute_raiser,
resolution_path,
DisputeState::DisputeNotRaised,
expiry,
);
let new_dispute_id = Self::generate_unique_id();
<DisputeStates<T>>::insert(new_dispute_id, new_dispute_state);
Ok(new_dispute_id)
}
}