Instruction Flow
A detailed overview of the flow of instructions in the Staking Program.
Instruction Flow
Pool Initialization
The initialize_stake_pool instruction creates both the config and pool accounts atomically:
pub fn initialize_pool_handler(ctx: Context<InitializeStakePool>, apr_bps: u128) -> Result<()> {
require!(
apr_bps >= MIN_APR_BPS && apr_bps <= MAX_APR_BPS,
StakingError::InvalidAprBps
);
require_gte!(
12,
ctx.accounts.stake_mint.decimals,
StakingError::MintDecimalsTooHigh
);
require_gte!(
12,
ctx.accounts.reward_mint.decimals,
StakingError::MintDecimalsTooHigh
);
*ctx.accounts.config = StakePoolConfig {
reward_mint: ctx.accounts.reward_mint.key(),
stake_mint: ctx.accounts.stake_mint.key(),
apr_bps,
bump: ctx.bumps.config,
};
*ctx.accounts.stake_pool = StakePool {
config: ctx.accounts.config.key(),
vault: ctx.accounts.vault.key(),
interest_index: 0,
interest_index_last_updated: Clock::get()?.unix_timestamp,
bump: ctx.bumps.stake_pool,
};
Ok(())
}Constraints:
- APR must be between
MIN_APR_BPS(1 bps = 0.01%) andMAX_APR_BPS(100,000 bps = 1000%) stake_mintandreward_mintdecimals cannot exceed12- Only signers listed in the
AUTHORITIESconstant are permitted to initialize the pool
Staking
The stake_to_stake_pool instruction handles both new and existing stake accounts:
pub fn stake_handler(ctx: Context<StakeToStakePool>, amount: u64) -> Result<()> {
ctx.accounts.validate(amount)?;
let interest_index = ctx.accounts.update_pool_interest_index()?; // we return it because it's more efficient than reloading the whole account
if ctx.accounts.stake_account.staked_amount == 0 {
ctx.accounts.initialize_user_stake_account(
amount,
ctx.bumps.stake_account,
interest_index,
)?;
} else {
require_keys_eq!(
ctx.accounts.stake_pool.key(),
ctx.accounts.stake_account.stake_pool,
StakingError::StakePoolMismatch
);
ctx.accounts
.update_user_stake_account(amount, interest_index)?;
}
ctx.accounts.transfer_to_vault(amount)?;
Ok(())
}Process:
Validates amount > 0 and mint decimals
Updates Global Interest Index to current time
Creates new StakeAccount if user has no stake, otherwise updates existing StakeAccount
Transfers stake_mint tokens from user's associated token account (ATA) to pool vault
Claiming Rewards
The claim_rewards instruction mints reward_mint tokens based on accrued rewards:
pub fn claim_handler(ctx: Context<ClaimRewards>) -> Result<()> {
ctx.accounts.validate()?;
process_claim(
&mut ctx.accounts.stake_pool,
&ctx.accounts.config,
&mut ctx.accounts.stake_account,
&ctx.accounts.reward_mint,
&ctx.accounts.user_reward_associated_token_account,
&ctx.accounts.token_program,
ctx.bumps.reward_mint,
)?;
Ok(())
}The process_claim utility function orchestrates the complete claim flow:
Process:
Updates the Global Interest Index to the current time
Calculates pending rewards using update_pending_rewards
Mints reward_mint tokens if pending_rewards > 0
Resets the baseline interest index via apply_claim
Unstaking
The unstake_from_stake_pool instruction handles both partial and full withdrawals:
pub fn unstake_handler(ctx: Context<UnstakeFromStakePool>, amount: u64) -> Result<()> {
require_keys_eq!(
ctx.accounts.stake_pool.key(),
ctx.accounts.stake_account.stake_pool
);
let mut should_close = false;
if amount == ctx.accounts.stake_account.staked_amount {
// Full unstake: claim rewards and mark for closing
process_claim(/* ... */)?;
should_close = true;
} else {
// Partial unstake: update interest and reset baseline
let now = Clock::get()?.unix_timestamp;
let interest_index = ctx.accounts.stake_pool
.update_interest_index(now, ctx.accounts.config.apr_bps)?;
ctx.accounts.stake_account.update_pending_rewards(interest_index)?;
ctx.accounts.stake_account.interest_index_at_deposit = interest_index;
}
ctx.accounts.transfer_staked_to_user(amount)?;
ctx.accounts.stake_account.staked_amount = ctx
.accounts.stake_account.staked_amount
.checked_sub(amount)
.ok_or(StakingError::Overflow)?;
if should_close {
close_stake_account(&mut ctx.accounts.stake_account, &mut ctx.accounts.user)?;
}
Ok(())
}| Type | Behavior |
|---|---|
| Full unstake | Automatically claims rewards and closes the StakeAccount, refunding the user's rent on the account |
| Partial unstake | Updates the Global Interest Index, recalculates pending rewards, and resets the baseline interest index |
Last updated on