Browse Source

Merge pull request #84 from solana-developers/check-transferring-flag

Add isTransferring check to transfer hook examples
John 1 year ago
parent
commit
dc2591459f

+ 3 - 7
tokens/token-2022/transfer-hook/counter/anchor/Anchor.toml

@@ -1,11 +1,6 @@
-[toolchain]
-
-[features]
-seeds = false
-skip-lint = false
 
 [programs.localnet]
-transfer_hook = "DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub"
+transfer_hook = "1qahDxKHeCLZhbBU2NyMU6vQCQmEUmdeSEBrG5drffK"
 
 [registry]
 url = "https://api.apr.dev"
@@ -15,4 +10,5 @@ cluster = "Localnet"
 wallet = "~/.config/solana/id.json"
 
 [scripts]
-test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
+test = "yarn run ts-mocha -p ./tsconfig.json -t 100000 tests/**/*.ts"
+

+ 4 - 2
tokens/token-2022/transfer-hook/counter/anchor/package.json

@@ -6,13 +6,15 @@
   "dependencies": {
     "@coral-xyz/anchor": "^0.30.0",
     "@solana/spl-token": "^0.4.0",
-    "@solana/web3.js": "^1.89.1"
+    "@solana/web3.js": "^1.92.3"
   },
   "devDependencies": {
     "@types/bn.js": "^5.1.0",
+    "@types/chai-as-promised": "^7.1.8",
+    "chai-as-promised": "^7.1.2",
     "@types/chai": "^4.3.0",
-    "@types/mocha": "^9.0.0",
     "chai": "^4.3.4",
+    "@types/mocha": "^9.0.0",
     "mocha": "^9.0.3",
     "prettier": "^2.6.2",
     "ts-mocha": "^10.0.0",

File diff suppressed because it is too large
+ 796 - 124
tokens/token-2022/transfer-hook/counter/anchor/pnpm-lock.yaml


+ 6 - 1
tokens/token-2022/transfer-hook/counter/anchor/programs/transfer-hook/Cargo.toml

@@ -1,3 +1,7 @@
+[toolchain]
+anchor_version = "0.30.0"
+solana_version = "1.18.15"
+
 [package]
 name = "transfer-hook"
 version = "0.1.0"
@@ -17,7 +21,8 @@ no-log-ix-name = []
 idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
 
 [dependencies]
-anchor-lang = {version = "0.30.0", features = ["interface-instructions"]}
+anchor-lang = { version = "0.30.0", features = ["interface-instructions"] }
 anchor-spl = "0.30.0"
+solana-program = "1.18.16"
 spl-tlv-account-resolution = "0.6.3"
 spl-transfer-hook-interface = "0.6.3"

+ 71 - 44
tokens/token-2022/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs

@@ -1,18 +1,36 @@
+use std::cell::RefMut;
+
 use anchor_lang::prelude::*;
 use anchor_spl::{
-    associated_token::AssociatedToken, token_2022::Token2022, token_interface::{Mint, TokenAccount}
+    associated_token::AssociatedToken,
+    token_2022::{
+        spl_token_2022::{
+            extension::{
+                transfer_hook::TransferHookAccount,
+                BaseStateWithExtensionsMut,
+                PodStateWithExtensionsMut,
+            },
+            pod::PodAccount,
+        },
+        Token2022,
+    },
+    token_interface::{ Mint, TokenAccount },
 };
 use spl_tlv_account_resolution::{
-    account::ExtraAccountMeta, seeds::Seed, state::ExtraAccountMetaList,
+    account::ExtraAccountMeta,
+    seeds::Seed,
+    state::ExtraAccountMetaList,
 };
 use spl_transfer_hook_interface::instruction::ExecuteInstruction;
 
-declare_id!("DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub");
+declare_id!("1qahDxKHeCLZhbBU2NyMU6vQCQmEUmdeSEBrG5drffK");
 
 #[error_code]
-pub enum MyError {
+pub enum TransferError {
     #[msg("The amount is too big")]
     AmountTooBig,
+    #[msg("The token is not currently transferring")]
+    IsNotCurrentlyTransferring,
 }
 
 #[program]
@@ -21,14 +39,14 @@ pub mod transfer_hook {
 
     #[interface(spl_transfer_hook_interface::initialize_extra_account_meta_list)]
     pub fn initialize_extra_account_meta_list(
-        ctx: Context<InitializeExtraAccountMetaList>,
+        ctx: Context<InitializeExtraAccountMetaList>
     ) -> Result<()> {
         let extra_account_metas = InitializeExtraAccountMetaList::extra_account_metas()?;
 
         // initialize ExtraAccountMetaList account with extra accounts
         ExtraAccountMetaList::init::<ExecuteInstruction>(
             &mut ctx.accounts.extra_account_meta_list.try_borrow_mut_data()?,
-            &extra_account_metas,
+            &extra_account_metas
         )?;
 
         Ok(())
@@ -36,20 +54,39 @@ pub mod transfer_hook {
 
     #[interface(spl_transfer_hook_interface::execute)]
     pub fn transfer_hook(ctx: Context<TransferHook>, amount: u64) -> Result<()> {
+        // Fail this instruction if it is not called from within a transfer hook
+        check_is_transferring(&ctx)?;
 
+        // Check if the amount is too big
         if amount > 50 {
-            msg!("The amount is too big {0}", amount);
-            //return err!(MyError::AmountTooBig);
+            msg!("The amount is too big: {}", amount);
+            //return err!(TransferError::AmountTooBig);
         }
 
-        let count = ctx.accounts.counter_account.counter.checked_add(1).unwrap();
+        // Increment the transfer count safely
+        let count = ctx.accounts.counter_account.counter
+            .checked_add(1)
+            .ok_or(TransferError::AmountTooBig)?;
 
         msg!("This token has been transferred {} times", count);
-       
+
         Ok(())
     }
 }
 
+fn check_is_transferring(ctx: &Context<TransferHook>) -> Result<()> {
+    let source_token_info = ctx.accounts.source_token.to_account_info();
+    let mut account_data_ref: RefMut<&mut [u8]> = source_token_info.try_borrow_mut_data()?;
+    let mut account = PodStateWithExtensionsMut::<PodAccount>::unpack(*account_data_ref)?;
+    let account_extension = account.get_extension_mut::<TransferHookAccount>()?;
+
+    if !bool::from(account_extension.transferring) {
+        return err!(TransferError::IsNotCurrentlyTransferring);
+    }
+
+    Ok(())
+}
+
 #[derive(Accounts)]
 pub struct InitializeExtraAccountMetaList<'info> {
     #[account(mut)]
@@ -58,20 +95,16 @@ pub struct InitializeExtraAccountMetaList<'info> {
     /// CHECK: ExtraAccountMetaList Account, must use these seeds
     #[account(
         init,
-        seeds = [b"extra-account-metas", mint.key().as_ref()], 
+        seeds = [b"extra-account-metas", mint.key().as_ref()],
         bump,
-        space = ExtraAccountMetaList::size_of(InitializeExtraAccountMetaList::extra_account_metas()?.len())?,
-        payer = payer,
+        space = ExtraAccountMetaList::size_of(
+            InitializeExtraAccountMetaList::extra_account_metas()?.len()
+        )?,
+        payer = payer
     )]
     pub extra_account_meta_list: AccountInfo<'info>,
     pub mint: InterfaceAccount<'info, Mint>,
-    #[account(
-        init,
-        seeds = [b"counter"], 
-        bump,
-        payer = payer,
-        space = 16
-    )]
+    #[account(init, seeds = [b"counter"], bump, payer = payer, space = 16)]
     pub counter_account: Account<'info, CounterAccount>,
     pub token_program: Program<'info, Token2022>,
     pub associated_token_program: Program<'info, AssociatedToken>,
