fix: referral pallet - force_confirm stats tracking and penalty_score usage

- force_confirm_referral now updates ReferrerStatsStorage (was missing)
- get_referral_score uses stored penalty_score from PenaltyPerRevocation
  instead of hardcoded (revoked*10)/4 formula
- Updated tests to match new penalty calculation behavior
This commit is contained in:
2026-02-11 04:37:38 +03:00
parent 9cc8bd1095
commit bc8e298ea3
3 changed files with 32 additions and 23 deletions
@@ -239,6 +239,11 @@ pub mod pezpallet {
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(),
@@ -343,21 +348,12 @@ pub mod pezpallet {
fn get_referral_score(who: &T::AccountId) -> RawScore {
let stats = ReferrerStatsStorage::<T>::get(who);
// Calculate good referrals (total minus revoked)
// Step 1: "Haksız olanı geri alma" - Remove revoked referrals from count
// This is NOT a penalty, it's correcting the record to reflect reality
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:
// Step 2: Calculate base score from good referrals
// Tiered 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
@@ -371,8 +367,10 @@ pub mod pezpallet {
_ => 500,
};
// Apply penalty (cannot go below 0)
base_score.saturating_sub(penalty_points)
// Step 3: "Cezalandırma" - Apply stored penalty from PenaltyPerRevocation
// Uses the pre-calculated penalty_score accumulated in on_citizenship_revoked()
// This is the actual punishment: "you should have been more careful"
base_score.saturating_sub(stats.penalty_score)
}
}
@@ -69,6 +69,7 @@ impl pezpallet_identity_kyc::Config for Test {
type OnKycApproved = Referral; // Referral pezpallet handles KYC approval hook
type OnCitizenshipRevoked = Referral; // Referral pezpallet handles revocation penalty
type CitizenNftProvider = MockCitizenNftProvider;
type DefaultReferrer = DefaultReferrerAccount;
type KycApplicationDeposit = KycApplicationDepositAmount;
type MaxStringLength = MaxStringLen;
type MaxCidLength = MaxCidLen;
@@ -317,28 +317,31 @@ fn referral_score_with_balanced_penalty() {
ReferrerStatsStorage::<Test>::mutate(REFERRER, |stats| {
stats.total_referrals = 10;
stats.revoked_referrals = 0;
stats.penalty_score = 0;
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 100);
// 10 total, 4 revoked = 6 good
// Penalty: (4 * 10) / 4 = 10 points deducted
// penalty_score: 4 * PenaltyPerRevocation(3) = 12
// Base score: 6 * 10 = 60
// Final: 60 - 10 = 50
// Final: 60 - 12 = 48
ReferrerStatsStorage::<Test>::mutate(REFERRER, |stats| {
stats.total_referrals = 10;
stats.revoked_referrals = 4;
stats.penalty_score = 4 * PenaltyPerRevocationAmount::get();
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 50);
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 48);
// 20 total, 8 revoked = 12 good (tier 2)
// Penalty: (8 * 10) / 4 = 20 points deducted
// penalty_score: 8 * PenaltyPerRevocation(3) = 24
// Base score: 100 + (2 * 5) = 110
// Final: 110 - 20 = 90
// Final: 110 - 24 = 86
ReferrerStatsStorage::<Test>::mutate(REFERRER, |stats| {
stats.total_referrals = 20;
stats.revoked_referrals = 8;
stats.penalty_score = 8 * PenaltyPerRevocationAmount::get();
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 90);
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 86);
});
}
@@ -349,12 +352,13 @@ fn referral_score_cannot_go_negative() {
new_test_ext().execute_with(|| {
// Extreme case: All referrals revoked
// 5 total, 5 revoked = 0 good
// Penalty: (5 * 10) / 4 = 12 points
// penalty_score: 5 * PenaltyPerRevocation(3) = 15
// Base score: 0
// Final: 0 - 12 = 0 (saturating_sub)
// Final: 0 - 15 = 0 (saturating_sub)
ReferrerStatsStorage::<Test>::mutate(REFERRER, |stats| {
stats.total_referrals = 5;
stats.revoked_referrals = 5;
stats.penalty_score = 5 * PenaltyPerRevocationAmount::get();
});
assert_eq!(ReferralPallet::get_referral_score(&REFERRER), 0);
});
@@ -412,6 +416,12 @@ fn force_confirm_referral_works() {
assert!(Referrals::<Test>::contains_key(REFERRED));
assert_eq!(Referrals::<Test>::get(REFERRED).unwrap().referrer, REFERRER);
// Verify ReferrerStats is updated (was missing before fix)
let stats = ReferrerStatsStorage::<Test>::get(REFERRER);
assert_eq!(stats.total_referrals, 1);
assert_eq!(stats.revoked_referrals, 0);
assert_eq!(stats.penalty_score, 0);
// Verify trait implementations
assert_eq!(ReferralPallet::get_inviter(&REFERRED), Some(REFERRER));
});