diff --git a/programs/token-2022/Cargo.toml b/programs/token-2022/Cargo.toml new file mode 100644 index 00000000..66ab6e52 --- /dev/null +++ b/programs/token-2022/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "pinocchio-token-2022" +description = "Pinocchio helpers to invoke Token 2022 program instructions" +version = "0.1.0" +edition = { workspace = true } +license = { workspace = true } +readme = "./README.md" +repository = { workspace = true } + +[lib] +crate-type = ["rlib"] + +[dependencies] +pinocchio = { workspace = true } +pinocchio-pubkey = { workspace = true } diff --git a/programs/token-2022/README.md b/programs/token-2022/README.md new file mode 100644 index 00000000..b9579f87 --- /dev/null +++ b/programs/token-2022/README.md @@ -0,0 +1,40 @@ +# pinocchio-token + +This crate contains [`pinocchio`](https://crates.io/crates/pinocchio) helpers to perform cross-program invocations (CPIs) for SPL Token 2022 instructions. + +Each instruction defines an `struct` with the accounts and parameters required. Once all values are set, you can call directly `invoke` or `invoke_signed` to perform the CPI. + +This is a `no_std` crate. + +> **Note:** The API defined in this crate is subject to change. + +## Examples + +Initializing a mint account: +```rust +// This example assumes that the instruction receives a writable `mint` +// account; `authority` is a `Pubkey`. +InitializeMint { + mint, + rent_sysvar, + decimals: 9, + mint_authority: authority, + freeze_authority: Some(authority), +}.invoke()?; +``` + +Performing a transfer of tokens: +```rust +// This example assumes that the instruction receives writable `from` and `to` +// accounts, and a signer `authority` account. +Transfer { + from, + to, + authority, + amount: 10, +}.invoke()?; +``` + +## License + +The code is licensed under the [Apache License Version 2.0](../LICENSE) diff --git a/programs/token-2022/src/instructions/approve.rs b/programs/token-2022/src/instructions/approve.rs new file mode 100644 index 00000000..80355a7d --- /dev/null +++ b/programs/token-2022/src/instructions/approve.rs @@ -0,0 +1,65 @@ +use core::slice::from_raw_parts; + +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + ProgramResult, +}; + +use crate::{write_bytes, UNINIT_BYTE}; + +/// Approves a delegate. +/// +/// ### Accounts: +/// 0. `[WRITE]` The token account. +/// 1. `[]` The delegate. +/// 2. `[SIGNER]` The source account owner. +pub struct Approve<'a> { + /// Source Account. + pub source: &'a AccountInfo, + /// Delegate Account + pub delegate: &'a AccountInfo, + /// Source Owner Account + pub authority: &'a AccountInfo, + /// Amount + pub amount: u64, +} + +impl Approve<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // Account metadata + let account_metas: [AccountMeta; 3] = [ + AccountMeta::writable(self.source.key()), + AccountMeta::readonly(self.delegate.key()), + AccountMeta::readonly_signer(self.authority.key()), + ]; + + // Instruction data + // - [0]: instruction discriminator (1 byte, u8) + // - [1..9]: amount (8 bytes, u64) + let mut instruction_data = [UNINIT_BYTE; 9]; + + // Set discriminator as u8 at offset [0] + write_bytes(&mut instruction_data, &[4]); + // Set amount as u64 at offset [1..9] + write_bytes(&mut instruction_data[1..], &self.amount.to_le_bytes()); + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) }, + }; + + invoke_signed( + &instruction, + &[self.source, self.delegate, self.authority], + signers, + ) + } +} diff --git a/programs/token-2022/src/instructions/approve_checked.rs b/programs/token-2022/src/instructions/approve_checked.rs new file mode 100644 index 00000000..9c924936 --- /dev/null +++ b/programs/token-2022/src/instructions/approve_checked.rs @@ -0,0 +1,74 @@ +use core::slice::from_raw_parts; + +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + ProgramResult, +}; + +use crate::{write_bytes, UNINIT_BYTE}; + +/// Approves a delegate. +/// +/// ### Accounts: +/// 0. `[WRITE]` The source account. +/// 1. `[]` The token mint. +/// 2. `[]` The delegate. +/// 3. `[SIGNER]` The source account owner. +pub struct ApproveChecked<'a> { + /// Source Account. + pub source: &'a AccountInfo, + /// Mint Account. + pub mint: &'a AccountInfo, + /// Delegate Account. + pub delegate: &'a AccountInfo, + /// Source Owner Account. + pub authority: &'a AccountInfo, + /// Amount. + pub amount: u64, + /// Decimals. + pub decimals: u8, +} + +impl ApproveChecked<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // Account metadata + let account_metas: [AccountMeta; 4] = [ + AccountMeta::writable(self.source.key()), + AccountMeta::readonly(self.mint.key()), + AccountMeta::readonly(self.delegate.key()), + AccountMeta::readonly_signer(self.authority.key()), + ]; + + // Instruction data + // - [0] : instruction discriminator (1 byte, u8) + // - [1..9]: amount (8 bytes, u64) + // - [9] : decimals (1 byte, u8) + let mut instruction_data = [UNINIT_BYTE; 10]; + + // Set discriminator as u8 at offset [0] + write_bytes(&mut instruction_data, &[13]); + // Set amount as u64 at offset [1..9] + write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); + // Set decimals as u8 at offset [9] + write_bytes(&mut instruction_data[9..], &[self.decimals]); + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) }, + }; + + invoke_signed( + &instruction, + &[self.source, self.mint, self.delegate, self.authority], + signers, + ) + } +} diff --git a/programs/token-2022/src/instructions/burn.rs b/programs/token-2022/src/instructions/burn.rs new file mode 100644 index 00000000..d379faf1 --- /dev/null +++ b/programs/token-2022/src/instructions/burn.rs @@ -0,0 +1,65 @@ +use core::slice::from_raw_parts; + +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + ProgramResult, +}; + +use crate::{write_bytes, UNINIT_BYTE}; + +/// Burns tokens by removing them from an account. +/// +/// ### Accounts: +/// 0. `[WRITE]` The account to burn from. +/// 1. `[WRITE]` The token mint. +/// 2. `[SIGNER]` The account's owner/delegate. +pub struct Burn<'a> { + /// Source of the Burn Account + pub account: &'a AccountInfo, + /// Mint Account + pub mint: &'a AccountInfo, + /// Owner of the Token Account + pub authority: &'a AccountInfo, + /// Amount + pub amount: u64, +} + +impl Burn<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // Account metadata + let account_metas: [AccountMeta; 3] = [ + AccountMeta::writable(self.account.key()), + AccountMeta::writable(self.mint.key()), + AccountMeta::readonly_signer(self.authority.key()), + ]; + + // Instruction data + // - [0]: instruction discriminator (1 byte, u8) + // - [1..9]: amount (8 bytes, u64) + let mut instruction_data = [UNINIT_BYTE; 9]; + + // Set discriminator as u8 at offset [0] + write_bytes(&mut instruction_data, &[8]); + // Set amount as u64 at offset [1..9] + write_bytes(&mut instruction_data[1..], &self.amount.to_le_bytes()); + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) }, + }; + + invoke_signed( + &instruction, + &[self.account, self.mint, self.authority], + signers, + ) + } +} diff --git a/programs/token-2022/src/instructions/burn_checked.rs b/programs/token-2022/src/instructions/burn_checked.rs new file mode 100644 index 00000000..cd316644 --- /dev/null +++ b/programs/token-2022/src/instructions/burn_checked.rs @@ -0,0 +1,69 @@ +use core::slice::from_raw_parts; + +use crate::{write_bytes, UNINIT_BYTE}; +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + ProgramResult, +}; + +/// Burns tokens by removing them from an account. +/// +/// ### Accounts: +/// 0. `[WRITE]` The account to burn from. +/// 1. `[WRITE]` The token mint. +/// 2. `[SIGNER]` The account's owner/delegate. +pub struct BurnChecked<'a> { + /// Source of the Burn Account + pub account: &'a AccountInfo, + /// Mint Account + pub mint: &'a AccountInfo, + /// Owner of the Token Account + pub authority: &'a AccountInfo, + /// Amount + pub amount: u64, + /// Decimals + pub decimals: u8, +} + +impl BurnChecked<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // Account metadata + let account_metas: [AccountMeta; 3] = [ + AccountMeta::writable(self.account.key()), + AccountMeta::writable(self.mint.key()), + AccountMeta::readonly_signer(self.authority.key()), + ]; + + // Instruction data + // - [0]: instruction discriminator (1 byte, u8) + // - [1..9]: amount (8 bytes, u64) + // - [9]: decimals (1 byte, u8) + let mut instruction_data = [UNINIT_BYTE; 10]; + + // Set discriminator as u8 at offset [0] + write_bytes(&mut instruction_data, &[15]); + // Set amount as u64 at offset [1..9] + write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); + // Set decimals as u8 at offset [9] + write_bytes(&mut instruction_data[9..], &[self.decimals]); + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) }, + }; + + invoke_signed( + &instruction, + &[self.account, self.mint, self.authority], + signers, + ) + } +} diff --git a/programs/token-2022/src/instructions/close_account.rs b/programs/token-2022/src/instructions/close_account.rs new file mode 100644 index 00000000..0d6d6947 --- /dev/null +++ b/programs/token-2022/src/instructions/close_account.rs @@ -0,0 +1,49 @@ +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + ProgramResult, +}; + +/// Close an account by transferring all its SOL to the destination account. +/// +/// ### Accounts: +/// 0. `[WRITE]` The account to close. +/// 1. `[WRITE]` The destination account. +/// 2. `[SIGNER]` The account's owner. +pub struct CloseAccount<'a> { + /// Token Account. + pub account: &'a AccountInfo, + /// Destination Account + pub destination: &'a AccountInfo, + /// Owner Account + pub authority: &'a AccountInfo, +} + +impl CloseAccount<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // account metadata + let account_metas: [AccountMeta; 3] = [ + AccountMeta::writable(self.account.key()), + AccountMeta::writable(self.destination.key()), + AccountMeta::readonly_signer(self.authority.key()), + ]; + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: &[9], + }; + + invoke_signed( + &instruction, + &[self.account, self.destination, self.authority], + signers, + ) + } +} diff --git a/programs/token-2022/src/instructions/freeze_account.rs b/programs/token-2022/src/instructions/freeze_account.rs new file mode 100644 index 00000000..3cf2fb55 --- /dev/null +++ b/programs/token-2022/src/instructions/freeze_account.rs @@ -0,0 +1,49 @@ +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + ProgramResult, +}; + +/// Freeze an Initialized account using the Mint's freeze_authority +/// +/// ### Accounts: +/// 0. `[WRITE]` The account to freeze. +/// 1. `[]` The token mint. +/// 2. `[SIGNER]` The mint freeze authority. +pub struct FreezeAccount<'a> { + /// Token Account to freeze. + pub account: &'a AccountInfo, + /// Mint Account. + pub mint: &'a AccountInfo, + /// Mint Freeze Authority Account + pub freeze_authority: &'a AccountInfo, +} + +impl FreezeAccount<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // account metadata + let account_metas: [AccountMeta; 3] = [ + AccountMeta::writable(self.account.key()), + AccountMeta::readonly(self.mint.key()), + AccountMeta::readonly_signer(self.freeze_authority.key()), + ]; + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: &[10], + }; + + invoke_signed( + &instruction, + &[self.account, self.mint, self.freeze_authority], + signers, + ) + } +} diff --git a/programs/token-2022/src/instructions/initialize_account.rs b/programs/token-2022/src/instructions/initialize_account.rs new file mode 100644 index 00000000..cd608146 --- /dev/null +++ b/programs/token-2022/src/instructions/initialize_account.rs @@ -0,0 +1,53 @@ +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + ProgramResult, +}; + +/// Initialize a new Token Account. +/// +/// ### Accounts: +/// 0. `[WRITE]` The account to initialize. +/// 1. `[]` The mint this account will be associated with. +/// 2. `[]` The new account's owner/multisignature. +/// 3. `[]` Rent sysvar +pub struct InitializeAccount<'a> { + /// New Account. + pub account: &'a AccountInfo, + /// Mint Account. + pub mint: &'a AccountInfo, + /// Owner of the new Account. + pub owner: &'a AccountInfo, + /// Rent Sysvar Account + pub rent_sysvar: &'a AccountInfo, +} + +impl InitializeAccount<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // account metadata + let account_metas: [AccountMeta; 4] = [ + AccountMeta::writable(self.account.key()), + AccountMeta::readonly(self.mint.key()), + AccountMeta::readonly(self.owner.key()), + AccountMeta::readonly(self.rent_sysvar.key()), + ]; + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: &[1], + }; + + invoke_signed( + &instruction, + &[self.account, self.mint, self.owner, self.rent_sysvar], + signers, + ) + } +} diff --git a/programs/token-2022/src/instructions/initialize_account_2.rs b/programs/token-2022/src/instructions/initialize_account_2.rs new file mode 100644 index 00000000..8dfa41b8 --- /dev/null +++ b/programs/token-2022/src/instructions/initialize_account_2.rs @@ -0,0 +1,66 @@ +use core::slice::from_raw_parts; + +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + pubkey::Pubkey, + ProgramResult, +}; + +use crate::{write_bytes, UNINIT_BYTE}; + +/// Initialize a new Token Account. +/// +/// ### Accounts: +/// 0. `[WRITE]` The account to initialize. +/// 1. `[]` The mint this account will be associated with. +/// 3. `[]` Rent sysvar +pub struct InitializeAccount2<'a> { + /// New Account. + pub account: &'a AccountInfo, + /// Mint Account. + pub mint: &'a AccountInfo, + /// Rent Sysvar Account + pub rent_sysvar: &'a AccountInfo, + /// Owner of the new Account. + pub owner: &'a Pubkey, +} + +impl InitializeAccount2<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // account metadata + let account_metas: [AccountMeta; 3] = [ + AccountMeta::writable(self.account.key()), + AccountMeta::readonly(self.mint.key()), + AccountMeta::readonly(self.rent_sysvar.key()), + ]; + + // instruction data + // - [0]: instruction discriminator (1 byte, u8) + // - [1..33]: owner (32 bytes, Pubkey) + let mut instruction_data = [UNINIT_BYTE; 33]; + + // Set discriminator as u8 at offset [0] + write_bytes(&mut instruction_data, &[16]); + // Set owner as [u8; 32] at offset [1..33] + write_bytes(&mut instruction_data[1..], self.owner); + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 33) }, + }; + + invoke_signed( + &instruction, + &[self.account, self.mint, self.rent_sysvar], + signers, + ) + } +} diff --git a/programs/token-2022/src/instructions/initialize_account_3.rs b/programs/token-2022/src/instructions/initialize_account_3.rs new file mode 100644 index 00000000..65c06392 --- /dev/null +++ b/programs/token-2022/src/instructions/initialize_account_3.rs @@ -0,0 +1,58 @@ +use core::slice::from_raw_parts; + +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + pubkey::Pubkey, + ProgramResult, +}; + +use crate::{write_bytes, UNINIT_BYTE}; + +/// Initialize a new Token Account. +/// +/// ### Accounts: +/// 0. `[WRITE]` The account to initialize. +/// 1. `[]` The mint this account will be associated with. +pub struct InitializeAccount3<'a> { + /// New Account. + pub account: &'a AccountInfo, + /// Mint Account. + pub mint: &'a AccountInfo, + /// Owner of the new Account. + pub owner: &'a Pubkey, +} + +impl InitializeAccount3<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // account metadata + let account_metas: [AccountMeta; 2] = [ + AccountMeta::writable(self.account.key()), + AccountMeta::readonly(self.mint.key()), + ]; + + // instruction data + // - [0]: instruction discriminator (1 byte, u8) + // - [1..33]: owner (32 bytes, Pubkey) + let mut instruction_data = [UNINIT_BYTE; 33]; + + // Set discriminator as u8 at offset [0] + write_bytes(&mut instruction_data, &[18]); + // Set owner as [u8; 32] at offset [1..33] + write_bytes(&mut instruction_data[1..], self.owner); + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 33) }, + }; + + invoke_signed(&instruction, &[self.account, self.mint], signers) + } +} diff --git a/programs/token-2022/src/instructions/initialize_mint.rs b/programs/token-2022/src/instructions/initialize_mint.rs new file mode 100644 index 00000000..2e93eea2 --- /dev/null +++ b/programs/token-2022/src/instructions/initialize_mint.rs @@ -0,0 +1,74 @@ +use core::slice::from_raw_parts; + +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + pubkey::Pubkey, + ProgramResult, +}; + +use crate::{write_bytes, UNINIT_BYTE}; + +/// Initialize a new mint. +/// +/// ### Accounts: +/// 0. `[WRITABLE]` Mint account +/// 1. `[]` Rent sysvar +pub struct InitializeMint<'a> { + /// Mint Account. + pub mint: &'a AccountInfo, + /// Rent sysvar Account. + pub rent_sysvar: &'a AccountInfo, + /// Decimals. + pub decimals: u8, + /// Mint Authority. + pub mint_authority: &'a Pubkey, + /// Freeze Authority. + pub freeze_authority: Option<&'a Pubkey>, +} + +impl InitializeMint<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // Account metadata + let account_metas: [AccountMeta; 2] = [ + AccountMeta::writable(self.mint.key()), + AccountMeta::readonly(self.rent_sysvar.key()), + ]; + + // Instruction data layout: + // - [0]: instruction discriminator (1 byte, u8) + // - [1]: decimals (1 byte, u8) + // - [2..34]: mint_authority (32 bytes, Pubkey) + // - [34]: freeze_authority presence flag (1 byte, u8) + // - [35..67]: freeze_authority (optional, 32 bytes, Pubkey) + let mut instruction_data = [UNINIT_BYTE; 67]; + + // Set discriminator as u8 at offset [0] + write_bytes(&mut instruction_data, &[0]); + // Set decimals as u8 at offset [1] + write_bytes(&mut instruction_data[1..2], &[self.decimals]); + // Set mint_authority as Pubkey at offset [2..34] + write_bytes(&mut instruction_data[2..34], self.mint_authority); + // Set COption & freeze_authority at offset [34..67] + if let Some(freeze_auth) = self.freeze_authority { + write_bytes(&mut instruction_data[34..35], &[1]); + write_bytes(&mut instruction_data[35..], freeze_auth); + } else { + write_bytes(&mut instruction_data[34..35], &[0]); + } + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 67) }, + }; + + invoke_signed(&instruction, &[self.mint, self.rent_sysvar], signers) + } +} diff --git a/programs/token-2022/src/instructions/initialize_mint_2.rs b/programs/token-2022/src/instructions/initialize_mint_2.rs new file mode 100644 index 00000000..7bf9f81c --- /dev/null +++ b/programs/token-2022/src/instructions/initialize_mint_2.rs @@ -0,0 +1,68 @@ +use core::slice::from_raw_parts; + +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + pubkey::Pubkey, + ProgramResult, +}; + +use crate::{write_bytes, UNINIT_BYTE}; + +/// Initialize a new mint. +/// +/// ### Accounts: +/// 0. `[WRITABLE]` Mint account +pub struct InitializeMint2<'a> { + /// Mint Account. + pub mint: &'a AccountInfo, + /// Decimals. + pub decimals: u8, + /// Mint Authority. + pub mint_authority: &'a Pubkey, + /// Freeze Authority. + pub freeze_authority: Option<&'a Pubkey>, +} + +impl InitializeMint2<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // Account metadata + let account_metas: [AccountMeta; 1] = [AccountMeta::writable(self.mint.key())]; + + // Instruction data layout: + // - [0]: instruction discriminator (1 byte, u8) + // - [1]: decimals (1 byte, u8) + // - [2..34]: mint_authority (32 bytes, Pubkey) + // - [34]: freeze_authority presence flag (1 byte, u8) + // - [35..67]: freeze_authority (optional, 32 bytes, Pubkey) + let mut instruction_data = [UNINIT_BYTE; 67]; + + // Set discriminator as u8 at offset [0] + write_bytes(&mut instruction_data, &[20]); + // Set decimals as u8 at offset [1] + write_bytes(&mut instruction_data[1..2], &[self.decimals]); + // Set mint_authority as Pubkey at offset [2..34] + write_bytes(&mut instruction_data[2..34], self.mint_authority); + // Set COption & freeze_authority at offset [34..67] + if let Some(freeze_auth) = self.freeze_authority { + write_bytes(&mut instruction_data[34..35], &[1]); + write_bytes(&mut instruction_data[35..], freeze_auth); + } else { + write_bytes(&mut instruction_data[34..35], &[0]); + } + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 67) }, + }; + + invoke_signed(&instruction, &[self.mint], signers) + } +} diff --git a/programs/token-2022/src/instructions/mint_to.rs b/programs/token-2022/src/instructions/mint_to.rs new file mode 100644 index 00000000..91ce6f90 --- /dev/null +++ b/programs/token-2022/src/instructions/mint_to.rs @@ -0,0 +1,66 @@ +use core::slice::from_raw_parts; + +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + ProgramResult, +}; + +use crate::{write_bytes, UNINIT_BYTE}; + +/// Mints new tokens to an account. +/// +/// ### Accounts: +/// 0. `[WRITE]` The mint. +/// 1. `[WRITE]` The account to mint tokens to. +/// 2. `[SIGNER]` The mint's minting authority. +/// +pub struct MintTo<'a> { + /// Mint Account. + pub mint: &'a AccountInfo, + /// Token Account. + pub account: &'a AccountInfo, + /// Mint Authority + pub mint_authority: &'a AccountInfo, + /// Amount + pub amount: u64, +} + +impl MintTo<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // account metadata + let account_metas: [AccountMeta; 3] = [ + AccountMeta::writable(self.mint.key()), + AccountMeta::writable(self.account.key()), + AccountMeta::readonly_signer(self.mint_authority.key()), + ]; + + // Instruction data layout: + // - [0]: instruction discriminator (1 byte, u8) + // - [1..9]: amount (8 bytes, u64) + let mut instruction_data = [UNINIT_BYTE; 9]; + + // Set discriminator as u8 at offset [0] + write_bytes(&mut instruction_data, &[7]); + // Set amount as u64 at offset [1..9] + write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) }, + }; + + invoke_signed( + &instruction, + &[self.mint, self.account, self.mint_authority], + signers, + ) + } +} diff --git a/programs/token-2022/src/instructions/mint_to_checked.rs b/programs/token-2022/src/instructions/mint_to_checked.rs new file mode 100644 index 00000000..9496932c --- /dev/null +++ b/programs/token-2022/src/instructions/mint_to_checked.rs @@ -0,0 +1,71 @@ +use core::slice::from_raw_parts; + +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + ProgramResult, +}; + +use crate::{write_bytes, UNINIT_BYTE}; + +/// Mints new tokens to an account. +/// +/// ### Accounts: +/// 0. `[WRITE]` The mint. +/// 1. `[WRITE]` The account to mint tokens to. +/// 2. `[SIGNER]` The mint's minting authority. +/// +pub struct MintToChecked<'a> { + /// Mint Account. + pub mint: &'a AccountInfo, + /// Token Account. + pub account: &'a AccountInfo, + /// Mint Authority + pub mint_authority: &'a AccountInfo, + /// Amount + pub amount: u64, + /// Decimals + pub decimals: u8, +} + +impl MintToChecked<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // account metadata + let account_metas: [AccountMeta; 3] = [ + AccountMeta::writable(self.mint.key()), + AccountMeta::writable(self.account.key()), + AccountMeta::readonly_signer(self.mint_authority.key()), + ]; + + // Instruction data layout: + // - [0]: instruction discriminator (1 byte, u8) + // - [1..9]: amount (8 bytes, u64) + // - [9]: decimals (1 byte, u8) + let mut instruction_data = [UNINIT_BYTE; 10]; + + // Set discriminator as u8 at offset [0] + write_bytes(&mut instruction_data, &[14]); + // Set amount as u64 at offset [1..9] + write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); + // Set decimals as u8 at offset [9] + write_bytes(&mut instruction_data[9..], &[self.decimals]); + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) }, + }; + + invoke_signed( + &instruction, + &[self.mint, self.account, self.mint_authority], + signers, + ) + } +} diff --git a/programs/token-2022/src/instructions/mod.rs b/programs/token-2022/src/instructions/mod.rs new file mode 100644 index 00000000..9287f8f9 --- /dev/null +++ b/programs/token-2022/src/instructions/mod.rs @@ -0,0 +1,39 @@ +mod approve; +mod approve_checked; +mod burn; +mod burn_checked; +mod close_account; +mod freeze_account; +mod initialize_account; +mod initialize_account_2; +mod initialize_account_3; +mod initialize_mint; +mod initialize_mint_2; +mod mint_to; +mod mint_to_checked; +mod revoke; +mod set_authority; +mod sync_native; +mod thaw_account; +mod transfer; +mod transfer_checked; + +pub use approve::*; +pub use approve_checked::*; +pub use burn::*; +pub use burn_checked::*; +pub use close_account::*; +pub use freeze_account::*; +pub use initialize_account::*; +pub use initialize_account_2::*; +pub use initialize_account_3::*; +pub use initialize_mint::*; +pub use initialize_mint_2::*; +pub use mint_to::*; +pub use mint_to_checked::*; +pub use revoke::*; +pub use set_authority::*; +pub use sync_native::*; +pub use thaw_account::*; +pub use transfer::*; +pub use transfer_checked::*; diff --git a/programs/token-2022/src/instructions/revoke.rs b/programs/token-2022/src/instructions/revoke.rs new file mode 100644 index 00000000..cbb4c815 --- /dev/null +++ b/programs/token-2022/src/instructions/revoke.rs @@ -0,0 +1,41 @@ +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + ProgramResult, +}; + +/// Revokes the delegate's authority. +/// +/// ### Accounts: +/// 0. `[WRITE]` The source account. +/// 1. `[SIGNER]` The source account owner. +pub struct Revoke<'a> { + /// Source Account. + pub source: &'a AccountInfo, + /// Source Owner Account. + pub authority: &'a AccountInfo, +} + +impl Revoke<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // account metadata + let account_metas: [AccountMeta; 2] = [ + AccountMeta::writable(self.source.key()), + AccountMeta::readonly_signer(self.authority.key()), + ]; + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: &[5], + }; + + invoke_signed(&instruction, &[self.source, self.authority], signers) + } +} diff --git a/programs/token-2022/src/instructions/set_authority.rs b/programs/token-2022/src/instructions/set_authority.rs new file mode 100644 index 00000000..ec39bbe8 --- /dev/null +++ b/programs/token-2022/src/instructions/set_authority.rs @@ -0,0 +1,81 @@ +use core::slice::from_raw_parts; + +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + pubkey::Pubkey, + ProgramResult, +}; + +use crate::{write_bytes, UNINIT_BYTE}; + +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum AuthorityType { + MintTokens = 0, + FreezeAccount = 1, + AccountOwner = 2, + CloseAccount = 3, +} + +/// Sets a new authority of a mint or account. +/// +/// ### Accounts: +/// 0. `[WRITE]` The mint or account to change the authority of. +/// 1. `[SIGNER]` The current authority of the mint or account. +pub struct SetAuthority<'a> { + /// Account (Mint or Token) + pub account: &'a AccountInfo, + + /// Authority of the Account. + pub authority: &'a AccountInfo, + + /// The type of authority to update. + pub authority_type: AuthorityType, + + /// The new authority + pub new_authority: Option<&'a Pubkey>, +} + +impl SetAuthority<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // account metadata + let account_metas: [AccountMeta; 2] = [ + AccountMeta::writable(self.account.key()), + AccountMeta::readonly_signer(self.authority.key()), + ]; + + // instruction data + // - [0]: instruction discriminator (1 byte, u8) + // - [1]: authority_type (1 byte, u8) + // - [2]: new_authority presence flag (1 byte, AuthorityType) + // - [3..35] new_authority (optional, 32 bytes, Pubkey) + let mut instruction_data = [UNINIT_BYTE; 35]; + + // Set discriminator as u8 at offset [0] + write_bytes(&mut instruction_data, &[6]); + // Set authority_type as u8 at offset [1] + write_bytes(&mut instruction_data[1..2], &[self.authority_type as u8]); + // Set new_authority as [u8; 32] at offset [2..35] + if let Some(new_authority) = self.new_authority { + write_bytes(&mut instruction_data[2..3], &[1]); + write_bytes(&mut instruction_data[3..], new_authority); + } else { + write_bytes(&mut instruction_data[2..3], &[0]); + } + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 35) }, + }; + + invoke_signed(&instruction, &[self.account, self.authority], signers) + } +} diff --git a/programs/token-2022/src/instructions/sync_native.rs b/programs/token-2022/src/instructions/sync_native.rs new file mode 100644 index 00000000..b6ea680c --- /dev/null +++ b/programs/token-2022/src/instructions/sync_native.rs @@ -0,0 +1,37 @@ +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + ProgramResult, +}; + +/// Given a native token account updates its amount field based +/// on the account's underlying `lamports`. +/// +/// ### Accounts: +/// 0. `[WRITE]` The native token account to sync with its underlying +/// lamports. +pub struct SyncNative<'a> { + /// Native Token Account + pub native_token: &'a AccountInfo, +} + +impl SyncNative<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // account metadata + let account_metas: [AccountMeta; 1] = [AccountMeta::writable(self.native_token.key())]; + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: &[17], + }; + + invoke_signed(&instruction, &[self.native_token], signers) + } +} diff --git a/programs/token-2022/src/instructions/thaw_account.rs b/programs/token-2022/src/instructions/thaw_account.rs new file mode 100644 index 00000000..1ffacf92 --- /dev/null +++ b/programs/token-2022/src/instructions/thaw_account.rs @@ -0,0 +1,49 @@ +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + ProgramResult, +}; + +/// Thaw a Frozen account using the Mint's freeze_authority +/// +/// ### Accounts: +/// 0. `[WRITE]` The account to thaw. +/// 1. `[]` The token mint. +/// 2. `[SIGNER]` The mint freeze authority. +pub struct ThawAccount<'a> { + /// Token Account to thaw. + pub account: &'a AccountInfo, + /// Mint Account. + pub mint: &'a AccountInfo, + /// Mint Freeze Authority Account + pub freeze_authority: &'a AccountInfo, +} + +impl ThawAccount<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // account metadata + let account_metas: [AccountMeta; 3] = [ + AccountMeta::writable(self.account.key()), + AccountMeta::readonly(self.mint.key()), + AccountMeta::readonly_signer(self.freeze_authority.key()), + ]; + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: &[11], + }; + + invoke_signed( + &instruction, + &[self.account, self.mint, self.freeze_authority], + signers, + ) + } +} diff --git a/programs/token-2022/src/instructions/transfer.rs b/programs/token-2022/src/instructions/transfer.rs new file mode 100644 index 00000000..389b0c23 --- /dev/null +++ b/programs/token-2022/src/instructions/transfer.rs @@ -0,0 +1,61 @@ +use core::slice::from_raw_parts; + +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + ProgramResult, +}; + +use crate::{write_bytes, UNINIT_BYTE}; + +/// Transfer Tokens from one Token Account to another. +/// +/// ### Accounts: +/// 0. `[WRITE]` Sender account +/// 1. `[WRITE]` Recipient account +/// 2. `[SIGNER]` Authority account +pub struct Transfer<'a> { + /// Sender account. + pub from: &'a AccountInfo, + /// Recipient account. + pub to: &'a AccountInfo, + /// Authority account. + pub authority: &'a AccountInfo, + /// Amount of microtokens to transfer. + pub amount: u64, +} + +impl Transfer<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // account metadata + let account_metas: [AccountMeta; 3] = [ + AccountMeta::writable(self.from.key()), + AccountMeta::writable(self.to.key()), + AccountMeta::readonly_signer(self.authority.key()), + ]; + + // Instruction data layout: + // - [0]: instruction discriminator (1 byte, u8) + // - [1..9]: amount (8 bytes, u64) + let mut instruction_data = [UNINIT_BYTE; 9]; + + // Set discriminator as u8 at offset [0] + write_bytes(&mut instruction_data, &[3]); + // Set amount as u64 at offset [1..9] + write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) }, + }; + + invoke_signed(&instruction, &[self.from, self.to, self.authority], signers) + } +} diff --git a/programs/token-2022/src/instructions/transfer_checked.rs b/programs/token-2022/src/instructions/transfer_checked.rs new file mode 100644 index 00000000..f973e95a --- /dev/null +++ b/programs/token-2022/src/instructions/transfer_checked.rs @@ -0,0 +1,70 @@ +use core::slice::from_raw_parts; + +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + program::invoke_signed, + ProgramResult, +}; + +use crate::{write_bytes, UNINIT_BYTE}; + +/// Transfer Tokens from one Token Account to another. +/// +/// ### Accounts: +/// 0. `[WRITE]` The source account. +/// 1. `[]` The token mint. +/// 2. `[WRITE]` The destination account. +/// 3. `[SIGNER]` The source account's owner/delegate. +pub struct TransferChecked<'a> { + /// Sender account. + pub from: &'a AccountInfo, + /// Mint Account + pub mint: &'a AccountInfo, + /// Recipient account. + pub to: &'a AccountInfo, + /// Authority account. + pub authority: &'a AccountInfo, + /// Amount of microtokens to transfer. + pub amount: u64, + /// Decimal for the Token + pub decimals: u8, +} + +impl TransferChecked<'_> { + #[inline(always)] + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + // account metadata + let account_metas: [AccountMeta; 4] = [ + AccountMeta::writable(self.from.key()), + AccountMeta::readonly(self.mint.key()), + AccountMeta::writable(self.to.key()), + AccountMeta::readonly_signer(self.authority.key()), + ]; + + // Instruction data layout: + // - [0]: instruction discriminator (1 byte, u8) + // - [1..9]: amount (8 bytes, u64) + // - [9]: decimals (1 byte, u8) + let mut instruction_data = [UNINIT_BYTE; 10]; + + // Set discriminator as u8 at offset [0] + write_bytes(&mut instruction_data, &[12]); + // Set amount as u64 at offset [1..9] + write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); + // Set decimals as u8 at offset [9] + write_bytes(&mut instruction_data[9..], &[self.decimals]); + + let instruction = Instruction { + program_id: &crate::ID, + accounts: &account_metas, + data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) }, + }; + + invoke_signed(&instruction, &[self.from, self.to, self.authority], signers) + } +} diff --git a/programs/token-2022/src/lib.rs b/programs/token-2022/src/lib.rs new file mode 100644 index 00000000..62e593b6 --- /dev/null +++ b/programs/token-2022/src/lib.rs @@ -0,0 +1,17 @@ +#![no_std] + +pub mod instructions; +pub mod state; + +pinocchio_pubkey::declare_id!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"); + +use core::mem::MaybeUninit; + +const UNINIT_BYTE: MaybeUninit = MaybeUninit::::uninit(); + +#[inline(always)] +fn write_bytes(destination: &mut [MaybeUninit], source: &[u8]) { + for (d, s) in destination.iter_mut().zip(source.iter()) { + d.write(*s); + } +} diff --git a/programs/token-2022/src/state/account_state.rs b/programs/token-2022/src/state/account_state.rs new file mode 100644 index 00000000..5380c96f --- /dev/null +++ b/programs/token-2022/src/state/account_state.rs @@ -0,0 +1,36 @@ +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum AccountState { + /// Account is not yet initialized + Uninitialized, + + /// Account is initialized; the account owner and/or delegate may perform + /// permitted operations on this account + Initialized, + + /// Account has been frozen by the mint freeze authority. Neither the + /// account owner nor the delegate are able to perform operations on + /// this account. + Frozen, +} + +impl From for AccountState { + fn from(value: u8) -> Self { + match value { + 0 => AccountState::Uninitialized, + 1 => AccountState::Initialized, + 2 => AccountState::Frozen, + _ => panic!("invalid account state value: {value}"), + } + } +} + +impl From for u8 { + fn from(value: AccountState) -> Self { + match value { + AccountState::Uninitialized => 0, + AccountState::Initialized => 1, + AccountState::Frozen => 2, + } + } +} diff --git a/programs/token-2022/src/state/mint.rs b/programs/token-2022/src/state/mint.rs new file mode 100644 index 00000000..25c1e6ee --- /dev/null +++ b/programs/token-2022/src/state/mint.rs @@ -0,0 +1,145 @@ +use pinocchio::{ + account_info::{AccountInfo, Ref}, + program_error::ProgramError, + pubkey::Pubkey, +}; + +use crate::ID; + +/// Mint data. +#[repr(C)] +pub struct Mint { + /// Indicates whether the mint authority is present or not. + mint_authority_flag: [u8; 4], + + /// Optional authority used to mint new tokens. The mint authority may only + /// be provided during mint creation. If no mint authority is present + /// then the mint has a fixed supply and no further tokens may be + /// minted. + mint_authority: Pubkey, + + /// Total supply of tokens. + supply: [u8; 8], + + /// Number of base 10 digits to the right of the decimal place. + decimals: u8, + + /// Is `true` if this structure has been initialized. + is_initialized: u8, + + /// Indicates whether the freeze authority is present or not. + freeze_authority_flag: [u8; 4], + + /// Optional authority to freeze token accounts. + freeze_authority: Pubkey, +} + +impl Mint { + /// The length of the `Mint` account data. + pub const LEN: usize = core::mem::size_of::(); + + /// Return a `Mint` from the given account info. + /// + /// This method performs owner and length validation on `AccountInfo`, safe borrowing + /// the account data. + #[inline] + pub fn from_account_info(account_info: &AccountInfo) -> Result, ProgramError> { + if account_info.data_len() != Self::LEN { + return Err(ProgramError::InvalidAccountData); + } + if account_info.owner() != &ID { + return Err(ProgramError::InvalidAccountOwner); + } + Ok(Ref::map(account_info.try_borrow_data()?, |data| unsafe { + Self::from_bytes(data) + })) + } + + /// Return a `Mint` from the given account info. + /// + /// This method performs owner and length validation on `AccountInfo`, but does not + /// perform the borrow check. + /// + /// # Safety + /// + /// The caller must ensure that it is safe to borrow the account data – e.g., there are + /// no mutable borrows of the account data. + #[inline] + pub unsafe fn from_account_info_unchecked( + account_info: &AccountInfo, + ) -> Result<&Self, ProgramError> { + if account_info.data_len() != Self::LEN { + return Err(ProgramError::InvalidAccountData); + } + if account_info.owner() != &ID { + return Err(ProgramError::InvalidAccountOwner); + } + Ok(Self::from_bytes(account_info.borrow_data_unchecked())) + } + + /// Return a `Mint` from the given bytes. + /// + /// # Safety + /// + /// The caller must ensure that `bytes` contains a valid representation of `Mint`. + #[inline(always)] + pub unsafe fn from_bytes(bytes: &[u8]) -> &Self { + &*(bytes.as_ptr() as *const Mint) + } + + #[inline(always)] + pub fn has_mint_authority(&self) -> bool { + self.mint_authority_flag[0] == 1 + } + + pub fn mint_authority(&self) -> Option<&Pubkey> { + if self.has_mint_authority() { + Some(self.mint_authority_unchecked()) + } else { + None + } + } + + /// Return the mint authority. + /// + /// This method should be used when the caller knows that the mint will have a mint + /// authority set since it skips the `Option` check. + #[inline(always)] + pub fn mint_authority_unchecked(&self) -> &Pubkey { + &self.mint_authority + } + + pub fn supply(&self) -> u64 { + unsafe { core::ptr::read_unaligned(self.supply.as_ptr() as *const u64) } + } + + pub fn decimals(&self) -> u8 { + self.decimals + } + + pub fn is_initialized(&self) -> bool { + self.is_initialized == 1 + } + + #[inline(always)] + pub fn has_freeze_authority(&self) -> bool { + self.freeze_authority_flag[0] == 1 + } + + pub fn freeze_authority(&self) -> Option<&Pubkey> { + if self.has_freeze_authority() { + Some(self.freeze_authority_unchecked()) + } else { + None + } + } + + /// Return the freeze authority. + /// + /// This method should be used when the caller knows that the mint will have a freeze + /// authority set since it skips the `Option` check. + #[inline(always)] + pub fn freeze_authority_unchecked(&self) -> &Pubkey { + &self.freeze_authority + } +} diff --git a/programs/token-2022/src/state/mod.rs b/programs/token-2022/src/state/mod.rs new file mode 100644 index 00000000..f73c8320 --- /dev/null +++ b/programs/token-2022/src/state/mod.rs @@ -0,0 +1,7 @@ +mod account_state; +mod mint; +mod token; + +pub use account_state::*; +pub use mint::*; +pub use token::*; diff --git a/programs/token-2022/src/state/token.rs b/programs/token-2022/src/state/token.rs new file mode 100644 index 00000000..2250a217 --- /dev/null +++ b/programs/token-2022/src/state/token.rs @@ -0,0 +1,198 @@ +use super::AccountState; +use pinocchio::{ + account_info::{AccountInfo, Ref}, + program_error::ProgramError, + pubkey::Pubkey, +}; + +use crate::ID; + +/// Token account data. +#[repr(C)] +pub struct TokenAccount { + /// The mint associated with this account + mint: Pubkey, + + /// The owner of this account. + owner: Pubkey, + + /// The amount of tokens this account holds. + amount: [u8; 8], + + /// Indicates whether the delegate is present or not. + delegate_flag: [u8; 4], + + /// If `delegate` is `Some` then `delegated_amount` represents + /// the amount authorized by the delegate. + delegate: Pubkey, + + /// The account's state. + state: u8, + + /// Indicates whether this account represents a native token or not. + is_native: [u8; 4], + + /// If is_native.is_some, this is a native token, and the value logs the + /// rent-exempt reserve. An Account is required to be rent-exempt, so + /// the value is used by the Processor to ensure that wrapped SOL + /// accounts do not drop below this threshold. + native_amount: [u8; 8], + + /// The amount delegated. + delegated_amount: [u8; 8], + + /// Indicates whether the close authority is present or not. + close_authority_flag: [u8; 4], + + /// Optional authority to close the account. + close_authority: Pubkey, +} + +impl TokenAccount { + pub const LEN: usize = core::mem::size_of::(); + + /// Return a `TokenAccount` from the given account info. + /// + /// This method performs owner and length validation on `AccountInfo`, safe borrowing + /// the account data. + #[inline] + pub fn from_account_info( + account_info: &AccountInfo, + ) -> Result, ProgramError> { + if account_info.data_len() != Self::LEN { + return Err(ProgramError::InvalidAccountData); + } + if account_info.owner() != &ID { + return Err(ProgramError::InvalidAccountData); + } + Ok(Ref::map(account_info.try_borrow_data()?, |data| unsafe { + Self::from_bytes(data) + })) + } + + /// Return a `TokenAccount` from the given account info. + /// + /// This method performs owner and length validation on `AccountInfo`, but does not + /// perform the borrow check. + /// + /// # Safety + /// + /// The caller must ensure that it is safe to borrow the account data – e.g., there are + /// no mutable borrows of the account data. + #[inline] + pub unsafe fn from_account_info_unchecked( + account_info: &AccountInfo, + ) -> Result<&TokenAccount, ProgramError> { + if account_info.data_len() != Self::LEN { + return Err(ProgramError::InvalidAccountData); + } + if account_info.owner() != &ID { + return Err(ProgramError::InvalidAccountData); + } + Ok(Self::from_bytes(account_info.borrow_data_unchecked())) + } + + /// Return a `TokenAccount` from the given bytes. + /// + /// # Safety + /// + /// The caller must ensure that `bytes` contains a valid representation of `TokenAccount`. + #[inline(always)] + pub unsafe fn from_bytes(bytes: &[u8]) -> &Self { + &*(bytes.as_ptr() as *const TokenAccount) + } + + pub fn mint(&self) -> &Pubkey { + &self.mint + } + + pub fn owner(&self) -> &Pubkey { + &self.owner + } + + pub fn amount(&self) -> u64 { + unsafe { core::ptr::read_unaligned(self.amount.as_ptr() as *const u64) } + } + + #[inline(always)] + pub fn has_delegate(&self) -> bool { + self.delegate_flag[0] == 1 + } + + pub fn delegate(&self) -> Option<&Pubkey> { + if self.has_delegate() { + Some(self.delegate_unchecked()) + } else { + None + } + } + + /// Use this when you know the account will have a delegate and want to skip the `Option` check. + #[inline(always)] + pub fn delegate_unchecked(&self) -> &Pubkey { + &self.delegate + } + + #[inline(always)] + pub fn state(&self) -> AccountState { + self.state.into() + } + + #[inline(always)] + pub fn is_native(&self) -> bool { + self.is_native[0] == 1 + } + + pub fn native_amount(&self) -> Option { + if self.is_native() { + Some(self.native_amount_unchecked()) + } else { + None + } + } + + /// Return the native amount. + /// + /// This method should be used when the caller knows that the token is native since it + /// skips the `Option` check. + #[inline(always)] + pub fn native_amount_unchecked(&self) -> u64 { + unsafe { core::ptr::read_unaligned(self.native_amount.as_ptr() as *const u64) } + } + + pub fn delegated_amount(&self) -> u64 { + unsafe { core::ptr::read_unaligned(self.delegated_amount.as_ptr() as *const u64) } + } + + #[inline(always)] + pub fn has_close_authority(&self) -> bool { + self.close_authority_flag[0] == 1 + } + + pub fn close_authority(&self) -> Option<&Pubkey> { + if self.has_close_authority() { + Some(self.close_authority_unchecked()) + } else { + None + } + } + + /// Return the close authority. + /// + /// This method should be used when the caller knows that the token will have a close + /// authority set since it skips the `Option` check. + #[inline(always)] + pub fn close_authority_unchecked(&self) -> &Pubkey { + &self.close_authority + } + + #[inline(always)] + pub fn is_initialized(&self) -> bool { + self.state != AccountState::Uninitialized as u8 + } + + #[inline(always)] + pub fn is_frozen(&self) -> bool { + self.state == AccountState::Frozen as u8 + } +}