@@ -81,45 +114,39 @@ pub struct InitializeExtraAccountMetaList<'info> {
 // Define extra account metas to store on extra_account_meta_list account
 impl<'info> InitializeExtraAccountMetaList<'info> {
     pub fn extra_account_metas() -> Result<Vec<ExtraAccountMeta>> {
-        Ok(vec![
-            ExtraAccountMeta::new_with_seeds(
-                &[Seed::Literal {
-                    bytes: b"counter".to_vec(),
-                }],
-                false, // is_signer
-                true,  // is_writable
-            )?
-        ])
+        Ok(
+            vec![
+                ExtraAccountMeta::new_with_seeds(
+                    &[
+                        Seed::Literal {
+                            bytes: b"counter".to_vec(),
+                        },
+                    ],
+                    false, // is_signer
+                    true // is_writable
+                )?
+            ]
+        )
     }
 }
+
 // Order of accounts matters for this struct.
 // The first 4 accounts are the accounts required for token transfer (source, mint, destination, owner)
 // Remaining accounts are the extra accounts required from the ExtraAccountMetaList account
 // These accounts are provided via CPI to this program from the token2022 program
 #[derive(Accounts)]
 pub struct TransferHook<'info> {
-    #[account(
-        token::mint = mint, 
-        token::authority = owner,
-    )]
+    #[account(token::mint = mint, token::authority = owner)]
     pub source_token: InterfaceAccount<'info, TokenAccount>,
     pub mint: InterfaceAccount<'info, Mint>,
-    #[account(
-        token::mint = mint,
-    )]
+    #[account(token::mint = mint)]
     pub destination_token: InterfaceAccount<'info, TokenAccount>,
     /// CHECK: source token account owner, can be SystemAccount or PDA owned by another program
     pub owner: UncheckedAccount<'info>,
     /// CHECK: ExtraAccountMetaList Account,
-    #[account(
-        seeds = [b"extra-account-metas", mint.key().as_ref()], 
-        bump
-    )]
+    #[account(seeds = [b"extra-account-metas", mint.key().as_ref()], bump)]
     pub extra_account_meta_list: UncheckedAccount<'info>,
-    #[account(
-        seeds = [b"counter"],
-        bump
-    )]
+    #[account(seeds = [b"counter"], bump)]
     pub counter_account: Account<'info, CounterAccount>,
 }
 

+ 26 - 2
tokens/token-2022/transfer-hook/counter/anchor/tests/transfer-hook.ts

@@ -12,9 +12,15 @@ import {
   getAssociatedTokenAddressSync,
   getMintLen,
 } from '@solana/spl-token';
-import { Keypair, PublicKey, SystemProgram, Transaction, sendAndConfirmTransaction } from '@solana/web3.js';
+import { Keypair, PublicKey, SendTransactionError, SystemProgram, Transaction, sendAndConfirmTransaction } from '@solana/web3.js';
+import { BN } from 'bn.js';
+import { expect } from 'chai';
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
 import type { TransferHook } from '../target/types/transfer_hook';
 
