Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: impl block rewards #1198

Merged
merged 83 commits into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
b9bd2c4
chore: init block rewards from liquidity rewards clone
wischli Feb 6, 2023
a69bb5e
feat: adjust extrinsics, add SessionManager
wischli Feb 7, 2023
a2f9a79
refactor: use OneSessionHandler, handle minting, add docs
wischli Feb 10, 2023
1c81915
chore: remove admin deposit/withdraw_stake
wischli Feb 10, 2023
c4dbe47
chore: impl BlockRewards for dev runtime
wischli Feb 10, 2023
795036d
feat: make reward source configurable
wischli Feb 13, 2023
664eee6
Merge remote-tracking branch 'origin' into feat/impl-block-rewards
wischli Feb 13, 2023
020ec78
feat: mint remainder
wischli Feb 13, 2023
ccae5c6
feat: finalize session handling
wischli Feb 13, 2023
a99a583
feat: init rewards at genesis
wischli Feb 14, 2023
1ee21af
fix: collator, treasury reward split
wischli Feb 14, 2023
257308d
refactor: use OnUnbalanced instead of PalletId
wischli Feb 14, 2023
bdb6d12
fix: beneficiary reward currency
wischli Feb 15, 2023
97292e2
chore: remove dummy bench, mock, tests
wischli Feb 15, 2023
4c48350
fix: session + collator selection benches
wischli Feb 15, 2023
6fa7c4a
refactor: hardcode many ATs
wischli Feb 15, 2023
0d73cf3
refactor: reward source v2
wischli Feb 16, 2023
a5ac903
refactor: issuance of rewards
wischli Feb 16, 2023
910139a
tests: apply RewardIssuance
wischli Feb 17, 2023
8b8c40f
refactor: total_rewards instead of beneficiary
wischli Feb 17, 2023
c5340dd
tests: add mock
wischli Feb 17, 2023
70bcdc9
fix: reward_to_mint
wischli Feb 21, 2023
4ad2bb0
tests: extend mock
wischli Feb 21, 2023
ce7d9d2
fix: new session handler, genesis setup
wischli Feb 21, 2023
e94c1d0
tests: almost done
wischli Feb 21, 2023
df29f21
feat: init new rewards instance in dev runtime
wischli Feb 21, 2023
9ccc965
tests: finish
wischli Feb 22, 2023
00fb722
Merge remote-tracking branch 'origin' into feat/impl-block-rewards
wischli Feb 22, 2023
6ecd801
fix: clippy
wischli Feb 22, 2023
12a1f6f
tests: add pallet_restricted_tokens
wischli Feb 22, 2023
70b46bb
tests: add ci
wischli Feb 27, 2023
6a23f9a
refactor: split up ci tests
wischli Feb 27, 2023
95770df
chore: add block_rewards to dev genesis cfg
wischli Feb 28, 2023
bce628a
feat: add benchmarks
wischli Feb 28, 2023
8aad4b0
Merge remote-tracking branch 'origin' into feat/impl-block-rewards
wischli Feb 28, 2023
c03962a
fix: add missing encoding to rewards currency
wischli Feb 28, 2023
b21e10a
fix: clippy
wischli Feb 28, 2023
2de4fb6
chore: bump nix
wischli Feb 28, 2023
48fd15e
ci: add fast-runtime flag to integration tests
wischli Feb 28, 2023
0a5c366
chore: remove open TODOs
wischli Feb 28, 2023
6971b5a
fmt: taplo
wischli Feb 28, 2023
17b6c67
refactor: replace epoch with session for b rewards
wischli Feb 28, 2023
69f703c
Merge remote-tracking branch 'origin' into feat/impl-block-rewards
wischli Feb 28, 2023
cec502e
tests: temp fix pools env
wischli Mar 1, 2023
f8044e5
feat: add block rewards init migration
wischli Mar 1, 2023
55b0272
fix: migration
wischli Mar 2, 2023
373a5ed
fix: add missing try-runtime feat to all tomls
wischli Mar 2, 2023
1d3da74
chore: update codeowners
wischli Mar 3, 2023
34a7c57
style: fmt
wischli Mar 3, 2023
60194a9
chore: apply self-review
wischli Mar 3, 2023
404e564
Merge remote-tracking branch 'origin' into feat/impl-block-rewards
wischli Mar 6, 2023
144ed20
fix: issues after rebasing
wischli Mar 6, 2023
c1304aa
chore: add session fail event
wischli Mar 7, 2023
78deabb
chore: add benches for session, collator selection
wischli Mar 9, 2023
5556b2c
fix: benchmarks
wischli Mar 10, 2023
5597f73
refactor: move pallet consts
wischli Mar 10, 2023
fdcd632
chore: add saturated conversion comment
wischli Mar 10, 2023
5f84538
refactor: remove unused Rewards requirement
wischli Mar 10, 2023
7070475
refactor: re-add staking currency
wischli Mar 16, 2023
43f6127
refactor: move block rewards consts
wischli Mar 16, 2023
7d4251c
Merge remote-tracking branch 'origin' into feat/impl-block-rewards
wischli Mar 16, 2023
19fb92a
chore: nix
wischli Mar 16, 2023
dd50f66
chore: provide conversion for StakingCurrency
wischli Mar 16, 2023
b2cbf25
Merge remote-tracking branch 'origin' into feat/impl-block-rewards
wischli Mar 16, 2023
ae05055
fix: lint
wischli Mar 16, 2023
b936c39
docs: improvements
wischli Mar 16, 2023
d5a4f1a
chore: bump nix
wischli Mar 16, 2023
93a481c
refactor: add StakeCurrency as associated type
wischli Mar 16, 2023
684dc68
fix: integration tests
wischli Mar 17, 2023
3f44316
refactor: collator stk
wischli Mar 20, 2023
cb72de4
Merge remote-tracking branch 'origin' into feat/impl-block-rewards
wischli Mar 20, 2023
7c64b8d
chore: bump nix
wischli Mar 20, 2023
27e4653
feat: add check to set_collator_rewards
wischli Mar 27, 2023
7223a31
chore: update CODEOWNERS
wischli Mar 27, 2023
02060eb
refactor: stake amt, group id via associated type
wischli Mar 27, 2023
5be160c
chore: move dev rewards config closer to prod
wischli Mar 27, 2023
ecd49de
refactor: abstract CfgCurrencyId
wischli Mar 28, 2023
8650cc8
Merge remote-tracking branch 'origin/main' into feat/impl-block-rewards
wischli Apr 13, 2023
69de861
Merge remote-tracking branch 'origin/main' into feat/impl-block-rewards
wischli Apr 14, 2023
97a0dbe
fix: bench errors after rebase
wischli Apr 14, 2023
5a3a6ad
fix: dev try-runtime
wischli Apr 14, 2023
572d9e2
fix: benchmarks
wischli Apr 17, 2023
cbb10b1
Merge branch 'main' into feat/impl-block-rewards
wischli Apr 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 0 additions & 10 deletions libs/traits/src/rewards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,16 +182,6 @@ pub trait CurrencyGroupChange {
fn currency_group(currency_id: Self::CurrencyId) -> Option<Self::GroupId>;
}

