Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
58 changes: 45 additions & 13 deletions pallets/parachain-staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,14 @@ pub mod pallet {

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

/// The maximum permill that a commission can change in one go.
#[pallet::constant]
type MaxCommissionChange: Get<Permill>;
Comment thread
DocteurPing marked this conversation as resolved.
Outdated

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

#[pallet::error]
Expand Down Expand Up @@ -432,6 +440,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 @@ -677,6 +689,11 @@ 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::genesis_config]
pub struct GenesisConfig<T: Config> {
pub stakers: GenesisStaker<T>,
Expand Down Expand Up @@ -1969,21 +1986,36 @@ 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 = T::MaxCommissionChange::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(())
}
}
Expand Down
7 changes: 6 additions & 1 deletion 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,9 @@ parameter_types! {
pub const MinDelegatorStake: Balance = 5;
pub const MinDelegation: Balance = 3;
pub const MaxUnstakeRequests: u32 = 6;
pub const MaxCommissionChange: Permill = Permill::from_percent(10);
pub const CommissionChangeInterval: BlockNumber = 1;

}

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

impl_opaque_keys! {
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
);
});
}
6 changes: 5 additions & 1 deletion precompiles/parachain-staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,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 @@ -206,6 +206,8 @@ parameter_types! {
pub const MinDelegatorStake: Balance = 5;
pub const MinDelegation: Balance = 3;
pub const MaxUnstakeRequests: u32 = 6;
pub const MaxCommissionChange: Permill = Permill::from_percent(10);
pub const CommissionChangeInterval: BlockNumber = 1;
}

impl parachain_staking::Config for Test {
Expand All @@ -229,6 +231,8 @@ impl parachain_staking::Config for Test {
type MaxUnstakeRequests = MaxUnstakeRequests;
type PotId = PotId;
type WeightInfo = parachain_staking::weights::WeightInfo<Test>;
type CommissionChangeInterval = CommissionChangeInterval;
type MaxCommissionChange = MaxCommissionChange;
}

impl_opaque_keys! {
Expand Down
4 changes: 4 additions & 0 deletions runtime/krest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,8 @@ pub mod staking {
pub const MaxCollatorCandidates: u32 = 128;
/// Maximum number of concurrent requests to unlock unstaked balance
pub const MaxUnstakeRequests: u32 = 10;
pub const MaxCommissionChange: Permill = Permill::from_percent(10); // Maximum 10% change
pub const CommissionChangeInterval: BlockNumber = DAYS; // 24 hours
}
}

Expand All @@ -851,6 +853,8 @@ impl parachain_staking::Config for Runtime {
type MaxUnstakeRequests = staking::MaxUnstakeRequests;

type WeightInfo = parachain_staking::weights::WeightInfo<Runtime>;
type CommissionChangeInterval = staking::CommissionChangeInterval;
type MaxCommissionChange = staking::MaxCommissionChange;
}

/// Implements the adapters for depositing unbalanced tokens on pots
Expand Down
5 changes: 5 additions & 0 deletions runtime/peaq-dev/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,9 @@ pub mod staking {
pub const MaxCollatorCandidates: u32 = 64;
/// Maximum number of concurrent requests to unlock unstaked balance
pub const MaxUnstakeRequests: u32 = 10;

pub const MaxCommissionChange: Permill = Permill::from_percent(10); // Maximum 10% change
pub const CommissionChangeInterval: BlockNumber = DAYS; // 24 hours
}
}

Expand All @@ -857,6 +860,8 @@ impl parachain_staking::Config for Runtime {
type MaxUnstakeRequests = staking::MaxUnstakeRequests;

type WeightInfo = parachain_staking::weights::WeightInfo<Runtime>;
type CommissionChangeInterval = staking::CommissionChangeInterval;
type MaxCommissionChange = staking::MaxCommissionChange;
}

/// Implements the adapters for depositing unbalanced tokens on pots
Expand Down
4 changes: 4 additions & 0 deletions runtime/peaq/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,8 @@ pub mod staking {
pub const MaxCollatorCandidates: u32 = 64;
/// Maximum number of concurrent requests to unlock unstaked balance
pub const MaxUnstakeRequests: u32 = 10;
pub const MaxCommissionChange: Permill = Permill::from_percent(10); // Maximum 10% change
pub const CommissionChangeInterval: BlockNumber = DAYS; // 24 hours
}
}

Expand All @@ -874,6 +876,8 @@ impl parachain_staking::Config for Runtime {
type MaxUnstakeRequests = staking::MaxUnstakeRequests;

type WeightInfo = parachain_staking::weights::WeightInfo<Runtime>;
type CommissionChangeInterval = staking::CommissionChangeInterval;
type MaxCommissionChange = staking::MaxCommissionChange;
}

/// Implements the adapters for depositing unbalanced tokens on pots
Expand Down