use crate::account::AccountView;
use crate::address::Address;
use crate::error::ProgramError;
use crate::instruction::{InstructionAccount, InstructionView, Signer};
use crate::ProgramResult;
#[inline(always)]
fn require_authority_signed_direct(authority: &AccountView) -> ProgramResult {
if authority.is_signer() {
Ok(())
} else {
Err(ProgramError::MissingRequiredSignature)
}
}
#[inline]
pub fn require_token_authority(
token_account: &AccountView,
authority: &AccountView,
) -> ProgramResult {
let data = token_account
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
if data.len() < 64 {
return Err(ProgramError::AccountDataTooSmall);
}
let mut owner_bytes = [0u8; 32];
owner_bytes.copy_from_slice(&data[32..64]);
let authority_bytes: [u8; 32] = *authority.address().as_array();
if owner_bytes == authority_bytes {
Ok(())
} else {
Err(ProgramError::IncorrectAuthority)
}
}
#[inline]
pub fn require_token_owner_eq(
token_account: &AccountView,
expected_owner: &Address,
) -> ProgramResult {
let data = token_account
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
if data.len() < 64 {
return Err(ProgramError::AccountDataTooSmall);
}
let mut actual = [0u8; 32];
actual.copy_from_slice(&data[32..64]);
if actual == *expected_owner.as_array() {
Ok(())
} else {
Err(ProgramError::IncorrectAuthority)
}
}
#[inline]
pub fn require_token_mint(
token_account: &AccountView,
expected_mint: &Address,
) -> ProgramResult {
let data = token_account
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
if data.len() < 32 {
return Err(ProgramError::AccountDataTooSmall);
}
let actual: [u8; 32] = {
let mut out = [0u8; 32];
out.copy_from_slice(&data[0..32]);
out
};
if actual == *expected_mint.as_array() {
Ok(())
} else {
Err(ProgramError::InvalidAccountData)
}
}
#[inline]
pub fn require_mint_authority(
mint_account: &AccountView,
expected_authority: &Address,
) -> ProgramResult {
let data = mint_account
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
if data.len() < 46 {
return Err(ProgramError::AccountDataTooSmall);
}
let tag = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
if tag != 1 {
return Err(ProgramError::InvalidAccountData);
}
let mut actual = [0u8; 32];
actual.copy_from_slice(&data[4..36]);
if actual == *expected_authority.as_array() {
Ok(())
} else {
Err(ProgramError::IncorrectAuthority)
}
}
#[inline]
pub fn require_mint_decimals(mint_account: &AccountView, expected: u8) -> ProgramResult {
let data = mint_account
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
if data.len() < 45 {
return Err(ProgramError::AccountDataTooSmall);
}
if data[44] == expected {
Ok(())
} else {
Err(ProgramError::InvalidAccountData)
}
}
#[inline]
pub fn require_mint_freeze_authority(
mint_account: &AccountView,
expected_freeze: &Address,
) -> ProgramResult {
let data = mint_account
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
if data.len() < 82 {
return Err(ProgramError::AccountDataTooSmall);
}
let tag = u32::from_le_bytes([data[46], data[47], data[48], data[49]]);
if tag != 1 {
return Err(ProgramError::InvalidAccountData);
}
let mut actual = [0u8; 32];
actual.copy_from_slice(&data[50..82]);
if actual == *expected_freeze.as_array() {
Ok(())
} else {
Err(ProgramError::IncorrectAuthority)
}
}
#[deprecated(
since = "0.2.0",
note = "use TransferChecked for Token-2022 safety (mint + decimals validation)"
)]
#[cfg(feature = "legacy-token-instructions")]
pub struct Transfer<'a> {
pub from: &'a AccountView,
pub to: &'a AccountView,
pub authority: &'a AccountView,
pub amount: u64,
}
#[allow(deprecated)]
#[cfg(feature = "legacy-token-instructions")]
impl Transfer<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
require_authority_signed_direct(self.authority)?;
self.invoke_signed_unchecked(&[])
}
#[inline]
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
self.invoke_signed_unchecked(signers)
}
#[inline(always)]
fn invoke_signed_unchecked(&self, signers: &[Signer]) -> ProgramResult {
let mut data = [0u8; 9];
data[0] = 3;
data[1..9].copy_from_slice(&self.amount.to_le_bytes());
let accounts = [
InstructionAccount::writable(self.from.address()),
InstructionAccount::writable(self.to.address()),
InstructionAccount::readonly_signer(self.authority.address()),
];
let views = [self.from, self.to, self.authority];
let instruction = InstructionView {
program_id: &TOKEN_PROGRAM_ID,
data: &data,
accounts: &accounts,
};
crate::cpi::invoke_signed(&instruction, &views, signers)
}
}
#[deprecated(
since = "0.2.0",
note = "use MintToChecked for Token-2022 safety (mint + decimals validation)"
)]
#[cfg(feature = "legacy-token-instructions")]
pub struct MintTo<'a> {
pub mint: &'a AccountView,
pub account: &'a AccountView,
pub mint_authority: &'a AccountView,
pub amount: u64,
}
#[allow(deprecated)]
#[cfg(feature = "legacy-token-instructions")]
impl MintTo<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
require_authority_signed_direct(self.mint_authority)?;
self.invoke_signed(&[])
}
#[inline]
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
let mut data = [0u8; 9];
data[0] = 7;
data[1..9].copy_from_slice(&self.amount.to_le_bytes());
let accounts = [
InstructionAccount::writable(self.mint.address()),
InstructionAccount::writable(self.account.address()),
InstructionAccount::readonly_signer(self.mint_authority.address()),
];
let views = [self.mint, self.account, self.mint_authority];
let instruction = InstructionView {
program_id: &TOKEN_PROGRAM_ID,
data: &data,
accounts: &accounts,
};
crate::cpi::invoke_signed(&instruction, &views, signers)
}
}
#[deprecated(
since = "0.2.0",
note = "use BurnChecked for Token-2022 safety (mint + decimals validation)"
)]
#[cfg(feature = "legacy-token-instructions")]
pub struct Burn<'a> {
pub account: &'a AccountView,
pub mint: &'a AccountView,
pub authority: &'a AccountView,
pub amount: u64,
}
#[allow(deprecated)]
#[cfg(feature = "legacy-token-instructions")]
impl Burn<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
require_authority_signed_direct(self.authority)?;
self.invoke_signed(&[])
}
#[inline]
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
let mut data = [0u8; 9];
data[0] = 8;
data[1..9].copy_from_slice(&self.amount.to_le_bytes());
let accounts = [
InstructionAccount::writable(self.account.address()),
InstructionAccount::writable(self.mint.address()),
InstructionAccount::readonly_signer(self.authority.address()),
];
let views = [self.account, self.mint, self.authority];
let instruction = InstructionView {
program_id: &TOKEN_PROGRAM_ID,
data: &data,
accounts: &accounts,
};
crate::cpi::invoke_signed(&instruction, &views, signers)
}
}
pub struct CloseAccount<'a> {
pub account: &'a AccountView,
pub destination: &'a AccountView,
pub authority: &'a AccountView,
}
impl CloseAccount<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
require_authority_signed_direct(self.authority)?;
self.invoke_signed(&[])
}
#[inline]
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
let data = [9u8];
let accounts = [
InstructionAccount::writable(self.account.address()),
InstructionAccount::writable(self.destination.address()),
InstructionAccount::readonly_signer(self.authority.address()),
];
let views = [self.account, self.destination, self.authority];
let instruction = InstructionView {
program_id: &TOKEN_PROGRAM_ID,
data: &data,
accounts: &accounts,
};
crate::cpi::invoke_signed(&instruction, &views, signers)
}
}
#[deprecated(
since = "0.2.0",
note = "use ApproveChecked for Token-2022 safety (mint + decimals validation)"
)]
#[cfg(feature = "legacy-token-instructions")]
pub struct Approve<'a> {
pub source: &'a AccountView,
pub delegate: &'a AccountView,
pub authority: &'a AccountView,
pub amount: u64,
}
#[allow(deprecated)]
#[cfg(feature = "legacy-token-instructions")]
impl Approve<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
require_authority_signed_direct(self.authority)?;
self.invoke_signed(&[])
}
#[inline]
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
let mut data = [0u8; 9];
data[0] = 4;
data[1..9].copy_from_slice(&self.amount.to_le_bytes());
let accounts = [
InstructionAccount::writable(self.source.address()),
InstructionAccount::readonly(self.delegate.address()),
InstructionAccount::readonly_signer(self.authority.address()),
];
let views = [self.source, self.delegate, self.authority];
let instruction = InstructionView {
program_id: &TOKEN_PROGRAM_ID,
data: &data,
accounts: &accounts,
};
crate::cpi::invoke_signed(&instruction, &views, signers)
}
}
pub struct Revoke<'a> {
pub source: &'a AccountView,
pub authority: &'a AccountView,
}
impl Revoke<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
require_authority_signed_direct(self.authority)?;
self.invoke_signed(&[])
}
#[inline]
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
let data = [5u8];
let accounts = [
InstructionAccount::writable(self.source.address()),
InstructionAccount::readonly_signer(self.authority.address()),
];
let views = [self.source, self.authority];
let instruction = InstructionView {
program_id: &TOKEN_PROGRAM_ID,
data: &data,
accounts: &accounts,
};
crate::cpi::invoke_signed(&instruction, &views, signers)
}
}
pub struct TransferChecked<'a> {
pub from: &'a AccountView,
pub mint: &'a AccountView,
pub to: &'a AccountView,
pub authority: &'a AccountView,
pub amount: u64,
pub decimals: u8,
}
impl TransferChecked<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
require_authority_signed_direct(self.authority)?;
self.invoke_signed_unchecked(&[])
}
#[inline]
pub fn invoke_strict(&self) -> ProgramResult {
require_authority_signed_direct(self.authority)?;
require_token_authority(self.from, self.authority)?;
self.invoke_signed_unchecked(&[])
}
#[inline]
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
self.invoke_signed_unchecked(signers)
}
#[inline]
pub fn invoke_signed_strict(&self, signers: &[Signer]) -> ProgramResult {
require_token_authority(self.from, self.authority)?;
self.invoke_signed_unchecked(signers)
}
#[inline(always)]
fn invoke_signed_unchecked(&self, signers: &[Signer]) -> ProgramResult {
let mut data = [0u8; 10];
data[0] = 12;
data[1..9].copy_from_slice(&self.amount.to_le_bytes());
data[9] = self.decimals;
let accounts = [
InstructionAccount::writable(self.from.address()),
InstructionAccount::readonly(self.mint.address()),
InstructionAccount::writable(self.to.address()),
InstructionAccount::readonly_signer(self.authority.address()),
];
let views = [self.from, self.mint, self.to, self.authority];
let instruction = InstructionView {
program_id: &TOKEN_PROGRAM_ID,
data: &data,
accounts: &accounts,
};
crate::cpi::invoke_signed(&instruction, &views, signers)
}
}
pub struct MintToChecked<'a> {
pub mint: &'a AccountView,
pub account: &'a AccountView,
pub mint_authority: &'a AccountView,
pub amount: u64,
pub decimals: u8,
}
impl MintToChecked<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
require_authority_signed_direct(self.mint_authority)?;
self.invoke_signed_unchecked(&[])
}
#[inline]
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
self.invoke_signed_unchecked(signers)
}
#[inline(always)]
fn invoke_signed_unchecked(&self, signers: &[Signer]) -> ProgramResult {
let mut data = [0u8; 10];
data[0] = 14;
data[1..9].copy_from_slice(&self.amount.to_le_bytes());
data[9] = self.decimals;
let accounts = [
InstructionAccount::writable(self.mint.address()),
InstructionAccount::writable(self.account.address()),
InstructionAccount::readonly_signer(self.mint_authority.address()),
];
let views = [self.mint, self.account, self.mint_authority];
let instruction = InstructionView {
program_id: &TOKEN_PROGRAM_ID,
data: &data,
accounts: &accounts,
};
crate::cpi::invoke_signed(&instruction, &views, signers)
}
}
pub struct BurnChecked<'a> {
pub account: &'a AccountView,
pub mint: &'a AccountView,
pub authority: &'a AccountView,
pub amount: u64,
pub decimals: u8,
}
impl BurnChecked<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
require_authority_signed_direct(self.authority)?;
self.invoke_signed_unchecked(&[])
}
#[inline]
pub fn invoke_strict(&self) -> ProgramResult {
require_authority_signed_direct(self.authority)?;
require_token_authority(self.account, self.authority)?;
self.invoke_signed_unchecked(&[])
}
#[inline]
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
self.invoke_signed_unchecked(signers)
}
#[inline]
pub fn invoke_signed_strict(&self, signers: &[Signer]) -> ProgramResult {
require_token_authority(self.account, self.authority)?;
self.invoke_signed_unchecked(signers)
}
#[inline(always)]
fn invoke_signed_unchecked(&self, signers: &[Signer]) -> ProgramResult {
let mut data = [0u8; 10];
data[0] = 15;
data[1..9].copy_from_slice(&self.amount.to_le_bytes());
data[9] = self.decimals;
let accounts = [
InstructionAccount::writable(self.account.address()),
InstructionAccount::writable(self.mint.address()),
InstructionAccount::readonly_signer(self.authority.address()),
];
let views = [self.account, self.mint, self.authority];
let instruction = InstructionView {
program_id: &TOKEN_PROGRAM_ID,
data: &data,
accounts: &accounts,
};
crate::cpi::invoke_signed(&instruction, &views, signers)
}
}
pub struct ApproveChecked<'a> {
pub source: &'a AccountView,
pub mint: &'a AccountView,
pub delegate: &'a AccountView,
pub authority: &'a AccountView,
pub amount: u64,
pub decimals: u8,
}
impl ApproveChecked<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
require_authority_signed_direct(self.authority)?;
self.invoke_signed_unchecked(&[])
}
#[inline]
pub fn invoke_strict(&self) -> ProgramResult {
require_authority_signed_direct(self.authority)?;
require_token_authority(self.source, self.authority)?;
self.invoke_signed_unchecked(&[])
}
#[inline]
pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
self.invoke_signed_unchecked(signers)
}
#[inline]
pub fn invoke_signed_strict(&self, signers: &[Signer]) -> ProgramResult {
require_token_authority(self.source, self.authority)?;
self.invoke_signed_unchecked(signers)
}
#[inline(always)]
fn invoke_signed_unchecked(&self, signers: &[Signer]) -> ProgramResult {
let mut data = [0u8; 10];
data[0] = 13;
data[1..9].copy_from_slice(&self.amount.to_le_bytes());
data[9] = self.decimals;
let accounts = [
InstructionAccount::writable(self.source.address()),
InstructionAccount::readonly(self.mint.address()),
InstructionAccount::readonly(self.delegate.address()),
InstructionAccount::readonly_signer(self.authority.address()),
];
let views = [self.source, self.mint, self.delegate, self.authority];
let instruction = InstructionView {
program_id: &TOKEN_PROGRAM_ID,
data: &data,
accounts: &accounts,
};
crate::cpi::invoke_signed(&instruction, &views, signers)
}
}
pub struct InitializeAccount<'a> {
pub account: &'a AccountView,
pub mint: &'a AccountView,
pub owner: &'a AccountView,
pub rent_sysvar: &'a AccountView,
}
impl InitializeAccount<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
let data = [1u8];
let accounts = [
InstructionAccount::writable(self.account.address()),
InstructionAccount::readonly(self.mint.address()),
InstructionAccount::readonly(self.owner.address()),
InstructionAccount::readonly(self.rent_sysvar.address()),
];
let views = [self.account, self.mint, self.owner, self.rent_sysvar];
let instruction = InstructionView {
program_id: &TOKEN_PROGRAM_ID,
data: &data,
accounts: &accounts,
};
crate::cpi::invoke(&instruction, &views)
}
}
pub const TOKEN_PROGRAM_ID: Address = Address::new_from_array(
five8_const::decode_32_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
);
pub mod instructions {
pub use super::{
ApproveChecked, BurnChecked, CloseAccount, InitializeAccount, MintToChecked, Revoke,
TransferChecked,
};
#[cfg(feature = "legacy-token-instructions")]
#[allow(deprecated)]
pub use super::{Approve, Burn, MintTo, Transfer};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn transfer_checked_discriminator_is_12() {
}
fn encode_checked(disc: u8, amount: u64, decimals: u8) -> [u8; 10] {
let mut data = [0u8; 10];
data[0] = disc;
data[1..9].copy_from_slice(&amount.to_le_bytes());
data[9] = decimals;
data
}
#[test]
fn transfer_checked_wire_format_is_stable() {
let out = encode_checked(12, 0x0102_0304_0506_0708, 9);
assert_eq!(out[0], 12);
assert_eq!(
&out[1..9],
&[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]
);
assert_eq!(out[9], 9);
}
#[test]
fn mint_to_checked_wire_format_is_stable() {
let out = encode_checked(14, 1000, 6);
assert_eq!(out[0], 14);
assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), 1000);
assert_eq!(out[9], 6);
}
#[test]
fn burn_checked_wire_format_is_stable() {
let out = encode_checked(15, 42, 8);
assert_eq!(out[0], 15);
assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), 42);
assert_eq!(out[9], 8);
}
#[test]
fn approve_checked_wire_format_is_stable() {
let out = encode_checked(13, u64::MAX, 0);
assert_eq!(out[0], 13);
assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), u64::MAX);
assert_eq!(out[9], 0);
}
#[test]
fn checked_encoding_round_trips_decimals_range() {
for d in 0u8..=255 {
let out = encode_checked(12, 1, d);
assert_eq!(out[9], d);
}
}
#[test]
fn checked_encoding_preserves_amount_bits() {
for shift in 0..8 {
let amount = 0xABu64 << (shift * 8);
let out = encode_checked(12, amount, 0);
let decoded = u64::from_le_bytes(out[1..9].try_into().unwrap());
assert_eq!(decoded, amount);
}
}
fn make_token_and_authority(
authority_bytes: [u8; 32],
token_owner_bytes: [u8; 32],
) -> (
std::vec::Vec<u8>,
std::vec::Vec<u8>,
crate::account::AccountView,
crate::account::AccountView,
) {
use hopper_native::{AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED};
let token_data_len = 165;
let mut token_backing = std::vec![0u8; RuntimeAccount::SIZE + token_data_len];
let token_raw = token_backing.as_mut_ptr() as *mut RuntimeAccount;
unsafe {
token_raw.write(RuntimeAccount {
borrow_state: NOT_BORROWED,
is_signer: 0,
is_writable: 1,
executable: 0,
resize_delta: 0,
address: NativeAddress::new_from_array([0xAA; 32]),
owner: NativeAddress::new_from_array([3; 32]),
lamports: 2_039_280,
data_len: token_data_len as u64,
});
let data_ptr = (token_raw as *mut u8).add(RuntimeAccount::SIZE);
core::ptr::copy_nonoverlapping(
token_owner_bytes.as_ptr(),
data_ptr.add(32),
32,
);
}
let token_backend = unsafe { NativeAccountView::new_unchecked(token_raw) };
let token_view = crate::account::AccountView::from_backend(token_backend);
let mut auth_backing = std::vec![0u8; RuntimeAccount::SIZE];
let auth_raw = auth_backing.as_mut_ptr() as *mut RuntimeAccount;
unsafe {
auth_raw.write(RuntimeAccount {
borrow_state: NOT_BORROWED,
is_signer: 1,
is_writable: 0,
executable: 0,
resize_delta: 0,
address: NativeAddress::new_from_array(authority_bytes),
owner: NativeAddress::new_from_array([0; 32]),
lamports: 0,
data_len: 0,
});
}
let auth_backend = unsafe { NativeAccountView::new_unchecked(auth_raw) };
let auth_view = crate::account::AccountView::from_backend(auth_backend);
(token_backing, auth_backing, token_view, auth_view)
}
#[test]
fn require_token_authority_accepts_matching_owner() {
let authority = [0x42u8; 32];
let (_tb, _ab, token, auth) = make_token_and_authority(authority, authority);
require_token_authority(&token, &auth).unwrap();
}
#[test]
fn require_token_authority_rejects_mismatched_owner() {
let authority = [0x42u8; 32];
let wrong_owner = [0x77u8; 32];
let (_tb, _ab, token, auth) = make_token_and_authority(authority, wrong_owner);
let err = require_token_authority(&token, &auth).unwrap_err();
assert!(matches!(err, ProgramError::IncorrectAuthority));
}
#[test]
fn require_token_authority_rejects_short_buffer() {
use hopper_native::{AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED};
let data_len = 50;
let mut backing = std::vec![0u8; RuntimeAccount::SIZE + data_len];
let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
unsafe {
raw.write(RuntimeAccount {
borrow_state: NOT_BORROWED,
is_signer: 0,
is_writable: 1,
executable: 0,
resize_delta: 0,
address: NativeAddress::new_from_array([0xAA; 32]),
owner: NativeAddress::new_from_array([3; 32]),
lamports: 0,
data_len: data_len as u64,
});
}
let backend = unsafe { NativeAccountView::new_unchecked(raw) };
let token = crate::account::AccountView::from_backend(backend);
let (_ab, _, _, auth) = make_token_and_authority([0x11; 32], [0x11; 32]);
let err = require_token_authority(&token, &auth).unwrap_err();
assert!(matches!(err, ProgramError::AccountDataTooSmall));
}
fn make_token_with_mint_and_owner(
mint_bytes: [u8; 32],
owner_bytes: [u8; 32],
) -> (std::vec::Vec<u8>, crate::account::AccountView) {
use hopper_native::{AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED};
let token_data_len = 165;
let mut backing = std::vec![0u8; RuntimeAccount::SIZE + token_data_len];
let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
unsafe {
raw.write(RuntimeAccount {
borrow_state: NOT_BORROWED,
is_signer: 0,
is_writable: 1,
executable: 0,
resize_delta: 0,
address: NativeAddress::new_from_array([0xAA; 32]),
owner: NativeAddress::new_from_array([3; 32]),
lamports: 2_039_280,
data_len: token_data_len as u64,
});
let data_ptr = (raw as *mut u8).add(RuntimeAccount::SIZE);
core::ptr::copy_nonoverlapping(mint_bytes.as_ptr(), data_ptr, 32);
core::ptr::copy_nonoverlapping(owner_bytes.as_ptr(), data_ptr.add(32), 32);
}
let backend = unsafe { NativeAccountView::new_unchecked(raw) };
let view = crate::account::AccountView::from_backend(backend);
(backing, view)
}
fn make_mint_with_authority_decimals(
mint_authority: [u8; 32],
decimals: u8,
) -> (std::vec::Vec<u8>, crate::account::AccountView) {
use hopper_native::{AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED};
let mint_data_len = 82;
let mut backing = std::vec![0u8; RuntimeAccount::SIZE + mint_data_len];
let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
unsafe {
raw.write(RuntimeAccount {
borrow_state: NOT_BORROWED,
is_signer: 0,
is_writable: 0,
executable: 0,
resize_delta: 0,
address: NativeAddress::new_from_array([0xBB; 32]),
owner: NativeAddress::new_from_array([3; 32]),
lamports: 1_461_600,
data_len: mint_data_len as u64,
});
let data_ptr = (raw as *mut u8).add(RuntimeAccount::SIZE);
let some_tag: [u8; 4] = 1u32.to_le_bytes();
core::ptr::copy_nonoverlapping(some_tag.as_ptr(), data_ptr, 4);
core::ptr::copy_nonoverlapping(mint_authority.as_ptr(), data_ptr.add(4), 32);
*data_ptr.add(44) = decimals;
*data_ptr.add(45) = 1;
}
let backend = unsafe { NativeAccountView::new_unchecked(raw) };
let view = crate::account::AccountView::from_backend(backend);
(backing, view)
}
#[test]
fn require_token_mint_accepts_matching_mint() {
let mint = [0xABu8; 32];
let (_b, view) = make_token_with_mint_and_owner(mint, [0; 32]);
let expected = crate::address::Address::new_from_array(mint);
require_token_mint(&view, &expected).unwrap();
}
#[test]
fn require_token_mint_rejects_mismatched_mint() {
let mint = [0xABu8; 32];
let (_b, view) = make_token_with_mint_and_owner(mint, [0; 32]);
let wrong = crate::address::Address::new_from_array([0xCDu8; 32]);
let err = require_token_mint(&view, &wrong).unwrap_err();
assert!(matches!(err, ProgramError::InvalidAccountData));
}
#[test]
fn require_token_owner_eq_matches() {
let owner = [0x77u8; 32];
let (_b, view) = make_token_with_mint_and_owner([0; 32], owner);
let expected = crate::address::Address::new_from_array(owner);
require_token_owner_eq(&view, &expected).unwrap();
}
#[test]
fn require_token_owner_eq_rejects_mismatch() {
let owner = [0x77u8; 32];
let (_b, view) = make_token_with_mint_and_owner([0; 32], owner);
let wrong = crate::address::Address::new_from_array([0x88u8; 32]);
let err = require_token_owner_eq(&view, &wrong).unwrap_err();
assert!(matches!(err, ProgramError::IncorrectAuthority));
}
#[test]
fn require_mint_authority_accepts_matching() {
let auth = [0x99u8; 32];
let (_b, view) = make_mint_with_authority_decimals(auth, 6);
let expected = crate::address::Address::new_from_array(auth);
require_mint_authority(&view, &expected).unwrap();
}
#[test]
fn require_mint_authority_rejects_mismatched() {
let auth = [0x99u8; 32];
let (_b, view) = make_mint_with_authority_decimals(auth, 6);
let wrong = crate::address::Address::new_from_array([0x00u8; 32]);
let err = require_mint_authority(&view, &wrong).unwrap_err();
assert!(matches!(err, ProgramError::IncorrectAuthority));
}
#[test]
fn require_mint_decimals_matches() {
let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
require_mint_decimals(&view, 9).unwrap();
}
#[test]
fn require_mint_decimals_rejects_mismatch() {
let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
let err = require_mint_decimals(&view, 6).unwrap_err();
assert!(matches!(err, ProgramError::InvalidAccountData));
}
#[test]
fn require_mint_freeze_authority_rejects_none_tag() {
let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
let expected = crate::address::Address::new_from_array([2u8; 32]);
let err = require_mint_freeze_authority(&view, &expected).unwrap_err();
assert!(matches!(err, ProgramError::InvalidAccountData));
}
}