use crate::error::PsyOptionsError;
use solana_program::{
account_info::AccountInfo,
program::invoke,
pubkey::Pubkey,
program_error::ProgramError,
program_pack::{IsInitialized, Pack},
system_instruction, system_program,
};
use spl_associated_token_account::{create_associated_token_account, get_associated_token_address};
use spl_token::{instruction as token_instruction, state::Account as SPLTokenAccount};
pub mod fee_owner_key {
use solana_program::declare_id;
declare_id!("6c33US7ErPmLXZog9SyChQUYUrrJY51k4GmzdhrbhNnD");
}
pub const NFT_MINT_LAMPORTS: u64 = 1_000_000_000 / 2;
#[repr(transparent)]
#[derive(Copy, Clone, Debug)]
struct U64F64(u128);
impl U64F64 {
#[inline(always)]
const fn mul_u64(self, other: u64) -> U64F64 {
U64F64(self.0 * other as u128)
}
#[inline(always)]
const fn floor(self) -> u64 {
(self.0 >> 64) as u64
}
}
pub fn check_or_create_fee_account<'a, 'b>(
funding_account: &AccountInfo<'a>,
spl_associated_token_program_acct: &AccountInfo<'a>,
fee_account: &AccountInfo<'a>,
fee_owner_acct: &AccountInfo<'a>,
asset_mint_acct: &AccountInfo<'a>,
spl_token_program_acct: &AccountInfo<'a>,
sys_program_acct: &AccountInfo<'a>,
sys_rent_acct: &AccountInfo<'a>,
) -> Result<(), ProgramError> {
let account_address = get_associated_token_address(&fee_owner_key::ID, &asset_mint_acct.key);
if account_address != *fee_account.key {
return Err(ProgramError::InvalidAccountData);
}
if *spl_associated_token_program_acct.key != spl_associated_token_account::id() {
return Err(ProgramError::InvalidAccountData);
}
let token_account_exists = {
let account_data = fee_account.try_borrow_data()?;
if account_data.len() == SPLTokenAccount::LEN {
let account = SPLTokenAccount::unpack_from_slice(&account_data)?;
account.is_initialized()
} else {
false
}
};
if !token_account_exists {
let create_account_ix = create_associated_token_account(
&funding_account.key,
&fee_owner_key::ID,
&asset_mint_acct.key,
);
invoke(
&create_account_ix,
&[
spl_associated_token_program_acct.clone(),
funding_account.clone(),
fee_account.clone(),
fee_owner_acct.clone(),
asset_mint_acct.clone(),
spl_token_program_acct.clone(),
sys_program_acct.clone(),
sys_rent_acct.clone(),
],
)?;
}
Ok(())
}
fn fee_bps(bps: u64) -> U64F64 {
U64F64(((bps as u128) << 64) / 10_000)
}
fn fee_rate() -> U64F64 {
fee_bps(5)
}
pub fn fee_amount(asset_quantity: u64) -> u64 {
let rate = fee_rate();
rate.mul_u64(asset_quantity).floor()
}
pub fn transfer_fee<'a>(
funding_acct: &AccountInfo<'a>,
system_program_acct: &AccountInfo<'a>,
spl_program_acct: &AccountInfo<'a>,
fee_recipient_acct: &AccountInfo<'a>,
asset_src: &AccountInfo<'a>,
asset_authority: &AccountInfo<'a>,
fee_owner_acct: &AccountInfo<'a>,
asset_amount: u64,
asset_mint: Pubkey,
) -> Result<(), ProgramError> {
let fee = fee_amount(asset_amount);
if fee > 0 {
{
let fee_acct_data = fee_recipient_acct.try_borrow_data()?;
let fee_spl_token_account = SPLTokenAccount::unpack_from_slice(&fee_acct_data)?;
if fee_recipient_acct.owner != spl_program_acct.key || !fee_spl_token_account.is_initialized() {
return Err(ProgramError::InvalidAccountData);
}
if fee_spl_token_account.owner != fee_owner_key::ID || fee_spl_token_account.mint != asset_mint {
return Err(PsyOptionsError::BadFeeOwner.into());
}
}
let transfer_fee_ix = token_instruction::transfer(
&spl_program_acct.key,
&asset_src.key,
&fee_recipient_acct.key,
&asset_authority.key,
&[],
fee,
)?;
invoke(
&transfer_fee_ix,
&[
asset_src.clone(),
fee_recipient_acct.clone(),
asset_authority.clone(),
spl_program_acct.clone(),
],
)?;
} else {
if !system_program::check_id(system_program_acct.key) {
return Err(ProgramError::InvalidAccountData);
}
invoke(
&system_instruction::transfer(&funding_acct.key, fee_owner_acct.key, NFT_MINT_LAMPORTS),
&[
funding_acct.clone(),
fee_owner_acct.clone(),
system_program_acct.clone(),
],
)?;
}
Ok(())
}