diff --git a/Cargo.lock b/Cargo.lock index 1e7e1953cde..6ed5ad421a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3859,6 +3859,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "spl-token-2022" +version = "0.1.0" +dependencies = [ + "arrayref", + "num-derive", + "num-traits", + "num_enum", + "solana-program", + "solana-sdk", + "thiserror", +] + [[package]] name = "spl-token-cli" version = "2.0.15" diff --git a/Cargo.toml b/Cargo.toml index 3fa00584df7..919dd29e345 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ members = [ "token-swap/program/fuzz", "token/cli", "token/program", + "token/program-2022", "utils/cgen", "utils/test-client", "token-lending/flash_loan_receiver", diff --git a/ci/cargo-test-bpf.sh b/ci/cargo-test-bpf.sh index 21a4212d7d2..ab9dc7cb164 100755 --- a/ci/cargo-test-bpf.sh +++ b/ci/cargo-test-bpf.sh @@ -24,17 +24,33 @@ if [[ -z $program_directory ]]; then usage "No program directory provided" fi -set -x - cd $program_directory run_dir=$(pwd) -if [[ -d $run_dir/program ]]; then - # Build/test just one BPF program - cd $run_dir/program - cargo +"$rust_stable" test-bpf -- --nocapture -else - # Build/test all BPF programs +if [[ -r $run_dir/Cargo.toml ]]; then + # Build/test just one BPF program + set -x + cd $run_dir + cargo +"$rust_stable" test-bpf -- --nocapture + exit 0 +fi + +run_all=1 +for program in $run_dir/program{,-*}; do + # Build/test all program directories + if [[ -r $program/Cargo.toml ]]; then + run_all= + ( + set -x + cd $program + cargo +"$rust_stable" test-bpf -- --nocapture + ) + fi +done + +if [[ -n $run_all ]]; then + # Build/test all directories + set -x for directory in $(ls -d $run_dir/*/); do cd $directory cargo +"$rust_stable" test-bpf -- --nocapture diff --git a/ci/solana-version.sh b/ci/solana-version.sh index 57d84dbd469..475e7a39749 100755 --- a/ci/solana-version.sh +++ b/ci/solana-version.sh @@ -27,7 +27,7 @@ if [[ -n $1 ]]; then solana --version ;; *) - echo "$0: Note: ignoring unknown argument: $1" >&2 + echo "solana-version.sh: Note: ignoring unknown argument: $1" >&2 ;; esac fi diff --git a/token/program-2022/Cargo.toml b/token/program-2022/Cargo.toml new file mode 100644 index 00000000000..1aadec80139 --- /dev/null +++ b/token/program-2022/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "spl-token-2022" +version = "0.1.0" +description = "Solana Program Library Token 2022" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +edition = "2018" +exclude = ["js/**"] + +[features] +no-entrypoint = [] + +[dependencies] +arrayref = "0.3.6" +num-derive = "0.3" +num-traits = "0.2" +num_enum = "0.5.4" +solana-program = "1.8.0" +thiserror = "1.0" + +[dev-dependencies] +solana-sdk = "1.8.0" + +[lib] +crate-type = ["cdylib", "lib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/token/program-2022/Xargo.toml b/token/program-2022/Xargo.toml new file mode 100644 index 00000000000..1744f098ae1 --- /dev/null +++ b/token/program-2022/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/token/program-2022/inc/token.h b/token/program-2022/inc/token.h new file mode 100644 index 00000000000..145c0c5e07b --- /dev/null +++ b/token/program-2022/inc/token.h @@ -0,0 +1,687 @@ +/* Autogenerated SPL Token program C Bindings */ + +#pragma once + +#include +#include +#include +#include + +/** + * Minimum number of multisignature signers (min N) + */ +#define Token_MIN_SIGNERS 1 + +/** + * Maximum number of multisignature signers (max N) + */ +#define Token_MAX_SIGNERS 11 + +/** + * Account state. + */ +enum Token_AccountState +#ifdef __cplusplus + : uint8_t +#endif // __cplusplus + { + /** + * Account is not yet initialized + */ + Token_AccountState_Uninitialized, + /** + * Account is initialized; the account owner and/or delegate may perform permitted operations + * on this account + */ + Token_AccountState_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. + */ + Token_AccountState_Frozen, +}; +#ifndef __cplusplus +typedef uint8_t Token_AccountState; +#endif // __cplusplus + +/** + * Specifies the authority type for SetAuthority instructions + */ +enum Token_AuthorityType +#ifdef __cplusplus + : uint8_t +#endif // __cplusplus + { + /** + * Authority to mint new tokens + */ + Token_AuthorityType_MintTokens, + /** + * Authority to freeze any account associated with the Mint + */ + Token_AuthorityType_FreezeAccount, + /** + * Owner of a given token account + */ + Token_AuthorityType_AccountOwner, + /** + * Authority to close a token account + */ + Token_AuthorityType_CloseAccount, +}; +#ifndef __cplusplus +typedef uint8_t Token_AuthorityType; +#endif // __cplusplus + +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_COption_Pubkey_Tag tag; + union { + struct { + Token_Pubkey some; + }; + }; +} Token_COption_Pubkey; + +/** + * 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 + * `CreateAccount` instruction that creates the account being initialized. + * Otherwise another party can acquire ownership of the uninitialized + * account. + * + * Accounts expected by this instruction: + * + * 0. `[writable]` The mint to initialize. + * 1. `[]` Rent sysvar + * + */ + 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. If this account is + * associated with another mint, that mint must be initialized before this + * command can succeed. + * + * The `InitializeAccount` instruction requires no signers and MUST be + * included within the same Transaction as the system program's + * `CreateAccount` instruction that creates the account being initialized. + * Otherwise another party can acquire ownership of the uninitialized + * account. + * + * Accounts expected by this instruction: + * + * 0. `[writable]` The account to initialize. + * 1. `[]` The mint this account will be associated with. + * 2. `[]` The new account's owner/multisignature. + * 3. `[]` Rent sysvar + */ + 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 + * `CreateAccount` instruction that creates the account being initialized. + * Otherwise another party can acquire ownership of the uninitialized + * account. + * + * Accounts expected by this instruction: + * + * 0. `[writable]` The multisignature account to initialize. + * 1. `[]` Rent sysvar + * 2. ..2+N. `[]` The signer accounts, must equal to N where 1 <= N <= + * 11. + */ + 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 authority of a mint or account. + * + * Accounts expected by this instruction: + * + * * Single authority + * 0. `[writable]` The mint or account to change the authority of. + * 1. `[signer]` The current authority of the mint or account. + * + * * Multisignature authority + * 0. `[writable]` The mint or account to change the authority of. + * 1. `[]` The mint's or account's current multisignature authority. + * 2. ..2+M `[signer]` M signer accounts + */ + Token_TokenInstruction_SetAuthority, + /** + * 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. + */ + 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. `[writable]` The token mint. + * 2. `[signer]` The account's owner/delegate. + * + * * Multisignature owner/delegate + * 0. `[writable]` The account to burn from. + * 1. `[writable]` The token mint. + * 2. `[]` The account's multisignature owner/delegate. + * 3. ..3+M `[signer]` M signer accounts. + */ + 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, + /** + * 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. + */ + Token_TokenInstruction_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. + */ + Token_TokenInstruction_ThawAccount, + /** + * Transfers tokens from one account to another either directly or via a + * delegate. If this account is associated with the native mint then equal + * amounts of SOL and Tokens will be transferred to the destination + * account. + * + * This instruction differs from Transfer in that the token mint and + * decimals value is checked by the caller. This may be useful when + * creating transactions offline or within a hardware wallet. + * + * Accounts expected by this instruction: + * + * * Single owner/delegate + * 0. `[writable]` The source account. + * 1. `[]` The token mint. + * 2. `[writable]` The destination account. + * 3. `[signer]` The source account's owner/delegate. + * + * * Multisignature owner/delegate + * 0. `[writable]` The source account. + * 1. `[]` The token mint. + * 2. `[writable]` The destination account. + * 3. `[]` The source account's multisignature owner/delegate. + * 4. ..4+M `[signer]` M signer accounts. + */ + Token_TokenInstruction_TransferChecked, + /** + * Approves a delegate. A delegate is given the authority over tokens on + * behalf of the source account's owner. + * + * This instruction differs from Approve in that the token mint and + * decimals value is checked by the caller. This may be useful when + * creating transactions offline or within a hardware wallet. + * + * Accounts expected by this instruction: + * + * * Single owner + * 0. `[writable]` The source account. + * 1. `[]` The token mint. + * 2. `[]` The delegate. + * 3. `[signer]` The source account owner. + * + * * Multisignature owner + * 0. `[writable]` The source account. + * 1. `[]` The token mint. + * 2. `[]` The delegate. + * 3. `[]` The source account's multisignature owner. + * 4. ..4+M `[signer]` M signer accounts + */ + Token_TokenInstruction_ApproveChecked, + /** + * Mints new tokens to an account. The native mint does not support + * minting. + * + * This instruction differs from MintTo in that the decimals value is + * checked by the caller. This may be useful when creating transactions + * offline or within a hardware wallet. + * + * Accounts expected by this instruction: + * + * * Single authority + * 0. `[writable]` The mint. + * 1. `[writable]` The account to mint tokens to. + * 2. `[signer]` The mint's minting authority. + * + * * Multisignature authority + * 0. `[writable]` The mint. + * 1. `[writable]` The account to mint tokens to. + * 2. `[]` The mint's multisignature mint-tokens authority. + * 3. ..3+M `[signer]` M signer accounts. + */ + Token_TokenInstruction_MintToChecked, + /** + * Burns tokens by removing them from an account. `BurnChecked` does not + * support accounts associated with the native mint, use `CloseAccount` + * instead. + * + * This instruction differs from Burn in that the decimals value is checked + * by the caller. This may be useful when creating transactions offline or + * within a hardware wallet. + * + * Accounts expected by this instruction: + * + * * Single owner/delegate + * 0. `[writable]` The account to burn from. + * 1. `[writable]` The token mint. + * 2. `[signer]` The account's owner/delegate. + * + * * Multisignature owner/delegate + * 0. `[writable]` The account to burn from. + * 1. `[writable]` The token mint. + * 2. `[]` The account's multisignature owner/delegate. + * 3. ..3+M `[signer]` M signer accounts. + */ + Token_TokenInstruction_BurnChecked, + /** + * Like InitializeAccount, but the owner pubkey is passed via instruction data + * rather than the accounts list. This variant may be preferable when using + * Cross Program Invocation from an instruction that does not need the owner's + * `AccountInfo` otherwise. + * + * Accounts expected by this instruction: + * + * 0. `[writable]` The account to initialize. + * 1. `[]` The mint this account will be associated with. + * 3. `[]` Rent sysvar + */ + Token_TokenInstruction_InitializeAccount2, + /** + * Given a wrapped / native token account (a token account containing SOL) + * updates its amount field based on the account's underlying `lamports`. + * This is useful if a non-wrapped SOL account uses `system_instruction::transfer` + * to move lamports to a wrapped token account, and needs to have its token + * `amount` field updated. + * + * Accounts expected by this instruction: + * + * 0. `[writable]` The native token account to sync with its underlying lamports. + */ + Token_TokenInstruction_SyncNative, +} Token_TokenInstruction_Tag; + +typedef struct Token_TokenInstruction_Token_InitializeMint_Body { + /** + * Number of base 10 digits to the right of the decimal place. + */ + uint8_t decimals; + /** + * The authority/multisignature to mint tokens. + */ + Token_Pubkey mint_authority; + /** + * The freeze authority/multisignature of the mint. + */ + struct Token_COption_Pubkey freeze_authority; +} 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_SetAuthority_Body { + /** + * The type of authority to update. + */ + Token_AuthorityType authority_type; + /** + * The new authority + */ + struct Token_COption_Pubkey new_authority; +} Token_TokenInstruction_Token_SetAuthority_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_TransferChecked_Body { + /** + * The amount of tokens to transfer. + */ + uint64_t amount; + /** + * Expected number of base 10 digits to the right of the decimal place. + */ + uint8_t decimals; +} Token_TokenInstruction_Token_TransferChecked_Body; + +typedef struct Token_TokenInstruction_Token_ApproveChecked_Body { + /** + * The amount of tokens the delegate is approved for. + */ + uint64_t amount; + /** + * Expected number of base 10 digits to the right of the decimal place. + */ + uint8_t decimals; +} Token_TokenInstruction_Token_ApproveChecked_Body; + +typedef struct Token_TokenInstruction_Token_MintToChecked_Body { + /** + * The amount of new tokens to mint. + */ + uint64_t amount; + /** + * Expected number of base 10 digits to the right of the decimal place. + */ + uint8_t decimals; +} Token_TokenInstruction_Token_MintToChecked_Body; + +typedef struct Token_TokenInstruction_Token_BurnChecked_Body { + /** + * The amount of tokens to burn. + */ + uint64_t amount; + /** + * Expected number of base 10 digits to the right of the decimal place. + */ + uint8_t decimals; +} Token_TokenInstruction_Token_BurnChecked_Body; + +typedef struct Token_TokenInstruction_Token_InitializeAccount2_Body { + /** + * The new account's owner/multisignature. + */ + Token_Pubkey owner; +} Token_TokenInstruction_Token_InitializeAccount2_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_SetAuthority_Body set_authority; + Token_TokenInstruction_Token_MintTo_Body mint_to; + Token_TokenInstruction_Token_Burn_Body burn; + Token_TokenInstruction_Token_TransferChecked_Body transfer_checked; + Token_TokenInstruction_Token_ApproveChecked_Body approve_checked; + Token_TokenInstruction_Token_MintToChecked_Body mint_to_checked; + Token_TokenInstruction_Token_BurnChecked_Body burn_checked; + Token_TokenInstruction_Token_InitializeAccount2_Body initialize_account2; + }; +} Token_TokenInstruction; + +/** + * Mint data. + */ +typedef struct Token_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. + */ + struct Token_COption_Pubkey mint_authority; + /** + * Total supply of tokens. + */ + uint64_t supply; + /** + * 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; + /** + * Optional authority to freeze token accounts. + */ + struct Token_COption_Pubkey freeze_authority; +} Token_Mint; + +/** + * A C representation of Rust's `std::option::Option` + */ +typedef enum Token_COption_u64_Tag { + /** + * No value + */ + Token_COption_u64_None_u64, + /** + * Some value `T` + */ + Token_COption_u64_Some_u64, +} Token_COption_u64_Tag; + +typedef struct Token_COption_u64 { + Token_COption_u64_Tag tag; + union { + struct { + uint64_t some; + }; + }; +} Token_COption_u64; + +/** + * 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 + */ + struct Token_COption_Pubkey delegate; + /** + * The account's state + */ + Token_AccountState state; + /** + * If is_some, this is a native token, and the value logs the rent-exempt reserve. An Account + * is required to be rent-exempt, so the value is used by the Processor to ensure that wrapped + * SOL accounts do not drop below this threshold. + */ + struct Token_COption_u64 is_native; + /** + * The amount delegated + */ + uint64_t delegated_amount; + /** + * Optional authority to close the account. + */ + struct Token_COption_Pubkey close_authority; +} 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; diff --git a/token/program-2022/program-id.md b/token/program-2022/program-id.md new file mode 100644 index 00000000000..8f749d41cee --- /dev/null +++ b/token/program-2022/program-id.md @@ -0,0 +1 @@ +Token22gQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA diff --git a/token/program-2022/src/entrypoint.rs b/token/program-2022/src/entrypoint.rs new file mode 100644 index 00000000000..53315751b9a --- /dev/null +++ b/token/program-2022/src/entrypoint.rs @@ -0,0 +1,21 @@ +//! Program entrypoint + +use crate::{error::TokenError, processor::Processor}; +use solana_program::{ + account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, + program_error::PrintProgramError, pubkey::Pubkey, +}; + +entrypoint!(process_instruction); +fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + 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::(); + return Err(error); + } + Ok(()) +} diff --git a/token/program-2022/src/error.rs b/token/program-2022/src/error.rs new file mode 100644 index 00000000000..ed3faedd42a --- /dev/null +++ b/token/program-2022/src/error.rs @@ -0,0 +1,87 @@ +//! Error types + +use num_derive::FromPrimitive; +use solana_program::{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 { + // 0 + /// Lamport balance below rent-exempt threshold. + #[error("Lamport balance below rent-exempt threshold")] + NotRentExempt, + /// Insufficient funds for the operation requested. + #[error("Insufficient funds")] + InsufficientFunds, + /// Invalid Mint. + #[error("Invalid Mint")] + InvalidMint, + /// Account not associated with this Mint. + #[error("Account not associated with this Mint")] + MintMismatch, + /// Owner does not match. + #[error("Owner does not match")] + OwnerMismatch, + + // 5 + /// This token's supply is fixed and new tokens cannot be minted. + #[error("Fixed supply")] + FixedSupply, + /// The account cannot be initialized because it is already being used. + #[error("Already in use")] + AlreadyInUse, + /// Invalid number of provided signers. + #[error("Invalid number of provided signers")] + InvalidNumberOfProvidedSigners, + /// Invalid number of required signers. + #[error("Invalid number of required signers")] + InvalidNumberOfRequiredSigners, + /// State is uninitialized. + #[error("State is unititialized")] + UninitializedState, + + // 10 + /// Instruction does not support native tokens + #[error("Instruction does not support native tokens")] + NativeNotSupported, + /// Non-native account can only be closed if its balance is zero + #[error("Non-native account can only be closed if its balance is zero")] + NonNativeHasBalance, + /// Invalid instruction + #[error("Invalid instruction")] + InvalidInstruction, + /// State is invalid for requested operation. + #[error("State is invalid for requested operation")] + InvalidState, + /// Operation overflowed + #[error("Operation overflowed")] + Overflow, + + // 15 + /// Account does not support specified authority type. + #[error("Account does not support specified authority type")] + AuthorityTypeNotSupported, + /// This token mint cannot freeze accounts. + #[error("This token mint cannot freeze accounts")] + MintCannotFreeze, + /// Account is frozen; all account operations will fail + #[error("Account is frozen")] + AccountFrozen, + /// Mint decimals mismatch between the client and mint + #[error("The provided decimals value different from the Mint decimals")] + MintDecimalsMismatch, + /// Instruction does not support non-native tokens + #[error("Instruction does not support non-native tokens")] + NonNativeNotSupported, +} +impl From for ProgramError { + fn from(e: TokenError) -> Self { + ProgramError::Custom(e as u32) + } +} +impl DecodeError for TokenError { + fn type_of() -> &'static str { + "TokenError" + } +} diff --git a/token/program-2022/src/instruction.rs b/token/program-2022/src/instruction.rs new file mode 100644 index 00000000000..c5936cf11a6 --- /dev/null +++ b/token/program-2022/src/instruction.rs @@ -0,0 +1,1522 @@ +//! Instruction types + +use crate::{check_program_account, error::TokenError}; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + program_error::ProgramError, + program_option::COption, + pubkey::Pubkey, + sysvar, +}; +use std::convert::TryInto; +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 + /// `CreateAccount` instruction that creates the account being initialized. + /// Otherwise another party can acquire ownership of the uninitialized + /// account. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The mint to initialize. + /// 1. `[]` Rent sysvar + /// + InitializeMint { + /// Number of base 10 digits to the right of the decimal place. + decimals: u8, + /// The authority/multisignature to mint tokens. + mint_authority: Pubkey, + /// The freeze authority/multisignature of the mint. + freeze_authority: COption, + }, + /// Initializes a new account to hold tokens. If this account is associated + /// with the native mint then the token balance of the initialized account + /// will be equal to the amount of SOL in the account. If this account is + /// associated with another mint, that mint must be initialized before this + /// command can succeed. + /// + /// The `InitializeAccount` instruction requires no signers and MUST be + /// included within the same Transaction as the system program's + /// `CreateAccount` instruction that creates the account being initialized. + /// Otherwise another party can acquire ownership of the uninitialized + /// account. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The account to initialize. + /// 1. `[]` The mint this account will be associated with. + /// 2. `[]` The new account's owner/multisignature. + /// 3. `[]` Rent sysvar + InitializeAccount, + /// Initializes a multisignature account with N provided signers. + /// + /// Multisignature accounts can used in place of any single owner/delegate + /// accounts in any token instruction that require an owner/delegate to be + /// present. The variant field represents the number of signers (M) + /// required to validate this multisignature account. + /// + /// The `InitializeMultisig` instruction requires no signers and MUST be + /// included within the same Transaction as the system program's + /// `CreateAccount` instruction that creates the account being initialized. + /// Otherwise another party can acquire ownership of the uninitialized + /// account. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The multisignature account to initialize. + /// 1. `[]` Rent sysvar + /// 2. ..2+N. `[]` The signer accounts, must equal to N where 1 <= N <= + /// 11. + InitializeMultisig { + /// The number of signers (M) required to validate this multisignature + /// account. + m: u8, + }, + /// Transfers tokens from one account to another either directly or via a + /// delegate. If this account is associated with the native mint then equal + /// amounts of SOL and Tokens will be transferred to the destination + /// account. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner/delegate + /// 0. `[writable]` The source account. + /// 1. `[writable]` The destination account. + /// 2. `[signer]` The source account's owner/delegate. + /// + /// * Multisignature owner/delegate + /// 0. `[writable]` The source account. + /// 1. `[writable]` The destination account. + /// 2. `[]` The source account's multisignature owner/delegate. + /// 3. ..3+M `[signer]` M signer accounts. + Transfer { + /// The amount of tokens to transfer. + amount: u64, + }, + /// Approves a delegate. A delegate is given the authority over tokens on + /// behalf of the source account's owner. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner + /// 0. `[writable]` The source account. + /// 1. `[]` The delegate. + /// 2. `[signer]` The source account owner. + /// + /// * Multisignature owner + /// 0. `[writable]` The source account. + /// 1. `[]` The delegate. + /// 2. `[]` The source account's multisignature owner. + /// 3. ..3+M `[signer]` M signer accounts + Approve { + /// The amount of tokens the delegate is approved for. + amount: u64, + }, + /// Revokes the delegate's authority. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner + /// 0. `[writable]` The source account. + /// 1. `[signer]` The source account owner. + /// + /// * Multisignature owner + /// 0. `[writable]` The source account. + /// 1. `[]` The source account's multisignature owner. + /// 2. ..2+M `[signer]` M signer accounts + Revoke, + /// Sets a new authority of a mint or account. + /// + /// Accounts expected by this instruction: + /// + /// * Single authority + /// 0. `[writable]` The mint or account to change the authority of. + /// 1. `[signer]` The current authority of the mint or account. + /// + /// * Multisignature authority + /// 0. `[writable]` The mint or account to change the authority of. + /// 1. `[]` The mint's or account's current multisignature authority. + /// 2. ..2+M `[signer]` M signer accounts + SetAuthority { + /// The type of authority to update. + authority_type: AuthorityType, + /// The new authority + new_authority: COption, + }, + /// Mints new tokens to an account. The native mint does not support + /// minting. + /// + /// Accounts expected by this instruction: + /// + /// * Single authority + /// 0. `[writable]` The mint. + /// 1. `[writable]` The account to mint tokens to. + /// 2. `[signer]` The mint's minting authority. + /// + /// * Multisignature authority + /// 0. `[writable]` The mint. + /// 1. `[writable]` The account to mint tokens to. + /// 2. `[]` The mint's multisignature mint-tokens authority. + /// 3. ..3+M `[signer]` M signer accounts. + MintTo { + /// The amount of new tokens to mint. + amount: u64, + }, + /// Burns tokens by removing them from an account. `Burn` does not support + /// accounts associated with the native mint, use `CloseAccount` instead. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner/delegate + /// 0. `[writable]` The account to burn from. + /// 1. `[writable]` The token mint. + /// 2. `[signer]` The account's owner/delegate. + /// + /// * Multisignature owner/delegate + /// 0. `[writable]` The account to burn from. + /// 1. `[writable]` The token mint. + /// 2. `[]` The account's multisignature owner/delegate. + /// 3. ..3+M `[signer]` M signer accounts. + Burn { + /// The amount of tokens to burn. + amount: u64, + }, + /// Close an account by transferring all its SOL to the destination account. + /// Non-native accounts may only be closed if its token amount is zero. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner + /// 0. `[writable]` The account to close. + /// 1. `[writable]` The destination account. + /// 2. `[signer]` The account's owner. + /// + /// * Multisignature owner + /// 0. `[writable]` The account to close. + /// 1. `[writable]` The destination account. + /// 2. `[]` The account's multisignature owner. + /// 3. ..3+M `[signer]` M signer accounts. + CloseAccount, + /// Freeze an Initialized account using the Mint's freeze_authority (if + /// set). + /// + /// Accounts expected by this instruction: + /// + /// * Single owner + /// 0. `[writable]` The account to freeze. + /// 1. `[]` The token mint. + /// 2. `[signer]` The mint freeze authority. + /// + /// * Multisignature owner + /// 0. `[writable]` The account to freeze. + /// 1. `[]` The token mint. + /// 2. `[]` The mint's multisignature freeze authority. + /// 3. ..3+M `[signer]` M signer accounts. + FreezeAccount, + /// Thaw a Frozen account using the Mint's freeze_authority (if set). + /// + /// Accounts expected by this instruction: + /// + /// * Single owner + /// 0. `[writable]` The account to freeze. + /// 1. `[]` The token mint. + /// 2. `[signer]` The mint freeze authority. + /// + /// * Multisignature owner + /// 0. `[writable]` The account to freeze. + /// 1. `[]` The token mint. + /// 2. `[]` The mint's multisignature freeze authority. + /// 3. ..3+M `[signer]` M signer accounts. + ThawAccount, + + /// Transfers tokens from one account to another either directly or via a + /// delegate. If this account is associated with the native mint then equal + /// amounts of SOL and Tokens will be transferred to the destination + /// account. + /// + /// This instruction differs from Transfer in that the token mint and + /// decimals value is checked by the caller. This may be useful when + /// creating transactions offline or within a hardware wallet. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner/delegate + /// 0. `[writable]` The source account. + /// 1. `[]` The token mint. + /// 2. `[writable]` The destination account. + /// 3. `[signer]` The source account's owner/delegate. + /// + /// * Multisignature owner/delegate + /// 0. `[writable]` The source account. + /// 1. `[]` The token mint. + /// 2. `[writable]` The destination account. + /// 3. `[]` The source account's multisignature owner/delegate. + /// 4. ..4+M `[signer]` M signer accounts. + TransferChecked { + /// The amount of tokens to transfer. + amount: u64, + /// Expected number of base 10 digits to the right of the decimal place. + decimals: u8, + }, + /// Approves a delegate. A delegate is given the authority over tokens on + /// behalf of the source account's owner. + /// + /// This instruction differs from Approve in that the token mint and + /// decimals value is checked by the caller. This may be useful when + /// creating transactions offline or within a hardware wallet. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner + /// 0. `[writable]` The source account. + /// 1. `[]` The token mint. + /// 2. `[]` The delegate. + /// 3. `[signer]` The source account owner. + /// + /// * Multisignature owner + /// 0. `[writable]` The source account. + /// 1. `[]` The token mint. + /// 2. `[]` The delegate. + /// 3. `[]` The source account's multisignature owner. + /// 4. ..4+M `[signer]` M signer accounts + ApproveChecked { + /// The amount of tokens the delegate is approved for. + amount: u64, + /// Expected number of base 10 digits to the right of the decimal place. + decimals: u8, + }, + /// Mints new tokens to an account. The native mint does not support + /// minting. + /// + /// This instruction differs from MintTo in that the decimals value is + /// checked by the caller. This may be useful when creating transactions + /// offline or within a hardware wallet. + /// + /// Accounts expected by this instruction: + /// + /// * Single authority + /// 0. `[writable]` The mint. + /// 1. `[writable]` The account to mint tokens to. + /// 2. `[signer]` The mint's minting authority. + /// + /// * Multisignature authority + /// 0. `[writable]` The mint. + /// 1. `[writable]` The account to mint tokens to. + /// 2. `[]` The mint's multisignature mint-tokens authority. + /// 3. ..3+M `[signer]` M signer accounts. + MintToChecked { + /// The amount of new tokens to mint. + amount: u64, + /// Expected number of base 10 digits to the right of the decimal place. + decimals: u8, + }, + /// Burns tokens by removing them from an account. `BurnChecked` does not + /// support accounts associated with the native mint, use `CloseAccount` + /// instead. + /// + /// This instruction differs from Burn in that the decimals value is checked + /// by the caller. This may be useful when creating transactions offline or + /// within a hardware wallet. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner/delegate + /// 0. `[writable]` The account to burn from. + /// 1. `[writable]` The token mint. + /// 2. `[signer]` The account's owner/delegate. + /// + /// * Multisignature owner/delegate + /// 0. `[writable]` The account to burn from. + /// 1. `[writable]` The token mint. + /// 2. `[]` The account's multisignature owner/delegate. + /// 3. ..3+M `[signer]` M signer accounts. + BurnChecked { + /// The amount of tokens to burn. + amount: u64, + /// Expected number of base 10 digits to the right of the decimal place. + decimals: u8, + }, + /// Like InitializeAccount, but the owner pubkey is passed via instruction data + /// rather than the accounts list. This variant may be preferable when using + /// Cross Program Invocation from an instruction that does not need the owner's + /// `AccountInfo` otherwise. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The account to initialize. + /// 1. `[]` The mint this account will be associated with. + /// 3. `[]` Rent sysvar + InitializeAccount2 { + /// The new account's owner/multisignature. + owner: Pubkey, + }, + /// Given a wrapped / native token account (a token account containing SOL) + /// updates its amount field based on the account's underlying `lamports`. + /// This is useful if a non-wrapped SOL account uses `system_instruction::transfer` + /// to move lamports to a wrapped token account, and needs to have its token + /// `amount` field updated. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The native token account to sync with its underlying lamports. + SyncNative, + /// Like InitializeAccount2, but does not require the Rent sysvar to be provided + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The account to initialize. + /// 1. `[]` The mint this account will be associated with. + InitializeAccount3 { + /// The new account's owner/multisignature. + owner: Pubkey, + }, + /// Like InitializeMultisig, but does not require the Rent sysvar to be provided + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The multisignature account to initialize. + /// 1. ..1+N. `[]` The signer accounts, must equal to N where 1 <= N <= + /// 11. + InitializeMultisig2 { + /// The number of signers (M) required to validate this multisignature + /// account. + m: u8, + }, + /// Like InitializeMint, but does not require the Rent sysvar to be provided + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The mint to initialize. + /// + InitializeMint2 { + /// Number of base 10 digits to the right of the decimal place. + decimals: u8, + /// The authority/multisignature to mint tokens. + mint_authority: Pubkey, + /// The freeze authority/multisignature of the mint. + freeze_authority: COption, + }, +} +impl TokenInstruction { + /// Unpacks a byte buffer into a [TokenInstruction](enum.TokenInstruction.html). + pub fn unpack(input: &[u8]) -> Result { + use TokenError::InvalidInstruction; + + let (&tag, rest) = input.split_first().ok_or(InvalidInstruction)?; + Ok(match tag { + 0 => { + let (&decimals, rest) = rest.split_first().ok_or(InvalidInstruction)?; + let (mint_authority, rest) = Self::unpack_pubkey(rest)?; + let (freeze_authority, _rest) = Self::unpack_pubkey_option(rest)?; + Self::InitializeMint { + mint_authority, + freeze_authority, + decimals, + } + } + 1 => Self::InitializeAccount, + 2 => { + let &m = rest.get(0).ok_or(InvalidInstruction)?; + Self::InitializeMultisig { m } + } + 3 | 4 | 7 | 8 => { + let amount = rest + .get(..8) + .and_then(|slice| slice.try_into().ok()) + .map(u64::from_le_bytes) + .ok_or(InvalidInstruction)?; + match tag { + 3 => Self::Transfer { amount }, + 4 => Self::Approve { amount }, + 7 => Self::MintTo { amount }, + 8 => Self::Burn { amount }, + _ => unreachable!(), + } + } + 5 => Self::Revoke, + 6 => { + let (authority_type, rest) = rest + .split_first() + .ok_or_else(|| ProgramError::from(InvalidInstruction)) + .and_then(|(&t, rest)| Ok((AuthorityType::from(t)?, rest)))?; + let (new_authority, _rest) = Self::unpack_pubkey_option(rest)?; + + Self::SetAuthority { + authority_type, + new_authority, + } + } + 9 => Self::CloseAccount, + 10 => Self::FreezeAccount, + 11 => Self::ThawAccount, + 12 => { + let (amount, rest) = rest.split_at(8); + let amount = amount + .try_into() + .ok() + .map(u64::from_le_bytes) + .ok_or(InvalidInstruction)?; + let (&decimals, _rest) = rest.split_first().ok_or(InvalidInstruction)?; + + Self::TransferChecked { amount, decimals } + } + 13 => { + let (amount, rest) = rest.split_at(8); + let amount = amount + .try_into() + .ok() + .map(u64::from_le_bytes) + .ok_or(InvalidInstruction)?; + let (&decimals, _rest) = rest.split_first().ok_or(InvalidInstruction)?; + + Self::ApproveChecked { amount, decimals } + } + 14 => { + let (amount, rest) = rest.split_at(8); + let amount = amount + .try_into() + .ok() + .map(u64::from_le_bytes) + .ok_or(InvalidInstruction)?; + let (&decimals, _rest) = rest.split_first().ok_or(InvalidInstruction)?; + + Self::MintToChecked { amount, decimals } + } + 15 => { + let (amount, rest) = rest.split_at(8); + let amount = amount + .try_into() + .ok() + .map(u64::from_le_bytes) + .ok_or(InvalidInstruction)?; + let (&decimals, _rest) = rest.split_first().ok_or(InvalidInstruction)?; + + Self::BurnChecked { amount, decimals } + } + 16 => { + let (owner, _rest) = Self::unpack_pubkey(rest)?; + Self::InitializeAccount2 { owner } + } + 17 => Self::SyncNative, + 18 => { + let (owner, _rest) = Self::unpack_pubkey(rest)?; + Self::InitializeAccount3 { owner } + } + 19 => { + let &m = rest.get(0).ok_or(InvalidInstruction)?; + Self::InitializeMultisig2 { m } + } + 20 => { + let (&decimals, rest) = rest.split_first().ok_or(InvalidInstruction)?; + let (mint_authority, rest) = Self::unpack_pubkey(rest)?; + let (freeze_authority, _rest) = Self::unpack_pubkey_option(rest)?; + Self::InitializeMint2 { + mint_authority, + freeze_authority, + decimals, + } + } + _ => return Err(TokenError::InvalidInstruction.into()), + }) + } + + /// Packs a [TokenInstruction](enum.TokenInstruction.html) into a byte buffer. + pub fn pack(&self) -> Vec { + let mut buf = Vec::with_capacity(size_of::()); + match self { + &Self::InitializeMint { + ref mint_authority, + ref freeze_authority, + decimals, + } => { + buf.push(0); + buf.push(decimals); + buf.extend_from_slice(mint_authority.as_ref()); + Self::pack_pubkey_option(freeze_authority, &mut buf); + } + Self::InitializeAccount => buf.push(1), + &Self::InitializeMultisig { m } => { + buf.push(2); + buf.push(m); + } + &Self::Transfer { amount } => { + buf.push(3); + buf.extend_from_slice(&amount.to_le_bytes()); + } + &Self::Approve { amount } => { + buf.push(4); + buf.extend_from_slice(&amount.to_le_bytes()); + } + &Self::MintTo { amount } => { + buf.push(7); + buf.extend_from_slice(&amount.to_le_bytes()); + } + &Self::Burn { amount } => { + buf.push(8); + buf.extend_from_slice(&amount.to_le_bytes()); + } + Self::Revoke => buf.push(5), + Self::SetAuthority { + authority_type, + ref new_authority, + } => { + buf.push(6); + buf.push(authority_type.into()); + Self::pack_pubkey_option(new_authority, &mut buf); + } + Self::CloseAccount => buf.push(9), + Self::FreezeAccount => buf.push(10), + Self::ThawAccount => buf.push(11), + &Self::TransferChecked { amount, decimals } => { + buf.push(12); + buf.extend_from_slice(&amount.to_le_bytes()); + buf.push(decimals); + } + &Self::ApproveChecked { amount, decimals } => { + buf.push(13); + buf.extend_from_slice(&amount.to_le_bytes()); + buf.push(decimals); + } + &Self::MintToChecked { amount, decimals } => { + buf.push(14); + buf.extend_from_slice(&amount.to_le_bytes()); + buf.push(decimals); + } + &Self::BurnChecked { amount, decimals } => { + buf.push(15); + buf.extend_from_slice(&amount.to_le_bytes()); + buf.push(decimals); + } + &Self::InitializeAccount2 { owner } => { + buf.push(16); + buf.extend_from_slice(owner.as_ref()); + } + &Self::SyncNative => { + buf.push(17); + } + &Self::InitializeAccount3 { owner } => { + buf.push(18); + buf.extend_from_slice(owner.as_ref()); + } + &Self::InitializeMultisig2 { m } => { + buf.push(19); + buf.push(m); + } + &Self::InitializeMint2 { + ref mint_authority, + ref freeze_authority, + decimals, + } => { + buf.push(20); + buf.push(decimals); + buf.extend_from_slice(mint_authority.as_ref()); + Self::pack_pubkey_option(freeze_authority, &mut buf); + } + }; + buf + } + + fn unpack_pubkey(input: &[u8]) -> Result<(Pubkey, &[u8]), ProgramError> { + if input.len() >= 32 { + let (key, rest) = input.split_at(32); + let pk = Pubkey::new(key); + Ok((pk, rest)) + } else { + Err(TokenError::InvalidInstruction.into()) + } + } + + fn unpack_pubkey_option(input: &[u8]) -> Result<(COption, &[u8]), ProgramError> { + match input.split_first() { + Option::Some((&0, rest)) => Ok((COption::None, rest)), + Option::Some((&1, rest)) if rest.len() >= 32 => { + let (key, rest) = rest.split_at(32); + let pk = Pubkey::new(key); + Ok((COption::Some(pk), rest)) + } + _ => Err(TokenError::InvalidInstruction.into()), + } + } + + fn pack_pubkey_option(value: &COption, buf: &mut Vec) { + match *value { + COption::Some(ref key) => { + buf.push(1); + buf.extend_from_slice(&key.to_bytes()); + } + COption::None => buf.push(0), + } + } +} + +/// Specifies the authority type for SetAuthority instructions +#[repr(u8)] +#[derive(Clone, Debug, PartialEq)] +pub enum AuthorityType { + /// Authority to mint new tokens + MintTokens, + /// Authority to freeze any account associated with the Mint + FreezeAccount, + /// Owner of a given token account + AccountOwner, + /// Authority to close a token account + CloseAccount, +} + +impl AuthorityType { + fn into(&self) -> u8 { + match self { + AuthorityType::MintTokens => 0, + AuthorityType::FreezeAccount => 1, + AuthorityType::AccountOwner => 2, + AuthorityType::CloseAccount => 3, + } + } + + fn from(index: u8) -> Result { + match index { + 0 => Ok(AuthorityType::MintTokens), + 1 => Ok(AuthorityType::FreezeAccount), + 2 => Ok(AuthorityType::AccountOwner), + 3 => Ok(AuthorityType::CloseAccount), + _ => Err(TokenError::InvalidInstruction.into()), + } + } +} + +/// Creates a `InitializeMint` instruction. +pub fn initialize_mint( + token_program_id: &Pubkey, + mint_pubkey: &Pubkey, + mint_authority_pubkey: &Pubkey, + freeze_authority_pubkey: Option<&Pubkey>, + decimals: u8, +) -> Result { + check_program_account(token_program_id)?; + let freeze_authority = freeze_authority_pubkey.cloned().into(); + let data = TokenInstruction::InitializeMint { + mint_authority: *mint_authority_pubkey, + freeze_authority, + decimals, + } + .pack(); + + let accounts = vec![ + AccountMeta::new(*mint_pubkey, false), + AccountMeta::new_readonly(sysvar::rent::id(), false), + ]; + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `InitializeMint2` instruction. +pub fn initialize_mint2( + token_program_id: &Pubkey, + mint_pubkey: &Pubkey, + mint_authority_pubkey: &Pubkey, + freeze_authority_pubkey: Option<&Pubkey>, + decimals: u8, +) -> Result { + check_program_account(token_program_id)?; + let freeze_authority = freeze_authority_pubkey.cloned().into(); + let data = TokenInstruction::InitializeMint2 { + mint_authority: *mint_authority_pubkey, + freeze_authority, + decimals, + } + .pack(); + + let accounts = vec![AccountMeta::new(*mint_pubkey, false)]; + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `InitializeAccount` instruction. +pub fn initialize_account( + token_program_id: &Pubkey, + account_pubkey: &Pubkey, + mint_pubkey: &Pubkey, + owner_pubkey: &Pubkey, +) -> Result { + check_program_account(token_program_id)?; + let data = TokenInstruction::InitializeAccount.pack(); + + let accounts = vec![ + AccountMeta::new(*account_pubkey, false), + AccountMeta::new_readonly(*mint_pubkey, false), + AccountMeta::new_readonly(*owner_pubkey, false), + AccountMeta::new_readonly(sysvar::rent::id(), false), + ]; + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `InitializeAccount2` instruction. +pub fn initialize_account2( + token_program_id: &Pubkey, + account_pubkey: &Pubkey, + mint_pubkey: &Pubkey, + owner_pubkey: &Pubkey, +) -> Result { + check_program_account(token_program_id)?; + let data = TokenInstruction::InitializeAccount2 { + owner: *owner_pubkey, + } + .pack(); + + let accounts = vec![ + AccountMeta::new(*account_pubkey, false), + AccountMeta::new_readonly(*mint_pubkey, false), + AccountMeta::new_readonly(sysvar::rent::id(), false), + ]; + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `InitializeAccount3` instruction. +pub fn initialize_account3( + token_program_id: &Pubkey, + account_pubkey: &Pubkey, + mint_pubkey: &Pubkey, + owner_pubkey: &Pubkey, +) -> Result { + check_program_account(token_program_id)?; + let data = TokenInstruction::InitializeAccount3 { + owner: *owner_pubkey, + } + .pack(); + + let accounts = vec![ + AccountMeta::new(*account_pubkey, false), + AccountMeta::new_readonly(*mint_pubkey, false), + ]; + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `InitializeMultisig` instruction. +pub fn initialize_multisig( + token_program_id: &Pubkey, + multisig_pubkey: &Pubkey, + signer_pubkeys: &[&Pubkey], + m: u8, +) -> Result { + check_program_account(token_program_id)?; + if !is_valid_signer_index(m as usize) + || !is_valid_signer_index(signer_pubkeys.len()) + || m as usize > signer_pubkeys.len() + { + return Err(ProgramError::MissingRequiredSignature); + } + let data = TokenInstruction::InitializeMultisig { m }.pack(); + + let mut accounts = Vec::with_capacity(1 + 1 + signer_pubkeys.len()); + accounts.push(AccountMeta::new(*multisig_pubkey, false)); + accounts.push(AccountMeta::new_readonly(sysvar::rent::id(), false)); + for signer_pubkey in signer_pubkeys.iter() { + accounts.push(AccountMeta::new_readonly(**signer_pubkey, false)); + } + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `InitializeMultisig2` instruction. +pub fn initialize_multisig2( + token_program_id: &Pubkey, + multisig_pubkey: &Pubkey, + signer_pubkeys: &[&Pubkey], + m: u8, +) -> Result { + check_program_account(token_program_id)?; + if !is_valid_signer_index(m as usize) + || !is_valid_signer_index(signer_pubkeys.len()) + || m as usize > signer_pubkeys.len() + { + return Err(ProgramError::MissingRequiredSignature); + } + let data = TokenInstruction::InitializeMultisig2 { m }.pack(); + + let mut accounts = Vec::with_capacity(1 + 1 + signer_pubkeys.len()); + accounts.push(AccountMeta::new(*multisig_pubkey, false)); + for signer_pubkey in signer_pubkeys.iter() { + accounts.push(AccountMeta::new_readonly(**signer_pubkey, false)); + } + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `Transfer` instruction. +pub fn transfer( + token_program_id: &Pubkey, + source_pubkey: &Pubkey, + destination_pubkey: &Pubkey, + authority_pubkey: &Pubkey, + signer_pubkeys: &[&Pubkey], + amount: u64, +) -> Result { + check_program_account(token_program_id)?; + let data = TokenInstruction::Transfer { amount }.pack(); + + let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); + accounts.push(AccountMeta::new(*source_pubkey, false)); + accounts.push(AccountMeta::new(*destination_pubkey, false)); + accounts.push(AccountMeta::new_readonly( + *authority_pubkey, + signer_pubkeys.is_empty(), + )); + for signer_pubkey in signer_pubkeys.iter() { + accounts.push(AccountMeta::new_readonly(**signer_pubkey, true)); + } + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates an `Approve` instruction. +pub fn approve( + token_program_id: &Pubkey, + source_pubkey: &Pubkey, + delegate_pubkey: &Pubkey, + owner_pubkey: &Pubkey, + signer_pubkeys: &[&Pubkey], + amount: u64, +) -> Result { + check_program_account(token_program_id)?; + let data = TokenInstruction::Approve { amount }.pack(); + + let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); + accounts.push(AccountMeta::new(*source_pubkey, false)); + accounts.push(AccountMeta::new_readonly(*delegate_pubkey, false)); + accounts.push(AccountMeta::new_readonly( + *owner_pubkey, + signer_pubkeys.is_empty(), + )); + for signer_pubkey in signer_pubkeys.iter() { + accounts.push(AccountMeta::new_readonly(**signer_pubkey, true)); + } + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `Revoke` instruction. +pub fn revoke( + token_program_id: &Pubkey, + source_pubkey: &Pubkey, + owner_pubkey: &Pubkey, + signer_pubkeys: &[&Pubkey], +) -> Result { + check_program_account(token_program_id)?; + let data = TokenInstruction::Revoke.pack(); + + let mut accounts = Vec::with_capacity(2 + signer_pubkeys.len()); + accounts.push(AccountMeta::new(*source_pubkey, false)); + accounts.push(AccountMeta::new_readonly( + *owner_pubkey, + signer_pubkeys.is_empty(), + )); + for signer_pubkey in signer_pubkeys.iter() { + accounts.push(AccountMeta::new_readonly(**signer_pubkey, true)); + } + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `SetAuthority` instruction. +pub fn set_authority( + token_program_id: &Pubkey, + owned_pubkey: &Pubkey, + new_authority_pubkey: Option<&Pubkey>, + authority_type: AuthorityType, + owner_pubkey: &Pubkey, + signer_pubkeys: &[&Pubkey], +) -> Result { + check_program_account(token_program_id)?; + let new_authority = new_authority_pubkey.cloned().into(); + let data = TokenInstruction::SetAuthority { + authority_type, + new_authority, + } + .pack(); + + let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); + accounts.push(AccountMeta::new(*owned_pubkey, false)); + accounts.push(AccountMeta::new_readonly( + *owner_pubkey, + signer_pubkeys.is_empty(), + )); + for signer_pubkey in signer_pubkeys.iter() { + accounts.push(AccountMeta::new_readonly(**signer_pubkey, true)); + } + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `MintTo` instruction. +pub fn mint_to( + token_program_id: &Pubkey, + mint_pubkey: &Pubkey, + account_pubkey: &Pubkey, + owner_pubkey: &Pubkey, + signer_pubkeys: &[&Pubkey], + amount: u64, +) -> Result { + check_program_account(token_program_id)?; + let data = TokenInstruction::MintTo { amount }.pack(); + + let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); + accounts.push(AccountMeta::new(*mint_pubkey, false)); + accounts.push(AccountMeta::new(*account_pubkey, false)); + accounts.push(AccountMeta::new_readonly( + *owner_pubkey, + signer_pubkeys.is_empty(), + )); + for signer_pubkey in signer_pubkeys.iter() { + accounts.push(AccountMeta::new_readonly(**signer_pubkey, true)); + } + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `Burn` instruction. +pub fn burn( + token_program_id: &Pubkey, + account_pubkey: &Pubkey, + mint_pubkey: &Pubkey, + authority_pubkey: &Pubkey, + signer_pubkeys: &[&Pubkey], + amount: u64, +) -> Result { + check_program_account(token_program_id)?; + let data = TokenInstruction::Burn { amount }.pack(); + + let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); + accounts.push(AccountMeta::new(*account_pubkey, false)); + accounts.push(AccountMeta::new(*mint_pubkey, false)); + accounts.push(AccountMeta::new_readonly( + *authority_pubkey, + signer_pubkeys.is_empty(), + )); + for signer_pubkey in signer_pubkeys.iter() { + accounts.push(AccountMeta::new_readonly(**signer_pubkey, true)); + } + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `CloseAccount` instruction. +pub fn close_account( + token_program_id: &Pubkey, + account_pubkey: &Pubkey, + destination_pubkey: &Pubkey, + owner_pubkey: &Pubkey, + signer_pubkeys: &[&Pubkey], +) -> Result { + check_program_account(token_program_id)?; + let data = TokenInstruction::CloseAccount.pack(); + + let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); + accounts.push(AccountMeta::new(*account_pubkey, false)); + accounts.push(AccountMeta::new(*destination_pubkey, false)); + accounts.push(AccountMeta::new_readonly( + *owner_pubkey, + signer_pubkeys.is_empty(), + )); + for signer_pubkey in signer_pubkeys.iter() { + accounts.push(AccountMeta::new_readonly(**signer_pubkey, true)); + } + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `FreezeAccount` instruction. +pub fn freeze_account( + token_program_id: &Pubkey, + account_pubkey: &Pubkey, + mint_pubkey: &Pubkey, + owner_pubkey: &Pubkey, + signer_pubkeys: &[&Pubkey], +) -> Result { + check_program_account(token_program_id)?; + let data = TokenInstruction::FreezeAccount.pack(); + + let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); + accounts.push(AccountMeta::new(*account_pubkey, false)); + accounts.push(AccountMeta::new_readonly(*mint_pubkey, false)); + accounts.push(AccountMeta::new_readonly( + *owner_pubkey, + signer_pubkeys.is_empty(), + )); + for signer_pubkey in signer_pubkeys.iter() { + accounts.push(AccountMeta::new_readonly(**signer_pubkey, true)); + } + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `ThawAccount` instruction. +pub fn thaw_account( + token_program_id: &Pubkey, + account_pubkey: &Pubkey, + mint_pubkey: &Pubkey, + owner_pubkey: &Pubkey, + signer_pubkeys: &[&Pubkey], +) -> Result { + check_program_account(token_program_id)?; + let data = TokenInstruction::ThawAccount.pack(); + + let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); + accounts.push(AccountMeta::new(*account_pubkey, false)); + accounts.push(AccountMeta::new_readonly(*mint_pubkey, false)); + accounts.push(AccountMeta::new_readonly( + *owner_pubkey, + signer_pubkeys.is_empty(), + )); + for signer_pubkey in signer_pubkeys.iter() { + accounts.push(AccountMeta::new_readonly(**signer_pubkey, true)); + } + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `TransferChecked` instruction. +#[allow(clippy::too_many_arguments)] +pub fn transfer_checked( + token_program_id: &Pubkey, + source_pubkey: &Pubkey, + mint_pubkey: &Pubkey, + destination_pubkey: &Pubkey, + authority_pubkey: &Pubkey, + signer_pubkeys: &[&Pubkey], + amount: u64, + decimals: u8, +) -> Result { + check_program_account(token_program_id)?; + let data = TokenInstruction::TransferChecked { amount, decimals }.pack(); + + let mut accounts = Vec::with_capacity(4 + signer_pubkeys.len()); + accounts.push(AccountMeta::new(*source_pubkey, false)); + accounts.push(AccountMeta::new_readonly(*mint_pubkey, false)); + accounts.push(AccountMeta::new(*destination_pubkey, false)); + accounts.push(AccountMeta::new_readonly( + *authority_pubkey, + signer_pubkeys.is_empty(), + )); + for signer_pubkey in signer_pubkeys.iter() { + accounts.push(AccountMeta::new_readonly(**signer_pubkey, true)); + } + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates an `ApproveChecked` instruction. +#[allow(clippy::too_many_arguments)] +pub fn approve_checked( + token_program_id: &Pubkey, + source_pubkey: &Pubkey, + mint_pubkey: &Pubkey, + delegate_pubkey: &Pubkey, + owner_pubkey: &Pubkey, + signer_pubkeys: &[&Pubkey], + amount: u64, + decimals: u8, +) -> Result { + check_program_account(token_program_id)?; + let data = TokenInstruction::ApproveChecked { amount, decimals }.pack(); + + let mut accounts = Vec::with_capacity(4 + signer_pubkeys.len()); + accounts.push(AccountMeta::new(*source_pubkey, false)); + accounts.push(AccountMeta::new_readonly(*mint_pubkey, false)); + accounts.push(AccountMeta::new_readonly(*delegate_pubkey, false)); + accounts.push(AccountMeta::new_readonly( + *owner_pubkey, + signer_pubkeys.is_empty(), + )); + for signer_pubkey in signer_pubkeys.iter() { + accounts.push(AccountMeta::new_readonly(**signer_pubkey, true)); + } + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `MintToChecked` instruction. +pub fn mint_to_checked( + token_program_id: &Pubkey, + mint_pubkey: &Pubkey, + account_pubkey: &Pubkey, + owner_pubkey: &Pubkey, + signer_pubkeys: &[&Pubkey], + amount: u64, + decimals: u8, +) -> Result { + check_program_account(token_program_id)?; + let data = TokenInstruction::MintToChecked { amount, decimals }.pack(); + + let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); + accounts.push(AccountMeta::new(*mint_pubkey, false)); + accounts.push(AccountMeta::new(*account_pubkey, false)); + accounts.push(AccountMeta::new_readonly( + *owner_pubkey, + signer_pubkeys.is_empty(), + )); + for signer_pubkey in signer_pubkeys.iter() { + accounts.push(AccountMeta::new_readonly(**signer_pubkey, true)); + } + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `BurnChecked` instruction. +pub fn burn_checked( + token_program_id: &Pubkey, + account_pubkey: &Pubkey, + mint_pubkey: &Pubkey, + authority_pubkey: &Pubkey, + signer_pubkeys: &[&Pubkey], + amount: u64, + decimals: u8, +) -> Result { + check_program_account(token_program_id)?; + let data = TokenInstruction::BurnChecked { amount, decimals }.pack(); + + let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); + accounts.push(AccountMeta::new(*account_pubkey, false)); + accounts.push(AccountMeta::new(*mint_pubkey, false)); + accounts.push(AccountMeta::new_readonly( + *authority_pubkey, + signer_pubkeys.is_empty(), + )); + for signer_pubkey in signer_pubkeys.iter() { + accounts.push(AccountMeta::new_readonly(**signer_pubkey, true)); + } + + Ok(Instruction { + program_id: *token_program_id, + accounts, + data, + }) +} + +/// Creates a `SyncNative` instruction +pub fn sync_native( + token_program_id: &Pubkey, + account_pubkey: &Pubkey, +) -> Result { + check_program_account(token_program_id)?; + + Ok(Instruction { + program_id: *token_program_id, + accounts: vec![AccountMeta::new(*account_pubkey, false)], + data: TokenInstruction::SyncNative.pack(), + }) +} + +/// Utility function that checks index is between MIN_SIGNERS and MAX_SIGNERS +pub fn is_valid_signer_index(index: usize) -> bool { + (MIN_SIGNERS..=MAX_SIGNERS).contains(&index) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_instruction_packing() { + let check = TokenInstruction::InitializeMint { + decimals: 2, + mint_authority: Pubkey::new(&[1u8; 32]), + freeze_authority: COption::None, + }; + let packed = check.pack(); + let mut expect = Vec::from([0u8, 2]); + expect.extend_from_slice(&[1u8; 32]); + expect.extend_from_slice(&[0]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeMint { + decimals: 2, + mint_authority: Pubkey::new(&[2u8; 32]), + freeze_authority: COption::Some(Pubkey::new(&[3u8; 32])), + }; + let packed = check.pack(); + let mut expect = vec![0u8, 2]; + expect.extend_from_slice(&[2u8; 32]); + expect.extend_from_slice(&[1]); + expect.extend_from_slice(&[3u8; 32]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeAccount; + let packed = check.pack(); + let expect = Vec::from([1u8]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeMultisig { m: 1 }; + let packed = check.pack(); + let expect = Vec::from([2u8, 1]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::Transfer { amount: 1 }; + let packed = check.pack(); + let expect = Vec::from([3u8, 1, 0, 0, 0, 0, 0, 0, 0]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::Approve { amount: 1 }; + let packed = check.pack(); + let expect = Vec::from([4u8, 1, 0, 0, 0, 0, 0, 0, 0]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::Revoke; + let packed = check.pack(); + let expect = Vec::from([5u8]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::SetAuthority { + authority_type: AuthorityType::FreezeAccount, + new_authority: COption::Some(Pubkey::new(&[4u8; 32])), + }; + let packed = check.pack(); + let mut expect = Vec::from([6u8, 1]); + expect.extend_from_slice(&[1]); + expect.extend_from_slice(&[4u8; 32]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::MintTo { amount: 1 }; + let packed = check.pack(); + let expect = Vec::from([7u8, 1, 0, 0, 0, 0, 0, 0, 0]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::Burn { amount: 1 }; + let packed = check.pack(); + let expect = Vec::from([8u8, 1, 0, 0, 0, 0, 0, 0, 0]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::CloseAccount; + let packed = check.pack(); + let expect = Vec::from([9u8]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::FreezeAccount; + let packed = check.pack(); + let expect = Vec::from([10u8]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::ThawAccount; + let packed = check.pack(); + let expect = Vec::from([11u8]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::TransferChecked { + amount: 1, + decimals: 2, + }; + let packed = check.pack(); + let expect = Vec::from([12u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::ApproveChecked { + amount: 1, + decimals: 2, + }; + let packed = check.pack(); + let expect = Vec::from([13u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::MintToChecked { + amount: 1, + decimals: 2, + }; + let packed = check.pack(); + let expect = Vec::from([14u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::BurnChecked { + amount: 1, + decimals: 2, + }; + let packed = check.pack(); + let expect = Vec::from([15u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeAccount2 { + owner: Pubkey::new(&[2u8; 32]), + }; + let packed = check.pack(); + let mut expect = vec![16u8]; + expect.extend_from_slice(&[2u8; 32]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::SyncNative; + let packed = check.pack(); + let expect = vec![17u8]; + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeAccount3 { + owner: Pubkey::new(&[2u8; 32]), + }; + let packed = check.pack(); + let mut expect = vec![18u8]; + expect.extend_from_slice(&[2u8; 32]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeMultisig2 { m: 1 }; + let packed = check.pack(); + let expect = Vec::from([19u8, 1]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeMint2 { + decimals: 2, + mint_authority: Pubkey::new(&[1u8; 32]), + freeze_authority: COption::None, + }; + let packed = check.pack(); + let mut expect = Vec::from([20u8, 2]); + expect.extend_from_slice(&[1u8; 32]); + expect.extend_from_slice(&[0]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeMint2 { + decimals: 2, + mint_authority: Pubkey::new(&[2u8; 32]), + freeze_authority: COption::Some(Pubkey::new(&[3u8; 32])), + }; + let packed = check.pack(); + let mut expect = vec![20u8, 2]; + expect.extend_from_slice(&[2u8; 32]); + expect.extend_from_slice(&[1]); + expect.extend_from_slice(&[3u8; 32]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + } +} diff --git a/token/program-2022/src/lib.rs b/token/program-2022/src/lib.rs new file mode 100644 index 00000000000..7bef43e51e0 --- /dev/null +++ b/token/program-2022/src/lib.rs @@ -0,0 +1,38 @@ +#![deny(missing_docs)] +#![cfg_attr(not(test), forbid(unsafe_code))] + +//! An ERC20-like Token program for the Solana blockchain + +pub mod error; +pub mod instruction; +pub mod native_mint; +pub mod processor; +pub mod state; + +#[cfg(not(feature = "no-entrypoint"))] +mod entrypoint; + +// Export current sdk types for downstream users building with a different sdk version +pub use solana_program; +use solana_program::{entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey}; + +/// 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_program::declare_id!("Token22gQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); + +/// Checks that the supplied program ID is the correct one for SPL-token +pub fn check_program_account(spl_token_program_id: &Pubkey) -> ProgramResult { + if spl_token_program_id != &id() { + return Err(ProgramError::IncorrectProgramId); + } + Ok(()) +} diff --git a/token/program-2022/src/native_mint.rs b/token/program-2022/src/native_mint.rs new file mode 100644 index 00000000000..0502f9f71e8 --- /dev/null +++ b/token/program-2022/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_program::declare_id!("So11111111111111111111111111111111111111112"); + +#[cfg(test)] +mod tests { + use super::*; + use solana_program::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) + ); + } +} diff --git a/token/program-2022/src/processor.rs b/token/program-2022/src/processor.rs new file mode 100644 index 00000000000..a1d86f06e16 --- /dev/null +++ b/token/program-2022/src/processor.rs @@ -0,0 +1,6160 @@ +//! Program state processor + +use crate::{ + error::TokenError, + instruction::{is_valid_signer_index, AuthorityType, TokenInstruction, MAX_SIGNERS}, + state::{Account, AccountState, Mint, Multisig}, +}; +use num_traits::FromPrimitive; +use solana_program::{ + account_info::{next_account_info, AccountInfo}, + decode_error::DecodeError, + entrypoint::ProgramResult, + msg, + program_error::{PrintProgramError, ProgramError}, + program_option::COption, + program_pack::{IsInitialized, Pack}, + pubkey::Pubkey, + sysvar::{rent::Rent, Sysvar}, +}; + +/// Program state handler. +pub struct Processor {} +impl Processor { + fn _process_initialize_mint( + accounts: &[AccountInfo], + decimals: u8, + mint_authority: Pubkey, + freeze_authority: COption, + rent_sysvar_account: bool, + ) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let mint_info = next_account_info(account_info_iter)?; + let mint_data_len = mint_info.data_len(); + let rent = if rent_sysvar_account { + Rent::from_account_info(next_account_info(account_info_iter)?)? + } else { + Rent::get()? + }; + + let mut mint = Mint::unpack_unchecked(&mint_info.data.borrow())?; + if mint.is_initialized { + return Err(TokenError::AlreadyInUse.into()); + } + + if !rent.is_exempt(mint_info.lamports(), mint_data_len) { + return Err(TokenError::NotRentExempt.into()); + } + + mint.mint_authority = COption::Some(mint_authority); + mint.decimals = decimals; + mint.is_initialized = true; + mint.freeze_authority = freeze_authority; + + Mint::pack(mint, &mut mint_info.data.borrow_mut())?; + + Ok(()) + } + + /// Processes an [InitializeMint](enum.TokenInstruction.html) instruction. + pub fn process_initialize_mint( + accounts: &[AccountInfo], + decimals: u8, + mint_authority: Pubkey, + freeze_authority: COption, + ) -> ProgramResult { + Self::_process_initialize_mint(accounts, decimals, mint_authority, freeze_authority, true) + } + + /// Processes an [InitializeMint2](enum.TokenInstruction.html) instruction. + pub fn process_initialize_mint2( + accounts: &[AccountInfo], + decimals: u8, + mint_authority: Pubkey, + freeze_authority: COption, + ) -> ProgramResult { + Self::_process_initialize_mint(accounts, decimals, mint_authority, freeze_authority, false) + } + + fn _process_initialize_account( + accounts: &[AccountInfo], + owner: Option<&Pubkey>, + rent_sysvar_account: bool, + ) -> 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 = if let Some(owner) = owner { + owner + } else { + next_account_info(account_info_iter)?.key + }; + let new_account_info_data_len = new_account_info.data_len(); + let rent = if rent_sysvar_account { + Rent::from_account_info(next_account_info(account_info_iter)?)? + } else { + Rent::get()? + }; + + let mut account = Account::unpack_unchecked(&new_account_info.data.borrow())?; + if account.is_initialized() { + return Err(TokenError::AlreadyInUse.into()); + } + + if !rent.is_exempt(new_account_info.lamports(), new_account_info_data_len) { + return Err(TokenError::NotRentExempt.into()); + } + + if *mint_info.key != crate::native_mint::id() { + let _ = Mint::unpack(&mint_info.data.borrow_mut()) + .map_err(|_| Into::::into(TokenError::InvalidMint))?; + } + + account.mint = *mint_info.key; + account.owner = *owner; + account.delegate = COption::None; + account.delegated_amount = 0; + account.state = AccountState::Initialized; + if *mint_info.key == crate::native_mint::id() { + let rent_exempt_reserve = rent.minimum_balance(new_account_info_data_len); + account.is_native = COption::Some(rent_exempt_reserve); + account.amount = new_account_info + .lamports() + .checked_sub(rent_exempt_reserve) + .ok_or(TokenError::Overflow)?; + } else { + account.is_native = COption::None; + account.amount = 0; + }; + + Account::pack(account, &mut new_account_info.data.borrow_mut())?; + + Ok(()) + } + + /// Processes an [InitializeAccount](enum.TokenInstruction.html) instruction. + pub fn process_initialize_account(accounts: &[AccountInfo]) -> ProgramResult { + Self::_process_initialize_account(accounts, None, true) + } + + /// Processes an [InitializeAccount2](enum.TokenInstruction.html) instruction. + pub fn process_initialize_account2(accounts: &[AccountInfo], owner: Pubkey) -> ProgramResult { + Self::_process_initialize_account(accounts, Some(&owner), true) + } + + /// Processes an [InitializeAccount3](enum.TokenInstruction.html) instruction. + pub fn process_initialize_account3(accounts: &[AccountInfo], owner: Pubkey) -> ProgramResult { + Self::_process_initialize_account(accounts, Some(&owner), false) + } + + fn _process_initialize_multisig( + accounts: &[AccountInfo], + m: u8, + rent_sysvar_account: bool, + ) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let multisig_info = next_account_info(account_info_iter)?; + let multisig_info_data_len = multisig_info.data_len(); + let rent = if rent_sysvar_account { + Rent::from_account_info(next_account_info(account_info_iter)?)? + } else { + Rent::get()? + }; + + let mut multisig = Multisig::unpack_unchecked(&multisig_info.data.borrow())?; + if multisig.is_initialized { + return Err(TokenError::AlreadyInUse.into()); + } + + if !rent.is_exempt(multisig_info.lamports(), multisig_info_data_len) { + return Err(TokenError::NotRentExempt.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; + + Multisig::pack(multisig, &mut multisig_info.data.borrow_mut())?; + + Ok(()) + } + + /// Processes a [InitializeMultisig](enum.TokenInstruction.html) instruction. + pub fn process_initialize_multisig(accounts: &[AccountInfo], m: u8) -> ProgramResult { + Self::_process_initialize_multisig(accounts, m, true) + } + + /// Processes a [InitializeMultisig2](enum.TokenInstruction.html) instruction. + pub fn process_initialize_multisig2(accounts: &[AccountInfo], m: u8) -> ProgramResult { + Self::_process_initialize_multisig(accounts, m, false) + } + + /// Processes a [Transfer](enum.TokenInstruction.html) instruction. + pub fn process_transfer( + program_id: &Pubkey, + accounts: &[AccountInfo], + amount: u64, + expected_decimals: Option, + ) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + + let source_account_info = next_account_info(account_info_iter)?; + + let expected_mint_info = if let Some(expected_decimals) = expected_decimals { + Some((next_account_info(account_info_iter)?, expected_decimals)) + } else { + None + }; + + let dest_account_info = next_account_info(account_info_iter)?; + let authority_info = next_account_info(account_info_iter)?; + + let mut source_account = Account::unpack(&source_account_info.data.borrow())?; + let mut dest_account = Account::unpack(&dest_account_info.data.borrow())?; + + if source_account.is_frozen() || dest_account.is_frozen() { + return Err(TokenError::AccountFrozen.into()); + } + if source_account.amount < amount { + return Err(TokenError::InsufficientFunds.into()); + } + if source_account.mint != dest_account.mint { + return Err(TokenError::MintMismatch.into()); + } + + if let Some((mint_info, expected_decimals)) = expected_mint_info { + if source_account.mint != *mint_info.key { + return Err(TokenError::MintMismatch.into()); + } + + let mint = Mint::unpack(&mint_info.data.borrow_mut())?; + if expected_decimals != mint.decimals { + return Err(TokenError::MintDecimalsMismatch.into()); + } + } + + let self_transfer = source_account_info.key == dest_account_info.key; + + 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()); + } + if !self_transfer { + source_account.delegated_amount = source_account + .delegated_amount + .checked_sub(amount) + .ok_or(TokenError::Overflow)?; + 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(), + )?, + }; + + // This check MUST occur just before the amounts are manipulated + // to ensure self-transfers are fully validated + if self_transfer { + return Ok(()); + } + + source_account.amount = source_account + .amount + .checked_sub(amount) + .ok_or(TokenError::Overflow)?; + dest_account.amount = dest_account + .amount + .checked_add(amount) + .ok_or(TokenError::Overflow)?; + + if source_account.is_native() { + let source_starting_lamports = source_account_info.lamports(); + **source_account_info.lamports.borrow_mut() = source_starting_lamports + .checked_sub(amount) + .ok_or(TokenError::Overflow)?; + + let dest_starting_lamports = dest_account_info.lamports(); + **dest_account_info.lamports.borrow_mut() = dest_starting_lamports + .checked_add(amount) + .ok_or(TokenError::Overflow)?; + } + + Account::pack(source_account, &mut source_account_info.data.borrow_mut())?; + Account::pack(dest_account, &mut dest_account_info.data.borrow_mut())?; + + Ok(()) + } + + /// Processes an [Approve](enum.TokenInstruction.html) instruction. + pub fn process_approve( + program_id: &Pubkey, + accounts: &[AccountInfo], + amount: u64, + expected_decimals: Option, + ) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + + let source_account_info = next_account_info(account_info_iter)?; + + let expected_mint_info = if let Some(expected_decimals) = expected_decimals { + Some((next_account_info(account_info_iter)?, expected_decimals)) + } else { + None + }; + let delegate_info = next_account_info(account_info_iter)?; + let owner_info = next_account_info(account_info_iter)?; + + let mut source_account = Account::unpack(&source_account_info.data.borrow())?; + + if source_account.is_frozen() { + return Err(TokenError::AccountFrozen.into()); + } + + if let Some((mint_info, expected_decimals)) = expected_mint_info { + if source_account.mint != *mint_info.key { + return Err(TokenError::MintMismatch.into()); + } + + let mint = Mint::unpack(&mint_info.data.borrow_mut())?; + if expected_decimals != mint.decimals { + return Err(TokenError::MintDecimalsMismatch.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; + + Account::pack(source_account, &mut source_account_info.data.borrow_mut())?; + + 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_account = Account::unpack(&source_account_info.data.borrow())?; + + 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; + + Account::pack(source_account, &mut source_account_info.data.borrow_mut())?; + + Ok(()) + } + + /// Processes a [SetAuthority](enum.TokenInstruction.html) instruction. + pub fn process_set_authority( + program_id: &Pubkey, + accounts: &[AccountInfo], + authority_type: AuthorityType, + new_authority: COption, + ) -> 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() == Account::get_packed_len() { + let mut account = Account::unpack(&account_info.data.borrow())?; + + if account.is_frozen() { + return Err(TokenError::AccountFrozen.into()); + } + + match authority_type { + AuthorityType::AccountOwner => { + 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()); + } + + account.delegate = COption::None; + account.delegated_amount = 0; + + if account.is_native() { + account.close_authority = COption::None; + } + } + 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()); + } + } + Account::pack(account, &mut account_info.data.borrow_mut())?; + } else if account_info.data_len() == Mint::get_packed_len() { + let mut mint = Mint::unpack(&account_info.data.borrow())?; + 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::::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::::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()); + } + } + Mint::pack(mint, &mut account_info.data.borrow_mut())?; + } 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, + expected_decimals: Option, + ) -> 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 = Account::unpack(&dest_account_info.data.borrow())?; + 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 = Mint::unpack(&mint_info.data.borrow())?; + if let Some(expected_decimals) = expected_decimals { + if expected_decimals != mint.decimals { + return Err(TokenError::MintDecimalsMismatch.into()); + } + } + + 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)?; + + mint.supply = mint + .supply + .checked_add(amount) + .ok_or(TokenError::Overflow)?; + + Account::pack(dest_account, &mut dest_account_info.data.borrow_mut())?; + Mint::pack(mint, &mut mint_info.data.borrow_mut())?; + + Ok(()) + } + + /// Processes a [Burn](enum.TokenInstruction.html) instruction. + pub fn process_burn( + program_id: &Pubkey, + accounts: &[AccountInfo], + amount: u64, + expected_decimals: Option, + ) -> 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_account = Account::unpack(&source_account_info.data.borrow())?; + let mut mint = Mint::unpack(&mint_info.data.borrow())?; + + if source_account.is_frozen() { + return Err(TokenError::AccountFrozen.into()); + } + if source_account.is_native() { + return Err(TokenError::NativeNotSupported.into()); + } + if source_account.amount < amount { + return Err(TokenError::InsufficientFunds.into()); + } + if mint_info.key != &source_account.mint { + return Err(TokenError::MintMismatch.into()); + } + + if let Some(expected_decimals) = expected_decimals { + if expected_decimals != mint.decimals { + return Err(TokenError::MintDecimalsMismatch.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 = source_account + .delegated_amount + .checked_sub(amount) + .ok_or(TokenError::Overflow)?; + 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 = source_account + .amount + .checked_sub(amount) + .ok_or(TokenError::Overflow)?; + mint.supply = mint + .supply + .checked_sub(amount) + .ok_or(TokenError::Overflow)?; + + Account::pack(source_account, &mut source_account_info.data.borrow_mut())?; + Mint::pack(mint, &mut mint_info.data.borrow_mut())?; + + 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_account = Account::unpack(&source_account_info.data.borrow())?; + 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(), + )?; + + let dest_starting_lamports = dest_account_info.lamports(); + **dest_account_info.lamports.borrow_mut() = dest_starting_lamports + .checked_add(source_account_info.lamports()) + .ok_or(TokenError::Overflow)?; + + **source_account_info.lamports.borrow_mut() = 0; + source_account.amount = 0; + + Account::pack(source_account, &mut source_account_info.data.borrow_mut())?; + + 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_account = Account::unpack(&source_account_info.data.borrow())?; + if freeze && source_account.is_frozen() || !freeze && !source_account.is_frozen() { + return Err(TokenError::InvalidState.into()); + } + if source_account.is_native() { + return Err(TokenError::NativeNotSupported.into()); + } + if mint_info.key != &source_account.mint { + return Err(TokenError::MintMismatch.into()); + } + + let mint = Mint::unpack(&mint_info.data.borrow_mut())?; + match mint.freeze_authority { + COption::Some(authority) => Self::validate_owner( + program_id, + &authority, + authority_info, + account_info_iter.as_slice(), + ), + COption::None => Err(TokenError::MintCannotFreeze.into()), + }?; + + source_account.state = if freeze { + AccountState::Frozen + } else { + AccountState::Initialized + }; + + Account::pack(source_account, &mut source_account_info.data.borrow_mut())?; + + Ok(()) + } + + /// Processes a [SyncNative](enum.TokenInstruction.html) instruction + pub fn process_sync_native(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let native_account_info = next_account_info(account_info_iter)?; + + if native_account_info.owner != program_id { + return Err(ProgramError::IncorrectProgramId); + } + let mut native_account = Account::unpack(&native_account_info.data.borrow())?; + + if let COption::Some(rent_exempt_reserve) = native_account.is_native { + let new_amount = native_account_info + .lamports() + .checked_sub(rent_exempt_reserve) + .ok_or(TokenError::Overflow)?; + if new_amount < native_account.amount { + return Err(TokenError::InvalidState.into()); + } + native_account.amount = new_amount; + } else { + return Err(TokenError::NonNativeNotSupported.into()); + } + + Account::pack(native_account, &mut native_account_info.data.borrow_mut())?; + 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, + } => { + msg!("Instruction: InitializeMint"); + Self::process_initialize_mint(accounts, decimals, mint_authority, freeze_authority) + } + TokenInstruction::InitializeMint2 { + decimals, + mint_authority, + freeze_authority, + } => { + msg!("Instruction: InitializeMint2"); + Self::process_initialize_mint2(accounts, decimals, mint_authority, freeze_authority) + } + TokenInstruction::InitializeAccount => { + msg!("Instruction: InitializeAccount"); + Self::process_initialize_account(accounts) + } + TokenInstruction::InitializeAccount2 { owner } => { + msg!("Instruction: InitializeAccount2"); + Self::process_initialize_account2(accounts, owner) + } + TokenInstruction::InitializeAccount3 { owner } => { + msg!("Instruction: InitializeAccount3"); + Self::process_initialize_account3(accounts, owner) + } + TokenInstruction::InitializeMultisig { m } => { + msg!("Instruction: InitializeMultisig"); + Self::process_initialize_multisig(accounts, m) + } + TokenInstruction::InitializeMultisig2 { m } => { + msg!("Instruction: InitializeMultisig2"); + Self::process_initialize_multisig2(accounts, m) + } + TokenInstruction::Transfer { amount } => { + msg!("Instruction: Transfer"); + Self::process_transfer(program_id, accounts, amount, None) + } + TokenInstruction::Approve { amount } => { + msg!("Instruction: Approve"); + Self::process_approve(program_id, accounts, amount, None) + } + TokenInstruction::Revoke => { + msg!("Instruction: Revoke"); + Self::process_revoke(program_id, accounts) + } + TokenInstruction::SetAuthority { + authority_type, + new_authority, + } => { + msg!("Instruction: SetAuthority"); + Self::process_set_authority(program_id, accounts, authority_type, new_authority) + } + TokenInstruction::MintTo { amount } => { + msg!("Instruction: MintTo"); + Self::process_mint_to(program_id, accounts, amount, None) + } + TokenInstruction::Burn { amount } => { + msg!("Instruction: Burn"); + Self::process_burn(program_id, accounts, amount, None) + } + TokenInstruction::CloseAccount => { + msg!("Instruction: CloseAccount"); + Self::process_close_account(program_id, accounts) + } + TokenInstruction::FreezeAccount => { + msg!("Instruction: FreezeAccount"); + Self::process_toggle_freeze_account(program_id, accounts, true) + } + TokenInstruction::ThawAccount => { + msg!("Instruction: ThawAccount"); + Self::process_toggle_freeze_account(program_id, accounts, false) + } + TokenInstruction::TransferChecked { amount, decimals } => { + msg!("Instruction: TransferChecked"); + Self::process_transfer(program_id, accounts, amount, Some(decimals)) + } + TokenInstruction::ApproveChecked { amount, decimals } => { + msg!("Instruction: ApproveChecked"); + Self::process_approve(program_id, accounts, amount, Some(decimals)) + } + TokenInstruction::MintToChecked { amount, decimals } => { + msg!("Instruction: MintToChecked"); + Self::process_mint_to(program_id, accounts, amount, Some(decimals)) + } + TokenInstruction::BurnChecked { amount, decimals } => { + msg!("Instruction: BurnChecked"); + Self::process_burn(program_id, accounts, amount, Some(decimals)) + } + TokenInstruction::SyncNative => { + msg!("Instruction: SyncNative"); + Self::process_sync_native(program_id, accounts) + } + } + } + + /// 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() == Multisig::get_packed_len() + { + let multisig = Multisig::unpack(&owner_account_info.data.borrow())?; + let mut num_signers = 0; + let mut matched = [false; MAX_SIGNERS]; + for signer in signers.iter() { + for (position, key) in multisig.signers[0..multisig.n as usize].iter().enumerate() { + if key == signer.key && !matched[position] { + if !signer.is_signer { + return Err(ProgramError::MissingRequiredSignature); + } + matched[position] = true; + num_signers += 1; + } + } + } + if num_signers < multisig.m { + return Err(ProgramError::MissingRequiredSignature); + } + return Ok(()); + } else if !owner_account_info.is_signer { + return Err(ProgramError::MissingRequiredSignature); + } + Ok(()) + } +} + +impl PrintProgramError for TokenError { + fn print(&self) + where + E: 'static + std::error::Error + DecodeError + PrintProgramError + FromPrimitive, + { + match self { + TokenError::NotRentExempt => msg!("Error: Lamport balance below rent-exempt threshold"), + TokenError::InsufficientFunds => msg!("Error: insufficient funds"), + TokenError::InvalidMint => msg!("Error: Invalid Mint"), + TokenError::MintMismatch => msg!("Error: Account not associated with this Mint"), + TokenError::OwnerMismatch => msg!("Error: owner does not match"), + TokenError::FixedSupply => msg!("Error: the total supply of this token is fixed"), + TokenError::AlreadyInUse => msg!("Error: account or token already in use"), + TokenError::InvalidNumberOfProvidedSigners => { + msg!("Error: Invalid number of provided signers") + } + TokenError::InvalidNumberOfRequiredSigners => { + msg!("Error: Invalid number of required signers") + } + TokenError::UninitializedState => msg!("Error: State is uninitialized"), + TokenError::NativeNotSupported => { + msg!("Error: Instruction does not support native tokens") + } + TokenError::NonNativeHasBalance => { + msg!("Error: Non-native account can only be closed if its balance is zero") + } + TokenError::InvalidInstruction => msg!("Error: Invalid instruction"), + TokenError::InvalidState => msg!("Error: Invalid account state for operation"), + TokenError::Overflow => msg!("Error: Operation overflowed"), + TokenError::AuthorityTypeNotSupported => { + msg!("Error: Account does not support specified authority type") + } + TokenError::MintCannotFreeze => msg!("Error: This token mint cannot freeze accounts"), + TokenError::AccountFrozen => msg!("Error: Account is frozen"), + TokenError::MintDecimalsMismatch => { + msg!("Error: decimals different from the Mint decimals") + } + TokenError::NonNativeNotSupported => { + msg!("Error: Instruction does not support non-native tokens") + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::instruction::*; + use solana_program::{ + account_info::IntoAccountInfo, clock::Epoch, instruction::Instruction, program_error, + sysvar::rent, + }; + use solana_sdk::account::{ + create_account_for_test, create_is_signer_account_infos, Account as SolanaAccount, + }; + + struct SyscallStubs {} + impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs { + fn sol_log(&self, _message: &str) {} + + fn sol_invoke_signed( + &self, + _instruction: &Instruction, + _account_infos: &[AccountInfo], + _signers_seeds: &[&[&[u8]]], + ) -> ProgramResult { + Err(ProgramError::Custom(42)) // Not supported + } + + fn sol_get_clock_sysvar(&self, _var_addr: *mut u8) -> u64 { + program_error::UNSUPPORTED_SYSVAR + } + + fn sol_get_epoch_schedule_sysvar(&self, _var_addr: *mut u8) -> u64 { + program_error::UNSUPPORTED_SYSVAR + } + + #[allow(deprecated)] + fn sol_get_fees_sysvar(&self, _var_addr: *mut u8) -> u64 { + program_error::UNSUPPORTED_SYSVAR + } + + fn sol_get_rent_sysvar(&self, var_addr: *mut u8) -> u64 { + unsafe { + *(var_addr as *mut _ as *mut Rent) = Rent::default(); + } + solana_program::entrypoint::SUCCESS + } + } + + fn do_process_instruction( + instruction: Instruction, + accounts: Vec<&mut SolanaAccount>, + ) -> ProgramResult { + { + use std::sync::Once; + static ONCE: Once = Once::new(); + + ONCE.call_once(|| { + solana_sdk::program_stubs::set_syscall_stubs(Box::new(SyscallStubs {})); + }); + } + + let mut meta = instruction + .accounts + .iter() + .zip(accounts) + .map(|(account_meta, account)| (&account_meta.pubkey, account_meta.is_signer, account)) + .collect::>(); + + let account_infos = create_is_signer_account_infos(&mut meta); + Processor::process(&instruction.program_id, &account_infos, &instruction.data) + } + + fn do_process_instruction_dups( + instruction: Instruction, + account_infos: Vec, + ) -> ProgramResult { + Processor::process(&instruction.program_id, &account_infos, &instruction.data) + } + + fn return_token_error_as_program_error() -> ProgramError { + TokenError::MintMismatch.into() + } + + fn rent_sysvar() -> SolanaAccount { + create_account_for_test(&Rent::default()) + } + + fn mint_minimum_balance() -> u64 { + Rent::default().minimum_balance(Mint::get_packed_len()) + } + + fn account_minimum_balance() -> u64 { + Rent::default().minimum_balance(Account::get_packed_len()) + } + + fn multisig_minimum_balance() -> u64 { + Rent::default().minimum_balance(Multisig::get_packed_len()) + } + + #[test] + fn test_print_error() { + let error = return_token_error_as_program_error(); + error.print::(); + } + + #[test] + #[should_panic(expected = "Custom(3)")] + fn test_error_unwrap() { + Err::<(), ProgramError>(return_token_error_as_program_error()).unwrap(); + } + + #[test] + fn test_unique_account_sizes() { + assert_ne!(Mint::get_packed_len(), 0); + assert_ne!(Mint::get_packed_len(), Account::get_packed_len()); + assert_ne!(Mint::get_packed_len(), Multisig::get_packed_len()); + assert_ne!(Account::get_packed_len(), 0); + assert_ne!(Account::get_packed_len(), Multisig::get_packed_len()); + assert_ne!(Multisig::get_packed_len(), 0); + } + + #[test] + fn test_pack_unpack() { + // Mint + let check = Mint { + mint_authority: COption::Some(Pubkey::new(&[1; 32])), + supply: 42, + decimals: 7, + is_initialized: true, + freeze_authority: COption::Some(Pubkey::new(&[2; 32])), + }; + let mut packed = vec![0; Mint::get_packed_len() + 1]; + assert_eq!( + Err(ProgramError::InvalidAccountData), + Mint::pack(check, &mut packed) + ); + let mut packed = vec![0; Mint::get_packed_len() - 1]; + assert_eq!( + Err(ProgramError::InvalidAccountData), + Mint::pack(check, &mut packed) + ); + let mut packed = vec![0; Mint::get_packed_len()]; + Mint::pack(check, &mut packed).unwrap(); + let expect = vec![ + 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 42, 0, 0, 0, 0, 0, 0, 0, 7, 1, 1, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + ]; + assert_eq!(packed, expect); + let unpacked = Mint::unpack(&packed).unwrap(); + assert_eq!(unpacked, check); + + // Account + let check = Account { + mint: Pubkey::new(&[1; 32]), + owner: Pubkey::new(&[2; 32]), + amount: 3, + delegate: COption::Some(Pubkey::new(&[4; 32])), + state: AccountState::Frozen, + is_native: COption::Some(5), + delegated_amount: 6, + close_authority: COption::Some(Pubkey::new(&[7; 32])), + }; + let mut packed = vec![0; Account::get_packed_len() + 1]; + assert_eq!( + Err(ProgramError::InvalidAccountData), + Account::pack(check, &mut packed) + ); + let mut packed = vec![0; Account::get_packed_len() - 1]; + assert_eq!( + Err(ProgramError::InvalidAccountData), + Account::pack(check, &mut packed) + ); + let mut packed = vec![0; Account::get_packed_len()]; + Account::pack(check, &mut packed).unwrap(); + let expect = vec![ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 1, 0, 0, 0, 5, 0, 0, + 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + ]; + assert_eq!(packed, expect); + let unpacked = Account::unpack(&packed).unwrap(); + assert_eq!(unpacked, check); + + // Multisig + let check = Multisig { + m: 1, + n: 2, + is_initialized: true, + signers: [Pubkey::new(&[3; 32]); MAX_SIGNERS], + }; + let mut packed = vec![0; Multisig::get_packed_len() + 1]; + assert_eq!( + Err(ProgramError::InvalidAccountData), + Multisig::pack(check, &mut packed) + ); + let mut packed = vec![0; Multisig::get_packed_len() - 1]; + assert_eq!( + Err(ProgramError::InvalidAccountData), + Multisig::pack(check, &mut packed) + ); + let mut packed = vec![0; Multisig::get_packed_len()]; + Multisig::pack(check, &mut packed).unwrap(); + let expect = vec![ + 1, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, + ]; + assert_eq!(packed, expect); + let unpacked = Multisig::unpack(&packed).unwrap(); + assert_eq!(unpacked, check); + } + + #[test] + fn test_initialize_mint() { + let program_id = crate::id(); + let owner_key = Pubkey::new_unique(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = SolanaAccount::new(42, Mint::get_packed_len(), &program_id); + let mint2_key = Pubkey::new_unique(); + let mut mint2_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mut rent_sysvar = rent_sysvar(); + + // mint is not rent exempt + assert_eq!( + Err(TokenError::NotRentExempt.into()), + do_process_instruction( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar] + ) + ); + + mint_account.lamports = mint_minimum_balance(); + + // create new mint + do_process_instruction( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .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, &mut rent_sysvar] + ) + ); + + // 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, &mut rent_sysvar], + ) + .unwrap(); + let mint = Mint::unpack_unchecked(&mint2_account.data).unwrap(); + assert_eq!(mint.freeze_authority, COption::Some(owner_key)); + } + + #[test] + fn test_initialize_mint2() { + let program_id = crate::id(); + let owner_key = Pubkey::new_unique(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = SolanaAccount::new(42, Mint::get_packed_len(), &program_id); + let mint2_key = Pubkey::new_unique(); + let mut mint2_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + + // mint is not rent exempt + assert_eq!( + Err(TokenError::NotRentExempt.into()), + do_process_instruction( + initialize_mint2(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account] + ) + ); + + mint_account.lamports = mint_minimum_balance(); + + // create new mint + do_process_instruction( + initialize_mint2(&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_mint2(&program_id, &mint_key, &owner_key, None, 2,).unwrap(), + vec![&mut mint_account] + ) + ); + + // create another mint that can freeze + do_process_instruction( + initialize_mint2(&program_id, &mint2_key, &owner_key, Some(&owner_key), 2).unwrap(), + vec![&mut mint2_account], + ) + .unwrap(); + let mint = Mint::unpack_unchecked(&mint2_account.data).unwrap(); + assert_eq!(mint.freeze_authority, COption::Some(owner_key)); + } + + #[test] + fn test_initialize_mint_account() { + let program_id = crate::id(); + let account_key = Pubkey::new_unique(); + let mut account_account = SolanaAccount::new(42, Account::get_packed_len(), &program_id); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mut rent_sysvar = rent_sysvar(); + + // account is not rent exempt + assert_eq!( + Err(TokenError::NotRentExempt.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, + &mut rent_sysvar + ], + ) + ); + + account_account.lamports = account_minimum_balance(); + + // mint is not valid (not initialized) + assert_eq!( + Err(TokenError::InvalidMint.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, + &mut rent_sysvar + ], + ) + ); + + // create mint + do_process_instruction( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + // 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, + &mut rent_sysvar, + ], + ) + .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, + &mut rent_sysvar + ], + ) + ); + } + + #[test] + fn test_transfer_dups() { + let program_id = crate::id(); + let account1_key = Pubkey::new_unique(); + let mut account1_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let mut account1_info: AccountInfo = (&account1_key, true, &mut account1_account).into(); + let account2_key = Pubkey::new_unique(); + let mut account2_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let mut account2_info: AccountInfo = (&account2_key, false, &mut account2_account).into(); + let account3_key = Pubkey::new_unique(); + let mut account3_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account3_info: AccountInfo = (&account3_key, false, &mut account3_account).into(); + let account4_key = Pubkey::new_unique(); + let mut account4_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account4_info: AccountInfo = (&account4_key, true, &mut account4_account).into(); + let multisig_key = Pubkey::new_unique(); + let mut multisig_account = SolanaAccount::new( + multisig_minimum_balance(), + Multisig::get_packed_len(), + &program_id, + ); + let multisig_info: AccountInfo = (&multisig_key, true, &mut multisig_account).into(); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let owner_info: AccountInfo = (&owner_key, true, &mut owner_account).into(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mint_info: AccountInfo = (&mint_key, false, &mut mint_account).into(); + let rent_key = rent::id(); + let mut rent_sysvar = rent_sysvar(); + let rent_info: AccountInfo = (&rent_key, false, &mut rent_sysvar).into(); + + // create mint + do_process_instruction_dups( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![mint_info.clone(), rent_info.clone()], + ) + .unwrap(); + + // create account + do_process_instruction_dups( + initialize_account(&program_id, &account1_key, &mint_key, &account1_key).unwrap(), + vec![ + account1_info.clone(), + mint_info.clone(), + account1_info.clone(), + rent_info.clone(), + ], + ) + .unwrap(); + + // create another account + do_process_instruction_dups( + initialize_account(&program_id, &account2_key, &mint_key, &owner_key).unwrap(), + vec![ + account2_info.clone(), + mint_info.clone(), + owner_info.clone(), + rent_info.clone(), + ], + ) + .unwrap(); + + // mint to account + do_process_instruction_dups( + mint_to(&program_id, &mint_key, &account1_key, &owner_key, &[], 1000).unwrap(), + vec![mint_info.clone(), account1_info.clone(), owner_info.clone()], + ) + .unwrap(); + + // source-owner transfer + do_process_instruction_dups( + transfer( + &program_id, + &account1_key, + &account2_key, + &account1_key, + &[], + 500, + ) + .unwrap(), + vec![ + account1_info.clone(), + account2_info.clone(), + account1_info.clone(), + ], + ) + .unwrap(); + + // source-owner TransferChecked + do_process_instruction_dups( + transfer_checked( + &program_id, + &account1_key, + &mint_key, + &account2_key, + &account1_key, + &[], + 500, + 2, + ) + .unwrap(), + vec![ + account1_info.clone(), + mint_info.clone(), + account2_info.clone(), + account1_info.clone(), + ], + ) + .unwrap(); + + // source-delegate transfer + let mut account = Account::unpack_unchecked(&account1_info.data.borrow()).unwrap(); + account.amount = 1000; + account.delegated_amount = 1000; + account.delegate = COption::Some(account1_key); + account.owner = owner_key; + Account::pack(account, &mut account1_info.data.borrow_mut()).unwrap(); + + do_process_instruction_dups( + transfer( + &program_id, + &account1_key, + &account2_key, + &account1_key, + &[], + 500, + ) + .unwrap(), + vec![ + account1_info.clone(), + account2_info.clone(), + account1_info.clone(), + ], + ) + .unwrap(); + + // source-delegate TransferChecked + do_process_instruction_dups( + transfer_checked( + &program_id, + &account1_key, + &mint_key, + &account2_key, + &account1_key, + &[], + 500, + 2, + ) + .unwrap(), + vec![ + account1_info.clone(), + mint_info.clone(), + account2_info.clone(), + account1_info.clone(), + ], + ) + .unwrap(); + + // test destination-owner transfer + do_process_instruction_dups( + initialize_account(&program_id, &account3_key, &mint_key, &account2_key).unwrap(), + vec![ + account3_info.clone(), + mint_info.clone(), + account2_info.clone(), + rent_info.clone(), + ], + ) + .unwrap(); + do_process_instruction_dups( + mint_to(&program_id, &mint_key, &account3_key, &owner_key, &[], 1000).unwrap(), + vec![mint_info.clone(), account3_info.clone(), owner_info.clone()], + ) + .unwrap(); + + account1_info.is_signer = false; + account2_info.is_signer = true; + do_process_instruction_dups( + transfer( + &program_id, + &account3_key, + &account2_key, + &account2_key, + &[], + 500, + ) + .unwrap(), + vec![ + account3_info.clone(), + account2_info.clone(), + account2_info.clone(), + ], + ) + .unwrap(); + + // destination-owner TransferChecked + do_process_instruction_dups( + transfer_checked( + &program_id, + &account3_key, + &mint_key, + &account2_key, + &account2_key, + &[], + 500, + 2, + ) + .unwrap(), + vec![ + account3_info.clone(), + mint_info.clone(), + account2_info.clone(), + account2_info.clone(), + ], + ) + .unwrap(); + + // test source-multisig signer + do_process_instruction_dups( + initialize_multisig(&program_id, &multisig_key, &[&account4_key], 1).unwrap(), + vec![ + multisig_info.clone(), + rent_info.clone(), + account4_info.clone(), + ], + ) + .unwrap(); + + do_process_instruction_dups( + initialize_account(&program_id, &account4_key, &mint_key, &multisig_key).unwrap(), + vec![ + account4_info.clone(), + mint_info.clone(), + multisig_info.clone(), + rent_info.clone(), + ], + ) + .unwrap(); + + do_process_instruction_dups( + mint_to(&program_id, &mint_key, &account4_key, &owner_key, &[], 1000).unwrap(), + vec![mint_info.clone(), account4_info.clone(), owner_info.clone()], + ) + .unwrap(); + + // source-multisig-signer transfer + do_process_instruction_dups( + transfer( + &program_id, + &account4_key, + &account2_key, + &multisig_key, + &[&account4_key], + 500, + ) + .unwrap(), + vec![ + account4_info.clone(), + account2_info.clone(), + multisig_info.clone(), + account4_info.clone(), + ], + ) + .unwrap(); + + // source-multisig-signer TransferChecked + do_process_instruction_dups( + transfer_checked( + &program_id, + &account4_key, + &mint_key, + &account2_key, + &multisig_key, + &[&account4_key], + 500, + 2, + ) + .unwrap(), + vec![ + account4_info.clone(), + mint_info.clone(), + account2_info.clone(), + multisig_info.clone(), + account4_info.clone(), + ], + ) + .unwrap(); + } + + #[test] + fn test_transfer() { + let program_id = crate::id(); + let account_key = Pubkey::new_unique(); + let mut account_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account2_key = Pubkey::new_unique(); + let mut account2_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account3_key = Pubkey::new_unique(); + let mut account3_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let delegate_key = Pubkey::new_unique(); + let mut delegate_account = SolanaAccount::default(); + let mismatch_key = Pubkey::new_unique(); + let mut mismatch_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let owner2_key = Pubkey::new_unique(); + let mut owner2_account = SolanaAccount::default(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mint2_key = Pubkey::new_unique(); + let mut rent_sysvar = rent_sysvar(); + + // create mint + do_process_instruction( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + // 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, + &mut rent_sysvar, + ], + ) + .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, + &mut rent_sysvar, + ], + ) + .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, + &mut rent_sysvar, + ], + ) + .unwrap(); + + // create mismatch account + do_process_instruction( + initialize_account(&program_id, &mismatch_key, &mint_key, &owner_key).unwrap(), + vec![ + &mut mismatch_account, + &mut mint_account, + &mut owner_account, + &mut rent_sysvar, + ], + ) + .unwrap(); + let mut account = Account::unpack_unchecked(&mismatch_account.data).unwrap(); + account.mint = mint2_key; + Account::pack(account, &mut mismatch_account.data).unwrap(); + + // mint to account + 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(); + + // incorrect decimals + assert_eq!( + Err(TokenError::MintDecimalsMismatch.into()), + do_process_instruction( + transfer_checked( + &program_id, + &account2_key, + &mint_key, + &account_key, + &owner_key, + &[], + 1, + 10 // <-- incorrect decimals + ) + .unwrap(), + vec![ + &mut account2_account, + &mut mint_account, + &mut account_account, + &mut owner_account, + ], + ) + ); + + // incorrect mint + assert_eq!( + Err(TokenError::MintMismatch.into()), + do_process_instruction( + transfer_checked( + &program_id, + &account2_key, + &account3_key, // <-- incorrect mint + &account_key, + &owner_key, + &[], + 1, + 2 + ) + .unwrap(), + vec![ + &mut account2_account, + &mut account3_account, // <-- incorrect mint + &mut account_account, + &mut owner_account, + ], + ) + ); + // transfer rest with explicit decimals + do_process_instruction( + transfer_checked( + &program_id, + &account2_key, + &mint_key, + &account_key, + &owner_key, + &[], + 500, + 2, + ) + .unwrap(), + vec![ + &mut account2_account, + &mut mint_account, + &mut account_account, + &mut owner_account, + ], + ) + .unwrap(); + + // 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_self_transfer() { + let program_id = crate::id(); + let account_key = Pubkey::new_unique(); + let mut account_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account2_key = Pubkey::new_unique(); + let mut account2_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account3_key = Pubkey::new_unique(); + let mut account3_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let delegate_key = Pubkey::new_unique(); + let mut delegate_account = SolanaAccount::default(); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let owner2_key = Pubkey::new_unique(); + let mut owner2_account = SolanaAccount::default(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mut rent_sysvar = rent_sysvar(); + + // create mint + do_process_instruction( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + // 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, + &mut rent_sysvar, + ], + ) + .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, + &mut rent_sysvar, + ], + ) + .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, + &mut rent_sysvar, + ], + ) + .unwrap(); + + // mint to account + 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(); + + let account_info = (&account_key, false, &mut account_account).into_account_info(); + let account3_info = (&account3_key, false, &mut account3_account).into_account_info(); + let delegate_info = (&delegate_key, true, &mut delegate_account).into_account_info(); + let owner_info = (&owner_key, true, &mut owner_account).into_account_info(); + let owner2_info = (&owner2_key, true, &mut owner2_account).into_account_info(); + let mint_info = (&mint_key, false, &mut mint_account).into_account_info(); + + // transfer + let instruction = transfer( + &program_id, + account_info.key, + account_info.key, + owner_info.key, + &[], + 1000, + ) + .unwrap(); + assert_eq!( + Ok(()), + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + account_info.clone(), + owner_info.clone(), + ], + &instruction.data, + ) + ); + // no balance change... + let account = Account::unpack_unchecked(&account_info.try_borrow_data().unwrap()).unwrap(); + assert_eq!(account.amount, 1000); + + // transfer checked + let instruction = transfer_checked( + &program_id, + account_info.key, + mint_info.key, + account_info.key, + owner_info.key, + &[], + 1000, + 2, + ) + .unwrap(); + assert_eq!( + Ok(()), + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + mint_info.clone(), + account_info.clone(), + owner_info.clone(), + ], + &instruction.data, + ) + ); + // no balance change... + let account = Account::unpack_unchecked(&account_info.try_borrow_data().unwrap()).unwrap(); + assert_eq!(account.amount, 1000); + + // missing signer + let mut owner_no_sign_info = owner_info.clone(); + let mut instruction = transfer( + &program_id, + account_info.key, + account_info.key, + owner_no_sign_info.key, + &[], + 1000, + ) + .unwrap(); + instruction.accounts[2].is_signer = false; + owner_no_sign_info.is_signer = false; + assert_eq!( + Err(ProgramError::MissingRequiredSignature), + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + account_info.clone(), + owner_no_sign_info.clone(), + ], + &instruction.data, + ) + ); + + // missing signer checked + let mut instruction = transfer_checked( + &program_id, + account_info.key, + mint_info.key, + account_info.key, + owner_no_sign_info.key, + &[], + 1000, + 2, + ) + .unwrap(); + instruction.accounts[3].is_signer = false; + assert_eq!( + Err(ProgramError::MissingRequiredSignature), + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + mint_info.clone(), + account_info.clone(), + owner_no_sign_info, + ], + &instruction.data, + ) + ); + + // missing owner + let instruction = transfer( + &program_id, + account_info.key, + account_info.key, + owner2_info.key, + &[], + 1000, + ) + .unwrap(); + assert_eq!( + Err(TokenError::OwnerMismatch.into()), + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + account_info.clone(), + owner2_info.clone(), + ], + &instruction.data, + ) + ); + + // missing owner checked + let instruction = transfer_checked( + &program_id, + account_info.key, + mint_info.key, + account_info.key, + owner2_info.key, + &[], + 1000, + 2, + ) + .unwrap(); + assert_eq!( + Err(TokenError::OwnerMismatch.into()), + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + mint_info.clone(), + account_info.clone(), + owner2_info.clone(), + ], + &instruction.data, + ) + ); + + // insufficient funds + let instruction = transfer( + &program_id, + account_info.key, + account_info.key, + owner_info.key, + &[], + 1001, + ) + .unwrap(); + assert_eq!( + Err(TokenError::InsufficientFunds.into()), + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + account_info.clone(), + owner_info.clone(), + ], + &instruction.data, + ) + ); + + // insufficient funds checked + let instruction = transfer_checked( + &program_id, + account_info.key, + mint_info.key, + account_info.key, + owner_info.key, + &[], + 1001, + 2, + ) + .unwrap(); + assert_eq!( + Err(TokenError::InsufficientFunds.into()), + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + mint_info.clone(), + account_info.clone(), + owner_info.clone(), + ], + &instruction.data, + ) + ); + + // incorrect decimals + let instruction = transfer_checked( + &program_id, + account_info.key, + mint_info.key, + account_info.key, + owner_info.key, + &[], + 1, + 10, // <-- incorrect decimals + ) + .unwrap(); + assert_eq!( + Err(TokenError::MintDecimalsMismatch.into()), + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + mint_info.clone(), + account_info.clone(), + owner_info.clone(), + ], + &instruction.data, + ) + ); + + // incorrect mint + let instruction = transfer_checked( + &program_id, + account_info.key, + account3_info.key, // <-- incorrect mint + account_info.key, + owner_info.key, + &[], + 1, + 2, + ) + .unwrap(); + assert_eq!( + Err(TokenError::MintMismatch.into()), + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + account3_info.clone(), // <-- incorrect mint + account_info.clone(), + owner_info.clone(), + ], + &instruction.data, + ) + ); + + // approve delegate + let instruction = approve( + &program_id, + account_info.key, + delegate_info.key, + owner_info.key, + &[], + 100, + ) + .unwrap(); + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + delegate_info.clone(), + owner_info.clone(), + ], + &instruction.data, + ) + .unwrap(); + + // delegate transfer + let instruction = transfer( + &program_id, + account_info.key, + account_info.key, + delegate_info.key, + &[], + 100, + ) + .unwrap(); + assert_eq!( + Ok(()), + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + account_info.clone(), + delegate_info.clone(), + ], + &instruction.data, + ) + ); + // no balance change... + let account = Account::unpack_unchecked(&account_info.try_borrow_data().unwrap()).unwrap(); + assert_eq!(account.amount, 1000); + assert_eq!(account.delegated_amount, 100); + + // delegate transfer checked + let instruction = transfer_checked( + &program_id, + account_info.key, + mint_info.key, + account_info.key, + delegate_info.key, + &[], + 100, + 2, + ) + .unwrap(); + assert_eq!( + Ok(()), + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + mint_info.clone(), + account_info.clone(), + delegate_info.clone(), + ], + &instruction.data, + ) + ); + // no balance change... + let account = Account::unpack_unchecked(&account_info.try_borrow_data().unwrap()).unwrap(); + assert_eq!(account.amount, 1000); + assert_eq!(account.delegated_amount, 100); + + // delegate insufficient funds + let instruction = transfer( + &program_id, + account_info.key, + account_info.key, + delegate_info.key, + &[], + 101, + ) + .unwrap(); + assert_eq!( + Err(TokenError::InsufficientFunds.into()), + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + account_info.clone(), + delegate_info.clone(), + ], + &instruction.data, + ) + ); + + // delegate insufficient funds checked + let instruction = transfer_checked( + &program_id, + account_info.key, + mint_info.key, + account_info.key, + delegate_info.key, + &[], + 101, + 2, + ) + .unwrap(); + assert_eq!( + Err(TokenError::InsufficientFunds.into()), + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + mint_info.clone(), + account_info.clone(), + delegate_info.clone(), + ], + &instruction.data, + ) + ); + + // owner transfer with delegate assigned + let instruction = transfer( + &program_id, + account_info.key, + account_info.key, + owner_info.key, + &[], + 1000, + ) + .unwrap(); + assert_eq!( + Ok(()), + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + account_info.clone(), + owner_info.clone(), + ], + &instruction.data, + ) + ); + // no balance change... + let account = Account::unpack_unchecked(&account_info.try_borrow_data().unwrap()).unwrap(); + assert_eq!(account.amount, 1000); + + // owner transfer with delegate assigned checked + let instruction = transfer_checked( + &program_id, + account_info.key, + mint_info.key, + account_info.key, + owner_info.key, + &[], + 1000, + 2, + ) + .unwrap(); + assert_eq!( + Ok(()), + Processor::process( + &instruction.program_id, + &[ + account_info.clone(), + mint_info.clone(), + account_info.clone(), + owner_info.clone(), + ], + &instruction.data, + ) + ); + // no balance change... + let account = Account::unpack_unchecked(&account_info.try_borrow_data().unwrap()).unwrap(); + assert_eq!(account.amount, 1000); + } + + #[test] + fn test_mintable_token_with_zero_supply() { + let program_id = crate::id(); + let account_key = Pubkey::new_unique(); + let mut account_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mut rent_sysvar = rent_sysvar(); + + // 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, &mut rent_sysvar], + ) + .unwrap(); + let mint = Mint::unpack_unchecked(&mint_account.data).unwrap(); + assert_eq!( + mint, + Mint { + mint_authority: COption::Some(owner_key), + supply: 0, + decimals, + is_initialized: true, + freeze_authority: COption::None, + } + ); + + // 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, + &mut rent_sysvar, + ], + ) + .unwrap(); + + // 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 _ = Mint::unpack(&mint_account.data).unwrap(); + let account = Account::unpack_unchecked(&account_account.data).unwrap(); + assert_eq!(account.amount, 42); + + // mint to 2, with incorrect decimals + assert_eq!( + Err(TokenError::MintDecimalsMismatch.into()), + do_process_instruction( + mint_to_checked( + &program_id, + &mint_key, + &account_key, + &owner_key, + &[], + 42, + decimals + 1 + ) + .unwrap(), + vec![&mut mint_account, &mut account_account, &mut owner_account], + ) + ); + + let _ = Mint::unpack(&mint_account.data).unwrap(); + let account = Account::unpack_unchecked(&account_account.data).unwrap(); + assert_eq!(account.amount, 42); + + // mint to 2 + do_process_instruction( + mint_to_checked( + &program_id, + &mint_key, + &account_key, + &owner_key, + &[], + 42, + decimals, + ) + .unwrap(), + vec![&mut mint_account, &mut account_account, &mut owner_account], + ) + .unwrap(); + let _ = Mint::unpack(&mint_account.data).unwrap(); + let account = Account::unpack_unchecked(&account_account.data).unwrap(); + assert_eq!(account.amount, 84); + } + + #[test] + fn test_approve_dups() { + let program_id = crate::id(); + let account1_key = Pubkey::new_unique(); + let mut account1_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account1_info: AccountInfo = (&account1_key, true, &mut account1_account).into(); + let account2_key = Pubkey::new_unique(); + let mut account2_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account2_info: AccountInfo = (&account2_key, false, &mut account2_account).into(); + let account3_key = Pubkey::new_unique(); + let mut account3_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account3_info: AccountInfo = (&account3_key, true, &mut account3_account).into(); + let multisig_key = Pubkey::new_unique(); + let mut multisig_account = SolanaAccount::new( + multisig_minimum_balance(), + Multisig::get_packed_len(), + &program_id, + ); + let multisig_info: AccountInfo = (&multisig_key, true, &mut multisig_account).into(); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let owner_info: AccountInfo = (&owner_key, true, &mut owner_account).into(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mint_info: AccountInfo = (&mint_key, false, &mut mint_account).into(); + let rent_key = rent::id(); + let mut rent_sysvar = rent_sysvar(); + let rent_info: AccountInfo = (&rent_key, false, &mut rent_sysvar).into(); + + // create mint + do_process_instruction_dups( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![mint_info.clone(), rent_info.clone()], + ) + .unwrap(); + + // create account + do_process_instruction_dups( + initialize_account(&program_id, &account1_key, &mint_key, &account1_key).unwrap(), + vec![ + account1_info.clone(), + mint_info.clone(), + account1_info.clone(), + rent_info.clone(), + ], + ) + .unwrap(); + + // create another account + do_process_instruction_dups( + initialize_account(&program_id, &account2_key, &mint_key, &owner_key).unwrap(), + vec![ + account2_info.clone(), + mint_info.clone(), + owner_info.clone(), + rent_info.clone(), + ], + ) + .unwrap(); + + // mint to account + do_process_instruction_dups( + mint_to(&program_id, &mint_key, &account1_key, &owner_key, &[], 1000).unwrap(), + vec![mint_info.clone(), account1_info.clone(), owner_info.clone()], + ) + .unwrap(); + + // source-owner approve + do_process_instruction_dups( + approve( + &program_id, + &account1_key, + &account2_key, + &account1_key, + &[], + 500, + ) + .unwrap(), + vec![ + account1_info.clone(), + account2_info.clone(), + account1_info.clone(), + ], + ) + .unwrap(); + + // source-owner approve_checked + do_process_instruction_dups( + approve_checked( + &program_id, + &account1_key, + &mint_key, + &account2_key, + &account1_key, + &[], + 500, + 2, + ) + .unwrap(), + vec![ + account1_info.clone(), + mint_info.clone(), + account2_info.clone(), + account1_info.clone(), + ], + ) + .unwrap(); + + // source-owner revoke + do_process_instruction_dups( + revoke(&program_id, &account1_key, &account1_key, &[]).unwrap(), + vec![account1_info.clone(), account1_info.clone()], + ) + .unwrap(); + + // test source-multisig signer + do_process_instruction_dups( + initialize_multisig(&program_id, &multisig_key, &[&account3_key], 1).unwrap(), + vec![ + multisig_info.clone(), + rent_info.clone(), + account3_info.clone(), + ], + ) + .unwrap(); + + do_process_instruction_dups( + initialize_account(&program_id, &account3_key, &mint_key, &multisig_key).unwrap(), + vec![ + account3_info.clone(), + mint_info.clone(), + multisig_info.clone(), + rent_info.clone(), + ], + ) + .unwrap(); + + do_process_instruction_dups( + mint_to(&program_id, &mint_key, &account3_key, &owner_key, &[], 1000).unwrap(), + vec![mint_info.clone(), account3_info.clone(), owner_info.clone()], + ) + .unwrap(); + + // source-multisig-signer approve + do_process_instruction_dups( + approve( + &program_id, + &account3_key, + &account2_key, + &multisig_key, + &[&account3_key], + 500, + ) + .unwrap(), + vec![ + account3_info.clone(), + account2_info.clone(), + multisig_info.clone(), + account3_info.clone(), + ], + ) + .unwrap(); + + // source-multisig-signer approve_checked + do_process_instruction_dups( + approve_checked( + &program_id, + &account3_key, + &mint_key, + &account2_key, + &multisig_key, + &[&account3_key], + 500, + 2, + ) + .unwrap(), + vec![ + account3_info.clone(), + mint_info.clone(), + account2_info.clone(), + multisig_info.clone(), + account3_info.clone(), + ], + ) + .unwrap(); + + // source-owner multisig-signer + do_process_instruction_dups( + revoke(&program_id, &account3_key, &multisig_key, &[&account3_key]).unwrap(), + vec![ + account3_info.clone(), + multisig_info.clone(), + account3_info.clone(), + ], + ) + .unwrap(); + } + + #[test] + fn test_approve() { + let program_id = crate::id(); + let account_key = Pubkey::new_unique(); + let mut account_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account2_key = Pubkey::new_unique(); + let mut account2_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let delegate_key = Pubkey::new_unique(); + let mut delegate_account = SolanaAccount::default(); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let owner2_key = Pubkey::new_unique(); + let mut owner2_account = SolanaAccount::default(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mut rent_sysvar = rent_sysvar(); + + // create mint + do_process_instruction( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + // 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, + &mut rent_sysvar, + ], + ) + .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, + &mut rent_sysvar, + ], + ) + .unwrap(); + + // mint to account + 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(); + + // approve delegate 2, with incorrect decimals + assert_eq!( + Err(TokenError::MintDecimalsMismatch.into()), + do_process_instruction( + approve_checked( + &program_id, + &account_key, + &mint_key, + &delegate_key, + &owner_key, + &[], + 100, + 0 // <-- incorrect decimals + ) + .unwrap(), + vec![ + &mut account_account, + &mut mint_account, + &mut delegate_account, + &mut owner_account, + ], + ) + ); + + // approve delegate 2, with incorrect mint + assert_eq!( + Err(TokenError::MintMismatch.into()), + do_process_instruction( + approve_checked( + &program_id, + &account_key, + &account2_key, // <-- bad mint + &delegate_key, + &owner_key, + &[], + 100, + 0 + ) + .unwrap(), + vec![ + &mut account_account, + &mut account2_account, // <-- bad mint + &mut delegate_account, + &mut owner_account, + ], + ) + ); + + // approve delegate 2 + do_process_instruction( + approve_checked( + &program_id, + &account_key, + &mint_key, + &delegate_key, + &owner_key, + &[], + 100, + 2, + ) + .unwrap(), + vec![ + &mut account_account, + &mut mint_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_dups() { + let program_id = crate::id(); + let account1_key = Pubkey::new_unique(); + let mut account1_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account1_info: AccountInfo = (&account1_key, true, &mut account1_account).into(); + let owner_key = Pubkey::new_unique(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mint_info: AccountInfo = (&mint_key, true, &mut mint_account).into(); + let rent_key = rent::id(); + let mut rent_sysvar = rent_sysvar(); + let rent_info: AccountInfo = (&rent_key, false, &mut rent_sysvar).into(); + + // create mint + do_process_instruction_dups( + initialize_mint(&program_id, &mint_key, &mint_key, Some(&mint_key), 2).unwrap(), + vec![mint_info.clone(), rent_info.clone()], + ) + .unwrap(); + + // create account + do_process_instruction_dups( + initialize_account(&program_id, &account1_key, &mint_key, &account1_key).unwrap(), + vec![ + account1_info.clone(), + mint_info.clone(), + account1_info.clone(), + rent_info.clone(), + ], + ) + .unwrap(); + + // set mint_authority when currently self + do_process_instruction_dups( + set_authority( + &program_id, + &mint_key, + Some(&owner_key), + AuthorityType::MintTokens, + &mint_key, + &[], + ) + .unwrap(), + vec![mint_info.clone(), mint_info.clone()], + ) + .unwrap(); + + // set freeze_authority when currently self + do_process_instruction_dups( + set_authority( + &program_id, + &mint_key, + Some(&owner_key), + AuthorityType::FreezeAccount, + &mint_key, + &[], + ) + .unwrap(), + vec![mint_info.clone(), mint_info.clone()], + ) + .unwrap(); + + // set account owner when currently self + do_process_instruction_dups( + set_authority( + &program_id, + &account1_key, + Some(&owner_key), + AuthorityType::AccountOwner, + &account1_key, + &[], + ) + .unwrap(), + vec![account1_info.clone(), account1_info.clone()], + ) + .unwrap(); + + // set close_authority when currently self + let mut account = Account::unpack_unchecked(&account1_info.data.borrow()).unwrap(); + account.close_authority = COption::Some(account1_key); + Account::pack(account, &mut account1_info.data.borrow_mut()).unwrap(); + + do_process_instruction_dups( + set_authority( + &program_id, + &account1_key, + Some(&owner_key), + AuthorityType::CloseAccount, + &account1_key, + &[], + ) + .unwrap(), + vec![account1_info.clone(), account1_info.clone()], + ) + .unwrap(); + } + + #[test] + fn test_set_authority() { + let program_id = crate::id(); + let account_key = Pubkey::new_unique(); + let mut account_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account2_key = Pubkey::new_unique(); + let mut account2_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let owner2_key = Pubkey::new_unique(); + let mut owner2_account = SolanaAccount::default(); + let owner3_key = Pubkey::new_unique(); + let mut owner3_account = SolanaAccount::default(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mint2_key = Pubkey::new_unique(); + let mut mint2_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mut rent_sysvar = rent_sysvar(); + + // create new mint with owner + do_process_instruction( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + // 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, &mut rent_sysvar], + ) + .unwrap(); + + // invalid account + assert_eq!( + Err(ProgramError::UninitializedAccount), + do_process_instruction( + set_authority( + &program_id, + &account_key, + Some(&owner2_key), + AuthorityType::AccountOwner, + &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, + &mut rent_sysvar, + ], + ) + .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, + &mut rent_sysvar, + ], + ) + .unwrap(); + + // missing owner + assert_eq!( + Err(TokenError::OwnerMismatch.into()), + do_process_instruction( + set_authority( + &program_id, + &account_key, + Some(&owner_key), + AuthorityType::AccountOwner, + &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::AccountOwner, + &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::AccountOwner, + &owner_key, + &[], + ) + .unwrap(), + vec![&mut account_account, &mut owner_account], + ) + ); + + // set delegate + do_process_instruction( + approve( + &program_id, + &account_key, + &owner2_key, + &owner_key, + &[], + u64::MAX, + ) + .unwrap(), + vec![ + &mut account_account, + &mut owner2_account, + &mut owner_account, + ], + ) + .unwrap(); + let account = Account::unpack_unchecked(&account_account.data).unwrap(); + assert_eq!(account.delegate, COption::Some(owner2_key)); + assert_eq!(account.delegated_amount, u64::MAX); + + // set owner + do_process_instruction( + set_authority( + &program_id, + &account_key, + Some(&owner3_key), + AuthorityType::AccountOwner, + &owner_key, + &[], + ) + .unwrap(), + vec![&mut account_account, &mut owner_account], + ) + .unwrap(); + + // check delegate cleared + let account = Account::unpack_unchecked(&account_account.data).unwrap(); + assert_eq!(account.delegate, COption::None); + assert_eq!(account.delegated_amount, 0); + + // set owner without existing delegate + do_process_instruction( + set_authority( + &program_id, + &account_key, + Some(&owner2_key), + AuthorityType::AccountOwner, + &owner3_key, + &[], + ) + .unwrap(), + vec![&mut account_account, &mut owner3_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(); + + // 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], + ) + ); + + // 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_dups() { + let program_id = crate::id(); + let account1_key = Pubkey::new_unique(); + let mut account1_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account1_info: AccountInfo = (&account1_key, true, &mut account1_account).into(); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let owner_info: AccountInfo = (&owner_key, true, &mut owner_account).into(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mint_info: AccountInfo = (&mint_key, true, &mut mint_account).into(); + let rent_key = rent::id(); + let mut rent_sysvar = rent_sysvar(); + let rent_info: AccountInfo = (&rent_key, false, &mut rent_sysvar).into(); + + // create mint + do_process_instruction_dups( + initialize_mint(&program_id, &mint_key, &mint_key, None, 2).unwrap(), + vec![mint_info.clone(), rent_info.clone()], + ) + .unwrap(); + + // create account + do_process_instruction_dups( + initialize_account(&program_id, &account1_key, &mint_key, &owner_key).unwrap(), + vec![ + account1_info.clone(), + mint_info.clone(), + owner_info.clone(), + rent_info.clone(), + ], + ) + .unwrap(); + + // mint_to when mint_authority is self + do_process_instruction_dups( + mint_to(&program_id, &mint_key, &account1_key, &mint_key, &[], 42).unwrap(), + vec![mint_info.clone(), account1_info.clone(), mint_info.clone()], + ) + .unwrap(); + + // mint_to_checked when mint_authority is self + do_process_instruction_dups( + mint_to_checked(&program_id, &mint_key, &account1_key, &mint_key, &[], 42, 2).unwrap(), + vec![mint_info.clone(), account1_info.clone(), mint_info.clone()], + ) + .unwrap(); + + // mint_to when mint_authority is account owner + let mut mint = Mint::unpack_unchecked(&mint_info.data.borrow()).unwrap(); + mint.mint_authority = COption::Some(account1_key); + Mint::pack(mint, &mut mint_info.data.borrow_mut()).unwrap(); + do_process_instruction_dups( + mint_to( + &program_id, + &mint_key, + &account1_key, + &account1_key, + &[], + 42, + ) + .unwrap(), + vec![ + mint_info.clone(), + account1_info.clone(), + account1_info.clone(), + ], + ) + .unwrap(); + + // mint_to_checked when mint_authority is account owner + do_process_instruction_dups( + mint_to( + &program_id, + &mint_key, + &account1_key, + &account1_key, + &[], + 42, + ) + .unwrap(), + vec![ + mint_info.clone(), + account1_info.clone(), + account1_info.clone(), + ], + ) + .unwrap(); + } + + #[test] + fn test_mint_to() { + let program_id = crate::id(); + let account_key = Pubkey::new_unique(); + let mut account_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account2_key = Pubkey::new_unique(); + let mut account2_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account3_key = Pubkey::new_unique(); + let mut account3_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let mismatch_key = Pubkey::new_unique(); + let mut mismatch_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let owner2_key = Pubkey::new_unique(); + let mut owner2_account = SolanaAccount::default(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mint2_key = Pubkey::new_unique(); + let uninitialized_key = Pubkey::new_unique(); + let mut uninitialized_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let mut rent_sysvar = rent_sysvar(); + + // create new mint with owner + do_process_instruction( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + // 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, + &mut rent_sysvar, + ], + ) + .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, + &mut rent_sysvar, + ], + ) + .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, + &mut rent_sysvar, + ], + ) + .unwrap(); + + // create mismatch account + do_process_instruction( + initialize_account(&program_id, &mismatch_key, &mint_key, &owner_key).unwrap(), + vec![ + &mut mismatch_account, + &mut mint_account, + &mut owner_account, + &mut rent_sysvar, + ], + ) + .unwrap(); + let mut account = Account::unpack_unchecked(&mismatch_account.data).unwrap(); + account.mint = mint2_key; + Account::pack(account, &mut mismatch_account.data).unwrap(); + + // 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 mint = Mint::unpack_unchecked(&mint_account.data).unwrap(); + assert_eq!(mint.supply, 42); + let account = Account::unpack_unchecked(&account_account.data).unwrap(); + assert_eq!(account.amount, 42); + + // mint to another account to test supply accumulation + 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 mint = Mint::unpack_unchecked(&mint_account.data).unwrap(); + assert_eq!(mint.supply, 84); + let account = Account::unpack_unchecked(&account2_account.data).unwrap(); + assert_eq!(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(ProgramError::UninitializedAccount), + 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_dups() { + let program_id = crate::id(); + let account1_key = Pubkey::new_unique(); + let mut account1_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account1_info: AccountInfo = (&account1_key, true, &mut account1_account).into(); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let owner_info: AccountInfo = (&owner_key, true, &mut owner_account).into(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mint_info: AccountInfo = (&mint_key, true, &mut mint_account).into(); + let rent_key = rent::id(); + let mut rent_sysvar = rent_sysvar(); + let rent_info: AccountInfo = (&rent_key, false, &mut rent_sysvar).into(); + + // create mint + do_process_instruction_dups( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![mint_info.clone(), rent_info.clone()], + ) + .unwrap(); + + // create account + do_process_instruction_dups( + initialize_account(&program_id, &account1_key, &mint_key, &account1_key).unwrap(), + vec![ + account1_info.clone(), + mint_info.clone(), + account1_info.clone(), + rent_info.clone(), + ], + ) + .unwrap(); + + // mint to account + do_process_instruction_dups( + mint_to(&program_id, &mint_key, &account1_key, &owner_key, &[], 1000).unwrap(), + vec![mint_info.clone(), account1_info.clone(), owner_info.clone()], + ) + .unwrap(); + + // source-owner burn + do_process_instruction_dups( + burn( + &program_id, + &mint_key, + &account1_key, + &account1_key, + &[], + 500, + ) + .unwrap(), + vec![ + account1_info.clone(), + mint_info.clone(), + account1_info.clone(), + ], + ) + .unwrap(); + + // source-owner burn_checked + do_process_instruction_dups( + burn_checked( + &program_id, + &account1_key, + &mint_key, + &account1_key, + &[], + 500, + 2, + ) + .unwrap(), + vec![ + account1_info.clone(), + mint_info.clone(), + account1_info.clone(), + ], + ) + .unwrap(); + + // mint-owner burn + do_process_instruction_dups( + mint_to(&program_id, &mint_key, &account1_key, &owner_key, &[], 1000).unwrap(), + vec![mint_info.clone(), account1_info.clone(), owner_info.clone()], + ) + .unwrap(); + let mut account = Account::unpack_unchecked(&account1_info.data.borrow()).unwrap(); + account.owner = mint_key; + Account::pack(account, &mut account1_info.data.borrow_mut()).unwrap(); + do_process_instruction_dups( + burn(&program_id, &account1_key, &mint_key, &mint_key, &[], 500).unwrap(), + vec![account1_info.clone(), mint_info.clone(), mint_info.clone()], + ) + .unwrap(); + + // mint-owner burn_checked + do_process_instruction_dups( + burn_checked( + &program_id, + &account1_key, + &mint_key, + &mint_key, + &[], + 500, + 2, + ) + .unwrap(), + vec![account1_info.clone(), mint_info.clone(), mint_info.clone()], + ) + .unwrap(); + + // source-delegate burn + do_process_instruction_dups( + mint_to(&program_id, &mint_key, &account1_key, &owner_key, &[], 1000).unwrap(), + vec![mint_info.clone(), account1_info.clone(), owner_info.clone()], + ) + .unwrap(); + let mut account = Account::unpack_unchecked(&account1_info.data.borrow()).unwrap(); + account.delegated_amount = 1000; + account.delegate = COption::Some(account1_key); + account.owner = owner_key; + Account::pack(account, &mut account1_info.data.borrow_mut()).unwrap(); + do_process_instruction_dups( + burn( + &program_id, + &account1_key, + &mint_key, + &account1_key, + &[], + 500, + ) + .unwrap(), + vec![ + account1_info.clone(), + mint_info.clone(), + account1_info.clone(), + ], + ) + .unwrap(); + + // source-delegate burn_checked + do_process_instruction_dups( + burn_checked( + &program_id, + &account1_key, + &mint_key, + &account1_key, + &[], + 500, + 2, + ) + .unwrap(), + vec![ + account1_info.clone(), + mint_info.clone(), + account1_info.clone(), + ], + ) + .unwrap(); + + // mint-delegate burn + do_process_instruction_dups( + mint_to(&program_id, &mint_key, &account1_key, &owner_key, &[], 1000).unwrap(), + vec![mint_info.clone(), account1_info.clone(), owner_info.clone()], + ) + .unwrap(); + let mut account = Account::unpack_unchecked(&account1_info.data.borrow()).unwrap(); + account.delegated_amount = 1000; + account.delegate = COption::Some(mint_key); + account.owner = owner_key; + Account::pack(account, &mut account1_info.data.borrow_mut()).unwrap(); + do_process_instruction_dups( + burn(&program_id, &account1_key, &mint_key, &mint_key, &[], 500).unwrap(), + vec![account1_info.clone(), mint_info.clone(), mint_info.clone()], + ) + .unwrap(); + + // mint-delegate burn_checked + do_process_instruction_dups( + burn_checked( + &program_id, + &account1_key, + &mint_key, + &mint_key, + &[], + 500, + 2, + ) + .unwrap(), + vec![account1_info.clone(), mint_info.clone(), mint_info.clone()], + ) + .unwrap(); + } + + #[test] + fn test_burn() { + let program_id = crate::id(); + let account_key = Pubkey::new_unique(); + let mut account_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account2_key = Pubkey::new_unique(); + let mut account2_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account3_key = Pubkey::new_unique(); + let mut account3_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let delegate_key = Pubkey::new_unique(); + let mut delegate_account = SolanaAccount::default(); + let mismatch_key = Pubkey::new_unique(); + let mut mismatch_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let owner2_key = Pubkey::new_unique(); + let mut owner2_account = SolanaAccount::default(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mint2_key = Pubkey::new_unique(); + let mut rent_sysvar = rent_sysvar(); + + // create new mint + do_process_instruction( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + // 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, + &mut rent_sysvar, + ], + ) + .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, + &mut rent_sysvar, + ], + ) + .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, + &mut rent_sysvar, + ], + ) + .unwrap(); + + // create mismatch account + do_process_instruction( + initialize_account(&program_id, &mismatch_key, &mint_key, &owner_key).unwrap(), + vec![ + &mut mismatch_account, + &mut mint_account, + &mut owner_account, + &mut rent_sysvar, + ], + ) + .unwrap(); + + // mint to account + 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 to mismatch account and change mint key + do_process_instruction( + mint_to(&program_id, &mint_key, &mismatch_key, &owner_key, &[], 1000).unwrap(), + vec![&mut mint_account, &mut mismatch_account, &mut owner_account], + ) + .unwrap(); + let mut account = Account::unpack_unchecked(&mismatch_account.data).unwrap(); + account.mint = mint2_key; + Account::pack(account, &mut mismatch_account.data).unwrap(); + + // missing signer + let mut instruction = + burn(&program_id, &account_key, &mint_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 mint_account, + &mut delegate_account + ], + ) + ); + + // missing owner + assert_eq!( + Err(TokenError::OwnerMismatch.into()), + do_process_instruction( + burn(&program_id, &account_key, &mint_key, &owner2_key, &[], 42).unwrap(), + vec![&mut account_account, &mut mint_account, &mut owner2_account], + ) + ); + + // mint mismatch + assert_eq!( + Err(TokenError::MintMismatch.into()), + do_process_instruction( + burn(&program_id, &mismatch_key, &mint_key, &owner_key, &[], 42).unwrap(), + vec![&mut mismatch_account, &mut mint_account, &mut owner_account], + ) + ); + + // burn + do_process_instruction( + burn(&program_id, &account_key, &mint_key, &owner_key, &[], 21).unwrap(), + vec![&mut account_account, &mut mint_account, &mut owner_account], + ) + .unwrap(); + + // burn_checked, with incorrect decimals + assert_eq!( + Err(TokenError::MintDecimalsMismatch.into()), + do_process_instruction( + burn_checked(&program_id, &account_key, &mint_key, &owner_key, &[], 21, 3).unwrap(), + vec![&mut account_account, &mut mint_account, &mut owner_account], + ) + ); + + // burn_checked + do_process_instruction( + burn_checked(&program_id, &account_key, &mint_key, &owner_key, &[], 21, 2).unwrap(), + vec![&mut account_account, &mut mint_account, &mut owner_account], + ) + .unwrap(); + + let mint = Mint::unpack_unchecked(&mint_account.data).unwrap(); + assert_eq!(mint.supply, 2000 - 42); + let account = Account::unpack_unchecked(&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, + &mint_key, + &owner_key, + &[], + 100_000_000 + ) + .unwrap(), + vec![&mut account_account, &mut mint_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, + &mint_key, + &owner_key, + &[], + 100_000_000 + ) + .unwrap(), + vec![&mut account_account, &mut mint_account, &mut owner_account], + ) + ); + + // burn via delegate + do_process_instruction( + burn(&program_id, &account_key, &mint_key, &delegate_key, &[], 84).unwrap(), + vec![ + &mut account_account, + &mut mint_account, + &mut delegate_account, + ], + ) + .unwrap(); + + // match + let mint = Mint::unpack_unchecked(&mint_account.data).unwrap(); + assert_eq!(mint.supply, 2000 - 42 - 84); + let account = Account::unpack_unchecked(&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, + &mint_key, + &delegate_key, + &[], + 100 + ) + .unwrap(), + vec![ + &mut account_account, + &mut mint_account, + &mut delegate_account + ], + ) + ); + } + + #[test] + fn test_multisig() { + let program_id = crate::id(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let account_key = Pubkey::new_unique(); + let mut account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account2_key = Pubkey::new_unique(); + let mut account2_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let multisig_key = Pubkey::new_unique(); + let mut multisig_account = SolanaAccount::new(42, Multisig::get_packed_len(), &program_id); + let multisig_delegate_key = Pubkey::new_unique(); + let mut multisig_delegate_account = SolanaAccount::new( + multisig_minimum_balance(), + Multisig::get_packed_len(), + &program_id, + ); + let signer_keys = vec![Pubkey::new_unique(); MAX_SIGNERS]; + let signer_key_refs: Vec<&Pubkey> = signer_keys.iter().collect(); + let mut signer_accounts = vec![SolanaAccount::new(0, 0, &program_id); MAX_SIGNERS]; + let mut rent_sysvar = rent_sysvar(); + + // multisig is not rent exempt + let account_info_iter = &mut signer_accounts.iter_mut(); + assert_eq!( + Err(TokenError::NotRentExempt.into()), + do_process_instruction( + initialize_multisig(&program_id, &multisig_key, &[&signer_keys[0]], 1).unwrap(), + vec![ + &mut multisig_account, + &mut rent_sysvar, + &mut account_info_iter.next().unwrap(), + ], + ) + ); + + multisig_account.lamports = multisig_minimum_balance(); + let mut multisig_account2 = multisig_account.clone(); + + // 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 rent_sysvar, + &mut account_info_iter.next().unwrap(), + ], + ) + .unwrap(); + + // single signer using `initialize_multisig2` + let account_info_iter = &mut signer_accounts.iter_mut(); + do_process_instruction( + initialize_multisig2(&program_id, &multisig_key, &[&signer_keys[0]], 1).unwrap(), + vec![ + &mut multisig_account2, + &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 rent_sysvar, + &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 new mint with multisig owner + do_process_instruction( + initialize_mint(&program_id, &mint_key, &multisig_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .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, + &mut rent_sysvar, + ], + ) + .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, + &mut rent_sysvar, + ], + ) + .unwrap(); + + // mint to account + 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, + &mint_key, + &multisig_key, + &[&signer_keys[0]], + 42, + ) + .unwrap(), + vec![ + &mut account, + &mut mint_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, + &mint_key, + &multisig_delegate_key, + &signer_key_refs, + 42, + ) + .unwrap(), + vec![ + &mut account, + &mut mint_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::new_unique(); + let mut account3_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let mint2_key = Pubkey::new_unique(); + let mut mint2_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + do_process_instruction( + initialize_mint( + &program_id, + &mint2_key, + &multisig_key, + Some(&multisig_key), + 2, + ) + .unwrap(), + vec![&mut mint2_account, &mut rent_sysvar], + ) + .unwrap(); + do_process_instruction( + initialize_account(&program_id, &account3_key, &mint2_key, &owner_key).unwrap(), + vec![ + &mut account3_account, + &mut mint2_account, + &mut owner_account, + &mut rent_sysvar, + ], + ) + .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::AccountOwner, + &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 = crate::id(); + let owner_key = Pubkey::new_unique(); + let mut signer_keys = [Pubkey::default(); MAX_SIGNERS]; + for signer_key in signer_keys.iter_mut().take(MAX_SIGNERS) { + *signer_key = Pubkey::new_unique(); + } + 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; Multisig::get_packed_len()]; + let mut multisig = Multisig::unpack_unchecked(&data).unwrap(); + multisig.m = MAX_SIGNERS as u8; + multisig.n = MAX_SIGNERS as u8; + multisig.signers = signer_keys; + multisig.is_initialized = true; + Multisig::pack(multisig, &mut data).unwrap(); + 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 multisig = + Multisig::unpack_unchecked(&owner_account_info.data.borrow()).unwrap(); + multisig.m = 1; + Multisig::pack(multisig, &mut owner_account_info.data.borrow_mut()).unwrap(); + } + Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers).unwrap(); + + // 2:1 + { + let mut multisig = + Multisig::unpack_unchecked(&owner_account_info.data.borrow()).unwrap(); + multisig.m = 2; + multisig.n = 1; + Multisig::pack(multisig, &mut owner_account_info.data.borrow_mut()).unwrap(); + } + assert_eq!( + Err(ProgramError::MissingRequiredSignature), + Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers) + ); + + // 0:11 + { + let mut multisig = + Multisig::unpack_unchecked(&owner_account_info.data.borrow()).unwrap(); + multisig.m = 0; + multisig.n = 11; + Multisig::pack(multisig, &mut owner_account_info.data.borrow_mut()).unwrap(); + } + Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers).unwrap(); + + // 2:11 but 0 provided + { + let mut multisig = + Multisig::unpack_unchecked(&owner_account_info.data.borrow()).unwrap(); + multisig.m = 2; + multisig.n = 11; + Multisig::pack(multisig, &mut owner_account_info.data.borrow_mut()).unwrap(); + } + assert_eq!( + Err(ProgramError::MissingRequiredSignature), + Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &[]) + ); + // 2:11 but 1 provided + { + let mut multisig = + Multisig::unpack_unchecked(&owner_account_info.data.borrow()).unwrap(); + multisig.m = 2; + multisig.n = 11; + Multisig::pack(multisig, &mut owner_account_info.data.borrow_mut()).unwrap(); + } + 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 multisig = + Multisig::unpack_unchecked(&owner_account_info.data.borrow()).unwrap(); + multisig.m = 2; + multisig.n = 11; + Multisig::pack(multisig, &mut owner_account_info.data.borrow_mut()).unwrap(); + } + Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers[5..7]) + .unwrap(); + + // 11:11, one is not a signer + { + let mut multisig = + Multisig::unpack_unchecked(&owner_account_info.data.borrow()).unwrap(); + multisig.m = 11; + multisig.n = 11; + Multisig::pack(multisig, &mut owner_account_info.data.borrow_mut()).unwrap(); + } + 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; + + // 11:11, single signer signs multiple times + { + let mut signer_lamports = 0; + let mut signer_data = vec![]; + let signers = vec![ + AccountInfo::new( + &signer_keys[5], + true, + false, + &mut signer_lamports, + &mut signer_data, + &program_id, + false, + Epoch::default(), + ); + MAX_SIGNERS + 1 + ]; + let mut multisig = + Multisig::unpack_unchecked(&owner_account_info.data.borrow()).unwrap(); + multisig.m = 11; + multisig.n = 11; + Multisig::pack(multisig, &mut owner_account_info.data.borrow_mut()).unwrap(); + assert_eq!( + Err(ProgramError::MissingRequiredSignature), + Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers) + ); + } + } + + #[test] + fn test_close_account_dups() { + let program_id = crate::id(); + let account1_key = Pubkey::new_unique(); + let mut account1_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account1_info: AccountInfo = (&account1_key, true, &mut account1_account).into(); + let account2_key = Pubkey::new_unique(); + let mut account2_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account2_info: AccountInfo = (&account2_key, true, &mut account2_account).into(); + let owner_key = Pubkey::new_unique(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mint_info: AccountInfo = (&mint_key, false, &mut mint_account).into(); + let rent_key = rent::id(); + let mut rent_sysvar = rent_sysvar(); + let rent_info: AccountInfo = (&rent_key, false, &mut rent_sysvar).into(); + + // create mint + do_process_instruction_dups( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![mint_info.clone(), rent_info.clone()], + ) + .unwrap(); + + // create account + do_process_instruction_dups( + initialize_account(&program_id, &account1_key, &mint_key, &account1_key).unwrap(), + vec![ + account1_info.clone(), + mint_info.clone(), + account1_info.clone(), + rent_info.clone(), + ], + ) + .unwrap(); + + // source-owner close + do_process_instruction_dups( + close_account( + &program_id, + &account1_key, + &account2_key, + &account1_key, + &[], + ) + .unwrap(), + vec![ + account1_info.clone(), + account2_info.clone(), + account1_info.clone(), + ], + ) + .unwrap(); + + // source-close-authority close + let mut account = Account::unpack_unchecked(&account1_info.data.borrow()).unwrap(); + account.close_authority = COption::Some(account1_key); + account.owner = owner_key; + Account::pack(account, &mut account1_info.data.borrow_mut()).unwrap(); + do_process_instruction_dups( + close_account( + &program_id, + &account1_key, + &account2_key, + &account1_key, + &[], + ) + .unwrap(), + vec![ + account1_info.clone(), + account2_info.clone(), + account1_info.clone(), + ], + ) + .unwrap(); + } + + #[test] + fn test_close_account() { + let program_id = crate::id(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let account_key = Pubkey::new_unique(); + let mut account_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account2_key = Pubkey::new_unique(); + let mut account2_account = SolanaAccount::new( + account_minimum_balance() + 42, + Account::get_packed_len(), + &program_id, + ); + let account3_key = Pubkey::new_unique(); + let mut account3_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let owner2_key = Pubkey::new_unique(); + let mut owner2_account = SolanaAccount::default(); + let mut rent_sysvar = rent_sysvar(); + + // uninitialized + assert_eq!( + Err(ProgramError::UninitializedAccount), + 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_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + do_process_instruction( + initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(), + vec![ + &mut account_account, + &mut mint_account, + &mut owner_account, + &mut rent_sysvar, + ], + ) + .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, + &mut rent_sysvar, + ], + ) + .unwrap(); + let account = Account::unpack_unchecked(&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, + &mut rent_sysvar, + ], + ) + .unwrap(); + let account = Account::unpack_unchecked(&account2_account.data).unwrap(); + assert!(account.is_native()); + assert_eq!(account.amount, 42); + + // 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, account_minimum_balance()); + + // empty account + do_process_instruction( + burn(&program_id, &account_key, &mint_key, &owner_key, &[], 42).unwrap(), + vec![&mut account_account, &mut mint_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(); + assert_eq!(account_account.lamports, 0); + assert_eq!(account3_account.lamports, 2 * account_minimum_balance()); + let account = Account::unpack_unchecked(&account_account.data).unwrap(); + assert_eq!(account.amount, 0); + + // fund and initialize new non-native account to test close authority + let account_key = Pubkey::new_unique(); + let mut account_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let owner2_key = Pubkey::new_unique(); + let mut owner2_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &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, + &mut rent_sysvar, + ], + ) + .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!(account3_account.lamports, 2 * account_minimum_balance() + 2); + let account = Account::unpack_unchecked(&account_account.data).unwrap(); + assert_eq!(account.amount, 0); + + // 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 = Account::unpack_unchecked(&account2_account.data).unwrap(); + assert!(account.is_native()); + assert_eq!(account_account.lamports, 0); + assert_eq!(account.amount, 0); + assert_eq!( + account3_account.lamports, + 3 * account_minimum_balance() + 2 + 42 + ); + } + + #[test] + fn test_native_token() { + let program_id = crate::id(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let account_key = Pubkey::new_unique(); + let mut account_account = SolanaAccount::new( + account_minimum_balance() + 40, + Account::get_packed_len(), + &program_id, + ); + let account2_key = Pubkey::new_unique(); + let mut account2_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account3_key = Pubkey::new_unique(); + let mut account3_account = SolanaAccount::new(account_minimum_balance(), 0, &program_id); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let owner2_key = Pubkey::new_unique(); + let mut owner2_account = SolanaAccount::default(); + let owner3_key = Pubkey::new_unique(); + let mut rent_sysvar = rent_sysvar(); + + // 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, + &mut rent_sysvar, + ], + ) + .unwrap(); + let account = Account::unpack_unchecked(&account_account.data).unwrap(); + assert!(account.is_native()); + assert_eq!(account.amount, 40); + + // 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, + &mut rent_sysvar, + ], + ) + .unwrap(); + let account = Account::unpack_unchecked(&account2_account.data).unwrap(); + assert!(account.is_native()); + assert_eq!(account.amount, 0); + + // 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 + let bogus_mint_key = Pubkey::new_unique(); + let mut bogus_mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + do_process_instruction( + initialize_mint(&program_id, &bogus_mint_key, &owner_key, None, 2).unwrap(), + vec![&mut bogus_mint_account, &mut rent_sysvar], + ) + .unwrap(); + + assert_eq!( + Err(TokenError::NativeNotSupported.into()), + do_process_instruction( + burn( + &program_id, + &account_key, + &bogus_mint_key, + &owner_key, + &[], + 42 + ) + .unwrap(), + vec![ + &mut account_account, + &mut bogus_mint_account, + &mut owner_account + ], + ) + ); + + // ensure can't transfer below rent-exempt reserve + assert_eq!( + Err(TokenError::InsufficientFunds.into()), + do_process_instruction( + transfer( + &program_id, + &account_key, + &account2_key, + &owner_key, + &[], + 50, + ) + .unwrap(), + vec![ + &mut account_account, + &mut account2_account, + &mut owner_account, + ], + ) + ); + + // transfer between native accounts + 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(); + assert_eq!(account_account.lamports, account_minimum_balance()); + let account = Account::unpack_unchecked(&account_account.data).unwrap(); + assert!(account.is_native()); + assert_eq!(account.amount, 0); + assert_eq!(account2_account.lamports, account_minimum_balance() + 40); + let account = Account::unpack_unchecked(&account2_account.data).unwrap(); + assert!(account.is_native()); + assert_eq!(account.amount, 40); + + // set close authority + do_process_instruction( + set_authority( + &program_id, + &account_key, + Some(&owner3_key), + AuthorityType::CloseAccount, + &owner_key, + &[], + ) + .unwrap(), + vec![&mut account_account, &mut owner_account], + ) + .unwrap(); + let account = Account::unpack_unchecked(&account_account.data).unwrap(); + assert_eq!(account.close_authority, COption::Some(owner3_key)); + + // set new account owner + do_process_instruction( + set_authority( + &program_id, + &account_key, + Some(&owner2_key), + AuthorityType::AccountOwner, + &owner_key, + &[], + ) + .unwrap(), + vec![&mut account_account, &mut owner_account], + ) + .unwrap(); + + // close authority cleared + let account = Account::unpack_unchecked(&account_account.data).unwrap(); + assert_eq!(account.close_authority, COption::None); + + // close native account + 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!(account3_account.lamports, 2 * account_minimum_balance()); + let account = Account::unpack_unchecked(&account_account.data).unwrap(); + assert!(account.is_native()); + assert_eq!(account.amount, 0); + } + + #[test] + fn test_overflow() { + let program_id = crate::id(); + let account_key = Pubkey::new_unique(); + let mut account_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account2_key = Pubkey::new_unique(); + let mut account2_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let owner2_key = Pubkey::new_unique(); + let mut owner2_account = SolanaAccount::default(); + let mint_owner_key = Pubkey::new_unique(); + let mut mint_owner_account = SolanaAccount::default(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mut rent_sysvar = rent_sysvar(); + + // create new mint with owner + do_process_instruction( + initialize_mint(&program_id, &mint_key, &mint_owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + // create an 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, + &mut rent_sysvar, + ], + ) + .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, + &mut rent_sysvar, + ], + ) + .unwrap(); + + // mint the max to an account + 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 = Account::unpack_unchecked(&account_account.data).unwrap(); + assert_eq!(account.amount, u64::MAX); + + // attempt to mint one more to account + 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, + ], + ) + ); + let account = Account::unpack_unchecked(&account_account.data).unwrap(); + assert_eq!(account.amount, u64::MAX); + + // atttempt to mint one more to the other account + assert_eq!( + Err(TokenError::Overflow.into()), + do_process_instruction( + mint_to( + &program_id, + &mint_key, + &account2_key, + &mint_owner_key, + &[], + 1, + ) + .unwrap(), + vec![ + &mut mint_account, + &mut account2_account, + &mut mint_owner_account, + ], + ) + ); + + // burn some of the supply + do_process_instruction( + burn(&program_id, &account_key, &mint_key, &owner_key, &[], 100).unwrap(), + vec![&mut account_account, &mut mint_account, &mut owner_account], + ) + .unwrap(); + let account = Account::unpack_unchecked(&account_account.data).unwrap(); + assert_eq!(account.amount, u64::MAX - 100); + + do_process_instruction( + mint_to( + &program_id, + &mint_key, + &account_key, + &mint_owner_key, + &[], + 100, + ) + .unwrap(), + vec![ + &mut mint_account, + &mut account_account, + &mut mint_owner_account, + ], + ) + .unwrap(); + let account = Account::unpack_unchecked(&account_account.data).unwrap(); + assert_eq!(account.amount, u64::MAX); + + // manipulate account balance to attempt overflow transfer + let mut account = Account::unpack_unchecked(&account2_account.data).unwrap(); + account.amount = 1; + Account::pack(account, &mut account2_account.data).unwrap(); + + 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 = crate::id(); + let account_key = Pubkey::new_unique(); + let mut account_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account2_key = Pubkey::new_unique(); + let mut account2_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mut rent_sysvar = rent_sysvar(); + + // 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, &mut rent_sysvar], + ) + .unwrap(); + + // 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, + &mut rent_sysvar, + ], + ) + .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, + &mut rent_sysvar, + ], + ) + .unwrap(); + + // fund first account + 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 mut account = Account::unpack_unchecked(&account2_account.data).unwrap(); + account.state = AccountState::Frozen; + Account::pack(account, &mut account2_account.data).unwrap(); + 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 mut account = Account::unpack_unchecked(&account_account.data).unwrap(); + account.state = AccountState::Initialized; + Account::pack(account, &mut account_account.data).unwrap(); + let mut account = Account::unpack_unchecked(&account2_account.data).unwrap(); + account.state = AccountState::Frozen; + Account::pack(account, &mut account2_account.data).unwrap(); + 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 mut account = Account::unpack_unchecked(&account_account.data).unwrap(); + account.state = AccountState::Frozen; + Account::pack(account, &mut account_account.data).unwrap(); + let delegate_key = Pubkey::new_unique(); + 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 mut account = Account::unpack_unchecked(&account_account.data).unwrap(); + account.delegate = COption::Some(delegate_key); + account.delegated_amount = 100; + Account::pack(account, &mut account_account.data).unwrap(); + 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::new_unique(); + assert_eq!( + Err(TokenError::AccountFrozen.into()), + do_process_instruction( + set_authority( + &program_id, + &account_key, + Some(&new_owner_key), + AuthorityType::AccountOwner, + &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, &mint_key, &owner_key, &[], 100).unwrap(), + vec![&mut account_account, &mut mint_account, &mut owner_account], + ) + ); + } + + #[test] + fn test_freeze_thaw_dups() { + let program_id = crate::id(); + let account1_key = Pubkey::new_unique(); + let mut account1_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account1_info: AccountInfo = (&account1_key, true, &mut account1_account).into(); + let owner_key = Pubkey::new_unique(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mint_info: AccountInfo = (&mint_key, true, &mut mint_account).into(); + let rent_key = rent::id(); + let mut rent_sysvar = rent_sysvar(); + let rent_info: AccountInfo = (&rent_key, false, &mut rent_sysvar).into(); + + // create mint + do_process_instruction_dups( + initialize_mint(&program_id, &mint_key, &owner_key, Some(&account1_key), 2).unwrap(), + vec![mint_info.clone(), rent_info.clone()], + ) + .unwrap(); + + // create account + do_process_instruction_dups( + initialize_account(&program_id, &account1_key, &mint_key, &account1_key).unwrap(), + vec![ + account1_info.clone(), + mint_info.clone(), + account1_info.clone(), + rent_info.clone(), + ], + ) + .unwrap(); + + // freeze where mint freeze_authority is account + do_process_instruction_dups( + freeze_account(&program_id, &account1_key, &mint_key, &account1_key, &[]).unwrap(), + vec![ + account1_info.clone(), + mint_info.clone(), + account1_info.clone(), + ], + ) + .unwrap(); + + // thaw where mint freeze_authority is account + let mut account = Account::unpack_unchecked(&account1_info.data.borrow()).unwrap(); + account.state = AccountState::Frozen; + Account::pack(account, &mut account1_info.data.borrow_mut()).unwrap(); + do_process_instruction_dups( + thaw_account(&program_id, &account1_key, &mint_key, &account1_key, &[]).unwrap(), + vec![ + account1_info.clone(), + mint_info.clone(), + account1_info.clone(), + ], + ) + .unwrap(); + } + + #[test] + fn test_freeze_account() { + let program_id = crate::id(); + let account_key = Pubkey::new_unique(); + let mut account_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let account_owner_key = Pubkey::new_unique(); + let mut account_owner_account = SolanaAccount::default(); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let owner2_key = Pubkey::new_unique(); + let mut owner2_account = SolanaAccount::default(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mut rent_sysvar = rent_sysvar(); + + // 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, &mut rent_sysvar], + ) + .unwrap(); + + // 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, + &mut rent_sysvar, + ], + ) + .unwrap(); + + // mint to account + 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 mut mint = Mint::unpack_unchecked(&mint_account.data).unwrap(); + mint.freeze_authority = COption::Some(owner_key); + Mint::pack(mint, &mut mint_account.data).unwrap(); + 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 = Account::unpack_unchecked(&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 = Account::unpack_unchecked(&account_account.data).unwrap(); + assert_eq!(account.state, AccountState::Initialized); + } + + #[test] + fn test_initialize_account2_and_3() { + let program_id = crate::id(); + let account_key = Pubkey::new_unique(); + let mut account_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let mut account2_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let mut account3_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mut rent_sysvar = rent_sysvar(); + + // create mint + do_process_instruction( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + do_process_instruction( + initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(), + vec![ + &mut account_account, + &mut mint_account, + &mut owner_account, + &mut rent_sysvar, + ], + ) + .unwrap(); + + do_process_instruction( + initialize_account2(&program_id, &account_key, &mint_key, &owner_key).unwrap(), + vec![&mut account2_account, &mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + assert_eq!(account_account, account2_account); + + do_process_instruction( + initialize_account3(&program_id, &account_key, &mint_key, &owner_key).unwrap(), + vec![&mut account3_account, &mut mint_account], + ) + .unwrap(); + + assert_eq!(account_account, account3_account); + } + + #[test] + fn test_sync_native() { + let program_id = crate::id(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let native_account_key = Pubkey::new_unique(); + let lamports = 40; + let mut native_account = SolanaAccount::new( + account_minimum_balance() + lamports, + Account::get_packed_len(), + &program_id, + ); + let non_native_account_key = Pubkey::new_unique(); + let mut non_native_account = SolanaAccount::new( + account_minimum_balance() + 50, + Account::get_packed_len(), + &program_id, + ); + + let owner_key = Pubkey::new_unique(); + let mut owner_account = SolanaAccount::default(); + let mut rent_sysvar = rent_sysvar(); + + // initialize non-native mint + do_process_instruction( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + // initialize non-native account + do_process_instruction( + initialize_account(&program_id, &non_native_account_key, &mint_key, &owner_key) + .unwrap(), + vec![ + &mut non_native_account, + &mut mint_account, + &mut owner_account, + &mut rent_sysvar, + ], + ) + .unwrap(); + + let account = Account::unpack_unchecked(&non_native_account.data).unwrap(); + assert!(!account.is_native()); + assert_eq!(account.amount, 0); + + // fail sync non-native + assert_eq!( + Err(TokenError::NonNativeNotSupported.into()), + do_process_instruction( + sync_native(&program_id, &non_native_account_key,).unwrap(), + vec![&mut non_native_account], + ) + ); + + // fail sync uninitialized + assert_eq!( + Err(ProgramError::UninitializedAccount), + do_process_instruction( + sync_native(&program_id, &native_account_key,).unwrap(), + vec![&mut native_account], + ) + ); + + // wrap native account + do_process_instruction( + initialize_account( + &program_id, + &native_account_key, + &crate::native_mint::id(), + &owner_key, + ) + .unwrap(), + vec![ + &mut native_account, + &mut mint_account, + &mut owner_account, + &mut rent_sysvar, + ], + ) + .unwrap(); + let account = Account::unpack_unchecked(&native_account.data).unwrap(); + assert!(account.is_native()); + assert_eq!(account.amount, lamports); + + // sync, no change + do_process_instruction( + sync_native(&program_id, &native_account_key).unwrap(), + vec![&mut native_account], + ) + .unwrap(); + let account = Account::unpack_unchecked(&native_account.data).unwrap(); + assert_eq!(account.amount, lamports); + + // transfer sol + let new_lamports = lamports + 50; + native_account.lamports = account_minimum_balance() + new_lamports; + + // success sync + do_process_instruction( + sync_native(&program_id, &native_account_key).unwrap(), + vec![&mut native_account], + ) + .unwrap(); + let account = Account::unpack_unchecked(&native_account.data).unwrap(); + assert_eq!(account.amount, new_lamports); + + // reduce sol + native_account.lamports -= 1; + + // fail sync + assert_eq!( + Err(TokenError::InvalidState.into()), + do_process_instruction( + sync_native(&program_id, &native_account_key,).unwrap(), + vec![&mut native_account], + ) + ); + } +} diff --git a/token/program-2022/src/state.rs b/token/program-2022/src/state.rs new file mode 100644 index 00000000000..c35f342831f --- /dev/null +++ b/token/program-2022/src/state.rs @@ -0,0 +1,288 @@ +//! State transition types + +use crate::instruction::MAX_SIGNERS; +use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}; +use num_enum::TryFromPrimitive; +use solana_program::{ + program_error::ProgramError, + program_option::COption, + program_pack::{IsInitialized, Pack, Sealed}, + pubkey::Pubkey, +}; + +/// 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, + /// Total supply of tokens. + pub supply: u64, + /// Number of base 10 digits to the right of the decimal place. + pub decimals: u8, + /// Is `true` if this structure has been initialized + pub is_initialized: bool, + /// Optional authority to freeze token accounts. + pub freeze_authority: COption, +} +impl Sealed for Mint {} +impl IsInitialized for Mint { + fn is_initialized(&self) -> bool { + self.is_initialized + } +} +impl Pack for Mint { + const LEN: usize = 82; + fn unpack_from_slice(src: &[u8]) -> Result { + let src = array_ref![src, 0, 82]; + let (mint_authority, supply, decimals, is_initialized, freeze_authority) = + array_refs![src, 36, 8, 1, 1, 36]; + let mint_authority = unpack_coption_key(mint_authority)?; + let supply = u64::from_le_bytes(*supply); + let decimals = decimals[0]; + let is_initialized = match is_initialized { + [0] => false, + [1] => true, + _ => return Err(ProgramError::InvalidAccountData), + }; + let freeze_authority = unpack_coption_key(freeze_authority)?; + Ok(Mint { + mint_authority, + supply, + decimals, + is_initialized, + freeze_authority, + }) + } + fn pack_into_slice(&self, dst: &mut [u8]) { + let dst = array_mut_ref![dst, 0, 82]; + let ( + mint_authority_dst, + supply_dst, + decimals_dst, + is_initialized_dst, + freeze_authority_dst, + ) = mut_array_refs![dst, 36, 8, 1, 1, 36]; + let &Mint { + ref mint_authority, + supply, + decimals, + is_initialized, + ref freeze_authority, + } = self; + pack_coption_key(mint_authority, mint_authority_dst); + *supply_dst = supply.to_le_bytes(); + decimals_dst[0] = decimals; + is_initialized_dst[0] = is_initialized as u8; + pack_coption_key(freeze_authority, freeze_authority_dst); + } +} + +/// Account data. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Account { + /// The mint associated with this account + pub mint: Pubkey, + /// The owner of this account. + pub owner: Pubkey, + /// The amount of tokens this account holds. + pub amount: u64, + /// If `delegate` is `Some` then `delegated_amount` represents + /// the amount authorized by the delegate + pub delegate: COption, + /// The account's state + pub state: AccountState, + /// If is_some, this is a native token, and the value logs the rent-exempt reserve. An Account + /// is required to be rent-exempt, so the value is used by the Processor to ensure that wrapped + /// SOL accounts do not drop below this threshold. + pub is_native: COption, + /// The amount delegated + pub delegated_amount: u64, + /// Optional authority to close the account. + pub close_authority: COption, +} +impl Account { + /// Checks if account is frozen + pub fn is_frozen(&self) -> bool { + self.state == AccountState::Frozen + } + /// Checks if account is native + pub fn is_native(&self) -> bool { + self.is_native.is_some() + } +} +impl Sealed for Account {} +impl IsInitialized for Account { + fn is_initialized(&self) -> bool { + self.state != AccountState::Uninitialized + } +} +impl Pack for Account { + const LEN: usize = 165; + fn unpack_from_slice(src: &[u8]) -> Result { + let src = array_ref![src, 0, 165]; + let (mint, owner, amount, delegate, state, is_native, delegated_amount, close_authority) = + array_refs![src, 32, 32, 8, 36, 1, 12, 8, 36]; + Ok(Account { + mint: Pubkey::new_from_array(*mint), + owner: Pubkey::new_from_array(*owner), + amount: u64::from_le_bytes(*amount), + delegate: unpack_coption_key(delegate)?, + state: AccountState::try_from_primitive(state[0]) + .or(Err(ProgramError::InvalidAccountData))?, + is_native: unpack_coption_u64(is_native)?, + delegated_amount: u64::from_le_bytes(*delegated_amount), + close_authority: unpack_coption_key(close_authority)?, + }) + } + fn pack_into_slice(&self, dst: &mut [u8]) { + let dst = array_mut_ref![dst, 0, 165]; + let ( + mint_dst, + owner_dst, + amount_dst, + delegate_dst, + state_dst, + is_native_dst, + delegated_amount_dst, + close_authority_dst, + ) = mut_array_refs![dst, 32, 32, 8, 36, 1, 12, 8, 36]; + let &Account { + ref mint, + ref owner, + amount, + ref delegate, + state, + ref is_native, + delegated_amount, + ref close_authority, + } = self; + mint_dst.copy_from_slice(mint.as_ref()); + owner_dst.copy_from_slice(owner.as_ref()); + *amount_dst = amount.to_le_bytes(); + pack_coption_key(delegate, delegate_dst); + state_dst[0] = state as u8; + pack_coption_u64(is_native, is_native_dst); + *delegated_amount_dst = delegated_amount.to_le_bytes(); + pack_coption_key(close_authority, close_authority_dst); + } +} + +/// Account state. +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive)] +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 Sealed for Multisig {} +impl IsInitialized for Multisig { + fn is_initialized(&self) -> bool { + self.is_initialized + } +} +impl Pack for Multisig { + const LEN: usize = 355; + fn unpack_from_slice(src: &[u8]) -> Result { + let src = array_ref![src, 0, 355]; + #[allow(clippy::ptr_offset_with_cast)] + let (m, n, is_initialized, signers_flat) = array_refs![src, 1, 1, 1, 32 * MAX_SIGNERS]; + let mut result = Multisig { + m: m[0], + n: n[0], + is_initialized: match is_initialized { + [0] => false, + [1] => true, + _ => return Err(ProgramError::InvalidAccountData), + }, + signers: [Pubkey::new_from_array([0u8; 32]); MAX_SIGNERS], + }; + for (src, dst) in signers_flat.chunks(32).zip(result.signers.iter_mut()) { + *dst = Pubkey::new(src); + } + Ok(result) + } + fn pack_into_slice(&self, dst: &mut [u8]) { + let dst = array_mut_ref![dst, 0, 355]; + #[allow(clippy::ptr_offset_with_cast)] + let (m, n, is_initialized, signers_flat) = mut_array_refs![dst, 1, 1, 1, 32 * MAX_SIGNERS]; + *m = [self.m]; + *n = [self.n]; + *is_initialized = [self.is_initialized as u8]; + for (i, src) in self.signers.iter().enumerate() { + let dst_array = array_mut_ref![signers_flat, 32 * i, 32]; + dst_array.copy_from_slice(src.as_ref()); + } + } +} + +// Helpers +fn pack_coption_key(src: &COption, dst: &mut [u8; 36]) { + let (tag, body) = mut_array_refs![dst, 4, 32]; + match src { + COption::Some(key) => { + *tag = [1, 0, 0, 0]; + body.copy_from_slice(key.as_ref()); + } + COption::None => { + *tag = [0; 4]; + } + } +} +fn unpack_coption_key(src: &[u8; 36]) -> Result, ProgramError> { + let (tag, body) = array_refs![src, 4, 32]; + match *tag { + [0, 0, 0, 0] => Ok(COption::None), + [1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))), + _ => Err(ProgramError::InvalidAccountData), + } +} +fn pack_coption_u64(src: &COption, dst: &mut [u8; 12]) { + let (tag, body) = mut_array_refs![dst, 4, 8]; + match src { + COption::Some(amount) => { + *tag = [1, 0, 0, 0]; + *body = amount.to_le_bytes(); + } + COption::None => { + *tag = [0; 4]; + } + } +} +fn unpack_coption_u64(src: &[u8; 12]) -> Result, ProgramError> { + let (tag, body) = array_refs![src, 4, 8]; + match *tag { + [0, 0, 0, 0] => Ok(COption::None), + [1, 0, 0, 0] => Ok(COption::Some(u64::from_le_bytes(*body))), + _ => Err(ProgramError::InvalidAccountData), + } +}