Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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