Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add delay and maximum change rate for commission #327

Open
wants to merge 9 commits into
base: dev
Choose a base branch
from
3 changes: 2 additions & 1 deletion node/src/parachain/dev_chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use sp_consensus_aura::sr25519::AuthorityId as AuraId;
use sp_core::{sr25519, Pair, Public};
use sp_runtime::{
traits::{IdentifyAccount, Verify},
Perbill,
Perbill, Permill,
};

/// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type.
Expand Down Expand Up @@ -133,6 +133,7 @@ fn configure_genesis(
parachain_staking: ParachainStakingConfig {
stakers,
max_candidate_stake: staking::MAX_COLLATOR_STAKE,
max_commission_change: Permill::from_percent(100),
},
inflation_manager: Default::default(),
block_reward: BlockRewardConfig {
Expand Down
3 changes: 2 additions & 1 deletion node/src/parachain/krest_chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use peaq_primitives_xcm::{AccountId, Balance};
use runtime_common::TOKEN_DECIMALS;
use sc_service::{ChainType, Properties};
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
use sp_runtime::Perbill;
use sp_runtime::{Perbill, Permill};

use crate::parachain::dev_chain_spec::{authority_keys_from_seed, get_account_id_from_seed};

Expand Down Expand Up @@ -118,6 +118,7 @@ fn configure_genesis(
parachain_staking: ParachainStakingConfig {
stakers,
max_candidate_stake: staking::MAX_COLLATOR_STAKE,
max_commission_change: Permill::from_percent(100),
},
inflation_manager: Default::default(),
block_reward: BlockRewardConfig {
Expand Down
3 changes: 2 additions & 1 deletion node/src/parachain/peaq_chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use peaq_runtime::{
use runtime_common::TOKEN_DECIMALS;
use sc_service::{ChainType, Properties};
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
use sp_runtime::Perbill;
use sp_runtime::{Perbill, Permill};

use crate::parachain::dev_chain_spec::{authority_keys_from_seed, get_account_id_from_seed};

Expand Down Expand Up @@ -122,6 +122,7 @@ fn configure_genesis(
parachain_staking: ParachainStakingConfig {
stakers,
max_candidate_stake: staking::MAX_COLLATOR_STAKE,
max_commission_change: Permill::from_percent(100),
},
inflation_manager: Default::default(),
block_reward: BlockRewardConfig {
Expand Down
86 changes: 72 additions & 14 deletions pallets/parachain-staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,10 @@ pub mod pallet {

/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;

/// The minimum interval between two commission changes.
#[pallet::constant]
type CommissionChangeInterval: Get<BlockNumberFor<Self>>;
}

#[pallet::error]
Expand Down Expand Up @@ -432,6 +436,10 @@ pub mod pallet {
CommissionTooHigh,
/// Sudo cannot force new round if payouts are ongoing
PayoutsOngoing,
/// The commission change is too high.
CommissionChangeTooHigh,
/// The commission change is too frequent.
CommissionChangeTooEarly,
}

#[pallet::event]
Expand Down Expand Up @@ -519,6 +527,9 @@ pub mod pallet {
/// The commission for a collator has been changed.
/// \[collator's account, new commission\]
CollatorCommissionChanged(T::AccountId, Permill),
/// The commission maximum change has been changed.
/// \[new value\]
MaxCommissionChangeUpdated(Permill),
}

#[pallet::hooks]
Expand Down Expand Up @@ -677,15 +688,29 @@ pub mod pallet {
pub(crate) type DelayedPayoutInfo<T: Config> =
StorageValue<_, DelayedPayoutInfoT<SessionIndex, BalanceOf<T>>, OptionQuery>;

#[pallet::storage]
#[pallet::getter(fn last_commission_change)]
pub type LastCommissionChange<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, BlockNumberFor<T>, ValueQuery>;

#[pallet::storage]
#[pallet::getter(fn max_commission_change)]
pub type MaxCommissionChange<T> = StorageValue<_, Permill, ValueQuery>;

#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub stakers: GenesisStaker<T>,
pub max_candidate_stake: BalanceOf<T>,
pub max_commission_change: Permill,
}

impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self { stakers: Default::default(), max_candidate_stake: Default::default() }
Self {
stakers: Default::default(),
max_candidate_stake: Default::default(),
max_commission_change: Permill::from_percent(100),
}
}
}

Expand Down Expand Up @@ -722,6 +747,8 @@ pub mod pallet {
let round: RoundInfo<BlockNumberFor<T>> =
RoundInfo::new(0u32, 0u32.into(), T::DefaultBlocksPerRound::get());
<Round<T>>::put(round);

MaxCommissionChange::<T>::put(self.max_commission_change);
}
}

Expand Down Expand Up @@ -1969,21 +1996,52 @@ pub mod pallet {
))]
pub fn set_commission(origin: OriginFor<T>, commission: Permill) -> DispatchResult {
let collator = ensure_signed(origin)?;
CandidatePool::<T>::get(&collator).ok_or(Error::<T>::CandidateNotFound)?;
if commission > Permill::from_percent(100) {
return Err(Error::<T>::CommissionTooHigh.into())
}
let current_block = <frame_system::Pallet<T>>::block_number();

<crate::pallet::CandidatePool<T>>::mutate(&collator, |maybe_candidate| {
if let Some(candidate) = maybe_candidate {
candidate.set_commission(commission);
}
});
// Check if the collator exists
let mut candidate =
CandidatePool::<T>::get(&collator).ok_or(Error::<T>::CandidateNotFound)?;

// Emit an event that the commission was updated.
Self::deposit_event(crate::pallet::Event::CollatorCommissionChanged(
collator, commission,
));
// Check the time since the last commission change
let last_change = LastCommissionChange::<T>::get(&collator);
ensure!(
current_block >= last_change + T::CommissionChangeInterval::get(),
Error::<T>::CommissionChangeTooEarly
);

// Check the maximum change commission
let max_change = MaxCommissionChange::<T>::get();
let current_commission = candidate.commission;
let change = if commission > current_commission {
commission - current_commission
} else {
current_commission - commission
};
ensure!(change <= max_change, Error::<T>::CommissionChangeTooHigh);

// Update the commission and the last change time
candidate.set_commission(commission);
CandidatePool::<T>::insert(&collator, candidate);
LastCommissionChange::<T>::insert(&collator, current_block);

// Emit an event that the commission was updated
Self::deposit_event(Event::CollatorCommissionChanged(collator, commission));
Ok(())
}

#[pallet::call_index(20)]
#[pallet::weight(<T as crate::pallet::Config>::WeightInfo::set_max_commission_change(
Permill::from_percent(100).deconstruct()
))]
pub fn set_max_commission_change(
origin: OriginFor<T>,
new_max_commission_change: Permill,
) -> DispatchResult {
ensure_root(origin)?;

MaxCommissionChange::<T>::put(new_max_commission_change);

Self::deposit_event(Event::MaxCommissionChangeUpdated(new_max_commission_change));
Ok(())
}
}
Expand Down
10 changes: 9 additions & 1 deletion pallets/parachain-staking/src/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ pub enum Versions {
_V8 = 8,
V9 = 9,
V10 = 10,
#[default]
V11 = 11,
#[default]
V12 = 12,
}

