use {
crate::token::{coption_is_some, create_token_account, validate_coption_tag},
anchor_lang::{
accounts::{Account, SlabInit, SlabSchema},
programs::Token,
require, require_eq, AccountConstraint, Id,
},
bytemuck::{Pod, Zeroable},
pinocchio::account::AccountView,
solana_address::Address,
solana_program_error::ProgramError,
};
pub(crate) fn validate_mint_initialized(data: &[u8]) -> Result<(), ProgramError> {
const MINT_INITIALIZED_OFFSET: usize = 36 + 8 + 1;
const MINT_AUTHORITY_TAG_OFFSET: usize = 0;
const MINT_FREEZE_AUTHORITY_TAG_OFFSET: usize = MINT_INITIALIZED_OFFSET + 1;
validate_coption_tag(data, MINT_AUTHORITY_TAG_OFFSET)?;
validate_coption_tag(data, MINT_FREEZE_AUTHORITY_TAG_OFFSET)?;
match data.get(MINT_INITIALIZED_OFFSET).copied() {
Some(1) => Ok(()),
Some(0) => Err(ProgramError::UninitializedAccount),
Some(_) => Err(ProgramError::InvalidAccountData),
None => Err(ProgramError::InvalidAccountData),
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Mint {
mint_authority_flag: [u8; 4],
mint_authority: Address,
supply: [u8; 8],
decimals: u8,
is_initialized: u8,
freeze_authority_flag: [u8; 4],
freeze_authority: Address,
}
unsafe impl Pod for Mint {}
unsafe impl Zeroable for Mint {}
#[doc(hidden)]
impl anchor_lang::IdlAccountType for Mint {}
impl anchor_lang::Space for Mint {
const INIT_SPACE: usize = core::mem::size_of::<Self>();
}
impl SlabSchema for Mint {
const DATA_OFFSET: usize = 0;
const MIN_DATA_LEN: usize = core::mem::size_of::<Self>();
#[inline(always)]
fn validate(view: &AccountView, data: &[u8]) -> Result<(), ProgramError> {
require!(view.owned_by(&Token::id()), ProgramError::IllegalOwner);
require_eq!(
data.len(),
core::mem::size_of::<Self>(),
ProgramError::InvalidAccountData
);
validate_mint_initialized(data)?;
Ok(())
}
}
#[derive(Default)]
pub struct MintInitParams<'a> {
pub decimals: Option<u8>,
pub authority: Option<&'a AccountView>,
pub freeze_authority: Option<&'a AccountView>,
}
impl SlabInit for Mint {
type Params<'a> = MintInitParams<'a>;
#[cold]
fn create_and_initialize<'a>(
payer: &AccountView,
account: &AccountView,
_space: usize,
params: &Self::Params<'a>,
signer_seeds: Option<&[&[u8]]>,
payer_signer_seeds: Option<&[&[u8]]>,
) -> Result<(), ProgramError> {
let decimals = params.decimals.ok_or(ProgramError::InvalidArgument)?;
let authority = params.authority.ok_or(ProgramError::InvalidArgument)?;
create_token_account(
payer,
account,
core::mem::size_of::<Self>(),
signer_seeds,
payer_signer_seeds,
)?;
pinocchio_token::instructions::InitializeMint2 {
mint: account,
decimals,
mint_authority: authority.address(),
freeze_authority: params.freeze_authority.map(|v| v.address()),
}
.invoke()
}
}
impl Mint {
pub const LEN: usize = core::mem::size_of::<Self>();
pub fn supply(&self) -> u64 {
u64::from_le_bytes(self.supply)
}
pub fn decimals(&self) -> u8 {
self.decimals
}
pub fn has_mint_authority(&self) -> bool {
coption_is_some(&self.mint_authority_flag)
}
pub fn mint_authority(&self) -> Option<&Address> {
if self.has_mint_authority() {
Some(&self.mint_authority)
} else {
None
}
}
pub fn is_initialized(&self) -> bool {
self.is_initialized == 1
}
pub fn has_freeze_authority(&self) -> bool {
coption_is_some(&self.freeze_authority_flag)
}
pub fn freeze_authority(&self) -> Option<&Address> {
if self.has_freeze_authority() {
Some(&self.freeze_authority)
} else {
None
}
}
}
pub struct AuthorityConstraint;
pub struct FreezeAuthorityConstraint;
pub struct DecimalsConstraint;
pub struct TokenProgramConstraint;
impl AccountConstraint<Account<Mint>> for AuthorityConstraint {
type Value = Address;
#[inline(always)]
fn check(account: &Account<Mint>, expected: &Address) -> Result<(), ProgramError> {
require_eq!(
account.mint_authority(),
Some(expected),
ProgramError::InvalidAccountData
);
Ok(())
}
}
impl AccountConstraint<Account<Mint>> for FreezeAuthorityConstraint {
type Value = Address;
#[inline(always)]
fn check(account: &Account<Mint>, expected: &Address) -> Result<(), ProgramError> {
require_eq!(
account.freeze_authority(),
Some(expected),
ProgramError::InvalidAccountData
);
Ok(())
}
}
impl AccountConstraint<Account<Mint>> for DecimalsConstraint {
type Value = u8;
#[inline(always)]
fn check(account: &Account<Mint>, expected: &u8) -> Result<(), ProgramError> {
require_eq!(
account.decimals(),
*expected,
ProgramError::InvalidAccountData
);
Ok(())
}
}
impl AccountConstraint<Account<Mint>> for TokenProgramConstraint {
type Value = Address;
#[inline(always)]
fn check(account: &Account<Mint>, expected: &Address) -> Result<(), ProgramError> {
require!(
AsRef::<AccountView>::as_ref(account).owned_by(expected),
ProgramError::IllegalOwner
);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mint_validation_rejects_non_canonical_coption_tags() {
let mut data = [0u8; Mint::LEN];
data[36 + 8 + 1] = 1;
assert_eq!(validate_mint_initialized(&data), Ok(()));
data[0] = 1;
data[1] = 2;
assert!(matches!(
validate_mint_initialized(&data),
Err(ProgramError::InvalidAccountData)
));
}
#[test]
fn mint_accessors_require_canonical_some_tags() {
let mint = Mint {
mint_authority_flag: [1, 2, 0, 0],
mint_authority: Address::new_from_array([1; 32]),
supply: [0; 8],
decimals: 6,
is_initialized: 1,
freeze_authority_flag: [1, 0, 0, 1],
freeze_authority: Address::new_from_array([2; 32]),
};
assert!(!mint.has_mint_authority());
assert!(!mint.has_freeze_authority());
}
}