pallet_election_provider_e2e_test/
lib.rs1#![cfg(test)]
19
20#[allow(unexpected_cfgs)]
22mod mock;
23
24pub(crate) const LOG_TARGET: &str = "tests::e2e-epm";
25
26use frame_support::{assert_err, assert_ok};
27use mock::*;
28use pallet_timestamp::Now;
29use sp_core::Get;
30use sp_runtime::Perbill;
31
32use crate::mock::RuntimeOrigin;
33
34use pallet_election_provider_multi_phase::CurrentPhase;
35
36#[macro_export]
38macro_rules! log {
39 ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
40 log::$level!(
41 target: crate::LOG_TARGET,
42 concat!("🛠️ ", $patter) $(, $values)*
43 )
44 };
45}
46
47fn log_current_time() {
48 log!(
49 trace,
50 "block: {:?}, session: {:?}, era: {:?}, EPM phase: {:?} ts: {:?}",
51 System::block_number(),
52 Session::current_index(),
53 pallet_staking::CurrentEra::<Runtime>::get(),
54 CurrentPhase::<Runtime>::get(),
55 Now::<Runtime>::get()
56 );
57}
58
59#[test]
60fn block_progression_works() {
61 let (ext, pool_state, _) = ExtBuilder::default().build_offchainify();
62
63 execute_with(ext, || {
64 assert_eq!(active_era(), 0);
65 assert_eq!(Session::current_index(), 0);
66 assert!(CurrentPhase::<Runtime>::get().is_off());
67
68 assert!(start_next_active_era(pool_state.clone()).is_ok());
69 assert_eq!(active_era(), 1);
70 assert_eq!(Session::current_index(), <SessionsPerEra as Get<u32>>::get());
71
72 assert!(CurrentPhase::<Runtime>::get().is_off());
73
74 roll_to_epm_signed();
75 assert!(CurrentPhase::<Runtime>::get().is_signed());
76 });
77
78 let (ext, pool_state, _) = ExtBuilder::default().build_offchainify();
79
80 execute_with(ext, || {
81 assert_eq!(active_era(), 0);
82 assert_eq!(Session::current_index(), 0);
83 assert!(CurrentPhase::<Runtime>::get().is_off());
84
85 assert!(start_next_active_era_delayed_solution(pool_state).is_ok());
86 assert!(CurrentPhase::<Runtime>::get().is_emergency());
88 assert_eq!(active_era(), 0);
90 assert_eq!(Session::current_index(), 2);
92 })
93}
94
95#[test]
96fn offchainify_works() {
97 use pallet_election_provider_multi_phase::QueuedSolution;
98
99 let staking_builder = StakingExtBuilder::default();
100 let epm_builder = EpmExtBuilder::default();
101 let (ext, pool_state, _) = ExtBuilder::default()
102 .epm(epm_builder)
103 .staking(staking_builder)
104 .build_offchainify();
105
106 execute_with(ext, || {
107 for _ in 0..100 {
110 roll_one(pool_state.clone(), false);
111 let current_phase = CurrentPhase::<Runtime>::get();
112
113 assert!(
114 match QueuedSolution::<Runtime>::get() {
115 Some(_) => current_phase.is_unsigned(),
116 None => !current_phase.is_unsigned(),
117 },
118 "solution must be queued *only* in unsigned phase"
119 );
120 }
121
122 for _ in 0..100 {
124 roll_one(pool_state.clone(), true);
125 assert_eq!(
126 QueuedSolution::<Runtime>::get(),
127 None,
128 "solution must never be submitted and stored since it is delayed"
129 );
130 }
131 })
132}
133
134#[test]
135fn mass_slash_doesnt_enter_emergency_phase() {
141 let epm_builder = EpmExtBuilder::default().disable_emergency_throttling();
142 let staking_builder = StakingExtBuilder::default().validator_count(7);
143 let (mut ext, _, _) = ExtBuilder::default()
144 .epm(epm_builder)
145 .staking(staking_builder)
146 .build_offchainify();
147
148 ext.execute_with(|| {
149 assert_eq!(pallet_staking::ForceEra::<Runtime>::get(), pallet_staking::Forcing::NotForcing);
150
151 let active_set_size_before_slash = Session::validators().len();
152
153 let slashed = slash_half_the_active_set();
155
156 let active_set_size_after_slash = Session::validators().len();
157
158 assert_eq!(active_set_size_before_slash, active_set_size_after_slash);
160
161 let active_set = Session::validators();
163 let potentially_disabled = slashed
164 .into_iter()
165 .map(|d| active_set.iter().position(|a| *a == d).unwrap() as u32)
166 .collect::<Vec<_>>();
167
168 let disabled = Session::disabled_validators();
171 for d in disabled.iter() {
172 assert!(potentially_disabled.contains(d));
173 }
174
175 let disabling_limit = pallet_session::disabling::UpToLimitWithReEnablingDisablingStrategy::<
177 SLASHING_DISABLING_FACTOR,
178 >::disable_limit(active_set_size_before_slash);
179 assert!(disabled.len() == disabling_limit);
180
181 assert_eq!(pallet_staking::ForceEra::<Runtime>::get(), pallet_staking::Forcing::NotForcing);
182 });
183}
184
185#[test]
186fn continuous_slashes_below_offending_threshold() {
196 let staking_builder = StakingExtBuilder::default().validator_count(10);
197 let epm_builder = EpmExtBuilder::default().disable_emergency_throttling();
198
199 let (ext, pool_state, _) = ExtBuilder::default()
200 .epm(epm_builder)
201 .staking(staking_builder)
202 .build_offchainify();
203
204 execute_with(ext, || {
205 assert_eq!(Session::validators().len(), 10);
206 let mut active_validator_set = Session::validators();
207
208 roll_to_epm_signed();
209
210 assert!(set_minimum_election_score(500, 1000, 500).is_ok());
212
213 while active_validator_set.len() > 0 {
216 let slashed = slash_percentage(Perbill::from_percent(10));
217 assert_eq!(slashed.len(), 1);
218
219 if start_next_active_era(pool_state.clone()).is_err() {
222 assert!(CurrentPhase::<Runtime>::get().is_emergency());
223 break;
224 }
225
226 active_validator_set = Session::validators();
227
228 log!(
229 trace,
230 "slashed 10% of active validators ({:?}). After slash: {:?}",
231 slashed,
232 active_validator_set
233 );
234 }
235 });
236}
237
238#[test]
239fn ledger_consistency_active_balance_below_ed() {
248 use pallet_staking::{Error, Event};
249
250 let (ext, pool_state, _) =
251 ExtBuilder::default().staking(StakingExtBuilder::default()).build_offchainify();
252
253 execute_with(ext, || {
254 assert_eq!(Staking::ledger(11.into()).unwrap().active, 1000);
255
256 assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1000));
259
260 assert_eq!(Staking::ledger(11.into()).unwrap().active, 0);
263 assert_eq!(Staking::ledger(11.into()).unwrap().total, 1000);
264
265 assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0));
268 assert_eq!(Staking::ledger(11.into()).unwrap().total, 1000);
269
270 assert_err!(
273 Staking::reap_stash(RuntimeOrigin::signed(11), 21, 0),
274 Error::<Runtime>::FundedTarget,
275 );
276
277 assert_eq!(
279 staking_events(),
280 [Event::Chilled { stash: 11 }, Event::Unbonded { stash: 11, amount: 1000 }]
281 );
282
283 advance_eras(
286 <Runtime as pallet_staking::Config>::BondingDuration::get() as usize,
287 pool_state,
288 );
289 assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0));
290 assert!(Staking::ledger(11.into()).is_err());
291 });
292}
293
294#[test]
295fn automatic_unbonding_pools() {
302 use pallet_nomination_pools::TotalValueLocked;
303
304 let unlocking_chunks_of = |account: AccountId| -> usize {
306 Staking::ledger(sp_staking::StakingAccount::Controller(account))
307 .unwrap()
308 .unlocking
309 .len()
310 };
311
312 let (ext, pool_state, _) = ExtBuilder::default()
313 .pools(PoolsExtBuilder::default().max_unbonding(1))
314 .staking(StakingExtBuilder::default().max_unlocking(1).bonding_duration(2))
315 .build_offchainify();
316
317 execute_with(ext, || {
318 assert_eq!(<Runtime as pallet_staking::Config>::MaxUnlockingChunks::get(), 1);
319 assert_eq!(<Runtime as pallet_staking::Config>::BondingDuration::get(), 2);
320 assert_eq!(<Runtime as pallet_nomination_pools::Config>::MaxUnbonding::get(), 1);
321
322 let init_free_balance_2 = Balances::free_balance(2);
324 let init_free_balance_3 = Balances::free_balance(3);
325
326 let pool_bonded_account = Pools::generate_bonded_account(1);
327
328 assert_ok!(Pools::create(RuntimeOrigin::signed(1), 5, 1, 1, 1));
330 assert_eq!(staked_amount_for(pool_bonded_account), 5);
331
332 let init_tvl = TotalValueLocked::<Runtime>::get();
333
334 assert_ok!(Pools::join(RuntimeOrigin::signed(2), 10, 1));
336 assert_eq!(staked_amount_for(pool_bonded_account), 15);
337
338 assert_ok!(Pools::join(RuntimeOrigin::signed(3), 10, 1));
340 assert_eq!(staked_amount_for(pool_bonded_account), 25);
341
342 assert_eq!(TotalValueLocked::<Runtime>::get(), 25);
343
344 assert_eq!(unlocking_chunks_of(pool_bonded_account), 0);
346
347 assert_ok!(Pools::unbond(RuntimeOrigin::signed(2), 2, 10));
349
350 assert_eq!(staked_amount_for(pool_bonded_account), 25);
352
353 assert_eq!(unlocking_chunks_of(pool_bonded_account), 1);
355
356 assert_err!(
360 Pools::unbond(RuntimeOrigin::signed(3), 3, 10),
361 pallet_staking::Error::<Runtime>::NoMoreChunks
362 );
363
364 assert_eq!(current_era(), 0);
365
366 for _ in 0..=<Runtime as pallet_staking::Config>::BondingDuration::get() {
368 start_next_active_era(pool_state.clone()).unwrap();
369 }
370 assert_eq!(current_era(), 3);
371 System::reset_events();
372
373 let staked_before_withdraw_pool = staked_amount_for(pool_bonded_account);
374 assert_eq!(delegated_balance_for(pool_bonded_account), 5 + 10 + 10);
375
376 assert_ok!(Pools::unbond(RuntimeOrigin::signed(3), 3, 10));
379 assert_eq!(unlocking_chunks_of(pool_bonded_account), 1);
380
381 assert_eq!(
382 staking_events(),
383 [
384 pallet_staking::Event::Withdrawn { stash: pool_bonded_account, amount: 10 },
387 pallet_staking::Event::Unbonded { stash: pool_bonded_account, amount: 10 }
388 ]
389 );
390
391 assert_eq!(delegated_balance_for(pool_bonded_account), 25);
393 assert_eq!(staked_before_withdraw_pool - 10, staked_amount_for(pool_bonded_account));
395
396 assert_eq!(TotalValueLocked::<Runtime>::get(), 25 - 10);
398
399 assert_eq!(delegated_balance_for(pool_bonded_account), 25);
402 assert_eq!(staked_amount_for(pool_bonded_account), 15);
403 assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(2), 2, 10));
404 assert_eq!(delegated_balance_for(pool_bonded_account), 15);
405
406 assert_eq!(Balances::free_balance(2), 20);
407 assert_eq!(TotalValueLocked::<Runtime>::get(), 15);
408
409 assert_err!(
411 Pools::withdraw_unbonded(RuntimeOrigin::signed(3), 3, 10),
412 pallet_nomination_pools::Error::<Runtime>::CannotWithdrawAny
413 );
414
415 for _ in 0..=<Runtime as pallet_staking::Config>::BondingDuration::get() {
417 start_next_active_era(pool_state.clone()).unwrap();
418 }
419 assert_eq!(current_era(), 6);
420 System::reset_events();
421
422 assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(3), 3, 10));
423
424 assert_eq!(delegated_balance_for(pool_bonded_account), 5); assert_eq!(Balances::free_balance(2), init_free_balance_2);
427 assert_eq!(Balances::free_balance(3), init_free_balance_3);
428
429 assert_eq!(TotalValueLocked::<Runtime>::get(), init_tvl);
430 });
431}