Skip to content

Commit

Permalink
fix test build issues of parachain-staking.
Browse files Browse the repository at this point in the history
The order of the pallets during runtime construction has changed.
see KILTprotocol/kilt-node#416
  • Loading branch information
b-yap committed Jun 12, 2023
1 parent 7799c7a commit 19dceb0
Show file tree
Hide file tree
Showing 9 changed files with 289 additions and 27 deletions.
3 changes: 3 additions & 0 deletions pallets/parachain-staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ pub(crate) mod mock;
#[cfg(test)]
pub(crate) mod tests;

#[cfg(any(feature = "try-runtime", test))]
mod try_state;

mod inflation;
mod set;
mod types;
Expand Down
16 changes: 11 additions & 5 deletions pallets/parachain-staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ use crate::{self as stake, types::NegativeImbalanceOf};
use frame_support::{
assert_ok, construct_runtime, parameter_types,
traits::{Currency, GenesisBuild, OnFinalize, OnInitialize, OnUnbalanced},
weights::Weight,
};
use pallet_authorship::EventHandler;
use sp_consensus_aura::sr25519::AuthorityId;
Expand Down Expand Up @@ -58,16 +57,15 @@ construct_runtime!(
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
Authorship: pallet_authorship::{Pallet, Storage},
StakePallet: stake::{Pallet, Call, Storage, Config<T>, Event<T>},
Session: pallet_session::{Pallet, Call, Storage, Event, Config<T>},
Aura: pallet_aura::{Pallet, Storage},
Session: pallet_session::{Pallet, Call, Storage, Event, Config<T>},
StakePallet: stake::{Pallet, Call, Storage, Config<T>, Event<T>},
Authorship: pallet_authorship::{Pallet, Storage},
}
);

parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const MaximumBlockWeight: Weight = Weight::from_parts(1024,0);
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::one();
pub const SS58Prefix: u8 = 42;
Expand Down Expand Up @@ -341,6 +339,13 @@ impl ExtBuilder {
ext.execute_with(|| System::set_block_number(1));
ext
}
pub fn build_and_execute_with_sanity_tests(self, test: impl FnOnce()) {
self.build().execute_with(|| {
test();
crate::try_state::do_try_state::<Test>()
.expect("Sanity test for parachain staking failed.");
})
}
}