pub(crate) fn on_runtime_upgrade<T: Config>() -> Weight {
Expand All @@ -30,6 +31,8 @@ pub(crate) fn on_runtime_upgrade<T: Config>() -> Weight {

mod upgrade {

use crate::MaxCommissionChange;

use super::*;

/// Migration implementation that deletes the old reward rate config and changes the staking ID.
Expand Down Expand Up @@ -102,6 +105,11 @@ mod upgrade {

log::info!("V11 Migrating Done.");
}

if onchain_storage_version < StorageVersion::new(Versions::V12 as u16) {
// Set the value of MaxCommissionChange to 10%
MaxCommissionChange::<T>::put(Permill::from_percent(10));
}
// update onchain storage version
StorageVersion::new(Versions::default() as u16).put::<Pallet<T>>();
weight_writes += 1;
Expand Down
15 changes: 11 additions & 4 deletions pallets/parachain-staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use sp_runtime::{
impl_opaque_keys,
testing::UintAuthorityId,
traits::{BlakeTwo256, ConvertInto, IdentityLookup, OpaqueKeys},
BuildStorage, Perbill,
BuildStorage, Perbill, Permill,
};
use sp_std::fmt::Debug;

Expand Down Expand Up @@ -154,6 +154,8 @@ parameter_types! {
pub const MinDelegatorStake: Balance = 5;
pub const MinDelegation: Balance = 3;
pub const MaxUnstakeRequests: u32 = 6;
pub const CommissionChangeInterval: BlockNumber = 1;

}

impl Config for Test {
Expand All @@ -177,6 +179,7 @@ impl Config for Test {
type MaxUnstakeRequests = MaxUnstakeRequests;
type PotId = PotId;
type WeightInfo = crate::weights::WeightInfo<Test>;
type CommissionChangeInterval = CommissionChangeInterval;
}

impl_opaque_keys! {
Expand Down Expand Up @@ -278,9 +281,13 @@ impl ExtBuilder {
for delegator in self.delegators.clone() {
stakers.push((delegator.0, Some(delegator.1), delegator.2));
}
stake::GenesisConfig::<Test> { stakers, max_candidate_stake: 160_000_000 * DECIMALS }
.assimilate_storage(&mut t)
.expect("Parachain Staking's storage can be assimilated");
stake::GenesisConfig::<Test> {
stakers,
max_candidate_stake: 160_000_000 * DECIMALS,
max_commission_change: Permill::from_percent(10),
}
.assimilate_storage(&mut t)
.expect("Parachain Staking's storage can be assimilated");

// stashes are the AccountId
let session_keys: Vec<_> = self
Expand Down
119 changes: 119 additions & 0 deletions pallets/parachain-staking/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3990,3 +3990,122 @@ fn check_snapshot_is_cleared() {
assert_eq!(at_stake.len(), 0);
});
}

#[test]
fn change_commission_too_frequently() {
ExtBuilder::default()
.with_balances(vec![(1, 1000), (2, 1000), (3, 1000)])
.with_collators(vec![(1, 500)])
.with_delegators(vec![(2, 1, 600), (3, 1, 400)])
.build()
.execute_with(|| {
assert!(System::events().is_empty());

assert_ok!(Balances::force_set_balance(
RawOrigin::Root.into(),
StakePallet::account_id(),
1000,
));

assert_ok!(StakePallet::set_commission(
RuntimeOrigin::signed(1),
Permill::from_percent(10)
));
let state = CandidatePool::<Test>::get(1).unwrap();
assert_eq!(state.commission, Permill::from_percent(10));
assert_eq!(
StakePallet::candidate_pool(1).unwrap().commission,
Permill::from_percent(10)
);

// change commission too frequently
assert_noop!(
StakePallet::set_commission(RuntimeOrigin::signed(1), Permill::from_percent(20)),
Error::<Test>::CommissionChangeTooEarly
);
// change commission too frequently
assert_noop!(
StakePallet::set_commission(RuntimeOrigin::signed(1), Permill::from_percent(30)),
Error::<Test>::CommissionChangeTooEarly
);
});
}

#[test]
fn change_commission_after_while() {
ExtBuilder::default()
.with_balances(vec![(1, 1000), (2, 1000), (3, 1000)])
.with_collators(vec![(1, 500)])
.with_delegators(vec![(2, 1, 600), (3, 1, 400)])
.build()
.execute_with(|| {
assert!(System::events().is_empty());

assert_ok!(Balances::force_set_balance(
RawOrigin::Root.into(),
StakePallet::account_id(),
1000,
));

assert_ok!(StakePallet::set_commission(
RuntimeOrigin::signed(1),
Permill::from_percent(10)
));
let state = CandidatePool::<Test>::get(1).unwrap();
assert_eq!(state.commission, Permill::from_percent(10));
assert_eq!(
StakePallet::candidate_pool(1).unwrap().commission,
Permill::from_percent(10)
);

// change commission after a while
roll_to(10, vec![]);
assert_ok!(StakePallet::set_commission(
RuntimeOrigin::signed(1),
Permill::from_percent(20)
));
let state = CandidatePool::<Test>::get(1).unwrap();
assert_eq!(state.commission, Permill::from_percent(20));
assert_eq!(
StakePallet::candidate_pool(1).unwrap().commission,
Permill::from_percent(20)
);
});
}

#[test]
fn change_commission_by_too_much() {
ExtBuilder::default()
.with_balances(vec![(1, 1000), (2, 1000), (3, 1000)])
.with_collators(vec![(1, 500)])
.with_delegators(vec![(2, 1, 600), (3, 1, 400)])
.build()
.execute_with(|| {
assert!(System::events().is_empty());

assert_ok!(Balances::force_set_balance(
RawOrigin::Root.into(),
StakePallet::account_id(),
1000,
));

assert_ok!(StakePallet::set_commission(
RuntimeOrigin::signed(1),
Permill::from_percent(10)
));
let state = CandidatePool::<Test>::get(1).unwrap();
assert_eq!(state.commission, Permill::from_percent(10));
assert_eq!(
StakePallet::candidate_pool(1).unwrap().commission,
Permill::from_percent(10)
);

roll_to(10, vec![]);

// change commission by too much
assert_noop!(
StakePallet::set_commission(RuntimeOrigin::signed(1), Permill::from_percent(30)),
Error::<Test>::CommissionChangeTooHigh
);
});
}
1 change: 1 addition & 0 deletions pallets/parachain-staking/src/weightinfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ pub trait WeightInfo {
fn unlock_unstaked(u: u32) -> Weight;
fn set_max_candidate_stake() -> Weight;
fn set_commission(n: u32, m: u32) -> Weight;
fn set_max_commission_change(n: u32) -> Weight;
}
Loading
Loading