Skip to content

Commit

Permalink
pallet_staking: Introduce Additional Account for Inflation Distributi…
Browse files Browse the repository at this point in the history
…on Alongside PBR (#2976)

* new impl

* fix benchs

* fix tests

* fix bench

* fix tests and gen typescripts

* fix test

* lock

* prettier

* fmt

* wrap arr

* fix ts types

* upgrades

* link

* fix

* fix

* fmt

* fix

* fix fmt

* fix

* add tests

* add bench

* fix

* fix

* fix docs

* fix test

* fix smoke

* fix merge

* test-tmp

* fix

* fix

* fix

* fix

* dummy

* fix

* add test "Staking - Rewards - Bond + Treasury"

* fix

* fix comment

* apply suggestions
  • Loading branch information
TarekkMA authored Oct 7, 2024
1 parent 4bd16bf commit c779eb3
Show file tree
Hide file tree
Showing 48 changed files with 12,669 additions and 9,648 deletions.
55 changes: 48 additions & 7 deletions pallets/parachain-staking/src/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
//! Benchmarking
use crate::{
AwardedPts, BalanceOf, BottomDelegations, Call, CandidateBondLessRequest, Config,
DelegationAction, EnableMarkingOffline, Pallet, ParachainBondConfig, ParachainBondInfo, Points,
Range, RewardPayment, Round, ScheduledRequest, TopDelegations,
DelegationAction, EnableMarkingOffline, InflationDistributionAccount,
InflationDistributionConfig, InflationDistributionInfo, Pallet, Points, Range, RewardPayment,
Round, ScheduledRequest, TopDelegations,
};
use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite};
use frame_support::traits::{Currency, Get, OnFinalize, OnInitialize};
Expand Down Expand Up @@ -280,13 +281,43 @@ benchmarks! {
let parachain_bond_account: T::AccountId = account("TEST", 0u32, USER_SEED);
}: _(RawOrigin::Root, parachain_bond_account.clone())
verify {
assert_eq!(Pallet::<T>::parachain_bond_info().account, parachain_bond_account);
assert_eq!(Pallet::<T>::inflation_distribution_info().0[0].account, parachain_bond_account);
}

set_parachain_bond_reserve_percent {
}: _(RawOrigin::Root, Percent::from_percent(33))
verify {
assert_eq!(Pallet::<T>::parachain_bond_info().percent, Percent::from_percent(33));
assert_eq!(Pallet::<T>::inflation_distribution_info().0[0].percent, Percent::from_percent(33));
}

set_inflation_distribution_config {
}: _(RawOrigin::Root, [
InflationDistributionAccount {
account: account("TEST1", 0u32, USER_SEED),
percent: Percent::from_percent(33),
},
InflationDistributionAccount {
account: account("TEST2", 1u32, USER_SEED),
percent: Percent::from_percent(22),
},
].into())
verify {
assert_eq!(
Pallet::<T>::inflation_distribution_info().0[0].account,
account("TEST1", 0u32, USER_SEED)
);
assert_eq!(
Pallet::<T>::inflation_distribution_info().0[0].percent,
Percent::from_percent(33)
);
assert_eq!(
Pallet::<T>::inflation_distribution_info().0[1].account,
account("TEST2", 1u32, USER_SEED)
);
assert_eq!(
Pallet::<T>::inflation_distribution_info().0[1].percent,
Percent::from_percent(22)
);
}

// ROOT DISPATCHABLES
Expand Down Expand Up @@ -1554,7 +1585,7 @@ benchmarks! {
let payout_round = round.current - reward_delay;
// may need:
// <Points<T>>
// <ParachainBondInfo<T>>
// <InflationDistributionInfo<T>>
// ensure parachain bond account exists so that deposit_into_existing succeeds
<Points<T>>::insert(payout_round, 100);

Expand All @@ -1564,10 +1595,13 @@ benchmarks! {
0,
min_candidate_stk::<T>(),
).0;
<ParachainBondInfo<T>>::put(ParachainBondConfig {
<InflationDistributionInfo<T>>::put::<InflationDistributionConfig<T::AccountId>>([
InflationDistributionAccount {
account,
percent: Percent::from_percent(50),
});
},
Default::default(),
].into());

}: { Pallet::<T>::prepare_staking_payouts(round, current_slot); }
verify {
Expand Down Expand Up @@ -2345,6 +2379,13 @@ mod tests {
});
}