+chai.use(chaiAsPromised);
+
 describe('transfer-hook', () => {
   // Configure the client to use the local cluster.
   const provider = anchor.AnchorProvider.env();
@@ -158,11 +164,29 @@ describe('transfer-hook', () => {
 
     console.log(`Extra accounts meta: ${extraAccountMetaListPDA}`);
     console.log(`Counter PDA: ${counterPDA}`);
-    console.log(`Transfer Instruction: ${JSON.stringify(transferInstructionWithHelper, null, 2)}`);
+    console.log(`Transfer Instruction: ${JSON.stringify(transferInstructionWithHelper)}`);
 
     const transaction = new Transaction().add(transferInstructionWithHelper);
 
     const txSig = await sendAndConfirmTransaction(connection, transaction, [wallet.payer], { skipPreflight: true });
     console.log('Transfer Signature:', txSig);
   });
+
+  it('Try call transfer hook without transfer', async () => {
+    const transferHookIx = await program.methods
+      .transferHook(new BN(1))
+      .accounts({
+        sourceToken: sourceTokenAccount,
+        mint: mint.publicKey,
+        destinationToken: destinationTokenAccount,
+        owner: wallet.publicKey,
+      })
+      .instruction();
+
+    const transaction = new Transaction().add(transferHookIx);
+
+    const sendPromise = sendAndConfirmTransaction(connection, transaction, [wallet.payer], { skipPreflight: false });
+
+    await expect(sendPromise).to.eventually.be.rejectedWith(SendTransactionError, program.idl.errors[1].msg);
+  });
 });

+ 5 - 2
tokens/token-2022/transfer-hook/hello-world/anchor/package.json

@@ -5,13 +5,16 @@
   },
   "dependencies": {
     "@coral-xyz/anchor": "^0.30.0",
-    "@solana/spl-token": "^0.4.0"
+    "@solana/spl-token": "^0.4.0",
+    "@solana/web3.js": "^1.92.3"
   },
   "devDependencies": {
     "@types/bn.js": "^5.1.0",
+    "@types/chai-as-promised": "^7.1.8",
+    "chai-as-promised": "^7.1.2",
     "@types/chai": "^4.3.0",
-    "@types/mocha": "^9.0.0",
     "chai": "^4.3.4",
+    "@types/mocha": "^9.0.0",
     "mocha": "^9.0.3",
     "prettier": "^2.6.2",
     "ts-mocha": "^10.0.0",

File diff suppressed because it is too large
+ 796 - 121
tokens/token-2022/transfer-hook/hello-world/anchor/pnpm-lock.yaml


+ 52 - 28
tokens/token-2022/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs

@@ -1,22 +1,42 @@
+use std::cell::RefMut;
+
 use anchor_lang::prelude::*;
 use anchor_spl::{
-    associated_token::AssociatedToken, token_interface::{
+    associated_token::AssociatedToken,
+    token_2022::spl_token_2022::{
+        extension::{
+            transfer_hook::TransferHookAccount,
+            BaseStateWithExtensionsMut,
+            PodStateWithExtensionsMut,
+        },
+        pod::PodAccount,
+    },
+    token_interface::{
         spl_pod::optional_keys::OptionalNonZeroPubkey,
         spl_token_2022::{
             extension::{
-                transfer_hook::TransferHook as TransferHookExtension, BaseStateWithExtensions,
+                transfer_hook::TransferHook as TransferHookExtension,
+                BaseStateWithExtensions,
                 StateWithExtensions,
             },
             state::Mint as MintState,
         },
-        Mint, Token2022, TokenAccount
+        Mint,
+        Token2022,
+        TokenAccount,
     },
 };
-use spl_tlv_account_resolution::{account::ExtraAccountMeta, state::ExtraAccountMetaList};
+use spl_tlv_account_resolution::{ account::ExtraAccountMeta, state::ExtraAccountMetaList };
 use spl_transfer_hook_interface::instruction::ExecuteInstruction;
 
 declare_id!("jY5DfVksJT8Le38LCaQhz5USeiGu4rUeVSS8QRAMoba");
 
+#[error_code]
+pub enum TransferError {
+    #[msg("The token is not currently transferring")]
+    IsNotCurrentlyTransferring,
+}
+
 #[program]
 pub mod transfer_hook {
     use super::*;
@@ -29,22 +49,23 @@ pub mod transfer_hook {
 
     #[interface(spl_transfer_hook_interface::initialize_extra_account_meta_list)]
     pub fn initialize_extra_account_meta_list(
-        ctx: Context<InitializeExtraAccountMetaList>,
+        ctx: Context<InitializeExtraAccountMetaList>
     ) -> Result<()> {
         let extra_account_metas = InitializeExtraAccountMetaList::extra_account_metas()?;
 
         // initialize ExtraAccountMetaList account with extra accounts
         ExtraAccountMetaList::init::<ExecuteInstruction>(
             &mut ctx.accounts.extra_account_meta_list.try_borrow_mut_data()?,
-            &extra_account_metas,
+            &extra_account_metas
         )?;
 
-
         Ok(())
     }
 
     #[interface(spl_transfer_hook_interface::execute)]
-    pub fn transfer_hook(_ctx: Context<TransferHook>, _amount: u64) -> Result<()> {
+    pub fn transfer_hook(ctx: Context<TransferHook>, _amount: u64) -> Result<()> {
+        // Fail this instruction if it is not called from within a transfer hook
+        check_is_transferring(&ctx)?;
 
         msg!("Hello Transfer Hook!");
 
@@ -52,6 +73,19 @@ pub mod transfer_hook {
     }
 }
 
+fn check_is_transferring(ctx: &Context<TransferHook>) -> Result<()> {
+    let source_token_info = ctx.accounts.source_token.to_account_info();
+    let mut account_data_ref: RefMut<&mut [u8]> = source_token_info.try_borrow_mut_data()?;
+    let mut account = PodStateWithExtensionsMut::<PodAccount>::unpack(*account_data_ref)?;
+    let account_extension = account.get_extension_mut::<TransferHookAccount>()?;
+
+    if !bool::from(account_extension.transferring) {
+        return err!(TransferError::IsNotCurrentlyTransferring);
+    }
+
+    Ok(())
+}
+
 #[derive(Accounts)]
 #[instruction(_decimals: u8)]
 pub struct Initialize<'info> {
@@ -64,7 +98,7 @@ pub struct Initialize<'info> {
         mint::decimals = _decimals,
         mint::authority = payer,
         extensions::transfer_hook::authority = payer,
-        extensions::transfer_hook::program_id = crate::ID,
+        extensions::transfer_hook::program_id = crate::ID
     )]
     pub mint_account: InterfaceAccount<'info, Mint>,
     pub token_program: Program<'info, Token2022>,
@@ -84,17 +118,13 @@ impl<'info> Initialize<'info> {
             OptionalNonZeroPubkey::try_from(Some(self.payer.key()))?
         );
 
-        assert_eq!(
-            extension_data.program_id,
-            OptionalNonZeroPubkey::try_from(Some(crate::ID))?
-        );
+        assert_eq!(extension_data.program_id, OptionalNonZeroPubkey::try_from(Some(crate::ID))?);
 
         msg!("{:?}", extension_data);
         Ok(())
     }
 }
 
-
 #[derive(Accounts)]
 pub struct InitializeExtraAccountMetaList<'info> {
     #[account(mut)]
@@ -103,10 +133,12 @@ pub struct InitializeExtraAccountMetaList<'info> {
     /// CHECK: ExtraAccountMetaList Account, must use these seeds
     #[account(
         init,
-        seeds = [b"extra-account-metas", mint.key().as_ref()], 
+        seeds = [b"extra-account-metas", mint.key().as_ref()],
         bump,
-        space = ExtraAccountMetaList::size_of(InitializeExtraAccountMetaList::extra_account_metas()?.len())?,
-        payer = payer,
+        space = ExtraAccountMetaList::size_of(
+            InitializeExtraAccountMetaList::extra_account_metas()?.len()
+        )?,
+        payer = payer
     )]
     pub extra_account_meta_list: UncheckedAccount<'info>,
     pub mint: InterfaceAccount<'info, Mint>,
