Files
pezkuwi-sdk/pezcumulus/teyrchains/pezpallets/staking-score/src/lib.rs
T
pezkuwichain 65b7f5e640 fix: Resolve cargo clippy errors and add CI workflow plan
## Changes

### Clippy Fixes
- Fixed deprecated `cargo_bin` usage in 27 test files (added #![allow(deprecated)])
- Fixed uninlined_format_args in zombienet-sdk-tests
- Fixed subxt API changes in revive/rpc/tests.rs (fetch signature, StorageValue)
- Fixed dead_code warnings in validator-pool and identity-kyc mocks
- Fixed field name `i` -> `_i` in tasks example

### CI Infrastructure
- Added .claude/WORKFLOW_PLAN.md for tracking CI fix progress
- Updated lychee.toml and taplo.toml configs

### Files Modified
- 27 test files with deprecated cargo_bin fix
- bizinikiwi/pezframe/revive/rpc/src/tests.rs (subxt API)
- pezkuwi/pezpallets/validator-pool/src/{mock,tests}.rs
- pezcumulus/teyrchains/pezpallets/identity-kyc/src/mock.rs
- bizinikiwi/pezframe/examples/tasks/src/tests.rs

## Status
- cargo clippy: PASSING
- Next: cargo fmt, zepter, workspace checks
2025-12-23 09:37:11 +03:00

329 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#![cfg_attr(not(feature = "std"), no_std)]
//! # Staking Score Pezpallet
//!
//! A pezpallet for calculating time-weighted staking scores based on stake amount and duration.
//!
//! ## Overview
//!
//! The Staking Score pezpallet calculates reputation scores from staking behavior by considering:
//! - **Stake Amount**: How much a user has staked
//! - **Stake Duration**: How long tokens have been staked
//! - **Nomination Count**: Number of validators nominated
//! - **Unlocking Chunks**: Pending unstake operations
//!
//! These metrics combine to produce a staking score that contributes to the composite
//! trust score in `pezpallet-trust`.
//!
//! ## Score Calculation
//!
//! ```text
//! staking_score = base_score + time_bonus
//!
//! where:
//! base_score = (staked_amount / UNITS) * 10
//! time_bonus = (months_staked * staked_amount * 0.05) / UNITS
//! ```
//!
//! ### Time-Based Rewards
//! - First month: Base score only
//! - Each additional month: +5% bonus on staked amount
//! - Maximum benefit achieved through long-term commitment
//! - Score increases linearly with time
//!
//! ## Workflow
//!
//! 1. User stakes tokens via main staking pezpallet
//! 2. User calls `start_score_tracking()` to begin time tracking
//! 3. Tracking start block is recorded
//! 4. `pezpallet-trust` queries staking score via `StakingScoreProvider` trait
//! 5. Score calculation uses current block number vs. start block
//! 6. Time bonus accumulates automatically each month
//!
//! ## Integration with Staking
//!
//! This pezpallet does not handle staking operations directly. It:
//! - Reads staking data from main staking pezpallet via `StakingInfoProvider`
//! - Tracks when users want to start earning time bonuses
//! - Calculates scores on-demand without modifying staking state
//!
//! ## Score Components
//!
//! ### Staked Amount
//! - Primary factor in score calculation
//! - Measured in balance units (UNITS = 10^12)
//! - Higher stake = higher base score
//!
//! ### Duration
//! - Measured in months (30 days * 24 hours * 60 min * 10 blocks/min)
//! - ~432,000 blocks per month
//! - Compounds monthly for long-term stakers
//!
//! ### Additional Metrics
//! - Nomination count (contributes to complexity score)
//! - Unlocking chunks (indicates unstaking activity)
//!
//! ## Interface
//!
//! ### Extrinsics
//!
//! - `start_score_tracking()` - Begin time-based score accumulation (user, one-time)
//!
//! ### Storage
//!
//! - `StakingStartBlock` - Block number when user started score tracking
//!
//! ### Trait Implementations
//!
//! - `StakingScoreProvider` - Query staking scores for trust calculation
//!
//! ## Dependencies
//!
//! This pezpallet requires:
//! - Main staking pezpallet implementing `StakingInfoProvider`
//! - `pezpallet-trust` as consumer of staking scores
//!
//! ## Runtime Integration Example
//!
//! ```ignore
//! impl pezpallet_staking_score::Config for Runtime {
//! type RuntimeEvent = RuntimeEvent;
//! type Balance = Balance;
//! type StakingInfo = Staking; // Main staking pezpallet
//! type WeightInfo = pezpallet_staking_score::weights::BizinikiwiWeight<Runtime>;
//! }
//! ```
pub use pezpallet::*;
// Mock staking info provider for benchmarking - ADD THIS
#[cfg(feature = "runtime-benchmarks")]
pub struct BenchmarkStakingInfoProvider;
#[cfg(feature = "runtime-benchmarks")]
impl<AccountId, Balance> StakingInfoProvider<AccountId, Balance> for BenchmarkStakingInfoProvider
where
Balance: From<u128>,
{
fn get_staking_details(_who: &AccountId) -> Option<StakingDetails<Balance>> {
// Always return valid stake for benchmarking
Some(StakingDetails {
staked_amount: (1000u128 * UNITS).into(),
nominations_count: 5,
unlocking_chunks_count: 2,
})
}
}
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub mod weights;
#[pezframe_support::pezpallet]
pub mod pezpallet {
use super::weights::WeightInfo; // Properly importing WeightInfo from parent module.
use core::ops::Div;
use pezframe_support::pezpallet_prelude::*;
use pezframe_system::pezpallet_prelude::*;
use pezsp_runtime::{
traits::{Saturating, Zero},
Perbill,
};
// --- Sabitler ---
pub const MONTH_IN_BLOCKS: u32 = 30 * 24 * 60 * 10;
pub const UNITS: u128 = 1_000_000_000_000;
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
#[pezpallet::config]
pub trait Config: pezframe_system::Config<RuntimeEvent: From<Event<Self>>>
where
// Ensuring BlockNumber is convertible from u32.
BlockNumberFor<Self>: From<u32>,
{
/// Balance type to be used for staking.
/// Adding all required mathematical and comparison properties.
type Balance: Member
+ Parameter
+ MaxEncodedLen
+ Copy
+ Default
+ PartialOrd
+ Saturating
+ Zero
+ Div<Output = Self::Balance> // Specifying that division result is also Balance.
+ From<u128>;
/// Interface to be used for reading staking data.
type StakingInfo: StakingInfoProvider<Self::AccountId, Self::Balance>;
/// To provide extrinsic weights.
type WeightInfo: WeightInfo;
}
// --- Depolama (Storage) ---
#[pezpallet::storage]
#[pezpallet::getter(fn staking_start_block)]
pub type StakingStartBlock<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, BlockNumberFor<T>, 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> },
}
#[pezpallet::error]
pub enum Error<T> {
/// Puan takibini başlatmak için önce stake yapmış olmalısınız.
NoStakeFound,
/// Puan takibi zaten daha önce başlatılmış.
TrackingAlreadyStarted,
}
#[pezpallet::call]
impl<T: Config> Pezpallet<T> {
/// Süreye dayalı puanlamayı manuel olarak aktive eder.
/// Bu fonksiyon, her kullanıcı tarafından sadece bir kez çağrılmalıdır.
#[pezpallet::call_index(0)]
#[pezpallet::weight(T::WeightInfo::start_score_tracking())]
pub fn start_score_tracking(origin: OriginFor<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
// 1. Kullanıcının puan takibini daha önce başlatıp başlatmadığını kontrol et.
ensure!(
StakingStartBlock::<T>::get(&who).is_none(),
Error::<T>::TrackingAlreadyStarted
);
// 2. Kullanıcının ana staking paletinde stake'i var mı diye kontrol et.
// `get_staking_details` artık Option döndürdüğü için `ok_or` ile hata yönetimi
// yapıyoruz.
let details =
T::StakingInfo::get_staking_details(&who).ok_or(Error::<T>::NoStakeFound)?;
ensure!(!details.staked_amount.is_zero(), Error::<T>::NoStakeFound);
// 3. O anki blok numarasını kaydet.
let current_block = pezframe_system::Pezpallet::<T>::block_number();
StakingStartBlock::<T>::insert(&who, current_block);
Self::deposit_event(Event::ScoreTrackingStarted { who, start_block: current_block });
Ok(())
}
}
// --- Arayüz (Trait) ve Tip Tanımları ---
/// Puanlamada kullanılacak ham skor tipi.
pub type RawScore = u32;
/// 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)]
pub struct StakingDetails<Balance> {
pub staked_amount: Balance,
pub nominations_count: u32,
pub unlocking_chunks_count: u32,
}
/// Bu paletin dış dünyaya sunduğu arayüz.
pub trait StakingScoreProvider<AccountId, BlockNumber> {
/// Returns the score and the duration in blocks used for calculation.
fn get_staking_score(who: &AccountId) -> (RawScore, BlockNumber);
}
/// Bu paletin, staking verilerini almak için ihtiyaç duyduğu arayüz.
pub trait StakingInfoProvider<AccountId, Balance> {
/// Verilen hesap için staking detaylarını döndürür.
/// Eğer kullanıcının stake'i yoksa `None` dönmelidir. Bu daha güvenli bir yöntemdir.
fn get_staking_details(who: &AccountId) -> Option<StakingDetails<Balance>>;
}
// --- Trait Implementasyonu ---
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.
let staking_details = match T::StakingInfo::get_staking_details(who) {
Some(details) => details,
None => return (0, Zero::zero()),
};
// Staked miktarı ana birime (HEZ) çevir.
let staked_hez: T::Balance = staking_details.staked_amount / UNITS.into();
// "Sıfır stake, sıfır puan" kuralını uygula.
if staked_hez.is_zero() {
return (0, Zero::zero());
}
// Miktara dayalı temel puanı hesapla.
let amount_score: u32 = if staked_hez <= 100u128.into() {
20
} else if staked_hez <= 250u128.into() {
30
} else if staked_hez <= 750u128.into() {
40
} else {
50 // 751+ HEZ
};
// Süreye dayalı çarpanı ve duration'ı hesapla.
let (_duration_multiplier, duration_for_return) = match StakingStartBlock::<T>::get(who)
{
// Eğer kullanıcı `start_score_tracking` çağırdıysa...
Some(start_block) => {
let current_block = pezframe_system::Pezpallet::<T>::block_number();
let duration_in_blocks = current_block.saturating_sub(start_block);
let multiplier = if duration_in_blocks >= (12 * MONTH_IN_BLOCKS).into() {
Perbill::from_rational(2u32, 1u32) // x2.0 (12 ay ve üstü)
} else if duration_in_blocks >= (6 * MONTH_IN_BLOCKS).into() {
Perbill::from_rational(17u32, 10u32) // x1.7 (6-11 ay)
} else if duration_in_blocks >= (3 * MONTH_IN_BLOCKS).into() {
Perbill::from_rational(7u32, 5u32) // x1.4 (3-5 ay)
} else if duration_in_blocks >= MONTH_IN_BLOCKS.into() {
Perbill::from_rational(6u32, 5u32) // x1.2 (1-2 ay)
} else {
Perbill::from_rational(1u32, 1u32) // x1.0 (< 1 ay)
};
(multiplier, duration_in_blocks)
},
// Eğer takip başlatılmadıysa, çarpan 1.0'dır.
None => (Perbill::from_rational(10u32, 10u32), Zero::zero()),
};
// Nihai puanı hesapla ve 100 ile sınırla.
let final_score = match StakingStartBlock::<T>::get(who) {
Some(start_block) => {
let current_block = pezframe_system::Pezpallet::<T>::block_number();
let duration_in_blocks = current_block.saturating_sub(start_block);
if duration_in_blocks >= (12 * MONTH_IN_BLOCKS).into() {
amount_score * 2 // x2.0
} else if duration_in_blocks >= (6 * MONTH_IN_BLOCKS).into() {
amount_score * 17 / 10 // x1.7
} else if duration_in_blocks >= (3 * MONTH_IN_BLOCKS).into() {
amount_score * 14 / 10 // x1.4
} else if duration_in_blocks >= MONTH_IN_BLOCKS.into() {
amount_score * 12 / 10 // x1.2
} else {
amount_score // x1.0
}
},
None => amount_score, // Takip başlatılmadıysa çarpan yok
};
(final_score.min(100), duration_for_return)
}
}
}