2020-10-16 10:38:12 -07:00
|
|
|
//! Program state processor
|
|
|
|
|
|
|
|
#![cfg(feature = "program")]
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
error::Error,
|
|
|
|
instruction::{InitArgs, StakePoolInstruction},
|
|
|
|
stake,
|
|
|
|
state::{StakePool, State},
|
|
|
|
};
|
|
|
|
use num_traits::FromPrimitive;
|
|
|
|
#[cfg(not(target_arch = "bpf"))]
|
|
|
|
use solana_sdk::instruction::Instruction;
|
|
|
|
#[cfg(target_arch = "bpf")]
|
|
|
|
use solana_sdk::program::invoke_signed;
|
|
|
|
use solana_sdk::{
|
|
|
|
account_info::next_account_info, account_info::AccountInfo, decode_error::DecodeError,
|
|
|
|
entrypoint::ProgramResult, info, program_error::PrintProgramError, program_error::ProgramError,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
};
|
|
|
|
use std::convert::TryFrom;
|
|
|
|
|
|
|
|
/// Program state handler.
|
|
|
|
pub struct Processor {}
|
|
|
|
impl Processor {
|
2020-10-19 04:26:05 -07:00
|
|
|
/// Suffix for deposit authority seed
|
|
|
|
pub const AUTHORITY_DEPOSIT: &'static [u8] = b"deposit";
|
|
|
|
/// Suffix for withdraw authority seed
|
|
|
|
pub const AUTHORITY_WITHDRAW: &'static [u8] = b"withdraw";
|
2020-10-16 10:38:12 -07:00
|
|
|
/// Calculates the authority id by generating a program address.
|
2020-10-19 04:26:05 -07:00
|
|
|
pub fn authority_id(
|
|
|
|
program_id: &Pubkey,
|
|
|
|
my_info: &Pubkey,
|
|
|
|
authority_type: &[u8],
|
|
|
|
bump_seed: u8,
|
|
|
|
) -> Result<Pubkey, Error> {
|
|
|
|
Pubkey::create_program_address(
|
|
|
|
&[&my_info.to_bytes()[..32], authority_type, &[bump_seed]],
|
|
|
|
program_id,
|
|
|
|
)
|
|
|
|
.or(Err(Error::InvalidProgramAddress))
|
2020-10-16 10:38:12 -07:00
|
|
|
}
|
2020-10-20 12:02:05 -07:00
|
|
|
/// Generates seed bump for stake pool authorities
|
|
|
|
pub fn find_authority_bump_seed(
|
|
|
|
program_id: &Pubkey,
|
|
|
|
my_info: &Pubkey,
|
|
|
|
authority_type: &[u8],
|
|
|
|
) -> u8 {
|
|
|
|
let (_pubkey, bump_seed) =
|
|
|
|
Pubkey::find_program_address(&[&my_info.to_bytes()[..32], authority_type], program_id);
|
|
|
|
bump_seed
|
|
|
|
}
|
2020-10-16 10:38:12 -07:00
|
|
|
|
|
|
|
/// Issue a stake_split instruction.
|
|
|
|
pub fn stake_split<'a>(
|
|
|
|
stake_pool: &Pubkey,
|
|
|
|
stake_account: AccountInfo<'a>,
|
|
|
|
authority: AccountInfo<'a>,
|
2020-10-20 12:02:05 -07:00
|
|
|
authority_type: &[u8],
|
|
|
|
bump_seed: u8,
|
2020-10-16 10:38:12 -07:00
|
|
|
amount: u64,
|
|
|
|
split_stake: AccountInfo<'a>,
|
|
|
|
) -> Result<(), ProgramError> {
|
|
|
|
let me_bytes = stake_pool.to_bytes();
|
2020-10-20 12:02:05 -07:00
|
|
|
let authority_signature_seeds = [&me_bytes[..32], authority_type, &[bump_seed]];
|
2020-10-16 10:38:12 -07:00
|
|
|
let signers = &[&authority_signature_seeds[..]];
|
|
|
|
|
|
|
|
let ix = stake::split_only(stake_account.key, authority.key, amount, split_stake.key);
|
|
|
|
|
|
|
|
invoke_signed(&ix, &[stake_account, authority, split_stake], signers)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Issue a stake_set_owner instruction.
|
|
|
|
pub fn stake_authorize<'a>(
|
|
|
|
stake_pool: &Pubkey,
|
|
|
|
stake_account: AccountInfo<'a>,
|
|
|
|
authority: AccountInfo<'a>,
|
2020-10-20 12:02:05 -07:00
|
|
|
authority_type: &[u8],
|
|
|
|
bump_seed: u8,
|
2020-10-16 10:38:12 -07:00
|
|
|
new_staker: &Pubkey,
|
|
|
|
staker_auth: stake::StakeAuthorize,
|
|
|
|
) -> Result<(), ProgramError> {
|
|
|
|
let me_bytes = stake_pool.to_bytes();
|
2020-10-20 12:02:05 -07:00
|
|
|
let authority_signature_seeds = [&me_bytes[..32], authority_type, &[bump_seed]];
|
2020-10-16 10:38:12 -07:00
|
|
|
let signers = &[&authority_signature_seeds[..]];
|
|
|
|
|
|
|
|
let ix = stake::authorize(stake_account.key, authority.key, new_staker, staker_auth);
|
|
|
|
|
|
|
|
invoke_signed(&ix, &[stake_account, authority], signers)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Issue a spl_token `Burn` instruction.
|
2020-10-20 12:02:05 -07:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2020-10-16 10:38:12 -07:00
|
|
|
pub fn token_burn<'a>(
|
|
|
|
stake_pool: &Pubkey,
|
|
|
|
token_program: AccountInfo<'a>,
|
|
|
|
burn_account: AccountInfo<'a>,
|
|
|
|
mint: AccountInfo<'a>,
|
|
|
|
authority: AccountInfo<'a>,
|
2020-10-20 12:02:05 -07:00
|
|
|
authority_type: &[u8],
|
|
|
|
bump_seed: u8,
|
2020-10-16 10:38:12 -07:00
|
|
|
amount: u64,
|
|
|
|
) -> Result<(), ProgramError> {
|
|
|
|
let me_bytes = stake_pool.to_bytes();
|
2020-10-20 12:02:05 -07:00
|
|
|
let authority_signature_seeds = [&me_bytes[..32], authority_type, &[bump_seed]];
|
2020-10-16 10:38:12 -07:00
|
|
|
let signers = &[&authority_signature_seeds[..]];
|
|
|
|
|
|
|
|
let ix = spl_token::instruction::burn(
|
|
|
|
token_program.key,
|
|
|
|
burn_account.key,
|
|
|
|
mint.key,
|
|
|
|
authority.key,
|
|
|
|
&[],
|
|
|
|
amount,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
invoke_signed(
|
|
|
|
&ix,
|
|
|
|
&[burn_account, mint, authority, token_program],
|
|
|
|
signers,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Issue a spl_token `MintTo` instruction.
|
2020-10-20 12:02:05 -07:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2020-10-16 10:38:12 -07:00
|
|
|
pub fn token_mint_to<'a>(
|
|
|
|
stake_pool: &Pubkey,
|
|
|
|
token_program: AccountInfo<'a>,
|
|
|
|
mint: AccountInfo<'a>,
|
|
|
|
destination: AccountInfo<'a>,
|
|
|
|
authority: AccountInfo<'a>,
|
2020-10-20 12:02:05 -07:00
|
|
|
authority_type: &[u8],
|
|
|
|
bump_seed: u8,
|
2020-10-16 10:38:12 -07:00
|
|
|
amount: u64,
|
|
|
|
) -> Result<(), ProgramError> {
|
|
|
|
let me_bytes = stake_pool.to_bytes();
|
2020-10-20 12:02:05 -07:00
|
|
|
let authority_signature_seeds = [&me_bytes[..32], authority_type, &[bump_seed]];
|
2020-10-16 10:38:12 -07:00
|
|
|
let signers = &[&authority_signature_seeds[..]];
|
2020-10-20 12:02:05 -07:00
|
|
|
|
2020-10-16 10:38:12 -07:00
|
|
|
let ix = spl_token::instruction::mint_to(
|
|
|
|
token_program.key,
|
|
|
|
mint.key,
|
|
|
|
destination.key,
|
|
|
|
authority.key,
|
|
|
|
&[],
|
|
|
|
amount,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
invoke_signed(&ix, &[mint, destination, authority, token_program], signers)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Processes an [Initialize](enum.Instruction.html).
|
|
|
|
pub fn process_initialize(
|
2020-10-20 12:02:05 -07:00
|
|
|
program_id: &Pubkey,
|
2020-10-16 10:38:12 -07:00
|
|
|
init: InitArgs,
|
|
|
|
accounts: &[AccountInfo],
|
|
|
|
) -> ProgramResult {
|
|
|
|
let account_info_iter = &mut accounts.iter();
|
|
|
|
let stake_pool_info = next_account_info(account_info_iter)?;
|
|
|
|
let owner_info = next_account_info(account_info_iter)?;
|
|
|
|
let pool_mint_info = next_account_info(account_info_iter)?;
|
|
|
|
let owner_fee_info = next_account_info(account_info_iter)?;
|
|
|
|
let token_program_info = next_account_info(account_info_iter)?;
|
|
|
|
|
2020-10-20 12:02:05 -07:00
|
|
|
// Stake pool account should not be already initialized
|
2020-10-16 10:38:12 -07:00
|
|
|
if State::Unallocated != State::deserialize(&stake_pool_info.data.borrow())? {
|
|
|
|
return Err(Error::AlreadyInUse.into());
|
|
|
|
}
|
|
|
|
|
2020-10-20 12:02:05 -07:00
|
|
|
// Numerator should be smaller than or equal to denominator (fee <= 1)
|
|
|
|
if init.fee.numerator > init.fee.denominator {
|
|
|
|
return Err(Error::FeeTooHigh.into());
|
|
|
|
}
|
|
|
|
|
2020-10-16 10:38:12 -07:00
|
|
|
let stake_pool = State::Init(StakePool {
|
|
|
|
owner: *owner_info.key,
|
2020-10-20 12:02:05 -07:00
|
|
|
deposit_bump_seed: Self::find_authority_bump_seed(
|
|
|
|
program_id,
|
|
|
|
stake_pool_info.key,
|
|
|
|
Self::AUTHORITY_DEPOSIT,
|
|
|
|
),
|
|
|
|
withdraw_bump_seed: Self::find_authority_bump_seed(
|
|
|
|
program_id,
|
|
|
|
stake_pool_info.key,
|
|
|
|
Self::AUTHORITY_WITHDRAW,
|
|
|
|
),
|
2020-10-16 10:38:12 -07:00
|
|
|
pool_mint: *pool_mint_info.key,
|
|
|
|
owner_fee_account: *owner_fee_info.key,
|
|
|
|
token_program_id: *token_program_info.key,
|
|
|
|
stake_total: 0,
|
|
|
|
pool_total: 0,
|
|
|
|
fee: init.fee,
|
|
|
|
});
|
|
|
|
stake_pool.serialize(&mut stake_pool_info.data.borrow_mut())
|
|
|
|
}
|
|
|
|
|
2020-10-20 12:02:05 -07:00
|
|
|
/// Processes [Deposit](enum.Instruction.html).
|
2020-10-16 10:38:12 -07:00
|
|
|
pub fn process_deposit(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
|
|
|
|
let account_info_iter = &mut accounts.iter();
|
2020-10-20 12:02:05 -07:00
|
|
|
// Stake pool
|
2020-10-16 10:38:12 -07:00
|
|
|
let stake_pool_info = next_account_info(account_info_iter)?;
|
2020-10-20 12:02:05 -07:00
|
|
|
// Stake pool deposit authority
|
2020-10-16 10:38:12 -07:00
|
|
|
let deposit_info = next_account_info(account_info_iter)?;
|
2020-10-20 12:02:05 -07:00
|
|
|
// Stake pool withdraw authority
|
2020-10-16 10:38:12 -07:00
|
|
|
let withdraw_info = next_account_info(account_info_iter)?;
|
2020-10-20 12:02:05 -07:00
|
|
|
// Stake account to join the pool (withdraw should be set to stake pool deposit)
|
2020-10-16 10:38:12 -07:00
|
|
|
let stake_info = next_account_info(account_info_iter)?;
|
2020-10-20 12:02:05 -07:00
|
|
|
// User account to receive pool tokens
|
2020-10-16 10:38:12 -07:00
|
|
|
let dest_user_info = next_account_info(account_info_iter)?;
|
2020-10-20 12:02:05 -07:00
|
|
|
// Account to receive pool fee tokens
|
2020-10-16 10:38:12 -07:00
|
|
|
let owner_fee_info = next_account_info(account_info_iter)?;
|
2020-10-20 12:02:05 -07:00
|
|
|
// Pool token mint account
|
|
|
|
let pool_mint_info = next_account_info(account_info_iter)?;
|
|
|
|
// Pool token program id
|
2020-10-16 10:38:12 -07:00
|
|
|
let token_program_info = next_account_info(account_info_iter)?;
|
|
|
|
|
|
|
|
let mut stake_pool = State::deserialize(&stake_pool_info.data.borrow())?.stake_pool()?;
|
|
|
|
|
|
|
|
if *withdraw_info.key
|
2020-10-19 04:26:05 -07:00
|
|
|
!= Self::authority_id(
|
|
|
|
program_id,
|
|
|
|
stake_pool_info.key,
|
|
|
|
Self::AUTHORITY_WITHDRAW,
|
|
|
|
stake_pool.withdraw_bump_seed,
|
|
|
|
)?
|
2020-10-16 10:38:12 -07:00
|
|
|
{
|
|
|
|
return Err(Error::InvalidProgramAddress.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
if *deposit_info.key
|
2020-10-19 04:26:05 -07:00
|
|
|
!= Self::authority_id(
|
|
|
|
program_id,
|
|
|
|
stake_pool_info.key,
|
|
|
|
Self::AUTHORITY_DEPOSIT,
|
|
|
|
stake_pool.deposit_bump_seed,
|
|
|
|
)?
|
2020-10-16 10:38:12 -07:00
|
|
|
{
|
|
|
|
return Err(Error::InvalidProgramAddress.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
if stake_pool.owner_fee_account != *owner_fee_info.key {
|
|
|
|
return Err(Error::InvalidInput.into());
|
|
|
|
}
|
|
|
|
if stake_pool.token_program_id != *token_program_info.key {
|
|
|
|
return Err(Error::InvalidInput.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
let stake_lamports = **stake_info.lamports.borrow();
|
|
|
|
let pool_amount = stake_pool
|
|
|
|
.calc_pool_deposit_amount(stake_lamports)
|
|
|
|
.ok_or(Error::CalculationFailure)?;
|
|
|
|
|
|
|
|
let fee_amount = stake_pool
|
|
|
|
.calc_fee_amount(pool_amount)
|
|
|
|
.ok_or(Error::CalculationFailure)?;
|
|
|
|
|
|
|
|
let user_amount = pool_amount
|
|
|
|
.checked_sub(fee_amount)
|
|
|
|
.ok_or(Error::CalculationFailure)?;
|
|
|
|
|
|
|
|
Self::stake_authorize(
|
|
|
|
stake_pool_info.key,
|
|
|
|
stake_info.clone(),
|
|
|
|
deposit_info.clone(),
|
2020-10-20 12:02:05 -07:00
|
|
|
Self::AUTHORITY_DEPOSIT,
|
2020-10-19 04:26:05 -07:00
|
|
|
stake_pool.deposit_bump_seed,
|
2020-10-16 10:38:12 -07:00
|
|
|
withdraw_info.key,
|
|
|
|
stake::StakeAuthorize::Withdrawer,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
let user_amount = <u64>::try_from(user_amount).or(Err(Error::CalculationFailure))?;
|
|
|
|
Self::token_mint_to(
|
|
|
|
stake_pool_info.key,
|
|
|
|
token_program_info.clone(),
|
|
|
|
pool_mint_info.clone(),
|
|
|
|
dest_user_info.clone(),
|
|
|
|
withdraw_info.clone(),
|
2020-10-23 05:25:53 -07:00
|
|
|
Self::AUTHORITY_WITHDRAW,
|
|
|
|
stake_pool.withdraw_bump_seed,
|
2020-10-16 10:38:12 -07:00
|
|
|
user_amount,
|
|
|
|
)?;
|
|
|
|
let fee_amount = <u64>::try_from(fee_amount).or(Err(Error::CalculationFailure))?;
|
|
|
|
Self::token_mint_to(
|
|
|
|
stake_pool_info.key,
|
|
|
|
token_program_info.clone(),
|
|
|
|
pool_mint_info.clone(),
|
|
|
|
owner_fee_info.clone(),
|
|
|
|
withdraw_info.clone(),
|
2020-10-20 12:02:05 -07:00
|
|
|
Self::AUTHORITY_WITHDRAW,
|
2020-10-19 04:26:05 -07:00
|
|
|
stake_pool.withdraw_bump_seed,
|
2020-10-16 10:38:12 -07:00
|
|
|
fee_amount as u64,
|
|
|
|
)?;
|
|
|
|
let pool_amount = <u64>::try_from(pool_amount).or(Err(Error::CalculationFailure))?;
|
|
|
|
stake_pool.pool_total += pool_amount;
|
|
|
|
stake_pool.stake_total += stake_lamports;
|
|
|
|
State::Init(stake_pool).serialize(&mut stake_pool_info.data.borrow_mut())?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-10-20 12:02:05 -07:00
|
|
|
/// Processes [Withdraw](enum.Instruction.html).
|
2020-10-16 10:38:12 -07:00
|
|
|
pub fn process_withdraw(
|
|
|
|
program_id: &Pubkey,
|
|
|
|
stake_amount: u64,
|
|
|
|
accounts: &[AccountInfo],
|
|
|
|
) -> ProgramResult {
|
|
|
|
let account_info_iter = &mut accounts.iter();
|
2020-10-20 12:02:05 -07:00
|
|
|
// Stake pool
|
2020-10-16 10:38:12 -07:00
|
|
|
let stake_pool_info = next_account_info(account_info_iter)?;
|
2020-10-20 12:02:05 -07:00
|
|
|
// Stake pool withdraw authority
|
2020-10-16 10:38:12 -07:00
|
|
|
let withdraw_info = next_account_info(account_info_iter)?;
|
2020-10-20 12:02:05 -07:00
|
|
|
// Stake account to split
|
|
|
|
let stake_split_from = next_account_info(account_info_iter)?;
|
|
|
|
// Unitialized stake account to receive withdrawal
|
|
|
|
let stake_split_to = next_account_info(account_info_iter)?;
|
|
|
|
// User account to set as a new withdraw authority
|
|
|
|
let user_stake_authority = next_account_info(account_info_iter)?;
|
|
|
|
// User account with pool tokens to burn from
|
|
|
|
let burn_from_info = next_account_info(account_info_iter)?;
|
|
|
|
// Pool token mint account
|
2020-10-16 10:38:12 -07:00
|
|
|
let pool_mint_info = next_account_info(account_info_iter)?;
|
2020-10-20 12:02:05 -07:00
|
|
|
// Pool token program id
|
2020-10-16 10:38:12 -07:00
|
|
|
let token_program_info = next_account_info(account_info_iter)?;
|
|
|
|
|
|
|
|
let mut stake_pool = State::deserialize(&stake_pool_info.data.borrow())?.stake_pool()?;
|
|
|
|
|
|
|
|
if *withdraw_info.key
|
2020-10-19 04:26:05 -07:00
|
|
|
!= Self::authority_id(
|
|
|
|
program_id,
|
|
|
|
stake_pool_info.key,
|
|
|
|
Self::AUTHORITY_WITHDRAW,
|
|
|
|
stake_pool.withdraw_bump_seed,
|
|
|
|
)?
|
2020-10-16 10:38:12 -07:00
|
|
|
{
|
|
|
|
return Err(Error::InvalidProgramAddress.into());
|
|
|
|
}
|
|
|
|
if stake_pool.token_program_id != *token_program_info.key {
|
|
|
|
return Err(Error::InvalidInput.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
let pool_amount = stake_pool
|
|
|
|
.calc_pool_withdraw_amount(stake_amount)
|
|
|
|
.ok_or(Error::CalculationFailure)?;
|
|
|
|
let pool_amount = <u64>::try_from(pool_amount).or(Err(Error::CalculationFailure))?;
|
|
|
|
|
|
|
|
Self::stake_split(
|
|
|
|
stake_pool_info.key,
|
2020-10-20 12:02:05 -07:00
|
|
|
stake_split_from.clone(),
|
2020-10-16 10:38:12 -07:00
|
|
|
withdraw_info.clone(),
|
2020-10-20 12:02:05 -07:00
|
|
|
Self::AUTHORITY_WITHDRAW,
|
2020-10-19 04:26:05 -07:00
|
|
|
stake_pool.withdraw_bump_seed,
|
2020-10-16 10:38:12 -07:00
|
|
|
stake_amount,
|
2020-10-20 12:02:05 -07:00
|
|
|
stake_split_to.clone(),
|
2020-10-16 10:38:12 -07:00
|
|
|
)?;
|
|
|
|
|
|
|
|
Self::stake_authorize(
|
|
|
|
stake_pool_info.key,
|
2020-10-20 12:02:05 -07:00
|
|
|
stake_split_to.clone(),
|
2020-10-16 10:38:12 -07:00
|
|
|
withdraw_info.clone(),
|
2020-10-20 12:02:05 -07:00
|
|
|
Self::AUTHORITY_WITHDRAW,
|
2020-10-19 04:26:05 -07:00
|
|
|
stake_pool.withdraw_bump_seed,
|
2020-10-20 12:02:05 -07:00
|
|
|
user_stake_authority.key,
|
2020-10-16 10:38:12 -07:00
|
|
|
stake::StakeAuthorize::Withdrawer,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Self::token_burn(
|
|
|
|
stake_pool_info.key,
|
|
|
|
token_program_info.clone(),
|
2020-10-20 12:02:05 -07:00
|
|
|
burn_from_info.clone(),
|
2020-10-16 10:38:12 -07:00
|
|
|
pool_mint_info.clone(),
|
|
|
|
withdraw_info.clone(),
|
2020-10-20 12:02:05 -07:00
|
|
|
Self::AUTHORITY_WITHDRAW,
|
2020-10-19 04:26:05 -07:00
|
|
|
stake_pool.withdraw_bump_seed,
|
2020-10-16 10:38:12 -07:00
|
|
|
pool_amount,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
stake_pool.pool_total -= pool_amount;
|
|
|
|
stake_pool.stake_total -= stake_amount;
|
|
|
|
State::Init(stake_pool).serialize(&mut stake_pool_info.data.borrow_mut())?;
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-10-20 12:02:05 -07:00
|
|
|
/// Processes [Claim](enum.Instruction.html).
|
|
|
|
pub fn process_claim(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
|
|
|
|
let account_info_iter = &mut accounts.iter();
|
|
|
|
// Stake pool
|
|
|
|
let stake_pool_info = next_account_info(account_info_iter)?;
|
|
|
|
// Stake pool withdraw authority
|
|
|
|
let withdraw_info = next_account_info(account_info_iter)?;
|
|
|
|
// Stake account to claim
|
|
|
|
let stake_to_claim = next_account_info(account_info_iter)?;
|
|
|
|
// User account to set as a new withdraw authority
|
|
|
|
let user_stake_authority = next_account_info(account_info_iter)?;
|
|
|
|
// User account with pool tokens to burn from
|
|
|
|
let burn_from_info = next_account_info(account_info_iter)?;
|
|
|
|
// Pool token account
|
|
|
|
let pool_mint_info = next_account_info(account_info_iter)?;
|
|
|
|
// Pool token program id
|
|
|
|
let token_program_info = next_account_info(account_info_iter)?;
|
|
|
|
|
|
|
|
let mut stake_pool = State::deserialize(&stake_pool_info.data.borrow())?.stake_pool()?;
|
|
|
|
|
|
|
|
if *withdraw_info.key
|
|
|
|
!= Self::authority_id(
|
|
|
|
program_id,
|
|
|
|
stake_pool_info.key,
|
|
|
|
Self::AUTHORITY_WITHDRAW,
|
|
|
|
stake_pool.withdraw_bump_seed,
|
|
|
|
)?
|
|
|
|
{
|
|
|
|
return Err(Error::InvalidProgramAddress.into());
|
|
|
|
}
|
|
|
|
if stake_pool.token_program_id != *token_program_info.key {
|
|
|
|
return Err(Error::InvalidInput.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
let stake_amount = **stake_to_claim.lamports.borrow();
|
|
|
|
let pool_amount = stake_pool
|
|
|
|
.calc_pool_withdraw_amount(stake_amount)
|
|
|
|
.ok_or(Error::CalculationFailure)?;
|
|
|
|
let pool_amount = <u64>::try_from(pool_amount).or(Err(Error::CalculationFailure))?;
|
|
|
|
|
|
|
|
Self::stake_authorize(
|
|
|
|
stake_pool_info.key,
|
|
|
|
stake_to_claim.clone(),
|
|
|
|
withdraw_info.clone(),
|
|
|
|
Self::AUTHORITY_WITHDRAW,
|
|
|
|
stake_pool.withdraw_bump_seed,
|
|
|
|
user_stake_authority.key,
|
|
|
|
stake::StakeAuthorize::Withdrawer,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Self::token_burn(
|
|
|
|
stake_pool_info.key,
|
|
|
|
token_program_info.clone(),
|
|
|
|
burn_from_info.clone(),
|
|
|
|
pool_mint_info.clone(),
|
|
|
|
withdraw_info.clone(),
|
|
|
|
Self::AUTHORITY_WITHDRAW,
|
|
|
|
stake_pool.withdraw_bump_seed,
|
|
|
|
pool_amount,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
stake_pool.pool_total -= pool_amount;
|
|
|
|
stake_pool.stake_total -= stake_amount;
|
|
|
|
State::Init(stake_pool).serialize(&mut stake_pool_info.data.borrow_mut())?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
/// Processes [SetStakeAuthority](enum.Instruction.html).
|
2020-10-16 10:38:12 -07:00
|
|
|
pub fn process_set_staking_auth(
|
|
|
|
program_id: &Pubkey,
|
|
|
|
accounts: &[AccountInfo],
|
|
|
|
) -> ProgramResult {
|
|
|
|
let account_info_iter = &mut accounts.iter();
|
|
|
|
let stake_pool_info = next_account_info(account_info_iter)?;
|
|
|
|
let owner_info = next_account_info(account_info_iter)?;
|
|
|
|
let withdraw_info = next_account_info(account_info_iter)?;
|
|
|
|
let stake_info = next_account_info(account_info_iter)?;
|
|
|
|
let staker_info = next_account_info(account_info_iter)?;
|
|
|
|
|
|
|
|
let stake_pool = State::deserialize(&stake_pool_info.data.borrow())?.stake_pool()?;
|
|
|
|
|
|
|
|
if *owner_info.key != stake_pool.owner {
|
|
|
|
return Err(Error::InvalidInput.into());
|
|
|
|
}
|
|
|
|
if !owner_info.is_signer {
|
|
|
|
return Err(Error::InvalidInput.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
if *withdraw_info.key
|
2020-10-19 04:26:05 -07:00
|
|
|
!= Self::authority_id(
|
|
|
|
program_id,
|
|
|
|
stake_pool_info.key,
|
|
|
|
Self::AUTHORITY_WITHDRAW,
|
|
|
|
stake_pool.withdraw_bump_seed,
|
|
|
|
)?
|
2020-10-16 10:38:12 -07:00
|
|
|
{
|
|
|
|
return Err(Error::InvalidProgramAddress.into());
|
|
|
|
}
|
2020-10-20 12:02:05 -07:00
|
|
|
|
2020-10-16 10:38:12 -07:00
|
|
|
Self::stake_authorize(
|
2020-10-20 12:02:05 -07:00
|
|
|
stake_pool_info.key,
|
2020-10-16 10:38:12 -07:00
|
|
|
stake_info.clone(),
|
|
|
|
withdraw_info.clone(),
|
2020-10-20 12:02:05 -07:00
|
|
|
Self::AUTHORITY_WITHDRAW,
|
2020-10-19 04:26:05 -07:00
|
|
|
stake_pool.withdraw_bump_seed,
|
2020-10-16 10:38:12 -07:00
|
|
|
staker_info.key,
|
|
|
|
stake::StakeAuthorize::Staker,
|
|
|
|
)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-10-20 12:02:05 -07:00
|
|
|
/// Processes [SetOwner](enum.Instruction.html).
|
2020-10-16 10:38:12 -07:00
|
|
|
pub fn process_set_owner(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
|
|
|
|
let account_info_iter = &mut accounts.iter();
|
|
|
|
let stake_pool_info = next_account_info(account_info_iter)?;
|
|
|
|
let owner_info = next_account_info(account_info_iter)?;
|
|
|
|
let new_owner_info = next_account_info(account_info_iter)?;
|
|
|
|
let new_owner_fee_info = next_account_info(account_info_iter)?;
|
|
|
|
|
|
|
|
let mut stake_pool = State::deserialize(&stake_pool_info.data.borrow())?.stake_pool()?;
|
|
|
|
|
|
|
|
if *owner_info.key != stake_pool.owner {
|
|
|
|
return Err(Error::InvalidInput.into());
|
|
|
|
}
|
|
|
|
if !owner_info.is_signer {
|
|
|
|
return Err(Error::InvalidInput.into());
|
|
|
|
}
|
|
|
|
stake_pool.owner = *new_owner_info.key;
|
|
|
|
stake_pool.owner_fee_account = *new_owner_fee_info.key;
|
|
|
|
State::Init(stake_pool).serialize(&mut stake_pool_info.data.borrow_mut())?;
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-10-20 12:02:05 -07:00
|
|
|
/// Processes [Instruction](enum.Instruction.html).
|
2020-10-16 10:38:12 -07:00
|
|
|
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
|
|
|
|
let instruction = StakePoolInstruction::deserialize(input)?;
|
|
|
|
match instruction {
|
|
|
|
StakePoolInstruction::Initialize(init) => {
|
|
|
|
info!("Instruction: Init");
|
|
|
|
Self::process_initialize(program_id, init, accounts)
|
|
|
|
}
|
|
|
|
StakePoolInstruction::Deposit => {
|
|
|
|
info!("Instruction: Deposit");
|
|
|
|
Self::process_deposit(program_id, accounts)
|
|
|
|
}
|
|
|
|
StakePoolInstruction::Withdraw(amount) => {
|
|
|
|
info!("Instruction: Withdraw");
|
|
|
|
Self::process_withdraw(program_id, amount, accounts)
|
|
|
|
}
|
2020-10-20 12:02:05 -07:00
|
|
|
StakePoolInstruction::Claim => {
|
|
|
|
info!("Instruction: Claim");
|
|
|
|
Self::process_claim(program_id, accounts)
|
|
|
|
}
|
2020-10-16 10:38:12 -07:00
|
|
|
StakePoolInstruction::SetStakingAuthority => {
|
|
|
|
info!("Instruction: SetStakingAuthority");
|
|
|
|
Self::process_set_staking_auth(program_id, accounts)
|
|
|
|
}
|
|
|
|
StakePoolInstruction::SetOwner => {
|
|
|
|
info!("Instruction: SetOwner");
|
|
|
|
Self::process_set_owner(program_id, accounts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test program id for the stake-pool program.
|
|
|
|
#[cfg(not(target_arch = "bpf"))]
|
|
|
|
const STAKE_POOL_PROGRAM_ID: Pubkey = Pubkey::new_from_array([2u8; 32]);
|
|
|
|
|
|
|
|
// Test program id for the token program.
|
|
|
|
#[cfg(not(target_arch = "bpf"))]
|
|
|
|
const TOKEN_PROGRAM_ID: Pubkey = Pubkey::new_from_array([1u8; 32]);
|
|
|
|
|
|
|
|
/// Routes invokes to the token program, used for testing.
|
|
|
|
/// TODO add routing to stake program for testing
|
|
|
|
#[cfg(not(target_arch = "bpf"))]
|
|
|
|
pub fn invoke_signed<'a>(
|
|
|
|
instruction: &Instruction,
|
|
|
|
account_infos: &[AccountInfo<'a>],
|
|
|
|
signers_seeds: &[&[&[u8]]],
|
|
|
|
) -> ProgramResult {
|
|
|
|
// mimic check for token program in accounts
|
|
|
|
if !account_infos.iter().any(|x| *x.key == TOKEN_PROGRAM_ID) {
|
|
|
|
return Err(ProgramError::InvalidAccountData);
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut new_account_infos = vec![];
|
|
|
|
for meta in instruction.accounts.iter() {
|
|
|
|
for account_info in account_infos.iter() {
|
|
|
|
if meta.pubkey == *account_info.key {
|
|
|
|
let mut new_account_info = account_info.clone();
|
|
|
|
for seeds in signers_seeds.iter() {
|
|
|
|
let signer =
|
|
|
|
Pubkey::create_program_address(seeds, &STAKE_POOL_PROGRAM_ID).unwrap();
|
|
|
|
if *account_info.key == signer {
|
|
|
|
new_account_info.is_signer = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
new_account_infos.push(new_account_info);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
spl_token::processor::Processor::process(
|
|
|
|
&instruction.program_id,
|
|
|
|
&new_account_infos,
|
|
|
|
&instruction.data,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PrintProgramError for Error {
|
|
|
|
fn print<E>(&self)
|
|
|
|
where
|
|
|
|
E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
|
|
|
|
{
|
|
|
|
match self {
|
|
|
|
Error::AlreadyInUse => info!("Error: AlreadyInUse"),
|
|
|
|
Error::InvalidProgramAddress => info!("Error: InvalidProgramAddress"),
|
|
|
|
Error::InvalidOwner => info!("Error: InvalidOwner"),
|
|
|
|
Error::ExpectedToken => info!("Error: ExpectedToken"),
|
|
|
|
Error::ExpectedAccount => info!("Error: ExpectedAccount"),
|
|
|
|
Error::InvalidSupply => info!("Error: InvalidSupply"),
|
|
|
|
Error::InvalidDelegate => info!("Error: InvalidDelegate"),
|
|
|
|
Error::InvalidState => info!("Error: InvalidState"),
|
|
|
|
Error::InvalidInput => info!("Error: InvalidInput"),
|
|
|
|
Error::InvalidOutput => info!("Error: InvalidOutput"),
|
|
|
|
Error::CalculationFailure => info!("Error: CalculationFailure"),
|
2020-10-20 12:02:05 -07:00
|
|
|
Error::FeeTooHigh => info!("Error: FeeTooHigh"),
|
2020-10-16 10:38:12 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pull in syscall stubs when building for non-BPF targets
|
|
|
|
#[cfg(not(target_arch = "bpf"))]
|
|
|
|
solana_sdk::program_stubs!();
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use crate::instruction::initialize;
|
|
|
|
use crate::instruction::Fee;
|
|
|
|
use crate::instruction::InitArgs;
|
|
|
|
use core::mem::size_of;
|
|
|
|
use solana_sdk::{
|
|
|
|
account::Account, account_info::create_is_signer_account_infos, instruction::Instruction,
|
|
|
|
program_pack::Pack, rent::Rent, sysvar::rent,
|
|
|
|
};
|
|
|
|
use spl_token::{
|
|
|
|
instruction::{initialize_account, initialize_mint},
|
|
|
|
processor::Processor as SplProcessor,
|
|
|
|
state::{Account as SplAccount, Mint as SplMint},
|
|
|
|
};
|
|
|
|
|
|
|
|
fn pubkey_rand() -> Pubkey {
|
|
|
|
Pubkey::new(&rand::random::<[u8; 32]>())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn do_process_instruction(
|
|
|
|
instruction: Instruction,
|
|
|
|
accounts: Vec<&mut Account>,
|
|
|
|
) -> ProgramResult {
|
|
|
|
let mut meta = instruction
|
|
|
|
.accounts
|
|
|
|
.iter()
|
|
|
|
.zip(accounts)
|
|
|
|
.map(|(account_meta, account)| (&account_meta.pubkey, account_meta.is_signer, account))
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let account_infos = create_is_signer_account_infos(&mut meta);
|
|
|
|
if instruction.program_id == STAKE_POOL_PROGRAM_ID {
|
|
|
|
Processor::process(&instruction.program_id, &account_infos, &instruction.data)
|
|
|
|
} else {
|
|
|
|
SplProcessor::process(&instruction.program_id, &account_infos, &instruction.data)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn account_minimum_balance() -> u64 {
|
|
|
|
Rent::default().minimum_balance(SplAccount::get_packed_len())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn mint_minimum_balance() -> u64 {
|
|
|
|
Rent::default().minimum_balance(SplMint::get_packed_len())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn mint_token(
|
|
|
|
program_id: &Pubkey,
|
|
|
|
mint_key: &Pubkey,
|
|
|
|
mut mint_account: &mut Account,
|
|
|
|
authority_key: &Pubkey,
|
|
|
|
amount: u64,
|
|
|
|
) -> (Pubkey, Account) {
|
|
|
|
let account_key = pubkey_rand();
|
|
|
|
let mut account_account = Account::new(
|
|
|
|
account_minimum_balance(),
|
|
|
|
SplAccount::get_packed_len(),
|
|
|
|
&program_id,
|
|
|
|
);
|
|
|
|
let mut authority_account = Account::default();
|
|
|
|
let mut rent_sysvar_account = rent::create_account(1, &Rent::free());
|
|
|
|
|
|
|
|
// create account
|
|
|
|
do_process_instruction(
|
|
|
|
initialize_account(&program_id, &account_key, &mint_key, authority_key).unwrap(),
|
|
|
|
vec![
|
|
|
|
&mut account_account,
|
|
|
|
&mut mint_account,
|
|
|
|
&mut authority_account,
|
|
|
|
&mut rent_sysvar_account,
|
|
|
|
],
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
do_process_instruction(
|
|
|
|
spl_token::instruction::mint_to(
|
|
|
|
&program_id,
|
|
|
|
&mint_key,
|
|
|
|
&account_key,
|
|
|
|
&authority_key,
|
|
|
|
&[],
|
|
|
|
amount,
|
|
|
|
)
|
|
|
|
.unwrap(),
|
|
|
|
vec![
|
|
|
|
&mut mint_account,
|
|
|
|
&mut account_account,
|
|
|
|
&mut authority_account,
|
|
|
|
],
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
(account_key, account_account)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_mint(program_id: &Pubkey, authority_key: &Pubkey) -> (Pubkey, Account) {
|
|
|
|
let mint_key = pubkey_rand();
|
|
|
|
let mut mint_account = Account::new(
|
|
|
|
mint_minimum_balance(),
|
|
|
|
SplMint::get_packed_len(),
|
|
|
|
&program_id,
|
|
|
|
);
|
|
|
|
let mut rent_sysvar_account = rent::create_account(1, &Rent::free());
|
|
|
|
|
|
|
|
// create token mint
|
|
|
|
do_process_instruction(
|
|
|
|
initialize_mint(&program_id, &mint_key, authority_key, None, 2).unwrap(),
|
|
|
|
vec![&mut mint_account, &mut rent_sysvar_account],
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
(mint_key, mint_account)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_initialize() {
|
|
|
|
let stake_pool_key = pubkey_rand();
|
|
|
|
let mut stake_pool_account = Account::new(0, size_of::<State>(), &STAKE_POOL_PROGRAM_ID);
|
|
|
|
let owner_key = pubkey_rand();
|
|
|
|
let mut owner_account = Account::default();
|
|
|
|
let authority_key = pubkey_rand();
|
|
|
|
|
|
|
|
let (pool_mint_key, mut pool_mint_account) = create_mint(&TOKEN_PROGRAM_ID, &authority_key);
|
|
|
|
let (pool_token_key, mut pool_token_account) = mint_token(
|
|
|
|
&TOKEN_PROGRAM_ID,
|
|
|
|
&pool_mint_key,
|
|
|
|
&mut pool_mint_account,
|
|
|
|
&authority_key,
|
|
|
|
0,
|
|
|
|
);
|
|
|
|
|
|
|
|
// StakePool Init
|
|
|
|
do_process_instruction(
|
|
|
|
initialize(
|
|
|
|
&STAKE_POOL_PROGRAM_ID,
|
|
|
|
&stake_pool_key,
|
|
|
|
&owner_key,
|
|
|
|
&pool_mint_key,
|
|
|
|
&pool_token_key,
|
|
|
|
&TOKEN_PROGRAM_ID,
|
|
|
|
InitArgs {
|
|
|
|
fee: Fee {
|
2020-10-20 12:02:05 -07:00
|
|
|
denominator: 10,
|
2020-10-16 10:38:12 -07:00
|
|
|
numerator: 2,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
.unwrap(),
|
|
|
|
vec![
|
|
|
|
&mut stake_pool_account,
|
|
|
|
&mut owner_account,
|
|
|
|
&mut pool_mint_account,
|
|
|
|
&mut pool_token_account,
|
|
|
|
&mut Account::default(),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
}
|