use {
bonfida_utils::{
checks::{check_account_key, check_account_owner, check_signer},
BorshSize, InstructionsAccount,
},
borsh::{BorshDeserialize, BorshSerialize},
solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
msg,
program::invoke_signed,
program_error::ProgramError,
pubkey::Pubkey,
rent::Rent,
system_program,
sysvar::Sysvar,
},
spl_token::state::Account,
};
use solana_program::program_pack::Pack;
use crate::state::{NftRecord, Tag};
#[derive(BorshDeserialize, BorshSerialize, BorshSize)]
pub struct Params {}
#[derive(InstructionsAccount)]
pub struct Accounts<'a, T> {
#[cons(writable)]
pub nft: &'a T,
#[cons(writable, signer)]
pub nft_owner: &'a T,
#[cons(writable)]
pub nft_record: &'a T,
#[cons(writable)]
pub token_destination: &'a T,
#[cons(writable)]
pub token_source: &'a T,
pub spl_token_program: &'a T,
pub system_program: &'a T,
}
impl<'a, 'b: 'a> Accounts<'a, AccountInfo<'b>> {
pub fn parse(
accounts: &'a [AccountInfo<'b>],
program_id: &Pubkey,
) -> Result<Self, ProgramError> {
let accounts_iter = &mut accounts.iter();
let accounts = Accounts {
nft: next_account_info(accounts_iter)?,
nft_owner: next_account_info(accounts_iter)?,
nft_record: next_account_info(accounts_iter)?,
token_destination: next_account_info(accounts_iter)?,
token_source: next_account_info(accounts_iter)?,
spl_token_program: next_account_info(accounts_iter)?,
system_program: next_account_info(accounts_iter)?,
};
check_account_key(accounts.spl_token_program, &spl_token::ID)?;
check_account_key(accounts.system_program, &system_program::ID)?;
check_account_owner(accounts.nft, &spl_token::ID)?;
check_account_owner(accounts.nft_record, program_id)?;
check_account_owner(accounts.token_destination, &spl_token::ID)?;
check_account_owner(accounts.token_source, &spl_token::ID)?;
check_signer(accounts.nft_owner)?;
Ok(accounts)
}
}
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
let accounts = Accounts::parse(accounts, program_id)?;
let mut nft_record = NftRecord::from_account_info(accounts.nft_record, Tag::ActiveRecord)
.or_else(|_| NftRecord::from_account_info(accounts.nft_record, Tag::InactiveRecord))?;
let nft = Account::unpack(&accounts.nft.data.borrow())?;
if nft.mint != nft_record.nft_mint {
msg!("+ NFT mint mismatch");
return Err(ProgramError::InvalidArgument);
}
if nft_record.is_active() {
check_account_key(accounts.nft_owner, &nft.owner)?;
if nft.amount != 1 {
msg!("+ Invalid NFT amount, received {}", nft.amount);
return Err(ProgramError::InvalidArgument);
}
} else {
check_account_key(accounts.nft_owner, &nft_record.owner)?
}
let token_account = Account::unpack(&accounts.token_source.data.borrow())?;
msg!("+ Withdrawing tokens {}", token_account.amount);
let ix = spl_token::instruction::transfer(
&spl_token::ID,
accounts.token_source.key,
accounts.token_destination.key,
accounts.nft_record.key,
&[],
token_account.amount,
)?;
let seeds: &[&[u8]] = &[
NftRecord::SEED,
&nft_record.name_account.to_bytes(),
&[nft_record.nonce],
];
invoke_signed(
&ix,
&[
accounts.spl_token_program.clone(),
accounts.token_source.clone(),
accounts.token_destination.clone(),
accounts.nft_record.clone(),
],
&[seeds],
)?;
let minimum_rent = Rent::get()?.minimum_balance(accounts.nft_record.data_len());
let lamports_to_withdraw = accounts
.nft_record
.lamports()
.checked_sub(minimum_rent)
.unwrap();
msg!("+ Withdrawing native SOL {}", lamports_to_withdraw);
let mut nft_record_lamports = accounts.nft_record.lamports.borrow_mut();
let mut nft_owner_lamports = accounts.nft_owner.lamports.borrow_mut();
**nft_record_lamports -= lamports_to_withdraw;
**nft_owner_lamports += lamports_to_withdraw;
nft_record.owner = *accounts.nft_owner.key;
nft_record.save(&mut accounts.nft_record.data.borrow_mut());
Ok(())
}