// pub trait BlockRewards {
// /// Type used to identify the group.
// type GroupId;

// /// Type used to identify the currency.
// type CurrencyId;

// fn
// }

#[cfg(feature = "std")]
pub mod mock {
use std::sync::{Mutex, MutexGuard};
Expand Down
193 changes: 100 additions & 93 deletions pallets/block-rewards/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ pub use frame_support::{
use frame_system::pallet_prelude::*;
use num_traits::sign::Unsigned;
pub use pallet::*;
use sp_runtime::{traits::Zero, FixedPointOperand};
pub use sp_runtime::Saturating;
use sp_runtime::{
traits::{AccountIdConversion, Zero},
FixedPointOperand,
};
use sp_std::{mem, vec::Vec};
use weights::WeightInfo;

Expand All @@ -76,15 +80,13 @@ pub struct CollatorChanges<T: Config> {
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound)]
#[scale_info(skip_type_params(T))]
pub struct EpochData<T: Config> {
duration: T::BlockNumber,
reward: T::Balance,
weights: BoundedBTreeMap<T::GroupId, T::Weight, T::MaxGroups>,
}

impl<T: Config> Default for EpochData<T> {
fn default() -> Self {
Self {
duration: T::InitialEpochDuration::get(),
reward: T::Balance::zero(),
weights: BoundedBTreeMap::default(),
}
Expand All @@ -97,7 +99,6 @@ impl<T: Config> Default for EpochData<T> {
)]
#[scale_info(skip_type_params(T))]
pub struct EpochChanges<T: Config> {
duration: Option<T::BlockNumber>,
reward: Option<T::Balance>,
weights: BoundedBTreeMap<T::GroupId, T::Weight, T::MaxChangesPerEpoch>,
collators: CollatorChanges<T>,
Expand All @@ -107,6 +108,8 @@ pub type DomainIdOf<T> = <<T as Config>::Domain as TypedGet>::Type;

#[frame_support::pallet]
pub mod pallet {
use frame_support::PalletId;

use super::*;

#[pallet::config]
Expand Down Expand Up @@ -156,11 +159,6 @@ pub mod pallet {
#[pallet::constant]
type MaxChangesPerEpoch: Get<u32> + TypeInfo + sp_std::fmt::Debug + Clone + PartialEq;

/// Initial epoch duration.
/// This value can be updated later using [`Pallet::set_epoch_duration()`]`.
#[pallet::constant]
type InitialEpochDuration: Get<Self::BlockNumber>;

#[pallet::constant]
type CollatorCurrencyId: Get<Self::CurrencyId> + TypeInfo;

Expand All @@ -173,6 +171,12 @@ pub mod pallet {
#[pallet::constant]
type MaxCollators: Get<u32> + TypeInfo + sp_std::fmt::Debug + Clone + PartialEq;

/// Identifier for the currency used to give the reward.
type RewardCurrency: Get<Self::CurrencyId>;

#[pallet::constant]
type RemainingRewardCollector: Get<Option<PalletId>>;

/// The identifier type for an authority.
type AuthorityId: Member
+ Parameter
Expand All @@ -188,16 +192,6 @@ pub mod pallet {
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);

/// Contains the timestamp in blocks when the current epoch is finalized.
//
// Although this value could be stored inside `EpochData`,
// we maintain it separately to avoid deserializing the whole EpochData struct each `on_initialize()` call.
// EpochData could be relatively big if there many groups.
// We dont have to deserialize the whole struct 99% of the time (assuming a duration of 100 blocks),
// we only need to perform that action when the epoch finalized, 1% of the time.
#[pallet::storage]
pub(super) type EndOfEpoch<T: Config> = StorageValue<_, T::BlockNumber, ValueQuery>;

/// Data associated to the current epoch.
#[pallet::storage]
pub(super) type ActiveEpochData<T: Config> = StorageValue<_, EpochData<T>, ValueQuery>;
Expand All @@ -211,7 +205,6 @@ pub mod pallet {
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
NewEpoch {
ends_on: T::BlockNumber,
reward: T::Balance,
last_changes: EpochChanges<T>,
},
Expand All @@ -224,64 +217,6 @@ pub mod pallet {
MaxChangesPerEpochReached,
}

#[pallet::hooks]
impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {
// TODO: Could be moved to SessionManager
fn on_initialize(current_block: T::BlockNumber) -> Weight {
let ends_on = EndOfEpoch::<T>::get();

if ends_on > current_block {
return T::DbWeight::get().reads(1);
}

let mut groups = 0;
let mut weight_changes = 0;

transactional::with_storage_layer(|| -> DispatchResult {
NextEpochChanges::<T>::try_mutate(|changes| -> DispatchResult {
ActiveEpochData::<T>::try_mutate(|epoch_data| {
for leaving in changes.collators.out.drain(..) {
Self::do_exit_collator(&leaving)?;
}
for joining in changes.collators.inc.drain(..) {
Self::do_init_collator(&joining)?;
}

groups = T::Rewards::distribute_reward_with_weights(
epoch_data.reward,
epoch_data.weights.iter().map(|(g, w)| (*g, *w)),
)
.map(|results| results.len() as u32)?;

for (&group_id, &weight) in &changes.weights {
epoch_data.weights.try_insert(group_id, weight).ok();
weight_changes += 1;
}

epoch_data.reward = changes.reward.unwrap_or(epoch_data.reward);
epoch_data.duration = changes.duration.unwrap_or(epoch_data.duration);

let ends_on = ends_on.max(current_block).ensure_add(epoch_data.duration)?;

EndOfEpoch::<T>::set(ends_on);

Self::deposit_event(Event::NewEpoch {
ends_on: ends_on,
reward: epoch_data.reward,
last_changes: mem::take(changes),
});

Ok(())
})
})
})
.ok();

// TODO: Apply joining + leaving as param
T::WeightInfo::on_initialize(groups, weight_changes)
}
}

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Claims the reward the associated to a currency.
Expand Down Expand Up @@ -309,17 +244,6 @@ pub mod pallet {
Ok(())
}

/// Admin method to set the duration used for the next epochs.
/// Current epoch is not affected by this call.
#[pallet::weight(T::WeightInfo::set_epoch_duration())]
pub fn set_epoch_duration(origin: OriginFor<T>, blocks: T::BlockNumber) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;

NextEpochChanges::<T>::mutate(|changes| changes.duration = Some(blocks));

Ok(())
}

/// Admin method to set the group weights used for the next epochs.
/// Current epoch is not affected by this call.
#[pallet::weight(T::WeightInfo::set_group_weight())]
Expand Down Expand Up @@ -370,12 +294,88 @@ impl<T: Config> Pallet<T> {
)?;
T::Currency::burn_from(T::CollatorCurrencyId::get(), who, amount).map(|_| ())
wischli marked this conversation as resolved.
Show resolved Hide resolved
}

/// Mint remaining rewards if not everything was distributed to the groups.
///
/// NOTE: Noop if [RemainingRewardCollector] is `None`.
fn do_mint_remainder(
total_reward: T::Balance,
reward_results: &Vec<Result<T::Balance, DispatchError>>,
) -> DispatchResult {
if let Some(pallet_id) = T::RemainingRewardCollector::get() {
let remaining_rewards = reward_results.iter().fold(total_reward, |acc, result| {
// Don't subtract from total rewards if group reward failed
acc.saturating_sub(result.unwrap_or_default())
});
T::Currency::mint_into(
T::RewardCurrency::get(),
&pallet_id.into_account_truncating(),
remaining_rewards,
)?;
}

Ok(())
}

/// Apply epoch changes and distribute rewards.
///
/// NOTE: Noop if any call fails.
fn do_advance_epoch() -> Weight {
let mut groups = 0;
let mut weight_changes = 0;
let mut num_joining = 0;
let mut num_leaving = 0;

transactional::with_storage_layer(|| -> DispatchResult {
NextEpochChanges::<T>::try_mutate(|changes| -> DispatchResult {
ActiveEpochData::<T>::try_mutate(|epoch_data| {
num_joining = changes.collators.inc.len();
num_leaving = changes.collators.out.len();
for leaving in changes.collators.out.drain(..) {
Self::do_exit_collator(&leaving)?;
}
for joining in changes.collators.inc.drain(..) {
Self::do_init_collator(&joining)?;
}

groups = T::Rewards::distribute_reward_with_weights(
epoch_data.reward,
epoch_data.weights.iter().map(|(g, w)| (*g, *w)),
)
.map(|results| {
Self::do_mint_remainder(epoch_data.reward, &results)?;
Ok(results.len() as u32)
})
.and_then(|num_groups| num_groups)?;

for (&group_id, &weight) in &changes.weights {
epoch_data.weights.try_insert(group_id, weight).ok();
weight_changes += 1;
}

epoch_data.reward = changes.reward.unwrap_or(epoch_data.reward);

Self::deposit_event(Event::NewEpoch {
reward: epoch_data.reward,
last_changes: mem::take(changes),
});

Ok(())
})
})
})
.ok();
lemunozm marked this conversation as resolved.
Show resolved Hide resolved

// TODO: Apply number of incoming and outgoing collators
T::WeightInfo::on_initialize(groups, weight_changes)
}
}

impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
type Public = T::AuthorityId;
}

// Should be instantiated after the original SessionHandler such that current and queued collators are up-to-date for the current session.
impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
type Key = T::AuthorityId;

Expand All @@ -390,6 +390,11 @@ impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
where
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
{
// MUST be called before updating collator set changes.
// Else the timing is off.
let mut weight = Self::do_advance_epoch();

// Prepare collator set changes for next session.
if changed {
let current = validators
.map(|(acc_id, _)| acc_id.clone())
Expand All @@ -416,11 +421,13 @@ impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
});
}

frame_system::Pallet::<T>::register_extra_weight_unchecked(
T::DbWeight::get().writes(1),
DispatchClass::Mandatory,
);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
}

frame_system::Pallet::<T>::register_extra_weight_unchecked(
weight,
DispatchClass::Mandatory,
);
}

