Unlocking the Secrets of Staking: A Deep Dive into the Stake Program on Solana

LE◎
Turbin3 Research
Published in
7 min readJun 18, 2024

This article offers a detailed exploration of the basics of the Staking Program on Solana!

We’re going to talk about

  • How does staking work
  • Stake Accounts Vs Vote Accounts
  • Calculation of stake rewards, and epoch boundary
  • Stake program instructions
  • Stake Authority

So if you’re interested in diving deep into these topics, follow along! You’ll not regret it

Note: Every instruction is clickable and will direct you to the corresponding code in the Stake Program repository. If you’re a nerd, have fun! :)

How does staking work on Solana

Staking your SOL is essential for securing the network. But what exactly does this mean?

In decentralized distributed systems, achieving consensus is crucial. To illustrate this, let’s consider an imaginary scenario:

Imagine living in a country where a newly elected official makes decisions every 400 milliseconds. As a voter, you can influence who gets elected over the next 2.5 to 3 days by allocating your tokens to support your preferred candidate. Additionally, you empower other officials to monitor the elected official to ensure they do not act maliciously.

The more tokens you allocate, the higher the chances that your chosen official will be selected to make decisions for the next 2.5 to 3 days. This process mirrors what happens when you stake your SOL.

In the staking system, there are primarily two participants:

  • Delegators: Regular users who stake their SOL.
  • Validators: Node operators who run voting nodes and have stakes delegated to them.

Their incentives are aligned, as stakers are rewarded for helping to validate the ledger by delegating their stake to validator nodes. Validators handle the task of replaying the ledger and sending votes to a per-node vote account, to which stakers delegate their stakes. The rest of the cluster uses these stake-weighted votes to select a block when forks arise.

This system introduces a layer of economic security, as stakers must be compensated for the risk of their stake being slashed. This mechanism ensures that validators are incentivized to perform their duties correctly.

Stake Accounts vs Vote Accounts

The rewards process on Solana is managed through two distinct on-chain programs: the Vote program and the Stake program.

  1. Vote Program: This program addresses the issue of making stakes slashable.
  2. Stake Program: This program acts as the custodian of the rewards pool and facilitates passive delegation.

The Stake program is responsible for distributing rewards to both stakers and validators when it is verified that a staker’s delegate has participated in validating the ledger.

How does it work:

Validators own Vote accounts. These accounts track validator votes, count credits generated by the validator, and maintain any additional state specific to the validator.

Here’s what you can find in the VoteState struct:

pub struct VoteState {
// The Solana node that votes in this account.
pub node_pubkey: Pubkey,
// The identity of the entity in charge of the lamports of this account,
// separate from the account's address and the authorized vote signer.
pub authorized_withdrawer: Pubkey,
// The commission taken by this VoteState for any rewards claimed by
// staker's Stake accounts. This is the percentage ceiling of the reward.
pub commission: u8,
// The submitted votes data structure.
pub votes: VecDeque<LandedVote>,
// The last slot to reach the full lockout commitment necessary
// for rewards.
pub root_slot: Option<Slot>,
// Only this identity is authorized to submit votes.
// This field can only modified by this identity.
authorized_voters: AuthorizedVoters,
// History of prior authorized voters and the epochs for which they were
// set, the bottom end of the range is inclusive, the top of the range is
// exclusive
prior_voters: CircBuf<(Pubkey, Epoch, Epoch)>,
// The total number of rewards this Vote program has generated over its
// lifetime.
pub epoch_credits: Vec<(Epoch, u64, u64)>,
// The most recent timestamp submitted with a vote
pub last_timestamp: BlockTimestamp,
}

Delegators own Stake Accounts. The StakeState account is strangely enough not a struct but an enum that can have 4 different forms:

  • Uninitialized
  • Initialized
  • Stake
  • RewardsPool

Note: Only the first three forms are used in staking, the fourth one is only used to identify RewardsPool

Here’s a look at the StakeStateV2 enum more in detail:

pub enum StakeStateV2 {
#[default]
Uninitialized,
Initialized(Meta),
Stake(Meta, Stake, StakeFlags),
RewardsPool,
}

To understand how Stake Accounts work, we should have a deeper look at StakeStateV2::Stake:

pub struct Meta {
pub rent_exempt_reserve: u64,
pub authorized: Authorized,
pub lockup: Lockup,
}

pub struct Stake {
pub delegation: Delegation,
/// credits observed is credits from vote account state when delegated or redeemed
pub credits_observed: u64,
}

pub struct StakeFlags {
bits: u8,
}

In the StakeStateV2::Stake enum, there are three different structs that manage and track different parts of the staking program:

  • Meta: Keeps track of authorization and lockups
  • Stake: Keeps track of the current delegation preferences of the staker and everything associated with the amount, activation, and deactivation of the stake.
  • StakeFlags: Additional flags for stake state, introduced with StakeStateV2.

To understand better how the amount of stake gets handled, in this section we’re going to analyze more in-depth the Delegation struct:

