solana-program-library/stake-pool/program/src/processor.rs

1696 lines
59 KiB
Rust

//! Program state processor
use crate::{
error::Error,
instruction::{InitArgs, StakePoolInstruction},
stake,
state::{StakePool, State},
};
use num_traits::FromPrimitive;
use solana_program::{
account_info::next_account_info, account_info::AccountInfo, decode_error::DecodeError,
entrypoint::ProgramResult, msg, program::invoke_signed, program_error::PrintProgramError,
program_error::ProgramError, program_pack::Pack, pubkey::Pubkey,
};
use std::convert::TryFrom;
/// Program state handler.
pub struct Processor {}
impl Processor {
/// 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";
/// Calculates the authority id by generating a program address.
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))
}
/// Generates seed bump for stake pool authorities
pub fn find_authority_bump_seed(
program_id: &Pubkey,
my_info: &Pubkey,
authority_type: &[u8],
) -> (Pubkey, u8) {
Pubkey::find_program_address(&[&my_info.to_bytes()[..32], authority_type], program_id)
}
/// Issue a stake_split instruction.
#[allow(clippy::too_many_arguments)]
pub fn stake_split<'a>(
stake_pool: &Pubkey,
stake_account: AccountInfo<'a>,
authority: AccountInfo<'a>,
authority_type: &[u8],
bump_seed: u8,
amount: u64,
split_stake: AccountInfo<'a>,
reserved: AccountInfo<'a>,
stake_program_info: AccountInfo<'a>,
) -> Result<(), ProgramError> {
let me_bytes = stake_pool.to_bytes();
let authority_signature_seeds = [&me_bytes[..32], authority_type, &[bump_seed]];
let signers = &[&authority_signature_seeds[..]];
let ix = stake::split_only(stake_account.key, authority.key, amount, split_stake.key);
invoke_signed(
&ix,
&[
stake_account,
reserved,
authority,
split_stake,
stake_program_info,
],
signers,
)
}
/// Issue a stake_set_owner instruction.
#[allow(clippy::too_many_arguments)]
pub fn stake_authorize<'a>(
stake_pool: &Pubkey,
stake_account: AccountInfo<'a>,
authority: AccountInfo<'a>,
authority_type: &[u8],
bump_seed: u8,
new_staker: &Pubkey,
staker_auth: stake::StakeAuthorize,
reserved: AccountInfo<'a>,
stake_program_info: AccountInfo<'a>,
) -> Result<(), ProgramError> {
let me_bytes = stake_pool.to_bytes();
let authority_signature_seeds = [&me_bytes[..32], authority_type, &[bump_seed]];
let signers = &[&authority_signature_seeds[..]];
let ix = stake::authorize(stake_account.key, authority.key, new_staker, staker_auth);
invoke_signed(
&ix,
&[stake_account, reserved, authority, stake_program_info],
signers,
)
}
/// Issue a spl_token `Burn` instruction.
#[allow(clippy::too_many_arguments)]
pub fn token_burn<'a>(
stake_pool: &Pubkey,
token_program: AccountInfo<'a>,
burn_account: AccountInfo<'a>,
mint: AccountInfo<'a>,
authority: AccountInfo<'a>,
authority_type: &[u8],
bump_seed: u8,
amount: u64,
) -> Result<(), ProgramError> {
let me_bytes = stake_pool.to_bytes();
let authority_signature_seeds = [&me_bytes[..32], authority_type, &[bump_seed]];
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.
#[allow(clippy::too_many_arguments)]
pub fn token_mint_to<'a>(
stake_pool: &Pubkey,
token_program: AccountInfo<'a>,
mint: AccountInfo<'a>,
destination: AccountInfo<'a>,
authority: AccountInfo<'a>,
authority_type: &[u8],
bump_seed: u8,
amount: u64,
) -> Result<(), ProgramError> {
let me_bytes = stake_pool.to_bytes();
let authority_signature_seeds = [&me_bytes[..32], authority_type, &[bump_seed]];
let signers = &[&authority_signature_seeds[..]];
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(
program_id: &Pubkey,
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)?;
// Stake pool account should not be already initialized
if State::Unallocated != State::deserialize(&stake_pool_info.data.borrow())? {
return Err(Error::AlreadyInUse.into());
}
// Numerator should be smaller than or equal to denominator (fee <= 1)
if init.fee.numerator > init.fee.denominator {
return Err(Error::FeeTooHigh.into());
}
// Check for owner fee account to have proper mint assigned
if *pool_mint_info.key
!= spl_token::state::Account::unpack_from_slice(&owner_fee_info.data.borrow())?.mint
{
return Err(Error::WrongAccountMint.into());
}
let (_, deposit_bump_seed) = Self::find_authority_bump_seed(
program_id,
stake_pool_info.key,
Self::AUTHORITY_DEPOSIT,
);
let (_, withdraw_bump_seed) = Self::find_authority_bump_seed(
program_id,
stake_pool_info.key,
Self::AUTHORITY_WITHDRAW,
);
let stake_pool = State::Init(StakePool {
owner: *owner_info.key,
deposit_bump_seed,
withdraw_bump_seed,
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())
}
/// Processes [Deposit](enum.Instruction.html).
pub fn process_deposit(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 deposit authority
let deposit_info = next_account_info(account_info_iter)?;
// Stake pool withdraw authority
let withdraw_info = next_account_info(account_info_iter)?;
// Stake account to join the pool (withdraw should be set to stake pool deposit)
let stake_info = next_account_info(account_info_iter)?;
// User account to receive pool tokens
let dest_user_info = next_account_info(account_info_iter)?;
// Account to receive pool fee tokens
let owner_fee_info = next_account_info(account_info_iter)?;
// Pool token mint account
let pool_mint_info = next_account_info(account_info_iter)?;
// (Reserved)
let reserved = next_account_info(account_info_iter)?;
// Pool token program id
let token_program_info = next_account_info(account_info_iter)?;
// Stake program id
let stake_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 *deposit_info.key
!= Self::authority_id(
program_id,
stake_pool_info.key,
Self::AUTHORITY_DEPOSIT,
stake_pool.deposit_bump_seed,
)?
{
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(),
Self::AUTHORITY_DEPOSIT,
stake_pool.deposit_bump_seed,
withdraw_info.key,
stake::StakeAuthorize::Withdrawer,
reserved.clone(),
stake_program_info.clone(),
)?;
Self::stake_authorize(
stake_pool_info.key,
stake_info.clone(),
deposit_info.clone(),
Self::AUTHORITY_DEPOSIT,
stake_pool.deposit_bump_seed,
withdraw_info.key,
stake::StakeAuthorize::Staker,
reserved.clone(),
stake_program_info.clone(),
)?;
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(),
Self::AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
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(),
Self::AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
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(())
}
/// Processes [Withdraw](enum.Instruction.html).
pub fn process_withdraw(
program_id: &Pubkey,
stake_amount: u64,
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 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
let pool_mint_info = next_account_info(account_info_iter)?;
// (Reserved)
let reserved = next_account_info(account_info_iter)?;
// Pool token program id
let token_program_info = next_account_info(account_info_iter)?;
// Stake program id
let stake_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 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,
stake_split_from.clone(),
withdraw_info.clone(),
Self::AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
stake_amount,
stake_split_to.clone(),
reserved.clone(),
stake_program_info.clone(),
)?;
Self::stake_authorize(
stake_pool_info.key,
stake_split_to.clone(),
withdraw_info.clone(),
Self::AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
user_stake_authority.key,
stake::StakeAuthorize::Withdrawer,
reserved.clone(),
stake_program_info.clone(),
)?;
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 [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)?;
// (Reserved)
let reserved = next_account_info(account_info_iter)?;
// Pool token program id
let token_program_info = next_account_info(account_info_iter)?;
// Stake program id
let stake_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,
reserved.clone(),
stake_program_info.clone(),
)?;
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).
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)?;
// (Reserved)
let reserved = next_account_info(account_info_iter)?;
// Stake program id
let stake_program_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
!= Self::authority_id(
program_id,
stake_pool_info.key,
Self::AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
)?
{
return Err(Error::InvalidProgramAddress.into());
}
Self::stake_authorize(
stake_pool_info.key,
stake_info.clone(),
withdraw_info.clone(),
Self::AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
staker_info.key,
stake::StakeAuthorize::Staker,
reserved.clone(),
stake_program_info.clone(),
)?;
Ok(())
}
/// Processes [SetOwner](enum.Instruction.html).
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());
}
// Check for owner fee account to have proper mint assigned
if stake_pool.pool_mint
!= spl_token::state::Account::unpack_from_slice(&new_owner_fee_info.data.borrow())?.mint
{
return Err(Error::WrongAccountMint.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(())
}
/// Processes [Instruction](enum.Instruction.html).
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
let instruction = StakePoolInstruction::deserialize(input)?;
match instruction {
StakePoolInstruction::Initialize(init) => {
msg!("Instruction: Init");
Self::process_initialize(program_id, init, accounts)
}
StakePoolInstruction::Deposit => {
msg!("Instruction: Deposit");
Self::process_deposit(program_id, accounts)
}
StakePoolInstruction::Withdraw(amount) => {
msg!("Instruction: Withdraw");
Self::process_withdraw(program_id, amount, accounts)
}
StakePoolInstruction::Claim => {
msg!("Instruction: Claim");
Self::process_claim(program_id, accounts)
}
StakePoolInstruction::SetStakingAuthority => {
msg!("Instruction: SetStakingAuthority");
Self::process_set_staking_auth(program_id, accounts)
}
StakePoolInstruction::SetOwner => {
msg!("Instruction: SetOwner");
Self::process_set_owner(program_id, accounts)
}
}
}
}
impl PrintProgramError for Error {
fn print<E>(&self)
where
E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
{
match self {
Error::AlreadyInUse => msg!("Error: AlreadyInUse"),
Error::InvalidProgramAddress => msg!("Error: InvalidProgramAddress"),
Error::InvalidOwner => msg!("Error: InvalidOwner"),
Error::ExpectedToken => msg!("Error: ExpectedToken"),
Error::ExpectedAccount => msg!("Error: ExpectedAccount"),
Error::InvalidSupply => msg!("Error: InvalidSupply"),
Error::InvalidDelegate => msg!("Error: InvalidDelegate"),
Error::InvalidState => msg!("Error: InvalidState"),
Error::InvalidInput => msg!("Error: InvalidInput"),
Error::InvalidOutput => msg!("Error: InvalidOutput"),
Error::CalculationFailure => msg!("Error: CalculationFailure"),
Error::FeeTooHigh => msg!("Error: FeeTooHigh"),
Error::WrongAccountMint => msg!("Error: WrongAccountMint"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::instruction::*;
use solana_program::{
instruction::AccountMeta, instruction::Instruction, native_token::sol_to_lamports,
program_pack::Pack, program_stubs, rent::Rent, sysvar,
};
use solana_sdk::account::{create_account, create_is_signer_account_infos, Account};
use spl_token::{
error::TokenError,
instruction::{initialize_account, initialize_mint},
processor::Processor as TokenProcessor,
state::{Account as SplAccount, Mint as SplMint},
};
/// Test program id for the stake-pool program.
const STAKE_POOL_PROGRAM_ID: Pubkey = Pubkey::new_from_array([2u8; 32]);
/// Test program id for the token program.
const TOKEN_PROGRAM_ID: Pubkey = Pubkey::new_from_array([1u8; 32]);
/// Actual stake account program id, used for tests
fn stake_program_id() -> Pubkey {
"Stake11111111111111111111111111111111111111"
.parse::<Pubkey>()
.unwrap()
}
const STAKE_ACCOUNT_LEN: usize = 100;
struct TestSyscallStubs {}
impl program_stubs::SyscallStubs for TestSyscallStubs {
fn sol_invoke_signed(
&self,
instruction: &Instruction,
account_infos: &[AccountInfo],
signers_seeds: &[&[&[u8]]],
) -> ProgramResult {
msg!("TestSyscallStubs::sol_invoke_signed()");
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);
}
}
}
match instruction.program_id {
TOKEN_PROGRAM_ID => invoke_token(&new_account_infos, &instruction.data),
pubkey => {
if pubkey == stake_program_id() {
invoke_stake(&new_account_infos, &instruction.data)
} else {
Err(ProgramError::IncorrectProgramId)
}
}
}
}
}
/// Mocks token instruction invocation
pub fn invoke_token<'a>(account_infos: &[AccountInfo<'a>], input: &[u8]) -> ProgramResult {
spl_token::processor::Processor::process(&TOKEN_PROGRAM_ID, &account_infos, &input)
}
/// Mocks stake account instruction invocation
pub fn invoke_stake<'a>(_account_infos: &[AccountInfo<'a>], _input: &[u8]) -> ProgramResult {
// For now always return ok
Ok(())
}
fn test_syscall_stubs() {
use std::sync::Once;
static ONCE: Once = Once::new();
ONCE.call_once(|| {
program_stubs::set_syscall_stubs(Box::new(TestSyscallStubs {}));
});
}
struct StakePoolInfo {
pub pool_key: Pubkey,
pub pool_account: Account,
pub deposit_bump_seed: u8,
pub withdraw_bump_seed: u8,
pub deposit_authority_key: Pubkey,
pub withdraw_authority_key: Pubkey,
pub fee: Fee,
pub owner_key: Pubkey,
pub owner_account: Account,
pub owner_fee_key: Pubkey,
pub owner_fee_account: Account,
pub mint_key: Pubkey,
pub mint_account: Account,
}
struct DepositInfo {
result: ProgramResult,
stake_account_key: Pubkey,
stake_account_account: Account,
}
struct Deposit {
stake_balance: u64,
tokens_to_issue: u64,
user_token_balance: u64,
fee_token_balance: u64,
pool_info: StakePoolInfo,
pool_token_receiver: TokenInfo,
}
struct WithdrawInfo {
result: ProgramResult,
}
struct Withdraw {
stake_balance: u64,
tokens_to_issue: u64,
withdraw_amount: u64,
tokens_to_burn: u64,
pool_info: StakePoolInfo,
user_withdrawer_key: Pubkey,
pool_token_receiver: TokenInfo,
deposit_info: DepositInfo,
}
struct ClaimInfo {
result: ProgramResult,
}
struct Claim {
tokens_to_issue: u64,
pool_info: StakePoolInfo,
user_withdrawer_key: Pubkey,
pool_token_receiver: TokenInfo,
deposit_info: DepositInfo,
allow_burn_to: Pubkey,
}
fn do_process_instruction(
instruction: Instruction,
accounts: Vec<&mut Account>,
) -> ProgramResult {
test_syscall_stubs();
// approximate the logic in the actual runtime which runs the instruction
// and only updates accounts if the instruction is successful
let mut account_clones = accounts.iter().map(|x| (*x).clone()).collect::<Vec<_>>();
let mut meta = instruction
.accounts
.iter()
.zip(account_clones.iter_mut())
.map(|(account_meta, account)| (&account_meta.pubkey, account_meta.is_signer, account))
.collect::<Vec<_>>();
let mut account_infos = create_is_signer_account_infos(&mut meta);
let res = if instruction.program_id == STAKE_POOL_PROGRAM_ID {
Processor::process(&instruction.program_id, &account_infos, &instruction.data)
} else {
TokenProcessor::process(&instruction.program_id, &account_infos, &instruction.data)
};
if res.is_ok() {
let mut account_metas = instruction
.accounts
.iter()
.zip(accounts)
.map(|(account_meta, account)| (&account_meta.pubkey, account))
.collect::<Vec<_>>();
for account_info in account_infos.iter_mut() {
for account_meta in account_metas.iter_mut() {
if account_info.key == account_meta.0 {
let account = &mut account_meta.1;
account.owner = *account_info.owner;
account.lamports = **account_info.lamports.borrow();
account.data = account_info.data.borrow().to_vec();
}
}
}
}
res
}
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())
}
struct TokenInfo {
key: Pubkey,
account: Account,
owner: Pubkey,
}
fn create_token_account(
program_id: &Pubkey,
mint_key: &Pubkey,
mint_account: &mut Account,
) -> TokenInfo {
let mut token = TokenInfo {
key: Pubkey::new_unique(),
account: Account::new(
account_minimum_balance(),
SplAccount::get_packed_len(),
&program_id,
),
owner: Pubkey::new_unique(),
};
let mut rent_sysvar_account = create_account(&Rent::free(), 1);
let mut owner_account = Account::default();
// create account
do_process_instruction(
initialize_account(&program_id, &token.key, &mint_key, &token.owner).unwrap(),
vec![
&mut token.account,
mint_account,
&mut owner_account,
&mut rent_sysvar_account,
],
)
.unwrap();
token
}
fn create_mint(program_id: &Pubkey, authority_key: &Pubkey) -> (Pubkey, Account) {
let mint_key = Pubkey::new_unique();
let mut mint_account = Account::new(
mint_minimum_balance(),
SplMint::get_packed_len(),
&program_id,
);
let mut rent_sysvar_account = create_account(&Rent::free(), 1);
// 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)
}
fn approve_token(
program_id: &Pubkey,
token_account_pubkey: &Pubkey,
mut token_account_account: &mut Account,
delegate_pubkey: &Pubkey,
owner_pubkey: &Pubkey,
amount: u64,
) {
do_process_instruction(
spl_token::instruction::approve(
&program_id,
token_account_pubkey,
delegate_pubkey,
owner_pubkey,
&[],
amount,
)
.unwrap(),
vec![
&mut token_account_account,
&mut Account::default(),
&mut Account::default(),
],
)
.unwrap();
}
const FEE_DEFAULT: Fee = Fee {
denominator: 100,
numerator: 5,
};
fn create_stake_pool_default() -> StakePoolInfo {
create_stake_pool(FEE_DEFAULT)
}
fn create_stake_pool(fee: Fee) -> StakePoolInfo {
let stake_pool_key = Pubkey::new_unique();
let owner_key = Pubkey::new_unique();
let mut stake_pool_account = Account::new(0, State::LEN, &STAKE_POOL_PROGRAM_ID);
let mut owner_account = Account::default();
// Calculate authority addresses
let (deposit_authority_key, deposit_bump_seed) = Pubkey::find_program_address(
&[&stake_pool_key.to_bytes()[..32], b"deposit"],
&STAKE_POOL_PROGRAM_ID,
);
let (withdraw_authority_key, withdraw_bump_seed) = Pubkey::find_program_address(
&[&stake_pool_key.to_bytes()[..32], b"withdraw"],
&STAKE_POOL_PROGRAM_ID,
);
let (mint_key, mut mint_account) = create_mint(&TOKEN_PROGRAM_ID, &withdraw_authority_key);
let mut token = create_token_account(&TOKEN_PROGRAM_ID, &mint_key, &mut mint_account);
// StakePool Init
let _result = do_process_instruction(
initialize(
&STAKE_POOL_PROGRAM_ID,
&stake_pool_key,
&owner_key,
&mint_key,
&token.key,
&TOKEN_PROGRAM_ID,
InitArgs { fee },
)
.unwrap(),
vec![
&mut stake_pool_account,
&mut owner_account,
&mut mint_account,
&mut token.account,
&mut Account::default(),
],
)
.expect("Error on stake pool initialize");
StakePoolInfo {
pool_key: stake_pool_key,
pool_account: stake_pool_account,
deposit_bump_seed,
withdraw_bump_seed,
deposit_authority_key,
withdraw_authority_key,
fee,
owner_key,
owner_account,
owner_fee_key: token.key,
owner_fee_account: token.account,
mint_key,
mint_account,
}
}
fn do_deposit(
pool_info: &mut StakePoolInfo,
stake_balance: u64,
token: &mut TokenInfo,
) -> DepositInfo {
let stake_account_key = Pubkey::new_unique();
let mut stake_account_account =
Account::new(stake_balance, STAKE_ACCOUNT_LEN, &stake_program_id());
// TODO: Set stake account Withdrawer authority to pool_info.deposit_authority_key
// Call deposit
let result = do_process_instruction(
deposit(
&STAKE_POOL_PROGRAM_ID,
&pool_info.pool_key,
&pool_info.deposit_authority_key,
&pool_info.withdraw_authority_key,
&stake_account_key,
&token.key,
&pool_info.owner_fee_key,
&pool_info.mint_key,
&TOKEN_PROGRAM_ID,
&stake_program_id(),
)
.unwrap(),
vec![
&mut pool_info.pool_account,
&mut Account::default(),
&mut Account::default(),
&mut stake_account_account,
&mut token.account,
&mut pool_info.owner_fee_account,
&mut pool_info.mint_account,
&mut Account::default(),
&mut Account::default(),
&mut Account::default(),
],
);
DepositInfo {
result,
stake_account_key,
stake_account_account,
}
}
fn do_withdraw(test_data: &mut Withdraw) -> WithdrawInfo {
approve_token(
&TOKEN_PROGRAM_ID,
&test_data.pool_token_receiver.key,
&mut test_data.pool_token_receiver.account,
&test_data.pool_info.withdraw_authority_key,
&test_data.pool_token_receiver.owner,
test_data.tokens_to_burn,
);
let stake_to_receive_key = Pubkey::new_unique();
let mut stake_to_receive_account = Account::new(
test_data.stake_balance,
STAKE_ACCOUNT_LEN,
&stake_program_id(),
);
let result = do_process_instruction(
withdraw(
&STAKE_POOL_PROGRAM_ID,
&test_data.pool_info.pool_key,
&test_data.pool_info.withdraw_authority_key,
&test_data.deposit_info.stake_account_key,
&stake_to_receive_key,
&test_data.user_withdrawer_key,
&test_data.pool_token_receiver.key,
&test_data.pool_info.mint_key,
&TOKEN_PROGRAM_ID,
&stake_program_id(),
test_data.withdraw_amount,
)
.unwrap(),
vec![
&mut test_data.pool_info.pool_account,
&mut Account::default(),
&mut test_data.deposit_info.stake_account_account,
&mut stake_to_receive_account,
&mut Account::default(),
&mut test_data.pool_token_receiver.account,
&mut test_data.pool_info.mint_account,
&mut Account::default(),
&mut Account::default(),
&mut Account::default(),
],
);
WithdrawInfo { result }
}
fn do_claim(test_data: &mut Claim) -> ClaimInfo {
approve_token(
&TOKEN_PROGRAM_ID,
&test_data.pool_token_receiver.key,
&mut test_data.pool_token_receiver.account,
&test_data.allow_burn_to,
&test_data.pool_token_receiver.owner,
test_data.tokens_to_issue,
);
let result = do_process_instruction(
claim(
&STAKE_POOL_PROGRAM_ID,
&test_data.pool_info.pool_key,
&test_data.pool_info.withdraw_authority_key,
&test_data.deposit_info.stake_account_key,
&test_data.user_withdrawer_key,
&test_data.pool_token_receiver.key,
&test_data.pool_info.mint_key,
&TOKEN_PROGRAM_ID,
&stake_program_id(),
)
.unwrap(),
vec![
&mut test_data.pool_info.pool_account,
&mut Account::default(),
&mut test_data.deposit_info.stake_account_account,
&mut Account::default(),
&mut test_data.pool_token_receiver.account,
&mut test_data.pool_info.mint_account,
&mut Account::default(),
&mut Account::default(),
&mut Account::default(),
],
);
ClaimInfo { result }
}
fn set_staking_authority_without_signer(
program_id: &Pubkey,
stake_pool: &Pubkey,
stake_pool_owner: &Pubkey,
stake_pool_withdraw: &Pubkey,
stake_account_to_update: &Pubkey,
stake_account_new_authority: &Pubkey,
stake_program_id: &Pubkey,
) -> Result<Instruction, ProgramError> {
let args = StakePoolInstruction::SetStakingAuthority;
let data = args.serialize()?;
let accounts = vec![
AccountMeta::new(*stake_pool, false),
AccountMeta::new_readonly(*stake_pool_owner, false),
AccountMeta::new_readonly(*stake_pool_withdraw, false),
AccountMeta::new(*stake_account_to_update, false),
AccountMeta::new_readonly(*stake_account_new_authority, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(*stake_program_id, false),
];
Ok(Instruction {
program_id: *program_id,
accounts,
data,
})
}
fn set_owner_without_signer(
program_id: &Pubkey,
stake_pool: &Pubkey,
stake_pool_owner: &Pubkey,
stake_pool_new_owner: &Pubkey,
stake_pool_new_fee_receiver: &Pubkey,
) -> Result<Instruction, ProgramError> {
let args = StakePoolInstruction::SetOwner;
let data = args.serialize()?;
let accounts = vec![
AccountMeta::new(*stake_pool, false),
AccountMeta::new_readonly(*stake_pool_owner, false),
AccountMeta::new_readonly(*stake_pool_new_owner, false),
AccountMeta::new_readonly(*stake_pool_new_fee_receiver, false),
];
Ok(Instruction {
program_id: *program_id,
accounts,
data,
})
}
#[test]
fn test_initialize() {
let pool_info = create_stake_pool_default();
// Read account data
let state = State::deserialize(&pool_info.pool_account.data).unwrap();
match state {
State::Unallocated => panic!("Stake pool state is not initialized after init"),
State::Init(stake_pool) => {
assert_eq!(stake_pool.deposit_bump_seed, pool_info.deposit_bump_seed);
assert_eq!(stake_pool.withdraw_bump_seed, pool_info.withdraw_bump_seed);
assert_eq!(stake_pool.fee.numerator, FEE_DEFAULT.numerator);
assert_eq!(stake_pool.fee.denominator, FEE_DEFAULT.denominator);
assert_eq!(stake_pool.owner, pool_info.owner_key);
assert_eq!(stake_pool.pool_mint, pool_info.mint_key);
assert_eq!(stake_pool.owner_fee_account, pool_info.owner_fee_key);
assert_eq!(stake_pool.token_program_id, TOKEN_PROGRAM_ID);
assert_eq!(stake_pool.stake_total, 0);
assert_eq!(stake_pool.pool_total, 0);
}
}
}
fn initialize_deposit_test() -> Deposit {
let stake_balance: u64 = sol_to_lamports(10.0);
let tokens_to_issue: u64 = 10_000_000_000;
let user_token_balance: u64 = 9_800_000_000;
let fee_token_balance: u64 = 200_000_000;
assert_eq!(tokens_to_issue, user_token_balance + fee_token_balance);
// Create stake account
let mut pool_info = create_stake_pool(Fee {
denominator: 100,
numerator: 2,
});
let pool_token_receiver = create_token_account(
&TOKEN_PROGRAM_ID,
&pool_info.mint_key,
&mut pool_info.mint_account,
);
Deposit {
stake_balance,
tokens_to_issue,
user_token_balance,
fee_token_balance,
pool_info,
pool_token_receiver,
}
}
#[test]
fn test_deposit() {
let mut test_data = initialize_deposit_test();
let deposit_info = do_deposit(
&mut test_data.pool_info,
test_data.stake_balance,
&mut test_data.pool_token_receiver,
);
deposit_info.result.expect("Fail on deposit");
// Test stake pool balance
let state = State::deserialize(&test_data.pool_info.pool_account.data).unwrap();
assert!(
matches!(state, State::Init(stake_pool) if stake_pool.stake_total == test_data.stake_balance && stake_pool.pool_total == test_data.tokens_to_issue)
);
// Test token balances
let user_token_state =
SplAccount::unpack_from_slice(&test_data.pool_token_receiver.account.data)
.expect("User token account is not initialized after deposit");
assert_eq!(user_token_state.amount, test_data.user_token_balance);
let fee_token_state =
SplAccount::unpack_from_slice(&test_data.pool_info.owner_fee_account.data)
.expect("Fee token account is not initialized after deposit");
assert_eq!(fee_token_state.amount, test_data.fee_token_balance);
// Test mint total issued tokens
let mint_state = SplMint::unpack_from_slice(&test_data.pool_info.mint_account.data)
.expect("Mint account is not initialized after deposit");
assert_eq!(mint_state.supply, test_data.stake_balance);
// TODO: Check stake account Withdrawer to match stake pool withdraw authority
}
#[test]
fn negative_test_deposit_wrong_withdraw_authority() {
let mut test_data = initialize_deposit_test();
test_data.pool_info.withdraw_authority_key = Pubkey::new_unique();
let deposit_info = do_deposit(
&mut test_data.pool_info,
test_data.stake_balance,
&mut test_data.pool_token_receiver,
);
assert_eq!(
deposit_info.result,
Err(Error::InvalidProgramAddress.into())
);
}
#[test]
fn negative_test_deposit_wrong_deposit_authority() {
let mut test_data = initialize_deposit_test();
test_data.pool_info.deposit_authority_key = Pubkey::new_unique();
let deposit_info = do_deposit(
&mut test_data.pool_info,
test_data.stake_balance,
&mut test_data.pool_token_receiver,
);
assert_eq!(
deposit_info.result,
Err(Error::InvalidProgramAddress.into())
);
}
#[test]
fn negative_test_deposit_wrong_owner_fee_account() {
let mut test_data = initialize_deposit_test();
test_data.pool_info.owner_fee_account = Account::default();
let deposit_info = do_deposit(
&mut test_data.pool_info,
test_data.stake_balance,
&mut test_data.pool_token_receiver,
);
assert_eq!(deposit_info.result, Err(ProgramError::InvalidAccountData));
}
fn initialize_withdraw_test() -> Withdraw {
let stake_balance = sol_to_lamports(20.0);
let tokens_to_issue = 20_000_000_000;
let withdraw_amount = sol_to_lamports(5.0);
let tokens_to_burn = 5_000_000_000;
let mut pool_info = create_stake_pool_default();
let user_withdrawer_key = Pubkey::new_unique();
let mut pool_token_receiver = create_token_account(
&TOKEN_PROGRAM_ID,
&pool_info.mint_key,
&mut pool_info.mint_account,
);
let deposit_info = do_deposit(&mut pool_info, stake_balance, &mut pool_token_receiver);
Withdraw {
stake_balance,
tokens_to_issue,
withdraw_amount,
tokens_to_burn,
pool_info,
user_withdrawer_key,
pool_token_receiver,
deposit_info,
}
}
#[test]
fn test_withdraw() {
let mut test_data = initialize_withdraw_test();
let withdraw_info = do_withdraw(&mut test_data);
withdraw_info.result.expect("Fail on deposit");
let fee_amount = test_data.stake_balance * FEE_DEFAULT.numerator / FEE_DEFAULT.denominator;
let user_token_state =
SplAccount::unpack_from_slice(&test_data.pool_token_receiver.account.data)
.expect("User token account is not initialized after withdraw");
assert_eq!(
user_token_state.amount,
test_data.stake_balance - fee_amount - test_data.withdraw_amount
);
// Check stake pool token amounts
let state = State::deserialize(&test_data.pool_info.pool_account.data).unwrap();
assert!(
matches!(state, State::Init(stake_pool) if stake_pool.stake_total == test_data.stake_balance - test_data.withdraw_amount && stake_pool.pool_total == test_data.tokens_to_issue - test_data.tokens_to_burn)
);
}
#[test]
fn negative_test_withdraw_wrong_withdraw_authority() {
let mut test_data = initialize_withdraw_test();
test_data.pool_info.withdraw_authority_key = Pubkey::new_unique();
let withdraw_info = do_withdraw(&mut test_data);
assert_eq!(
withdraw_info.result,
Err(Error::InvalidProgramAddress.into())
);
}
#[test]
fn negative_test_withdraw_all() {
let mut test_data = initialize_withdraw_test();
test_data.withdraw_amount = test_data.stake_balance;
let withdraw_info = do_withdraw(&mut test_data);
assert_eq!(
withdraw_info.result,
Err(Error::InvalidProgramAddress.into())
);
}
#[test]
fn negative_test_withdraw_excess_amount() {
let mut test_data = initialize_withdraw_test();
test_data.withdraw_amount *= 2;
let withdraw_info = do_withdraw(&mut test_data);
assert_eq!(
withdraw_info.result,
Err(Error::InvalidProgramAddress.into())
);
}
fn initialize_claim_test() -> Claim {
let mut pool_info = create_stake_pool_default();
let user_withdrawer_key = Pubkey::new_unique();
let stake_balance = sol_to_lamports(20.0);
let tokens_to_issue = 20_000_000_000;
let mut pool_token_receiver = create_token_account(
&TOKEN_PROGRAM_ID,
&pool_info.mint_key,
&mut pool_info.mint_account,
);
let deposit_info = do_deposit(&mut pool_info, stake_balance, &mut pool_token_receiver);
// Need to deposit more to cover deposit fee
let fee_amount = stake_balance * FEE_DEFAULT.numerator / FEE_DEFAULT.denominator;
let extra_deposit = (fee_amount * FEE_DEFAULT.denominator)
/ (FEE_DEFAULT.denominator - FEE_DEFAULT.numerator);
let _extra_deposit_info =
do_deposit(&mut pool_info, extra_deposit, &mut pool_token_receiver);
Claim {
tokens_to_issue,
allow_burn_to: pool_info.withdraw_authority_key,
pool_info,
user_withdrawer_key,
pool_token_receiver,
deposit_info,
}
}
#[test]
fn test_claim() {
let mut test_data = initialize_claim_test();
let claim_info = do_claim(&mut test_data);
assert_eq!(claim_info.result, Ok(()));
let user_token_state =
SplAccount::unpack_from_slice(&test_data.pool_token_receiver.account.data)
.expect("User token account is not initialized after withdraw");
assert_eq!(user_token_state.amount, 0);
// TODO: Check deposit_info.stake_account_account Withdrawer to change to user_withdrawer_key
}
#[test]
fn negative_test_claim_not_enough_approved() {
let mut test_data = initialize_claim_test();
test_data.tokens_to_issue /= 2; // Approve less tokens for burning than required
let claim_info = do_claim(&mut test_data);
assert_eq!(claim_info.result, Err(TokenError::InsufficientFunds.into()));
}
#[test]
fn negative_test_claim_approve_to_wrong_account() {
let mut test_data = initialize_claim_test();
test_data.allow_burn_to = test_data.pool_info.deposit_authority_key; // Change token burn authority
let claim_info = do_claim(&mut test_data);
assert_eq!(claim_info.result, Err(TokenError::OwnerMismatch.into()));
}
#[test]
fn negative_test_claim_twice() {
let mut test_data = initialize_claim_test();
let claim_info = do_claim(&mut test_data);
assert_eq!(claim_info.result, Ok(()));
let result = do_process_instruction(
claim(
&STAKE_POOL_PROGRAM_ID,
&test_data.pool_info.pool_key,
&test_data.pool_info.withdraw_authority_key,
&test_data.deposit_info.stake_account_key,
&test_data.user_withdrawer_key,
&test_data.pool_token_receiver.key,
&test_data.pool_info.mint_key,
&TOKEN_PROGRAM_ID,
&stake_program_id(),
)
.unwrap(),
vec![
&mut test_data.pool_info.pool_account,
&mut Account::default(),
&mut test_data.deposit_info.stake_account_account,
&mut Account::default(),
&mut test_data.pool_token_receiver.account,
&mut test_data.pool_info.mint_account,
&mut Account::default(),
&mut Account::default(),
&mut Account::default(),
],
);
assert_eq!(result, Err(Error::InvalidProgramAddress.into()));
}
#[test]
fn test_set_staking_authority() {
let mut pool_info = create_stake_pool_default();
let stake_balance = sol_to_lamports(10.0);
let stake_key = Pubkey::new_unique();
let mut stake_account = Account::new(stake_balance, STAKE_ACCOUNT_LEN, &stake_program_id());
let new_authority_key = Pubkey::new_unique();
let mut new_authority_account =
Account::new(stake_balance, STAKE_ACCOUNT_LEN, &stake_program_id());
let _result = do_process_instruction(
set_staking_authority(
&STAKE_POOL_PROGRAM_ID,
&pool_info.pool_key,
&pool_info.owner_key,
&pool_info.withdraw_authority_key,
&stake_key,
&new_authority_key,
&stake_program_id(),
)
.unwrap(),
vec![
&mut pool_info.pool_account,
&mut pool_info.owner_fee_account,
&mut Account::default(),
&mut stake_account,
&mut new_authority_account,
&mut Account::default(),
&mut Account::default(),
],
)
.expect("Error on set_owner");
}
#[test]
fn negative_test_set_staking_authority_owner() {
let mut pool_info = create_stake_pool_default();
let stake_balance = sol_to_lamports(10.0);
let stake_key = Pubkey::new_unique();
let mut stake_account = Account::new(stake_balance, STAKE_ACCOUNT_LEN, &stake_program_id());
let new_authority_key = Pubkey::new_unique();
let mut new_authority_account =
Account::new(stake_balance, STAKE_ACCOUNT_LEN, &stake_program_id());
let result = do_process_instruction(
set_staking_authority(
&STAKE_POOL_PROGRAM_ID,
&pool_info.pool_key,
&Pubkey::new_unique(),
&pool_info.withdraw_authority_key,
&stake_key,
&new_authority_key,
&stake_program_id(),
)
.unwrap(),
vec![
&mut pool_info.pool_account,
&mut Account::default(),
&mut Account::default(),
&mut stake_account,
&mut new_authority_account,
&mut Account::default(),
&mut Account::default(),
],
);
assert_eq!(result, Err(Error::InvalidInput.into()));
}
#[test]
fn negative_test_set_staking_authority_signer() {
let mut pool_info = create_stake_pool_default();
let stake_balance = sol_to_lamports(10.0);
let stake_key = Pubkey::new_unique();
let mut stake_account = Account::new(stake_balance, STAKE_ACCOUNT_LEN, &stake_program_id());
let new_authority_key = Pubkey::new_unique();
let mut new_authority_account =
Account::new(stake_balance, STAKE_ACCOUNT_LEN, &stake_program_id());
let result = do_process_instruction(
set_staking_authority_without_signer(
&STAKE_POOL_PROGRAM_ID,
&pool_info.pool_key,
&Pubkey::new_unique(),
&pool_info.withdraw_authority_key,
&stake_key,
&new_authority_key,
&stake_program_id(),
)
.unwrap(),
vec![
&mut pool_info.pool_account,
&mut pool_info.owner_fee_account,
&mut Account::default(),
&mut stake_account,
&mut new_authority_account,
&mut Account::default(),
&mut Account::default(),
],
);
assert_eq!(result, Err(Error::InvalidInput.into()));
}
#[test]
fn test_set_owner() {
let mut pool_info = create_stake_pool_default();
let new_owner_key = Pubkey::new_unique();
let mut new_owner_account = Account::default();
let mut new_owner_fee = create_token_account(
&TOKEN_PROGRAM_ID,
&pool_info.mint_key,
&mut pool_info.mint_account,
);
let _result = do_process_instruction(
set_owner(
&STAKE_POOL_PROGRAM_ID,
&pool_info.pool_key,
&pool_info.owner_key,
&new_owner_key,
&new_owner_fee.key,
)
.unwrap(),
vec![
&mut pool_info.pool_account,
&mut pool_info.owner_account,
&mut new_owner_account,
&mut new_owner_fee.account,
],
)
.expect("Error on set_owner");
let state = State::deserialize(&pool_info.pool_account.data).unwrap();
assert!(
matches!(state, State::Init(stake_pool) if stake_pool.owner == new_owner_key && stake_pool.owner_fee_account == new_owner_fee.key)
);
}
#[test]
fn negative_test_set_owner_owner() {
let mut pool_info = create_stake_pool_default();
let new_owner_key = Pubkey::new_unique();
let mut new_owner_account = Account::default();
let mut new_owner_fee = create_token_account(
&TOKEN_PROGRAM_ID,
&pool_info.mint_key,
&mut pool_info.mint_account,
);
let result = do_process_instruction(
set_owner(
&STAKE_POOL_PROGRAM_ID,
&pool_info.pool_key,
&Pubkey::new_unique(),
&new_owner_key,
&new_owner_fee.key,
)
.unwrap(),
vec![
&mut pool_info.pool_account,
&mut Account::default(),
&mut new_owner_account,
&mut new_owner_fee.account,
],
);
assert_eq!(result, Err(Error::InvalidInput.into()));
}
#[test]
fn negative_test_set_owner_signer() {
let mut pool_info = create_stake_pool_default();
let new_owner_key = Pubkey::new_unique();
let mut new_owner_account = Account::default();
let mut new_owner_fee = create_token_account(
&TOKEN_PROGRAM_ID,
&pool_info.mint_key,
&mut pool_info.mint_account,
);
let result = do_process_instruction(
set_owner_without_signer(
&STAKE_POOL_PROGRAM_ID,
&pool_info.pool_key,
&pool_info.owner_key,
&new_owner_key,
&new_owner_fee.key,
)
.unwrap(),
vec![
&mut pool_info.pool_account,
&mut pool_info.owner_account,
&mut new_owner_account,
&mut new_owner_fee.account,
],
);
assert_eq!(result, Err(Error::InvalidInput.into()));
}
}