@@ -129,22 +161,14 @@ impl<'info> InitializeExtraAccountMetaList<'info> {
 // These accounts are provided via CPI to this program from the token2022 program
 #[derive(Accounts)]
 pub struct TransferHook<'info> {
-    #[account(
-        token::mint = mint, 
-        token::authority = owner,
-    )]
+    #[account(token::mint = mint, token::authority = owner)]
     pub source_token: InterfaceAccount<'info, TokenAccount>,
     pub mint: InterfaceAccount<'info, Mint>,
-    #[account(
-        token::mint = mint,
-    )]
+    #[account(token::mint = mint)]
     pub destination_token: InterfaceAccount<'info, TokenAccount>,
     /// CHECK: source token account owner, can be SystemAccount or PDA owned by another program
     pub owner: UncheckedAccount<'info>,
     /// CHECK: ExtraAccountMetaList Account,
-    #[account(
-        seeds = [b"extra-account-metas", mint.key().as_ref()], 
-        bump
-    )]
+    #[account(seeds = [b"extra-account-metas", mint.key().as_ref()], bump)]
     pub extra_account_meta_list: UncheckedAccount<'info>,
 }

+ 24 - 1
tokens/token-2022/transfer-hook/hello-world/anchor/tests/transfer-hook.ts

@@ -8,9 +8,14 @@ import {
   createTransferCheckedWithTransferHookInstruction,
   getAssociatedTokenAddressSync,
 } from '@solana/spl-token';
-import { Keypair, Transaction, sendAndConfirmTransaction } from '@solana/web3.js';
+import { Keypair, SendTransactionError, Transaction, sendAndConfirmTransaction } from '@solana/web3.js';
+import { expect } from 'chai';
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
 import type { TransferHook } from '../target/types/transfer_hook';
 
+chai.use(chaiAsPromised);
+
 describe('transfer-hook', () => {
   // Configure the client to use the local cluster.
   const provider = anchor.AnchorProvider.env();
@@ -122,4 +127,22 @@ describe('transfer-hook', () => {
     const txSig = await sendAndConfirmTransaction(connection, transaction, [wallet.payer], { skipPreflight: true });
     console.log('Transfer Signature:', txSig);
   });
+
+  it('Try call transfer hook without transfer', async () => {
+    const transferHookIx = await program.methods
+      .transferHook(new anchor.BN(1))
+      .accounts({
+        sourceToken: sourceTokenAccount,
+        mint: mint.publicKey,
+        destinationToken: destinationTokenAccount,
+        owner: wallet.publicKey,
+      })
+      .instruction();
+
+    const transaction = new Transaction().add(transferHookIx);
+
+    const sendPromise = sendAndConfirmTransaction(connection, transaction, [wallet.payer], { skipPreflight: false });
+
+    await expect(sendPromise).to.eventually.be.rejectedWith(SendTransactionError, program.idl.errors[0].msg);
+  });
 });

