From fb0d1572cdac2a1f248952bfcb6b3cb98de341b6 Mon Sep 17 00:00:00 2001
From: librelois <c@elo.tf>
Date: Wed, 26 Jan 2022 21:00:41 +0100
Subject: [PATCH] fix(authority-members): MaxAuthorities constraint must be
 applied

---
 pallets/authority-members/src/lib.rs   | 21 +++++++++++++++
 pallets/authority-members/src/mock.rs  |  4 ++-
 pallets/authority-members/src/tests.rs | 36 +++++++++++++++++++++++++-
 runtime/common/src/pallets_config.rs   |  1 +
 4 files changed, 60 insertions(+), 2 deletions(-)

diff --git a/pallets/authority-members/src/lib.rs b/pallets/authority-members/src/lib.rs
index d3f9c51b0..fb3f77fbb 100644
--- a/pallets/authority-members/src/lib.rs
+++ b/pallets/authority-members/src/lib.rs
@@ -66,6 +66,9 @@ pub mod pallet {
         type KeysWrapper: Parameter + Into<Self::Keys>;
         type IsMember: IsMember<Self::MemberId>;
         type OnRemovedMember: OnRemovedMember<Self::MemberId>;
+        /// Max number of authorities allowed
+        #[pallet::constant]
+        type MaxAuthorities: Get<u32>;
         #[pallet::constant]
         type MaxKeysLife: Get<SessionIndex>;
         #[pallet::constant]
@@ -113,6 +116,7 @@ pub mod pallet {
                 .collect::<Vec<T::MemberId>>();
             members_ids.sort();
 
+            AuthoritiesCounter::<T>::put(members_ids.len() as u32);
             OnlineAuthorities::<T>::put(members_ids.clone());
             MustRotateKeysBefore::<T>::insert(T::MaxKeysLife::get(), members_ids);
         }
@@ -125,6 +129,10 @@ pub mod pallet {
     pub type AccountIdOf<T: Config> =
         StorageMap<_, Twox64Concat, T::MemberId, T::AccountId, OptionQuery>;
 
+    #[pallet::storage]
+    #[pallet::getter(fn authorities_counter)]
+    pub type AuthoritiesCounter<T: Config> = StorageValue<_, u32, ValueQuery>;
+
     #[pallet::storage]
     #[pallet::getter(fn incoming)]
     pub type IncomingAuthorities<T: Config> = StorageValue<_, Vec<T::MemberId>, ValueQuery>;
@@ -198,6 +206,8 @@ pub mod pallet {
         NotMember,
         /// Session keys not provided
         SessionKeysNotProvided,
+        /// Too man aAuthorities
+        TooManyAuthorities,
     }
 
     // CALLS //
@@ -258,6 +268,9 @@ pub mod pallet {
             if Self::is_online(member_id) && !is_outgoing {
                 return Err(Error::<T>::AlreadyOnline.into());
             }
+            if AuthoritiesCounter::<T>::get() >= T::MaxAuthorities::get() {
+                return Err(Error::<T>::TooManyAuthorities.into());
+            }
 
             // Apply phase //
             if is_outgoing {
@@ -398,6 +411,7 @@ pub mod pallet {
                 }
             });
             if not_already_inserted {
+                AuthoritiesCounter::<T>::mutate(|counter| *counter += 1);
                 AccountIdOf::<T>::insert(member_id, account_id);
                 Self::deposit_event(Event::MemberGoOnline(member_id));
             }
@@ -413,6 +427,11 @@ pub mod pallet {
                 }
             });
             if not_already_inserted {
+                AuthoritiesCounter::<T>::mutate(|counter| {
+                    if *counter > 0 {
+                        *counter -= 1
+                    }
+                });
                 Self::deposit_event(Event::MemberGoOffline(member_id));
             }
             not_already_inserted
