instruction.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. //! Instruction types
  2. use {
  3. solana_instruction::{AccountMeta, Instruction},
  4. solana_program_error::ProgramError,
  5. solana_pubkey::Pubkey,
  6. spl_discriminator::{ArrayDiscriminator, SplDiscriminate},
  7. spl_pod::{bytemuck::pod_slice_to_bytes, list::ListView},
  8. spl_tlv_account_resolution::account::ExtraAccountMeta,
  9. std::convert::TryInto,
  10. };
  11. const SYSTEM_PROGRAM_ID: Pubkey = Pubkey::from_str_const("11111111111111111111111111111111");
  12. /// Instructions supported by the transfer hook interface.
  13. #[repr(C)]
  14. #[derive(Clone, Debug, PartialEq)]
  15. pub enum TransferHookInstruction {
  16. /// Runs additional transfer logic.
  17. ///
  18. /// Accounts expected by this instruction:
  19. ///
  20. /// 0. `[]` Source account
  21. /// 1. `[]` Token mint
  22. /// 2. `[]` Destination account
  23. /// 3. `[]` Source account's owner/delegate
  24. /// 4. `[]` (Optional) Validation account
  25. /// 5. ..`5+M` `[]` `M` optional additional accounts, written in
  26. /// validation account data
  27. Execute {
  28. /// Amount of tokens to transfer
  29. amount: u64,
  30. },
  31. /// Initializes the extra account metas on an account, writing into the
  32. /// first open TLV space.
  33. ///
  34. /// Accounts expected by this instruction:
  35. ///
  36. /// 0. `[w]` Account with extra account metas
  37. /// 1. `[]` Mint
  38. /// 2. `[s]` Mint authority
  39. /// 3. `[]` System program
  40. InitializeExtraAccountMetaList {
  41. /// List of `ExtraAccountMeta`s to write into the account
  42. extra_account_metas: Vec<ExtraAccountMeta>,
  43. },
  44. /// Updates the extra account metas on an account by overwriting the
  45. /// existing list.
  46. ///
  47. /// Accounts expected by this instruction:
  48. ///
  49. /// 0. `[w]` Account with extra account metas
  50. /// 1. `[]` Mint
  51. /// 2. `[s]` Mint authority
  52. UpdateExtraAccountMetaList {
  53. /// The new list of `ExtraAccountMetas` to overwrite the existing entry
  54. /// in the account.
  55. extra_account_metas: Vec<ExtraAccountMeta>,
  56. },
  57. }
  58. /// TLV instruction type only used to define the discriminator. The actual data
  59. /// is entirely managed by `ExtraAccountMetaList`, and it is the only data
  60. /// contained by this type.
  61. #[derive(SplDiscriminate)]
  62. #[discriminator_hash_input("spl-transfer-hook-interface:execute")]
  63. pub struct ExecuteInstruction;
  64. /// TLV instruction type used to initialize extra account metas
  65. /// for the transfer hook
  66. #[derive(SplDiscriminate)]
  67. #[discriminator_hash_input("spl-transfer-hook-interface:initialize-extra-account-metas")]
  68. pub struct InitializeExtraAccountMetaListInstruction;
  69. /// TLV instruction type used to update extra account metas
  70. /// for the transfer hook
  71. #[derive(SplDiscriminate)]
  72. #[discriminator_hash_input("spl-transfer-hook-interface:update-extra-account-metas")]
  73. pub struct UpdateExtraAccountMetaListInstruction;
  74. impl TransferHookInstruction {
  75. /// Unpacks a byte buffer into a
  76. /// [`TransferHookInstruction`](enum.TransferHookInstruction.html).
  77. pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
  78. if input.len() < ArrayDiscriminator::LENGTH {
  79. return Err(ProgramError::InvalidInstructionData);
  80. }
  81. let (discriminator, rest) = input.split_at(ArrayDiscriminator::LENGTH);
  82. Ok(match discriminator {
  83. ExecuteInstruction::SPL_DISCRIMINATOR_SLICE => {
  84. let amount = rest
  85. .get(..8)
  86. .and_then(|slice| slice.try_into().ok())
  87. .map(u64::from_le_bytes)
  88. .ok_or(ProgramError::InvalidInstructionData)?;
  89. Self::Execute { amount }
  90. }
  91. InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE => {
  92. let list_view = ListView::<ExtraAccountMeta>::unpack(rest)?;
  93. let extra_account_metas = list_view.to_vec();
  94. Self::InitializeExtraAccountMetaList {
  95. extra_account_metas,
  96. }
  97. }
  98. UpdateExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE => {
  99. let list_view = ListView::<ExtraAccountMeta>::unpack(rest)?;
  100. let extra_account_metas = list_view.to_vec();
  101. Self::UpdateExtraAccountMetaList {
  102. extra_account_metas,
  103. }
  104. }
  105. _ => return Err(ProgramError::InvalidInstructionData),
  106. })
  107. }
  108. /// Packs a [`TransferHookInstruction`](enum.TransferHookInstruction.html)
  109. /// into a byte buffer.
  110. pub fn pack(&self) -> Vec<u8> {
  111. let mut buf = vec![];
  112. match self {
  113. Self::Execute { amount } => {
  114. buf.extend_from_slice(ExecuteInstruction::SPL_DISCRIMINATOR_SLICE);
  115. buf.extend_from_slice(&amount.to_le_bytes());
  116. }
  117. Self::InitializeExtraAccountMetaList {
  118. extra_account_metas,
  119. } => {
  120. buf.extend_from_slice(
  121. InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE,
  122. );
  123. buf.extend_from_slice(&(extra_account_metas.len() as u32).to_le_bytes());
  124. buf.extend_from_slice(pod_slice_to_bytes(extra_account_metas));
  125. }
  126. Self::UpdateExtraAccountMetaList {
  127. extra_account_metas,
  128. } => {
  129. buf.extend_from_slice(
  130. UpdateExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE,
  131. );
  132. buf.extend_from_slice(&(extra_account_metas.len() as u32).to_le_bytes());
  133. buf.extend_from_slice(pod_slice_to_bytes(extra_account_metas));
  134. }
  135. };
  136. buf
  137. }
  138. }
  139. /// Creates an `Execute` instruction, provided all of the additional required
  140. /// account metas
  141. #[allow(clippy::too_many_arguments)]
  142. pub fn execute_with_extra_account_metas(
  143. program_id: &Pubkey,
  144. source_pubkey: &Pubkey,
  145. mint_pubkey: &Pubkey,
  146. destination_pubkey: &Pubkey,
  147. authority_pubkey: &Pubkey,
  148. validate_state_pubkey: &Pubkey,
  149. additional_accounts: &[AccountMeta],
  150. amount: u64,
  151. ) -> Instruction {
  152. let mut instruction = execute(
  153. program_id,
  154. source_pubkey,
  155. mint_pubkey,
  156. destination_pubkey,
  157. authority_pubkey,
  158. amount,
  159. );
  160. instruction
  161. .accounts
  162. .push(AccountMeta::new_readonly(*validate_state_pubkey, false));
  163. instruction.accounts.extend_from_slice(additional_accounts);
  164. instruction
  165. }
  166. /// Creates an `Execute` instruction, without the additional accounts
  167. #[allow(clippy::too_many_arguments)]
  168. pub fn execute(
  169. program_id: &Pubkey,
  170. source_pubkey: &Pubkey,
  171. mint_pubkey: &Pubkey,
  172. destination_pubkey: &Pubkey,
  173. authority_pubkey: &Pubkey,
  174. amount: u64,
  175. ) -> Instruction {
  176. let data = TransferHookInstruction::Execute { amount }.pack();
  177. let accounts = vec![
  178. AccountMeta::new_readonly(*source_pubkey, false),
  179. AccountMeta::new_readonly(*mint_pubkey, false),
  180. AccountMeta::new_readonly(*destination_pubkey, false),
  181. AccountMeta::new_readonly(*authority_pubkey, false),
  182. ];
  183. Instruction {
  184. program_id: *program_id,
  185. accounts,
  186. data,
  187. }
  188. }
  189. /// Creates a `InitializeExtraAccountMetaList` instruction.
  190. pub fn initialize_extra_account_meta_list(
  191. program_id: &Pubkey,
  192. extra_account_metas_pubkey: &Pubkey,
  193. mint_pubkey: &Pubkey,
  194. authority_pubkey: &Pubkey,
  195. extra_account_metas: &[ExtraAccountMeta],
  196. ) -> Instruction {
  197. let data = TransferHookInstruction::InitializeExtraAccountMetaList {
  198. extra_account_metas: extra_account_metas.to_vec(),
  199. }
  200. .pack();
  201. let accounts = vec![
  202. AccountMeta::new(*extra_account_metas_pubkey, false),
  203. AccountMeta::new_readonly(*mint_pubkey, false),
  204. AccountMeta::new_readonly(*authority_pubkey, true),
  205. AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
  206. ];
  207. Instruction {
  208. program_id: *program_id,
  209. accounts,
  210. data,
  211. }
  212. }
  213. /// Creates a `UpdateExtraAccountMetaList` instruction.
  214. pub fn update_extra_account_meta_list(
  215. program_id: &Pubkey,
  216. extra_account_metas_pubkey: &Pubkey,
  217. mint_pubkey: &Pubkey,
  218. authority_pubkey: &Pubkey,
  219. extra_account_metas: &[ExtraAccountMeta],
  220. ) -> Instruction {
  221. let data = TransferHookInstruction::UpdateExtraAccountMetaList {
  222. extra_account_metas: extra_account_metas.to_vec(),
  223. }
  224. .pack();
  225. let accounts = vec![
  226. AccountMeta::new(*extra_account_metas_pubkey, false),
  227. AccountMeta::new_readonly(*mint_pubkey, false),
  228. AccountMeta::new_readonly(*authority_pubkey, true),
  229. ];
  230. Instruction {
  231. program_id: *program_id,
  232. accounts,
  233. data,
  234. }
  235. }
  236. #[cfg(test)]
  237. mod test {
  238. use {
  239. super::*, crate::NAMESPACE, solana_sha256_hasher::hashv, spl_pod::bytemuck::pod_from_bytes,
  240. };
  241. #[test]
  242. fn system_program_id() {
  243. assert_eq!(solana_system_interface::program::id(), SYSTEM_PROGRAM_ID);
  244. }
  245. #[test]
  246. fn validate_packing() {
  247. let amount = 111_111_111;
  248. let check = TransferHookInstruction::Execute { amount };
  249. let packed = check.pack();
  250. // Please use ExecuteInstruction::SPL_DISCRIMINATOR in your program, the
  251. // following is just for test purposes
  252. let preimage = hashv(&[format!("{NAMESPACE}:execute").as_bytes()]);
  253. let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
  254. let mut expect = vec![];
  255. expect.extend_from_slice(discriminator.as_ref());
  256. expect.extend_from_slice(&amount.to_le_bytes());
  257. assert_eq!(packed, expect);
  258. let unpacked = TransferHookInstruction::unpack(&expect).unwrap();
  259. assert_eq!(unpacked, check);
  260. }
  261. #[test]
  262. fn initialize_validation_pubkeys_packing() {
  263. let extra_meta_len_bytes = &[
  264. 1, 0, 0, 0, // `1u32`
  265. ];
  266. let extra_meta_bytes = &[
  267. 0, // `AccountMeta`
  268. 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,
  269. 1, 1, 1, // pubkey
  270. 0, // is_signer
  271. 0, // is_writable
  272. ];
  273. let extra_account_metas =
  274. vec![*pod_from_bytes::<ExtraAccountMeta>(extra_meta_bytes).unwrap()];
  275. let check = TransferHookInstruction::InitializeExtraAccountMetaList {
  276. extra_account_metas,
  277. };
  278. let packed = check.pack();
  279. // Please use INITIALIZE_EXTRA_ACCOUNT_METAS_DISCRIMINATOR in your program,
  280. // the following is just for test purposes
  281. let preimage = hashv(&[format!("{NAMESPACE}:initialize-extra-account-metas").as_bytes()]);
  282. let discriminator = &preimage.as_ref()[..ArrayDiscriminator::LENGTH];
  283. let mut expect = vec![];
  284. expect.extend_from_slice(discriminator.as_ref());
  285. expect.extend_from_slice(extra_meta_len_bytes);
  286. expect.extend_from_slice(extra_meta_bytes);
  287. assert_eq!(packed, expect);
  288. let unpacked = TransferHookInstruction::unpack(&expect).unwrap();
  289. assert_eq!(unpacked, check);
  290. }
  291. }