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

Implement inflation rewards for mpc authorities #315

Merged
merged 7 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

88 changes: 86 additions & 2 deletions pallets/roles/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@
// along with Tangle. If not, see <http://www.gnu.org/licenses/>.

use super::*;
use frame_support::pallet_prelude::DispatchResult;
use sp_runtime::{Percent, Saturating};
use frame_support::{
log,
pallet_prelude::DispatchResult,
traits::{Currency, OneSessionHandler},
};
use sp_runtime::{traits::CheckedDiv, Percent, Saturating};

use tangle_primitives::{jobs::JobKey, traits::roles::RolesHandler};

/// Implements RolesHandler for the pallet.
Expand Down Expand Up @@ -180,4 +185,83 @@ impl<T: Config> Pallet<T> {
pub(crate) fn kill_stash(stash: &T::AccountId) {
<Ledger<T>>::remove(&stash);
}

pub fn distribute_rewards() -> DispatchResult {
let total_rewards = T::InflationRewardPerEra::get();

let mut tss_validators: Vec<T::AccountId> = Default::default();
let mut zksaas_validators: Vec<T::AccountId> = Default::default();

for (acc, role_types) in AccountRolesMapping::<T>::iter() {
if role_types.contains(&RoleType::Tss) {
tss_validators.push(acc.clone())
}

if role_types.contains(&RoleType::ZkSaas) {
zksaas_validators.push(acc)
}
}

log::debug!("Found {:?} tss validators", tss_validators.len());
log::debug!("Found {:?} zksaas validators", zksaas_validators.len());

let reward_distribution = T::ValidatorRewardDistribution::get();

let dist = reward_distribution.get_reward_distribution();

let tss_validator_reward = dist
.0
.mul_floor(total_rewards)
.checked_div(&(tss_validators.len() as u32).into())
.unwrap_or(Zero::zero());
let zksaas_validators_reward = dist
.1
.mul_floor(total_rewards)
.checked_div(&(zksaas_validators.len() as u32).into())
.unwrap_or(Zero::zero());

log::debug!("Reward for tss validator : {:?}", tss_validator_reward);
log::debug!("Reward for zksaas validator : {:?}", zksaas_validators_reward);

for validator in tss_validators {
T::Currency::deposit_creating(&validator, tss_validator_reward);
}

for validator in zksaas_validators {
T::Currency::deposit_creating(&validator, zksaas_validators_reward);
}

Ok(())
}
}

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

impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
type Key = T::AuthorityId;

fn on_genesis_session<'a, I: 'a>(_validators: I)
where
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
{
// nothing to be done
}

fn on_new_session<'a, I: 'a>(_changed: bool, _validators: I, _queued_validators: I)
where
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
{
// nothing to be done
}

fn on_disabled(_i: u32) {
// ignore
}

// Distribute the inflation rewards
fn on_before_session_ending() {
let _ = Self::distribute_rewards();
}
}
14 changes: 13 additions & 1 deletion pallets/roles/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use frame_support::{
traits::{Currency, Get},
CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
};
use tangle_primitives::roles::ValidatorRewardDistribution;

pub use pallet::*;
use parity_scale_codec::{Decode, Encode};
Expand All @@ -42,6 +43,8 @@ mod tests;
mod weights;
pub use weights::WeightInfo;

use sp_runtime::RuntimeAppPublic;

/// The ledger of a (bonded) stash.
#[derive(
PartialEqNoBound,
Expand Down Expand Up @@ -122,6 +125,15 @@ pub mod pallet {
/// The config that verifies MPC related functions
type MPCHandler: MPCHandler<Self::AccountId, BlockNumberFor<Self>, BalanceOf<Self>>;

/// The inflation reward to distribute per era
type InflationRewardPerEra: Get<BalanceOf<Self>>;

/// The inflation distribution based on validator type
type ValidatorRewardDistribution: Get<ValidatorRewardDistribution>;

/// The type used to identify an authority
type AuthorityId: RuntimeAppPublic + Decode;

type WeightInfo: WeightInfo;
}

