|
@@ -115,13 +115,13 @@
|
|
|
// Do not delete! Cannot be uncommented by default, because of Parity decl_module! issue.
|
|
|
//#![warn(missing_docs)]
|
|
|
|
|
|
-use types::{ApprovedProposalData, FinalizedProposalData, ProposalStakeManager};
|
|
|
+use types::{ApprovedProposalData, FinalizedProposalData, MemberId};
|
|
|
|
|
|
pub use types::{
|
|
|
- ActiveStake, ApprovedProposalStatus, BalanceOf, CurrencyOf, DefaultStakeHandlerProvider,
|
|
|
+ ActiveStake, ApprovedProposalStatus, BalanceOf, BalanceOfCurrency, CurrencyOf,
|
|
|
FinalizationData, NegativeImbalance, Proposal, ProposalCodeDecoder, ProposalCreationParameters,
|
|
|
- ProposalDecisionStatus, ProposalExecutable, ProposalParameters, ProposalStatus, StakeHandler,
|
|
|
- StakeHandlerProvider, VoteKind, VotersParameters, VotingResults,
|
|
|
+ ProposalDecisionStatus, ProposalExecutable, ProposalParameters, ProposalStatus, StakingHandler,
|
|
|
+ VoteKind, VotersParameters, VotingResults,
|
|
|
};
|
|
|
|
|
|
pub(crate) mod types;
|
|
@@ -132,7 +132,7 @@ mod tests;
|
|
|
use codec::Decode;
|
|
|
use frame_support::dispatch::{DispatchError, DispatchResult, UnfilteredDispatchable};
|
|
|
use frame_support::storage::IterableStorageMap;
|
|
|
-use frame_support::traits::{Currency, Get};
|
|
|
+use frame_support::traits::Get;
|
|
|
use frame_support::{
|
|
|
decl_error, decl_event, decl_module, decl_storage, ensure, print, Parameter, StorageDoubleMap,
|
|
|
};
|
|
@@ -142,8 +142,6 @@ use system::{ensure_root, RawOrigin};
|
|
|
|
|
|
use common::origin::ActorOriginValidator;
|
|
|
|
|
|
-type MemberId<T> = <T as membership::Trait>::MemberId;
|
|
|
-
|
|
|
/// Proposals engine trait.
|
|
|
pub trait Trait:
|
|
|
system::Trait + pallet_timestamp::Trait + stake::Trait + membership::Trait
|
|
@@ -167,14 +165,14 @@ pub trait Trait:
|
|
|
/// Proposal Id type
|
|
|
type ProposalId: From<u32> + Parameter + Default + Copy;
|
|
|
|
|
|
- /// Provides stake logic implementation. Can be used to mock stake logic.
|
|
|
- type StakeHandlerProvider: StakeHandlerProvider<Self>;
|
|
|
+ /// Provides stake logic implementation.
|
|
|
+ type StakingHandler: StakingHandler<Self>;
|
|
|
|
|
|
/// The fee is applied when cancel the proposal. A fee would be slashed (burned).
|
|
|
- type CancellationFee: Get<BalanceOf<Self>>;
|
|
|
+ type CancellationFee: Get<BalanceOfCurrency<Self>>;
|
|
|
|
|
|
/// The fee is applied when the proposal gets rejected. A fee would be slashed (burned).
|
|
|
- type RejectionFee: Get<BalanceOf<Self>>;
|
|
|
+ type RejectionFee: Get<BalanceOfCurrency<Self>>;
|
|
|
|
|
|
/// Defines max allowed proposal title length.
|
|
|
type TitleMaxLength: Get<u32>;
|
|
@@ -214,7 +212,6 @@ decl_event!(
|
|
|
MemberId = MemberId<T>,
|
|
|
<T as system::Trait>::BlockNumber,
|
|
|
<T as system::Trait>::AccountId,
|
|
|
- <T as stake::Trait>::StakeId,
|
|
|
{
|
|
|
/// Emits on proposal creation.
|
|
|
/// Params:
|
|
@@ -226,7 +223,7 @@ decl_event!(
|
|
|
/// Params:
|
|
|
/// - Id of a updated proposal.
|
|
|
/// - New proposal status
|
|
|
- ProposalStatusUpdated(ProposalId, ProposalStatus<BlockNumber, StakeId, AccountId>),
|
|
|
+ ProposalStatusUpdated(ProposalId, ProposalStatus<BlockNumber, AccountId>),
|
|
|
|
|
|
/// Emits on voting for the proposal
|
|
|
/// Params:
|
|
@@ -324,10 +321,6 @@ decl_storage! {
|
|
|
/// Double map for preventing duplicate votes. Should be cleaned after usage.
|
|
|
pub VoteExistsByProposalByVoter get(fn vote_by_proposal_by_voter):
|
|
|
double_map hasher(blake2_128_concat) T::ProposalId, hasher(blake2_128_concat) MemberId<T> => VoteKind;
|
|
|
-
|
|
|
- /// Map proposal id by stake id. Required by StakingEventsHandler callback call
|
|
|
- pub StakesProposals get(fn stakes_proposals): map hasher(blake2_128_concat)
|
|
|
- T::StakeId => T::ProposalId;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -341,10 +334,10 @@ decl_module! {
|
|
|
fn deposit_event() = default;
|
|
|
|
|
|
/// Exports const - the fee is applied when cancel the proposal. A fee would be slashed (burned).
|
|
|
- const CancellationFee: BalanceOf<T> = T::CancellationFee::get();
|
|
|
+ const CancellationFee: BalanceOfCurrency<T> = T::CancellationFee::get();
|
|
|
|
|
|
/// Exports const - the fee is applied when the proposal gets rejected. A fee would be slashed (burned).
|
|
|
- const RejectionFee: BalanceOf<T> = T::RejectionFee::get();
|
|
|
+ const RejectionFee: BalanceOfCurrency<T> = T::RejectionFee::get();
|
|
|
|
|
|
/// Exports const - max allowed proposal title length.
|
|
|
const TitleMaxLength: u32 = T::TitleMaxLength::get();
|
|
@@ -452,7 +445,7 @@ impl<T: Trait> Module<T> {
|
|
|
pub fn create_proposal(
|
|
|
creation_params: ProposalCreationParameters<
|
|
|
T::BlockNumber,
|
|
|
- types::BalanceOf<T>,
|
|
|
+ BalanceOfCurrency<T>,
|
|
|
MemberId<T>,
|
|
|
T::AccountId,
|
|
|
>,
|
|
@@ -472,27 +465,15 @@ impl<T: Trait> Module<T> {
|
|
|
let new_proposal_id = next_proposal_count_value;
|
|
|
let proposal_id = T::ProposalId::from(new_proposal_id);
|
|
|
|
|
|
- // Check stake_balance for value and create stake if value exists, else take None
|
|
|
- // If create_stake() returns error - return error from extrinsic
|
|
|
- let stake_id_result = creation_params
|
|
|
- .stake_balance
|
|
|
- .map(|stake_amount| {
|
|
|
- ProposalStakeManager::<T>::create_stake(
|
|
|
- stake_amount,
|
|
|
- creation_params.account_id.clone(),
|
|
|
- )
|
|
|
- })
|
|
|
- .transpose()?;
|
|
|
+ let stake_data = if let Some(stake_balance) = creation_params.stake_balance {
|
|
|
+ T::StakingHandler::lock(&creation_params.account_id, stake_balance);
|
|
|
|
|
|
- let mut stake_data = None;
|
|
|
- if let Some(stake_id) = stake_id_result {
|
|
|
- stake_data = Some(ActiveStake {
|
|
|
- stake_id,
|
|
|
+ Some(ActiveStake {
|
|
|
source_account_id: creation_params.account_id,
|
|
|
- });
|
|
|
-
|
|
|
- <StakesProposals<T>>::insert(stake_id, proposal_id);
|
|
|
- }
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ None
|
|
|
+ };
|
|
|
|
|
|
let new_proposal = Proposal {
|
|
|
created_at: Self::current_block(),
|
|
@@ -528,10 +509,10 @@ impl<T: Trait> Module<T> {
|
|
|
/// - provided parameters: approval_threshold_percentage and slashing_threshold_percentage > 0
|
|
|
/// - provided stake balance and parameters.required_stake are valid
|
|
|
pub fn ensure_create_proposal_parameters_are_valid(
|
|
|
- parameters: &ProposalParameters<T::BlockNumber, types::BalanceOf<T>>,
|
|
|
+ parameters: &ProposalParameters<T::BlockNumber, BalanceOfCurrency<T>>,
|
|
|
title: &[u8],
|
|
|
description: &[u8],
|
|
|
- stake_balance: Option<types::BalanceOf<T>>,
|
|
|
+ stake_balance: Option<BalanceOfCurrency<T>>,
|
|
|
exact_execution_block: Option<T::BlockNumber>,
|
|
|
) -> DispatchResult {
|
|
|
ensure!(!title.is_empty(), Error::<T>::EmptyTitleProvided);
|
|
@@ -594,38 +575,6 @@ impl<T: Trait> Module<T> {
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
- /// Callback from StakingEventsHandler. Refunds unstaked imbalance back to the source account.
|
|
|
- /// There can be a lot of invariant breaks in the scope of this proposal.
|
|
|
- /// Such situations are handled by adding error messages to the log.
|
|
|
- pub fn refund_proposal_stake(stake_id: T::StakeId, imbalance: NegativeImbalance<T>) {
|
|
|
- if <StakesProposals<T>>::contains_key(stake_id) {
|
|
|
- let proposal_id = Self::stakes_proposals(stake_id);
|
|
|
-
|
|
|
- if <Proposals<T>>::contains_key(proposal_id) {
|
|
|
- let proposal = Self::proposals(proposal_id);
|
|
|
-
|
|
|
- if let ProposalStatus::Active(active_stake_result) = proposal.status {
|
|
|
- if let Some(active_stake) = active_stake_result {
|
|
|
- let refunding_result = CurrencyOf::<T>::resolve_into_existing(
|
|
|
- &active_stake.source_account_id,
|
|
|
- imbalance,
|
|
|
- );
|
|
|
-
|
|
|
- if refunding_result.is_err() {
|
|
|
- print("Broken invariant: cannot refund");
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- print("Broken invariant: proposal status is not Active");
|
|
|
- }
|
|
|
- } else {
|
|
|
- print("Broken invariant: proposal doesn't exist");
|
|
|
- }
|
|
|
- } else {
|
|
|
- print("Broken invariant: stake doesn't exist");
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
/// Resets voting results for active proposals.
|
|
|
/// Possible application includes new council elections.
|
|
|
pub fn reset_active_proposals() {
|
|
@@ -677,12 +626,8 @@ impl<T: Trait> Module<T> {
|
|
|
fn veto_pending_execution_proposal(proposal_id: T::ProposalId, proposal: ProposalOf<T>) {
|
|
|
<PendingExecutionProposalIds<T>>::remove(proposal_id);
|
|
|
|
|
|
- let vetoed_proposal_status = ProposalStatus::finalized(
|
|
|
- ProposalDecisionStatus::Vetoed,
|
|
|
- None,
|
|
|
- None,
|
|
|
- Self::current_block(),
|
|
|
- );
|
|
|
+ let vetoed_proposal_status =
|
|
|
+ ProposalStatus::finalized(ProposalDecisionStatus::Vetoed, Self::current_block());
|
|
|
|
|
|
<Proposals<T>>::insert(
|
|
|
proposal_id,
|
|
@@ -714,9 +659,10 @@ impl<T: Trait> Module<T> {
|
|
|
Err(error) => ApprovedProposalStatus::failed_execution(error.what()),
|
|
|
};
|
|
|
|
|
|
- let proposal_execution_status = approved_proposal
|
|
|
- .finalisation_status_data
|
|
|
- .create_approved_proposal_status(approved_proposal_status);
|
|
|
+ let proposal_execution_status = ProposalStatus::Finalized(FinalizationData {
|
|
|
+ proposal_status: ProposalDecisionStatus::Approved(approved_proposal_status),
|
|
|
+ finalized_at: approved_proposal.finalisation_status_data.finalized_at,
|
|
|
+ });
|
|
|
|
|
|
Self::deposit_event(RawEvent::ProposalStatusUpdated(
|
|
|
approved_proposal.proposal_id,
|
|
@@ -751,16 +697,11 @@ impl<T: Trait> Module<T> {
|
|
|
// deal with stakes if necessary
|
|
|
let slash_balance =
|
|
|
Self::calculate_slash_balance(&decision_status, &proposal.parameters);
|
|
|
- let slash_and_unstake_result =
|
|
|
- Self::slash_and_unstake(active_stake.clone(), slash_balance);
|
|
|
+ Self::slash_and_unstake(active_stake, slash_balance);
|
|
|
|
|
|
// create finalized proposal status with error if any
|
|
|
- let new_proposal_status = ProposalStatus::finalized(
|
|
|
- decision_status,
|
|
|
- slash_and_unstake_result.err(),
|
|
|
- active_stake,
|
|
|
- Self::current_block(),
|
|
|
- );
|
|
|
+ let new_proposal_status =
|
|
|
+ ProposalStatus::finalized(decision_status, Self::current_block());
|
|
|
|
|
|
if clean_finilized_proposal {
|
|
|
Self::remove_proposal_data(&proposal_id);
|
|
@@ -780,39 +721,37 @@ impl<T: Trait> Module<T> {
|
|
|
|
|
|
// Slashes the stake and perform unstake only in case of existing stake
|
|
|
fn slash_and_unstake(
|
|
|
- current_stake_data: Option<ActiveStake<T::StakeId, T::AccountId>>,
|
|
|
- slash_balance: BalanceOf<T>,
|
|
|
- ) -> Result<(), &'static str> {
|
|
|
+ current_stake_data: Option<ActiveStake<T::AccountId>>,
|
|
|
+ slash_balance: BalanceOfCurrency<T>,
|
|
|
+ ) {
|
|
|
// only if stake exists
|
|
|
if let Some(stake_data) = current_stake_data {
|
|
|
if !slash_balance.is_zero() {
|
|
|
- ProposalStakeManager::<T>::slash(stake_data.stake_id, slash_balance)?;
|
|
|
+ T::StakingHandler::slash(&stake_data.source_account_id, Some(slash_balance));
|
|
|
}
|
|
|
|
|
|
- ProposalStakeManager::<T>::remove_stake(stake_data.stake_id)?;
|
|
|
+ T::StakingHandler::unlock(&stake_data.source_account_id);
|
|
|
}
|
|
|
-
|
|
|
- Ok(())
|
|
|
}
|
|
|
|
|
|
// Calculates required slash based on finalization ProposalDecisionStatus and proposal parameters.
|
|
|
// Method visibility allows testing.
|
|
|
pub(crate) fn calculate_slash_balance(
|
|
|
decision_status: &ProposalDecisionStatus,
|
|
|
- proposal_parameters: &ProposalParameters<T::BlockNumber, types::BalanceOf<T>>,
|
|
|
- ) -> types::BalanceOf<T> {
|
|
|
+ proposal_parameters: &ProposalParameters<T::BlockNumber, BalanceOfCurrency<T>>,
|
|
|
+ ) -> BalanceOfCurrency<T> {
|
|
|
match decision_status {
|
|
|
ProposalDecisionStatus::Rejected | ProposalDecisionStatus::Expired => {
|
|
|
T::RejectionFee::get()
|
|
|
}
|
|
|
ProposalDecisionStatus::Approved { .. } | ProposalDecisionStatus::Vetoed => {
|
|
|
- BalanceOf::<T>::zero()
|
|
|
+ BalanceOfCurrency::<T>::zero()
|
|
|
}
|
|
|
ProposalDecisionStatus::Canceled => T::CancellationFee::get(),
|
|
|
ProposalDecisionStatus::Slashed => proposal_parameters
|
|
|
.required_stake
|
|
|
.clone()
|
|
|
- .unwrap_or_else(BalanceOf::<T>::zero), // stake if set or zero
|
|
|
+ .unwrap_or_else(BalanceOfCurrency::<T>::zero), // stake if set or zero
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -888,8 +827,7 @@ type FinalizedProposal<T> = FinalizedProposalData<
|
|
|
<T as Trait>::ProposalId,
|
|
|
<T as system::Trait>::BlockNumber,
|
|
|
MemberId<T>,
|
|
|
- types::BalanceOf<T>,
|
|
|
- <T as stake::Trait>::StakeId,
|
|
|
+ BalanceOfCurrency<T>,
|
|
|
<T as system::Trait>::AccountId,
|
|
|
>;
|
|
|
|
|
@@ -898,8 +836,7 @@ type ApprovedProposal<T> = ApprovedProposalData<
|
|
|
<T as Trait>::ProposalId,
|
|
|
<T as system::Trait>::BlockNumber,
|
|
|
MemberId<T>,
|
|
|
- types::BalanceOf<T>,
|
|
|
- <T as stake::Trait>::StakeId,
|
|
|
+ BalanceOfCurrency<T>,
|
|
|
<T as system::Trait>::AccountId,
|
|
|
>;
|
|
|
|
|
@@ -907,7 +844,6 @@ type ApprovedProposal<T> = ApprovedProposalData<
|
|
|
type ProposalOf<T> = Proposal<
|
|
|
<T as system::Trait>::BlockNumber,
|
|
|
MemberId<T>,
|
|
|
- types::BalanceOf<T>,
|
|
|
- <T as stake::Trait>::StakeId,
|
|
|
+ BalanceOfCurrency<T>,
|
|
|
<T as system::Trait>::AccountId,
|
|
|
>;
|