Kaynağa Gözat

Relocate program2 into program

Michael Vines 5 yıl önce
ebeveyn
işleme
83d9488261

+ 34 - 0
program/Cargo.toml

@@ -0,0 +1,34 @@
+
+# Note: This crate must be built using do.sh
+
+[package]
+name = "spl-token"
+version = "1.1.0"
+description = "Solana Program Library Token"
+authors = ["Solana Maintainers <maintainers@solana.foundation>"]
+repository = "https://github.com/solana-labs/solana-program-library"
+license = "Apache-2.0"
+edition = "2018"
+exclude = ["js/**"]
+
+[features]
+no-entrypoint = []
+skip-no-mangle = ["solana-sdk/skip-no-mangle"]
+program = ["solana-sdk/program"]
+default = ["solana-sdk/default"]
+
+[dependencies]
+num-derive = "0.3"
+num-traits = "0.2"
+remove_dir_all = "=0.5.0"
+solana-sdk = { version = "1.3.4", default-features = false, optional = true }
+thiserror = "1.0"
+
+[dev-dependencies]
+rand = { version = "0.7.0"}
+
+[build-dependencies]
+cbindgen = "=0.14.2"
+
+[lib]
+crate-type = ["cdylib", "lib"]

+ 2 - 0
program/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 65 - 0
program/build.rs

@@ -0,0 +1,65 @@
+extern crate cbindgen;
+
+use std::env;
+
+fn main() {
+    println!("cargo:rerun-if-env-changed=SPL_CBINDGEN");
+    println!("cargo:rerun-if-changed=inc/token.h");
+    if std::path::Path::new("inc/token.h").exists() && env::var("SPL_CBINDGEN").is_err() {
+        return;
+    }
+
+    println!("cargo:warning=Generating inc/token.h");
+    let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
+    let config = cbindgen::Config {
+        header: Some("/* Autogenerated SPL Token program C Bindings */".to_string()),
+        after_includes: Some(format!(
+            "{}{}{}",
+            format!(
+                "\n#define TOKEN_MAJOR_VERSION {}",
+                env!("CARGO_PKG_VERSION_MAJOR")
+            ),
+            format!(
+                "\n#define TOKEN_MINOR_VERSION {}",
+                env!("CARGO_PKG_VERSION_MINOR")
+            ),
+            format!(
+                "\n#define TOKEN_PATCH_VERSION {}",
+                env!("CARGO_PKG_VERSION_PATCH")
+            )
+        )),
+        language: cbindgen::Language::C,
+        line_length: 80,
+        style: cbindgen::Style::Both,
+        tab_width: 4,
+        cpp_compat: true,
+        pragma_once: true,
+        enumeration: cbindgen::EnumConfig {
+            prefix_with_name: true,
+            ..cbindgen::EnumConfig::default()
+        },
+        export: cbindgen::ExportConfig {
+            prefix: Some("Token_".to_string()),
+            include: vec![
+                "TokenInstruction".to_string(),
+                "Mint".to_string(),
+                "Account".to_string(),
+                "Multisig".to_string(),
+            ],
+            exclude: vec!["DECIMALS".to_string()],
+            ..cbindgen::ExportConfig::default()
+        },
+        parse: cbindgen::ParseConfig {
+            parse_deps: true,
+            include: Some(vec!["solana-sdk".to_string()]),
+            ..cbindgen::ParseConfig::default()
+        },
+        ..cbindgen::Config::default()
+    };
+    cbindgen::Builder::new()
+        .with_crate(crate_dir)
+        .with_config(config)
+        .generate()
+        .unwrap()
+        .write_to_file("inc/token.h");
+}

+ 359 - 0
program/inc/token.h

@@ -0,0 +1,359 @@
+/* Autogenerated SPL Token program C Bindings */
+
+#pragma once
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#define TOKEN_MAJOR_VERSION 1
+#define TOKEN_MINOR_VERSION 1
+#define TOKEN_PATCH_VERSION 0
+
+/**
+ * Maximum number of multisignature signers (max N)
+ */
+#define Token_MAX_SIGNERS 11
+
+/**
+ * Minimum number of multisignature signers (min N)
+ */
+#define Token_MIN_SIGNERS 1
+
+/**
+ * Instructions supported by the token program.
+ */
+typedef enum Token_TokenInstruction_Tag {
+    /**
+     * 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 `CreateInstruction` 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.
+     *      * If supply is non-zero: `[writable]` The account to hold all the newly minted tokens.
+     *      * If supply is zero: `[]` The owner/multisignature of the mint.
+     *   2. `[]` (optional) The owner/multisignature of the mint if supply is non-zero, if
+     *                      present then further minting is supported.
+     *
+     */
+    Token_TokenInstruction_InitializeMint,
+    /**
+     * 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.
+     *
+     * The `InitializeAccount` instruction requires no signers and MUST be included within
+     * the same Transaction as the system program's `CreateInstruction` 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.
+     */
+    Token_TokenInstruction_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 `CreateInstruction` 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. ..1+N. `[]` The signer accounts, must equal to N where 1 <= N <= 11.
+     */
+    Token_TokenInstruction_InitializeMultisig,
+    /**
+     * 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.
+     */
+    Token_TokenInstruction_Transfer,
+    /**
+     * 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
+     */
+    Token_TokenInstruction_Approve,
+    /**
+     * 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
+     */
+    Token_TokenInstruction_Revoke,
+    /**
+     * Sets a new owner of a mint or account.
+     *
+     * Accounts expected by this instruction:
+     *
+     *   * Single owner
+     *   0. `[writable]` The mint or account to change the owner of.
+     *   1. `[]` The new owner/delegate/multisignature.
+     *   2. `[signer]` The owner of the mint or account.
+     *
+     *   * Multisignature owner
+     *   0. `[writable]` The mint or account to change the owner of.
+     *   1. `[]` The new owner/delegate/multisignature.
+     *   2. `[]` The mint's or account's multisignature owner.
+     *   3. ..3+M '[signer]' M signer accounts
+     */
+    Token_TokenInstruction_SetOwner,
+    /**
+     * Mints new tokens to an account.  The native mint does not support minting.
+     *
+     * Accounts expected by this instruction:
+     *
+     *   * Single owner
+     *   0. `[writable]` The mint.
+     *   1. `[writable]` The account to mint tokens to.
+     *   2. `[signer]` The mint's owner.
+     *
+     *   * Multisignature owner
+     *   0. `[writable]` The mint.
+     *   1. `[writable]` The account to mint tokens to.
+     *   2. `[]` The mint's multisignature owner.
+     *   3. ..3+M '[signer]' M signer accounts.
+     */
+    Token_TokenInstruction_MintTo,
+    /**
+     * 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. `[signer]` The account's owner/delegate.
+     *
+     *   * Multisignature owner/delegate
+     *   0. `[writable]` The account to burn from.
+     *   1. `[]` The account's multisignature owner/delegate.
+     *   2. ..2+M '[signer]' M signer accounts.
+     */
+    Token_TokenInstruction_Burn,
+    /**
+     * 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.
+     */
+    Token_TokenInstruction_CloseAccount,
+} Token_TokenInstruction_Tag;
+
+typedef struct Token_TokenInstruction_Token_InitializeMint_Body {
+    /**
+     * Initial amount of tokens to mint.
+     */
+    uint64_t amount;
+    /**
+     * Number of base 10 digits to the right of the decimal place.
+     */
+    uint8_t decimals;
+} Token_TokenInstruction_Token_InitializeMint_Body;
+
+typedef struct Token_TokenInstruction_Token_InitializeMultisig_Body {
+    /**
+     * The number of signers (M) required to validate this multisignature account.
+     */
+    uint8_t m;
+} Token_TokenInstruction_Token_InitializeMultisig_Body;
+
+typedef struct Token_TokenInstruction_Token_Transfer_Body {
+    /**
+     * The amount of tokens to transfer.
+     */
+    uint64_t amount;
+} Token_TokenInstruction_Token_Transfer_Body;
+
+typedef struct Token_TokenInstruction_Token_Approve_Body {
+    /**
+     * The amount of tokens the delegate is approved for.
+     */
+    uint64_t amount;
+} Token_TokenInstruction_Token_Approve_Body;
+
+typedef struct Token_TokenInstruction_Token_MintTo_Body {
+    /**
+     * The amount of new tokens to mint.
+     */
+    uint64_t amount;
+} Token_TokenInstruction_Token_MintTo_Body;
+
+typedef struct Token_TokenInstruction_Token_Burn_Body {
+    /**
+     * The amount of tokens to burn.
+     */
+    uint64_t amount;
+} Token_TokenInstruction_Token_Burn_Body;
+
+typedef struct Token_TokenInstruction {
+    Token_TokenInstruction_Tag tag;
+    union {
+        Token_TokenInstruction_Token_InitializeMint_Body initialize_mint;
+        Token_TokenInstruction_Token_InitializeMultisig_Body initialize_multisig;
+        Token_TokenInstruction_Token_Transfer_Body transfer;
+        Token_TokenInstruction_Token_Approve_Body approve;
+        Token_TokenInstruction_Token_MintTo_Body mint_to;
+        Token_TokenInstruction_Token_Burn_Body burn;
+    };
+} Token_TokenInstruction;
+
+typedef uint8_t Token_Pubkey[32];
+
+/**
+ * A C representation of Rust's `std::option::Option`
+ */
+typedef enum Token_COption_Pubkey_Tag {
+    /**
+     * No value
+     */
+    Token_COption_Pubkey_None_Pubkey,
+    /**
+     * Some value `T`
+     */
+    Token_COption_Pubkey_Some_Pubkey,
+} Token_COption_Pubkey_Tag;
+
+typedef struct Token_COption_Pubkey_Token_Some_Body_Pubkey {
+    Token_Pubkey _0;
+} Token_COption_Pubkey_Token_Some_Body_Pubkey;
+
+typedef struct Token_COption_Pubkey {
+    Token_COption_Pubkey_Tag tag;
+    union {
+        Token_COption_Pubkey_Token_Some_Body_Pubkey some;
+    };
+} Token_COption_Pubkey;
+
+/**
+ * Mint data.
+ */
+typedef struct Token_Mint {
+    /**
+     * Optional owner, used to mint new tokens.  The owner may only
+     * be provided during mint creation.  If no owner is present then the mint
+     * has a fixed supply and no further tokens may be minted.
+     */
+    Token_COption_Pubkey owner;
+    /**
+     * Number of base 10 digits to the right of the decimal place.
+     */
+    uint8_t decimals;
+    /**
+     * Is `true` if this structure has been initialized
+     */
+    bool is_initialized;
+} Token_Mint;
+
+/**
+ * Account data.
+ */
+typedef struct Token_Account {
+    /**
+     * The mint associated with this account
+     */
+    Token_Pubkey mint;
+    /**
+     * The owner of this account.
+     */
+    Token_Pubkey owner;
+    /**
+     * The amount of tokens this account holds.
+     */
+    uint64_t amount;
+    /**
+     * If `delegate` is `Some` then `delegated_amount` represents
+     * the amount authorized by the delegate
+     */
+    Token_COption_Pubkey delegate;
+    /**
+     * Is `true` if this structure has been initialized
+     */
+    bool is_initialized;
+    /**
+     * Is this a native token
+     */
+    bool is_native;
+    /**
+     * The amount delegated
+     */
+    uint64_t delegated_amount;
+} Token_Account;
+
+/**
+ * Multisignature data.
+ */
+typedef struct Token_Multisig {
+    /**
+     * Number of signers required
+     */
+    uint8_t m;
+    /**
+     * Number of valid signers
+     */
+    uint8_t n;
+    /**
+     * Is `true` if this structure has been initialized
+     */
+    bool is_initialized;
+    /**
+     * Signer public keys
+     */
+    Token_Pubkey signers[Token_MAX_SIGNERS];
+} Token_Multisig;

+ 1 - 0
program/program-id.md

@@ -0,0 +1 @@
+TokensVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o

+ 24 - 0
program/src/entrypoint.rs

@@ -0,0 +1,24 @@
+//! Program entrypoint
+
+#![cfg(feature = "program")]
+#![cfg(not(feature = "no-entrypoint"))]
+
+use crate::{error::TokenError, processor::Processor};
+use solana_sdk::{
+    account_info::AccountInfo, entrypoint, entrypoint::ProgramResult,
+    program_error::PrintProgramError, pubkey::Pubkey,
+};
+
+entrypoint!(process_instruction);
+fn process_instruction<'a>(
+    program_id: &Pubkey,
+    accounts: &'a [AccountInfo<'a>],
+    instruction_data: &[u8],
+) -> ProgramResult {
+    if let Err(error) = Processor::process(program_id, accounts, instruction_data) {
+        // catch the error so we can print it
+        error.print::<TokenError>();
+        return Err(error);
+    }
+    Ok(())
+}

+ 71 - 0
program/src/error.rs

@@ -0,0 +1,71 @@
+//! Error types
+
+use num_derive::FromPrimitive;
+use solana_sdk::{decode_error::DecodeError, program_error::ProgramError};
+use thiserror::Error;
+
+/// Errors that may be returned by the Token program.
+#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
+pub enum TokenError {
+    /// Insufficient funds for the operation requested.
+    #[error("Insufficient funds")]
+    InsufficientFunds,
+    /// Account not associated with this Mint.
+    #[error("Account not associated with this Mint")]
+    MintMismatch,
+    /// Owner does not match.
+    #[error("Owner does not match")]
+    OwnerMismatch,
+    /// 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("AlreadyInUse")]
+    AlreadyInUse,
+    /// An owner is required if initial supply is zero.
+    #[error("An owner is required if supply is zero")]
+    OwnerRequiredIfNoInitialSupply,
+    /// 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 unititialized")]
+    UninitializedState,
+    /// 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,
+    /// 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,
+}
+impl From<TokenError> for ProgramError {
+    fn from(e: TokenError) -> Self {
+        ProgramError::Custom(e as u32)
+    }
+}
+impl<T> DecodeError<T> for TokenError {
+    fn type_of() -> &'static str {
+        "TokenError"
+    }
+}

+ 915 - 0
program/src/instruction.rs

