Staking Mechanics

Learn about the staking mechanics in liquid staking protocols.

Last updated: 2024-03-21
Edit on GitHub

Overview

The staking mechanics are the core functionality of your liquid staking protocol. This guide covers the implementation of secure and efficient staking operations, including stake delegation, validator management, and reward distribution.

Staking Mechanics Flow

Stake Delegation

Implement secure stake delegation to validators with proper validation and error handling.

Stake Delegation Implementation

1use solana_program::{
2    program_pack::Pack,
3    stake::state::{Authorized, Lockup, StakeState},
4    system_instruction,
5};
6
7pub struct StakeDelegation {
8    /// Stake pool
9    pub stake_pool: Pubkey,
10    
11    /// Validator vote account
12    pub validator_vote: Pubkey,
13    
14    /// Stake authority
15    pub stake_authority: Pubkey,
16    
17    /// Withdraw authority
18    pub withdraw_authority: Pubkey,
19}
20
21impl StakeDelegation {
22    pub fn create_stake_account(
23        &self,
24        program_id: &Pubkey,
25        payer: &Pubkey,
26        stake_account: &Keypair,
27        amount: u64,
28    ) -> Result<Vec<Instruction>, ProgramError> {
29        let mut instructions = vec![];
30        
31        // Calculate rent-exempt reserve
32        let rent = Rent::get()?;
33        let lamports = rent.minimum_balance(std::mem::size_of::<StakeState>())
34            .checked_add(amount)
35            .ok_or(StakePoolError::CalculationFailure)?;
36            
37        // Create stake account
38        instructions.push(
39            system_instruction::create_account(
40                payer,
41                &stake_account.pubkey(),
42                lamports,
43                std::mem::size_of::<StakeState>() as u64,
44                &stake::program::id(),
45            ),
46        );
47        
48        // Initialize stake account
49        instructions.push(
50            stake::instruction::initialize(
51                &stake_account.pubkey(),
52                &Authorized {
53                    staker: self.stake_authority,
54                    withdrawer: self.withdraw_authority,
55                },
56                &Lockup::default(), // No lockup
57            ),
58        );
59        
60        // Delegate stake
61        instructions.push(
62            stake::instruction::delegate_stake(
63                &stake_account.pubkey(),
64                &self.stake_authority,
65                &self.validator_vote,
66            ),
67        );
68        
69        Ok(instructions)
70    }
71    
72    pub fn redelegate_stake(
73        &self,
74        program_id: &Pubkey,
75        stake_account: &Pubkey,
76        new_validator_vote: &Pubkey,
77    ) -> Result<Vec<Instruction>, ProgramError> {
78        let mut instructions = vec![];
79        
80        // Deactivate current stake
81        instructions.push(
82            stake::instruction::deactivate_stake(
83                stake_account,
84                &self.stake_authority,
85            ),
86        );
87        
88        // Wait for next epoch
89        // Then delegate to new validator
90        instructions.push(
91            stake::instruction::delegate_stake(
92                stake_account,
93                &self.stake_authority,
94                new_validator_vote,
95            ),
96        );
97        
98        Ok(instructions)
99    }
100}

Validator Management

Implement efficient validator selection and management based on performance metrics.

Validator Management Implementation