pub struct Delegation {
// The pubkey of the VoteState instance the lamports are delegated to.
pub voter_pubkey: Pubkey,
// The staked amount (subject to warmup and cooldown) for generating
// rewards.
pub stake: u64,
// The epoch at which this stake was activated, std::Epoch::MAX if is
// a bootstrap stake
pub activation_epoch: Epoch,
// The epoch at which this stake was de-activated, std::Epoch::MAX if not
// deactivated
pub deactivation_epoch: Epoch,
// Deprecated
pub warmup_cooldown_rate: f64,
}

The voter_pubkey serves as the link between the VoteState and the StakeState, the stake instead indicates the amount of "power" (or stake) we possess.

Notably, the activation_epoch and deactivation_epoch fields allow us to determine how much stake is currently active or inactive. For instance, if we want to partially unstake, we would need to split the StakeState account and then unstake only a portion since partial unstaking within the same StakeState account is not possible.

Calculation of rewards

Do all validators earn the same APY? No!

Earning rates for validators on Solana vary due to the voting process. Validators are responsible for validating transactions and producing blocks. They vote on blocks they deem valid, and each vote is a transaction for which the validator pays a fee. Successful and correct votes earn the validator a credit.

Throughout an epoch, validators accumulate these credits. At the end of the epoch, they can exchange these credits for a share of the inflation rewards.

At the epoch boundary, calculations determine the amount of inflation rewards each staker will receive. This is done using the calculate_stake_points_and_credits function, which calculates the points earned during the epoch by using both the StakeState and the VoteState. This involves multiplying the credits in the VoteState by the stake amount.

The distribution of rewards is then managed by the calculate_stake_rewards function. This function calculates the distributions and updates needed and returns a tuple in the case of a payout:

  • Staker Rewards: The rewards to be distributed to stakers.
  • Voter Rewards: The rewards to be distributed to validators.
  • New Credits Observed: The updated value for credits observed in the stake.

If the calculated payout is less than one lamport, no distribution occurs.

The Stake Program

We have discussed how staking generally works, but the Stake Program offers much more. Below are some of the important instructions that can be executed:

Split and Merge

One of the notable features of the Stake Program is the ability to split and merge Stake Accounts.

  • The split Instruction: This allows the staker authority to transfer a portion of the current stake to a new, identical staking account.
  • The merge Instruction: This enables the staker authority to combine two identical Stake Accounts into a single account containing the total stake amount.

Lock

A lesser-known feature is the ability to lock the SOL in your stake account. At the inception of the account, the user can designate a custodian, who is the only person able to lock the stake based on UNIX time or epoch. This custodian information is stored in the Lockup struct within the Meta struct:

pub struct Meta {
pub rent_exempt_reserve: u64,
pub authorized: Authorized,
pub lockup: Lockup,
}

pub struct Lockup {
/// UnixTimestamp at which this stake will allow withdrawal, unless the
/// transaction is signed by the custodian
pub unix_timestamp: UnixTimestamp,
/// epoch height at which this stake will allow withdrawal, unless the
/// transaction is signed by the custodian
pub epoch: Epoch,
/// custodian signature on a transaction exempts the operation from
/// lockup constraints
pub custodian: Pubkey,
}

The set_lockup instruction allows the withdrawal authority to set or update lockup parameters. Withdrawal before the lockup period is only possible if the custodian signs the transaction.

Delegate, Deactivate and Redelegate

  • The delegate instruction: Delegates the entire balance of the staking account to a particular vote account. While DelegateStake can be called multiple times, re-delegation is delayed by one epoch.
  • The deactivate instruction: Deactivates the stake in the account.
  • The redelegate instruction: Deactivates the current stake, transfers it to a new uninitialized stake account, and schedules it for activation.

Stake Program Authority

Understanding the stake program authority requires examining the Meta struct, specifically the authorized field:

pub struct Meta {
pub rent_exempt_reserve: u64,
pub authorized: Authorized,
pub lockup: Lockup,
}

pub struct Authorized {
pub staker: Pubkey,
pub withdrawer: Pubkey,
}

Each stake account has two signing authorities:

  • Stake Authority: Signs transactions for delegating stake, deactivating stake delegation, splitting and merging stake accounts, and setting a new stake authority.
  • Withdraw Authority: Signs transactions for withdrawing undelegated stake into a wallet address, setting a new withdraw authority, and setting a new stake authority.

These authorities are established when the stake account is created and can be updated to authorize new signing addresses at any time. The stake and withdraw authority can be the same or different addresses. The withdraw authority keypair holds significant control, as it is required to liquidate tokens in the stake account and can reset the stake authority if necessary.

Note: Ensuring the security of the withdraw authority against loss or theft is crucial.

Additionally, there is a third, less common authority known as the Custodian. Set at the inception of the account, the custodian keypair can override the lockup period.

Congratulations! You now know how the staking program works! Hopefully, this content was entertaining and you got to learn something new.

If you want to follow my journey and you don’t want to miss out on my next piece of content, follow me on Twitter!

--

--