Staking Complete Analysis

Introduction

The Staking pallet is the central component of the Proof-of-Stake system in Taler blockchain. It implements a complex economic model with support for standard and vivid staking, validator management, nomination and reward distribution.

File Structure

1. mod.rs - Main Pallet Structure

1.1 Imports and Dependencies

// Main dependencies
use frame_election_provider_support::{ElectionProvider, ElectionProviderBase, SortedListProvider, VoteWeight};
use frame_support::{pallet_prelude::*, traits::{Currency, Defensive, EnsureOrigin, ...}};
use sp_staking::{EraIndex, SessionIndex, StakingAccount};

1.2 Configuration (Config trait)

Currency Types:

Currency: Main currency for staking (LockableCurrency)
CurrencyBalance: Balance type with constraints
CurrencyToVote: Balance to vote conversion

Time Parameters:

UnixTime: Time for calculating era duration
SessionsPerEra: Number of sessions per era
BondingDuration: Bonding duration
SlashDeferDuration: Deferred slash application

Economic Parameters:

IssuanceLimit: Token issuance limit
StandartStakingInterest: Standard interest rate
VividStakingInterestPerMonth: Additional rate per months

System Components:

ElectionProvider: Validator election provider
VoterList: List of voters (nominators)
TargetList: List of targets (validators)
SessionInterface: Interface for session management

1.3 Storage (Data Storage)

Account Management:

pub type Bonded = StorageMap<_, Twox64Concat, T::AccountId, T::AccountId>;
pub type Ledger = StorageMap<_, Blake2_128Concat, T::AccountId, StakingLedger>;
pub type Payee = StorageMap<_, Twox64Concat, T::AccountId, RewardDestination>;

Validators and Nominators:

pub type Validators = CountedStorageMap<_, Twox64Concat, T::AccountId, ValidatorPrefs>;
pub type Nominators = CountedStorageMap<_, Twox64Concat, T::AccountId, Nominations>;

Era Management:

pub type CurrentEra = StorageValue<_, EraIndex>;
pub type ActiveEra = StorageValue<_, ActiveEraInfo>;
pub type ErasStakers = StorageDoubleMap<_, Twox64Concat, EraIndex, Twox64Concat, T::AccountId, Exposure>>;

1.4 Events

EraPaid: Era payout
Rewarded: Staker reward
Slashed: Staker slash
Bonded: Bonding funds
Unbonded: Unbonding funds
VividStakingScheduled: Vivid staking scheduling
VividStakingCancelled: Vivid staking cancellation

1.5 Errors

NotController/NotStash: Wrong account type
AlreadyBonded/AlreadyPaired: Already linked accounts
InsufficientBond: Insufficient bond
VividStakingInProgress: Vivid staking in progress

1.6 Dispatchable Functions

Main Operations:

bond(): Bonding funds
bond_extra(): Additional bonding
unbond(): Unbonding funds
withdraw_unbonded(): Withdrawing funds

Vivid Staking:

vivid(): Activate vivid staking
unvivid(): Cancel vivid staking

Validation:

validate(): Become validator
nominate(): Nominate validators
chill(): Stop participation

2. impls.rs - Logic Implementation

2.1 Main Data Structures

StakingLedger:

pub struct StakingLedger {
    pub stash: T::AccountId,           // Stash account
    pub total: BalanceOf,          // Total balance
    pub active: BalanceOf,         // Active balance
    pub unlocking: BoundedVec>, T::MaxUnlockingChunks>,
    pub claimed_rewards: BoundedVec,
    pub vivid_staking: Option,
    pub controller: Option,
}

Exposure:

pub struct Exposure {
    pub total: Balance,                // Total exposure
    pub own: Balance,                  // Own exposure
    pub others: Vec>,
    pub vivid_staking: Option,
}

2.2 Key Functions

Ledger Management:

pub fn ledger(account: StakingAccount) -> Result, Error> {
    StakingLedger::::get(account)
}

pub fn bonded(stash: &T::AccountId) -> Option {
    StakingLedger::::paired_account(Stash(stash.clone()))
}

Interest Calculation:

