solana-program-library/binary-oracle-pair/program/src/processor.rs

590 lines
21 KiB
Rust

//! Program state processor
use crate::{
error::PoolError,
instruction::PoolInstruction,
state::{Decision, Pool, POOL_VERSION},
};
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
account_info::next_account_info,
account_info::AccountInfo,
clock::{Clock, Slot},
entrypoint::ProgramResult,
msg,
program::{invoke, invoke_signed},
program_error::ProgramError,
program_pack::{IsInitialized, Pack},
pubkey::Pubkey,
rent::Rent,
sysvar::Sysvar,
};
use spl_token::state::{Account, Mint};
/// Program state handler.
pub struct Processor {}
impl Processor {
/// Calculates the authority id by generating a program address.
pub fn authority_id(
program_id: &Pubkey,
my_info: &Pubkey,
bump_seed: u8,
) -> Result<Pubkey, ProgramError> {
Pubkey::create_program_address(&[&my_info.to_bytes()[..32], &[bump_seed]], program_id)
.map_err(|_| PoolError::InvalidAuthorityData.into())
}
/// Transfer tokens with authority
#[allow(clippy::too_many_arguments)]
pub fn transfer<'a>(
token_program_id: AccountInfo<'a>,
source_account: AccountInfo<'a>,
destination_account: AccountInfo<'a>,
program_authority_account: AccountInfo<'a>,
user_authority_account: AccountInfo<'a>,
amount: u64,
pool_pub_key: &Pubkey,
bump_seed: u8,
) -> ProgramResult {
if program_authority_account.key == user_authority_account.key {
let me_bytes = pool_pub_key.to_bytes();
let authority_signature_seeds = [&me_bytes[..32], &[bump_seed]];
let signers = &[&authority_signature_seeds[..]];
invoke_signed(
&spl_token::instruction::transfer(
token_program_id.key,
source_account.key,
destination_account.key,
program_authority_account.key,
&[program_authority_account.key],
amount,
)
.unwrap(),
&[
token_program_id,
program_authority_account,
source_account,
destination_account,
],
signers,
)
} else {
invoke(
&spl_token::instruction::transfer(
token_program_id.key,
source_account.key,
destination_account.key,
user_authority_account.key,
&[user_authority_account.key],
amount,
)
.unwrap(),
&[
token_program_id,
user_authority_account,
source_account,
destination_account,
],
)
}
}
/// Mint tokens
pub fn mint<'a>(
token_program_id: AccountInfo<'a>,
mint_account: AccountInfo<'a>,
destination_account: AccountInfo<'a>,
authority_account: AccountInfo<'a>,
amount: u64,
pool_pub_key: &Pubkey,
bump_seed: u8,
) -> ProgramResult {
let me_bytes = pool_pub_key.to_bytes();
let authority_signature_seeds = [&me_bytes[..32], &[bump_seed]];
let signers = &[&authority_signature_seeds[..]];
invoke_signed(
&spl_token::instruction::mint_to(
token_program_id.key,
mint_account.key,
destination_account.key,
authority_account.key,
&[authority_account.key],
amount,
)
.unwrap(),
&[
token_program_id,
mint_account,
destination_account,
authority_account,
],
signers,
)
}
/// Burn tokens
#[allow(clippy::too_many_arguments)]
pub fn burn<'a>(
token_program_id: AccountInfo<'a>,
source_account: AccountInfo<'a>,
mint_account: AccountInfo<'a>,
program_authority_account: AccountInfo<'a>,
user_authority_account: AccountInfo<'a>,
amount: u64,
pool_pub_key: &Pubkey,
bump_seed: u8,
) -> ProgramResult {
if program_authority_account.key == user_authority_account.key {
let me_bytes = pool_pub_key.to_bytes();
let authority_signature_seeds = [&me_bytes[..32], &[bump_seed]];
let signers = &[&authority_signature_seeds[..]];
invoke_signed(
&spl_token::instruction::burn(
token_program_id.key,
source_account.key,
mint_account.key,
program_authority_account.key,
&[program_authority_account.key],
amount,
)
.unwrap(),
&[
token_program_id,
program_authority_account,
source_account,
mint_account,
],
signers,
)
} else {
invoke(
&spl_token::instruction::burn(
token_program_id.key,
source_account.key,
mint_account.key,
user_authority_account.key,
&[user_authority_account.key],
amount,
)
.unwrap(),
&[
token_program_id,
user_authority_account,
source_account,
mint_account,
],
)
}
}
/// Initialize the pool
pub fn process_init_pool(
program_id: &Pubkey,
accounts: &[AccountInfo],
mint_end_slot: Slot,
decide_end_slot: Slot,
bump_seed: u8,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let pool_account_info = next_account_info(account_info_iter)?;
let authority_info = next_account_info(account_info_iter)?;
let decider_info = next_account_info(account_info_iter)?;
let deposit_token_mint_info = next_account_info(account_info_iter)?;
let deposit_account_info = next_account_info(account_info_iter)?;
let token_pass_mint_info = next_account_info(account_info_iter)?;
let token_fail_mint_info = next_account_info(account_info_iter)?;
let rent_info = next_account_info(account_info_iter)?;
let rent = &Rent::from_account_info(rent_info)?;
let token_program_info = next_account_info(account_info_iter)?;
let mut pool = Pool::try_from_slice(&pool_account_info.data.borrow())?;
// Pool account should not be already initialized
if pool.is_initialized() {
return Err(PoolError::AlreadyInUse.into());
}
// Check if pool account is rent-exempt
if !rent.is_exempt(pool_account_info.lamports(), pool_account_info.data_len()) {
return Err(PoolError::NotRentExempt.into());
}
// Check if deposit token's mint owner is token program
if deposit_token_mint_info.owner != token_program_info.key {
return Err(PoolError::InvalidTokenMint.into());
}
// Check if deposit token mint is initialized
let deposit_token_mint = Mint::unpack(&deposit_token_mint_info.data.borrow())?;
// Check if bump seed is correct
let authority = Self::authority_id(program_id, pool_account_info.key, bump_seed)?;
if &authority != authority_info.key {
return Err(PoolError::InvalidAuthorityAccount.into());
}
let deposit_account = Account::unpack_unchecked(&deposit_account_info.data.borrow())?;
if deposit_account.is_initialized() {
return Err(PoolError::DepositAccountInUse.into());
}
let token_pass = Mint::unpack_unchecked(&token_pass_mint_info.data.borrow())?;
if token_pass.is_initialized() {
return Err(PoolError::TokenMintInUse.into());
}
let token_fail = Mint::unpack_unchecked(&token_fail_mint_info.data.borrow())?;
if token_fail.is_initialized() {
return Err(PoolError::TokenMintInUse.into());
}
invoke(
&spl_token::instruction::initialize_account(
token_program_info.key,
deposit_account_info.key,
deposit_token_mint_info.key,
authority_info.key,
)
.unwrap(),
&[
token_program_info.clone(),
deposit_account_info.clone(),
deposit_token_mint_info.clone(),
authority_info.clone(),
rent_info.clone(),
],
)?;
invoke(
&spl_token::instruction::initialize_mint(
&spl_token::id(),
token_pass_mint_info.key,
authority_info.key,
None,
deposit_token_mint.decimals,
)
.unwrap(),
&[
token_program_info.clone(),
token_pass_mint_info.clone(),
rent_info.clone(),
],
)?;
invoke(
&spl_token::instruction::initialize_mint(
&spl_token::id(),
token_fail_mint_info.key,
authority_info.key,
None,
deposit_token_mint.decimals,
)
.unwrap(),
&[
token_program_info.clone(),
token_fail_mint_info.clone(),
rent_info.clone(),
],
)?;
pool.version = POOL_VERSION;
pool.bump_seed = bump_seed;
pool.token_program_id = *token_program_info.key;
pool.deposit_account = *deposit_account_info.key;
pool.token_pass_mint = *token_pass_mint_info.key;
pool.token_fail_mint = *token_fail_mint_info.key;
pool.decider = *decider_info.key;
pool.mint_end_slot = mint_end_slot;
pool.decide_end_slot = decide_end_slot;
pool.decision = Decision::Undecided;
pool.serialize(&mut *pool_account_info.data.borrow_mut())
.map_err(|e| e.into())
}
/// Process Deposit instruction
pub fn process_deposit(
program_id: &Pubkey,
accounts: &[AccountInfo],
amount: u64,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let pool_account_info = next_account_info(account_info_iter)?;
let authority_account_info = next_account_info(account_info_iter)?;
let user_transfer_authority_info = next_account_info(account_info_iter)?;
let user_token_account_info = next_account_info(account_info_iter)?;
let pool_deposit_token_account_info = next_account_info(account_info_iter)?;
let token_pass_mint_info = next_account_info(account_info_iter)?;
let token_fail_mint_info = next_account_info(account_info_iter)?;
let token_pass_destination_account_info = next_account_info(account_info_iter)?;
let token_fail_destination_account_info = next_account_info(account_info_iter)?;
let clock_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(clock_info)?;
let token_program_id_info = next_account_info(account_info_iter)?;
if amount == 0 {
return Err(PoolError::InvalidAmount.into());
}
let pool = Pool::try_from_slice(&pool_account_info.data.borrow())?;
if clock.slot > pool.mint_end_slot {
return Err(PoolError::InvalidSlotForDeposit.into());
}
let authority_pub_key =
Self::authority_id(program_id, pool_account_info.key, pool.bump_seed)?;
if *authority_account_info.key != authority_pub_key {
return Err(PoolError::InvalidAuthorityAccount.into());
}
// Transfer deposit tokens from user's account to our deposit account
Self::transfer(
token_program_id_info.clone(),
user_token_account_info.clone(),
pool_deposit_token_account_info.clone(),
authority_account_info.clone(),
user_transfer_authority_info.clone(),
amount,
pool_account_info.key,
pool.bump_seed,
)?;
// Mint PASS tokens to user account
Self::mint(
token_program_id_info.clone(),
token_pass_mint_info.clone(),
token_pass_destination_account_info.clone(),
authority_account_info.clone(),
amount,
pool_account_info.key,
pool.bump_seed,
)?;
// Mint FAIL tokens to user account
Self::mint(
token_program_id_info.clone(),
token_fail_mint_info.clone(),
token_fail_destination_account_info.clone(),
authority_account_info.clone(),
amount,
pool_account_info.key,
pool.bump_seed,
)?;
Ok(())
}
/// Process Withdraw instruction
pub fn process_withdraw(
program_id: &Pubkey,
accounts: &[AccountInfo],
amount: u64,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let pool_account_info = next_account_info(account_info_iter)?;
let authority_account_info = next_account_info(account_info_iter)?;
let user_transfer_authority_info = next_account_info(account_info_iter)?;
let pool_deposit_token_account_info = next_account_info(account_info_iter)?;
let token_pass_user_account_info = next_account_info(account_info_iter)?;
let token_fail_user_account_info = next_account_info(account_info_iter)?;
let token_pass_mint_info = next_account_info(account_info_iter)?;
let token_fail_mint_info = next_account_info(account_info_iter)?;
let user_token_destination_account_info = next_account_info(account_info_iter)?;
let clock_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(clock_info)?;
let token_program_id_info = next_account_info(account_info_iter)?;
if amount == 0 {
return Err(PoolError::InvalidAmount.into());
}
let user_pass_token_account = Account::unpack(&token_pass_user_account_info.data.borrow())?;
let user_fail_token_account = Account::unpack(&token_fail_user_account_info.data.borrow())?;
let pool = Pool::try_from_slice(&pool_account_info.data.borrow())?;
let authority_pub_key =
Self::authority_id(program_id, pool_account_info.key, pool.bump_seed)?;
if *authority_account_info.key != authority_pub_key {
return Err(PoolError::InvalidAuthorityAccount.into());
}
match pool.decision {
Decision::Pass => {
// Burn PASS tokens
Self::burn(
token_program_id_info.clone(),
token_pass_user_account_info.clone(),
token_pass_mint_info.clone(),
authority_account_info.clone(),
user_transfer_authority_info.clone(),
amount,
&pool_account_info.key,
pool.bump_seed,
)?;
// Transfer deposit tokens from pool deposit account to user destination account
Self::transfer(
token_program_id_info.clone(),
pool_deposit_token_account_info.clone(),
user_token_destination_account_info.clone(),
authority_account_info.clone(),
authority_account_info.clone(),
amount,
pool_account_info.key,
pool.bump_seed,
)?;
}
Decision::Fail => {
// Burn FAIL tokens
Self::burn(
token_program_id_info.clone(),
token_fail_user_account_info.clone(),
token_fail_mint_info.clone(),
authority_account_info.clone(),
user_transfer_authority_info.clone(),
amount,
&pool_account_info.key,
pool.bump_seed,
)?;
// Transfer deposit tokens from pool deposit account to user destination account
Self::transfer(
token_program_id_info.clone(),
pool_deposit_token_account_info.clone(),
user_token_destination_account_info.clone(),
authority_account_info.clone(),
authority_account_info.clone(),
amount,
pool_account_info.key,
pool.bump_seed,
)?;
}
Decision::Undecided => {
let current_slot = clock.slot;
if current_slot < pool.mint_end_slot || current_slot > pool.decide_end_slot {
let possible_withdraw_amount = amount
.min(user_pass_token_account.amount)
.min(user_fail_token_account.amount);
// Burn PASS tokens
Self::burn(
token_program_id_info.clone(),
token_pass_user_account_info.clone(),
token_pass_mint_info.clone(),
authority_account_info.clone(),
user_transfer_authority_info.clone(),
possible_withdraw_amount,
&pool_account_info.key,
pool.bump_seed,
)?;
// Burn FAIL tokens
Self::burn(
token_program_id_info.clone(),
token_fail_user_account_info.clone(),
token_fail_mint_info.clone(),
authority_account_info.clone(),
user_transfer_authority_info.clone(),
amount,
&pool_account_info.key,
pool.bump_seed,
)?;
// Transfer deposit tokens from pool deposit account to user destination account
Self::transfer(
token_program_id_info.clone(),
pool_deposit_token_account_info.clone(),
user_token_destination_account_info.clone(),
authority_account_info.clone(),
authority_account_info.clone(),
amount,
pool_account_info.key,
pool.bump_seed,
)?;
} else {
return Err(PoolError::NoDecisionMadeYet.into());
}
}
}
Ok(())
}
/// Process Decide instruction
pub fn process_decide(
_program_id: &Pubkey,
accounts: &[AccountInfo],
decision: bool,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let pool_account_info = next_account_info(account_info_iter)?;
let decider_account_info = next_account_info(account_info_iter)?;
let clock_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(clock_info)?;
let mut pool = Pool::try_from_slice(&pool_account_info.data.borrow())?;
if *decider_account_info.key != pool.decider {
return Err(PoolError::WrongDeciderAccount.into());
}
if !decider_account_info.is_signer {
return Err(PoolError::SignatureMissing.into());
}
if pool.decision != Decision::Undecided {
return Err(PoolError::DecisionAlreadyMade.into());
}
let current_slot = clock.slot;
if current_slot < pool.mint_end_slot || current_slot > pool.decide_end_slot {
return Err(PoolError::InvalidSlotForDecision.into());
}
pool.decision = if decision {
Decision::Pass
} else {
Decision::Fail
};
pool.serialize(&mut *pool_account_info.data.borrow_mut())
.map_err(|e| e.into())
}
/// Processes an instruction
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
input: &[u8],
) -> ProgramResult {
let instruction = PoolInstruction::try_from_slice(input)?;
match instruction {
PoolInstruction::InitPool(init_args) => {
msg!("Instruction: InitPool");
Self::process_init_pool(
program_id,
accounts,
init_args.mint_end_slot,
init_args.decide_end_slot,
init_args.bump_seed,
)
}
PoolInstruction::Deposit(amount) => {
msg!("Instruction: Deposit");
Self::process_deposit(program_id, accounts, amount)
}
PoolInstruction::Withdraw(amount) => {
msg!("Instruction: Withdraw");
Self::process_withdraw(program_id, accounts, amount)
}
PoolInstruction::Decide(decision) => {
msg!("Instruction: Decide");
Self::process_decide(program_id, accounts, decision)
}
}
}
}