/// Compare whether the difference of both sides is at most `precision * left`.
Expand Down Expand Up @@ -384,6 +389,7 @@ pub(crate) fn roll_to_claim_rewards(n: BlockNumber, authors: Vec<Option<AccountI
StakePallet::note_author(*author);
// author must convert RewardCount to Rewards before claiming
assert_ok!(StakePallet::increment_collator_rewards(RuntimeOrigin::signed(*author)));

// author claims rewards
assert_ok!(StakePallet::claim_rewards(RuntimeOrigin::signed(*author)));

Expand Down
15 changes: 9 additions & 6 deletions pallets/parachain-staking/src/set.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// KILT Blockchain – https://botlabs.org
// Copyright (C) 2019-2022 BOTLabs GmbH
// Copyright (C) 2019-2023 BOTLabs GmbH

// The KILT Blockchain is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
Expand All @@ -23,7 +23,6 @@ use sp_runtime::{traits::Zero, SaturatedConversion};
use sp_std::{
cmp::Ordering,
convert::TryInto,
fmt::Debug,
ops::{Index, Range, RangeFull},
};

Expand All @@ -38,12 +37,16 @@ use sp_std::prelude::*;
#[codec(mel_bound(T: MaxEncodedLen))]
pub struct OrderedSet<T, S: Get<u32>>(BoundedVec<T, S>);

impl<T: Ord + Clone + Debug, S: Get<u32>> OrderedSet<T, S> {
impl<T: Ord + Clone, S: Get<u32>> OrderedSet<T, S> {
/// Create a new empty set.
pub fn new() -> Self {
Self(BoundedVec::default())
}

pub fn iter(&self) -> sp_std::slice::Iter<'_, T> {
self.0.iter()
}

/// Creates an ordered set from a `BoundedVec`.
///
/// The vector will be sorted reversily (from greatest to lowest) and
Expand All @@ -52,7 +55,7 @@ impl<T: Ord + Clone + Debug, S: Get<u32>> OrderedSet<T, S> {
let mut v = bv.into_inner();
v.sort_by(|a, b| b.cmp(a));
v.dedup();
Self::from_sorted_set(v.try_into().expect("No values were added"))
Self::from_sorted_set(v.try_into().map_err(|_| ()).expect("No values were added"))
}

/// Create a set from a `BoundedVec`.
Expand Down Expand Up @@ -233,9 +236,9 @@ impl<T: Ord + Clone + Debug, S: Get<u32>> OrderedSet<T, S> {
}
}

impl<T: Ord + Clone + Debug, S: Get<u32>> From<BoundedVec<T, S>> for OrderedSet<T, S> {
impl<T: Ord + Clone, S: Get<u32>> From<BoundedVec<T, S>> for OrderedSet<T, S> {
fn from(bv: BoundedVec<T, S>) -> Self {
OrderedSet::<T, S>::from(bv)
Self::from(bv)
}
}

Expand Down
4 changes: 2 additions & 2 deletions pallets/parachain-staking/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3143,8 +3143,7 @@ fn authorities_per_round() {
(11, 100 * stake),
])
.with_collators(vec![(1, stake), (2, stake), (3, stake), (4, stake)])
.build()
.execute_with(|| {
.build_and_execute_with_sanity_tests(|| {
assert_eq!(StakePallet::selected_candidates().into_inner(), vec![1, 2]);
// reward 1 once per round
let authors: Vec<Option<AccountId>> =
Expand All @@ -3168,6 +3167,7 @@ fn authorities_per_round() {
// roll to last block of round 2
// should multiply with 4 because there are only 4 candidates
roll_to_claim_rewards(14, authors.clone());

let reward_2 = inflation.collator.reward_rate.per_block * stake * 4;
assert_eq!(Balances::free_balance(1), stake + reward_0 + reward_1 + reward_2);

Expand Down
244 changes: 244 additions & 0 deletions pallets/parachain-staking/src/try_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
// KILT Blockchain – https://botlabs.org
// Copyright (C) 2019-2023 BOTLabs GmbH

// The KILT Blockchain is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// The KILT Blockchain is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

// If you feel like getting in touch with us, you can do so at [email protected]

use frame_support::{ensure, traits::Get};
use scale_info::prelude::format;
use sp_runtime::{
traits::{CheckedAdd, Zero},
SaturatedConversion, Saturating,
};

use crate::{
set::OrderedSet,
types::{BalanceOf, Candidate, Stake},
CandidatePool, Config, DelegatorState, LastDelegation, MaxCollatorCandidateStake,
MaxSelectedCandidates, Pallet, Round, TopCandidates, TotalCollatorStake,
};

pub fn log_and_return_error_message(error_message: String) -> &'static str {
log::error!("{}", error_message);
"Sanity test error"
}

pub(crate) fn do_try_state<T: Config>() -> Result<(), &'static str> {
validate_candiate_pool::<T>()?;
validate_delegators::<T>()?;
validate_top_candidates::<T>()?;
validate_stake::<T>()
}

fn validate_candiate_pool<T: Config>() -> Result<(), &'static str> {
// check if enough collators are set.
ensure!(
CandidatePool::<T>::count() >= T::MinCollators::get(),
log_and_return_error_message(format!(
"Insufficient collators. Collators count: {:?}. Min required collators: {:?}",
CandidatePool::<T>::count(),
T::MinCollators::get()
))
);

CandidatePool::<T>::iter_values().try_for_each(
|candidate: Candidate<T::AccountId, BalanceOf<T>, _>| -> Result<(), &'static str> {
let sum_delegations: BalanceOf<T> = candidate
.delegators
.iter()
.fold(Zero::zero(), |acc, stake| acc.saturating_add(stake.amount));

// total stake should be the sum of delegators stake + colator stake.
let stake_total = sum_delegations.checked_add(&candidate.stake);
ensure!(
stake_total == Some(candidate.total),
log_and_return_error_message(format!(
"Total stake of collator {:?} does not match. Saved stake: {:?}. Calculated stake: {:?}",
candidate.id, candidate.stake, stake_total
))
);

// Min required stake should be set
ensure!(
candidate.stake >= T::MinCollatorCandidateStake::get(),
log_and_return_error_message(format!(
"Stake of collator {:?} insufficient. Required stake: {:?}. Owned Stake: {:?} ",
candidate.id,
T::MinCollatorCandidateStake::get(),
candidate.stake
))
);

validate_delegators_from_collator::<T>(candidate.delegators)?;

// check min and max stake for each candidate
ensure!(
candidate.stake <= MaxCollatorCandidateStake::<T>::get(),
log_and_return_error_message(format!(
"Candidate {:?} exceeded stake. Allowed stake: {:?}. Owned Stake: {:?}",
candidate.id,
MaxCollatorCandidateStake::<T>::get(),
candidate.stake
))
);

Ok(())
},
)
}

fn validate_top_candidates<T: Config>() -> Result<(), &'static str> {
let top_candidates = TopCandidates::<T>::get();

// check if enough top candidates are set.
ensure!(
top_candidates.len() >= T::MinRequiredCollators::get().saturated_into(),
log_and_return_error_message(format!(
"Not enough candidates are set. Candidate count: {:?}. Required: {:?}",
top_candidates.len(),
T::MinRequiredCollators::get()
))
);

top_candidates.iter().try_for_each(|stake| -> Result<(), &'static str> {
// top candidates should be part of the candidate pool.
ensure!(
CandidatePool::<T>::contains_key(&stake.owner),
log_and_return_error_message(format!(
"Unknown candidate {:?} in top candidates.",
stake.owner
))
);

// an account can not be candidate and delegator.
ensure!(
DelegatorState::<T>::get(&stake.owner).is_none(),
log_and_return_error_message(format!(
"Account {:?} is delegator and candidate.",
stake.owner
))
);

// a top candidate should be active.
ensure!(
Pallet::<T>::is_active_candidate(&stake.owner).unwrap(),
log_and_return_error_message(format!("Top candidate {:?} is inactive", stake.owner))
);

Ok(())
})
}

