From f3f119f01948c3fd827e569dbb4ffb9199997823 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 3 May 2024 15:05:09 -0500 Subject: [PATCH] add transferhook constraint to example --- .../anchor/TransferHookHelloWorld/Anchor.toml | 14 ++++- .../programs/transfer-hook/src/lib.rs | 63 ++++++++++++++++++- .../tests/transfer-hook.ts | 49 +++------------ 3 files changed, 80 insertions(+), 46 deletions(-) diff --git a/tokens/token-2022/transfer-hook/anchor/TransferHookHelloWorld/Anchor.toml b/tokens/token-2022/transfer-hook/anchor/TransferHookHelloWorld/Anchor.toml index 5ea6666b..0572273f 100644 --- a/tokens/token-2022/transfer-hook/anchor/TransferHookHelloWorld/Anchor.toml +++ b/tokens/token-2022/transfer-hook/anchor/TransferHookHelloWorld/Anchor.toml @@ -1,11 +1,11 @@ [toolchain] [features] -seeds = false +resolution = true skip-lint = false [programs.localnet] -transfer_hook = "DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub" +transfer_hook = "jY5DfVksJT8Le38LCaQhz5USeiGu4rUeVSS8QRAMoba" [registry] url = "https://api.apr.dev" @@ -17,8 +17,16 @@ wallet = "~/.config/solana/id.json" [scripts] test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" +[test] +startup_wait = 5000 +shutdown_wait = 2000 +upgradeable = false + [test.validator] +bind_address = "0.0.0.0" url = "https://api.devnet.solana.com" +ledger = ".anchor/test-ledger" +rpc_port = 8899 [[test.validator.clone]] -address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" \ No newline at end of file +address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" diff --git a/tokens/token-2022/transfer-hook/anchor/TransferHookHelloWorld/programs/transfer-hook/src/lib.rs b/tokens/token-2022/transfer-hook/anchor/TransferHookHelloWorld/programs/transfer-hook/src/lib.rs index e4664a8f..8e7b5044 100644 --- a/tokens/token-2022/transfer-hook/anchor/TransferHookHelloWorld/programs/transfer-hook/src/lib.rs +++ b/tokens/token-2022/transfer-hook/anchor/TransferHookHelloWorld/programs/transfer-hook/src/lib.rs @@ -1,16 +1,32 @@ use anchor_lang::prelude::*; use anchor_spl::{ - associated_token::AssociatedToken, token_2022::Token2022, token_interface::{Mint, TokenAccount} + associated_token::AssociatedToken, token_interface::{ + spl_pod::optional_keys::OptionalNonZeroPubkey, + spl_token_2022::{ + extension::{ + transfer_hook::TransferHook as TransferHookExtension, BaseStateWithExtensions, + StateWithExtensions, + }, + state::Mint as MintState, + }, + Mint, Token2022, TokenAccount + }, }; use spl_tlv_account_resolution::{account::ExtraAccountMeta, state::ExtraAccountMetaList}; use spl_transfer_hook_interface::instruction::ExecuteInstruction; -declare_id!("DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub"); +declare_id!("jY5DfVksJT8Le38LCaQhz5USeiGu4rUeVSS8QRAMoba"); #[program] pub mod transfer_hook { use super::*; + // create a mint account that specifies this program as the transfer hook program + pub fn initialize(ctx: Context, _decimals: u8) -> Result<()> { + ctx.accounts.check_mint_data()?; + Ok(()) + } + #[interface(spl_transfer_hook_interface::initialize_extra_account_meta_list)] pub fn initialize_extra_account_meta_list( ctx: Context, @@ -36,6 +52,49 @@ pub mod transfer_hook { } } +#[derive(Accounts)] +#[instruction(_decimals: u8)] +pub struct Initialize<'info> { + #[account(mut)] + pub payer: Signer<'info>, + + #[account( + init, + payer = payer, + mint::decimals = _decimals, + mint::authority = payer, + extensions::transfer_hook::authority = payer, + extensions::transfer_hook::program_id = crate::ID, + )] + pub mint_account: InterfaceAccount<'info, Mint>, + pub token_program: Program<'info, Token2022>, + pub system_program: Program<'info, System>, +} + +// helper to check mint data, and demonstrate how to read mint extension data within a program +impl<'info> Initialize<'info> { + pub fn check_mint_data(&self) -> Result<()> { + let mint = &self.mint_account.to_account_info(); + let mint_data = mint.data.borrow(); + let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; + let extension_data = mint_with_extension.get_extension::()?; + + assert_eq!( + extension_data.authority, + OptionalNonZeroPubkey::try_from(Some(self.payer.key()))? + ); + + assert_eq!( + extension_data.program_id, + OptionalNonZeroPubkey::try_from(Some(crate::ID))? + ); + + msg!("{:?}", extension_data); + Ok(()) + } +} + + #[derive(Accounts)] pub struct InitializeExtraAccountMetaList<'info> { #[account(mut)] diff --git a/tokens/token-2022/transfer-hook/anchor/TransferHookHelloWorld/tests/transfer-hook.ts b/tokens/token-2022/transfer-hook/anchor/TransferHookHelloWorld/tests/transfer-hook.ts index afda0989..38b8c4f5 100644 --- a/tokens/token-2022/transfer-hook/anchor/TransferHookHelloWorld/tests/transfer-hook.ts +++ b/tokens/token-2022/transfer-hook/anchor/TransferHookHelloWorld/tests/transfer-hook.ts @@ -2,17 +2,12 @@ import * as anchor from "@coral-xyz/anchor"; import { Program } from "@coral-xyz/anchor"; import { TransferHook } from "../target/types/transfer_hook"; import { - SystemProgram, Transaction, sendAndConfirmTransaction, Keypair, } from "@solana/web3.js"; import { - ExtensionType, TOKEN_2022_PROGRAM_ID, - getMintLen, - createInitializeMintInstruction, - createInitializeTransferHookInstruction, ASSOCIATED_TOKEN_PROGRAM_ID, createAssociatedTokenAccountInstruction, createMintToInstruction, @@ -31,7 +26,7 @@ describe("transfer-hook", () => { // Generate keypair to use as address for the transfer-hook enabled mint const mint = new Keypair(); - const decimals = 9; + const decimals = 2; // Sender token account address const sourceTokenAccount = getAssociatedTokenAddressSync( @@ -52,41 +47,13 @@ describe("transfer-hook", () => { ASSOCIATED_TOKEN_PROGRAM_ID ); - it("Create Mint Account with Transfer Hook Extension", async () => { - const extensions = [ExtensionType.TransferHook]; - const mintLen = getMintLen(extensions); - const lamports = - await provider.connection.getMinimumBalanceForRentExemption(mintLen); - - const transaction = new Transaction().add( - SystemProgram.createAccount({ - fromPubkey: wallet.publicKey, - newAccountPubkey: mint.publicKey, - space: mintLen, - lamports: lamports, - programId: TOKEN_2022_PROGRAM_ID, - }), - createInitializeTransferHookInstruction( - mint.publicKey, - wallet.publicKey, - program.programId, // Transfer Hook Program ID - TOKEN_2022_PROGRAM_ID - ), - createInitializeMintInstruction( - mint.publicKey, - decimals, - wallet.publicKey, - null, - TOKEN_2022_PROGRAM_ID - ) - ); - - const txSig = await sendAndConfirmTransaction( - provider.connection, - transaction, - [wallet.payer, mint] - ); - console.log(`Transaction Signature: ${txSig}`); + it("Create Mint with Transfer Hook Extension", async () => { + const transactionSignature = await program.methods + .initialize(decimals) + .accounts({ mintAccount: mint.publicKey }) + .signers([mint]) + .rpc({ skipPreflight: true }); + console.log("Your transaction signature", transactionSignature); }); // Create the two token accounts for the transfer-hook enabled mint