@@ -0,0 +1,915 @@
+//! Instruction types
+
+use crate::{error::TokenError, option::COption};
+use solana_sdk::{
+    instruction::{AccountMeta, Instruction},
+    program_error::ProgramError,
+    pubkey::Pubkey,
+};
+use std::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;
+
+/// Instructions supported by the token program.
+#[repr(C)]
+#[derive(Clone, Debug, PartialEq)]
+pub enum TokenInstruction {
+    /// 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 `CreateInstruction` 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.
+    ///
+    InitializeMint {
+        /// Number of base 10 digits to the right of the decimal place.
+        decimals: u8,
+        /// The authority/multisignature to mint tokens. If present, further minting is supported.
+        mint_authority: COption<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.
+    ///
+    /// The `InitializeAccount` instruction requires no signers and MUST be included within
+    /// the same Transaction as the system program's `CreateInstruction` 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.
+    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 `CreateInstruction` 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. ..1+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 multisignature authority.
+    ///   3. ..3+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. `[signer]` The account's owner/delegate.
+    ///
+    ///   * Multisignature owner/delegate
+    ///   0. `[writable]` The account to burn from.
+    ///   1. `[]` The account's multisignature owner/delegate.
+    ///   2. ..2+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,
+}
+impl TokenInstruction {
+    /// Unpacks a byte buffer into a [TokenInstruction](enum.TokenInstruction.html).
+    pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
+        if input.len() < size_of::<u8>() {
+            return Err(TokenError::InvalidInstruction.into());
+        }
+        Ok(match input[0] {
+            0 => {
+                if input.len() < size_of::<u8>() + size_of::<u8>() + size_of::<bool>() {
+                    return Err(TokenError::InvalidInstruction.into());
+                }
+                let mut input_len = 0;
+                input_len += size_of::<u8>();
+
+                let decimals = unsafe { *(&input[input_len] as *const u8) };
+                input_len += size_of::<u8>();
+
+                let mint_authority = COption::unpack_or(
+                    input,
+                    &mut input_len,
+                    Into::<ProgramError>::into(TokenError::InvalidInstruction),
+                )?;
+                let freeze_authority = COption::unpack_or(
+                    input,
+                    &mut input_len,
+                    Into::<ProgramError>::into(TokenError::InvalidInstruction),
+                )?;
+
+                Self::InitializeMint {
+                    mint_authority,
+                    freeze_authority,
+                    decimals,
+                }
+            }
+            1 => Self::InitializeAccount,
+            2 => {
+                if input.len() < size_of::<u8>() + size_of::<u8>() {
+                    return Err(TokenError::InvalidInstruction.into());
+                }
+                #[allow(clippy::cast_ptr_alignment)]
+                let m = unsafe { *(&input[1] as *const u8) };
+                Self::InitializeMultisig { m }
+            }
+            3 => {
+                if input.len() < size_of::<u8>() + size_of::<u64>() {
+                    return Err(TokenError::InvalidInstruction.into());
+                }
+                #[allow(clippy::cast_ptr_alignment)]
+                let amount = unsafe { *(&input[size_of::<u8>()] as *const u8 as *const u64) };
+                Self::Transfer { amount }
+            }
+            4 => {
+                if input.len() < size_of::<u8>() + size_of::<u64>() {
+                    return Err(TokenError::InvalidInstruction.into());
+                }
+                #[allow(clippy::cast_ptr_alignment)]
+                let amount = unsafe { *(&input[size_of::<u8>()] as *const u8 as *const u64) };
+                Self::Approve { amount }
+            }
+            5 => Self::Revoke,
+            6 => {
+                if input.len() < size_of::<u8>() + size_of::<u8>() {
+                    return Err(TokenError::InvalidInstruction.into());
+                }
+                let mut input_len = 0;
+                input_len += size_of::<u8>();
+                let authority_type = AuthorityType::from(input[1])?;
+                input_len += size_of::<u8>();
+
+                let new_authority = COption::unpack_or(
+                    input,
+                    &mut input_len,
+                    Into::<ProgramError>::into(TokenError::InvalidInstruction),
+                )?;
+
+                Self::SetAuthority {
+                    authority_type,
+                    new_authority,
+                }
+            }
+            7 => {
+                if input.len() < size_of::<u8>() + size_of::<u64>() {
+                    return Err(TokenError::InvalidInstruction.into());
+                }
+                #[allow(clippy::cast_ptr_alignment)]
+                let amount = unsafe { *(&input[size_of::<u8>()] as *const u8 as *const u64) };
+                Self::MintTo { amount }
+            }
+            8 => {
+                if input.len() < size_of::<u8>() + size_of::<u64>() {
+                    return Err(TokenError::InvalidInstruction.into());
+                }
+                #[allow(clippy::cast_ptr_alignment)]
+                let amount = unsafe { *(&input[size_of::<u8>()] as *const u8 as *const u64) };
+                Self::Burn { amount }
+            }
+            9 => Self::CloseAccount,
+            10 => Self::FreezeAccount,
+            11 => Self::ThawAccount,
+            _ => return Err(TokenError::InvalidInstruction.into()),
+        })
+    }
+
+    /// Packs a [TokenInstruction](enum.TokenInstruction.html) into a byte buffer.
+    pub fn pack(&self) -> Result<Vec<u8>, ProgramError> {
+        let mut output = vec![0u8; size_of::<TokenInstruction>()];
+        let mut output_len = 0;
+        match self {
+            Self::InitializeMint {
+                mint_authority,
+                freeze_authority,
+                decimals,
+            } => {
+                output[output_len] = 0;
+                output_len += size_of::<u8>();
+
+                let value = unsafe { &mut *(&mut output[output_len] as *mut u8) };
+                *value = *decimals;
+                output_len += size_of::<u8>();
+
+                mint_authority.pack(&mut output, &mut output_len);
+                freeze_authority.pack(&mut output, &mut output_len);
+            }
+            Self::InitializeAccount => {
+                output[output_len] = 1;
+                output_len += size_of::<u8>();
+            }
+            Self::InitializeMultisig { m } => {
+                output[output_len] = 2;
+                output_len += size_of::<u8>();
+
+                #[allow(clippy::cast_ptr_alignment)]
+                let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u8) };
+                *value = *m;
+                output_len += size_of::<u8>();
+            }
+            Self::Transfer { amount } => {
+                output[output_len] = 3;
+                output_len += size_of::<u8>();
+
+                #[allow(clippy::cast_ptr_alignment)]
+                let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u64) };
+                *value = *amount;
+                output_len += size_of::<u64>();
+            }
+            Self::Approve { amount } => {
+                output[output_len] = 4;
+                output_len += size_of::<u8>();
+
+                #[allow(clippy::cast_ptr_alignment)]
+                let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u64) };
+                *value = *amount;
+                output_len += size_of::<u64>();
+            }
+            Self::Revoke => {
+                output[output_len] = 5;
+                output_len += size_of::<u8>();
+            }
+            Self::SetAuthority {
+                authority_type,
+                new_authority,
+            } => {
+                output[output_len] = 6;
+                output_len += size_of::<u8>();
+
+                output[output_len] = authority_type.into();
+                output_len += size_of::<u8>();
+
+                new_authority.pack(&mut output, &mut output_len);
+            }
+            Self::MintTo { amount } => {
+                output[output_len] = 7;
+                output_len += size_of::<u8>();
+
+                #[allow(clippy::cast_ptr_alignment)]
+                let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u64) };
+                *value = *amount;
+                output_len += size_of::<u64>();
+            }
+            Self::Burn { amount } => {
+                output[output_len] = 8;
+                output_len += size_of::<u8>();
+
+                #[allow(clippy::cast_ptr_alignment)]
+                let value = unsafe { &mut *(&mut output[output_len] as *mut u8 as *mut u64) };
+                *value = *amount;
+                output_len += size_of::<u64>();
+            }
+            Self::CloseAccount => {
+                output[output_len] = 9;
+                output_len += size_of::<u8>();
+            }
+            Self::FreezeAccount => {
+                output[output_len] = 10;
+                output_len += size_of::<u8>();
+            }
+            Self::ThawAccount => {
+                output[output_len] = 11;
+                output_len += size_of::<u8>();
+            }
+        }
+
+        output.truncate(output_len);
+        Ok(output)
+    }
+}
+
+/// 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,
+    /// Holder of a given token account
+    AccountHolder,
+    /// Authority to close a token account
+    CloseAccount,
+}
+
+impl AuthorityType {
+    fn into(&self) -> u8 {
+        match self {
+            AuthorityType::MintTokens => 0,
+            AuthorityType::FreezeAccount => 1,
+            AuthorityType::AccountHolder => 2,
+            AuthorityType::CloseAccount => 3,
+        }
+    }
+
+    fn from(index: u8) -> Result<Self, ProgramError> {
+        match index {
+            0 => Ok(AuthorityType::MintTokens),
+            1 => Ok(AuthorityType::FreezeAccount),
+            2 => Ok(AuthorityType::AccountHolder),
+            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> {
+    let mint_authority = COption::Some(*mint_authority_pubkey);
+    let freeze_authority = freeze_authority_pubkey.cloned().into();
+    let data = TokenInstruction::InitializeMint {
+        mint_authority,
+        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> {
+    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),
+    ];
+
+    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> {
+    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 + 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> {
+    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(**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> {
+    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(**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> {
+    let data = TokenInstruction::Revoke.pack()?;
+
+    let mut accounts = Vec::with_capacity(2 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new_readonly(*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(**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> {
+    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(**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> {
+    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(**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,
+    authority_pubkey: &Pubkey,
+    signer_pubkeys: &[&Pubkey],
+    amount: u64,
+) -> Result<Instruction, ProgramError> {
+    let data = TokenInstruction::Burn { amount }.pack()?;
+
+    let mut accounts = Vec::with_capacity(2 + signer_pubkeys.len());
+    accounts.push(AccountMeta::new(*account_pubkey, false));
+    accounts.push(AccountMeta::new_readonly(
+        *authority_pubkey,
+        signer_pubkeys.is_empty(),
+    ));
+    for signer_pubkey in signer_pubkeys.iter() {
+        accounts.push(AccountMeta::new(**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> {
+    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(**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> {
+    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(**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> {
+    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(**signer_pubkey, true));
+    }
+
+    Ok(Instruction {
+        program_id: *token_program_id,
+        accounts,
+        data,
+    })
+}
+
+/// Utility function that checks index is between MIN_SIGNERS and MAX_SIGNERS
+pub fn is_valid_signer_index(index: usize) -> bool {
+    !(index < MIN_SIGNERS || index > MAX_SIGNERS)
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_instruction_packing() {
+        let check = TokenInstruction::InitializeMint {
+            decimals: 2,
+            mint_authority: COption::None,
+            freeze_authority: COption::None,
+        };
+        let packed = check.pack().unwrap();
+        let expect = Vec::from([0u8, 2, 0, 0]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+
+        let check = TokenInstruction::InitializeMint {
+            decimals: 2,
+            mint_authority: COption::Some(Pubkey::new(&[2u8; 32])),
+            freeze_authority: COption::Some(Pubkey::new(&[3u8; 32])),
+        };
+        let packed = check.pack().unwrap();
+        let mut expect = vec![0u8, 2];
+        expect.extend_from_slice(&[1]);
+        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().unwrap();
+        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().unwrap();
+        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().unwrap();
+        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().unwrap();
+        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().unwrap();
+        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(&[4u8; 32])),
+        };
+        let packed = check.pack().unwrap();
+        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().unwrap();
+        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().unwrap();
+        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().unwrap();
+        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().unwrap();
+        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().unwrap();
+        let expect = Vec::from([11u8]);
+        assert_eq!(packed, expect);
+        let unpacked = TokenInstruction::unpack(&expect).unwrap();
+        assert_eq!(unpacked, check);
+    }
+}

+ 28 - 0
program/src/lib.rs

@@ -0,0 +1,28 @@
+#![deny(missing_docs)]
+
+//! An ERC20-like Token program for the Solana blockchain
+
+pub mod entrypoint;
+pub mod error;
+pub mod instruction;
+pub mod native_mint;
+pub mod option;
+pub mod processor;
+pub mod state;
+
+// Export current solana-sdk types for downstream users who may also be building with a different
+// solana-sdk version
+pub use solana_sdk;
+
+/// Convert the UI representation of a token amount (using the decimals field defined in its mint)
+/// to the raw amount
+pub fn ui_amount_to_amount(ui_amount: f64, decimals: u8) -> u64 {
+    (ui_amount * 10_usize.pow(decimals as u32) as f64) as u64
+}
+
+/// Convert a raw amount to its UI representation (using the decimals field defined in its mint)
+pub fn amount_to_ui_amount(amount: u64, decimals: u8) -> f64 {
+    amount as f64 / 10_usize.pow(decimals as u32) as f64
+}
+
+solana_sdk::declare_id!("TokensVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o");

+ 24 - 0
program/src/native_mint.rs

@@ -0,0 +1,24 @@
+//! 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_sdk::declare_id!("So12111111111111111111111111111111111111111");
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use solana_sdk::native_token::*;
+
+    #[test]
+    fn test_decimals() {
+        assert!(
+            lamports_to_sol(42) - crate::amount_to_ui_amount(42, DECIMALS).abs() < f64::EPSILON
+        );
+        assert_eq!(
+            sol_to_lamports(42.),
+            crate::ui_amount_to_amount(42., DECIMALS)
+        );
+    }
+}

+ 1112 - 0
program/src/option.rs

@@ -0,0 +1,1112 @@
+//! A C representation of Rust's `std::option::Option` used accross the FFI
+//! boundary for Solana program interfaces
+//!
+//! This implementation mostly matches `std::option` except iterators since the iteration
+//! trait requires returning `std::option::Option`
+
+use std::pin::Pin;
+use std::{
+    convert, hint, mem,
+    ops::{Deref, DerefMut},
+};
+
+/// A C representation of Rust's `std::option::Option`
+#[repr(C)]
+#[derive(Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
+pub enum COption<T> {
+    /// No value
+    None,
+    /// Some value `T`
+    Some(T),
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Type implementation
+/////////////////////////////////////////////////////////////////////////////
+
+impl<T> COption<T> {
+    /////////////////////////////////////////////////////////////////////////
+    // Querying the contained values
+    /////////////////////////////////////////////////////////////////////////
+
+    /// Returns `true` if the option is a [`COption::Some`] value.
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let x: COption<u32> = COption::Some(2);
+    /// assert_eq!(x.is_some(), true);
+    ///
+    /// let x: COption<u32> = COption::None;
+    /// assert_eq!(x.is_some(), false);
+    /// ```
+    ///
+    /// [`COption::Some`]: #variant.COption::Some
+    #[must_use = "if you intended to assert that this has a value, consider `.unwrap()` instead"]
+    #[inline]
+    pub fn is_some(&self) -> bool {
+        match *self {
+            COption::Some(_) => true,
+            COption::None => false,
+        }
+    }
+
+    /// Returns `true` if the option is a [`COption::None`] value.
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let x: COption<u32> = COption::Some(2);
+    /// assert_eq!(x.is_none(), false);
+    ///
+    /// let x: COption<u32> = COption::None;
+    /// assert_eq!(x.is_none(), true);
+    /// ```
+    ///
+    /// [`COption::None`]: #variant.COption::None
+    #[must_use = "if you intended to assert that this doesn't have a value, consider \
+                  `.and_then(|| panic!(\"`COption` had a value when expected `COption::None`\"))` instead"]
+    #[inline]
+    pub fn is_none(&self) -> bool {
+        !self.is_some()
+    }
+
+    /// Returns `true` if the option is a [`COption::Some`] value containing the given value.
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// #![feature(option_result_contains)]
+    ///
+    /// let x: COption<u32> = COption::Some(2);
+    /// assert_eq!(x.contains(&2), true);
+    ///
+    /// let x: COption<u32> = COption::Some(3);
+    /// assert_eq!(x.contains(&2), false);
+    ///
+    /// let x: COption<u32> = COption::None;
+    /// assert_eq!(x.contains(&2), false);
+    /// ```
+    #[must_use]
+    #[inline]
+    pub fn contains<U>(&self, x: &U) -> bool
+    where
+        U: PartialEq<T>,
+    {
+        match self {
+            COption::Some(y) => x == y,
+            COption::None => false,
+        }
+    }
+
+    /////////////////////////////////////////////////////////////////////////
+    // Adapter for working with references
+    /////////////////////////////////////////////////////////////////////////
+
+    /// Converts from `&COption<T>` to `COption<&T>`.
+    ///
+    /// # Examples
+    ///
+    /// Converts an `COption<`[`String`]`>` into an `COption<`[`usize`]`>`, preserving the original.
+    /// The [`map`] method takes the `self` argument by value, consuming the original,
+    /// so this technique uses `as_ref` to first take an `COption` to a reference
+    /// to the value inside the original.
+    ///
+    /// [`map`]: enum.COption.html#method.map
+    /// [`String`]: ../../std/string/struct.String.html
+    /// [`usize`]: ../../std/primitive.usize.html
+    ///
+    /// ```ignore
+    /// let text: COption<String> = COption::Some("Hello, world!".to_string());
+    /// // First, cast `COption<String>` to `COption<&String>` with `as_ref`,
+    /// // then consume *that* with `map`, leaving `text` on the stack.
+    /// let text_length: COption<usize> = text.as_ref().map(|s| s.len());
+    /// println!("still can print text: {:?}", text);
+    /// ```
+    #[inline]
+    pub fn as_ref(&self) -> COption<&T> {
+        match *self {
+            COption::Some(ref x) => COption::Some(x),
+            COption::None => COption::None,
+        }
+    }
+
+    /// Converts from `&mut COption<T>` to `COption<&mut T>`.
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let mut x = COption::Some(2);
+    /// match x.as_mut() {
+    ///     COption::Some(v) => *v = 42,
+    ///     COption::None => {},
+    /// }
+    /// assert_eq!(x, COption::Some(42));
+    /// ```
+    #[inline]
+    pub fn as_mut(&mut self) -> COption<&mut T> {
+        match *self {
+            COption::Some(ref mut x) => COption::Some(x),
+            COption::None => COption::None,
+        }
+    }
+
+    /// Converts from [`Pin`]`<&COption<T>>` to `COption<`[`Pin`]`<&T>>`.
+    ///
+    /// [`Pin`]: ../pin/struct.Pin.html
+    #[inline]
+    #[allow(clippy::wrong_self_convention)]
+    pub fn as_pin_ref(self: Pin<&Self>) -> COption<Pin<&T>> {
+        unsafe { Pin::get_ref(self).as_ref().map(|x| Pin::new_unchecked(x)) }
+    }
+
+    /// Converts from [`Pin`]`<&mut COption<T>>` to `COption<`[`Pin`]`<&mut T>>`.
+    ///
+    /// [`Pin`]: ../pin/struct.Pin.html
+    #[inline]
+    #[allow(clippy::wrong_self_convention)]
+    pub fn as_pin_mut(self: Pin<&mut Self>) -> COption<Pin<&mut T>> {
+        unsafe {
+            Pin::get_unchecked_mut(self)
+                .as_mut()
+                .map(|x| Pin::new_unchecked(x))
+        }
+    }
+
+    /////////////////////////////////////////////////////////////////////////
+    // Getting to contained values
+    /////////////////////////////////////////////////////////////////////////
+
+    /// Unwraps an option, yielding the content of a [`COption::Some`].
+    ///
+    /// # Panics
+    ///
+    /// Panics if the value is a [`COption::None`] with a custom panic message provided by
+    /// `msg`.
+    ///
+    /// [`COption::Some`]: #variant.COption::Some
+    /// [`COption::None`]: #variant.COption::None
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let x = COption::Some("value");
+    /// assert_eq!(x.expect("the world is ending"), "value");
+    /// ```
+    ///
+    /// ```ignore{.should_panic}
+    /// let x: COption<&str> = COption::None;
+    /// x.expect("the world is ending"); // panics with `the world is ending`
+    /// ```
+    #[inline]
+    pub fn expect(self, msg: &str) -> T {
+        match self {
+            COption::Some(val) => val,
+            COption::None => expect_failed(msg),
+        }
+    }
+
+    /// Moves the value `v` out of the `COption<T>` if it is [`COption::Some(v)`].
+    ///
+    /// In general, because this function may panic, its use is discouraged.
+    /// Instead, prefer to use pattern matching and handle the [`COption::None`]
+    /// case explicitly.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the self value equals [`COption::None`].
+    ///
+    /// [`COption::Some(v)`]: #variant.COption::Some
+    /// [`COption::None`]: #variant.COption::None
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let x = COption::Some("air");
+    /// assert_eq!(x.unwrap(), "air");
+    /// ```
+    ///
+    /// ```ignore{.should_panic}
+    /// let x: COption<&str> = COption::None;
+    /// assert_eq!(x.unwrap(), "air"); // fails
+    /// ```
+    #[inline]
+    pub fn unwrap(self) -> T {
+        match self {
+            COption::Some(val) => val,
+            COption::None => panic!("called `COption::unwrap()` on a `COption::None` value"),
+        }
+    }
+
+    /// Returns the contained value or a default.
+    ///
+    /// Arguments passed to `unwrap_or` are eagerly evaluated; if you are passing
+    /// the result of a function call, it is recommended to use [`unwrap_or_else`],
+    /// which is lazily evaluated.
+    ///
+    /// [`unwrap_or_else`]: #method.unwrap_or_else
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// assert_eq!(COption::Some("car").unwrap_or("bike"), "car");
+    /// assert_eq!(COption::None.unwrap_or("bike"), "bike");
+    /// ```
+    #[inline]
+    pub fn unwrap_or(self, def: T) -> T {
+        match self {
+            COption::Some(x) => x,
+            COption::None => def,
+        }
+    }
+
+    /// Returns the contained value or computes it from a closure.
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let k = 10;
+    /// assert_eq!(COption::Some(4).unwrap_or_else(|| 2 * k), 4);
+    /// assert_eq!(COption::None.unwrap_or_else(|| 2 * k), 20);
+    /// ```
+    #[inline]
+    pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T {
+        match self {
+            COption::Some(x) => x,
+            COption::None => f(),
+        }
+    }
+
+    /////////////////////////////////////////////////////////////////////////
+    // Transforming contained values
+    /////////////////////////////////////////////////////////////////////////
+
+    /// Maps an `COption<T>` to `COption<U>` by applying a function to a contained value.
+    ///
+    /// # Examples
+    ///
+    /// Converts an `COption<`[`String`]`>` into an `COption<`[`usize`]`>`, consuming the original:
+    ///
+    /// [`String`]: ../../std/string/struct.String.html
+    /// [`usize`]: ../../std/primitive.usize.html
+    ///
+    /// ```ignore
+    /// let maybe_some_string = COption::Some(String::from("Hello, World!"));
+    /// // `COption::map` takes self *by value*, consuming `maybe_some_string`
+    /// let maybe_some_len = maybe_some_string.map(|s| s.len());
+    ///
+    /// assert_eq!(maybe_some_len, COption::Some(13));
+    /// ```
+    #[inline]
+    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> COption<U> {
+        match self {
+            COption::Some(x) => COption::Some(f(x)),
+            COption::None => COption::None,
+        }
+    }
+
+    /// Applies a function to the contained value (if any),
+    /// or returns the provided default (if not).
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let x = COption::Some("foo");
+    /// assert_eq!(x.map_or(42, |v| v.len()), 3);
+    ///
+    /// let x: COption<&str> = COption::None;
+    /// assert_eq!(x.map_or(42, |v| v.len()), 42);
+    /// ```
+    #[inline]
+    pub fn map_or<U, F: FnOnce(T) -> U>(self, default: U, f: F) -> U {
+        match self {
+            COption::Some(t) => f(t),
+            COption::None => default,
+        }
+    }
+
+    /// Applies a function to the contained value (if any),
+    /// or computes a default (if not).
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let k = 21;
+    ///
+    /// let x = COption::Some("foo");
+    /// assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 3);
+    ///
+    /// let x: COption<&str> = COption::None;
+    /// assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 42);
+    /// ```
+    #[inline]
+    pub fn map_or_else<U, D: FnOnce() -> U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U {
+        match self {
+            COption::Some(t) => f(t),
+            COption::None => default(),
+        }
+    }
+
+    /// Transforms the `COption<T>` into a [`Result<T, E>`], mapping [`COption::Some(v)`] to
+    /// [`Ok(v)`] and [`COption::None`] to [`Err(err)`].
+    ///
+    /// Arguments passed to `ok_or` are eagerly evaluated; if you are passing the
+    /// result of a function call, it is recommended to use [`ok_or_else`], which is
+    /// lazily evaluated.
+    ///
+    /// [`Result<T, E>`]: ../../std/result/enum.Result.html
+    /// [`Ok(v)`]: ../../std/result/enum.Result.html#variant.Ok
+    /// [`Err(err)`]: ../../std/result/enum.Result.html#variant.Err
+    /// [`COption::None`]: #variant.COption::None
+    /// [`COption::Some(v)`]: #variant.COption::Some
+    /// [`ok_or_else`]: #method.ok_or_else
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let x = COption::Some("foo");
+    /// assert_eq!(x.ok_or(0), Ok("foo"));
+    ///
+    /// let x: COption<&str> = COption::None;
+    /// assert_eq!(x.ok_or(0), Err(0));
+    /// ```
+    #[inline]
+    pub fn ok_or<E>(self, err: E) -> Result<T, E> {
+        match self {
+            COption::Some(v) => Ok(v),
+            COption::None => Err(err),
+        }
+    }
+
+    /// Transforms the `COption<T>` into a [`Result<T, E>`], mapping [`COption::Some(v)`] to
+    /// [`Ok(v)`] and [`COption::None`] to [`Err(err())`].
+    ///
+    /// [`Result<T, E>`]: ../../std/result/enum.Result.html
+    /// [`Ok(v)`]: ../../std/result/enum.Result.html#variant.Ok
+    /// [`Err(err())`]: ../../std/result/enum.Result.html#variant.Err
+    /// [`COption::None`]: #variant.COption::None
+    /// [`COption::Some(v)`]: #variant.COption::Some
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let x = COption::Some("foo");
+    /// assert_eq!(x.ok_or_else(|| 0), Ok("foo"));
+    ///
+    /// let x: COption<&str> = COption::None;
+    /// assert_eq!(x.ok_or_else(|| 0), Err(0));
+    /// ```
+    #[inline]
+    pub fn ok_or_else<E, F: FnOnce() -> E>(self, err: F) -> Result<T, E> {
+        match self {
+            COption::Some(v) => Ok(v),
+            COption::None => Err(err()),
+        }
+    }
+
+    /////////////////////////////////////////////////////////////////////////
+    // Boolean operations on the values, eager and lazy
+    /////////////////////////////////////////////////////////////////////////
+
+    /// Returns [`COption::None`] if the option is [`COption::None`], otherwise returns `optb`.
+    ///
+    /// [`COption::None`]: #variant.COption::None
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let x = COption::Some(2);
+    /// let y: COption<&str> = COption::None;
+    /// assert_eq!(x.and(y), COption::None);
+    ///
+    /// let x: COption<u32> = COption::None;
+    /// let y = COption::Some("foo");
+    /// assert_eq!(x.and(y), COption::None);
+    ///
+    /// let x = COption::Some(2);
+    /// let y = COption::Some("foo");
+    /// assert_eq!(x.and(y), COption::Some("foo"));
+    ///
+    /// let x: COption<u32> = COption::None;
+    /// let y: COption<&str> = COption::None;
+    /// assert_eq!(x.and(y), COption::None);
+    /// ```
+    #[inline]
+    pub fn and<U>(self, optb: COption<U>) -> COption<U> {
+        match self {
+            COption::Some(_) => optb,
+            COption::None => COption::None,
+        }
+    }
+
+    /// Returns [`COption::None`] if the option is [`COption::None`], otherwise calls `f` with the
+    /// wrapped value and returns the result.
+    ///
+    /// COption::Some languages call this operation flatmap.
+    ///
+    /// [`COption::None`]: #variant.COption::None
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// fn sq(x: u32) -> COption<u32> { COption::Some(x * x) }
+    /// fn nope(_: u32) -> COption<u32> { COption::None }
+    ///
+    /// assert_eq!(COption::Some(2).and_then(sq).and_then(sq), COption::Some(16));
+    /// assert_eq!(COption::Some(2).and_then(sq).and_then(nope), COption::None);
+    /// assert_eq!(COption::Some(2).and_then(nope).and_then(sq), COption::None);
+    /// assert_eq!(COption::None.and_then(sq).and_then(sq), COption::None);
+    /// ```
+    #[inline]
+    pub fn and_then<U, F: FnOnce(T) -> COption<U>>(self, f: F) -> COption<U> {
+        match self {
+            COption::Some(x) => f(x),
+            COption::None => COption::None,
+        }
+    }
+
+    /// Returns [`COption::None`] if the option is [`COption::None`], otherwise calls `predicate`
+    /// with the wrapped value and returns:
+    ///
+    /// - [`COption::Some(t)`] if `predicate` returns `true` (where `t` is the wrapped
+    ///   value), and
+    /// - [`COption::None`] if `predicate` returns `false`.
+    ///
+    /// This function works similar to [`Iterator::filter()`]. You can imagine
+    /// the `COption<T>` being an iterator over one or zero elements. `filter()`
+    /// lets you decide which elements to keep.
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// fn is_even(n: &i32) -> bool {
+    ///     n % 2 == 0
+    /// }
+    ///
+    /// assert_eq!(COption::None.filter(is_even), COption::None);
+    /// assert_eq!(COption::Some(3).filter(is_even), COption::None);
+    /// assert_eq!(COption::Some(4).filter(is_even), COption::Some(4));
+    /// ```
+    ///
+    /// [`COption::None`]: #variant.COption::None
+    /// [`COption::Some(t)`]: #variant.COption::Some
+    /// [`Iterator::filter()`]: ../../std/iter/trait.Iterator.html#method.filter
+    #[inline]
+    pub fn filter<P: FnOnce(&T) -> bool>(self, predicate: P) -> Self {
+        if let COption::Some(x) = self {
+            if predicate(&x) {
+                return COption::Some(x);
+            }
+        }
+        COption::None
+    }
+
+    /// Returns the option if it contains a value, otherwise returns `optb`.
+    ///
+    /// Arguments passed to `or` are eagerly evaluated; if you are passing the
+    /// result of a function call, it is recommended to use [`or_else`], which is
+    /// lazily evaluated.
+    ///
+    /// [`or_else`]: #method.or_else
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let x = COption::Some(2);
+    /// let y = COption::None;
+    /// assert_eq!(x.or(y), COption::Some(2));
+    ///
+    /// let x = COption::None;
+    /// let y = COption::Some(100);
+    /// assert_eq!(x.or(y), COption::Some(100));
+    ///
+    /// let x = COption::Some(2);
+    /// let y = COption::Some(100);
+    /// assert_eq!(x.or(y), COption::Some(2));
+    ///
+    /// let x: COption<u32> = COption::None;
+    /// let y = COption::None;
+    /// assert_eq!(x.or(y), COption::None);
+    /// ```
+    #[inline]
+    pub fn or(self, optb: COption<T>) -> COption<T> {
+        match self {
+            COption::Some(_) => self,
+            COption::None => optb,
+        }
+    }
+
+    /// Returns the option if it contains a value, otherwise calls `f` and
+    /// returns the result.
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// fn nobody() -> COption<&'static str> { COption::None }
+    /// fn vikings() -> COption<&'static str> { COption::Some("vikings") }
+    ///
+    /// assert_eq!(COption::Some("barbarians").or_else(vikings), COption::Some("barbarians"));
+    /// assert_eq!(COption::None.or_else(vikings), COption::Some("vikings"));
+    /// assert_eq!(COption::None.or_else(nobody), COption::None);
+    /// ```
+    #[inline]
+    pub fn or_else<F: FnOnce() -> COption<T>>(self, f: F) -> COption<T> {
+        match self {
+            COption::Some(_) => self,
+            COption::None => f(),
+        }
+    }
+
+    /// Returns [`COption::Some`] if exactly one of `self`, `optb` is [`COption::Some`], otherwise returns [`COption::None`].
+    ///
+    /// [`COption::Some`]: #variant.COption::Some
+    /// [`COption::None`]: #variant.COption::None
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let x = COption::Some(2);
+    /// let y: COption<u32> = COption::None;
+    /// assert_eq!(x.xor(y), COption::Some(2));
+    ///
+    /// let x: COption<u32> = COption::None;
+    /// let y = COption::Some(2);
+    /// assert_eq!(x.xor(y), COption::Some(2));
+    ///
+    /// let x = COption::Some(2);
+    /// let y = COption::Some(2);
+    /// assert_eq!(x.xor(y), COption::None);
+    ///
+    /// let x: COption<u32> = COption::None;
+    /// let y: COption<u32> = COption::None;
+    /// assert_eq!(x.xor(y), COption::None);
+    /// ```
+    #[inline]
+    pub fn xor(self, optb: COption<T>) -> COption<T> {
+        match (self, optb) {
+            (COption::Some(a), COption::None) => COption::Some(a),
+            (COption::None, COption::Some(b)) => COption::Some(b),
+            _ => COption::None,
+        }
+    }
+
+    /////////////////////////////////////////////////////////////////////////
+    // Entry-like operations to insert if COption::None and return a reference
+    /////////////////////////////////////////////////////////////////////////
+
+    /// Inserts `v` into the option if it is [`COption::None`], then
+    /// returns a mutable reference to the contained value.
+    ///
+    /// [`COption::None`]: #variant.COption::None
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let mut x = COption::None;
+    ///
+    /// {
+    ///     let y: &mut u32 = x.get_or_insert(5);
+    ///     assert_eq!(y, &5);
+    ///
+    ///     *y = 7;
+    /// }
+    ///
+    /// assert_eq!(x, COption::Some(7));
+    /// ```
+    #[inline]
+    pub fn get_or_insert(&mut self, v: T) -> &mut T {
+        self.get_or_insert_with(|| v)
+    }
+
+    /// Inserts a value computed from `f` into the option if it is [`COption::None`], then
+    /// returns a mutable reference to the contained value.
+    ///
+    /// [`COption::None`]: #variant.COption::None
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let mut x = COption::None;
+    ///
+    /// {
+    ///     let y: &mut u32 = x.get_or_insert_with(|| 5);
+    ///     assert_eq!(y, &5);
+    ///
+    ///     *y = 7;
+    /// }
+    ///
+    /// assert_eq!(x, COption::Some(7));
+    /// ```
+    #[inline]
+    pub fn get_or_insert_with<F: FnOnce() -> T>(&mut self, f: F) -> &mut T {
+        if let COption::None = *self {
+            *self = COption::Some(f())
+        }
+
+        match *self {
+            COption::Some(ref mut v) => v,
+            COption::None => unsafe { hint::unreachable_unchecked() },
+        }
+    }
+
+    /////////////////////////////////////////////////////////////////////////
+    // Misc
+    /////////////////////////////////////////////////////////////////////////
+
+    /// Replaces the actual value in the option by the value given in parameter,
+    /// returning the old value if present,
+    /// leaving a [`COption::Some`] in its place without deinitializing either one.
+    ///
+    /// [`COption::Some`]: #variant.COption::Some
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let mut x = COption::Some(2);
+    /// let old = x.replace(5);
+    /// assert_eq!(x, COption::Some(5));
+    /// assert_eq!(old, COption::Some(2));
+    ///
+    /// let mut x = COption::None;
+    /// let old = x.replace(3);
+    /// assert_eq!(x, COption::Some(3));
+    /// assert_eq!(old, COption::None);
+    /// ```
+    #[inline]
+    pub fn replace(&mut self, value: T) -> COption<T> {
+        mem::replace(self, COption::Some(value))
+    }
+
+    /////////////////////////////////////////////////////////////////////////
+    // SPL Token-Specific Methods
+    /////////////////////////////////////////////////////////////////////////
+
+    /// Packs a COption into a mutable slice as compactly as possible
+    #[inline]
+    pub fn pack(&self, output: &mut [u8], cursor: &mut usize)
+    where
+        T: Copy,
+    {
+        match self {
+            COption::Some(some_value) => {
+                output[*cursor] = 1;
+                *cursor += mem::size_of::<u8>();
+
+                #[allow(clippy::cast_ptr_alignment)]
+                let value = unsafe { &mut *(&mut output[*cursor] as *mut u8 as *mut T) };
+                *value = *some_value;
+                *cursor += mem::size_of::<T>();
+            }
+            COption::None => {
+                output[*cursor] = 0;
+                *cursor += mem::size_of::<u8>();
+            }
+        }
+    }
+
+    /// Unpacks a COption from a compact slice
+    #[inline]
+    pub fn unpack_or<E>(input: &[u8], cursor: &mut usize, error: E) -> Result<COption<T>, E>
+    where
+        T: Copy,
+    {
+        match input[*cursor] {
+            0 => {
+                *cursor += mem::size_of::<u8>();
+                Ok(COption::None)
+            }
+            1 => {
+                *cursor += mem::size_of::<u8>();
+                #[allow(clippy::cast_ptr_alignment)]
+                let result = unsafe { *(&input[*cursor] as *const u8 as *const T) };
+                *cursor += mem::size_of::<T>();
+                Ok(COption::Some(result))
+            }
+            _ => Err(error),
+        }
+    }
+}
+
+impl<T: Copy> COption<&T> {
+    /// Maps an `COption<&T>` to an `COption<T>` by copying the contents of the
+    /// option.
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let x = 12;
+    /// let opt_x = COption::Some(&x);
+    /// assert_eq!(opt_x, COption::Some(&12));
+    /// let copied = opt_x.copied();
+    /// assert_eq!(copied, COption::Some(12));
+    /// ```
+    pub fn copied(self) -> COption<T> {
+        self.map(|&t| t)
+    }
+}
+
+impl<T: Copy> COption<&mut T> {
+    /// Maps an `COption<&mut T>` to an `COption<T>` by copying the contents of the
+    /// option.
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let mut x = 12;
+    /// let opt_x = COption::Some(&mut x);
+    /// assert_eq!(opt_x, COption::Some(&mut 12));
+    /// let copied = opt_x.copied();
+    /// assert_eq!(copied, COption::Some(12));
+    /// ```
+    pub fn copied(self) -> COption<T> {
+        self.map(|&mut t| t)
+    }
+}
+
+impl<T: Clone> COption<&T> {
+    /// Maps an `COption<&T>` to an `COption<T>` by cloning the contents of the
+    /// option.
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let x = 12;
+    /// let opt_x = COption::Some(&x);
+    /// assert_eq!(opt_x, COption::Some(&12));
+    /// let cloned = opt_x.cloned();
+    /// assert_eq!(cloned, COption::Some(12));
+    /// ```
+    pub fn cloned(self) -> COption<T> {
+        self.map(|t| t.clone())
+    }
+}
+
+impl<T: Clone> COption<&mut T> {
+    /// Maps an `COption<&mut T>` to an `COption<T>` by cloning the contents of the
+    /// option.
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let mut x = 12;
+    /// let opt_x = COption::Some(&mut x);
+    /// assert_eq!(opt_x, COption::Some(&mut 12));
+    /// let cloned = opt_x.cloned();
+    /// assert_eq!(cloned, COption::Some(12));
+    /// ```
+    pub fn cloned(self) -> COption<T> {
+        self.map(|t| t.clone())
+    }
+}
+
+impl<T: Default> COption<T> {
+    /// Returns the contained value or a default
+    ///
+    /// Consumes the `self` argument then, if [`COption::Some`], returns the contained
+    /// value, otherwise if [`COption::None`], returns the [default value] for that
+    /// type.
+    ///
+    /// # Examples
+    ///
+    /// Converts a string to an integer, turning poorly-formed strings
+    /// into 0 (the default value for integers). [`parse`] converts
+    /// a string to any other type that implements [`FromStr`], returning
+    /// [`COption::None`] on error.
+    ///
+    /// ```ignore
+    /// let good_year_from_input = "1909";
+    /// let bad_year_from_input = "190blarg";
+    /// let good_year = good_year_from_input.parse().ok().unwrap_or_default();
+    /// let bad_year = bad_year_from_input.parse().ok().unwrap_or_default();
+    ///
+    /// assert_eq!(1909, good_year);
+    /// assert_eq!(0, bad_year);
+    /// ```
+    ///
+    /// [`COption::Some`]: #variant.COption::Some
+    /// [`COption::None`]: #variant.COption::None
+    /// [default value]: ../default/trait.Default.html#tymethod.default
+    /// [`parse`]: ../../std/primitive.str.html#method.parse
+    /// [`FromStr`]: ../../std/str/trait.FromStr.html
+    #[inline]
+    pub fn unwrap_or_default(self) -> T {
+        match self {
+            COption::Some(x) => x,
+            COption::None => Default::default(),
+        }
+    }
+}
+
+impl<T: Deref> COption<T> {
+    /// Converts from `COption<T>` (or `&COption<T>`) to `COption<&T::Target>`.
+    ///
+    /// Leaves the original COption in-place, creating a new one with a reference
+    /// to the original one, additionally coercing the contents via [`Deref`].
+    ///
+    /// [`Deref`]: ../../std/ops/trait.Deref.html
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// #![feature(inner_deref)]
+    ///
+    /// let x: COption<String> = COption::Some("hey".to_owned());
+    /// assert_eq!(x.as_deref(), COption::Some("hey"));
+    ///
+    /// let x: COption<String> = COption::None;
+    /// assert_eq!(x.as_deref(), COption::None);
+    /// ```
+    pub fn as_deref(&self) -> COption<&T::Target> {
+        self.as_ref().map(|t| t.deref())
+    }
+}
+
+impl<T: DerefMut> COption<T> {
+    /// Converts from `COption<T>` (or `&mut COption<T>`) to `COption<&mut T::Target>`.
+    ///
+    /// Leaves the original `COption` in-place, creating a new one containing a mutable reference to
+    /// the inner type's `Deref::Target` type.
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// #![feature(inner_deref)]
+    ///
+    /// let mut x: COption<String> = COption::Some("hey".to_owned());
+    /// assert_eq!(x.as_deref_mut().map(|x| {
+    ///     x.make_ascii_uppercase();
+    ///     x
+    /// }), COption::Some("HEY".to_owned().as_mut_str()));
+    /// ```
+    pub fn as_deref_mut(&mut self) -> COption<&mut T::Target> {
+        self.as_mut().map(|t| t.deref_mut())
+    }
+}
+
+impl<T, E> COption<Result<T, E>> {
+    /// Transposes an `COption` of a [`Result`] into a [`Result`] of an `COption`.
+    ///
+    /// [`COption::None`] will be mapped to [`Ok`]`(`[`COption::None`]`)`.
+    /// [`COption::Some`]`(`[`Ok`]`(_))` and [`COption::Some`]`(`[`Err`]`(_))` will be mapped to
+    /// [`Ok`]`(`[`COption::Some`]`(_))` and [`Err`]`(_)`.
+    ///
+    /// [`COption::None`]: #variant.COption::None
+    /// [`Ok`]: ../../std/result/enum.Result.html#variant.Ok
+    /// [`COption::Some`]: #variant.COption::Some
+    /// [`Err`]: ../../std/result/enum.Result.html#variant.Err
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// #[derive(Debug, Eq, PartialEq)]
+    /// struct COption::SomeErr;
+    ///
+    /// let x: Result<COption<i32>, COption::SomeErr> = Ok(COption::Some(5));
+    /// let y: COption<Result<i32, COption::SomeErr>> = COption::Some(Ok(5));
+    /// assert_eq!(x, y.transpose());
+    /// ```
+    #[inline]
+    pub fn transpose(self) -> Result<COption<T>, E> {
+        match self {
+            COption::Some(Ok(x)) => Ok(COption::Some(x)),
+            COption::Some(Err(e)) => Err(e),
+            COption::None => Ok(COption::None),
+        }
+    }
+}
+
+// This is a separate function to reduce the code size of .expect() itself.
+#[inline(never)]
+#[cold]
+fn expect_failed(msg: &str) -> ! {
+    panic!("{}", msg)
+}
+
+// // This is a separate function to reduce the code size of .expect_none() itself.
+// #[inline(never)]
+// #[cold]
+// fn expect_none_failed(msg: &str, value: &dyn fmt::Debug) -> ! {
+//     panic!("{}: {:?}", msg, value)
+// }
+
+/////////////////////////////////////////////////////////////////////////////
+// Trait implementations
+/////////////////////////////////////////////////////////////////////////////
+
+impl<T: Clone> Clone for COption<T> {
+    #[inline]
+    fn clone(&self) -> Self {
+        match self {
+            COption::Some(x) => COption::Some(x.clone()),
+            COption::None => COption::None,
+        }
+    }
+
+    #[inline]
+    fn clone_from(&mut self, source: &Self) {
+        match (self, source) {
+            (COption::Some(to), COption::Some(from)) => to.clone_from(from),
+            (to, from) => *to = from.clone(),
+        }
+    }
+}
+
+impl<T> Default for COption<T> {
+    /// Returns [`COption::None`][COption::COption::None].
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// let opt: COption<u32> = COption::default();
+    /// assert!(opt.is_none());
+    /// ```
+    #[inline]
+    fn default() -> COption<T> {
+        COption::None
+    }
+}
+
+impl<T> From<T> for COption<T> {
+    fn from(val: T) -> COption<T> {
+        COption::Some(val)
+    }
+}
+
+impl<'a, T> From<&'a COption<T>> for COption<&'a T> {
+    fn from(o: &'a COption<T>) -> COption<&'a T> {
+        o.as_ref()
+    }
+}
+
+impl<'a, T> From<&'a mut COption<T>> for COption<&'a mut T> {
+    fn from(o: &'a mut COption<T>) -> COption<&'a mut T> {
+        o.as_mut()
+    }
+}
+
+impl<T> COption<COption<T>> {
+    /// Converts from `COption<COption<T>>` to `COption<T>`
+    ///
+    /// # Examples
+    /// Basic usage:
+    /// ```ignore
+    /// #![feature(option_flattening)]
+    /// let x: COption<COption<u32>> = COption::Some(COption::Some(6));
+    /// assert_eq!(COption::Some(6), x.flatten());
+    ///
+    /// let x: COption<COption<u32>> = COption::Some(COption::None);
+    /// assert_eq!(COption::None, x.flatten());
+    ///
+    /// let x: COption<COption<u32>> = COption::None;
+    /// assert_eq!(COption::None, x.flatten());
+    /// ```
+    /// Flattening once only removes one level of nesting:
+    /// ```ignore
+    /// #![feature(option_flattening)]
+    /// let x: COption<COption<COption<u32>>> = COption::Some(COption::Some(COption::Some(6)));
+    /// assert_eq!(COption::Some(COption::Some(6)), x.flatten());
+    /// assert_eq!(COption::Some(6), x.flatten().flatten());
+    /// ```
+    #[inline]
+    pub fn flatten(self) -> COption<T> {
+        self.and_then(convert::identity)
+    }
+}
+
+impl<T> From<Option<T>> for COption<T> {
+    fn from(option: Option<T>) -> Self {
+        match option {
+            Some(value) => COption::Some(value),
+            None => COption::None,
+        }
+    }
+}
+
+impl<T> Into<Option<T>> for COption<T> {
+    fn into(self) -> Option<T> {
+        match self {
+            COption::Some(value) => Some(value),
+            COption::None => None,
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use solana_sdk::pubkey::Pubkey;
+
+    #[test]
+    fn test_from_rust_option() {
+        let option = Some(99u64);
+        let c_option: COption<u64> = option.into();
+        assert_eq!(c_option, COption::Some(99u64));
+        let expected = c_option.into();
+        assert_eq!(option, expected);
+
+        let option = None;
+        let c_option: COption<u64> = option.into();
+        assert_eq!(c_option, COption::None);
+        let expected = c_option.into();
+        assert_eq!(option, expected);
+    }
+
+    #[test]
+    fn test_coption_packing() {
+        // Solana Pubkey
+        let option_pubkey = COption::Some(Pubkey::new(&[2u8; 32]));
+        let expected_size = mem::size_of::<u8>() + mem::size_of::<Pubkey>();
+        let mut output = vec![0u8; expected_size];
+        let mut cursor = 0;
+        option_pubkey.pack(&mut output, &mut cursor);
+
+        let mut expected = vec![1u8];
+        expected.extend_from_slice(&[2u8; 32]);
+        assert_eq!(output, expected);
+
+        let mut cursor = 0;
+        let unpacked = COption::unpack_or(&expected, &mut cursor, "Error".to_string()).unwrap();
+        assert_eq!(unpacked, option_pubkey);
+
+        let option_pubkey: COption<Pubkey> = COption::None;
+        let expected_size = mem::size_of::<u8>();
+        let mut output = vec![0u8; expected_size];
+        let mut cursor = 0;
+        option_pubkey.pack(&mut output, &mut cursor);
+
+        let expected = vec![0u8];
+        assert_eq!(output, expected);
+
+        let mut cursor = 0;
+        let unpacked = COption::unpack_or(&expected, &mut cursor, "Error".to_string()).unwrap();
+        assert_eq!(unpacked, option_pubkey);
+
+        // u64
+        let option_pubkey = COption::Some(99u64);
+        let expected_size = mem::size_of::<u8>() + mem::size_of::<u64>();
+        let mut output = vec![0u8; expected_size];
+        let mut cursor = 0;
+        option_pubkey.pack(&mut output, &mut cursor);
+
+        let mut expected = vec![1u8];
+        expected.extend_from_slice(&[99, 0, 0, 0, 0, 0, 0, 0]);
+        assert_eq!(output, expected);
+
+        let mut cursor = 0;
+        let unpacked = COption::unpack_or(&expected, &mut cursor, "Error".to_string()).unwrap();
+        assert_eq!(unpacked, option_pubkey);
+
+        let option_pubkey: COption<u64> = COption::None;
+        let expected_size = mem::size_of::<u8>();
+        let mut output = vec![0u8; expected_size];
+        let mut cursor = 0;
+        option_pubkey.pack(&mut output, &mut cursor);
+
+        let expected = vec![0u8];
+        assert_eq!(output, expected);
+
+        let mut cursor = 0;
+        let unpacked = COption::unpack_or(&expected, &mut cursor, "Error".to_string()).unwrap();
+        assert_eq!(unpacked, option_pubkey);
+    }
+}

+ 3128 - 0
program/src/processor.rs

@@ -0,0 +1,3128 @@
+//! Program state processor
+
+#![cfg(feature = "program")]
+
+use crate::{
+    error::TokenError,
+    instruction::{is_valid_signer_index, AuthorityType, TokenInstruction},
+    option::COption,
+    state::{self, Account, AccountState, IsInitialized, Mint, Multisig},
+};
+use num_traits::FromPrimitive;
+use solana_sdk::{
+    account_info::{next_account_info, AccountInfo},
+    decode_error::DecodeError,
+    entrypoint::ProgramResult,
+    info,
+    program_error::{PrintProgramError, ProgramError},
+    pubkey::Pubkey,
+};
+use std::mem::size_of;
+
+/// Program state handler.
+pub struct Processor {}
+impl Processor {
+    /// Processes an [InitializeMint](enum.TokenInstruction.html) instruction.
+    pub fn process_initialize_mint(
+        accounts: &[AccountInfo],
+        decimals: u8,
+        mint_authority: COption<Pubkey>,
+        freeze_authority: COption<Pubkey>,
+    ) -> ProgramResult {
+        let account_info_iter = &mut accounts.iter();
+        let mint_info = next_account_info(account_info_iter)?;
+
+        let mut mint_info_data = mint_info.data.borrow_mut();
+        let mut mint: &mut Mint = state::unpack_unchecked(&mut mint_info_data)?;
+        if mint.is_initialized {
+            return Err(TokenError::AlreadyInUse.into());
+        }
+
+        if mint_authority.is_none() {
+            return Err(TokenError::OwnerRequiredIfNoInitialSupply.into());
+        }
+
+        mint.mint_authority = mint_authority;
+        mint.decimals = decimals;
+        mint.is_initialized = true;
+        mint.freeze_authority = freeze_authority;
+
+        Ok(())
+    }
+
+    /// Processes an [InitializeAccount](enum.TokenInstruction.html) instruction.
+    pub fn process_initialize_account(accounts: &[AccountInfo]) -> ProgramResult {
+        let account_info_iter = &mut accounts.iter();
+        let new_account_info = next_account_info(account_info_iter)?;
+        let mint_info = next_account_info(account_info_iter)?;
+        let owner_info = next_account_info(account_info_iter)?;
+
+        let mut new_account_data = new_account_info.data.borrow_mut();
+        let mut account: &mut Account = state::unpack_unchecked(&mut new_account_data)?;
+        if account.is_initialized() {
+            return Err(TokenError::AlreadyInUse.into());
+        }
+
+        account.mint = *mint_info.key;
+        account.owner = *owner_info.key;
+        account.delegate = COption::None;
+        account.delegated_amount = 0;
+        account.state = AccountState::Initialized;
+        if *mint_info.key == crate::native_mint::id() {
+            account.is_native = true;
+            account.amount = new_account_info.lamports();
+        } else {
+            account.is_native = false;
+            account.amount = 0;
+        };
+
+        Ok(())
+    }
+
+    /// Processes a [InitializeMultisig](enum.TokenInstruction.html) instruction.
+    pub fn process_initialize_multisig(accounts: &[AccountInfo], m: u8) -> ProgramResult {
+        let account_info_iter = &mut accounts.iter();
+        let multisig_info = next_account_info(account_info_iter)?;
+        let mut multisig_account_data = multisig_info.data.borrow_mut();
+        let mut multisig: &mut Multisig = state::unpack_unchecked(&mut multisig_account_data)?;
+        if multisig.is_initialized {
+            return Err(TokenError::AlreadyInUse.into());
+        }
+
+        let signer_infos = account_info_iter.as_slice();
+        multisig.m = m;
+        multisig.n = signer_infos.len() as u8;
+        if !is_valid_signer_index(multisig.n as usize) {
+            return Err(TokenError::InvalidNumberOfProvidedSigners.into());
+        }
+        if !is_valid_signer_index(multisig.m as usize) {
+            return Err(TokenError::InvalidNumberOfRequiredSigners.into());
+        }
+        for (i, signer_info) in signer_infos.iter().enumerate() {
+            multisig.signers[i] = *signer_info.key;
+        }
+        multisig.is_initialized = true;
+
+        Ok(())
+    }
+
+    /// Processes a [Transfer](enum.TokenInstruction.html) instruction.
+    pub fn process_transfer(
+        program_id: &Pubkey,
+        accounts: &[AccountInfo],
+        amount: u64,
+    ) -> ProgramResult {
+        let account_info_iter = &mut accounts.iter();
+        let source_account_info = next_account_info(account_info_iter)?;
+        let dest_account_info = next_account_info(account_info_iter)?;
+        let authority_info = next_account_info(account_info_iter)?;
+
+        if source_account_info.key == dest_account_info.key {
+            return Ok(());
+        }
+
+        let mut source_data = source_account_info.data.borrow_mut();
+        let mut source_account: &mut Account = state::unpack(&mut source_data)?;
+        let mut dest_data = dest_account_info.data.borrow_mut();
+        let mut dest_account: &mut Account = state::unpack(&mut dest_data)?;
+
+        if source_account.amount < amount {
+            return Err(TokenError::InsufficientFunds.into());
+        }
+        if source_account.mint != dest_account.mint {
+            return Err(TokenError::MintMismatch.into());
+        }
+        if source_account.is_frozen() || dest_account.is_frozen() {
+            return Err(TokenError::AccountFrozen.into());
+        }
+
+        match source_account.delegate {
+            COption::Some(ref delegate) if authority_info.key == delegate => {
+                Self::validate_owner(
+                    program_id,
+                    delegate,
+                    authority_info,
+                    account_info_iter.as_slice(),
+                )?;
+                if source_account.delegated_amount < amount {
+                    return Err(TokenError::InsufficientFunds.into());
+                }
+                source_account.delegated_amount -= amount;
+                if source_account.delegated_amount == 0 {
+                    source_account.delegate = COption::None;
+                }
+            }
+            _ => Self::validate_owner(
+                program_id,
+                &source_account.owner,
+                authority_info,
+                account_info_iter.as_slice(),
+            )?,
+        };
+
+        source_account.amount -= amount;
+        dest_account.amount = dest_account
+            .amount
+            .checked_add(amount)
+            .ok_or(TokenError::Overflow)?;
+
+        if source_account.is_native {
+            **source_account_info.lamports.borrow_mut() -= amount;
+            **dest_account_info.lamports.borrow_mut() += amount;
+        }
+
+        Ok(())
+    }
+
+    /// Processes an [Approve](enum.TokenInstruction.html) instruction.
+    pub fn process_approve(
+        program_id: &Pubkey,
+        accounts: &[AccountInfo],
+        amount: u64,
+    ) -> ProgramResult {
+        let account_info_iter = &mut accounts.iter();
+        let source_account_info = next_account_info(account_info_iter)?;
+
+        let mut source_data = source_account_info.data.borrow_mut();
+        let mut source_account: &mut Account = state::unpack(&mut source_data)?;
+        let delegate_info = next_account_info(account_info_iter)?;
+        let owner_info = next_account_info(account_info_iter)?;
+
+        if source_account.is_frozen() {
+            return Err(TokenError::AccountFrozen.into());
+        }
+
+        Self::validate_owner(
+            program_id,
+            &source_account.owner,
+            owner_info,
+            account_info_iter.as_slice(),
+        )?;
+
+        source_account.delegate = COption::Some(*delegate_info.key);
+        source_account.delegated_amount = amount;
+
+        Ok(())
+    }
+
+    /// Processes an [Revoke](enum.TokenInstruction.html) instruction.
+    pub fn process_revoke(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
+        let account_info_iter = &mut accounts.iter();
+        let source_account_info = next_account_info(account_info_iter)?;
+
+        let mut source_data = source_account_info.data.borrow_mut();
+        let mut source_account: &mut Account = state::unpack(&mut source_data)?;
+        let owner_info = next_account_info(account_info_iter)?;
+
+        if source_account.is_frozen() {
+            return Err(TokenError::AccountFrozen.into());
+        }
+
+        Self::validate_owner(
+            program_id,
+            &source_account.owner,
+            owner_info,
+            account_info_iter.as_slice(),
+        )?;
+
+        source_account.delegate = COption::None;
+        source_account.delegated_amount = 0;
+
+        Ok(())
+    }
+
+    /// Processes a [SetAuthority](enum.TokenInstruction.html) instruction.
+    pub fn process_set_authority(
+        program_id: &Pubkey,
+        accounts: &[AccountInfo],
+        authority_type: AuthorityType,
+        new_authority: COption<Pubkey>,
+    ) -> ProgramResult {
+        let account_info_iter = &mut accounts.iter();
+        let account_info = next_account_info(account_info_iter)?;
+        let authority_info = next_account_info(account_info_iter)?;
+
+        if account_info.data_len() == size_of::<Account>() {
+            let mut account_data = account_info.data.borrow_mut();
+            let mut account: &mut Account = state::unpack(&mut account_data)?;
+
+            if account.is_frozen() {
+                return Err(TokenError::AccountFrozen.into());
+            }
+
+            match authority_type {
+                AuthorityType::AccountHolder => {
+                    Self::validate_owner(
+                        program_id,
+                        &account.owner,
+                        authority_info,
+                        account_info_iter.as_slice(),
+                    )?;
+
+                    if let COption::Some(authority) = new_authority {
+                        account.owner = authority;
+                    } else {
+                        return Err(TokenError::InvalidInstruction.into());
+                    }
+                }
+                AuthorityType::CloseAccount => {
+                    let authority = account.close_authority.unwrap_or(account.owner);
+                    Self::validate_owner(
+                        program_id,
+                        &authority,
+                        authority_info,
+                        account_info_iter.as_slice(),
+                    )?;
+                    account.close_authority = new_authority;
+                }
+                _ => {
+                    return Err(TokenError::AuthorityTypeNotSupported.into());
+                }
+            }
+        } else if account_info.data_len() == size_of::<Mint>() {
+            let mut account_data = account_info.data.borrow_mut();
+            let mut mint: &mut Mint = state::unpack(&mut account_data)?;
+
+            match authority_type {
+                AuthorityType::MintTokens => {
+                    // Once a mint's supply is fixed, it cannot be undone by setting a new
+                    // mint_authority
+                    let mint_authority = mint
+                        .mint_authority
+                        .ok_or(Into::<ProgramError>::into(TokenError::FixedSupply))?;
+                    Self::validate_owner(
+                        program_id,
+                        &mint_authority,
+                        authority_info,
+                        account_info_iter.as_slice(),
+                    )?;
+                    mint.mint_authority = new_authority;
+                }
+                AuthorityType::FreezeAccount => {
+                    // Once a mint's freeze authority is disabled, it cannot be re-enabled by
+                    // setting a new freeze_authority
+                    let freeze_authority = mint
+                        .freeze_authority
+                        .ok_or(Into::<ProgramError>::into(TokenError::MintCannotFreeze))?;
+                    Self::validate_owner(
+                        program_id,
+                        &freeze_authority,
+                        authority_info,
+                        account_info_iter.as_slice(),
+                    )?;
+                    mint.freeze_authority = new_authority;
+                }
+                _ => {
+                    return Err(TokenError::AuthorityTypeNotSupported.into());
+                }
+            }
+        } else {
+            return Err(ProgramError::InvalidArgument);
+        }
+
+        Ok(())
+    }
+
+    /// Processes a [MintTo](enum.TokenInstruction.html) instruction.
+    pub fn process_mint_to(
+        program_id: &Pubkey,
+        accounts: &[AccountInfo],
+        amount: u64,
+    ) -> ProgramResult {
+        let account_info_iter = &mut accounts.iter();
+        let mint_info = next_account_info(account_info_iter)?;
+        let dest_account_info = next_account_info(account_info_iter)?;
+        let owner_info = next_account_info(account_info_iter)?;
+
+        let mut dest_account_data = dest_account_info.data.borrow_mut();
+        let mut dest_account: &mut Account = state::unpack(&mut dest_account_data)?;
+
+        if dest_account.is_frozen() {
+            return Err(TokenError::AccountFrozen.into());
+        }
+
+        if dest_account.is_native {
+            return Err(TokenError::NativeNotSupported.into());
+        }
+        if mint_info.key != &dest_account.mint {
+            return Err(TokenError::MintMismatch.into());
+        }
+
+        let mut mint_info_data = mint_info.data.borrow_mut();
+        let mint: &mut Mint = state::unpack(&mut mint_info_data)?;
+
+        match mint.mint_authority {
+            COption::Some(mint_authority) => {
+                Self::validate_owner(
+                    program_id,
+                    &mint_authority,
+                    owner_info,
+                    account_info_iter.as_slice(),
+                )?;
+            }
+            COption::None => {
+                return Err(TokenError::FixedSupply.into());
+            }
+        }
+
+        dest_account.amount = dest_account
+            .amount
+            .checked_add(amount)
+            .ok_or(TokenError::Overflow)?;
+
+        Ok(())
+    }
+
+    /// Processes a [Burn](enum.TokenInstruction.html) instruction.
+    pub fn process_burn(
+        program_id: &Pubkey,
+        accounts: &[AccountInfo],
+        amount: u64,
+    ) -> ProgramResult {
+        let account_info_iter = &mut accounts.iter();
+        let source_account_info = next_account_info(account_info_iter)?;
+        let authority_info = next_account_info(account_info_iter)?;
+
+        let mut source_data = source_account_info.data.borrow_mut();
+        let source_account: &mut Account = state::unpack(&mut source_data)?;
+
+        if source_account.is_native {
+            return Err(TokenError::NativeNotSupported.into());
+        }
+        if source_account.amount < amount {
+            return Err(TokenError::InsufficientFunds.into());
+        }
+        if source_account.is_frozen() {
+            return Err(TokenError::AccountFrozen.into());
+        }
+
+        match source_account.delegate {
+            COption::Some(ref delegate) if authority_info.key == delegate => {
+                Self::validate_owner(
+                    program_id,
+                    delegate,
+                    authority_info,
+                    account_info_iter.as_slice(),
+                )?;
+
+                if source_account.delegated_amount < amount {
+                    return Err(TokenError::InsufficientFunds.into());
+                }
+                source_account.delegated_amount -= amount;
+                if source_account.delegated_amount == 0 {
+                    source_account.delegate = COption::None;
+                }
+            }
+            _ => Self::validate_owner(
+                program_id,
+                &source_account.owner,
+                authority_info,
+                account_info_iter.as_slice(),
+            )?,
+        }
+
+        source_account.amount -= amount;
+
+        Ok(())
+    }
+
+    /// Processes a [CloseAccount](enum.TokenInstruction.html) instruction.
+    pub fn process_close_account(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
+        let account_info_iter = &mut accounts.iter();
+        let source_account_info = next_account_info(account_info_iter)?;
+        let dest_account_info = next_account_info(account_info_iter)?;
+        let authority_info = next_account_info(account_info_iter)?;
+
+        let mut source_data = source_account_info.data.borrow_mut();
+        let source_account: &mut Account = state::unpack(&mut source_data)?;
+
+        if !source_account.is_native && source_account.amount != 0 {
+            return Err(TokenError::NonNativeHasBalance.into());
+        }
+
+        let authority = source_account
+            .close_authority
+            .unwrap_or(source_account.owner);
+        Self::validate_owner(
+            program_id,
+            &authority,
+            authority_info,
+            account_info_iter.as_slice(),
+        )?;
+
+        **dest_account_info.lamports.borrow_mut() += source_account_info.lamports();
+        **source_account_info.lamports.borrow_mut() = 0;
+        source_account.amount = 0;
+
+        Ok(())
+    }
+
+    /// Processes a [FreezeAccount](enum.TokenInstruction.html) or a
+    /// [ThawAccount](enum.TokenInstruction.html) instruction.
+    pub fn process_toggle_freeze_account(
+        program_id: &Pubkey,
+        accounts: &[AccountInfo],
+        freeze: bool,
+    ) -> ProgramResult {
+        let account_info_iter = &mut accounts.iter();
+        let source_account_info = next_account_info(account_info_iter)?;
+        let mint_info = next_account_info(account_info_iter)?;
+        let authority_info = next_account_info(account_info_iter)?;
+
+        let mut source_data = source_account_info.data.borrow_mut();
+        let source_account: &mut Account = state::unpack(&mut source_data)?;
+
+        if source_account.is_native {
+            return Err(TokenError::NativeNotSupported.into());
+        }
+        if mint_info.key != &source_account.mint {
+            return Err(TokenError::MintMismatch.into());
+        }
+        if freeze && source_account.is_frozen() || !freeze && !source_account.is_frozen() {
+            return Err(TokenError::InvalidState.into());
+        }
+
+        let mut mint_info_data = mint_info.data.borrow_mut();
+        let mint: &mut Mint = state::unpack(&mut mint_info_data)?;
+
+        match mint.freeze_authority {
+            COption::Some(authority) => {
+                Self::validate_owner(
+                    program_id,
+                    &authority,
+                    authority_info,
+                    account_info_iter.as_slice(),
+                )?;
+            }
+            COption::None => {
+                return Err(TokenError::MintCannotFreeze.into());
+            }
+        }
+
+        source_account.state = if freeze {
+            AccountState::Frozen
+        } else {
+            AccountState::Initialized
+        };
+
+        Ok(())
+    }
+
+    /// Processes an [Instruction](enum.Instruction.html).
+    pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
+        let instruction = TokenInstruction::unpack(input)?;
+
+        match instruction {
+            TokenInstruction::InitializeMint {
+                decimals,
+                mint_authority,
+                freeze_authority,
+            } => {
+                info!("Instruction: InitializeMint");
+                Self::process_initialize_mint(accounts, decimals, mint_authority, freeze_authority)
+            }
+            TokenInstruction::InitializeAccount => {
+                info!("Instruction: InitializeAccount");
+                Self::process_initialize_account(accounts)
+            }
+            TokenInstruction::InitializeMultisig { m } => {
+                info!("Instruction: InitializeMultisig");
+                Self::process_initialize_multisig(accounts, m)
+            }
+            TokenInstruction::Transfer { amount } => {
+                info!("Instruction: Transfer");
+                Self::process_transfer(program_id, accounts, amount)
+            }
+            TokenInstruction::Approve { amount } => {
+                info!("Instruction: Approve");
+                Self::process_approve(program_id, accounts, amount)
+            }
+            TokenInstruction::Revoke => {
+                info!("Instruction: Revoke");
+                Self::process_revoke(program_id, accounts)
+            }
+            TokenInstruction::SetAuthority {
+                authority_type,
+                new_authority,
+            } => {
+                info!("Instruction: SetAuthority");
+                Self::process_set_authority(program_id, accounts, authority_type, new_authority)
+            }
+            TokenInstruction::MintTo { amount } => {
+                info!("Instruction: MintTo");
+                Self::process_mint_to(program_id, accounts, amount)
+            }
+            TokenInstruction::Burn { amount } => {
+                info!("Instruction: Burn");
+                Self::process_burn(program_id, accounts, amount)
+            }
+            TokenInstruction::CloseAccount => {
+                info!("Instruction: CloseAccount");
+                Self::process_close_account(program_id, accounts)
+            }
+            TokenInstruction::FreezeAccount => {
+                info!("Instruction: FreezeAccount");
+                Self::process_toggle_freeze_account(program_id, accounts, true)
+            }
+            TokenInstruction::ThawAccount => {
+                info!("Instruction: FreezeAccount");
+                Self::process_toggle_freeze_account(program_id, accounts, false)
+            }
+        }
+    }
+
+    /// Validates owner(s) are present
+    pub fn validate_owner(
+        program_id: &Pubkey,
+        expected_owner: &Pubkey,
+        owner_account_info: &AccountInfo,
+        signers: &[AccountInfo],
+    ) -> ProgramResult {
+        if expected_owner != owner_account_info.key {
+            return Err(TokenError::OwnerMismatch.into());
+        }
+        if program_id == owner_account_info.owner
+            && owner_account_info.data_len() == std::mem::size_of::<Multisig>()
+        {
+            let mut owner_data = owner_account_info.data.borrow_mut();
+            let multisig: &mut Multisig = state::unpack(&mut owner_data)?;
+            let mut num_signers = 0;
+            for signer in signers.iter() {
+                if multisig.signers[0..multisig.n as usize].contains(signer.key) {
+                    if !signer.is_signer {
+                        return Err(ProgramError::MissingRequiredSignature);
+                    }
+                    num_signers += 1;
+                }
+            }
+            if num_signers < multisig.m {
+                return Err(ProgramError::MissingRequiredSignature);
+            }
+        } else if !owner_account_info.is_signer {
+            return Err(ProgramError::MissingRequiredSignature);
+        }
+        Ok(())
+    }
+}
+
+impl PrintProgramError for TokenError {
+    fn print<E>(&self)
+    where
+        E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
+    {
+        match self {
+            TokenError::InsufficientFunds => info!("Error: insufficient funds"),
+            TokenError::MintMismatch => info!("Error: Account not associated with this Mint"),
+            TokenError::OwnerMismatch => info!("Error: owner does not match"),
+            TokenError::FixedSupply => info!("Error: the total supply of this token is fixed"),
+            TokenError::AlreadyInUse => info!("Error: account or token already in use"),
+            TokenError::OwnerRequiredIfNoInitialSupply => {
+                info!("Error: An owner is required if supply is zero")
+            }
+            TokenError::InvalidNumberOfProvidedSigners => {
+                info!("Error: Invalid number of provided signers")
+            }
+            TokenError::InvalidNumberOfRequiredSigners => {
+                info!("Error: Invalid number of required signers")
+            }
+            TokenError::UninitializedState => info!("Error: State is uninitialized"),
+            TokenError::NativeNotSupported => {
+                info!("Error: Instruction does not support native tokens")
+            }
+            TokenError::NonNativeHasBalance => {
+                info!("Error: Non-native account can only be closed if its balance is zero")
+            }
+            TokenError::InvalidInstruction => info!("Error: Invalid instruction"),
+            TokenError::InvalidState => info!("Error: Invalid account state for operation"),
+            TokenError::Overflow => info!("Error: Operation overflowed"),
+            TokenError::AuthorityTypeNotSupported => {
+                info!("Error: Account does not support specified authority type")
+            }
+            TokenError::MintCannotFreeze => info!("Error: This token mint cannot freeze accounts"),
+            TokenError::AccountFrozen => info!("Error: Account is frozen"),
+        }
+    }
+}
+
+// Pull in syscall stubs when building for non-BPF targets
+#[cfg(not(target_arch = "bpf"))]
+solana_sdk::program_stubs!();
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::instruction::{
+        approve, burn, close_account, freeze_account, initialize_account, initialize_mint,
+        initialize_multisig, mint_to, revoke, set_authority, thaw_account, transfer, MAX_SIGNERS,
+    };
+    use solana_sdk::{
+        account::Account as SolanaAccount, account_info::create_is_signer_account_infos,
+        clock::Epoch, instruction::Instruction,
+    };
+
+    fn pubkey_rand() -> Pubkey {
+        Pubkey::new(&rand::random::<[u8; 32]>())
+    }
+
+    fn do_process_instruction(
+        instruction: Instruction,
+        accounts: Vec<&mut SolanaAccount>,
+    ) -> ProgramResult {
+        let mut meta = instruction
+            .accounts
+            .iter()
+            .zip(accounts)
+            .map(|(account_meta, account)| (&account_meta.pubkey, account_meta.is_signer, account))
+            .collect::<Vec<_>>();
+
+        let account_infos = create_is_signer_account_infos(&mut meta);
+        Processor::process(&instruction.program_id, &account_infos, &instruction.data)
+    }
+
+    fn return_token_error_as_program_error() -> ProgramError {
+        TokenError::MintMismatch.into()
+    }
+
+    #[test]
+    fn test_print_error() {
+        let error = return_token_error_as_program_error();
+        error.print::<TokenError>();
+    }
+
+    #[test]
+    #[should_panic(expected = "Custom(1)")]
+    fn test_error_unwrap() {
+        Err::<(), ProgramError>(return_token_error_as_program_error()).unwrap();
+    }
+
+    #[test]
+    fn test_unique_account_sizes() {
+        assert_ne!(size_of::<Mint>(), 0);
+        assert_ne!(size_of::<Mint>(), size_of::<Account>());
+        assert_ne!(size_of::<Mint>(), size_of::<Multisig>());
+        assert_ne!(size_of::<Account>(), 0);
+        assert_ne!(size_of::<Account>(), size_of::<Multisig>());
+        assert_ne!(size_of::<Multisig>(), 0);
+    }
+
+    #[test]
+    fn test_initialize_mint() {
+        let program_id = pubkey_rand();
+        let owner_key = pubkey_rand();
+        let mint_key = pubkey_rand();
+        let mut mint_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+        let mint2_key = pubkey_rand();
+        let mut mint2_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+
+        // mint_authority not provided
+        let mut instruction = initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap();
+        instruction.data = TokenInstruction::InitializeMint {
+            mint_authority: COption::None,
+            freeze_authority: COption::None,
+            decimals: 2,
+        }
+        .pack()
+        .unwrap();
+        assert_eq!(
+            Err(TokenError::OwnerRequiredIfNoInitialSupply.into()),
+            do_process_instruction(instruction, vec![&mut mint_account])
+        );
+
+        // create new mint
+        do_process_instruction(
+            initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(),
+            vec![&mut mint_account],
+        )
+        .unwrap();
+
+        // create twice
+        assert_eq!(
+            Err(TokenError::AlreadyInUse.into()),
+            do_process_instruction(
+                initialize_mint(&program_id, &mint_key, &owner_key, None, 2,).unwrap(),
+                vec![&mut mint_account]
+            )
+        );
+
+        // create another mint that can freeze
+        do_process_instruction(
+            initialize_mint(&program_id, &mint2_key, &owner_key, Some(&owner_key), 2).unwrap(),
+            vec![&mut mint2_account],
+        )
+        .unwrap();
+        let mint2: &mut Mint = state::unpack(&mut mint2_account.data).unwrap();
+        assert_eq!(mint2.freeze_authority, COption::Some(owner_key));
+    }
+
+    #[test]
+    fn test_initialize_mint_account() {
+        let program_id = pubkey_rand();
+        let account_key = pubkey_rand();
+        let mut account_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let owner_key = pubkey_rand();
+        let mut owner_account = SolanaAccount::default();
+        let mint_key = pubkey_rand();
+        let mut mint_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+
+        // create account
+        do_process_instruction(
+            initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // create twice
+        assert_eq!(
+            Err(TokenError::AlreadyInUse.into()),
+            do_process_instruction(
+                initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
+                vec![&mut account_account, &mut mint_account, &mut owner_account],
+            )
+        );
+    }
+
+    #[test]
+    fn test_transfer() {
+        let program_id = pubkey_rand();
+        let account_key = pubkey_rand();
+        let mut account_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let account2_key = pubkey_rand();
+        let mut account2_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let account3_key = pubkey_rand();
+        let mut account3_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let delegate_key = pubkey_rand();
+        let mut delegate_account = SolanaAccount::default();
+        let mismatch_key = pubkey_rand();
+        let mut mismatch_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let owner_key = pubkey_rand();
+        let mut owner_account = SolanaAccount::default();
+        let owner2_key = pubkey_rand();
+        let mut owner2_account = SolanaAccount::default();
+        let mint_key = pubkey_rand();
+        let mut mint_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+        let mint2_key = pubkey_rand();
+        let mut mint2_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+
+        // create account
+        do_process_instruction(
+            initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // create another account
+        do_process_instruction(
+            initialize_account(&program_id, &account2_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account2_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // create another account
+        do_process_instruction(
+            initialize_account(&program_id, &account3_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account3_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // create mismatch account
+        do_process_instruction(
+            initialize_account(&program_id, &mismatch_key, &mint2_key, &owner_key).unwrap(),
+            vec![
+                &mut mismatch_account,
+                &mut mint2_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+
+        // create new mint & mint to account
+        do_process_instruction(
+            initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(),
+            vec![&mut mint_account],
+        )
+        .unwrap();
+        do_process_instruction(
+            mint_to(&program_id, &mint_key, &account_key, &owner_key, &[], 1000).unwrap(),
+            vec![&mut mint_account, &mut account_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // missing signer
+        let mut instruction = transfer(
+            &program_id,
+            &account_key,
+            &account2_key,
+            &owner_key,
+            &[],
+            1000,
+        )
+        .unwrap();
+        instruction.accounts[2].is_signer = false;
+        assert_eq!(
+            Err(ProgramError::MissingRequiredSignature),
+            do_process_instruction(
+                instruction,
+                vec![
+                    &mut account_account,
+                    &mut account2_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+
+        // mismatch mint
+        assert_eq!(
+            Err(TokenError::MintMismatch.into()),
+            do_process_instruction(
+                transfer(
+                    &program_id,
+                    &account_key,
+                    &mismatch_key,
+                    &owner_key,
+                    &[],
+                    1000
+                )
+                .unwrap(),
+                vec![
+                    &mut account_account,
+                    &mut mismatch_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+
+        // missing owner
+        assert_eq!(
+            Err(TokenError::OwnerMismatch.into()),
+            do_process_instruction(
+                transfer(
+                    &program_id,
+                    &account_key,
+                    &account2_key,
+                    &owner2_key,
+                    &[],
+                    1000
+                )
+                .unwrap(),
+                vec![
+                    &mut account_account,
+                    &mut account2_account,
+                    &mut owner2_account,
+                ],
+            )
+        );
+
+        // transfer
+        do_process_instruction(
+            transfer(
+                &program_id,
+                &account_key,
+                &account2_key,
+                &owner_key,
+                &[],
+                1000,
+            )
+            .unwrap(),
+            vec![
+                &mut account_account,
+                &mut account2_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+
+        // insufficient funds
+        assert_eq!(
+            Err(TokenError::InsufficientFunds.into()),
+            do_process_instruction(
+                transfer(&program_id, &account_key, &account2_key, &owner_key, &[], 1).unwrap(),
+                vec![
+                    &mut account_account,
+                    &mut account2_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+
+        // transfer half back
+        do_process_instruction(
+            transfer(
+                &program_id,
+                &account2_key,
+                &account_key,
+                &owner_key,
+                &[],
+                500,
+            )
+            .unwrap(),
+            vec![
+                &mut account2_account,
+                &mut account_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+
+        // transfer rest
+        do_process_instruction(
+            transfer(
+                &program_id,
+                &account2_key,
+                &account_key,
+                &owner_key,
+                &[],
+                500,
+            )
+            .unwrap(),
+            vec![
+                &mut account2_account,
+                &mut account_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+
+        // transfer to self
+        {
+            let instruction = transfer(
+                &program_id,
+                &account_key,
+                &account_key,
+                &owner_key,
+                &[],
+                500,
+            )
+            .unwrap();
+            let account_account_info = AccountInfo::from((
+                &instruction.accounts[0].pubkey,
+                instruction.accounts[0].is_signer,
+                &mut account_account,
+            ));
+            let owner_account_info = AccountInfo::from((
+                &instruction.accounts[2].pubkey,
+                instruction.accounts[2].is_signer,
+                &mut owner_account,
+            ));
+            Processor::process(
+                &instruction.program_id,
+                &[
+                    account_account_info.clone(),
+                    account_account_info,
+                    owner_account_info,
+                ],
+                &instruction.data,
+            )
+            .unwrap()
+        }
+        let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        assert_eq!(account.amount, 1000);
+
+        // insufficient funds
+        assert_eq!(
+            Err(TokenError::InsufficientFunds.into()),
+            do_process_instruction(
+                transfer(&program_id, &account2_key, &account_key, &owner_key, &[], 1).unwrap(),
+                vec![
+                    &mut account2_account,
+                    &mut account_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+
+        // approve delegate
+        do_process_instruction(
+            approve(
+                &program_id,
+                &account_key,
+                &delegate_key,
+                &owner_key,
+                &[],
+                100,
+            )
+            .unwrap(),
+            vec![
+                &mut account_account,
+                &mut delegate_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+
+        // transfer via delegate
+        do_process_instruction(
+            transfer(
+                &program_id,
+                &account_key,
+                &account2_key,
+                &delegate_key,
+                &[],
+                100,
+            )
+            .unwrap(),
+            vec![
+                &mut account_account,
+                &mut account2_account,
+                &mut delegate_account,
+            ],
+        )
+        .unwrap();
+
+        // insufficient funds approved via delegate
+        assert_eq!(
+            Err(TokenError::OwnerMismatch.into()),
+            do_process_instruction(
+                transfer(
+                    &program_id,
+                    &account_key,
+                    &account2_key,
+                    &delegate_key,
+                    &[],
+                    100
+                )
+                .unwrap(),
+                vec![
+                    &mut account_account,
+                    &mut account2_account,
+                    &mut delegate_account,
+                ],
+            )
+        );
+
+        // transfer rest
+        do_process_instruction(
+            transfer(
+                &program_id,
+                &account_key,
+                &account2_key,
+                &owner_key,
+                &[],
+                900,
+            )
+            .unwrap(),
+            vec![
+                &mut account_account,
+                &mut account2_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+
+        // approve delegate
+        do_process_instruction(
+            approve(
+                &program_id,
+                &account_key,
+                &delegate_key,
+                &owner_key,
+                &[],
+                100,
+            )
+            .unwrap(),
+            vec![
+                &mut account_account,
+                &mut delegate_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+
+        // insufficient funds in source account via delegate
+        assert_eq!(
+            Err(TokenError::InsufficientFunds.into()),
+            do_process_instruction(
+                transfer(
+                    &program_id,
+                    &account_key,
+                    &account2_key,
+                    &delegate_key,
+                    &[],
+                    100
+                )
+                .unwrap(),
+                vec![
+                    &mut account_account,
+                    &mut account2_account,
+                    &mut delegate_account,
+                ],
+            )
+        );
+    }
+
+    #[test]
+    fn test_mintable_token_with_zero_supply() {
+        let program_id = pubkey_rand();
+        let account_key = pubkey_rand();
+        let mut account_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let owner_key = pubkey_rand();
+        let mut owner_account = SolanaAccount::default();
+        let mint_key = pubkey_rand();
+        let mut mint_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+
+        // create account
+        do_process_instruction(
+            initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account_account, &mut owner_account, &mut mint_account],
+        )
+        .unwrap();
+
+        // create mint-able token with zero supply
+        let decimals = 2;
+        do_process_instruction(
+            initialize_mint(&program_id, &mint_key, &owner_key, None, decimals).unwrap(),
+            vec![&mut mint_account],
+        )
+        .unwrap();
+        let mint: &mut Mint = state::unpack(&mut mint_account.data).unwrap();
+        assert_eq!(
+            *mint,
+            Mint {
+                mint_authority: COption::Some(owner_key),
+                decimals,
+                is_initialized: true,
+                freeze_authority: COption::None,
+            }
+        );
+
+        // mint to
+        do_process_instruction(
+            mint_to(&program_id, &mint_key, &account_key, &owner_key, &[], 42).unwrap(),
+            vec![&mut mint_account, &mut account_account, &mut owner_account],
+        )
+        .unwrap();
+
+        let _: &mut Mint = state::unpack(&mut mint_account.data).unwrap();
+        let dest_account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        assert_eq!(dest_account.amount, 42);
+    }
+
+    #[test]
+    fn test_approve() {
+        let program_id = pubkey_rand();
+        let account_key = pubkey_rand();
+        let mut account_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let account2_key = pubkey_rand();
+        let mut account2_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let delegate_key = pubkey_rand();
+        let mut delegate_account = SolanaAccount::default();
+        let owner_key = pubkey_rand();
+        let mut owner_account = SolanaAccount::default();
+        let owner2_key = pubkey_rand();
+        let mut owner2_account = SolanaAccount::default();
+        let mint_key = pubkey_rand();
+        let mut mint_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+
+        // create account
+        do_process_instruction(
+            initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account_account, &mut owner_account, &mut mint_account],
+        )
+        .unwrap();
+
+        // create another account
+        do_process_instruction(
+            initialize_account(&program_id, &account2_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account2_account, &mut owner_account, &mut mint_account],
+        )
+        .unwrap();
+
+        // create new mint & mint to account
+        do_process_instruction(
+            initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(),
+            vec![&mut mint_account, &mut account_account],
+        )
+        .unwrap();
+        do_process_instruction(
+            mint_to(&program_id, &mint_key, &account_key, &owner_key, &[], 1000).unwrap(),
+            vec![&mut mint_account, &mut account_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // missing signer
+        let mut instruction = approve(
+            &program_id,
+            &account_key,
+            &delegate_key,
+            &owner_key,
+            &[],
+            100,
+        )
+        .unwrap();
+        instruction.accounts[2].is_signer = false;
+        assert_eq!(
+            Err(ProgramError::MissingRequiredSignature),
+            do_process_instruction(
+                instruction,
+                vec![
+                    &mut account_account,
+                    &mut delegate_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+
+        // no owner
+        assert_eq!(
+            Err(TokenError::OwnerMismatch.into()),
+            do_process_instruction(
+                approve(
+                    &program_id,
+                    &account_key,
+                    &delegate_key,
+                    &owner2_key,
+                    &[],
+                    100
+                )
+                .unwrap(),
+                vec![
+                    &mut account_account,
+                    &mut delegate_account,
+                    &mut owner2_account,
+                ],
+            )
+        );
+
+        // approve delegate
+        do_process_instruction(
+            approve(
+                &program_id,
+                &account_key,
+                &delegate_key,
+                &owner_key,
+                &[],
+                100,
+            )
+            .unwrap(),
+            vec![
+                &mut account_account,
+                &mut delegate_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+
+        // revoke delegate
+        do_process_instruction(
+            revoke(&program_id, &account_key, &owner_key, &[]).unwrap(),
+            vec![&mut account_account, &mut owner_account],
+        )
+        .unwrap();
+    }
+
+    #[test]
+    fn test_set_authority() {
+        let program_id = pubkey_rand();
+        let account_key = pubkey_rand();
+        let mut account_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let account2_key = pubkey_rand();
+        let mut account2_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let owner_key = pubkey_rand();
+        let mut owner_account = SolanaAccount::default();
+        let owner2_key = pubkey_rand();
+        let mut owner2_account = SolanaAccount::default();
+        let owner3_key = pubkey_rand();
+        let mint_key = pubkey_rand();
+        let mut mint_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+        let mint2_key = pubkey_rand();
+        let mut mint2_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+
+        // invalid account
+        assert_eq!(
+            Err(TokenError::UninitializedState.into()),
+            do_process_instruction(
+                set_authority(
+                    &program_id,
+                    &account_key,
+                    Some(&owner2_key),
+                    AuthorityType::AccountHolder,
+                    &owner_key,
+                    &[]
+                )
+                .unwrap(),
+                vec![&mut account_account, &mut owner_account],
+            )
+        );
+
+        // create account
+        do_process_instruction(
+            initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // create another account
+        do_process_instruction(
+            initialize_account(&program_id, &account2_key, &mint2_key, &owner_key).unwrap(),
+            vec![
+                &mut account2_account,
+                &mut mint2_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+
+        // missing owner
+        assert_eq!(
+            Err(TokenError::OwnerMismatch.into()),
+            do_process_instruction(
+                set_authority(
+                    &program_id,
+                    &account_key,
+                    Some(&owner_key),
+                    AuthorityType::AccountHolder,
+                    &owner2_key,
+                    &[]
+                )
+                .unwrap(),
+                vec![&mut account_account, &mut owner2_account],
+            )
+        );
+
+        // owner did not sign
+        let mut instruction = set_authority(
+            &program_id,
+            &account_key,
+            Some(&owner2_key),
+            AuthorityType::AccountHolder,
+            &owner_key,
+            &[],
+        )
+        .unwrap();
+        instruction.accounts[1].is_signer = false;
+        assert_eq!(
+            Err(ProgramError::MissingRequiredSignature),
+            do_process_instruction(instruction, vec![&mut account_account, &mut owner_account,],)
+        );
+
+        // wrong authority type
+        assert_eq!(
+            Err(TokenError::AuthorityTypeNotSupported.into()),
+            do_process_instruction(
+                set_authority(
+                    &program_id,
+                    &account_key,
+                    Some(&owner2_key),
+                    AuthorityType::FreezeAccount,
+                    &owner_key,
+                    &[],
+                )
+                .unwrap(),
+                vec![&mut account_account, &mut owner_account],
+            )
+        );
+
+        // account owner may not be set to None
+        assert_eq!(
+            Err(TokenError::InvalidInstruction.into()),
+            do_process_instruction(
+                set_authority(
+                    &program_id,
+                    &account_key,
+                    None,
+                    AuthorityType::AccountHolder,
+                    &owner_key,
+                    &[],
+                )
+                .unwrap(),
+                vec![&mut account_account, &mut owner_account],
+            )
+        );
+
+        // set owner
+        do_process_instruction(
+            set_authority(
+                &program_id,
+                &account_key,
+                Some(&owner2_key),
+                AuthorityType::AccountHolder,
+                &owner_key,
+                &[],
+            )
+            .unwrap(),
+            vec![&mut account_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // set close_authority
+        do_process_instruction(
+            set_authority(
+                &program_id,
+                &account_key,
+                Some(&owner2_key),
+                AuthorityType::CloseAccount,
+                &owner2_key,
+                &[],
+            )
+            .unwrap(),
+            vec![&mut account_account, &mut owner2_account],
+        )
+        .unwrap();
+
+        // close_authority may be set to None
+        do_process_instruction(
+            set_authority(
+                &program_id,
+                &account_key,
+                None,
+                AuthorityType::CloseAccount,
+                &owner2_key,
+                &[],
+            )
+            .unwrap(),
+            vec![&mut account_account, &mut owner2_account],
+        )
+        .unwrap();
+
+        // create new mint with owner
+        do_process_instruction(
+            initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(),
+            vec![&mut mint_account],
+        )
+        .unwrap();
+
+        // wrong owner
+        assert_eq!(
+            Err(TokenError::OwnerMismatch.into()),
+            do_process_instruction(
+                set_authority(
+                    &program_id,
+                    &mint_key,
+                    Some(&owner3_key),
+                    AuthorityType::MintTokens,
+                    &owner2_key,
+                    &[]
+                )
+                .unwrap(),
+                vec![&mut mint_account, &mut owner2_account],
+            )
+        );
+
+        // owner did not sign
+        let mut instruction = set_authority(
+            &program_id,
+            &mint_key,
+            Some(&owner2_key),
+            AuthorityType::MintTokens,
+            &owner_key,
+            &[],
+        )
+        .unwrap();
+        instruction.accounts[1].is_signer = false;
+        assert_eq!(
+            Err(ProgramError::MissingRequiredSignature),
+            do_process_instruction(instruction, vec![&mut mint_account, &mut owner_account],)
+        );
+
+        // cannot freeze
+        assert_eq!(
+            Err(TokenError::MintCannotFreeze.into()),
+            do_process_instruction(
+                set_authority(
+                    &program_id,
+                    &mint_key,
+                    Some(&owner2_key),
+                    AuthorityType::FreezeAccount,
+                    &owner_key,
+                    &[],
+                )
+                .unwrap(),
+                vec![&mut mint_account, &mut owner_account],
+            )
+        );
+
+        // set owner
+        do_process_instruction(
+            set_authority(
+                &program_id,
+                &mint_key,
+                Some(&owner2_key),
+                AuthorityType::MintTokens,
+                &owner_key,
+                &[],
+            )
+            .unwrap(),
+            vec![&mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // set owner to None
+        do_process_instruction(
+            set_authority(
+                &program_id,
+                &mint_key,
+                None,
+                AuthorityType::MintTokens,
+                &owner2_key,
+                &[],
+            )
+            .unwrap(),
+            vec![&mut mint_account, &mut owner2_account],
+        )
+        .unwrap();
+
+        // test unsetting mint_authority is one-way operation
+        assert_eq!(
+            Err(TokenError::FixedSupply.into()),
+            do_process_instruction(
+                set_authority(
+                    &program_id,
+                    &mint2_key,
+                    Some(&owner2_key),
+                    AuthorityType::MintTokens,
+                    &owner_key,
+                    &[]
+                )
+                .unwrap(),
+                vec![&mut mint_account, &mut owner_account],
+            )
+        );
+
+        // create mint with owner and freeze_authority
+        do_process_instruction(
+            initialize_mint(&program_id, &mint2_key, &owner_key, Some(&owner_key), 2).unwrap(),
+            vec![&mut mint2_account],
+        )
+        .unwrap();
+
+        // set freeze_authority
+        do_process_instruction(
+            set_authority(
+                &program_id,
+                &mint2_key,
+                Some(&owner2_key),
+                AuthorityType::FreezeAccount,
+                &owner_key,
+                &[],
+            )
+            .unwrap(),
+            vec![&mut mint2_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // test unsetting freeze_authority is one-way operation
+        do_process_instruction(
+            set_authority(
+                &program_id,
+                &mint2_key,
+                None,
+                AuthorityType::FreezeAccount,
+                &owner2_key,
+                &[],
+            )
+            .unwrap(),
+            vec![&mut mint2_account, &mut owner2_account],
+        )
+        .unwrap();
+
+        assert_eq!(
+            Err(TokenError::MintCannotFreeze.into()),
+            do_process_instruction(
+                set_authority(
+                    &program_id,
+                    &mint2_key,
+                    Some(&owner2_key),
+                    AuthorityType::FreezeAccount,
+                    &owner_key,
+                    &[],
+                )
+                .unwrap(),
+                vec![&mut mint2_account, &mut owner2_account],
+            )
+        );
+    }
+
+    #[test]
+    fn test_mint_to() {
+        let program_id = pubkey_rand();
+        let account_key = pubkey_rand();
+        let mut account_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let account2_key = pubkey_rand();
+        let mut account2_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let account3_key = pubkey_rand();
+        let mut account3_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let mismatch_key = pubkey_rand();
+        let mut mismatch_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let owner_key = pubkey_rand();
+        let mut owner_account = SolanaAccount::default();
+        let owner2_key = pubkey_rand();
+        let mut owner2_account = SolanaAccount::default();
+        let mint_key = pubkey_rand();
+        let mut mint_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+        let mint2_key = pubkey_rand();
+        let mut mint2_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+        let uninitialized_key = pubkey_rand();
+        let mut uninitialized_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+
+        // create account
+        do_process_instruction(
+            initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // create another account
+        do_process_instruction(
+            initialize_account(&program_id, &account2_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account2_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // create another account
+        do_process_instruction(
+            initialize_account(&program_id, &account3_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account3_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // create mismatch account
+        do_process_instruction(
+            initialize_account(&program_id, &mismatch_key, &mint2_key, &owner_key).unwrap(),
+            vec![
+                &mut mismatch_account,
+                &mut mint2_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+
+        // create new mint with owner
+        do_process_instruction(
+            initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(),
+            vec![&mut mint_account],
+        )
+        .unwrap();
+
+        // mint to
+        do_process_instruction(
+            mint_to(&program_id, &mint_key, &account2_key, &owner_key, &[], 42).unwrap(),
+            vec![&mut mint_account, &mut account2_account, &mut owner_account],
+        )
+        .unwrap();
+
+        let _: &mut Mint = state::unpack(&mut mint_account.data).unwrap();
+        let dest_account: &mut Account = state::unpack(&mut account2_account.data).unwrap();
+        assert_eq!(dest_account.amount, 42);
+
+        // missing signer
+        let mut instruction =
+            mint_to(&program_id, &mint_key, &account2_key, &owner_key, &[], 42).unwrap();
+        instruction.accounts[2].is_signer = false;
+        assert_eq!(
+            Err(ProgramError::MissingRequiredSignature),
+            do_process_instruction(
+                instruction,
+                vec![&mut mint_account, &mut account2_account, &mut owner_account],
+            )
+        );
+
+        // mismatch account
+        assert_eq!(
+            Err(TokenError::MintMismatch.into()),
+            do_process_instruction(
+                mint_to(&program_id, &mint_key, &mismatch_key, &owner_key, &[], 42).unwrap(),
+                vec![&mut mint_account, &mut mismatch_account, &mut owner_account],
+            )
+        );
+
+        // missing owner
+        assert_eq!(
+            Err(TokenError::OwnerMismatch.into()),
+            do_process_instruction(
+                mint_to(&program_id, &mint_key, &account2_key, &owner2_key, &[], 42).unwrap(),
+                vec![
+                    &mut mint_account,
+                    &mut account2_account,
+                    &mut owner2_account,
+                ],
+            )
+        );
+
+        // uninitialized destination account
+        assert_eq!(
+            Err(TokenError::UninitializedState.into()),
+            do_process_instruction(
+                mint_to(
+                    &program_id,
+                    &mint_key,
+                    &uninitialized_key,
+                    &owner_key,
+                    &[],
+                    42
+                )
+                .unwrap(),
+                vec![
+                    &mut mint_account,
+                    &mut uninitialized_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+
+        // unset mint_authority and test minting fails
+        do_process_instruction(
+            set_authority(
+                &program_id,
+                &mint_key,
+                None,
+                AuthorityType::MintTokens,
+                &owner_key,
+                &[],
+            )
+            .unwrap(),
+            vec![&mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+        assert_eq!(
+            Err(TokenError::FixedSupply.into()),
+            do_process_instruction(
+                mint_to(&program_id, &mint_key, &account2_key, &owner_key, &[], 42).unwrap(),
+                vec![&mut mint_account, &mut account2_account, &mut owner_account],
+            )
+        );
+    }
+
+    #[test]
+    fn test_burn() {
+        let program_id = pubkey_rand();
+        let account_key = pubkey_rand();
+        let mut account_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let account2_key = pubkey_rand();
+        let mut account2_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let account3_key = pubkey_rand();
+        let mut account3_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let delegate_key = pubkey_rand();
+        let mut delegate_account = SolanaAccount::default();
+        let mismatch_key = pubkey_rand();
+        let mut mismatch_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let owner_key = pubkey_rand();
+        let mut owner_account = SolanaAccount::default();
+        let owner2_key = pubkey_rand();
+        let mut owner2_account = SolanaAccount::default();
+        let mint_key = pubkey_rand();
+        let mut mint_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+        let mint2_key = pubkey_rand();
+        let mut mint2_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+
+        // create account
+        do_process_instruction(
+            initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // create another account
+        do_process_instruction(
+            initialize_account(&program_id, &account2_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account2_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // create another account
+        do_process_instruction(
+            initialize_account(&program_id, &account3_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account3_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // create mismatch account
+        do_process_instruction(
+            initialize_account(&program_id, &mismatch_key, &mint2_key, &owner_key).unwrap(),
+            vec![
+                &mut mismatch_account,
+                &mut mint2_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+
+        // create new mint
+        do_process_instruction(
+            initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(),
+            vec![&mut mint_account],
+        )
+        .unwrap();
+        do_process_instruction(
+            mint_to(&program_id, &mint_key, &account_key, &owner_key, &[], 1000).unwrap(),
+            vec![&mut mint_account, &mut account_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // missing signer
+        let mut instruction = burn(&program_id, &account_key, &delegate_key, &[], 42).unwrap();
+        instruction.accounts[1].is_signer = false;
+        assert_eq!(
+            Err(TokenError::OwnerMismatch.into()),
+            do_process_instruction(
+                instruction,
+                vec![&mut account_account, &mut delegate_account],
+            )
+        );
+
+        // missing owner
+        assert_eq!(
+            Err(TokenError::OwnerMismatch.into()),
+            do_process_instruction(
+                burn(&program_id, &account_key, &owner2_key, &[], 42).unwrap(),
+                vec![&mut account_account, &mut owner2_account],
+            )
+        );
+
+        // burn
+        do_process_instruction(
+            burn(&program_id, &account_key, &owner_key, &[], 42).unwrap(),
+            vec![&mut account_account, &mut owner_account],
+        )
+        .unwrap();
+
+        let _: &mut Mint = state::unpack(&mut mint_account.data).unwrap();
+        let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        assert_eq!(account.amount, 1000 - 42);
+
+        // insufficient funds
+        assert_eq!(
+            Err(TokenError::InsufficientFunds.into()),
+            do_process_instruction(
+                burn(&program_id, &account_key, &owner_key, &[], 100_000_000).unwrap(),
+                vec![&mut account_account, &mut owner_account],
+            )
+        );
+
+        // approve delegate
+        do_process_instruction(
+            approve(
+                &program_id,
+                &account_key,
+                &delegate_key,
+                &owner_key,
+                &[],
+                84,
+            )
+            .unwrap(),
+            vec![
+                &mut account_account,
+                &mut delegate_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+
+        // not a delegate of source account
+        assert_eq!(
+            Err(TokenError::InsufficientFunds.into()),
+            do_process_instruction(
+                burn(&program_id, &account_key, &owner_key, &[], 100_000_000).unwrap(),
+                vec![&mut account_account, &mut owner_account],
+            )
+        );
+
+        // burn via delegate
+        do_process_instruction(
+            burn(&program_id, &account_key, &delegate_key, &[], 84).unwrap(),
+            vec![&mut account_account, &mut delegate_account],
+        )
+        .unwrap();
+
+        // match
+        let _: &mut Mint = state::unpack(&mut mint_account.data).unwrap();
+        let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        assert_eq!(account.amount, 1000 - 42 - 84);
+
+        // insufficient funds approved via delegate
+        assert_eq!(
+            Err(TokenError::OwnerMismatch.into()),
+            do_process_instruction(
+                burn(&program_id, &account_key, &delegate_key, &[], 100).unwrap(),
+                vec![&mut account_account, &mut delegate_account],
+            )
+        );
+    }
+
+    #[test]
+    fn test_multisig() {
+        let program_id = pubkey_rand();
+        let mint_key = pubkey_rand();
+        let mut mint_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+        let account_key = pubkey_rand();
+        let mut account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let account2_key = pubkey_rand();
+        let mut account2_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let owner_key = pubkey_rand();
+        let mut owner_account = SolanaAccount::default();
+        let multisig_key = pubkey_rand();
+        let mut multisig_account = SolanaAccount::new(0, size_of::<Multisig>(), &program_id);
+        let multisig_delegate_key = pubkey_rand();
+        let mut multisig_delegate_account =
+            SolanaAccount::new(0, size_of::<Multisig>(), &program_id);
+        let signer_keys = vec![pubkey_rand(); MAX_SIGNERS];
+        let signer_key_refs: Vec<&Pubkey> = signer_keys.iter().map(|key| key).collect();
+        let mut signer_accounts = vec![SolanaAccount::new(0, 0, &program_id); MAX_SIGNERS];
+
+        // single signer
+        let account_info_iter = &mut signer_accounts.iter_mut();
+        do_process_instruction(
+            initialize_multisig(&program_id, &multisig_key, &[&signer_keys[0]], 1).unwrap(),
+            vec![
+                &mut multisig_account,
+                &mut account_info_iter.next().unwrap(),
+            ],
+        )
+        .unwrap();
+
+        // multiple signer
+        let account_info_iter = &mut signer_accounts.iter_mut();
+        do_process_instruction(
+            initialize_multisig(
+                &program_id,
+                &multisig_delegate_key,
+                &signer_key_refs,
+                MAX_SIGNERS as u8,
+            )
+            .unwrap(),
+            vec![
+                &mut multisig_delegate_account,
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+            ],
+        )
+        .unwrap();
+
+        // create account with multisig owner
+        do_process_instruction(
+            initialize_account(&program_id, &account_key, &mint_key, &multisig_key).unwrap(),
+            vec![&mut account, &mut mint_account, &mut multisig_account],
+        )
+        .unwrap();
+
+        // create another account with multisig owner
+        do_process_instruction(
+            initialize_account(
+                &program_id,
+                &account2_key,
+                &mint_key,
+                &multisig_delegate_key,
+            )
+            .unwrap(),
+            vec![
+                &mut account2_account,
+                &mut mint_account,
+                &mut multisig_account,
+            ],
+        )
+        .unwrap();
+
+        // create new mint with multisig owner
+        do_process_instruction(
+            initialize_mint(&program_id, &mint_key, &multisig_key, None, 2).unwrap(),
+            vec![&mut mint_account],
+        )
+        .unwrap();
+        let account_info_iter = &mut signer_accounts.iter_mut();
+        do_process_instruction(
+            mint_to(
+                &program_id,
+                &mint_key,
+                &account_key,
+                &multisig_key,
+                &[&signer_keys[0]],
+                1000,
+            )
+            .unwrap(),
+            vec![
+                &mut mint_account,
+                &mut account,
+                &mut multisig_account,
+                &mut account_info_iter.next().unwrap(),
+            ],
+        )
+        .unwrap();
+
+        // approve
+        let account_info_iter = &mut signer_accounts.iter_mut();
+        do_process_instruction(
+            approve(
+                &program_id,
+                &account_key,
+                &multisig_delegate_key,
+                &multisig_key,
+                &[&signer_keys[0]],
+                100,
+            )
+            .unwrap(),
+            vec![
+                &mut account,
+                &mut multisig_delegate_account,
+                &mut multisig_account,
+                &mut account_info_iter.next().unwrap(),
+            ],
+        )
+        .unwrap();
+
+        // transfer
+        let account_info_iter = &mut signer_accounts.iter_mut();
+        do_process_instruction(
+            transfer(
+                &program_id,
+                &account_key,
+                &account2_key,
+                &multisig_key,
+                &[&signer_keys[0]],
+                42,
+            )
+            .unwrap(),
+            vec![
+                &mut account,
+                &mut account2_account,
+                &mut multisig_account,
+                &mut account_info_iter.next().unwrap(),
+            ],
+        )
+        .unwrap();
+
+        // transfer via delegate
+        let account_info_iter = &mut signer_accounts.iter_mut();
+        do_process_instruction(
+            transfer(
+                &program_id,
+                &account_key,
+                &account2_key,
+                &multisig_delegate_key,
+                &signer_key_refs,
+                42,
+            )
+            .unwrap(),
+            vec![
+                &mut account,
+                &mut account2_account,
+                &mut multisig_delegate_account,
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+            ],
+        )
+        .unwrap();
+
+        // mint to
+        let account_info_iter = &mut signer_accounts.iter_mut();
+        do_process_instruction(
+            mint_to(
+                &program_id,
+                &mint_key,
+                &account2_key,
+                &multisig_key,
+                &[&signer_keys[0]],
+                42,
+            )
+            .unwrap(),
+            vec![
+                &mut mint_account,
+                &mut account2_account,
+                &mut multisig_account,
+                &mut account_info_iter.next().unwrap(),
+            ],
+        )
+        .unwrap();
+
+        // burn
+        let account_info_iter = &mut signer_accounts.iter_mut();
+        do_process_instruction(
+            burn(
+                &program_id,
+                &account_key,
+                &multisig_key,
+                &[&signer_keys[0]],
+                42,
+            )
+            .unwrap(),
+            vec![
+                &mut account,
+                &mut multisig_account,
+                &mut account_info_iter.next().unwrap(),
+            ],
+        )
+        .unwrap();
+
+        // burn via delegate
+        let account_info_iter = &mut signer_accounts.iter_mut();
+        do_process_instruction(
+            burn(
+                &program_id,
+                &account_key,
+                &multisig_delegate_key,
+                &signer_key_refs,
+                42,
+            )
+            .unwrap(),
+            vec![
+                &mut account,
+                &mut multisig_delegate_account,
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+                &mut account_info_iter.next().unwrap(),
+            ],
+        )
+        .unwrap();
+
+        // freeze account
+        let account3_key = pubkey_rand();
+        let mut account3_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let mint2_key = pubkey_rand();
+        let mut mint2_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+        do_process_instruction(
+            initialize_account(&program_id, &account3_key, &mint2_key, &owner_key).unwrap(),
+            vec![
+                &mut account3_account,
+                &mut mint2_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+        do_process_instruction(
+            initialize_mint(
+                &program_id,
+                &mint2_key,
+                &multisig_key,
+                Some(&multisig_key),
+                2,
+            )
+            .unwrap(),
+            vec![&mut mint2_account],
+        )
+        .unwrap();
+        let account_info_iter = &mut signer_accounts.iter_mut();
+        do_process_instruction(
+            mint_to(
+                &program_id,
+                &mint2_key,
+                &account3_key,
+                &multisig_key,
+                &[&signer_keys[0]],
+                1000,
+            )
+            .unwrap(),
+            vec![
+                &mut mint2_account,
+                &mut account3_account,
+                &mut multisig_account,
+                &mut account_info_iter.next().unwrap(),
+            ],
+        )
+        .unwrap();
+        let account_info_iter = &mut signer_accounts.iter_mut();
+        do_process_instruction(
+            freeze_account(
+                &program_id,
+                &account3_key,
+                &mint2_key,
+                &multisig_key,
+                &[&signer_keys[0]],
+            )
+            .unwrap(),
+            vec![
+                &mut account3_account,
+                &mut mint2_account,
+                &mut multisig_account,
+                &mut account_info_iter.next().unwrap(),
+            ],
+        )
+        .unwrap();
+
+        // do SetAuthority on mint
+        let account_info_iter = &mut signer_accounts.iter_mut();
+        do_process_instruction(
+            set_authority(
+                &program_id,
+                &mint_key,
+                Some(&owner_key),
+                AuthorityType::MintTokens,
+                &multisig_key,
+                &[&signer_keys[0]],
+            )
+            .unwrap(),
+            vec![
+                &mut mint_account,
+                &mut multisig_account,
+                &mut account_info_iter.next().unwrap(),
+            ],
+        )
+        .unwrap();
+
+        // do SetAuthority on account
+        let account_info_iter = &mut signer_accounts.iter_mut();
+        do_process_instruction(
+            set_authority(
+                &program_id,
+                &account_key,
+                Some(&owner_key),
+                AuthorityType::AccountHolder,
+                &multisig_key,
+                &[&signer_keys[0]],
+            )
+            .unwrap(),
+            vec![
+                &mut account,
+                &mut multisig_account,
+                &mut account_info_iter.next().unwrap(),
+            ],
+        )
+        .unwrap();
+    }
+
+    #[test]
+    fn test_validate_owner() {
+        let program_id = pubkey_rand();
+        let owner_key = pubkey_rand();
+        let mut signer_keys = [Pubkey::default(); MAX_SIGNERS];
+        for signer_key in signer_keys.iter_mut().take(MAX_SIGNERS) {
+            *signer_key = pubkey_rand();
+        }
+        let mut signer_lamports = 0;
+        let mut signer_data = vec![];
+        let mut signers = vec![
+            AccountInfo::new(
+                &owner_key,
+                true,
+                false,
+                &mut signer_lamports,
+                &mut signer_data,
+                &program_id,
+                false,
+                Epoch::default(),
+            );
+            MAX_SIGNERS + 1
+        ];
+        for (signer, key) in signers.iter_mut().zip(&signer_keys) {
+            signer.key = key;
+        }
+        let mut lamports = 0;
+        let mut data = vec![0; size_of::<Multisig>()];
+        let mut multisig: &mut Multisig = state::unpack_unchecked(&mut data).unwrap();
+        multisig.m = MAX_SIGNERS as u8;
+        multisig.n = MAX_SIGNERS as u8;
+        multisig.signers = signer_keys;
+        multisig.is_initialized = true;
+        let owner_account_info = AccountInfo::new(
+            &owner_key,
+            false,
+            false,
+            &mut lamports,
+            &mut data,
+            &program_id,
+            false,
+            Epoch::default(),
+        );
+
+        // full 11 of 11
+        Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers).unwrap();
+
+        // 1 of 11
+        {
+            let mut data_ref_mut = owner_account_info.data.borrow_mut();
+            let mut multisig: &mut Multisig = state::unpack(&mut data_ref_mut).unwrap();
+            multisig.m = 1;
+        }
+        Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers).unwrap();
+
+        // 2:1
+        {
+            let mut data_ref_mut = owner_account_info.data.borrow_mut();
+            let mut multisig: &mut Multisig = state::unpack(&mut data_ref_mut).unwrap();
+            multisig.m = 2;
+            multisig.n = 1;
+        }
+        assert_eq!(
+            Err(ProgramError::MissingRequiredSignature),
+            Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers)
+        );
+
+        // 0:11
+        {
+            let mut data_ref_mut = owner_account_info.data.borrow_mut();
+            let mut multisig: &mut Multisig = state::unpack(&mut data_ref_mut).unwrap();
+            multisig.m = 0;
+            multisig.n = 11;
+        }
+        Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers).unwrap();
+
+        // 2:11 but 0 provided
+        {
+            let mut data_ref_mut = owner_account_info.data.borrow_mut();
+            let mut multisig: &mut Multisig = state::unpack(&mut data_ref_mut).unwrap();
+            multisig.m = 2;
+            multisig.n = 11;
+        }
+        assert_eq!(
+            Err(ProgramError::MissingRequiredSignature),
+            Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &[])
+        );
+        // 2:11 but 1 provided
+        {
+            let mut data_ref_mut = owner_account_info.data.borrow_mut();
+            let mut multisig: &mut Multisig = state::unpack(&mut data_ref_mut).unwrap();
+            multisig.m = 2;
+            multisig.n = 11;
+        }
+        assert_eq!(
+            Err(ProgramError::MissingRequiredSignature),
+            Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers[0..1])
+        );
+
+        // 2:11, 2 from middle provided
+        {
+            let mut data_ref_mut = owner_account_info.data.borrow_mut();
+            let mut multisig: &mut Multisig = state::unpack(&mut data_ref_mut).unwrap();
+            multisig.m = 2;
+            multisig.n = 11;
+        }
+        Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers[5..7])
+            .unwrap();
+
+        // 11:11, one is not a signer
+        {
+            let mut data_ref_mut = owner_account_info.data.borrow_mut();
+            let mut multisig: &mut Multisig = state::unpack(&mut data_ref_mut).unwrap();
+            multisig.m = 2;
+            multisig.n = 11;
+        }
+        signers[5].is_signer = false;
+        assert_eq!(
+            Err(ProgramError::MissingRequiredSignature),
+            Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers)
+        );
+        signers[5].is_signer = true;
+    }
+
+    #[test]
+    fn test_close_account() {
+        let program_id = pubkey_rand();
+        let mint_key = pubkey_rand();
+        let mut mint_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+        let account_key = pubkey_rand();
+        let mut account_account = SolanaAccount::new(42, size_of::<Account>(), &program_id);
+        let account2_key = pubkey_rand();
+        let mut account2_account = SolanaAccount::new(2, size_of::<Account>(), &program_id);
+        let account3_key = pubkey_rand();
+        let mut account3_account = SolanaAccount::new(2, size_of::<Account>(), &program_id);
+        let owner_key = pubkey_rand();
+        let mut owner_account = SolanaAccount::default();
+        let owner2_key = pubkey_rand();
+        let mut owner2_account = SolanaAccount::default();
+
+        // uninitialized
+        assert_eq!(
+            Err(TokenError::UninitializedState.into()),
+            do_process_instruction(
+                close_account(&program_id, &account_key, &account3_key, &owner2_key, &[]).unwrap(),
+                vec![
+                    &mut account_account,
+                    &mut account3_account,
+                    &mut owner2_account,
+                ],
+            )
+        );
+
+        // initialize and mint to non-native account
+        do_process_instruction(
+            initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+        do_process_instruction(
+            initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(),
+            vec![&mut mint_account, &mut account_account, &mut owner_account],
+        )
+        .unwrap();
+        do_process_instruction(
+            mint_to(&program_id, &mint_key, &account_key, &owner_key, &[], 42).unwrap(),
+            vec![&mut mint_account, &mut account_account, &mut owner_account],
+        )
+        .unwrap();
+        let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        assert_eq!(account.amount, 42);
+
+        // initialize native account
+        do_process_instruction(
+            initialize_account(
+                &program_id,
+                &account2_key,
+                &crate::native_mint::id(),
+                &owner_key,
+            )
+            .unwrap(),
+            vec![&mut account2_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+        let account: &mut Account = state::unpack(&mut account2_account.data).unwrap();
+        assert!(account.is_native);
+        assert_eq!(account.amount, 2);
+
+        // close non-native account with balance
+        assert_eq!(
+            Err(TokenError::NonNativeHasBalance.into()),
+            do_process_instruction(
+                close_account(&program_id, &account_key, &account3_key, &owner_key, &[]).unwrap(),
+                vec![
+                    &mut account_account,
+                    &mut account3_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+        assert_eq!(account_account.lamports, 42);
+
+        // empty account
+        do_process_instruction(
+            burn(&program_id, &account_key, &owner_key, &[], 42).unwrap(),
+            vec![&mut account_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // wrong owner
+        assert_eq!(
+            Err(TokenError::OwnerMismatch.into()),
+            do_process_instruction(
+                close_account(&program_id, &account_key, &account3_key, &owner2_key, &[]).unwrap(),
+                vec![
+                    &mut account_account,
+                    &mut account3_account,
+                    &mut owner2_account,
+                ],
+            )
+        );
+
+        // close account
+        do_process_instruction(
+            close_account(&program_id, &account_key, &account3_key, &owner_key, &[]).unwrap(),
+            vec![
+                &mut account_account,
+                &mut account3_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+        let account: &mut Account = state::unpack_unchecked(&mut account_account.data).unwrap();
+        assert_eq!(account_account.lamports, 0);
+        assert_eq!(account.amount, 0);
+        assert_eq!(account3_account.lamports, 44);
+
+        // fund and initialize new non-native account to test close authority
+        let account_key = pubkey_rand();
+        let mut account_account = SolanaAccount::new(42, size_of::<Account>(), &program_id);
+        let owner2_key = pubkey_rand();
+        let mut owner2_account = SolanaAccount::new(42, size_of::<Account>(), &program_id);
+        do_process_instruction(
+            initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+        account_account.lamports = 2;
+
+        do_process_instruction(
+            set_authority(
+                &program_id,
+                &account_key,
+                Some(&owner2_key),
+                AuthorityType::CloseAccount,
+                &owner_key,
+                &[],
+            )
+            .unwrap(),
+            vec![&mut account_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // account owner cannot authorize close if close_authority is set
+        assert_eq!(
+            Err(TokenError::OwnerMismatch.into()),
+            do_process_instruction(
+                close_account(&program_id, &account_key, &account3_key, &owner_key, &[]).unwrap(),
+                vec![
+                    &mut account_account,
+                    &mut account3_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+
+        // close non-native account with close_authority
+        do_process_instruction(
+            close_account(&program_id, &account_key, &account3_key, &owner2_key, &[]).unwrap(),
+            vec![
+                &mut account_account,
+                &mut account3_account,
+                &mut owner2_account,
+            ],
+        )
+        .unwrap();
+        assert_eq!(account_account.lamports, 0);
+        assert_eq!(account.amount, 0);
+        assert_eq!(account3_account.lamports, 46);
+
+        // close native account
+        do_process_instruction(
+            close_account(&program_id, &account2_key, &account3_key, &owner_key, &[]).unwrap(),
+            vec![
+                &mut account2_account,
+                &mut account3_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+        let account: &mut Account = state::unpack_unchecked(&mut account2_account.data).unwrap();
+        assert!(account.is_native);
+        assert_eq!(account_account.lamports, 0);
+        assert_eq!(account.amount, 0);
+        assert_eq!(account3_account.lamports, 48);
+    }
+
+    #[test]
+    fn test_native_token() {
+        let program_id = pubkey_rand();
+        let mut mint_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+        let account_key = pubkey_rand();
+        let mut account_account = SolanaAccount::new(42, size_of::<Account>(), &program_id);
+        let account2_key = pubkey_rand();
+        let mut account2_account = SolanaAccount::new(2, size_of::<Account>(), &program_id);
+        let account3_key = pubkey_rand();
+        let mut account3_account = SolanaAccount::new(2, 0, &program_id);
+        let owner_key = pubkey_rand();
+        let mut owner_account = SolanaAccount::default();
+
+        // initialize native account
+        do_process_instruction(
+            initialize_account(
+                &program_id,
+                &account_key,
+                &crate::native_mint::id(),
+                &owner_key,
+            )
+            .unwrap(),
+            vec![&mut account_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+        let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        assert!(account.is_native);
+        assert_eq!(account.amount, 42);
+
+        // initialize native account
+        do_process_instruction(
+            initialize_account(
+                &program_id,
+                &account2_key,
+                &crate::native_mint::id(),
+                &owner_key,
+            )
+            .unwrap(),
+            vec![&mut account2_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+        let account: &mut Account = state::unpack(&mut account2_account.data).unwrap();
+        assert!(account.is_native);
+        assert_eq!(account.amount, 2);
+
+        // mint_to unsupported
+        assert_eq!(
+            Err(TokenError::NativeNotSupported.into()),
+            do_process_instruction(
+                mint_to(
+                    &program_id,
+                    &crate::native_mint::id(),
+                    &account_key,
+                    &owner_key,
+                    &[],
+                    42
+                )
+                .unwrap(),
+                vec![&mut mint_account, &mut account_account, &mut owner_account],
+            )
+        );
+
+        // burn unsupported
+        assert_eq!(
+            Err(TokenError::NativeNotSupported.into()),
+            do_process_instruction(
+                burn(&program_id, &account_key, &owner_key, &[], 42).unwrap(),
+                vec![&mut account_account, &mut owner_account],
+            )
+        );
+
+        // initialize native account
+        do_process_instruction(
+            transfer(
+                &program_id,
+                &account_key,
+                &account2_key,
+                &owner_key,
+                &[],
+                40,
+            )
+            .unwrap(),
+            vec![
+                &mut account_account,
+                &mut account2_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+
+        let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        assert!(account.is_native);
+        assert_eq!(account_account.lamports, 2);
+        assert_eq!(account.amount, 2);
+        let account: &mut Account = state::unpack(&mut account2_account.data).unwrap();
+        assert!(account.is_native);
+        assert_eq!(account2_account.lamports, 42);
+        assert_eq!(account.amount, 42);
+
+        // close native account
+        do_process_instruction(
+            close_account(&program_id, &account_key, &account3_key, &owner_key, &[]).unwrap(),
+            vec![
+                &mut account_account,
+                &mut account3_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+        let account: &mut Account = state::unpack_unchecked(&mut account_account.data).unwrap();
+        assert!(account.is_native);
+        assert_eq!(account_account.lamports, 0);
+        assert_eq!(account.amount, 0);
+        assert_eq!(account3_account.lamports, 4);
+    }
+
+    #[test]
+    fn test_overflow() {
+        let program_id = pubkey_rand();
+        let account_key = pubkey_rand();
+        let mut account_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let account2_key = pubkey_rand();
+        let mut account2_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let owner_key = pubkey_rand();
+        let mut owner_account = SolanaAccount::default();
+        let owner2_key = pubkey_rand();
+        let mut owner2_account = SolanaAccount::default();
+        let mint_owner_key = pubkey_rand();
+        let mut mint_owner_account = SolanaAccount::default();
+        let mint_key = pubkey_rand();
+        let mut mint_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+
+        // create victim account
+        do_process_instruction(
+            initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // create another account
+        do_process_instruction(
+            initialize_account(&program_id, &account2_key, &mint_key, &owner2_key).unwrap(),
+            vec![
+                &mut account2_account,
+                &mut mint_account,
+                &mut owner2_account,
+            ],
+        )
+        .unwrap();
+
+        // create new mint with owner
+        do_process_instruction(
+            initialize_mint(&program_id, &mint_key, &mint_owner_key, None, 2).unwrap(),
+            vec![&mut mint_account],
+        )
+        .unwrap();
+
+        // mint the max to attacker
+        do_process_instruction(
+            mint_to(
+                &program_id,
+                &mint_key,
+                &account2_key,
+                &mint_owner_key,
+                &[],
+                42,
+            )
+            .unwrap(),
+            vec![
+                &mut mint_account,
+                &mut account2_account,
+                &mut mint_owner_account,
+            ],
+        )
+        .unwrap();
+        let account: &mut Account = state::unpack(&mut account2_account.data).unwrap();
+        assert_eq!(account.amount, 42);
+
+        // mint the max to victum
+        do_process_instruction(
+            mint_to(
+                &program_id,
+                &mint_key,
+                &account_key,
+                &mint_owner_key,
+                &[],
+                u64::MAX,
+            )
+            .unwrap(),
+            vec![
+                &mut mint_account,
+                &mut account_account,
+                &mut mint_owner_account,
+            ],
+        )
+        .unwrap();
+        let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        assert_eq!(account.amount, u64::MAX);
+
+        // mint one more
+        assert_eq!(
+            Err(TokenError::Overflow.into()),
+            do_process_instruction(
+                mint_to(
+                    &program_id,
+                    &mint_key,
+                    &account_key,
+                    &mint_owner_key,
+                    &[],
+                    1,
+                )
+                .unwrap(),
+                vec![
+                    &mut mint_account,
+                    &mut account_account,
+                    &mut mint_owner_account,
+                ],
+            )
+        );
+
+        // mint back to large amount
+        let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        account.amount = 0;
+        do_process_instruction(
+            mint_to(
+                &program_id,
+                &mint_key,
+                &account_key,
+                &mint_owner_key,
+                &[],
+                u64::MAX,
+            )
+            .unwrap(),
+            vec![
+                &mut mint_account,
+                &mut account_account,
+                &mut mint_owner_account,
+            ],
+        )
+        .unwrap();
+        let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        assert_eq!(account.amount, u64::MAX);
+
+        // transfer to burn victim
+        assert_eq!(
+            Err(TokenError::Overflow.into()),
+            do_process_instruction(
+                transfer(
+                    &program_id,
+                    &account2_key,
+                    &account_key,
+                    &owner2_key,
+                    &[],
+                    1,
+                )
+                .unwrap(),
+                vec![
+                    &mut account2_account,
+                    &mut account_account,
+                    &mut owner2_account,
+                ],
+            )
+        );
+    }
+
+    #[test]
+    fn test_frozen() {
+        let program_id = pubkey_rand();
+        let account_key = pubkey_rand();
+        let mut account_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let account2_key = pubkey_rand();
+        let mut account2_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let owner_key = pubkey_rand();
+        let mut owner_account = SolanaAccount::default();
+        let mint_key = pubkey_rand();
+        let mut mint_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+
+        // create account
+        do_process_instruction(
+            initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // create another account
+        do_process_instruction(
+            initialize_account(&program_id, &account2_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account2_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // create new mint and fund first account
+        do_process_instruction(
+            initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(),
+            vec![&mut mint_account],
+        )
+        .unwrap();
+        do_process_instruction(
+            mint_to(&program_id, &mint_key, &account_key, &owner_key, &[], 1000).unwrap(),
+            vec![&mut mint_account, &mut account_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // no transfer if either account is frozen
+        let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        account.state = AccountState::Frozen;
+        assert_eq!(
+            Err(TokenError::AccountFrozen.into()),
+            do_process_instruction(
+                transfer(
+                    &program_id,
+                    &account_key,
+                    &account2_key,
+                    &owner_key,
+                    &[],
+                    500,
+                )
+                .unwrap(),
+                vec![
+                    &mut account_account,
+                    &mut account2_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+
+        let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        account.state = AccountState::Initialized;
+        let account2: &mut Account = state::unpack(&mut account2_account.data).unwrap();
+        account2.state = AccountState::Frozen;
+        assert_eq!(
+            Err(TokenError::AccountFrozen.into()),
+            do_process_instruction(
+                transfer(
+                    &program_id,
+                    &account_key,
+                    &account2_key,
+                    &owner_key,
+                    &[],
+                    500,
+                )
+                .unwrap(),
+                vec![
+                    &mut account_account,
+                    &mut account2_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+
+        // no approve if account is frozen
+        let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        account.state = AccountState::Frozen;
+        let delegate_key = pubkey_rand();
+        let mut delegate_account = SolanaAccount::default();
+        assert_eq!(
+            Err(TokenError::AccountFrozen.into()),
+            do_process_instruction(
+                approve(
+                    &program_id,
+                    &account_key,
+                    &delegate_key,
+                    &owner_key,
+                    &[],
+                    100
+                )
+                .unwrap(),
+                vec![
+                    &mut account_account,
+                    &mut delegate_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+
+        // no revoke if account is frozen
+        let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        account.delegate = COption::Some(delegate_key);
+        account.delegated_amount = 100;
+        assert_eq!(
+            Err(TokenError::AccountFrozen.into()),
+            do_process_instruction(
+                revoke(&program_id, &account_key, &owner_key, &[]).unwrap(),
+                vec![&mut account_account, &mut owner_account,],
+            )
+        );
+
+        // no set authority if account is frozen
+        let new_owner_key = pubkey_rand();
+        assert_eq!(
+            Err(TokenError::AccountFrozen.into()),
+            do_process_instruction(
+                set_authority(
+                    &program_id,
+                    &account_key,
+                    Some(&new_owner_key),
+                    AuthorityType::AccountHolder,
+                    &owner_key,
+                    &[]
+                )
+                .unwrap(),
+                vec![&mut account_account, &mut owner_account,],
+            )
+        );
+
+        // no mint_to if destination account is frozen
+        assert_eq!(
+            Err(TokenError::AccountFrozen.into()),
+            do_process_instruction(
+                mint_to(&program_id, &mint_key, &account_key, &owner_key, &[], 100).unwrap(),
+                vec![&mut mint_account, &mut account_account, &mut owner_account,],
+            )
+        );
+
+        // no burn if account is frozen
+        assert_eq!(
+            Err(TokenError::AccountFrozen.into()),
+            do_process_instruction(
+                burn(&program_id, &account_key, &owner_key, &[], 100).unwrap(),
+                vec![&mut account_account, &mut owner_account,],
+            )
+        );
+    }
+
+    #[test]
+    fn test_freeze_account() {
+        let program_id = pubkey_rand();
+        let account_key = pubkey_rand();
+        let mut account_account = SolanaAccount::new(0, size_of::<Account>(), &program_id);
+        let account_owner_key = pubkey_rand();
+        let mut account_owner_account = SolanaAccount::default();
+        let owner_key = pubkey_rand();
+        let mut owner_account = SolanaAccount::default();
+        let owner2_key = pubkey_rand();
+        let mut owner2_account = SolanaAccount::default();
+        let mint_key = pubkey_rand();
+        let mut mint_account = SolanaAccount::new(0, size_of::<Mint>(), &program_id);
+
+        // create account
+        do_process_instruction(
+            initialize_account(&program_id, &account_key, &mint_key, &account_owner_key).unwrap(),
+            vec![
+                &mut account_account,
+                &mut mint_account,
+                &mut account_owner_account,
+            ],
+        )
+        .unwrap();
+
+        // create new mint with owner different from account owner
+        do_process_instruction(
+            initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(),
+            vec![&mut mint_account],
+        )
+        .unwrap();
+        do_process_instruction(
+            mint_to(&program_id, &mint_key, &account_key, &owner_key, &[], 1000).unwrap(),
+            vec![&mut mint_account, &mut account_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // mint cannot freeze
+        assert_eq!(
+            Err(TokenError::MintCannotFreeze.into()),
+            do_process_instruction(
+                freeze_account(&program_id, &account_key, &mint_key, &owner_key, &[]).unwrap(),
+                vec![&mut account_account, &mut mint_account, &mut owner_account],
+            )
+        );
+
+        // missing freeze_authority
+        let mint: &mut Mint = state::unpack(&mut mint_account.data).unwrap();
+        mint.freeze_authority = COption::Some(owner_key);
+        assert_eq!(
+            Err(TokenError::OwnerMismatch.into()),
+            do_process_instruction(
+                freeze_account(&program_id, &account_key, &mint_key, &owner2_key, &[]).unwrap(),
+                vec![&mut account_account, &mut mint_account, &mut owner2_account],
+            )
+        );
+
+        // check explicit thaw
+        assert_eq!(
+            Err(TokenError::InvalidState.into()),
+            do_process_instruction(
+                thaw_account(&program_id, &account_key, &mint_key, &owner2_key, &[]).unwrap(),
+                vec![&mut account_account, &mut mint_account, &mut owner2_account],
+            )
+        );
+
+        // freeze
+        do_process_instruction(
+            freeze_account(&program_id, &account_key, &mint_key, &owner_key, &[]).unwrap(),
+            vec![&mut account_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+        let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        assert_eq!(account.state, AccountState::Frozen);
+
+        // check explicit freeze
+        assert_eq!(
+            Err(TokenError::InvalidState.into()),
+            do_process_instruction(
+                freeze_account(&program_id, &account_key, &mint_key, &owner_key, &[]).unwrap(),
+                vec![&mut account_account, &mut mint_account, &mut owner_account],
+            )
+        );
+
+        // check thaw authority
+        assert_eq!(
+            Err(TokenError::OwnerMismatch.into()),
+            do_process_instruction(
+                thaw_account(&program_id, &account_key, &mint_key, &owner2_key, &[]).unwrap(),
+                vec![&mut account_account, &mut mint_account, &mut owner2_account],
+            )
+        );
+
+        // thaw
+        do_process_instruction(
+            thaw_account(&program_id, &account_key, &mint_key, &owner_key, &[]).unwrap(),
+            vec![&mut account_account, &mut mint_account, &mut owner_account],
+        )
+        .unwrap();
+        let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
+        assert_eq!(account.state, AccountState::Initialized);
+    }
+}

+ 122 - 0
program/src/state.rs

@@ -0,0 +1,122 @@
+//! State transition types
+
+use crate::{error::TokenError, instruction::MAX_SIGNERS, option::COption};
+use solana_sdk::{program_error::ProgramError, pubkey::Pubkey};
+use std::mem::size_of;
+
+/// 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>,
+    /// 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 IsInitialized for Mint {
+    fn is_initialized(&self) -> bool {
+        self.is_initialized
+    }
+}
+
+/// 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,
+    /// Is this a native token
+    pub is_native: bool,
+    /// 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
+    }
+}
+impl IsInitialized for Account {
+    fn is_initialized(&self) -> bool {
+        self.state != AccountState::Uninitialized
+    }
+}
+
+/// Account state.
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum AccountState {
+    /// Account is not yet initialized
+    Uninitialized,
+    /// Account is initialized; the account owner and/or delegate may perform permitted operations
+    /// on this account
+    Initialized,
+    /// Account has been frozen by the mint freeze authority. Neither the account owner nor
+    /// the delegate are able to perform operations on this account.
+    Frozen,
+}
+
+impl Default for AccountState {
+    fn default() -> Self {
+        AccountState::Uninitialized
+    }
+}
+
+/// 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 IsInitialized for Multisig {
+    fn is_initialized(&self) -> bool {
+        self.is_initialized
+    }
+}
+
+/// Check is a token state is initialized
+pub trait IsInitialized {
+    /// Is initialized
+    fn is_initialized(&self) -> bool;
+}
+
+/// Unpacks a token state from a bytes buffer while assuring that the state is initialized.
+pub fn unpack<T: IsInitialized>(input: &mut [u8]) -> Result<&mut T, ProgramError> {
+    let mut_ref: &mut T = unpack_unchecked(input)?;
+    if !mut_ref.is_initialized() {
+        return Err(TokenError::UninitializedState.into());
+    }
+    Ok(mut_ref)
+}
+/// Unpacks a token state from a bytes buffer without checking that the state is initialized.
+pub fn unpack_unchecked<T: IsInitialized>(input: &mut [u8]) -> Result<&mut T, ProgramError> {
+    if input.len() != size_of::<T>() {
+        return Err(ProgramError::InvalidAccountData);
+    }
+    #[allow(clippy::cast_ptr_alignment)]
+    Ok(unsafe { &mut *(&mut input[0] as *mut u8 as *mut T) })
+}