File diff suppressed because it is too large
+ 803 - 236
tokens/token-2022/transfer-hook/transfer-cost/anchor/pnpm-lock.yaml


+ 119 - 98
tokens/token-2022/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs

@@ -1,12 +1,22 @@
-use std::str::FromStr;
-use anchor_lang::{
-    prelude::*, solana_program::pubkey::Pubkey, 
-};
+use std::{ cell::RefMut, str::FromStr };
+use anchor_lang::{ prelude::*, solana_program::pubkey::Pubkey };
 use anchor_spl::{
-    associated_token::AssociatedToken, token::Token, token_interface::{transfer_checked, Mint, TokenAccount, TransferChecked}
+    associated_token::AssociatedToken,
+    token::Token,
+    token_2022::spl_token_2022::{
+        extension::{
+            transfer_hook::TransferHookAccount,
+            BaseStateWithExtensionsMut,
+            PodStateWithExtensionsMut,
+        },
+        pod::PodAccount,
+    },
+    token_interface::{ transfer_checked, Mint, TokenAccount, TransferChecked },
 };
 use spl_tlv_account_resolution::{
-    account::ExtraAccountMeta, seeds::Seed, state::ExtraAccountMetaList,
+    account::ExtraAccountMeta,
+    seeds::Seed,
+    state::ExtraAccountMetaList,
 };
 use spl_transfer_hook_interface::instruction::ExecuteInstruction;
 
@@ -16,9 +26,11 @@ use spl_transfer_hook_interface::instruction::ExecuteInstruction;
 declare_id!("FjcHckEgXcBhFmSGai3FRpDLiT6hbpV893n8iTxVd81g");
 
 #[error_code]
-pub enum MyError {
-   #[msg("Amount Too big")]
-   AmountTooBig,
+pub enum TransferError {
+    #[msg("Amount Too big")]
+    AmountTooBig,
+    #[msg("The token is not currently transferring")]
+    IsNotCurrentlyTransferring,
 }
 
 #[program]
@@ -27,14 +39,14 @@ pub mod transfer_hook {
 
     #[interface(spl_transfer_hook_interface::initialize_extra_account_meta_list)]
     pub fn initialize_extra_account_meta_list(
-        ctx: Context<InitializeExtraAccountMetaList>,
+        ctx: Context<InitializeExtraAccountMetaList>
     ) -> Result<()> {
         let extra_account_metas = InitializeExtraAccountMetaList::extra_account_metas()?;
-      
+
         // initialize ExtraAccountMetaList account with extra accounts
         ExtraAccountMetaList::init::<ExecuteInstruction>(
             &mut ctx.accounts.extra_account_meta_list.try_borrow_mut_data()?,
-            &extra_account_metas,
+            &extra_account_metas
         )?;
 
         Ok(())
@@ -42,10 +54,12 @@ pub mod transfer_hook {
 
     #[interface(spl_transfer_hook_interface::execute)]
     pub fn transfer_hook(ctx: Context<TransferHook>, amount: u64) -> Result<()> {
+        // Fail this instruction if it is not called from within a transfer hook
+        check_is_transferring(&ctx)?;
 
         if amount > 50 {
-            //msg!("The amount is too big {0}", amount);
-            //return err!(MyError::AmountTooBig);
+            msg!("The amount is too big {0}", amount);
+            //return err!(TransferError::AmountTooBig);
         }
 
         ctx.accounts.counter_account.counter += 1;
@@ -54,7 +68,10 @@ pub mod transfer_hook {
 
         // All accounts are non writable so you can not burn any of them for example here
         msg!("Is writable mint {0}", ctx.accounts.mint.to_account_info().is_writable);
-        msg!("Is destination mint {0}", ctx.accounts.destination_token.to_account_info().is_writable);
+        msg!(
+            "Is destination mint {0}",
+            ctx.accounts.destination_token.to_account_info().is_writable
+        );
         msg!("Is source mint {0}", ctx.accounts.source_token.to_account_info().is_writable);
 
         let signer_seeds: &[&[&[u8]]] = &[&[b"delegate", &[ctx.bumps.delegate]]];
@@ -62,23 +79,32 @@ pub mod transfer_hook {
         // Transfer WSOL from sender to delegate token account using delegate PDA
         // transfer lamports amount equal to token transfer amount
         transfer_checked(
-            CpiContext::new(
-                ctx.accounts.token_program.to_account_info(),
-                TransferChecked {
-                    from: ctx.accounts.sender_wsol_token_account.to_account_info(),
-                    mint: ctx.accounts.wsol_mint.to_account_info(),
-                    to: ctx.accounts.delegate_wsol_token_account.to_account_info(),
-                    authority: ctx.accounts.delegate.to_account_info(),
-                },
-            )
-            .with_signer(signer_seeds),
+            CpiContext::new(ctx.accounts.token_program.to_account_info(), TransferChecked {
+                from: ctx.accounts.sender_wsol_token_account.to_account_info(),
+                mint: ctx.accounts.wsol_mint.to_account_info(),
+                to: ctx.accounts.delegate_wsol_token_account.to_account_info(),
+                authority: ctx.accounts.delegate.to_account_info(),
+            }).with_signer(signer_seeds),
             amount,
-            ctx.accounts.wsol_mint.decimals,
+            ctx.accounts.wsol_mint.decimals
         )?;
         Ok(())
     }
 }
 
+fn check_is_transferring(ctx: &Context<TransferHook>) -> Result<()> {
+    let source_token_info = ctx.accounts.source_token.to_account_info();
+    let mut account_data_ref: RefMut<&mut [u8]> = source_token_info.try_borrow_mut_data()?;
+    let mut account = PodStateWithExtensionsMut::<PodAccount>::unpack(*account_data_ref)?;
+    let account_extension = account.get_extension_mut::<TransferHookAccount>()?;
+
+    if !bool::from(account_extension.transferring) {
+        return err!(TransferError::IsNotCurrentlyTransferring);
+    }
+
+    Ok(())
+}
+
 #[derive(Accounts)]
 pub struct InitializeExtraAccountMetaList<'info> {
     #[account(mut)]
@@ -87,20 +113,16 @@ pub struct InitializeExtraAccountMetaList<'info> {
     /// CHECK: ExtraAccountMetaList Account, must use these seeds
     #[account(
         init,
-        seeds = [b"extra-account-metas", mint.key().as_ref()], 
+        seeds = [b"extra-account-metas", mint.key().as_ref()],
         bump,
-        space = ExtraAccountMetaList::size_of(InitializeExtraAccountMetaList::extra_account_metas()?.len())?,
-        payer = payer,
+        space = ExtraAccountMetaList::size_of(
+            InitializeExtraAccountMetaList::extra_account_metas()?.len()
+        )?,
+        payer = payer
     )]
     pub extra_account_meta_list: AccountInfo<'info>,
     pub mint: InterfaceAccount<'info, Mint>,
-    #[account(
-        init,
-        seeds = [b"counter"],
-        bump,
-        payer = payer,
-        space = 9
-    )]
+    #[account(init, seeds = [b"counter"], bump, payer = payer, space = 9)]
     pub counter_account: Account<'info, CounterAccount>,
     pub system_program: Program<'info, System>,
 }