fn on_before_session_ending() {
Expand Down
26 changes: 21 additions & 5 deletions pallets/rewards/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ pub mod pallet {
/// Specify the internal reward mechanism used by this pallet.
/// Check available mechanisms at [`mechanism`] module.
type RewardMechanism: RewardMechanism;

/// Type used to identify the income stream for rewards.
/// By setting this `None`, the rewards will be minted.
#[pallet::constant]
type RewardSource: Get<Option<frame_support::PalletId>>;
wischli marked this conversation as resolved.
Show resolved Hide resolved
}

#[pallet::pallet]
Expand Down Expand Up @@ -230,11 +235,22 @@ pub mod pallet {
Groups::<T, I>::try_mutate(group_id, |group| {
let reward_to_mint = T::RewardMechanism::reward_group(group, reward)?;

T::Currency::mint_into(
T::RewardCurrency::get(),
&T::PalletId::get().into_account_truncating(),
reward_to_mint,
)?;
// TODO: Check with @Luis whether we want to move this into a trait
if let Some(pallet_id) = T::RewardSource::get() {
T::Currency::transfer(
T::RewardCurrency::get(),
&pallet_id.into_account_truncating(),
&T::PalletId::get().into_account_truncating(),
reward,
true,
)?;
} else {
T::Currency::mint_into(
T::RewardCurrency::get(),
&T::PalletId::get().into_account_truncating(),
reward,
)?;
}
wischli marked this conversation as resolved.
Show resolved Hide resolved

Self::deposit_event(Event::GroupRewarded {
group_id,
Expand Down