| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- //! Instruction types
- use {
- solana_instruction::{AccountMeta, Instruction},
- solana_program_error::ProgramError,
- solana_pubkey::Pubkey,
- spl_discriminator::{ArrayDiscriminator, SplDiscriminate},
- spl_pod::{bytemuck::pod_slice_to_bytes, list::ListView},
- spl_tlv_account_resolution::account::ExtraAccountMeta,
- std::convert::TryInto,
- };
- const SYSTEM_PROGRAM_ID: Pubkey = Pubkey::from_str_const("11111111111111111111111111111111");
- /// Instructions supported by the transfer hook interface.
- #[repr(C)]
- #[derive(Clone, Debug, PartialEq)]
- pub enum TransferHookInstruction {
- /// Runs additional transfer logic.
- ///
- /// Accounts expected by this instruction:
- ///
- /// 0. `[]` Source account
- /// 1. `[]` Token mint
- /// 2. `[]` Destination account
- /// 3. `[]` Source account's owner/delegate
- /// 4. `[]` (Optional) Validation account
- /// 5. ..`5+M` `[]` `M` optional additional accounts, written in
- /// validation account data
- Execute {
- /// Amount of tokens to transfer
- amount: u64,
- },
- /// Initializes the extra account metas on an account, writing into the
- /// first open TLV space.
- ///
- /// Accounts expected by this instruction:
- ///
- /// 0. `[w]` Account with extra account metas
- /// 1. `[]` Mint
- /// 2. `[s]` Mint authority
- /// 3. `[]` System program
- InitializeExtraAccountMetaList {
- /// List of `ExtraAccountMeta`s to write into the account
- extra_account_metas: Vec<ExtraAccountMeta>,
- },
- /// Updates the extra account metas on an account by overwriting the
- /// existing list.
- ///
- /// Accounts expected by this instruction:
- ///
- /// 0. `[w]` Account with extra account metas
- /// 1. `[]` Mint
- /// 2. `[s]` Mint authority
- UpdateExtraAccountMetaList {
- /// The new list of `ExtraAccountMetas` to overwrite the existing entry
- /// in the account.
- extra_account_metas: Vec<ExtraAccountMeta>,
- },
- }
- /// TLV instruction type only used to define the discriminator. The actual data
- /// is entirely managed by `ExtraAccountMetaList`, and it is the only data
- /// contained by this type.
- #[derive(SplDiscriminate)]
- #[discriminator_hash_input("spl-transfer-hook-interface:execute")]
- pub struct ExecuteInstruction;
- /// TLV instruction type used to initialize extra account metas
- /// for the transfer hook
- #[derive(SplDiscriminate)]
- #[discriminator_hash_input("spl-transfer-hook-interface:initialize-extra-account-metas")]
- pub struct InitializeExtraAccountMetaListInstruction;
- /// TLV instruction type used to update extra account metas
- /// for the transfer hook
- #[derive(SplDiscriminate)]
- #[discriminator_hash_input("spl-transfer-hook-interface:update-extra-account-metas")]
- pub struct UpdateExtraAccountMetaListInstruction;
- impl TransferHookInstruction {
- /// Unpacks a byte buffer into a
- /// [`TransferHookInstruction`](enum.TransferHookInstruction.html).
- pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
- if input.len() < ArrayDiscriminator::LENGTH {
- return Err(ProgramError::InvalidInstructionData);
- }
- let (discriminator, rest) = input.split_at(ArrayDiscriminator::LENGTH);
- Ok(match discriminator {
- ExecuteInstruction::SPL_DISCRIMINATOR_SLICE => {
- let amount = rest
- .get(..8)
- .and_then(|slice| slice.try_into().ok())
- .map(u64::from_le_bytes)
- .ok_or(ProgramError::InvalidInstructionData)?;
- Self::Execute { amount }
- }
- InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE => {
- let list_view = ListView::<ExtraAccountMeta>::unpack(rest)?;
- let extra_account_metas = list_view.to_vec();
- Self::InitializeExtraAccountMetaList {
- extra_account_metas,
- }
- }
- UpdateExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE => {
- let list_view = ListView::<ExtraAccountMeta>::unpack(rest)?;
- let extra_account_metas = list_view.to_vec();
- Self::UpdateExtraAccountMetaList {
- extra_account_metas,
- }
- }
- _ => return Err(ProgramError::InvalidInstructionData),
- })
- }
- /// Packs a [`TransferHookInstruction`](enum.TransferHookInstruction.html)
- /// into a byte buffer.
- pub fn pack(&self) -> Vec<u8> {
- let mut buf = vec![];
- match self {
- Self::Execute { amount } => {
- buf.extend_from_slice(ExecuteInstruction::SPL_DISCRIMINATOR_SLICE);
- buf.extend_from_slice(&amount.to_le_bytes());
- }
- Self::InitializeExtraAccountMetaList {
- extra_account_metas,
- } => {
- buf.extend_from_slice(
- InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE,
- );
- buf.extend_from_slice(&(extra_account_metas.len() as u32).to_le_bytes());
- buf.extend_from_slice(pod_slice_to_bytes(extra_account_metas));
- }
- Self::UpdateExtraAccountMetaList {
- extra_account_metas,
- } => {
- buf.extend_from_slice(
- UpdateExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE,
- );
- buf.extend_from_slice(&(extra_account_metas.len() as u32).to_le_bytes());
- buf.extend_from_slice(pod_slice_to_bytes(extra_account_metas));
- }
- };
- buf
- }
- }
- /// Creates an `Execute` instruction, provided all of the additional required
- /// account metas
- #[allow(clippy::too_many_arguments)]
- pub fn execute_with_extra_account_metas(
- program_id: &Pubkey,
- source_pubkey: &Pubkey,
- mint_pubkey: &Pubkey,
- destination_pubkey: &Pubkey,
- authority_pubkey: &Pubkey,
- validate_state_pubkey: &Pubkey,
- additional_accounts: &[AccountMeta],
- amount: u64,
- ) -> Instruction {
- let mut instruction = execute(
- program_id,
- source_pubkey,
- mint_pubkey,
- destination_pubkey,
- authority_pubkey,
- amount,
- );
- instruction
- .accounts
- .push(AccountMeta::new_readonly(*validate_state_pubkey, false));
- instruction.accounts.extend_from_slice(additional_accounts);
- instruction
- }
- /// Creates an `Execute` instruction, without the additional accounts
- #[allow(clippy::too_many_arguments)]
- pub fn execute(
- program_id: &Pubkey,
- source_pubkey: &Pubkey,
- mint_pubkey: &Pubkey,
- destination_pubkey: &Pubkey,
- authority_pubkey: &Pubkey,
- amount: u64,
- ) -> Instruction {
- let data = TransferHookInstruction::Execute { amount }.pack();
- let accounts = vec![
- AccountMeta::new_readonly(*source_pubkey, false),
- AccountMeta::new_readonly(*mint_pubkey, false),
- AccountMeta::new_readonly(*destination_pubkey, false),
- AccountMeta::new_readonly(*authority_pubkey, false),
- ];
- Instruction {
- program_id: *program_id,
- accounts,
- data,
- }
- }
- /// Creates a `InitializeExtraAccountMetaList` instruction.
- pub fn initialize_extra_account_meta_list(
- program_id: &Pubkey,
- extra_account_metas_pubkey: &Pubkey,
- mint_pubkey: &Pubkey,
- authority_pubkey: &Pubkey,
- extra_account_metas: &[ExtraAccountMeta],
- ) -> Instruction {
- let data = TransferHookInstruction::InitializeExtraAccountMetaList {
- extra_account_metas: extra_account_metas.to_vec(),
- }
- .pack();
- let accounts = vec![
- AccountMeta::new(*extra_account_metas_pubkey, false),
- AccountMeta::new_readonly(*mint_pubkey, false),
- AccountMeta::new_readonly(*authority_pubkey, true),
- AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
- ];
- Instruction {
- program_id: *program_id,
- accounts,
- data,
- }
- }
- /// Creates a `UpdateExtraAccountMetaList` instruction.
- pub fn update_extra_account_meta_list(
- program_id: &Pubkey,
- extra_account_metas_pubkey: &Pubkey,
- mint_pubkey: &Pubkey,
- authority_pubkey: &Pubkey,
- extra_account_metas: &[ExtraAccountMeta],
- ) -> Instruction {
- let data = TransferHookInstruction::UpdateExtraAccountMetaList {
- extra_account_metas: extra_account_metas.to_vec(),
- }
- .pack();
- let accounts = vec![
- AccountMeta::new(*extra_account_metas_pubkey, false),
- AccountMeta::new_readonly(*mint_pubkey, false),
- AccountMeta::new_readonly(*authority_pubkey, true),
- ];
- Instruction {
- program_id: *program_id,
- accounts,
- data,
- }
- }
- #[cfg(test)]
- mod test {
- use {
- super::*, crate::NAMESPACE, solana_sha256_hasher::hashv, spl_pod::bytemuck::pod_from_bytes,
- };
- #[test]
- fn system_program_id() {
- assert_eq!(solana_system_interface::program::id(), SYSTEM_PROGRAM_ID);
- }
- #[test]
- fn validate_packing() {
- let amount = 111_111_111;
- let check = TransferHookInstruction::Execute { amount };
- let packed = check.pack();
- // Please use ExecuteInstruction::SPL_DISCRIMINATOR in your program, the
- // following is just for test purposes
- let preimage = hashv(&[format!("{NAMESPACE}:execute").as_bytes()]);
- let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
- let mut expect = vec![];
- expect.extend_from_slice(discriminator.as_ref());
- expect.extend_from_slice(&amount.to_le_bytes());
- assert_eq!(packed, expect);
- let unpacked = TransferHookInstruction::unpack(&expect).unwrap();
- assert_eq!(unpacked, check);
- }
- #[test]
- fn initialize_validation_pubkeys_packing() {
- let extra_meta_len_bytes = &[
- 1, 0, 0, 0, // `1u32`
- ];
- let extra_meta_bytes = &[
- 0, // `AccountMeta`
- 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, // pubkey
- 0, // is_signer
- 0, // is_writable
- ];
- let extra_account_metas =
- vec![*pod_from_bytes::<ExtraAccountMeta>(extra_meta_bytes).unwrap()];
- let check = TransferHookInstruction::InitializeExtraAccountMetaList {
- extra_account_metas,
- };
- let packed = check.pack();
- // Please use INITIALIZE_EXTRA_ACCOUNT_METAS_DISCRIMINATOR in your program,
- // the following is just for test purposes
- let preimage = hashv(&[format!("{NAMESPACE}:initialize-extra-account-metas").as_bytes()]);
- let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
- let mut expect = vec![];
- expect.extend_from_slice(discriminator.as_ref());
- expect.extend_from_slice(extra_meta_len_bytes);
- expect.extend_from_slice(extra_meta_bytes);
- assert_eq!(packed, expect);
- let unpacked = TransferHookInstruction::unpack(&expect).unwrap();
- assert_eq!(unpacked, check);
- }
- }
|