From bd454cf395249fea6c03bc18c314cf39ca5e8cc0 Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Fri, 13 Feb 2026 20:13:50 +0300 Subject: [PATCH] feat: wire trust score system with cross-chain staking data and component triggers - Add CachedStakingDetails storage and receive_staking_details extrinsic to staking-score pallet for Asset Hub XCM data reception - Add TrustScoreUpdater triggers to referral, tiki, and perwerde pallets so component score changes propagate to trust pallet - Wire runtime hooks (OnKycApproved, OnCitizenshipRevoked) to Referral and CitizenNftProvider to Tiki in people.rs - Fix PerwerdeScoreSource and ReferralScoreSource to read actual pallet data - Fix EnsureOrigin trait feature unification issue by removing cfg gate from try_successful_origin and adding default Err(()) implementation - Fix workspace Cargo.toml default-features for pezkuwi-subxt dependencies --- Cargo.toml | 8 +-- .../pezframe/support/src/traits/dispatch.rs | 14 +++-- .../teyrchains/pezpallets/perwerde/src/lib.rs | 24 ++++++- .../pezpallets/perwerde/src/mock.rs | 1 + .../teyrchains/pezpallets/referral/src/lib.rs | 27 +++++++- .../pezpallets/referral/src/mock.rs | 1 + .../pezpallets/staking-score/src/lib.rs | 46 +++++++++++++- .../teyrchains/pezpallets/tiki/src/lib.rs | 22 +++++++ .../teyrchains/pezpallets/tiki/src/mock.rs | 1 + .../teyrchains/pezpallets/trust/src/lib.rs | 6 ++ .../people/people-pezkuwichain/src/people.rs | 63 ++++++++++--------- 11 files changed, 170 insertions(+), 43 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8d839b27..4f52aff7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1516,12 +1516,12 @@ static_assertions = { version = "1.1.0", default-features = false } static_init = { version = "1.0.3" } strum = { version = "0.26.3", default-features = false } # Pezkuwi-subxt (vendored from subxt with pezsp_runtime support) -pezkuwi-subxt = { path = "vendor/pezkuwi-subxt/subxt", version = "0.44.0" } +pezkuwi-subxt = { path = "vendor/pezkuwi-subxt/subxt", version = "0.44.0", default-features = false } pezkuwi-subxt-codegen = { path = "vendor/pezkuwi-subxt/codegen", version = "0.44.0" } -pezkuwi-subxt-core = { path = "vendor/pezkuwi-subxt/core", version = "0.44.0" } -pezkuwi-subxt-lightclient = { path = "vendor/pezkuwi-subxt/lightclient", version = "0.44.0" } +pezkuwi-subxt-core = { path = "vendor/pezkuwi-subxt/core", version = "0.44.0", default-features = false } +pezkuwi-subxt-lightclient = { path = "vendor/pezkuwi-subxt/lightclient", version = "0.44.0", default-features = false } pezkuwi-subxt-macro = { path = "vendor/pezkuwi-subxt/macro", version = "0.44.0" } -pezkuwi-subxt-metadata = { path = "vendor/pezkuwi-subxt/metadata", version = "0.44.0" } +pezkuwi-subxt-metadata = { path = "vendor/pezkuwi-subxt/metadata", version = "0.44.0", default-features = false } pezkuwi-subxt-rpcs = { path = "vendor/pezkuwi-subxt/rpcs", version = "0.44.0" } pezkuwi-subxt-signer = { path = "vendor/pezkuwi-subxt/signer", version = "0.44.0" } pezkuwi-subxt-utils-fetchmetadata = { path = "vendor/pezkuwi-subxt/utils/fetch-metadata", version = "0.44.0" } diff --git a/bizinikiwi/pezframe/support/src/traits/dispatch.rs b/bizinikiwi/pezframe/support/src/traits/dispatch.rs index 70e2c7df..4d109780 100644 --- a/bizinikiwi/pezframe/support/src/traits/dispatch.rs +++ b/bizinikiwi/pezframe/support/src/traits/dispatch.rs @@ -72,8 +72,14 @@ pub trait EnsureOrigin { /// is impossible. /// /// ** Should be used for benchmarking only!!! ** - #[cfg(feature = "runtime-benchmarks")] - fn try_successful_origin() -> Result; + /// + /// Default implementation returns `Err(())` to handle feature unification issues where + /// pezframe-support/runtime-benchmarks is enabled but the implementing crate's + /// runtime-benchmarks feature is not. Implementations should override this when + /// runtime-benchmarks feature is enabled. + fn try_successful_origin() -> Result { + Err(()) + } } /// [`EnsureOrigin`] implementation that checks that an origin has equal or higher privilege @@ -180,8 +186,8 @@ pub trait EnsureOriginWithArg { /// /// Default implementation returns `Err(())` to handle feature unification issues where /// pezframe-support/runtime-benchmarks is enabled but the implementing crate's - /// runtime-benchmarks feature is not. Implementations should override this. - #[cfg(feature = "runtime-benchmarks")] + /// runtime-benchmarks feature is not. Implementations should override this when + /// runtime-benchmarks feature is enabled. fn try_successful_origin(_a: &Argument) -> Result { Err(()) } diff --git a/pezcumulus/teyrchains/pezpallets/perwerde/src/lib.rs b/pezcumulus/teyrchains/pezpallets/perwerde/src/lib.rs index c7115e72..3b9ebda2 100644 --- a/pezcumulus/teyrchains/pezpallets/perwerde/src/lib.rs +++ b/pezcumulus/teyrchains/pezpallets/perwerde/src/lib.rs @@ -86,6 +86,17 @@ pub use pezpallet::*; +/// Trait for notifying trust score system when perwerde score changes. +/// Defined locally to avoid cyclic dependency with pezpallet-trust. +pub trait TrustScoreUpdater { + fn on_score_component_changed(who: &AccountId); +} + +/// Noop implementation for mock environments. +impl TrustScoreUpdater for () { + fn on_score_component_changed(_who: &AccountId) {} +} + #[cfg(feature = "runtime-benchmarks")] mod benchmarking; pub mod weights; @@ -130,6 +141,9 @@ pub mod pezpallet { /// Used for StudentCourses storage bound #[pezpallet::constant] type MaxCoursesPerStudent: Get; + + /// Trust score updater - notifies trust pallet when perwerde score changes + type TrustScoreUpdater: TrustScoreUpdater; } #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] @@ -292,7 +306,15 @@ pub mod pezpallet { Enrollments::::insert((&student, course_id), enrollment); - Self::deposit_event(Event::CourseCompleted { student, course_id, points }); + Self::deposit_event(Event::CourseCompleted { + student: student.clone(), + course_id, + points, + }); + + // Notify trust pallet that student's perwerde score component changed + T::TrustScoreUpdater::on_score_component_changed(&student); + Ok(()) } diff --git a/pezcumulus/teyrchains/pezpallets/perwerde/src/mock.rs b/pezcumulus/teyrchains/pezpallets/perwerde/src/mock.rs index 1bd8a0b4..309ef16f 100644 --- a/pezcumulus/teyrchains/pezpallets/perwerde/src/mock.rs +++ b/pezcumulus/teyrchains/pezpallets/perwerde/src/mock.rs @@ -111,6 +111,7 @@ impl pezpallet_perwerde::Config for Test { type MaxCourseLinkLength = MaxCourseLinkLength; type MaxStudentsPerCourse = MaxStudentsPerCourse; type MaxCoursesPerStudent = MaxCoursesPerStudent; + type TrustScoreUpdater = (); } // Council Paletinin Mock Kurulumu (construct_runtime'da gerekli olduğu için kalıyor) diff --git a/pezcumulus/teyrchains/pezpallets/referral/src/lib.rs b/pezcumulus/teyrchains/pezpallets/referral/src/lib.rs index 702b61b7..9574373a 100644 --- a/pezcumulus/teyrchains/pezpallets/referral/src/lib.rs +++ b/pezcumulus/teyrchains/pezpallets/referral/src/lib.rs @@ -108,6 +108,17 @@ mod benchmarking; extern crate alloc; use crate::weights::WeightInfo; +/// Trait for notifying trust score system when referral score changes. +/// Defined locally to avoid cyclic dependency with pezpallet-trust. +pub trait TrustScoreUpdater { + fn on_score_component_changed(who: &AccountId); +} + +/// Noop implementation for mock environments and pallets that don't need trust updates. +impl TrustScoreUpdater for () { + fn on_score_component_changed(_who: &AccountId) {} +} + #[pezframe_support::pezpallet] pub mod pezpallet { use super::*; @@ -136,6 +147,9 @@ pub mod pezpallet { /// Default: 3 (each bad referral costs 3x a good referral) #[pezpallet::constant] type PenaltyPerRevocation: Get; + + /// Trust score updater - notifies trust pallet when referral score changes + type TrustScoreUpdater: TrustScoreUpdater; } // --- Storage Items --- @@ -256,11 +270,14 @@ pub mod pezpallet { // Emit event Self::deposit_event(Event::ReferralConfirmed { - referrer, + referrer: referrer.clone(), referred, new_referrer_count: new_count, }); + // Notify trust pallet that referrer's score component changed + T::TrustScoreUpdater::on_score_component_changed(&referrer); + Ok(()) } } @@ -308,6 +325,9 @@ pub mod pezpallet { referred: who.clone(), new_referrer_count: new_count, }); + + // Notify trust pallet that referrer's score component changed + T::TrustScoreUpdater::on_score_component_changed(referrer); } } } @@ -333,11 +353,14 @@ pub mod pezpallet { // Emit penalty event Self::deposit_event(Event::ReferralPenalized { - referrer, + referrer: referrer.clone(), revoked_citizen: who.clone(), new_penalty_score: updated_stats.penalty_score, total_revoked: updated_stats.revoked_referrals, }); + + // Notify trust pallet that referrer's score component changed + T::TrustScoreUpdater::on_score_component_changed(&referrer); } } } diff --git a/pezcumulus/teyrchains/pezpallets/referral/src/mock.rs b/pezcumulus/teyrchains/pezpallets/referral/src/mock.rs index 6f5303f3..1e39e5f7 100644 --- a/pezcumulus/teyrchains/pezpallets/referral/src/mock.rs +++ b/pezcumulus/teyrchains/pezpallets/referral/src/mock.rs @@ -87,6 +87,7 @@ impl pezpallet_referral::Config for Test { type WeightInfo = (); type DefaultReferrer = DefaultReferrerAccount; type PenaltyPerRevocation = PenaltyPerRevocationAmount; + type TrustScoreUpdater = (); } /// Build test externalities with founding citizens diff --git a/pezcumulus/teyrchains/pezpallets/staking-score/src/lib.rs b/pezcumulus/teyrchains/pezpallets/staking-score/src/lib.rs index 96240fb5..edec8006 100644 --- a/pezcumulus/teyrchains/pezpallets/staking-score/src/lib.rs +++ b/pezcumulus/teyrchains/pezpallets/staking-score/src/lib.rs @@ -173,11 +173,20 @@ pub mod pezpallet { pub type StakingStartBlock = StorageMap<_, Blake2_128Concat, T::AccountId, BlockNumberFor, OptionQuery>; + /// Cached staking details received from Asset Hub via XCM. + /// This allows People Chain to access staking data without direct access to staking pallet. + #[pezpallet::storage] + #[pezpallet::getter(fn cached_staking_details)] + pub type CachedStakingDetails = + StorageMap<_, Blake2_128Concat, T::AccountId, StakingDetails, OptionQuery>; + #[pezpallet::event] #[pezpallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// A user started time-based scoring. ScoreTrackingStarted { who: T::AccountId, start_block: BlockNumberFor }, + /// Staking details received from Asset Hub via XCM. + StakingDetailsReceived { who: T::AccountId, staked_amount: T::Balance }, } #[pezpallet::error] @@ -186,6 +195,8 @@ pub mod pezpallet { NoStakeFound, /// Puan takibi zaten daha önce başlatılmış. TrackingAlreadyStarted, + /// Origin is not authorized to send staking details (must be Asset Hub via XCM). + UnauthorizedOrigin, } #[pezpallet::call] @@ -217,6 +228,31 @@ pub mod pezpallet { Self::deposit_event(Event::ScoreTrackingStarted { who, start_block: current_block }); Ok(()) } + + /// Receive staking details from Asset Hub via XCM Transact. + /// This extrinsic is called by Asset Hub's staking pallet to push staking data + /// to the People Chain so trust scores can be calculated. + /// + /// Only root origin is accepted (XCM Transact from sibling chain arrives as root). + #[pezpallet::call_index(1)] + #[pezpallet::weight(T::WeightInfo::start_score_tracking())] + pub fn receive_staking_details( + origin: OriginFor, + who: T::AccountId, + staked_amount: T::Balance, + nominations_count: u32, + unlocking_chunks_count: u32, + ) -> DispatchResult { + ensure_root(origin)?; + + let details = + StakingDetails { staked_amount, nominations_count, unlocking_chunks_count }; + + CachedStakingDetails::::insert(&who, details); + + Self::deposit_event(Event::StakingDetailsReceived { who, staked_amount }); + Ok(()) + } } // --- Arayüz (Trait) ve Tip Tanımları --- @@ -226,7 +262,7 @@ pub mod pezpallet { /// Staking ile ilgili detayları bir arada tutan ve dışarıdan alınacak veri yapısı. /// `Default` ekledik çünkü testlerde ve mock'larda işimizi kolaylaştıracak. - #[derive(Default, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, Debug)] + #[derive(Default, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, Debug, MaxEncodedLen)] pub struct StakingDetails { pub staked_amount: Balance, pub nominations_count: u32, @@ -250,10 +286,14 @@ pub mod pezpallet { impl StakingScoreProvider> for Pezpallet { fn get_staking_score(who: &T::AccountId) -> (RawScore, BlockNumberFor) { - // 1. Staking detaylarını al. Eğer stake yoksa (None) 0 puan döndür. + // 1. Staking detaylarını al. Önce StakingInfo provider'ı dene, + // bulunamazsa CachedStakingDetails'e (XCM ile gelen veri) bak. let staking_details = match T::StakingInfo::get_staking_details(who) { Some(details) => details, - None => return (0, Zero::zero()), + None => match CachedStakingDetails::::get(who) { + Some(cached) => cached, + None => return (0, Zero::zero()), + }, }; // Staked miktarı ana birime (HEZ) çevir. diff --git a/pezcumulus/teyrchains/pezpallets/tiki/src/lib.rs b/pezcumulus/teyrchains/pezpallets/tiki/src/lib.rs index d710906d..74ba503b 100644 --- a/pezcumulus/teyrchains/pezpallets/tiki/src/lib.rs +++ b/pezcumulus/teyrchains/pezpallets/tiki/src/lib.rs @@ -106,6 +106,17 @@ pub use pezpallet::*; use alloc::{format, vec::Vec}; use pezframe_support::pezpallet_prelude::{MaybeSerializeDeserialize, Parameter, RuntimeDebug}; use pezsp_runtime::DispatchError; + +/// Trait for notifying trust score system when tiki score changes. +/// Defined locally to avoid cyclic dependency with pezpallet-trust. +pub trait TrustScoreUpdater { + fn on_score_component_changed(who: &AccountId); +} + +/// Noop implementation for mock environments. +impl TrustScoreUpdater for () { + fn on_score_component_changed(_who: &AccountId) {} +} use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; @@ -157,6 +168,9 @@ pub mod pezpallet { + Copy + MaybeSerializeDeserialize + 'static; + + /// Trust score updater - notifies trust pallet when tiki score changes + type TrustScoreUpdater: TrustScoreUpdater; } #[derive( @@ -527,6 +541,10 @@ pub mod pezpallet { Self::update_nft_metadata(dest_account)?; Self::deposit_event(Event::TikiGranted { who: dest_account.clone(), tiki }); + + // Notify trust pallet that user's tiki score component changed + T::TrustScoreUpdater::on_score_component_changed(dest_account); + Ok(()) } @@ -556,6 +574,10 @@ pub mod pezpallet { Self::update_nft_metadata(target_account)?; Self::deposit_event(Event::TikiRevoked { who: target_account.clone(), tiki }); + + // Notify trust pallet that user's tiki score component changed + T::TrustScoreUpdater::on_score_component_changed(target_account); + Ok(()) } diff --git a/pezcumulus/teyrchains/pezpallets/tiki/src/mock.rs b/pezcumulus/teyrchains/pezpallets/tiki/src/mock.rs index a5b450da..21e9a799 100644 --- a/pezcumulus/teyrchains/pezpallets/tiki/src/mock.rs +++ b/pezcumulus/teyrchains/pezpallets/tiki/src/mock.rs @@ -269,6 +269,7 @@ impl crate::Config for Test { type TikiCollectionId = TikiCollectionId; type MaxTikisPerUser = MaxTikisPerUser; type Tiki = TikiEnum; + type TrustScoreUpdater = (); } pub fn new_test_ext() -> pezsp_io::TestExternalities { diff --git a/pezcumulus/teyrchains/pezpallets/trust/src/lib.rs b/pezcumulus/teyrchains/pezpallets/trust/src/lib.rs index aa1d0e95..96158c89 100644 --- a/pezcumulus/teyrchains/pezpallets/trust/src/lib.rs +++ b/pezcumulus/teyrchains/pezpallets/trust/src/lib.rs @@ -129,6 +129,12 @@ pub trait TrustScoreUpdater { fn on_score_component_changed(who: &AccountId); } +/// Noop implementation of TrustScoreUpdater for use in mock environments +/// and pallets that don't need to trigger trust score updates. +impl TrustScoreUpdater for () { + fn on_score_component_changed(_who: &AccountId) {} +} + pub trait PerwerdeScoreProvider { fn get_perwerde_score(who: &AccountId) -> u32; } diff --git a/pezcumulus/teyrchains/runtimes/people/people-pezkuwichain/src/people.rs b/pezcumulus/teyrchains/runtimes/people/people-pezkuwichain/src/people.rs index 7b547c84..1b54da36 100644 --- a/pezcumulus/teyrchains/runtimes/people/people-pezkuwichain/src/people.rs +++ b/pezcumulus/teyrchains/runtimes/people/people-pezkuwichain/src/people.rs @@ -254,35 +254,35 @@ parameter_types! { pub const MaxCidLength: u32 = 64; } -/// Noop implementation for OnKycApproved hook -pub struct OnKycApprovedHook; -impl pezpallet_identity_kyc::types::OnKycApproved for OnKycApprovedHook { - fn on_kyc_approved(_who: &AccountId, _referrer: &AccountId) { - // Hook implementation - referral pezpallet integration +// OnKycApproved hook → Delegates to Referral pallet for referral confirmation +// Referral pallet implements OnKycApproved trait directly and also triggers TrustScoreUpdater +// OnCitizenshipRevoked hook → Delegates to Referral pallet for penalty tracking +// Referral pallet implements OnCitizenshipRevoked trait directly and also triggers TrustScoreUpdater +// CitizenNftProvider → Delegates to Tiki pallet for citizenship NFT minting/burning + +/// Adapter struct that bridges each pallet's local TrustScoreUpdater trait +/// to the Trust pallet's on_score_component_changed implementation. +/// This avoids cyclic dependencies between component pallets and pezpallet-trust. +pub struct TrustScoreNotifier; + +impl pezpallet_referral::TrustScoreUpdater for TrustScoreNotifier { + fn on_score_component_changed(who: &AccountId) { + use pezpallet_trust::TrustScoreUpdater; + >::on_score_component_changed(who); } } -/// Noop implementation for OnCitizenshipRevoked hook -pub struct OnCitizenshipRevokedHook; -impl pezpallet_identity_kyc::types::OnCitizenshipRevoked for OnCitizenshipRevokedHook { - fn on_citizenship_revoked(_who: &AccountId) { - // Penalty logic can be added here +impl pezpallet_tiki::TrustScoreUpdater for TrustScoreNotifier { + fn on_score_component_changed(who: &AccountId) { + use pezpallet_trust::TrustScoreUpdater; + >::on_score_component_changed(who); } } -/// Citizen NFT provider - noop implementation for now -pub struct CitizenNftProviderImpl; -impl pezpallet_identity_kyc::types::CitizenNftProvider for CitizenNftProviderImpl { - fn mint_citizen_nft(_who: &AccountId) -> Result<(), pezsp_runtime::DispatchError> { - Ok(()) - } - - fn mint_citizen_nft_confirmed(_who: &AccountId) -> Result<(), pezsp_runtime::DispatchError> { - Ok(()) - } - - fn burn_citizen_nft(_who: &AccountId) -> Result<(), pezsp_runtime::DispatchError> { - Ok(()) +impl pezpallet_perwerde::TrustScoreUpdater for TrustScoreNotifier { + fn on_score_component_changed(who: &AccountId) { + use pezpallet_trust::TrustScoreUpdater; + >::on_score_component_changed(who); } } @@ -292,9 +292,9 @@ impl pezpallet_identity_kyc::Config for Runtime { // Vatandaşlık kararları için Divan (Anayasa Mahkemesi) yetkili type GovernanceOrigin = crate::RootOrDiwanOrTechnical; type WeightInfo = pezpallet_identity_kyc::weights::BizinikiwiWeight; - type OnKycApproved = OnKycApprovedHook; - type OnCitizenshipRevoked = OnCitizenshipRevokedHook; - type CitizenNftProvider = CitizenNftProviderImpl; + type OnKycApproved = Referral; + type OnCitizenshipRevoked = Referral; + type CitizenNftProvider = Tiki; type KycApplicationDeposit = KycApplicationDeposit; type MaxStringLength = MaxStringLength; type MaxCidLength = MaxCidLength; @@ -365,6 +365,7 @@ impl pezpallet_perwerde::Config for Runtime { type MaxCourseLinkLength = MaxCourseLinkLength; type MaxStudentsPerCourse = MaxStudentsPerCourse; type MaxCoursesPerStudent = MaxCoursesPerStudent; + type TrustScoreUpdater = TrustScoreNotifier; } // ============================================================================= @@ -388,6 +389,7 @@ impl pezpallet_referral::Config for Runtime { type WeightInfo = pezpallet_referral::weights::BizinikiwiWeight; type DefaultReferrer = DefaultReferrer; type PenaltyPerRevocation = PenaltyPerRevocation; + type TrustScoreUpdater = TrustScoreNotifier; } // ============================================================================= @@ -455,6 +457,7 @@ impl pezpallet_tiki::Config for Runtime { type TikiCollectionId = TikiCollectionId; type MaxTikisPerUser = MaxTikisPerUser; type Tiki = pezpallet_tiki::Tiki; + type TrustScoreUpdater = TrustScoreNotifier; } // ============================================================================= @@ -570,18 +573,20 @@ impl pezpallet_trust::StakingScoreProvider for StakingSc } /// Referral score source for Trust pezpallet +/// Uses the referral pallet's tiered scoring with penalty system pub struct ReferralScoreSource; impl pezpallet_trust::ReferralScoreProvider for ReferralScoreSource { fn get_referral_score(who: &AccountId) -> u32 { - Referral::referral_count(who) + >::get_referral_score(who) } } /// Perwerde (education) score source for Trust pezpallet +/// Sums completed course points from the Perwerde pallet pub struct PerwerdeScoreSource; impl pezpallet_trust::PerwerdeScoreProvider for PerwerdeScoreSource { - fn get_perwerde_score(_who: &AccountId) -> u32 { - 0 // Placeholder - Perwerde pezpallet integration needed + fn get_perwerde_score(who: &AccountId) -> u32 { + pezpallet_perwerde::Pezpallet::::get_perwerde_score(who) } }