fn calculate_interest(era: EraIndex, opt_vivid_staking: &Option) -> Perbill {
    if let Some(vivid_staking) = opt_vivid_staking {
        if vivid_staking.starting_era <= era && vivid_staking.ending_era >= era {
            let eras_count = vivid_staking.ending_era.saturating_sub(vivid_staking.starting_era.saturating_sub(1));
            let months_count = eras_count / 28;
            return T::StandartStakingInterest::get() + 
                   Perbill::from_parts(T::VividStakingInterestPerMonth::get().deconstruct() * months_count);
        }
    }
    T::StandartStakingInterest::get()
}

Reward Payout:

pub(super) fn do_payout_stakers(
    validator_stash: T::AccountId,
    era: EraIndex,
) -> DispatchResultWithPostInfo {
    // Era validation
    let current_era = CurrentEra::::get().ok_or_else(|| {
        Error::::InvalidEraToReward.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
    })?;
    
    // Get ledger and check for already claimed rewards
    let mut ledger = Self::ledger(account.clone())?;
    match ledger.claimed_rewards.binary_search(&era) {
        Ok(_) => return Err(Error::::AlreadyClaimed),
        Err(pos) => ledger.claimed_rewards.try_insert(pos, era)?,
    }
    
    // Get era exposure
    let exposure = >::get(&era, &stash);
    
    // Calculate rewards for nominators
    for nominator in exposure.others.iter() {
        let interest = Self::calculate_interest(era, &nominator.vivid_staking);
        let mut nominator_reward: BalanceOf = portion * interest * nominator.value;
        
        // Deduct validator commission
        validator_payment_commissions += validator_commission * nominator_reward;
        nominator_reward -= validator_commission * nominator_reward;
        
        // Create payout
        if let Some((imbalance, dest)) = Self::make_payout(&nominator.who, nominator_reward) {
            nominator_payout_count += 1;
            Self::deposit_event(Event::::Rewarded {
                stash: nominator.who.clone(),
                dest,
                amount: imbalance.peek(),
            });
            total_imbalance.subsume(imbalance);
        }
    }
    
    // Calculate rewards for validator
    let validator_own_interest = Self::calculate_interest(era, &exposure.vivid_staking);
    let validator_own_reward = portion * validator_own_interest * exposure.own;
    let validator_reward = validator_own_reward + validator_payment_commissions;
    
    // Payout to validator
    if let Some((imbalance, dest)) = Self::make_payout(&stash, validator_reward) {
        Self::deposit_event(Event::::Rewarded {
            stash: stash.clone(),
            dest,
            amount: imbalance.peek(),
        });
        total_imbalance.subsume(imbalance);
    }
    
    T::Reward::on_unbalanced(total_imbalance);
    Ok(Some(T::WeightInfo::payout_stakers_alive_staked(nominator_payout_count)).into())
}

2.3 Election Provider Implementation

Getting Voters:

pub fn get_npos_voters(bounds: DataProviderBounds) -> Vec> {
    let mut voters_size_tracker: StaticTracker = StaticTracker::default();
    let final_predicted_len = {
        let all_voter_count = T::VoterList::count();
        bounds.count.unwrap_or(all_voter_count.into()).min(all_voter_count.into()).0
    };
    
    let mut all_voters = Vec::<_>::with_capacity(final_predicted_len as usize);
    let weight_of = Self::weight_of_fn();
    
    let mut voters_seen = 0u32;
    let mut validators_taken = 0u32;
    let mut nominators_taken = 0u32;
    let mut min_active_stake = u64::MAX;
    
    let mut sorted_voters = T::VoterList::iter();
    while all_voters.len() < final_predicted_len as usize
        && voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
    {
        let voter = match sorted_voters.next() {
            Some(voter) => {
                voters_seen.saturating_inc();
                voter
            },
            None => break,
        };
        
        let voter_weight = weight_of(&voter);
        if voter_weight.is_zero() {
            continue;
        }
        
        if let Some(Nominations { targets, .. }) = >::get(&voter) {
            if !targets.is_empty() {
                let voter = (voter, voter_weight, targets);
                if voters_size_tracker.try_register_voter(&voter, &bounds).is_err() {
                    Self::deposit_event(Event::::SnapshotVotersSizeExceeded {
                        size: voters_size_tracker.size as u32,
                    });
                    break;
                }
                all_voters.push(voter);
                nominators_taken.saturating_inc();
            }
        } else if Validators::::contains_key(&voter) {
            let self_vote = (
                voter.clone(),
                voter_weight,
                vec![voter.clone()].try_into().expect("MaxVotesPerVoter must be >= 1"),
            );
            
            if voters_size_tracker.try_register_voter(&self_vote, &bounds).is_err() {
                Self::deposit_event(Event::::SnapshotVotersSizeExceeded {
                    size: voters_size_tracker.size as u32,
                });
                break;
            }
            all_voters.push(self_vote);
            validators_taken.saturating_inc();
        }
        
        min_active_stake = if voter_weight < min_active_stake { voter_weight } else { min_active_stake };
    }
    
    MinimumActiveStake::::put(min_active_stake);
    all_voters
}

