use {
crate::{
error::VaultError,
state::{Key, Vault},
},
borsh::BorshDeserialize,
solana_program::{
account_info::AccountInfo,
borsh::try_from_slice_unchecked,
entrypoint::ProgramResult,
msg,
program::{invoke, invoke_signed},
program_error::ProgramError,
program_pack::{IsInitialized, Pack},
pubkey::Pubkey,
system_instruction,
sysvar::{rent::Rent, Sysvar},
},
std::convert::TryInto,
};
pub fn assert_initialized<T: Pack + IsInitialized>(
account_info: &AccountInfo,
) -> Result<T, ProgramError> {
let account: T = T::unpack_unchecked(&account_info.data.borrow())?;
if !account.is_initialized() {
Err(VaultError::Uninitialized.into())
} else {
Ok(account)
}
}
pub fn assert_rent_exempt(rent: &Rent, account_info: &AccountInfo) -> ProgramResult {
if !rent.is_exempt(account_info.lamports(), account_info.data_len()) {
Err(VaultError::NotRentExempt.into())
} else {
Ok(())
}
}
pub fn assert_owned_by(account: &AccountInfo, owner: &Pubkey) -> ProgramResult {
if account.owner != owner {
Err(VaultError::IncorrectOwner.into())
} else {
Ok(())
}
}
pub fn assert_token_matching(vault: &Vault, token: &AccountInfo) -> ProgramResult {
if vault.token_program != *token.key {
return Err(VaultError::TokenProgramProvidedDoesNotMatchVault.into());
}
Ok(())
}
pub fn assert_vault_authority_correct(
vault: &Vault,
vault_authority_info: &AccountInfo,
) -> ProgramResult {
if !vault_authority_info.is_signer {
return Err(VaultError::AuthorityIsNotSigner.into());
}
if *vault_authority_info.key != vault.authority {
return Err(VaultError::AuthorityDoesNotMatch.into());
}
Ok(())
}
pub fn assert_token_program_matches_package(token_program_info: &AccountInfo) -> ProgramResult {
if *token_program_info.key != spl_token::id() {
return Err(VaultError::InvalidTokenProgram.into());
}
Ok(())
}
#[inline(always)]
pub fn create_or_allocate_account_raw<'a>(
program_id: Pubkey,
new_account_info: &AccountInfo<'a>,
rent_sysvar_info: &AccountInfo<'a>,
system_program_info: &AccountInfo<'a>,
payer_info: &AccountInfo<'a>,
size: usize,
signer_seeds: &[&[u8]],
) -> Result<(), ProgramError> {
let rent = &Rent::from_account_info(rent_sysvar_info)?;
let required_lamports = rent
.minimum_balance(size)
.max(1)
.saturating_sub(new_account_info.lamports());
if required_lamports > 0 {
msg!("Transfer {} lamports to the new account", required_lamports);
invoke(
&system_instruction::transfer(&payer_info.key, new_account_info.key, required_lamports),
&[
payer_info.clone(),
new_account_info.clone(),
system_program_info.clone(),
],
)?;
}
msg!("Allocate space for the account");
invoke_signed(
&system_instruction::allocate(new_account_info.key, size.try_into().unwrap()),
&[new_account_info.clone(), system_program_info.clone()],
&[&signer_seeds],
)?;
msg!("Assign the account to the owning program");
invoke_signed(
&system_instruction::assign(new_account_info.key, &program_id),
&[new_account_info.clone(), system_program_info.clone()],
&[&signer_seeds],
)?;
msg!("Completed assignation!");
Ok(())
}
#[inline(always)]
pub fn spl_token_transfer(params: TokenTransferParams<'_, '_>) -> ProgramResult {
let TokenTransferParams {
source,
destination,
authority,
token_program,
amount,
authority_signer_seeds,
} = params;
let result = invoke_signed(
&spl_token::instruction::transfer(
token_program.key,
source.key,
destination.key,
authority.key,
&[],
amount,
)?,
&[source, destination, authority, token_program],
&[authority_signer_seeds],
);
result.map_err(|_| VaultError::TokenTransferFailed.into())
}
pub fn spl_token_mint_to(params: TokenMintToParams<'_, '_>) -> ProgramResult {
let TokenMintToParams {
mint,
destination,
authority,
token_program,
amount,
authority_signer_seeds,
} = params;
let result = invoke_signed(
&spl_token::instruction::mint_to(
token_program.key,
mint.key,
destination.key,
authority.key,
&[],
amount,
)?,
&[mint, destination, authority, token_program],
&[authority_signer_seeds],
);
result.map_err(|_| VaultError::TokenMintToFailed.into())
}
#[inline(always)]
pub fn spl_token_burn(params: TokenBurnParams<'_, '_>) -> ProgramResult {
let TokenBurnParams {
mint,
source,
authority,
token_program,
amount,
authority_signer_seeds,
} = params;
let result = invoke_signed(
&spl_token::instruction::burn(
token_program.key,
source.key,
mint.key,
authority.key,
&[],
amount,
)?,
&[source, mint, authority, token_program],
&[authority_signer_seeds],
);
result.map_err(|_| VaultError::TokenBurnFailed.into())
}
pub struct TokenTransferParams<'a: 'b, 'b> {
pub source: AccountInfo<'a>,
pub destination: AccountInfo<'a>,
pub amount: u64,
pub authority: AccountInfo<'a>,
pub authority_signer_seeds: &'b [&'b [u8]],
pub token_program: AccountInfo<'a>,
}
pub struct TokenMintToParams<'a: 'b, 'b> {
pub mint: AccountInfo<'a>,
pub destination: AccountInfo<'a>,
pub amount: u64,
pub authority: AccountInfo<'a>,
pub authority_signer_seeds: &'b [&'b [u8]],
pub token_program: AccountInfo<'a>,
}
pub struct TokenBurnParams<'a: 'b, 'b> {
pub mint: AccountInfo<'a>,
pub source: AccountInfo<'a>,
pub amount: u64,
pub authority: AccountInfo<'a>,
pub authority_signer_seeds: &'b [&'b [u8]],
pub token_program: AccountInfo<'a>,
}
pub fn try_from_slice_checked<T: BorshDeserialize>(
data: &[u8],
data_type: Key,
data_size: usize,
) -> Result<T, ProgramError> {
if (data[0] != data_type as u8 && data[0] != Key::Uninitialized as u8)
|| data.len() != data_size
{
return Err(VaultError::DataTypeMismatch.into());
}
let result: T = try_from_slice_unchecked(data)?;
Ok(result)
}
pub fn assert_derivation(
program_id: &Pubkey,
account: &AccountInfo,
path: &[&[u8]],
) -> Result<u8, ProgramError> {
let (key, bump) = Pubkey::find_program_address(&path, program_id);
if key != *account.key {
return Err(VaultError::DerivedKeyInvalid.into());
}
Ok(bump)
}