Expand Down Expand Up @@ -171,7 +183,7 @@ pub mod pallet {
/// Mapping of resource to bridge index
pub type AccountRolesMapping<T: Config> = StorageMap<
_,
Blake2_256,
Blake2_128Concat,
T::AccountId,
BoundedVec<RoleType, T::MaxRolesPerAccount>,
ValueQuery,
Expand Down
13 changes: 10 additions & 3 deletions pallets/roles/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,9 @@ use sp_core::H256;
use sp_runtime::{
testing::{Header, UintAuthorityId},
traits::IdentityLookup,
BuildStorage, DispatchResult, Perbill,
BuildStorage, DispatchResult, Perbill, Percent,
};
use tangle_primitives::{jobs::*, traits::jobs::MPCHandler};

use tangle_primitives::{jobs::*, roles::ValidatorRewardDistribution, traits::jobs::MPCHandler};
pub type AccountId = u64;
pub type Balance = u128;
pub type BlockNumber = u64;
Expand Down Expand Up @@ -243,11 +242,19 @@ impl JobsHandler<AccountId> for MockJobsHandler {
}
}

parameter_types! {
pub InflationRewardPerEra: Balance = 10_000;
pub Reward : ValidatorRewardDistribution = ValidatorRewardDistribution::try_new(Percent::from_rational(1_u32,2_u32), Percent::from_rational(1_u32,2_u32)).unwrap();
}

impl Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type JobsHandler = MockJobsHandler;
type MaxRolesPerAccount = ConstU32<2>;
type MPCHandler = MockMPCHandler;
type InflationRewardPerEra = InflationRewardPerEra;
type AuthorityId = UintAuthorityId;
type ValidatorRewardDistribution = Reward;
type WeightInfo = ();
}

Expand Down
57 changes: 57 additions & 0 deletions pallets/roles/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,60 @@ fn test_unbound_funds_should_work_if_no_role_assigned() {
})]);
});
}

#[test]
fn test_reward_dist_works_as_expected_with_one_validator() {
new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| {
assert_eq!(Balances::free_balance(1), 20_000);

// Roles user is interested in re-staking.
let role_records = vec![
RoleStakingRecord {
metadata: RoleTypeMetadata::Tss(Default::default()),
re_staked: 2500,
},
RoleStakingRecord {
metadata: RoleTypeMetadata::ZkSaas(Default::default()),
re_staked: 2500,
},
];

assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(1), role_records));

// The reward is 100, we have 5 authorities
assert_ok!(Roles::distribute_rewards());

// ensure the distribution is correct
assert_eq!(Balances::free_balance(1), 20_000 + 10_000);
});
}

#[test]
fn test_reward_dist_works_as_expected_with_multiple_validator() {
new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| {
let _reward_amount = 10_000;
assert_eq!(Balances::free_balance(1), 20_000);

// Roles user is interested in re-staking.
let role_records = vec![
RoleStakingRecord {
metadata: RoleTypeMetadata::Tss(Default::default()),
re_staked: 2500,
},
RoleStakingRecord {
metadata: RoleTypeMetadata::ZkSaas(Default::default()),
re_staked: 2500,
},
];

assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(1), role_records.clone()));
assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(2), role_records));

// The reward is 100, we have 5 authorities
assert_ok!(Roles::distribute_rewards());

// ensure the distribution is correct
assert_eq!(Balances::free_balance(1), 20_000 + 5000);
assert_eq!(Balances::free_balance(2), 20_000 + 5000);
});
}
1 change: 1 addition & 0 deletions primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ serde = { workspace = true }
smallvec = { workspace = true }
sp-arithmetic = { workspace = true }
sp-core = { workspace = true }
sp-std = { workspace = true }
sp-runtime = { workspace = true }

[features]
Expand Down
26 changes: 26 additions & 0 deletions primitives/src/types/roles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with Tangle. If not, see <http://www.gnu.org/licenses/>.
use frame_support::{dispatch::Vec, pallet_prelude::*};
use parity_scale_codec::alloc::string::ToString;
use scale_info::prelude::string::String;
use sp_arithmetic::Percent;
use sp_std::ops::Add;

/// Role type to be used in the system.
#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo)]
Expand Down Expand Up @@ -81,3 +85,25 @@ pub enum ReStakingOption {
// Re-stake only the given amount of funds for selected role.
Custom(u64),
}

/// Represents the reward distribution percentages for validators in a key generation process.
pub struct ValidatorRewardDistribution {
/// The percentage share of the reward allocated for TSS
tss_share: Percent,
/// The percentage share of the reward allocated for the ZK-SaaS
zksaas_share: Percent,
}

impl ValidatorRewardDistribution {
pub fn try_new(tss_share: Percent, zksaas_share: Percent) -> Result<Self, String> {
if !tss_share.add(zksaas_share).is_one() {
return Err("Shares must add to One".to_string())
}

Ok(Self { tss_share, zksaas_share })
}

pub fn get_reward_distribution(self) -> (Percent, Percent) {
(self.tss_share, self.zksaas_share)
}
}
Loading