diff --git a/Cargo.toml b/Cargo.toml index 83e59e79..6ec48b41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ members = [ "vendor/solana/programs/compute-budget", "vendor/solana/programs/loader-v4", "vendor/solana/programs/system", + "vendor/solana/programs/token", ] default-members = [ "core-primitives", @@ -182,6 +183,7 @@ solana-metrics = { path = "vendor/solana/metrics" } solana-poseidon = { path = "vendor/solana/poseidon", default-features = false } solana-program-runtime = { path = "vendor/solana/program-runtime", default-features = false } solana-system-program = { path = "vendor/solana/programs/system", default-features = false } +spl-token = { path = "vendor/solana/programs/token" } [profile.release] panic = "unwind" diff --git a/frame/solana/Cargo.toml b/frame/solana/Cargo.toml index 9e832e98..2ff28107 100644 --- a/frame/solana/Cargo.toml +++ b/frame/solana/Cargo.toml @@ -50,6 +50,7 @@ rand = { workspace = true, default-features = true } solana-sdk = { workspace = true, features = ["dev-context-only-utils"] } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } +spl-token = { workspace = true } [features] default = ["std"] diff --git a/frame/solana/src/lib.rs b/frame/solana/src/lib.rs index f920fb3e..6473ff60 100644 --- a/frame/solana/src/lib.rs +++ b/frame/solana/src/lib.rs @@ -30,6 +30,7 @@ extern crate derive_where; extern crate solana_metrics; pub use pallet::*; +pub use types::*; pub use solana_rbpf; pub use solana_sdk::{pubkey::Pubkey, transaction::VersionedTransaction as Transaction}; @@ -40,6 +41,7 @@ mod runtime; mod svm; #[cfg(test)] mod tests; +mod types; use frame_support::{ dispatch::{DispatchErrorWithPostInfo, PostDispatchInfo}, @@ -85,10 +87,12 @@ impl> + From> EnsureOrigin for Ensure pub mod pallet { use super::*; use crate::runtime::bank::Bank; + use core::marker::PhantomData; use frame_support::{dispatch::DispatchInfo, pallet_prelude::*, traits::fungible}; use frame_system::{pallet_prelude::*, CheckWeight}; use np_runtime::traits::LossyInto; use solana_sdk::{ + account::Account, clock, fee_calculator::FeeCalculator, hash::Hash, @@ -128,7 +132,7 @@ pub mod pallet { + LossyInto; #[pallet::no_default] - type Currency: fungible::Unbalanced; + type Currency: fungible::Mutate; #[pallet::constant] #[pallet::no_default_bounds] @@ -183,20 +187,6 @@ pub mod pallet { #[pallet::origin] pub type Origin = RawOrigin; - #[derive(Decode, Encode, MaxEncodedLen, TypeInfo)] - #[scale_info(skip_type_params(T))] - pub struct HashInfo { - pub fee_calculator: FeeCalculator, - pub hash_index: BlockNumberFor, - pub timestamp: T::Moment, - } - - impl HashInfo { - pub fn lamports_per_signature(&self) -> u64 { - self.fee_calculator.lamports_per_signature - } - } - #[pallet::storage] #[pallet::getter(fn slot)] pub type Slot = StorageValue<_, clock::Slot, ValueQuery>; @@ -210,13 +200,48 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn account_meta)] - pub type AccountMeta = - StorageMap<_, Twox64Concat, T::AccountId, crate::runtime::meta::AccountMeta>; + pub type AccountMeta = StorageMap<_, Twox64Concat, T::AccountId, AccountMetadata>; #[pallet::storage] #[pallet::getter(fn account_data)] - pub type AccountData = - StorageMap<_, Twox64Concat, T::AccountId, BoundedVec>; + pub type AccountData = StorageMap< + _, + Twox64Concat, + T::AccountId, + BoundedVec, + ValueQuery, + >; + + #[pallet::genesis_config] + #[derive_where(Default)] + pub struct GenesisConfig { + accounts: Vec<(Pubkey, Account)>, + _marker: PhantomData, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + self.accounts.iter().for_each(|(pubkey, account)| { + let who = T::AccountIdConversion::convert(*pubkey); + assert!(>::account_exists(&who)); + >::insert( + &who, + AccountMetadata { + rent_epoch: account.rent_epoch, + owner: account.owner, + executable: account.executable, + }, + ); + (!account.data.is_empty()).then(|| { + >::insert( + who, + BoundedVec::try_from(account.data.clone()).expect("valid data"), + ); + }); + }); + } + } #[pallet::hooks] impl Hooks> for Pallet { diff --git a/frame/solana/src/mock.rs b/frame/solana/src/mock.rs index 0b4dc226..9e0aab5a 100644 --- a/frame/solana/src/mock.rs +++ b/frame/solana/src/mock.rs @@ -116,8 +116,8 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![ - (Keypair::alice().account_id(), 10_000_000_000_000_000_000u128), - (Keypair::bob().account_id(), 10_000_000_000_000_000_000u128), + (Keypair::alice().account_id(), sol_into_balances(10)), + (Keypair::bob().account_id(), sol_into_balances(10)), ], } .assimilate_storage(&mut t) @@ -125,6 +125,14 @@ pub fn new_test_ext() -> sp_io::TestExternalities { t.into() } +pub const fn sol_into_lamports(sol: u64) -> u64 { + sol * 10u64.pow(9) +} + +pub const fn sol_into_balances(sol: u64) -> Balance { + (sol_into_lamports(sol) as Balance) * 10u128.pow(9) +} + pub trait KeypairExt: Sized { fn create() -> Self; @@ -138,9 +146,6 @@ pub trait KeypairExt: Sized { fn bob() -> Self { Self::get("Bob") } - fn charlie() -> Self { - Self::get("Charlie") - } } fn pair_to_bytes(pair: Pair) -> [u8; 64] { diff --git a/frame/solana/src/runtime/bank.rs b/frame/solana/src/runtime/bank.rs index 639244e2..92ee2b65 100644 --- a/frame/solana/src/runtime/bank.rs +++ b/frame/solana/src/runtime/bank.rs @@ -17,7 +17,6 @@ // along with this program. If not, see . use crate::{ - runtime::lamports::Lamports, svm::{ account_loader::{ CheckedTransactionDetails, TransactionCheckResult, TransactionLoadResult, @@ -31,7 +30,7 @@ use crate::{ }, transaction_results::TransactionExecutionResult, }, - AccountData, AccountMeta, Config, Pallet, + AccountData, AccountMeta, AccountMetadata, Config, Lamports, Pallet, }; use frame_support::{ sp_runtime::traits::{Convert, ConvertBack, SaturatedConversion}, @@ -507,7 +506,7 @@ impl Bank { fn store_account(&self, pubkey: &T::AccountId, account: &AccountSharedData) { >::mutate(pubkey, |meta| { - *meta = Some(crate::runtime::meta::AccountMeta { + *meta = Some(AccountMetadata { rent_epoch: account.rent_epoch(), owner: *account.owner(), executable: account.executable(), @@ -561,13 +560,10 @@ impl TransactionProcessingCallback for Bank { let account = >::get(&pubkey)?; let lamports = >::new(T::Currency::reducible_balance(&pubkey, Preserve, Polite)); - let data = >::get(&pubkey); + let data = >::get(&pubkey).into(); Some(AccountSharedData::from(Account { lamports: lamports.get(), - data: match data { - Some(data) => data.into(), - None => vec![], - }, + data, owner: account.owner, executable: account.executable, rent_epoch: account.rent_epoch, diff --git a/frame/solana/src/runtime/meta.rs b/frame/solana/src/runtime/meta.rs deleted file mode 100644 index 5c4bb0ea..00000000 --- a/frame/solana/src/runtime/meta.rs +++ /dev/null @@ -1,49 +0,0 @@ -// This file is part of Noir. - -// Copyright (c) Haderech Pte. Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; -use serde::{Deserialize, Serialize}; -use solana_sdk::{clock::Epoch, pubkey::Pubkey}; - -/// This struct will be backed by mmaped and snapshotted data files. -/// So the data layout must be stable and consistent across the entire cluster! -#[derive( - Serialize, - Deserialize, - Clone, - Debug, - Default, - Eq, - PartialEq, - Decode, - Encode, - MaxEncodedLen, - TypeInfo, -)] -#[repr(C)] -pub struct AccountMeta { - // lamports in the account - //pub lamports: u64, - /// the epoch at which this account will next owe rent - pub rent_epoch: Epoch, - /// the program that owns this account. If executable, the program that loads this account. - pub owner: Pubkey, - /// this account's data contains a loaded program (and is now read-only) - pub executable: bool, -} diff --git a/frame/solana/src/runtime/mod.rs b/frame/solana/src/runtime/mod.rs index 8664e4bf..7679852e 100644 --- a/frame/solana/src/runtime/mod.rs +++ b/frame/solana/src/runtime/mod.rs @@ -19,5 +19,3 @@ #![allow(unexpected_cfgs)] pub mod bank; -pub mod lamports; -pub mod meta; diff --git a/frame/solana/src/svm/transaction_processor.rs b/frame/solana/src/svm/transaction_processor.rs index a39e66c1..c505ca28 100644 --- a/frame/solana/src/svm/transaction_processor.rs +++ b/frame/solana/src/svm/transaction_processor.rs @@ -402,10 +402,8 @@ impl TransactionProcessor { _check_program_modification_slot: bool, _limit_to_load_programs: bool, ) -> ProgramCacheForTxBatch { - let mut loaded_programs_for_tx_batch = ProgramCacheForTxBatch::default(); - // FIXME: program_runtime_environments. - loaded_programs_for_tx_batch.environments = ProgramRuntimeEnvironments { + let environments = ProgramRuntimeEnvironments { program_runtime_v1: Arc::new( create_program_runtime_environment_v1( &Default::default(), @@ -420,6 +418,8 @@ impl TransactionProcessor { false, /* debugging_features */ )), }; + let mut loaded_programs_for_tx_batch = + ProgramCacheForTxBatch::new(self.slot, environments, None, self.epoch); // FIXME: load builtins. for cached_program in self.program_cache.iter() { diff --git a/frame/solana/src/tests.rs b/frame/solana/src/tests.rs index 1700118f..427b03de 100644 --- a/frame/solana/src/tests.rs +++ b/frame/solana/src/tests.rs @@ -16,39 +16,41 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{ - mock::*, - runtime::{bank::Bank, meta::AccountMeta as Meta}, - *, -}; +use crate::{mock::*, runtime::bank::Bank, *}; use frame_support::{ sp_runtime::traits::Convert, - traits::{fungible::Inspect, Get}, + traits::{ + fungible::{Inspect, Mutate}, + Get, + }, + BoundedVec, }; +use frame_system::pallet_prelude::BlockNumberFor; use solana_sdk::{ - bpf_loader_upgradeable, + bpf_loader, bpf_loader_upgradeable, hash::Hash, instruction::{self, Instruction}, message::SimpleAddressLoader, + program_pack::Pack, reserved_account_keys::ReservedAccountKeys, signature::{Keypair, Signer}, - system_program, system_transaction, + system_instruction, system_program, system_transaction, transaction::{MessageHash, Result, SanitizedTransaction, Transaction, VersionedTransaction}, }; -fn before_each() -> Bank { +fn before_each() { >::insert( &Keypair::alice().account_id(), - Meta { rent_epoch: u64::MAX, owner: system_program::id(), executable: false }, + AccountMetadata { rent_epoch: u64::MAX, owner: system_program::id(), executable: false }, ); >::insert( &Keypair::bob().account_id(), - Meta { rent_epoch: u64::MAX, owner: system_program::id(), executable: false }, + AccountMetadata { rent_epoch: u64::MAX, owner: system_program::id(), executable: false }, ); - System::set_block_number(2); - >::put(2); + set_block_number(2); + let blockhash = HashConversion::convert(Hash::default()); >::insert( blockhash, @@ -58,8 +60,15 @@ fn before_each() -> Bank { timestamp: <::GenesisTimestamp as Get>::get() + 400, }, ); +} + +fn set_block_number(n: BlockNumberFor) { + System::set_block_number(n); + >::put(n); +} - >::new(>::get()) +fn mock_bank() -> Bank { + Bank::new(>::get()) } fn process_transaction(bank: &Bank, tx: Transaction) -> Result<()> { @@ -76,32 +85,45 @@ fn process_transaction(bank: &Bank, tx: Transaction) -> Result<()> { bank.load_execute_and_commit_sanitized_transaction(sanitized_tx) } +fn deploy_program(program_id: &Pubkey, data: Vec) { + let who = AccountIdConversion::convert(*program_id); + System::inc_providers(&who); + Balances::mint_into(&who, sol_into_balances(1)).unwrap(); + >::insert( + &who, + AccountMetadata { rent_epoch: u64::MAX, owner: bpf_loader::id(), executable: true }, + ); + >::insert(&who, BoundedVec::try_from(data).expect("account data")); +} + #[test] fn create_account_tx_should_work() { new_test_ext().execute_with(|| { - let bank = before_each(); + before_each(); + let bank = mock_bank(); let from = Keypair::alice(); - let to = Keypair::charlie(); + let to = Keypair::get("Account"); let tx = system_transaction::create_account( &from, &to, Hash::default(), - 1_000_000_000, + sol_into_lamports(1), 0, &system_program::id(), ); assert!(process_transaction(&bank, tx).is_ok()); - assert_eq!(Balances::total_balance(&to.account_id()), 1_000_000_000_000_000_000u128); + assert_eq!(Balances::total_balance(&to.account_id()), sol_into_balances(1)); }); } #[test] fn deploy_program_tx_should_work() { new_test_ext().execute_with(|| { - let bank = before_each(); + before_each(); + let bank = mock_bank(); let payer = Keypair::alice(); let program_keypair = Keypair::get("Program"); @@ -110,9 +132,9 @@ fn deploy_program_tx_should_work() { let program_data = std::fs::read("tests/example-programs/simple-transfer/simple_transfer_program.so") .expect("program data"); - let program_account_balance = 1_000_000_000; + let program_account_balance = sol_into_lamports(1); let buffer_account_space = program_data.len(); - let buffer_account_balance = 1_000_000_000; + let buffer_account_balance = sol_into_lamports(1); let create_buffer = bpf_loader_upgradeable::create_buffer( &payer.pubkey(), @@ -155,10 +177,13 @@ fn deploy_program_tx_should_work() { tx.sign(&[&payer, &program_keypair], Hash::default()); assert!(process_transaction(&bank, tx).is_ok()); + set_block_number(3); + let bank = mock_bank(); + let recipient = Keypair::bob(); let simple_transfer = Instruction::new_with_bytes( program_keypair.pubkey(), - &1_000_000_000u64.to_be_bytes(), + &sol_into_lamports(1).to_be_bytes(), vec![ instruction::AccountMeta::new(payer.pubkey(), true), instruction::AccountMeta::new(recipient.pubkey(), false), @@ -169,9 +194,85 @@ fn deploy_program_tx_should_work() { tx.sign(&[&payer], Hash::default()); assert!(process_transaction(&bank, tx).is_ok()); - assert_eq!( - Balances::total_balance(&recipient.account_id()), - 11_000_000_000_000_000_000u128 + assert_eq!(Balances::total_balance(&recipient.account_id()), sol_into_balances(11)); + }); +} + +#[test] +fn spl_token_program_should_work() { + new_test_ext().execute_with(|| { + before_each(); + let bank = mock_bank(); + + let authority = Keypair::alice(); + let owner = Keypair::bob(); + let mint = Keypair::get("Mint"); + + let token_program_id = Pubkey::parse("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); + let program_data = + std::fs::read("tests/example-programs/token/token_program.so").expect("program data"); + + deploy_program(&token_program_id, program_data); + + let create_account = system_instruction::create_account( + &authority.pubkey(), + &mint.pubkey(), + sol_into_lamports(1), + 82, + &token_program_id, + ); + let initialize_mint = spl_token::instruction::initialize_mint2( + &token_program_id, + &mint.pubkey(), + &authority.pubkey(), + None, + 9, + ) + .expect("initialize_mint2 instruction"); + let mut tx = Transaction::new_with_payer( + &[create_account, initialize_mint], + Some(&authority.pubkey()), + ); + tx.sign(&[&authority, &mint], Hash::default()); + assert!(process_transaction(&bank, tx).is_ok()); + + let account = Keypair::get("Account"); + let create_account = system_instruction::create_account( + &owner.pubkey(), + &account.pubkey(), + sol_into_lamports(1), + 165, + &token_program_id, ); + let initialize_account = spl_token::instruction::initialize_account( + &token_program_id, + &account.pubkey(), + &mint.pubkey(), + &owner.pubkey(), + ) + .expect("initialize_account instruction"); + let mint_to = spl_token::instruction::mint_to( + &token_program_id, + &mint.pubkey(), + &account.pubkey(), + &authority.pubkey(), + &[], + sol_into_lamports(1_000), + ) + .expect("mint_to instruction"); + let mut tx = Transaction::new_with_payer( + &[create_account, initialize_account, mint_to], + Some(&owner.pubkey()), + ); + tx.sign(&[&authority, &account, &owner], Hash::default()); + assert!(process_transaction(&bank, tx).is_ok()); + + let state = spl_token::state::Account::unpack_from_slice(&>::get( + &account.account_id(), + )) + .expect("token acccount state"); + assert_eq!(state.mint, mint.pubkey()); + assert_eq!(state.owner, owner.pubkey()); + assert_eq!(state.amount, sol_into_lamports(1_000)); }); } diff --git a/frame/solana/src/runtime/lamports.rs b/frame/solana/src/types.rs similarity index 84% rename from frame/solana/src/runtime/lamports.rs rename to frame/solana/src/types.rs index 61b477b4..de03f49f 100644 --- a/frame/solana/src/runtime/lamports.rs +++ b/frame/solana/src/types.rs @@ -18,11 +18,38 @@ use crate::{BalanceOf, Config}; use frame_support::sp_runtime::traits::{CheckedAdd, CheckedMul, CheckedSub, Get, Saturating}; +use frame_system::pallet_prelude::BlockNumberFor; use nostd::cmp::Ordering; use np_runtime::traits::LossyInto; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -use solana_sdk::instruction::InstructionError; +use solana_sdk::{ + clock::Epoch, fee_calculator::FeeCalculator, instruction::InstructionError, pubkey::Pubkey, +}; + +#[derive(Decode, Encode, MaxEncodedLen, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct HashInfo { + pub fee_calculator: FeeCalculator, + pub hash_index: BlockNumberFor, + pub timestamp: T::Moment, +} + +impl HashInfo { + pub fn lamports_per_signature(&self) -> u64 { + self.fee_calculator.lamports_per_signature + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq, Decode, Encode, MaxEncodedLen, TypeInfo)] +pub struct AccountMetadata { + /// the epoch at which this account will next owe rent + pub rent_epoch: Epoch, + /// the program that owns this account. If executable, the program that loads this account. + pub owner: Pubkey, + /// this account's data contains a loaded program (and is now read-only) + pub executable: bool, +} #[derive(Clone, PartialEq, Eq, Decode, Encode, MaxEncodedLen, TypeInfo)] #[derive_where(Copy, Debug)] diff --git a/frame/solana/tests/example-programs/token/token_program.so b/frame/solana/tests/example-programs/token/token_program.so new file mode 100644 index 00000000..9eeed9a0 Binary files /dev/null and b/frame/solana/tests/example-programs/token/token_program.so differ diff --git a/vendor/solana/metrics/Cargo.toml b/vendor/solana/metrics/Cargo.toml index 0008ace0..6935e7c9 100644 --- a/vendor/solana/metrics/Cargo.toml +++ b/vendor/solana/metrics/Cargo.toml @@ -12,7 +12,7 @@ edition = "2021" [dependencies] crossbeam-channel = "0.5.13" gethostname = "0.2.3" -lazy_static = "1.4.0" +lazy_static = "1.5.0" log = { workspace = true, default-features = true } reqwest = { version = "0.11.27", default-features = false, features = ["blocking", "brotli", "deflate", "gzip", "rustls-tls", "json"] } solana-sdk = { workspace = true, default-features = true } diff --git a/vendor/solana/programs/token/Cargo.toml b/vendor/solana/programs/token/Cargo.toml new file mode 100644 index 00000000..22a7bc79 --- /dev/null +++ b/vendor/solana/programs/token/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "spl-token" +version = "7.0.0" +description = "Solana Program Library Token" +authors = ["Solana Labs Maintainers "] +repository = "https://github.com/solana-labs/solana-program-library" +license = "Apache-2.0" +edition = "2021" +exclude = ["js/**"] + +[features] +no-entrypoint = [] +test-sbf = [] + +[dependencies] +arrayref = "0.3.9" +bytemuck = { workspace = true } +num-derive = { workspace = true } +num-traits = { workspace = true } +num_enum = { workspace = true } +solana-program = { workspace = true } +thiserror = { workspace = true, default-features = true } + +[dev-dependencies] +lazy_static = "1.5.0" +proptest = "1.6" +serial_test = "3.2.0" +#solana-program-test = "2.1.0" +solana-sdk = { workspace = true } + +[lib] +crate-type = ["cdylib", "lib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[lints] +workspace = true diff --git a/vendor/solana/programs/token/README.md b/vendor/solana/programs/token/README.md new file mode 100644 index 00000000..81167bfd --- /dev/null +++ b/vendor/solana/programs/token/README.md @@ -0,0 +1,13 @@ +# Token program + +A token program on the Solana blockchain, usable for fungible and non-fungible tokens. + +This program provides an interface and implementation that third parties can +utilize to create and use their tokens. + +Full documentation is available at the [SPL Token docs](https://spl.solana.com/token). + +## Audit + +The repository [README](https://github.com/solana-labs/solana-program-library#audits) +contains information about program audits. diff --git a/vendor/solana/programs/token/Xargo.toml b/vendor/solana/programs/token/Xargo.toml new file mode 100644 index 00000000..1744f098 --- /dev/null +++ b/vendor/solana/programs/token/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/vendor/solana/programs/token/inc/token.h b/vendor/solana/programs/token/inc/token.h new file mode 100644 index 00000000..145c0c5e --- /dev/null +++ b/vendor/solana/programs/token/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/vendor/solana/programs/token/program-id.md b/vendor/solana/programs/token/program-id.md new file mode 100644 index 00000000..f397edf0 --- /dev/null +++ b/vendor/solana/programs/token/program-id.md @@ -0,0 +1 @@ +TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA diff --git a/vendor/solana/programs/token/src/entrypoint.rs b/vendor/solana/programs/token/src/entrypoint.rs new file mode 100644 index 00000000..366ee78d --- /dev/null +++ b/vendor/solana/programs/token/src/entrypoint.rs @@ -0,0 +1,23 @@ +//! Program entrypoint + +use { + crate::{error::TokenError, processor::Processor}, + solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::PrintProgramError, + pubkey::Pubkey, + }, +}; + +solana_program::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/vendor/solana/programs/token/src/error.rs b/vendor/solana/programs/token/src/error.rs new file mode 100644 index 00000000..a758d2c9 --- /dev/null +++ b/vendor/solana/programs/token/src/error.rs @@ -0,0 +1,141 @@ +//! Error types + +use { + num_derive::FromPrimitive, + solana_program::{ + decode_error::DecodeError, + msg, + program_error::{PrintProgramError, ProgramError}, + }, + 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 uninitialized")] + UninitializedState, + + // 10 + /// Instruction does not support native tokens + #[error("Instruction does not support native tokens")] + NativeNotSupported, + /// Non-native account can only be closed if its balance is zero + #[error("Non-native account can only be closed if its balance is zero")] + NonNativeHasBalance, + /// Invalid instruction + #[error("Invalid instruction")] + InvalidInstruction, + /// State is invalid for requested operation. + #[error("State is invalid for requested operation")] + InvalidState, + /// Operation overflowed + #[error("Operation overflowed")] + Overflow, + + // 15 + /// Account does not support specified authority type. + #[error("Account does not support specified authority type")] + AuthorityTypeNotSupported, + /// This token mint cannot freeze accounts. + #[error("This token mint cannot freeze accounts")] + MintCannotFreeze, + /// Account is frozen; all account operations will fail + #[error("Account is frozen")] + AccountFrozen, + /// Mint decimals mismatch between the client and mint + #[error("The provided decimals value different from the Mint decimals")] + MintDecimalsMismatch, + /// Instruction does not support non-native tokens + #[error("Instruction does not support non-native tokens")] + NonNativeNotSupported, +} +impl From for ProgramError { + fn from(e: TokenError) -> Self { + ProgramError::Custom(e as u32) + } +} +impl DecodeError for TokenError { + fn type_of() -> &'static str { + "TokenError" + } +} + +impl PrintProgramError for TokenError { + fn print(&self) + where + E: 'static + + std::error::Error + + DecodeError + + PrintProgramError + + num_traits::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") + } + } + } +} diff --git a/vendor/solana/programs/token/src/instruction.rs b/vendor/solana/programs/token/src/instruction.rs new file mode 100644 index 00000000..b797a7d8 --- /dev/null +++ b/vendor/solana/programs/token/src/instruction.rs @@ -0,0 +1,1705 @@ +//! Instruction types + +use { + crate::{check_program_account, error::TokenError}, + solana_program::{ + instruction::{AccountMeta, Instruction}, + program_error::ProgramError, + program_option::COption, + pubkey::Pubkey, + sysvar, + }, + std::{convert::TryInto, mem::size_of}, +}; + +/// Minimum number of multisignature signers (min N) +pub const MIN_SIGNERS: usize = 1; +/// Maximum number of multisignature signers (max N) +pub const MAX_SIGNERS: usize = 11; +/// Serialized length of a `u64`, for unpacking +const U64_BYTES: usize = 8; + +/// Instructions supported by the token program. +#[repr(C)] +#[derive(Clone, Debug, PartialEq)] +pub enum TokenInstruction<'a> { + /// Initializes a new mint and optionally deposits all the newly minted + /// tokens in an account. + /// + /// The `InitializeMint` instruction requires no signers and MUST be + /// included within the same Transaction as the system program's + /// `CreateAccount` instruction that creates the account being initialized. + /// Otherwise another party can acquire ownership of the uninitialized + /// account. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The mint to initialize. + /// 1. `[]` Rent sysvar + InitializeMint { + /// Number of base 10 digits to the right of the decimal place. + decimals: u8, + /// The authority/multisignature to mint tokens. + mint_authority: Pubkey, + /// The freeze authority/multisignature of the mint. + freeze_authority: COption, + }, + /// 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, + }, + /// Gets the required size of an account for the given mint as a + /// little-endian `u64`. + /// + /// Return data can be fetched using `sol_get_return_data` and deserializing + /// the return data as a little-endian `u64`. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[]` The mint to calculate for + GetAccountDataSize, // typically, there's also data, but this program ignores it + /// Initialize the Immutable Owner extension for the given token account + /// + /// Fails if the account has already been initialized, so must be called + /// before `InitializeAccount`. + /// + /// No-ops in this version of the program, but is included for compatibility + /// with the Associated Token Account program. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[writable]` The account to initialize. + /// + /// Data expected by this instruction: + /// None + InitializeImmutableOwner, + /// Convert an Amount of tokens to a `UiAmount` string, using the given + /// mint. In this version of the program, the mint can only specify the + /// number of decimals. + /// + /// Fails on an invalid mint. + /// + /// Return data can be fetched using `sol_get_return_data` and deserialized + /// with `String::from_utf8`. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[]` The mint to calculate for + AmountToUiAmount { + /// The amount of tokens to reformat. + amount: u64, + }, + /// Convert a `UiAmount` of tokens to a little-endian `u64` raw Amount, + /// using the given mint. In this version of the program, the mint can + /// only specify the number of decimals. + /// + /// Return data can be fetched using `sol_get_return_data` and deserializing + /// the return data as a little-endian `u64`. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[]` The mint to calculate for + UiAmountToAmount { + /// The `ui_amount` of tokens to reformat. + ui_amount: &'a str, + }, + // Any new variants also need to be added to program-2022 `TokenInstruction`, so that the + // latter remains a superset of this instruction set. New variants also need to be added to + // token/js/src/instructions/types.ts to maintain @solana/spl-token compatibility +} +impl<'a> TokenInstruction<'a> { + /// Unpacks a byte buffer into a + /// [`TokenInstruction`](enum.TokenInstruction.html). + pub fn unpack(input: &'a [u8]) -> Result { + use TokenError::InvalidInstruction; + + let (&tag, rest) = input.split_first().ok_or(InvalidInstruction)?; + Ok(match tag { + 0 => { + let (&decimals, rest) = rest.split_first().ok_or(InvalidInstruction)?; + let (mint_authority, rest) = Self::unpack_pubkey(rest)?; + let (freeze_authority, _rest) = Self::unpack_pubkey_option(rest)?; + Self::InitializeMint { + mint_authority, + freeze_authority, + decimals, + } + } + 1 => Self::InitializeAccount, + 2 => { + let &m = rest.first().ok_or(InvalidInstruction)?; + Self::InitializeMultisig { m } + } + 3 | 4 | 7 | 8 => { + let amount = rest + .get(..8) + .and_then(|slice| slice.try_into().ok()) + .map(u64::from_le_bytes) + .ok_or(InvalidInstruction)?; + match tag { + 3 => Self::Transfer { amount }, + 4 => Self::Approve { amount }, + 7 => Self::MintTo { amount }, + 8 => Self::Burn { amount }, + _ => unreachable!(), + } + } + 5 => Self::Revoke, + 6 => { + let (authority_type, rest) = rest + .split_first() + .ok_or_else(|| ProgramError::from(InvalidInstruction)) + .and_then(|(&t, rest)| Ok((AuthorityType::from(t)?, rest)))?; + let (new_authority, _rest) = Self::unpack_pubkey_option(rest)?; + + Self::SetAuthority { + authority_type, + new_authority, + } + } + 9 => Self::CloseAccount, + 10 => Self::FreezeAccount, + 11 => Self::ThawAccount, + 12 => { + let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?; + Self::TransferChecked { amount, decimals } + } + 13 => { + let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?; + Self::ApproveChecked { amount, decimals } + } + 14 => { + let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?; + Self::MintToChecked { amount, decimals } + } + 15 => { + let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?; + Self::BurnChecked { amount, decimals } + } + 16 => { + let (owner, _rest) = Self::unpack_pubkey(rest)?; + Self::InitializeAccount2 { owner } + } + 17 => Self::SyncNative, + 18 => { + let (owner, _rest) = Self::unpack_pubkey(rest)?; + Self::InitializeAccount3 { owner } + } + 19 => { + let &m = rest.first().ok_or(InvalidInstruction)?; + Self::InitializeMultisig2 { m } + } + 20 => { + let (&decimals, rest) = rest.split_first().ok_or(InvalidInstruction)?; + let (mint_authority, rest) = Self::unpack_pubkey(rest)?; + let (freeze_authority, _rest) = Self::unpack_pubkey_option(rest)?; + Self::InitializeMint2 { + mint_authority, + freeze_authority, + decimals, + } + } + 21 => Self::GetAccountDataSize, + 22 => Self::InitializeImmutableOwner, + 23 => { + let (amount, _rest) = Self::unpack_u64(rest)?; + Self::AmountToUiAmount { amount } + } + 24 => { + let ui_amount = std::str::from_utf8(rest).map_err(|_| InvalidInstruction)?; + Self::UiAmountToAmount { ui_amount } + } + _ => return Err(TokenError::InvalidInstruction.into()), + }) + } + + /// Packs a [`TokenInstruction`](enum.TokenInstruction.html) into a byte + /// buffer. + pub fn pack(&self) -> Vec { + 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); + } + &Self::GetAccountDataSize => { + buf.push(21); + } + &Self::InitializeImmutableOwner => { + buf.push(22); + } + &Self::AmountToUiAmount { amount } => { + buf.push(23); + buf.extend_from_slice(&amount.to_le_bytes()); + } + Self::UiAmountToAmount { ui_amount } => { + buf.push(24); + buf.extend_from_slice(ui_amount.as_bytes()); + } + }; + buf + } + + fn unpack_pubkey(input: &[u8]) -> Result<(Pubkey, &[u8]), ProgramError> { + if input.len() >= 32 { + let (key, rest) = input.split_at(32); + let pk = Pubkey::try_from(key).map_err(|_| TokenError::InvalidInstruction)?; + Ok((pk, rest)) + } else { + Err(TokenError::InvalidInstruction.into()) + } + } + + fn unpack_pubkey_option(input: &[u8]) -> Result<(COption, &[u8]), ProgramError> { + match input.split_first() { + Option::Some((&0, rest)) => Ok((COption::None, rest)), + Option::Some((&1, rest)) if rest.len() >= 32 => { + let (key, rest) = rest.split_at(32); + let pk = Pubkey::try_from(key).map_err(|_| TokenError::InvalidInstruction)?; + Ok((COption::Some(pk), rest)) + } + _ => Err(TokenError::InvalidInstruction.into()), + } + } + + fn pack_pubkey_option(value: &COption, buf: &mut Vec) { + match *value { + COption::Some(ref key) => { + buf.push(1); + buf.extend_from_slice(&key.to_bytes()); + } + COption::None => buf.push(0), + } + } + + fn unpack_u64(input: &[u8]) -> Result<(u64, &[u8]), ProgramError> { + let value = input + .get(..U64_BYTES) + .and_then(|slice| slice.try_into().ok()) + .map(u64::from_le_bytes) + .ok_or(TokenError::InvalidInstruction)?; + Ok((value, &input[U64_BYTES..])) + } + + fn unpack_amount_decimals(input: &[u8]) -> Result<(u64, u8, &[u8]), ProgramError> { + let (amount, rest) = Self::unpack_u64(input)?; + let (&decimals, rest) = rest.split_first().ok_or(TokenError::InvalidInstruction)?; + Ok((amount, decimals, rest)) + } +} + +/// Specifies the authority type for `SetAuthority` instructions +#[repr(u8)] +#[derive(Clone, Debug, PartialEq)] +pub enum AuthorityType { + /// Authority to mint new tokens + MintTokens, + /// Authority to freeze any account associated with the Mint + FreezeAccount, + /// Owner of a given token account + AccountOwner, + /// Authority to close a token account + CloseAccount, +} + +impl AuthorityType { + fn into(&self) -> u8 { + match self { + AuthorityType::MintTokens => 0, + AuthorityType::FreezeAccount => 1, + AuthorityType::AccountOwner => 2, + AuthorityType::CloseAccount => 3, + } + } + + fn from(index: u8) -> Result { + 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(), + }) +} + +/// Creates a `GetAccountDataSize` instruction +pub fn get_account_data_size( + token_program_id: &Pubkey, + mint_pubkey: &Pubkey, +) -> Result { + check_program_account(token_program_id)?; + + Ok(Instruction { + program_id: *token_program_id, + accounts: vec![AccountMeta::new_readonly(*mint_pubkey, false)], + data: TokenInstruction::GetAccountDataSize.pack(), + }) +} + +/// Creates a `InitializeImmutableOwner` instruction +pub fn initialize_immutable_owner( + token_program_id: &Pubkey, + account_pubkey: &Pubkey, +) -> Result { + check_program_account(token_program_id)?; + Ok(Instruction { + program_id: *token_program_id, + accounts: vec![AccountMeta::new(*account_pubkey, false)], + data: TokenInstruction::InitializeImmutableOwner.pack(), + }) +} + +/// Creates an `AmountToUiAmount` instruction +pub fn amount_to_ui_amount( + token_program_id: &Pubkey, + mint_pubkey: &Pubkey, + amount: u64, +) -> Result { + check_program_account(token_program_id)?; + + Ok(Instruction { + program_id: *token_program_id, + accounts: vec![AccountMeta::new_readonly(*mint_pubkey, false)], + data: TokenInstruction::AmountToUiAmount { amount }.pack(), + }) +} + +/// Creates a `UiAmountToAmount` instruction +pub fn ui_amount_to_amount( + token_program_id: &Pubkey, + mint_pubkey: &Pubkey, + ui_amount: &str, +) -> Result { + check_program_account(token_program_id)?; + + Ok(Instruction { + program_id: *token_program_id, + accounts: vec![AccountMeta::new_readonly(*mint_pubkey, false)], + data: TokenInstruction::UiAmountToAmount { ui_amount }.pack(), + }) +} + +/// Utility function that checks index is between `MIN_SIGNERS` and +/// `MAX_SIGNERS` +pub fn is_valid_signer_index(index: usize) -> bool { + (MIN_SIGNERS..=MAX_SIGNERS).contains(&index) +} + +#[cfg(test)] +mod test { + use {super::*, proptest::prelude::*}; + + #[test] + fn test_instruction_packing() { + let check = TokenInstruction::InitializeMint { + decimals: 2, + mint_authority: Pubkey::new_from_array([1u8; 32]), + freeze_authority: COption::None, + }; + let packed = check.pack(); + let mut expect = Vec::from([0u8, 2]); + expect.extend_from_slice(&[1u8; 32]); + expect.extend_from_slice(&[0]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeMint { + decimals: 2, + mint_authority: Pubkey::new_from_array([2u8; 32]), + freeze_authority: COption::Some(Pubkey::new_from_array([3u8; 32])), + }; + let packed = check.pack(); + let mut expect = vec![0u8, 2]; + expect.extend_from_slice(&[2u8; 32]); + expect.extend_from_slice(&[1]); + expect.extend_from_slice(&[3u8; 32]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeAccount; + let packed = check.pack(); + let expect = Vec::from([1u8]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeMultisig { m: 1 }; + let packed = check.pack(); + let expect = Vec::from([2u8, 1]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::Transfer { amount: 1 }; + let packed = check.pack(); + let expect = Vec::from([3u8, 1, 0, 0, 0, 0, 0, 0, 0]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::Approve { amount: 1 }; + let packed = check.pack(); + let expect = Vec::from([4u8, 1, 0, 0, 0, 0, 0, 0, 0]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::Revoke; + let packed = check.pack(); + let expect = Vec::from([5u8]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::SetAuthority { + authority_type: AuthorityType::FreezeAccount, + new_authority: COption::Some(Pubkey::new_from_array([4u8; 32])), + }; + let packed = check.pack(); + let mut expect = Vec::from([6u8, 1]); + expect.extend_from_slice(&[1]); + expect.extend_from_slice(&[4u8; 32]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::MintTo { amount: 1 }; + let packed = check.pack(); + let expect = Vec::from([7u8, 1, 0, 0, 0, 0, 0, 0, 0]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::Burn { amount: 1 }; + let packed = check.pack(); + let expect = Vec::from([8u8, 1, 0, 0, 0, 0, 0, 0, 0]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::CloseAccount; + let packed = check.pack(); + let expect = Vec::from([9u8]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::FreezeAccount; + let packed = check.pack(); + let expect = Vec::from([10u8]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::ThawAccount; + let packed = check.pack(); + let expect = Vec::from([11u8]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::TransferChecked { + amount: 1, + decimals: 2, + }; + let packed = check.pack(); + let expect = Vec::from([12u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::ApproveChecked { + amount: 1, + decimals: 2, + }; + let packed = check.pack(); + let expect = Vec::from([13u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::MintToChecked { + amount: 1, + decimals: 2, + }; + let packed = check.pack(); + let expect = Vec::from([14u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::BurnChecked { + amount: 1, + decimals: 2, + }; + let packed = check.pack(); + let expect = Vec::from([15u8, 1, 0, 0, 0, 0, 0, 0, 0, 2]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeAccount2 { + owner: Pubkey::new_from_array([2u8; 32]), + }; + let packed = check.pack(); + let mut expect = vec![16u8]; + expect.extend_from_slice(&[2u8; 32]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::SyncNative; + let packed = check.pack(); + let expect = vec![17u8]; + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeAccount3 { + owner: Pubkey::new_from_array([2u8; 32]), + }; + let packed = check.pack(); + let mut expect = vec![18u8]; + expect.extend_from_slice(&[2u8; 32]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeMultisig2 { m: 1 }; + let packed = check.pack(); + let expect = Vec::from([19u8, 1]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeMint2 { + decimals: 2, + mint_authority: Pubkey::new_from_array([1u8; 32]), + freeze_authority: COption::None, + }; + let packed = check.pack(); + let mut expect = Vec::from([20u8, 2]); + expect.extend_from_slice(&[1u8; 32]); + expect.extend_from_slice(&[0]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeMint2 { + decimals: 2, + mint_authority: Pubkey::new_from_array([2u8; 32]), + freeze_authority: COption::Some(Pubkey::new_from_array([3u8; 32])), + }; + let packed = check.pack(); + let mut expect = vec![20u8, 2]; + expect.extend_from_slice(&[2u8; 32]); + expect.extend_from_slice(&[1]); + expect.extend_from_slice(&[3u8; 32]); + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::GetAccountDataSize; + let packed = check.pack(); + let expect = vec![21u8]; + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::InitializeImmutableOwner; + let packed = check.pack(); + let expect = vec![22u8]; + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::AmountToUiAmount { amount: 42 }; + let packed = check.pack(); + let expect = vec![23u8, 42, 0, 0, 0, 0, 0, 0, 0]; + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + + let check = TokenInstruction::UiAmountToAmount { ui_amount: "0.42" }; + let packed = check.pack(); + let expect = vec![24u8, 48, 46, 52, 50]; + assert_eq!(packed, expect); + let unpacked = TokenInstruction::unpack(&expect).unwrap(); + assert_eq!(unpacked, check); + } + + #[test] + fn test_instruction_unpack_panic() { + for i in 0..255u8 { + for j in 1..10 { + let mut data = vec![0; j]; + data[0] = i; + let _no_panic = TokenInstruction::unpack(&data); + } + } + } + + proptest! { + #![proptest_config(ProptestConfig::with_cases(1024))] + #[test] + fn test_instruction_unpack_proptest( + data in prop::collection::vec(any::(), 0..255) + ) { + let _no_panic = TokenInstruction::unpack(&data); + } + } +} diff --git a/vendor/solana/programs/token/src/lib.rs b/vendor/solana/programs/token/src/lib.rs new file mode 100644 index 00000000..25175df7 --- /dev/null +++ b/vendor/solana/programs/token/src/lib.rs @@ -0,0 +1,93 @@ +#![allow(clippy::arithmetic_side_effects)] +#![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 +} + +/// Convert a raw amount to its UI representation (using the decimals field +/// defined in its mint) +pub fn amount_to_ui_amount_string(amount: u64, decimals: u8) -> String { + let decimals = decimals as usize; + if decimals > 0 { + // Left-pad zeros to decimals + 1, so we at least have an integer zero + let mut s = format!("{:01$}", amount, decimals + 1); + // Add the decimal point (Sorry, "," locales!) + s.insert(s.len() - decimals, '.'); + s + } else { + amount.to_string() + } +} + +/// Convert a raw amount to its UI representation using the given decimals field +/// Excess zeroes or unneeded decimal point are trimmed. +pub fn amount_to_ui_amount_string_trimmed(amount: u64, decimals: u8) -> String { + let mut s = amount_to_ui_amount_string(amount, decimals); + if decimals > 0 { + let zeros_trimmed = s.trim_end_matches('0'); + s = zeros_trimmed.trim_end_matches('.').to_string(); + } + s +} + +/// Try to convert a UI representation of a token amount to its raw amount using +/// the given decimals field +pub fn try_ui_amount_into_amount(ui_amount: String, decimals: u8) -> Result { + let decimals = decimals as usize; + let mut parts = ui_amount.split('.'); + // splitting a string, even an empty one, will always yield an iterator of + // at least length == 1 + let mut amount_str = parts.next().unwrap().to_string(); + let after_decimal = parts.next().unwrap_or(""); + let after_decimal = after_decimal.trim_end_matches('0'); + if (amount_str.is_empty() && after_decimal.is_empty()) + || parts.next().is_some() + || after_decimal.len() > decimals + { + return Err(ProgramError::InvalidArgument); + } + + amount_str.push_str(after_decimal); + for _ in 0..decimals.saturating_sub(after_decimal.len()) { + amount_str.push('0'); + } + amount_str + .parse::() + .map_err(|_| ProgramError::InvalidArgument) +} + +solana_program::declare_id!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); + +/// Checks that the supplied program ID is the correct one for SPL-token +pub fn check_program_account(spl_token_program_id: &Pubkey) -> ProgramResult { + if spl_token_program_id != &id() { + return Err(ProgramError::IncorrectProgramId); + } + Ok(()) +} diff --git a/vendor/solana/programs/token/src/native_mint.rs b/vendor/solana/programs/token/src/native_mint.rs new file mode 100644 index 00000000..bd489eee --- /dev/null +++ b/vendor/solana/programs/token/src/native_mint.rs @@ -0,0 +1,23 @@ +//! 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::*, 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/vendor/solana/programs/token/src/processor.rs b/vendor/solana/programs/token/src/processor.rs new file mode 100644 index 00000000..4702693a --- /dev/null +++ b/vendor/solana/programs/token/src/processor.rs @@ -0,0 +1,7026 @@ +//! Program state processor + +use { + crate::{ + amount_to_ui_amount_string_trimmed, + error::TokenError, + instruction::{is_valid_signer_index, AuthorityType, TokenInstruction, MAX_SIGNERS}, + state::{Account, AccountState, Mint, Multisig}, + try_ui_amount_into_amount, + }, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + msg, + program::set_return_data, + program_error::ProgramError, + program_memory::sol_memcmp, + program_option::COption, + program_pack::{IsInitialized, Pack}, + pubkey::{Pubkey, PUBKEY_BYTES}, + system_program, + 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( + program_id: &Pubkey, + 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()); + } + + let is_native_mint = Self::cmp_pubkeys(mint_info.key, &crate::native_mint::id()); + if !is_native_mint { + Self::check_account_owner(program_id, mint_info)?; + let _ = Mint::unpack(&mint_info.data.borrow_mut()) + .map_err(|_| Into::::into(TokenError::InvalidMint))?; + } + + account.mint = *mint_info.key; + account.owner = *owner; + account.close_authority = COption::None; + account.delegate = COption::None; + account.delegated_amount = 0; + account.state = AccountState::Initialized; + if is_native_mint { + 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( + program_id: &Pubkey, + accounts: &[AccountInfo], + ) -> ProgramResult { + Self::_process_initialize_account(program_id, accounts, None, true) + } + + /// Processes an [`InitializeAccount2`](enum.TokenInstruction.html) + /// instruction. + pub fn process_initialize_account2( + program_id: &Pubkey, + accounts: &[AccountInfo], + owner: Pubkey, + ) -> ProgramResult { + Self::_process_initialize_account(program_id, accounts, Some(&owner), true) + } + + /// Processes an [`InitializeAccount3`](enum.TokenInstruction.html) + /// instruction. + pub fn process_initialize_account3( + program_id: &Pubkey, + accounts: &[AccountInfo], + owner: Pubkey, + ) -> ProgramResult { + Self::_process_initialize_account(program_id, 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 destination_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 destination_account = Account::unpack(&destination_account_info.data.borrow())?; + + if source_account.is_frozen() || destination_account.is_frozen() { + return Err(TokenError::AccountFrozen.into()); + } + if source_account.amount < amount { + return Err(TokenError::InsufficientFunds.into()); + } + if !Self::cmp_pubkeys(&source_account.mint, &destination_account.mint) { + return Err(TokenError::MintMismatch.into()); + } + + if let Some((mint_info, expected_decimals)) = expected_mint_info { + if !Self::cmp_pubkeys(mint_info.key, &source_account.mint) { + 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 = + Self::cmp_pubkeys(source_account_info.key, destination_account_info.key); + + match source_account.delegate { + COption::Some(ref delegate) if Self::cmp_pubkeys(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(), + )?, + }; + + if self_transfer || amount == 0 { + Self::check_account_owner(program_id, source_account_info)?; + Self::check_account_owner(program_id, destination_account_info)?; + } + + // 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)?; + destination_account.amount = destination_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 destination_starting_lamports = destination_account_info.lamports(); + **destination_account_info.lamports.borrow_mut() = destination_starting_lamports + .checked_add(amount) + .ok_or(TokenError::Overflow)?; + } + + Account::pack(source_account, &mut source_account_info.data.borrow_mut())?; + Account::pack( + destination_account, + &mut destination_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 !Self::cmp_pubkeys(mint_info.key, &source_account.mint) { + 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 destination_account_info = next_account_info(account_info_iter)?; + let owner_info = next_account_info(account_info_iter)?; + + let mut destination_account = Account::unpack(&destination_account_info.data.borrow())?; + if destination_account.is_frozen() { + return Err(TokenError::AccountFrozen.into()); + } + + if destination_account.is_native() { + return Err(TokenError::NativeNotSupported.into()); + } + if !Self::cmp_pubkeys(mint_info.key, &destination_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()), + } + + if amount == 0 { + Self::check_account_owner(program_id, mint_info)?; + Self::check_account_owner(program_id, destination_account_info)?; + } + + destination_account.amount = destination_account + .amount + .checked_add(amount) + .ok_or(TokenError::Overflow)?; + + mint.supply = mint + .supply + .checked_add(amount) + .ok_or(TokenError::Overflow)?; + + Account::pack( + destination_account, + &mut destination_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 !Self::cmp_pubkeys(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()); + } + } + + if !source_account.is_owned_by_system_program_or_incinerator() { + match source_account.delegate { + COption::Some(ref delegate) if Self::cmp_pubkeys(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(), + )?, + } + } + + if amount == 0 { + Self::check_account_owner(program_id, source_account_info)?; + Self::check_account_owner(program_id, mint_info)?; + } + + 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 destination_account_info = next_account_info(account_info_iter)?; + let authority_info = next_account_info(account_info_iter)?; + + if Self::cmp_pubkeys(source_account_info.key, destination_account_info.key) { + return Err(ProgramError::InvalidAccountData); + } + + let 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); + if !source_account.is_owned_by_system_program_or_incinerator() { + Self::validate_owner( + program_id, + &authority, + authority_info, + account_info_iter.as_slice(), + )?; + } else if !solana_program::incinerator::check_id(destination_account_info.key) { + return Err(ProgramError::InvalidAccountData); + } + + let destination_starting_lamports = destination_account_info.lamports(); + **destination_account_info.lamports.borrow_mut() = destination_starting_lamports + .checked_add(source_account_info.lamports()) + .ok_or(TokenError::Overflow)?; + + **source_account_info.lamports.borrow_mut() = 0; + delete_account(source_account_info)?; + + 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 !Self::cmp_pubkeys(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)?; + Self::check_account_owner(program_id, native_account_info)?; + + 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 a [`GetAccountDataSize`](enum.TokenInstruction.html) + /// instruction + pub fn process_get_account_data_size( + program_id: &Pubkey, + accounts: &[AccountInfo], + ) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + // make sure the mint is valid + let mint_info = next_account_info(account_info_iter)?; + Self::check_account_owner(program_id, mint_info)?; + let _ = Mint::unpack(&mint_info.data.borrow()) + .map_err(|_| Into::::into(TokenError::InvalidMint))?; + set_return_data(&Account::LEN.to_le_bytes()); + Ok(()) + } + + /// Processes an [`InitializeImmutableOwner`](enum.TokenInstruction.html) + /// instruction + pub fn process_initialize_immutable_owner(accounts: &[AccountInfo]) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let token_account_info = next_account_info(account_info_iter)?; + let account = Account::unpack_unchecked(&token_account_info.data.borrow())?; + if account.is_initialized() { + return Err(TokenError::AlreadyInUse.into()); + } + msg!("Please upgrade to SPL Token 2022 for immutable owner support"); + Ok(()) + } + + /// Processes an [`AmountToUiAmount`](enum.TokenInstruction.html) + /// instruction + pub fn process_amount_to_ui_amount( + program_id: &Pubkey, + accounts: &[AccountInfo], + amount: u64, + ) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let mint_info = next_account_info(account_info_iter)?; + Self::check_account_owner(program_id, mint_info)?; + + let mint = Mint::unpack(&mint_info.data.borrow_mut()) + .map_err(|_| Into::::into(TokenError::InvalidMint))?; + let ui_amount = amount_to_ui_amount_string_trimmed(amount, mint.decimals); + + set_return_data(&ui_amount.into_bytes()); + Ok(()) + } + + /// Processes an [`AmountToUiAmount`](enum.TokenInstruction.html) + /// instruction + pub fn process_ui_amount_to_amount( + program_id: &Pubkey, + accounts: &[AccountInfo], + ui_amount: &str, + ) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let mint_info = next_account_info(account_info_iter)?; + Self::check_account_owner(program_id, mint_info)?; + + let mint = Mint::unpack(&mint_info.data.borrow_mut()) + .map_err(|_| Into::::into(TokenError::InvalidMint))?; + let amount = try_ui_amount_into_amount(ui_amount.to_string(), mint.decimals)?; + + set_return_data(&amount.to_le_bytes()); + 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(program_id, accounts) + } + TokenInstruction::InitializeAccount2 { owner } => { + msg!("Instruction: InitializeAccount2"); + Self::process_initialize_account2(program_id, accounts, owner) + } + TokenInstruction::InitializeAccount3 { owner } => { + msg!("Instruction: InitializeAccount3"); + Self::process_initialize_account3(program_id, 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) + } + TokenInstruction::GetAccountDataSize => { + msg!("Instruction: GetAccountDataSize"); + Self::process_get_account_data_size(program_id, accounts) + } + TokenInstruction::InitializeImmutableOwner => { + msg!("Instruction: InitializeImmutableOwner"); + Self::process_initialize_immutable_owner(accounts) + } + TokenInstruction::AmountToUiAmount { amount } => { + msg!("Instruction: AmountToUiAmount"); + Self::process_amount_to_ui_amount(program_id, accounts, amount) + } + TokenInstruction::UiAmountToAmount { ui_amount } => { + msg!("Instruction: UiAmountToAmount"); + Self::process_ui_amount_to_amount(program_id, accounts, ui_amount) + } + } + } + + /// Checks that the account is owned by the expected program + pub fn check_account_owner(program_id: &Pubkey, account_info: &AccountInfo) -> ProgramResult { + if !Self::cmp_pubkeys(program_id, account_info.owner) { + Err(ProgramError::IncorrectProgramId) + } else { + Ok(()) + } + } + + /// Checks two pubkeys for equality in a computationally cheap way using + /// `sol_memcmp` + pub fn cmp_pubkeys(a: &Pubkey, b: &Pubkey) -> bool { + sol_memcmp(a.as_ref(), b.as_ref(), PUBKEY_BYTES) == 0 + } + + /// Validates owner(s) are present + pub fn validate_owner( + program_id: &Pubkey, + expected_owner: &Pubkey, + owner_account_info: &AccountInfo, + signers: &[AccountInfo], + ) -> ProgramResult { + if !Self::cmp_pubkeys(expected_owner, owner_account_info.key) { + return Err(TokenError::OwnerMismatch.into()); + } + if Self::cmp_pubkeys(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 Self::cmp_pubkeys(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(()) + } +} + +/// Helper function to mostly delete an account in a test environment. We could +/// potentially muck around the bytes assuming that a vec is passed in, but that +/// would be more trouble than it's worth. +#[cfg(not(target_os = "solana"))] +fn delete_account(account_info: &AccountInfo) -> Result<(), ProgramError> { + account_info.assign(&system_program::id()); + let mut account_data = account_info.data.borrow_mut(); + let data_len = account_data.len(); + solana_program::program_memory::sol_memset(*account_data, 0, data_len); + Ok(()) +} + +/// Helper function to totally delete an account on-chain +#[cfg(target_os = "solana")] +fn delete_account(account_info: &AccountInfo) -> Result<(), ProgramError> { + account_info.assign(&system_program::id()); + account_info.realloc(0, false) +} + +#[cfg(test)] +mod tests { + use { + super::*, + crate::instruction::*, + serial_test::serial, + solana_program::{ + account_info::IntoAccountInfo, + clock::Epoch, + instruction::Instruction, + program_error::{self, PrintProgramError}, + sysvar::rent, + }, + solana_sdk::account::{ + create_account_for_test, create_is_signer_account_infos, Account as SolanaAccount, + }, + std::sync::{Arc, RwLock}, + }; + + lazy_static::lazy_static! { + static ref EXPECTED_DATA: Arc>> = Arc::new(RwLock::new(Vec::new())); + } + + fn set_expected_data(expected_data: Vec) { + *EXPECTED_DATA.write().unwrap() = expected_data; + } + + 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 sol_set_return_data(&self, data: &[u8]) { + assert_eq!(&*EXPECTED_DATA.write().unwrap(), data) + } + } + + 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] + fn test_error_as_custom() { + assert_eq!( + return_token_error_as_program_error(), + ProgramError::Custom(3) + ); + } + + #[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_from_array([1; 32])), + supply: 42, + decimals: 7, + is_initialized: true, + freeze_authority: COption::Some(Pubkey::new_from_array([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_from_array([1; 32]), + owner: Pubkey::new_from_array([2; 32]), + amount: 3, + delegate: COption::Some(Pubkey::new_from_array([4; 32])), + state: AccountState::Frozen, + is_native: COption::Some(5), + delegated_amount: 6, + close_authority: COption::Some(Pubkey::new_from_array([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_from_array([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(); + + // mint not owned by program + let not_program_id = Pubkey::new_unique(); + mint_account.owner = not_program_id; + assert_eq!( + Err(ProgramError::IncorrectProgramId), + 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 + ], + ) + ); + mint_account.owner = program_id; + + // create account + do_process_instruction( + initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(), + vec![ + &mut account_account, + &mut mint_account, + &mut owner_account, + &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, + ], + ) + ); + + // account not owned by program + let not_program_id = Pubkey::new_unique(); + account_account.owner = not_program_id; + assert_eq!( + Err(ProgramError::IncorrectProgramId), + do_process_instruction( + transfer(&program_id, &account_key, &account2_key, &owner_key, &[], 0,).unwrap(), + vec![ + &mut account_account, + &mut account2_account, + &mut owner2_account, + ], + ) + ); + account_account.owner = program_id; + + // account 2 not owned by program + let not_program_id = Pubkey::new_unique(); + account2_account.owner = not_program_id; + assert_eq!( + Err(ProgramError::IncorrectProgramId), + do_process_instruction( + transfer(&program_id, &account_key, &account2_key, &owner_key, &[], 0,).unwrap(), + vec![ + &mut account_account, + &mut account2_account, + &mut owner2_account, + ], + ) + ); + account2_account.owner = program_id; + + // 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(); + + // not a delegate of source account + assert_eq!( + Err(TokenError::OwnerMismatch.into()), + do_process_instruction( + transfer( + &program_id, + &account_key, + &account2_key, + &owner2_key, // <-- incorrect owner or delegate + &[], + 1, + ) + .unwrap(), + vec![ + &mut account_account, + &mut account2_account, + &mut owner2_account, + ], + ) + ); + + // insufficient funds approved via delegate + assert_eq!( + Err(TokenError::InsufficientFunds.into()), + do_process_instruction( + transfer( + &program_id, + &account_key, + &account2_key, + &delegate_key, + &[], + 101 + ) + .unwrap(), + vec![ + &mut account_account, + &mut account2_account, + &mut delegate_account, + ], + ) + ); + + // 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, + &[], + 1 + ) + .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, + ], + ) + ); + + // mint not owned by program + let not_program_id = Pubkey::new_unique(); + mint_account.owner = not_program_id; + assert_eq!( + Err(ProgramError::IncorrectProgramId), + do_process_instruction( + mint_to(&program_id, &mint_key, &account_key, &owner_key, &[], 0).unwrap(), + vec![&mut mint_account, &mut account_account, &mut owner_account], + ) + ); + mint_account.owner = program_id; + + // account not owned by program + let not_program_id = Pubkey::new_unique(); + account_account.owner = not_program_id; + assert_eq!( + Err(ProgramError::IncorrectProgramId), + do_process_instruction( + mint_to(&program_id, &mint_key, &account_key, &owner_key, &[], 0).unwrap(), + vec![&mut mint_account, &mut account_account, &mut owner_account], + ) + ); + account_account.owner = program_id; + + // 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], + ) + ); + + // account not owned by program + let not_program_id = Pubkey::new_unique(); + account_account.owner = not_program_id; + assert_eq!( + Err(ProgramError::IncorrectProgramId), + do_process_instruction( + burn(&program_id, &account_key, &mint_key, &owner_key, &[], 0).unwrap(), + vec![&mut account_account, &mut mint_account, &mut owner_account], + ) + ); + account_account.owner = program_id; + + // mint not owned by program + let not_program_id = Pubkey::new_unique(); + mint_account.owner = not_program_id; + assert_eq!( + Err(ProgramError::IncorrectProgramId), + do_process_instruction( + burn(&program_id, &account_key, &mint_key, &owner_key, &[], 0).unwrap(), + vec![&mut account_account, &mut mint_account, &mut owner_account], + ) + ); + mint_account.owner = program_id; + + // 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::OwnerMismatch.into()), + do_process_instruction( + burn( + &program_id, + &account_key, + &mint_key, + &owner2_key, // <-- incorrect owner or delegate + &[], + 1, + ) + .unwrap(), + vec![&mut account_account, &mut mint_account, &mut owner2_account], + ) + ); + + // insufficient funds approved via delegate + assert_eq!( + Err(TokenError::InsufficientFunds.into()), + do_process_instruction( + burn(&program_id, &account_key, &mint_key, &delegate_key, &[], 85).unwrap(), + vec![ + &mut account_account, + &mut mint_account, + &mut delegate_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, &[], 1).unwrap(), + vec![ + &mut account_account, + &mut mint_account, + &mut delegate_account + ], + ) + ); + } + + #[test] + fn test_burn_and_close_system_and_incinerator_tokens() { + 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 incinerator_account_key = Pubkey::new_unique(); + let mut incinerator_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let system_account_key = Pubkey::new_unique(); + let mut system_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 recipient_key = Pubkey::new_unique(); + let mut recipient_account = SolanaAccount::default(); + let mut mock_incinerator_account = SolanaAccount::default(); + let mint_key = Pubkey::new_unique(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + + // create new mint + do_process_instruction( + initialize_mint2(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account], + ) + .unwrap(); + + // create account + do_process_instruction( + initialize_account3(&program_id, &account_key, &mint_key, &owner_key).unwrap(), + vec![&mut account_account, &mut mint_account], + ) + .unwrap(); + + // create incinerator- and system-owned accounts + do_process_instruction( + initialize_account3( + &program_id, + &incinerator_account_key, + &mint_key, + &solana_program::incinerator::id(), + ) + .unwrap(), + vec![&mut incinerator_account, &mut mint_account], + ) + .unwrap(); + do_process_instruction( + initialize_account3( + &program_id, + &system_account_key, + &mint_key, + &solana_program::system_program::id(), + ) + .unwrap(), + vec![&mut system_account, &mut mint_account], + ) + .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(); + + // transfer half to incinerator, half to system program + do_process_instruction( + transfer( + &program_id, + &account_key, + &incinerator_account_key, + &owner_key, + &[], + 500, + ) + .unwrap(), + vec![ + &mut account_account, + &mut incinerator_account, + &mut owner_account, + ], + ) + .unwrap(); + do_process_instruction( + transfer( + &program_id, + &account_key, + &system_account_key, + &owner_key, + &[], + 500, + ) + .unwrap(), + vec![ + &mut account_account, + &mut system_account, + &mut owner_account, + ], + ) + .unwrap(); + + // close with balance fails + assert_eq!( + Err(TokenError::NonNativeHasBalance.into()), + do_process_instruction( + close_account( + &program_id, + &incinerator_account_key, + &solana_program::incinerator::id(), + &owner_key, + &[] + ) + .unwrap(), + vec![ + &mut incinerator_account, + &mut mock_incinerator_account, + &mut owner_account, + ], + ) + ); + assert_eq!( + Err(TokenError::NonNativeHasBalance.into()), + do_process_instruction( + close_account( + &program_id, + &system_account_key, + &solana_program::incinerator::id(), + &owner_key, + &[] + ) + .unwrap(), + vec![ + &mut system_account, + &mut mock_incinerator_account, + &mut owner_account, + ], + ) + ); + + // anyone can burn + do_process_instruction( + burn( + &program_id, + &incinerator_account_key, + &mint_key, + &recipient_key, + &[], + 500, + ) + .unwrap(), + vec![ + &mut incinerator_account, + &mut mint_account, + &mut recipient_account, + ], + ) + .unwrap(); + do_process_instruction( + burn( + &program_id, + &system_account_key, + &mint_key, + &recipient_key, + &[], + 500, + ) + .unwrap(), + vec![ + &mut system_account, + &mut mint_account, + &mut recipient_account, + ], + ) + .unwrap(); + + // closing fails if destination is not the incinerator + assert_eq!( + Err(ProgramError::InvalidAccountData), + do_process_instruction( + close_account( + &program_id, + &incinerator_account_key, + &recipient_key, + &owner_key, + &[] + ) + .unwrap(), + vec![ + &mut incinerator_account, + &mut recipient_account, + &mut owner_account, + ], + ) + ); + assert_eq!( + Err(ProgramError::InvalidAccountData), + do_process_instruction( + close_account( + &program_id, + &system_account_key, + &recipient_key, + &owner_key, + &[] + ) + .unwrap(), + vec![ + &mut system_account, + &mut recipient_account, + &mut owner_account, + ], + ) + ); + + // closing succeeds with incinerator recipient + do_process_instruction( + close_account( + &program_id, + &incinerator_account_key, + &solana_program::incinerator::id(), + &owner_key, + &[], + ) + .unwrap(), + vec![ + &mut incinerator_account, + &mut mock_incinerator_account, + &mut owner_account, + ], + ) + .unwrap(); + + do_process_instruction( + close_account( + &program_id, + &system_account_key, + &solana_program::incinerator::id(), + &owner_key, + &[], + ) + .unwrap(), + vec![ + &mut system_account, + &mut mock_incinerator_account, + &mut owner_account, + ], + ) + .unwrap(); + } + + #[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, + 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, + 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, 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, + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + 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, + 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, + 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, + 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, + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + 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, + 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, + 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, + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + account_info_iter.next().unwrap(), + 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, + 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, + 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, + 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, + 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_owner_close_account_dups() { + let program_id = crate::id(); + 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(); + + let to_close_key = Pubkey::new_unique(); + let mut to_close_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let to_close_account_info: AccountInfo = + (&to_close_key, true, &mut to_close_account).into(); + let destination_account_key = Pubkey::new_unique(); + let mut destination_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let destination_account_info: AccountInfo = + (&destination_account_key, true, &mut destination_account).into(); + // create account + do_process_instruction_dups( + initialize_account(&program_id, &to_close_key, &mint_key, &to_close_key).unwrap(), + vec![ + to_close_account_info.clone(), + mint_info.clone(), + to_close_account_info.clone(), + rent_info.clone(), + ], + ) + .unwrap(); + + // source-owner close + do_process_instruction_dups( + close_account( + &program_id, + &to_close_key, + &destination_account_key, + &to_close_key, + &[], + ) + .unwrap(), + vec![ + to_close_account_info.clone(), + destination_account_info.clone(), + to_close_account_info.clone(), + ], + ) + .unwrap(); + assert_eq!(*to_close_account_info.data.borrow(), &[0u8; Account::LEN]); + } + + #[test] + fn test_close_authority_close_account_dups() { + let program_id = crate::id(); + 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(); + + let to_close_key = Pubkey::new_unique(); + let mut to_close_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let to_close_account_info: AccountInfo = + (&to_close_key, true, &mut to_close_account).into(); + let destination_account_key = Pubkey::new_unique(); + let mut destination_account = SolanaAccount::new( + account_minimum_balance(), + Account::get_packed_len(), + &program_id, + ); + let destination_account_info: AccountInfo = + (&destination_account_key, true, &mut destination_account).into(); + // create account + do_process_instruction_dups( + initialize_account(&program_id, &to_close_key, &mint_key, &to_close_key).unwrap(), + vec![ + to_close_account_info.clone(), + mint_info.clone(), + to_close_account_info.clone(), + rent_info.clone(), + ], + ) + .unwrap(); + let mut account = Account::unpack_unchecked(&to_close_account_info.data.borrow()).unwrap(); + account.close_authority = COption::Some(to_close_key); + account.owner = owner_key; + Account::pack(account, &mut to_close_account_info.data.borrow_mut()).unwrap(); + do_process_instruction_dups( + close_account( + &program_id, + &to_close_key, + &destination_account_key, + &to_close_key, + &[], + ) + .unwrap(), + vec![ + to_close_account_info.clone(), + destination_account_info.clone(), + to_close_account_info.clone(), + ], + ) + .unwrap(); + assert_eq!(*to_close_account_info.data.borrow(), &[0u8; Account::LEN]); + } + + #[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(); + assert_eq!(account2_account.data, [0u8; Account::LEN]); + 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()); + assert_eq!(account_account.data, [0u8; Account::LEN]); + } + + #[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); + + // attempt 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(); + + // fail sync, not owned by program + let not_program_id = Pubkey::new_unique(); + native_account.owner = not_program_id; + assert_eq!( + Err(ProgramError::IncorrectProgramId), + do_process_instruction( + sync_native(&program_id, &native_account_key,).unwrap(), + vec![&mut native_account], + ) + ); + native_account.owner = program_id; + + 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], + ) + ); + } + + #[test] + #[serial] + fn test_get_account_data_size() { + // see integration tests for return-data validity + let program_id = crate::id(); + let owner_key = Pubkey::new_unique(); + let mut rent_sysvar = rent_sysvar(); + let mut mint_account = + SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id); + let mint_key = Pubkey::new_unique(); + // fail if an invalid mint is passed in + assert_eq!( + Err(TokenError::InvalidMint.into()), + do_process_instruction( + get_account_data_size(&program_id, &mint_key).unwrap(), + vec![&mut mint_account], + ) + ); + + do_process_instruction( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + set_expected_data(Account::LEN.to_le_bytes().to_vec()); + do_process_instruction( + get_account_data_size(&program_id, &mint_key).unwrap(), + vec![&mut mint_account], + ) + .unwrap(); + } + + #[test] + fn test_initialize_immutable_owner() { + 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 + do_process_instruction( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + // success initialize immutable + do_process_instruction( + initialize_immutable_owner(&program_id, &account_key).unwrap(), + vec![&mut account_account], + ) + .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(); + + // fail post-init + assert_eq!( + Err(TokenError::AlreadyInUse.into()), + do_process_instruction( + initialize_immutable_owner(&program_id, &account_key).unwrap(), + vec![&mut account_account], + ) + ); + } + + #[test] + #[serial] + fn test_amount_to_ui_amount() { + let program_id = crate::id(); + 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 mut rent_sysvar = rent_sysvar(); + + // fail if an invalid mint is passed in + assert_eq!( + Err(TokenError::InvalidMint.into()), + do_process_instruction( + amount_to_ui_amount(&program_id, &mint_key, 110).unwrap(), + vec![&mut mint_account], + ) + ); + + // create mint + do_process_instruction( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + set_expected_data("0.23".as_bytes().to_vec()); + do_process_instruction( + amount_to_ui_amount(&program_id, &mint_key, 23).unwrap(), + vec![&mut mint_account], + ) + .unwrap(); + + set_expected_data("1.1".as_bytes().to_vec()); + do_process_instruction( + amount_to_ui_amount(&program_id, &mint_key, 110).unwrap(), + vec![&mut mint_account], + ) + .unwrap(); + + set_expected_data("42".as_bytes().to_vec()); + do_process_instruction( + amount_to_ui_amount(&program_id, &mint_key, 4200).unwrap(), + vec![&mut mint_account], + ) + .unwrap(); + + set_expected_data("0".as_bytes().to_vec()); + do_process_instruction( + amount_to_ui_amount(&program_id, &mint_key, 0).unwrap(), + vec![&mut mint_account], + ) + .unwrap(); + } + + #[test] + #[serial] + fn test_ui_amount_to_amount() { + let program_id = crate::id(); + 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 mut rent_sysvar = rent_sysvar(); + + // fail if an invalid mint is passed in + assert_eq!( + Err(TokenError::InvalidMint.into()), + do_process_instruction( + ui_amount_to_amount(&program_id, &mint_key, "1.1").unwrap(), + vec![&mut mint_account], + ) + ); + + // create mint + do_process_instruction( + initialize_mint(&program_id, &mint_key, &owner_key, None, 2).unwrap(), + vec![&mut mint_account, &mut rent_sysvar], + ) + .unwrap(); + + set_expected_data(23u64.to_le_bytes().to_vec()); + do_process_instruction( + ui_amount_to_amount(&program_id, &mint_key, "0.23").unwrap(), + vec![&mut mint_account], + ) + .unwrap(); + + set_expected_data(20u64.to_le_bytes().to_vec()); + do_process_instruction( + ui_amount_to_amount(&program_id, &mint_key, "0.20").unwrap(), + vec![&mut mint_account], + ) + .unwrap(); + + set_expected_data(20u64.to_le_bytes().to_vec()); + do_process_instruction( + ui_amount_to_amount(&program_id, &mint_key, "0.2000").unwrap(), + vec![&mut mint_account], + ) + .unwrap(); + + set_expected_data(20u64.to_le_bytes().to_vec()); + do_process_instruction( + ui_amount_to_amount(&program_id, &mint_key, ".20").unwrap(), + vec![&mut mint_account], + ) + .unwrap(); + + set_expected_data(110u64.to_le_bytes().to_vec()); + do_process_instruction( + ui_amount_to_amount(&program_id, &mint_key, "1.1").unwrap(), + vec![&mut mint_account], + ) + .unwrap(); + + set_expected_data(110u64.to_le_bytes().to_vec()); + do_process_instruction( + ui_amount_to_amount(&program_id, &mint_key, "1.10").unwrap(), + vec![&mut mint_account], + ) + .unwrap(); + + set_expected_data(4200u64.to_le_bytes().to_vec()); + do_process_instruction( + ui_amount_to_amount(&program_id, &mint_key, "42").unwrap(), + vec![&mut mint_account], + ) + .unwrap(); + + set_expected_data(4200u64.to_le_bytes().to_vec()); + do_process_instruction( + ui_amount_to_amount(&program_id, &mint_key, "42.").unwrap(), + vec![&mut mint_account], + ) + .unwrap(); + + set_expected_data(0u64.to_le_bytes().to_vec()); + do_process_instruction( + ui_amount_to_amount(&program_id, &mint_key, "0").unwrap(), + vec![&mut mint_account], + ) + .unwrap(); + + // fail if invalid ui_amount passed in + assert_eq!( + Err(ProgramError::InvalidArgument), + do_process_instruction( + ui_amount_to_amount(&program_id, &mint_key, "").unwrap(), + vec![&mut mint_account], + ) + ); + assert_eq!( + Err(ProgramError::InvalidArgument), + do_process_instruction( + ui_amount_to_amount(&program_id, &mint_key, ".").unwrap(), + vec![&mut mint_account], + ) + ); + assert_eq!( + Err(ProgramError::InvalidArgument), + do_process_instruction( + ui_amount_to_amount(&program_id, &mint_key, "0.111").unwrap(), + vec![&mut mint_account], + ) + ); + assert_eq!( + Err(ProgramError::InvalidArgument), + do_process_instruction( + ui_amount_to_amount(&program_id, &mint_key, "0.t").unwrap(), + vec![&mut mint_account], + ) + ); + } +} diff --git a/vendor/solana/programs/token/src/state.rs b/vendor/solana/programs/token/src/state.rs new file mode 100644 index 00000000..f7595e79 --- /dev/null +++ b/vendor/solana/programs/token/src/state.rs @@ -0,0 +1,492 @@ +//! State transition types + +use { + crate::instruction::MAX_SIGNERS, + arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}, + num_enum::TryFromPrimitive, + solana_program::{ + program_error::ProgramError, + program_option::COption, + program_pack::{IsInitialized, Pack, Sealed}, + pubkey::{Pubkey, PUBKEY_BYTES}, + }, +}; + +/// Mint data. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Mint { + /// Optional authority used to mint new tokens. The mint authority may only + /// be provided during mint creation. If no mint authority is present + /// then the mint has a fixed supply and no further tokens may be + /// minted. + pub mint_authority: COption, + /// 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_native.is_some`, this is a native token, and the value logs the + /// rent-exempt reserve. An Account is required to be rent-exempt, so + /// the value is used by the Processor to ensure that wrapped SOL + /// accounts do not drop below this threshold. + pub is_native: COption, + /// 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() + } + /// Checks if a token Account's owner is the `system_program` or the + /// incinerator + pub fn is_owned_by_system_program_or_incinerator(&self) -> bool { + solana_program::system_program::check_id(&self.owner) + || solana_program::incinerator::check_id(&self.owner) + } +} +impl Sealed for Account {} +impl IsInitialized for Account { + fn is_initialized(&self) -> bool { + self.state != AccountState::Uninitialized + } +} +impl Pack for Account { + const LEN: usize = 165; + fn unpack_from_slice(src: &[u8]) -> Result { + let src = array_ref![src, 0, 165]; + let (mint, owner, amount, delegate, state, is_native, delegated_amount, close_authority) = + array_refs![src, 32, 32, 8, 36, 1, 12, 8, 36]; + Ok(Account { + mint: Pubkey::new_from_array(*mint), + owner: Pubkey::new_from_array(*owner), + amount: u64::from_le_bytes(*amount), + delegate: unpack_coption_key(delegate)?, + state: AccountState::try_from_primitive(state[0]) + .or(Err(ProgramError::InvalidAccountData))?, + is_native: unpack_coption_u64(is_native)?, + delegated_amount: u64::from_le_bytes(*delegated_amount), + close_authority: unpack_coption_key(close_authority)?, + }) + } + fn pack_into_slice(&self, dst: &mut [u8]) { + let dst = array_mut_ref![dst, 0, 165]; + let ( + mint_dst, + owner_dst, + amount_dst, + delegate_dst, + state_dst, + is_native_dst, + delegated_amount_dst, + close_authority_dst, + ) = mut_array_refs![dst, 32, 32, 8, 36, 1, 12, 8, 36]; + let &Account { + ref mint, + ref owner, + amount, + ref delegate, + state, + ref is_native, + delegated_amount, + ref close_authority, + } = self; + mint_dst.copy_from_slice(mint.as_ref()); + owner_dst.copy_from_slice(owner.as_ref()); + *amount_dst = amount.to_le_bytes(); + pack_coption_key(delegate, delegate_dst); + state_dst[0] = state as u8; + pack_coption_u64(is_native, is_native_dst); + *delegated_amount_dst = delegated_amount.to_le_bytes(); + pack_coption_key(close_authority, close_authority_dst); + } +} + +/// Account state. +#[repr(u8)] +#[derive(Clone, Copy, Debug, Default, PartialEq, TryFromPrimitive)] +pub enum AccountState { + /// Account is not yet initialized + #[default] + Uninitialized, + /// Account is initialized; the account owner and/or delegate may perform + /// permitted operations on this account + Initialized, + /// Account has been frozen by the mint freeze authority. Neither the + /// account owner nor the delegate are able to perform operations on + /// this account. + Frozen, +} + +/// Multisignature data. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Multisig { + /// Number of signers required + pub m: u8, + /// Number of valid signers + pub n: u8, + /// Is `true` if this structure has been initialized + pub is_initialized: bool, + /// Signer public keys + pub signers: [Pubkey; MAX_SIGNERS], +} +impl Sealed for Multisig {} +impl IsInitialized for Multisig { + fn is_initialized(&self) -> bool { + self.is_initialized + } +} +impl Pack for Multisig { + const LEN: usize = 355; + fn unpack_from_slice(src: &[u8]) -> Result { + let src = array_ref![src, 0, 355]; + #[allow(clippy::ptr_offset_with_cast)] + let (m, n, is_initialized, signers_flat) = array_refs![src, 1, 1, 1, 32 * MAX_SIGNERS]; + let mut result = Multisig { + m: m[0], + n: n[0], + is_initialized: match is_initialized { + [0] => false, + [1] => true, + _ => return Err(ProgramError::InvalidAccountData), + }, + signers: [Pubkey::new_from_array([0u8; 32]); MAX_SIGNERS], + }; + for (src, dst) in signers_flat.chunks(32).zip(result.signers.iter_mut()) { + *dst = Pubkey::try_from(src).map_err(|_| ProgramError::InvalidAccountData)?; + } + Ok(result) + } + fn pack_into_slice(&self, dst: &mut [u8]) { + let dst = array_mut_ref![dst, 0, 355]; + #[allow(clippy::ptr_offset_with_cast)] + let (m, n, is_initialized, signers_flat) = mut_array_refs![dst, 1, 1, 1, 32 * MAX_SIGNERS]; + *m = [self.m]; + *n = [self.n]; + *is_initialized = [self.is_initialized as u8]; + for (i, src) in self.signers.iter().enumerate() { + let dst_array = array_mut_ref![signers_flat, 32 * i, 32]; + dst_array.copy_from_slice(src.as_ref()); + } + } +} + +// Helpers +fn pack_coption_key(src: &COption, 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), + } +} + +const SPL_TOKEN_ACCOUNT_MINT_OFFSET: usize = 0; +const SPL_TOKEN_ACCOUNT_OWNER_OFFSET: usize = 32; + +/// A trait for token Account structs to enable efficiently unpacking various +/// fields without unpacking the complete state. +pub trait GenericTokenAccount { + /// Check if the account data is a valid token account + fn valid_account_data(account_data: &[u8]) -> bool; + + /// Call after account length has already been verified to unpack the + /// account owner + fn unpack_account_owner_unchecked(account_data: &[u8]) -> &Pubkey { + Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_OWNER_OFFSET) + } + + /// Call after account length has already been verified to unpack the + /// account mint + fn unpack_account_mint_unchecked(account_data: &[u8]) -> &Pubkey { + Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_MINT_OFFSET) + } + + /// Call after account length has already been verified to unpack a Pubkey + /// at the specified offset. Panics if `account_data.len()` is less than + /// `PUBKEY_BYTES` + fn unpack_pubkey_unchecked(account_data: &[u8], offset: usize) -> &Pubkey { + bytemuck::from_bytes(&account_data[offset..offset + PUBKEY_BYTES]) + } + + /// Unpacks an account's owner from opaque account data. + fn unpack_account_owner(account_data: &[u8]) -> Option<&Pubkey> { + if Self::valid_account_data(account_data) { + Some(Self::unpack_account_owner_unchecked(account_data)) + } else { + None + } + } + + /// Unpacks an account's mint from opaque account data. + fn unpack_account_mint(account_data: &[u8]) -> Option<&Pubkey> { + if Self::valid_account_data(account_data) { + Some(Self::unpack_account_mint_unchecked(account_data)) + } else { + None + } + } +} + +/// The offset of state field in Account's C representation +pub const ACCOUNT_INITIALIZED_INDEX: usize = 108; + +/// Check if the account data buffer represents an initialized account. +/// This is checking the `state` (`AccountState`) field of an Account object. +pub fn is_initialized_account(account_data: &[u8]) -> bool { + *account_data + .get(ACCOUNT_INITIALIZED_INDEX) + .unwrap_or(&(AccountState::Uninitialized as u8)) + != AccountState::Uninitialized as u8 +} + +impl GenericTokenAccount for Account { + fn valid_account_data(account_data: &[u8]) -> bool { + account_data.len() == Account::LEN && is_initialized_account(account_data) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mint_unpack_from_slice() { + let src: [u8; 82] = [0; 82]; + let mint = Mint::unpack_from_slice(&src).unwrap(); + assert!(!mint.is_initialized); + + let mut src: [u8; 82] = [0; 82]; + src[45] = 2; + let mint = Mint::unpack_from_slice(&src).unwrap_err(); + assert_eq!(mint, ProgramError::InvalidAccountData); + } + + #[test] + fn test_account_state() { + let account_state = AccountState::default(); + assert_eq!(account_state, AccountState::Uninitialized); + } + + #[test] + fn test_multisig_unpack_from_slice() { + let src: [u8; 355] = [0; 355]; + let multisig = Multisig::unpack_from_slice(&src).unwrap(); + assert_eq!(multisig.m, 0); + assert_eq!(multisig.n, 0); + assert!(!multisig.is_initialized); + + let mut src: [u8; 355] = [0; 355]; + src[0] = 1; + src[1] = 1; + src[2] = 1; + let multisig = Multisig::unpack_from_slice(&src).unwrap(); + assert_eq!(multisig.m, 1); + assert_eq!(multisig.n, 1); + assert!(multisig.is_initialized); + + let mut src: [u8; 355] = [0; 355]; + src[2] = 2; + let multisig = Multisig::unpack_from_slice(&src).unwrap_err(); + assert_eq!(multisig, ProgramError::InvalidAccountData); + } + + #[test] + fn test_unpack_coption_key() { + let src: [u8; 36] = [0; 36]; + let result = unpack_coption_key(&src).unwrap(); + assert_eq!(result, COption::None); + + let mut src: [u8; 36] = [0; 36]; + src[1] = 1; + let result = unpack_coption_key(&src).unwrap_err(); + assert_eq!(result, ProgramError::InvalidAccountData); + } + + #[test] + fn test_unpack_coption_u64() { + let src: [u8; 12] = [0; 12]; + let result = unpack_coption_u64(&src).unwrap(); + assert_eq!(result, COption::None); + + let mut src: [u8; 12] = [0; 12]; + src[0] = 1; + let result = unpack_coption_u64(&src).unwrap(); + assert_eq!(result, COption::Some(0)); + + let mut src: [u8; 12] = [0; 12]; + src[1] = 1; + let result = unpack_coption_u64(&src).unwrap_err(); + assert_eq!(result, ProgramError::InvalidAccountData); + } + + #[test] + fn test_unpack_token_owner() { + // Account data length < Account::LEN, unpack will not return a key + let src: [u8; 12] = [0; 12]; + let result = Account::unpack_account_owner(&src); + assert_eq!(result, Option::None); + + // The right account data size and initialized, unpack will return some key + let mut src: [u8; Account::LEN] = [0; Account::LEN]; + src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8; + let result = Account::unpack_account_owner(&src); + assert!(result.is_some()); + + // The right account data size and frozen, unpack will return some key + src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8; + let result = Account::unpack_account_owner(&src); + assert!(result.is_some()); + + // The right account data size and uninitialized, unpack will return None + src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8; + let result = Account::unpack_account_mint(&src); + assert_eq!(result, Option::None); + + // Account data length > account data size, unpack will not return a key + let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5]; + let result = Account::unpack_account_owner(&src); + assert_eq!(result, Option::None); + } + + #[test] + fn test_unpack_token_mint() { + // Account data length < Account::LEN, unpack will not return a key + let src: [u8; 12] = [0; 12]; + let result = Account::unpack_account_mint(&src); + assert_eq!(result, Option::None); + + // The right account data size and initialized, unpack will return some key + let mut src: [u8; Account::LEN] = [0; Account::LEN]; + src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8; + let result = Account::unpack_account_mint(&src); + assert!(result.is_some()); + + // The right account data size and frozen, unpack will return some key + src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8; + let result = Account::unpack_account_mint(&src); + assert!(result.is_some()); + + // The right account data size and uninitialized, unpack will return None + src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8; + let result = Account::unpack_account_mint(&src); + assert_eq!(result, Option::None); + + // Account data length > account data size, unpack will not return a key + let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5]; + let result = Account::unpack_account_mint(&src); + assert_eq!(result, Option::None); + } +}