From 1d64a1317a8bb5cf5c18bca87f89cf7d02da36df Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Sat, 7 Feb 2026 00:43:28 +0300 Subject: [PATCH] feat: add staking score pallet to relay chain and fix referral default Relay Chain: - Add pezpallet-staking-score to runtime - Implement RelayStakingInfoProvider to read from pallet_staking - StakingScore pallet index = 92 People Chain: - Add DefaultReferrer type to identity-kyc pallet Config - Change DefaultReferrer from Alice to founder address - Make referrer parameter optional in apply_for_citizenship - Fallback to DefaultReferrer when no valid referrer provided --- .../pezpallets/identity-kyc/src/lib.rs | 39 ++++++++++++------ .../people/people-pezkuwichain/src/people.rs | 11 ++++- pezkuwi/runtime/pezkuwichain/Cargo.toml | 4 ++ pezkuwi/runtime/pezkuwichain/src/lib.rs | 40 +++++++++++++++++++ 4 files changed, 79 insertions(+), 15 deletions(-) diff --git a/pezcumulus/teyrchains/pezpallets/identity-kyc/src/lib.rs b/pezcumulus/teyrchains/pezpallets/identity-kyc/src/lib.rs index 05a58fdb..4a8c84e5 100644 --- a/pezcumulus/teyrchains/pezpallets/identity-kyc/src/lib.rs +++ b/pezcumulus/teyrchains/pezpallets/identity-kyc/src/lib.rs @@ -131,6 +131,9 @@ pub mod pezpallet { type WeightInfo: WeightInfo; + /// Default referrer account (founder) - used when no valid referrer is provided + type DefaultReferrer: Get; + /// Hook called when citizenship is approved - used by referral pezpallet type OnKycApproved: crate::types::OnKycApproved; @@ -284,49 +287,59 @@ pub mod pezpallet { /// /// # Arguments /// - `identity_hash`: H256 hash of identity documents (calculated off-chain) - /// - `referrer`: Account of existing citizen who will vouch for you + /// - `referrer`: Optional account of existing citizen who will vouch for you. + /// If None or invalid, DefaultReferrer (founder) is used. /// /// # Workflow - /// 1. Applicant submits hash + referrer - /// 2. Deposit is reserved (spam prevention) - /// 3. Status becomes PendingReferral - /// 4. Referrer must call approve_referral + /// 1. Applicant submits hash + optional referrer + /// 2. If referrer is None/invalid, DefaultReferrer is used + /// 3. Deposit is reserved (spam prevention) + /// 4. Status becomes PendingReferral + /// 5. Referrer must call approve_referral #[pezpallet::call_index(0)] #[pezpallet::weight(T::WeightInfo::apply_for_citizenship())] pub fn apply_for_citizenship( origin: OriginFor, identity_hash: H256, - referrer: T::AccountId, + referrer: Option, ) -> DispatchResult { let applicant = ensure_signed(origin)?; - // Cannot refer yourself - ensure!(applicant != referrer, Error::::SelfReferral); - // Must not have existing application ensure!( KycStatuses::::get(&applicant) == KycLevel::NotStarted, Error::::ApplicationAlreadyExists ); - // Referrer must be an approved citizen + // Determine the actual referrer: + // 1. Use provided referrer if valid (approved citizen and not self) + // 2. Fall back to DefaultReferrer otherwise + let actual_referrer = referrer + .filter(|r| *r != applicant) // Not self-referral + .filter(|r| KycStatuses::::get(r) == KycLevel::Approved) // Must be citizen + .unwrap_or_else(|| T::DefaultReferrer::get()); + + // Verify the actual referrer is valid (including DefaultReferrer) ensure!( - KycStatuses::::get(&referrer) == KycLevel::Approved, + KycStatuses::::get(&actual_referrer) == KycLevel::Approved, Error::::ReferrerNotCitizen ); + // Cannot refer yourself (even with DefaultReferrer) + ensure!(applicant != actual_referrer, Error::::SelfReferral); + // Reserve deposit (spam prevention, returned on approval) let deposit = T::KycApplicationDeposit::get(); T::Currency::reserve(&applicant, deposit)?; // Store application (only hash, no personal data) - let application = CitizenshipApplication { identity_hash, referrer: referrer.clone() }; + let application = CitizenshipApplication { identity_hash, referrer: actual_referrer.clone() }; Applications::::insert(&applicant, application); // Update status KycStatuses::::insert(&applicant, KycLevel::PendingReferral); - Self::deposit_event(Event::CitizenshipApplied { applicant, referrer, identity_hash }); + Self::deposit_event(Event::CitizenshipApplied { applicant, referrer: actual_referrer, identity_hash }); Ok(()) } diff --git a/pezcumulus/teyrchains/runtimes/people/people-pezkuwichain/src/people.rs b/pezcumulus/teyrchains/runtimes/people/people-pezkuwichain/src/people.rs index 7036b2f7..969b8fee 100644 --- a/pezcumulus/teyrchains/runtimes/people/people-pezkuwichain/src/people.rs +++ b/pezcumulus/teyrchains/runtimes/people/people-pezkuwichain/src/people.rs @@ -298,6 +298,7 @@ impl pezpallet_identity_kyc::Config for Runtime { type KycApplicationDeposit = KycApplicationDeposit; type MaxStringLength = MaxStringLength; type MaxCidLength = MaxCidLength; + type DefaultReferrer = DefaultReferrer; } // ============================================================================= @@ -371,8 +372,14 @@ impl pezpallet_perwerde::Config for Runtime { // ============================================================================= parameter_types! { - /// Default referrer account (genesis/system account) - pub DefaultReferrer: AccountId = pezsp_keyring::Sr25519Keyring::Alice.to_account_id(); + /// Default referrer account - Founder address + /// SS58: 5CyuFfbF95rzBxru7c9yEsX4XmQXUxpLUcbj9RLg9K1cGiiF + pub DefaultReferrer: AccountId = AccountId::from([ + 0x28, 0x92, 0x5e, 0xd8, 0xb4, 0xc0, 0xc9, 0x54, + 0x02, 0xb3, 0x15, 0x63, 0x25, 0x1f, 0xd3, 0x18, + 0x41, 0x43, 0x51, 0x11, 0x4b, 0x1c, 0x77, 0x97, + 0xee, 0x78, 0x86, 0x66, 0xd2, 0x7d, 0x63, 0x05, + ]); /// Penalty per revocation (trust score reduction) pub const PenaltyPerRevocation: u32 = 10; } diff --git a/pezkuwi/runtime/pezkuwichain/Cargo.toml b/pezkuwi/runtime/pezkuwichain/Cargo.toml index 98987bba..48bcf029 100644 --- a/pezkuwi/runtime/pezkuwichain/Cargo.toml +++ b/pezkuwi/runtime/pezkuwichain/Cargo.toml @@ -112,6 +112,7 @@ pezkuwi-teyrchain-primitives = { workspace = true } # Custom Pezkuwi Pallets pezpallet-validator-pool = { workspace = true } +pezpallet-staking-score = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } @@ -192,6 +193,7 @@ std = [ "pezpallet-treasury/std", "pezpallet-utility/std", "pezpallet-validator-pool/std", + "pezpallet-staking-score/std", "pezpallet-vesting/std", "pezpallet-whitelist/std", "pezpallet-xcm-benchmarks?/std", @@ -284,6 +286,7 @@ runtime-benchmarks = [ "pezpallet-treasury/runtime-benchmarks", "pezpallet-utility/runtime-benchmarks", "pezpallet-validator-pool/runtime-benchmarks", + "pezpallet-staking-score/runtime-benchmarks", "pezpallet-vesting/runtime-benchmarks", "pezpallet-whitelist/runtime-benchmarks", "pezpallet-xcm-benchmarks/runtime-benchmarks", @@ -366,6 +369,7 @@ try-runtime = [ "pezpallet-treasury/try-runtime", "pezpallet-utility/try-runtime", "pezpallet-validator-pool/try-runtime", + "pezpallet-staking-score/try-runtime", "pezpallet-vesting/try-runtime", "pezpallet-whitelist/try-runtime", "pezpallet-xcm-benchmarks?/try-runtime", diff --git a/pezkuwi/runtime/pezkuwichain/src/lib.rs b/pezkuwi/runtime/pezkuwichain/src/lib.rs index 52436681..9015a738 100644 --- a/pezkuwi/runtime/pezkuwichain/src/lib.rs +++ b/pezkuwi/runtime/pezkuwichain/src/lib.rs @@ -565,6 +565,42 @@ impl pezpallet_staking::Config for Runtime { type BenchmarkingConfig = PezkuwiStakingBenchmarkingConfig; } +// ===================================================== +// STAKING SCORE CONFIGURATION +// ===================================================== + +/// Relay Chain StakingInfoProvider - reads directly from pezpallet_staking +/// This is the REAL implementation that accesses actual staking data +pub struct RelayStakingInfoProvider; + +impl pezpallet_staking_score::StakingInfoProvider + for RelayStakingInfoProvider +{ + fn get_staking_details( + who: &AccountId, + ) -> Option> { + // Get staking ledger from pezpallet_staking + let ledger = pezpallet_staking::Ledger::::get(who)?; + + // Get nominations if any + let nominations_count = pezpallet_staking::Nominators::::get(who) + .map(|n| n.targets.len() as u32) + .unwrap_or(0); + + Some(pezpallet_staking_score::StakingDetails { + staked_amount: ledger.active, + nominations_count, + unlocking_chunks_count: ledger.unlocking.len() as u32, + }) + } +} + +impl pezpallet_staking_score::Config for Runtime { + type Balance = Balance; + type StakingInfo = RelayStakingInfoProvider; + type WeightInfo = pezpallet_staking_score::weights::BizinikiwiWeight; +} + // ===================================================== // FAST UNSTAKE CONFIGURATION // ===================================================== @@ -1570,6 +1606,9 @@ construct_runtime! { // TNPoS Validator Pool - Shadow Mode (runs parallel to NPoS) ValidatorPool: pezpallet_validator_pool = 91, + // Staking Score - Time-weighted staking reputation score + StakingScore: pezpallet_staking_score = 92, + // Root testing pezpallet. RootTesting: pezpallet_root_testing = 249, @@ -1821,6 +1860,7 @@ mod benches { [pezpallet_whitelist, Whitelist] // Pezkuwichain Custom Pallets [pezpallet_validator_pool, ValidatorPool] + [pezpallet_staking_score, StakingScore] // XCM [pezpallet_xcm, PalletXcmExtrinsicsBenchmark::] [pezpallet_xcm_benchmarks::fungible, pezpallet_xcm_benchmarks::fungible::Pezpallet::]