Skip to content

Commit

Permalink
Update parachain staking (#1460)
Browse files Browse the repository at this point in the history
* Fix staking collator snapshot to use total counted instead of total exposure(1719)

* deprecate delegator leave in favor of batch schedule revoke(1760)

* move Default config associated types to GenesisConfig(1798)

* Fix overflow for large round numbers(1817)

* Fix clippy
  • Loading branch information
hqwangningbo authored Oct 12, 2024
1 parent 9ac8443 commit c62e816
Show file tree
Hide file tree
Showing 12 changed files with 138 additions and 107 deletions.
17 changes: 12 additions & 5 deletions node/service/src/chain_spec/bifrost_kusama.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@

use crate::chain_spec::{get_account_id_from_seed, get_from_seed, RelayExtensions};
use bifrost_kusama_runtime::{
constants::currency::DOLLARS, AccountId, Balance, BalancesConfig, BlockNumber,
DefaultBlocksPerRound, InflationInfo, Range, SS58Prefix, VestingConfig,
constants::currency::DOLLARS, AccountId, Balance, BalancesConfig, BlockNumber, InflationInfo,
Range, SS58Prefix, VestingConfig,
};
use bifrost_primitives::{
BifrostKusamaChainId, CurrencyId, CurrencyId::*, TokenInfo, TokenSymbol::*,
};
use bifrost_runtime_common::AuraId;
use bifrost_runtime_common::{constants::time::HOURS, AuraId};
use cumulus_primitives_core::ParaId;
use frame_benchmarking::{account, whitelisted_caller};
use hex_literal::hex;
Expand All @@ -33,7 +33,7 @@ use sc_service::ChainType;
use serde::de::DeserializeOwned;
use serde_json as json;
use sp_core::{crypto::UncheckedInto, sr25519};
use sp_runtime::{traits::Zero, Perbill};
use sp_runtime::{traits::Zero, Perbill, Percent};
use std::{
collections::BTreeMap,
fs::{read_dir, File},
Expand All @@ -50,6 +50,10 @@ pub fn ENDOWMENT() -> u128 {
1_000_000 * DOLLARS
}

const COLLATOR_COMMISSION: Perbill = Perbill::from_percent(10);
const PARACHAIN_BOND_RESERVE_PERCENT: Percent = Percent::from_percent(0);
const BLOCKS_PER_ROUND: u32 = 2 * HOURS;

pub fn inflation_config() -> InflationInfo<Balance> {
fn to_round_inflation(annual: Range<Perbill>) -> Range<Perbill> {
use bifrost_parachain_staking::inflation::{
Expand All @@ -58,7 +62,7 @@ pub fn inflation_config() -> InflationInfo<Balance> {
perbill_annual_to_perbill_round(
annual,
// rounds per year
BLOCKS_PER_YEAR / DefaultBlocksPerRound::get(),
BLOCKS_PER_YEAR / BLOCKS_PER_ROUND,
)
}
let annual = Range {
Expand Down Expand Up @@ -171,6 +175,9 @@ pub fn bifrost_genesis(
.collect::<Vec<_>>(),
"delegations": delegations,
"inflationConfig": inflation_config(),
"collatorCommission": COLLATOR_COMMISSION,
"parachainBondReservePercent": PARACHAIN_BOND_RESERVE_PERCENT,
"blocksPerRound": BLOCKS_PER_ROUND,
},
})
}
Expand Down
4 changes: 3 additions & 1 deletion pallets/buy-back/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ pub mod pallet {
n.saturating_sub(info.last_buyback_cycle)
.saturated_into::<u32>()
.saturating_sub(One::one()) =>
{
if let Some(swap_out_min) = SwapOutMin::<T>::get(currency_id) {
if let Some(e) =
Self::buy_back(&buyback_address, currency_id, &info, swap_out_min)
Expand All @@ -295,7 +296,8 @@ pub mod pallet {
info.last_buyback = n;
Infos::<T>::insert(currency_id, info);
SwapOutMin::<T>::remove(currency_id);
},
}
},
_ => (),
}
}
Expand Down
1 change: 1 addition & 0 deletions pallets/parachain-staking/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ std = [
"pallet-authorship/std",
"pallet-session/std",
"log/std",
"bifrost-primitives/std",
]
runtime-benchmarks = ["frame-benchmarking"]
try-runtime = ["frame-support/try-runtime"]
26 changes: 19 additions & 7 deletions pallets/parachain-staking/src/inflation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
use sp_runtime::{PerThing, Perbill, RuntimeDebug};
use substrate_fixed::{
transcendental::pow as floatpow,
types::{I32F32, I64F64},
};
use substrate_fixed::{transcendental::pow as floatpow, types::I64F64};

use crate::{
pallet::{BalanceOf, Config},
Expand Down Expand Up @@ -76,10 +73,10 @@ pub fn perbill_annual_to_perbill_round(
annual: Range<Perbill>,
rounds_per_year: u32,
) -> Range<Perbill> {
let exponent = I32F32::from_num(1) / I32F32::from_num(rounds_per_year);
let exponent = I64F64::from_num(1) / I64F64::from_num(rounds_per_year);
let annual_to_round = |annual: Perbill| -> Perbill {
let x = I32F32::from_num(annual.deconstruct()) / I32F32::from_num(Perbill::ACCURACY);
let y: I64F64 = floatpow(I32F32::from_num(1) + x, exponent)
let x = I64F64::from_num(annual.deconstruct()) / I64F64::from_num(Perbill::ACCURACY);
let y: I64F64 = floatpow(I64F64::from_num(1) + x, exponent)
.expect("Cannot overflow since rounds_per_year is u32 so worst case 0; QED");
Perbill::from_parts(
((y - I64F64::from_num(1)) * I64F64::from_num(Perbill::ACCURACY))
Expand Down Expand Up @@ -208,4 +205,19 @@ mod tests {
mock_round_issuance_range(10_000_000, mock_annual_to_round(schedule, 8766))
);
}

#[test]
fn inflation_does_not_panic_at_round_number_limit() {
let schedule = Range {
min: Perbill::from_percent(100),
ideal: Perbill::from_percent(100),
max: Perbill::from_percent(100),
};
mock_round_issuance_range(u32::MAX.into(), mock_annual_to_round(schedule, u32::MAX));
mock_round_issuance_range(u64::MAX.into(), mock_annual_to_round(schedule, u32::MAX));
mock_round_issuance_range(u128::MAX.into(), mock_annual_to_round(schedule, u32::MAX));
mock_round_issuance_range(u32::MAX.into(), mock_annual_to_round(schedule, 1));
mock_round_issuance_range(u64::MAX.into(), mock_annual_to_round(schedule, 1));
mock_round_issuance_range(u128::MAX.into(), mock_annual_to_round(schedule, 1));
}
}
83 changes: 53 additions & 30 deletions pallets/parachain-staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,6 @@ pub mod pallet {
/// Minimum number of blocks per round
#[pallet::constant]
type MinBlocksPerRound: Get<u32>;
/// Default number of blocks per round at genesis
#[pallet::constant]
type DefaultBlocksPerRound: Get<u32>;
/// Number of rounds that candidates remain bonded before exit request is executable
#[pallet::constant]
type LeaveCandidatesDelay: Get<RoundIndex>;
Expand Down Expand Up @@ -164,12 +161,6 @@ pub mod pallet {
/// Maximum delegations per delegator
#[pallet::constant]
type MaxDelegationsPerDelegator: Get<u32>;
/// Default commission due to collators, is `CollatorCommission` storage value in genesis
#[pallet::constant]
type DefaultCollatorCommission: Get<Perbill>;
/// Default percent of inflation set aside for parachain bond account
#[pallet::constant]
type DefaultParachainBondReservePercent: Get<Percent>;
/// Minimum stake required for any candidate to be in `SelectedCandidates` for the round
#[pallet::constant]
type MinCollatorStk: Get<BalanceOf<Self>>;
Expand Down Expand Up @@ -591,18 +582,41 @@ pub mod pallet {
>;

#[pallet::genesis_config]
#[derive(frame_support::DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
/// Initialize balance and register all as collators: `(collator AccountId, balance
/// Amount)`
pub candidates: Vec<(AccountIdOf<T>, BalanceOf<T>)>,
/// Vec of tuples of the format (delegator AccountId, collator AccountId, delegation
/// Amount)
/// Initialize balance and make delegations:
/// `(delegator AccountId, collator AccountId, delegation Amount, auto-compounding
/// Percent)`
pub delegations: Vec<(AccountIdOf<T>, AccountIdOf<T>, BalanceOf<T>)>,
/// Inflation configuration
pub inflation_config: InflationInfo<BalanceOf<T>>,
/// Default fixed percent a collator takes off the top of due rewards
pub collator_commission: Perbill,
/// Default percent of inflation set aside for parachain bond every round
pub parachain_bond_reserve_percent: Percent,
/// Default number of blocks in a round
pub blocks_per_round: u32,
}

impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self {
candidates: vec![],
delegations: vec![],
inflation_config: Default::default(),
collator_commission: Default::default(),
parachain_bond_reserve_percent: Default::default(),
blocks_per_round: 1u32,
}
}
}

#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
assert!(self.blocks_per_round > 0, "Blocks per round must be > 0");
<InflationConfig<T>>::put(self.inflation_config.clone());
let mut candidate_count = 0u32;
// Initialize the candidates
Expand Down Expand Up @@ -656,15 +670,15 @@ pub mod pallet {
}
}
// Set collator commission to default config
<CollatorCommission<T>>::put(T::DefaultCollatorCommission::get());
<CollatorCommission<T>>::put(self.collator_commission);
// Set parachain bond config to default config
<ParachainBondInfo<T>>::put(ParachainBondConfig {
// must be set soon; if not => due inflation will be sent to collators/delegators
account: AccountIdOf::<T>::decode(
&mut sp_runtime::traits::TrailingZeroInput::zeroes(),
)
.expect("infinite length input; no invalid inputs for type; qed"),
percent: T::DefaultParachainBondReservePercent::get(),
percent: self.parachain_bond_reserve_percent,
payment_in_round: T::PaymentInRound::get(),
});
// Set total selected candidates to minimum config
Expand All @@ -673,7 +687,7 @@ pub mod pallet {
let (v_count, _, total_staked) = <Pallet<T>>::select_top_candidates(1u32);
// Start Round 1 at Block 0
let round: RoundInfo<BlockNumberFor<T>> =
RoundInfo::new(1u32, 0u32.into(), T::DefaultBlocksPerRound::get());
RoundInfo::new(1u32, 0u32.into(), self.blocks_per_round);
<Round<T>>::put(round);
// Snapshot total stake
<Staked<T>>::insert(1u32, <Total<T>>::get());
Expand Down Expand Up @@ -1134,18 +1148,21 @@ pub mod pallet {
)
}

#[pallet::call_index(18)]
#[pallet::weight(<T as Config>::WeightInfo::schedule_leave_delegators())]
/// DEPRECATED use batch util with schedule_revoke_delegation for all delegations
/// Request to leave the set of delegators. If successful, the caller is scheduled to be
/// allowed to exit via a [DelegationAction::Revoke] towards all existing delegations.
/// Success forbids future delegation requests until the request is invoked or cancelled.
#[pallet::call_index(18)]
#[pallet::weight(<T as Config>::WeightInfo::schedule_leave_delegators())]
pub fn schedule_leave_delegators(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let delegator = ensure_signed(origin)?;
Self::delegator_schedule_revoke_all(delegator)
}

/// DEPRECATED use batch util with execute_delegation_request for all delegations
/// Execute the right to exit the set of delegators and revoke all ongoing delegations.
#[pallet::call_index(19)]
#[pallet::weight(<T as Config>::WeightInfo::execute_leave_delegators(*delegation_count))]
/// Execute the right to exit the set of delegators and revoke all ongoing delegations.
pub fn execute_leave_delegators(
origin: OriginFor<T>,
delegator: AccountIdOf<T>,
Expand All @@ -1154,10 +1171,12 @@ pub mod pallet {
ensure_signed(origin)?;
Self::delegator_execute_scheduled_revoke_all(delegator, delegation_count)
}
#[pallet::call_index(20)]
#[pallet::weight(<T as Config>::WeightInfo::cancel_leave_delegators())]

/// DEPRECATED use batch util with cancel_delegation_request for all delegations
/// Cancel a pending request to exit the set of delegators. Success clears the pending exit
/// request (thereby resetting the delay upon another `leave_delegators` call).
#[pallet::call_index(20)]
#[pallet::weight(<T as Config>::WeightInfo::cancel_leave_delegators())]
pub fn cancel_leave_delegators(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let delegator = ensure_signed(origin)?;
Self::delegator_cancel_scheduled_revoke_all(delegator)
Expand Down Expand Up @@ -1710,19 +1729,21 @@ pub mod pallet {
collator_count = collator_count.saturating_add(1u32);
delegation_count = delegation_count.saturating_add(state.delegation_count);
total = total.saturating_add(state.total_counted);
let snapshot_total = state.total_counted;
let top_rewardable_delegations = Self::get_rewardable_delegators(account);

let CountedDelegations { uncounted_stake, rewardable_delegations } =
Self::get_rewardable_delegators(&account);
let total_counted = state.total_counted.saturating_sub(uncounted_stake);

let snapshot = CollatorSnapshot {
bond: state.bond,
delegations: top_rewardable_delegations,
total: state.total_counted,
delegations: rewardable_delegations,
total: total_counted,
};
<AtStake<T>>::insert(now, account, snapshot);
Self::deposit_event(Event::CollatorChosen {
round: now,
collator_account: account.clone(),
total_exposed_amount: snapshot_total,
total_exposed_amount: state.total_counted,
});
}
// insert canonical collator set
Expand All @@ -1739,15 +1760,14 @@ pub mod pallet {
/// - else, do nothing
///
/// The intended bond amounts will be used while calculating rewards.
fn get_rewardable_delegators(
collator: &AccountIdOf<T>,
) -> Vec<Bond<AccountIdOf<T>, BalanceOf<T>>> {
fn get_rewardable_delegators(collator: &AccountIdOf<T>) -> CountedDelegations<T> {
let requests = <DelegationScheduledRequests<T>>::get(collator)
.into_iter()
.map(|x| (x.delegator, x.action))
.collect::<BTreeMap<_, _>>();

<TopDelegations<T>>::get(collator)
let mut uncounted_stake = BalanceOf::<T>::zero();
let rewardable_delegations = <TopDelegations<T>>::get(collator)
.expect("all members of CandidateQ must be candidates")
.delegations
.into_iter()
Expand All @@ -1760,6 +1780,7 @@ pub mod pallet {
revoke request",
bond.owner
);
uncounted_stake = uncounted_stake.saturating_add(bond.amount);
BalanceOf::<T>::zero()
},
Some(DelegationAction::Decrease(amount)) => {
Expand All @@ -1768,13 +1789,15 @@ pub mod pallet {
decrease request",
bond.owner
);
uncounted_stake = uncounted_stake.saturating_add(*amount);
bond.amount.saturating_sub(*amount)
},
};

bond
})
.collect()
.collect();
CountedDelegations { uncounted_stake, rewardable_delegations }
}

/// Temporary JIT migration of a single delegator's reserve -> lock. This will query
Expand Down
12 changes: 8 additions & 4 deletions pallets/parachain-staking/src/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use frame_system::pallet_prelude::BlockNumberFor;
use scale_info::prelude::string::String;
use sp_runtime::{
traits::{AccountIdConversion, Saturating, Zero},
Perbill, TryRuntimeError,
Perbill, Percent, TryRuntimeError,
};
use sp_std::{convert::TryInto, vec::Vec};

Expand All @@ -53,6 +53,10 @@ use crate::{
RoundInfo, Staked, TopDelegations, TotalSelected,
};

const COLLATOR_COMMISSION: Perbill = Perbill::from_percent(10);
const PARACHAIN_BOND_RESERVE_PERCENT: Percent = Percent::from_percent(0);
const BLOCKS_PER_ROUND: u32 = 2 * 300;

/// Migration to purge staking storage bloat for `Points` and `AtStake` storage items
pub struct InitGenesisMigration<T>(PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for InitGenesisMigration<T> {
Expand Down Expand Up @@ -97,12 +101,12 @@ impl<T: Config> OnRuntimeUpgrade for InitGenesisMigration<T> {
}
}
// Set collator commission to default config
<CollatorCommission<T>>::put(T::DefaultCollatorCommission::get());
<CollatorCommission<T>>::put(COLLATOR_COMMISSION);
// Set parachain bond config to default config
<ParachainBondInfo<T>>::put(ParachainBondConfig {
// must be set soon; if not => due inflation will be sent to collators/delegators
account: T::PalletId::get().into_account_truncating(),
percent: T::DefaultParachainBondReservePercent::get(),
percent: PARACHAIN_BOND_RESERVE_PERCENT,
payment_in_round: T::PaymentInRound::get(),
});
// Set total selected candidates to minimum config
Expand All @@ -111,7 +115,7 @@ impl<T: Config> OnRuntimeUpgrade for InitGenesisMigration<T> {
<Pallet<T>>::select_top_candidates(1u32);
// Start Round 1 at Block 0
let round: RoundInfo<BlockNumberFor<T>> =
RoundInfo::new(1u32, 0u32.into(), T::DefaultBlocksPerRound::get());
RoundInfo::new(1u32, 0u32.into(), BLOCKS_PER_ROUND);
<Round<T>>::put(round);
// Snapshot total stake
<Staked<T>>::insert(1u32, <Total<T>>::get());
Expand Down
Loading

0 comments on commit c62e816

Please sign in to comment.