use crate::{
session_rotation::{Eras, Rotator},
tests::session_mock::{CurrentIndex, Timestamp},
};
use super::*;
#[test]
fn forcing_force_none() {
ExtBuilder::default().build_and_execute(|| {
ForceEra::<T>::put(Forcing::ForceNone);
Session::roll_to_next_session();
assert_eq!(
staking_events_since_last_call(),
vec![Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 1 }]
);
Session::roll_to_next_session();
assert_eq!(
staking_events_since_last_call(),
vec![Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 1 }]
);
Session::roll_to_next_session();
assert_eq!(
staking_events_since_last_call(),
vec![Event::SessionRotated { starting_session: 6, active_era: 1, planned_era: 1 }]
);
Session::roll_to_next_session();
assert_eq!(
staking_events_since_last_call(),
vec![Event::SessionRotated { starting_session: 7, active_era: 1, planned_era: 1 }]
);
Session::roll_to_next_session();
assert_eq!(
staking_events_since_last_call(),
vec![Event::SessionRotated { starting_session: 8, active_era: 1, planned_era: 1 }]
);
});
}
#[test]
fn forcing_no_forcing_default() {
ExtBuilder::default().build_and_execute(|| {
ForceEra::<T>::put(Forcing::NotForcing);
Session::roll_until_active_era(2);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 },
Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 7500 },
Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 }
]
);
});
}
#[test]
fn forcing_force_always() {
ExtBuilder::default()
.session_per_era(6)
.no_flush_events()
.build_and_execute(|| {
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 1, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 2, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 3, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 4, active_era: 0, planned_era: 1 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 5, active_era: 0, planned_era: 1 },
Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 15000 },
Event::SessionRotated { starting_session: 6, active_era: 1, planned_era: 1 }
]
);
ForceEra::<T>::put(Forcing::ForceAlways);
Session::roll_until_active_era(2);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 7, active_era: 1, planned_era: 2 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 8, active_era: 1, planned_era: 2 },
Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 7500 },
Event::SessionRotated { starting_session: 9, active_era: 2, planned_era: 3 }
]
);
});
}
#[test]
fn forcing_force_new() {
ExtBuilder::default()
.session_per_era(6)
.no_flush_events()
.build_and_execute(|| {
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 1, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 2, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 3, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 4, active_era: 0, planned_era: 1 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 5, active_era: 0, planned_era: 1 },
Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 15000 },
Event::SessionRotated { starting_session: 6, active_era: 1, planned_era: 1 }
]
);
ForceEra::<T>::put(Forcing::ForceNew);
Session::roll_until_active_era(2);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 7, active_era: 1, planned_era: 2 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 8, active_era: 1, planned_era: 2 },
Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 7500 },
Event::SessionRotated { starting_session: 9, active_era: 2, planned_era: 2 }
]
);
Session::roll_until_active_era(3);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 10, active_era: 2, planned_era: 2 },
Event::SessionRotated { starting_session: 11, active_era: 2, planned_era: 2 },
Event::SessionRotated { starting_session: 12, active_era: 2, planned_era: 2 },
Event::SessionRotated { starting_session: 13, active_era: 2, planned_era: 3 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 14, active_era: 2, planned_era: 3 },
Event::EraPaid { era_index: 2, validator_payout: 15000, remainder: 15000 },
Event::SessionRotated { starting_session: 15, active_era: 3, planned_era: 3 }
]
);
});
}
#[test]
fn activation_timestamp_when_no_planned_era() {
ExtBuilder::default().session_per_era(6).build_and_execute(|| {
Session::roll_until_active_era(2);
let current_index = CurrentIndex::get();
let _ = staking_events_since_last_call();
assert_eq!(Rotator::<T>::active_era(), 2);
assert_eq!(Rotator::<T>::planned_era(), 2);
<Staking as pallet_staking_async_rc_client::AHStakingInterface>::on_relay_session_report(
pallet_staking_async_rc_client::SessionReport::new_terminal(
current_index,
vec![],
Some((Timestamp::get() + time_per_session(), 3)),
),
);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::Unexpected(UnexpectedKind::UnknownValidatorActivation),
Event::SessionRotated {
starting_session: current_index + 1,
active_era: 2,
planned_era: 2
}
]
);
});
}
#[test]
#[should_panic]
fn activation_timestamp_when_era_planning_not_complete() {
todo!("what if we receive an activation timestamp when the era planning (election) is not complete?");
}
#[test]
fn max_era_duration_safety_guard() {
ExtBuilder::default().build_and_execute(|| {
let ideal_era_payout = total_payout_for(time_per_era());
let ideal_treasury_payout = RemainderRatio::get() * ideal_era_payout;
let ideal_validator_payout = ideal_era_payout - ideal_treasury_payout;
let max_validator_payout = 7 * ideal_validator_payout;
let max_treasury_payout = 7 * ideal_treasury_payout;
assert_eq!(ideal_treasury_payout, 7500);
assert_eq!(ideal_validator_payout, 7500);
assert_eq!(max_treasury_payout, 52500);
assert_eq!(max_validator_payout, 52500);
Session::roll_until_active_era(2);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 },
Event::EraPaid {
era_index: 1,
validator_payout: ideal_validator_payout,
remainder: ideal_treasury_payout
},
Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 }
]
);
Timestamp::set(Timestamp::get() + 2 * MaxEraDuration::get());
Session::roll_until_active_era(3);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 7, active_era: 2, planned_era: 3 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 8, active_era: 2, planned_era: 3 },
Event::Unexpected(UnexpectedKind::EraDurationBoundExceeded),
Event::EraPaid {
era_index: 2,
validator_payout: max_validator_payout,
remainder: max_treasury_payout
},
Event::SessionRotated { starting_session: 9, active_era: 3, planned_era: 3 }
]
);
});
}
#[test]
fn era_cleanup_history_depth_works_with_prune_era_step_extrinsic() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(active_era(), 1);
Session::roll_until_active_era(HistoryDepth::get() - 1);
assert!(matches!(
&staking_events_since_last_call()[..],
&[
..,
Event::SessionRotated { starting_session: 236, active_era: 78, planned_era: 79 },
Event::EraPaid { era_index: 78, validator_payout: 7500, remainder: 7500 },
Event::SessionRotated { starting_session: 237, active_era: 79, planned_era: 79 }
]
));
assert_ok!(Eras::<T>::era_fully_present(1));
assert_ok!(Eras::<T>::era_fully_present(2));
assert_ok!(Eras::<T>::era_fully_present(HistoryDepth::get() - 1));
Session::roll_until_active_era(HistoryDepth::get());
assert_ok!(Eras::<T>::era_fully_present(1));
assert_ok!(Eras::<T>::era_fully_present(2));
assert_ok!(Eras::<T>::era_fully_present(HistoryDepth::get()));
Session::roll_until_active_era(HistoryDepth::get() + 1);
assert_ok!(Eras::<T>::era_fully_present(1));
assert_ok!(Eras::<T>::era_fully_present(2));
assert_ok!(Eras::<T>::era_fully_present(HistoryDepth::get() + 1));
assert!(matches!(
&staking_events_since_last_call()[..],
&[
..,
Event::EraPaid { era_index: 80, validator_payout: 7500, remainder: 7500 },
Event::SessionRotated { starting_session: 243, active_era: 81, planned_era: 81 }
]
));
Session::roll_until_active_era(HistoryDepth::get() + 2);
assert_ok!(Eras::<T>::era_fully_present(1)); assert_ok!(Eras::<T>::era_fully_present(2));
assert_ok!(Eras::<T>::era_fully_present(3));
assert_ok!(Eras::<T>::era_fully_present(HistoryDepth::get() + 2));
assert!(matches!(
&staking_events_since_last_call()[..],
&[
..,
Event::EraPaid { era_index: 81, validator_payout: 7500, remainder: 7500 },
Event::SessionRotated { starting_session: 246, active_era: 82, planned_era: 82 }
]
));
assert_noop!(
Staking::prune_era_step(RuntimeOrigin::signed(99), 2),
Error::<T>::EraNotPrunable
);
assert_noop!(
Staking::prune_era_step(RuntimeOrigin::signed(99), HistoryDepth::get() + 2),
Error::<T>::EraNotPrunable
);
use crate::PruningStep::*;
let steps_order = [
ErasStakersPaged,
ErasStakersOverview,
ErasValidatorPrefs,
ClaimedRewards,
ErasValidatorReward,
ErasRewardPoints,
SingleEntryCleanups,
ValidatorSlashInEra,
];
let _ = staking_events_since_last_call();
for expected_step in steps_order.iter() {
loop {
let current_state = EraPruningState::<T>::get(1)
.expect("Era 1 should be marked for pruning at this point");
assert_eq!(
current_state, *expected_step,
"Expected to be in step {:?} but was in {:?}",
expected_step, current_state
);
let result = Staking::prune_era_step(RuntimeOrigin::signed(99), 1);
assert_ok!(&result);
let post_info = result.unwrap();
assert_eq!(
post_info.pays_fee,
frame_support::dispatch::Pays::No,
"Should return Pays::No when work is done for step {:?}",
expected_step
);
assert!(
post_info.actual_weight.is_some(),
"Should report actual weight for {:?}",
expected_step
);
let actual_weight = post_info.actual_weight.unwrap();
assert!(
actual_weight.ref_time() > 0,
"Should report non-zero ref_time for {:?}",
expected_step
);
let new_state = EraPruningState::<T>::get(1).unwrap_or(ErasStakersPaged);
if new_state != current_state {
break; }
}
match expected_step {
ErasStakersPaged => assert_eq!(
crate::ErasStakersPaged::<T>::iter_prefix_values((1,)).count(),
0,
"{expected_step:?} should be empty after completing step"
),
ErasStakersOverview => assert_eq!(
crate::ErasStakersOverview::<T>::iter_prefix_values(1).count(),
0,
"{expected_step:?} should be empty after completing step"
),
ErasValidatorPrefs => assert_eq!(
crate::ErasValidatorPrefs::<T>::iter_prefix_values(1).count(),
0,
"{expected_step:?} should be empty after completing step"
),
ClaimedRewards => assert_eq!(
crate::ClaimedRewards::<T>::iter_prefix_values(1).count(),
0,
"{expected_step:?} should be empty after completing step"
),
ErasValidatorReward => {
assert!(
!crate::ErasValidatorReward::<T>::contains_key(1),
"{expected_step:?} should be empty after completing step"
);
},
ErasRewardPoints => {
assert!(
!crate::ErasRewardPoints::<T>::contains_key(1),
"{expected_step:?} should be empty after completing step"
);
},
SingleEntryCleanups => {
assert!(
!crate::ErasTotalStake::<T>::contains_key(1),
"{expected_step:?} should be empty after completing step"
);
assert!(
!crate::ErasNominatorsSlashable::<T>::contains_key(1),
"ErasNominatorsSlashable should be empty after completing SingleEntryCleanups step"
);
},
ValidatorSlashInEra => assert_eq!(
crate::ValidatorSlashInEra::<T>::iter_prefix_values(1).count(),
0,
"{expected_step:?} should be empty after completing step"
),
}
}
assert!(
EraPruningState::<T>::get(1).is_none(),
"EraPruningState should be removed after final step"
);
assert!(matches!(&staking_events_since_last_call()[..], &[Event::EraPruned { index: 1 }]));
let result = Staking::prune_era_step(RuntimeOrigin::signed(99), 1);
assert_noop!(result, Error::<T>::EraNotPrunable);
assert_ok!(Eras::<T>::era_absent(1));
assert_ok!(Eras::<T>::era_fully_present(2));
let result = Staking::prune_era_step(RuntimeOrigin::signed(99), 1);
assert_noop!(result, Error::<T>::EraNotPrunable);
});
}
#[test]
fn progress_many_eras_with_try_state() {
ExtBuilder::default().build_and_execute(|| {
Session::roll_until_active_era_with(
HistoryDepth::get().max(BondingDuration::get()) + 2,
|| {
Staking::do_try_state(System::block_number()).unwrap();
},
);
})
}
mod inflation {
use super::*;
#[test]
fn max_staked_rewards_default_not_set_works() {
ExtBuilder::default().build_and_execute(|| {
let default_stakers_payout = validator_payout_for(time_per_era());
assert!(default_stakers_payout > 0);
assert_eq!(<MaxStakedRewards<Test>>::get(), None);
Session::roll_until_active_era(2);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 },
Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 7500 },
Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 }
]
);
assert_eq!(ErasValidatorReward::<Test>::get(0).unwrap(), default_stakers_payout);
})
}
#[test]
fn max_staked_rewards_default_equal_100() {
ExtBuilder::default().build_and_execute(|| {
let default_stakers_payout = validator_payout_for(time_per_era());
assert!(default_stakers_payout > 0);
<MaxStakedRewards<Test>>::set(Some(Percent::from_parts(100)));
Session::roll_until_active_era(2);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 },
Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 7500 },
Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 }
]
);
assert_eq!(ErasValidatorReward::<Test>::get(0).unwrap(), default_stakers_payout);
});
}
#[test]
fn max_staked_rewards_works() {
ExtBuilder::default().nominate(true).build_and_execute(|| {
assert_ok!(Staking::set_staking_configs(
RuntimeOrigin::root(),
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Set(Percent::from_percent(10)),
ConfigOp::Noop,
));
assert_eq!(<MaxStakedRewards<Test>>::get(), Some(Percent::from_percent(10)));
assert_eq!(Session::validators().len(), 2);
assert!(Session::validators().contains(&11) & Session::validators().contains(&21));
assert_eq!(RewardRemainderUnbalanced::get(), 0);
Session::roll_until_active_era(2);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 },
Event::EraPaid { era_index: 1, validator_payout: 1500, remainder: 13500 },
Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 }
]
);
let treasury_payout = RewardRemainderUnbalanced::get();
let validators_payout = ErasValidatorReward::<Test>::get(1).unwrap();
let total_payout = treasury_payout + validators_payout;
assert_eq!(total_payout, total_payout_for(time_per_era()));
assert_eq!(validators_payout, Percent::from_percent(10) * total_payout);
assert_eq!(treasury_payout, Percent::from_percent(90) * total_payout);
})
}
}