// This file is part of Substrate. // Copyright (C) 2021 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use frame_support::{assert_ok, assert_storage_noop, traits::IntegrityTest}; use super::*; use frame_election_provider_support::SortedListProvider; use list::Bag; use mock::{test_utils::*, *}; mod pallet { use super::*; #[test] fn rebag_works() { ExtBuilder::default().add_ids(vec![(42, 20)]).build_and_execute(|| { // given assert_eq!( List::::get_bags(), vec![(10, vec![1]), (20, vec![42]), (1_000, vec![2, 3, 4])] ); // when increasing vote weight to the level of non-existent bag NextVoteWeight::set(2_000); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); // then a new bag is created and the id moves into it assert_eq!( List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![42])] ); // when decreasing weight within the range of the current bag NextVoteWeight::set(1001); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); // then the id does not move assert_eq!( List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![42])] ); // when reducing weight to the level of a non-existent bag NextVoteWeight::set(30); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); // then a new bag is created and the id moves into it assert_eq!( List::::get_bags(), vec![(10, vec![1]), (30, vec![42]), (1_000, vec![2, 3, 4])] ); // when increasing weight to the level of a pre-existing bag NextVoteWeight::set(500); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); // then the id moves into that bag assert_eq!( List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 42])] ); }); } // Rebagging the tail of a bag results in the old bag having a new tail and an overall correct // state. #[test] fn rebag_tail_works() { ExtBuilder::default().build_and_execute(|| { // given assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // when NextVoteWeight::set(10); assert_ok!(BagsList::rebag(Origin::signed(0), 4)); // then assert_eq!(List::::get_bags(), vec![(10, vec![1, 4]), (1_000, vec![2, 3])]); assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(Some(2), Some(3), 1_000)); // when assert_ok!(BagsList::rebag(Origin::signed(0), 3)); // then assert_eq!(List::::get_bags(), vec![(10, vec![1, 4, 3]), (1_000, vec![2])]); assert_eq!(Bag::::get(10).unwrap(), Bag::new(Some(1), Some(3), 10)); assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(Some(2), Some(2), 1_000)); assert_eq!(get_list_as_ids(), vec![2u32, 1, 4, 3]); // when assert_ok!(BagsList::rebag(Origin::signed(0), 2)); // then assert_eq!(List::::get_bags(), vec![(10, vec![1, 4, 3, 2])]); assert_eq!(Bag::::get(1_000), None); }); } // Rebagging the head of a bag results in the old bag having a new head and an overall correct // state. #[test] fn rebag_head_works() { ExtBuilder::default().build_and_execute(|| { // when NextVoteWeight::set(10); assert_ok!(BagsList::rebag(Origin::signed(0), 2)); // then assert_eq!(List::::get_bags(), vec![(10, vec![1, 2]), (1_000, vec![3, 4])]); assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(Some(3), Some(4), 1_000)); // when assert_ok!(BagsList::rebag(Origin::signed(0), 3)); // then assert_eq!(List::::get_bags(), vec![(10, vec![1, 2, 3]), (1_000, vec![4])]); assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(Some(4), Some(4), 1_000)); // when assert_ok!(BagsList::rebag(Origin::signed(0), 4)); // then assert_eq!(List::::get_bags(), vec![(10, vec![1, 2, 3, 4])]); assert_eq!(Bag::::get(1_000), None); }); } #[test] fn wrong_rebag_is_noop() { ExtBuilder::default().build_and_execute(|| { let node_3 = list::Node::::get(&3).unwrap(); // when account 3 is _not_ misplaced with weight 500 NextVoteWeight::set(500); assert!(!node_3.is_misplaced(500)); // then calling rebag on account 3 with weight 500 is a noop assert_storage_noop!(assert_eq!(BagsList::rebag(Origin::signed(0), 3), Ok(()))); // when account 42 is not in the list assert!(!BagsList::contains(&42)); // then rebag-ing account 42 is a noop assert_storage_noop!(assert_eq!(BagsList::rebag(Origin::signed(0), 42), Ok(()))); }); } #[test] #[should_panic = "thresholds must strictly increase, and have no duplicates"] fn duplicate_in_bags_threshold_panics() { const DUPE_THRESH: &[VoteWeight; 4] = &[10, 20, 30, 30]; BagThresholds::set(DUPE_THRESH); BagsList::integrity_test(); } #[test] #[should_panic = "thresholds must strictly increase, and have no duplicates"] fn decreasing_in_bags_threshold_panics() { const DECREASING_THRESH: &[VoteWeight; 4] = &[10, 30, 20, 40]; BagThresholds::set(DECREASING_THRESH); BagsList::integrity_test(); } #[test] fn empty_threshold_works() { BagThresholds::set(Default::default()); // which is the same as passing `()` to `Get<_>`. ExtBuilder::default().build_and_execute(|| { // everyone in the same bag. assert_eq!(List::::get_bags(), vec![(VoteWeight::MAX, vec![1, 2, 3, 4])]); // any insertion goes there as well. assert_ok!(List::::insert(5, 999)); assert_ok!(List::::insert(6, 0)); assert_eq!( List::::get_bags(), vec![(VoteWeight::MAX, vec![1, 2, 3, 4, 5, 6])] ); // any rebag is noop. assert_storage_noop!(assert!(BagsList::rebag(Origin::signed(0), 1).is_ok())); assert_storage_noop!(assert!(BagsList::rebag(Origin::signed(0), 10).is_ok())); }) } } mod sorted_list_provider { use super::*; #[test] fn iter_works() { ExtBuilder::default().build_and_execute(|| { let expected = vec![2, 3, 4, 1]; for (i, id) in BagsList::iter().enumerate() { assert_eq!(id, expected[i]) } }); } #[test] fn count_works() { ExtBuilder::default().build_and_execute(|| { // given assert_eq!(BagsList::count(), 4); // when inserting assert_ok!(BagsList::on_insert(201, 0)); // then the count goes up assert_eq!(BagsList::count(), 5); // when removing BagsList::on_remove(&201); // then the count goes down assert_eq!(BagsList::count(), 4); // when updating BagsList::on_update(&201, VoteWeight::MAX); // then the count stays the same assert_eq!(BagsList::count(), 4); }); } #[test] fn on_insert_works() { ExtBuilder::default().build_and_execute(|| { // when assert_ok!(BagsList::on_insert(6, 1_000)); // then the bags assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 6])]); // and list correctly include the new id, assert_eq!(BagsList::iter().collect::>(), vec![2, 3, 4, 6, 1]); // and the count is incremented. assert_eq!(BagsList::count(), 5); // when assert_ok!(BagsList::on_insert(7, 1_001)); // then the bags assert_eq!( List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 6]), (2_000, vec![7])] ); // and list correctly include the new id, assert_eq!(BagsList::iter().collect::>(), vec![7, 2, 3, 4, 6, 1]); // and the count is incremented. assert_eq!(BagsList::count(), 6); }) } #[test] fn on_insert_errors_with_duplicate_id() { ExtBuilder::default().build_and_execute(|| { // given assert!(get_list_as_ids().contains(&3)); // then assert_storage_noop!(assert_eq!( BagsList::on_insert(3, 20).unwrap_err(), Error::Duplicate )); }); } #[test] fn on_update_works() { ExtBuilder::default().add_ids(vec![(42, 20)]).build_and_execute(|| { // given assert_eq!( List::::get_bags(), vec![(10, vec![1]), (20, vec![42]), (1_000, vec![2, 3, 4])] ); assert_eq!(BagsList::count(), 5); // when increasing weight to the level of non-existent bag BagsList::on_update(&42, 2_000); // then the bag is created with the id in it, assert_eq!( List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2000, vec![42])] ); // and the id position is updated in the list. assert_eq!(BagsList::iter().collect::>(), vec![42, 2, 3, 4, 1]); // when decreasing weight within the range of the current bag BagsList::on_update(&42, 1_001); // then the id does not change bags, assert_eq!( List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2000, vec![42])] ); // or change position in the list. assert_eq!(BagsList::iter().collect::>(), vec![42, 2, 3, 4, 1]); // when increasing weight to the level of a non-existent bag with the max threshold BagsList::on_update(&42, VoteWeight::MAX); // the the new bag is created with the id in it, assert_eq!( List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (VoteWeight::MAX, vec![42])] ); // and the id position is updated in the list. assert_eq!(BagsList::iter().collect::>(), vec![42, 2, 3, 4, 1]); // when decreasing the weight to a pre-existing bag BagsList::on_update(&42, 1_000); // then id is moved to the correct bag (as the last member), assert_eq!( List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 42])] ); // and the id position is updated in the list. assert_eq!(BagsList::iter().collect::>(), vec![2, 3, 4, 42, 1]); // since we have only called on_update, the `count` has not changed. assert_eq!(BagsList::count(), 5); }); } #[test] fn on_remove_works() { let ensure_left = |id, counter| { assert!(!ListNodes::::contains_key(id)); assert_eq!(BagsList::count(), counter); assert_eq!(CounterForListNodes::::get(), counter); assert_eq!(ListNodes::::iter().count() as u32, counter); }; ExtBuilder::default().build_and_execute(|| { // it is a noop removing a non-existent id assert!(!ListNodes::::contains_key(42)); assert_storage_noop!(BagsList::on_remove(&42)); // when removing a node from a bag with multiple nodes BagsList::on_remove(&2); // then assert_eq!(get_list_as_ids(), vec![3, 4, 1]); assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 4])]); ensure_left(2, 3); // when removing a node from a bag with only one node BagsList::on_remove(&1); // then assert_eq!(get_list_as_ids(), vec![3, 4]); assert_eq!(List::::get_bags(), vec![(1_000, vec![3, 4])]); ensure_left(1, 2); // when removing all remaining ids BagsList::on_remove(&4); assert_eq!(get_list_as_ids(), vec![3]); ensure_left(4, 1); BagsList::on_remove(&3); // then the storage is completely cleaned up assert_eq!(get_list_as_ids(), Vec::::new()); ensure_left(3, 0); }); } #[test] fn contains_works() { ExtBuilder::default().build_and_execute(|| { assert!(GENESIS_IDS.iter().all(|(id, _)| BagsList::contains(id))); let non_existent_ids = vec![&42, &666, &13]; assert!(non_existent_ids.iter().all(|id| !BagsList::contains(id))); }) } }