use crate::account::AccountView;
use crate::address::Address;
use crate::borrow::Ref;
use crate::error::ProgramError;
use crate::foreign::{ExplainExternal, ExternalAccount, ExternalExplainSink, ExternalZeroCopy};
use crate::instruction::{InstructionAccount, InstructionView, Signer};
use crate::ProgramResult;
use core::mem::MaybeUninit;
pub use crate::token_mint::{InitializeMint2, MintConfig, MintPlan, MintProgram};
pub const MAX_TOKEN_MULTISIG_SIGNERS: usize = 11;
#[inline(always)]
fn require_authority_signed_direct(authority: &AccountView<'_>) -> ProgramResult {
if authority.is_signer() {
Ok(())
} else {
Err(ProgramError::MissingRequiredSignature)
}
}
#[inline(always)]
fn authority_meta<'a>(
authority: &'a AccountView<'a>,
multisig_signers: &[&'a AccountView<'a>],
) -> InstructionAccount<'a> {
if multisig_signers.is_empty() {
InstructionAccount::readonly_signer(authority.address())
} else {
InstructionAccount::readonly(authority.address())
}
}
#[inline]
fn require_multisig_signers_direct(multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
if multisig_signers.len() > MAX_TOKEN_MULTISIG_SIGNERS {
return Err(ProgramError::InvalidArgument);
}
for signer in multisig_signers {
require_authority_signed_direct(signer)?;
}
Ok(())
}
#[doc(hidden)]
pub mod encoders {
#[inline(always)]
fn amount_ix(disc: u8, amount: u64) -> [u8; 9] {
let mut data = [0u8; 9];
data[0] = disc;
data[1..9].copy_from_slice(&amount.to_le_bytes());
data
}
#[inline(always)]
fn amount_checked_ix(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
}
#[inline(always)]
pub fn encode_transfer(amount: u64) -> [u8; 9] {
amount_ix(3, amount)
}
#[inline(always)]
pub fn encode_approve(amount: u64) -> [u8; 9] {
amount_ix(4, amount)
}
#[inline(always)]
pub fn encode_mint_to(amount: u64) -> [u8; 9] {
amount_ix(7, amount)
}
#[inline(always)]
pub fn encode_burn(amount: u64) -> [u8; 9] {
amount_ix(8, amount)
}
#[inline(always)]
pub fn encode_transfer_checked(amount: u64, decimals: u8) -> [u8; 10] {
amount_checked_ix(12, amount, decimals)
}
#[inline(always)]
pub fn encode_approve_checked(amount: u64, decimals: u8) -> [u8; 10] {
amount_checked_ix(13, amount, decimals)
}
#[inline(always)]
pub fn encode_mint_to_checked(amount: u64, decimals: u8) -> [u8; 10] {
amount_checked_ix(14, amount, decimals)
}
#[inline(always)]
pub fn encode_burn_checked(amount: u64, decimals: u8) -> [u8; 10] {
amount_checked_ix(15, amount, decimals)
}
#[inline(always)]
pub fn encode_revoke() -> [u8; 1] {
[5]
}
#[inline(always)]
pub fn encode_close_account() -> [u8; 1] {
[9]
}
#[inline(always)]
pub fn encode_freeze_account() -> [u8; 1] {
[10]
}
#[inline(always)]
pub fn encode_thaw_account() -> [u8; 1] {
[11]
}
#[inline(always)]
pub fn encode_sync_native() -> [u8; 1] {
[17]
}
#[inline(always)]
pub fn encode_initialize_account() -> [u8; 1] {
[1]
}
#[inline(always)]
pub fn encode_initialize_account_with_owner(discriminator: u8, owner: &[u8; 32]) -> [u8; 33] {
let mut data = [0u8; 33];
data[0] = discriminator;
data[1..33].copy_from_slice(owner);
data
}
#[inline(always)]
pub fn encode_set_authority(
authority_type: u8,
new_authority: Option<&[u8; 32]>,
) -> ([u8; 35], usize) {
let mut data = [0u8; 35];
data[0] = 6;
data[1] = authority_type;
match new_authority {
Some(key) => {
data[2] = 1;
data[3..35].copy_from_slice(key);
(data, 35)
}
None => {
data[2] = 0;
(data, 3)
}
}
}
}
#[inline]
fn invoke_token_signed<'a, const FIXED: usize>(
data: &[u8],
fixed_accounts: [InstructionAccount<'a>; FIXED],
fixed_views: [&'a AccountView<'a>; FIXED],
multisig_signers: &[&'a AccountView<'a>],
signer_seeds: &[Signer<'_, '_>],
) -> ProgramResult {
let total = FIXED
.checked_add(multisig_signers.len())
.ok_or(ProgramError::ArithmeticOverflow)?;
if multisig_signers.len() > MAX_TOKEN_MULTISIG_SIGNERS
|| total > crate::cpi::MAX_STATIC_CPI_ACCOUNTS
{
return Err(ProgramError::InvalidArgument);
}
let mut accounts: [MaybeUninit<InstructionAccount<'a>>; crate::cpi::MAX_STATIC_CPI_ACCOUNTS] =
[MaybeUninit::uninit(); crate::cpi::MAX_STATIC_CPI_ACCOUNTS];
let mut views: [MaybeUninit<&'a AccountView<'a>>; crate::cpi::MAX_STATIC_CPI_ACCOUNTS] =
[MaybeUninit::uninit(); crate::cpi::MAX_STATIC_CPI_ACCOUNTS];
let mut index = 0;
while index < FIXED {
accounts[index].write(fixed_accounts[index]);
views[index].write(fixed_views[index]);
index += 1;
}
for signer in multisig_signers {
accounts[index].write(InstructionAccount::readonly_signer(signer.address()));
views[index].write(*signer);
index += 1;
}
let accounts = unsafe {
core::slice::from_raw_parts(accounts.as_ptr() as *const InstructionAccount<'a>, total)
};
let views =
unsafe { core::slice::from_raw_parts(views.as_ptr() as *const &'a AccountView<'a>, total) };
let instruction = InstructionView {
program_id: &TOKEN_PROGRAM_ID,
data,
accounts,
};
crate::cpi::invoke_signed_with_bounds::<{ crate::cpi::MAX_STATIC_CPI_ACCOUNTS }>(
&instruction,
views,
signer_seeds,
)
}
#[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);
}
if crate::address::keys_eq_bytes(&data[32..64], authority.address().as_array()) {
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);
}
if crate::address::keys_eq_bytes(&data[32..64], 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);
}
if crate::address::keys_eq_bytes(&data[0..32], 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);
}
if crate::address::keys_eq_bytes(&data[4..36], 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);
}
if crate::address::keys_eq_bytes(&data[50..82], expected_freeze.as_array()) {
Ok(())
} else {
Err(ProgramError::IncorrectAuthority)
}
}
#[deprecated(
since = "0.2.0",
note = "use TransferChecked for explicit classic SPL mint and decimals validation"
)]
#[cfg(feature = "legacy-token-instructions")]
pub struct Transfer<'a> {
pub from: &'a AccountView<'a>,
pub to: &'a AccountView<'a>,
pub authority: &'a AccountView<'a>,
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 data = encoders::encode_transfer(self.amount);
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 explicit classic SPL mint and decimals validation"
)]
#[cfg(feature = "legacy-token-instructions")]
pub struct MintTo<'a> {
pub mint: &'a AccountView<'a>,
pub account: &'a AccountView<'a>,
pub mint_authority: &'a AccountView<'a>,
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 data = encoders::encode_mint_to(self.amount);
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 explicit classic SPL mint and decimals validation"
)]
#[cfg(feature = "legacy-token-instructions")]
pub struct Burn<'a> {
pub account: &'a AccountView<'a>,
pub mint: &'a AccountView<'a>,
pub authority: &'a AccountView<'a>,
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 data = encoders::encode_burn(self.amount);
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<'a>,
pub destination: &'a AccountView<'a>,
pub authority: &'a AccountView<'a>,
}
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 {
self.invoke_signed_unchecked_with_multisig(&[], signers)
}
#[inline]
pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
require_multisig_signers_direct(multisig_signers)?;
self.invoke_signed_multisig(multisig_signers, &[])
}
#[inline]
pub fn invoke_signed_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
}
#[inline(always)]
fn invoke_signed_unchecked_with_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
let data = encoders::encode_close_account();
let accounts = [
InstructionAccount::writable(self.account.address()),
InstructionAccount::writable(self.destination.address()),
authority_meta(self.authority, multisig_signers),
];
let views = [self.account, self.destination, self.authority];
invoke_token_signed(&data, accounts, views, multisig_signers, signers)
}
}
#[deprecated(
since = "0.2.0",
note = "use ApproveChecked for explicit classic SPL mint and decimals validation"
)]
#[cfg(feature = "legacy-token-instructions")]
pub struct Approve<'a> {
pub source: &'a AccountView<'a>,
pub delegate: &'a AccountView<'a>,
pub authority: &'a AccountView<'a>,
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 data = encoders::encode_approve(self.amount);
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<'a>,
pub authority: &'a AccountView<'a>,
}
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 {
self.invoke_signed_unchecked_with_multisig(&[], signers)
}
#[inline]
pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
require_multisig_signers_direct(multisig_signers)?;
self.invoke_signed_multisig(multisig_signers, &[])
}
#[inline]
pub fn invoke_signed_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
}
#[inline(always)]
fn invoke_signed_unchecked_with_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
let data = encoders::encode_revoke();
let accounts = [
InstructionAccount::writable(self.source.address()),
authority_meta(self.authority, multisig_signers),
];
let views = [self.source, self.authority];
invoke_token_signed(&data, accounts, views, multisig_signers, signers)
}
}
pub struct TransferChecked<'a> {
pub from: &'a AccountView<'a>,
pub mint: &'a AccountView<'a>,
pub to: &'a AccountView<'a>,
pub authority: &'a AccountView<'a>,
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_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
require_multisig_signers_direct(multisig_signers)?;
self.invoke_signed_multisig(multisig_signers, &[])
}
#[inline]
pub fn invoke_signed_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
self.invoke_signed_unchecked_with_multisig(multisig_signers, 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 {
self.invoke_signed_unchecked_with_multisig(&[], signers)
}
#[inline(always)]
fn invoke_signed_unchecked_with_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
let data = encoders::encode_transfer_checked(self.amount, self.decimals);
let accounts = [
InstructionAccount::writable(self.from.address()),
InstructionAccount::readonly(self.mint.address()),
InstructionAccount::writable(self.to.address()),
authority_meta(self.authority, multisig_signers),
];
let views = [self.from, self.mint, self.to, self.authority];
invoke_token_signed(&data, accounts, views, multisig_signers, signers)
}
}
pub struct MintToChecked<'a> {
pub mint: &'a AccountView<'a>,
pub account: &'a AccountView<'a>,
pub mint_authority: &'a AccountView<'a>,
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]
pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
require_multisig_signers_direct(multisig_signers)?;
self.invoke_signed_multisig(multisig_signers, &[])
}
#[inline]
pub fn invoke_signed_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
}
#[inline(always)]
fn invoke_signed_unchecked(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
self.invoke_signed_unchecked_with_multisig(&[], signers)
}
#[inline(always)]
fn invoke_signed_unchecked_with_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
let data = encoders::encode_mint_to_checked(self.amount, self.decimals);
let accounts = [
InstructionAccount::writable(self.mint.address()),
InstructionAccount::writable(self.account.address()),
authority_meta(self.mint_authority, multisig_signers),
];
let views = [self.mint, self.account, self.mint_authority];
invoke_token_signed(&data, accounts, views, multisig_signers, signers)
}
}
pub struct BurnChecked<'a> {
pub account: &'a AccountView<'a>,
pub mint: &'a AccountView<'a>,
pub authority: &'a AccountView<'a>,
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_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
require_multisig_signers_direct(multisig_signers)?;
self.invoke_signed_multisig(multisig_signers, &[])
}
#[inline]
pub fn invoke_signed_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
self.invoke_signed_unchecked_with_multisig(multisig_signers, 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 {
self.invoke_signed_unchecked_with_multisig(&[], signers)
}
#[inline(always)]
fn invoke_signed_unchecked_with_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
let data = encoders::encode_burn_checked(self.amount, self.decimals);
let accounts = [
InstructionAccount::writable(self.account.address()),
InstructionAccount::writable(self.mint.address()),
authority_meta(self.authority, multisig_signers),
];
let views = [self.account, self.mint, self.authority];
invoke_token_signed(&data, accounts, views, multisig_signers, signers)
}
}
pub struct ApproveChecked<'a> {
pub source: &'a AccountView<'a>,
pub mint: &'a AccountView<'a>,
pub delegate: &'a AccountView<'a>,
pub authority: &'a AccountView<'a>,
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_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
require_multisig_signers_direct(multisig_signers)?;
self.invoke_signed_multisig(multisig_signers, &[])
}
#[inline]
pub fn invoke_signed_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
self.invoke_signed_unchecked_with_multisig(multisig_signers, 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 {
self.invoke_signed_unchecked_with_multisig(&[], signers)
}
#[inline(always)]
fn invoke_signed_unchecked_with_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
let data = encoders::encode_approve_checked(self.amount, self.decimals);
let accounts = [
InstructionAccount::writable(self.source.address()),
InstructionAccount::readonly(self.mint.address()),
InstructionAccount::readonly(self.delegate.address()),
authority_meta(self.authority, multisig_signers),
];
let views = [self.source, self.mint, self.delegate, self.authority];
invoke_token_signed(&data, accounts, views, multisig_signers, signers)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TokenAuthorityType {
MintTokens = 0,
FreezeAccount = 1,
AccountOwner = 2,
CloseAccount = 3,
}
pub struct SetAuthority<'a> {
pub account: &'a AccountView<'a>,
pub current_authority: &'a AccountView<'a>,
pub authority_type: TokenAuthorityType,
pub new_authority: Option<&'a Address>,
}
impl SetAuthority<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
require_authority_signed_direct(self.current_authority)?;
self.invoke_signed_unchecked_with_multisig(&[], &[])
}
#[inline]
pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
self.invoke_signed_unchecked_with_multisig(&[], signers)
}
#[inline]
pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
require_multisig_signers_direct(multisig_signers)?;
self.invoke_signed_multisig(multisig_signers, &[])
}
#[inline]
pub fn invoke_signed_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
}
#[inline(always)]
fn invoke_signed_unchecked_with_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
let (data, len) = encoders::encode_set_authority(
self.authority_type as u8,
self.new_authority.map(|a| a.as_array()),
);
let accounts = [
InstructionAccount::writable(self.account.address()),
authority_meta(self.current_authority, multisig_signers),
];
let views = [self.account, self.current_authority];
invoke_token_signed(&data[..len], accounts, views, multisig_signers, signers)
}
}
pub struct FreezeAccount<'a> {
pub account: &'a AccountView<'a>,
pub mint: &'a AccountView<'a>,
pub freeze_authority: &'a AccountView<'a>,
}
impl FreezeAccount<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
require_authority_signed_direct(self.freeze_authority)?;
self.invoke_signed_unchecked_with_multisig(&[], &[])
}
#[inline]
pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
self.invoke_signed_unchecked_with_multisig(&[], signers)
}
#[inline]
pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
require_multisig_signers_direct(multisig_signers)?;
self.invoke_signed_multisig(multisig_signers, &[])
}
#[inline]
pub fn invoke_signed_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
}
#[inline(always)]
fn invoke_signed_unchecked_with_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
let data = encoders::encode_freeze_account();
let accounts = [
InstructionAccount::writable(self.account.address()),
InstructionAccount::readonly(self.mint.address()),
authority_meta(self.freeze_authority, multisig_signers),
];
let views = [self.account, self.mint, self.freeze_authority];
invoke_token_signed(&data, accounts, views, multisig_signers, signers)
}
}
pub struct ThawAccount<'a> {
pub account: &'a AccountView<'a>,
pub mint: &'a AccountView<'a>,
pub freeze_authority: &'a AccountView<'a>,
}
impl ThawAccount<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
require_authority_signed_direct(self.freeze_authority)?;
self.invoke_signed_unchecked_with_multisig(&[], &[])
}
#[inline]
pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
self.invoke_signed_unchecked_with_multisig(&[], signers)
}
#[inline]
pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
require_multisig_signers_direct(multisig_signers)?;
self.invoke_signed_multisig(multisig_signers, &[])
}
#[inline]
pub fn invoke_signed_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
}
#[inline(always)]
fn invoke_signed_unchecked_with_multisig(
&self,
multisig_signers: &[&AccountView<'_>],
signers: &[Signer<'_, '_>],
) -> ProgramResult {
let data = encoders::encode_thaw_account();
let accounts = [
InstructionAccount::writable(self.account.address()),
InstructionAccount::readonly(self.mint.address()),
authority_meta(self.freeze_authority, multisig_signers),
];
let views = [self.account, self.mint, self.freeze_authority];
invoke_token_signed(&data, accounts, views, multisig_signers, signers)
}
}
pub struct SyncNative<'a> {
pub account: &'a AccountView<'a>,
}
impl SyncNative<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
let data = encoders::encode_sync_native();
let accounts = [InstructionAccount::writable(self.account.address())];
let views = [self.account];
invoke_token_signed(&data, accounts, views, &[], &[])
}
}
pub struct InitializeAccount<'a> {
pub account: &'a AccountView<'a>,
pub mint: &'a AccountView<'a>,
pub owner: &'a AccountView<'a>,
pub rent_sysvar: &'a AccountView<'a>,
}
impl InitializeAccount<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
let data = encoders::encode_initialize_account();
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 struct InitializeAccount2<'a> {
pub account: &'a AccountView<'a>,
pub mint: &'a AccountView<'a>,
pub owner: &'a Address,
pub rent_sysvar: &'a AccountView<'a>,
}
impl InitializeAccount2<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
let data = encoders::encode_initialize_account_with_owner(16, self.owner.as_array());
let accounts = [
InstructionAccount::writable(self.account.address()),
InstructionAccount::readonly(self.mint.address()),
InstructionAccount::readonly(self.rent_sysvar.address()),
];
let views = [self.account, self.mint, self.rent_sysvar];
invoke_token_signed(&data, accounts, views, &[], &[])
}
}
pub struct InitializeAccount3<'a> {
pub account: &'a AccountView<'a>,
pub mint: &'a AccountView<'a>,
pub owner: &'a Address,
}
impl InitializeAccount3<'_> {
#[inline]
pub fn invoke(&self) -> ProgramResult {
let data = encoders::encode_initialize_account_with_owner(18, self.owner.as_array());
let accounts = [
InstructionAccount::writable(self.account.address()),
InstructionAccount::readonly(self.mint.address()),
];
let views = [self.account, self.mint];
invoke_token_signed(&data, accounts, views, &[], &[])
}
}
pub const TOKEN_PROGRAM_ID: Address = Address::new_from_array(crate::__decode_base58_32(
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
));
pub const SPL_TOKEN_ACCOUNT_LEN: usize = 165;
pub const SPL_MINT_LEN: usize = 82;
const TOKEN_ACCOUNT_MINT_OFFSET: usize = 0;
const TOKEN_ACCOUNT_AUTHORITY_OFFSET: usize = 32;
const TOKEN_ACCOUNT_AMOUNT_OFFSET: usize = 64;
const TOKEN_ACCOUNT_STATE_OFFSET: usize = 108;
const MINT_AUTHORITY_TAG_OFFSET: usize = 0;
const MINT_AUTHORITY_OFFSET: usize = 4;
const MINT_SUPPLY_OFFSET: usize = 36;
const MINT_DECIMALS_OFFSET: usize = 44;
const MINT_INITIALIZED_OFFSET: usize = 45;
const MINT_FREEZE_AUTHORITY_TAG_OFFSET: usize = 46;
const MINT_FREEZE_AUTHORITY_OFFSET: usize = 50;
pub struct SplTokenAccount;
pub struct SplTokenAccountView<'a> {
data: Ref<'a, [u8]>,
}
impl SplTokenAccountView<'_> {
#[inline(always)]
pub fn mint(&self) -> Address {
read_address_unchecked(&self.data, TOKEN_ACCOUNT_MINT_OFFSET)
}
#[inline(always)]
pub fn authority(&self) -> Address {
read_address_unchecked(&self.data, TOKEN_ACCOUNT_AUTHORITY_OFFSET)
}
#[inline(always)]
pub fn amount(&self) -> u64 {
read_u64_unchecked(&self.data, TOKEN_ACCOUNT_AMOUNT_OFFSET)
}
#[inline(always)]
pub fn state(&self) -> u8 {
self.data[TOKEN_ACCOUNT_STATE_OFFSET]
}
#[inline(always)]
pub fn is_initialized(&self) -> bool {
self.state() != 0
}
}
impl ExternalZeroCopy for SplTokenAccount {
type View<'a> = SplTokenAccountView<'a>;
const OWNER: Option<Address> = Some(TOKEN_PROGRAM_ID);
const MIN_LEN: usize = SPL_TOKEN_ACCOUNT_LEN;
#[inline]
fn view<'a>(data: Ref<'a, [u8]>) -> Result<Self::View<'a>, ProgramError> {
Ok(SplTokenAccountView { data })
}
}
impl ExplainExternal for SplTokenAccount {
fn explain<S: ExternalExplainSink>(account: &AccountView<'_>, sink: &mut S) -> ProgramResult {
let account = ExternalAccount::<SplTokenAccount>::try_new(account)?;
account.with_view(|token| {
sink.field_str("adapter", "SplTokenAccount")?;
sink.field_address("mint", &token.mint())?;
sink.field_address("authority", &token.authority())?;
sink.field_u64("amount", token.amount())?;
sink.field_bool("initialized", token.is_initialized())
})
}
}
pub struct SplMint;
pub struct SplMintView<'a> {
data: Ref<'a, [u8]>,
}
impl SplMintView<'_> {
#[inline(always)]
pub fn mint_authority(&self) -> Option<Address> {
read_coption_address(&self.data, MINT_AUTHORITY_TAG_OFFSET, MINT_AUTHORITY_OFFSET)
}
#[inline(always)]
pub fn supply(&self) -> u64 {
read_u64_unchecked(&self.data, MINT_SUPPLY_OFFSET)
}
#[inline(always)]
pub fn decimals(&self) -> u8 {
self.data[MINT_DECIMALS_OFFSET]
}
#[inline(always)]
pub fn is_initialized(&self) -> bool {
self.data[MINT_INITIALIZED_OFFSET] != 0
}
#[inline(always)]
pub fn freeze_authority(&self) -> Option<Address> {
read_coption_address(
&self.data,
MINT_FREEZE_AUTHORITY_TAG_OFFSET,
MINT_FREEZE_AUTHORITY_OFFSET,
)
}
}
impl ExternalZeroCopy for SplMint {
type View<'a> = SplMintView<'a>;
const OWNER: Option<Address> = Some(TOKEN_PROGRAM_ID);
const MIN_LEN: usize = SPL_MINT_LEN;
#[inline]
fn view<'a>(data: Ref<'a, [u8]>) -> Result<Self::View<'a>, ProgramError> {
Ok(SplMintView { data })
}
}
impl ExplainExternal for SplMint {
fn explain<S: ExternalExplainSink>(account: &AccountView<'_>, sink: &mut S) -> ProgramResult {
let account = ExternalAccount::<SplMint>::try_new(account)?;
account.with_view(|mint| {
sink.field_str("adapter", "SplMint")?;
sink.field_u64("supply", mint.supply())?;
sink.field_u64("decimals", mint.decimals() as u64)?;
sink.field_bool("initialized", mint.is_initialized())
})
}
}
#[derive(Debug)]
pub struct CheckedTokenMint<'info> {
account: ExternalAccount<'info, SplTokenAccount>,
mint: Address,
}
impl<'info> CheckedTokenMint<'info> {
#[inline(always)]
pub const fn account(&self) -> ExternalAccount<'info, SplTokenAccount> {
self.account
}
#[inline(always)]
pub const fn mint(&self) -> Address {
self.mint
}
}
#[derive(Debug)]
pub struct CheckedTokenAuthority<'info> {
account: ExternalAccount<'info, SplTokenAccount>,
authority: Address,
}
impl<'info> CheckedTokenAuthority<'info> {
#[inline(always)]
pub const fn account(&self) -> ExternalAccount<'info, SplTokenAccount> {
self.account
}
#[inline(always)]
pub const fn authority(&self) -> Address {
self.authority
}
}
#[derive(Debug)]
pub struct CheckedMintDecimals<'info> {
account: ExternalAccount<'info, SplMint>,
decimals: u8,
}
impl<'info> CheckedMintDecimals<'info> {
#[inline(always)]
pub const fn account(&self) -> ExternalAccount<'info, SplMint> {
self.account
}
#[inline(always)]
pub const fn decimals(&self) -> u8 {
self.decimals
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TokenAmountSnapshot {
amount: u64,
}
impl TokenAmountSnapshot {
#[inline(always)]
pub const fn amount(self) -> u64 {
self.amount
}
}
impl<'info> ExternalAccount<'info, SplTokenAccount> {
#[inline]
pub fn token_amount(&self) -> Result<u64, ProgramError> {
Ok(self.view()?.amount())
}
#[inline]
pub fn checked_mint(
&self,
expected_mint: &Address,
) -> Result<CheckedTokenMint<'info>, ProgramError> {
let mint = self.view()?.mint();
if &mint == expected_mint {
Ok(CheckedTokenMint {
account: *self,
mint,
})
} else {
Err(ProgramError::InvalidAccountData)
}
}
#[inline]
pub fn checked_authority(
&self,
expected_authority: &Address,
) -> Result<CheckedTokenAuthority<'info>, ProgramError> {
let authority = self.view()?.authority();
if &authority == expected_authority {
Ok(CheckedTokenAuthority {
account: *self,
authority,
})
} else {
Err(ProgramError::IncorrectAuthority)
}
}
#[inline]
pub fn amount_snapshot(&self) -> Result<TokenAmountSnapshot, ProgramError> {
Ok(TokenAmountSnapshot {
amount: self.token_amount()?,
})
}
#[inline]
pub fn assert_amount_delta(
&self,
before: TokenAmountSnapshot,
expected_delta: i128,
) -> ProgramResult {
let after = self.token_amount()? as i128;
let expected = (before.amount as i128)
.checked_add(expected_delta)
.ok_or(ProgramError::ArithmeticOverflow)?;
if expected < 0 || expected > u64::MAX as i128 {
return Err(ProgramError::ArithmeticOverflow);
}
if after == expected {
Ok(())
} else {
Err(ProgramError::InvalidAccountData)
}
}
#[inline]
pub fn assert_amount_unchanged(&self, before: TokenAmountSnapshot) -> ProgramResult {
self.assert_amount_delta(before, 0)
}
}
impl<'info> ExternalAccount<'info, SplMint> {
#[inline]
pub fn checked_decimals(
&self,
expected: u8,
) -> Result<CheckedMintDecimals<'info>, ProgramError> {
let decimals = self.view()?.decimals();
if decimals == expected {
Ok(CheckedMintDecimals {
account: *self,
decimals,
})
} else {
Err(ProgramError::InvalidAccountData)
}
}
}
#[inline(always)]
fn read_address_unchecked(data: &[u8], offset: usize) -> Address {
let mut bytes = [0u8; 32];
bytes.copy_from_slice(&data[offset..offset + 32]);
Address::new_from_array(bytes)
}
#[inline(always)]
fn read_u64_unchecked(data: &[u8], offset: usize) -> u64 {
u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
])
}
#[inline(always)]
fn read_u32_unchecked(data: &[u8], offset: usize) -> u32 {
u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
#[inline(always)]
fn read_coption_address(data: &[u8], tag_offset: usize, address_offset: usize) -> Option<Address> {
match read_u32_unchecked(data, tag_offset) {
1 => Some(read_address_unchecked(data, address_offset)),
_ => None,
}
}
pub mod instructions {
pub use super::{
ApproveChecked, BurnChecked, CloseAccount, FreezeAccount, InitializeAccount,
InitializeAccount2, InitializeAccount3, MintToChecked, Revoke, SetAuthority, SyncNative,
ThawAccount, TokenAuthorityType, TransferChecked,
};
#[cfg(feature = "legacy-token-instructions")]
#[allow(deprecated)]
pub use super::{Approve, Burn, MintTo, Transfer};
}
#[cfg(test)]
mod tests {
use super::*;
use hopper_native::{
AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
};
fn make_account(owner: Address, data: &[u8]) -> (std::vec::Vec<u64>, AccountView<'static>) {
let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + data.len()).div_ceil(8)];
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([7; 32]),
owner: NativeAddress::new_from_array(owner.to_bytes()),
lamports: 1,
data_len: data.len() as u64,
});
let data_ptr = (backing.as_mut_ptr() as *mut u8).add(RuntimeAccount::SIZE);
core::ptr::copy_nonoverlapping(data.as_ptr(), data_ptr, data.len());
}
let backend = unsafe { NativeAccountView::new_unchecked(raw) };
(backing, AccountView::from_backend(backend))
}
fn token_account_data(
mint: Address,
authority: Address,
amount: u64,
) -> [u8; SPL_TOKEN_ACCOUNT_LEN] {
let mut data = [0u8; SPL_TOKEN_ACCOUNT_LEN];
data[0..32].copy_from_slice(mint.as_bytes());
data[32..64].copy_from_slice(authority.as_bytes());
data[64..72].copy_from_slice(&amount.to_le_bytes());
data[108] = 1;
data
}
fn mint_data(authority: Address, supply: u64, decimals: u8) -> [u8; SPL_MINT_LEN] {
let mut data = [0u8; SPL_MINT_LEN];
data[0..4].copy_from_slice(&1u32.to_le_bytes());
data[4..36].copy_from_slice(authority.as_bytes());
data[36..44].copy_from_slice(&supply.to_le_bytes());
data[44] = decimals;
data[45] = 1;
data
}
#[test]
fn transfer_checked_discriminator_is_12() {
}
#[test]
fn spl_external_token_account_view_proofs_and_amount_delta() {
let mint = Address::new_from_array([2; 32]);
let authority = Address::new_from_array([3; 32]);
let data = token_account_data(mint, authority, 100);
let (mut backing, account) = make_account(TOKEN_PROGRAM_ID, &data);
let token = ExternalAccount::<SplTokenAccount>::try_new(&account).unwrap();
let view = token.view().unwrap();
assert_eq!(view.mint(), mint);
assert_eq!(view.authority(), authority);
assert_eq!(view.amount(), 100);
assert!(view.is_initialized());
assert_eq!(token.checked_mint(&mint).unwrap().mint(), mint);
assert_eq!(
token.checked_authority(&authority).unwrap().authority(),
authority
);
assert_eq!(
token
.checked_mint(&Address::new_from_array([9; 32]))
.unwrap_err(),
ProgramError::InvalidAccountData
);
let before = token.amount_snapshot().unwrap();
let backing_bytes = unsafe {
core::slice::from_raw_parts_mut(backing.as_mut_ptr() as *mut u8, backing.len() * 8)
};
backing_bytes[RuntimeAccount::SIZE + 64..RuntimeAccount::SIZE + 72]
.copy_from_slice(&150u64.to_le_bytes());
token.assert_amount_delta(before, 50).unwrap();
assert_eq!(
token.assert_amount_delta(before, 49).unwrap_err(),
ProgramError::InvalidAccountData
);
}
#[test]
fn spl_external_mint_view_and_decimals_proof() {
let authority = Address::new_from_array([4; 32]);
let data = mint_data(authority, 1_000_000, 6);
let (_backing, account) = make_account(TOKEN_PROGRAM_ID, &data);
let mint = ExternalAccount::<SplMint>::try_new(&account).unwrap();
let view = mint.view().unwrap();
assert_eq!(view.mint_authority(), Some(authority));
assert_eq!(view.supply(), 1_000_000);
assert_eq!(view.decimals(), 6);
assert!(view.is_initialized());
assert_eq!(mint.checked_decimals(6).unwrap().decimals(), 6);
assert_eq!(
mint.checked_decimals(9).unwrap_err(),
ProgramError::InvalidAccountData
);
}
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);
}
}
#[test]
fn authority_and_initialize_encodings_match_spl_token_wire_format() {
let authority = Address::new_from_array([9; 32]);
let (set_authority, len) = encoders::encode_set_authority(
TokenAuthorityType::AccountOwner as u8,
Some(authority.as_array()),
);
assert_eq!(len, 35);
assert_eq!(set_authority[0], 6);
assert_eq!(set_authority[1], 2);
assert_eq!(set_authority[2], 1);
assert_eq!(&set_authority[3..35], authority.as_bytes());
let (set_authority, len) =
encoders::encode_set_authority(TokenAuthorityType::CloseAccount as u8, None);
assert_eq!(len, 3);
assert_eq!(&set_authority[..3], &[6, 3, 0]);
let init2 = encoders::encode_initialize_account_with_owner(16, authority.as_array());
let init3 = encoders::encode_initialize_account_with_owner(18, authority.as_array());
assert_eq!(init2[0], 16);
assert_eq!(init3[0], 18);
assert_eq!(&init2[1..33], authority.as_bytes());
assert_eq!(&init3[1..33], authority.as_bytes());
}
#[test]
fn shipped_encoders_match_pre_refactor_golden_bytes() {
assert_eq!(encoders::encode_transfer(1), [3, 1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(encoders::encode_approve(1), [4, 1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(encoders::encode_mint_to(1), [7, 1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(encoders::encode_burn(1), [8, 1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(
encoders::encode_transfer_checked(1, 9),
[12, 1, 0, 0, 0, 0, 0, 0, 0, 9]
);
assert_eq!(
encoders::encode_approve_checked(1, 9),
[13, 1, 0, 0, 0, 0, 0, 0, 0, 9]
);
assert_eq!(
encoders::encode_mint_to_checked(1, 9),
[14, 1, 0, 0, 0, 0, 0, 0, 0, 9]
);
assert_eq!(
encoders::encode_burn_checked(1, 9),
[15, 1, 0, 0, 0, 0, 0, 0, 0, 9]
);
assert_eq!(encoders::encode_revoke(), [5]);
assert_eq!(encoders::encode_close_account(), [9]);
assert_eq!(encoders::encode_freeze_account(), [10]);
assert_eq!(encoders::encode_thaw_account(), [11]);
assert_eq!(encoders::encode_sync_native(), [17]);
assert_eq!(encoders::encode_initialize_account(), [1]);
let owner = [
0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31,
];
let init2 = encoders::encode_initialize_account_with_owner(16, &owner);
assert_eq!(init2[0], 16);
assert_eq!(&init2[1..33], &owner);
let init3 = encoders::encode_initialize_account_with_owner(18, &owner);
assert_eq!(init3[0], 18);
assert_eq!(&init3[1..33], &owner);
let (sa_some, some_len) = encoders::encode_set_authority(2, Some(&owner));
assert_eq!(some_len, 35);
assert_eq!(sa_some[0], 6);
assert_eq!(sa_some[1], 2);
assert_eq!(sa_some[2], 1);
assert_eq!(&sa_some[3..35], &owner);
let (sa_none, none_len) = encoders::encode_set_authority(3, None);
assert_eq!(none_len, 3);
assert_eq!(&sa_none[..3], &[6, 3, 0]);
}
fn make_token_and_authority(
authority_bytes: [u8; 32],
token_owner_bytes: [u8; 32],
) -> (
std::vec::Vec<u64>,
std::vec::Vec<u64>,
crate::account::AccountView<'static>,
crate::account::AccountView<'static>,
) {
use hopper_native::{
AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
NOT_BORROWED,
};
let token_data_len = 165;
let mut token_backing =
std::vec![0u64; (RuntimeAccount::SIZE + token_data_len).div_ceil(8)];
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![0u64; (RuntimeAccount::SIZE).div_ceil(8)];
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![0u64; (RuntimeAccount::SIZE + data_len).div_ceil(8)];
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<u64>, crate::account::AccountView<'static>) {
use hopper_native::{
AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
NOT_BORROWED,
};
let token_data_len = 165;
let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + token_data_len).div_ceil(8)];
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<u64>, crate::account::AccountView<'static>) {
use hopper_native::{
AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
NOT_BORROWED,
};
let mint_data_len = 82;
let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + mint_data_len).div_ceil(8)];
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));
}
}