Staking Functions Detailed

Detailed Analysis of Key Staking Pallet Functions

1. Staking Ledger Management

1.1 Getting and Updating Ledger

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

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

Purpose: Managing relationships between stash and controller accounts, retrieving staking information.

1.2 Slashable Balance Calculation

pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf {
    Self::ledger(Stash(stash.clone())).map(|l| l.active).unwrap_or_default()
}

pub fn slashable_balance_of_vote_weight(
    stash: &T::AccountId,
    issuance: BalanceOf,
) -> VoteWeight {
    T::CurrencyToVote::to_vote(Self::slashable_balance_of(stash), issuance)
}

Purpose: Determining the amount that can be slashed and converting to votes for elections.

2. Interest and Reward System

2.1 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()
}

Calculation Logic:

1. Check vivid staking activity in current era

2. Count number of locked months

3. Calculation: base_rate + additional_rate * months

2.2 Reward Payout

pub(super) fn do_payout_stakers(
    validator_stash: T::AccountId,
    era: EraIndex,
) -> DispatchResultWithPostInfo {
    // Input data validation
    let current_era = CurrentEra::::get().ok_or_else(|| {
        Error::::InvalidEraToReward
            .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))
    })?;
    
    // Era history check
    let history_depth = T::HistoryDepth::get();
    ensure!(
        era <= current_era && era >= current_era.saturating_sub(history_depth),
        Error::::InvalidEraToReward
    );
    
    // 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())
}

Key Steps:

1. Era validation and payout permission check

2. Get exposure (nominators and their stakes)

3. Calculate interest considering vivid staking

4. Deduct validator commission

5. Create payouts via make_payout()

2.3 Creating Payouts

fn make_payout(
    stash: &T::AccountId,
    amount: BalanceOf,
) -> Option<(PositiveImbalanceOf, RewardDestination)> {
    let dest = Self::payee(StakingAccount::Stash(stash.clone()));
    let maybe_imbalance = match dest {
        RewardDestination::Controller => Self::bonded(stash)
            .map(|controller| T::Currency::deposit_creating(&controller, amount)),
        RewardDestination::Stash => T::Currency::deposit_into_existing(stash, amount).ok(),
        RewardDestination::Staked => Self::ledger(Stash(stash.clone()))
            .and_then(|mut ledger| {
                ledger.active += amount;
                ledger.total += amount;
                let r = T::Currency::deposit_into_existing(stash, amount).ok();
                let _ = ledger.update().defensive_proof("ledger fetched from storage, so it exists; qed.");
                Ok(r)
            })
            .unwrap_or_default(),
        RewardDestination::Account(dest_account) => {
            Some(T::Currency::deposit_creating(&dest_account, amount))
        },
        RewardDestination::None => None,
    };
    maybe_imbalance.map(|imbalance| (imbalance, Self::payee(StakingAccount::Stash(stash.clone()))))
}

Reward Recipient Types:

Controller: To controller account
Stash: To stash account
Staked: Automatic addition to stake
Account: To specified account
None: No payout

3. Era Management

3.1 Planning New Session

fn new_session(
    session_index: SessionIndex,
    is_genesis: bool,
) -> Option>> {
    if let Some(current_era) = Self::current_era() {
        let current_era_start_session_index = Self::eras_start_session_index(current_era)
            .unwrap_or_else(|| {
                frame_support::print("Error: start_session_index must be set for current_era");
                0
            });
        
        let era_length = session_index.saturating_sub(current_era_start_session_index);
        
        match ForceEra::::get() {
            Forcing::ForceNew => (),
            Forcing::ForceAlways => (),
            Forcing::NotForcing if era_length >= T::SessionsPerEra::get() => (),
            _ => return None,
        }
        
        let maybe_new_era_validators = Self::try_trigger_new_era(session_index, is_genesis);
        if maybe_new_era_validators.is_some() && matches!(ForceEra::::get(), Forcing::ForceNew) {
            Self::set_force_era(Forcing::NotForcing);
        }
        
        maybe_new_era_validators
    } else {
        Self::try_trigger_new_era(session_index, is_genesis)
    }
}

Planning Logic:

1. Check current era

2. Analyze forcing modes (ForceNew, ForceAlways)

3. Check if session limit in era is reached

4. Trigger new era if necessary

3.2 Starting New Era

