feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -0,0 +1,74 @@
[package]
name = "pezpallet-referral"
version = "1.0.0"
description = "PezkuwiChain Referral Management Pallet"
authors.workspace = true
homepage.workspace = true
edition.workspace = true
license.workspace = true
publish = false
repository.workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { default-features = false, features = ["derive"], workspace = true }
pezframe-benchmarking = { optional = true, workspace = true }
pezframe-support = { default-features = false, workspace = true }
pezframe-system = { default-features = false, workspace = true }
log = { default-features = false, workspace = true }
pezpallet-identity-kyc = { workspace = true, default-features = false }
scale-info = { default-features = false, features = [
"derive",
], workspace = true }
pezsp-runtime = { default-features = false, workspace = true }
pezsp-std = { default-features = false, workspace = true }
# Projemizin özel tiplerini ve trait'lerini içeren kütüphane
pezkuwi-primitives = { workspace = true, default-features = false }
[dev-dependencies]
pezpallet-balances = { workspace = true }
pezpallet-nfts = { workspace = true }
pezsp-core = { workspace = true }
pezsp-io = { workspace = true }
[features]
default = ["std"]
std = [
"codec/std",
"pezframe-benchmarking?/std",
"pezframe-support/std",
"pezframe-system/std",
"log/std",
"pezpallet-balances/std",
"pezpallet-identity-kyc/std",
"pezpallet-nfts/std",
"pezkuwi-primitives/std",
"scale-info/std",
"pezsp-core/std",
"pezsp-io/std",
"pezsp-runtime/std",
"pezsp-std/std",
]
runtime-benchmarks = [
"pezframe-benchmarking",
"pezframe-benchmarking/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezframe-system/runtime-benchmarks",
"pezpallet-balances/runtime-benchmarks",
"pezpallet-identity-kyc/runtime-benchmarks",
"pezpallet-nfts/runtime-benchmarks",
"pezkuwi-primitives/runtime-benchmarks",
"pezsp-io/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
]
try-runtime = [
"pezframe-support/try-runtime",
"pezframe-system/try-runtime",
"pezpallet-balances/try-runtime",
"pezpallet-identity-kyc/try-runtime",
"pezpallet-nfts/try-runtime",
"pezsp-runtime/try-runtime",
]
@@ -0,0 +1,47 @@
//! Benchmarking setup for pezpallet-referral
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use crate::Pallet as Referral;
use pezframe_benchmarking::v2::*;
use pezframe_system::RawOrigin;
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn initiate_referral() {
let referrer: T::AccountId = account("referrer", 0, 0);
let referred: T::AccountId = account("referred", 0, 1);
// Ensure the `referred` account has not been referred before
PendingReferrals::<T>::remove(&referred);
Referrals::<T>::remove(&referred);
#[extrinsic_call]
initiate_referral(RawOrigin::Signed(referrer.clone()), referred.clone());
assert_eq!(PendingReferrals::<T>::get(&referred), Some(referrer));
}
#[benchmark]
fn force_confirm_referral() {
let referrer: T::AccountId = account("referrer", 0, 0);
let referred: T::AccountId = account("referred", 0, 1);
// Ensure clean state
PendingReferrals::<T>::remove(&referred);
Referrals::<T>::remove(&referred);
ReferralCount::<T>::remove(&referrer);
#[extrinsic_call]
force_confirm_referral(RawOrigin::Root, referrer.clone(), referred.clone());
assert!(Referrals::<T>::contains_key(&referred));
assert_eq!(ReferralCount::<T>::get(&referrer), 1);
}
impl_benchmark_test_suite!(Referral, crate::mock::new_test_ext(), crate::mock::Test);
}
@@ -0,0 +1,380 @@
#![cfg_attr(not(feature = "std"), no_std)]
//! # Referral Pallet
//!
//! A pallet for managing user referrals and tracking network growth through invitation mechanics.
//!
//! ## Overview
//!
//! The Referral pallet implements a referral system that incentivizes user growth by tracking
//! and rewarding users who successfully invite others to complete KYC verification. Referral
//! counts contribute to trust scores and validator eligibility.
//!
//! ## Referral Workflow
//!
//! ### Initiation Phase
//!
//! 1. User A calls `initiate_referral(user_b_account)` to invite User B
//! 2. System creates a pending referral record linking B to A
//! 3. User B must not have been referred by anyone else
//! 4. Self-referral is prevented
//!
//! ### Confirmation Phase
//!
//! 1. User B completes identity registration and KYC application
//! 2. KYC authority approves User B's application
//! 3. `OnKycApproved` hook automatically fires
//! 4. System:
//! - Converts pending referral to confirmed referral
//! - Increments User A's referral count
//! - Records block number of confirmation
//! - Emits `ReferralConfirmed` event
//!
//! ## Referral Score System
//!
//! The referral count contributes to the trust score calculation in `pezpallet-trust`:
//! - Each successful referral increases the referrer's reputation
//! - Referral count is used by `ReferralScoreProvider` trait
//! - Higher referral counts improve validator pool eligibility
//! - Community validators require active referral participation
//!
//! ## Security Features
//!
//! - **One Referrer Per User**: Each user can only be referred once
//! - **No Self-Referral**: Users cannot refer themselves
//! - **KYC Verification Required**: Referrals only count after KYC approval
//! - **Immutable History**: Confirmed referrals cannot be changed
//! - **Block Number Recording**: Transparent audit trail
//!
//! ## Interface
//!
//! ### User Extrinsics
//!
//! - `initiate_referral(referred)` - Invite a new user to the ecosystem
//!
//! ### Storage
//!
//! - `PendingReferrals` - Invited users awaiting KYC approval (referred → referrer)
//! - `ReferralCount` - Number of successful referrals per user (referrer → count)
//! - `Referrals` - Confirmed referral records with metadata (referred → ReferralInfo)
//!
//! ### Trait Implementations
//!
//! - `OnKycApproved` - Hook called by `pezpallet-identity-kyc` upon KYC approval
//! - `ReferralScoreProvider` - Query interface for trust score calculation
//! - `InviterProvider` - Query who referred a specific user
//!
//! ## Integration Points
//!
//! ### With pezpallet-identity-kyc
//! - Listens for KYC approval events via `OnKycApproved` hook
//! - Automatically confirms pending referrals upon approval
//!
//! ### With pezpallet-trust
//! - Provides referral scores for composite trust calculation
//! - Contributes to overall reputation metrics
//!
//! ### With pezpallet-validator-pool
//! - Community validator category requires referral participation
//! - Referral count affects pool eligibility
//!
//! ## Runtime Integration Example
//!
//! ```ignore
//! impl pezpallet_referral::Config for Runtime {
//! type RuntimeEvent = RuntimeEvent;
//! type WeightInfo = pezpallet_referral::weights::BizinikiwiWeight<Runtime>;
//! }
//!
//! // Configure pezpallet-identity-kyc to notify referral pallet
//! impl pezpallet_identity_kyc::Config for Runtime {
//! // ...
//! type OnKycApproved = Referral; // Hook referral confirmation
//! }
//! ```
pub use pallet::*;
#[cfg(test)]
mod mock;
pub mod types; // Adding our new types module
pub mod weights;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
extern crate alloc;
use crate::weights::WeightInfo;
#[pezframe_support::pallet]
pub mod pallet {
use super::*;
use crate::types::{InviterProvider, RawScore, ReferralScoreProvider, ReferrerStats};
use pezframe_support::pezpallet_prelude::*;
use pezframe_system::pezpallet_prelude::*;
use pezpallet_identity_kyc::types::{KycStatus, OnCitizenshipRevoked, OnKycApproved};
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: pezframe_system::Config + pezpallet_identity_kyc::Config + TypeInfo {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
type WeightInfo: weights::WeightInfo;
/// Default referrer account - used when no referrer is specified
/// This allows automatic assignment of founder as referrer for users without invitations
type DefaultReferrer: Get<Self::AccountId>;
/// Penalty score per revoked referral
/// DIRECT RESPONSIBILITY: Bad referrals reduce referrer's score
/// Default: 3 (each bad referral costs 3x a good referral)
#[pallet::constant]
type PenaltyPerRevocation: Get<u32>;
}
// --- Storage Items ---
/// Holds users awaiting to join system via referral.
/// (Referred AccountId -> Referrer AccountId)
#[pallet::storage]
#[pallet::getter(fn pending_referrals)]
pub type PendingReferrals<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, T::AccountId, OptionQuery>;
/// Holds successfully completed referral count per user.
/// (Referrer AccountId -> Count)
#[pallet::storage]
#[pallet::getter(fn referral_count)]
pub type ReferralCount<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, u32, ValueQuery>;
/// Holds who a user invited and transaction details.
/// (Referred AccountId -> ReferralInfo)
#[pallet::storage]
#[pallet::getter(fn referrals)]
pub type Referrals<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, ReferralInfo<T>, OptionQuery>;
/// Referrer statistics for direct responsibility tracking
/// ACCOUNTABILITY: Tracks good and bad referrals for penalty calculation
#[pallet::storage]
#[pallet::getter(fn referrer_stats)]
pub type ReferrerStatsStorage<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, ReferrerStats, ValueQuery>;
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct ReferralInfo<T: Config> {
pub referrer: T::AccountId,
pub created_at: BlockNumberFor<T>,
}
// --- Events ---
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// When a user invites another user.
ReferralInitiated { referrer: T::AccountId, referred: T::AccountId },
/// When invited user successfully completes KYC process.
ReferralConfirmed {
referrer: T::AccountId,
referred: T::AccountId,
new_referrer_count: u32,
},
/// When a referral is penalized due to revoked citizenship
/// DIRECT RESPONSIBILITY: Only the referrer is affected
ReferralPenalized {
referrer: T::AccountId,
revoked_citizen: T::AccountId,
new_penalty_score: u32,
total_revoked: u32,
},
}
// --- Errors ---
#[pallet::error]
pub enum Error<T> {
/// A user cannot invite themselves.
SelfReferral,
/// This user has already been invited by someone else.
AlreadyReferred,
}
// --- Extrinsics ---
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Initiates a referral record to invite another user to the system.
#[pallet::call_index(0)]
#[pallet::weight(<T as Config>::WeightInfo::initiate_referral())]
pub fn initiate_referral(origin: OriginFor<T>, referred: T::AccountId) -> DispatchResult {
let referrer = ensure_signed(origin)?;
ensure!(referrer != referred, Error::<T>::SelfReferral);
ensure!(!Referrals::<T>::contains_key(&referred), Error::<T>::AlreadyReferred);
ensure!(!PendingReferrals::<T>::contains_key(&referred), Error::<T>::AlreadyReferred);
PendingReferrals::<T>::insert(&referred, &referrer);
Self::deposit_event(Event::ReferralInitiated { referrer, referred });
Ok(())
}
/// Sudo-only extrinsic to manually confirm a referral (for fixing historical data).
/// This bypasses the normal KYC approval flow and directly confirms the referral.
#[pallet::call_index(1)]
#[pallet::weight(<T as Config>::WeightInfo::force_confirm_referral())]
pub fn force_confirm_referral(
origin: OriginFor<T>,
referrer: T::AccountId,
referred: T::AccountId,
) -> DispatchResult {
ensure_root(origin)?;
ensure!(referrer != referred, Error::<T>::SelfReferral);
ensure!(!Referrals::<T>::contains_key(&referred), Error::<T>::AlreadyReferred);
// Increment referrer's count
let new_count = ReferralCount::<T>::get(&referrer).saturating_add(1);
ReferralCount::<T>::insert(&referrer, new_count);
// Create and store referral info
let referral_info = ReferralInfo {
referrer: referrer.clone(),
created_at: pezframe_system::Pallet::<T>::block_number(),
};
Referrals::<T>::insert(referred.clone(), referral_info);
// Remove from pending if it exists
PendingReferrals::<T>::remove(&referred);
// Emit event
Self::deposit_event(Event::ReferralConfirmed {
referrer,
referred,
new_referrer_count: new_count,
});
Ok(())
}
}
// --- Trait Implementations ---
impl<T: Config> OnKycApproved<T::AccountId> for Pallet<T> {
fn on_kyc_approved(who: &T::AccountId, referrer: &T::AccountId) {
// Security check: Verify on-chain that the user's KYC status is actually
// "Approved" before confirming the referral.
if pezpallet_identity_kyc::Pallet::<T>::get_kyc_status(who) ==
pezpallet_identity_kyc::types::KycLevel::Approved
{
// Check if this referral already exists (prevent double-counting)
if Referrals::<T>::contains_key(who) {
return; // Already processed
}
// UPDATED (Gemini suggestion): Use referrer from parameter directly
// This ensures data consistency between identity-kyc and referral pallets
// Previously we looked up from storage which could cause data loss
// Clean up legacy PendingReferrals if exists
PendingReferrals::<T>::remove(who);
// Increment referrer's count
let new_count = ReferralCount::<T>::get(referrer).saturating_add(1);
ReferralCount::<T>::insert(referrer, new_count);
// Update referrer stats for direct responsibility tracking
ReferrerStatsStorage::<T>::mutate(referrer, |stats| {
stats.total_referrals = stats.total_referrals.saturating_add(1);
});
// Create and store referral info
let referral_info = ReferralInfo {
referrer: referrer.clone(),
created_at: pezframe_system::Pallet::<T>::block_number(),
};
Referrals::<T>::insert(who.clone(), referral_info);
// Emit confirmation event
Self::deposit_event(Event::ReferralConfirmed {
referrer: referrer.clone(),
referred: who.clone(),
new_referrer_count: new_count,
});
}
}
}
/// Implementation for direct responsibility penalty system
/// Called when a citizen's status is revoked (malicious actor identified)
impl<T: Config> OnCitizenshipRevoked<T::AccountId> for Pallet<T> {
fn on_citizenship_revoked(who: &T::AccountId) {
// Find the referrer of the revoked citizen
if let Some(referral_info) = Referrals::<T>::get(who) {
let referrer = referral_info.referrer;
let penalty_per_revocation = T::PenaltyPerRevocation::get();
// Update referrer stats - DIRECT RESPONSIBILITY
// Only the direct referrer is penalized, not the chain
ReferrerStatsStorage::<T>::mutate(&referrer, |stats| {
stats.revoked_referrals = stats.revoked_referrals.saturating_add(1);
stats.penalty_score =
stats.penalty_score.saturating_add(penalty_per_revocation);
});
let updated_stats = ReferrerStatsStorage::<T>::get(&referrer);
// Emit penalty event
Self::deposit_event(Event::ReferralPenalized {
referrer,
revoked_citizen: who.clone(),
new_penalty_score: updated_stats.penalty_score,
total_revoked: updated_stats.revoked_referrals,
});
}
}
}
impl<T: Config> ReferralScoreProvider<T::AccountId> for Pallet<T> {
type Score = RawScore;
fn get_referral_score(who: &T::AccountId) -> RawScore {
let stats = ReferrerStatsStorage::<T>::get(who);
// Calculate good referrals (total minus revoked)
let good_referrals = stats.total_referrals.saturating_sub(stats.revoked_referrals);
// BALANCED PENALTY SYSTEM (Gemini's suggestion):
// "Every 4 bad referrals = -10 points"
// This is equivalent to "1 bad = -2.5 points"
// Much fairer than "1 bad = 3 good deleted"
//
// Formula: penalty_points = (revoked_referrals / 4) * 10
// Simplified: penalty_points = revoked_referrals * 10 / 4 = revoked_referrals * 2.5
// Using integer math: penalty_points = (revoked_referrals * 10) / 4
let penalty_points = (stats.revoked_referrals.saturating_mul(10)) / 4;
// Calculate base score from good referrals
// Scoring system with max 500 points:
// 0 referrals = 0 points
// 1-10 referrals = count * 10 points (10, 20, 30, ..., 100)
// 11-50 referrals = 100 + ((count - 10) * 5) = 105, 110, ..., 300
// 51-100 referrals = 300 + ((count - 50) * 4) = 304, 308, ..., 500
// 101+ referrals = 500 points (maximum)
let base_score = match good_referrals {
0 => 0,
1..=10 => good_referrals * 10,
11..=50 => 100 + ((good_referrals - 10) * 5),
51..=100 => 300 + ((good_referrals - 50) * 4),
_ => 500,
};
// Apply penalty (cannot go below 0)
base_score.saturating_sub(penalty_points)
}
}
impl<T: Config> InviterProvider<T::AccountId> for Pallet<T> {
fn get_inviter(who: &T::AccountId) -> Option<T::AccountId> {
Referrals::<T>::get(who).map(|info| info.referrer)
}
}
}
@@ -0,0 +1,131 @@
// pezkuwi/pallets/referral/src/mock.rs (Updated for new trustless model)
use crate as pezpallet_referral;
use pezframe_support::{
construct_runtime, derive_impl, parameter_types,
traits::{ConstU128, ConstU32},
};
use pezframe_system::EnsureRoot;
use pezsp_core::H256;
use pezsp_runtime::BuildStorage;
type Block = pezframe_system::mocking::MockBlock<Test>;
pub type AccountId = u64;
pub type Balance = u128;
// Test accounts
pub const FOUNDER: AccountId = 100;
pub const REFERRER: AccountId = 1;
pub const REFERRED: AccountId = 2;
pub const USER_3: AccountId = 3;
construct_runtime!(
pub enum Test
{
System: pezframe_system,
Balances: pezpallet_balances,
IdentityKyc: pezpallet_identity_kyc,
Referral: pezpallet_referral,
}
);
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for Test {
type Block = Block;
type AccountData = pezpallet_balances::AccountData<Balance>;
}
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl pezpallet_balances::Config for Test {
type Balance = Balance;
type ExistentialDeposit = ConstU128<1>;
type AccountStore = System;
}
parameter_types! {
pub const KycApplicationDepositAmount: Balance = 100;
pub const MaxStringLen: u32 = 50;
pub const MaxCidLen: u32 = 128;
pub const PenaltyPerRevocationAmount: u32 = 3;
}
// Mock implementation for CitizenNftProvider
pub struct MockCitizenNftProvider;
impl pezpallet_identity_kyc::types::CitizenNftProvider<AccountId> for MockCitizenNftProvider {
fn mint_citizen_nft(_who: &AccountId) -> pezsp_runtime::DispatchResult {
Ok(())
}
fn mint_citizen_nft_confirmed(_who: &AccountId) -> pezsp_runtime::DispatchResult {
Ok(())
}
fn burn_citizen_nft(_who: &AccountId) -> pezsp_runtime::DispatchResult {
Ok(())
}
}
impl pezpallet_identity_kyc::Config for Test {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type GovernanceOrigin = EnsureRoot<AccountId>;
type WeightInfo = ();
type OnKycApproved = Referral; // Referral pallet handles KYC approval hook
type OnCitizenshipRevoked = Referral; // Referral pallet handles revocation penalty
type CitizenNftProvider = MockCitizenNftProvider;
type KycApplicationDeposit = KycApplicationDepositAmount;
type MaxStringLength = MaxStringLen;
type MaxCidLength = MaxCidLen;
}
// Default referrer for testing (founder account)
pub struct DefaultReferrerAccount;
impl pezframe_support::traits::Get<AccountId> for DefaultReferrerAccount {
fn get() -> AccountId {
FOUNDER
}
}
impl pezpallet_referral::Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
type DefaultReferrer = DefaultReferrerAccount;
type PenaltyPerRevocation = PenaltyPerRevocationAmount;
}
/// Build test externalities with founding citizens
pub fn new_test_ext() -> pezsp_io::TestExternalities {
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
pezpallet_balances::GenesisConfig::<Test> {
balances: vec![
(FOUNDER, 1_000_000),
(REFERRER, 10_000),
(REFERRED, 10_000),
(USER_3, 10_000),
],
..Default::default()
}
.assimilate_storage(&mut t)
.unwrap();
// Add founding citizens via genesis config
pezpallet_identity_kyc::GenesisConfig::<Test> {
founding_citizens: vec![
(FOUNDER, H256::from_low_u64_be(1)),
(REFERRER, H256::from_low_u64_be(2)),
],
_phantom: Default::default(),
}
.assimilate_storage(&mut t)
.unwrap();
let mut ext = pezsp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
/// Build test externalities for penalty tests (needs revoked citizens)
pub fn new_test_ext_with_citizens() -> pezsp_io::TestExternalities {
new_test_ext()
}
@@ -0,0 +1,538 @@
use crate::{
mock::*, pallet::ReferralInfo, Error, Event, PendingReferrals, ReferralCount, Referrals,
ReferrerStatsStorage,
};
use pezframe_support::{assert_noop, assert_ok};
use pezpallet_identity_kyc::types::{OnCitizenshipRevoked, OnKycApproved};
use pezsp_runtime::DispatchError;
type ReferralPallet = crate::Pallet<Test>;
// ============================================================================
// initiate_referral Tests
// ============================================================================
#[test]
fn initiate_referral_works() {
new_test_ext().execute_with(|| {
// REFERRER (citizen) invites REFERRED
assert_ok!(ReferralPallet::initiate_referral(RuntimeOrigin::signed(REFERRER), REFERRED));
// Verification: Correct record is added to pending referrals list.
assert_eq!(ReferralPallet::pending_referrals(REFERRED), Some(REFERRER));
// Correct event is emitted.
System::assert_last_event(
Event::ReferralInitiated { referrer: REFERRER, referred: REFERRED }.into(),
);
});
}
#[test]
fn initiate_referral_fails_for_self_referral() {
new_test_ext().execute_with(|| {
// User cannot invite themselves.
assert_noop!(
ReferralPallet::initiate_referral(RuntimeOrigin::signed(REFERRER), REFERRER),
Error::<Test>::SelfReferral
);
});
}
#[test]
fn initiate_referral_fails_if_already_referred() {
new_test_ext().execute_with(|| {
// First referral succeeds
assert_ok!(ReferralPallet::initiate_referral(RuntimeOrigin::signed(REFERRER), REFERRED));
// Second referral attempt by USER_3 fails
assert_noop!(
ReferralPallet::initiate_referral(RuntimeOrigin::signed(USER_3), REFERRED),
Error::<Test>::AlreadyReferred
);
});
}
// ============================================================================
// on_kyc_approved Hook Tests (Updated for new trait signature)
// ============================================================================
#[test]
fn on_kyc_approved_hook_works() {
new_test_ext().execute_with(|| {
// Setup: REFERRER invites REFERRED via PendingReferrals
assert_ok!(ReferralPallet::initiate_referral(RuntimeOrigin::signed(REFERRER), REFERRED));
// Set user's KYC as approved
pezpallet_identity_kyc::KycStatuses::<Test>::insert(
REFERRED,
pezpallet_identity_kyc::types::KycLevel::Approved,
);
// Action: Call on_kyc_approved with referrer parameter
ReferralPallet::on_kyc_approved(&REFERRED, &REFERRER);
// Verification
// 1. Pending referral record is deleted
assert_eq!(PendingReferrals::<Test>::get(REFERRED), None);
// 2. Referrer's referral count increases by 1
assert_eq!(ReferralCount::<Test>::get(REFERRER), 1);
// 3. Permanent referral information is created
assert!(Referrals::<Test>::contains_key(REFERRED));
let referral_info = Referrals::<Test>::get(REFERRED).unwrap();
assert_eq!(referral_info.referrer, REFERRER);
// 4. ReferrerStats updated
let stats = ReferrerStatsStorage::<Test>::get(REFERRER);
assert_eq!(stats.total_referrals, 1);
assert_eq!(stats.revoked_referrals, 0);
// 5. Correct event is emitted
System::assert_last_event(
Event::ReferralConfirmed {
referrer: REFERRER,
referred: REFERRED,
new_referrer_count: 1,
}
.into(),
);
});
}
#[test]
fn on_kyc_approved_uses_referrer_parameter() {
new_test_ext().execute_with(|| {
// No pending referral - but referrer is passed as parameter
// This tests the new model where identity-kyc passes referrer directly
pezpallet_identity_kyc::KycStatuses::<Test>::insert(
REFERRED,
pezpallet_identity_kyc::types::KycLevel::Approved,
);
// Call with explicit referrer parameter
ReferralPallet::on_kyc_approved(&REFERRED, &REFERRER);
// Should use the passed referrer, not look up from PendingReferrals
let referral_info = Referrals::<Test>::get(REFERRED).unwrap();
assert_eq!(referral_info.referrer, REFERRER);
assert_eq!(ReferralCount::<Test>::get(REFERRER), 1);
});
}
#[test]
fn on_kyc_approved_does_nothing_if_not_approved_status() {
new_test_ext().execute_with(|| {
// User's KYC is NOT approved - status is still NotStarted
// on_kyc_approved should do nothing
let initial_count = ReferralCount::<Test>::get(REFERRER);
ReferralPallet::on_kyc_approved(&REFERRED, &REFERRER);
// No changes should have occurred
assert_eq!(ReferralCount::<Test>::get(REFERRER), initial_count);
assert!(Referrals::<Test>::get(REFERRED).is_none());
});
}
#[test]
fn on_kyc_approved_prevents_double_counting() {
new_test_ext().execute_with(|| {
pezpallet_identity_kyc::KycStatuses::<Test>::insert(
REFERRED,
pezpallet_identity_kyc::types::KycLevel::Approved,
);
// First approval
ReferralPallet::on_kyc_approved(&REFERRED, &REFERRER);
assert_eq!(ReferralCount::<Test>::get(REFERRER), 1);
// Second approval attempt should be ignored (already processed)
ReferralPallet::on_kyc_approved(&REFERRED, &REFERRER);
assert_eq!(ReferralCount::<Test>::get(REFERRER), 1); // Still 1
});
}
// ============================================================================
// on_citizenship_revoked Tests (Direct Responsibility Penalty)
// ============================================================================
#[test]
fn on_citizenship_revoked_penalizes_referrer() {
new_test_ext().execute_with(|| {
// Setup: Complete referral first
pezpallet_identity_kyc::KycStatuses::<Test>::insert(
REFERRED,
pezpallet_identity_kyc::types::KycLevel::Approved,
);
ReferralPallet::on_kyc_approved(&REFERRED, &REFERRER);
// Verify initial stats
let stats = ReferrerStatsStorage::<Test>::get(REFERRER);
assert_eq!(stats.total_referrals, 1);
assert_eq!(stats.revoked_referrals, 0);
assert_eq!(stats.penalty_score, 0);
// Action: Citizenship revoked (malicious actor identified)
ReferralPallet::on_citizenship_revoked(&REFERRED);
// Verify penalty applied
let stats = ReferrerStatsStorage::<Test>::get(REFERRER);
assert_eq!(stats.total_referrals, 1);
assert_eq!(stats.revoked_referrals, 1);
assert_eq!(stats.penalty_score, PenaltyPerRevocationAmount::get());
// Verify event
System::assert_last_event(
Event::ReferralPenalized {
referrer: REFERRER,
revoked_citizen: REFERRED,
new_penalty_score: PenaltyPerRevocationAmount::get(),
total_revoked: 1,
}
.into(),
);
});
}
#[test]
fn on_citizenship_revoked_does_nothing_if_no_referral() {
new_test_ext().execute_with(|| {
// Try to revoke someone who was never referred
let unknown_user = 999;
ReferralPallet::on_citizenship_revoked(&unknown_user);
// No penalty events should be emitted
// (this is safe - just a no-op)
});
}
// ============================================================================
// Referral Score Calculation Tests (with balanced penalty)
// ============================================================================
#[test]
fn referral_score_tier_0_to_10() {
use crate::types::ReferralScoreProvider;
new_test_ext().execute_with(|| {
// Update stats directly for testing
ReferrerStatsStorage::<Test>::mutate(&REFERRER, |stats| {
stats.total_referrals = 0;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 0);
// 1 referral = 10 points
ReferrerStatsStorage::<Test>::mutate(&REFERRER, |stats| {
stats.total_referrals = 1;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 10);
// 5 referrals = 50 points
ReferrerStatsStorage::<Test>::mutate(&REFERRER, |stats| {
stats.total_referrals = 5;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 50);
// 10 referrals = 100 points
ReferrerStatsStorage::<Test>::mutate(&REFERRER, |stats| {
stats.total_referrals = 10;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 100);
});
}
#[test]
fn referral_score_tier_11_to_50() {
use crate::types::ReferralScoreProvider;
new_test_ext().execute_with(|| {
// 11 referrals: 100 + (1 * 5) = 105
ReferrerStatsStorage::<Test>::mutate(&REFERRER, |stats| {
stats.total_referrals = 11;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 105);
// 20 referrals: 100 + (10 * 5) = 150
ReferrerStatsStorage::<Test>::mutate(&REFERRER, |stats| {
stats.total_referrals = 20;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 150);
// 50 referrals: 100 + (40 * 5) = 300
ReferrerStatsStorage::<Test>::mutate(&REFERRER, |stats| {
stats.total_referrals = 50;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 300);
});
}
#[test]
fn referral_score_tier_51_to_100() {
use crate::types::ReferralScoreProvider;
new_test_ext().execute_with(|| {
// 51 referrals: 300 + (1 * 4) = 304
ReferrerStatsStorage::<Test>::mutate(&REFERRER, |stats| {
stats.total_referrals = 51;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 304);
// 75 referrals: 300 + (25 * 4) = 400
ReferrerStatsStorage::<Test>::mutate(&REFERRER, |stats| {
stats.total_referrals = 75;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 400);
// 100 referrals: 300 + (50 * 4) = 500
ReferrerStatsStorage::<Test>::mutate(&REFERRER, |stats| {
stats.total_referrals = 100;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 500);
});
}
#[test]
fn referral_score_capped_at_500() {
use crate::types::ReferralScoreProvider;
new_test_ext().execute_with(|| {
// 101+ referrals capped at 500
ReferrerStatsStorage::<Test>::mutate(&REFERRER, |stats| {
stats.total_referrals = 101;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 500);
// Even 1000 referrals = 500
ReferrerStatsStorage::<Test>::mutate(&REFERRER, |stats| {
stats.total_referrals = 1000;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 500);
});
}
#[test]
fn referral_score_with_balanced_penalty() {
use crate::types::ReferralScoreProvider;
new_test_ext().execute_with(|| {
// 10 good referrals = 100 points
ReferrerStatsStorage::<Test>::mutate(&REFERRER, |stats| {
stats.total_referrals = 10;
stats.revoked_referrals = 0;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 100);
// 10 total, 4 revoked = 6 good
// Penalty: (4 * 10) / 4 = 10 points deducted
// Base score: 6 * 10 = 60
// Final: 60 - 10 = 50
ReferrerStatsStorage::<Test>::mutate(&REFERRER, |stats| {
stats.total_referrals = 10;
stats.revoked_referrals = 4;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 50);
// 20 total, 8 revoked = 12 good (tier 2)
// Penalty: (8 * 10) / 4 = 20 points deducted
// Base score: 100 + (2 * 5) = 110
// Final: 110 - 20 = 90
ReferrerStatsStorage::<Test>::mutate(&REFERRER, |stats| {
stats.total_referrals = 20;
stats.revoked_referrals = 8;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 90);
});
}
#[test]
fn referral_score_cannot_go_negative() {
use crate::types::ReferralScoreProvider;
new_test_ext().execute_with(|| {
// Extreme case: All referrals revoked
// 5 total, 5 revoked = 0 good
// Penalty: (5 * 10) / 4 = 12 points
// Base score: 0
// Final: 0 - 12 = 0 (saturating_sub)
ReferrerStatsStorage::<Test>::mutate(&REFERRER, |stats| {
stats.total_referrals = 5;
stats.revoked_referrals = 5;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 0);
});
}
// ============================================================================
// InviterProvider Trait Tests
// ============================================================================
#[test]
fn get_inviter_returns_correct_referrer() {
use crate::types::InviterProvider;
new_test_ext().execute_with(|| {
// Complete referral
pezpallet_identity_kyc::KycStatuses::<Test>::insert(
REFERRED,
pezpallet_identity_kyc::types::KycLevel::Approved,
);
ReferralPallet::on_kyc_approved(&REFERRED, &REFERRER);
// Verify InviterProvider trait
assert_eq!(ReferralPallet::get_inviter(&REFERRED), Some(REFERRER));
});
}
#[test]
fn get_inviter_returns_none_for_non_referred() {
use crate::types::InviterProvider;
new_test_ext().execute_with(|| {
// User was not referred
assert_eq!(ReferralPallet::get_inviter(&999), None);
});
}
// ============================================================================
// Force Confirm Referral Tests (Sudo-only)
// ============================================================================
#[test]
fn force_confirm_referral_works() {
use crate::types::{InviterProvider, ReferralScoreProvider};
new_test_ext().execute_with(|| {
// Force confirm referral (sudo-only)
assert_ok!(ReferralPallet::force_confirm_referral(
RuntimeOrigin::root(),
REFERRER,
REFERRED
));
// Verify storage updates
assert_eq!(ReferralCount::<Test>::get(REFERRER), 1);
assert!(Referrals::<Test>::contains_key(REFERRED));
assert_eq!(Referrals::<Test>::get(REFERRED).unwrap().referrer, REFERRER);
// Verify trait implementations
assert_eq!(ReferralPallet::get_inviter(&REFERRED), Some(REFERRER));
});
}
#[test]
fn force_confirm_referral_requires_root() {
new_test_ext().execute_with(|| {
// Non-root origin should fail
assert_noop!(
ReferralPallet::force_confirm_referral(
RuntimeOrigin::signed(REFERRER),
REFERRER,
REFERRED
),
DispatchError::BadOrigin
);
});
}
#[test]
fn force_confirm_referral_prevents_self_referral() {
new_test_ext().execute_with(|| {
assert_noop!(
ReferralPallet::force_confirm_referral(RuntimeOrigin::root(), REFERRER, REFERRER),
Error::<Test>::SelfReferral
);
});
}
#[test]
fn force_confirm_referral_prevents_duplicate() {
new_test_ext().execute_with(|| {
// First force confirm succeeds
assert_ok!(ReferralPallet::force_confirm_referral(
RuntimeOrigin::root(),
REFERRER,
REFERRED
));
// Second attempt fails
assert_noop!(
ReferralPallet::force_confirm_referral(RuntimeOrigin::root(), REFERRER, REFERRED),
Error::<Test>::AlreadyReferred
);
});
}
// ============================================================================
// Integration Tests
// ============================================================================
#[test]
fn complete_referral_flow_integration() {
use crate::types::{InviterProvider, ReferralScoreProvider};
new_test_ext().execute_with(|| {
// Step 1: Initiate referral (legacy way via PendingReferrals)
assert_ok!(ReferralPallet::initiate_referral(RuntimeOrigin::signed(REFERRER), REFERRED));
assert_eq!(PendingReferrals::<Test>::get(REFERRED), Some(REFERRER));
// Step 2: KYC approval triggers confirmation
pezpallet_identity_kyc::KycStatuses::<Test>::insert(
REFERRED,
pezpallet_identity_kyc::types::KycLevel::Approved,
);
ReferralPallet::on_kyc_approved(&REFERRED, &REFERRER);
// Step 3: Verify all storage updates
assert_eq!(PendingReferrals::<Test>::get(REFERRED), None);
assert_eq!(ReferralCount::<Test>::get(REFERRER), 1);
assert!(Referrals::<Test>::contains_key(REFERRED));
// Step 4: Verify trait implementations
assert_eq!(ReferralPallet::get_inviter(&REFERRED), Some(REFERRER));
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 10);
});
}
#[test]
fn multiple_referrals_for_same_referrer() {
new_test_ext().execute_with(|| {
// REFERRER refers 3 people
let referred1 = 10;
let referred2 = 11;
let referred3 = 12;
// Approve all via direct calls
for &referred in &[referred1, referred2, referred3] {
pezpallet_identity_kyc::KycStatuses::<Test>::insert(
referred,
pezpallet_identity_kyc::types::KycLevel::Approved,
);
ReferralPallet::on_kyc_approved(&referred, &REFERRER);
}
// Verify count
assert_eq!(ReferralCount::<Test>::get(REFERRER), 3);
// Verify stats
let stats = ReferrerStatsStorage::<Test>::get(REFERRER);
assert_eq!(stats.total_referrals, 3);
});
}
#[test]
fn referral_info_stores_block_number() {
new_test_ext().execute_with(|| {
let block_number = 42u64;
System::set_block_number(block_number);
pezpallet_identity_kyc::KycStatuses::<Test>::insert(
REFERRED,
pezpallet_identity_kyc::types::KycLevel::Approved,
);
ReferralPallet::on_kyc_approved(&REFERRED, &REFERRER);
// Verify stored block number
let info = Referrals::<Test>::get(REFERRED).unwrap();
assert_eq!(info.created_at, block_number);
assert_eq!(info.referrer, REFERRER);
});
}
@@ -0,0 +1,62 @@
use codec::{Decode, Encode, MaxEncodedLen};
use pezframe_support::pezpallet_prelude::RuntimeDebug;
use scale_info::TypeInfo;
// --- GENERAL TYPES ---
/// Structure representing a simple NFT.
/// Note: The actual NFT structure will be more detailed in `pezpallet-tiki`.
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen, Default)]
pub struct Tiki {
pub id: u32,
// metadata and other fields can be added in the future.
}
/// Raw score type to be used in scoring.
pub type RawScore = u32;
/// Referrer statistics for direct responsibility tracking
/// Used to apply penalties when referrals turn out to be malicious
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen, Default)]
pub struct ReferrerStats {
/// Total number of successful referrals
pub total_referrals: u32,
/// Number of referrals that were later revoked (bad referrals)
pub revoked_referrals: u32,
/// Penalty score (affects trust score negatively)
/// Formula: revoked_referrals * PenaltyPerRevocation
pub penalty_score: u32,
}
impl ReferrerStats {
/// Check if referrer has a good track record
/// Returns true if less than 10% of referrals were revoked
pub fn has_good_track_record(&self) -> bool {
if self.total_referrals == 0 {
return true;
}
// Good if less than 10% revoked
self.revoked_referrals * 10 < self.total_referrals
}
/// Calculate adjusted referral score with penalty
/// Good referrals contribute positively, bad ones contribute negatively
pub fn adjusted_score(&self, penalty_per_revocation: u32) -> i32 {
let positive = self.total_referrals.saturating_sub(self.revoked_referrals) as i32;
let negative = (self.revoked_referrals * penalty_per_revocation) as i32;
positive.saturating_sub(negative)
}
}
// --- EXTERNAL INTERFACES (TRAITS) ---
/// Interface for querying an account's inviter.
pub trait InviterProvider<AccountId> {
fn get_inviter(who: &AccountId) -> Option<AccountId>;
}
/// Interface for calculating an account's referral score.
pub trait ReferralScoreProvider<AccountId> {
type Score;
fn get_referral_score(who: &AccountId) -> Self::Score;
}
@@ -0,0 +1,125 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Autogenerated weights for `pezpallet_referral`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0
//! DATE: 2025-12-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `MamostePC`, CPU: `11th Gen Intel(R) Core(TM) i9-11950H @ 2.60GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
// Executed Command:
// ./target/release/frame-omni-bencher
// v1
// benchmark
// pallet
// --runtime
// target/release/wbuild/people-pezkuwichain-runtime/people_pezkuwichain_runtime.compact.compressed.wasm
// --pallets
// pezpallet_referral
// -e
// all
// --steps
// 50
// --repeat
// 20
// --output
// pezcumulus/teyrchains/pallets/referral/src/weights.rs
// --template
// bizinikiwi/.maintain/frame-weight-template.hbs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
#![allow(dead_code)]
use pezframe_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for `pezpallet_referral`.
pub trait WeightInfo {
fn initiate_referral() -> Weight;
fn force_confirm_referral() -> Weight;
}
/// Weights for `pezpallet_referral` using the Bizinikiwi node and recommended hardware.
pub struct BizinikiwiWeight<T>(PhantomData<T>);
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
/// Storage: `Referral::Referrals` (r:1 w:0)
/// Proof: `Referral::Referrals` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
/// Storage: `Referral::PendingReferrals` (r:1 w:1)
/// Proof: `Referral::PendingReferrals` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
fn initiate_referral() -> Weight {
// Proof Size summary in bytes:
// Measured: `3`
// Estimated: `3549`
// Minimum execution time: 11_365_000 picoseconds.
Weight::from_parts(11_696_000, 3549)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `Referral::Referrals` (r:1 w:1)
/// Proof: `Referral::Referrals` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
/// Storage: `Referral::ReferralCount` (r:1 w:1)
/// Proof: `Referral::ReferralCount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
/// Storage: `Referral::PendingReferrals` (r:0 w:1)
/// Proof: `Referral::PendingReferrals` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
fn force_confirm_referral() -> Weight {
// Proof Size summary in bytes:
// Measured: `3`
// Estimated: `3549`
// Minimum execution time: 14_792_000 picoseconds.
Weight::from_parts(15_141_000, 3549)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(3_u64))
}
}
// For backwards compatibility and tests.
impl WeightInfo for () {
/// Storage: `Referral::Referrals` (r:1 w:0)
/// Proof: `Referral::Referrals` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
/// Storage: `Referral::PendingReferrals` (r:1 w:1)
/// Proof: `Referral::PendingReferrals` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
fn initiate_referral() -> Weight {
// Proof Size summary in bytes:
// Measured: `3`
// Estimated: `3549`
// Minimum execution time: 11_365_000 picoseconds.
Weight::from_parts(11_696_000, 3549)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `Referral::Referrals` (r:1 w:1)
/// Proof: `Referral::Referrals` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
/// Storage: `Referral::ReferralCount` (r:1 w:1)
/// Proof: `Referral::ReferralCount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
/// Storage: `Referral::PendingReferrals` (r:0 w:1)
/// Proof: `Referral::PendingReferrals` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
fn force_confirm_referral() -> Weight {
// Proof Size summary in bytes:
// Measured: `3`
// Estimated: `3549`
// Minimum execution time: 14_792_000 picoseconds.
Weight::from_parts(15_141_000, 3549)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
}