From dd85da39b3042b41213d2dbbf38b28d513425643 Mon Sep 17 00:00:00 2001
From: 1xstj <106580853+1xstj@users.noreply.github.com>
Date: Fri, 24 Nov 2023 12:08:44 +0000
Subject: [PATCH 1/7] first pass : simple distribution
---
Cargo.lock | 1 +
pallets/roles/src/impls.rs | 79 ++++++++++++++++++++++++++++++++++-
pallets/roles/src/lib.rs | 14 ++++++-
primitives/Cargo.toml | 1 +
primitives/src/types/roles.rs | 26 ++++++++++++
5 files changed, 119 insertions(+), 2 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 706b22215..ecb58ac71 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -15423,6 +15423,7 @@ dependencies = [
"sp-arithmetic 16.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)",
"sp-core 21.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)",
"sp-runtime 24.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)",
+ "sp-std 8.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)",
]
[[package]]
diff --git a/pallets/roles/src/impls.rs b/pallets/roles/src/impls.rs
index c64a1bf35..5ac9f175e 100644
--- a/pallets/roles/src/impls.rs
+++ b/pallets/roles/src/impls.rs
@@ -15,8 +15,13 @@
// along with Tangle. If not, see .
use super::*;
-use frame_support::pallet_prelude::DispatchResult;
+use frame_support::{
+ log,
+ pallet_prelude::DispatchResult,
+ traits::{Currency, ExistenceRequirement::KeepAlive, OneSessionHandler},
+};
use sp_runtime::{Percent, Saturating};
+use sp_std::ops::Div;
use tangle_primitives::{jobs::JobKey, traits::roles::RolesHandler};
/// Implements RolesHandler for the pallet.
@@ -180,4 +185,76 @@ impl Pallet {
pub(crate) fn kill_stash(stash: &T::AccountId) {
>::remove(&stash);
}
+
+ pub fn distribute_rewards() -> DispatchResult {
+ let total_rewards = T::InflationRewardPerEra::get();
+
+ let mut tss_validators: Vec = Default::default();
+ let mut zksaas_validators: Vec = Default::default();
+
+ for (acc, role_types) in AccountRolesMapping::::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).div((tss_validators.len() as u32).into());
+ let zksaas_validators_reward =
+ dist.1.mul_floor(total_rewards).div((zksaas_validators.len() as u32).into());
+
+ 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 sp_runtime::BoundToRuntimeAppPublic for Pallet {
+ type Public = T::AuthorityId;
+}
+
+impl OneSessionHandler for Pallet {
+ type Key = T::AuthorityId;
+
+ fn on_genesis_session<'a, I: 'a>(_validators: I)
+ where
+ I: Iterator- ,
+ {
+ // nothing to be done
+ }
+
+ fn on_new_session<'a, I: 'a>(_changed: bool, _validators: I, _queued_validators: I)
+ where
+ I: Iterator
- ,
+ {
+ // nothing to be done
+ }
+
+ fn on_disabled(_i: u32) {
+ // ignore
+ }
+
+ // Distribute the inflation rewards
+ fn on_before_session_ending() {
+ let _ = Self::distribute_rewards();
+ }
}
diff --git a/pallets/roles/src/lib.rs b/pallets/roles/src/lib.rs
index 826b4f51d..4c48749a5 100644
--- a/pallets/roles/src/lib.rs
+++ b/pallets/roles/src/lib.rs
@@ -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};
@@ -42,6 +43,8 @@ mod tests;
mod weights;
pub use weights::WeightInfo;
+use sp_runtime::RuntimeAppPublic;
+
/// The ledger of a (bonded) stash.
#[derive(
PartialEqNoBound,
@@ -122,6 +125,15 @@ pub mod pallet {
/// The config that verifies MPC related functions
type MPCHandler: MPCHandler, BalanceOf>;
+ /// The inflation reward to distribute per era
+ type InflationRewardPerEra: Get>;
+
+ /// The inflation distribution based on validator type
+ type ValidatorRewardDistribution: Get;
+
+ /// The type used to identify an authority
+ type AuthorityId: RuntimeAppPublic + Decode;
+
type WeightInfo: WeightInfo;
}
@@ -171,7 +183,7 @@ pub mod pallet {
/// Mapping of resource to bridge index
pub type AccountRolesMapping = StorageMap<
_,
- Blake2_256,
+ Blake2_128Concat,
T::AccountId,
BoundedVec,
ValueQuery,
diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml
index b26338a84..f5d9626a2 100644
--- a/primitives/Cargo.toml
+++ b/primitives/Cargo.toml
@@ -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]
diff --git a/primitives/src/types/roles.rs b/primitives/src/types/roles.rs
index f1d935757..a21438e7a 100644
--- a/primitives/src/types/roles.rs
+++ b/primitives/src/types/roles.rs
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Tangle. If not, see .
use frame_support::{dispatch::Vec, pallet_prelude::*};
+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)]
@@ -81,3 +83,27 @@ 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 {
+ 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)
+ }
+}
+
+
From cd6dce50bdbda814254c55d55e732c9a5ff604cd Mon Sep 17 00:00:00 2001
From: 1xstj <106580853+1xstj@users.noreply.github.com>
Date: Fri, 24 Nov 2023 12:27:11 +0000
Subject: [PATCH 2/7] add tests
---
pallets/roles/src/impls.rs | 21 +++++++++-----
pallets/roles/src/mock.rs | 13 +++++++--
pallets/roles/src/tests.rs | 57 ++++++++++++++++++++++++++++++++++++++
3 files changed, 81 insertions(+), 10 deletions(-)
diff --git a/pallets/roles/src/impls.rs b/pallets/roles/src/impls.rs
index 5ac9f175e..6c3e51b7b 100644
--- a/pallets/roles/src/impls.rs
+++ b/pallets/roles/src/impls.rs
@@ -18,10 +18,10 @@ use super::*;
use frame_support::{
log,
pallet_prelude::DispatchResult,
- traits::{Currency, ExistenceRequirement::KeepAlive, OneSessionHandler},
+ traits::{Currency, OneSessionHandler},
};
-use sp_runtime::{Percent, Saturating};
-use sp_std::ops::Div;
+use sp_runtime::{traits::CheckedDiv, Percent, Saturating};
+
use tangle_primitives::{jobs::JobKey, traits::roles::RolesHandler};
/// Implements RolesHandler for the pallet.
@@ -208,10 +208,17 @@ impl Pallet {
let reward_distribution = T::ValidatorRewardDistribution::get();
let dist = reward_distribution.get_reward_distribution();
- let tss_validator_reward =
- dist.0.mul_floor(total_rewards).div((tss_validators.len() as u32).into());
- let zksaas_validators_reward =
- dist.1.mul_floor(total_rewards).div((zksaas_validators.len() as u32).into());
+
+ 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);
diff --git a/pallets/roles/src/mock.rs b/pallets/roles/src/mock.rs
index 760badba1..b785a83da 100644
--- a/pallets/roles/src/mock.rs
+++ b/pallets/roles/src/mock.rs
@@ -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;
@@ -243,11 +242,19 @@ impl JobsHandler 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 = ();
}
diff --git a/pallets/roles/src/tests.rs b/pallets/roles/src/tests.rs
index 8dbfe0b2d..302c88305 100644
--- a/pallets/roles/src/tests.rs
+++ b/pallets/roles/src/tests.rs
@@ -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);
+ });
+}
From 52a9ce9382d91d7c98e1309692cf865a780be66a Mon Sep 17 00:00:00 2001
From: 1xstj <106580853+1xstj@users.noreply.github.com>
Date: Fri, 24 Nov 2023 12:29:40 +0000
Subject: [PATCH 3/7] add tests
---
primitives/src/types/roles.rs | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/primitives/src/types/roles.rs b/primitives/src/types/roles.rs
index a21438e7a..f5c66f53f 100644
--- a/primitives/src/types/roles.rs
+++ b/primitives/src/types/roles.rs
@@ -87,23 +87,21 @@ pub enum ReStakingOption {
/// 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,
+ tss_share: Percent,
/// The percentage share of the reward allocated for the ZK-SaaS
- zksaas_share : Percent
+ zksaas_share: Percent,
}
impl ValidatorRewardDistribution {
- pub fn try_new(tss_share : Percent, zksaas_share: Percent) -> Result {
+ pub fn try_new(tss_share: Percent, zksaas_share: Percent) -> Result {
if !tss_share.add(zksaas_share).is_one() {
return Err("Shares must add to One".to_string())
}
- Ok(Self { tss_share , zksaas_share})
+ Ok(Self { tss_share, zksaas_share })
}
pub fn get_reward_distribution(self) -> (Percent, Percent) {
(self.tss_share, self.zksaas_share)
}
}
-
-
From f95fc45432709a706587cb107658d28db141a708 Mon Sep 17 00:00:00 2001
From: 1xstj <106580853+1xstj@users.noreply.github.com>
Date: Fri, 24 Nov 2023 12:57:57 +0000
Subject: [PATCH 4/7] clippy fix
---
primitives/src/types/roles.rs | 1 +
1 file changed, 1 insertion(+)
diff --git a/primitives/src/types/roles.rs b/primitives/src/types/roles.rs
index f5c66f53f..7c750b8f5 100644
--- a/primitives/src/types/roles.rs
+++ b/primitives/src/types/roles.rs
@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Tangle. If not, see .
use frame_support::{dispatch::Vec, pallet_prelude::*};
+use scale_info::prelude::string::String;
use sp_arithmetic::Percent;
use sp_std::ops::Add;
From a57dc1371821ba4c4c192f10e5ce2f0ca51cc866 Mon Sep 17 00:00:00 2001
From: 1xstj <106580853+1xstj@users.noreply.github.com>
Date: Fri, 24 Nov 2023 13:24:28 +0000
Subject: [PATCH 5/7] clippy fix
---
primitives/src/types/roles.rs | 1 +
1 file changed, 1 insertion(+)
diff --git a/primitives/src/types/roles.rs b/primitives/src/types/roles.rs
index 7c750b8f5..042cdd9bd 100644
--- a/primitives/src/types/roles.rs
+++ b/primitives/src/types/roles.rs
@@ -17,6 +17,7 @@ use frame_support::{dispatch::Vec, pallet_prelude::*};
use scale_info::prelude::string::String;
use sp_arithmetic::Percent;
use sp_std::ops::Add;
+use parity_scale_codec::alloc::string::ToString;
/// Role type to be used in the system.
#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo)]
From 0cb0a90fc8d101b1f9f9592cfb0249addd7662fa Mon Sep 17 00:00:00 2001
From: 1xstj <106580853+1xstj@users.noreply.github.com>
Date: Fri, 24 Nov 2023 13:26:37 +0000
Subject: [PATCH 6/7] clippy fix
---
primitives/src/types/roles.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/primitives/src/types/roles.rs b/primitives/src/types/roles.rs
index 042cdd9bd..c000602d5 100644
--- a/primitives/src/types/roles.rs
+++ b/primitives/src/types/roles.rs
@@ -14,10 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with Tangle. If not, see .
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;
-use parity_scale_codec::alloc::string::ToString;
/// Role type to be used in the system.
#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo)]
From 50e27aef61570cac46f6cf6629b1fcbc0e1e9e54 Mon Sep 17 00:00:00 2001
From: 1xstj <106580853+1xstj@users.noreply.github.com>
Date: Mon, 27 Nov 2023 16:59:35 +0000
Subject: [PATCH 7/7] rename era to session
---
pallets/roles/src/impls.rs | 2 +-
pallets/roles/src/lib.rs | 2 +-
pallets/roles/src/mock.rs | 4 ++--
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/pallets/roles/src/impls.rs b/pallets/roles/src/impls.rs
index 6c3e51b7b..e5d79030e 100644
--- a/pallets/roles/src/impls.rs
+++ b/pallets/roles/src/impls.rs
@@ -187,7 +187,7 @@ impl Pallet {
}
pub fn distribute_rewards() -> DispatchResult {
- let total_rewards = T::InflationRewardPerEra::get();
+ let total_rewards = T::InflationRewardPerSession::get();
let mut tss_validators: Vec = Default::default();
let mut zksaas_validators: Vec = Default::default();
diff --git a/pallets/roles/src/lib.rs b/pallets/roles/src/lib.rs
index 4c48749a5..db532431a 100644
--- a/pallets/roles/src/lib.rs
+++ b/pallets/roles/src/lib.rs
@@ -126,7 +126,7 @@ pub mod pallet {
type MPCHandler: MPCHandler, BalanceOf>;
/// The inflation reward to distribute per era
- type InflationRewardPerEra: Get>;
+ type InflationRewardPerSession: Get>;
/// The inflation distribution based on validator type
type ValidatorRewardDistribution: Get;
diff --git a/pallets/roles/src/mock.rs b/pallets/roles/src/mock.rs
index b785a83da..73710fe6d 100644
--- a/pallets/roles/src/mock.rs
+++ b/pallets/roles/src/mock.rs
@@ -243,7 +243,7 @@ impl JobsHandler for MockJobsHandler {
}
parameter_types! {
- pub InflationRewardPerEra: Balance = 10_000;
+ pub InflationRewardPerSession: Balance = 10_000;
pub Reward : ValidatorRewardDistribution = ValidatorRewardDistribution::try_new(Percent::from_rational(1_u32,2_u32), Percent::from_rational(1_u32,2_u32)).unwrap();
}
@@ -252,7 +252,7 @@ impl Config for Runtime {
type JobsHandler = MockJobsHandler;
type MaxRolesPerAccount = ConstU32<2>;
type MPCHandler = MockMPCHandler;
- type InflationRewardPerEra = InflationRewardPerEra;
+ type InflationRewardPerSession = InflationRewardPerSession;
type AuthorityId = UintAuthorityId;
type ValidatorRewardDistribution = Reward;
type WeightInfo = ();