fn start_era(start_session: SessionIndex) {
    let active_era = ActiveEra::::mutate(|active_era| {
        let new_index = active_era.as_ref().map(|info| info.index + 1).unwrap_or(0);
        *active_era = Some(ActiveEraInfo {
            index: new_index,
            start: None,
        });
        new_index
    });
    
    let bonding_duration = T::BondingDuration::get();
    
    BondedEras::::mutate(|bonded| {
        bonded.push((active_era, start_session));
        
        if active_era > bonding_duration {
            let first_kept = active_era - bonding_duration;
            let n_to_prune = bonded.iter().take_while(|&&(era_idx, _)| era_idx < first_kept).count();
            
            for (pruned_era, _) in bonded.drain(..n_to_prune) {
                slashing::clear_era_metadata::(pruned_era);
            }
            
            if let Some(&(_, first_session)) = bonded.first() {
                T::SessionInterface::prune_historical_up_to(first_session);
            }
        }
    });
    
    Self::apply_unapplied_slashes(active_era);
}

Actions on Era Start:

1. Increment active era index

2. Update BondedEras

3. Clean old data (slashing metadata)

4. Apply deferred slashes

3.3 Ending Era

fn end_era(active_era: ActiveEraInfo, _session_index: SessionIndex) {
    if let Some(active_era_start) = active_era.start {
        let now_as_millis_u64 = T::UnixTime::now().as_millis().saturated_into::();
        let era_duration = (now_as_millis_u64 - active_era_start).saturated_into::();
        let staked = Self::eras_total_stake(&active_era.index);
        let issuance = T::Currency::total_issuance();
        
        let portion = Perbill::from_rational(era_duration as u64, MILLISECONDS_PER_YEAR);
        let estimated_interest = T::StandartStakingInterest::get()
            + Perbill::from_parts(T::VividStakingInterestPerMonth::get().deconstruct() * 12);
        
        let estimated_payout = estimated_interest * portion * staked;
        
        if (issuance + estimated_payout) <= T::IssuanceLimit::get() && >::get() {
            Self::deposit_event(Event::::EraPaid { era_index: active_era.index });
            >::insert(&active_era.index, era_duration);
        } else {
            >::set(false);
        }
        
        >::kill();
    }
}

Ending Logic:

1. Calculate era duration

2. Estimate payouts considering issuance limit

3. Stop staking when limit is exceeded

4. Clean offender data

4. Election Provider Implementation

4.1 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
}

Voter Retrieval Algorithm:

1. Iterate through VoterList

2. Filter by weight (exclude zero weights)

3. Separate nominators and validators

4. Apply bounds constraints

5. Track minimum active stake

4.2 Getting Targets

pub fn get_npos_targets(bounds: DataProviderBounds) -> Vec {
    let mut targets_size_tracker: StaticTracker = StaticTracker::default();
    let final_predicted_len = {
        let all_target_count = T::TargetList::count();
        bounds.count.unwrap_or(all_target_count.into()).min(all_target_count.into()).0
    };
    
    let mut all_targets = Vec::::with_capacity(final_predicted_len as usize);
    let mut targets_seen = 0;
    
    let mut targets_iter = T::TargetList::iter();
    while all_targets.len() < final_predicted_len as usize
        && targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
    {
        let target = match targets_iter.next() {
            Some(target) => {
                targets_seen.saturating_inc();
                target
            },
            None => break,
        };
        
        if targets_size_tracker.try_register_target(target.clone(), &bounds).is_err() {
            Self::deposit_event(Event::::SnapshotTargetsSizeExceeded {
                size: targets_size_tracker.size as u32,
            });
            break;
        }
        
        if Validators::::contains_key(&target) {
            all_targets.push(target);
        }
    }
    
    all_targets
}

5. Runtime API

5.1 Nominations Quota

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

5.2 Payout Estimation

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
    })
}

Estimation Algorithm:

1. Get active era

2. Calculate era duration

3. Iterate through nominated validators

4. Calculate rewards considering vivid staking and commissions

5. Sum all potential payouts

Conclusion

The Staking pallet implements a complex economic model with support for:

Flexible interest system (base + vivid)
Issuance limits
Efficient election algorithms
Runtime API for external integrations
Protection against abuse

The architecture provides high performance and security for the Proof-of-Stake system.