Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding try_state hook for Treasury pallet #1820

Merged
merged 18 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 80 additions & 5 deletions substrate/frame/treasury/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,16 @@ use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;

use sp_runtime::{
traits::{AccountIdConversion, CheckedAdd, Saturating, StaticLookup, Zero},
traits::{
AccountIdConversion, CheckedAdd, SaturatedConversion, Saturating, StaticLookup, Zero,
},
Permill, RuntimeDebug,
};
use sp_std::{collections::btree_map::BTreeMap, prelude::*};

use frame_support::{
print,
dispatch::{DispatchResult, DispatchResultWithPostInfo},
ensure, print,
traits::{
tokens::Pay, Currency, ExistenceRequirement::KeepAlive, Get, Imbalance, OnUnbalanced,
ReservableCurrency, WithdrawReasons,
Expand Down Expand Up @@ -456,6 +459,12 @@ pub mod pallet {
Weight::zero()
}
}

#[cfg(feature = "try-runtime")]
fn try_state(_: T::BlockNumber) -> Result<(), sp_runtime::TryRuntimeError> {
Self::do_try_state()?;
Ok(())
}
}

#[derive(Default)]
Expand Down Expand Up @@ -855,7 +864,7 @@ pub mod pallet {
// spend has expired and no further status update is expected.
Spends::<T, I>::remove(index);
Self::deposit_event(Event::<T, I>::SpendProcessed { index });
return Ok(Pays::No.into())
return Ok(Pays::No.into());
}

let payment_id = match spend.status {
Expand All @@ -872,11 +881,11 @@ pub mod pallet {
Status::Success | Status::Unknown => {
Spends::<T, I>::remove(index);
Self::deposit_event(Event::<T, I>::SpendProcessed { index });
return Ok(Pays::No.into())
return Ok(Pays::No.into());
},
Status::InProgress => return Err(Error::<T, I>::Inconclusive.into()),
}
return Ok(Pays::Yes.into())
return Ok(Pays::Yes.into());
}

/// Void previously approved spend.
Expand Down Expand Up @@ -1020,6 +1029,72 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
// Must never be less than 0 but better be safe.
.saturating_sub(T::Currency::minimum_balance())
}

/// Ensure the correctness of the state of this pallet.
///
/// ## Invariants
///
/// 1. [`ProposalCount`] >= [`Proposals`].count(). The [`ProposalCount`] SV is only increased, but never
/// decreased whereas individual proposals can be removed from the [`Proposals`] SM.
/// 2. [`T::ProposalBondMinimum`] * Number of proposals with non-zero bond <= sum_{p in Proposals} p.bond.
/// 3. (Optional) [`T::ProposalBondMaximum`] * Number of proposals with non-zero bond >= sum_{p in Proposals} p.bond.
/// 4. Each [`ProposalIndex`] contained in [`Approvals`] should exist in [`Proposals`]. Note, that this
/// automatically implies Approvals.count() <= Proposals.count().
/// 5. [`SpendCount`] >= [`Spends`].count(). Follows the same reasoning as (1).
/// 6. For each spend entry contained in `Spends` we should have spend.expire_at > spend.valid_from.
#[cfg(any(feature = "try-runtime", test))]
fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
ensure!(
ProposalCount::<T, I>::get() as usize >= Proposals::<T, I>::iter().count(),
"Actual number of proposals exceeds `ProposalCount`."
);

let (total_amount_bonded, non_zero_count) = Proposals::<T, I>::iter().fold(
(BalanceOf::<T, I>::zero(), 0usize),
|(sum, count), (_index, proposal)| {
if proposal.bond != BalanceOf::<T, I>::zero() {
(sum + proposal.bond, count + 1)
} else {
(sum, count)
}
},
);
ensure!(
T::ProposalBondMinimum::get() * non_zero_count.saturated_into() <= total_amount_bonded,
"Total amount bonded falls short of required minimum."
);
if let Some(proposal_bond_maximum) = T::ProposalBondMaximum::get() {
ensure!(
proposal_bond_maximum * non_zero_count.saturated_into() >= total_amount_bonded,
"Total amount bonded exceeds required maximum."
);
}

Approvals::<T, I>::get()
.iter()
.try_for_each(|proposal_index| -> DispatchResult {
ensure!(
Proposals::<T, I>::contains_key(proposal_index),
"Proposal indices in `Approvals` must also be contained in `Proposals`."
);
Ok(())
})?;

ensure!(
SpendCount::<T, I>::get() as usize >= Spends::<T, I>::iter().count(),
"Number of spends exceeds `SpendCount`."
);

Spends::<T, I>::iter().try_for_each(|(_index, spend)| -> DispatchResult {
ensure!(
spend.valid_from < spend.expire_at,
"Spend cannot expire before it becomes valid."
);
Ok(())
})?;

Ok(())
}
}

impl<T: Config<I>, I: 'static> OnUnbalanced<NegativeImbalanceOf<T, I>> for Pallet<T, I> {
Expand Down
Loading