Skip to content
Open
Show file tree
Hide file tree
Changes from 11 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
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(
Comment thread
DocteurPing marked this conversation as resolved.
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