Compare commits
5 Commits
04a63e234d
...
573e57dfb4
Author | SHA1 | Date | |
---|---|---|---|
573e57dfb4 | |||
46d4716f67 | |||
9cb7f3c782 | |||
c55d9a05d9 | |||
5847097e94 |
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -3648,7 +3648,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ghost-networks"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
dependencies = [
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
@ -3834,7 +3834,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ghost-slow-clap"
|
||||
version = "0.3.17"
|
||||
version = "0.3.19"
|
||||
dependencies = [
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
@ -3843,6 +3843,8 @@ dependencies = [
|
||||
"log",
|
||||
"pallet-balances",
|
||||
"pallet-session",
|
||||
"pallet-staking",
|
||||
"pallet-staking-reward-curve",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"serde",
|
||||
@ -3868,7 +3870,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ghost-traits"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
dependencies = [
|
||||
"frame-support",
|
||||
"sp-runtime 31.0.1",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ghost-networks"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
@ -50,4 +50,6 @@ runtime-benchmarks = [
|
||||
try-runtime = [
|
||||
"frame-support/try-runtime",
|
||||
"frame-system/try-runtime",
|
||||
"pallet-staking/try-runtime",
|
||||
"pallet-balances/try-runtime",
|
||||
]
|
||||
|
@ -8,13 +8,13 @@ use frame_support::{
|
||||
};
|
||||
use frame_system::pallet_prelude::*;
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
use sp_runtime::{
|
||||
traits::{CheckedSub, CheckedAdd, AtLeast32BitUnsigned, Member},
|
||||
curve::PiecewiseLinear,
|
||||
DispatchResult,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
use sp_std::convert::TryInto;
|
||||
use sp_std::{prelude::*, convert::TryInto};
|
||||
|
||||
pub use ghost_traits::networks::{
|
||||
NetworkDataBasicHandler, NetworkDataInspectHandler,
|
||||
@ -93,6 +93,8 @@ where
|
||||
false => total_issuance.saturating_sub(Balance::from(bridged_in - bridged_out)),
|
||||
};
|
||||
|
||||
NullifyNeeded::<T>::set(true);
|
||||
|
||||
match piecewise_linear
|
||||
.calculate_for_fraction_times_denominator(total_staked, adjusted_issuance)
|
||||
.checked_mul(&accumulated_balance)
|
||||
@ -165,6 +167,10 @@ pub mod module {
|
||||
NetworkRemoved { chain_id: T::NetworkId },
|
||||
}
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn nullify_needed)]
|
||||
pub type NullifyNeeded<T: Config> = StorageValue<_, bool, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn bridged_imbalance)]
|
||||
pub type BridgedImbalance<T: Config> =
|
||||
@ -221,7 +227,18 @@ pub mod module {
|
||||
pub struct Pallet<T>(PhantomData<T>);
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn on_initialize(_: BlockNumberFor<T>) -> Weight {
|
||||
T::DbWeight::get().reads_writes(1, 1)
|
||||
}
|
||||
|
||||
fn on_finalize(_: BlockNumberFor<T>) {
|
||||
if Self::nullify_needed() {
|
||||
Self::nullify_commission();
|
||||
NullifyNeeded::<T>::put(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
@ -596,6 +613,10 @@ impl<T: Config> NetworkDataInspectHandler<NetworkData> for Pallet<T> {
|
||||
fn iter() -> PrefixIterator<(Self::NetworkId, NetworkData)> {
|
||||
Networks::<T>::iter()
|
||||
}
|
||||
|
||||
fn is_nullification_period() -> bool {
|
||||
NullifyNeeded::<T>::get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> NetworkDataMutateHandler<NetworkData, BalanceOf<T>> for Pallet<T> {
|
||||
|
@ -798,7 +798,6 @@ fn bridged_inlation_reward_works() {
|
||||
let total_staked_not_ideal: u128 = 68;
|
||||
let total_issuance: u128 = 100;
|
||||
|
||||
|
||||
assert_eq!(BridgedInflationCurve::<RewardCurve, Test>::era_payout(
|
||||
total_staked_ideal * 1_000,
|
||||
total_issuance * 1_000,
|
||||
@ -913,3 +912,26 @@ fn bridged_inlation_reward_works() {
|
||||
1, total_issuance * 1_000_000_000_000_000_000_000_000 + amount, 0), (0, 0));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bridged_inflation_era_payout_triggers_need_of_nullification() {
|
||||
ExtBuilder::build()
|
||||
.execute_with(|| {
|
||||
let chain_id: u32 = 1;
|
||||
let amount: u128 = 1337 * 1_000_000_000;
|
||||
let commission: u128 = amount / 100; // 1% commission
|
||||
let total_staked_ideal: u128 = 69;
|
||||
let total_issuance: u128 = 100;
|
||||
|
||||
assert_ok!(GhostNetworks::accumulate_commission(&commission));
|
||||
assert_ok!(GhostNetworks::increase_gatekeeper_amount(&chain_id, &amount));
|
||||
assert_eq!(NullifyNeeded::<Test>::get(), false);
|
||||
assert_eq!(BridgedInflationCurve::<RewardCurve, Test>::era_payout(
|
||||
total_staked_ideal * 1_000,
|
||||
total_issuance * 1_000 + amount,
|
||||
0), (commission, 0));
|
||||
assert_eq!(NullifyNeeded::<Test>::get(), true);
|
||||
GhostNetworks::on_finalize(69);
|
||||
assert_eq!(NullifyNeeded::<Test>::get(), false);
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ghost-slow-clap"
|
||||
version = "0.3.17"
|
||||
version = "0.3.19"
|
||||
description = "Applause protocol for the EVM bridge"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
@ -27,11 +27,13 @@ sp-staking = { workspace = true }
|
||||
sp-io = { workspace = true }
|
||||
sp-std = { workspace = true }
|
||||
|
||||
pallet-balances = { workspace = true }
|
||||
ghost-networks = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pallet-session = { workspace = true, default-features = true }
|
||||
pallet-balances = { workspace = true }
|
||||
pallet-session = { workspace = true }
|
||||
pallet-staking = { workspace = true }
|
||||
pallet-staking-reward-curve = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
@ -49,6 +51,7 @@ std = [
|
||||
"sp-io/std",
|
||||
"sp-std/std",
|
||||
"pallet-session/std",
|
||||
"pallet-staking/std",
|
||||
"pallet-balances/std",
|
||||
"ghost-networks/std",
|
||||
]
|
||||
|
@ -558,7 +558,7 @@ impl<T: Config> Pallet<T> {
|
||||
received_claps_key: &(SessionIndex, &H256, &H256),
|
||||
) -> DispatchResult {
|
||||
ApplausesForTransaction::<T>::try_mutate(received_claps_key, |is_applaused| {
|
||||
if *is_applaused {
|
||||
if *is_applaused || T::NetworkDataHandler::is_nullification_period() {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
@ -858,9 +858,9 @@ impl<T: Config> Pallet<T> {
|
||||
fn is_good_actor(
|
||||
authority_index: usize,
|
||||
session_index: SessionIndex,
|
||||
average_claps: u32,
|
||||
median_claps: u32,
|
||||
) -> bool {
|
||||
if average_claps == 0 {
|
||||
if median_claps == 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -869,10 +869,10 @@ impl<T: Config> Pallet<T> {
|
||||
.or_default()
|
||||
.claps;
|
||||
|
||||
let authority_deviation = if number_of_claps < average_claps {
|
||||
Perbill::from_rational(average_claps - number_of_claps, average_claps)
|
||||
let authority_deviation = if number_of_claps < median_claps {
|
||||
Perbill::from_rational(median_claps - number_of_claps, median_claps)
|
||||
} else {
|
||||
Perbill::from_rational(number_of_claps - average_claps, average_claps)
|
||||
Perbill::from_rational(number_of_claps - median_claps, median_claps)
|
||||
};
|
||||
authority_deviation < Perbill::from_percent(T::OffenceThreshold::get())
|
||||
}
|
||||
@ -886,6 +886,28 @@ impl<T: Config> Pallet<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_median_claps(session_index: &SessionIndex) -> u32 {
|
||||
let mut claps_in_session = ClapsInSession::<T>::get(session_index)
|
||||
.values()
|
||||
.filter_map(|value| (!value.disabled).then(|| value.claps))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if claps_in_session.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
claps_in_session.sort();
|
||||
let number_of_claps = claps_in_session.len();
|
||||
|
||||
if number_of_claps % 2 == 0 {
|
||||
let mid_left = claps_in_session[number_of_claps / 2 - 1];
|
||||
let mid_right = claps_in_session[number_of_claps / 2];
|
||||
(mid_left + mid_right) / 2
|
||||
} else {
|
||||
claps_in_session[number_of_claps / 2]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn set_test_authorities(authorities: Vec<T::AuthorityId>) {
|
||||
let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities)
|
||||
@ -931,21 +953,12 @@ impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
|
||||
let validators = T::ValidatorSet::validators();
|
||||
let authorities = Authorities::<T>::get();
|
||||
|
||||
let (sum_claps, total_claps) = ClapsInSession::<T>::get(&session_index)
|
||||
.iter()
|
||||
.filter(|(_, value)| !value.disabled)
|
||||
.fold((0u32, 0u32), |(sum, total), (_, value)|
|
||||
(sum.saturating_add(value.claps), total.saturating_add(1))
|
||||
);
|
||||
|
||||
let average_claps = sum_claps
|
||||
.checked_div(total_claps)
|
||||
.unwrap_or_default();
|
||||
let median_claps = Self::calculate_median_claps(&session_index);
|
||||
|
||||
let offenders = validators
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter(|(index, _)| !Self::is_good_actor(*index, session_index, average_claps))
|
||||
.filter(|(index, _)| !Self::is_good_actor(*index, session_index, median_claps))
|
||||
.filter_map(|(_, id)| {
|
||||
<T::ValidatorSet as ValidatorSetWithIdentification<T::AccountId>>::IdentificationOf::convert(
|
||||
id.clone(),
|
||||
|
@ -2,15 +2,13 @@
|
||||
|
||||
use frame_support::{
|
||||
derive_impl, parameter_types,
|
||||
traits::{ConstU32, ConstU64},
|
||||
weights::Weight,
|
||||
PalletId,
|
||||
traits::{ConstU32, ConstU64}, weights::Weight,
|
||||
};
|
||||
use frame_system::EnsureRoot;
|
||||
use pallet_session::historical as pallet_session_historical;
|
||||
use sp_runtime::{
|
||||
testing::{TestXt, UintAuthorityId},
|
||||
traits::ConvertInto,
|
||||
traits::ConvertInto, curve::PiecewiseLinear,
|
||||
Permill,
|
||||
};
|
||||
use sp_staking::{
|
||||
@ -176,9 +174,20 @@ impl ghost_networks::Config for Runtime {
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
pallet_staking_reward_curve::build! {
|
||||
const REWARD_CURVE: PiecewiseLinear<'static> = curve!(
|
||||
min_inflation: 0_006_000,
|
||||
max_inflation: 1_000_000,
|
||||
ideal_stake: 0_690_000,
|
||||
falloff: 0_050_000,
|
||||
max_piece_count: 100,
|
||||
test_precision: 0_005_000,
|
||||
);
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static ExistentialDeposit: u64 = 2;
|
||||
pub const TreasuryPalletId: PalletId = PalletId(*b"mck/test");
|
||||
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
|
||||
}
|
||||
|
||||
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
|
||||
|
@ -10,6 +10,9 @@ use sp_core::offchain::{
|
||||
};
|
||||
use sp_runtime::testing::UintAuthorityId;
|
||||
|
||||
use ghost_networks::BridgedInflationCurve;
|
||||
use pallet_staking::EraPayout;
|
||||
|
||||
const MAX_DEVIATION_DEPTH: u32 = 10;
|
||||
|
||||
fn prepare_evm_network(
|
||||
@ -262,18 +265,24 @@ fn should_mark_clapped_transaction_when_clap_received() {
|
||||
fn should_applause_and_take_next_claps() {
|
||||
let (network_id, transaction_hash, unique_transaction_hash) =
|
||||
generate_unique_hash(None, None, None, None);
|
||||
let (_, receiver, amount) = get_mocked_metadata();
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
let session_index = advance_session_and_get_index();
|
||||
let storage_key = (session_index, transaction_hash, unique_transaction_hash);
|
||||
|
||||
assert_eq!(pallet::ApplausesForTransaction::<Runtime>::get(&storage_key), false);
|
||||
assert_eq!(Balances::balance(&receiver), 0);
|
||||
assert_ok!(do_clap_from(session_index, network_id, 0, false));
|
||||
assert_eq!(pallet::ApplausesForTransaction::<Runtime>::get(&storage_key), false);
|
||||
assert_eq!(Balances::balance(&receiver), 0);
|
||||
assert_ok!(do_clap_from(session_index, network_id, 1, false));
|
||||
assert_eq!(pallet::ApplausesForTransaction::<Runtime>::get(&storage_key), true);
|
||||
assert_eq!(Balances::balance(&receiver), amount);
|
||||
assert_ok!(do_clap_from(session_index, network_id, 2, false));
|
||||
assert_eq!(pallet::ApplausesForTransaction::<Runtime>::get(&storage_key), true);
|
||||
assert_eq!(Balances::balance(&receiver), amount);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@ -530,10 +539,74 @@ fn should_throw_error_on_commission_overflow() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_nullify_commission_on_finalize() {
|
||||
let total_staked = 69_000_000;
|
||||
let total_issuance = 100_000_000;
|
||||
|
||||
let (network_id, _, _) = generate_unique_hash(None, None, None, None);
|
||||
let (_, _, amount) = get_mocked_metadata();
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
let _ = prepare_evm_network(Some(network_id), Some(500_000_000));
|
||||
let session_index = advance_session_and_get_index();
|
||||
|
||||
assert_eq!(Networks::accumulated_commission(), 0);
|
||||
assert_ok!(do_clap_from(session_index, network_id, 0, false));
|
||||
assert_ok!(do_clap_from(session_index, network_id, 1, false));
|
||||
assert_eq!(Networks::accumulated_commission(), amount.saturating_div(2));
|
||||
assert_eq!(Networks::is_nullification_period(), false);
|
||||
|
||||
assert_eq!(BridgedInflationCurve::<RewardCurve, Runtime>::era_payout(
|
||||
total_staked,
|
||||
(total_issuance + amount).into(),
|
||||
0), (1260099399952u128, 208739900600048u128)); // precomputed values
|
||||
assert_eq!(Networks::is_nullification_period(), true);
|
||||
Networks::on_finalize(System::block_number());
|
||||
|
||||
assert_eq!(Networks::is_nullification_period(), false);
|
||||
assert_eq!(Networks::accumulated_commission(), 0);
|
||||
assert_ok!(do_clap_from(session_index, network_id, 2, false));
|
||||
assert_eq!(Networks::accumulated_commission(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_avoid_applause_during_nullification_period() {
|
||||
let total_staked = 69_000_000;
|
||||
let total_issuance = 100_000_000;
|
||||
|
||||
let (network_id, _, _) = generate_unique_hash(None, None, None, None);
|
||||
let (_, receiver, amount) = get_mocked_metadata();
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
let _ = prepare_evm_network(Some(network_id), Some(0));
|
||||
let session_index = advance_session_and_get_index();
|
||||
|
||||
assert_eq!(Networks::is_nullification_period(), false);
|
||||
assert_eq!(BridgedInflationCurve::<RewardCurve, Runtime>::era_payout(
|
||||
total_staked,
|
||||
total_issuance,
|
||||
0), (0, 0));
|
||||
assert_eq!(Networks::is_nullification_period(), true);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: check event
|
||||
// TODO: multiple logs will create multiple records
|
||||
// TODO: errors should be checked as much as possible
|
||||
// TODO: offences generated as expected
|
||||
// TODO: deal with below existential amount after commission
|
||||
|
||||
fn advance_session_and_get_index() -> u32 {
|
||||
advance_session();
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ghost-traits"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
@ -21,6 +21,7 @@ pub trait NetworkDataBasicHandler {
|
||||
pub trait NetworkDataInspectHandler<Network>: NetworkDataBasicHandler {
|
||||
fn get(n: &Self::NetworkId) -> Option<Network>;
|
||||
fn iter() -> PrefixIterator<(Self::NetworkId, Network)>;
|
||||
fn is_nullification_period() -> bool;
|
||||
}
|
||||
|
||||
pub trait NetworkDataMutateHandler<Network, Balance>: NetworkDataInspectHandler<Network> {
|
||||
|
Loading…
Reference in New Issue
Block a user