fn validate_delegators_from_collator<T: Config>(
delegators: OrderedSet<Stake<T::AccountId, BalanceOf<T>>, T::MaxDelegatorsPerCollator>,
) -> Result<(), &'static str> {
delegators
.iter()
.try_for_each(|delegator_stake| -> Result<(), &'static str> {
let last_delegation = LastDelegation::<T>::get(&delegator_stake.owner);
let round = Round::<T>::get();
let counter = if last_delegation.round < round.current {
0u32
} else {
last_delegation.counter
};

// each delegator should not exceed the [MaxDelegationsPerRound]
ensure!(
counter <= T::MaxDelegationsPerRound::get(),
log_and_return_error_message(format!(
"Delegator {:?} exceeded delegations per round. Allowed delegations {:?}. Confirmed delegations {:?}",
delegator_stake.owner, T::MaxDelegationsPerRound::get(), counter
))
);

// each delegator should have the min required stake
ensure!(
delegator_stake.amount >= T::MinDelegatorStake::get(),
log_and_return_error_message(format!(
"Delegator {:?} insufficient stake. Required stake: {:?}. Owned stake: {:?}",
delegator_stake.owner,
T::MinDelegatorStake::get(),
delegator_stake.amount
))
);

ensure!(
DelegatorState::<T>::get(&delegator_stake.owner).is_some(),
log_and_return_error_message(format!("Unknown delegator {:?}", delegator_stake.owner))
);

Ok(())
})
}

fn validate_stake<T: Config>() -> Result<(), &'static str> {
// the total fund has to be the sum over the first [MaxSelectedCandidates] of
// [TopCandidates].
let top_candidates = TopCandidates::<T>::get();
let top_n = MaxSelectedCandidates::<T>::get().saturated_into::<usize>();

let total_stake = TotalCollatorStake::<T>::get();

let collator_delegator_stake = top_candidates
.iter()
.take(top_n)
.fold(Zero::zero(), |acc: BalanceOf<T>, details| acc.saturating_add(details.amount));

let collator_stake = top_candidates
.iter()
.take(top_n)
.filter_map(|stake| CandidatePool::<T>::get(&stake.owner))
.fold(Zero::zero(), |acc: BalanceOf<T>, candidate| acc.saturating_add(candidate.stake));

let delegator_state = collator_delegator_stake.saturating_sub(collator_stake);

ensure!(
total_stake.collators == collator_stake,
log_and_return_error_message(format!(
"Corrupted total collator stake. Saved total stake: {:?}. Calculated stake: {:?}",
total_stake.collators, collator_stake
))
);

ensure!(
total_stake.delegators == delegator_state,
log_and_return_error_message(format!(
"Corrupted total delegator stake. Saved total stake: {:?}. Calculated stake: {:?}",
total_stake.delegators, delegator_state
))
);

Ok(())
}

fn validate_delegators<T: Config>() -> Result<(), &'static str> {
DelegatorState::<T>::iter_values().try_for_each(
|delegator_details| -> Result<(), &'static str> {
let Some(owner) = &delegator_details.owner else {
return Err("owner not found");
};

ensure!(
CandidatePool::<T>::contains_key(owner),
log_and_return_error_message(format!(
"Collator {:?} not found",
delegator_details.owner
))
);
Ok(())
},
)
}
10 changes: 6 additions & 4 deletions runtime/amplitude/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1185,12 +1185,14 @@ construct_runtime!(
Bounties: pallet_bounties::{Pallet, Call, Storage, Event<T>} = 20,
ChildBounties: pallet_child_bounties::{Pallet, Call, Storage, Event<T>} = 21,

// Collator support. The order of these 4 are important and shall not change.
Authorship: pallet_authorship::{Pallet, Storage} = 30,
Session: pallet_session::{Pallet, Call, Storage, Event, Config<T>} = 32,
// Consensus support.
// The following order MUST NOT be changed: Aura -> Session -> Staking -> Authorship -> AuraExt
// Dependencies: AuraExt on Aura, Authorship and Session on ParachainStaking
Aura: pallet_aura::{Pallet, Storage, Config<T>} = 33,
AuraExt: cumulus_pallet_aura_ext::{Pallet, Storage, Config} = 34,
Session: pallet_session::{Pallet, Call, Storage, Event, Config<T>} = 32,
ParachainStaking: parachain_staking::{Pallet, Call, Storage, Event<T>, Config<T>} = 35,
Authorship: pallet_authorship::{Pallet, Storage} = 30,
AuraExt: cumulus_pallet_aura_ext::{Pallet, Storage, Config} = 34,

// XCM helpers.
XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event<T>} = 40,
Expand Down
Loading

0 comments on commit 19dceb0

Please sign in to comment.