diff --git a/Cargo.lock b/Cargo.lock index 7dce07fd5..b025942c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7768,6 +7768,7 @@ dependencies = [ "hex-literal", "log", "pallet-balances", + "pallet-jobs", "pallet-session", "pallet-staking", "pallet-timestamp", diff --git a/pallets/jobs/src/tests.rs b/pallets/jobs/src/tests.rs index e6a189fe8..9097ce02e 100644 --- a/pallets/jobs/src/tests.rs +++ b/pallets/jobs/src/tests.rs @@ -18,7 +18,7 @@ use super::*; use frame_support::{assert_noop, assert_ok}; use mock::*; -use pallet_roles::profile::{Profile, Record, SharedRestakeProfile}; +use pallet_roles::profile::{IndependentRestakeProfile, Profile, Record, SharedRestakeProfile}; use tangle_primitives::{ jobs::{ DKGTSSPhaseOneJobType, DKGTSSPhaseTwoJobType, DKGTSSSignatureResult, DigitalSignatureType, @@ -28,6 +28,7 @@ use tangle_primitives::{ }, roles::{RoleType, ThresholdSignatureRoleType, ZeroKnowledgeRoleType}, }; + const ALICE: u8 = 1; const BOB: u8 = 2; const CHARLIE: u8 = 3; @@ -41,7 +42,10 @@ const HUNDRED: u8 = 100; pub fn shared_profile() -> Profile { let profile = SharedRestakeProfile { records: BoundedVec::try_from(vec![ - Record { role: RoleType::Tss(ThresholdSignatureRoleType::TssGG20), amount: None }, + Record { + role: RoleType::Tss(ThresholdSignatureRoleType::ZengoGG20Secp256k1), + amount: None, + }, Record { role: RoleType::ZkSaaS(ZeroKnowledgeRoleType::ZkSaaSGroth16), amount: None }, ]) .unwrap(), @@ -511,19 +515,172 @@ fn jobs_submission_e2e_works_for_zksaas() { }); } -// #[test] -// fn withdraw_validator_rewards_works() { -// new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { -// System::set_block_number(1); -// -// ValidatorRewards::::insert(1, 100); -// ValidatorRewards::::insert(2, 100); -// ValidatorRewards::::insert(3, 100); -// -// // can withdraw the reward by validator -// for validator in [1, 2, 3] { -// assert_ok!(Jobs::withdraw_rewards(RuntimeOrigin::signed(validator))); -// assert_eq!(ValidatorRewards::::get(validator), None); -// } -// }); -// } +#[test] +fn jobs_validator_checks_work() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + Balances::make_free_balance_be(&mock_pub_key(TEN), 100); + + let participants = vec![ALICE, BOB, CHARLIE, DAVE, EVE]; + + // all validators sign up in roles pallet + let profile = shared_profile(); + for validator in participants.clone() { + assert_ok!(Roles::create_profile( + RuntimeOrigin::signed(mock_pub_key(validator)), + profile.clone() + )); + } + + // submit job with existing validators + let threshold_signature_role_type = ThresholdSignatureRoleType::ZengoGG20Secp256k1; + let submission = JobSubmission { + expiry: 10, + ttl: 200, + job_type: JobType::DKGTSSPhaseOne(DKGTSSPhaseOneJobType { + participants: participants.clone().iter().map(|x| mock_pub_key(*x)).collect(), + threshold: 3, + permitted_caller: Some(mock_pub_key(TEN)), + role_type: threshold_signature_role_type, + }), + }; + assert_ok!(Jobs::submit_job(RuntimeOrigin::signed(mock_pub_key(TEN)), submission)); + + // ======= active validator cannot reduce stake =============== + let reduced_profile = SharedRestakeProfile { + records: BoundedVec::try_from(vec![ + Record { + role: RoleType::Tss(ThresholdSignatureRoleType::ZengoGG20Secp256k1), + amount: None, + }, + Record { + role: RoleType::ZkSaaS(ZeroKnowledgeRoleType::ZkSaaSGroth16), + amount: None, + }, + ]) + .unwrap(), + amount: 500, // reduce stake by 50% + }; + + for validator in participants.clone() { + assert_noop!( + Roles::update_profile( + RuntimeOrigin::signed(mock_pub_key(validator)), + Profile::Shared(reduced_profile.clone()) + ), + pallet_roles::Error::::InsufficientRestakingBond + ); + } + + // ========= active validator cannot delete profile with active job ============= + for validator in participants.clone() { + assert_noop!( + Roles::delete_profile(RuntimeOrigin::signed(mock_pub_key(validator)),), + pallet_roles::Error::::ProfileDeleteRequestFailed + ); + } + + // ========= active validator cannot remove role with active job ============= + let reduced_profile = SharedRestakeProfile { + records: BoundedVec::try_from(vec![Record { + role: RoleType::ZkSaaS(ZeroKnowledgeRoleType::ZkSaaSGroth16), + amount: None, + }]) + .unwrap(), + amount: 500, // reduce stake by 50% + }; + for validator in participants.clone() { + assert_noop!( + Roles::update_profile( + RuntimeOrigin::signed(mock_pub_key(validator)), + Profile::Shared(reduced_profile.clone()) + ), + pallet_roles::Error::::RoleCannotBeRemoved + ); + } + + // ========= active validator can remove role without active job ========= + let reduced_profile = SharedRestakeProfile { + records: BoundedVec::try_from(vec![Record { + role: RoleType::Tss(ThresholdSignatureRoleType::ZengoGG20Secp256k1), + amount: None, + }]) + .unwrap(), + amount: 1000, + }; + + for validator in participants.clone() { + assert_ok!(Roles::update_profile( + RuntimeOrigin::signed(mock_pub_key(validator)), + Profile::Shared(reduced_profile.clone()) + )); + } + + // ========= active validator can add a new role with current active role ========= + let updated_profile = SharedRestakeProfile { + records: BoundedVec::try_from(vec![ + Record { + role: RoleType::Tss(ThresholdSignatureRoleType::ZengoGG20Secp256k1), + amount: None, + }, + Record { + role: RoleType::ZkSaaS(ZeroKnowledgeRoleType::ZkSaaSGroth16), + amount: None, + }, + ]) + .unwrap(), + amount: 1000, + }; + + for validator in participants.clone() { + assert_ok!(Roles::update_profile( + RuntimeOrigin::signed(mock_pub_key(validator)), + Profile::Shared(updated_profile.clone()) + )); + } + + // ========= active validator can increase stake with current active role ========= + let updated_profile = SharedRestakeProfile { + records: BoundedVec::try_from(vec![ + Record { + role: RoleType::Tss(ThresholdSignatureRoleType::ZengoGG20Secp256k1), + amount: None, + }, + Record { + role: RoleType::ZkSaaS(ZeroKnowledgeRoleType::ZkSaaSGroth16), + amount: None, + }, + ]) + .unwrap(), + amount: 1500, + }; + + for validator in participants.clone() { + assert_ok!(Roles::update_profile( + RuntimeOrigin::signed(mock_pub_key(validator)), + Profile::Shared(updated_profile.clone()) + )); + } + + // ========= active validator can reduce stake on non active role ========= + let updated_profile = IndependentRestakeProfile { + records: BoundedVec::try_from(vec![ + Record { + role: RoleType::Tss(ThresholdSignatureRoleType::ZengoGG20Secp256k1), + amount: Some(1500), + }, + Record { + role: RoleType::ZkSaaS(ZeroKnowledgeRoleType::ZkSaaSGroth16), + amount: Some(500), // reduced by 3x + }, + ]) + .unwrap(), + }; + + for validator in participants.clone() { + assert_ok!(Roles::update_profile( + RuntimeOrigin::signed(mock_pub_key(validator)), + Profile::Independent(updated_profile.clone()) + )); + } + }); +} diff --git a/pallets/roles/Cargo.toml b/pallets/roles/Cargo.toml index beeb85e06..fc1680686 100644 --- a/pallets/roles/Cargo.toml +++ b/pallets/roles/Cargo.toml @@ -37,6 +37,7 @@ serde_json = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } hex = { workspace = true } +pallet-jobs = { workspace = true } [features] default = ["std"] @@ -60,6 +61,7 @@ std = [ "tangle-primitives/std", "tangle-crypto-primitives/std", "frame-election-provider-support/std", + "pallet-jobs/std" ] try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/roles/src/lib.rs b/pallets/roles/src/lib.rs index 332a98f45..9d331469e 100644 --- a/pallets/roles/src/lib.rs +++ b/pallets/roles/src/lib.rs @@ -450,23 +450,14 @@ pub mod pallet { pallet_staking::Validators::::contains_key(&stash_account), Error::::NotValidator ); - let mut ledger = Ledger::::get(&stash_account).ok_or(Error::::NoProfileFound)?; + let 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 role_type = job.0; - // Submit request to exit from the known set. - let res = - T::JobsHandler::exit_from_known_set(stash_account.clone(), role_type, job.1); - - if res.is_err() { - pending_jobs.push((role_type, job.1)); - } else { - // Remove role from the profile. - ledger.profile.remove_role_from_profile(role_type); - } + pending_jobs.push((role_type, job.1)); } if !pending_jobs.is_empty() { @@ -481,7 +472,7 @@ pub mod pallet { // the moment. Self::deposit_event(Event::::PendingJobs { pending_jobs }); return Err(Error::::ProfileDeleteRequestFailed.into()) - }; + } Self::deposit_event(Event::::ProfileDeleted { account: stash_account.clone() }); diff --git a/pallets/roles/src/mock.rs b/pallets/roles/src/mock.rs index 49d76ec70..22900ce44 100644 --- a/pallets/roles/src/mock.rs +++ b/pallets/roles/src/mock.rs @@ -24,7 +24,9 @@ use frame_election_provider_support::{ use frame_support::{ construct_runtime, parameter_types, traits::{ConstU128, ConstU32, ConstU64, Contains, Hooks}, + PalletId, }; +use frame_system::EnsureSigned; use pallet_session::historical as pallet_session_historical; use sp_core::{ sr25519::{self, Signature}, @@ -43,7 +45,7 @@ use sp_staking::{ use tangle_crypto_primitives::crypto::AuthorityId as RoleKeyId; use tangle_primitives::{ jobs::{ - traits::{JobsHandler, MPCHandler}, + traits::{JobToFee, JobsHandler, MPCHandler}, *, }, roles::ValidatorRewardDistribution, @@ -96,26 +98,6 @@ impl pallet_balances::Config for Runtime { type MaxFreezes = (); } -pub struct MockMPCHandler; - -impl MPCHandler for MockMPCHandler { - fn verify(_data: JobWithResult) -> DispatchResult { - Ok(()) - } - - fn verify_validator_report( - _validator: AccountId, - _offence: ValidatorOffenceType, - _signatures: Vec>, - ) -> DispatchResult { - Ok(()) - } - - fn validate_authority_key(_validator: AccountId, _authority_key: Vec) -> DispatchResult { - Ok(()) - } -} - type IdentificationTuple = (AccountId, AccountId); type Offence = crate::offences::ValidatorOffence; @@ -278,6 +260,51 @@ impl JobsHandler for MockJobsHandler { } } +pub struct MockJobToFeeHandler; + +impl JobToFee for MockJobToFeeHandler { + type Balance = Balance; + + fn job_to_fee(_job: &JobSubmission) -> Balance { + Default::default() + } +} + +pub struct MockMPCHandler; + +impl MPCHandler for MockMPCHandler { + fn verify(_data: JobWithResult) -> DispatchResult { + Ok(()) + } + + fn verify_validator_report( + _validator: AccountId, + _offence: ValidatorOffenceType, + _signatures: Vec>, + ) -> DispatchResult { + Ok(()) + } + + fn validate_authority_key(_validator: AccountId, _authority_key: Vec) -> DispatchResult { + Ok(()) + } +} + +parameter_types! { + pub const JobsPalletId: PalletId = PalletId(*b"py/jobss"); +} + +impl pallet_jobs::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ForceOrigin = EnsureSigned; + type Currency = Balances; + type JobToFee = MockJobToFeeHandler; + type RolesHandler = Roles; + type MPCHandler = MockMPCHandler; + type PalletId = JobsPalletId; + type WeightInfo = (); +} + parameter_types! { 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(); @@ -285,7 +312,7 @@ parameter_types! { impl Config for Runtime { type RuntimeEvent = RuntimeEvent; - type JobsHandler = MockJobsHandler; + type JobsHandler = Jobs; type MaxRolesPerAccount = ConstU32<2>; type MPCHandler = MockMPCHandler; type InflationRewardPerSession = InflationRewardPerSession; @@ -308,6 +335,7 @@ construct_runtime!( Session: pallet_session, Staking: pallet_staking, Historical: pallet_session_historical, + Jobs: pallet_jobs } ); diff --git a/pallets/roles/src/tests.rs b/pallets/roles/src/tests.rs index 0f22069b7..ce073b679 100644 --- a/pallets/roles/src/tests.rs +++ b/pallets/roles/src/tests.rs @@ -184,7 +184,6 @@ fn test_create_profile_should_fail_if_min_required_restake_condition_is_not_met_ #[test] fn test_update_profile_from_independent_to_shared() { new_test_ext(vec![1, 2, 3, 4]).execute_with(|| { - println!("{:?}", pallet::MinRestakingBond::::get()); // Lets create independent profile. let profile = independent_profile(); assert_ok!(Roles::create_profile(RuntimeOrigin::signed(mock_pub_key(1)), profile.clone()));