@@ -113,51 +135,61 @@ impl<'info> InitializeExtraAccountMetaList<'info> {
 
         // index 0-3 are the accounts required for token transfer (source, mint, destination, owner)
         // index 4 is address of ExtraAccountMetaList account
-        Ok(vec![
-            // index 5, wrapped SOL mint
-            ExtraAccountMeta::new_with_pubkey(&Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap(), false, false)?,
-            // index 6, token program (for wsol token transfer)
-            ExtraAccountMeta::new_with_pubkey(&Token::id(), false, false)?,
-            // index 7, associated token program
-            ExtraAccountMeta::new_with_pubkey(&AssociatedToken::id(), false, false)?,
-            // index 8, delegate PDA
-            ExtraAccountMeta::new_with_seeds(
-                &[Seed::Literal {
-                    bytes: b"delegate".to_vec(),
-                }],
-                false, // is_signer
-                true,  // is_writable
-            )?,
-            // index 9, delegate wrapped SOL token account
-            ExtraAccountMeta::new_external_pda_with_seeds(
-                7, // associated token program index
-                &[
-                    Seed::AccountKey { index: 8 }, // owner index (delegate PDA)
-                    Seed::AccountKey { index: 6 }, // token program index
-                    Seed::AccountKey { index: 5 }, // wsol mint index
-                ],
-                false, // is_signer
-                true,  // is_writable
-            )?,
-            // index 10, sender wrapped SOL token account
-            ExtraAccountMeta::new_external_pda_with_seeds(
-                7, // associated token program index
-                &[
-                    Seed::AccountKey { index: 3 }, // owner index
-                    Seed::AccountKey { index: 6 }, // token program index
-                    Seed::AccountKey { index: 5 }, // wsol mint index
-                ],
-                false, // is_signer
-                true,  // is_writable
-            )?,
-            ExtraAccountMeta::new_with_seeds(
-                &[Seed::Literal {
-                    bytes: b"counter".to_vec(),
-                }],
-                false, // is_signer
-                true,  // is_writable
-            )?,
-        ])
+        Ok(
+            vec![
+                // index 5, wrapped SOL mint
+                ExtraAccountMeta::new_with_pubkey(
+                    &Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap(),
+                    false,
+                    false
+                )?,
+                // index 6, token program (for wsol token transfer)
+                ExtraAccountMeta::new_with_pubkey(&Token::id(), false, false)?,
+                // index 7, associated token program
+                ExtraAccountMeta::new_with_pubkey(&AssociatedToken::id(), false, false)?,
+                // index 8, delegate PDA
+                ExtraAccountMeta::new_with_seeds(
+                    &[
+                        Seed::Literal {
+                            bytes: b"delegate".to_vec(),
+                        },
+                    ],
+                    false, // is_signer
+                    true // is_writable
+                )?,
+                // index 9, delegate wrapped SOL token account
+                ExtraAccountMeta::new_external_pda_with_seeds(
+                    7, // associated token program index
+                    &[
+                        Seed::AccountKey { index: 8 }, // owner index (delegate PDA)
+                        Seed::AccountKey { index: 6 }, // token program index
+                        Seed::AccountKey { index: 5 }, // wsol mint index
+                    ],
+                    false, // is_signer
+                    true // is_writable
+                )?,
+                // index 10, sender wrapped SOL token account
+                ExtraAccountMeta::new_external_pda_with_seeds(
+                    7, // associated token program index
+                    &[
+                        Seed::AccountKey { index: 3 }, // owner index
+                        Seed::AccountKey { index: 6 }, // token program index
+                        Seed::AccountKey { index: 5 }, // wsol mint index
+                    ],
+                    false, // is_signer
+                    true // is_writable
+                )?,
+                ExtraAccountMeta::new_with_seeds(
+                    &[
+                        Seed::Literal {
+                            bytes: b"counter".to_vec(),
+                        },
+                    ],
+                    false, // is_signer
+                    true // is_writable
+                )?
+            ]
+        )
     }
 }
 
