Files
pezkuwi-subxt/substrate/frame/bags-list/src/tests.rs
T
Shawn Tabrizi 6d6f1e173b Update Pallet Bags List to use ListError (#11342)
* extract list error changes from kiz-revamp-sorted-list-providers-2-approval-stake

* some fixes

* weight -> score

* Update tests.rs

* Update tests.rs

* more fixes

* remove score updated event

* Update frame/bags-list/src/lib.rs

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
2022-05-03 23:13:58 +01:00

658 lines
19 KiB
Rust

// This file is part of Substrate.
// Copyright (C) 2021-2022 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_noop, assert_ok, assert_storage_noop, traits::IntegrityTest};
use super::*;
use frame_election_provider_support::{SortedListProvider, VoteWeight};
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::<Runtime>::get_bags(),
vec![(10, vec![1]), (20, vec![42]), (1_000, vec![2, 3, 4])]
);
// when increasing score to the level of non-existent bag
StakingMock::set_score_of(&42, 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::<Runtime>::get_bags(),
vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![42])]
);
// when decreasing score within the range of the current bag
StakingMock::set_score_of(&42, 1_001);
assert_ok!(BagsList::rebag(Origin::signed(0), 42));
// then the id does not move
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![42])]
);
// when reducing score to the level of a non-existent bag
StakingMock::set_score_of(&42, 30);
assert_ok!(BagsList::rebag(Origin::signed(0), 42));
// then a new bag is created and the id moves into it
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (30, vec![42]), (1_000, vec![2, 3, 4])]
);
// when increasing score to the level of a pre-existing bag
StakingMock::set_score_of(&42, 500);
assert_ok!(BagsList::rebag(Origin::signed(0), 42));
// then the id moves into that bag
assert_eq!(
List::<Runtime>::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::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
// when
StakingMock::set_score_of(&4, 10);
assert_ok!(BagsList::rebag(Origin::signed(0), 4));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1, 4]), (1_000, vec![2, 3])]);
assert_eq!(Bag::<Runtime>::get(1_000).unwrap(), Bag::new(Some(2), Some(3), 1_000));
// when
StakingMock::set_score_of(&3, 10);
assert_ok!(BagsList::rebag(Origin::signed(0), 3));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1, 4, 3]), (1_000, vec![2])]);
assert_eq!(Bag::<Runtime>::get(10).unwrap(), Bag::new(Some(1), Some(3), 10));
assert_eq!(Bag::<Runtime>::get(1_000).unwrap(), Bag::new(Some(2), Some(2), 1_000));
assert_eq!(get_list_as_ids(), vec![2u32, 1, 4, 3]);
// when
StakingMock::set_score_of(&2, 10);
assert_ok!(BagsList::rebag(Origin::signed(0), 2));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1, 4, 3, 2])]);
assert_eq!(Bag::<Runtime>::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
StakingMock::set_score_of(&2, 10);
assert_ok!(BagsList::rebag(Origin::signed(0), 2));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1, 2]), (1_000, vec![3, 4])]);
assert_eq!(Bag::<Runtime>::get(1_000).unwrap(), Bag::new(Some(3), Some(4), 1_000));
// when
StakingMock::set_score_of(&3, 10);
assert_ok!(BagsList::rebag(Origin::signed(0), 3));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1, 2, 3]), (1_000, vec![4])]);
assert_eq!(Bag::<Runtime>::get(1_000).unwrap(), Bag::new(Some(4), Some(4), 1_000));
// when
StakingMock::set_score_of(&4, 10);
assert_ok!(BagsList::rebag(Origin::signed(0), 4));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1, 2, 3, 4])]);
assert_eq!(Bag::<Runtime>::get(1_000), None);
});
}
#[test]
fn wrong_rebag_is_noop() {
ExtBuilder::default().build_and_execute(|| {
let node_3 = list::Node::<Runtime>::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::<Runtime>::get_bags(), vec![(VoteWeight::MAX, vec![1, 2, 3, 4])]);
// any insertion goes there as well.
assert_ok!(List::<Runtime>::insert(5, 999));
assert_ok!(List::<Runtime>::insert(6, 0));
assert_eq!(
List::<Runtime>::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()));
})
}
#[test]
fn put_in_front_of_two_node_bag_heavier_is_tail() {
ExtBuilder::default()
.skip_genesis_ids()
.add_ids(vec![(10, 15), (11, 16)])
.build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(20, vec![10, 11])]);
// when
assert_ok!(BagsList::put_in_front_of(Origin::signed(11), 10));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(20, vec![11, 10])]);
});
}
#[test]
fn put_in_front_of_two_node_bag_heavier_is_head() {
ExtBuilder::default()
.skip_genesis_ids()
.add_ids(vec![(11, 16), (10, 15)])
.build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(20, vec![11, 10])]);
// when
assert_ok!(BagsList::put_in_front_of(Origin::signed(11), 10));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(20, vec![11, 10])]);
});
}
#[test]
fn put_in_front_of_non_terminal_nodes_heavier_behind() {
ExtBuilder::default().add_ids(vec![(5, 1_000)]).build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]);
StakingMock::set_score_of(&3, 999);
// when
assert_ok!(BagsList::put_in_front_of(Origin::signed(4), 3));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 4, 3, 5])]);
});
}
#[test]
fn put_in_front_of_non_terminal_nodes_heavier_in_front() {
ExtBuilder::default()
.add_ids(vec![(5, 1_000), (6, 1_000)])
.build_and_execute(|| {
// given
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5, 6])]
);
StakingMock::set_score_of(&5, 999);
// when
assert_ok!(BagsList::put_in_front_of(Origin::signed(3), 5));
// then
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (1_000, vec![2, 4, 3, 5, 6])]
);
});
}
#[test]
fn put_in_front_of_lighter_is_head_heavier_is_non_terminal() {
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
StakingMock::set_score_of(&2, 999);
// when
assert_ok!(BagsList::put_in_front_of(Origin::signed(3), 2));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 2, 4])]);
});
}
#[test]
fn put_in_front_of_heavier_is_tail_lighter_is_non_terminal() {
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
StakingMock::set_score_of(&3, 999);
// when
assert_ok!(BagsList::put_in_front_of(Origin::signed(4), 3));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 4, 3])]);
});
}
#[test]
fn put_in_front_of_heavier_is_tail_lighter_is_head() {
ExtBuilder::default().add_ids(vec![(5, 1_000)]).build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]);
StakingMock::set_score_of(&2, 999);
// when
assert_ok!(BagsList::put_in_front_of(Origin::signed(5), 2));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![5, 2, 3, 4])]);
});
}
#[test]
fn put_in_front_of_heavier_is_head_lighter_is_not_terminal() {
ExtBuilder::default().add_ids(vec![(5, 1_000)]).build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]);
StakingMock::set_score_of(&4, 999);
// when
BagsList::put_in_front_of(Origin::signed(2), 4).unwrap();
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 2, 4, 5])]);
});
}
#[test]
fn put_in_front_of_lighter_is_tail_heavier_is_not_terminal() {
ExtBuilder::default().add_ids(vec![(5, 900)]).build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]);
// when
BagsList::put_in_front_of(Origin::signed(3), 5).unwrap();
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 4, 3, 5])]);
});
}
#[test]
fn put_in_front_of_lighter_is_tail_heavier_is_head() {
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
StakingMock::set_score_of(&4, 999);
// when
BagsList::put_in_front_of(Origin::signed(2), 4).unwrap();
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 2, 4])]);
});
}
#[test]
fn put_in_front_of_errors_if_heavier_is_less_than_lighter() {
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
StakingMock::set_score_of(&3, 999);
// then
assert_noop!(
BagsList::put_in_front_of(Origin::signed(3), 2),
crate::pallet::Error::<Runtime>::List(ListError::NotHeavier)
);
});
}
#[test]
fn put_in_front_of_errors_if_heavier_is_equal_weight_to_lighter() {
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
// then
assert_noop!(
BagsList::put_in_front_of(Origin::signed(3), 4),
crate::pallet::Error::<Runtime>::List(ListError::NotHeavier)
);
});
}
#[test]
fn put_in_front_of_errors_if_nodes_not_found() {
// `heavier` not found
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
assert!(!ListNodes::<Runtime>::contains_key(5));
// then
assert_noop!(
BagsList::put_in_front_of(Origin::signed(5), 4),
crate::pallet::Error::<Runtime>::List(ListError::NodeNotFound)
);
});
// `lighter` not found
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
assert!(!ListNodes::<Runtime>::contains_key(5));
// then
assert_noop!(
BagsList::put_in_front_of(Origin::signed(4), 5),
crate::pallet::Error::<Runtime>::List(ListError::NodeNotFound)
);
});
}
#[test]
fn put_in_front_of_errors_if_nodes_not_in_same_bag() {
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
// then
assert_noop!(
BagsList::put_in_front_of(Origin::signed(4), 1),
crate::pallet::Error::<Runtime>::List(ListError::NotInSameBag)
);
});
}
}
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 iter_from_works() {
ExtBuilder::default().add_ids(vec![(5, 5), (6, 15)]).build_and_execute(|| {
// given
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1, 5]), (20, vec![6]), (1000, vec![2, 3, 4])]
);
assert_eq!(BagsList::iter_from(&2).unwrap().collect::<Vec<_>>(), vec![3, 4, 6, 1, 5]);
assert_eq!(BagsList::iter_from(&3).unwrap().collect::<Vec<_>>(), vec![4, 6, 1, 5]);
assert_eq!(BagsList::iter_from(&4).unwrap().collect::<Vec<_>>(), vec![6, 1, 5]);
assert_eq!(BagsList::iter_from(&6).unwrap().collect::<Vec<_>>(), vec![1, 5]);
assert_eq!(BagsList::iter_from(&1).unwrap().collect::<Vec<_>>(), vec![5]);
assert!(BagsList::iter_from(&5).unwrap().collect::<Vec<_>>().is_empty());
assert!(BagsList::iter_from(&7).is_err());
assert_storage_noop!(assert!(BagsList::iter_from(&8).is_err()));
});
}
#[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).unwrap();
// then the count goes down
assert_eq!(BagsList::count(), 4);
// when updating
assert_noop!(BagsList::on_update(&201, VoteWeight::MAX), ListError::NodeNotFound);
// 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::<Runtime>::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<_>>(), 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::<Runtime>::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<_>>(), 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(),
ListError::Duplicate
));
});
}
#[test]
fn on_update_works() {
ExtBuilder::default().add_ids(vec![(42, 20)]).build_and_execute(|| {
// given
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (20, vec![42]), (1_000, vec![2, 3, 4])]
);
assert_eq!(BagsList::count(), 5);
// when increasing score to the level of non-existent bag
BagsList::on_update(&42, 2_000).unwrap();
// then the bag is created with the id in it,
assert_eq!(
List::<Runtime>::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<_>>(), vec![42, 2, 3, 4, 1]);
// when decreasing score within the range of the current bag
BagsList::on_update(&42, 1_001).unwrap();
// then the id does not change bags,
assert_eq!(
List::<Runtime>::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<_>>(), vec![42, 2, 3, 4, 1]);
// when increasing score to the level of a non-existent bag with the max threshold
BagsList::on_update(&42, VoteWeight::MAX).unwrap();
// the the new bag is created with the id in it,
assert_eq!(
List::<Runtime>::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<_>>(), vec![42, 2, 3, 4, 1]);
// when decreasing the score to a pre-existing bag
BagsList::on_update(&42, 1_000).unwrap();
// then id is moved to the correct bag (as the last member),
assert_eq!(
List::<Runtime>::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<_>>(), 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::<Runtime>::contains_key(id));
assert_eq!(BagsList::count(), counter);
assert_eq!(ListNodes::<Runtime>::count(), counter);
assert_eq!(ListNodes::<Runtime>::iter().count() as u32, counter);
};
ExtBuilder::default().build_and_execute(|| {
// it is a noop removing a non-existent id
assert!(!ListNodes::<Runtime>::contains_key(42));
assert_noop!(BagsList::on_remove(&42), ListError::NodeNotFound);
// when removing a node from a bag with multiple nodes
BagsList::on_remove(&2).unwrap();
// then
assert_eq!(get_list_as_ids(), vec![3, 4, 1]);
assert_eq!(List::<Runtime>::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).unwrap();
// then
assert_eq!(get_list_as_ids(), vec![3, 4]);
assert_eq!(List::<Runtime>::get_bags(), vec![(1_000, vec![3, 4])]);
ensure_left(1, 2);
// when removing all remaining ids
BagsList::on_remove(&4).unwrap();
assert_eq!(get_list_as_ids(), vec![3]);
ensure_left(4, 1);
BagsList::on_remove(&3).unwrap();
// then the storage is completely cleaned up
assert_eq!(get_list_as_ids(), Vec::<AccountId>::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)));
})
}
}