diff --git a/pallets/slow-clap/Cargo.toml b/pallets/slow-clap/Cargo.toml index fc583a4..7dfa57c 100644 --- a/pallets/slow-clap/Cargo.toml +++ b/pallets/slow-clap/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghost-slow-clap" -version = "0.3.55" +version = "0.3.56" description = "Applause protocol for the EVM bridge" license.workspace = true authors.workspace = true diff --git a/pallets/slow-clap/src/lib.rs b/pallets/slow-clap/src/lib.rs index cfd33dc..ddcb063 100644 --- a/pallets/slow-clap/src/lib.rs +++ b/pallets/slow-clap/src/lib.rs @@ -8,8 +8,8 @@ use serde::{Deserialize, Deserializer}; use frame_support::{ pallet_prelude::*, traits::{ - tokens::fungible::{Inspect, Mutate}, - DisabledValidators, Get, OneSessionHandler, ValidatorSet, ValidatorSetWithIdentification, + Currency, DisabledValidators, Get, OneSessionHandler, ValidatorSet, + ValidatorSetWithIdentification, }, WeakBoundedVec, }; @@ -17,6 +17,7 @@ use frame_system::{ offchain::{SendTransactionTypes, SubmitTransaction}, pallet_prelude::*, }; + pub use pallet::*; use sp_core::H256; @@ -27,12 +28,12 @@ use sp_runtime::{ storage_lock::{StorageLock, Time}, HttpError, }, - traits::{BlockNumberProvider, Convert, Saturating, TrailingZeroInput}, + traits::{AtLeast32BitUnsigned, BlockNumberProvider, Convert, Saturating, TrailingZeroInput}, Perbill, RuntimeAppPublic, RuntimeDebug, }; use sp_staking::{ offence::{Kind, Offence, ReportOffence}, - SessionIndex, + EraIndex, SessionIndex, }; use sp_std::{collections::btree_map::BTreeMap, prelude::*, vec::Vec}; @@ -202,7 +203,7 @@ impl core::fmt::Debug for OffchainErr { pub type NetworkIdOf = <::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId; pub type BalanceOf = - <::Currency as Inspect<::AccountId>>::Balance; + <::Currency as Currency<::AccountId>>::Balance; pub type ValidatorId = <::ValidatorSet as ValidatorSet< ::AccountId, @@ -217,6 +218,12 @@ pub type IdentificationTuple = ( type OffchainResult = Result>>; +pub trait ApplauseListener { + fn get_current_era() -> EraIndex; + fn get_threshold_amount(era: EraIndex) -> Balance; + fn get_validator_total_exposure(era: EraIndex, index: usize) -> Balance; +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -239,7 +246,7 @@ pub mod pallet { + MaxEncodedLen; type ValidatorSet: ValidatorSetWithIdentification; - type Currency: Inspect + Mutate; + type Currency: Currency; type NetworkDataHandler: NetworkDataBasicHandler + NetworkDataInspectHandler + NetworkDataMutateHandler>; @@ -250,6 +257,7 @@ pub mod pallet { ThrottlingOffence>, >; type DisabledValidators: DisabledValidators; + type ApplauseListener: ApplauseListener>; #[pallet::constant] type MaxAuthorities: Get; @@ -311,6 +319,19 @@ pub mod pallet { TimeWentBackwards, } + #[pallet::storage] + #[pallet::getter(fn clapped_amount)] + pub(super) type ClappedAmount = StorageNMap< + _, + ( + NMapKey, + NMapKey, + NMapKey, + ), + BalanceOf, + ValueQuery, + >; + #[pallet::storage] #[pallet::getter(fn block_commitments)] pub(super) type BlockCommitments = StorageMap< @@ -584,33 +605,23 @@ impl Pallet { let (session_index, clap_unique_hash) = Self::mended_session_index(&clap); let mut claps_in_session = ClapsInSession::::get(&session_index); - let disabled_authorities = claps_in_session - .values() - .filter(|info| info.disabled) - .count(); - - let active_authorities = Authorities::::get(&session_index) - .len() - .saturating_sub(disabled_authorities); - let received_claps_key = (session_index, &clap.transaction_hash, &clap_unique_hash); - let number_of_received_claps = - ReceivedClaps::::try_mutate(&received_claps_key, |tree_of_claps| { - let number_of_claps = tree_of_claps.len(); - match (tree_of_claps.contains(&clap.authority_index), clap.removed) { - (true, true) => tree_of_claps - .remove(&clap.authority_index) - .then(|| number_of_claps.saturating_sub(1)) - .ok_or(Error::::UnregisteredClapRemove), - (true, false) => Err(Error::::AlreadyClapped), - (false, true) => Err(Error::::UnregisteredClapRemove), - (false, false) => tree_of_claps - .try_insert(clap.authority_index) - .map(|_| number_of_claps.saturating_add(1)) - .map_err(|_| Error::::TooMuchAuthorities), - } - })?; + ReceivedClaps::::try_mutate(&received_claps_key, |tree_of_claps| { + let number_of_claps = tree_of_claps.len(); + match (tree_of_claps.contains(&clap.authority_index), clap.removed) { + (true, true) => tree_of_claps + .remove(&clap.authority_index) + .then(|| number_of_claps.saturating_sub(1)) + .ok_or(Error::::UnregisteredClapRemove), + (true, false) => Err(Error::::AlreadyClapped), + (false, true) => Err(Error::::UnregisteredClapRemove), + (false, false) => tree_of_claps + .try_insert(clap.authority_index) + .map(|_| number_of_claps.saturating_add(1)) + .map_err(|_| Error::::TooMuchAuthorities), + } + })?; claps_in_session .entry(clap.authority_index) @@ -631,8 +642,7 @@ impl Pallet { }); let enough_authorities = - Perbill::from_rational(number_of_received_claps as u32, active_authorities as u32) - > Perbill::from_percent(T::ApplauseThreshold::get()); + Self::validator_clap_by_amount(clap.authority_index, &received_claps_key); if enough_authorities { let _ = Self::try_applause(&clap, &received_claps_key).inspect_err(|error_msg| { @@ -647,6 +657,24 @@ impl Pallet { Ok(()) } + fn validator_clap_by_amount( + authority_index: AuthIndex, + received_claps_key: &(SessionIndex, &H256, &H256), + ) -> bool { + let era = T::ApplauseListener::get_current_era(); + let threshold_amount = T::ApplauseListener::get_threshold_amount(era); + let new_clapped_amount = + T::ApplauseListener::get_validator_total_exposure(era, authority_index as usize); + + let total_clapped = ClappedAmount::::mutate(received_claps_key, |clapped_amount| { + let total_clapped = clapped_amount.saturating_add(new_clapped_amount); + *clapped_amount = total_clapped; + total_clapped + }); + + total_clapped >= threshold_amount + } + fn try_applause( clap: &Clap, BalanceOf>, received_claps_key: &(SessionIndex, &H256, &H256), @@ -670,9 +698,7 @@ impl Pallet { let _ = T::NetworkDataHandler::accumulate_commission(&commission) .map_err(|_| Error::::CouldNotAccumulateCommission)?; - if final_amount > T::Currency::minimum_balance() { - T::Currency::mint_into(&clap.receiver, final_amount)?; - } + let _ = T::Currency::deposit_creating(&clap.receiver, final_amount); *is_applaused = true; diff --git a/pallets/slow-clap/src/mock.rs b/pallets/slow-clap/src/mock.rs index befec45..1fcb9a4 100644 --- a/pallets/slow-clap/src/mock.rs +++ b/pallets/slow-clap/src/mock.rs @@ -9,7 +9,7 @@ use pallet_session::historical as pallet_session_historical; use sp_runtime::{ curve::PiecewiseLinear, testing::{TestXt, UintAuthorityId}, - traits::ConvertInto, + traits::{AtLeast32BitUnsigned, ConvertInto}, Permill, }; use sp_staking::{ @@ -20,7 +20,7 @@ use sp_staking::{ use sp_runtime::BuildStorage; use crate as slow_clap; -use crate::Config; +use crate::{ApplauseListener, Config, EraIndex}; type Block = frame_system::mocking::MockBlock; @@ -174,6 +174,32 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); } +type Balance = u64; + +pub struct TestSomeCoolTrait; +impl ApplauseListener for TestSomeCoolTrait +where + Balance: AtLeast32BitUnsigned + From, +{ + fn get_current_era() -> EraIndex { + 1 + } + + fn get_threshold_amount(_era: EraIndex) -> Balance { + 666_666_667u64.into() + } + + fn get_validator_total_exposure(_era: EraIndex, index: usize) -> Balance { + match index { + 0 => 500_000_000u64, + 1 => 300_000_000u64, + 2 => 200_000_000u64, + _ => 0, + } + .into() + } +} + impl Config for Runtime { type RuntimeEvent = RuntimeEvent; type AuthorityId = UintAuthorityId; @@ -184,6 +210,7 @@ impl Config for Runtime { type BlockNumberProvider = System; type ReportUnresponsiveness = OffenceHandler; type DisabledValidators = Session; + type ApplauseListener = TestSomeCoolTrait; type MaxAuthorities = ConstU32<5>; type ApplauseThreshold = ConstU32<50>; diff --git a/pallets/slow-clap/src/tests.rs b/pallets/slow-clap/src/tests.rs index 15f2ed0..2a46775 100644 --- a/pallets/slow-clap/src/tests.rs +++ b/pallets/slow-clap/src/tests.rs @@ -562,25 +562,25 @@ fn should_applause_and_take_next_claps() { pallet::ApplausesForTransaction::::get(&storage_key), false ); - assert_eq!(Balances::balance(&receiver), 0); + assert_eq!(Balances::total_balance(&receiver), 0); assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key), false ); - assert_eq!(Balances::balance(&receiver), 0); + assert_eq!(Balances::total_balance(&receiver), 0); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key), true ); - assert_eq!(Balances::balance(&receiver), amount); + assert_eq!(Balances::total_balance(&receiver), amount); assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_eq!( pallet::ApplausesForTransaction::::get(&storage_key), true ); - assert_eq!(Balances::balance(&receiver), amount); + assert_eq!(Balances::total_balance(&receiver), amount); }); } @@ -812,8 +812,8 @@ fn should_clap_without_applause_on_gatekeeper_amount_overflow() { pallet::ApplausesForTransaction::::get(&storage_key_first), true ); - assert_eq!(Balances::balance(&first_receiver), big_amount); - assert_eq!(Balances::balance(&second_receiver), 0); + assert_eq!(Balances::total_balance(&first_receiver), big_amount); + assert_eq!(Balances::total_balance(&second_receiver), 0); for authority_index in 0..=2 { let clap = Clap { @@ -831,8 +831,8 @@ fn should_clap_without_applause_on_gatekeeper_amount_overflow() { assert_ok!(SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature)); } - assert_eq!(Balances::balance(&first_receiver), big_amount); - assert_eq!(Balances::balance(&second_receiver), 0); + assert_eq!(Balances::total_balance(&first_receiver), big_amount); + assert_eq!(Balances::total_balance(&second_receiver), 0); assert_eq!(Networks::gatekeeper_amount(network_id), big_amount); assert_eq!(Networks::bridged_imbalance().bridged_in, big_amount); @@ -876,8 +876,8 @@ fn should_clap_without_applause_on_commission_overflow() { pallet::ApplausesForTransaction::::get(&storage_key_first), true ); - assert_eq!(Balances::balance(&first_receiver), big_amount); - assert_eq!(Balances::balance(&second_receiver), 0); + assert_eq!(Balances::total_balance(&first_receiver), big_amount); + assert_eq!(Balances::total_balance(&second_receiver), 0); for authority_index in 0..=2 { let clap = Clap { @@ -895,8 +895,8 @@ fn should_clap_without_applause_on_commission_overflow() { assert_ok!(SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature)); } - assert_eq!(Balances::balance(&first_receiver), big_amount); - assert_eq!(Balances::balance(&second_receiver), 0); + assert_eq!(Balances::total_balance(&first_receiver), big_amount); + assert_eq!(Balances::total_balance(&second_receiver), 0); assert_eq!(Networks::gatekeeper_amount(network_id), big_amount); assert_eq!(Networks::gatekeeper_amount(network_id_other), big_amount); @@ -967,13 +967,13 @@ fn should_avoid_applause_during_nullification_period() { assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false)); - assert_eq!(Balances::balance(&receiver), 0); + assert_eq!(Balances::total_balance(&receiver), 0); Networks::on_finalize(System::block_number()); assert_eq!(Networks::is_nullification_period(), false); assert_ok!(do_clap_from(session_index, network_id, 2, false)); - assert_eq!(Balances::balance(&receiver), amount); + assert_eq!(Balances::total_balance(&receiver), amount); }); } @@ -995,7 +995,7 @@ fn should_avoid_session_overlap_on_mended_session_index() { pallet::ApplausesForTransaction::::get(&storage_key), false ); - assert_eq!(Balances::balance(&receiver), 0u64); + assert_eq!(Balances::total_balance(&receiver), 0u64); assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_ok!(do_clap_from(session_index, network_id, 2, false)); @@ -1004,7 +1004,7 @@ fn should_avoid_session_overlap_on_mended_session_index() { pallet::ApplausesForTransaction::::get(&storage_key), true ); - assert_eq!(Balances::balance(&receiver), amount); + assert_eq!(Balances::total_balance(&receiver), amount); }); } @@ -1094,7 +1094,7 @@ fn should_not_fail_on_sub_existential_balance() { assert_eq!(Networks::gatekeeper_amount(network_id), 0); assert_eq!(Networks::bridged_imbalance().bridged_in, 0); assert_eq!(Networks::bridged_imbalance().bridged_out, 0); - assert_eq!(Balances::balance(&receiver), 0); + assert_eq!(Balances::total_balance(&receiver), 0); assert_eq!( SlowClap::applauses_for_transaction(&received_claps_key), false @@ -1108,7 +1108,7 @@ fn should_not_fail_on_sub_existential_balance() { assert_eq!(Networks::gatekeeper_amount(network_id), amount); assert_eq!(Networks::bridged_imbalance().bridged_in, 0); assert_eq!(Networks::bridged_imbalance().bridged_out, 0); - assert_eq!(Balances::balance(&receiver), 0); + assert_eq!(Balances::total_balance(&receiver), 0); assert_eq!( SlowClap::applauses_for_transaction(&received_claps_key), true @@ -1175,6 +1175,40 @@ fn should_register_block_commitments() { }); } +#[test] +fn should_accumulate_clapped_amount() { + let (network_id, transaction_hash, unique_transaction_hash) = + generate_unique_hash(None, None, None, None); + + new_test_ext().execute_with(|| { + let session_index = advance_session_and_get_index(); + let storage_key = (session_index, transaction_hash, unique_transaction_hash); + + let era = ::ApplauseListener::get_current_era(); + let threshold_amount = ::ApplauseListener::get_threshold_amount(era); + + assert_eq!( + ClappedAmount::::get(&storage_key) < threshold_amount, + true + ); + assert_ok!(do_clap_from(session_index, network_id, 2, false)); + assert_eq!( + ClappedAmount::::get(&storage_key) < threshold_amount, + true + ); + assert_ok!(do_clap_from(session_index, network_id, 1, false)); + assert_eq!( + ClappedAmount::::get(&storage_key) < threshold_amount, + true + ); + assert_ok!(do_clap_from(session_index, network_id, 0, false)); + assert_eq!( + ClappedAmount::::get(&storage_key) > threshold_amount, + true + ); + }); +} + fn advance_session_and_get_index() -> u32 { advance_session(); assert_eq!(Session::validators(), Vec::::new());