1pub struct ValidatorManager {
2    /// Maximum number of validators
3    pub max_validators: u32,
4    
5    /// Minimum stake amount per validator
6    pub min_stake_amount: u64,
7    
8    /// Maximum stake amount per validator
9    pub max_stake_amount: u64,
10    
11    /// Performance metrics
12    pub metrics: ValidatorMetrics,
13}
14
15impl ValidatorManager {
16    pub fn select_validators(
17        &self,
18        validators: &[ValidatorInfo],
19        total_stake: u64,
20    ) -> Result<Vec<ValidatorStake>, ProgramError> {
21        // Sort validators by score
22        let mut scored_validators: Vec<_> = validators
23            .iter()
24            .map(|v| {
25                let score = self.calculate_validator_score(v);
26                (v, score)
27            })
28            .collect();
29            
30        scored_validators.sort_by(|(_, a), (_, b)| b.partial_cmp(a).unwrap());
31        
32        // Calculate stake distribution
33        let mut distribution = Vec::new();
34        let mut remaining_stake = total_stake;
35        
36        for (validator, score) in scored_validators {
37            if distribution.len() >= self.max_validators as usize {
38                break;
39            }
40            
41            // Calculate target stake based on score
42            let target_stake = (total_stake as f64 * score) as u64;
43            let target_stake = target_stake
44                .max(self.min_stake_amount)
45                .min(self.max_stake_amount)
46                .min(remaining_stake);
47                
48            if target_stake >= self.min_stake_amount {
49                distribution.push(ValidatorStake {
50                    vote_account: validator.vote_account,
51                    stake_amount: target_stake,
52                });
53                
54                remaining_stake = remaining_stake
55                    .checked_sub(target_stake)
56                    .ok_or(StakePoolError::CalculationFailure)?;
57            }
58        }
59        
60        Ok(distribution)
61    }
62    
63    fn calculate_validator_score(&self, validator: &ValidatorInfo) -> f64 {
64        let mut score = 0.0;
65        
66        // Base score (30%)
67        score += 0.3 * self.calculate_base_score(validator);
68        
69        // Performance score (40%)
70        score += 0.4 * self.calculate_performance_score(validator);
71        
72        // Risk score (30%)
73        score += 0.3 * self.calculate_risk_score(validator);
74        
75        score
76    }
77    
78    fn calculate_base_score(&self, validator: &ValidatorInfo) -> f64 {
79        let mut score = 0.0;
80        
81        // Commission (lower is better)
82        score += (100.0 - validator.commission as f64) / 100.0;
83        
84        // Self stake ratio
85        let self_stake_ratio = validator.self_stake as f64
86            / validator.total_stake as f64;
87        score += self_stake_ratio.min(0.1) * 10.0; // Cap at 10%
88        
89        score / 2.0 // Normalize to 0-1
90    }
91    
92    fn calculate_performance_score(&self, validator: &ValidatorInfo) -> f64 {
93        let mut score = 0.0;
94        
95        // Uptime
96        score += validator.uptime;
97        
98        // Skip rate (lower is better)
99        score += 1.0 - validator.skip_rate;
100        
101        // Vote credits
102        let normalized_credits = (validator.epoch_credits as f64)
103            / self.metrics.max_epoch_credits as f64;
104        score += normalized_credits;
105        
106        score / 3.0 // Normalize to 0-1
107    }
108    
109    fn calculate_risk_score(&self, validator: &ValidatorInfo) -> f64 {
110        let mut score = 0.0;
111        
112        // Stake concentration (lower is better)
113        let concentration = validator.total_stake as f64
114            / self.metrics.total_network_stake as f64;
115        score += 1.0 - concentration;
116        
117        // Version score
118        score += if validator.version >= self.metrics.min_version {
119            1.0
120        } else {
121            0.0
122        };
123        
124        // Delinquent status
125        score += if !validator.delinquent { 1.0 } else { 0.0 };
126        
127        score / 3.0 // Normalize to 0-1
128    }
129}

Reward Distribution

Implement efficient reward collection and distribution with proper fee handling.

Reward Distribution Implementation

