use hopper_runtime::account::AccountView;
use hopper_runtime::address::Address;
use hopper_runtime::error::ProgramError;
use hopper_runtime::instruction::{InstructionAccount, InstructionView, Signer};
use hopper_runtime::ProgramResult;
use crate::constants::{TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenProgramKind {
Spl,
Token2022,
}
impl TokenProgramKind {
#[inline(always)]
pub const fn program_id(self) -> &'static Address {
match self {
TokenProgramKind::Spl => &TOKEN_PROGRAM_ID,
TokenProgramKind::Token2022 => &TOKEN_2022_PROGRAM_ID,
}
}
#[inline(always)]
pub fn from_owner(owner: &Address) -> Result<Self, ProgramError> {
if owner == &TOKEN_PROGRAM_ID {
Ok(TokenProgramKind::Spl)
} else if owner == &TOKEN_2022_PROGRAM_ID {
Ok(TokenProgramKind::Token2022)
} else {
Err(ProgramError::IncorrectProgramId)
}
}
#[inline(always)]
pub fn for_account(view: &AccountView) -> Result<Self, ProgramError> {
if view.owned_by(&TOKEN_PROGRAM_ID) {
Ok(TokenProgramKind::Spl)
} else if view.owned_by(&TOKEN_2022_PROGRAM_ID) {
Ok(TokenProgramKind::Token2022)
} else {
Err(ProgramError::IncorrectProgramId)
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct InterfaceTokenAccount<'a> {
data: &'a [u8],
pub kind: TokenProgramKind,
}
impl<'a> InterfaceTokenAccount<'a> {
pub fn from_data(data: &'a [u8], kind: TokenProgramKind) -> Result<Self, ProgramError> {
if data.len() < crate::token::TOKEN_ACCOUNT_LEN {
return Err(ProgramError::InvalidAccountData);
}
Ok(Self { data, kind })
}
#[inline(always)]
pub fn data(&self) -> &'a [u8] {
self.data
}
#[inline(always)]
pub fn mint(&self) -> Result<&'a Address, ProgramError> {
crate::token::token_account_mint(self.data)
}
#[inline(always)]
pub fn owner(&self) -> Result<&'a Address, ProgramError> {
crate::token::token_account_owner(self.data)
}
#[inline(always)]
pub fn amount(&self) -> Result<u64, ProgramError> {
crate::token::token_account_amount(self.data)
}
#[inline(always)]
pub fn state(&self) -> Result<u8, ProgramError> {
crate::token::token_account_state(self.data)
}
#[inline(always)]
pub fn assert_initialized(&self) -> Result<(), ProgramError> {
crate::token::check_token_initialized(self.data)
}
#[inline(always)]
pub fn assert_owner(&self, expected: &Address) -> Result<(), ProgramError> {
crate::token::check_token_owner(self.data, expected)
}
#[inline(always)]
pub fn assert_mint(&self, expected: &Address) -> Result<(), ProgramError> {
crate::token::check_token_mint(self.data, expected)
}
}
#[derive(Debug, Clone, Copy)]
pub struct InterfaceMint<'a> {
data: &'a [u8],
pub kind: TokenProgramKind,
}
impl<'a> InterfaceMint<'a> {
pub fn from_data(data: &'a [u8], kind: TokenProgramKind) -> Result<Self, ProgramError> {
if data.len() < crate::mint::MINT_LEN {
return Err(ProgramError::InvalidAccountData);
}
Ok(Self { data, kind })
}
#[inline(always)]
pub fn data(&self) -> &'a [u8] {
self.data
}
#[inline(always)]
pub fn supply(&self) -> Result<u64, ProgramError> {
crate::mint::mint_supply(self.data)
}
#[inline(always)]
pub fn decimals(&self) -> Result<u8, ProgramError> {
crate::mint::mint_decimals(self.data)
}
#[inline(always)]
pub fn authority(&self) -> Result<Option<&'a Address>, ProgramError> {
crate::mint::mint_authority(self.data)
}
#[inline(always)]
pub fn freeze_authority(&self) -> Result<Option<&'a Address>, ProgramError> {
crate::mint::mint_freeze_authority(self.data)
}
#[inline(always)]
pub fn assert_initialized(&self) -> Result<(), ProgramError> {
crate::mint::check_mint_initialized(self.data)
}
}
#[inline]
pub fn interface_transfer_checked<'a>(
source: &'a AccountView,
mint: &'a AccountView,
destination: &'a AccountView,
authority: &'a AccountView,
amount: u64,
decimals: u8,
) -> ProgramResult {
interface_transfer_checked_signed(source, mint, destination, authority, amount, decimals, &[])
}
#[inline]
pub fn interface_transfer_checked_signed<'a>(
source: &'a AccountView,
mint: &'a AccountView,
destination: &'a AccountView,
authority: &'a AccountView,
amount: u64,
decimals: u8,
signers: &[Signer],
) -> ProgramResult {
let kind = TokenProgramKind::for_account(source)?;
let mut data = [0u8; 10];
data[0] = 12; data[1..9].copy_from_slice(&amount.to_le_bytes());
data[9] = decimals;
let accounts = [
InstructionAccount::writable(source.address()),
InstructionAccount::readonly(mint.address()),
InstructionAccount::writable(destination.address()),
InstructionAccount::readonly_signer(authority.address()),
];
let views = [source, mint, destination, authority];
let instruction = InstructionView {
program_id: kind.program_id(),
data: &data,
accounts: &accounts,
};
hopper_runtime::cpi::invoke_signed(&instruction, &views, signers)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn token_program_kind_from_owner_matches_known_programs() {
assert_eq!(
TokenProgramKind::from_owner(&TOKEN_PROGRAM_ID).unwrap(),
TokenProgramKind::Spl,
);
assert_eq!(
TokenProgramKind::from_owner(&TOKEN_2022_PROGRAM_ID).unwrap(),
TokenProgramKind::Token2022,
);
}
#[test]
fn token_program_kind_from_owner_rejects_other_programs() {
let other = Address::new_from_array([7u8; 32]);
assert!(matches!(
TokenProgramKind::from_owner(&other),
Err(ProgramError::IncorrectProgramId),
));
}
#[test]
fn token_program_kind_program_id_is_stable() {
assert_eq!(TokenProgramKind::Spl.program_id(), &TOKEN_PROGRAM_ID,);
assert_eq!(
TokenProgramKind::Token2022.program_id(),
&TOKEN_2022_PROGRAM_ID,
);
}
}