diff --git a/Cargo.lock b/Cargo.lock index 6e998ef754..3737cdc374 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -709,7 +709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" dependencies = [ "borsh-derive 0.10.3", - "hashbrown 0.13.2", + "hashbrown 0.12.3", ] [[package]] @@ -3299,6 +3299,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "num-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e6a0fd4f737c707bd9086cc16c925f294943eb62eb71499e9fd4cf71f8b9f4e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -3370,6 +3381,15 @@ dependencies = [ "num_enum_derive 0.6.1", ] +[[package]] +name = "num_enum" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" +dependencies = [ + "num_enum_derive 0.7.0", +] + [[package]] name = "num_enum_derive" version = "0.5.11" @@ -3394,6 +3414,18 @@ dependencies = [ "syn 2.0.37", ] +[[package]] +name = "num_enum_derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" +dependencies = [ + "proc-macro-crate 1.1.0", + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "num_threads" version = "0.1.3" @@ -5077,8 +5109,10 @@ dependencies = [ "serde_json", "solana-config-program", "solana-sdk", + "spl-pod", "spl-token", "spl-token-2022", + "spl-token-metadata-interface", "thiserror", "zstd", ] @@ -5155,7 +5189,7 @@ dependencies = [ "memmap2", "memoffset 0.9.0", "modular-bitfield", - "num-derive", + "num-derive 0.3.3", "num-traits", "num_cpus", "num_enum 0.6.1", @@ -5198,7 +5232,7 @@ dependencies = [ "bincode", "bytemuck", "log", - "num-derive", + "num-derive 0.3.3", "num-traits", "rustc_version 0.4.0", "serde", @@ -6160,6 +6194,7 @@ dependencies = [ "solana-transaction-status", "solana-vote", "solana-vote-program", + "spl-pod", "spl-token", "spl-token-2022", "static_assertions", @@ -6483,7 +6518,7 @@ dependencies = [ "log", "memoffset 0.9.0", "num-bigint 0.4.4", - "num-derive", + "num-derive 0.3.3", "num-traits", "parking_lot 0.12.1", "rand 0.8.5", @@ -6519,7 +6554,7 @@ dependencies = [ "libc", "libsecp256k1", "log", - "num-derive", + "num-derive 0.3.3", "num-traits", "percentage", "rand 0.8.5", @@ -6632,7 +6667,7 @@ dependencies = [ "dialoguer", "hidapi", "log", - "num-derive", + "num-derive 0.3.3", "num-traits", "parking_lot 0.12.1", "qstring", @@ -6691,6 +6726,7 @@ dependencies = [ "solana-version", "solana-vote", "solana-vote-program", + "spl-pod", "spl-token", "spl-token-2022", "stream-cancel", @@ -6824,7 +6860,7 @@ dependencies = [ "memmap2", "memoffset 0.9.0", "modular-bitfield", - "num-derive", + "num-derive 0.3.3", "num-traits", "num_cpus", "num_enum 0.6.1", @@ -6904,7 +6940,7 @@ dependencies = [ "libsecp256k1", "log", "memmap2", - "num-derive", + "num-derive 0.3.3", "num-traits", "num_enum 0.6.1", "pbkdf2 0.11.0", @@ -7232,7 +7268,7 @@ dependencies = [ "Inflector", "base64 0.21.4", "bincode", - "borsh 0.9.3", + "borsh 0.10.3", "bs58", "lazy_static", "log", @@ -7410,7 +7446,7 @@ dependencies = [ "assert_matches", "bincode", "log", - "num-derive", + "num-derive 0.3.3", "num-traits", "rustc_version 0.4.0", "serde", @@ -7471,7 +7507,7 @@ dependencies = [ "bytemuck", "criterion", "curve25519-dalek", - "num-derive", + "num-derive 0.3.3", "num-traits", "solana-program-runtime", "solana-sdk", @@ -7504,7 +7540,7 @@ dependencies = [ "itertools", "lazy_static", "merlin", - "num-derive", + "num-derive 0.3.3", "num-traits", "rand 0.7.3", "serde", @@ -7562,13 +7598,13 @@ dependencies = [ [[package]] name = "spl-associated-token-account" -version = "1.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978dba3bcbe88d0c2c58366c254d9ea41c5f73357e72fc0bdee4d6b5fc99c8f4" +checksum = "385e31c29981488f2820b2022d8e731aae3b02e6e18e2fd854e4c9a94dc44fc3" dependencies = [ "assert_matches", - "borsh 0.9.3", - "num-derive", + "borsh 0.10.3", + "num-derive 0.4.0", "num-traits", "solana-program", "spl-token", @@ -7576,6 +7612,41 @@ dependencies = [ "thiserror", ] +[[package]] +name = "spl-discriminator" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cce5d563b58ef1bb2cdbbfe0dfb9ffdc24903b10ae6a4df2d8f425ece375033f" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator-derive", +] + +[[package]] +name = "spl-discriminator-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadbefec4f3c678215ca72bd71862697bb06b41fd77c0088902dd3203354387b" +dependencies = [ + "quote", + "spl-discriminator-syn", + "syn 2.0.37", +] + +[[package]] +name = "spl-discriminator-syn" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e5f2044ca42c8938d54d1255ce599c79a1ffd86b677dfab695caa20f9ffc3f2" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.7", + "syn 2.0.37", + "thiserror", +] + [[package]] name = "spl-instruction-padding" version = "0.1.0" @@ -7588,46 +7659,145 @@ dependencies = [ [[package]] name = "spl-memo" -version = "3.0.1" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0dc6f70db6bacea7ff25870b016a65ba1d1b6013536f08e4fd79a8f9005325" +checksum = "f0f180b03318c3dbab3ef4e1e4d46d5211ae3c780940dd0a28695aba4b59a75a" dependencies = [ "solana-program", ] [[package]] -name = "spl-token" -version = "3.5.0" +name = "spl-pod" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e85e168a785e82564160dcb87b2a8e04cee9bfd1f4d488c729d53d6a4bd300d" +checksum = "2881dddfca792737c0706fa0175345ab282b1b0879c7d877bad129645737c079" +dependencies = [ + "borsh 0.10.3", + "bytemuck", + "solana-program", + "solana-zk-token-sdk", + "spl-program-error", +] + +[[package]] +name = "spl-program-error" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "249e0318493b6bcf27ae9902600566c689b7dfba9f1bdff5893e92253374e78c" +dependencies = [ + "num-derive 0.4.0", + "num-traits", + "solana-program", + "spl-program-error-derive", + "thiserror", +] + +[[package]] +name = "spl-program-error-derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5269c8e868da17b6552ef35a51355a017bd8e0eae269c201fef830d35fa52c" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.7", + "syn 2.0.37", +] + +[[package]] +name = "spl-tlv-account-resolution" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-type-length-value", +] + +[[package]] +name = "spl-token" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08459ba1b8f7c1020b4582c4edf0f5c7511a5e099a7a97570c9698d4f2337060" dependencies = [ "arrayref", "bytemuck", - "num-derive", + "num-derive 0.3.3", "num-traits", - "num_enum 0.5.11", + "num_enum 0.6.1", "solana-program", "thiserror", ] [[package]] name = "spl-token-2022" -version = "0.6.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0043b590232c400bad5ee9eb983ced003d15163c4c5d56b090ac6d9a57457b47" +checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86" dependencies = [ "arrayref", "bytemuck", - "num-derive", + "num-derive 0.4.0", "num-traits", - "num_enum 0.5.11", + "num_enum 0.7.0", "solana-program", "solana-zk-token-sdk", "spl-memo", + "spl-pod", "spl-token", + "spl-token-metadata-interface", + "spl-transfer-hook-interface", + "spl-type-length-value", "thiserror", ] +[[package]] +name = "spl-token-metadata-interface" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c16ce3ba6979645fb7627aa1e435576172dd63088dc7848cb09aa331fa1fe4f" +dependencies = [ + "borsh 0.10.3", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-type-length-value", +] + +[[package]] +name = "spl-transfer-hook-interface" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051d31803f873cabe71aec3c1b849f35248beae5d19a347d93a5c9cccc5d5a9b" +dependencies = [ + "arrayref", + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-tlv-account-resolution", + "spl-type-length-value", +] + +[[package]] +name = "spl-type-length-value" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a468e6f6371f9c69aae760186ea9f1a01c2908351b06a5e0026d21cfc4d7ecac" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", +] + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 6e31e785e6..c0f31b9e37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -373,11 +373,13 @@ solana-vote-program = { path = "programs/vote", version = "=1.17.0" } solana-zk-keygen = { path = "zk-keygen", version = "=1.17.0" } solana-zk-token-proof-program = { path = "programs/zk-token-proof", version = "=1.17.0" } solana-zk-token-sdk = { path = "zk-token-sdk", version = "=1.17.0" } -spl-associated-token-account = "=1.1.3" +spl-associated-token-account = "=2.2.0" spl-instruction-padding = "0.1" -spl-memo = "=3.0.1" -spl-token = "=3.5.0" -spl-token-2022 = "=0.6.1" +spl-memo = "=4.0.0" +spl-pod = "=0.1.0" +spl-token = "=4.0.0" +spl-token-2022 = "=0.9.0" +spl-token-metadata-interface = "=0.2.0" static_assertions = "1.1.0" stream-cancel = "0.8.1" strum = "0.24" @@ -423,8 +425,10 @@ crossbeam-epoch = { git = "https://github.com/solana-labs/crossbeam", rev = "fd2 # * spl-associated-token-account # * spl-instruction-padding # * spl-memo +# * spl-pod # * spl-token # * spl-token-2022 +# * spl-token-metadata-interface # # They, in turn, depend on a number of crates that we also include directly using `path` # specifications. For example, `spl-token` depends on `solana-program`. And we explicitly specify diff --git a/account-decoder/Cargo.toml b/account-decoder/Cargo.toml index bb82b077dc..3f883ddc23 100644 --- a/account-decoder/Cargo.toml +++ b/account-decoder/Cargo.toml @@ -23,11 +23,13 @@ solana-config-program = { workspace = true } solana-sdk = { workspace = true } spl-token = { workspace = true, features = ["no-entrypoint"] } spl-token-2022 = { workspace = true, features = ["no-entrypoint"] } +spl-token-metadata-interface = { workspace = true } thiserror = { workspace = true } zstd = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } +spl-pod = { workspace = true } [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/account-decoder/src/parse_token.rs b/account-decoder/src/parse_token.rs index 6f03a306cd..42633e9d8e 100644 --- a/account-decoder/src/parse_token.rs +++ b/account-decoder/src/parse_token.rs @@ -290,12 +290,10 @@ mod test { use { super::*, crate::parse_token_extension::{UiMemoTransfer, UiMintCloseAuthority}, - spl_token_2022::{ - extension::{ - immutable_owner::ImmutableOwner, memo_transfer::MemoTransfer, - mint_close_authority::MintCloseAuthority, ExtensionType, StateWithExtensionsMut, - }, - pod::OptionalNonZeroPubkey, + spl_pod::optional_keys::OptionalNonZeroPubkey, + spl_token_2022::extension::{ + immutable_owner::ImmutableOwner, memo_transfer::MemoTransfer, + mint_close_authority::MintCloseAuthority, ExtensionType, StateWithExtensionsMut, }, }; @@ -506,10 +504,11 @@ mod test { delegate: COption::None, delegated_amount: 0, }; - let account_size = ExtensionType::get_account_len::(&[ + let account_size = ExtensionType::try_calculate_account_len::(&[ ExtensionType::ImmutableOwner, ExtensionType::MemoTransfer, - ]); + ]) + .unwrap(); let mut account_data = vec![0; account_size]; let mut account_state = StateWithExtensionsMut::::unpack_uninitialized(&mut account_data).unwrap(); @@ -586,7 +585,8 @@ mod test { fn test_parse_token_mint_with_extensions() { let owner_pubkey = SplTokenPubkey::new_from_array([3; 32]); let mint_size = - ExtensionType::get_account_len::(&[ExtensionType::MintCloseAuthority]); + ExtensionType::try_calculate_account_len::(&[ExtensionType::MintCloseAuthority]) + .unwrap(); let mint_base = Mint { mint_authority: COption::Some(owner_pubkey), supply: 42, diff --git a/account-decoder/src/parse_token_extension.rs b/account-decoder/src/parse_token_extension.rs index 0df45a6b6d..39d26d83a2 100644 --- a/account-decoder/src/parse_token_extension.rs +++ b/account-decoder/src/parse_token_extension.rs @@ -6,6 +6,7 @@ use { solana_program::pubkey::Pubkey, solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalPubkey, }, + spl_token_metadata_interface::state::TokenMetadata, }; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -24,15 +25,21 @@ pub enum UiExtension { InterestBearingConfig(UiInterestBearingConfig), CpiGuard(UiCpiGuard), PermanentDelegate(UiPermanentDelegate), - UnparseableExtension, NonTransferableAccount, + ConfidentialTransferFeeConfig(UiConfidentialTransferFeeConfig), + ConfidentialTransferFeeAmount(UiConfidentialTransferFeeAmount), + TransferHook(UiTransferHook), + TransferHookAccount(UiTransferHookAccount), + MetadataPointer(UiMetadataPointer), + TokenMetadata(UiTokenMetadata), + UnparseableExtension, } pub fn parse_extension( extension_type: &ExtensionType, account: &StateWithExtensions, ) -> UiExtension { - match &extension_type { + match extension_type { ExtensionType::Uninitialized => UiExtension::Uninitialized, ExtensionType::TransferFeeConfig => account .get_extension::() @@ -50,10 +57,18 @@ pub fn parse_extension( .get_extension::() .map(|&extension| UiExtension::ConfidentialTransferMint(extension.into())) .unwrap_or(UiExtension::UnparseableExtension), + ExtensionType::ConfidentialTransferFeeConfig => account + .get_extension::() + .map(|&extension| UiExtension::ConfidentialTransferFeeConfig(extension.into())) + .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::ConfidentialTransferAccount => account .get_extension::() .map(|&extension| UiExtension::ConfidentialTransferAccount(extension.into())) .unwrap_or(UiExtension::UnparseableExtension), + ExtensionType::ConfidentialTransferFeeAmount => account + .get_extension::() + .map(|&extension| UiExtension::ConfidentialTransferFeeAmount(extension.into())) + .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::DefaultAccountState => account .get_extension::() .map(|&extension| UiExtension::DefaultAccountState(extension.into())) @@ -77,6 +92,22 @@ pub fn parse_extension( .map(|&extension| UiExtension::PermanentDelegate(extension.into())) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::NonTransferableAccount => UiExtension::NonTransferableAccount, + ExtensionType::MetadataPointer => account + .get_extension::() + .map(|&extension| UiExtension::MetadataPointer(extension.into())) + .unwrap_or(UiExtension::UnparseableExtension), + ExtensionType::TokenMetadata => account + .get_variable_len_extension::() + .map(|extension| UiExtension::TokenMetadata(extension.into())) + .unwrap_or(UiExtension::UnparseableExtension), + ExtensionType::TransferHook => account + .get_extension::() + .map(|&extension| UiExtension::TransferHook(extension.into())) + .unwrap_or(UiExtension::UnparseableExtension), + ExtensionType::TransferHookAccount => account + .get_extension::() + .map(|&extension| UiExtension::TransferHookAccount(extension.into())) + .unwrap_or(UiExtension::UnparseableExtension), } } @@ -251,9 +282,7 @@ impl From for UiPermanentDeleg pub struct UiConfidentialTransferMint { pub authority: Option, pub auto_approve_new_accounts: bool, - pub auditor_encryption_pubkey: Option, - pub withdraw_withheld_authority_encryption_pubkey: Option, - pub withheld_amount: String, + pub auditor_elgamal_pubkey: Option, } impl From @@ -263,19 +292,44 @@ impl From confidential_transfer_mint: extension::confidential_transfer::ConfidentialTransferMint, ) -> Self { let authority: Option = confidential_transfer_mint.authority.into(); - let auditor_encryption_pubkey: Option = - confidential_transfer_mint.auditor_encryption_pubkey.into(); - let withdraw_withheld_authority_encryption_pubkey: Option = - confidential_transfer_mint - .withdraw_withheld_authority_encryption_pubkey - .into(); + let auditor_elgamal_pubkey: Option = + confidential_transfer_mint.auditor_elgamal_pubkey.into(); Self { authority: authority.map(|pubkey| pubkey.to_string()), auto_approve_new_accounts: confidential_transfer_mint.auto_approve_new_accounts.into(), - auditor_encryption_pubkey: auditor_encryption_pubkey.map(|pubkey| pubkey.to_string()), - withdraw_withheld_authority_encryption_pubkey: - withdraw_withheld_authority_encryption_pubkey.map(|pubkey| pubkey.to_string()), - withheld_amount: format!("{}", confidential_transfer_mint.withheld_amount), + auditor_elgamal_pubkey: auditor_elgamal_pubkey.map(|pubkey| pubkey.to_string()), + } + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiConfidentialTransferFeeConfig { + pub authority: Option, + pub withdraw_withheld_authority_elgamal_pubkey: Option, + pub harvest_to_mint_enabled: bool, + pub withheld_amount: String, +} + +impl From + for UiConfidentialTransferFeeConfig +{ + fn from( + confidential_transfer_fee_config: extension::confidential_transfer_fee::ConfidentialTransferFeeConfig, + ) -> Self { + let authority: Option = confidential_transfer_fee_config.authority.into(); + let withdraw_withheld_authority_elgamal_pubkey: Option = + confidential_transfer_fee_config + .withdraw_withheld_authority_elgamal_pubkey + .into(); + Self { + authority: authority.map(|pubkey| pubkey.to_string()), + withdraw_withheld_authority_elgamal_pubkey: withdraw_withheld_authority_elgamal_pubkey + .map(|pubkey| pubkey.to_string()), + harvest_to_mint_enabled: confidential_transfer_fee_config + .harvest_to_mint_enabled + .into(), + withheld_amount: format!("{}", confidential_transfer_fee_config.withheld_amount), } } } @@ -284,7 +338,7 @@ impl From #[serde(rename_all = "camelCase")] pub struct UiConfidentialTransferAccount { pub approved: bool, - pub encryption_pubkey: String, + pub elgamal_pubkey: String, pub pending_balance_lo: String, pub pending_balance_hi: String, pub available_balance: String, @@ -295,7 +349,6 @@ pub struct UiConfidentialTransferAccount { pub maximum_pending_balance_credit_counter: u64, pub expected_pending_balance_credit_counter: u64, pub actual_pending_balance_credit_counter: u64, - pub withheld_amount: String, } impl From @@ -306,7 +359,7 @@ impl From ) -> Self { Self { approved: confidential_transfer_account.approved.into(), - encryption_pubkey: format!("{}", confidential_transfer_account.encryption_pubkey), + elgamal_pubkey: format!("{}", confidential_transfer_account.elgamal_pubkey), pending_balance_lo: format!("{}", confidential_transfer_account.pending_balance_lo), pending_balance_hi: format!("{}", confidential_transfer_account.pending_balance_hi), available_balance: format!("{}", confidential_transfer_account.available_balance), @@ -332,7 +385,99 @@ impl From actual_pending_balance_credit_counter: confidential_transfer_account .actual_pending_balance_credit_counter .into(), - withheld_amount: format!("{}", confidential_transfer_account.withheld_amount), + } + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiConfidentialTransferFeeAmount { + pub withheld_amount: String, +} + +impl From + for UiConfidentialTransferFeeAmount +{ + fn from( + confidential_transfer_fee_amount: extension::confidential_transfer_fee::ConfidentialTransferFeeAmount, + ) -> Self { + Self { + withheld_amount: format!("{}", confidential_transfer_fee_amount.withheld_amount), + } + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiMetadataPointer { + pub authority: Option, + pub metadata_address: Option, +} + +impl From for UiMetadataPointer { + fn from(metadata_pointer: extension::metadata_pointer::MetadataPointer) -> Self { + let authority: Option = metadata_pointer.authority.into(); + let metadata_address: Option = metadata_pointer.metadata_address.into(); + Self { + authority: authority.map(|pubkey| pubkey.to_string()), + metadata_address: metadata_address.map(|pubkey| pubkey.to_string()), + } + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiTokenMetadata { + pub update_authority: Option, + pub mint: String, + pub name: String, + pub symbol: String, + pub uri: String, + pub additional_metadata: Vec<(String, String)>, +} + +impl From for UiTokenMetadata { + fn from(token_metadata: TokenMetadata) -> Self { + let update_authority: Option = token_metadata.update_authority.into(); + Self { + update_authority: update_authority.map(|pubkey| pubkey.to_string()), + mint: token_metadata.mint.to_string(), + name: token_metadata.name, + symbol: token_metadata.symbol, + uri: token_metadata.uri, + additional_metadata: token_metadata.additional_metadata, + } + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiTransferHook { + pub authority: Option, + pub program_id: Option, +} + +impl From for UiTransferHook { + fn from(transfer_hook: extension::transfer_hook::TransferHook) -> Self { + let authority: Option = transfer_hook.authority.into(); + let program_id: Option = transfer_hook.program_id.into(); + Self { + authority: authority.map(|pubkey| pubkey.to_string()), + program_id: program_id.map(|pubkey| pubkey.to_string()), + } + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiTransferHookAccount { + pub transferring: bool, +} + +impl From for UiTransferHookAccount { + fn from(transfer_hook: extension::transfer_hook::TransferHookAccount) -> Self { + Self { + transferring: transfer_hook.transferring.into(), } } } diff --git a/fetch-spl.sh b/fetch-spl.sh index 2d65148894..bb8e84ebb2 100755 --- a/fetch-spl.sh +++ b/fetch-spl.sh @@ -45,7 +45,7 @@ fetch_program() { } fetch_program token 3.5.0 TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA BPFLoader2111111111111111111111111111111111 -fetch_program token-2022 0.6.0 TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb BPFLoaderUpgradeab1e11111111111111111111111 +fetch_program token-2022 0.9.0 TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb BPFLoaderUpgradeab1e11111111111111111111111 fetch_program memo 1.0.0 Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo BPFLoader1111111111111111111111111111111111 fetch_program memo 3.0.0 MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr BPFLoader2111111111111111111111111111111111 fetch_program associated-token-account 1.1.2 ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL BPFLoader2111111111111111111111111111111111 diff --git a/ledger/Cargo.toml b/ledger/Cargo.toml index f6fbb140e5..df52fb3462 100644 --- a/ledger/Cargo.toml +++ b/ledger/Cargo.toml @@ -78,6 +78,7 @@ features = ["lz4"] bs58 = { workspace = true } solana-account-decoder = { workspace = true } solana-logger = { workspace = true } +spl-pod = { workspace = true } test-case = { workspace = true } [build-dependencies] diff --git a/ledger/src/token_balances.rs b/ledger/src/token_balances.rs index 41f3a38ac3..204bd43359 100644 --- a/ledger/src/token_balances.rs +++ b/ledger/src/token_balances.rs @@ -121,12 +121,12 @@ mod test { use { super::*, solana_sdk::{account::Account, genesis_config::create_genesis_config}, + spl_pod::optional_keys::OptionalNonZeroPubkey, spl_token_2022::{ extension::{ immutable_owner::ImmutableOwner, memo_transfer::MemoTransfer, mint_close_authority::MintCloseAuthority, ExtensionType, StateWithExtensionsMut, }, - pod::OptionalNonZeroPubkey, solana_program::{program_option::COption, program_pack::Pack}, }, std::collections::BTreeMap, @@ -291,7 +291,8 @@ mod test { let mint_authority = Pubkey::new_unique(); let mint_size = - ExtensionType::get_account_len::(&[ExtensionType::MintCloseAuthority]); + ExtensionType::try_calculate_account_len::(&[ExtensionType::MintCloseAuthority]) + .unwrap(); let mint_base = Mint { mint_authority: COption::None, supply: 4242, @@ -339,10 +340,11 @@ mod test { delegated_amount: 0, close_authority: COption::None, }; - let account_size = ExtensionType::get_account_len::(&[ + let account_size = ExtensionType::try_calculate_account_len::(&[ ExtensionType::ImmutableOwner, ExtensionType::MemoTransfer, - ]); + ]) + .unwrap(); let mut account_data = vec![0; account_size]; let mut account_state = StateWithExtensionsMut::::unpack_uninitialized(&mut account_data) @@ -381,10 +383,11 @@ mod test { delegated_amount: 0, close_authority: COption::None, }; - let account_size = ExtensionType::get_account_len::(&[ + let account_size = ExtensionType::try_calculate_account_len::(&[ ExtensionType::ImmutableOwner, ExtensionType::MemoTransfer, - ]); + ]) + .unwrap(); let mut account_data = vec![0; account_size]; let mut account_state = StateWithExtensionsMut::::unpack_uninitialized(&mut account_data) diff --git a/program-test/src/programs.rs b/program-test/src/programs.rs index 2224252da7..ed96be7644 100644 --- a/program-test/src/programs.rs +++ b/program-test/src/programs.rs @@ -30,7 +30,7 @@ static SPL_PROGRAMS: &[(Pubkey, Pubkey, &[u8])] = &[ ( spl_token_2022::ID, solana_sdk::bpf_loader_upgradeable::ID, - include_bytes!("programs/spl_token_2022-0.6.0.so"), + include_bytes!("programs/spl_token_2022-0.9.0.so"), ), ( spl_memo_1_0::ID, diff --git a/program-test/src/programs/spl_token_2022-0.6.0.so b/program-test/src/programs/spl_token_2022-0.6.0.so deleted file mode 100644 index 0638fee195..0000000000 Binary files a/program-test/src/programs/spl_token_2022-0.6.0.so and /dev/null differ diff --git a/program-test/src/programs/spl_token_2022-0.9.0.so b/program-test/src/programs/spl_token_2022-0.9.0.so new file mode 100644 index 0000000000..805d3ed1a4 Binary files /dev/null and b/program-test/src/programs/spl_token_2022-0.9.0.so differ diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index b321cf1842..babcf314e7 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -2918,6 +2918,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "num-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e6a0fd4f737c707bd9086cc16c925f294943eb62eb71499e9fd4cf71f8b9f4e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "num-integer" version = "0.1.42" @@ -2970,15 +2981,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_enum" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d829733185c1ca374f17e52b762f24f535ec625d2cc1f070e34c8a9068f341b" -dependencies = [ - "num_enum_derive 0.5.9", -] - [[package]] name = "num_enum" version = "0.6.1" @@ -2989,15 +2991,12 @@ dependencies = [ ] [[package]] -name = "num_enum_derive" -version = "0.5.9" +name = "num_enum" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be1598bf1c313dcdd12092e3f1920f463462525a21b7b4e11b4168353d0123e" +checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" dependencies = [ - "proc-macro-crate 1.1.3", - "proc-macro2", - "quote", - "syn 1.0.109", + "num_enum_derive 0.7.0", ] [[package]] @@ -3012,6 +3011,18 @@ dependencies = [ "syn 2.0.37", ] +[[package]] +name = "num_enum_derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "num_threads" version = "0.1.5" @@ -4450,6 +4461,7 @@ dependencies = [ "solana-sdk", "spl-token", "spl-token-2022", + "spl-token-metadata-interface", "thiserror", "zstd", ] @@ -4478,7 +4490,7 @@ dependencies = [ "lz4", "memmap2", "modular-bitfield", - "num-derive", + "num-derive 0.3.0", "num-traits", "num_cpus", "num_enum 0.6.1", @@ -4518,7 +4530,7 @@ dependencies = [ "bincode", "bytemuck", "log", - "num-derive", + "num-derive 0.3.0", "num-traits", "rustc_version", "serde", @@ -5241,7 +5253,7 @@ dependencies = [ "log", "memoffset 0.9.0", "num-bigint 0.4.4", - "num-derive", + "num-derive 0.3.0", "num-traits", "parking_lot 0.12.1", "rand 0.8.5", @@ -5273,7 +5285,7 @@ dependencies = [ "itertools", "libc", "log", - "num-derive", + "num-derive 0.3.0", "num-traits", "percentage", "rand 0.8.5", @@ -5378,7 +5390,7 @@ dependencies = [ "console", "dialoguer", "log", - "num-derive", + "num-derive 0.3.0", "num-traits", "parking_lot 0.12.1", "qstring", @@ -5525,7 +5537,7 @@ dependencies = [ "lz4", "memmap2", "modular-bitfield", - "num-derive", + "num-derive 0.3.0", "num-traits", "num_cpus", "num_enum 0.6.1", @@ -5695,7 +5707,7 @@ dependencies = [ name = "solana-sbf-rust-error-handling" version = "1.17.0" dependencies = [ - "num-derive", + "num-derive 0.3.0", "num-traits", "solana-program", "thiserror", @@ -6020,7 +6032,7 @@ dependencies = [ "libsecp256k1 0.6.0", "log", "memmap2", - "num-derive", + "num-derive 0.3.0", "num-traits", "num_enum 0.6.1", "pbkdf2 0.11.0", @@ -6246,7 +6258,7 @@ dependencies = [ "Inflector", "base64 0.21.4", "bincode", - "borsh 0.9.3", + "borsh 0.10.3", "bs58", "lazy_static", "log", @@ -6409,7 +6421,7 @@ version = "1.17.0" dependencies = [ "bincode", "log", - "num-derive", + "num-derive 0.3.0", "num-traits", "rustc_version", "serde", @@ -6428,7 +6440,7 @@ name = "solana-zk-token-proof-program" version = "1.17.0" dependencies = [ "bytemuck", - "num-derive", + "num-derive 0.3.0", "num-traits", "solana-program-runtime", "solana-sdk", @@ -6449,7 +6461,7 @@ dependencies = [ "itertools", "lazy_static", "merlin", - "num-derive", + "num-derive 0.3.0", "num-traits", "rand 0.7.3", "serde", @@ -6505,13 +6517,13 @@ dependencies = [ [[package]] name = "spl-associated-token-account" -version = "1.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978dba3bcbe88d0c2c58366c254d9ea41c5f73357e72fc0bdee4d6b5fc99c8f4" +checksum = "385e31c29981488f2820b2022d8e731aae3b02e6e18e2fd854e4c9a94dc44fc3" dependencies = [ "assert_matches", - "borsh 0.9.3", - "num-derive", + "borsh 0.10.3", + "num-derive 0.4.0", "num-traits", "solana-program", "spl-token", @@ -6520,47 +6532,181 @@ dependencies = [ ] [[package]] -name = "spl-memo" -version = "3.0.1" +name = "spl-discriminator" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0dc6f70db6bacea7ff25870b016a65ba1d1b6013536f08e4fd79a8f9005325" +checksum = "cce5d563b58ef1bb2cdbbfe0dfb9ffdc24903b10ae6a4df2d8f425ece375033f" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator-derive", +] + +[[package]] +name = "spl-discriminator-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadbefec4f3c678215ca72bd71862697bb06b41fd77c0088902dd3203354387b" +dependencies = [ + "quote", + "spl-discriminator-syn", + "syn 2.0.37", +] + +[[package]] +name = "spl-discriminator-syn" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e5f2044ca42c8938d54d1255ce599c79a1ffd86b677dfab695caa20f9ffc3f2" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.7", + "syn 2.0.37", + "thiserror", +] + +[[package]] +name = "spl-memo" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f180b03318c3dbab3ef4e1e4d46d5211ae3c780940dd0a28695aba4b59a75a" dependencies = [ "solana-program", ] [[package]] -name = "spl-token" -version = "3.5.0" +name = "spl-pod" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e85e168a785e82564160dcb87b2a8e04cee9bfd1f4d488c729d53d6a4bd300d" +checksum = "2881dddfca792737c0706fa0175345ab282b1b0879c7d877bad129645737c079" +dependencies = [ + "borsh 0.10.3", + "bytemuck", + "solana-program", + "solana-zk-token-sdk", + "spl-program-error", +] + +[[package]] +name = "spl-program-error" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "249e0318493b6bcf27ae9902600566c689b7dfba9f1bdff5893e92253374e78c" +dependencies = [ + "num-derive 0.4.0", + "num-traits", + "solana-program", + "spl-program-error-derive", + "thiserror", +] + +[[package]] +name = "spl-program-error-derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5269c8e868da17b6552ef35a51355a017bd8e0eae269c201fef830d35fa52c" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.7", + "syn 2.0.37", +] + +[[package]] +name = "spl-tlv-account-resolution" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-type-length-value", +] + +[[package]] +name = "spl-token" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08459ba1b8f7c1020b4582c4edf0f5c7511a5e099a7a97570c9698d4f2337060" dependencies = [ "arrayref", "bytemuck", - "num-derive", + "num-derive 0.3.0", "num-traits", - "num_enum 0.5.9", + "num_enum 0.6.1", "solana-program", "thiserror", ] [[package]] name = "spl-token-2022" -version = "0.6.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0043b590232c400bad5ee9eb983ced003d15163c4c5d56b090ac6d9a57457b47" +checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86" dependencies = [ "arrayref", "bytemuck", - "num-derive", + "num-derive 0.4.0", "num-traits", - "num_enum 0.5.9", + "num_enum 0.7.0", "solana-program", "solana-zk-token-sdk", "spl-memo", + "spl-pod", "spl-token", + "spl-token-metadata-interface", + "spl-transfer-hook-interface", + "spl-type-length-value", "thiserror", ] +[[package]] +name = "spl-token-metadata-interface" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c16ce3ba6979645fb7627aa1e435576172dd63088dc7848cb09aa331fa1fe4f" +dependencies = [ + "borsh 0.10.3", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-type-length-value", +] + +[[package]] +name = "spl-transfer-hook-interface" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051d31803f873cabe71aec3c1b849f35248beae5d19a347d93a5c9cccc5d5a9b" +dependencies = [ + "arrayref", + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-tlv-account-resolution", + "spl-type-length-value", +] + +[[package]] +name = "spl-type-length-value" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a468e6f6371f9c69aae760186ea9f1a01c2908351b06a5e0026d21cfc4d7ecac" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", +] + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/programs/sbf/Cargo.toml b/programs/sbf/Cargo.toml index 15d4b12e91..5e2370576d 100644 --- a/programs/sbf/Cargo.toml +++ b/programs/sbf/Cargo.toml @@ -171,8 +171,10 @@ targets = ["x86_64-unknown-linux-gnu"] # * spl-associated-token-account # * spl-instruction-padding # * spl-memo +# * spl-pod # * spl-token # * spl-token-2022 +# * spl-token-metadata-interface # # They are included indirectly, for example, `account-decoder` depends on # diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index f6fa5160f9..3edd4b3800 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -64,6 +64,7 @@ tokio-util = { workspace = true, features = ["codec", "compat"] } serial_test = { workspace = true } solana-net-utils = { workspace = true } solana-stake-program = { workspace = true } +spl-pod = { workspace = true } symlink = { workspace = true } [lib] diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 997102b3e6..d37a6f8823 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -4683,12 +4683,12 @@ pub mod tests { vote_instruction, vote_state::{self, Vote, VoteInit, VoteStateVersions, MAX_LOCKOUT_HISTORY}, }, + spl_pod::optional_keys::OptionalNonZeroPubkey, spl_token_2022::{ extension::{ immutable_owner::ImmutableOwner, memo_transfer::MemoTransfer, mint_close_authority::MintCloseAuthority, ExtensionType, StateWithExtensionsMut, }, - pod::OptionalNonZeroPubkey, solana_program::{program_option::COption, pubkey::Pubkey as SplTokenPubkey}, state::{AccountState as TokenAccountState, Mint}, }, @@ -7439,10 +7439,11 @@ pub mod tests { delegated_amount: 30, close_authority: COption::Some(owner), }; - let account_size = ExtensionType::get_account_len::(&[ + let account_size = ExtensionType::try_calculate_account_len::(&[ ExtensionType::ImmutableOwner, ExtensionType::MemoTransfer, - ]); + ]) + .unwrap(); let mut account_data = vec![0; account_size]; let mut account_state = StateWithExtensionsMut::::unpack_uninitialized(&mut account_data) @@ -7466,8 +7467,10 @@ pub mod tests { bank.store_account(&token_account_pubkey, &token_account); // Add the mint - let mint_size = - ExtensionType::get_account_len::(&[ExtensionType::MintCloseAuthority]); + let mint_size = ExtensionType::try_calculate_account_len::(&[ + ExtensionType::MintCloseAuthority, + ]) + .unwrap(); let mint_base = Mint { mint_authority: COption::Some(owner), supply: 500, @@ -7931,10 +7934,11 @@ pub mod tests { delegated_amount: 30, close_authority: COption::Some(owner), }; - let account_size = ExtensionType::get_account_len::(&[ + let account_size = ExtensionType::try_calculate_account_len::(&[ ExtensionType::ImmutableOwner, ExtensionType::MemoTransfer, - ]); + ]) + .unwrap(); let mut account_data = vec![0; account_size]; let mut account_state = StateWithExtensionsMut::::unpack_uninitialized(&mut account_data) @@ -7957,8 +7961,10 @@ pub mod tests { }); bank.store_account(&token_account_pubkey, &token_account); - let mint_size = - ExtensionType::get_account_len::(&[ExtensionType::MintCloseAuthority]); + let mint_size = ExtensionType::try_calculate_account_len::(&[ + ExtensionType::MintCloseAuthority, + ]) + .unwrap(); let mint_base = Mint { mint_authority: COption::Some(owner), supply: 500, diff --git a/transaction-status/Cargo.toml b/transaction-status/Cargo.toml index bbdbc6b0bd..3c830f5914 100644 --- a/transaction-status/Cargo.toml +++ b/transaction-status/Cargo.toml @@ -13,8 +13,7 @@ edition = { workspace = true } Inflector = { workspace = true } base64 = { workspace = true } bincode = { workspace = true } -# NOTE: Use the workspace version once spl-associated-token-account uses borsh 0.10. -borsh0-9 = { package = "borsh", version = "0.9.3" } +borsh = { workspace = true } bs58 = { workspace = true } lazy_static = { workspace = true } log = { workspace = true } diff --git a/transaction-status/src/parse_associated_token.rs b/transaction-status/src/parse_associated_token.rs index f14eab3289..e03fd185a6 100644 --- a/transaction-status/src/parse_associated_token.rs +++ b/transaction-status/src/parse_associated_token.rs @@ -2,7 +2,7 @@ use { crate::parse_instruction::{ check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum, }, - borsh0_9::BorshDeserialize, + borsh::BorshDeserialize, serde_json::json, solana_sdk::{instruction::CompiledInstruction, message::AccountKeys, pubkey::Pubkey}, spl_associated_token_account::instruction::AssociatedTokenAccountInstruction, diff --git a/transaction-status/src/parse_token.rs b/transaction-status/src/parse_token.rs index 33a29ff140..ce57111c95 100644 --- a/transaction-status/src/parse_token.rs +++ b/transaction-status/src/parse_token.rs @@ -3,9 +3,10 @@ use { check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum, }, extension::{ - confidential_transfer::*, cpi_guard::*, default_account_state::*, interest_bearing_mint::*, - memo_transfer::*, mint_close_authority::*, permanent_delegate::*, reallocate::*, - transfer_fee::*, + confidential_transfer::*, confidential_transfer_fee::*, cpi_guard::*, + default_account_state::*, interest_bearing_mint::*, memo_transfer::*, metadata_pointer::*, + mint_close_authority::*, permanent_delegate::*, reallocate::*, transfer_fee::*, + transfer_hook::*, }, serde_json::{json, Map, Value}, solana_account_decoder::parse_token::{token_amount_to_ui_amount, UiAccountState}, @@ -229,7 +230,10 @@ pub fn parse_token( | AuthorityType::CloseMint | AuthorityType::InterestRate | AuthorityType::PermanentDelegate - | AuthorityType::ConfidentialTransferMint => "mint", + | AuthorityType::ConfidentialTransferMint + | AuthorityType::TransferHookProgramId + | AuthorityType::ConfidentialTransferFeeConfig + | AuthorityType::MetadataPointer => "mint", AuthorityType::AccountOwner | AuthorityType::CloseAccount => "account", }; let mut value = json!({ @@ -590,6 +594,62 @@ pub fn parse_token( account_keys, ) } + TokenInstruction::TransferHookExtension => { + if instruction.data.len() < 2 { + return Err(ParseInstructionError::InstructionNotParsable( + ParsableProgram::SplToken, + )); + } + parse_transfer_hook_instruction( + &instruction.data[1..], + &instruction.accounts, + account_keys, + ) + } + TokenInstruction::ConfidentialTransferFeeExtension => { + if instruction.data.len() < 2 { + return Err(ParseInstructionError::InstructionNotParsable( + ParsableProgram::SplToken, + )); + } + parse_confidential_transfer_fee_instruction( + &instruction.data[1..], + &instruction.accounts, + account_keys, + ) + } + TokenInstruction::WithdrawExcessLamports => { + check_num_token_accounts(&instruction.accounts, 3)?; + let mut value = json!({ + "source": account_keys[instruction.accounts[0] as usize].to_string(), + "destination": account_keys[instruction.accounts[1] as usize].to_string(), + }); + let map = value.as_object_mut().unwrap(); + parse_signers( + map, + 2, + account_keys, + &instruction.accounts, + "authority", + "multisigAuthority", + ); + Ok(ParsedInstructionEnum { + instruction_type: "withdrawExcessLamports".to_string(), + info: value, + }) + } + TokenInstruction::MetadataPointerExtension => { + if instruction.data.len() < 2 { + return Err(ParseInstructionError::InstructionNotParsable( + ParsableProgram::SplToken, + )); + } + parse_metadata_pointer_instruction( + &instruction.data[1..], + &instruction.accounts, + account_keys, + ) + } } } @@ -606,6 +666,9 @@ pub enum UiAuthorityType { InterestRate, PermanentDelegate, ConfidentialTransferMint, + TransferHookProgramId, + ConfidentialTransferFeeConfig, + MetadataPointer, } impl From for UiAuthorityType { @@ -621,6 +684,11 @@ impl From for UiAuthorityType { AuthorityType::InterestRate => UiAuthorityType::InterestRate, AuthorityType::PermanentDelegate => UiAuthorityType::PermanentDelegate, AuthorityType::ConfidentialTransferMint => UiAuthorityType::ConfidentialTransferMint, + AuthorityType::TransferHookProgramId => UiAuthorityType::TransferHookProgramId, + AuthorityType::ConfidentialTransferFeeConfig => { + UiAuthorityType::ConfidentialTransferFeeConfig + } + AuthorityType::MetadataPointer => UiAuthorityType::MetadataPointer, } } } @@ -642,6 +710,12 @@ pub enum UiExtensionType { CpiGuard, PermanentDelegate, NonTransferableAccount, + TransferHook, + TransferHookAccount, + ConfidentialTransferFeeConfig, + ConfidentialTransferFeeAmount, + MetadataPointer, + TokenMetadata, } impl From for UiExtensionType { @@ -663,6 +737,16 @@ impl From for UiExtensionType { ExtensionType::CpiGuard => UiExtensionType::CpiGuard, ExtensionType::PermanentDelegate => UiExtensionType::PermanentDelegate, ExtensionType::NonTransferableAccount => UiExtensionType::NonTransferableAccount, + ExtensionType::TransferHook => UiExtensionType::TransferHook, + ExtensionType::TransferHookAccount => UiExtensionType::TransferHookAccount, + ExtensionType::ConfidentialTransferFeeConfig => { + UiExtensionType::ConfidentialTransferFeeConfig + } + ExtensionType::ConfidentialTransferFeeAmount => { + UiExtensionType::ConfidentialTransferFeeAmount + } + ExtensionType::MetadataPointer => UiExtensionType::MetadataPointer, + ExtensionType::TokenMetadata => UiExtensionType::TokenMetadata, } } } diff --git a/transaction-status/src/parse_token/extension/confidential_transfer.rs b/transaction-status/src/parse_token/extension/confidential_transfer.rs index 44384f1193..138e885fb3 100644 --- a/transaction-status/src/parse_token/extension/confidential_transfer.rs +++ b/transaction-status/src/parse_token/extension/confidential_transfer.rs @@ -192,8 +192,8 @@ pub(in crate::parse_token) fn parse_confidential_transfer_instruction( let proof_instruction_offset: i8 = transfer_data.proof_instruction_offset; let mut value = json!({ "source": account_keys[account_indexes[0] as usize].to_string(), - "destination": account_keys[account_indexes[1] as usize].to_string(), - "mint": account_keys[account_indexes[2] as usize].to_string(), + "mint": account_keys[account_indexes[1] as usize].to_string(), + "destination": account_keys[account_indexes[2] as usize].to_string(), "instructionsSysvar": account_keys[account_indexes[3] as usize].to_string(), "newSourceDecryptableAvailableBalance": format!("{}", transfer_data.new_source_decryptable_available_balance), "proofInstructionOffset": proof_instruction_offset, @@ -322,85 +322,37 @@ pub(in crate::parse_token) fn parse_confidential_transfer_instruction( info: value, }) } - ConfidentialTransferInstruction::WithdrawWithheldTokensFromMint => { - check_num_token_accounts(account_indexes, 4)?; - let withdraw_withheld_data: WithdrawWithheldTokensFromMintData = + ConfidentialTransferInstruction::TransferWithSplitProofs => { + check_num_token_accounts(account_indexes, 7)?; + let transfer_data: TransferWithSplitProofsInstructionData = *decode_instruction_data(instruction_data).map_err(|_| { ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken) })?; - let proof_instruction_offset: i8 = withdraw_withheld_data.proof_instruction_offset; let mut value = json!({ - "mint": account_keys[account_indexes[0] as usize].to_string(), - "feeRecipient": account_keys[account_indexes[1] as usize].to_string(), - "instructionsSysvar": account_keys[account_indexes[2] as usize].to_string(), - "proofInstructionOffset": proof_instruction_offset, - + "source": account_keys[account_indexes[0] as usize].to_string(), + "mint": account_keys[account_indexes[1] as usize].to_string(), + "destination": account_keys[account_indexes[2] as usize].to_string(), + "ciphertextCommitmentEqualityContext": account_keys[account_indexes[3] as usize].to_string(), + "batchedGroupedCiphertext2HandlesValidityContext": account_keys[account_indexes[4] as usize].to_string(), + "batchedRangeProofContext": account_keys[account_indexes[5] as usize].to_string(), + "owner": account_keys[account_indexes[6] as usize].to_string(), + "newSourceDecryptableAvailableBalance": format!("{}", transfer_data.new_source_decryptable_available_balance), + "noOpOnUninitializedSplitContextState": bool::from(transfer_data.no_op_on_uninitialized_split_context_state), + "closeSplitContextStateOnExecution": bool::from(transfer_data.close_split_context_state_on_execution), }); let map = value.as_object_mut().unwrap(); - parse_signers( - map, - 3, - account_keys, - account_indexes, - "withdrawWithheldAuthority", - "multisigWithdrawWithheldAuthority", - ); - Ok(ParsedInstructionEnum { - instruction_type: "withdrawWithheldConfidentialTransferTokensFromMint".to_string(), - info: value, - }) - } - ConfidentialTransferInstruction::WithdrawWithheldTokensFromAccounts => { - let withdraw_withheld_data: WithdrawWithheldTokensFromAccountsData = - *decode_instruction_data(instruction_data).map_err(|_| { - ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken) - })?; - let num_token_accounts = withdraw_withheld_data.num_token_accounts; - check_num_token_accounts(account_indexes, 4 + num_token_accounts as usize)?; - let proof_instruction_offset: i8 = withdraw_withheld_data.proof_instruction_offset; - let mut value = json!({ - "mint": account_keys[account_indexes[0] as usize].to_string(), - "feeRecipient": account_keys[account_indexes[1] as usize].to_string(), - "instructionsSysvar": account_keys[account_indexes[2] as usize].to_string(), - "proofInstructionOffset": proof_instruction_offset, - }); - let map = value.as_object_mut().unwrap(); - let mut source_accounts: Vec = vec![]; - let first_source_account_index = account_indexes - .len() - .saturating_sub(num_token_accounts as usize); - for i in account_indexes[first_source_account_index..].iter() { - source_accounts.push(account_keys[*i as usize].to_string()); + if transfer_data.close_split_context_state_on_execution.into() { + map.insert( + "lamportDestination".to_string(), + json!(account_keys[account_indexes[7] as usize].to_string()), + ); + map.insert( + "contextStateOwner".to_string(), + json!(account_keys[account_indexes[8] as usize].to_string()), + ); } - map.insert("sourceAccounts".to_string(), json!(source_accounts)); - parse_signers( - map, - 3, - account_keys, - &account_indexes[..first_source_account_index], - "withdrawWithheldAuthority", - "multisigWithdrawWithheldAuthority", - ); Ok(ParsedInstructionEnum { - instruction_type: "withdrawWithheldConfidentialTransferTokensFromAccounts" - .to_string(), - info: value, - }) - } - ConfidentialTransferInstruction::HarvestWithheldTokensToMint => { - check_num_token_accounts(account_indexes, 1)?; - let mut value = json!({ - "mint": account_keys[account_indexes[0] as usize].to_string(), - - }); - let map = value.as_object_mut().unwrap(); - let mut source_accounts: Vec = vec![]; - for i in account_indexes.iter().skip(1) { - source_accounts.push(account_keys[*i as usize].to_string()); - } - map.insert("sourceAccounts".to_string(), json!(source_accounts)); - Ok(ParsedInstructionEnum { - instruction_type: "harvestWithheldConfidentialTransferTokensToMint".to_string(), + instruction_type: "confidentialTransferWithSplitProofs".to_string(), info: value, }) } diff --git a/transaction-status/src/parse_token/extension/confidential_transfer_fee.rs b/transaction-status/src/parse_token/extension/confidential_transfer_fee.rs new file mode 100644 index 0000000000..f35fad62c0 --- /dev/null +++ b/transaction-status/src/parse_token/extension/confidential_transfer_fee.rs @@ -0,0 +1,159 @@ +use { + super::*, + solana_account_decoder::parse_token_extension::UiConfidentialTransferFeeConfig, + spl_token_2022::{ + extension::confidential_transfer_fee::{instruction::*, ConfidentialTransferFeeConfig}, + instruction::{decode_instruction_data, decode_instruction_type}, + }, +}; + +pub(in crate::parse_token) fn parse_confidential_transfer_fee_instruction( + instruction_data: &[u8], + account_indexes: &[u8], + account_keys: &AccountKeys, +) -> Result { + match decode_instruction_type(instruction_data) + .map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken))? + { + ConfidentialTransferFeeInstruction::InitializeConfidentialTransferFeeConfig => { + check_num_token_accounts(account_indexes, 1)?; + let confidential_transfer_mint: ConfidentialTransferFeeConfig = + *decode_instruction_data(instruction_data).map_err(|_| { + ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken) + })?; + let confidential_transfer_mint: UiConfidentialTransferFeeConfig = + confidential_transfer_mint.into(); + let mut value = json!({ + "mint": account_keys[account_indexes[0] as usize].to_string(), + }); + let map = value.as_object_mut().unwrap(); + map.append(json!(confidential_transfer_mint).as_object_mut().unwrap()); + Ok(ParsedInstructionEnum { + instruction_type: "initializeConfidentialTransferFeeConfig".to_string(), + info: value, + }) + } + ConfidentialTransferFeeInstruction::WithdrawWithheldTokensFromMint => { + check_num_token_accounts(account_indexes, 4)?; + let withdraw_withheld_data: WithdrawWithheldTokensFromMintData = + *decode_instruction_data(instruction_data).map_err(|_| { + ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken) + })?; + let proof_instruction_offset: i8 = withdraw_withheld_data.proof_instruction_offset; + let mut value = json!({ + "mint": account_keys[account_indexes[0] as usize].to_string(), + "feeRecipient": account_keys[account_indexes[1] as usize].to_string(), + "instructionsSysvar": account_keys[account_indexes[2] as usize].to_string(), + "proofInstructionOffset": proof_instruction_offset, + + }); + let map = value.as_object_mut().unwrap(); + parse_signers( + map, + 3, + account_keys, + account_indexes, + "withdrawWithheldAuthority", + "multisigWithdrawWithheldAuthority", + ); + Ok(ParsedInstructionEnum { + instruction_type: "withdrawWithheldConfidentialTransferTokensFromMint".to_string(), + info: value, + }) + } + ConfidentialTransferFeeInstruction::WithdrawWithheldTokensFromAccounts => { + let withdraw_withheld_data: WithdrawWithheldTokensFromAccountsData = + *decode_instruction_data(instruction_data).map_err(|_| { + ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken) + })?; + let num_token_accounts = withdraw_withheld_data.num_token_accounts; + check_num_token_accounts(account_indexes, 4 + num_token_accounts as usize)?; + let proof_instruction_offset: i8 = withdraw_withheld_data.proof_instruction_offset; + let mut value = json!({ + "mint": account_keys[account_indexes[0] as usize].to_string(), + "feeRecipient": account_keys[account_indexes[1] as usize].to_string(), + "instructionsSysvar": account_keys[account_indexes[2] as usize].to_string(), + "proofInstructionOffset": proof_instruction_offset, + }); + let map = value.as_object_mut().unwrap(); + let mut source_accounts: Vec = vec![]; + let first_source_account_index = account_indexes + .len() + .saturating_sub(num_token_accounts as usize); + for i in account_indexes[first_source_account_index..].iter() { + source_accounts.push(account_keys[*i as usize].to_string()); + } + map.insert("sourceAccounts".to_string(), json!(source_accounts)); + parse_signers( + map, + 3, + account_keys, + &account_indexes[..first_source_account_index], + "withdrawWithheldAuthority", + "multisigWithdrawWithheldAuthority", + ); + Ok(ParsedInstructionEnum { + instruction_type: "withdrawWithheldConfidentialTransferTokensFromAccounts" + .to_string(), + info: value, + }) + } + ConfidentialTransferFeeInstruction::HarvestWithheldTokensToMint => { + check_num_token_accounts(account_indexes, 1)?; + let mut value = json!({ + "mint": account_keys[account_indexes[0] as usize].to_string(), + + }); + let map = value.as_object_mut().unwrap(); + let mut source_accounts: Vec = vec![]; + for i in account_indexes.iter().skip(1) { + source_accounts.push(account_keys[*i as usize].to_string()); + } + map.insert("sourceAccounts".to_string(), json!(source_accounts)); + Ok(ParsedInstructionEnum { + instruction_type: "harvestWithheldConfidentialTransferTokensToMint".to_string(), + info: value, + }) + } + ConfidentialTransferFeeInstruction::EnableHarvestToMint => { + check_num_token_accounts(account_indexes, 2)?; + let mut value = json!({ + "account": account_keys[account_indexes[0] as usize].to_string(), + + }); + let map = value.as_object_mut().unwrap(); + parse_signers( + map, + 1, + account_keys, + account_indexes, + "owner", + "multisigOwner", + ); + Ok(ParsedInstructionEnum { + instruction_type: "enableConfidentialTransferFeeHarvestToMint".to_string(), + info: value, + }) + } + ConfidentialTransferFeeInstruction::DisableHarvestToMint => { + check_num_token_accounts(account_indexes, 2)?; + let mut value = json!({ + "account": account_keys[account_indexes[0] as usize].to_string(), + + }); + let map = value.as_object_mut().unwrap(); + parse_signers( + map, + 1, + account_keys, + account_indexes, + "owner", + "multisigOwner", + ); + Ok(ParsedInstructionEnum { + instruction_type: "disableConfidentialTransferFeeHarvestToMint".to_string(), + info: value, + }) + } + } +} diff --git a/transaction-status/src/parse_token/extension/metadata_pointer.rs b/transaction-status/src/parse_token/extension/metadata_pointer.rs new file mode 100644 index 0000000000..e88a14732f --- /dev/null +++ b/transaction-status/src/parse_token/extension/metadata_pointer.rs @@ -0,0 +1,192 @@ +use { + super::*, + spl_token_2022::{ + extension::metadata_pointer::instruction::*, + instruction::{decode_instruction_data, decode_instruction_type}, + }, +}; + +pub(in crate::parse_token) fn parse_metadata_pointer_instruction( + instruction_data: &[u8], + account_indexes: &[u8], + account_keys: &AccountKeys, +) -> Result { + match decode_instruction_type(instruction_data) + .map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken))? + { + MetadataPointerInstruction::Initialize => { + check_num_token_accounts(account_indexes, 1)?; + let InitializeInstructionData { + authority, + metadata_address, + } = *decode_instruction_data(instruction_data).map_err(|_| { + ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken) + })?; + let mut value = json!({ + "mint": account_keys[account_indexes[0] as usize].to_string(), + }); + let map = value.as_object_mut().unwrap(); + if let Some(authority) = Option::::from(authority) { + map.insert("authority".to_string(), json!(authority.to_string())); + } + if let Some(metadata_address) = Option::::from(metadata_address) { + map.insert( + "metadataAddress".to_string(), + json!(metadata_address.to_string()), + ); + } + Ok(ParsedInstructionEnum { + instruction_type: "initializeMetadataPointer".to_string(), + info: value, + }) + } + MetadataPointerInstruction::Update => { + check_num_token_accounts(account_indexes, 2)?; + let UpdateInstructionData { metadata_address } = + *decode_instruction_data(instruction_data).map_err(|_| { + ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken) + })?; + let mut value = json!({ + "mint": account_keys[account_indexes[0] as usize].to_string(), + }); + let map = value.as_object_mut().unwrap(); + if let Some(metadata_address) = Option::::from(metadata_address) { + map.insert( + "metadataAddress".to_string(), + json!(metadata_address.to_string()), + ); + } + parse_signers( + map, + 1, + account_keys, + account_indexes, + "authority", + "multisigAuthority", + ); + Ok(ParsedInstructionEnum { + instruction_type: "updateMetadataPointer".to_string(), + info: value, + }) + } + } +} + +#[cfg(test)] +mod test { + use { + super::*, crate::parse_token::test::*, solana_sdk::pubkey::Pubkey, + spl_token_2022::solana_program::message::Message, + }; + + #[test] + fn test_parse_metadata_pointer_instruction() { + let mint_pubkey = Pubkey::new_unique(); + let authority = Pubkey::new_unique(); + let metadata_address = Pubkey::new_unique(); + + // Initialize variations + let init_ix = initialize( + &spl_token_2022::id(), + &mint_pubkey, + Some(authority), + Some(metadata_address), + ) + .unwrap(); + let message = Message::new(&[init_ix], None); + let compiled_instruction = convert_compiled_instruction(&message.instructions[0]); + assert_eq!( + parse_token( + &compiled_instruction, + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "initializeMetadataPointer".to_string(), + info: json!({ + "mint": mint_pubkey.to_string(), + "authority": authority.to_string(), + "metadataAddress": metadata_address.to_string(), + }) + } + ); + + let init_ix = initialize(&spl_token_2022::id(), &mint_pubkey, None, None).unwrap(); + let message = Message::new(&[init_ix], None); + let compiled_instruction = convert_compiled_instruction(&message.instructions[0]); + assert_eq!( + parse_token( + &compiled_instruction, + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "initializeMetadataPointer".to_string(), + info: json!({ + "mint": mint_pubkey.to_string(), + }) + } + ); + + // Single owner Update + let update_ix = update( + &spl_token_2022::id(), + &mint_pubkey, + &authority, + &[], + Some(metadata_address), + ) + .unwrap(); + let message = Message::new(&[update_ix], None); + let compiled_instruction = convert_compiled_instruction(&message.instructions[0]); + assert_eq!( + parse_token( + &compiled_instruction, + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "updateMetadataPointer".to_string(), + info: json!({ + "mint": mint_pubkey.to_string(), + "authority": authority.to_string(), + "metadataAddress": metadata_address.to_string(), + }) + } + ); + + // Multisig Update + let multisig_pubkey = Pubkey::new_unique(); + let multisig_signer0 = Pubkey::new_unique(); + let multisig_signer1 = Pubkey::new_unique(); + let update_ix = update( + &spl_token_2022::id(), + &mint_pubkey, + &multisig_pubkey, + &[&multisig_signer0, &multisig_signer1], + Some(metadata_address), + ) + .unwrap(); + let message = Message::new(&[update_ix], None); + let compiled_instruction = convert_compiled_instruction(&message.instructions[0]); + assert_eq!( + parse_token( + &compiled_instruction, + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "updateMetadataPointer".to_string(), + info: json!({ + "mint": mint_pubkey.to_string(), + "metadataAddress": metadata_address.to_string(), + "multisigAuthority": multisig_pubkey.to_string(), + "signers": vec![ + multisig_signer0.to_string(), + multisig_signer1.to_string(), + ], + }) + } + ); + } +} diff --git a/transaction-status/src/parse_token/extension/mod.rs b/transaction-status/src/parse_token/extension/mod.rs index 1dd2a82933..8e65ddfcfc 100644 --- a/transaction-status/src/parse_token/extension/mod.rs +++ b/transaction-status/src/parse_token/extension/mod.rs @@ -1,11 +1,14 @@ use super::*; pub(super) mod confidential_transfer; +pub(super) mod confidential_transfer_fee; pub(super) mod cpi_guard; pub(super) mod default_account_state; pub(super) mod interest_bearing_mint; pub(super) mod memo_transfer; +pub(super) mod metadata_pointer; pub(super) mod mint_close_authority; pub(super) mod permanent_delegate; pub(super) mod reallocate; pub(super) mod transfer_fee; +pub(super) mod transfer_hook; diff --git a/transaction-status/src/parse_token/extension/transfer_hook.rs b/transaction-status/src/parse_token/extension/transfer_hook.rs new file mode 100644 index 0000000000..e6b33c058f --- /dev/null +++ b/transaction-status/src/parse_token/extension/transfer_hook.rs @@ -0,0 +1,186 @@ +use { + super::*, + spl_token_2022::{ + extension::transfer_hook::instruction::*, + instruction::{decode_instruction_data, decode_instruction_type}, + }, +}; + +pub(in crate::parse_token) fn parse_transfer_hook_instruction( + instruction_data: &[u8], + account_indexes: &[u8], + account_keys: &AccountKeys, +) -> Result { + match decode_instruction_type(instruction_data) + .map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken))? + { + TransferHookInstruction::Initialize => { + check_num_token_accounts(account_indexes, 1)?; + let InitializeInstructionData { + authority, + program_id, + } = *decode_instruction_data(instruction_data).map_err(|_| { + ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken) + })?; + let mut value = json!({ + "mint": account_keys[account_indexes[0] as usize].to_string(), + }); + let map = value.as_object_mut().unwrap(); + if let Some(authority) = Option::::from(authority) { + map.insert("authority".to_string(), json!(authority.to_string())); + } + if let Some(program_id) = Option::::from(program_id) { + map.insert("programId".to_string(), json!(program_id.to_string())); + } + Ok(ParsedInstructionEnum { + instruction_type: "initializeTransferHook".to_string(), + info: value, + }) + } + TransferHookInstruction::Update => { + check_num_token_accounts(account_indexes, 2)?; + let UpdateInstructionData { program_id } = *decode_instruction_data(instruction_data) + .map_err(|_| { + ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken) + })?; + let mut value = json!({ + "mint": account_keys[account_indexes[0] as usize].to_string(), + }); + let map = value.as_object_mut().unwrap(); + if let Some(program_id) = Option::::from(program_id) { + map.insert("programId".to_string(), json!(program_id.to_string())); + } + parse_signers( + map, + 1, + account_keys, + account_indexes, + "authority", + "multisigAuthority", + ); + Ok(ParsedInstructionEnum { + instruction_type: "updateTransferHook".to_string(), + info: value, + }) + } + } +} + +#[cfg(test)] +mod test { + use { + super::*, crate::parse_token::test::*, solana_sdk::pubkey::Pubkey, + spl_token_2022::solana_program::message::Message, + }; + + #[test] + fn test_parse_transfer_hook_instruction() { + let mint_pubkey = Pubkey::new_unique(); + let authority = Pubkey::new_unique(); + let program_id = Pubkey::new_unique(); + + // Initialize variations + let init_ix = initialize( + &spl_token_2022::id(), + &mint_pubkey, + Some(authority), + Some(program_id), + ) + .unwrap(); + let message = Message::new(&[init_ix], None); + let compiled_instruction = convert_compiled_instruction(&message.instructions[0]); + assert_eq!( + parse_token( + &compiled_instruction, + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "initializeTransferHook".to_string(), + info: json!({ + "mint": mint_pubkey.to_string(), + "authority": authority.to_string(), + "programId": program_id.to_string(), + }) + } + ); + + let init_ix = initialize(&spl_token_2022::id(), &mint_pubkey, None, None).unwrap(); + let message = Message::new(&[init_ix], None); + let compiled_instruction = convert_compiled_instruction(&message.instructions[0]); + assert_eq!( + parse_token( + &compiled_instruction, + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "initializeTransferHook".to_string(), + info: json!({ + "mint": mint_pubkey.to_string(), + }) + } + ); + + // Single owner Update + let update_ix = update( + &spl_token_2022::id(), + &mint_pubkey, + &authority, + &[], + Some(program_id), + ) + .unwrap(); + let message = Message::new(&[update_ix], None); + let compiled_instruction = convert_compiled_instruction(&message.instructions[0]); + assert_eq!( + parse_token( + &compiled_instruction, + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "updateTransferHook".to_string(), + info: json!({ + "mint": mint_pubkey.to_string(), + "authority": authority.to_string(), + "programId": program_id.to_string(), + }) + } + ); + + // Multisig Update + let multisig_pubkey = Pubkey::new_unique(); + let multisig_signer0 = Pubkey::new_unique(); + let multisig_signer1 = Pubkey::new_unique(); + let update_ix = update( + &spl_token_2022::id(), + &mint_pubkey, + &multisig_pubkey, + &[&multisig_signer0, &multisig_signer1], + Some(program_id), + ) + .unwrap(); + let message = Message::new(&[update_ix], None); + let compiled_instruction = convert_compiled_instruction(&message.instructions[0]); + assert_eq!( + parse_token( + &compiled_instruction, + &AccountKeys::new(&message.account_keys, None) + ) + .unwrap(), + ParsedInstructionEnum { + instruction_type: "updateTransferHook".to_string(), + info: json!({ + "mint": mint_pubkey.to_string(), + "programId": program_id.to_string(), + "multisigAuthority": multisig_pubkey.to_string(), + "signers": vec![ + multisig_signer0.to_string(), + multisig_signer1.to_string(), + ], + }) + } + ); + } +}