Liquid Staking
Learn about the core concepts and implementation of liquid staking on Solana.
Prerequisites
- Strong understanding of Solana's architecture and stake program
- Experience with Rust and Solana program development
- Familiarity with SPL token standards
- Basic knowledge of validator operations
Understanding Liquid Staking
Liquid staking protocols enable users to stake their SOL while receiving a liquid token representation (stSOL) that can be used across the DeFi ecosystem. This mechanism solves the capital efficiency problem of traditional staking while maintaining the security benefits of the Solana network.
Diagram: Flow of assets and operations in a liquid staking protocol
Protocol Components
- •Stake Pool
Central contract managing stake accounts and token minting
- •Validator List
Manages approved validators and stake distribution
- •Token Mint
SPL token representing staked SOL (stSOL)
- •Fee Management
Handles protocol fees and reward distribution
Key Features
- •Liquid Staking
Stake SOL while maintaining liquidity through stSOL
- •Validator Management
Automated selection and stake distribution
- •Reward Distribution
Efficient staking rewards collection and distribution
- •Safety Features
Slashing protection and emergency procedures
Protocol Architecture
The protocol is built on several key smart contracts that work together to provide secure and efficient liquid staking services:
Core Program Structure
1use solana_program::{
2 account_info::AccountInfo,
3 entrypoint,
4 entrypoint::ProgramResult,
5 pubkey::Pubkey,
6 program_error::ProgramError,
7};
8
9// Program state structures
10#[derive(Clone, Debug, PartialEq)]
11pub struct StakePool {
12 /// Protocol version
13 pub version: u8,
14
15 /// Manager authority
16 pub manager: Pubkey,
17
18 /// Staker authority
19 pub staker: Pubkey,
20
21 /// Pool token mint
22 pub pool_mint: Pubkey,
23
24 /// Total pool stake
25 pub total_stake_lamports: u64,
26
27 /// Total supply of pool tokens
28 pub pool_token_supply: u64,
29
30 /// Last epoch stake pool was updated
31 pub last_update_epoch: u64,
32
33 /// Fee schedule
34 pub fee_schedule: FeeSchedule,
35}
36
37// Program entry point
38entrypoint!(process_instruction);
39
40pub fn process_instruction(
41 program_id: &Pubkey,
42 accounts: &[AccountInfo],
43 instruction_data: &[u8],
44) -> ProgramResult {
45 // Process instructions based on discriminator
46 let instruction = StakePoolInstruction::try_from_slice(instruction_data)?;
47 match instruction {
48 StakePoolInstruction::Initialize { fee_schedule } => {
49 process_initialize(program_id, accounts, fee_schedule)
50 }
51 StakePoolInstruction::Deposit { amount } => {
52 process_deposit(program_id, accounts, amount)
53 }
54 StakePoolInstruction::Withdraw { amount } => {
55 process_withdraw(program_id, accounts, amount)
56 }
57 // ... other instructions
58 }
59}
Important Considerations
- Proper access controls and authority management
- Secure state transitions and data validation
- Efficient stake rebalancing strategies
- Transparent fee structures and reward distribution
- Emergency procedures and safety mechanisms
Core Implementation
The core implementation of a liquid staking protocol involves several key components working together to provide secure and efficient staking services. This section covers the essential implementation details.
Program State
Define the core program state structures that will manage the protocol's data:
State Structures
1use solana_program::{
2 program_pack::{IsInitialized, Pack, Sealed},
3 pubkey::Pubkey,
4};
5
6#[derive(Clone, Debug, Default, PartialEq)]
7pub struct StakePool {
8 /// Protocol version for upgrade management
9 pub version: u8,
10
11 /// Manager authority for admin operations
12 pub manager: Pubkey,
13
14 /// Staker authority for validator management
15 pub staker: Pubkey,
16
17 /// Withdraw authority for stake accounts
18 pub withdraw_authority: Pubkey,
19
20 /// Validator list storage account
21 pub validator_list: Pubkey,
22
23 /// Reserve stake account
24 pub reserve_stake: Pubkey,
25
26 /// Pool token mint
27 pub pool_mint: Pubkey,
28
29 /// Manager fee account
30 pub manager_fee_account: Pubkey,
31
32 /// Total pool stake in lamports
33 pub total_lamports: u64,
34
35 /// Total supply of pool tokens
36 pub pool_token_supply: u64,
37
38 /// Last epoch stake pool was updated
39 pub last_update_epoch: u64,
40
41 /// Fee schedule
42 pub fee_schedule: FeeSchedule,
43
44 /// Protocol state
45 pub state: ProtocolState,
46}
47
48impl Sealed for StakePool {}
49
50impl Pack for StakePool {
51 const LEN: usize = 165; // Calculated size
52
53 fn pack_into_slice(&self, dst: &mut [u8]) {
54 let mut cursor = std::io::Cursor::new(dst);
55 bincode::serialize_into(&mut cursor, self).unwrap();
56 }
57
58 fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
59 let pool: StakePool = bincode::deserialize(src)
60 .map_err(|_| ProgramError::InvalidAccountData)?;
61 Ok(pool)
62 }
63}
Instruction Processing
Implement the core instruction processing logic:
Instruction Processing
1#[derive(Clone, Debug, PartialEq)]
2pub enum StakePoolInstruction {
3 /// Initialize a new stake pool
4 Initialize {
5 fee_schedule: FeeSchedule,
6 },
7
8 /// Add liquidity to the pool
9 Deposit {
10 amount: u64,
11 },
12
13 /// Remove liquidity from the pool
14 Withdraw {
15 amount: u64,
16 },
17
18 /// Update validator list
19 UpdateValidatorList {
20 validators: Vec<Pubkey>,
21 },
22
23 /// Update pool balance and collect rewards
24 UpdatePoolBalance,
25
26 /// Emergency operations
27 EmergencyOperation {
28 operation_type: EmergencyOperationType,
29 },
30}
31
32pub fn process_instruction(
33 program_id: &Pubkey,
34 accounts: &[AccountInfo],
35 instruction_data: &[u8],
36) -> ProgramResult {
37 let instruction = StakePoolInstruction::try_from_slice(instruction_data)?;
38
39 match instruction {
40 StakePoolInstruction::Initialize { fee_schedule } => {
41 process_initialize(program_id, accounts, fee_schedule)
42 }
43 StakePoolInstruction::Deposit { amount } => {
44 process_deposit(program_id, accounts, amount)
45 }
46 StakePoolInstruction::Withdraw { amount } => {
47 process_withdraw(program_id, accounts, amount)
48 }
49 StakePoolInstruction::UpdateValidatorList { validators } => {
50 process_update_validator_list(program_id, accounts, validators)
51 }
52 StakePoolInstruction::UpdatePoolBalance => {
53 process_update_pool_balance(program_id, accounts)
54 }
55 StakePoolInstruction::EmergencyOperation { operation_type } => {
56 process_emergency_operation(program_id, accounts, operation_type)
57 }
58 }
59}
Account Management
Implement secure account management and validation:
Account Management
1pub fn validate_stake_pool_accounts(
2 program_id: &Pubkey,
3 stake_pool: &AccountInfo,
4 validator_list: &AccountInfo,
5 pool_mint: &AccountInfo,
6 authority: &AccountInfo,
7) -> ProgramResult {
8 // Verify account ownership
9 if stake_pool.owner != program_id {
10 return Err(ProgramError::IncorrectProgramId);
11 }
12
13 // Verify account data
14 let pool_data = StakePool::try_from_slice(&stake_pool.data.borrow())?;
15
16 // Verify authority
17 if authority.key != &pool_data.manager {
18 return Err(StakePoolError::InvalidAuthority.into());
19 }
20
21 // Verify validator list
22 if validator_list.key != &pool_data.validator_list {
23 return Err(StakePoolError::InvalidValidatorList.into());
24 }
25
26 // Verify pool mint
27 if pool_mint.key != &pool_data.pool_mint {
28 return Err(StakePoolError::InvalidPoolMint.into());
29 }
30
31 Ok(())
32}
33
34pub fn create_program_address(
35 program_id: &Pubkey,
36 seeds: &[&[u8]],
37) -> Result<Pubkey, ProgramError> {
38 Pubkey::create_program_address(seeds, program_id)
39 .map_err(|_| ProgramError::InvalidSeeds)
40}
41
42pub fn find_stake_program_address(
43 program_id: &Pubkey,
44 validator_list: &Pubkey,
45 stake_pool: &Pubkey,
46) -> (Pubkey, u8) {
47 Pubkey::find_program_address(
48 &[
49 validator_list.as_ref(),
50 stake_pool.as_ref(),
51 ],
52 program_id,
53 )
54}
Security Best Practices
- Always validate account ownership and data
- Implement proper error handling for all operations
- Use program derived addresses (PDAs) for authority management
- Include comprehensive tests for all functionality
- Consider adding upgrade mechanisms for future improvements
Token Management
Efficient token management is crucial for a liquid staking protocol. This section covers token minting, burning, and exchange rate calculations.
Token Setup
Initialize the liquid staking token (stSOL) using the SPL Token program:
Token Initialization
1import {
2 Token,
3 TOKEN_PROGRAM_ID,
4 MintLayout,
5 AccountLayout,
6} from '@solana/spl-token'
7import { Connection, Keypair, PublicKey, SystemProgram } from '@solana/web3.js'
8
9async function initializeStakeToken(
10 connection: Connection,
11 payer: Keypair,
12 authority: PublicKey,
13) {
14 // Calculate space and rent
15 const mintRent = await connection.getMinimumBalanceForRentExemption(
16 MintLayout.span
17 )
18
19 // Generate mint account
20 const mintAccount = Keypair.generate()
21
22 // Create mint account
23 const createMintAccountIx = SystemProgram.createAccount({
24 fromPubkey: payer.publicKey,
25 newAccountPubkey: mintAccount.publicKey,
26 lamports: mintRent,
27 space: MintLayout.span,
28 programId: TOKEN_PROGRAM_ID,
29 })
30
31 // Initialize mint
32 const initMintIx = Token.createInitMintInstruction(
33 TOKEN_PROGRAM_ID,
34 mintAccount.publicKey,
35 9, // 9 decimals to match SOL
36 authority,
37 authority, // Freeze authority (optional)
38 )
39
40 // Create and send transaction
41 const tx = new Transaction()
42 .add(createMintAccountIx)
43 .add(initMintIx)
44
45 await connection.sendTransaction(tx, [payer, mintAccount])
46
47 return mintAccount.publicKey
48}
Exchange Rate Management
Implement accurate exchange rate calculation and updates:
Exchange Rate
1#[derive(Clone, Debug, Default, PartialEq)]
2pub struct ExchangeRate {
3 /// Total SOL (including rewards)
4 pub total_sol: u64,
5
6 /// Total stSOL supply
7 pub total_st_sol: u64,
8
9 /// Last update epoch
10 pub last_update_epoch: u64,
11}
12
13impl ExchangeRate {
14 pub fn calculate_rate(&self) -> Result<f64, ProgramError> {
15 if self.total_st_sol == 0 {
16 return Ok(1.0);
17 }
18
19 Ok(self.total_sol as f64 / self.total_st_sol as f64)
20 }
21
22 pub fn sol_to_st_sol(&self, sol_amount: u64) -> Result<u64, ProgramError> {
23 let rate = self.calculate_rate()?;
24 Ok((sol_amount as f64 / rate) as u64)
25 }
26
27 pub fn st_sol_to_sol(&self, st_sol_amount: u64) -> Result<u64, ProgramError> {
28 let rate = self.calculate_rate()?;
29 Ok((st_sol_amount as f64 * rate) as u64)
30 }
31
32 pub fn update_with_rewards(
33 &mut self,
34 rewards: u64,
35 epoch: u64,
36 ) -> ProgramResult {
37 // Add rewards to total SOL
38 self.total_sol = self.total_sol
39 .checked_add(rewards)
40 .ok_or(StakePoolError::CalculationFailure)?;
41
42 // Update epoch
43 self.last_update_epoch = epoch;
44
45 Ok(())
46 }
47}
Token Operations
Implement secure token minting and burning operations:
Token Operations
1pub struct TokenManager {
2 /// Pool token mint
3 pub pool_mint: Pubkey,
4
5 /// Mint authority
6 pub mint_authority: Pubkey,
7
8 /// Exchange rate
9 pub exchange_rate: ExchangeRate,
10}
11
12impl TokenManager {
13 pub fn mint_tokens(
14 &self,
15 amount: u64,
16 destination: &Pubkey,
17 ) -> ProgramResult {
18 // Calculate token amount based on exchange rate
19 let token_amount = self.exchange_rate
20 .sol_to_st_sol(amount)?;
21
22 // Create mint instruction
23 let mint_ix = spl_token::instruction::mint_to(
24 &spl_token::id(),
25 &self.pool_mint,
26 destination,
27 &self.mint_authority,
28 &[],
29 token_amount,
30 )?;
31
32 // Process instruction
33 invoke_signed(
34 &mint_ix,
35 &[/* account infos */],
36 &[/* seeds */],
37 )?;
38
39 Ok(())
40 }
41
42 pub fn burn_tokens(
43 &self,
44 token_amount: u64,
45 source: &Pubkey,
46 ) -> ProgramResult {
47 // Calculate SOL amount based on exchange rate
48 let sol_amount = self.exchange_rate
49 .st_sol_to_sol(token_amount)?;
50
51 // Create burn instruction
52 let burn_ix = spl_token::instruction::burn(
53 &spl_token::id(),
54 source,
55 &self.pool_mint,
56 &self.mint_authority,
57 &[],
58 token_amount,
59 )?;
60
61 // Process instruction
62 invoke_signed(
63 &burn_ix,
64 &[/* account infos */],
65 &[/* seeds */],
66 )?;
67
68 Ok(())
69 }
70}
Token Management Best Practices
- Use checked math operations to prevent overflows
- Maintain accurate exchange rates with proper decimal handling
- Implement proper authority checks for all token operations
- Consider implementing token supply caps if needed
- Add events/logs for important token operations
Validator Operations
Effective validator management is crucial for protocol security and performance. This section covers validator selection, monitoring, and stake management.
Validator Selection
Implement criteria-based validator selection with scoring mechanism:
Validator Selection
1pub struct ValidatorScore {
2 /// Vote account address
3 pub vote_account: Pubkey,
4
5 /// Base score (0-100)
6 pub base_score: u8,
7
8 /// Performance metrics
9 pub performance: ValidatorPerformance,
10
11 /// Risk metrics
12 pub risk_score: ValidatorRisk,
13}
14
15#[derive(Clone, Debug)]
16pub struct ValidatorPerformance {
17 /// Uptime percentage (0-100)
18 pub uptime: u8,
19
20 /// Average skip rate
21 pub skip_rate: f64,
22
23 /// Commission percentage
24 pub commission: u8,
25
26 /// Stake concentration
27 pub stake_concentration: f64,
28
29 /// Historical performance score
30 pub historical_performance: u8,
31}
32
33impl ValidatorScore {
34 pub fn calculate_score(&self) -> u8 {
35 let mut score = self.base_score;
36
37 // Adjust for performance
38 score = score
39 .checked_mul(self.performance.uptime)
40 .unwrap_or(0)
41 .checked_div(100)
42 .unwrap_or(0);
43
44 // Penalize for high skip rate
45 if self.performance.skip_rate > 0.01 {
46 score = score.saturating_sub(10);
47 }
48
49 // Penalize for high commission
50 if self.performance.commission > 10 {
51 score = score.saturating_sub(
52 self.performance.commission.saturating_sub(10)
53 );
54 }
55
56 // Penalize for stake concentration
57 if self.performance.stake_concentration > 0.1 {
58 score = score.saturating_sub(10);
59 }
60
61 score
62 }
63}
Stake Management
Implement secure stake account management and delegation:
Stake Management
1pub struct StakeManager {
2 /// Stake pool
3 pub stake_pool: Pubkey,
4
5 /// Validator list
6 pub validator_list: Pubkey,
7
8 /// Reserve stake account
9 pub reserve_stake: Pubkey,
10
11 /// Stake authority
12 pub stake_authority: Pubkey,
13}
14
15impl StakeManager {
16 pub fn create_validator_stake_account(
17 &self,
18 program_id: &Pubkey,
19 validator: &Pubkey,
20 seed: &[u8],
21 ) -> Result<Pubkey, ProgramError> {
22 let (stake_account, _) = Pubkey::find_program_address(
23 &[
24 validator.as_ref(),
25 seed,
26 ],
27 program_id,
28 );
29
30 Ok(stake_account)
31 }
32
33 pub fn delegate_stake(
34 &self,
35 stake_account: &Pubkey,
36 validator_vote: &Pubkey,
37 amount: u64,
38 ) -> ProgramResult {
39 // Create stake account if needed
40 let create_stake_ix = system_instruction::create_account(
41 &self.stake_pool,
42 stake_account,
43 amount,
44 std::mem::size_of::<StakeState>() as u64,
45 &stake::program::id(),
46 );
47
48 // Initialize stake account
49 let init_stake_ix = stake::instruction::initialize(
50 stake_account,
51 &stake::state::Authorized {
52 staker: self.stake_authority,
53 withdrawer: self.stake_authority,
54 },
55 &stake::state::Lockup::default(),
56 );
57
58 // Delegate stake
59 let delegate_ix = stake::instruction::delegate_stake(
60 stake_account,
61 &self.stake_authority,
62 validator_vote,
63 );
64
65 // Process instructions
66 invoke_signed(
67 &create_stake_ix,
68 &[/* account infos */],
69 &[/* seeds */],
70 )?;
71
72 invoke_signed(
73 &init_stake_ix,
74 &[/* account infos */],
75 &[/* seeds */],
76 )?;
77
78 invoke_signed(
79 &delegate_ix,
80 &[/* account infos */],
81 &[/* seeds */],
82 )?;
83
84 Ok(())
85 }
86
87 pub fn rebalance_stakes(
88 &self,
89 validators: &[ValidatorStakeInfo],
90 target_stake: u64,
91 ) -> ProgramResult {
92 for validator in validators {
93 let current_stake = validator.active_stake_lamports;
94
95 if current_stake > target_stake {
96 // Decrease stake
97 self.decrease_validator_stake(
98 &validator.vote_account_address,
99 current_stake - target_stake,
100 )?;
101 } else if current_stake < target_stake {
102 // Increase stake
103 self.increase_validator_stake(
104 &validator.vote_account_address,
105 target_stake - current_stake,
106 )?;
107 }
108 }
109
110 Ok(())
111 }
112}
Performance Monitoring
Implement continuous validator performance monitoring:
Performance Monitoring
1pub struct ValidatorMonitor {
2 /// Performance thresholds
3 pub thresholds: MonitorThresholds,
4
5 /// Historical performance data
6 pub history: ValidatorHistory,
7}
8
9impl ValidatorMonitor {
10 pub fn update_performance(
11 &mut self,
12 validator: &Pubkey,
13 epoch_stats: &EpochStats,
14 ) -> ValidatorStatus {
15 // Calculate performance metrics
16 let uptime = calculate_uptime(epoch_stats);
17 let skip_rate = calculate_skip_rate(epoch_stats);
18 let apy = calculate_apy(epoch_stats);
19
20 // Update historical data
21 self.history.update(validator, epoch_stats);
22
23 // Check against thresholds
24 if uptime < self.thresholds.min_uptime {
25 return ValidatorStatus::Delinquent;
26 }
27
28 if skip_rate > self.thresholds.max_skip_rate {
29 return ValidatorStatus::Delinquent;
30 }
31
32 if apy < self.thresholds.min_apy {
33 return ValidatorStatus::Underperforming;
34 }
35
36 ValidatorStatus::Active
37 }
38
39 pub fn handle_validator_emergency(
40 &self,
41 stake_pool: &mut StakePool,
42 validator: &Pubkey,
43 ) -> ProgramResult {
44 // Move stake to emergency reserve
45 stake_pool.emergency_unstake(validator)?;
46
47 // Mark validator as emergency
48 stake_pool.update_validator_status(
49 validator,
50 ValidatorStatus::Emergency,
51 )?;
52
53 // Emit emergency event
54 emit!(ValidatorEmergency {
55 stake_pool: stake_pool.pubkey(),
56 validator: *validator,
57 timestamp: Clock::get()?.unix_timestamp,
58 });
59
60 Ok(())
61 }
62}
Validator Management Best Practices
- Implement gradual stake changes to avoid market impact
- Monitor validator performance and uptime continuously
- Maintain proper stake distribution across validators
- Implement emergency procedures for validator misbehavior
- Consider implementing a grace period before removing validators
Reward Mechanics
Efficient reward distribution is crucial for protocol success. This section covers reward collection, calculation, and distribution mechanisms.
Reward Collection
Implement secure reward collection from validators:
Reward Collection
1pub struct RewardCollector {
2 /// Stake pool
3 pub stake_pool: Pubkey,
4
5 /// Reserve stake account
6 pub reserve_stake: Pubkey,
7
8 /// Fee account
9 pub fee_account: Pubkey,
10
11 /// Collection thresholds
12 pub thresholds: CollectionThresholds,
13}
14
15impl RewardCollector {
16 pub fn collect_validator_rewards(
17 &self,
18 validator_list: &[ValidatorStakeInfo],
19 ) -> ProgramResult {
20 let mut total_rewards = 0;
21
22 for validator in validator_list {
23 // Get stake account
24 let stake_account = self.get_validator_stake_account(
25 &validator.vote_account_address,
26 )?;
27
28 // Calculate rewards
29 let rewards = calculate_stake_rewards(&stake_account)?;
30
31 if rewards > self.thresholds.min_collection_amount {
32 // Withdraw rewards to reserve
33 self.withdraw_stake_rewards(
34 &stake_account,
35 &self.reserve_stake,
36 rewards,
37 )?;
38
39 total_rewards = total_rewards
40 .checked_add(rewards)
41 .ok_or(StakePoolError::CalculationFailure)?;
42 }
43 }
44
45 // Update pool metrics
46 self.update_pool_metrics(total_rewards)?;
47
48 // Emit collection event
49 emit!(RewardsCollected {
50 stake_pool: self.stake_pool,
51 total_rewards,
52 epoch: Clock::get()?.epoch,
53 });
54
55 Ok(())
56 }
57
58 fn withdraw_stake_rewards(
59 &self,
60 stake_account: &Pubkey,
61 reserve_stake: &Pubkey,
62 amount: u64,
63 ) -> ProgramResult {
64 // Create withdraw instruction
65 let withdraw_ix = stake::instruction::withdraw(
66 stake_account,
67 &self.stake_pool,
68 reserve_stake,
69 amount,
70 &[/* additional signers */],
71 );
72
73 // Process instruction
74 invoke_signed(
75 &withdraw_ix,
76 &[/* account infos */],
77 &[/* seeds */],
78 )?;
79
80 Ok(())
81 }
82}
Reward Distribution
Implement efficient reward distribution to token holders:
Reward Distribution
1pub struct RewardDistributor {
2 /// Distribution frequency (epochs)
3 pub distribution_frequency: u64,
4
5 /// Last distribution epoch
6 pub last_distribution: u64,
7
8 /// Minimum distribution amount
9 pub min_distribution: u64,
10
11 /// Fee schedule
12 pub fee_schedule: FeeSchedule,
13}
14
15impl RewardDistributor {
16 pub fn distribute_rewards(
17 &mut self,
18 stake_pool: &mut StakePool,
19 total_rewards: u64,
20 ) -> ProgramResult {
21 let current_epoch = Clock::get()?.epoch;
22
23 // Check if distribution is needed
24 if current_epoch.saturating_sub(self.last_distribution)
25 < self.distribution_frequency {
26 return Ok(());
27 }
28
29 // Check minimum distribution
30 if total_rewards < self.min_distribution {
31 return Ok(());
32 }
33
34 // Calculate fees
35 let fees = self.fee_schedule.calculate_reward_fees(total_rewards)?;
36 let protocol_fee = fees.protocol_fee;
37 let treasury_fee = fees.treasury_fee;
38
39 // Calculate net rewards
40 let net_rewards = total_rewards
41 .checked_sub(protocol_fee)?
42 .checked_sub(treasury_fee)?;
43
44 // Update exchange rate
45 stake_pool.exchange_rate.update_with_rewards(
46 net_rewards,
47 current_epoch,
48 )?;
49
50 // Transfer fees
51 self.transfer_protocol_fee(protocol_fee)?;
52 self.transfer_treasury_fee(treasury_fee)?;
53
54 // Update distribution state
55 self.last_distribution = current_epoch;
56
57 // Emit distribution event
58 emit!(RewardsDistributed {
59 stake_pool: stake_pool.pubkey(),
60 total_rewards,
61 net_rewards,
62 protocol_fee,
63 treasury_fee,
64 epoch: current_epoch,
65 });
66
67 Ok(())
68 }
69}
Fee Management
Implement transparent fee calculation and management:
Fee Management
1#[derive(Clone, Debug, Default, PartialEq)]
2pub struct FeeSchedule {
3 /// Protocol fee percentage (0-100)
4 pub protocol_fee: u8,
5
6 /// Treasury fee percentage (0-100)
7 pub treasury_fee: u8,
8
9 /// Validator commission (0-100)
10 pub validator_commission: u8,
11}
12
13impl FeeSchedule {
14 pub fn calculate_reward_fees(
15 &self,
16 reward_amount: u64,
17 ) -> Result<FeeBreakdown, ProgramError> {
18 // Calculate protocol fee
19 let protocol_fee = (reward_amount as u128)
20 .checked_mul(self.protocol_fee as u128)
21 .and_then(|r| r.checked_div(100))
22 .and_then(|r| u64::try_from(r).ok())
23 .ok_or(StakePoolError::CalculationFailure)?;
24
25 // Calculate treasury fee
26 let treasury_fee = (reward_amount as u128)
27 .checked_mul(self.treasury_fee as u128)
28 .and_then(|r| r.checked_div(100))
29 .and_then(|r| u64::try_from(r).ok())
30 .ok_or(StakePoolError::CalculationFailure)?;
31
32 // Calculate validator commission
33 let validator_commission = (reward_amount as u128)
34 .checked_mul(self.validator_commission as u128)
35 .and_then(|r| r.checked_div(100))
36 .and_then(|r| u64::try_from(r).ok())
37 .ok_or(StakePoolError::CalculationFailure)?;
38
39 Ok(FeeBreakdown {
40 protocol_fee,
41 treasury_fee,
42 validator_commission,
43 })
44 }
45
46 pub fn update_fee_schedule(
47 &mut self,
48 new_schedule: FeeSchedule,
49 authority: &AccountInfo,
50 ) -> ProgramResult {
51 // Verify fee authority
52 verify_fee_authority(authority)?;
53
54 // Validate new fees
55 if !new_schedule.is_valid() {
56 return Err(StakePoolError::InvalidFeeSchedule.into());
57 }
58
59 // Update fees
60 *self = new_schedule;
61
62 // Emit fee update event
63 emit!(FeeScheduleUpdated {
64 protocol_fee: self.protocol_fee,
65 treasury_fee: self.treasury_fee,
66 validator_commission: self.validator_commission,
67 });
68
69 Ok(())
70 }
71}
Reward Management Best Practices
- Use checked math operations for all calculations
- Implement proper authority checks for fee management
- Consider implementing a reward smoothing mechanism
- Maintain transparent fee structures
- Add comprehensive event logging for all operations
Security Considerations
Security is paramount for liquid staking protocols. This section covers essential security measures, access controls, and emergency procedures.
Access Control
Implement robust access control mechanisms:
Access Control
1#[derive(Clone, Debug, PartialEq)]
2pub enum AuthorityType {
3 Manager, // Protocol admin
4 Staker, // Validator management
5 Depositor, // User deposits
6 Emergency, // Emergency operations
7}
8
9pub struct AccessControl {
10 /// Manager authority (protocol admin)
11 pub manager: Pubkey,
12
13 /// Staker authority (validator management)
14 pub staker: Pubkey,
15
16 /// Emergency authority
17 pub emergency: Pubkey,
18
19 /// Pending authority changes
20 pub pending_authorities: Option<PendingAuthorities>,
21}
22
23impl AccessControl {
24 pub fn verify_authority(
25 &self,
26 authority: &AccountInfo,
27 authority_type: AuthorityType,
28 ) -> ProgramResult {
29 // Verify authority is a signer
30 if !authority.is_signer {
31 return Err(StakePoolError::SignatureMissing.into());
32 }
33
34 // Verify authority matches expected
35 match authority_type {
36 AuthorityType::Manager => {
37 if authority.key != &self.manager {
38 return Err(StakePoolError::InvalidManagerAuthority.into());
39 }
40 }
41 AuthorityType::Staker => {
42 if authority.key != &self.staker {
43 return Err(StakePoolError::InvalidStakerAuthority.into());
44 }
45 }
46 AuthorityType::Emergency => {
47 if authority.key != &self.emergency {
48 return Err(StakePoolError::InvalidEmergencyAuthority.into());
49 }
50 }
51 _ => return Err(StakePoolError::InvalidAuthorityType.into()),
52 }
53
54 Ok(())
55 }
56}
Emergency Procedures
Implement robust emergency handling procedures:
Emergency Procedures
1pub struct EmergencyModule {
2 /// Emergency authority
3 pub emergency_authority: Pubkey,
4
5 /// Protocol state
6 pub state: ProtocolState,
7
8 /// Emergency timelock
9 pub timelock: u64,
10
11 /// Required confirmations
12 pub required_confirmations: u8,
13}
14
15impl EmergencyModule {
16 pub fn initiate_emergency_shutdown(
17 &mut self,
18 authority: &AccountInfo,
19 reason: EmergencyReason,
20 ) -> ProgramResult {
21 // Verify emergency authority
22 self.verify_emergency_authority(authority)?;
23
24 // Update protocol state
25 self.state = ProtocolState::Emergency;
26
27 // Start timelock
28 self.timelock = Clock::get()?.unix_timestamp;
29
30 // Emit emergency event
31 emit!(EmergencyShutdown {
32 authority: *authority.key,
33 reason,
34 timestamp: self.timelock,
35 });
36
37 Ok(())
38 }
39
40 pub fn process_emergency_unstake(
41 &mut self,
42 stake_pool: &mut StakePool,
43 validator: &Pubkey,
44 ) -> ProgramResult {
45 // Verify emergency state
46 if self.state != ProtocolState::Emergency {
47 return Err(StakePoolError::InvalidState.into());
48 }
49
50 // Verify timelock has passed
51 let current_time = Clock::get()?.unix_timestamp;
52 if current_time < self.timelock + EMERGENCY_TIMELOCK_DURATION {
53 return Err(StakePoolError::TimelockNotExpired.into());
54 }
55
56 // Process unstake
57 stake_pool.emergency_unstake(validator)?;
58
59 Ok(())
60 }
61}
Rate Limiting
Implement rate limiting to prevent abuse:
Rate Limiting
1pub struct RateLimiter {
2 /// Maximum deposits per epoch
3 pub max_epoch_deposits: u64,
4
5 /// Maximum withdrawals per epoch
6 pub max_epoch_withdrawals: u64,
7
8 /// Current epoch stats
9 pub epoch_stats: EpochStats,
10}
11
12impl RateLimiter {
13 pub fn check_deposit_limit(
14 &self,
15 amount: u64,
16 ) -> ProgramResult {
17 let current_epoch_deposits = self.epoch_stats
18 .total_deposits
19 .checked_add(amount)
20 .ok_or(StakePoolError::CalculationFailure)?;
21
22 if current_epoch_deposits > self.max_epoch_deposits {
23 return Err(StakePoolError::DepositLimitExceeded.into());
24 }
25
26 Ok(())
27 }
28
29 pub fn check_withdrawal_limit(
30 &self,
31 amount: u64,
32 ) -> ProgramResult {
33 let current_epoch_withdrawals = self.epoch_stats
34 .total_withdrawals
35 .checked_add(amount)
36 .ok_or(StakePoolError::CalculationFailure)?;
37
38 if current_epoch_withdrawals > self.max_epoch_withdrawals {
39 return Err(StakePoolError::WithdrawalLimitExceeded.into());
40 }
41
42 Ok(())
43 }
44}
Slashing Protection
Implement mechanisms to protect against validator slashing:
Slashing Protection
1pub struct SlashingProtection {
2 /// Slashing threshold
3 pub slashing_threshold: u64,
4
5 /// Emergency unstake threshold
6 pub emergency_threshold: u64,
7
8 /// Recovery pool
9 pub recovery_pool: Pubkey,
10}
11
12impl SlashingProtection {
13 pub fn handle_slashing_event(
14 &self,
15 stake_pool: &mut StakePool,
16 validator: &Pubkey,
17 slash_amount: u64,
18 ) -> ProgramResult {
19 // Calculate slash percentage
20 let slash_percentage = (slash_amount * 100) / stake_pool.total_lamports;
21
22 if slash_percentage >= self.emergency_threshold {
23 // Emergency unstake all funds
24 stake_pool.emergency_unstake(validator)?;
25 } else if slash_percentage >= self.slashing_threshold {
26 // Partial unstake
27 stake_pool.partial_unstake(
28 validator,
29 slash_amount * 2, // Extra safety margin
30 )?;
31 }
32
33 // Move slashed amount to recovery pool
34 stake_pool.transfer_to_recovery_pool(
35 validator,
36 slash_amount,
37 )?;
38
39 Ok(())
40 }
41}
Critical Security Measures
- Multi-signature requirements for critical operations
- Timelock delays for authority changes
- Rate limiting for deposits and withdrawals
- Comprehensive event logging and monitoring
- Emergency shutdown procedures with proper access controls
- Regular security audits and vulnerability assessments
Testing Guidelines
Comprehensive testing is crucial for protocol security and reliability. This section covers testing strategies, frameworks, and best practices.
Unit Testing
Implement thorough unit tests for all program components:
Unit Tests
1#[cfg(test)]
2mod tests {
3 use super::*;
4 use solana_program_test::*;
5 use solana_sdk::signature::Signer;
6
7 #[tokio::test]
8 async fn test_initialize_stake_pool() {
9 // Create program test environment
10 let program_test = ProgramTest::new(
11 "my_stake_pool",
12 id(),
13 processor!(process_instruction),
14 );
15
16 let (mut banks_client, payer, recent_blockhash) = program_test.start().await;
17
18 // Generate necessary keypairs
19 let stake_pool_keypair = Keypair::new();
20 let validator_list_keypair = Keypair::new();
21 let pool_mint_keypair = Keypair::new();
22
23 // Create initialization instruction
24 let init_ix = initialize_stake_pool(
25 &id(),
26 &stake_pool_keypair.pubkey(),
27 &validator_list_keypair.pubkey(),
28 &pool_mint_keypair.pubkey(),
29 &payer.pubkey(),
30 &FeeSchedule::default(),
31 );
32
33 // Create and sign transaction
34 let mut transaction = Transaction::new_with_payer(
35 &[init_ix],
36 Some(&payer.pubkey()),
37 );
38 transaction.sign(
39 &[&payer, &stake_pool_keypair],
40 recent_blockhash,
41 );
42
43 // Submit and confirm transaction
44 banks_client
45 .process_transaction(transaction)
46 .await
47 .unwrap();
48
49 // Verify initialization
50 let stake_pool = get_stake_pool(&mut banks_client, &stake_pool_keypair.pubkey())
51 .await
52 .unwrap();
53
54 assert!(stake_pool.is_initialized);
55 assert_eq!(stake_pool.total_lamports, 0);
56 }
57
58 #[tokio::test]
59 async fn test_deposit() {
60 // Test setup
61 let (mut banks_client, stake_pool, user) = setup_stake_pool().await;
62
63 // Create deposit instruction
64 let deposit_amount = 1_000_000_000; // 1 SOL
65 let deposit_ix = deposit(
66 &id(),
67 &stake_pool.pubkey(),
68 &user.pubkey(),
69 deposit_amount,
70 );
71
72 // Execute deposit
73 let result = execute_transaction(
74 &mut banks_client,
75 deposit_ix,
76 &[&user],
77 ).await;
78
79 // Verify deposit
80 assert!(result.is_ok());
81
82 let updated_pool = get_stake_pool(&mut banks_client, &stake_pool.pubkey())
83 .await
84 .unwrap();
85
86 assert_eq!(
87 updated_pool.total_lamports,
88 deposit_amount,
89 );
90 }
91}
Integration Testing
Test complete protocol workflows and component interactions:
Integration Tests
1#[cfg(test)]
2mod integration_tests {
3 use super::*;
4
5 async fn test_complete_stake_workflow() {
6 // Initialize test environment
7 let (
8 mut banks_client,
9 stake_pool,
10 validator_list,
11 user,
12 ) = setup_test_environment().await;
13
14 // 1. Deposit SOL
15 let deposit_amount = 1_000_000_000;
16 deposit_sol(
17 &mut banks_client,
18 &stake_pool,
19 &user,
20 deposit_amount,
21 ).await.unwrap();
22
23 // 2. Delegate to validator
24 let validator = add_test_validator(
25 &mut banks_client,
26 &validator_list,
27 ).await.unwrap();
28
29 delegate_stake(
30 &mut banks_client,
31 &stake_pool,
32 &validator,
33 deposit_amount,
34 ).await.unwrap();
35
36 // 3. Update pool balance (simulate epoch)
37 advance_epoch(&mut banks_client).await;
38 update_pool_balance(
39 &mut banks_client,
40 &stake_pool,
41 ).await.unwrap();
42
43 // 4. Distribute rewards
44 let rewards = 50_000_000; // 0.05 SOL
45 distribute_rewards(
46 &mut banks_client,
47 &stake_pool,
48 rewards,
49 ).await.unwrap();
50
51 // 5. Withdraw SOL
52 let withdraw_amount = deposit_amount / 2;
53 withdraw_sol(
54 &mut banks_client,
55 &stake_pool,
56 &user,
57 withdraw_amount,
58 ).await.unwrap();
59
60 // Verify final state
61 let final_pool = get_stake_pool(
62 &mut banks_client,
63 &stake_pool.pubkey(),
64 ).await.unwrap();
65
66 assert_eq!(
67 final_pool.total_lamports,
68 deposit_amount + rewards - withdraw_amount,
69 );
70 }
71}
Security Testing
Implement security-focused test cases:
Security Tests
1#[cfg(test)]
2mod security_tests {
3 use super::*;
4
5 #[tokio::test]
6 async fn test_authority_checks() {
7 let (mut banks_client, stake_pool, user) = setup_stake_pool().await;
8
9 // Try to update pool with wrong authority
10 let wrong_authority = Keypair::new();
11 let result = update_pool_with_authority(
12 &mut banks_client,
13 &stake_pool,
14 &wrong_authority,
15 ).await;
16
17 assert_eq!(
18 result.unwrap_err(),
19 StakePoolError::InvalidAuthority.into(),
20 );
21 }
22
23 #[tokio::test]
24 async fn test_deposit_limits() {
25 let (mut banks_client, stake_pool, user) = setup_stake_pool().await;
26
27 // Try to deposit more than limit
28 let excess_amount = MAX_DEPOSIT_AMOUNT + 1;
29 let result = deposit_sol(
30 &mut banks_client,
31 &stake_pool,
32 &user,
33 excess_amount,
34 ).await;
35
36 assert_eq!(
37 result.unwrap_err(),
38 StakePoolError::DepositLimitExceeded.into(),
39 );
40 }
41
42 #[tokio::test]
43 async fn test_emergency_procedures() {
44 let (mut banks_client, stake_pool, emergency_authority) = setup_stake_pool().await;
45
46 // Test emergency shutdown
47 let result = initiate_emergency_shutdown(
48 &mut banks_client,
49 &stake_pool,
50 &emergency_authority,
51 ).await;
52
53 assert!(result.is_ok());
54
55 // Verify operations are restricted
56 let deposit_result = deposit_sol(
57 &mut banks_client,
58 &stake_pool,
59 &Keypair::new(),
60 1_000_000,
61 ).await;
62
63 assert_eq!(
64 deposit_result.unwrap_err(),
65 StakePoolError::PoolFrozen.into(),
66 );
67 }
68}
Testing Best Practices
- Write comprehensive unit tests for all components
- Include integration tests for complete workflows
- Test all error conditions and edge cases
- Implement security-focused test cases
- Use proper test fixtures and helper functions
- Maintain high test coverage
Deployment Guide
This section covers the deployment process, including program deployment, initialization, and post-deployment verification steps.
Program Deployment
Deploy your liquid staking program to the Solana network:
Program Deployment
1# Build the program
2cargo build-bpf
3
4# Deploy to devnet for testing
5solana program deploy \
6 --program-id stake_pool_keypair.json \
7 --keypair deployer_keypair.json \
8 --url https://api.devnet.solana.com \
9 target/deploy/liquid_staking.so
10
11# Verify deployment
12solana program show --url devnet <PROGRAM_ID>
13
14# Initialize program accounts
15solana program call \
16 --program-id <PROGRAM_ID> \
17 --keypair admin_keypair.json \
18 --url https://api.devnet.solana.com \
19 initialize \
20 <ENCODED_INITIALIZE_INSTRUCTION>
Protocol Initialization
Initialize the protocol with proper configuration:
Protocol Initialization
1import {
2 Connection,
3 Keypair,
4 PublicKey,
5 sendAndConfirmTransaction,
6 Transaction,
7} from '@solana/web3.js'
8import { StakePool } from './stake_pool'
9
10async function initializeProtocol(
11 connection: Connection,
12 admin: Keypair,
13 config: ProtocolConfig,
14) {
15 // Generate necessary keypairs
16 const stakePoolKeypair = Keypair.generate()
17 const validatorListKeypair = Keypair.generate()
18 const mintKeypair = Keypair.generate()
19
20 // Create stake pool
21 const stakePool = await StakePool.create(
22 connection,
23 admin,
24 {
25 stakePool: stakePoolKeypair.publicKey,
26 validatorList: validatorListKeypair.publicKey,
27 poolMint: mintKeypair.publicKey,
28 feeSchedule: config.feeSchedule,
29 maxValidators: config.maxValidators,
30 epochFee: config.epochFee,
31 },
32 )
33
34 // Initialize validator list
35 await stakePool.initializeValidatorList()
36
37 // Set up initial validators
38 for (const validator of config.initialValidators) {
39 await stakePool.addValidator(validator)
40 }
41
42 // Configure rate limits
43 await stakePool.configureRateLimits({
44 maxDepositsPerEpoch: config.maxDepositsPerEpoch,
45 maxWithdrawalsPerEpoch: config.maxWithdrawalsPerEpoch,
46 })
47
48 return {
49 stakePool: stakePool.publicKey,
50 validatorList: validatorListKeypair.publicKey,
51 poolMint: mintKeypair.publicKey,
52 }
53}
Deployment Verification
Verify the deployment and protocol functionality:
Deployment Verification
1async function verifyDeployment(
2 connection: Connection,
3 stakePool: PublicKey,
4) {
5 // 1. Verify program and account deployment
6 const programInfo = await connection.getAccountInfo(stakePool)
7 if (!programInfo) {
8 throw new Error('Stake pool not deployed')
9 }
10
11 // 2. Verify initialization
12 const pool = await StakePool.load(connection, stakePool)
13 if (!pool.isInitialized) {
14 throw new Error('Stake pool not initialized')
15 }
16
17 // 3. Test basic operations
18 // Test deposit
19 const testDeposit = await pool.deposit(
20 testUser,
21 web3.LAMPORTS_PER_SOL, // 1 SOL
22 )
23 await connection.confirmTransaction(testDeposit)
24
25 // Test withdrawal
26 const testWithdraw = await pool.withdraw(
27 testUser,
28 web3.LAMPORTS_PER_SOL / 2, // 0.5 SOL
29 )
30 await connection.confirmTransaction(testWithdraw)
31
32 // 4. Verify validator list
33 const validators = await pool.getValidators()
34 if (validators.length === 0) {
35 throw new Error('No validators configured')
36 }
37
38 // 5. Verify fee configuration
39 const fees = await pool.getFees()
40 if (!fees.epochFee || !fees.withdrawalFee) {
41 throw new Error('Fees not configured')
42 }
43
44 // 6. Test emergency procedures
45 await pool.testEmergencyProcedures()
46
47 console.log('Deployment verification complete')
48 return true
49}
Monitoring Setup
Set up monitoring and alerting:
Monitoring Configuration
1import { MonitoringService } from './monitoring'
2
3async function setupMonitoring(
4 connection: Connection,
5 stakePool: PublicKey,
6 config: MonitoringConfig,
7) {
8 const monitoring = new MonitoringService({
9 connection,
10 stakePool,
11 alertEndpoint: config.alertEndpoint,
12 metricsEndpoint: config.metricsEndpoint,
13 })
14
15 // Configure metrics collection
16 await monitoring.configureMetrics({
17 collectionInterval: 60, // seconds
18 metrics: [
19 'total_stake',
20 'active_validators',
21 'reward_rate',
22 'token_supply',
23 'exchange_rate',
24 ],
25 })
26
27 // Configure alerts
28 await monitoring.configureAlerts({
29 validators: {
30 uptimeThreshold: 0.98,
31 skipRateThreshold: 0.01,
32 commissionThreshold: 10,
33 },
34 pool: {
35 minStakeThreshold: web3.LAMPORTS_PER_SOL * 1000,
36 maxStakeThreshold: web3.LAMPORTS_PER_SOL * 1000000,
37 rewardRateThreshold: 0.05,
38 },
39 security: {
40 rateLimit: {
41 deposits: 1000,
42 withdrawals: 1000,
43 },
44 slashingThreshold: 0.01,
45 },
46 })
47
48 // Start monitoring
49 await monitoring.start()
50
51 return monitoring
52}
Deployment Checklist
- All tests pass on devnet/testnet
- Security audit is completed
- Emergency procedures are tested
- Monitoring systems are configured
- Documentation is complete
- Multi-sig authorities are set up