-
Notifications
You must be signed in to change notification settings - Fork 20
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
Identity staking improvement #3074
Changes from all commits
f8ffd65
362fa6d
73fb800
588bbe8
85d6259
f6659cd
ca3017d
4b98a78
dcdc633
2847b3b
39aa39c
5fee034
f78fb41
7fb73ef
23afb38
697f17d
ddef5e1
1302cc7
5b43190
e13f9a6
abb6985
9fceab1
e874f4a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,7 +47,11 @@ use frame_support::{ | |
}; | ||
use pallet_parachain_staking as ParaStaking; | ||
use sp_core::crypto::AccountId32; | ||
use sp_runtime::{traits::CheckedSub, Perbill, SaturatedConversion}; | ||
use sp_runtime::{ | ||
traits::{CheckedSub, Zero}, | ||
Perbill, SaturatedConversion, | ||
}; | ||
use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; | ||
|
||
pub use pallet::*; | ||
|
||
|
@@ -74,7 +78,6 @@ pub mod pallet { | |
|
||
use core_primitives::Identity; | ||
use frame_system::pallet_prelude::*; | ||
use sp_runtime::traits::Zero; | ||
|
||
/// The current storage version. | ||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); | ||
|
@@ -116,6 +119,8 @@ pub mod pallet { | |
type AccountIdConvert: AccountIdConvert<Self>; | ||
// For extrinsics that should only be called by origins from TEE | ||
type TEECallOrigin: EnsureOrigin<Self::RuntimeOrigin>; | ||
#[pallet::constant] | ||
type MaxIDGraphAccountsPerCall: Get<u16>; | ||
} | ||
|
||
#[pallet::error] | ||
|
@@ -153,44 +158,24 @@ pub mod pallet { | |
// when the score user count would exceed `MaxScoreUserCount` | ||
MaxScoreUserCountReached, | ||
// the token staking amount has been updated already for the round | ||
TokenStakingAmountAlreadyUpdated, | ||
RoundRewardsAlreadyDistributed, | ||
// the maximum number of IDGraph accounts has been reached | ||
MaxIDGraphAccountsPerCallReached, | ||
} | ||
|
||
#[pallet::event] | ||
#[pallet::generate_deposit(pub (crate) fn deposit_event)] | ||
pub enum Event<T: Config> { | ||
PoolStarted { | ||
start_block: BlockNumberFor<T>, | ||
}, | ||
PoolStarted { start_block: BlockNumberFor<T> }, | ||
PoolStopped {}, | ||
ScoreFeederSet { | ||
new_score_feeder: Option<T::AccountId>, | ||
}, | ||
RoundConfigSet { | ||
new_config: RoundSetting, | ||
}, | ||
ScoreUpdated { | ||
who: Identity, | ||
new_score: Score, | ||
}, | ||
ScoreRemoved { | ||
who: Identity, | ||
}, | ||
ScoreFeederSet { new_score_feeder: Option<T::AccountId> }, | ||
RoundConfigSet { new_config: RoundSetting }, | ||
ScoreUpdated { who: Identity, new_score: Score }, | ||
ScoreRemoved { who: Identity }, | ||
ScoreCleared {}, | ||
TokenStakingAmountUpdated { | ||
account: T::AccountId, | ||
amount: BalanceOf<T>, | ||
}, | ||
RewardDistributionStarted { | ||
total: BalanceOf<T>, | ||
distributed: BalanceOf<T>, | ||
round_index: RoundIndex, | ||
}, | ||
RewardDistributionCompleted {}, | ||
RewardClaimed { | ||
who: T::AccountId, | ||
amount: BalanceOf<T>, | ||
}, | ||
RewardDistributionStarted { round_index: RoundIndex }, | ||
RewardDistributionCompleted { round_index: RoundIndex }, | ||
RewardClaimed { who: T::AccountId, amount: BalanceOf<T> }, | ||
} | ||
|
||
#[pallet::storage] | ||
|
@@ -231,6 +216,11 @@ pub mod pallet { | |
#[pallet::getter(fn state)] | ||
pub type State<T: Config> = StorageValue<_, PoolState, ValueQuery>; | ||
|
||
/// The round index of the last token distribution | ||
#[pallet::storage] | ||
#[pallet::getter(fn last_rewards_distribution_round)] | ||
pub type LastTokenDistributionRound<T: Config> = StorageValue<_, RoundIndex, ValueQuery>; | ||
|
||
#[pallet::genesis_config] | ||
pub struct GenesisConfig<T: Config> { | ||
pub state: PoolState, | ||
|
@@ -268,50 +258,14 @@ pub mod pallet { | |
} | ||
|
||
// We are about to start a new round | ||
// 1. update round info | ||
// - update round info | ||
let round_index = r.index.saturating_add(1); | ||
r.index = round_index; | ||
r.start_block = now; | ||
Round::<T>::put(r); | ||
weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); | ||
|
||
// 2. calculate payout | ||
let round_reward: BalanceOf<T> = (T::YearlyInflation::get() * T::YearlyIssuance::get() | ||
/ YEARS.into()) * Self::round_config().interval.into(); | ||
let round_reward_u128 = round_reward.saturated_into::<u128>(); | ||
|
||
let total_stake_u128 = ParaStaking::Pallet::<T>::total().saturated_into::<u128>(); | ||
let total_score = Self::total_score(); | ||
let n = Self::round_config().stake_coef_n; | ||
let m = Self::round_config().stake_coef_m; | ||
|
||
let mut all_user_reward = BalanceOf::<T>::zero(); | ||
|
||
for (a, mut p) in Scores::<T>::iter() { | ||
let user_stake_u128 = ParaStaking::Pallet::<T>::delegator_state(&a) | ||
.map(|s| s.total) | ||
.unwrap_or_default() | ||
.saturated_into::<u128>(); | ||
let user_reward_u128 = round_reward_u128 | ||
.saturating_mul(p.score.into()) | ||
.saturating_div(total_score.into()) | ||
.saturating_mul(num_integer::Roots::nth_root(&user_stake_u128.pow(n), m)) | ||
.saturating_div(num_integer::Roots::nth_root(&total_stake_u128.pow(n), m)); | ||
let user_reward = user_reward_u128.saturated_into::<BalanceOf<T>>(); | ||
|
||
p.last_round_reward = user_reward; | ||
p.total_reward += user_reward; | ||
p.unpaid_reward += user_reward; | ||
all_user_reward += user_reward; | ||
Scores::<T>::insert(&a, p); | ||
weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1)); | ||
} | ||
|
||
Self::deposit_event(Event::<T>::RewardDistributionStarted { | ||
total: round_reward, | ||
distributed: all_user_reward, | ||
round_index, | ||
}); | ||
Self::deposit_event(Event::<T>::RewardDistributionStarted { round_index }); | ||
|
||
weight | ||
} | ||
|
@@ -477,41 +431,70 @@ pub mod pallet { | |
|
||
#[pallet::call_index(9)] | ||
#[pallet::weight((195_000_000, DispatchClass::Normal))] | ||
pub fn update_token_staking_amount( | ||
pub fn distribute_rewards( | ||
origin: OriginFor<T>, | ||
account: T::AccountId, | ||
amount: BalanceOf<T>, | ||
round_index: RoundIndex, | ||
id_graphs_staking: Vec<(T::AccountId, BalanceOf<T>)>, | ||
) -> DispatchResultWithPostInfo { | ||
let _ = T::TEECallOrigin::ensure_origin(origin)?; | ||
|
||
match Scores::<T>::get(&account) { | ||
Some(mut s) => { | ||
if round_index <= s.last_token_distributed_round { | ||
return Err(Error::<T>::TokenStakingAmountAlreadyUpdated.into()); | ||
} | ||
let last_round = Round::<T>::get(); | ||
s.last_round_reward += amount; | ||
silva-fj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
s.total_reward += amount; | ||
s.unpaid_reward += amount; | ||
s.last_token_distributed_round = last_round.index; | ||
Scores::<T>::insert(account.clone(), s); | ||
Self::deposit_event(Event::TokenStakingAmountUpdated { | ||
account: account.clone(), | ||
amount, | ||
}); | ||
Ok(Pays::No.into()) | ||
}, | ||
None => Err(Error::<T>::UserNotExist.into()), | ||
ensure!( | ||
round_index > LastTokenDistributionRound::<T>::get(), | ||
Error::<T>::RoundRewardsAlreadyDistributed | ||
); | ||
|
||
let id_graphs_staking_map: BTreeMap<T::AccountId, BalanceOf<T>> = | ||
id_graphs_staking.into_iter().collect(); | ||
|
||
ensure!( | ||
id_graphs_staking_map.len() <= T::MaxIDGraphAccountsPerCall::get() as usize, | ||
Error::<T>::MaxIDGraphAccountsPerCallReached | ||
); | ||
|
||
let round_reward: BalanceOf<T> = (T::YearlyInflation::get() * T::YearlyIssuance::get() | ||
/ YEARS.into()) * Self::round_config().interval.into(); | ||
let round_reward_u128 = round_reward.saturated_into::<u128>(); | ||
|
||
let total_stake_u128 = ParaStaking::Pallet::<T>::total().saturated_into::<u128>(); | ||
let total_score = Self::total_score(); | ||
let n = Self::round_config().stake_coef_n; | ||
let m = Self::round_config().stake_coef_m; | ||
|
||
for (a, mut p) in Scores::<T>::iter() { | ||
let default_staking = BalanceOf::<T>::zero(); | ||
let id_graph_staking = id_graphs_staking_map.get(&a).unwrap_or(&default_staking); | ||
Kailai-Wang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let user_stake_u128 = ParaStaking::Pallet::<T>::delegator_state(&a) | ||
.map(|s| s.total) | ||
.unwrap_or_default() | ||
.saturated_into::<u128>() | ||
+ (*id_graph_staking).saturated_into::<u128>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we add it again? IDGraph should contain the prime key already IIRC There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, I didn't know the IDGraph also contains the prime identity. I'll check it out 👍🏼 |
||
let user_reward_u128 = round_reward_u128 | ||
.saturating_mul(p.score.into()) | ||
.saturating_div(total_score.into()) | ||
.saturating_mul(num_integer::Roots::nth_root(&user_stake_u128.pow(n), m)) | ||
.saturating_div(num_integer::Roots::nth_root(&total_stake_u128.pow(n), m)); | ||
let user_reward = user_reward_u128.saturated_into::<BalanceOf<T>>(); | ||
|
||
p.last_round_reward = user_reward; | ||
p.total_reward += user_reward; | ||
p.unpaid_reward += user_reward; | ||
Scores::<T>::insert(&a, p); | ||
} | ||
|
||
Ok(Pays::No.into()) | ||
} | ||
|
||
#[pallet::call_index(10)] | ||
#[pallet::weight((195_000_000, DispatchClass::Normal))] | ||
pub fn complete_reward_distribution(origin: OriginFor<T>) -> DispatchResultWithPostInfo { | ||
pub fn complete_reward_distribution( | ||
origin: OriginFor<T>, | ||
round_index: RoundIndex, | ||
) -> DispatchResultWithPostInfo { | ||
let _ = T::TEECallOrigin::ensure_origin(origin)?; | ||
|
||
Self::deposit_event(Event::RewardDistributionCompleted {}); | ||
LastTokenDistributionRound::<T>::put(round_index); | ||
|
||
Self::deposit_event(Event::RewardDistributionCompleted { round_index }); | ||
|
||
Ok(Pays::No.into()) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry my bad.
We have
MaxScoreUserCount
already and we do iterate over them all in the originalon_initialize
hook. Based on that it should be no problem if we do the whole loop in an extrinsic, so we don't need this constant and batched call in the worker.But we do need to specify the (max) weights in the extrinsic weight macro - this is the important
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we call
distribute
in batch and again we might fall into the problem of inconsistent calculation contextThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd still propose a different paradigm, instead of calling
distribute
ext that accepts a vector with potentially 1M entries (that's theMaxScoreUserCount
runtime binding), can we:distribute
distribute
, we iterate all entrieis inScores
and do the reward calculation/distribution in this atomic extrinsicHow does this sound to you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No worries, that's actually what I had in mind with the initial implementation (when I added a new field to the
ScorePayment
), but back then It was not clear to me how the logic was supposed to be. I'll refactor it to do that