diff --git a/Cargo.lock b/Cargo.lock index 424d01cc7..3286acf5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17319,4 +17319,4 @@ checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" dependencies = [ "cc", "pkg-config", -] +] \ No newline at end of file diff --git a/pallets/roles/src/impls.rs b/pallets/roles/src/impls.rs index c1e296a77..3115e754a 100644 --- a/pallets/roles/src/impls.rs +++ b/pallets/roles/src/impls.rs @@ -29,7 +29,7 @@ use sp_runtime::{ use sp_staking::offence::Offence; use tangle_primitives::{ jobs::{JobKey, ReportValidatorOffence}, - traits::roles::RolesHandler, + traits::{jobs::MPCHandler, roles::RolesHandler}, }; /// Implements RolesHandler for the pallet. @@ -82,54 +82,52 @@ impl RolesHandler for Pallet { /// Functions for the pallet. impl Pallet { - /// Add new role to the given account. + /// Validate updated profile for the given account. + /// This function will validate the updated profile for the given account. /// /// # Parameters /// - `account`: The account ID of the validator. - /// - `role`: Selected role type. - pub(crate) fn add_role(account: T::AccountId, role: RoleType) -> DispatchResult { - AccountRolesMapping::::try_mutate(&account, |roles| { - if !roles.contains(&role) { - roles.try_push(role.clone()).map_err(|_| Error::::MaxRoles)?; - - Ok(()) - } else { - Err(Error::::HasRoleAssigned.into()) + /// - `updated_profile`: The updated profile. + pub(crate) fn validate_updated_profile( + account: T::AccountId, + updated_profile: Profile, + ) -> DispatchResult { + let ledger = Self::ledger(&account).ok_or(Error::::NoProfileFound)?; + let removed_roles = ledger.profile.get_removed_roles(&updated_profile); + if !removed_roles.is_empty() { + let active_jobs = T::JobsHandler::get_active_jobs(account.clone()); + // Check removed roles has any active jobs. + for role in removed_roles { + for job in active_jobs.clone() { + let job_key = job.0; + if job_key.get_role_type() == role { + return Err(Error::::RoleCannotBeRemoved.into()) + } + } } - }) - } - - /// Remove role from the given account. - /// - /// # Parameters - /// - `account`: The account ID of the validator. - /// - `role`: Selected role type. - pub(crate) fn remove_role(account: T::AccountId, role: RoleType) -> DispatchResult { - AccountRolesMapping::::try_mutate(&account, |roles| { - if roles.contains(&role) { - roles.retain(|r| r != &role); + }; - Ok(()) - } else { - Err(Error::::NoRoleAssigned.into()) + let records = updated_profile.get_records(); + let min_restaking_bond = MinRestakingBond::::get(); + + for record in records { + if updated_profile.is_independent() { + // TODO: User cannot update profile to lower the restaking amount if there are any + // active services. + let record_restake = record.amount.unwrap_or_default(); + // Restaking amount of record should meet min restaking amount requirement. + ensure!( + record_restake >= min_restaking_bond, + Error::::InsufficientRestakingBond + ); } - }) - } - - /// Check if the given account has the given role. - /// - /// # Parameters - /// - `account`: The account ID of the validator. - /// - `role`: Selected role type. - /// - /// # Returns - /// Returns `true` if the validator is permitted to work with this job type, otherwise `false`. - pub(crate) fn has_role(account: T::AccountId, role: RoleType) -> bool { - let assigned_roles = AccountRolesMapping::::get(account); - match assigned_roles.iter().find(|r| **r == role) { - Some(_) => true, - None => false, + // validate the metadata + T::MPCHandler::validate_authority_key( + account.clone(), + record.metadata.get_authority_key(), + )?; } + Ok(()) } /// Check if account can chill, unbound and withdraw funds. @@ -148,18 +146,18 @@ impl Pallet { false } - /// Calculate max re-stake amount for the given account. + /// Calculate max restake amount for the given account. /// /// # Parameters /// - `total_stake`: Total stake of the validator /// /// # Returns - /// Returns the max re-stake amount. - pub(crate) fn calculate_max_re_stake_amount(total_stake: BalanceOf) -> BalanceOf { - // User can re-stake max 50% of the total stake + /// Returns the max restake amount. + pub(crate) fn calculate_max_restake_amount(total_stake: BalanceOf) -> BalanceOf { + // User can restake max 50% of the total stake Percent::from_percent(50) * total_stake } - /// Calculate slash value for re-staked amount + /// Calculate slash value for restaked amount /// /// # Parameters /// - slash_fraction: Slash fraction of total-stake @@ -167,7 +165,7 @@ impl Pallet { /// /// # Returns /// Returns the slash value - pub(crate) fn calculate_re_stake_slash_value( + pub(crate) fn calculate_restake_slash_value( slash_fraction: Perbill, total_stake: BalanceOf, ) -> BalanceOf { @@ -175,23 +173,6 @@ impl Pallet { slash_fraction * total_stake } - /// Apply slash on re-staked amount. - /// This function will apply slash on re-staked amount and update ledger. - /// - /// # Parameters - /// - `account`: The account ID of the validator. - /// - `slash_value`: Slash value. - pub(crate) fn apply_slash_on_re_stake(account: T::AccountId, slash_value: BalanceOf) { - // Get ledger for the validator account - let mut ledger = Ledger::::get(&account) - .unwrap_or(RoleStakingLedger::::default_from(account.clone())); - - // apply slash - ledger.total = ledger.total.saturating_sub(slash_value); - // Update ledger - Self::update_ledger(&account, &ledger); - } - /// Report offence for the given validator. /// This function will report validators for committing offence. /// @@ -224,17 +205,22 @@ impl Pallet { session_index: offence_report.session_index, validator_set_count: offence_report.validator_set_count, offenders, + role_type: offence_report.role_type, offence_type: offence_report.offence_type, }; let _ = T::ReportOffences::report_offence(sp_std::vec![], offence.clone()); // Update and apply slash on ledger for all offenders let slash_fraction = offence.slash_fraction(offence.validator_set_count); for offender in offence_report.offenders { + let mut profile_ledger = + >::get(&offender).ok_or(Error::::NoProfileFound)?; let staking_ledger = pallet_staking::Ledger::::get(&offender).ok_or(Error::::NotValidator)?; - let re_stake_slash_value = - Self::calculate_re_stake_slash_value(slash_fraction, staking_ledger.total); - Self::apply_slash_on_re_stake(offender, re_stake_slash_value); + let slash_value = + Self::calculate_restake_slash_value(slash_fraction, staking_ledger.total); + // apply slash + profile_ledger.total = profile_ledger.total.saturating_sub(slash_value); + Self::update_ledger(&offender, &profile_ledger); } Ok(()) @@ -245,18 +231,10 @@ impl Pallet { /// # Parameters /// - `staker`: The stash account ID. /// - `ledger`: The new ledger. - /// - /// # Note - /// This function will set a lock on the stash account. pub(crate) fn update_ledger(staker: &T::AccountId, ledger: &RoleStakingLedger) { >::insert(staker, ledger); } - /// Kill the stash account and remove all related information. - pub(crate) fn kill_stash(stash: &T::AccountId) { - >::remove(&stash); - } - pub fn distribute_rewards() -> DispatchResult { let total_rewards = T::InflationRewardPerSession::get(); diff --git a/pallets/roles/src/lib.rs b/pallets/roles/src/lib.rs index ce500d0c0..51dd6a61a 100644 --- a/pallets/roles/src/lib.rs +++ b/pallets/roles/src/lib.rs @@ -18,7 +18,6 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::all)] -use codec::MaxEncodedLen; use frame_support::{ ensure, traits::{Currency, Get, ValidatorSet, ValidatorSetWithIdentification}, @@ -29,15 +28,18 @@ use tangle_primitives::roles::ValidatorRewardDistribution; pub use pallet::*; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; -use sp_runtime::{codec, traits::Zero, Saturating}; +use sp_runtime::{traits::Zero, Saturating}; use sp_staking::offence::ReportOffence; -use sp_std::{convert::TryInto, prelude::*, vec}; +use sp_std::{collections::btree_map::BTreeMap, convert::TryInto, prelude::*, vec}; use tangle_primitives::{ roles::{RoleType, RoleTypeMetadata}, traits::jobs::JobsHandler, }; + mod impls; -use sp_std::collections::btree_map::BTreeMap; +mod profile; +use profile::{Profile, Record}; + #[cfg(test)] pub(crate) mod mock; pub mod offences; @@ -50,47 +52,37 @@ use sp_runtime::RuntimeAppPublic; /// The ledger of a (bonded) stash. #[derive( - PartialEqNoBound, - EqNoBound, - CloneNoBound, - Encode, - Decode, - RuntimeDebugNoBound, - TypeInfo, - MaxEncodedLen, + PartialEqNoBound, EqNoBound, CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, )] #[scale_info(skip_type_params(T))] pub struct RoleStakingLedger { /// The stash account whose balance is actually locked and at stake. pub stash: T::AccountId, - /// The total amount of the stash's balance that is re-staked for all selected roles. - /// This re-staked balance we are currently accounting for new slashing conditions. + /// The total amount of the stash's balance that is restaked for all selected roles. + /// This restaked balance we are currently accounting for new slashing conditions. #[codec(compact)] pub total: BalanceOf, - /// The list of roles and their re-staked amounts. - pub roles: BTreeMap>, -} - -/// The information regarding the re-staked amount for a particular role. -#[derive(PartialEqNoBound, EqNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, Clone)] -#[scale_info(skip_type_params(T))] -pub struct RoleStakingRecord { - /// Metadata associated with the role. - pub metadata: RoleTypeMetadata, - /// The total amount of the stash's balance that is re-staked for selected role. - #[codec(compact)] - pub re_staked: BalanceOf, + /// Restaking Profile + pub profile: Profile, + /// Roles map with their respective records. + pub roles: BTreeMap>, } impl RoleStakingLedger { - /// Initializes the default object using the given `validator`. - pub fn default_from(stash: T::AccountId) -> Self { - Self { stash, total: Zero::zero(), roles: Default::default() } + /// New staking ledger for a stash account. + pub fn new(stash: T::AccountId, profile: Profile) -> Self { + let total_restake = profile.get_total_profile_restake(); + let roles = profile + .get_records() + .into_iter() + .map(|record| (record.metadata.get_role_type(), record)) + .collect::>(); + Self { stash, total: total_restake.into(), profile, roles } } - /// Returns `true` if the stash account has no funds at all. - pub fn is_empty(&self) -> bool { - self.total.is_zero() + /// Returns the total amount of the stash's balance that is restaked for all selected roles. + pub fn total_restake(&self) -> BalanceOf { + self.total } } @@ -175,6 +167,20 @@ pub mod pallet { RoleRemoved { account: T::AccountId, role: RoleType }, /// Slashed validator. Slashed { account: T::AccountId, amount: BalanceOf }, + /// New profile created. + ProfileCreated { + account: T::AccountId, + total_profile_restake: BalanceOf, + roles: Vec, + }, + /// Profile updated. + ProfileUpdated { + account: T::AccountId, + total_profile_restake: BalanceOf, + roles: Vec, + }, + /// Profile deleted. + ProfileDeleted { account: T::AccountId }, /// Pending jobs,that cannot be opted out at the moment. PendingJobs { pending_jobs: Vec<(JobKey, JobId)> }, } @@ -183,22 +189,29 @@ pub mod pallet { pub enum Error { /// Not a validator. NotValidator, - /// Validator has active role assigned + /// Validator has active role assigned. HasRoleAssigned, - /// No role assigned to provided validator. - NoRoleAssigned, + /// Given role is not assigned to the validator. + RoleNotAssigned, /// Max role limit reached for the account. MaxRoles, - /// Invalid Re-staking amount, should not exceed total staked amount. - ExceedsMaxReStakeValue, - /// Re staking amount should be greater than minimum re-staking bond requirement. - InsufficientReStakingBond, - /// Stash controller account already added to Roles Ledger - AccountAlreadyPaired, + /// Role cannot due to pending jobs, which can't be opted out at the moment. + RoleCannotBeRemoved, + /// Restaking amount cannot be lowered if there are any pending jobs. You can only add more + RestakingAmountCannotBeUpdated, + /// Invalid Restaking amount, should not exceed total staked amount. + ExceedsMaxRestakeValue, + /// Re staking amount should be greater than minimum Restaking bond requirement. + InsufficientRestakingBond, + /// Profile Update failed. + ProfileUpdateFailed, + /// Profile already exists for given validator account. + ProfileAlreadyExists, /// Stash controller account not found in Roles Ledger. - AccountNotPaired, - /// Role clear request failed due to pending jobs, which can't be opted out at the moment. - RoleClearRequestFailed, + NoProfileFound, + /// Profile delete request failed due to pending jobs, which can't be opted out at the + /// moment. + ProfileDeleteRequestFailed, } /// Map from all "controller" accounts to the info regarding the staking. @@ -221,27 +234,27 @@ pub mod pallet { /// The minimum re staking bond to become and maintain the role. #[pallet::storage] #[pallet::getter(fn min_active_bond)] - pub(super) type MinReStakingBond = StorageValue<_, BalanceOf, ValueQuery>; + pub(super) type MinRestakingBond = StorageValue<_, BalanceOf, ValueQuery>; - /// Assigns roles to the validator. + /// Create profile for the validator. + /// Validator can choose roles he is interested to opt-in and restake tokens for it. + /// Staking can be done shared or independently for each role. /// /// # Parameters /// /// - `origin`: Origin of the transaction. - /// - `records`: List of roles user is interested to re-stake. + /// - `profile`: Profile to be created /// /// This function will return error if /// - Account is not a validator account. - /// - Role is already assigned to the validator. - /// - Min re-staking bond is not met. + /// - Profile already exists for the validator. + /// - Min Restaking bond is not met. + /// - Restaking amount is exceeds max Restaking value. #[pallet::call] impl Pallet { #[pallet::weight({0})] #[pallet::call_index(0)] - pub fn assign_roles( - origin: OriginFor, - records: Vec>, - ) -> DispatchResult { + pub fn create_profile(origin: OriginFor, profile: Profile) -> DispatchResult { let stash_account = ensure_signed(origin)?; // Ensure stash account is a validator. ensure!( @@ -249,57 +262,189 @@ pub mod pallet { Error::::NotValidator ); - let mut ledger = Ledger::::get(&stash_account) - .unwrap_or(RoleStakingLedger::::default_from(stash_account.clone())); + // Ensure no profile is assigned to the validator. + ensure!(!Ledger::::contains_key(&stash_account), Error::::ProfileAlreadyExists); + let ledger = RoleStakingLedger::::new(stash_account.clone(), profile.clone()); + let total_profile_restake = profile.get_total_profile_restake(); + + // Restaking amount of profile should meet min Restaking amount requirement. + let min_restaking_bond = MinRestakingBond::::get(); + ensure!( + total_profile_restake >= min_restaking_bond, + Error::::InsufficientRestakingBond + ); + // Total restaking amount should not exceed max_restaking_amount. let staking_ledger = pallet_staking::Ledger::::get(&stash_account).ok_or(Error::::NotValidator)?; - - let max_re_staking_bond = Self::calculate_max_re_stake_amount(staking_ledger.active); + let max_restaking_bond = Self::calculate_max_restake_amount(staking_ledger.active); + ensure!( + total_profile_restake <= max_restaking_bond, + Error::::ExceedsMaxRestakeValue + ); // Validate role staking records. - for record in records.clone() { - let role = record.metadata.get_role_type(); - let re_stake_amount = record.re_staked; - // Check if role is already assigned. - ensure!( - !Self::has_role(stash_account.clone(), role.clone()), - Error::::HasRoleAssigned - ); - + let records = profile.get_records(); + for record in records { + if profile.is_independent() { + // Restaking amount of record should meet min Restaking amount requirement. + let record_restake = record.amount.unwrap_or_default(); + ensure!( + record_restake >= min_restaking_bond, + Error::::InsufficientRestakingBond + ); + } // validate the metadata T::MPCHandler::validate_authority_key( stash_account.clone(), record.metadata.get_authority_key(), )?; + } - // Re-staking amount of record should meet min re-staking amount requirement. - let min_re_staking_bond = MinReStakingBond::::get(); - ensure!( - re_stake_amount >= min_re_staking_bond, - Error::::InsufficientReStakingBond - ); + let profile_roles: BoundedVec = + BoundedVec::try_from(profile.get_roles()).map_err(|_| Error::::MaxRoles)?; + + AccountRolesMapping::::insert(&stash_account, profile_roles); + + Self::update_ledger(&stash_account, &ledger); + Self::deposit_event(Event::::ProfileCreated { + account: stash_account.clone(), + total_profile_restake, + roles: profile.get_roles(), + }); + + Ok(()) + } + + /// Update profile of the validator. + /// This function will update the profile of the validator. + /// If user wants to remove any role, please ensure that all the jobs associated with the + /// role are completed else this tx will fail. + /// If user wants to add any role, please ensure that the Restaking amount is greater than + /// required min Restaking bond. + /// + /// # Parameters + /// - `origin`: Origin of the transaction. + /// - `profile`: Updated profile. + /// + /// This function will return error if + /// - Account is not a validator account. + /// - Profile is not assigned to the validator. + /// - If there are any pending jobs for the role which user wants to remove. + /// - Restaking amount is exceeds max Restaking value. + /// - Restaking amount is less than min Restaking bond. + #[pallet::weight({0})] + #[pallet::call_index(1)] + pub fn update_profile(origin: OriginFor, updated_profile: Profile) -> DispatchResult { + let stash_account = ensure_signed(origin)?; + // Ensure stash account is a validator. + ensure!( + pallet_staking::Validators::::contains_key(&stash_account), + Error::::NotValidator + ); + let mut ledger = Ledger::::get(&stash_account).ok_or(Error::::NoProfileFound)?; + + let total_profile_restake = updated_profile.get_total_profile_restake(); + // Restaking amount of record should meet min Restaking amount requirement. + let min_restaking_bond = MinRestakingBond::::get(); + ensure!( + total_profile_restake >= min_restaking_bond, + Error::::InsufficientRestakingBond + ); + + let staking_ledger = + pallet_staking::Ledger::::get(&stash_account).ok_or(Error::::NotValidator)?; + + let max_restaking_bond = Self::calculate_max_restake_amount(staking_ledger.active); + // Total restaking amount should not exceed max_restaking_amount. + ensure!( + total_profile_restake <= max_restaking_bond, + Error::::ExceedsMaxRestakeValue + ); + + Self::validate_updated_profile(stash_account.clone(), updated_profile.clone())?; + ledger.profile = updated_profile.clone(); + ledger.total = total_profile_restake; + + let profile_roles: BoundedVec = + BoundedVec::try_from(updated_profile.get_roles()) + .map_err(|_| Error::::MaxRoles)?; - // Total re_staking amount should not exceed max_re_staking_amount. - ensure!( - ledger.total.saturating_add(re_stake_amount) <= max_re_staking_bond, - Error::::ExceedsMaxReStakeValue + AccountRolesMapping::::insert(&stash_account, profile_roles); + Self::update_ledger(&stash_account, &ledger); + + Self::deposit_event(Event::::ProfileUpdated { + account: stash_account.clone(), + total_profile_restake, + roles: updated_profile.get_roles(), + }); + + Ok(()) + } + + /// Delete profile of the validator. + /// This function will submit the request to exit from all the services and will fails if + /// all the job are not completed. + /// + /// + /// # Parameters + /// - `origin`: Origin of the transaction. + /// + /// This function will return error if + /// - Account is not a validator account. + /// - Profile is not assigned to the validator. + /// - All the jobs are not completed. + #[pallet::weight({0})] + #[pallet::call_index(2)] + pub fn delete_profile(origin: OriginFor) -> DispatchResult { + let stash_account = ensure_signed(origin)?; + // Ensure stash account is a validator. + ensure!( + pallet_staking::Validators::::contains_key(&stash_account), + Error::::NotValidator + ); + let mut ledger = Ledger::::get(&stash_account).ok_or(Error::::NoProfileFound)?; + + // Submit request to exit from all the services. + let active_jobs = T::JobsHandler::get_active_jobs(stash_account.clone()); + let mut pending_jobs = Vec::new(); + for job in active_jobs { + let job_key = job.0; + // Submit request to exit from the known set. + let res = T::JobsHandler::exit_from_known_set( + stash_account.clone(), + job_key.clone(), + job.1, ); - ledger.total = ledger.total.saturating_add(re_stake_amount); - ledger.roles.insert(record.metadata.get_role_type(), record); + if res.is_err() { + pending_jobs.push((job_key.clone(), job.1)); + } else { + // Remove role from the profile. + ledger.profile.remove_role_from_profile(job_key.get_role_type()); + } } - // Now that records are validated we can add them and update ledger - for record in records { - let role = record.metadata.get_role_type(); - Self::add_role(stash_account.clone(), role.clone())?; - Self::deposit_event(Event::::RoleAssigned { - account: stash_account.clone(), - role, - }); - } - Self::update_ledger(&stash_account, &ledger); + if !pending_jobs.is_empty() { + // Update account roles mapping. + let profile_roles: BoundedVec = + BoundedVec::try_from(ledger.profile.get_roles()) + .map_err(|_| Error::::MaxRoles)?; + + AccountRolesMapping::::insert(&stash_account, profile_roles); + + // Profile delete request failed due to pending jobs, which can't be opted out at + // the moment. + Self::deposit_event(Event::::PendingJobs { pending_jobs }); + return Err(Error::::ProfileDeleteRequestFailed.into()) + }; + + Self::deposit_event(Event::::ProfileDeleted { account: stash_account.clone() }); + + // Remove entry from ledger. + Ledger::::remove(&stash_account); + // Remove entry from account roles mapping. + AccountRolesMapping::::remove(&stash_account); Ok(()) } @@ -316,8 +461,8 @@ pub mod pallet { /// - Role is not assigned to the validator. /// - All the jobs are not completed. #[pallet::weight({0})] - #[pallet::call_index(1)] - pub fn clear_role(origin: OriginFor, role: RoleType) -> DispatchResult { + #[pallet::call_index(3)] + pub fn remove_role(origin: OriginFor, role: RoleType) -> DispatchResult { let stash_account = ensure_signed(origin)?; // Ensure stash account is a validator. ensure!( @@ -325,15 +470,19 @@ pub mod pallet { Error::::NotValidator ); - // check if role is assigned. - ensure!( - Self::has_role(stash_account.clone(), role.clone()), - Error::::NoRoleAssigned - ); + let mut ledger = Self::ledger(&stash_account).ok_or(Error::::NoProfileFound)?; + + // Check if role is assigned. + ensure!(ledger.profile.has_role(role.clone()), Error::::RoleNotAssigned); // Get active jobs for the role. let active_jobs = T::JobsHandler::get_active_jobs(stash_account.clone()); - let mut role_cleared = true; + + if active_jobs.is_empty() { + // Remove role from the profile. + ledger.profile.remove_role_from_profile(role.clone()); + } + let mut pending_jobs = Vec::new(); for job in active_jobs { let job_key = job.0; @@ -346,23 +495,28 @@ pub mod pallet { ); if res.is_err() { - role_cleared = false; pending_jobs.push((job_key.clone(), job.1)); + } else { + // Remove role from the profile. + ledger.profile.remove_role_from_profile(role.clone()); } } } - if !role_cleared { + if !pending_jobs.is_empty() { // Role clear request failed due to pending jobs, which can't be opted out at the // moment. Self::deposit_event(Event::::PendingJobs { pending_jobs }); - return Err(Error::::RoleClearRequestFailed.into()) + return Err(Error::::RoleCannotBeRemoved.into()) }; + let profile_roles: BoundedVec = + BoundedVec::try_from(ledger.profile.get_roles()) + .map_err(|_| Error::::MaxRoles)?; - // Remove role from the mapping. - Self::remove_role(stash_account.clone(), role.clone())?; - // Remove stash account related info. - Self::kill_stash(&stash_account); + AccountRolesMapping::::insert(&stash_account, profile_roles); + + ledger.total = ledger.profile.get_total_profile_restake().into(); + Self::update_ledger(&stash_account, &ledger); Self::deposit_event(Event::::RoleRemoved { account: stash_account, role }); @@ -382,7 +536,7 @@ pub mod pallet { /// - Account is not a validator account. /// - Role is assigned to the validator. #[pallet::weight({0})] - #[pallet::call_index(2)] + #[pallet::call_index(4)] pub fn chill(origin: OriginFor) -> DispatchResult { let account = ensure_signed(origin.clone())?; // Ensure no role is assigned to the account before chilling. @@ -407,7 +561,7 @@ pub mod pallet { /// - If there is any active role assigned to the user. /// #[pallet::weight({0})] - #[pallet::call_index(3)] + #[pallet::call_index(5)] pub fn unbound_funds( origin: OriginFor, #[pallet::compact] amount: BalanceOf, @@ -433,7 +587,7 @@ pub mod pallet { /// This function will return error if /// - If there is any active role assigned to the user. #[pallet::weight({0})] - #[pallet::call_index(4)] + #[pallet::call_index(6)] pub fn withdraw_unbonded(origin: OriginFor) -> DispatchResult { let account = ensure_signed(origin.clone())?; // Ensure no role is assigned to the account and is eligible to withdraw. diff --git a/pallets/roles/src/offences.rs b/pallets/roles/src/offences.rs index c0d3dbb07..cce540d44 100644 --- a/pallets/roles/src/offences.rs +++ b/pallets/roles/src/offences.rs @@ -1,3 +1,19 @@ +// This file is part of Webb. +// Copyright (C) 2022-2023 Webb Technologies Inc. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + use super::*; use sp_runtime::{Perbill, Saturating}; use sp_staking::{ @@ -14,6 +30,8 @@ pub struct ValidatorOffence { pub validator_set_count: u32, /// Authorities that committed an offence. pub offenders: Vec, + /// Role type against which offence is reported. + pub role_type: RoleType, /// The different types of the offence. pub offence_type: ValidatorOffenceType, } diff --git a/pallets/roles/src/profile.rs b/pallets/roles/src/profile.rs new file mode 100644 index 000000000..3c3083ca4 --- /dev/null +++ b/pallets/roles/src/profile.rs @@ -0,0 +1,142 @@ +// This file is part of Webb. +// Copyright (C) 2022-2023 Webb Technologies Inc. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use crate::{BalanceOf, Config}; +use frame_support::pallet_prelude::*; +use sp_runtime::traits::Zero; +use sp_std::vec::Vec; +use tangle_primitives::roles::{RoleType, RoleTypeMetadata}; + +#[derive( + PartialEqNoBound, EqNoBound, CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, +)] +#[scale_info(skip_type_params(T))] +pub struct Record { + pub metadata: RoleTypeMetadata, + pub amount: Option>, +} + +#[derive( + PartialEqNoBound, EqNoBound, CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, +)] +#[scale_info(skip_type_params(T))] +pub struct IndependentRestakeProfile { + pub records: BoundedVec, T::MaxRolesPerAccount>, +} + +#[derive( + PartialEqNoBound, EqNoBound, CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, +)] +#[scale_info(skip_type_params(T))] +pub struct SharedRestakeProfile { + pub records: BoundedVec, T::MaxRolesPerAccount>, + pub amount: BalanceOf, +} + +#[derive( + PartialEqNoBound, EqNoBound, CloneNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, +)] +#[scale_info(skip_type_params(T))] +pub enum Profile { + Independent(IndependentRestakeProfile), + Shared(SharedRestakeProfile), +} + +impl Profile { + /// Checks if the profile is an independent profile. + pub fn is_independent(&self) -> bool { + matches!(self, Profile::Independent(_)) + } + + /// Checks if the profile is a shared profile. + pub fn is_shared(&self) -> bool { + matches!(self, Profile::Shared(_)) + } + + /// Returns the total profile restake. + pub fn get_total_profile_restake(&self) -> BalanceOf { + match self { + Profile::Independent(profile) => profile + .records + .iter() + .fold(Zero::zero(), |acc, record| acc + record.amount.unwrap_or_default()), + Profile::Shared(profile) => profile.amount, + } + } + + /// Returns staking record details containing role metadata and restake amount. + pub fn get_records(&self) -> Vec> { + match self { + Profile::Independent(profile) => profile.records.clone().into_inner(), + Profile::Shared(profile) => profile.records.clone().into_inner(), + } + } + + /// Returns roles in the profile. + pub fn get_roles(&self) -> Vec { + match self { + Profile::Independent(profile) => + profile.records.iter().map(|record| record.metadata.get_role_type()).collect(), + Profile::Shared(profile) => + profile.records.iter().map(|record| record.metadata.get_role_type()).collect(), + } + } + + /// Checks if the profile contains given role. + pub fn has_role(&self, role_type: RoleType) -> bool { + match self { + Profile::Independent(profile) => profile + .records + .iter() + .any(|record| record.metadata.get_role_type() == role_type), + Profile::Shared(profile) => profile + .records + .iter() + .any(|record| record.metadata.get_role_type() == role_type), + } + } + + /// Removes given role from the profile. + pub fn remove_role_from_profile(&mut self, role_type: RoleType) { + match self { + Profile::Independent(profile) => { + profile.records.retain(|record| record.metadata.get_role_type() != role_type); + }, + Profile::Shared(profile) => { + profile.records.retain(|record| record.metadata.get_role_type() != role_type); + }, + } + } + + /// Return roles from current profile removed in updated profile. + pub fn get_removed_roles(&self, updated_profile: &Profile) -> Vec { + // Get the roles from the current profile. + let roles = self.get_roles(); + let updated_roles = updated_profile.get_roles(); + // Returns roles in current profile that have been removed in updated profile. + roles + .iter() + .filter_map(|role| { + let updated_role = updated_roles.iter().find(|updated_role| *updated_role == role); + if updated_role.is_none() { + Some(role.clone()) + } else { + None + } + }) + .collect() + } +} diff --git a/pallets/roles/src/tests.rs b/pallets/roles/src/tests.rs index 35f2cccdb..e535ba16c 100644 --- a/pallets/roles/src/tests.rs +++ b/pallets/roles/src/tests.rs @@ -13,251 +13,282 @@ // // You should have received a copy of the GNU General Public License // along with Tangle. If not, see . + #![cfg(test)] use super::*; -use frame_support::{assert_err, assert_ok}; +use frame_support::{assert_err, assert_ok, BoundedVec}; use mock::*; -use sp_std::default::Default; +use profile::{IndependentRestakeProfile, Record, SharedRestakeProfile}; +use sp_std::{default::Default, vec}; use tangle_primitives::jobs::ReportValidatorOffence; +pub fn independent_profile() -> Profile { + let profile = IndependentRestakeProfile { + records: BoundedVec::try_from(vec![ + Record { metadata: RoleTypeMetadata::Tss(Default::default()), amount: Some(2500) }, + Record { metadata: RoleTypeMetadata::ZkSaas(Default::default()), amount: Some(2500) }, + ]) + .unwrap(), + }; + Profile::Independent(profile) +} + +pub fn shared_profile() -> Profile { + let profile = SharedRestakeProfile { + records: BoundedVec::try_from(vec![ + Record { metadata: RoleTypeMetadata::Tss(Default::default()), amount: None }, + Record { metadata: RoleTypeMetadata::ZkSaas(Default::default()), amount: None }, + ]) + .unwrap(), + amount: 5000, + }; + Profile::Shared(profile) +} + +// Test create independent profile. #[test] -fn test_assign_roles() { +fn test_create_independent_profile() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { - // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens - - // Roles user is interested in re-staking. - let mut role_records: BTreeMap<_, _> = Default::default(); - role_records.insert( - RoleType::Tss, - RoleStakingRecord { - metadata: RoleTypeMetadata::Tss(Default::default()), - re_staked: 5000, - }, - ); + let profile = independent_profile(); + assert_ok!(Roles::create_profile(RuntimeOrigin::signed(1), profile.clone())); + + assert_events(vec![RuntimeEvent::Roles(crate::Event::ProfileCreated { + account: 1, + total_profile_restake: profile.get_total_profile_restake().into(), + roles: profile.get_roles(), + })]); + // Get the ledger to check if the profile is created. + let ledger = Roles::ledger(1).unwrap(); + assert_eq!(ledger.profile, profile); + assert!(ledger.profile.is_independent()); + }); +} - assert_ok!(Roles::assign_roles( - RuntimeOrigin::signed(1), - vec![RoleStakingRecord { - metadata: RoleTypeMetadata::Tss(Default::default()), - re_staked: 5000, - }] - )); +// Test create shared profile. +#[test] +fn test_create_shared_profile() { + new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { + let profile = shared_profile(); + assert_ok!(Roles::create_profile(RuntimeOrigin::signed(1), profile.clone())); - assert_events(vec![RuntimeEvent::Roles(crate::Event::RoleAssigned { + assert_events(vec![RuntimeEvent::Roles(crate::Event::ProfileCreated { account: 1, - role: RoleType::Tss, + total_profile_restake: profile.get_total_profile_restake().into(), + roles: profile.get_roles(), })]); - // Lets verify role assigned to account. - assert_eq!(Roles::has_role(1, RoleType::Tss), true); - // Verify ledger mapping - assert_eq!( - Roles::ledger(1), - Some(RoleStakingLedger { stash: 1, total: 5000, roles: role_records }) - ); + // Get the ledger to check if the profile is created. + let ledger = Roles::ledger(1).unwrap(); + assert_eq!(ledger.profile, profile); + assert!(ledger.profile.is_shared()); }); } -// test assign multiple roles to an account. +// Test create profile should fail if user is not a validator. #[test] -fn test_assign_multiple_roles() { +fn test_create_profile_should_fail_if_user_is_not_a_validator() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { - // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens - - // 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, - }, - ]; - - // Roles user is interested in re-staking. - let mut role_records_map: BTreeMap<_, _> = Default::default(); - role_records_map.insert( - RoleType::Tss, - RoleStakingRecord { - metadata: RoleTypeMetadata::Tss(Default::default()), - re_staked: 2500, - }, - ); - role_records_map.insert( - RoleType::ZkSaaS, - RoleStakingRecord { - metadata: RoleTypeMetadata::ZkSaas(Default::default()), - re_staked: 2500, - }, + let profile = shared_profile(); + assert_err!( + Roles::create_profile(RuntimeOrigin::signed(5), profile.clone()), + Error::::NotValidator ); + }); +} - assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(1), role_records.clone())); +// Test create profile should fail if user already has a profile. +#[test] +fn test_create_profile_should_fail_if_user_already_has_a_profile() { + new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { + let profile = shared_profile(); + assert_ok!(Roles::create_profile(RuntimeOrigin::signed(1), profile.clone())); + assert_err!( + Roles::create_profile(RuntimeOrigin::signed(1), profile.clone()), + Error::::ProfileAlreadyExists + ); + }); +} - // Lets verify role assigned to account. - assert_eq!(Roles::has_role(1, RoleType::Tss), true); +// Test create profile should fail if min required restake condition is not met. +// Min restake required is 2500. +#[test] +fn test_create_profile_should_fail_if_min_required_restake_condition_is_not_met() { + new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { + pallet::MinRestakingBond::::put(2500); - // Lets verify role assigned to account. - assert_eq!(Roles::has_role(1, RoleType::ZkSaaS), true); + let profile = Profile::Shared(SharedRestakeProfile { + records: BoundedVec::try_from(vec![ + Record { metadata: RoleTypeMetadata::Tss(Default::default()), amount: None }, + Record { metadata: RoleTypeMetadata::ZkSaas(Default::default()), amount: None }, + ]) + .unwrap(), + amount: 1000, + }); - assert_eq!( - Roles::ledger(1), - Some(RoleStakingLedger { stash: 1, total: 5000, roles: role_records_map }) + assert_err!( + Roles::create_profile(RuntimeOrigin::signed(1), profile.clone()), + Error::::InsufficientRestakingBond ); }); } -// Test assign roles, should fail if total re-stake value exceeds max re-stake value. -// Max re-stake value is 5000 (50% of total staked value). +// Test create profile should fail if min required restake condition is not met. +// In case of independent profile, each role should meet the min required restake condition. +// Min restake required is 2500. #[test] -fn test_assign_roles_should_fail_if_total_re_stake_value_exceeds_max_re_stake_value() { +fn test_create_profile_should_fail_if_min_required_restake_condition_is_not_met_for_independent_profile( +) { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { - // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens - - // Roles user is interested in re-staking. - let role_records = vec![ - RoleStakingRecord { - metadata: RoleTypeMetadata::Tss(Default::default()), - re_staked: 5000, - }, - RoleStakingRecord { - metadata: RoleTypeMetadata::ZkSaas(Default::default()), - re_staked: 5000, - }, - ]; - // Since max re_stake limit is 5000 it should fail with `ExceedsMaxReStakeValue` error. + pallet::MinRestakingBond::::put(2500); + + let profile = Profile::Independent(IndependentRestakeProfile { + records: BoundedVec::try_from(vec![ + Record { metadata: RoleTypeMetadata::Tss(Default::default()), amount: Some(1000) }, + Record { + metadata: RoleTypeMetadata::ZkSaas(Default::default()), + amount: Some(1000), + }, + ]) + .unwrap(), + }); + assert_err!( - Roles::assign_roles(RuntimeOrigin::signed(1), role_records), - Error::::ExceedsMaxReStakeValue + Roles::create_profile(RuntimeOrigin::signed(1), profile.clone()), + Error::::InsufficientRestakingBond ); }); } +// Update profile from independent to shared. #[test] -fn test_clear_role() { +fn test_update_profile_from_independent_to_shared() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { - // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens + // Lets create independent profile. + let profile = independent_profile(); + assert_ok!(Roles::create_profile(RuntimeOrigin::signed(1), profile.clone())); - // Roles user is interested in re-staking. - let role_records = vec![RoleStakingRecord { - metadata: RoleTypeMetadata::Tss(Default::default()), - re_staked: 5000, - }]; + // Get the ledger to check if the profile is created. + let ledger = Roles::ledger(1).unwrap(); + assert!(ledger.profile.is_independent()); + assert_eq!(ledger.total_restake(), 5000); - assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(1), role_records)); + let updated_profile = shared_profile(); - // Now lets clear the role - assert_ok!(Roles::clear_role(RuntimeOrigin::signed(1), RoleType::Tss)); + assert_ok!(Roles::update_profile(RuntimeOrigin::signed(1), updated_profile.clone())); - assert_events(vec![RuntimeEvent::Roles(crate::Event::RoleRemoved { + assert_events(vec![RuntimeEvent::Roles(crate::Event::ProfileUpdated { account: 1, - role: RoleType::Tss, + total_profile_restake: profile.get_total_profile_restake().into(), + roles: profile.get_roles(), })]); - - // Role should be removed from account role mappings. - assert_eq!(Roles::has_role(1, RoleType::Tss), false); - - // Ledger should be removed from ledger mappings. - assert_eq!(Roles::ledger(1), None); + // Get updated ledger and check if the profile is updated. + let ledger = Roles::ledger(1).unwrap(); + assert_eq!(ledger.profile, updated_profile); + assert!(ledger.profile.is_shared()); }); } +// Update profile from shared to independent. #[test] -fn test_assign_roles_should_fail_if_not_validator() { +fn test_update_profile_from_shared_to_independent() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { - // we will use account 5 which is not a validator + // Lets create shared profile. + let profile = shared_profile(); + assert_ok!(Roles::create_profile(RuntimeOrigin::signed(1), profile.clone())); - // Roles user is interested in re-staking. - let role_records = vec![RoleStakingRecord { - metadata: RoleTypeMetadata::Tss(Default::default()), - re_staked: 5000, - }]; + // Get the ledger to check if the profile is created. + let ledger = Roles::ledger(1).unwrap(); + assert!(ledger.profile.is_shared()); + assert_eq!(ledger.total_restake(), 5000); - assert_err!( - Roles::assign_roles(RuntimeOrigin::signed(5), role_records), - Error::::NotValidator - ); + let updated_profile = independent_profile(); + assert_ok!(Roles::update_profile(RuntimeOrigin::signed(1), updated_profile.clone())); + + assert_events(vec![RuntimeEvent::Roles(crate::Event::ProfileUpdated { + account: 1, + total_profile_restake: profile.get_total_profile_restake().into(), + roles: profile.get_roles(), + })]); + // Get updated ledger and check if the profile is updated. + let ledger = Roles::ledger(1).unwrap(); + assert_eq!(ledger.profile, updated_profile); + assert!(ledger.profile.is_independent()); + assert_eq!(ledger.total_restake(), 5000); }); } +// Test delete profile. #[test] -fn test_unbound_funds_should_work() { +fn test_delete_profile() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { - // Initially validator account has staked 10_000 tokens and wants to re-stake 5000 tokens - // for providing TSS services. - - // Roles user is interested in re-staking. - let role_records = vec![RoleStakingRecord { - metadata: RoleTypeMetadata::Tss(Default::default()), - re_staked: 5000, - }]; - - assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(1), role_records)); + // Lets create shared profile. + let profile = shared_profile(); + assert_ok!(Roles::create_profile(RuntimeOrigin::signed(1), profile.clone())); - // Lets verify role is assigned to account. - assert_eq!(Roles::has_role(1, RoleType::Tss), true); - - // Lets clear the role. - assert_ok!(Roles::clear_role(RuntimeOrigin::signed(1), RoleType::Tss)); - - // Role should be removed from account role mappings. - assert_eq!(Roles::has_role(1, RoleType::Tss), false); - - // unbound funds. - assert_ok!(Roles::unbound_funds(RuntimeOrigin::signed(1), 5000)); + // Get the ledger to check if the profile is created. + let ledger = Roles::ledger(1).unwrap(); + assert!(ledger.profile.is_shared()); + assert_eq!(ledger.total_restake(), 5000); - assert_events(vec![RuntimeEvent::Staking(pallet_staking::Event::Unbonded { - stash: 1, - amount: 5000, - })]); + assert_ok!(Roles::delete_profile(RuntimeOrigin::signed(1))); - // Get pallet staking ledger mapping. - let staking_ledger = pallet_staking::Ledger::::get(1).unwrap(); - // Since we we have unbounded 5000 tokens, we should have 5000 tokens in staking ledger. - assert_eq!(staking_ledger.active, 5000); + assert_events(vec![RuntimeEvent::Roles(crate::Event::ProfileDeleted { account: 1 })]); + assert_eq!(Roles::ledger(1), None); }); } -// Test unbound should fail if role is assigned to account. +// Test remove role from profile. #[test] -fn test_unbound_funds_should_fail_if_role_assigned() { +fn test_remove_role_from_profile() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { - // Initially validator account has staked 10_000 tokens and wants to re-stake 5000 tokens - // for providing TSS services. + // Lets create shared profile. + let profile = shared_profile(); + assert_ok!(Roles::create_profile(RuntimeOrigin::signed(1), profile.clone())); - // Roles user is interested in re-staking. - let role_records = vec![RoleStakingRecord { - metadata: RoleTypeMetadata::Tss(Default::default()), - re_staked: 5000, - }]; + // Get the ledger to check if the profile is created. + let ledger = Roles::ledger(1).unwrap(); + assert!(ledger.profile.is_shared()); + assert_eq!(ledger.total_restake(), 5000); + assert!(ledger.profile.has_role(RoleType::Tss)); - assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(1), role_records)); + // Lets remove Tss role from the profile. + assert_ok!(Roles::remove_role(RuntimeOrigin::signed(1), RoleType::Tss)); - // Lets verify role is assigned to account. - assert_eq!(Roles::has_role(1, RoleType::Tss), true); + assert_events(vec![RuntimeEvent::Roles(crate::Event::RoleRemoved { + account: 1, + role: RoleType::Tss, + })]); - // Lets try to unbound funds. - assert_err!( - Roles::unbound_funds(RuntimeOrigin::signed(1), 5000), - Error::::HasRoleAssigned - ); + // Get the updated ledger to check if the role is removed. + let ledger = Roles::ledger(1).unwrap(); + assert!(!ledger.profile.has_role(RoleType::Tss)); }); } -// Test unbound should work if no role assigned to account. #[test] -fn test_unbound_funds_should_work_if_no_role_assigned() { +fn test_unbound_funds_should_work() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { - // Initially validator account has staked 10_000 tokens. + // Lets create shared profile. + let profile = shared_profile(); + assert_ok!(Roles::create_profile(RuntimeOrigin::signed(1), profile.clone())); + + // Lets delete profile by opting out of all services. + assert_ok!(Roles::delete_profile(RuntimeOrigin::signed(1))); + + assert_eq!(Roles::ledger(1), None); - // Since validator has not opted for any roles, he should be able to unbound his funds. + // unbound funds. assert_ok!(Roles::unbound_funds(RuntimeOrigin::signed(1), 5000)); assert_events(vec![RuntimeEvent::Staking(pallet_staking::Event::Unbonded { stash: 1, amount: 5000, })]); + + // Get pallet staking ledger mapping. + let staking_ledger = pallet_staking::Ledger::::get(1).unwrap(); + // Since we we have unbounded 5000 tokens, we should have 5000 tokens in staking ledger. + assert_eq!(staking_ledger.active, 5000); }); } @@ -266,19 +297,8 @@ 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)); + let profile = shared_profile(); + assert_ok!(Roles::create_profile(RuntimeOrigin::signed(1), profile.clone())); // The reward is 100, we have 5 authorities assert_ok!(Roles::distribute_rewards()); @@ -294,20 +314,9 @@ fn test_reward_dist_works_as_expected_with_multiple_validator() { 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)); + let profile = shared_profile(); + assert_ok!(Roles::create_profile(RuntimeOrigin::signed(1), profile.clone())); + assert_ok!(Roles::create_profile(RuntimeOrigin::signed(2), profile.clone())); // The reward is 100, we have 5 authorities assert_ok!(Roles::distribute_rewards()); @@ -322,31 +331,20 @@ fn test_reward_dist_works_as_expected_with_multiple_validator() { #[test] fn test_report_offence_should_work() { new_test_ext_raw_authorities(vec![1, 2, 3, 4]).execute_with(|| { - // Initially validator account has staked 10_000 tokens and wants to re-stake 5000 tokens - // for providing TSS services. - - // Roles user is interested in re-staking. - let role_records = vec![RoleStakingRecord { - metadata: RoleTypeMetadata::Tss(Default::default()), - re_staked: 5000, - }]; - - assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(1), role_records)); + let profile = shared_profile(); + assert_ok!(Roles::create_profile(RuntimeOrigin::signed(1), profile.clone())); - // Lets verify role is assigned to account. - assert_eq!(Roles::has_role(1, RoleType::Tss), true); - - // get current session index. + // Get current session index. let session_index = pallet_session::CurrentIndex::::get(); // Create offence report. let offence_report = ReportValidatorOffence { session_index, validator_set_count: 4, + role_type: RoleType::Tss, offence_type: tangle_primitives::jobs::ValidatorOffenceType::Inactivity, offenders: vec![1], }; - // Lets report offence. assert_ok!(Roles::report_offence(offence_report)); // Should slash 700 tokens diff --git a/primitives/src/types/jobs.rs b/primitives/src/types/jobs.rs index 4922f79be..734c399b4 100644 --- a/primitives/src/types/jobs.rs +++ b/primitives/src/types/jobs.rs @@ -481,6 +481,8 @@ pub struct ReportValidatorOffence { pub validator_set_count: u32, /// The type of offence pub offence_type: ValidatorOffenceType, + /// Role type against which offence is reported. + pub role_type: RoleType, /// Offenders pub offenders: Vec, } diff --git a/primitives/src/types/roles.rs b/primitives/src/types/roles.rs index 1014c49fb..e1aa2e187 100644 --- a/primitives/src/types/roles.rs +++ b/primitives/src/types/roles.rs @@ -81,15 +81,6 @@ pub struct ZkSaasRoleMetadata { authority_key: Vec, } -/// Role type to be used in the system. -#[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)] -pub enum ReStakingOption { - // Re-stake all the staked funds for selected role. - Full, - // 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