1pub struct RewardDistributor {
2    /// Stake pool
3    pub stake_pool: Pubkey,
4    
5    /// Fee account
6    pub fee_account: Pubkey,
7    
8    /// Protocol fee percentage
9    pub protocol_fee: u8,
10    
11    /// Minimum distribution amount
12    pub min_distribution: u64,
13}
14
15impl RewardDistributor {
16    pub fn collect_rewards(
17        &self,
18        program_id: &Pubkey,
19        validator_list: &[ValidatorStakeInfo],
20    ) -> Result<Vec<Instruction>, ProgramError> {
21        let mut instructions = vec![];
22        let mut total_rewards = 0;
23        
24        for validator in validator_list {
25            // Get stake account
26            let (stake_account, _) = Pubkey::find_program_address(
27                &[
28                    &validator.vote_account.to_bytes(),
29                    &self.stake_pool.to_bytes(),
30                ],
31                program_id,
32            );
33            
34            // Calculate rewards
35            let rewards = self.calculate_stake_rewards(&stake_account)?;
36            if rewards > 0 {
37                // Withdraw rewards to reserve
38                instructions.push(
39                    stake::instruction::withdraw(
40                        &stake_account,
41                        &self.stake_pool,
42                        &self.stake_pool,
43                        rewards,
44                        None,
45                    ),
46                );
47                
48                total_rewards = total_rewards
49                    .checked_add(rewards)
50                    .ok_or(StakePoolError::CalculationFailure)?;
51            }
52        }
53        
54        // Process fees
55        if total_rewards >= self.min_distribution {
56            let fee_amount = (total_rewards as u128)
57                .checked_mul(self.protocol_fee as u128)
58                .and_then(|x| x.checked_div(100))
59                .and_then(|x| u64::try_from(x).ok())
60                .ok_or(StakePoolError::CalculationFailure)?;
61                
62            // Transfer fees
63            instructions.push(
64                system_instruction::transfer(
65                    &self.stake_pool,
66                    &self.fee_account,
67                    fee_amount,
68                ),
69            );
70            
71            // Update pool token supply to reflect rewards
72            let remaining_rewards = total_rewards
73                .checked_sub(fee_amount)
74                .ok_or(StakePoolError::CalculationFailure)?;
75                
76            instructions.push(
77                self.update_pool_token_supply(remaining_rewards)?,
78            );
79        }
80        
81        Ok(instructions)
82    }
83    
84    fn calculate_stake_rewards(
85        &self,
86        stake_account: &Pubkey,
87    ) -> Result<u64, ProgramError> {
88        // Get stake account data
89        let account = get_stake_account(stake_account)?;
90        
91        // Calculate rewards since last collection
92        let rewards = account
93            .rewards_since_last_collection()
94            .ok_or(StakePoolError::CalculationFailure)?;
95            
96        Ok(rewards)
97    }
98    
99    fn update_pool_token_supply(
100        &self,
101        rewards: u64,
102    ) -> Result<Instruction, ProgramError> {
103        // Calculate new tokens based on rewards
104        let pool_tokens = (rewards as f64 * self.get_exchange_rate()?) as u64;
105        
106        // Create mint instruction
107        Ok(spl_token::instruction::mint_to(
108            &spl_token::id(),
109            &self.pool_mint,
110            &self.pool_token_account,
111            &self.authority,
112            &[],
113            pool_tokens,
114        )?)
115    }
116}

Exchange Rate Management

Implement accurate exchange rate calculations and updates.

Exchange Rate Implementation

1pub struct ExchangeRate {
2    /// Total SOL (including rewards)
3    pub total_sol: u64,
4    
5    /// Total stSOL supply
6    pub total_st_sol: u64,
7    
8    /// Last update epoch
9    pub last_update_epoch: u64,
10}
11
12impl ExchangeRate {
13    pub fn calculate_rate(&self) -> Result<f64, ProgramError> {
14        if self.total_st_sol == 0 {
15            return Ok(1.0);
16        }
17        
18        Ok(self.total_sol as f64 / self.total_st_sol as f64)
19    }
20    
21    pub fn sol_to_st_sol(
22        &self,
23        sol_amount: u64,
24    ) -> Result<u64, ProgramError> {
25        let rate = self.calculate_rate()?;
26        Ok((sol_amount as f64 / rate) as u64)
27    }
28    
29    pub fn st_sol_to_sol(
30        &self,
31        st_sol_amount: u64,
32    ) -> Result<u64, ProgramError> {
33        let rate = self.calculate_rate()?;
34        Ok((st_sol_amount as f64 * rate) as u64)
35    }
36    
37    pub fn update_with_rewards(
38        &mut self,
39        rewards: u64,
40        epoch: u64,
41    ) -> ProgramResult {
42        // Add rewards to total SOL
43        self.total_sol = self.total_sol
44            .checked_add(rewards)
45            .ok_or(StakePoolError::CalculationFailure)?;
46            
47        // Update epoch
48        self.last_update_epoch = epoch;
49        
50        Ok(())
51    }
52}
Implementation Considerations
  • Always use checked math operations to prevent overflows
  • Implement proper validation for all operations
  • Consider gas efficiency in validator selection
  • Maintain accurate exchange rates
  • Implement proper slashing protection
  • Add comprehensive event logging