@@ -433,6 +452,7 @@ pub mod pallet {
                 .is_ok()
         }
         fn remove_in(member_id: T::MemberId) {
+            AuthoritiesCounter::<T>::mutate(|counter| counter.saturating_sub(1));
             IncomingAuthorities::<T>::mutate(|members_ids| {
                 if let Ok(index) = members_ids.binary_search(&member_id) {
                     members_ids.remove(index);
@@ -440,6 +460,7 @@ pub mod pallet {
             })
         }
         fn remove_online(member_id: T::MemberId) {
+            AuthoritiesCounter::<T>::mutate(|counter| counter.saturating_add(1));
             OnlineAuthorities::<T>::mutate(|members_ids| {
                 if let Ok(index) = members_ids.binary_search(&member_id) {
                     members_ids.remove(index);
diff --git a/pallets/authority-members/src/mock.rs b/pallets/authority-members/src/mock.rs
index 6362d638a..6f499362f 100644
--- a/pallets/authority-members/src/mock.rs
+++ b/pallets/authority-members/src/mock.rs
@@ -151,6 +151,7 @@ impl pallet_authority_members::Config for Test {
     type Event = Event;
     type KeysWrapper = MockSessionKeys;
     type IsMember = TestIsSmithMember;
+    type MaxAuthorities = ConstU32<4>;
     type MaxKeysLife = ConstU32<5>;
     type MaxOfflineSessions = ConstU32<2>;
     type MemberId = u64;
@@ -175,8 +176,9 @@ pub fn new_test_ext(initial_authorities_len: u64) -> sp_io::TestExternalities {
         for (ref k, ..) in &keys {
             frame_system::Pallet::<Test>::inc_providers(k);
         }
-        // A dedicated test account
+        // Some dedicated test account
         frame_system::Pallet::<Test>::inc_providers(&12);
+        frame_system::Pallet::<Test>::inc_providers(&15);
     });
     pallet_authority_members::GenesisConfig::<Test> {
         initial_authorities,
diff --git a/pallets/authority-members/src/tests.rs b/pallets/authority-members/src/tests.rs
index 9ed210742..f92c27e05 100644
--- a/pallets/authority-members/src/tests.rs
+++ b/pallets/authority-members/src/tests.rs
@@ -17,7 +17,7 @@
 use super::*;
 use crate::mock::*;
 use crate::MemberData;
-use frame_support::assert_ok;
+use frame_support::{assert_err, assert_ok};
 use sp_runtime::testing::UintAuthorityId;
 
 const EMPTY: Vec<u64> = Vec::new();
@@ -224,6 +224,40 @@ fn test_go_online() {
     });
 }
 
+#[test]
+fn test_too_many_authorities() {
+    new_test_ext(3).execute_with(|| {
+        run_to_block(1);
+
+        // Member 12 set his session keys then go online
+        assert_ok!(AuthorityMembers::set_session_keys(
+            Origin::signed(12),
+            12,
+            UintAuthorityId(12).into(),
+        ));
+        assert_eq!(AuthorityMembers::authorities_counter(), 3);
+        assert_ok!(AuthorityMembers::go_online(Origin::signed(12), 12),);
+
+        // Member 15 can't go online because there is already 4 authorities "planned"
+        assert_ok!(AuthorityMembers::set_session_keys(
+            Origin::signed(15),
+            15,
+            UintAuthorityId(15).into(),
+        ));
+        assert_eq!(AuthorityMembers::authorities_counter(), 4);
+        assert_err!(
+            AuthorityMembers::go_online(Origin::signed(15), 15),
+            Error::<Test>::TooManyAuthorities,
+        );
+
+        // If member 3 go_offline, member 15 can go_online
+        assert_ok!(AuthorityMembers::go_offline(Origin::signed(3), 3),);
+        assert_eq!(AuthorityMembers::authorities_counter(), 3);
+        assert_ok!(AuthorityMembers::go_online(Origin::signed(15), 15),);
+        assert_eq!(AuthorityMembers::authorities_counter(), 4);
+    });
+}
+
 #[test]
 fn test_go_online_then_go_offline_in_same_session() {
     new_test_ext(3).execute_with(|| {
diff --git a/runtime/common/src/pallets_config.rs b/runtime/common/src/pallets_config.rs
index 53a3ff52b..79c92c3eb 100644
--- a/runtime/common/src/pallets_config.rs
+++ b/runtime/common/src/pallets_config.rs
@@ -183,6 +183,7 @@ macro_rules! pallets_config {
 			type OnRemovedMember = OnRemovedAuthorityMemberHandler<Runtime>;
 			type MemberId = IdtyIndex;
 			type MemberIdOf = Identity;
+			type MaxAuthorities = MaxAuthorities;
 			type MaxKeysLife = frame_support::pallet_prelude::ConstU32<1_000>;
 			type MaxOfflineSessions = frame_support::pallet_prelude::ConstU32<100>;
 			type RemoveMemberOrigin = EnsureRoot<Self::AccountId>;
-- 
GitLab