@@ -167,26 +199,18 @@ impl<'info> InitializeExtraAccountMetaList<'info> {
 // These accounts are provided via CPI to this program from the token2022 program
 #[derive(Accounts)]
 pub struct TransferHook<'info> {
-    #[account(
-        token::mint = mint, 
-        token::authority = owner,
-    )]
+    #[account(token::mint = mint, token::authority = owner)]
     pub source_token: InterfaceAccount<'info, TokenAccount>,
     pub mint: InterfaceAccount<'info, Mint>,
-    #[account(
-        token::mint = mint,
-    )]
+    #[account(token::mint = mint)]
     pub destination_token: InterfaceAccount<'info, TokenAccount>,
     /// CHECK: source token account owner, can be SystemAccount or PDA owned by another program
     pub owner: UncheckedAccount<'info>,
     /// CHECK: ExtraAccountMetaList Account,
-    #[account(
-        seeds = [b"extra-account-metas", mint.key().as_ref()], 
-        bump
-    )]
+    #[account(seeds = [b"extra-account-metas", mint.key().as_ref()], bump)]
     pub extra_account_meta_list: UncheckedAccount<'info>,
     pub wsol_mint: InterfaceAccount<'info, Mint>,
-    pub token_program:Program<'info, Token>,
+    pub token_program: Program<'info, Token>,
     pub associated_token_program: Program<'info, AssociatedToken>,
     #[account(
         mut,
@@ -206,14 +230,11 @@ pub struct TransferHook<'info> {
         token::authority = owner,
     )]
     pub sender_wsol_token_account: InterfaceAccount<'info, TokenAccount>,
-    #[account(
-        seeds = [b"counter"],
-        bump
-    )]
+    #[account(seeds = [b"counter"], bump)]
     pub counter_account: Account<'info, CounterAccount>,
 }
 
 #[account]
 pub struct CounterAccount {
-    counter: u8
+    counter: u8,
 }

+ 3 - 1
tokens/token-2022/transfer-hook/whitelist/anchor/package.json

