Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Update Pallet Bags List to use ListError #11342

Merged
merged 9 commits into from
May 3, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 26 additions & 17 deletions frame/bags-list/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,17 +189,21 @@ pub mod pallet {
pub enum Event<T: Config<I>, I: 'static = ()> {
/// Moved an account from one bag to another.
Rebagged { who: T::AccountId, from: T::Score, to: T::Score },
/// Updated the score of some account to the given amount.
ScoreUpdated { who: T::AccountId, new_score: T::Score },
}

#[pallet::error]
#[cfg_attr(test, derive(PartialEq))]
pub enum Error<T, I = ()> {
/// Attempted to place node in front of a node in another bag.
NotInSameBag,
/// Id not found in list.
IdNotFound,
/// An Id does not have a greater score than another Id.
NotHeavier,
/// A error in the list interface implementation.
List(ListError),
}

impl<T, I> From<ListError> for Error<T, I> {
fn from(t: ListError) -> Self {
Error::<T, I>::List(t)
}
}

#[pallet::call]
Expand Down Expand Up @@ -231,7 +235,9 @@ pub mod pallet {
#[pallet::weight(T::WeightInfo::put_in_front_of())]
pub fn put_in_front_of(origin: OriginFor<T>, lighter: T::AccountId) -> DispatchResult {
let heavier = ensure_signed(origin)?;
List::<T, I>::put_in_front_of(&lighter, &heavier).map_err(Into::into)
List::<T, I>::put_in_front_of(&lighter, &heavier)
.map_err::<Error<T, I>, _>(Into::into)
.map_err::<DispatchError, _>(Into::into)
kianenigma marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand All @@ -250,16 +256,19 @@ pub mod pallet {
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Move an account from one bag to another, depositing an event on success.
///
/// If the account changed bags, returns `Some((from, to))`.
pub fn do_rebag(account: &T::AccountId, new_weight: T::Score) -> Option<(T::Score, T::Score)> {
// if no voter at that node, don't do anything.
// the caller just wasted the fee to call this.
let maybe_movement = list::Node::<T, I>::get(account)
.and_then(|node| List::update_position_for(node, new_weight));
/// If the account changed bags, returns `Ok((from, to))`.
shawntabrizi marked this conversation as resolved.
Show resolved Hide resolved
pub fn do_rebag(
account: &T::AccountId,
new_score: T::Score,
) -> Result<Option<(T::Score, T::Score)>, ListError> {
// If no voter at that node, don't do anything. the caller just wasted the fee to call this.
let node = list::Node::<T, I>::get(&account).ok_or(ListError::NodeNotFound)?;
let maybe_movement = List::update_position_for(node, new_score);
if let Some((from, to)) = maybe_movement {
Self::deposit_event(Event::<T, I>::Rebagged { who: account.clone(), from, to });
};
maybe_movement
Self::deposit_event(Event::<T, I>::ScoreUpdated { who: account.clone(), new_score });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be clear, this event is currently non-existent, because we don't have the notion of a "stored score" yet. That should come in your next PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably this event is being emitted in the wrong place then...

Ok(maybe_movement)
}

/// Equivalent to `ListBags::get`, but public. Useful for tests in outside of this crate.
Expand Down Expand Up @@ -296,11 +305,11 @@ impl<T: Config<I>, I: 'static> SortedListProvider<T::AccountId> for Pallet<T, I>
List::<T, I>::insert(id, score)
}

fn on_update(id: &T::AccountId, new_score: T::Score) {
Pallet::<T, I>::do_rebag(id, new_score);
fn on_update(id: &T::AccountId, new_score: T::Score) -> Result<(), ListError> {
Pallet::<T, I>::do_rebag(id, new_score).map(|_| ())
KiChjang marked this conversation as resolved.
Show resolved Hide resolved
}

fn on_remove(id: &T::AccountId) {
fn on_remove(id: &T::AccountId) -> Result<(), ListError> {
List::<T, I>::remove(id)
}

Expand Down
39 changes: 25 additions & 14 deletions frame/bags-list/src/list/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
use crate::Config;
use codec::{Decode, Encode, MaxEncodedLen};
use frame_election_provider_support::ScoreProvider;
use frame_support::{traits::Get, DefaultNoBound};
use frame_support::{
traits::{Defensive, Get},
DefaultNoBound, PalletError,
};
use scale_info::TypeInfo;
use sp_runtime::traits::{Bounded, Zero};
use sp_std::{
Expand All @@ -38,10 +41,14 @@ use sp_std::{
vec::Vec,
};

#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo, PalletError)]
pub enum ListError {
/// A duplicate id has been detected.
Duplicate,
/// An Id does not have a greater score than another Id.
NotHeavier,
/// Attempted to place node in front of a node in another bag.
NotInSameBag,
/// Given node id was not found.
NodeNotFound,
}
Expand Down Expand Up @@ -321,9 +328,13 @@ impl<T: Config<I>, I: 'static> List<T, I> {
Ok(())
}

/// Remove an id from the list.
pub(crate) fn remove(id: &T::AccountId) {
Self::remove_many(sp_std::iter::once(id));
/// Remove an id from the list, returning an error if `id` does not exists.
pub(crate) fn remove(id: &T::AccountId) -> Result<(), ListError> {
if !Self::contains(id) {
return Err(ListError::NodeNotFound)
}
let _ = Self::remove_many(sp_std::iter::once(id));
Ok(())
}

/// Remove many ids from the list.
Expand Down Expand Up @@ -416,31 +427,31 @@ impl<T: Config<I>, I: 'static> List<T, I> {
pub(crate) fn put_in_front_of(
lighter_id: &T::AccountId,
heavier_id: &T::AccountId,
) -> Result<(), crate::pallet::Error<T, I>> {
use crate::pallet;
) -> Result<(), ListError> {
use frame_support::ensure;

let lighter_node = Node::<T, I>::get(lighter_id).ok_or(pallet::Error::IdNotFound)?;
let heavier_node = Node::<T, I>::get(heavier_id).ok_or(pallet::Error::IdNotFound)?;
let lighter_node = Node::<T, I>::get(&lighter_id).ok_or(ListError::NodeNotFound)?;
let heavier_node = Node::<T, I>::get(&heavier_id).ok_or(ListError::NodeNotFound)?;

ensure!(lighter_node.bag_upper == heavier_node.bag_upper, pallet::Error::NotInSameBag);
ensure!(lighter_node.bag_upper == heavier_node.bag_upper, ListError::NotInSameBag);

// this is the most expensive check, so we do it last.
ensure!(
T::ScoreProvider::score(heavier_id) > T::ScoreProvider::score(lighter_id),
pallet::Error::NotHeavier
T::ScoreProvider::score(&heavier_id) > T::ScoreProvider::score(&lighter_id),
ListError::NotHeavier
);

// remove the heavier node from this list. Note that this removes the node from storage and
// decrements the node counter.
Self::remove(heavier_id);
// defensive: both nodes have been checked to exist.
let _ = Self::remove(&heavier_id).defensive();

// re-fetch `lighter_node` from storage since it may have been updated when `heavier_node`
// was removed.
let lighter_node = Node::<T, I>::get(lighter_id).ok_or_else(|| {
debug_assert!(false, "id that should exist cannot be found");
crate::log!(warn, "id that should exist cannot be found");
pallet::Error::IdNotFound
ListError::NodeNotFound
})?;

// insert `heavier_node` directly in front of `lighter_node`. This will update both nodes
Expand Down
16 changes: 8 additions & 8 deletions frame/bags-list/src/list/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::{
ListBags, ListNodes,
};
use frame_election_provider_support::{SortedListProvider, VoteWeight};
use frame_support::{assert_ok, assert_storage_noop};
use frame_support::{assert_noop, assert_ok, assert_storage_noop};

#[test]
fn basic_setup_works() {
Expand Down Expand Up @@ -98,7 +98,7 @@ fn remove_last_node_in_bags_cleans_bag() {
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);

// bump 1 to a bigger bag
List::<Runtime>::remove(&1);
List::<Runtime>::remove(&1).unwrap();
assert_ok!(List::<Runtime>::insert(1, 10_000));

// then the bag with bound 10 is wiped from storage.
Expand Down Expand Up @@ -265,18 +265,18 @@ mod list {
ExtBuilder::default().build_and_execute(|| {
// removing a non-existent id is a noop
assert!(!ListNodes::<Runtime>::contains_key(42));
assert_storage_noop!(List::<Runtime>::remove(&42));
assert_noop!(List::<Runtime>::remove(&42), ListError::NodeNotFound);

// when removing a node from a bag with multiple nodes:
List::<Runtime>::remove(&2);
List::<Runtime>::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:
List::<Runtime>::remove(&1);
List::<Runtime>::remove(&1).unwrap();

// then
assert_eq!(get_list_as_ids(), vec![3, 4]);
Expand All @@ -286,11 +286,11 @@ mod list {
assert!(!ListBags::<Runtime>::contains_key(10));

// remove remaining ids to make sure storage cleans up as expected
List::<Runtime>::remove(&3);
List::<Runtime>::remove(&3).unwrap();
ensure_left(3, 1);
assert_eq!(get_list_as_ids(), vec![4]);

List::<Runtime>::remove(&4);
List::<Runtime>::remove(&4).unwrap();
ensure_left(4, 0);
assert_eq!(get_list_as_ids(), Vec::<AccountId>::new());

Expand Down Expand Up @@ -573,7 +573,7 @@ mod bags {
});

// when we make a pre-existing bag empty
List::<Runtime>::remove(&1);
List::<Runtime>::remove(&1).unwrap();

// then
assert_eq!(Bag::<Runtime>::get(10), None)
Expand Down
40 changes: 20 additions & 20 deletions frame/bags-list/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ mod pallet {
vec![(10, vec![1]), (20, vec![42]), (1_000, vec![2, 3, 4])]
);

// when increasing vote weight to the level of non-existent bag
// 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));

Expand All @@ -44,7 +44,7 @@ mod pallet {
vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![42])]
);

// when decreasing weight within the range of the current bag
// 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));

Expand All @@ -54,7 +54,7 @@ mod pallet {
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
// 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));

Expand All @@ -64,7 +64,7 @@ mod pallet {
vec![(10, vec![1]), (30, vec![42]), (1_000, vec![2, 3, 4])]
);

// when increasing weight to the level of a pre-existing bag
// 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));

Expand Down Expand Up @@ -380,7 +380,7 @@ mod pallet {
// then
assert_noop!(
BagsList::put_in_front_of(Origin::signed(3), 2),
crate::pallet::Error::<Runtime>::NotHeavier
crate::pallet::Error::<Runtime>::List(ListError::NotHeavier)
);
});
}
Expand All @@ -394,7 +394,7 @@ mod pallet {
// then
assert_noop!(
BagsList::put_in_front_of(Origin::signed(3), 4),
crate::pallet::Error::<Runtime>::NotHeavier
crate::pallet::Error::<Runtime>::List(ListError::NotHeavier)
);
});
}
Expand All @@ -411,7 +411,7 @@ mod pallet {
// then
assert_noop!(
BagsList::put_in_front_of(Origin::signed(5), 4),
crate::pallet::Error::<Runtime>::IdNotFound
crate::pallet::Error::<Runtime>::List(ListError::NodeNotFound)
);
});

Expand All @@ -425,7 +425,7 @@ mod pallet {
// then
assert_noop!(
BagsList::put_in_front_of(Origin::signed(4), 5),
crate::pallet::Error::<Runtime>::IdNotFound
crate::pallet::Error::<Runtime>::List(ListError::NodeNotFound)
);
});
}
Expand All @@ -439,7 +439,7 @@ mod pallet {
// then
assert_noop!(
BagsList::put_in_front_of(Origin::signed(4), 1),
crate::pallet::Error::<Runtime>::NotInSameBag
crate::pallet::Error::<Runtime>::List(ListError::NotInSameBag)
);
});
}
Expand Down Expand Up @@ -491,12 +491,12 @@ mod sorted_list_provider {
assert_eq!(BagsList::count(), 5);

// when removing
BagsList::on_remove(&201);
BagsList::on_remove(&201).unwrap();
// then the count goes down
assert_eq!(BagsList::count(), 4);

// when updating
BagsList::on_update(&201, VoteWeight::MAX);
assert_noop!(BagsList::on_update(&201, VoteWeight::MAX), ListError::NodeNotFound);
// then the count stays the same
assert_eq!(BagsList::count(), 4);
});
Expand Down Expand Up @@ -555,7 +555,7 @@ mod sorted_list_provider {
assert_eq!(BagsList::count(), 5);

// when increasing weight to the level of non-existent bag
BagsList::on_update(&42, 2_000);
BagsList::on_update(&42, 2_000).unwrap();

// then the bag is created with the id in it,
assert_eq!(
Expand All @@ -566,7 +566,7 @@ mod sorted_list_provider {
assert_eq!(BagsList::iter().collect::<Vec<_>>(), vec![42, 2, 3, 4, 1]);

// when decreasing weight within the range of the current bag
BagsList::on_update(&42, 1_001);
BagsList::on_update(&42, 1_001).unwrap();

// then the id does not change bags,
assert_eq!(
Expand All @@ -577,7 +577,7 @@ mod sorted_list_provider {
assert_eq!(BagsList::iter().collect::<Vec<_>>(), 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);
BagsList::on_update(&42, VoteWeight::MAX).unwrap();

// the the new bag is created with the id in it,
assert_eq!(
Expand All @@ -588,7 +588,7 @@ mod sorted_list_provider {
assert_eq!(BagsList::iter().collect::<Vec<_>>(), vec![42, 2, 3, 4, 1]);

// when decreasing the weight to a pre-existing bag
BagsList::on_update(&42, 1_000);
BagsList::on_update(&42, 1_000).unwrap();

// then id is moved to the correct bag (as the last member),
assert_eq!(
Expand All @@ -615,29 +615,29 @@ mod sorted_list_provider {
ExtBuilder::default().build_and_execute(|| {
// it is a noop removing a non-existent id
assert!(!ListNodes::<Runtime>::contains_key(42));
assert_storage_noop!(BagsList::on_remove(&42));
assert_noop!(BagsList::on_remove(&42), ListError::NodeNotFound);

// when removing a node from a bag with multiple nodes
BagsList::on_remove(&2);
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);
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);
BagsList::on_remove(&4).unwrap();
assert_eq!(get_list_as_ids(), vec![3]);
ensure_left(4, 1);
BagsList::on_remove(&3);
BagsList::on_remove(&3).unwrap();

// then the storage is completely cleaned up
assert_eq!(get_list_as_ids(), Vec::<AccountId>::new());
Expand Down
Loading