#[test]
fn bench_set_inflation_distribution_config() {
new_test_ext().execute_with(|| {
assert_ok!(Pallet::<Test>::test_benchmark_set_inflation_distribution_config());
});
}

#[test]
fn bench_set_total_selected() {
new_test_ext().execute_with(|| {
Expand Down
132 changes: 83 additions & 49 deletions pallets/parachain-staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ pub mod pallet {
CannotSetBelowMin,
RoundLengthMustBeGreaterThanTotalSelectedCollators,
NoWritingSameValue,
TotalInflationDistributionPercentExceeds100,
TooLowCandidateCountWeightHintJoinCandidates,
TooLowCandidateCountWeightHintCancelLeaveCandidates,
TooLowCandidateCountToLeaveCandidates,
Expand Down Expand Up @@ -400,17 +401,15 @@ pub mod pallet {
rewards: BalanceOf<T>,
},
/// Transferred to account which holds funds reserved for parachain bond.
ReservedForParachainBond {
InflationDistributed {
index: u32,
account: T::AccountId,
value: BalanceOf<T>,
},
/// Account (re)set for parachain bond treasury.
ParachainBondAccountSet {
old: T::AccountId,
new: T::AccountId,
InflationDistributionConfigUpdated {
old: InflationDistributionConfig<T::AccountId>,
new: InflationDistributionConfig<T::AccountId>,
},
/// Percent of inflation reserved for parachain bond (re)set.
ParachainBondReservePercentSet { old: Percent, new: Percent },
/// Annual inflation input (first 3) was used to derive new per-round inflation (last 3)
InflationSet {
annual_min: Perbill,
Expand Down Expand Up @@ -518,10 +517,13 @@ pub mod pallet {
pub(crate) type TotalSelected<T: Config> = StorageValue<_, u32, ValueQuery>;

#[pallet::storage]
#[pallet::getter(fn parachain_bond_info)]
/// Parachain bond config info { account, percent_of_inflation }
pub(crate) type ParachainBondInfo<T: Config> =
StorageValue<_, ParachainBondConfig<T::AccountId>, ValueQuery>;
#[pallet::getter(fn inflation_distribution_info)]
/// Inflation distribution configuration, including accounts that should receive inflation
/// before it is distributed to collators and delegators.
///
/// The sum of the distribution percents must be less than or equal to 100.
pub(crate) type InflationDistributionInfo<T: Config> =
StorageValue<_, InflationDistributionConfig<T::AccountId>, ValueQuery>;

#[pallet::storage]
#[pallet::getter(fn round)]
Expand Down Expand Up @@ -787,12 +789,20 @@ pub mod pallet {
// Set collator commission to default config
<CollatorCommission<T>>::put(self.collator_commission);
// Set parachain bond config to default config
<ParachainBondInfo<T>>::put(ParachainBondConfig {
let pbr = InflationDistributionAccount {
// must be set soon; if not => due inflation will be sent to collators/delegators
account: T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
.expect("infinite length input; no invalid inputs for type; qed"),
percent: self.parachain_bond_reserve_percent,
});
};
let zeroed_account = InflationDistributionAccount {
account: T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
.expect("infinite length input; no invalid inputs for type; qed"),
percent: Percent::zero(),
};
<InflationDistributionInfo<T>>::put::<InflationDistributionConfig<T::AccountId>>(
[pbr, zeroed_account].into(),
);
// Set total selected candidates to value from config
assert!(
self.num_selected_candidates >= T::MinSelectedCandidates::get(),
Expand Down Expand Up @@ -872,46 +882,40 @@ pub mod pallet {
Ok(().into())
}

/// Set the account that will hold funds set aside for parachain bond
/// Deprecated: please use `set_inflation_distribution_config` instead.
///
/// Set the account that will hold funds set aside for parachain bond
#[pallet::call_index(2)]
#[pallet::weight(<T as Config>::WeightInfo::set_parachain_bond_account())]
pub fn set_parachain_bond_account(
origin: OriginFor<T>,
new: T::AccountId,
) -> DispatchResultWithPostInfo {
T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
let ParachainBondConfig {
account: old,
percent,
} = <ParachainBondInfo<T>>::get();
ensure!(old != new, Error::<T>::NoWritingSameValue);
<ParachainBondInfo<T>>::put(ParachainBondConfig {
account: new.clone(),
percent,
});
Self::deposit_event(Event::ParachainBondAccountSet { old, new });
Ok(().into())
T::MonetaryGovernanceOrigin::ensure_origin(origin.clone())?;
let old = <InflationDistributionInfo<T>>::get().0;
let new = InflationDistributionAccount {
account: new,
percent: old[0].percent.clone(),
};
Pallet::<T>::set_inflation_distribution_config(origin, [new, old[1].clone()].into())
}

/// Deprecated: please use `set_inflation_distribution_config` instead.
///
/// Set the percent of inflation set aside for parachain bond
#[pallet::call_index(3)]
#[pallet::weight(<T as Config>::WeightInfo::set_parachain_bond_reserve_percent())]
pub fn set_parachain_bond_reserve_percent(
origin: OriginFor<T>,
new: Percent,
) -> DispatchResultWithPostInfo {
T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
let ParachainBondConfig {
account,
percent: old,
} = <ParachainBondInfo<T>>::get();
ensure!(old != new, Error::<T>::NoWritingSameValue);
<ParachainBondInfo<T>>::put(ParachainBondConfig {
account,
T::MonetaryGovernanceOrigin::ensure_origin(origin.clone())?;
let old = <InflationDistributionInfo<T>>::get().0;
let new = InflationDistributionAccount {
account: old[0].account.clone(),
percent: new,
});
Self::deposit_event(Event::ParachainBondReservePercentSet { old, new });
Ok(().into())
};
Pallet::<T>::set_inflation_distribution_config(origin, [new, old[1].clone()].into())
}

/// Set the total number of collator candidates selected per round
Expand Down Expand Up @@ -1465,6 +1469,32 @@ pub mod pallet {
T::MonetaryGovernanceOrigin::ensure_origin(origin.clone())?;
Self::join_candidates_inner(account, bond, candidate_count)
}

/// Set the inflation distribution configuration.
#[pallet::call_index(32)]
#[pallet::weight(<T as Config>::WeightInfo::set_inflation_distribution_config())]
pub fn set_inflation_distribution_config(
origin: OriginFor<T>,
new: InflationDistributionConfig<T::AccountId>,
) -> DispatchResultWithPostInfo {
T::MonetaryGovernanceOrigin::ensure_origin(origin)?;
let old = <InflationDistributionInfo<T>>::get().0;
let new = new.0;
ensure!(old != new, Error::<T>::NoWritingSameValue);
let total_percent = new.iter().fold(0, |acc, x| acc + x.percent.deconstruct());
ensure!(
total_percent <= 100,
Error::<T>::TotalInflationDistributionPercentExceeds100,
);
<InflationDistributionInfo<T>>::put::<InflationDistributionConfig<T::AccountId>>(
new.clone().into(),
);
Self::deposit_event(Event::InflationDistributionConfigUpdated {
old: old.into(),
new: new.into(),
});
Ok(().into())
}
}

/// Represents a payout made via `pay_one_collator_reward`.
Expand Down Expand Up @@ -1836,20 +1866,24 @@ pub mod pallet {

// Compute total issuance based on round duration
let total_issuance = Self::compute_issuance(round_duration, round_length);

// reserve portion of issuance for parachain bond account
let mut left_issuance = total_issuance;
let bond_config = <ParachainBondInfo<T>>::get();
let parachain_bond_reserve = bond_config.percent * total_issuance;
if let Ok(imb) =
T::Currency::deposit_into_existing(&bond_config.account, parachain_bond_reserve)
{
// update round issuance if transfer succeeds
left_issuance = left_issuance.saturating_sub(imb.peek());
Self::deposit_event(Event::ReservedForParachainBond {
account: bond_config.account,
value: imb.peek(),
});

let configs = <InflationDistributionInfo<T>>::get().0;
for (index, config) in configs.iter().enumerate() {
if config.percent.is_zero() {
continue;
}
let reserve = config.percent * total_issuance;
if let Ok(imb) = T::Currency::deposit_into_existing(&config.account, reserve) {
// update round issuance if transfer succeeds
left_issuance = left_issuance.saturating_sub(imb.peek());
Self::deposit_event(Event::InflationDistributed {
index: index as u32,
account: config.account.clone(),
value: imb.peek(),
});
}
}

let payout = DelayedPayout {
Expand Down
87 changes: 87 additions & 0 deletions pallets/parachain-staking/src/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,90 @@

// You should have received a copy of the GNU General Public License
// along with Moonbeam. If not, see <http://www.gnu.org/licenses/>.

use frame_support::{traits::OnRuntimeUpgrade, weights::Weight};

use crate::*;

#[derive(
Clone,
PartialEq,
Eq,
parity_scale_codec::Decode,
parity_scale_codec::Encode,
sp_runtime::RuntimeDebug,
)]
/// Reserve information { account, percent_of_inflation }
pub struct OldParachainBondConfig<AccountId> {
/// Account which receives funds intended for parachain bond
pub account: AccountId,
/// Percent of inflation set aside for parachain bond account
pub percent: sp_runtime::Percent,
}

pub struct MigrateParachainBondConfig<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateParachainBondConfig<T> {
fn on_runtime_upgrade() -> Weight {
let (account, percent) = if let Some(config) =
frame_support::storage::migration::get_storage_value::<
OldParachainBondConfig<T::AccountId>,
>(b"ParachainStaking", b"ParachainBondInfo", &[])
{
(config.account, config.percent)
} else {
return Weight::default();
};

let pbr = InflationDistributionAccount { account, percent };
let treasury = InflationDistributionAccount::<T::AccountId>::default();
let configs: InflationDistributionConfig<T::AccountId> = [pbr, treasury].into();

//***** Start mutate storage *****//

InflationDistributionInfo::<T>::put(configs);

// Remove storage value ParachainStaking::ParachainBondInfo
frame_support::storage::unhashed::kill(&frame_support::storage::storage_prefix(
b"ParachainStaking",
b"ParachainBondInfo",
));

Weight::default()
}

#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::DispatchError> {
use frame_support::ensure;
use parity_scale_codec::Encode;

let state = frame_support::storage::migration::get_storage_value::<
OldParachainBondConfig<T::AccountId>,
>(b"ParachainStaking", b"ParachainBondInfo", &[]);

ensure!(state.is_some(), "State not found");

Ok(state.unwrap().encode())
}

#[cfg(feature = "try-runtime")]
fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::DispatchError> {
use frame_support::ensure;

let old_state: OldParachainBondConfig<T::AccountId> =
parity_scale_codec::Decode::decode(&mut &state[..])
.map_err(|_| sp_runtime::DispatchError::Other("Failed to decode old state"))?;

let new_state = InflationDistributionInfo::<T>::get();

let pbr = InflationDistributionAccount {
account: old_state.account,
percent: old_state.percent,
};
let treasury = InflationDistributionAccount::<T::AccountId>::default();
let expected_new_state: InflationDistributionConfig<T::AccountId> = [pbr, treasury].into();

ensure!(new_state == expected_new_state, "State migration failed");

Ok(())
}
}
Loading

0 comments on commit c779eb3

Please sign in to comment.