@@ -9,9 +9,11 @@
   },
   "devDependencies": {
     "@types/bn.js": "^5.1.0",
+    "@types/chai-as-promised": "^7.1.8",
+    "chai-as-promised": "^7.1.2",
     "@types/chai": "^4.3.0",
-    "@types/mocha": "^9.0.0",
     "chai": "^4.3.4",
+    "@types/mocha": "^9.0.0",
     "mocha": "^9.0.3",
     "prettier": "^2.6.2",
     "ts-mocha": "^10.0.0",

File diff suppressed because it is too large
+ 790 - 119
tokens/token-2022/transfer-hook/whitelist/anchor/pnpm-lock.yaml


+ 64 - 41
tokens/token-2022/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs

@@ -1,18 +1,39 @@
+use std::cell::RefMut;
+
 use anchor_lang::prelude::*;
-use anchor_spl::token_interface::{Mint, TokenAccount};
-use spl_tlv_account_resolution::{account::ExtraAccountMeta, seeds::Seed, state::ExtraAccountMetaList};
+use anchor_spl::{
+    token_2022::spl_token_2022::{
+        extension::{
+            transfer_hook::TransferHookAccount,
+            BaseStateWithExtensionsMut,
+            PodStateWithExtensionsMut,
+        },
+        pod::PodAccount,
+    },
+    token_interface::{ Mint, TokenAccount },
+};
+use spl_tlv_account_resolution::{
+    account::ExtraAccountMeta,
+    seeds::Seed,
+    state::ExtraAccountMetaList,
+};
 use spl_transfer_hook_interface::instruction::ExecuteInstruction;
 
 declare_id!("DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub");
 
+#[error_code]
+pub enum TransferError {
+    #[msg("The token is not currently transferring")]
+    IsNotCurrentlyTransferring,
+}
+
 #[program]
 pub mod transfer_hook {
-
     use super::*;
 
     #[interface(spl_transfer_hook_interface::initialize_extra_account_meta_list)]
     pub fn initialize_extra_account_meta_list(
-        ctx: Context<InitializeExtraAccountMetaList>,
+        ctx: Context<InitializeExtraAccountMetaList>
     ) -> Result<()> {
         // set authority field on white_list account as payer address
         ctx.accounts.white_list.authority = ctx.accounts.payer.key();
@@ -22,13 +43,15 @@ pub mod transfer_hook {
         // initialize ExtraAccountMetaList account with extra accounts
         ExtraAccountMetaList::init::<ExecuteInstruction>(
             &mut ctx.accounts.extra_account_meta_list.try_borrow_mut_data()?,
-            &extra_account_metas,
+            &extra_account_metas
         )?;
         Ok(())
     }
 
     #[interface(spl_transfer_hook_interface::execute)]
     pub fn transfer_hook(ctx: Context<TransferHook>, _amount: u64) -> Result<()> {
+        // Fail this instruction if it is not called from within a transfer hook
+        check_is_transferring(&ctx)?;
 
         if !ctx.accounts.white_list.white_list.contains(&ctx.accounts.destination_token.key()) {
             panic!("Account not in white list!");
@@ -40,7 +63,6 @@ pub mod transfer_hook {
     }
 
     pub fn add_to_whitelist(ctx: Context<AddToWhiteList>) -> Result<()> {
-
         if ctx.accounts.white_list.authority != ctx.accounts.signer.key() {
             panic!("Only the authority can add to the white list!");
         }
@@ -51,7 +73,19 @@ pub mod transfer_hook {
 
         Ok(())
     }
+}
+
+fn check_is_transferring(ctx: &Context<TransferHook>) -> Result<()> {
+    let source_token_info = ctx.accounts.source_token.to_account_info();
+    let mut account_data_ref: RefMut<&mut [u8]> = source_token_info.try_borrow_mut_data()?;
+    let mut account = PodStateWithExtensionsMut::<PodAccount>::unpack(*account_data_ref)?;
+    let account_extension = account.get_extension_mut::<TransferHookAccount>()?;
 
+    if !bool::from(account_extension.transferring) {
+        return err!(TransferError::IsNotCurrentlyTransferring);
+    }
+
+    Ok(())
 }
 
 #[derive(Accounts)]
@@ -62,36 +96,36 @@ pub struct InitializeExtraAccountMetaList<'info> {
     /// CHECK: ExtraAccountMetaList Account, must use these seeds
     #[account(
         init,
-        seeds = [b"extra-account-metas", mint.key().as_ref()], 
+        seeds = [b"extra-account-metas", mint.key().as_ref()],
         bump,
-        space = ExtraAccountMetaList::size_of(InitializeExtraAccountMetaList::extra_account_metas()?.len())?,
-        payer = payer,
+        space = ExtraAccountMetaList::size_of(
+            InitializeExtraAccountMetaList::extra_account_metas()?.len()
+        )?,
+        payer = payer
     )]
     pub extra_account_meta_list: AccountInfo<'info>,
     pub mint: InterfaceAccount<'info, Mint>,
     pub system_program: Program<'info, System>,
-    #[account(
-        init_if_needed,
-        seeds = [b"white_list"],
-        bump,
-        payer = payer,
-        space = 400
-    )]
+    #[account(init_if_needed, seeds = [b"white_list"], bump, payer = payer, space = 400)]
     pub white_list: Account<'info, WhiteList>,
 }
 
 // Define extra account metas to store on extra_account_meta_list account
 impl<'info> InitializeExtraAccountMetaList<'info> {
     pub fn extra_account_metas() -> Result<Vec<ExtraAccountMeta>> {
-        Ok(vec![
-            ExtraAccountMeta::new_with_seeds(
-                &[Seed::Literal {
-                    bytes: "white_list".as_bytes().to_vec(),
-                }], 
-                false, // is_signer
-                true,  // is_writable
-            )?,
-        ])
+        Ok(
+            vec![
+                ExtraAccountMeta::new_with_seeds(
+                    &[
+                        Seed::Literal {
+                            bytes: "white_list".as_bytes().to_vec(),
+                        },
+                    ],
+                    false, // is_signer
+                    true // is_writable
+                )?
+            ]
+        )
     }
 }
 
@@ -101,28 +135,17 @@ impl<'info> InitializeExtraAccountMetaList<'info> {
 // These accounts are provided via CPI to this program from the token2022 program
 #[derive(Accounts)]
 pub struct TransferHook<'info> {
-    #[account(
-        token::mint = mint, 
-        token::authority = owner,
-    )]
+    #[account(token::mint = mint, token::authority = owner)]
     pub source_token: InterfaceAccount<'info, TokenAccount>,
     pub mint: InterfaceAccount<'info, Mint>,
-    #[account(
-        token::mint = mint,
-    )]
+    #[account(token::mint = mint)]
     pub destination_token: InterfaceAccount<'info, TokenAccount>,
     /// CHECK: source token account owner, can be SystemAccount or PDA owned by another program
     pub owner: UncheckedAccount<'info>,
     /// CHECK: ExtraAccountMetaList Account,
-    #[account(
-        seeds = [b"extra-account-metas", mint.key().as_ref()], 
-        bump
-    )]
+    #[account(seeds = [b"extra-account-metas", mint.key().as_ref()], bump)]
     pub extra_account_meta_list: UncheckedAccount<'info>,
-    #[account(
-        seeds = [b"white_list"],
-        bump
-    )]
+    #[account(seeds = [b"white_list"], bump)]
     pub white_list: Account<'info, WhiteList>,
 }
 
@@ -145,4 +168,4 @@ pub struct AddToWhiteList<'info> {
 pub struct WhiteList {
     pub authority: Pubkey,
     pub white_list: Vec<Pubkey>,
-}
+}

Some files were not shown because too many files changed in this diff