2.4 Session Management

SessionManager Implementation:

impl pallet_session::SessionManager for Pallet {
    fn new_session(new_index: SessionIndex) -> Option> {
        log!(trace, "planning new session {}", new_index);
        CurrentPlannedSession::::put(new_index);
        Self::new_session(new_index, false).map(|v| v.into_inner())
    }
    
    fn start_session(start_index: SessionIndex) {
        log!(trace, "starting session {}", start_index);
        Self::start_session(start_index)
    }
    
    fn end_session(end_index: SessionIndex) {
        log!(trace, "ending session {}", end_index);
        Self::end_session(end_index)
    }
}

2.5 Runtime API

API for External Calls:

pub fn api_nominations_quota(balance: BalanceOf) -> u32 {
    T::NominationsQuota::get_quota(balance)
}

pub fn api_estimate_staking_payout(account: T::AccountId) -> BalanceOf {
    let Some(active_era) = Self::active_era() else { return Zero::zero() };
    
    let era_duration: u64 = T::SessionsPerEra::get()
        .saturating_mul(T::NextNewSession::average_session_length().saturated_into())
        .saturating_mul(T::ExpectedBlockTime::get().saturated_into())
        .try_into().unwrap_or(0);
    
    let portion = Perbill::from_rational(era_duration, MILLISECONDS_PER_YEAR);
    
    let Some(Nominations { targets, .. }) = Self::nominators(&account) else {
        return Zero::zero();
    };
    
    targets.iter().fold(Zero::zero(), |acc, validator| {
        let exposure = Self::eras_stakers_clipped(active_era.index, validator);
        let Some(nominator_exposure) = exposure.others.iter().find(|&x| x.who == account) else {
            return acc;
        };
        
        let validator_prefs = Self::eras_validator_prefs(&active_era.index, &validator);
        let interest = Self::calculate_interest(active_era.index, &nominator_exposure.vivid_staking);
        
        let nominator_reward: BalanceOf = portion
            * interest * Perbill::one()
            .saturating_sub(validator_prefs.commission)
            * nominator_exposure.value;
        
        acc + nominator_reward
    })
}

Taler Staking Features

1. Vivid Staking

Additional interest for long-term locking
Parameters: starting_era, ending_era
Calculation: StandartStakingInterest + VividStakingInterestPerMonth * months

2. Issuance Limits

IssuanceLimit: Maximum token issuance
Control through StakingActive flag
Stop staking when limit is reached

3. Custom Data Types

VividStakingState: Vivid staking state
Extended Exposure with vivid support
Special events for vivid operations

4. Economic Model

Base rate: StandartStakingInterest
Additional rate: VividStakingInterestPerMonth * months
Issuance limit: IssuanceLimit
Control through StakingActive

Architectural Decisions

1. Separation of Concerns

mod.rs: Structure, configuration, events, errors
impls.rs: Business logic, algorithms, interfaces

2. Modularity

Separate modules for slashing, weights, benchmarking
Clear separation of storage, events, errors

3. Performance

Optimized storage operations
BoundedVec for size limitations
Efficient election algorithms

4. Security

Checks at all levels
Defensive programming
Input data validation

Conclusion

The Staking pallet is a complex Proof-of-Stake management system with support for:

Standard staking
Vivid staking with additional interest
Flexible reward system
Administrative management
Runtime API for external integrations

The architecture provides high performance, security and scalability of the staking system.

Related Documents