Ver código fonte

interface: Split out instructions, state, errors from program (#76)

Everything needed by Agave is now split into the spl-token-interface crate. On-chain programs can also use this as an rlib.
Peter Keay 2 meses atrás
pai
commit
e1400276bb

+ 41 - 0
.github/actions/setup/action.yml

@@ -13,6 +13,9 @@ inputs:
   clippy:
     description: Install Clippy if `true`. Defaults to `false`.
     required: false
+  purge:
+    description: Purge unused directories if `true`. Defaults to `false`.
+    required: false
   rustfmt:
     description: Install Rustfmt if `true`. Defaults to `false`.
     required: false
@@ -32,6 +35,44 @@ runs:
         node-version: 20
         cache: 'pnpm'
 
+    - name: Purge unused ubuntu runner directories
+      if: ${{ inputs.purge == 'true' }}
+      shell: bash
+      run: |
+        # If there are still disk space issues, try to add more packages from
+        # https://github.com/jlumbroso/free-disk-space
+        sudo rm -rf /usr/share/dotnet
+        sudo rm -rf /usr/share/swift
+        sudo rm -rf /usr/share/mysql
+        sudo rm -rf /usr/share/az_*
+        sudo rm -rf /usr/share/postgresql-common
+        sudo rm -rf /opt/ghc
+        sudo rm -rf /usr/local/.ghcup
+        sudo rm -rf /opt/az
+        sudo rm -rf /opt/pipx
+        sudo rm -rf /opt/microsoft
+        sudo rm -rf /opt/google
+        sudo rm -rf /opt/hostedtoolcache
+        sudo rm -rf /usr/local/lib/android
+        sudo rm -rf /usr/local/lib/heroku
+        sudo rm -rf /imagegeneration
+        sudo rm -rf "$AGENT_TOOLSDIRECTORY"
+        sudo docker image prune --all --force
+        sudo docker system prune -af
+        # Clear additional caches that might be large
+        sudo rm -rf /tmp/*
+        sudo rm -rf /var/tmp/*
+        # This packages aren't that big, ~500MB total
+        #sudo apt-get remove -y '^php.*' --fix-missing
+        #sudo apt-get remove -y '^dotnet-.*' --fix-missing
+        #sudo apt-get remove -y '^mongodb-.*' --fix-missing
+        #sudo apt-get remove -y '^mysql-.*' --fix-missing
+        sudo apt-get remove -y '^aspnetcore-.*' azure-cli google-chrome-stable firefox powershell mono-devel libgl1-mesa-dri google-cloud-cli --fix-missing
+        sudo apt-get autoremove -y
+        sudo apt-get clean
+        #sudo swapoff -a
+        #sudo rm -f /mnt/swapfile
+        
     - name: Install Dependencies
       run: pnpm install --frozen-lockfile
       shell: bash

+ 23 - 0
.github/workflows/main.yml

@@ -99,6 +99,28 @@ jobs:
       - name: Lint
         run: pnpm p-token:lint
 
+  format_and_lint_interface:
+    name: Format, Lint & Test Interface
+    runs-on: ubuntu-latest
+    steps:
+      - name: Git Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Environment
+        uses: ./.github/actions/setup
+        with:
+          clippy: true
+          rustfmt: true
+
+      - name: Format
+        run: pnpm interface:format
+
+      - name: Lint
+        run: pnpm interface:lint
+
+      - name: Lint
+        run: pnpm interface:test
+
   audit_rust:
     name: Audit Rust
     runs-on: ubuntu-latest
@@ -294,6 +316,7 @@ jobs:
         with:
           cargo-cache-key: cargo-test-ptoken
           solana: true
+          purge: true
 
       - name: Restore Program Builds
         uses: actions/cache/restore@v4

+ 41 - 0
interface/Cargo.toml

@@ -0,0 +1,41 @@
+[package]
+name = "spl-token-interface"
+version = "1.0.0"
+description = "Solana Program Library Token Interface"
+documentation = "https://docs.rs/spl-token-interface"
+readme = "README.md"
+authors = { workspace = true }
+repository = { workspace = true }
+license = { workspace = true }
+edition = { workspace = true }
+
+[dependencies]
+arrayref = "0.3.9"
+bytemuck = "1.20.0"
+num-derive = "0.4"
+num_enum = "0.7.4"
+num-traits = "0.2"
+solana-instruction = "2.3.0"
+solana-program-error = "2.2.2"
+solana-program-option = "2.2.1"
+solana-program-pack = "2.2.1"
+solana-pubkey = { version = "2.4.0", features = ["bytemuck"] }
+solana-sdk-ids = "2.2.1"
+thiserror = "2.0"
+
+[dev-dependencies]
+proptest = "1.5"
+strum = "0.24"
+strum_macros = "0.24"
+
+[lib]
+crate-type = ["lib"]
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[lints]
+workspace = true
+
+[package.metadata.solana]
+program-id = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"

+ 14 - 0
interface/README.md

@@ -0,0 +1,14 @@
+# Token Interface
+
+A token program interface on the Solana blockchain, usable for fungible and
+non-fungible tokens.
+
+This crate provides an interface that third parties can utilize to create and
+use their tokens.
+
+Full documentation is available at [https://www.solana-program.com/docs/token](https://www.solana-program.com/docs/token)
+
+## Audit
+
+The audit repository [README](https://github.com/solana-labs/solana-program-library#audits)
+contains information about program audits.

+ 166 - 0
interface/src/error.rs

@@ -0,0 +1,166 @@
+//! Error types
+
+use {
+    num_derive::FromPrimitive,
+    solana_program_error::{ProgramError, ToStr},
+    thiserror::Error,
+};
+
+/// Errors that may be returned by the Token program.
+#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
+#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
+pub enum TokenError {
+    // 0
+    /// Lamport balance below rent-exempt threshold.
+    #[error("Lamport balance below rent-exempt threshold")]
+    NotRentExempt,
+    /// Insufficient funds for the operation requested.
+    #[error("Insufficient funds")]
+    InsufficientFunds,
+    /// Invalid Mint.
+    #[error("Invalid Mint")]
+    InvalidMint,
+    /// Account not associated with this Mint.
+    #[error("Account not associated with this Mint")]
+    MintMismatch,
+    /// Owner does not match.
+    #[error("Owner does not match")]
+    OwnerMismatch,
+
+    // 5
+    /// This token's supply is fixed and new tokens cannot be minted.
+    #[error("Fixed supply")]
+    FixedSupply,
+    /// The account cannot be initialized because it is already being used.
+    #[error("Already in use")]
+    AlreadyInUse,
+    /// Invalid number of provided signers.
+    #[error("Invalid number of provided signers")]
+    InvalidNumberOfProvidedSigners,
+    /// Invalid number of required signers.
+    #[error("Invalid number of required signers")]
+    InvalidNumberOfRequiredSigners,
+    /// State is uninitialized.
+    #[error("State is uninitialized")]
+    UninitializedState,
+
+    // 10
+    /// Instruction does not support native tokens
+    #[error("Instruction does not support native tokens")]
+    NativeNotSupported,
+    /// Non-native account can only be closed if its balance is zero
+    #[error("Non-native account can only be closed if its balance is zero")]
+    NonNativeHasBalance,
+    /// Invalid instruction
+    #[error("Invalid instruction")]
+    InvalidInstruction,
+    /// State is invalid for requested operation.
+    #[error("State is invalid for requested operation")]
+    InvalidState,
+    /// Operation overflowed
+    #[error("Operation overflowed")]
+    Overflow,
+
+    // 15
+    /// Account does not support specified authority type.
+    #[error("Account does not support specified authority type")]
+    AuthorityTypeNotSupported,
+    /// This token mint cannot freeze accounts.
+    #[error("This token mint cannot freeze accounts")]
+    MintCannotFreeze,
+    /// Account is frozen; all account operations will fail
+    #[error("Account is frozen")]
+    AccountFrozen,
+    /// Mint decimals mismatch between the client and mint
+    #[error("The provided decimals value different from the Mint decimals")]
+    MintDecimalsMismatch,
+    /// Instruction does not support non-native tokens
+    #[error("Instruction does not support non-native tokens")]
+    NonNativeNotSupported,
+}
+impl From<TokenError> for ProgramError {
+    fn from(e: TokenError) -> Self {
+        ProgramError::Custom(e as u32)
+    }
+}
+
+impl TryFrom<u32> for TokenError {
+    type Error = ProgramError;
+    fn try_from(error: u32) -> Result<Self, Self::Error> {
+        match error {
+            0 => Ok(TokenError::NotRentExempt),
+            1 => Ok(TokenError::InsufficientFunds),
+            2 => Ok(TokenError::InvalidMint),
+            3 => Ok(TokenError::MintMismatch),
+            4 => Ok(TokenError::OwnerMismatch),
+            5 => Ok(TokenError::FixedSupply),
+            6 => Ok(TokenError::AlreadyInUse),
+            7 => Ok(TokenError::InvalidNumberOfProvidedSigners),
+            8 => Ok(TokenError::InvalidNumberOfRequiredSigners),
+            9 => Ok(TokenError::UninitializedState),
+            10 => Ok(TokenError::NativeNotSupported),
+            11 => Ok(TokenError::NonNativeHasBalance),
+            12 => Ok(TokenError::InvalidInstruction),
+            13 => Ok(TokenError::InvalidState),
+            14 => Ok(TokenError::Overflow),
+            15 => Ok(TokenError::AuthorityTypeNotSupported),
+            16 => Ok(TokenError::MintCannotFreeze),
+            17 => Ok(TokenError::AccountFrozen),
+            18 => Ok(TokenError::MintDecimalsMismatch),
+            19 => Ok(TokenError::NonNativeNotSupported),
+            _ => Err(ProgramError::InvalidArgument),
+        }
+    }
+}
+
+impl ToStr for TokenError {
+    fn to_str<E>(&self) -> &'static str {
+        match self {
+            TokenError::NotRentExempt => "Error: Lamport balance below rent-exempt threshold",
+            TokenError::InsufficientFunds => "Error: insufficient funds",
+            TokenError::InvalidMint => "Error: Invalid Mint",
+            TokenError::MintMismatch => "Error: Account not associated with this Mint",
+            TokenError::OwnerMismatch => "Error: owner does not match",
+            TokenError::FixedSupply => "Error: the total supply of this token is fixed",
+            TokenError::AlreadyInUse => "Error: account or token already in use",
+            TokenError::InvalidNumberOfProvidedSigners => {
+                "Error: Invalid number of provided signers"
+            }
+            TokenError::InvalidNumberOfRequiredSigners => {
+                "Error: Invalid number of required signers"
+            }
+            TokenError::UninitializedState => "Error: State is uninitialized",
+            TokenError::NativeNotSupported => "Error: Instruction does not support native tokens",
+            TokenError::NonNativeHasBalance => {
+                "Error: Non-native account can only be closed if its balance is zero"
+            }
+            TokenError::InvalidInstruction => "Error: Invalid instruction",
+            TokenError::InvalidState => "Error: Invalid account state for operation",
+            TokenError::Overflow => "Error: Operation overflowed",
+            TokenError::AuthorityTypeNotSupported => {
+                "Error: Account does not support specified authority type"
+            }
+            TokenError::MintCannotFreeze => "Error: This token mint cannot freeze accounts",
+            TokenError::AccountFrozen => "Error: Account is frozen",
+            TokenError::MintDecimalsMismatch => "Error: decimals different from the Mint decimals",
+            TokenError::NonNativeNotSupported => {
+                "Error: Instruction does not support non-native tokens"
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use {super::*, strum::IntoEnumIterator};
+    #[test]
+    fn test_parse_error_from_primitive_exhaustive() {
+        for variant in TokenError::iter() {
+            let variant_u32 = variant as u32;
+            assert_eq!(
+                TokenError::from_repr(variant_u32 as usize).unwrap(),
+                TokenError::try_from(variant_u32).unwrap()
+            );
+        }
+    }
+}

+ 1703 - 0
interface/src/instruction.rs

@@ -0,0 +1,1703 @@
+//! Instruction types
+
+use {
+    crate::{check_program_account, error::TokenError},
+    solana_instruction::{AccountMeta, Instruction},
+    solana_program_error::ProgramError,
+    solana_program_option::COption,
+    solana_pubkey::Pubkey,
+    solana_sdk_ids::sysvar,
+    std::{convert::TryInto, mem::size_of},
+};
+
+/// Minimum number of multisignature signers (min N)
+pub const MIN_SIGNERS: usize = 1;
+/// Maximum number of multisignature signers (max N)
+pub const MAX_SIGNERS: usize = 11;
+/// Serialized length of a `u64`, for unpacking
+const U64_BYTES: usize = 8;
+
+/// Instructions supported by the token program.
+#[repr(C)]
+#[derive(Clone, Debug, PartialEq)]
+pub enum TokenInstruction<'a> {
+    /// Initializes a new mint and optionally deposits all the newly minted
+    /// tokens in an account.
+    ///
+    /// The `InitializeMint` instruction requires no signers and MUST be
+    /// included within the same Transaction as the system program's
+    /// `CreateAccount` instruction that creates the account being initialized.
+    /// Otherwise another party can acquire ownership of the uninitialized
+    /// account.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   0. `[writable]` The mint to initialize.
+    ///   1. `[]` Rent sysvar
+    InitializeMint {
+        /// Number of base 10 digits to the right of the decimal place.
+        decimals: u8,
+        /// The authority/multisignature to mint tokens.
+        mint_authority: Pubkey,
+        /// The freeze authority/multisignature of the mint.
+        freeze_authority: COption<Pubkey>,
+    },
+    /// Initializes a new account to hold tokens.  If this account is associated
+    /// with the native mint then the token balance of the initialized account
+    /// will be equal to the amount of SOL in the account. If this account is
+    /// associated with another mint, that mint must be initialized before this
+    /// command can succeed.
+    ///
+    /// The `InitializeAccount` instruction requires no signers and MUST be
+    /// included within the same Transaction as the system program's
+    /// `CreateAccount` instruction that creates the account being initialized.
+    /// Otherwise another party can acquire ownership of the uninitialized
+    /// account.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   0. `[writable]`  The account to initialize.
+    ///   1. `[]` The mint this account will be associated with.
+    ///   2. `[]` The new account's owner/multisignature.
+    ///   3. `[]` Rent sysvar
+    InitializeAccount,
+    /// Initializes a multisignature account with N provided signers.
+    ///
+    /// Multisignature accounts can used in place of any single owner/delegate
+    /// accounts in any token instruction that require an owner/delegate to be
+    /// present.  The variant field represents the number of signers (M)
+    /// required to validate this multisignature account.
+    ///
+    /// The `InitializeMultisig` instruction requires no signers and MUST be
+    /// included within the same Transaction as the system program's
+    /// `CreateAccount` instruction that creates the account being initialized.
+    /// Otherwise another party can acquire ownership of the uninitialized
+    /// account.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   0. `[writable]` The multisignature account to initialize.
+    ///   1. `[]` Rent sysvar
+    ///   2. ..`2+N`. `[]` The signer accounts, must equal to N where `1 <= N <=
+    ///      11`.
+    InitializeMultisig {
+        /// The number of signers (M) required to validate this multisignature
+        /// account.
+        m: u8,
+    },
+    /// Transfers tokens from one account to another either directly or via a
+    /// delegate.  If this account is associated with the native mint then equal
+    /// amounts of SOL and Tokens will be transferred to the destination
+    /// account.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single owner/delegate
+    ///   0. `[writable]` The source account.
+    ///   1. `[writable]` The destination account.
+    ///   2. `[signer]` The source account's owner/delegate.
+    ///
+    ///   * Multisignature owner/delegate
+    ///   0. `[writable]` The source account.
+    ///   1. `[writable]` The destination account.
+    ///   2. `[]` The source account's multisignature owner/delegate.
+    ///   3. ..`3+M` `[signer]` M signer accounts.
+    Transfer {
+        /// The amount of tokens to transfer.
+        amount: u64,
+    },
+    /// Approves a delegate.  A delegate is given the authority over tokens on
+    /// behalf of the source account's owner.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single owner
+    ///   0. `[writable]` The source account.
+    ///   1. `[]` The delegate.
+    ///   2. `[signer]` The source account owner.
+    ///
+    ///   * Multisignature owner
+    ///   0. `[writable]` The source account.
+    ///   1. `[]` The delegate.
+    ///   2. `[]` The source account's multisignature owner.
+    ///   3. ..`3+M` `[signer]` M signer accounts
+    Approve {
+        /// The amount of tokens the delegate is approved for.
+        amount: u64,
+    },
+    /// Revokes the delegate's authority.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single owner
+    ///   0. `[writable]` The source account.
+    ///   1. `[signer]` The source account owner.
+    ///
+    ///   * Multisignature owner
+    ///   0. `[writable]` The source account.
+    ///   1. `[]` The source account's multisignature owner.
+    ///   2. ..`2+M` `[signer]` M signer accounts
+    Revoke,
+    /// Sets a new authority of a mint or account.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single authority
+    ///   0. `[writable]` The mint or account to change the authority of.
+    ///   1. `[signer]` The current authority of the mint or account.
+    ///
+    ///   * Multisignature authority
+    ///   0. `[writable]` The mint or account to change the authority of.
+    ///   1. `[]` The mint's or account's current multisignature authority.
+    ///   2. ..`2+M` `[signer]` M signer accounts
+    SetAuthority {
+        /// The type of authority to update.
+        authority_type: AuthorityType,
+        /// The new authority
+        new_authority: COption<Pubkey>,
+    },
+    /// Mints new tokens to an account.  The native mint does not support
+    /// minting.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single authority
+    ///   0. `[writable]` The mint.
+    ///   1. `[writable]` The account to mint tokens to.
+    ///   2. `[signer]` The mint's minting authority.
+    ///
+    ///   * Multisignature authority
+    ///   0. `[writable]` The mint.
+    ///   1. `[writable]` The account to mint tokens to.
+    ///   2. `[]` The mint's multisignature mint-tokens authority.
+    ///   3. ..`3+M` `[signer]` M signer accounts.
+    MintTo {
+        /// The amount of new tokens to mint.
+        amount: u64,
+    },
+    /// Burns tokens by removing them from an account.  `Burn` does not support
+    /// accounts associated with the native mint, use `CloseAccount` instead.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single owner/delegate
+    ///   0. `[writable]` The account to burn from.
+    ///   1. `[writable]` The token mint.
+    ///   2. `[signer]` The account's owner/delegate.
+    ///
+    ///   * Multisignature owner/delegate
+    ///   0. `[writable]` The account to burn from.
+    ///   1. `[writable]` The token mint.
+    ///   2. `[]` The account's multisignature owner/delegate.
+    ///   3. ..`3+M` `[signer]` M signer accounts.
+    Burn {
+        /// The amount of tokens to burn.
+        amount: u64,
+    },
+    /// Close an account by transferring all its SOL to the destination account.
+    /// Non-native accounts may only be closed if its token amount is zero.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single owner
+    ///   0. `[writable]` The account to close.
+    ///   1. `[writable]` The destination account.
+    ///   2. `[signer]` The account's owner.
+    ///
+    ///   * Multisignature owner
+    ///   0. `[writable]` The account to close.
+    ///   1. `[writable]` The destination account.
+    ///   2. `[]` The account's multisignature owner.
+    ///   3. ..`3+M` `[signer]` M signer accounts.
+    CloseAccount,
+    /// Freeze an Initialized account using the Mint's `freeze_authority` (if
+    /// set).
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single owner
+    ///   0. `[writable]` The account to freeze.
+    ///   1. `[]` The token mint.
+    ///   2. `[signer]` The mint freeze authority.
+    ///
+    ///   * Multisignature owner
+    ///   0. `[writable]` The account to freeze.
+    ///   1. `[]` The token mint.
+    ///   2. `[]` The mint's multisignature freeze authority.
+    ///   3. ..`3+M` `[signer]` M signer accounts.
+    FreezeAccount,
+    /// Thaw a Frozen account using the Mint's `freeze_authority` (if set).
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single owner
+    ///   0. `[writable]` The account to freeze.
+    ///   1. `[]` The token mint.
+    ///   2. `[signer]` The mint freeze authority.
+    ///
+    ///   * Multisignature owner
+    ///   0. `[writable]` The account to freeze.
+    ///   1. `[]` The token mint.
+    ///   2. `[]` The mint's multisignature freeze authority.
+    ///   3. ..`3+M` `[signer]` M signer accounts.
+    ThawAccount,
+
+    /// Transfers tokens from one account to another either directly or via a
+    /// delegate.  If this account is associated with the native mint then equal
+    /// amounts of SOL and Tokens will be transferred to the destination
+    /// account.
+    ///
+    /// This instruction differs from Transfer in that the token mint and
+    /// decimals value is checked by the caller.  This may be useful when
+    /// creating transactions offline or within a hardware wallet.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single owner/delegate
+    ///   0. `[writable]` The source account.
+    ///   1. `[]` The token mint.
+    ///   2. `[writable]` The destination account.
+    ///   3. `[signer]` The source account's owner/delegate.
+    ///
+    ///   * Multisignature owner/delegate
+    ///   0. `[writable]` The source account.
+    ///   1. `[]` The token mint.
+    ///   2. `[writable]` The destination account.
+    ///   3. `[]` The source account's multisignature owner/delegate.
+    ///   4. ..`4+M` `[signer]` M signer accounts.
+    TransferChecked {
+        /// The amount of tokens to transfer.
+        amount: u64,
+        /// Expected number of base 10 digits to the right of the decimal place.
+        decimals: u8,
+    },
+    /// Approves a delegate.  A delegate is given the authority over tokens on
+    /// behalf of the source account's owner.
+    ///
+    /// This instruction differs from Approve in that the token mint and
+    /// decimals value is checked by the caller.  This may be useful when
+    /// creating transactions offline or within a hardware wallet.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single owner
+    ///   0. `[writable]` The source account.
+    ///   1. `[]` The token mint.
+    ///   2. `[]` The delegate.
+    ///   3. `[signer]` The source account owner.
+    ///
+    ///   * Multisignature owner
+    ///   0. `[writable]` The source account.
+    ///   1. `[]` The token mint.
+    ///   2. `[]` The delegate.
+    ///   3. `[]` The source account's multisignature owner.
+    ///   4. ..`4+M` `[signer]` M signer accounts
+    ApproveChecked {
+        /// The amount of tokens the delegate is approved for.
+        amount: u64,
+        /// Expected number of base 10 digits to the right of the decimal place.
+        decimals: u8,
+    },
+    /// Mints new tokens to an account.  The native mint does not support
+    /// minting.
+    ///
+    /// This instruction differs from `MintTo` in that the decimals value is
+    /// checked by the caller. This may be useful when creating transactions
+    /// offline or within a hardware wallet.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single authority
+    ///   0. `[writable]` The mint.
+    ///   1. `[writable]` The account to mint tokens to.
+    ///   2. `[signer]` The mint's minting authority.
+    ///
+    ///   * Multisignature authority
+    ///   0. `[writable]` The mint.
+    ///   1. `[writable]` The account to mint tokens to.
+    ///   2. `[]` The mint's multisignature mint-tokens authority.
+    ///   3. ..`3+M` `[signer]` M signer accounts.
+    MintToChecked {
+        /// The amount of new tokens to mint.
+        amount: u64,
+        /// Expected number of base 10 digits to the right of the decimal place.
+        decimals: u8,
+    },
+    /// Burns tokens by removing them from an account.  `BurnChecked` does not
+    /// support accounts associated with the native mint, use `CloseAccount`
+    /// instead.
+    ///
+    /// This instruction differs from Burn in that the decimals value is checked
+    /// by the caller. This may be useful when creating transactions offline or
+    /// within a hardware wallet.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   * Single owner/delegate
+    ///   0. `[writable]` The account to burn from.
+    ///   1. `[writable]` The token mint.
+    ///   2. `[signer]` The account's owner/delegate.
+    ///
+    ///   * Multisignature owner/delegate
+    ///   0. `[writable]` The account to burn from.
+    ///   1. `[writable]` The token mint.
+    ///   2. `[]` The account's multisignature owner/delegate.
+    ///   3. ..`3+M` `[signer]` M signer accounts.
+    BurnChecked {
+        /// The amount of tokens to burn.
+        amount: u64,
+        /// Expected number of base 10 digits to the right of the decimal place.
+        decimals: u8,
+    },
+    /// Like [`InitializeAccount`], but the owner pubkey is passed via
+    /// instruction data rather than the accounts list. This variant may be
+    /// preferable when using Cross Program Invocation from an instruction
+    /// that does not need the owner's `AccountInfo` otherwise.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   0. `[writable]`  The account to initialize.
+    ///   1. `[]` The mint this account will be associated with.
+    ///   3. `[]` Rent sysvar
+    InitializeAccount2 {
+        /// The new account's owner/multisignature.
+        owner: Pubkey,
+    },
+    /// Given a wrapped / native token account (a token account containing SOL)
+    /// updates its amount field based on the account's underlying `lamports`.
+    /// This is useful if a non-wrapped SOL account uses
+    /// `system_instruction::transfer` to move lamports to a wrapped token
+    /// account, and needs to have its token `amount` field updated.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   0. `[writable]`  The native token account to sync with its underlying
+    ///      lamports.
+    SyncNative,
+    /// Like [`InitializeAccount2`], but does not require the Rent sysvar to be
+    /// provided
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   0. `[writable]`  The account to initialize.
+    ///   1. `[]` The mint this account will be associated with.
+    InitializeAccount3 {
+        /// The new account's owner/multisignature.
+        owner: Pubkey,
+    },
+    /// Like [`InitializeMultisig`], but does not require the Rent sysvar to be
+    /// provided
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   0. `[writable]` The multisignature account to initialize.
+    ///   1. ..`1+N` `[]` The signer accounts, must equal to N where `1 <= N <=
+    ///      11`.
+    InitializeMultisig2 {
+        /// The number of signers (M) required to validate this multisignature
+        /// account.
+        m: u8,
+    },
+    /// Like [`InitializeMint`], but does not require the Rent sysvar to be
+    /// provided
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   0. `[writable]` The mint to initialize.
+    InitializeMint2 {
+        /// Number of base 10 digits to the right of the decimal place.
+        decimals: u8,
+        /// The authority/multisignature to mint tokens.
+        mint_authority: Pubkey,
+        /// The freeze authority/multisignature of the mint.
+        freeze_authority: COption<Pubkey>,
+    },
+    /// Gets the required size of an account for the given mint as a
+    /// little-endian `u64`.
+    ///
+    /// Return data can be fetched using `sol_get_return_data` and deserializing
+    /// the return data as a little-endian `u64`.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   0. `[]` The mint to calculate for
+    GetAccountDataSize, // typically, there's also data, but this program ignores it
+    /// Initialize the Immutable Owner extension for the given token account
+    ///
+    /// Fails if the account has already been initialized, so must be called
+    /// before `InitializeAccount`.
+    ///
+    /// No-ops in this version of the program, but is included for compatibility
+    /// with the Associated Token Account program.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   0. `[writable]`  The account to initialize.
+    ///
+    /// Data expected by this instruction:
+    ///   None
+    InitializeImmutableOwner,
+    /// Convert an Amount of tokens to a `UiAmount` string, using the given
+    /// mint. In this version of the program, the mint can only specify the
+    /// number of decimals.
+    ///
+    /// Fails on an invalid mint.
+    ///
+    /// Return data can be fetched using `sol_get_return_data` and deserialized
+    /// with `String::from_utf8`.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   0. `[]` The mint to calculate for
+    AmountToUiAmount {
+        /// The amount of tokens to reformat.
+        amount: u64,
+    },
+    /// Convert a `UiAmount` of tokens to a little-endian `u64` raw Amount,
+    /// using the given mint. In this version of the program, the mint can
+    /// only specify the number of decimals.
+    ///
+    /// Return data can be fetched using `sol_get_return_data` and deserializing
+    /// the return data as a little-endian `u64`.
+    ///
+    /// Accounts expected by this instruction:
+    ///
+    ///   0. `[]` The mint to calculate for
+    UiAmountToAmount {
+        /// The `ui_amount` of tokens to reformat.
+        ui_amount: &'a str,
+    },
+    // Any new variants also need to be added to program-2022 `TokenInstruction`, so that the
+    // latter remains a superset of this instruction set. New variants also need to be added to
+    // token/js/src/instructions/types.ts to maintain @solana/spl-token compatibility
+}
+impl<'a> TokenInstruction<'a> {
+    /// Unpacks a byte buffer into a
+    /// [`TokenInstruction`](enum.TokenInstruction.html).
+    pub fn unpack(input: &'a [u8]) -> Result<Self, ProgramError> {
+        use TokenError::InvalidInstruction;
+
+        let (&tag, rest) = input.split_first().ok_or(InvalidInstruction)?;
+        Ok(match tag {
+            0 => {
+                let (&decimals, rest) = rest.split_first().ok_or(InvalidInstruction)?;
+                let (mint_authority, rest) = Self::unpack_pubkey(rest)?;
+                let (freeze_authority, _rest) = Self::unpack_pubkey_option(rest)?;
+                Self::InitializeMint {
+                    mint_authority,
+                    freeze_authority,
+                    decimals,
+                }
+            }
+            1 => Self::InitializeAccount,
+            2 => {
+                let &m = rest.first().ok_or(InvalidInstruction)?;
+                Self::InitializeMultisig { m }
+            }
+            3 | 4 | 7 | 8 => {
+                let amount = rest
+                    .get(..8)
+                    .and_then(|slice| slice.try_into().ok())
+                    .map(u64::from_le_bytes)
+                    .ok_or(InvalidInstruction)?;
+                match tag {
+                    3 => Self::Transfer { amount },
+                    4 => Self::Approve { amount },
+                    7 => Self::MintTo { amount },
+                    8 => Self::Burn { amount },
+                    _ => unreachable!(),
+                }
+            }
+            5 => Self::Revoke,
+            6 => {
+                let (authority_type, rest) = rest
+                    .split_first()
+                    .ok_or_else(|| ProgramError::from(InvalidInstruction))
+                    .and_then(|(&t, rest)| Ok((AuthorityType::from(t)?, rest)))?;
+                let (new_authority, _rest) = Self::unpack_pubkey_option(rest)?;
+
+                Self::SetAuthority {
+                    authority_type,
+                    new_authority,
+                }
+            }
+            9 => Self::CloseAccount,
+            10 => Self::FreezeAccount,
+            11 => Self::ThawAccount,
+            12 => {
+                let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?;
+                Self::TransferChecked { amount, decimals }
+            }
+            13 => {
+                let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?;
+                Self::ApproveChecked { amount, decimals }
+            }
+            14 => {
+                let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?;
+                Self::MintToChecked { amount, decimals }
+            }
+            15 => {
+                let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?;
+                Self::BurnChecked { amount, decimals }
+            }
+            16 => {
+                let (owner, _rest) = Self::unpack_pubkey(rest)?;
+                Self::InitializeAccount2 { owner }
+            }
+            17 => Self::SyncNative,
+            18 => {
+                let (owner, _rest) = Self::unpack_pubkey(rest)?;
+                Self::InitializeAccount3 { owner }
+            }
+            19 => {
+                let &m = rest.first().ok_or(InvalidInstruction)?;
+                Self::InitializeMultisig2 { m }
+            }
+            20 => {
+                let (&decimals, rest) = rest.split_first().ok_or(InvalidInstruction)?;
+                let (mint_authority, rest) = Self::unpack_pubkey(rest)?;
+                let (freeze_authority, _rest) = Self::unpack_pubkey_option(rest)?;
+                Self::InitializeMint2 {
+                    mint_authority,
+                    freeze_authority,
+                    decimals,
+                }
+            }
+            21 => Self::GetAccountDataSize,
+            22 => Self::InitializeImmutableOwner,
+            23 => {
+                let (amount, _rest) = Self::unpack_u64(rest)?;
+                Self::AmountToUiAmount { amount }
+            }
+            24 => {
+                let ui_amount = std::str::from_utf8(rest).map_err(|_| InvalidInstruction)?;
+                Self::UiAmountToAmount { ui_amount }
+            }
+            _ => return Err(TokenError::InvalidInstruction.into()),
+        })
+    }
+
+    /// Packs a [`TokenInstruction`](enum.TokenInstruction.html) into a byte
+    /// buffer.
+    pub fn pack(&self) -> Vec<u8> {
+        let mut buf = Vec::with_capacity(size_of::<Self>());
+        match self {
+            &Self::InitializeMint {
+                ref mint_authority,
+                ref freeze_authority,
+                decimals,
+            } => {
+                buf.push(0);
+                buf.push(decimals);
+                buf.extend_from_slice(mint_authority.as_ref());
+                Self::pack_pubkey_option(freeze_authority, &mut buf);
+            }
+            Self::InitializeAccount => buf.push(1),
+            &Self::InitializeMultisig { m } => {
+                buf.push(2);
+                buf.push(m);
+            }
+            &Self::Transfer { amount } => {
+                buf.push(3);
+                buf.extend_from_slice(&amount.to_le_bytes());
+            }
+            &Self::Approve { amount } => {
+                buf.push(4);
+                buf.extend_from_slice(&amount.to_le_bytes());
+            }
+            &Self::MintTo { amount } => {
+                buf.push(7);
+                buf.extend_from_slice(&amount.to_le_bytes());
+            }
+            &Self::Burn { amount } => {
+                buf.push(8);
+                buf.extend_from_slice(&amount.to_le_bytes());
+            }
+            Self::Revoke => buf.push(5),
+            Self::SetAuthority {
+                authority_type,
+                ref new_authority,
+            } => {
+                buf.push(6);
+                buf.push(authority_type.into());
+                Self::pack_pubkey_option(new_authority, &mut buf);
+            }
+            Self::CloseAccount => buf.push(9),
+            Self::FreezeAccount => buf.push(10),
+            Self::ThawAccount => buf.push(11),
+            &Self::TransferChecked { amount, decimals } => {
+                buf.push(12);
+                buf.extend_from_slice(&amount.to_le_bytes());
+                buf.push(decimals);
+            }
+            &Self::ApproveChecked { amount, decimals } => {
+                buf.push(13);
+                buf.extend_from_slice(&amount.to_le_bytes());
+                buf.push(decimals);
+            }
+            &Self::MintToChecked { amount, decimals } => {
+                buf.push(14);
+                buf.extend_from_slice(&amount.to_le_bytes());
+                buf.push(decimals);
+            }
+            &Self::BurnChecked { amount, decimals } => {
+                buf.push(15);
+                buf.extend_from_slice(&amount.to_le_bytes());
+                buf.push(decimals);
+            }
+            &Self::InitializeAccount2 { owner } => {
+                buf.push(16);
+                buf.extend_from_slice(owner.as_ref());
+            }
+            &Self::SyncNative => {
+                buf.push(17);
+            }
+            &Self::InitializeAccount3 { owner } => {
+                buf.push(18);
+                buf.extend_from_slice(owner.as_ref());
+            }
+            &Self::InitializeMultisig2 { m } => {
+                buf.push(19);
+                buf.push(m);
+            }
+            &Self::InitializeMint2 {
+                ref mint_authority,
+                ref freeze_authority,
+                decimals,
+            } => {
+                buf.push(20);
+                buf.push(decimals);
+                buf.extend_from_slice(mint_authority.as_ref());
+                Self::pack_pubkey_option(freeze_authority, &mut buf);
+            }
+            &Self::GetAccountDataSize => {
+                buf.push(21);
+            }
+            &Self::InitializeImmutableOwner => {
+                buf.push(22);
+            }
+            &Self::AmountToUiAmount { amount } => {
+                buf.push(23);
+                buf.extend_from_slice(&amount.to_le_bytes());
+            }
+            Self::UiAmountToAmount { ui_amount } => {
+                buf.push(24);
+                buf.extend_from_slice(ui_amount.as_bytes());
+            }
+        };
+        buf
+    }
+
+    fn unpack_pubkey(input: &[u8]) -> Result<(Pubkey, &[u8]), ProgramError> {
+        if input.len() >= 32 {
+            let (key, rest) = input.split_at(32);
+            let pk = Pubkey::try_from(key).map_err(|_| TokenError::InvalidInstruction)?;
+            Ok((pk, rest))
+        } else {
+            Err(TokenError::InvalidInstruction.into())
+        }
+    }
+
+    fn unpack_pubkey_option(input: &[u8]) -> Result<(COption<Pubkey>, &[u8]), ProgramError> {
+        match input.split_first() {
+            Option::Some((&0, rest)) => Ok((COption::None, rest)),
+            Option::Some((&1, rest)) if rest.len() >= 32 => {
+                let (key, rest) = rest.split_at(32);
+                let pk = Pubkey::try_from(key).map_err(|_| TokenError::InvalidInstruction)?;
+                Ok((COption::Some(pk), rest))
+            }
+            _ => Err(TokenError::InvalidInstruction.into()),
+        }
+    }
+
+    fn pack_pubkey_option(value: &COption<Pubkey>, buf: &mut Vec<u8>) {
+        match *value {
+            COption::Some(ref key) => {
+                buf.push(1);
+                buf.extend_from_slice(&key.to_bytes());
+            }
+            COption::None => buf.push(0),
+        }
+    }
+
+    fn unpack_u64(input: &[u8]) -> Result<(u64, &[u8]), ProgramError> {
+        let value = input
+            .get(..U64_BYTES)
+            .and_then(|slice| slice.try_into().ok())
+            .map(u64::from_le_bytes)
+            .ok_or(TokenError::InvalidInstruction)?;
+        Ok((value, &input[U64_BYTES..]))
+    }
+
+    fn unpack_amount_decimals(input: &[u8]) -> Result<(u64, u8, &[u8]), ProgramError> {
+        let (amount, rest) = Self::unpack_u64(input)?;
+        let (&decimals, rest) = rest.split_first().ok_or(TokenError::InvalidInstruction)?;
+        Ok((amount, decimals, rest))
+    }
+}
+
+/// Specifies the authority type for `SetAuthority` instructions
+#[repr(u8)]
+#[derive(Clone, Debug, PartialEq)]
+pub enum AuthorityType {
+    /// Authority to mint new tokens
+    MintTokens,
+    /// Authority to freeze any account associated with the Mint
+    FreezeAccount,
+    /// Owner of a given token account
+    AccountOwner,
+    /// Authority to close a token account
+    CloseAccount,
+}
+
+impl AuthorityType {
+    fn into(&self) -> u8 {
+        match self {
+            AuthorityType::MintTokens => 0,
+            AuthorityType::FreezeAccount => 1,
+            AuthorityType::AccountOwner => 2,
+            AuthorityType::CloseAccount => 3,
+        }
+    }
+
+    fn from(index: u8) -> Result<Self, ProgramError> {
+        match index {
+            0 => Ok(AuthorityType::MintTokens),
+            1 => Ok(AuthorityType::FreezeAccount),
+            2 => Ok(AuthorityType::AccountOwner),
+            3 => Ok(AuthorityType::CloseAccount),
+            _ => Err(TokenError::InvalidInstruction.into()),
+        }
+    }
+}
+
+/// Creates a `InitializeMint` instruction.
+pub fn initialize_mint(
+    token_program_id: &Pubkey,
+    mint_pubkey: &Pubkey,
+    mint_authority_pubkey: &Pubkey,
+    freeze_authority_pubkey: Option<&Pubkey>,
+    decimals: u8,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let freeze_authority = freeze_authority_pubkey.cloned().into();
+    let data = TokenInstruction::InitializeMint {
+        mint_authority: *mint_authority_pubkey,
+        freeze_authority,
+        decimals,
+    }
+    .pack();
+
+    let accounts = vec![
+        AccountMeta::new(*mint_pubkey, false),
+        AccountMeta::new_readonly(sysvar::rent::id(), false),
+    ];
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `InitializeMint2` instruction.
+pub fn initialize_mint2(
+    token_program_id: &Pubkey,
+    mint_pubkey: &Pubkey,
+    mint_authority_pubkey: &Pubkey,
+    freeze_authority_pubkey: Option<&Pubkey>,
+    decimals: u8,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let freeze_authority = freeze_authority_pubkey.cloned().into();
+    let data = TokenInstruction::InitializeMint2 {
+        mint_authority: *mint_authority_pubkey,
+        freeze_authority,
+        decimals,
+    }
+    .pack();
+
+    let accounts = vec![AccountMeta::new(*mint_pubkey, false)];
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `InitializeAccount` instruction.
+pub fn initialize_account(
+    token_program_id: &Pubkey,
+    account_pubkey: &Pubkey,
+    mint_pubkey: &Pubkey,
+    owner_pubkey: &Pubkey,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let data = TokenInstruction::InitializeAccount.pack();
+
+    let accounts = vec![
+        AccountMeta::new(*account_pubkey, false),
+        AccountMeta::new_readonly(*mint_pubkey, false),
+        AccountMeta::new_readonly(*owner_pubkey, false),
+        AccountMeta::new_readonly(sysvar::rent::id(), false),
+    ];
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `InitializeAccount2` instruction.
+pub fn initialize_account2(
+    token_program_id: &Pubkey,
+    account_pubkey: &Pubkey,
+    mint_pubkey: &Pubkey,
+    owner_pubkey: &Pubkey,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let data = TokenInstruction::InitializeAccount2 {
+        owner: *owner_pubkey,
+    }
+    .pack();
+
+    let accounts = vec![
+        AccountMeta::new(*account_pubkey, false),
+        AccountMeta::new_readonly(*mint_pubkey, false),
+        AccountMeta::new_readonly(sysvar::rent::id(), false),
+    ];
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `InitializeAccount3` instruction.
+pub fn initialize_account3(
+    token_program_id: &Pubkey,
+    account_pubkey: &Pubkey,
+    mint_pubkey: &Pubkey,
+    owner_pubkey: &Pubkey,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let data = TokenInstruction::InitializeAccount3 {
+        owner: *owner_pubkey,
+    }
+    .pack();
+
+    let accounts = vec![
+        AccountMeta::new(*account_pubkey, false),
+        AccountMeta::new_readonly(*mint_pubkey, false),
+    ];
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `InitializeMultisig` instruction.
+pub fn initialize_multisig(
+    token_program_id: &Pubkey,
+    multisig_pubkey: &Pubkey,
+    signer_pubkeys: &[&Pubkey],
+    m: u8,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    if !is_valid_signer_index(m as usize)
+        || !is_valid_signer_index(signer_pubkeys.len())
+        || m as usize > signer_pubkeys.len()
+    {
+        return Err(ProgramError::MissingRequiredSignature);
+    }
+    let data = TokenInstruction::InitializeMultisig { m }.pack();
+
+    let mut accounts = Vec::with_capacity(1 + 1 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new(*multisig_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(sysvar::rent::id(), false));
+    for signer_pubkey in signer_pubkeys.iter() {
+        accounts.push(AccountMeta::new_readonly(**signer_pubkey, false));
+    }
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `InitializeMultisig2` instruction.
+pub fn initialize_multisig2(
+    token_program_id: &Pubkey,
+    multisig_pubkey: &Pubkey,
+    signer_pubkeys: &[&Pubkey],
+    m: u8,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    if !is_valid_signer_index(m as usize)
+        || !is_valid_signer_index(signer_pubkeys.len())
+        || m as usize > signer_pubkeys.len()
+    {
+        return Err(ProgramError::MissingRequiredSignature);
+    }
+    let data = TokenInstruction::InitializeMultisig2 { m }.pack();
+
+    let mut accounts = Vec::with_capacity(1 + 1 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new(*multisig_pubkey, false));
+    for signer_pubkey in signer_pubkeys.iter() {
+        accounts.push(AccountMeta::new_readonly(**signer_pubkey, false));
+    }
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `Transfer` instruction.
+pub fn transfer(
+    token_program_id: &Pubkey,
+    source_pubkey: &Pubkey,
+    destination_pubkey: &Pubkey,
+    authority_pubkey: &Pubkey,
+    signer_pubkeys: &[&Pubkey],
+    amount: u64,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let data = TokenInstruction::Transfer { amount }.pack();
+
+    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new(*source_pubkey, false));
+    accounts.push(AccountMeta::new(*destination_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(
+        *authority_pubkey,
+        signer_pubkeys.is_empty(),
+    ));
+    for signer_pubkey in signer_pubkeys.iter() {
+        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
+    }
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates an `Approve` instruction.
+pub fn approve(
+    token_program_id: &Pubkey,
+    source_pubkey: &Pubkey,
+    delegate_pubkey: &Pubkey,
+    owner_pubkey: &Pubkey,
+    signer_pubkeys: &[&Pubkey],
+    amount: u64,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let data = TokenInstruction::Approve { amount }.pack();
+
+    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new(*source_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(*delegate_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(
+        *owner_pubkey,
+        signer_pubkeys.is_empty(),
+    ));
+    for signer_pubkey in signer_pubkeys.iter() {
+        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
+    }
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `Revoke` instruction.
+pub fn revoke(
+    token_program_id: &Pubkey,
+    source_pubkey: &Pubkey,
+    owner_pubkey: &Pubkey,
+    signer_pubkeys: &[&Pubkey],
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let data = TokenInstruction::Revoke.pack();
+
+    let mut accounts = Vec::with_capacity(2 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new(*source_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(
+        *owner_pubkey,
+        signer_pubkeys.is_empty(),
+    ));
+    for signer_pubkey in signer_pubkeys.iter() {
+        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
+    }
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `SetAuthority` instruction.
+pub fn set_authority(
+    token_program_id: &Pubkey,
+    owned_pubkey: &Pubkey,
+    new_authority_pubkey: Option<&Pubkey>,
+    authority_type: AuthorityType,
+    owner_pubkey: &Pubkey,
+    signer_pubkeys: &[&Pubkey],
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let new_authority = new_authority_pubkey.cloned().into();
+    let data = TokenInstruction::SetAuthority {
+        authority_type,
+        new_authority,
+    }
+    .pack();
+
+    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new(*owned_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(
+        *owner_pubkey,
+        signer_pubkeys.is_empty(),
+    ));
+    for signer_pubkey in signer_pubkeys.iter() {
+        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
+    }
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `MintTo` instruction.
+pub fn mint_to(
+    token_program_id: &Pubkey,
+    mint_pubkey: &Pubkey,
+    account_pubkey: &Pubkey,
+    owner_pubkey: &Pubkey,
+    signer_pubkeys: &[&Pubkey],
+    amount: u64,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let data = TokenInstruction::MintTo { amount }.pack();
+
+    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new(*mint_pubkey, false));
+    accounts.push(AccountMeta::new(*account_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(
+        *owner_pubkey,
+        signer_pubkeys.is_empty(),
+    ));
+    for signer_pubkey in signer_pubkeys.iter() {
+        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
+    }
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `Burn` instruction.
+pub fn burn(
+    token_program_id: &Pubkey,
+    account_pubkey: &Pubkey,
+    mint_pubkey: &Pubkey,
+    authority_pubkey: &Pubkey,
+    signer_pubkeys: &[&Pubkey],
+    amount: u64,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let data = TokenInstruction::Burn { amount }.pack();
+
+    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new(*account_pubkey, false));
+    accounts.push(AccountMeta::new(*mint_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(
+        *authority_pubkey,
+        signer_pubkeys.is_empty(),
+    ));
+    for signer_pubkey in signer_pubkeys.iter() {
+        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
+    }
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `CloseAccount` instruction.
+pub fn close_account(
+    token_program_id: &Pubkey,
+    account_pubkey: &Pubkey,
+    destination_pubkey: &Pubkey,
+    owner_pubkey: &Pubkey,
+    signer_pubkeys: &[&Pubkey],
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let data = TokenInstruction::CloseAccount.pack();
+
+    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new(*account_pubkey, false));
+    accounts.push(AccountMeta::new(*destination_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(
+        *owner_pubkey,
+        signer_pubkeys.is_empty(),
+    ));
+    for signer_pubkey in signer_pubkeys.iter() {
+        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
+    }
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `FreezeAccount` instruction.
+pub fn freeze_account(
+    token_program_id: &Pubkey,
+    account_pubkey: &Pubkey,
+    mint_pubkey: &Pubkey,
+    owner_pubkey: &Pubkey,
+    signer_pubkeys: &[&Pubkey],
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let data = TokenInstruction::FreezeAccount.pack();
+
+    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new(*account_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(*mint_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(
+        *owner_pubkey,
+        signer_pubkeys.is_empty(),
+    ));
+    for signer_pubkey in signer_pubkeys.iter() {
+        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
+    }
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `ThawAccount` instruction.
+pub fn thaw_account(
+    token_program_id: &Pubkey,
+    account_pubkey: &Pubkey,
+    mint_pubkey: &Pubkey,
+    owner_pubkey: &Pubkey,
+    signer_pubkeys: &[&Pubkey],
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let data = TokenInstruction::ThawAccount.pack();
+
+    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new(*account_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(*mint_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(
+        *owner_pubkey,
+        signer_pubkeys.is_empty(),
+    ));
+    for signer_pubkey in signer_pubkeys.iter() {
+        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
+    }
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `TransferChecked` instruction.
+#[allow(clippy::too_many_arguments)]
+pub fn transfer_checked(
+    token_program_id: &Pubkey,
+    source_pubkey: &Pubkey,
+    mint_pubkey: &Pubkey,
+    destination_pubkey: &Pubkey,
+    authority_pubkey: &Pubkey,
+    signer_pubkeys: &[&Pubkey],
+    amount: u64,
+    decimals: u8,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let data = TokenInstruction::TransferChecked { amount, decimals }.pack();
+
+    let mut accounts = Vec::with_capacity(4 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new(*source_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(*mint_pubkey, false));
+    accounts.push(AccountMeta::new(*destination_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(
+        *authority_pubkey,
+        signer_pubkeys.is_empty(),
+    ));
+    for signer_pubkey in signer_pubkeys.iter() {
+        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
+    }
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates an `ApproveChecked` instruction.
+#[allow(clippy::too_many_arguments)]
+pub fn approve_checked(
+    token_program_id: &Pubkey,
+    source_pubkey: &Pubkey,
+    mint_pubkey: &Pubkey,
+    delegate_pubkey: &Pubkey,
+    owner_pubkey: &Pubkey,
+    signer_pubkeys: &[&Pubkey],
+    amount: u64,
+    decimals: u8,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let data = TokenInstruction::ApproveChecked { amount, decimals }.pack();
+
+    let mut accounts = Vec::with_capacity(4 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new(*source_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(*mint_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(*delegate_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(
+        *owner_pubkey,
+        signer_pubkeys.is_empty(),
+    ));
+    for signer_pubkey in signer_pubkeys.iter() {
+        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
+    }
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `MintToChecked` instruction.
+pub fn mint_to_checked(
+    token_program_id: &Pubkey,
+    mint_pubkey: &Pubkey,
+    account_pubkey: &Pubkey,
+    owner_pubkey: &Pubkey,
+    signer_pubkeys: &[&Pubkey],
+    amount: u64,
+    decimals: u8,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let data = TokenInstruction::MintToChecked { amount, decimals }.pack();
+
+    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new(*mint_pubkey, false));
+    accounts.push(AccountMeta::new(*account_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(
+        *owner_pubkey,
+        signer_pubkeys.is_empty(),
+    ));
+    for signer_pubkey in signer_pubkeys.iter() {
+        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
+    }
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `BurnChecked` instruction.
+pub fn burn_checked(
+    token_program_id: &Pubkey,
+    account_pubkey: &Pubkey,
+    mint_pubkey: &Pubkey,
+    authority_pubkey: &Pubkey,
+    signer_pubkeys: &[&Pubkey],
+    amount: u64,
+    decimals: u8,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    let data = TokenInstruction::BurnChecked { amount, decimals }.pack();
+
+    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new(*account_pubkey, false));
+    accounts.push(AccountMeta::new(*mint_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(
+        *authority_pubkey,
+        signer_pubkeys.is_empty(),
+    ));
+    for signer_pubkey in signer_pubkeys.iter() {
+        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
+    }
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Creates a `SyncNative` instruction
+pub fn sync_native(
+    token_program_id: &Pubkey,
+    account_pubkey: &Pubkey,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts: vec![AccountMeta::new(*account_pubkey, false)],
+        data: TokenInstruction::SyncNative.pack(),
+    })
+}
+
+/// Creates a `GetAccountDataSize` instruction
+pub fn get_account_data_size(
+    token_program_id: &Pubkey,
+    mint_pubkey: &Pubkey,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts: vec![AccountMeta::new_readonly(*mint_pubkey, false)],
+        data: TokenInstruction::GetAccountDataSize.pack(),
+    })
+}
+
+/// Creates a `InitializeImmutableOwner` instruction
+pub fn initialize_immutable_owner(
+    token_program_id: &Pubkey,
+    account_pubkey: &Pubkey,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts: vec![AccountMeta::new(*account_pubkey, false)],
+        data: TokenInstruction::InitializeImmutableOwner.pack(),
+    })
+}
+
+/// Creates an `AmountToUiAmount` instruction
+pub fn amount_to_ui_amount(
+    token_program_id: &Pubkey,
+    mint_pubkey: &Pubkey,
+    amount: u64,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts: vec![AccountMeta::new_readonly(*mint_pubkey, false)],
+        data: TokenInstruction::AmountToUiAmount { amount }.pack(),
+    })
+}
+
+/// Creates a `UiAmountToAmount` instruction
+pub fn ui_amount_to_amount(
+    token_program_id: &Pubkey,
+    mint_pubkey: &Pubkey,
+    ui_amount: &str,
+) -> Result<Instruction, ProgramError> {
+    check_program_account(token_program_id)?;
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts: vec![AccountMeta::new_readonly(*mint_pubkey, false)],
+        data: TokenInstruction::UiAmountToAmount { ui_amount }.pack(),
+    })
+}
+
+/// Utility function that checks index is between `MIN_SIGNERS` and
+/// `MAX_SIGNERS`
+pub fn is_valid_signer_index(index: usize) -> bool {
+    (MIN_SIGNERS..=MAX_SIGNERS).contains(&index)
+}
+
+#[cfg(test)]
+mod test {
+    use {super::*, proptest::prelude::*};
+
+    #[test]
+    fn test_instruction_packing() {
+        let check = TokenInstruction::InitializeMint {
+            decimals: 2,
+            mint_authority: Pubkey::new_from_array([1u8; 32]),
+            freeze_authority: COption::None,
+        };
+        let packed = check.pack();
+        let mut expect = Vec::from([0u8, 2]);
+        expect.extend_from_slice(&[1u8; 32]);
+        expect.extend_from_slice(&[0]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::InitializeMint {
+            decimals: 2,
+            mint_authority: Pubkey::new_from_array([2u8; 32]),
+            freeze_authority: COption::Some(Pubkey::new_from_array([3u8; 32])),
+        };
+        let packed = check.pack();
+        let mut expect = vec![0u8, 2];
+        expect.extend_from_slice(&[2u8; 32]);
+        expect.extend_from_slice(&[1]);
+        expect.extend_from_slice(&[3u8; 32]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::InitializeAccount;
+        let packed = check.pack();
+        let expect = Vec::from([1u8]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::InitializeMultisig { m: 1 };
+        let packed = check.pack();
+        let expect = Vec::from([2u8, 1]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::Transfer { amount: 1 };
+        let packed = check.pack();
+        let expect = Vec::from([3u8, 1, 0, 0, 0, 0, 0, 0, 0]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::Approve { amount: 1 };
+        let packed = check.pack();
+        let expect = Vec::from([4u8, 1, 0, 0, 0, 0, 0, 0, 0]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::Revoke;
+        let packed = check.pack();
+        let expect = Vec::from([5u8]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::SetAuthority {
+            authority_type: AuthorityType::FreezeAccount,
+            new_authority: COption::Some(Pubkey::new_from_array([4u8; 32])),
+        };
+        let packed = check.pack();
+        let mut expect = Vec::from([6u8, 1]);
+        expect.extend_from_slice(&[1]);
+        expect.extend_from_slice(&[4u8; 32]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::MintTo { amount: 1 };
+        let packed = check.pack();
+        let expect = Vec::from([7u8, 1, 0, 0, 0, 0, 0, 0, 0]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::Burn { amount: 1 };
+        let packed = check.pack();
+        let expect = Vec::from([8u8, 1, 0, 0, 0, 0, 0, 0, 0]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::CloseAccount;
+        let packed = check.pack();
+        let expect = Vec::from([9u8]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::FreezeAccount;
+        let packed = check.pack();
+        let expect = Vec::from([10u8]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::ThawAccount;
+        let packed = check.pack();
+        let expect = Vec::from([11u8]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::TransferChecked {
+            amount: 1,
+            decimals: 2,
+        };
+        let packed = check.pack();
+        let expect = Vec::from([12u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::ApproveChecked {
+            amount: 1,
+            decimals: 2,
+        };
+        let packed = check.pack();
+        let expect = Vec::from([13u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::MintToChecked {
+            amount: 1,
+            decimals: 2,
+        };
+        let packed = check.pack();
+        let expect = Vec::from([14u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::BurnChecked {
+            amount: 1,
+            decimals: 2,
+        };
+        let packed = check.pack();
+        let expect = Vec::from([15u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::InitializeAccount2 {
+            owner: Pubkey::new_from_array([2u8; 32]),
+        };
+        let packed = check.pack();
+        let mut expect = vec![16u8];
+        expect.extend_from_slice(&[2u8; 32]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::SyncNative;
+        let packed = check.pack();
+        let expect = vec![17u8];
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::InitializeAccount3 {
+            owner: Pubkey::new_from_array([2u8; 32]),
+        };
+        let packed = check.pack();
+        let mut expect = vec![18u8];
+        expect.extend_from_slice(&[2u8; 32]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::InitializeMultisig2 { m: 1 };
+        let packed = check.pack();
+        let expect = Vec::from([19u8, 1]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::InitializeMint2 {
+            decimals: 2,
+            mint_authority: Pubkey::new_from_array([1u8; 32]),
+            freeze_authority: COption::None,
+        };
+        let packed = check.pack();
+        let mut expect = Vec::from([20u8, 2]);
+        expect.extend_from_slice(&[1u8; 32]);
+        expect.extend_from_slice(&[0]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::InitializeMint2 {
+            decimals: 2,
+            mint_authority: Pubkey::new_from_array([2u8; 32]),
+            freeze_authority: COption::Some(Pubkey::new_from_array([3u8; 32])),
+        };
+        let packed = check.pack();
+        let mut expect = vec![20u8, 2];
+        expect.extend_from_slice(&[2u8; 32]);
+        expect.extend_from_slice(&[1]);
+        expect.extend_from_slice(&[3u8; 32]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::GetAccountDataSize;
+        let packed = check.pack();
+        let expect = vec![21u8];
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::InitializeImmutableOwner;
+        let packed = check.pack();
+        let expect = vec![22u8];
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::AmountToUiAmount { amount: 42 };
+        let packed = check.pack();
+        let expect = vec![23u8, 42, 0, 0, 0, 0, 0, 0, 0];
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::UiAmountToAmount { ui_amount: "0.42" };
+        let packed = check.pack();
+        let expect = vec![24u8, 48, 46, 52, 50];
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+    }
+
+    #[test]
+    fn test_instruction_unpack_panic() {
+        for i in 0..255u8 {
+            for j in 1..10 {
+                let mut data = vec![0; j];
+                data[0] = i;
+                let _no_panic = TokenInstruction::unpack(&data);
+            }
+        }
+    }
+
+    proptest! {
+        #![proptest_config(ProptestConfig::with_cases(1024))]
+        #[test]
+        fn test_instruction_unpack_proptest(
+            data in prop::collection::vec(any::<u8>(), 0..255)
+        ) {
+            let _no_panic = TokenInstruction::unpack(&data);
+        }
+    }
+}

+ 25 - 0
interface/src/lib.rs

@@ -0,0 +1,25 @@
+#![allow(clippy::arithmetic_side_effects)]
+#![deny(missing_docs)]
+#![cfg_attr(not(test), warn(unsafe_code))]
+
+//! An ERC20-like Token program for the Solana blockchain
+
+use {
+    solana_program_error::{ProgramError, ProgramResult},
+    solana_pubkey::Pubkey,
+};
+
+pub mod error;
+pub mod instruction;
+pub mod native_mint;
+pub mod state;
+
+solana_pubkey::declare_id!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
+
+/// Checks that the supplied program ID is the correct one for SPL-token
+pub fn check_program_account(spl_token_program_id: &Pubkey) -> ProgramResult {
+    if spl_token_program_id != &id() {
+        return Err(ProgramError::IncorrectProgramId);
+    }
+    Ok(())
+}

+ 7 - 0
interface/src/native_mint.rs

@@ -0,0 +1,7 @@
+//! The Mint that represents the native token
+
+/// There are `10^9` lamports in one SOL
+pub const DECIMALS: u8 = 9;
+
+// The Mint for native SOL Token accounts
+solana_pubkey::declare_id!("So11111111111111111111111111111111111111112");

+ 490 - 0
interface/src/state.rs

@@ -0,0 +1,490 @@
+//! State transition types
+
+use {
+    crate::instruction::MAX_SIGNERS,
+    arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs},
+    num_enum::TryFromPrimitive,
+    solana_program_error::ProgramError,
+    solana_program_option::COption,
+    solana_program_pack::{IsInitialized, Pack, Sealed},
+    solana_pubkey::{Pubkey, PUBKEY_BYTES},
+};
+
+/// Mint data.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, PartialEq)]
+pub struct Mint {
+    /// 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.
+    pub mint_authority: COption<Pubkey>,
+    /// Total supply of tokens.
+    pub supply: u64,
+    /// Number of base 10 digits to the right of the decimal place.
+    pub decimals: u8,
+    /// Is `true` if this structure has been initialized
+    pub is_initialized: bool,
+    /// Optional authority to freeze token accounts.
+    pub freeze_authority: COption<Pubkey>,
+}
+impl Sealed for Mint {}
+impl IsInitialized for Mint {
+    fn is_initialized(&self) -> bool {
+        self.is_initialized
+    }
+}
+impl Pack for Mint {
+    const LEN: usize = 82;
+    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
+        let src = array_ref![src, 0, 82];
+        let (mint_authority, supply, decimals, is_initialized, freeze_authority) =
+            array_refs![src, 36, 8, 1, 1, 36];
+        let mint_authority = unpack_coption_key(mint_authority)?;
+        let supply = u64::from_le_bytes(*supply);
+        let decimals = decimals[0];
+        let is_initialized = match is_initialized {
+            [0] => false,
+            [1] => true,
+            _ => return Err(ProgramError::InvalidAccountData),
+        };
+        let freeze_authority = unpack_coption_key(freeze_authority)?;
+        Ok(Mint {
+            mint_authority,
+            supply,
+            decimals,
+            is_initialized,
+            freeze_authority,
+        })
+    }
+    fn pack_into_slice(&self, dst: &mut [u8]) {
+        let dst = array_mut_ref![dst, 0, 82];
+        let (
+            mint_authority_dst,
+            supply_dst,
+            decimals_dst,
+            is_initialized_dst,
+            freeze_authority_dst,
+        ) = mut_array_refs![dst, 36, 8, 1, 1, 36];
+        let &Mint {
+            ref mint_authority,
+            supply,
+            decimals,
+            is_initialized,
+            ref freeze_authority,
+        } = self;
+        pack_coption_key(mint_authority, mint_authority_dst);
+        *supply_dst = supply.to_le_bytes();
+        decimals_dst[0] = decimals;
+        is_initialized_dst[0] = is_initialized as u8;
+        pack_coption_key(freeze_authority, freeze_authority_dst);
+    }
+}
+
+/// Account data.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, PartialEq)]
+pub struct Account {
+    /// The mint associated with this account
+    pub mint: Pubkey,
+    /// The owner of this account.
+    pub owner: Pubkey,
+    /// The amount of tokens this account holds.
+    pub amount: u64,
+    /// If `delegate` is `Some` then `delegated_amount` represents
+    /// the amount authorized by the delegate
+    pub delegate: COption<Pubkey>,
+    /// The account's state
+    pub state: AccountState,
+    /// 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.
+    pub is_native: COption<u64>,
+    /// The amount delegated
+    pub delegated_amount: u64,
+    /// Optional authority to close the account.
+    pub close_authority: COption<Pubkey>,
+}
+impl Account {
+    /// Checks if account is frozen
+    pub fn is_frozen(&self) -> bool {
+        self.state == AccountState::Frozen
+    }
+    /// Checks if account is native
+    pub fn is_native(&self) -> bool {
+        self.is_native.is_some()
+    }
+    /// Checks if a token Account's owner is the `system_program` or the
+    /// incinerator
+    pub fn is_owned_by_system_program_or_incinerator(&self) -> bool {
+        solana_sdk_ids::system_program::check_id(&self.owner)
+            || solana_sdk_ids::incinerator::check_id(&self.owner)
+    }
+}
+impl Sealed for Account {}
+impl IsInitialized for Account {
+    fn is_initialized(&self) -> bool {
+        self.state != AccountState::Uninitialized
+    }
+}
+impl Pack for Account {
+    const LEN: usize = 165;
+    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
+        let src = array_ref![src, 0, 165];
+        let (mint, owner, amount, delegate, state, is_native, delegated_amount, close_authority) =
+            array_refs![src, 32, 32, 8, 36, 1, 12, 8, 36];
+        Ok(Account {
+            mint: Pubkey::new_from_array(*mint),
+            owner: Pubkey::new_from_array(*owner),
+            amount: u64::from_le_bytes(*amount),
+            delegate: unpack_coption_key(delegate)?,
+            state: AccountState::try_from_primitive(state[0])
+                .or(Err(ProgramError::InvalidAccountData))?,
+            is_native: unpack_coption_u64(is_native)?,
+            delegated_amount: u64::from_le_bytes(*delegated_amount),
+            close_authority: unpack_coption_key(close_authority)?,
+        })
+    }
+    fn pack_into_slice(&self, dst: &mut [u8]) {
+        let dst = array_mut_ref![dst, 0, 165];
+        let (
+            mint_dst,
+            owner_dst,
+            amount_dst,
+            delegate_dst,
+            state_dst,
+            is_native_dst,
+            delegated_amount_dst,
+            close_authority_dst,
+        ) = mut_array_refs![dst, 32, 32, 8, 36, 1, 12, 8, 36];
+        let &Account {
+            ref mint,
+            ref owner,
+            amount,
+            ref delegate,
+            state,
+            ref is_native,
+            delegated_amount,
+            ref close_authority,
+        } = self;
+        mint_dst.copy_from_slice(mint.as_ref());
+        owner_dst.copy_from_slice(owner.as_ref());
+        *amount_dst = amount.to_le_bytes();
+        pack_coption_key(delegate, delegate_dst);
+        state_dst[0] = state as u8;
+        pack_coption_u64(is_native, is_native_dst);
+        *delegated_amount_dst = delegated_amount.to_le_bytes();
+        pack_coption_key(close_authority, close_authority_dst);
+    }
+}
+
+/// Account state.
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, TryFromPrimitive)]
+pub enum AccountState {
+    /// Account is not yet initialized
+    #[default]
+    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,
+}
+
+/// Multisignature data.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, PartialEq)]
+pub struct Multisig {
+    /// Number of signers required
+    pub m: u8,
+    /// Number of valid signers
+    pub n: u8,
+    /// Is `true` if this structure has been initialized
+    pub is_initialized: bool,
+    /// Signer public keys
+    pub signers: [Pubkey; MAX_SIGNERS],
+}
+impl Sealed for Multisig {}
+impl IsInitialized for Multisig {
+    fn is_initialized(&self) -> bool {
+        self.is_initialized
+    }
+}
+impl Pack for Multisig {
+    const LEN: usize = 355;
+    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
+        let src = array_ref![src, 0, 355];
+        #[allow(clippy::ptr_offset_with_cast)]
+        let (m, n, is_initialized, signers_flat) = array_refs![src, 1, 1, 1, 32 * MAX_SIGNERS];
+        let mut result = Multisig {
+            m: m[0],
+            n: n[0],
+            is_initialized: match is_initialized {
+                [0] => false,
+                [1] => true,
+                _ => return Err(ProgramError::InvalidAccountData),
+            },
+            signers: [Pubkey::new_from_array([0u8; 32]); MAX_SIGNERS],
+        };
+        for (src, dst) in signers_flat.chunks(32).zip(result.signers.iter_mut()) {
+            *dst = Pubkey::try_from(src).map_err(|_| ProgramError::InvalidAccountData)?;
+        }
+        Ok(result)
+    }
+    fn pack_into_slice(&self, dst: &mut [u8]) {
+        let dst = array_mut_ref![dst, 0, 355];
+        #[allow(clippy::ptr_offset_with_cast)]
+        let (m, n, is_initialized, signers_flat) = mut_array_refs![dst, 1, 1, 1, 32 * MAX_SIGNERS];
+        *m = [self.m];
+        *n = [self.n];
+        *is_initialized = [self.is_initialized as u8];
+        for (i, src) in self.signers.iter().enumerate() {
+            let dst_array = array_mut_ref![signers_flat, 32 * i, 32];
+            dst_array.copy_from_slice(src.as_ref());
+        }
+    }
+}
+
+// Helpers
+fn pack_coption_key(src: &COption<Pubkey>, dst: &mut [u8; 36]) {
+    let (tag, body) = mut_array_refs![dst, 4, 32];
+    match src {
+        COption::Some(key) => {
+            *tag = [1, 0, 0, 0];
+            body.copy_from_slice(key.as_ref());
+        }
+        COption::None => {
+            *tag = [0; 4];
+        }
+    }
+}
+fn unpack_coption_key(src: &[u8; 36]) -> Result<COption<Pubkey>, ProgramError> {
+    let (tag, body) = array_refs![src, 4, 32];
+    match *tag {
+        [0, 0, 0, 0] => Ok(COption::None),
+        [1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))),
+        _ => Err(ProgramError::InvalidAccountData),
+    }
+}
+fn pack_coption_u64(src: &COption<u64>, dst: &mut [u8; 12]) {
+    let (tag, body) = mut_array_refs![dst, 4, 8];
+    match src {
+        COption::Some(amount) => {
+            *tag = [1, 0, 0, 0];
+            *body = amount.to_le_bytes();
+        }
+        COption::None => {
+            *tag = [0; 4];
+        }
+    }
+}
+fn unpack_coption_u64(src: &[u8; 12]) -> Result<COption<u64>, ProgramError> {
+    let (tag, body) = array_refs![src, 4, 8];
+    match *tag {
+        [0, 0, 0, 0] => Ok(COption::None),
+        [1, 0, 0, 0] => Ok(COption::Some(u64::from_le_bytes(*body))),
+        _ => Err(ProgramError::InvalidAccountData),
+    }
+}
+
+const SPL_TOKEN_ACCOUNT_MINT_OFFSET: usize = 0;
+const SPL_TOKEN_ACCOUNT_OWNER_OFFSET: usize = 32;
+
+/// A trait for token Account structs to enable efficiently unpacking various
+/// fields without unpacking the complete state.
+pub trait GenericTokenAccount {
+    /// Check if the account data is a valid token account
+    fn valid_account_data(account_data: &[u8]) -> bool;
+
+    /// Call after account length has already been verified to unpack the
+    /// account owner
+    fn unpack_account_owner_unchecked(account_data: &[u8]) -> &Pubkey {
+        Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_OWNER_OFFSET)
+    }
+
+    /// Call after account length has already been verified to unpack the
+    /// account mint
+    fn unpack_account_mint_unchecked(account_data: &[u8]) -> &Pubkey {
+        Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_MINT_OFFSET)
+    }
+
+    /// Call after account length has already been verified to unpack a Pubkey
+    /// at the specified offset. Panics if `account_data.len()` is less than
+    /// `PUBKEY_BYTES`
+    fn unpack_pubkey_unchecked(account_data: &[u8], offset: usize) -> &Pubkey {
+        bytemuck::from_bytes(&account_data[offset..offset + PUBKEY_BYTES])
+    }
+
+    /// Unpacks an account's owner from opaque account data.
+    fn unpack_account_owner(account_data: &[u8]) -> Option<&Pubkey> {
+        if Self::valid_account_data(account_data) {
+            Some(Self::unpack_account_owner_unchecked(account_data))
+        } else {
+            None
+        }
+    }
+
+    /// Unpacks an account's mint from opaque account data.
+    fn unpack_account_mint(account_data: &[u8]) -> Option<&Pubkey> {
+        if Self::valid_account_data(account_data) {
+            Some(Self::unpack_account_mint_unchecked(account_data))
+        } else {
+            None
+        }
+    }
+}
+
+/// The offset of state field in Account's C representation
+pub const ACCOUNT_INITIALIZED_INDEX: usize = 108;
+
+/// Check if the account data buffer represents an initialized account.
+/// This is checking the `state` (`AccountState`) field of an Account object.
+pub fn is_initialized_account(account_data: &[u8]) -> bool {
+    *account_data
+        .get(ACCOUNT_INITIALIZED_INDEX)
+        .unwrap_or(&(AccountState::Uninitialized as u8))
+        != AccountState::Uninitialized as u8
+}
+
+impl GenericTokenAccount for Account {
+    fn valid_account_data(account_data: &[u8]) -> bool {
+        account_data.len() == Account::LEN && is_initialized_account(account_data)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_mint_unpack_from_slice() {
+        let src: [u8; 82] = [0; 82];
+        let mint = Mint::unpack_from_slice(&src).unwrap();
+        assert!(!mint.is_initialized);
+
+        let mut src: [u8; 82] = [0; 82];
+        src[45] = 2;
+        let mint = Mint::unpack_from_slice(&src).unwrap_err();
+        assert_eq!(mint, ProgramError::InvalidAccountData);
+    }
+
+    #[test]
+    fn test_account_state() {
+        let account_state = AccountState::default();
+        assert_eq!(account_state, AccountState::Uninitialized);
+    }
+
+    #[test]
+    fn test_multisig_unpack_from_slice() {
+        let src: [u8; 355] = [0; 355];
+        let multisig = Multisig::unpack_from_slice(&src).unwrap();
+        assert_eq!(multisig.m, 0);
+        assert_eq!(multisig.n, 0);
+        assert!(!multisig.is_initialized);
+
+        let mut src: [u8; 355] = [0; 355];
+        src[0] = 1;
+        src[1] = 1;
+        src[2] = 1;
+        let multisig = Multisig::unpack_from_slice(&src).unwrap();
+        assert_eq!(multisig.m, 1);
+        assert_eq!(multisig.n, 1);
+        assert!(multisig.is_initialized);
+
+        let mut src: [u8; 355] = [0; 355];
+        src[2] = 2;
+        let multisig = Multisig::unpack_from_slice(&src).unwrap_err();
+        assert_eq!(multisig, ProgramError::InvalidAccountData);
+    }
+
+    #[test]
+    fn test_unpack_coption_key() {
+        let src: [u8; 36] = [0; 36];
+        let result = unpack_coption_key(&src).unwrap();
+        assert_eq!(result, COption::None);
+
+        let mut src: [u8; 36] = [0; 36];
+        src[1] = 1;
+        let result = unpack_coption_key(&src).unwrap_err();
+        assert_eq!(result, ProgramError::InvalidAccountData);
+    }
+
+    #[test]
+    fn test_unpack_coption_u64() {
+        let src: [u8; 12] = [0; 12];
+        let result = unpack_coption_u64(&src).unwrap();
+        assert_eq!(result, COption::None);
+
+        let mut src: [u8; 12] = [0; 12];
+        src[0] = 1;
+        let result = unpack_coption_u64(&src).unwrap();
+        assert_eq!(result, COption::Some(0));
+
+        let mut src: [u8; 12] = [0; 12];
+        src[1] = 1;
+        let result = unpack_coption_u64(&src).unwrap_err();
+        assert_eq!(result, ProgramError::InvalidAccountData);
+    }
+
+    #[test]
+    fn test_unpack_token_owner() {
+        // Account data length < Account::LEN, unpack will not return a key
+        let src: [u8; 12] = [0; 12];
+        let result = Account::unpack_account_owner(&src);
+        assert_eq!(result, Option::None);
+
+        // The right account data size and initialized, unpack will return some key
+        let mut src: [u8; Account::LEN] = [0; Account::LEN];
+        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
+        let result = Account::unpack_account_owner(&src);
+        assert!(result.is_some());
+
+        // The right account data size and frozen, unpack will return some key
+        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
+        let result = Account::unpack_account_owner(&src);
+        assert!(result.is_some());
+
+        // The right account data size and uninitialized, unpack will return None
+        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
+        let result = Account::unpack_account_mint(&src);
+        assert_eq!(result, Option::None);
+
+        // Account data length > account data size, unpack will not return a key
+        let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
+        let result = Account::unpack_account_owner(&src);
+        assert_eq!(result, Option::None);
+    }
+
+    #[test]
+    fn test_unpack_token_mint() {
+        // Account data length < Account::LEN, unpack will not return a key
+        let src: [u8; 12] = [0; 12];
+        let result = Account::unpack_account_mint(&src);
+        assert_eq!(result, Option::None);
+
+        // The right account data size and initialized, unpack will return some key
+        let mut src: [u8; Account::LEN] = [0; Account::LEN];
+        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
+        let result = Account::unpack_account_mint(&src);
+        assert!(result.is_some());
+
+        // The right account data size and frozen, unpack will return some key
+        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
+        let result = Account::unpack_account_mint(&src);
+        assert!(result.is_some());
+
+        // The right account data size and uninitialized, unpack will return None
+        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
+        let result = Account::unpack_account_mint(&src);
+        assert_eq!(result, Option::None);
+
+        // Account data length > account data size, unpack will not return a key
+        let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
+        let result = Account::unpack_account_mint(&src);
+        assert_eq!(result, Option::None);
+    }
+}

+ 4 - 1
package.json

@@ -32,7 +32,10 @@
     "fixtures:generate": "zx ./scripts/rust/fixtures.mjs generate",
     "fixtures:run": "zx ./scripts/rust/fixtures.mjs run",
     "p-interface:format": "zx ./scripts/rust/format.mjs p-interface",
-    "p-interface:lint": "zx ./scripts/rust/lint.mjs p-interface"
+    "p-interface:lint": "zx ./scripts/rust/lint.mjs p-interface",
+    "interface:format": "zx ./scripts/rust/format.mjs interface",
+    "interface:lint": "zx ./scripts/rust/lint.mjs interface",
+    "interface:test": "zx ./scripts/rust/test.mjs interface"
   },
   "devDependencies": {
     "@codama/renderers-js": "^1.2.7",

+ 1 - 0
program/Cargo.toml

@@ -30,6 +30,7 @@ solana-pubkey = { workspace = true, features = ["bytemuck"] }
 solana-rent = "2.2.1"
 solana-sdk-ids = "2.2.1"
 solana-sysvar = { version = "2.2.2", features = ["bincode"] }
+spl-token-interface = { version = "1.0", path = "../interface" }
 thiserror = "2.0"
 
 [dev-dependencies]

+ 2 - 1
program/src/entrypoint.rs

@@ -1,11 +1,12 @@
 //! Program entrypoint
 
 use {
-    crate::{error::TokenError, processor::Processor},
+    crate::processor::Processor,
     solana_account_info::AccountInfo,
     solana_msg::msg,
     solana_program_error::{ProgramResult, ToStr},
     solana_pubkey::Pubkey,
+    spl_token_interface::error::TokenError,
 };
 
 solana_program_entrypoint::entrypoint!(process_instruction);

+ 1 - 165
program/src/error.rs

@@ -1,166 +1,2 @@
 //! Error types
-
-use {
-    num_derive::FromPrimitive,
-    solana_program_error::{ProgramError, ToStr},
-    thiserror::Error,
-};
-
-/// Errors that may be returned by the Token program.
-#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
-#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
-pub enum TokenError {
-    // 0
-    /// Lamport balance below rent-exempt threshold.
-    #[error("Lamport balance below rent-exempt threshold")]
-    NotRentExempt,
-    /// Insufficient funds for the operation requested.
-    #[error("Insufficient funds")]
-    InsufficientFunds,
-    /// Invalid Mint.
-    #[error("Invalid Mint")]
-    InvalidMint,
-    /// Account not associated with this Mint.
-    #[error("Account not associated with this Mint")]
-    MintMismatch,
-    /// Owner does not match.
-    #[error("Owner does not match")]
-    OwnerMismatch,
-
-    // 5
-    /// This token's supply is fixed and new tokens cannot be minted.
-    #[error("Fixed supply")]
-    FixedSupply,
-    /// The account cannot be initialized because it is already being used.
-    #[error("Already in use")]
-    AlreadyInUse,
-    /// Invalid number of provided signers.
-    #[error("Invalid number of provided signers")]
-    InvalidNumberOfProvidedSigners,
-    /// Invalid number of required signers.
-    #[error("Invalid number of required signers")]
-    InvalidNumberOfRequiredSigners,
-    /// State is uninitialized.
-    #[error("State is uninitialized")]
-    UninitializedState,
-
-    // 10
-    /// Instruction does not support native tokens
-    #[error("Instruction does not support native tokens")]
-    NativeNotSupported,
-    /// Non-native account can only be closed if its balance is zero
-    #[error("Non-native account can only be closed if its balance is zero")]
-    NonNativeHasBalance,
-    /// Invalid instruction
-    #[error("Invalid instruction")]
-    InvalidInstruction,
-    /// State is invalid for requested operation.
-    #[error("State is invalid for requested operation")]
-    InvalidState,
-    /// Operation overflowed
-    #[error("Operation overflowed")]
-    Overflow,
-
-    // 15
-    /// Account does not support specified authority type.
-    #[error("Account does not support specified authority type")]
-    AuthorityTypeNotSupported,
-    /// This token mint cannot freeze accounts.
-    #[error("This token mint cannot freeze accounts")]
-    MintCannotFreeze,
-    /// Account is frozen; all account operations will fail
-    #[error("Account is frozen")]
-    AccountFrozen,
-    /// Mint decimals mismatch between the client and mint
-    #[error("The provided decimals value different from the Mint decimals")]
-    MintDecimalsMismatch,
-    /// Instruction does not support non-native tokens
-    #[error("Instruction does not support non-native tokens")]
-    NonNativeNotSupported,
-}
-impl From<TokenError> for ProgramError {
-    fn from(e: TokenError) -> Self {
-        ProgramError::Custom(e as u32)
-    }
-}
-
-impl TryFrom<u32> for TokenError {
-    type Error = ProgramError;
-    fn try_from(error: u32) -> Result<Self, Self::Error> {
-        match error {
-            0 => Ok(TokenError::NotRentExempt),
-            1 => Ok(TokenError::InsufficientFunds),
-            2 => Ok(TokenError::InvalidMint),
-            3 => Ok(TokenError::MintMismatch),
-            4 => Ok(TokenError::OwnerMismatch),
-            5 => Ok(TokenError::FixedSupply),
-            6 => Ok(TokenError::AlreadyInUse),
-            7 => Ok(TokenError::InvalidNumberOfProvidedSigners),
-            8 => Ok(TokenError::InvalidNumberOfRequiredSigners),
-            9 => Ok(TokenError::UninitializedState),
-            10 => Ok(TokenError::NativeNotSupported),
-            11 => Ok(TokenError::NonNativeHasBalance),
-            12 => Ok(TokenError::InvalidInstruction),
-            13 => Ok(TokenError::InvalidState),
-            14 => Ok(TokenError::Overflow),
-            15 => Ok(TokenError::AuthorityTypeNotSupported),
-            16 => Ok(TokenError::MintCannotFreeze),
-            17 => Ok(TokenError::AccountFrozen),
-            18 => Ok(TokenError::MintDecimalsMismatch),
-            19 => Ok(TokenError::NonNativeNotSupported),
-            _ => Err(ProgramError::InvalidArgument),
-        }
-    }
-}
-
-impl ToStr for TokenError {
-    fn to_str<E>(&self) -> &'static str {
-        match self {
-            TokenError::NotRentExempt => "Error: Lamport balance below rent-exempt threshold",
-            TokenError::InsufficientFunds => "Error: insufficient funds",
-            TokenError::InvalidMint => "Error: Invalid Mint",
-            TokenError::MintMismatch => "Error: Account not associated with this Mint",
-            TokenError::OwnerMismatch => "Error: owner does not match",
-            TokenError::FixedSupply => "Error: the total supply of this token is fixed",
-            TokenError::AlreadyInUse => "Error: account or token already in use",
-            TokenError::InvalidNumberOfProvidedSigners => {
-                "Error: Invalid number of provided signers"
-            }
-            TokenError::InvalidNumberOfRequiredSigners => {
-                "Error: Invalid number of required signers"
-            }
-            TokenError::UninitializedState => "Error: State is uninitialized",
-            TokenError::NativeNotSupported => "Error: Instruction does not support native tokens",
-            TokenError::NonNativeHasBalance => {
-                "Error: Non-native account can only be closed if its balance is zero"
-            }
-            TokenError::InvalidInstruction => "Error: Invalid instruction",
-            TokenError::InvalidState => "Error: Invalid account state for operation",
-            TokenError::Overflow => "Error: Operation overflowed",
-            TokenError::AuthorityTypeNotSupported => {
-                "Error: Account does not support specified authority type"
-            }
-            TokenError::MintCannotFreeze => "Error: This token mint cannot freeze accounts",
-            TokenError::AccountFrozen => "Error: Account is frozen",
-            TokenError::MintDecimalsMismatch => "Error: decimals different from the Mint decimals",
-            TokenError::NonNativeNotSupported => {
-                "Error: Instruction does not support non-native tokens"
-            }
-        }
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use {super::*, strum::IntoEnumIterator};
-    #[test]
-    fn test_parse_error_from_primitive_exhaustive() {
-        for variant in TokenError::iter() {
-            let variant_u32 = variant as u32;
-            assert_eq!(
-                TokenError::from_repr(variant_u32 as usize).unwrap(),
-                TokenError::try_from(variant_u32).unwrap()
-            );
-        }
-    }
-}
+pub use spl_token_interface::error::*;

+ 1 - 1702
program/src/instruction.rs

@@ -1,1703 +1,2 @@
 //! Instruction types
-
-use {
-    crate::{check_program_account, error::TokenError},
-    solana_instruction::{AccountMeta, Instruction},
-    solana_program_error::ProgramError,
-    solana_program_option::COption,
-    solana_pubkey::Pubkey,
-    solana_sdk_ids::sysvar,
-    std::{convert::TryInto, mem::size_of},
-};
-
-/// Minimum number of multisignature signers (min N)
-pub const MIN_SIGNERS: usize = 1;
-/// Maximum number of multisignature signers (max N)
-pub const MAX_SIGNERS: usize = 11;
-/// Serialized length of a `u64`, for unpacking
-const U64_BYTES: usize = 8;
-
-/// Instructions supported by the token program.
-#[repr(C)]
-#[derive(Clone, Debug, PartialEq)]
-pub enum TokenInstruction<'a> {
-    /// Initializes a new mint and optionally deposits all the newly minted
-    /// tokens in an account.
-    ///
-    /// The `InitializeMint` instruction requires no signers and MUST be
-    /// included within the same Transaction as the system program's
-    /// `CreateAccount` instruction that creates the account being initialized.
-    /// Otherwise another party can acquire ownership of the uninitialized
-    /// account.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   0. `[writable]` The mint to initialize.
-    ///   1. `[]` Rent sysvar
-    InitializeMint {
-        /// Number of base 10 digits to the right of the decimal place.
-        decimals: u8,
-        /// The authority/multisignature to mint tokens.
-        mint_authority: Pubkey,
-        /// The freeze authority/multisignature of the mint.
-        freeze_authority: COption<Pubkey>,
-    },
-    /// Initializes a new account to hold tokens.  If this account is associated
-    /// with the native mint then the token balance of the initialized account
-    /// will be equal to the amount of SOL in the account. If this account is
-    /// associated with another mint, that mint must be initialized before this
-    /// command can succeed.
-    ///
-    /// The `InitializeAccount` instruction requires no signers and MUST be
-    /// included within the same Transaction as the system program's
-    /// `CreateAccount` instruction that creates the account being initialized.
-    /// Otherwise another party can acquire ownership of the uninitialized
-    /// account.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   0. `[writable]`  The account to initialize.
-    ///   1. `[]` The mint this account will be associated with.
-    ///   2. `[]` The new account's owner/multisignature.
-    ///   3. `[]` Rent sysvar
-    InitializeAccount,
-    /// Initializes a multisignature account with N provided signers.
-    ///
-    /// Multisignature accounts can used in place of any single owner/delegate
-    /// accounts in any token instruction that require an owner/delegate to be
-    /// present.  The variant field represents the number of signers (M)
-    /// required to validate this multisignature account.
-    ///
-    /// The `InitializeMultisig` instruction requires no signers and MUST be
-    /// included within the same Transaction as the system program's
-    /// `CreateAccount` instruction that creates the account being initialized.
-    /// Otherwise another party can acquire ownership of the uninitialized
-    /// account.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   0. `[writable]` The multisignature account to initialize.
-    ///   1. `[]` Rent sysvar
-    ///   2. ..`2+N`. `[]` The signer accounts, must equal to N where `1 <= N <=
-    ///      11`.
-    InitializeMultisig {
-        /// The number of signers (M) required to validate this multisignature
-        /// account.
-        m: u8,
-    },
-    /// Transfers tokens from one account to another either directly or via a
-    /// delegate.  If this account is associated with the native mint then equal
-    /// amounts of SOL and Tokens will be transferred to the destination
-    /// account.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   * Single owner/delegate
-    ///   0. `[writable]` The source account.
-    ///   1. `[writable]` The destination account.
-    ///   2. `[signer]` The source account's owner/delegate.
-    ///
-    ///   * Multisignature owner/delegate
-    ///   0. `[writable]` The source account.
-    ///   1. `[writable]` The destination account.
-    ///   2. `[]` The source account's multisignature owner/delegate.
-    ///   3. ..`3+M` `[signer]` M signer accounts.
-    Transfer {
-        /// The amount of tokens to transfer.
-        amount: u64,
-    },
-    /// Approves a delegate.  A delegate is given the authority over tokens on
-    /// behalf of the source account's owner.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   * Single owner
-    ///   0. `[writable]` The source account.
-    ///   1. `[]` The delegate.
-    ///   2. `[signer]` The source account owner.
-    ///
-    ///   * Multisignature owner
-    ///   0. `[writable]` The source account.
-    ///   1. `[]` The delegate.
-    ///   2. `[]` The source account's multisignature owner.
-    ///   3. ..`3+M` `[signer]` M signer accounts
-    Approve {
-        /// The amount of tokens the delegate is approved for.
-        amount: u64,
-    },
-    /// Revokes the delegate's authority.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   * Single owner
-    ///   0. `[writable]` The source account.
-    ///   1. `[signer]` The source account owner.
-    ///
-    ///   * Multisignature owner
-    ///   0. `[writable]` The source account.
-    ///   1. `[]` The source account's multisignature owner.
-    ///   2. ..`2+M` `[signer]` M signer accounts
-    Revoke,
-    /// Sets a new authority of a mint or account.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   * Single authority
-    ///   0. `[writable]` The mint or account to change the authority of.
-    ///   1. `[signer]` The current authority of the mint or account.
-    ///
-    ///   * Multisignature authority
-    ///   0. `[writable]` The mint or account to change the authority of.
-    ///   1. `[]` The mint's or account's current multisignature authority.
-    ///   2. ..`2+M` `[signer]` M signer accounts
-    SetAuthority {
-        /// The type of authority to update.
-        authority_type: AuthorityType,
-        /// The new authority
-        new_authority: COption<Pubkey>,
-    },
-    /// Mints new tokens to an account.  The native mint does not support
-    /// minting.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   * Single authority
-    ///   0. `[writable]` The mint.
-    ///   1. `[writable]` The account to mint tokens to.
-    ///   2. `[signer]` The mint's minting authority.
-    ///
-    ///   * Multisignature authority
-    ///   0. `[writable]` The mint.
-    ///   1. `[writable]` The account to mint tokens to.
-    ///   2. `[]` The mint's multisignature mint-tokens authority.
-    ///   3. ..`3+M` `[signer]` M signer accounts.
-    MintTo {
-        /// The amount of new tokens to mint.
-        amount: u64,
-    },
-    /// Burns tokens by removing them from an account.  `Burn` does not support
-    /// accounts associated with the native mint, use `CloseAccount` instead.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   * Single owner/delegate
-    ///   0. `[writable]` The account to burn from.
-    ///   1. `[writable]` The token mint.
-    ///   2. `[signer]` The account's owner/delegate.
-    ///
-    ///   * Multisignature owner/delegate
-    ///   0. `[writable]` The account to burn from.
-    ///   1. `[writable]` The token mint.
-    ///   2. `[]` The account's multisignature owner/delegate.
-    ///   3. ..`3+M` `[signer]` M signer accounts.
-    Burn {
-        /// The amount of tokens to burn.
-        amount: u64,
-    },
-    /// Close an account by transferring all its SOL to the destination account.
-    /// Non-native accounts may only be closed if its token amount is zero.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   * Single owner
-    ///   0. `[writable]` The account to close.
-    ///   1. `[writable]` The destination account.
-    ///   2. `[signer]` The account's owner.
-    ///
-    ///   * Multisignature owner
-    ///   0. `[writable]` The account to close.
-    ///   1. `[writable]` The destination account.
-    ///   2. `[]` The account's multisignature owner.
-    ///   3. ..`3+M` `[signer]` M signer accounts.
-    CloseAccount,
-    /// Freeze an Initialized account using the Mint's `freeze_authority` (if
-    /// set).
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   * Single owner
-    ///   0. `[writable]` The account to freeze.
-    ///   1. `[]` The token mint.
-    ///   2. `[signer]` The mint freeze authority.
-    ///
-    ///   * Multisignature owner
-    ///   0. `[writable]` The account to freeze.
-    ///   1. `[]` The token mint.
-    ///   2. `[]` The mint's multisignature freeze authority.
-    ///   3. ..`3+M` `[signer]` M signer accounts.
-    FreezeAccount,
-    /// Thaw a Frozen account using the Mint's `freeze_authority` (if set).
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   * Single owner
-    ///   0. `[writable]` The account to freeze.
-    ///   1. `[]` The token mint.
-    ///   2. `[signer]` The mint freeze authority.
-    ///
-    ///   * Multisignature owner
-    ///   0. `[writable]` The account to freeze.
-    ///   1. `[]` The token mint.
-    ///   2. `[]` The mint's multisignature freeze authority.
-    ///   3. ..`3+M` `[signer]` M signer accounts.
-    ThawAccount,
-
-    /// Transfers tokens from one account to another either directly or via a
-    /// delegate.  If this account is associated with the native mint then equal
-    /// amounts of SOL and Tokens will be transferred to the destination
-    /// account.
-    ///
-    /// This instruction differs from Transfer in that the token mint and
-    /// decimals value is checked by the caller.  This may be useful when
-    /// creating transactions offline or within a hardware wallet.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   * Single owner/delegate
-    ///   0. `[writable]` The source account.
-    ///   1. `[]` The token mint.
-    ///   2. `[writable]` The destination account.
-    ///   3. `[signer]` The source account's owner/delegate.
-    ///
-    ///   * Multisignature owner/delegate
-    ///   0. `[writable]` The source account.
-    ///   1. `[]` The token mint.
-    ///   2. `[writable]` The destination account.
-    ///   3. `[]` The source account's multisignature owner/delegate.
-    ///   4. ..`4+M` `[signer]` M signer accounts.
-    TransferChecked {
-        /// The amount of tokens to transfer.
-        amount: u64,
-        /// Expected number of base 10 digits to the right of the decimal place.
-        decimals: u8,
-    },
-    /// Approves a delegate.  A delegate is given the authority over tokens on
-    /// behalf of the source account's owner.
-    ///
-    /// This instruction differs from Approve in that the token mint and
-    /// decimals value is checked by the caller.  This may be useful when
-    /// creating transactions offline or within a hardware wallet.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   * Single owner
-    ///   0. `[writable]` The source account.
-    ///   1. `[]` The token mint.
-    ///   2. `[]` The delegate.
-    ///   3. `[signer]` The source account owner.
-    ///
-    ///   * Multisignature owner
-    ///   0. `[writable]` The source account.
-    ///   1. `[]` The token mint.
-    ///   2. `[]` The delegate.
-    ///   3. `[]` The source account's multisignature owner.
-    ///   4. ..`4+M` `[signer]` M signer accounts
-    ApproveChecked {
-        /// The amount of tokens the delegate is approved for.
-        amount: u64,
-        /// Expected number of base 10 digits to the right of the decimal place.
-        decimals: u8,
-    },
-    /// Mints new tokens to an account.  The native mint does not support
-    /// minting.
-    ///
-    /// This instruction differs from `MintTo` in that the decimals value is
-    /// checked by the caller. This may be useful when creating transactions
-    /// offline or within a hardware wallet.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   * Single authority
-    ///   0. `[writable]` The mint.
-    ///   1. `[writable]` The account to mint tokens to.
-    ///   2. `[signer]` The mint's minting authority.
-    ///
-    ///   * Multisignature authority
-    ///   0. `[writable]` The mint.
-    ///   1. `[writable]` The account to mint tokens to.
-    ///   2. `[]` The mint's multisignature mint-tokens authority.
-    ///   3. ..`3+M` `[signer]` M signer accounts.
-    MintToChecked {
-        /// The amount of new tokens to mint.
-        amount: u64,
-        /// Expected number of base 10 digits to the right of the decimal place.
-        decimals: u8,
-    },
-    /// Burns tokens by removing them from an account.  `BurnChecked` does not
-    /// support accounts associated with the native mint, use `CloseAccount`
-    /// instead.
-    ///
-    /// This instruction differs from Burn in that the decimals value is checked
-    /// by the caller. This may be useful when creating transactions offline or
-    /// within a hardware wallet.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   * Single owner/delegate
-    ///   0. `[writable]` The account to burn from.
-    ///   1. `[writable]` The token mint.
-    ///   2. `[signer]` The account's owner/delegate.
-    ///
-    ///   * Multisignature owner/delegate
-    ///   0. `[writable]` The account to burn from.
-    ///   1. `[writable]` The token mint.
-    ///   2. `[]` The account's multisignature owner/delegate.
-    ///   3. ..`3+M` `[signer]` M signer accounts.
-    BurnChecked {
-        /// The amount of tokens to burn.
-        amount: u64,
-        /// Expected number of base 10 digits to the right of the decimal place.
-        decimals: u8,
-    },
-    /// Like [`InitializeAccount`], but the owner pubkey is passed via
-    /// instruction data rather than the accounts list. This variant may be
-    /// preferable when using Cross Program Invocation from an instruction
-    /// that does not need the owner's `AccountInfo` otherwise.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   0. `[writable]`  The account to initialize.
-    ///   1. `[]` The mint this account will be associated with.
-    ///   3. `[]` Rent sysvar
-    InitializeAccount2 {
-        /// The new account's owner/multisignature.
-        owner: Pubkey,
-    },
-    /// Given a wrapped / native token account (a token account containing SOL)
-    /// updates its amount field based on the account's underlying `lamports`.
-    /// This is useful if a non-wrapped SOL account uses
-    /// `system_instruction::transfer` to move lamports to a wrapped token
-    /// account, and needs to have its token `amount` field updated.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   0. `[writable]`  The native token account to sync with its underlying
-    ///      lamports.
-    SyncNative,
-    /// Like [`InitializeAccount2`], but does not require the Rent sysvar to be
-    /// provided
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   0. `[writable]`  The account to initialize.
-    ///   1. `[]` The mint this account will be associated with.
-    InitializeAccount3 {
-        /// The new account's owner/multisignature.
-        owner: Pubkey,
-    },
-    /// Like [`InitializeMultisig`], but does not require the Rent sysvar to be
-    /// provided
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   0. `[writable]` The multisignature account to initialize.
-    ///   1. ..`1+N` `[]` The signer accounts, must equal to N where `1 <= N <=
-    ///      11`.
-    InitializeMultisig2 {
-        /// The number of signers (M) required to validate this multisignature
-        /// account.
-        m: u8,
-    },
-    /// Like [`InitializeMint`], but does not require the Rent sysvar to be
-    /// provided
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   0. `[writable]` The mint to initialize.
-    InitializeMint2 {
-        /// Number of base 10 digits to the right of the decimal place.
-        decimals: u8,
-        /// The authority/multisignature to mint tokens.
-        mint_authority: Pubkey,
-        /// The freeze authority/multisignature of the mint.
-        freeze_authority: COption<Pubkey>,
-    },
-    /// Gets the required size of an account for the given mint as a
-    /// little-endian `u64`.
-    ///
-    /// Return data can be fetched using `sol_get_return_data` and deserializing
-    /// the return data as a little-endian `u64`.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   0. `[]` The mint to calculate for
-    GetAccountDataSize, // typically, there's also data, but this program ignores it
-    /// Initialize the Immutable Owner extension for the given token account
-    ///
-    /// Fails if the account has already been initialized, so must be called
-    /// before `InitializeAccount`.
-    ///
-    /// No-ops in this version of the program, but is included for compatibility
-    /// with the Associated Token Account program.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   0. `[writable]`  The account to initialize.
-    ///
-    /// Data expected by this instruction:
-    ///   None
-    InitializeImmutableOwner,
-    /// Convert an Amount of tokens to a `UiAmount` string, using the given
-    /// mint. In this version of the program, the mint can only specify the
-    /// number of decimals.
-    ///
-    /// Fails on an invalid mint.
-    ///
-    /// Return data can be fetched using `sol_get_return_data` and deserialized
-    /// with `String::from_utf8`.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   0. `[]` The mint to calculate for
-    AmountToUiAmount {
-        /// The amount of tokens to reformat.
-        amount: u64,
-    },
-    /// Convert a `UiAmount` of tokens to a little-endian `u64` raw Amount,
-    /// using the given mint. In this version of the program, the mint can
-    /// only specify the number of decimals.
-    ///
-    /// Return data can be fetched using `sol_get_return_data` and deserializing
-    /// the return data as a little-endian `u64`.
-    ///
-    /// Accounts expected by this instruction:
-    ///
-    ///   0. `[]` The mint to calculate for
-    UiAmountToAmount {
-        /// The `ui_amount` of tokens to reformat.
-        ui_amount: &'a str,
-    },
-    // Any new variants also need to be added to program-2022 `TokenInstruction`, so that the
-    // latter remains a superset of this instruction set. New variants also need to be added to
-    // token/js/src/instructions/types.ts to maintain @solana/spl-token compatibility
-}
-impl<'a> TokenInstruction<'a> {
-    /// Unpacks a byte buffer into a
-    /// [`TokenInstruction`](enum.TokenInstruction.html).
-    pub fn unpack(input: &'a [u8]) -> Result<Self, ProgramError> {
-        use TokenError::InvalidInstruction;
-
-        let (&tag, rest) = input.split_first().ok_or(InvalidInstruction)?;
-        Ok(match tag {
-            0 => {
-                let (&decimals, rest) = rest.split_first().ok_or(InvalidInstruction)?;
-                let (mint_authority, rest) = Self::unpack_pubkey(rest)?;
-                let (freeze_authority, _rest) = Self::unpack_pubkey_option(rest)?;
-                Self::InitializeMint {
-                    mint_authority,
-                    freeze_authority,
-                    decimals,
-                }
-            }
-            1 => Self::InitializeAccount,
-            2 => {
-                let &m = rest.first().ok_or(InvalidInstruction)?;
-                Self::InitializeMultisig { m }
-            }
-            3 | 4 | 7 | 8 => {
-                let amount = rest
-                    .get(..8)
-                    .and_then(|slice| slice.try_into().ok())
-                    .map(u64::from_le_bytes)
-                    .ok_or(InvalidInstruction)?;
-                match tag {
-                    3 => Self::Transfer { amount },
-                    4 => Self::Approve { amount },
-                    7 => Self::MintTo { amount },
-                    8 => Self::Burn { amount },
-                    _ => unreachable!(),
-                }
-            }
-            5 => Self::Revoke,
-            6 => {
-                let (authority_type, rest) = rest
-                    .split_first()
-                    .ok_or_else(|| ProgramError::from(InvalidInstruction))
-                    .and_then(|(&t, rest)| Ok((AuthorityType::from(t)?, rest)))?;
-                let (new_authority, _rest) = Self::unpack_pubkey_option(rest)?;
-
-                Self::SetAuthority {
-                    authority_type,
-                    new_authority,
-                }
-            }
-            9 => Self::CloseAccount,
-            10 => Self::FreezeAccount,
-            11 => Self::ThawAccount,
-            12 => {
-                let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?;
-                Self::TransferChecked { amount, decimals }
-            }
-            13 => {
-                let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?;
-                Self::ApproveChecked { amount, decimals }
-            }
-            14 => {
-                let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?;
-                Self::MintToChecked { amount, decimals }
-            }
-            15 => {
-                let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?;
-                Self::BurnChecked { amount, decimals }
-            }
-            16 => {
-                let (owner, _rest) = Self::unpack_pubkey(rest)?;
-                Self::InitializeAccount2 { owner }
-            }
-            17 => Self::SyncNative,
-            18 => {
-                let (owner, _rest) = Self::unpack_pubkey(rest)?;
-                Self::InitializeAccount3 { owner }
-            }
-            19 => {
-                let &m = rest.first().ok_or(InvalidInstruction)?;
-                Self::InitializeMultisig2 { m }
-            }
-            20 => {
-                let (&decimals, rest) = rest.split_first().ok_or(InvalidInstruction)?;
-                let (mint_authority, rest) = Self::unpack_pubkey(rest)?;
-                let (freeze_authority, _rest) = Self::unpack_pubkey_option(rest)?;
-                Self::InitializeMint2 {
-                    mint_authority,
-                    freeze_authority,
-                    decimals,
-                }
-            }
-            21 => Self::GetAccountDataSize,
-            22 => Self::InitializeImmutableOwner,
-            23 => {
-                let (amount, _rest) = Self::unpack_u64(rest)?;
-                Self::AmountToUiAmount { amount }
-            }
-            24 => {
-                let ui_amount = std::str::from_utf8(rest).map_err(|_| InvalidInstruction)?;
-                Self::UiAmountToAmount { ui_amount }
-            }
-            _ => return Err(TokenError::InvalidInstruction.into()),
-        })
-    }
-
-    /// Packs a [`TokenInstruction`](enum.TokenInstruction.html) into a byte
-    /// buffer.
-    pub fn pack(&self) -> Vec<u8> {
-        let mut buf = Vec::with_capacity(size_of::<Self>());
-        match self {
-            &Self::InitializeMint {
-                ref mint_authority,
-                ref freeze_authority,
-                decimals,
-            } => {
-                buf.push(0);
-                buf.push(decimals);
-                buf.extend_from_slice(mint_authority.as_ref());
-                Self::pack_pubkey_option(freeze_authority, &mut buf);
-            }
-            Self::InitializeAccount => buf.push(1),
-            &Self::InitializeMultisig { m } => {
-                buf.push(2);
-                buf.push(m);
-            }
-            &Self::Transfer { amount } => {
-                buf.push(3);
-                buf.extend_from_slice(&amount.to_le_bytes());
-            }
-            &Self::Approve { amount } => {
-                buf.push(4);
-                buf.extend_from_slice(&amount.to_le_bytes());
-            }
-            &Self::MintTo { amount } => {
-                buf.push(7);
-                buf.extend_from_slice(&amount.to_le_bytes());
-            }
-            &Self::Burn { amount } => {
-                buf.push(8);
-                buf.extend_from_slice(&amount.to_le_bytes());
-            }
-            Self::Revoke => buf.push(5),
-            Self::SetAuthority {
-                authority_type,
-                ref new_authority,
-            } => {
-                buf.push(6);
-                buf.push(authority_type.into());
-                Self::pack_pubkey_option(new_authority, &mut buf);
-            }
-            Self::CloseAccount => buf.push(9),
-            Self::FreezeAccount => buf.push(10),
-            Self::ThawAccount => buf.push(11),
-            &Self::TransferChecked { amount, decimals } => {
-                buf.push(12);
-                buf.extend_from_slice(&amount.to_le_bytes());
-                buf.push(decimals);
-            }
-            &Self::ApproveChecked { amount, decimals } => {
-                buf.push(13);
-                buf.extend_from_slice(&amount.to_le_bytes());
-                buf.push(decimals);
-            }
-            &Self::MintToChecked { amount, decimals } => {
-                buf.push(14);
-                buf.extend_from_slice(&amount.to_le_bytes());
-                buf.push(decimals);
-            }
-            &Self::BurnChecked { amount, decimals } => {
-                buf.push(15);
-                buf.extend_from_slice(&amount.to_le_bytes());
-                buf.push(decimals);
-            }
-            &Self::InitializeAccount2 { owner } => {
-                buf.push(16);
-                buf.extend_from_slice(owner.as_ref());
-            }
-            &Self::SyncNative => {
-                buf.push(17);
-            }
-            &Self::InitializeAccount3 { owner } => {
-                buf.push(18);
-                buf.extend_from_slice(owner.as_ref());
-            }
-            &Self::InitializeMultisig2 { m } => {
-                buf.push(19);
-                buf.push(m);
-            }
-            &Self::InitializeMint2 {
-                ref mint_authority,
-                ref freeze_authority,
-                decimals,
-            } => {
-                buf.push(20);
-                buf.push(decimals);
-                buf.extend_from_slice(mint_authority.as_ref());
-                Self::pack_pubkey_option(freeze_authority, &mut buf);
-            }
-            &Self::GetAccountDataSize => {
-                buf.push(21);
-            }
-            &Self::InitializeImmutableOwner => {
-                buf.push(22);
-            }
-            &Self::AmountToUiAmount { amount } => {
-                buf.push(23);
-                buf.extend_from_slice(&amount.to_le_bytes());
-            }
-            Self::UiAmountToAmount { ui_amount } => {
-                buf.push(24);
-                buf.extend_from_slice(ui_amount.as_bytes());
-            }
-        };
-        buf
-    }
-
-    fn unpack_pubkey(input: &[u8]) -> Result<(Pubkey, &[u8]), ProgramError> {
-        if input.len() >= 32 {
-            let (key, rest) = input.split_at(32);
-            let pk = Pubkey::try_from(key).map_err(|_| TokenError::InvalidInstruction)?;
-            Ok((pk, rest))
-        } else {
-            Err(TokenError::InvalidInstruction.into())
-        }
-    }
-
-    fn unpack_pubkey_option(input: &[u8]) -> Result<(COption<Pubkey>, &[u8]), ProgramError> {
-        match input.split_first() {
-            Option::Some((&0, rest)) => Ok((COption::None, rest)),
-            Option::Some((&1, rest)) if rest.len() >= 32 => {
-                let (key, rest) = rest.split_at(32);
-                let pk = Pubkey::try_from(key).map_err(|_| TokenError::InvalidInstruction)?;
-                Ok((COption::Some(pk), rest))
-            }
-            _ => Err(TokenError::InvalidInstruction.into()),
-        }
-    }
-
-    fn pack_pubkey_option(value: &COption<Pubkey>, buf: &mut Vec<u8>) {
-        match *value {
-            COption::Some(ref key) => {
-                buf.push(1);
-                buf.extend_from_slice(&key.to_bytes());
-            }
-            COption::None => buf.push(0),
-        }
-    }
-
-    fn unpack_u64(input: &[u8]) -> Result<(u64, &[u8]), ProgramError> {
-        let value = input
-            .get(..U64_BYTES)
-            .and_then(|slice| slice.try_into().ok())
-            .map(u64::from_le_bytes)
-            .ok_or(TokenError::InvalidInstruction)?;
-        Ok((value, &input[U64_BYTES..]))
-    }
-
-    fn unpack_amount_decimals(input: &[u8]) -> Result<(u64, u8, &[u8]), ProgramError> {
-        let (amount, rest) = Self::unpack_u64(input)?;
-        let (&decimals, rest) = rest.split_first().ok_or(TokenError::InvalidInstruction)?;
-        Ok((amount, decimals, rest))
-    }
-}
-
-/// Specifies the authority type for `SetAuthority` instructions
-#[repr(u8)]
-#[derive(Clone, Debug, PartialEq)]
-pub enum AuthorityType {
-    /// Authority to mint new tokens
-    MintTokens,
-    /// Authority to freeze any account associated with the Mint
-    FreezeAccount,
-    /// Owner of a given token account
-    AccountOwner,
-    /// Authority to close a token account
-    CloseAccount,
-}
-
-impl AuthorityType {
-    fn into(&self) -> u8 {
-        match self {
-            AuthorityType::MintTokens => 0,
-            AuthorityType::FreezeAccount => 1,
-            AuthorityType::AccountOwner => 2,
-            AuthorityType::CloseAccount => 3,
-        }
-    }
-
-    fn from(index: u8) -> Result<Self, ProgramError> {
-        match index {
-            0 => Ok(AuthorityType::MintTokens),
-            1 => Ok(AuthorityType::FreezeAccount),
-            2 => Ok(AuthorityType::AccountOwner),
-            3 => Ok(AuthorityType::CloseAccount),
-            _ => Err(TokenError::InvalidInstruction.into()),
-        }
-    }
-}
-
-/// Creates a `InitializeMint` instruction.
-pub fn initialize_mint(
-    token_program_id: &Pubkey,
-    mint_pubkey: &Pubkey,
-    mint_authority_pubkey: &Pubkey,
-    freeze_authority_pubkey: Option<&Pubkey>,
-    decimals: u8,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let freeze_authority = freeze_authority_pubkey.cloned().into();
-    let data = TokenInstruction::InitializeMint {
-        mint_authority: *mint_authority_pubkey,
-        freeze_authority,
-        decimals,
-    }
-    .pack();
-
-    let accounts = vec![
-        AccountMeta::new(*mint_pubkey, false),
-        AccountMeta::new_readonly(sysvar::rent::id(), false),
-    ];
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `InitializeMint2` instruction.
-pub fn initialize_mint2(
-    token_program_id: &Pubkey,
-    mint_pubkey: &Pubkey,
-    mint_authority_pubkey: &Pubkey,
-    freeze_authority_pubkey: Option<&Pubkey>,
-    decimals: u8,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let freeze_authority = freeze_authority_pubkey.cloned().into();
-    let data = TokenInstruction::InitializeMint2 {
-        mint_authority: *mint_authority_pubkey,
-        freeze_authority,
-        decimals,
-    }
-    .pack();
-
-    let accounts = vec![AccountMeta::new(*mint_pubkey, false)];
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `InitializeAccount` instruction.
-pub fn initialize_account(
-    token_program_id: &Pubkey,
-    account_pubkey: &Pubkey,
-    mint_pubkey: &Pubkey,
-    owner_pubkey: &Pubkey,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let data = TokenInstruction::InitializeAccount.pack();
-
-    let accounts = vec![
-        AccountMeta::new(*account_pubkey, false),
-        AccountMeta::new_readonly(*mint_pubkey, false),
-        AccountMeta::new_readonly(*owner_pubkey, false),
-        AccountMeta::new_readonly(sysvar::rent::id(), false),
-    ];
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `InitializeAccount2` instruction.
-pub fn initialize_account2(
-    token_program_id: &Pubkey,
-    account_pubkey: &Pubkey,
-    mint_pubkey: &Pubkey,
-    owner_pubkey: &Pubkey,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let data = TokenInstruction::InitializeAccount2 {
-        owner: *owner_pubkey,
-    }
-    .pack();
-
-    let accounts = vec![
-        AccountMeta::new(*account_pubkey, false),
-        AccountMeta::new_readonly(*mint_pubkey, false),
-        AccountMeta::new_readonly(sysvar::rent::id(), false),
-    ];
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `InitializeAccount3` instruction.
-pub fn initialize_account3(
-    token_program_id: &Pubkey,
-    account_pubkey: &Pubkey,
-    mint_pubkey: &Pubkey,
-    owner_pubkey: &Pubkey,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let data = TokenInstruction::InitializeAccount3 {
-        owner: *owner_pubkey,
-    }
-    .pack();
-
-    let accounts = vec![
-        AccountMeta::new(*account_pubkey, false),
-        AccountMeta::new_readonly(*mint_pubkey, false),
-    ];
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `InitializeMultisig` instruction.
-pub fn initialize_multisig(
-    token_program_id: &Pubkey,
-    multisig_pubkey: &Pubkey,
-    signer_pubkeys: &[&Pubkey],
-    m: u8,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    if !is_valid_signer_index(m as usize)
-        || !is_valid_signer_index(signer_pubkeys.len())
-        || m as usize > signer_pubkeys.len()
-    {
-        return Err(ProgramError::MissingRequiredSignature);
-    }
-    let data = TokenInstruction::InitializeMultisig { m }.pack();
-
-    let mut accounts = Vec::with_capacity(1 + 1 + signer_pubkeys.len());
-    accounts.push(AccountMeta::new(*multisig_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(sysvar::rent::id(), false));
-    for signer_pubkey in signer_pubkeys.iter() {
-        accounts.push(AccountMeta::new_readonly(**signer_pubkey, false));
-    }
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `InitializeMultisig2` instruction.
-pub fn initialize_multisig2(
-    token_program_id: &Pubkey,
-    multisig_pubkey: &Pubkey,
-    signer_pubkeys: &[&Pubkey],
-    m: u8,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    if !is_valid_signer_index(m as usize)
-        || !is_valid_signer_index(signer_pubkeys.len())
-        || m as usize > signer_pubkeys.len()
-    {
-        return Err(ProgramError::MissingRequiredSignature);
-    }
-    let data = TokenInstruction::InitializeMultisig2 { m }.pack();
-
-    let mut accounts = Vec::with_capacity(1 + 1 + signer_pubkeys.len());
-    accounts.push(AccountMeta::new(*multisig_pubkey, false));
-    for signer_pubkey in signer_pubkeys.iter() {
-        accounts.push(AccountMeta::new_readonly(**signer_pubkey, false));
-    }
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `Transfer` instruction.
-pub fn transfer(
-    token_program_id: &Pubkey,
-    source_pubkey: &Pubkey,
-    destination_pubkey: &Pubkey,
-    authority_pubkey: &Pubkey,
-    signer_pubkeys: &[&Pubkey],
-    amount: u64,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let data = TokenInstruction::Transfer { amount }.pack();
-
-    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
-    accounts.push(AccountMeta::new(*source_pubkey, false));
-    accounts.push(AccountMeta::new(*destination_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(
-        *authority_pubkey,
-        signer_pubkeys.is_empty(),
-    ));
-    for signer_pubkey in signer_pubkeys.iter() {
-        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
-    }
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates an `Approve` instruction.
-pub fn approve(
-    token_program_id: &Pubkey,
-    source_pubkey: &Pubkey,
-    delegate_pubkey: &Pubkey,
-    owner_pubkey: &Pubkey,
-    signer_pubkeys: &[&Pubkey],
-    amount: u64,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let data = TokenInstruction::Approve { amount }.pack();
-
-    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
-    accounts.push(AccountMeta::new(*source_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(*delegate_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(
-        *owner_pubkey,
-        signer_pubkeys.is_empty(),
-    ));
-    for signer_pubkey in signer_pubkeys.iter() {
-        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
-    }
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `Revoke` instruction.
-pub fn revoke(
-    token_program_id: &Pubkey,
-    source_pubkey: &Pubkey,
-    owner_pubkey: &Pubkey,
-    signer_pubkeys: &[&Pubkey],
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let data = TokenInstruction::Revoke.pack();
-
-    let mut accounts = Vec::with_capacity(2 + signer_pubkeys.len());
-    accounts.push(AccountMeta::new(*source_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(
-        *owner_pubkey,
-        signer_pubkeys.is_empty(),
-    ));
-    for signer_pubkey in signer_pubkeys.iter() {
-        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
-    }
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `SetAuthority` instruction.
-pub fn set_authority(
-    token_program_id: &Pubkey,
-    owned_pubkey: &Pubkey,
-    new_authority_pubkey: Option<&Pubkey>,
-    authority_type: AuthorityType,
-    owner_pubkey: &Pubkey,
-    signer_pubkeys: &[&Pubkey],
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let new_authority = new_authority_pubkey.cloned().into();
-    let data = TokenInstruction::SetAuthority {
-        authority_type,
-        new_authority,
-    }
-    .pack();
-
-    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
-    accounts.push(AccountMeta::new(*owned_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(
-        *owner_pubkey,
-        signer_pubkeys.is_empty(),
-    ));
-    for signer_pubkey in signer_pubkeys.iter() {
-        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
-    }
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `MintTo` instruction.
-pub fn mint_to(
-    token_program_id: &Pubkey,
-    mint_pubkey: &Pubkey,
-    account_pubkey: &Pubkey,
-    owner_pubkey: &Pubkey,
-    signer_pubkeys: &[&Pubkey],
-    amount: u64,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let data = TokenInstruction::MintTo { amount }.pack();
-
-    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
-    accounts.push(AccountMeta::new(*mint_pubkey, false));
-    accounts.push(AccountMeta::new(*account_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(
-        *owner_pubkey,
-        signer_pubkeys.is_empty(),
-    ));
-    for signer_pubkey in signer_pubkeys.iter() {
-        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
-    }
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `Burn` instruction.
-pub fn burn(
-    token_program_id: &Pubkey,
-    account_pubkey: &Pubkey,
-    mint_pubkey: &Pubkey,
-    authority_pubkey: &Pubkey,
-    signer_pubkeys: &[&Pubkey],
-    amount: u64,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let data = TokenInstruction::Burn { amount }.pack();
-
-    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
-    accounts.push(AccountMeta::new(*account_pubkey, false));
-    accounts.push(AccountMeta::new(*mint_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(
-        *authority_pubkey,
-        signer_pubkeys.is_empty(),
-    ));
-    for signer_pubkey in signer_pubkeys.iter() {
-        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
-    }
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `CloseAccount` instruction.
-pub fn close_account(
-    token_program_id: &Pubkey,
-    account_pubkey: &Pubkey,
-    destination_pubkey: &Pubkey,
-    owner_pubkey: &Pubkey,
-    signer_pubkeys: &[&Pubkey],
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let data = TokenInstruction::CloseAccount.pack();
-
-    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
-    accounts.push(AccountMeta::new(*account_pubkey, false));
-    accounts.push(AccountMeta::new(*destination_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(
-        *owner_pubkey,
-        signer_pubkeys.is_empty(),
-    ));
-    for signer_pubkey in signer_pubkeys.iter() {
-        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
-    }
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `FreezeAccount` instruction.
-pub fn freeze_account(
-    token_program_id: &Pubkey,
-    account_pubkey: &Pubkey,
-    mint_pubkey: &Pubkey,
-    owner_pubkey: &Pubkey,
-    signer_pubkeys: &[&Pubkey],
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let data = TokenInstruction::FreezeAccount.pack();
-
-    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
-    accounts.push(AccountMeta::new(*account_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(*mint_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(
-        *owner_pubkey,
-        signer_pubkeys.is_empty(),
-    ));
-    for signer_pubkey in signer_pubkeys.iter() {
-        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
-    }
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `ThawAccount` instruction.
-pub fn thaw_account(
-    token_program_id: &Pubkey,
-    account_pubkey: &Pubkey,
-    mint_pubkey: &Pubkey,
-    owner_pubkey: &Pubkey,
-    signer_pubkeys: &[&Pubkey],
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let data = TokenInstruction::ThawAccount.pack();
-
-    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
-    accounts.push(AccountMeta::new(*account_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(*mint_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(
-        *owner_pubkey,
-        signer_pubkeys.is_empty(),
-    ));
-    for signer_pubkey in signer_pubkeys.iter() {
-        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
-    }
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `TransferChecked` instruction.
-#[allow(clippy::too_many_arguments)]
-pub fn transfer_checked(
-    token_program_id: &Pubkey,
-    source_pubkey: &Pubkey,
-    mint_pubkey: &Pubkey,
-    destination_pubkey: &Pubkey,
-    authority_pubkey: &Pubkey,
-    signer_pubkeys: &[&Pubkey],
-    amount: u64,
-    decimals: u8,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let data = TokenInstruction::TransferChecked { amount, decimals }.pack();
-
-    let mut accounts = Vec::with_capacity(4 + signer_pubkeys.len());
-    accounts.push(AccountMeta::new(*source_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(*mint_pubkey, false));
-    accounts.push(AccountMeta::new(*destination_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(
-        *authority_pubkey,
-        signer_pubkeys.is_empty(),
-    ));
-    for signer_pubkey in signer_pubkeys.iter() {
-        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
-    }
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates an `ApproveChecked` instruction.
-#[allow(clippy::too_many_arguments)]
-pub fn approve_checked(
-    token_program_id: &Pubkey,
-    source_pubkey: &Pubkey,
-    mint_pubkey: &Pubkey,
-    delegate_pubkey: &Pubkey,
-    owner_pubkey: &Pubkey,
-    signer_pubkeys: &[&Pubkey],
-    amount: u64,
-    decimals: u8,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let data = TokenInstruction::ApproveChecked { amount, decimals }.pack();
-
-    let mut accounts = Vec::with_capacity(4 + signer_pubkeys.len());
-    accounts.push(AccountMeta::new(*source_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(*mint_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(*delegate_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(
-        *owner_pubkey,
-        signer_pubkeys.is_empty(),
-    ));
-    for signer_pubkey in signer_pubkeys.iter() {
-        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
-    }
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `MintToChecked` instruction.
-pub fn mint_to_checked(
-    token_program_id: &Pubkey,
-    mint_pubkey: &Pubkey,
-    account_pubkey: &Pubkey,
-    owner_pubkey: &Pubkey,
-    signer_pubkeys: &[&Pubkey],
-    amount: u64,
-    decimals: u8,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let data = TokenInstruction::MintToChecked { amount, decimals }.pack();
-
-    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
-    accounts.push(AccountMeta::new(*mint_pubkey, false));
-    accounts.push(AccountMeta::new(*account_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(
-        *owner_pubkey,
-        signer_pubkeys.is_empty(),
-    ));
-    for signer_pubkey in signer_pubkeys.iter() {
-        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
-    }
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `BurnChecked` instruction.
-pub fn burn_checked(
-    token_program_id: &Pubkey,
-    account_pubkey: &Pubkey,
-    mint_pubkey: &Pubkey,
-    authority_pubkey: &Pubkey,
-    signer_pubkeys: &[&Pubkey],
-    amount: u64,
-    decimals: u8,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    let data = TokenInstruction::BurnChecked { amount, decimals }.pack();
-
-    let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
-    accounts.push(AccountMeta::new(*account_pubkey, false));
-    accounts.push(AccountMeta::new(*mint_pubkey, false));
-    accounts.push(AccountMeta::new_readonly(
-        *authority_pubkey,
-        signer_pubkeys.is_empty(),
-    ));
-    for signer_pubkey in signer_pubkeys.iter() {
-        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
-    }
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts,
-        data,
-    })
-}
-
-/// Creates a `SyncNative` instruction
-pub fn sync_native(
-    token_program_id: &Pubkey,
-    account_pubkey: &Pubkey,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts: vec![AccountMeta::new(*account_pubkey, false)],
-        data: TokenInstruction::SyncNative.pack(),
-    })
-}
-
-/// Creates a `GetAccountDataSize` instruction
-pub fn get_account_data_size(
-    token_program_id: &Pubkey,
-    mint_pubkey: &Pubkey,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts: vec![AccountMeta::new_readonly(*mint_pubkey, false)],
-        data: TokenInstruction::GetAccountDataSize.pack(),
-    })
-}
-
-/// Creates a `InitializeImmutableOwner` instruction
-pub fn initialize_immutable_owner(
-    token_program_id: &Pubkey,
-    account_pubkey: &Pubkey,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts: vec![AccountMeta::new(*account_pubkey, false)],
-        data: TokenInstruction::InitializeImmutableOwner.pack(),
-    })
-}
-
-/// Creates an `AmountToUiAmount` instruction
-pub fn amount_to_ui_amount(
-    token_program_id: &Pubkey,
-    mint_pubkey: &Pubkey,
-    amount: u64,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts: vec![AccountMeta::new_readonly(*mint_pubkey, false)],
-        data: TokenInstruction::AmountToUiAmount { amount }.pack(),
-    })
-}
-
-/// Creates a `UiAmountToAmount` instruction
-pub fn ui_amount_to_amount(
-    token_program_id: &Pubkey,
-    mint_pubkey: &Pubkey,
-    ui_amount: &str,
-) -> Result<Instruction, ProgramError> {
-    check_program_account(token_program_id)?;
-
-    Ok(Instruction {
-        program_id: *token_program_id,
-        accounts: vec![AccountMeta::new_readonly(*mint_pubkey, false)],
-        data: TokenInstruction::UiAmountToAmount { ui_amount }.pack(),
-    })
-}
-
-/// Utility function that checks index is between `MIN_SIGNERS` and
-/// `MAX_SIGNERS`
-pub fn is_valid_signer_index(index: usize) -> bool {
-    (MIN_SIGNERS..=MAX_SIGNERS).contains(&index)
-}
-
-#[cfg(test)]
-mod test {
-    use {super::*, proptest::prelude::*};
-
-    #[test]
-    fn test_instruction_packing() {
-        let check = TokenInstruction::InitializeMint {
-            decimals: 2,
-            mint_authority: Pubkey::new_from_array([1u8; 32]),
-            freeze_authority: COption::None,
-        };
-        let packed = check.pack();
-        let mut expect = Vec::from([0u8, 2]);
-        expect.extend_from_slice(&[1u8; 32]);
-        expect.extend_from_slice(&[0]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::InitializeMint {
-            decimals: 2,
-            mint_authority: Pubkey::new_from_array([2u8; 32]),
-            freeze_authority: COption::Some(Pubkey::new_from_array([3u8; 32])),
-        };
-        let packed = check.pack();
-        let mut expect = vec![0u8, 2];
-        expect.extend_from_slice(&[2u8; 32]);
-        expect.extend_from_slice(&[1]);
-        expect.extend_from_slice(&[3u8; 32]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::InitializeAccount;
-        let packed = check.pack();
-        let expect = Vec::from([1u8]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::InitializeMultisig { m: 1 };
-        let packed = check.pack();
-        let expect = Vec::from([2u8, 1]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::Transfer { amount: 1 };
-        let packed = check.pack();
-        let expect = Vec::from([3u8, 1, 0, 0, 0, 0, 0, 0, 0]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::Approve { amount: 1 };
-        let packed = check.pack();
-        let expect = Vec::from([4u8, 1, 0, 0, 0, 0, 0, 0, 0]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::Revoke;
-        let packed = check.pack();
-        let expect = Vec::from([5u8]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::SetAuthority {
-            authority_type: AuthorityType::FreezeAccount,
-            new_authority: COption::Some(Pubkey::new_from_array([4u8; 32])),
-        };
-        let packed = check.pack();
-        let mut expect = Vec::from([6u8, 1]);
-        expect.extend_from_slice(&[1]);
-        expect.extend_from_slice(&[4u8; 32]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::MintTo { amount: 1 };
-        let packed = check.pack();
-        let expect = Vec::from([7u8, 1, 0, 0, 0, 0, 0, 0, 0]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::Burn { amount: 1 };
-        let packed = check.pack();
-        let expect = Vec::from([8u8, 1, 0, 0, 0, 0, 0, 0, 0]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::CloseAccount;
-        let packed = check.pack();
-        let expect = Vec::from([9u8]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::FreezeAccount;
-        let packed = check.pack();
-        let expect = Vec::from([10u8]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::ThawAccount;
-        let packed = check.pack();
-        let expect = Vec::from([11u8]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::TransferChecked {
-            amount: 1,
-            decimals: 2,
-        };
-        let packed = check.pack();
-        let expect = Vec::from([12u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::ApproveChecked {
-            amount: 1,
-            decimals: 2,
-        };
-        let packed = check.pack();
-        let expect = Vec::from([13u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::MintToChecked {
-            amount: 1,
-            decimals: 2,
-        };
-        let packed = check.pack();
-        let expect = Vec::from([14u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::BurnChecked {
-            amount: 1,
-            decimals: 2,
-        };
-        let packed = check.pack();
-        let expect = Vec::from([15u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::InitializeAccount2 {
-            owner: Pubkey::new_from_array([2u8; 32]),
-        };
-        let packed = check.pack();
-        let mut expect = vec![16u8];
-        expect.extend_from_slice(&[2u8; 32]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::SyncNative;
-        let packed = check.pack();
-        let expect = vec![17u8];
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::InitializeAccount3 {
-            owner: Pubkey::new_from_array([2u8; 32]),
-        };
-        let packed = check.pack();
-        let mut expect = vec![18u8];
-        expect.extend_from_slice(&[2u8; 32]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::InitializeMultisig2 { m: 1 };
-        let packed = check.pack();
-        let expect = Vec::from([19u8, 1]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::InitializeMint2 {
-            decimals: 2,
-            mint_authority: Pubkey::new_from_array([1u8; 32]),
-            freeze_authority: COption::None,
-        };
-        let packed = check.pack();
-        let mut expect = Vec::from([20u8, 2]);
-        expect.extend_from_slice(&[1u8; 32]);
-        expect.extend_from_slice(&[0]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::InitializeMint2 {
-            decimals: 2,
-            mint_authority: Pubkey::new_from_array([2u8; 32]),
-            freeze_authority: COption::Some(Pubkey::new_from_array([3u8; 32])),
-        };
-        let packed = check.pack();
-        let mut expect = vec![20u8, 2];
-        expect.extend_from_slice(&[2u8; 32]);
-        expect.extend_from_slice(&[1]);
-        expect.extend_from_slice(&[3u8; 32]);
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::GetAccountDataSize;
-        let packed = check.pack();
-        let expect = vec![21u8];
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::InitializeImmutableOwner;
-        let packed = check.pack();
-        let expect = vec![22u8];
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::AmountToUiAmount { amount: 42 };
-        let packed = check.pack();
-        let expect = vec![23u8, 42, 0, 0, 0, 0, 0, 0, 0];
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-
-        let check = TokenInstruction::UiAmountToAmount { ui_amount: "0.42" };
-        let packed = check.pack();
-        let expect = vec![24u8, 48, 46, 52, 50];
-        assert_eq!(packed, expect);
-        let unpacked = TokenInstruction::unpack(&expect).unwrap();
-        assert_eq!(unpacked, check);
-    }
-
-    #[test]
-    fn test_instruction_unpack_panic() {
-        for i in 0..255u8 {
-            for j in 1..10 {
-                let mut data = vec![0; j];
-                data[0] = i;
-                let _no_panic = TokenInstruction::unpack(&data);
-            }
-        }
-    }
-
-    proptest! {
-        #![proptest_config(ProptestConfig::with_cases(1024))]
-        #[test]
-        fn test_instruction_unpack_proptest(
-            data in prop::collection::vec(any::<u8>(), 0..255)
-        ) {
-            let _no_panic = TokenInstruction::unpack(&data);
-        }
-    }
-}
+pub use spl_token_interface::instruction::*;

+ 3 - 14
program/src/lib.rs

@@ -36,10 +36,9 @@ pub mod solana_program {
         pub use solana_pubkey::{Pubkey, PUBKEY_BYTES};
     }
 }
-use {
-    solana_program_error::{ProgramError, ProgramResult},
-    solana_pubkey::Pubkey,
-};
+use solana_program_error::ProgramError;
+// Re-export spl_token_interface items
+pub use spl_token_interface::{check_id, check_program_account, id, ID};
 
 /// Convert the UI representation of a token amount (using the decimals field
 /// defined in its mint) to the raw amount
@@ -104,13 +103,3 @@ pub fn try_ui_amount_into_amount(ui_amount: String, decimals: u8) -> Result<u64,
         .parse::<u64>()
         .map_err(|_| ProgramError::InvalidArgument)
 }
-
-solana_pubkey::declare_id!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
-
-/// Checks that the supplied program ID is the correct one for SPL-token
-pub fn check_program_account(spl_token_program_id: &Pubkey) -> ProgramResult {
-    if spl_token_program_id != &id() {
-        return Err(ProgramError::IncorrectProgramId);
-    }
-    Ok(())
-}

+ 1 - 19
program/src/native_mint.rs

@@ -1,20 +1,2 @@
 //! The Mint that represents the native token
-
-/// There are `10^9` lamports in one SOL
-pub const DECIMALS: u8 = 9;
-
-// The Mint for native SOL Token accounts
-solana_pubkey::declare_id!("So11111111111111111111111111111111111111112");
-
-#[cfg(test)]
-mod tests {
-    use {super::*, solana_native_token::sol_str_to_lamports};
-
-    #[test]
-    fn test_decimals() {
-        assert_eq!(
-            sol_str_to_lamports("42.").unwrap(),
-            crate::try_ui_amount_into_amount("42.".to_string(), DECIMALS).unwrap()
-        );
-    }
-}
+pub use spl_token_interface::native_mint::*;

+ 1 - 489
program/src/state.rs

@@ -1,490 +1,2 @@
 //! State transition types
-
-use {
-    crate::instruction::MAX_SIGNERS,
-    arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs},
-    num_enum::TryFromPrimitive,
-    solana_program_error::ProgramError,
-    solana_program_option::COption,
-    solana_program_pack::{IsInitialized, Pack, Sealed},
-    solana_pubkey::{Pubkey, PUBKEY_BYTES},
-};
-
-/// Mint data.
-#[repr(C)]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
-pub struct Mint {
-    /// 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.
-    pub mint_authority: COption<Pubkey>,
-    /// Total supply of tokens.
-    pub supply: u64,
-    /// Number of base 10 digits to the right of the decimal place.
-    pub decimals: u8,
-    /// Is `true` if this structure has been initialized
-    pub is_initialized: bool,
-    /// Optional authority to freeze token accounts.
-    pub freeze_authority: COption<Pubkey>,
-}
-impl Sealed for Mint {}
-impl IsInitialized for Mint {
-    fn is_initialized(&self) -> bool {
-        self.is_initialized
-    }
-}
-impl Pack for Mint {
-    const LEN: usize = 82;
-    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
-        let src = array_ref![src, 0, 82];
-        let (mint_authority, supply, decimals, is_initialized, freeze_authority) =
-            array_refs![src, 36, 8, 1, 1, 36];
-        let mint_authority = unpack_coption_key(mint_authority)?;
-        let supply = u64::from_le_bytes(*supply);
-        let decimals = decimals[0];
-        let is_initialized = match is_initialized {
-            [0] => false,
-            [1] => true,
-            _ => return Err(ProgramError::InvalidAccountData),
-        };
-        let freeze_authority = unpack_coption_key(freeze_authority)?;
-        Ok(Mint {
-            mint_authority,
-            supply,
-            decimals,
-            is_initialized,
-            freeze_authority,
-        })
-    }
-    fn pack_into_slice(&self, dst: &mut [u8]) {
-        let dst = array_mut_ref![dst, 0, 82];
-        let (
-            mint_authority_dst,
-            supply_dst,
-            decimals_dst,
-            is_initialized_dst,
-            freeze_authority_dst,
-        ) = mut_array_refs![dst, 36, 8, 1, 1, 36];
-        let &Mint {
-            ref mint_authority,
-            supply,
-            decimals,
-            is_initialized,
-            ref freeze_authority,
-        } = self;
-        pack_coption_key(mint_authority, mint_authority_dst);
-        *supply_dst = supply.to_le_bytes();
-        decimals_dst[0] = decimals;
-        is_initialized_dst[0] = is_initialized as u8;
-        pack_coption_key(freeze_authority, freeze_authority_dst);
-    }
-}
-
-/// Account data.
-#[repr(C)]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
-pub struct Account {
-    /// The mint associated with this account
-    pub mint: Pubkey,
-    /// The owner of this account.
-    pub owner: Pubkey,
-    /// The amount of tokens this account holds.
-    pub amount: u64,
-    /// If `delegate` is `Some` then `delegated_amount` represents
-    /// the amount authorized by the delegate
-    pub delegate: COption<Pubkey>,
-    /// The account's state
-    pub state: AccountState,
-    /// 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.
-    pub is_native: COption<u64>,
-    /// The amount delegated
-    pub delegated_amount: u64,
-    /// Optional authority to close the account.
-    pub close_authority: COption<Pubkey>,
-}
-impl Account {
-    /// Checks if account is frozen
-    pub fn is_frozen(&self) -> bool {
-        self.state == AccountState::Frozen
-    }
-    /// Checks if account is native
-    pub fn is_native(&self) -> bool {
-        self.is_native.is_some()
-    }
-    /// Checks if a token Account's owner is the `system_program` or the
-    /// incinerator
-    pub fn is_owned_by_system_program_or_incinerator(&self) -> bool {
-        solana_sdk_ids::system_program::check_id(&self.owner)
-            || solana_sdk_ids::incinerator::check_id(&self.owner)
-    }
-}
-impl Sealed for Account {}
-impl IsInitialized for Account {
-    fn is_initialized(&self) -> bool {
-        self.state != AccountState::Uninitialized
-    }
-}
-impl Pack for Account {
-    const LEN: usize = 165;
-    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
-        let src = array_ref![src, 0, 165];
-        let (mint, owner, amount, delegate, state, is_native, delegated_amount, close_authority) =
-            array_refs![src, 32, 32, 8, 36, 1, 12, 8, 36];
-        Ok(Account {
-            mint: Pubkey::new_from_array(*mint),
-            owner: Pubkey::new_from_array(*owner),
-            amount: u64::from_le_bytes(*amount),
-            delegate: unpack_coption_key(delegate)?,
-            state: AccountState::try_from_primitive(state[0])
-                .or(Err(ProgramError::InvalidAccountData))?,
-            is_native: unpack_coption_u64(is_native)?,
-            delegated_amount: u64::from_le_bytes(*delegated_amount),
-            close_authority: unpack_coption_key(close_authority)?,
-        })
-    }
-    fn pack_into_slice(&self, dst: &mut [u8]) {
-        let dst = array_mut_ref![dst, 0, 165];
-        let (
-            mint_dst,
-            owner_dst,
-            amount_dst,
-            delegate_dst,
-            state_dst,
-            is_native_dst,
-            delegated_amount_dst,
-            close_authority_dst,
-        ) = mut_array_refs![dst, 32, 32, 8, 36, 1, 12, 8, 36];
-        let &Account {
-            ref mint,
-            ref owner,
-            amount,
-            ref delegate,
-            state,
-            ref is_native,
-            delegated_amount,
-            ref close_authority,
-        } = self;
-        mint_dst.copy_from_slice(mint.as_ref());
-        owner_dst.copy_from_slice(owner.as_ref());
-        *amount_dst = amount.to_le_bytes();
-        pack_coption_key(delegate, delegate_dst);
-        state_dst[0] = state as u8;
-        pack_coption_u64(is_native, is_native_dst);
-        *delegated_amount_dst = delegated_amount.to_le_bytes();
-        pack_coption_key(close_authority, close_authority_dst);
-    }
-}
-
-/// Account state.
-#[repr(u8)]
-#[derive(Clone, Copy, Debug, Default, PartialEq, TryFromPrimitive)]
-pub enum AccountState {
-    /// Account is not yet initialized
-    #[default]
-    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,
-}
-
-/// Multisignature data.
-#[repr(C)]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
-pub struct Multisig {
-    /// Number of signers required
-    pub m: u8,
-    /// Number of valid signers
-    pub n: u8,
-    /// Is `true` if this structure has been initialized
-    pub is_initialized: bool,
-    /// Signer public keys
-    pub signers: [Pubkey; MAX_SIGNERS],
-}
-impl Sealed for Multisig {}
-impl IsInitialized for Multisig {
-    fn is_initialized(&self) -> bool {
-        self.is_initialized
-    }
-}
-impl Pack for Multisig {
-    const LEN: usize = 355;
-    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
-        let src = array_ref![src, 0, 355];
-        #[allow(clippy::ptr_offset_with_cast)]
-        let (m, n, is_initialized, signers_flat) = array_refs![src, 1, 1, 1, 32 * MAX_SIGNERS];
-        let mut result = Multisig {
-            m: m[0],
-            n: n[0],
-            is_initialized: match is_initialized {
-                [0] => false,
-                [1] => true,
-                _ => return Err(ProgramError::InvalidAccountData),
-            },
-            signers: [Pubkey::new_from_array([0u8; 32]); MAX_SIGNERS],
-        };
-        for (src, dst) in signers_flat.chunks(32).zip(result.signers.iter_mut()) {
-            *dst = Pubkey::try_from(src).map_err(|_| ProgramError::InvalidAccountData)?;
-        }
-        Ok(result)
-    }
-    fn pack_into_slice(&self, dst: &mut [u8]) {
-        let dst = array_mut_ref![dst, 0, 355];
-        #[allow(clippy::ptr_offset_with_cast)]
-        let (m, n, is_initialized, signers_flat) = mut_array_refs![dst, 1, 1, 1, 32 * MAX_SIGNERS];
-        *m = [self.m];
-        *n = [self.n];
-        *is_initialized = [self.is_initialized as u8];
-        for (i, src) in self.signers.iter().enumerate() {
-            let dst_array = array_mut_ref![signers_flat, 32 * i, 32];
-            dst_array.copy_from_slice(src.as_ref());
-        }
-    }
-}
-
-// Helpers
-fn pack_coption_key(src: &COption<Pubkey>, dst: &mut [u8; 36]) {
-    let (tag, body) = mut_array_refs![dst, 4, 32];
-    match src {
-        COption::Some(key) => {
-            *tag = [1, 0, 0, 0];
-            body.copy_from_slice(key.as_ref());
-        }
-        COption::None => {
-            *tag = [0; 4];
-        }
-    }
-}
-fn unpack_coption_key(src: &[u8; 36]) -> Result<COption<Pubkey>, ProgramError> {
-    let (tag, body) = array_refs![src, 4, 32];
-    match *tag {
-        [0, 0, 0, 0] => Ok(COption::None),
-        [1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))),
-        _ => Err(ProgramError::InvalidAccountData),
-    }
-}
-fn pack_coption_u64(src: &COption<u64>, dst: &mut [u8; 12]) {
-    let (tag, body) = mut_array_refs![dst, 4, 8];
-    match src {
-        COption::Some(amount) => {
-            *tag = [1, 0, 0, 0];
-            *body = amount.to_le_bytes();
-        }
-        COption::None => {
-            *tag = [0; 4];
-        }
-    }
-}
-fn unpack_coption_u64(src: &[u8; 12]) -> Result<COption<u64>, ProgramError> {
-    let (tag, body) = array_refs![src, 4, 8];
-    match *tag {
-        [0, 0, 0, 0] => Ok(COption::None),
-        [1, 0, 0, 0] => Ok(COption::Some(u64::from_le_bytes(*body))),
-        _ => Err(ProgramError::InvalidAccountData),
-    }
-}
-
-const SPL_TOKEN_ACCOUNT_MINT_OFFSET: usize = 0;
-const SPL_TOKEN_ACCOUNT_OWNER_OFFSET: usize = 32;
-
-/// A trait for token Account structs to enable efficiently unpacking various
-/// fields without unpacking the complete state.
-pub trait GenericTokenAccount {
-    /// Check if the account data is a valid token account
-    fn valid_account_data(account_data: &[u8]) -> bool;
-
-    /// Call after account length has already been verified to unpack the
-    /// account owner
-    fn unpack_account_owner_unchecked(account_data: &[u8]) -> &Pubkey {
-        Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_OWNER_OFFSET)
-    }
-
-    /// Call after account length has already been verified to unpack the
-    /// account mint
-    fn unpack_account_mint_unchecked(account_data: &[u8]) -> &Pubkey {
-        Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_MINT_OFFSET)
-    }
-
-    /// Call after account length has already been verified to unpack a Pubkey
-    /// at the specified offset. Panics if `account_data.len()` is less than
-    /// `PUBKEY_BYTES`
-    fn unpack_pubkey_unchecked(account_data: &[u8], offset: usize) -> &Pubkey {
-        bytemuck::from_bytes(&account_data[offset..offset + PUBKEY_BYTES])
-    }
-
-    /// Unpacks an account's owner from opaque account data.
-    fn unpack_account_owner(account_data: &[u8]) -> Option<&Pubkey> {
-        if Self::valid_account_data(account_data) {
-            Some(Self::unpack_account_owner_unchecked(account_data))
-        } else {
-            None
-        }
-    }
-
-    /// Unpacks an account's mint from opaque account data.
-    fn unpack_account_mint(account_data: &[u8]) -> Option<&Pubkey> {
-        if Self::valid_account_data(account_data) {
-            Some(Self::unpack_account_mint_unchecked(account_data))
-        } else {
-            None
-        }
-    }
-}
-
-/// The offset of state field in Account's C representation
-pub const ACCOUNT_INITIALIZED_INDEX: usize = 108;
-
-/// Check if the account data buffer represents an initialized account.
-/// This is checking the `state` (`AccountState`) field of an Account object.
-pub fn is_initialized_account(account_data: &[u8]) -> bool {
-    *account_data
-        .get(ACCOUNT_INITIALIZED_INDEX)
-        .unwrap_or(&(AccountState::Uninitialized as u8))
-        != AccountState::Uninitialized as u8
-}
-
-impl GenericTokenAccount for Account {
-    fn valid_account_data(account_data: &[u8]) -> bool {
-        account_data.len() == Account::LEN && is_initialized_account(account_data)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_mint_unpack_from_slice() {
-        let src: [u8; 82] = [0; 82];
-        let mint = Mint::unpack_from_slice(&src).unwrap();
-        assert!(!mint.is_initialized);
-
-        let mut src: [u8; 82] = [0; 82];
-        src[45] = 2;
-        let mint = Mint::unpack_from_slice(&src).unwrap_err();
-        assert_eq!(mint, ProgramError::InvalidAccountData);
-    }
-
-    #[test]
-    fn test_account_state() {
-        let account_state = AccountState::default();
-        assert_eq!(account_state, AccountState::Uninitialized);
-    }
-
-    #[test]
-    fn test_multisig_unpack_from_slice() {
-        let src: [u8; 355] = [0; 355];
-        let multisig = Multisig::unpack_from_slice(&src).unwrap();
-        assert_eq!(multisig.m, 0);
-        assert_eq!(multisig.n, 0);
-        assert!(!multisig.is_initialized);
-
-        let mut src: [u8; 355] = [0; 355];
-        src[0] = 1;
-        src[1] = 1;
-        src[2] = 1;
-        let multisig = Multisig::unpack_from_slice(&src).unwrap();
-        assert_eq!(multisig.m, 1);
-        assert_eq!(multisig.n, 1);
-        assert!(multisig.is_initialized);
-
-        let mut src: [u8; 355] = [0; 355];
-        src[2] = 2;
-        let multisig = Multisig::unpack_from_slice(&src).unwrap_err();
-        assert_eq!(multisig, ProgramError::InvalidAccountData);
-    }
-
-    #[test]
-    fn test_unpack_coption_key() {
-        let src: [u8; 36] = [0; 36];
-        let result = unpack_coption_key(&src).unwrap();
-        assert_eq!(result, COption::None);
-
-        let mut src: [u8; 36] = [0; 36];
-        src[1] = 1;
-        let result = unpack_coption_key(&src).unwrap_err();
-        assert_eq!(result, ProgramError::InvalidAccountData);
-    }
-
-    #[test]
-    fn test_unpack_coption_u64() {
-        let src: [u8; 12] = [0; 12];
-        let result = unpack_coption_u64(&src).unwrap();
-        assert_eq!(result, COption::None);
-
-        let mut src: [u8; 12] = [0; 12];
-        src[0] = 1;
-        let result = unpack_coption_u64(&src).unwrap();
-        assert_eq!(result, COption::Some(0));
-
-        let mut src: [u8; 12] = [0; 12];
-        src[1] = 1;
-        let result = unpack_coption_u64(&src).unwrap_err();
-        assert_eq!(result, ProgramError::InvalidAccountData);
-    }
-
-    #[test]
-    fn test_unpack_token_owner() {
-        // Account data length < Account::LEN, unpack will not return a key
-        let src: [u8; 12] = [0; 12];
-        let result = Account::unpack_account_owner(&src);
-        assert_eq!(result, Option::None);
-
-        // The right account data size and initialized, unpack will return some key
-        let mut src: [u8; Account::LEN] = [0; Account::LEN];
-        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
-        let result = Account::unpack_account_owner(&src);
-        assert!(result.is_some());
-
-        // The right account data size and frozen, unpack will return some key
-        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
-        let result = Account::unpack_account_owner(&src);
-        assert!(result.is_some());
-
-        // The right account data size and uninitialized, unpack will return None
-        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
-        let result = Account::unpack_account_mint(&src);
-        assert_eq!(result, Option::None);
-
-        // Account data length > account data size, unpack will not return a key
-        let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
-        let result = Account::unpack_account_owner(&src);
-        assert_eq!(result, Option::None);
-    }
-
-    #[test]
-    fn test_unpack_token_mint() {
-        // Account data length < Account::LEN, unpack will not return a key
-        let src: [u8; 12] = [0; 12];
-        let result = Account::unpack_account_mint(&src);
-        assert_eq!(result, Option::None);
-
-        // The right account data size and initialized, unpack will return some key
-        let mut src: [u8; Account::LEN] = [0; Account::LEN];
-        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
-        let result = Account::unpack_account_mint(&src);
-        assert!(result.is_some());
-
-        // The right account data size and frozen, unpack will return some key
-        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
-        let result = Account::unpack_account_mint(&src);
-        assert!(result.is_some());
-
-        // The right account data size and uninitialized, unpack will return None
-        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
-        let result = Account::unpack_account_mint(&src);
-        assert_eq!(result, Option::None);
-
-        // Account data length > account data size, unpack will not return a key
-        let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
-        let result = Account::unpack_account_mint(&src);
-        assert_eq!(result, Option::None);
-    }
-}
+pub use spl_token_interface::state::*;

+ 1 - 1
program/tests/assert_instruction_count.rs

@@ -5,7 +5,7 @@ use {
     solana_account::{Account as SolanaAccount, ReadableAccount},
     solana_program_pack::Pack,
     solana_pubkey::Pubkey,
-    spl_token::{
+    spl_token_interface::{
         id, instruction,
         state::{Account, Mint},
     },

+ 1 - 1
program/tests/processor.rs

@@ -12,7 +12,7 @@ use {
     solana_pubkey::Pubkey,
     solana_rent::Rent,
     solana_sdk_ids::sysvar::rent,
-    spl_token::{
+    spl_token_interface::{
         error::TokenError,
         instruction::{
             amount_to_ui_amount, approve, approve_checked, burn, burn_checked, close_account,