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
This commit is contained in:
2026-02-13 20:13:50 +03:00
parent 7cc2f831b4
commit bd454cf395
11 changed files with 170 additions and 43 deletions
@@ -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<AccountId> {
fn on_score_component_changed(who: &AccountId);
}
/// Noop implementation for mock environments.
impl<AccountId> TrustScoreUpdater<AccountId> 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<u32>;
/// Trust score updater - notifies trust pallet when perwerde score changes
type TrustScoreUpdater: TrustScoreUpdater<Self::AccountId>;
}
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
@@ -292,7 +306,15 @@ pub mod pezpallet {
Enrollments::<T>::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(())
}
@@ -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)
@@ -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<AccountId> {
fn on_score_component_changed(who: &AccountId);
}
/// Noop implementation for mock environments and pallets that don't need trust updates.
impl<AccountId> TrustScoreUpdater<AccountId> 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<u32>;
/// Trust score updater - notifies trust pallet when referral score changes
type TrustScoreUpdater: TrustScoreUpdater<Self::AccountId>;
}
// --- 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);
}
}
}
@@ -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
@@ -173,11 +173,20 @@ pub mod pezpallet {
pub type StakingStartBlock<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, BlockNumberFor<T>, 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<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, StakingDetails<T::Balance>, OptionQuery>;
#[pezpallet::event]
#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A user started time-based scoring.
ScoreTrackingStarted { who: T::AccountId, start_block: BlockNumberFor<T> },
/// 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<T>,
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::<T>::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<Balance> {
pub staked_amount: Balance,
pub nominations_count: u32,
@@ -250,10 +286,14 @@ pub mod pezpallet {
impl<T: Config> StakingScoreProvider<T::AccountId, BlockNumberFor<T>> for Pezpallet<T> {
fn get_staking_score(who: &T::AccountId) -> (RawScore, BlockNumberFor<T>) {
// 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::<T>::get(who) {
Some(cached) => cached,
None => return (0, Zero::zero()),
},
};
// Staked miktarı ana birime (HEZ) çevir.
@@ -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<AccountId> {
fn on_score_component_changed(who: &AccountId);
}
/// Noop implementation for mock environments.
impl<AccountId> TrustScoreUpdater<AccountId> 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<Self::AccountId>;
}
#[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(())
}
@@ -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 {
@@ -129,6 +129,12 @@ pub trait TrustScoreUpdater<AccountId> {
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<AccountId> TrustScoreUpdater<AccountId> for () {
fn on_score_component_changed(_who: &AccountId) {}
}
pub trait PerwerdeScoreProvider<AccountId> {
fn get_perwerde_score(who: &AccountId) -> u32;
}
@@ -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<AccountId> 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<AccountId> for TrustScoreNotifier {
fn on_score_component_changed(who: &AccountId) {
use pezpallet_trust::TrustScoreUpdater;
<Trust as TrustScoreUpdater<AccountId>>::on_score_component_changed(who);
}
}
/// Noop implementation for OnCitizenshipRevoked hook
pub struct OnCitizenshipRevokedHook;
impl pezpallet_identity_kyc::types::OnCitizenshipRevoked<AccountId> for OnCitizenshipRevokedHook {
fn on_citizenship_revoked(_who: &AccountId) {
// Penalty logic can be added here
impl pezpallet_tiki::TrustScoreUpdater<AccountId> for TrustScoreNotifier {
fn on_score_component_changed(who: &AccountId) {
use pezpallet_trust::TrustScoreUpdater;
<Trust as TrustScoreUpdater<AccountId>>::on_score_component_changed(who);
}
}
/// Citizen NFT provider - noop implementation for now
pub struct CitizenNftProviderImpl;
impl pezpallet_identity_kyc::types::CitizenNftProvider<AccountId> 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<AccountId> for TrustScoreNotifier {
fn on_score_component_changed(who: &AccountId) {
use pezpallet_trust::TrustScoreUpdater;
<Trust as TrustScoreUpdater<AccountId>>::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<Runtime>;
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<Runtime>;
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<AccountId, BlockNumber> 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<AccountId> for ReferralScoreSource {
fn get_referral_score(who: &AccountId) -> u32 {
Referral::referral_count(who)
<Referral as pezpallet_referral::types::ReferralScoreProvider<AccountId>>::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<AccountId> 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::<Runtime>::get_perwerde_score(who)
}
}