* Parse stake and system instructions (#13035)
* Fix token account check
* Add helper to check num accounts
* Add parse_stake
* Add parse_system
* Fix AuthorizeNonce docs
* Remove jsonParsed unstable markers
* Clippy
(cherry picked from commit 46d0019955)
* Fix for older clippy
Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
			
			
This commit is contained in:
		| @@ -165,7 +165,7 @@ Returns all information associated with the account of provided Pubkey | |||||||
| - `<object>` - (optional) Configuration object containing the following optional fields: | - `<object>` - (optional) Configuration object containing the following optional fields: | ||||||
|   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) |   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) | ||||||
|   - `encoding: <string>` - encoding for Account data, either "base58" (*slow*), "base64", or jsonParsed". "base58" is limited to Account data of less than 128 bytes. "base64" will return base64 encoded data for Account data of any size. |   - `encoding: <string>` - encoding for Account data, either "base58" (*slow*), "base64", or jsonParsed". "base58" is limited to Account data of less than 128 bytes. "base64" will return base64 encoded data for Account data of any size. | ||||||
|     Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a parser cannot be found, the field falls back to base64 encoding, detectable when the `data` field is type `<string>`. **jsonParsed encoding is UNSTABLE** |     Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a parser cannot be found, the field falls back to base64 encoding, detectable when the `data` field is type `<string>`. | ||||||
|   - (optional) `dataSlice: <object>` - limit the returned account data using the provided `offset: <usize>` and `length: <usize>` fields; only available for "base58" or "base64" encoding. |   - (optional) `dataSlice: <object>` - limit the returned account data using the provided `offset: <usize>` and `length: <usize>` fields; only available for "base58" or "base64" encoding. | ||||||
|  |  | ||||||
| #### Results: | #### Results: | ||||||
| @@ -314,7 +314,7 @@ Returns identity and transaction information about a confirmed block in the ledg | |||||||
| #### Parameters: | #### Parameters: | ||||||
|  |  | ||||||
| - `<u64>` - slot, as u64 integer | - `<u64>` - slot, as u64 integer | ||||||
| - `<string>` - encoding for each returned Transaction, either "json", "jsonParsed", "base58" (*slow*), or "base64". If parameter not provided, the default encoding is JSON. **jsonParsed encoding is UNSTABLE** | - `<string>` - encoding for each returned Transaction, either "json", "jsonParsed", "base58" (*slow*), or "base64". If parameter not provided, the default encoding is JSON. | ||||||
|   Parsed-JSON encoding attempts to use program-specific instruction parsers to return more human-readable and explicit data in the `transaction.message.instructions` list. If parsed-JSON is requested but a parser cannot be found, the instruction falls back to regular JSON encoding (`accounts`, `data`, and `programIdIndex` fields). |   Parsed-JSON encoding attempts to use program-specific instruction parsers to return more human-readable and explicit data in the `transaction.message.instructions` list. If parsed-JSON is requested but a parser cannot be found, the instruction falls back to regular JSON encoding (`accounts`, `data`, and `programIdIndex` fields). | ||||||
|  |  | ||||||
| #### Results: | #### Results: | ||||||
| @@ -512,7 +512,7 @@ Returns transaction details for a confirmed transaction | |||||||
|  |  | ||||||
| - `<string>` - transaction signature as base-58 encoded string | - `<string>` - transaction signature as base-58 encoded string | ||||||
| N encoding attempts to use program-specific instruction parsers to return more human-readable and explicit data in the `transaction.message.instructions` list. If parsed-JSON is requested but a parser cannot be found, the instruction falls back to regular JSON encoding (`accounts`, `data`, and `programIdIndex` fields). | N encoding attempts to use program-specific instruction parsers to return more human-readable and explicit data in the `transaction.message.instructions` list. If parsed-JSON is requested but a parser cannot be found, the instruction falls back to regular JSON encoding (`accounts`, `data`, and `programIdIndex` fields). | ||||||
| - `<string>` - (optional) encoding for the returned Transaction, either "json", "jsonParsed", "base58" (*slow*), or "base64". If parameter not provided, the default encoding is JSON. **jsonParsed encoding is UNSTABLE** | - `<string>` - (optional) encoding for the returned Transaction, either "json", "jsonParsed", "base58" (*slow*), or "base64". If parameter not provided, the default encoding is JSON. | ||||||
|  |  | ||||||
| #### Results: | #### Results: | ||||||
|  |  | ||||||
| @@ -894,7 +894,7 @@ Returns the account information for a list of Pubkeys | |||||||
| - `<object>` - (optional) Configuration object containing the following optional fields: | - `<object>` - (optional) Configuration object containing the following optional fields: | ||||||
|   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) |   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) | ||||||
|   - `encoding: <string>` - encoding for Account data, either "base58" (*slow*), "base64", or jsonParsed". "base58" is limited to Account data of less than 128 bytes. "base64" will return base64 encoded data for Account data of any size. |   - `encoding: <string>` - encoding for Account data, either "base58" (*slow*), "base64", or jsonParsed". "base58" is limited to Account data of less than 128 bytes. "base64" will return base64 encoded data for Account data of any size. | ||||||
|     Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a parser cannot be found, the field falls back to base64 encoding, detectable when the `data` field is type `<string>`. **jsonParsed encoding is UNSTABLE** |     Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a parser cannot be found, the field falls back to base64 encoding, detectable when the `data` field is type `<string>`. | ||||||
|   - (optional) `dataSlice: <object>` - limit the returned account data using the provided `offset: <usize>` and `length: <usize>` fields; only available for "base58" or "base64" encoding. |   - (optional) `dataSlice: <object>` - limit the returned account data using the provided `offset: <usize>` and `length: <usize>` fields; only available for "base58" or "base64" encoding. | ||||||
|  |  | ||||||
| #### Results: | #### Results: | ||||||
| @@ -937,7 +937,7 @@ Returns all accounts owned by the provided program Pubkey | |||||||
| - `<object>` - (optional) Configuration object containing the following optional fields: | - `<object>` - (optional) Configuration object containing the following optional fields: | ||||||
|   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) |   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) | ||||||
|   - `encoding: <string>` - encoding for Account data, either "base58" (*slow*), "base64" or jsonParsed". |   - `encoding: <string>` - encoding for Account data, either "base58" (*slow*), "base64" or jsonParsed". | ||||||
|     Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a parser cannot be found, the field falls back to base64 encoding, detectable when the `data` field is type `<string>`. If parsed-JSON is requested for the SPL Token program, when a valid mint cannot be found for a particular account, that account will be filtered out from results. **jsonParsed encoding is UNSTABLE** |     Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a parser cannot be found, the field falls back to base64 encoding, detectable when the `data` field is type `<string>`. If parsed-JSON is requested for the SPL Token program, when a valid mint cannot be found for a particular account, that account will be filtered out from results. | ||||||
|   - (optional) `dataSlice: <object>` - limit the returned account data using the provided `offset: <usize>` and `length: <usize>` fields; only available for "base58" or "base64" encoding. |   - (optional) `dataSlice: <object>` - limit the returned account data using the provided `offset: <usize>` and `length: <usize>` fields; only available for "base58" or "base64" encoding. | ||||||
|   - (optional) `filters: <array>` - filter results using various [filter objects](jsonrpc-api.md#filters); account must meet all filter criteria to be included in results |   - (optional) `filters: <array>` - filter results using various [filter objects](jsonrpc-api.md#filters); account must meet all filter criteria to be included in results | ||||||
|  |  | ||||||
| @@ -1192,7 +1192,7 @@ Returns all SPL Token accounts by approved Delegate. **UNSTABLE** | |||||||
| - `<object>` - (optional) Configuration object containing the following optional fields: | - `<object>` - (optional) Configuration object containing the following optional fields: | ||||||
|   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) |   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) | ||||||
|   - `encoding: <string>` - encoding for Account data, either "base58" (*slow*), "base64" or jsonParsed". |   - `encoding: <string>` - encoding for Account data, either "base58" (*slow*), "base64" or jsonParsed". | ||||||
|     Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a valid mint cannot be found for a particular account, that account will be filtered out from results. **jsonParsed encoding is UNSTABLE** |     Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a valid mint cannot be found for a particular account, that account will be filtered out from results. | ||||||
|   - (optional) `dataSlice: <object>` - limit the returned account data using the provided `offset: <usize>` and `length: <usize>` fields; only available for "base58" or "base64" encoding. |   - (optional) `dataSlice: <object>` - limit the returned account data using the provided `offset: <usize>` and `length: <usize>` fields; only available for "base58" or "base64" encoding. | ||||||
|  |  | ||||||
| #### Results: | #### Results: | ||||||
| @@ -1229,7 +1229,7 @@ Returns all SPL Token accounts by token owner. **UNSTABLE** | |||||||
| - `<object>` - (optional) Configuration object containing the following optional fields: | - `<object>` - (optional) Configuration object containing the following optional fields: | ||||||
|   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) |   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) | ||||||
|   - `encoding: <string>` - encoding for Account data, either "base58" (*slow*), "base64" or jsonParsed". |   - `encoding: <string>` - encoding for Account data, either "base58" (*slow*), "base64" or jsonParsed". | ||||||
|     Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a valid mint cannot be found for a particular account, that account will be filtered out from results. **jsonParsed encoding is UNSTABLE** |     Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a valid mint cannot be found for a particular account, that account will be filtered out from results. | ||||||
|   - (optional) `dataSlice: <object>` - limit the returned account data using the provided `offset: <usize>` and `length: <usize>` fields; only available for "base58" or "base64" encoding. |   - (optional) `dataSlice: <object>` - limit the returned account data using the provided `offset: <usize>` and `length: <usize>` fields; only available for "base58" or "base64" encoding. | ||||||
|  |  | ||||||
| #### Results: | #### Results: | ||||||
| @@ -1553,7 +1553,7 @@ Subscribe to an account to receive notifications when the lamports or data for a | |||||||
| - `<object>` - (optional) Configuration object containing the following optional fields: | - `<object>` - (optional) Configuration object containing the following optional fields: | ||||||
|   - `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) |   - `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) | ||||||
|   - `encoding: <string>` - encoding for Account data, either "base58" (*slow*), "base64" or jsonParsed". |   - `encoding: <string>` - encoding for Account data, either "base58" (*slow*), "base64" or jsonParsed". | ||||||
|     Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a parser cannot be found, the field falls back to binary encoding, detectable when the `data` field is type `<string>`. **jsonParsed encoding is UNSTABLE** |     Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a parser cannot be found, the field falls back to binary encoding, detectable when the `data` field is type `<string>`. | ||||||
|  |  | ||||||
| #### Results: | #### Results: | ||||||
|  |  | ||||||
| @@ -1663,7 +1663,7 @@ Subscribe to a program to receive notifications when the lamports or data for a | |||||||
| - `<object>` - (optional) Configuration object containing the following optional fields: | - `<object>` - (optional) Configuration object containing the following optional fields: | ||||||
|   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) |   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) | ||||||
|   - `encoding: <string>` - encoding for Account data, either "base58" (*slow*), "base64" or jsonParsed". |   - `encoding: <string>` - encoding for Account data, either "base58" (*slow*), "base64" or jsonParsed". | ||||||
|     Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a parser cannot be found, the field falls back to base64 encoding, detectable when the `data` field is type `<string>`. **jsonParsed encoding is UNSTABLE** |     Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a parser cannot be found, the field falls back to base64 encoding, detectable when the `data` field is type `<string>`. | ||||||
|   - (optional) `filters: <array>` - filter results using various [filter objects](jsonrpc-api.md#filters); account must meet all filter criteria to be included in results |   - (optional) `filters: <array>` - filter results using various [filter objects](jsonrpc-api.md#filters); account must meet all filter criteria to be included in results | ||||||
|  |  | ||||||
| #### Results: | #### Results: | ||||||
|   | |||||||
| @@ -148,7 +148,8 @@ pub enum SystemInstruction { | |||||||
|     /// Change the entity authorized to execute nonce instructions on the account |     /// Change the entity authorized to execute nonce instructions on the account | ||||||
|     /// |     /// | ||||||
|     /// # Account references |     /// # Account references | ||||||
|     ///   0. [WRITE, SIGNER] Nonce account |     ///   0. [WRITE] Nonce account | ||||||
|  |     ///   1. [SIGNER] Nonce authority | ||||||
|     /// |     /// | ||||||
|     /// The `Pubkey` parameter identifies the entity to authorize |     /// The `Pubkey` parameter identifies the entity to authorize | ||||||
|     AuthorizeNonceAccount(Pubkey), |     AuthorizeNonceAccount(Pubkey), | ||||||
|   | |||||||
| @@ -6,6 +6,8 @@ extern crate serde_derive; | |||||||
| pub mod parse_accounts; | pub mod parse_accounts; | ||||||
| pub mod parse_bpf_loader; | pub mod parse_bpf_loader; | ||||||
| pub mod parse_instruction; | pub mod parse_instruction; | ||||||
|  | pub mod parse_stake; | ||||||
|  | pub mod parse_system; | ||||||
| pub mod parse_token; | pub mod parse_token; | ||||||
|  |  | ||||||
| use crate::{ | use crate::{ | ||||||
|   | |||||||
| @@ -1,8 +1,11 @@ | |||||||
| use crate::{parse_bpf_loader::parse_bpf_loader, parse_token::parse_token}; | use crate::{ | ||||||
|  |     parse_bpf_loader::parse_bpf_loader, parse_stake::parse_stake, parse_system::parse_system, | ||||||
|  |     parse_token::parse_token, | ||||||
|  | }; | ||||||
| use inflector::Inflector; | use inflector::Inflector; | ||||||
| use serde_json::Value; | use serde_json::Value; | ||||||
| use solana_account_decoder::parse_token::spl_token_id_v2_0; | use solana_account_decoder::parse_token::spl_token_id_v2_0; | ||||||
| use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey}; | use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey, system_program}; | ||||||
| use std::{ | use std::{ | ||||||
|     collections::HashMap, |     collections::HashMap, | ||||||
|     str::{from_utf8, FromStr}, |     str::{from_utf8, FromStr}, | ||||||
| @@ -13,12 +16,16 @@ lazy_static! { | |||||||
|     static ref BPF_LOADER_PROGRAM_ID: Pubkey = solana_sdk::bpf_loader::id(); |     static ref BPF_LOADER_PROGRAM_ID: Pubkey = solana_sdk::bpf_loader::id(); | ||||||
|     static ref MEMO_PROGRAM_ID: Pubkey = |     static ref MEMO_PROGRAM_ID: Pubkey = | ||||||
|         Pubkey::from_str(&spl_memo_v1_0::id().to_string()).unwrap(); |         Pubkey::from_str(&spl_memo_v1_0::id().to_string()).unwrap(); | ||||||
|  |     static ref STAKE_PROGRAM_ID: Pubkey = solana_stake_program::id(); | ||||||
|  |     static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id(); | ||||||
|     static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v2_0(); |     static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v2_0(); | ||||||
|     static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableProgram> = { |     static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableProgram> = { | ||||||
|         let mut m = HashMap::new(); |         let mut m = HashMap::new(); | ||||||
|         m.insert(*MEMO_PROGRAM_ID, ParsableProgram::SplMemo); |         m.insert(*MEMO_PROGRAM_ID, ParsableProgram::SplMemo); | ||||||
|         m.insert(*TOKEN_PROGRAM_ID, ParsableProgram::SplToken); |         m.insert(*TOKEN_PROGRAM_ID, ParsableProgram::SplToken); | ||||||
|         m.insert(*BPF_LOADER_PROGRAM_ID, ParsableProgram::BpfLoader); |         m.insert(*BPF_LOADER_PROGRAM_ID, ParsableProgram::BpfLoader); | ||||||
|  |         m.insert(*STAKE_PROGRAM_ID, ParsableProgram::Stake); | ||||||
|  |         m.insert(*SYSTEM_PROGRAM_ID, ParsableProgram::System); | ||||||
|         m |         m | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| @@ -61,6 +68,8 @@ pub enum ParsableProgram { | |||||||
|     SplMemo, |     SplMemo, | ||||||
|     SplToken, |     SplToken, | ||||||
|     BpfLoader, |     BpfLoader, | ||||||
|  |     Stake, | ||||||
|  |     System, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn parse( | pub fn parse( | ||||||
| @@ -77,6 +86,8 @@ pub fn parse( | |||||||
|         ParsableProgram::BpfLoader => { |         ParsableProgram::BpfLoader => { | ||||||
|             serde_json::to_value(parse_bpf_loader(instruction, account_keys)?)? |             serde_json::to_value(parse_bpf_loader(instruction, account_keys)?)? | ||||||
|         } |         } | ||||||
|  |         ParsableProgram::Stake => serde_json::to_value(parse_stake(instruction, account_keys)?)?, | ||||||
|  |         ParsableProgram::System => serde_json::to_value(parse_system(instruction, account_keys)?)?, | ||||||
|     }; |     }; | ||||||
|     Ok(ParsedInstruction { |     Ok(ParsedInstruction { | ||||||
|         program: format!("{:?}", program_name).to_kebab_case(), |         program: format!("{:?}", program_name).to_kebab_case(), | ||||||
| @@ -89,6 +100,20 @@ fn parse_memo(instruction: &CompiledInstruction) -> Value { | |||||||
|     Value::String(from_utf8(&instruction.data).unwrap().to_string()) |     Value::String(from_utf8(&instruction.data).unwrap().to_string()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub(crate) fn check_num_accounts( | ||||||
|  |     accounts: &[u8], | ||||||
|  |     num: usize, | ||||||
|  |     parsable_program: ParsableProgram, | ||||||
|  | ) -> Result<(), ParseInstructionError> { | ||||||
|  |     if accounts.len() < num { | ||||||
|  |         Err(ParseInstructionError::InstructionKeyMismatch( | ||||||
|  |             parsable_program, | ||||||
|  |         )) | ||||||
|  |     } else { | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod test { | mod test { | ||||||
|     use super::*; |     use super::*; | ||||||
|   | |||||||
							
								
								
									
										450
									
								
								transaction-status/src/parse_stake.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										450
									
								
								transaction-status/src/parse_stake.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,450 @@ | |||||||
|  | use crate::parse_instruction::{ | ||||||
|  |     check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum, | ||||||
|  | }; | ||||||
|  | use bincode::deserialize; | ||||||
|  | use serde_json::{json, Map}; | ||||||
|  | use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey}; | ||||||
|  | use solana_stake_program::stake_instruction::StakeInstruction; | ||||||
|  |  | ||||||
|  | pub fn parse_stake( | ||||||
|  |     instruction: &CompiledInstruction, | ||||||
|  |     account_keys: &[Pubkey], | ||||||
|  | ) -> Result<ParsedInstructionEnum, ParseInstructionError> { | ||||||
|  |     let stake_instruction: StakeInstruction = deserialize(&instruction.data) | ||||||
|  |         .map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::Stake))?; | ||||||
|  |     match instruction.accounts.iter().max() { | ||||||
|  |         Some(index) if (*index as usize) < account_keys.len() => {} | ||||||
|  |         _ => { | ||||||
|  |             // Runtime should prevent this from ever happening | ||||||
|  |             return Err(ParseInstructionError::InstructionKeyMismatch( | ||||||
|  |                 ParsableProgram::Stake, | ||||||
|  |             )); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     match stake_instruction { | ||||||
|  |         StakeInstruction::Initialize(authorized, lockup) => { | ||||||
|  |             check_num_stake_accounts(&instruction.accounts, 2)?; | ||||||
|  |             let authorized = json!({ | ||||||
|  |                 "staker": authorized.staker.to_string(), | ||||||
|  |                 "withdrawer": authorized.withdrawer.to_string(), | ||||||
|  |             }); | ||||||
|  |             let lockup = json!({ | ||||||
|  |                 "unixTimestamp": lockup.unix_timestamp, | ||||||
|  |                 "epoch": lockup.epoch, | ||||||
|  |                 "custodian": lockup.custodian.to_string(), | ||||||
|  |             }); | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "initialize".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "rentSysvar": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
|  |                     "authorized": authorized, | ||||||
|  |                     "lockup": lockup, | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         StakeInstruction::Authorize(new_authorized, authority_type) => { | ||||||
|  |             check_num_stake_accounts(&instruction.accounts, 3)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "authorize".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "clockSysvar": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
|  |                     "authority": account_keys[instruction.accounts[2] as usize].to_string(), | ||||||
|  |                     "newAuthority": new_authorized.to_string(), | ||||||
|  |                     "authorityType": authority_type, | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         StakeInstruction::DelegateStake => { | ||||||
|  |             check_num_stake_accounts(&instruction.accounts, 6)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "delegate".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "voteAccount": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
|  |                     "clockSysvar": account_keys[instruction.accounts[2] as usize].to_string(), | ||||||
|  |                     "stakeHistorySysvar": account_keys[instruction.accounts[3] as usize].to_string(), | ||||||
|  |                     "stakeConfigAccount": account_keys[instruction.accounts[4] as usize].to_string(), | ||||||
|  |                     "stakeAuthority": account_keys[instruction.accounts[5] as usize].to_string(), | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         StakeInstruction::Split(lamports) => { | ||||||
|  |             check_num_stake_accounts(&instruction.accounts, 3)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "split".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "newSplitAccount": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
|  |                     "stakeAuthority": account_keys[instruction.accounts[2] as usize].to_string(), | ||||||
|  |                     "lamports": lamports, | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         StakeInstruction::Withdraw(lamports) => { | ||||||
|  |             check_num_stake_accounts(&instruction.accounts, 5)?; | ||||||
|  |             let mut value = json!({ | ||||||
|  |                 "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                 "destination": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
|  |                 "clockSysvar": account_keys[instruction.accounts[2] as usize].to_string(), | ||||||
|  |                 "stakeHistorySysvar": account_keys[instruction.accounts[3] as usize].to_string(), | ||||||
|  |                 "withdrawAuthority": account_keys[instruction.accounts[4] as usize].to_string(), | ||||||
|  |                 "lamports": lamports, | ||||||
|  |             }); | ||||||
|  |             let map = value.as_object_mut().unwrap(); | ||||||
|  |             if instruction.accounts.len() == 6 { | ||||||
|  |                 map.insert( | ||||||
|  |                     "custodian".to_string(), | ||||||
|  |                     json!(account_keys[instruction.accounts[5] as usize].to_string()), | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "withdraw".to_string(), | ||||||
|  |                 info: value, | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         StakeInstruction::Deactivate => { | ||||||
|  |             check_num_stake_accounts(&instruction.accounts, 3)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "deactivate".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "clockSysvar": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
|  |                     "stakeAuthority": account_keys[instruction.accounts[2] as usize].to_string(), | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         StakeInstruction::SetLockup(lockup_args) => { | ||||||
|  |             check_num_stake_accounts(&instruction.accounts, 2)?; | ||||||
|  |             let mut lockup_map = Map::new(); | ||||||
|  |             if let Some(timestamp) = lockup_args.unix_timestamp { | ||||||
|  |                 lockup_map.insert("unixTimestamp".to_string(), json!(timestamp)); | ||||||
|  |             } | ||||||
|  |             if let Some(epoch) = lockup_args.epoch { | ||||||
|  |                 lockup_map.insert("epoch".to_string(), json!(epoch)); | ||||||
|  |             } | ||||||
|  |             if let Some(custodian) = lockup_args.custodian { | ||||||
|  |                 lockup_map.insert("custodian".to_string(), json!(custodian.to_string())); | ||||||
|  |             } | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "setLockup".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "custodian": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
|  |                     "lockup": lockup_map, | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         StakeInstruction::Merge => { | ||||||
|  |             check_num_stake_accounts(&instruction.accounts, 5)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "merge".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "destination": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "source": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
|  |                     "clockSysvar": account_keys[instruction.accounts[2] as usize].to_string(), | ||||||
|  |                     "stakeHistorySysvar": account_keys[instruction.accounts[3] as usize].to_string(), | ||||||
|  |                     "stakeAuthority": account_keys[instruction.accounts[4] as usize].to_string(), | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         StakeInstruction::AuthorizeWithSeed(args) => { | ||||||
|  |             check_num_stake_accounts(&instruction.accounts, 2)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "authorizeWithSeed".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "authorityBase": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
|  |                     "newAuthorized": args.new_authorized_pubkey.to_string(), | ||||||
|  |                     "authorityType": args.stake_authorize, | ||||||
|  |                     "authoritySeed": args.authority_seed, | ||||||
|  |                     "authorityOwner": args.authority_owner.to_string(), | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn check_num_stake_accounts(accounts: &[u8], num: usize) -> Result<(), ParseInstructionError> { | ||||||
|  |     check_num_accounts(accounts, num, ParsableProgram::Stake) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | mod test { | ||||||
|  |     use super::*; | ||||||
|  |     use solana_sdk::{message::Message, pubkey::Pubkey}; | ||||||
|  |     use solana_stake_program::{ | ||||||
|  |         stake_instruction::{self, LockupArgs}, | ||||||
|  |         stake_state::{Authorized, Lockup, StakeAuthorize}, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_parse_stake_instruction() { | ||||||
|  |         let mut keys: Vec<Pubkey> = vec![]; | ||||||
|  |         for _ in 0..6 { | ||||||
|  |             keys.push(Pubkey::new_rand()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let authorized = Authorized { | ||||||
|  |             staker: Pubkey::new_rand(), | ||||||
|  |             withdrawer: Pubkey::new_rand(), | ||||||
|  |         }; | ||||||
|  |         let lockup = Lockup { | ||||||
|  |             unix_timestamp: 1_234_567_890, | ||||||
|  |             epoch: 11, | ||||||
|  |             custodian: Pubkey::new_rand(), | ||||||
|  |         }; | ||||||
|  |         let lamports = 55; | ||||||
|  |  | ||||||
|  |         let instructions = | ||||||
|  |             stake_instruction::create_account(&keys[0], &keys[1], &authorized, &lockup, lamports); | ||||||
|  |         let message = Message::new(&instructions, None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_stake(&message.instructions[1], &keys[0..3]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "initialize".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": keys[1].to_string(), | ||||||
|  |                     "rentSysvar": keys[2].to_string(), | ||||||
|  |                     "authorized": { | ||||||
|  |                         "staker": authorized.staker.to_string(), | ||||||
|  |                         "withdrawer": authorized.withdrawer.to_string(), | ||||||
|  |                     }, | ||||||
|  |                     "lockup": { | ||||||
|  |                         "unixTimestamp": lockup.unix_timestamp, | ||||||
|  |                         "epoch": lockup.epoch, | ||||||
|  |                         "custodian": lockup.custodian.to_string(), | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_stake(&message.instructions[1], &keys[0..2]).is_err()); | ||||||
|  |  | ||||||
|  |         let authority_type = StakeAuthorize::Staker; | ||||||
|  |         let instruction = | ||||||
|  |             stake_instruction::authorize(&keys[1], &keys[0], &keys[3], authority_type); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_stake(&message.instructions[0], &keys[0..3]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "authorize".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": keys[1].to_string(), | ||||||
|  |                     "clockSysvar": keys[2].to_string(), | ||||||
|  |                     "authority": keys[0].to_string(), | ||||||
|  |                     "newAuthority": keys[3].to_string(), | ||||||
|  |                     "authorityType": authority_type, | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_stake(&message.instructions[0], &keys[0..2]).is_err()); | ||||||
|  |  | ||||||
|  |         let instruction = stake_instruction::delegate_stake(&keys[1], &keys[0], &keys[2]); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_stake(&message.instructions[0], &keys[0..6]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "delegate".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": keys[1].to_string(), | ||||||
|  |                     "voteAccount": keys[2].to_string(), | ||||||
|  |                     "clockSysvar": keys[3].to_string(), | ||||||
|  |                     "stakeHistorySysvar": keys[4].to_string(), | ||||||
|  |                     "stakeConfigAccount": keys[5].to_string(), | ||||||
|  |                     "stakeAuthority": keys[0].to_string(), | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_stake(&message.instructions[0], &keys[0..5]).is_err()); | ||||||
|  |  | ||||||
|  |         let instructions = stake_instruction::split(&keys[2], &keys[0], lamports, &keys[1]); | ||||||
|  |         let message = Message::new(&instructions, None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_stake(&message.instructions[1], &keys[0..3]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "split".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": keys[2].to_string(), | ||||||
|  |                     "newSplitAccount": keys[1].to_string(), | ||||||
|  |                     "stakeAuthority": keys[0].to_string(), | ||||||
|  |                     "lamports": lamports, | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_stake(&message.instructions[1], &keys[0..2]).is_err()); | ||||||
|  |  | ||||||
|  |         let instruction = stake_instruction::withdraw(&keys[1], &keys[0], &keys[2], lamports, None); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_stake(&message.instructions[0], &keys[0..5]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "withdraw".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": keys[1].to_string(), | ||||||
|  |                     "destination": keys[2].to_string(), | ||||||
|  |                     "clockSysvar": keys[3].to_string(), | ||||||
|  |                     "stakeHistorySysvar": keys[4].to_string(), | ||||||
|  |                     "withdrawAuthority": keys[0].to_string(), | ||||||
|  |                     "lamports": lamports, | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         let instruction = | ||||||
|  |             stake_instruction::withdraw(&keys[2], &keys[0], &keys[3], lamports, Some(&keys[1])); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_stake(&message.instructions[0], &keys[0..6]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "withdraw".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": keys[2].to_string(), | ||||||
|  |                     "destination": keys[3].to_string(), | ||||||
|  |                     "clockSysvar": keys[4].to_string(), | ||||||
|  |                     "stakeHistorySysvar": keys[5].to_string(), | ||||||
|  |                     "withdrawAuthority": keys[0].to_string(), | ||||||
|  |                     "custodian": keys[1].to_string(), | ||||||
|  |                     "lamports": lamports, | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_stake(&message.instructions[0], &keys[0..4]).is_err()); | ||||||
|  |  | ||||||
|  |         let instruction = stake_instruction::deactivate_stake(&keys[1], &keys[0]); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_stake(&message.instructions[0], &keys[0..3]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "deactivate".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": keys[1].to_string(), | ||||||
|  |                     "clockSysvar": keys[2].to_string(), | ||||||
|  |                     "stakeAuthority": keys[0].to_string(), | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_stake(&message.instructions[0], &keys[0..2]).is_err()); | ||||||
|  |  | ||||||
|  |         let instructions = stake_instruction::merge(&keys[1], &keys[0], &keys[2]); | ||||||
|  |         let message = Message::new(&instructions, None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_stake(&message.instructions[0], &keys[0..5]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "merge".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "destination": keys[1].to_string(), | ||||||
|  |                     "source": keys[2].to_string(), | ||||||
|  |                     "clockSysvar": keys[3].to_string(), | ||||||
|  |                     "stakeHistorySysvar": keys[4].to_string(), | ||||||
|  |                     "stakeAuthority": keys[0].to_string(), | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_stake(&message.instructions[0], &keys[0..4]).is_err()); | ||||||
|  |  | ||||||
|  |         let seed = "test_seed"; | ||||||
|  |         let instruction = stake_instruction::authorize_with_seed( | ||||||
|  |             &keys[1], | ||||||
|  |             &keys[0], | ||||||
|  |             seed.to_string(), | ||||||
|  |             &keys[2], | ||||||
|  |             &keys[3], | ||||||
|  |             authority_type, | ||||||
|  |         ); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_stake(&message.instructions[0], &keys[0..2]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "authorizeWithSeed".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": keys[1].to_string(), | ||||||
|  |                     "authorityOwner": keys[2].to_string(), | ||||||
|  |                     "newAuthorized": keys[3].to_string(), | ||||||
|  |                     "authorityBase": keys[0].to_string(), | ||||||
|  |                     "authoritySeed": seed, | ||||||
|  |                     "authorityType": authority_type, | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_stake(&message.instructions[0], &keys[0..1]).is_err()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_parse_set_lockup() { | ||||||
|  |         let mut keys: Vec<Pubkey> = vec![]; | ||||||
|  |         for _ in 0..2 { | ||||||
|  |             keys.push(Pubkey::new_rand()); | ||||||
|  |         } | ||||||
|  |         let unix_timestamp = 1_234_567_890; | ||||||
|  |         let epoch = 11; | ||||||
|  |         let custodian = Pubkey::new_rand(); | ||||||
|  |  | ||||||
|  |         let lockup = LockupArgs { | ||||||
|  |             unix_timestamp: Some(unix_timestamp), | ||||||
|  |             epoch: None, | ||||||
|  |             custodian: None, | ||||||
|  |         }; | ||||||
|  |         let instruction = stake_instruction::set_lockup(&keys[1], &lockup, &keys[0]); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_stake(&message.instructions[0], &keys[0..2]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "setLockup".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": keys[1].to_string(), | ||||||
|  |                     "custodian": keys[0].to_string(), | ||||||
|  |                     "lockup": { | ||||||
|  |                         "unixTimestamp": unix_timestamp | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         let lockup = LockupArgs { | ||||||
|  |             unix_timestamp: Some(unix_timestamp), | ||||||
|  |             epoch: Some(epoch), | ||||||
|  |             custodian: None, | ||||||
|  |         }; | ||||||
|  |         let instruction = stake_instruction::set_lockup(&keys[1], &lockup, &keys[0]); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_stake(&message.instructions[0], &keys[0..2]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "setLockup".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": keys[1].to_string(), | ||||||
|  |                     "custodian": keys[0].to_string(), | ||||||
|  |                     "lockup": { | ||||||
|  |                         "unixTimestamp": unix_timestamp, | ||||||
|  |                         "epoch": epoch, | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         let lockup = LockupArgs { | ||||||
|  |             unix_timestamp: Some(unix_timestamp), | ||||||
|  |             epoch: Some(epoch), | ||||||
|  |             custodian: Some(custodian), | ||||||
|  |         }; | ||||||
|  |         let instruction = stake_instruction::set_lockup(&keys[1], &lockup, &keys[0]); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_stake(&message.instructions[0], &keys[0..2]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "setLockup".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "stakeAccount": keys[1].to_string(), | ||||||
|  |                     "custodian": keys[0].to_string(), | ||||||
|  |                     "lockup": { | ||||||
|  |                         "unixTimestamp": unix_timestamp, | ||||||
|  |                         "epoch": epoch, | ||||||
|  |                         "custodian": custodian.to_string(), | ||||||
|  |                     } | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         assert!(parse_stake(&message.instructions[0], &keys[0..1]).is_err()); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										430
									
								
								transaction-status/src/parse_system.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										430
									
								
								transaction-status/src/parse_system.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,430 @@ | |||||||
|  | use crate::parse_instruction::{ | ||||||
|  |     check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum, | ||||||
|  | }; | ||||||
|  | use bincode::deserialize; | ||||||
|  | use serde_json::json; | ||||||
|  | use solana_sdk::{ | ||||||
|  |     instruction::CompiledInstruction, pubkey::Pubkey, system_instruction::SystemInstruction, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | pub fn parse_system( | ||||||
|  |     instruction: &CompiledInstruction, | ||||||
|  |     account_keys: &[Pubkey], | ||||||
|  | ) -> Result<ParsedInstructionEnum, ParseInstructionError> { | ||||||
|  |     let system_instruction: SystemInstruction = deserialize(&instruction.data) | ||||||
|  |         .map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::System))?; | ||||||
|  |     match instruction.accounts.iter().max() { | ||||||
|  |         Some(index) if (*index as usize) < account_keys.len() => {} | ||||||
|  |         _ => { | ||||||
|  |             // Runtime should prevent this from ever happening | ||||||
|  |             return Err(ParseInstructionError::InstructionKeyMismatch( | ||||||
|  |                 ParsableProgram::System, | ||||||
|  |             )); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     match system_instruction { | ||||||
|  |         SystemInstruction::CreateAccount { | ||||||
|  |             lamports, | ||||||
|  |             space, | ||||||
|  |             owner, | ||||||
|  |         } => { | ||||||
|  |             check_num_system_accounts(&instruction.accounts, 2)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "createAccount".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "source": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "newAccount": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
|  |                     "lamports": lamports, | ||||||
|  |                     "space": space, | ||||||
|  |                     "owner": owner.to_string(), | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         SystemInstruction::Assign { owner } => { | ||||||
|  |             check_num_system_accounts(&instruction.accounts, 1)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "assign".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "account": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "owner": owner.to_string(), | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         SystemInstruction::Transfer { lamports } => { | ||||||
|  |             check_num_system_accounts(&instruction.accounts, 2)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "transfer".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "source": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "destination": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
|  |                     "lamports": lamports, | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         SystemInstruction::CreateAccountWithSeed { | ||||||
|  |             base, | ||||||
|  |             seed, | ||||||
|  |             lamports, | ||||||
|  |             space, | ||||||
|  |             owner, | ||||||
|  |         } => { | ||||||
|  |             check_num_system_accounts(&instruction.accounts, 3)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "createAccountWithSeed".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "source": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "newAccount": account_keys[instruction.accounts[2] as usize].to_string(), | ||||||
|  |                     "base": base.to_string(), | ||||||
|  |                     "seed": seed, | ||||||
|  |                     "lamports": lamports, | ||||||
|  |                     "space": space, | ||||||
|  |                     "owner": owner.to_string(), | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         SystemInstruction::AdvanceNonceAccount => { | ||||||
|  |             check_num_system_accounts(&instruction.accounts, 3)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "advanceNonce".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "nonceAccount": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "recentBlockhashesSysvar": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
|  |                     "nonceAuthority": account_keys[instruction.accounts[2] as usize].to_string(), | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         SystemInstruction::WithdrawNonceAccount(lamports) => { | ||||||
|  |             check_num_system_accounts(&instruction.accounts, 5)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "withdrawFromNonce".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "nonceAccount": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "destination": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
|  |                     "recentBlockhashesSysvar": account_keys[instruction.accounts[2] as usize].to_string(), | ||||||
|  |                     "rentSysvar": account_keys[instruction.accounts[3] as usize].to_string(), | ||||||
|  |                     "nonceAuthority": account_keys[instruction.accounts[4] as usize].to_string(), | ||||||
|  |                     "lamports": lamports, | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         SystemInstruction::InitializeNonceAccount(authority) => { | ||||||
|  |             check_num_system_accounts(&instruction.accounts, 3)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "initializeNonce".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "nonceAccount": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "recentBlockhashesSysvar": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
|  |                     "rentSysvar": account_keys[instruction.accounts[2] as usize].to_string(), | ||||||
|  |                     "nonceAuthority": authority.to_string(), | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         SystemInstruction::AuthorizeNonceAccount(authority) => { | ||||||
|  |             check_num_system_accounts(&instruction.accounts, 1)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "authorizeNonce".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "nonceAccount": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "nonceAuthority": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
|  |                     "newAuthorized": authority.to_string(), | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         SystemInstruction::Allocate { space } => { | ||||||
|  |             check_num_system_accounts(&instruction.accounts, 1)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "allocate".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "account": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "space": space, | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         SystemInstruction::AllocateWithSeed { | ||||||
|  |             base, | ||||||
|  |             seed, | ||||||
|  |             space, | ||||||
|  |             owner, | ||||||
|  |         } => { | ||||||
|  |             check_num_system_accounts(&instruction.accounts, 2)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "allocateWithSeed".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "account": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "base": base.to_string(), | ||||||
|  |                     "seed": seed, | ||||||
|  |                     "space": space, | ||||||
|  |                     "owner": owner.to_string(), | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         SystemInstruction::AssignWithSeed { base, seed, owner } => { | ||||||
|  |             check_num_system_accounts(&instruction.accounts, 2)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "assignWithSeed".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "account": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "base": base.to_string(), | ||||||
|  |                     "seed": seed, | ||||||
|  |                     "owner": owner.to_string(), | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         SystemInstruction::TransferWithSeed { | ||||||
|  |             lamports, | ||||||
|  |             from_seed, | ||||||
|  |             from_owner, | ||||||
|  |         } => { | ||||||
|  |             check_num_system_accounts(&instruction.accounts, 3)?; | ||||||
|  |             Ok(ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "transferWithSeed".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "source": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|  |                     "sourceBase": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
|  |                     "destination": account_keys[instruction.accounts[2] as usize].to_string(), | ||||||
|  |                     "lamports": lamports, | ||||||
|  |                     "sourceSeed": from_seed, | ||||||
|  |                     "sourceOwner": from_owner.to_string(), | ||||||
|  |                 }), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn check_num_system_accounts(accounts: &[u8], num: usize) -> Result<(), ParseInstructionError> { | ||||||
|  |     check_num_accounts(accounts, num, ParsableProgram::System) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | mod test { | ||||||
|  |     use super::*; | ||||||
|  |     use solana_sdk::{message::Message, pubkey::Pubkey, system_instruction}; | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_parse_system_instruction() { | ||||||
|  |         let mut keys: Vec<Pubkey> = vec![]; | ||||||
|  |         for _ in 0..6 { | ||||||
|  |             keys.push(Pubkey::new_rand()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let lamports = 55; | ||||||
|  |         let space = 128; | ||||||
|  |  | ||||||
|  |         let instruction = | ||||||
|  |             system_instruction::create_account(&keys[0], &keys[1], lamports, space, &keys[2]); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_system(&message.instructions[0], &keys[0..2]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "createAccount".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "source": keys[0].to_string(), | ||||||
|  |                     "newAccount": keys[1].to_string(), | ||||||
|  |                     "lamports": lamports, | ||||||
|  |                     "owner": keys[2].to_string(), | ||||||
|  |                     "space": space, | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_system(&message.instructions[0], &keys[0..1]).is_err()); | ||||||
|  |  | ||||||
|  |         let instruction = system_instruction::assign(&keys[0], &keys[1]); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_system(&message.instructions[0], &keys[0..1]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "assign".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "account": keys[0].to_string(), | ||||||
|  |                     "owner": keys[1].to_string(), | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_system(&message.instructions[0], &[]).is_err()); | ||||||
|  |  | ||||||
|  |         let instruction = system_instruction::transfer(&keys[0], &keys[1], lamports); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_system(&message.instructions[0], &keys[0..2]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "transfer".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "source": keys[0].to_string(), | ||||||
|  |                     "destination": keys[1].to_string(), | ||||||
|  |                     "lamports": lamports, | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_system(&message.instructions[0], &keys[0..1]).is_err()); | ||||||
|  |  | ||||||
|  |         let seed = "test_seed"; | ||||||
|  |         let instruction = system_instruction::create_account_with_seed( | ||||||
|  |             &keys[0], &keys[1], &keys[2], seed, lamports, space, &keys[3], | ||||||
|  |         ); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_system(&message.instructions[0], &keys[0..3]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "createAccountWithSeed".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "source": keys[0].to_string(), | ||||||
|  |                     "newAccount": keys[1].to_string(), | ||||||
|  |                     "lamports": lamports, | ||||||
|  |                     "base": keys[2].to_string(), | ||||||
|  |                     "seed": seed, | ||||||
|  |                     "owner": keys[3].to_string(), | ||||||
|  |                     "space": space, | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_system(&message.instructions[0], &keys[0..2]).is_err()); | ||||||
|  |  | ||||||
|  |         let instruction = system_instruction::allocate(&keys[0], space); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_system(&message.instructions[0], &keys[0..1]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "allocate".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "account": keys[0].to_string(), | ||||||
|  |                     "space": space, | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_system(&message.instructions[0], &[]).is_err()); | ||||||
|  |  | ||||||
|  |         let instruction = | ||||||
|  |             system_instruction::allocate_with_seed(&keys[1], &keys[0], seed, space, &keys[2]); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_system(&message.instructions[0], &keys[0..2]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "allocateWithSeed".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "account": keys[1].to_string(), | ||||||
|  |                     "base": keys[0].to_string(), | ||||||
|  |                     "seed": seed, | ||||||
|  |                     "owner": keys[2].to_string(), | ||||||
|  |                     "space": space, | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_system(&message.instructions[0], &keys[0..1]).is_err()); | ||||||
|  |  | ||||||
|  |         let instruction = system_instruction::assign_with_seed(&keys[1], &keys[0], seed, &keys[2]); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_system(&message.instructions[0], &keys[0..2]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "assignWithSeed".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "account": keys[1].to_string(), | ||||||
|  |                     "base": keys[0].to_string(), | ||||||
|  |                     "seed": seed, | ||||||
|  |                     "owner": keys[2].to_string(), | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_system(&message.instructions[0], &keys[0..1]).is_err()); | ||||||
|  |  | ||||||
|  |         let instruction = system_instruction::transfer_with_seed( | ||||||
|  |             &keys[1], | ||||||
|  |             &keys[0], | ||||||
|  |             seed.to_string(), | ||||||
|  |             &keys[3], | ||||||
|  |             &keys[2], | ||||||
|  |             lamports, | ||||||
|  |         ); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_system(&message.instructions[0], &keys[0..3]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "transferWithSeed".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "source": keys[1].to_string(), | ||||||
|  |                     "sourceBase": keys[0].to_string(), | ||||||
|  |                     "sourceSeed": seed, | ||||||
|  |                     "sourceOwner": keys[3].to_string(), | ||||||
|  |                     "lamports": lamports, | ||||||
|  |                     "destination": keys[2].to_string() | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_system(&message.instructions[0], &keys[0..2]).is_err()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_parse_system_instruction_nonce() { | ||||||
|  |         let mut keys: Vec<Pubkey> = vec![]; | ||||||
|  |         for _ in 0..5 { | ||||||
|  |             keys.push(Pubkey::new_rand()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let instruction = system_instruction::advance_nonce_account(&keys[1], &keys[0]); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_system(&message.instructions[0], &keys[0..3]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "advanceNonce".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "nonceAccount": keys[1].to_string(), | ||||||
|  |                     "recentBlockhashesSysvar": keys[2].to_string(), | ||||||
|  |                     "nonceAuthority": keys[0].to_string(), | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_system(&message.instructions[0], &keys[0..2]).is_err()); | ||||||
|  |  | ||||||
|  |         let lamports = 55; | ||||||
|  |         let instruction = | ||||||
|  |             system_instruction::withdraw_nonce_account(&keys[1], &keys[0], &keys[2], lamports); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_system(&message.instructions[0], &keys[0..5]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "withdrawFromNonce".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "nonceAccount": keys[1].to_string(), | ||||||
|  |                     "destination": keys[2].to_string(), | ||||||
|  |                     "recentBlockhashesSysvar": keys[3].to_string(), | ||||||
|  |                     "rentSysvar": keys[4].to_string(), | ||||||
|  |                     "nonceAuthority": keys[0].to_string(), | ||||||
|  |                     "lamports": lamports | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_system(&message.instructions[0], &keys[0..4]).is_err()); | ||||||
|  |  | ||||||
|  |         let instructions = | ||||||
|  |             system_instruction::create_nonce_account(&keys[0], &keys[1], &keys[4], lamports); | ||||||
|  |         let message = Message::new(&instructions, None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_system(&message.instructions[1], &keys[0..4]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "initializeNonce".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "nonceAccount": keys[1].to_string(), | ||||||
|  |                     "recentBlockhashesSysvar": keys[2].to_string(), | ||||||
|  |                     "rentSysvar": keys[3].to_string(), | ||||||
|  |                     "nonceAuthority": keys[4].to_string(), | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_system(&message.instructions[1], &keys[0..3]).is_err()); | ||||||
|  |  | ||||||
|  |         let instruction = system_instruction::authorize_nonce_account(&keys[1], &keys[0], &keys[2]); | ||||||
|  |         let message = Message::new(&[instruction], None); | ||||||
|  |         assert_eq!( | ||||||
|  |             parse_system(&message.instructions[0], &keys[0..2]).unwrap(), | ||||||
|  |             ParsedInstructionEnum { | ||||||
|  |                 instruction_type: "authorizeNonce".to_string(), | ||||||
|  |                 info: json!({ | ||||||
|  |                     "nonceAccount": keys[1].to_string(), | ||||||
|  |                     "newAuthorized": keys[2].to_string(), | ||||||
|  |                     "nonceAuthority": keys[0].to_string(), | ||||||
|  |                 }), | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |         assert!(parse_system(&message.instructions[0], &keys[0..1]).is_err()); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,4 +1,6 @@ | |||||||
| use crate::parse_instruction::{ParsableProgram, ParseInstructionError, ParsedInstructionEnum}; | use crate::parse_instruction::{ | ||||||
|  |     check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum, | ||||||
|  | }; | ||||||
| use serde_json::{json, Map, Value}; | use serde_json::{json, Map, Value}; | ||||||
| use solana_account_decoder::parse_token::token_amount_to_ui_amount; | use solana_account_decoder::parse_token::token_amount_to_ui_amount; | ||||||
| use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey}; | use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey}; | ||||||
| @@ -13,11 +15,14 @@ pub fn parse_token( | |||||||
| ) -> Result<ParsedInstructionEnum, ParseInstructionError> { | ) -> Result<ParsedInstructionEnum, ParseInstructionError> { | ||||||
|     let token_instruction = TokenInstruction::unpack(&instruction.data) |     let token_instruction = TokenInstruction::unpack(&instruction.data) | ||||||
|         .map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken))?; |         .map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken))?; | ||||||
|     if instruction.accounts.len() > account_keys.len() { |     match instruction.accounts.iter().max() { | ||||||
|         // Runtime should prevent this from ever happening |         Some(index) if (*index as usize) < account_keys.len() => {} | ||||||
|         return Err(ParseInstructionError::InstructionKeyMismatch( |         _ => { | ||||||
|             ParsableProgram::SplToken, |             // Runtime should prevent this from ever happening | ||||||
|         )); |             return Err(ParseInstructionError::InstructionKeyMismatch( | ||||||
|  |                 ParsableProgram::SplToken, | ||||||
|  |             )); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|     match token_instruction { |     match token_instruction { | ||||||
|         TokenInstruction::InitializeMint { |         TokenInstruction::InitializeMint { | ||||||
| @@ -25,11 +30,7 @@ pub fn parse_token( | |||||||
|             mint_authority, |             mint_authority, | ||||||
|             freeze_authority, |             freeze_authority, | ||||||
|         } => { |         } => { | ||||||
|             if instruction.accounts.len() < 2 { |             check_num_token_accounts(&instruction.accounts, 2)?; | ||||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( |  | ||||||
|                     ParsableProgram::SplToken, |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             let mut value = json!({ |             let mut value = json!({ | ||||||
|                 "mint": account_keys[instruction.accounts[0] as usize].to_string(), |                 "mint": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|                 "decimals": decimals, |                 "decimals": decimals, | ||||||
| @@ -49,11 +50,7 @@ pub fn parse_token( | |||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
|         TokenInstruction::InitializeAccount => { |         TokenInstruction::InitializeAccount => { | ||||||
|             if instruction.accounts.len() < 4 { |             check_num_token_accounts(&instruction.accounts, 4)?; | ||||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( |  | ||||||
|                     ParsableProgram::SplToken, |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             Ok(ParsedInstructionEnum { |             Ok(ParsedInstructionEnum { | ||||||
|                 instruction_type: "initializeAccount".to_string(), |                 instruction_type: "initializeAccount".to_string(), | ||||||
|                 info: json!({ |                 info: json!({ | ||||||
| @@ -65,11 +62,7 @@ pub fn parse_token( | |||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
|         TokenInstruction::InitializeMultisig { m } => { |         TokenInstruction::InitializeMultisig { m } => { | ||||||
|             if instruction.accounts.len() < 3 { |             check_num_token_accounts(&instruction.accounts, 3)?; | ||||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( |  | ||||||
|                     ParsableProgram::SplToken, |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             let mut signers: Vec<String> = vec![]; |             let mut signers: Vec<String> = vec![]; | ||||||
|             for i in instruction.accounts[2..].iter() { |             for i in instruction.accounts[2..].iter() { | ||||||
|                 signers.push(account_keys[*i as usize].to_string()); |                 signers.push(account_keys[*i as usize].to_string()); | ||||||
| @@ -85,11 +78,7 @@ pub fn parse_token( | |||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
|         TokenInstruction::Transfer { amount } => { |         TokenInstruction::Transfer { amount } => { | ||||||
|             if instruction.accounts.len() < 3 { |             check_num_token_accounts(&instruction.accounts, 3)?; | ||||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( |  | ||||||
|                     ParsableProgram::SplToken, |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             let mut value = json!({ |             let mut value = json!({ | ||||||
|                 "source": account_keys[instruction.accounts[0] as usize].to_string(), |                 "source": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|                 "destination": account_keys[instruction.accounts[1] as usize].to_string(), |                 "destination": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
| @@ -110,11 +99,7 @@ pub fn parse_token( | |||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
|         TokenInstruction::Approve { amount } => { |         TokenInstruction::Approve { amount } => { | ||||||
|             if instruction.accounts.len() < 3 { |             check_num_token_accounts(&instruction.accounts, 3)?; | ||||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( |  | ||||||
|                     ParsableProgram::SplToken, |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             let mut value = json!({ |             let mut value = json!({ | ||||||
|                 "source": account_keys[instruction.accounts[0] as usize].to_string(), |                 "source": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|                 "delegate": account_keys[instruction.accounts[1] as usize].to_string(), |                 "delegate": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
| @@ -135,11 +120,7 @@ pub fn parse_token( | |||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
|         TokenInstruction::Revoke => { |         TokenInstruction::Revoke => { | ||||||
|             if instruction.accounts.len() < 2 { |             check_num_token_accounts(&instruction.accounts, 2)?; | ||||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( |  | ||||||
|                     ParsableProgram::SplToken, |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             let mut value = json!({ |             let mut value = json!({ | ||||||
|                 "source": account_keys[instruction.accounts[0] as usize].to_string(), |                 "source": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|             }); |             }); | ||||||
| @@ -161,11 +142,7 @@ pub fn parse_token( | |||||||
|             authority_type, |             authority_type, | ||||||
|             new_authority, |             new_authority, | ||||||
|         } => { |         } => { | ||||||
|             if instruction.accounts.len() < 2 { |             check_num_token_accounts(&instruction.accounts, 2)?; | ||||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( |  | ||||||
|                     ParsableProgram::SplToken, |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             let owned = match authority_type { |             let owned = match authority_type { | ||||||
|                 AuthorityType::MintTokens | AuthorityType::FreezeAccount => "mint", |                 AuthorityType::MintTokens | AuthorityType::FreezeAccount => "mint", | ||||||
|                 AuthorityType::AccountOwner | AuthorityType::CloseAccount => "account", |                 AuthorityType::AccountOwner | AuthorityType::CloseAccount => "account", | ||||||
| @@ -193,11 +170,7 @@ pub fn parse_token( | |||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
|         TokenInstruction::MintTo { amount } => { |         TokenInstruction::MintTo { amount } => { | ||||||
|             if instruction.accounts.len() < 3 { |             check_num_token_accounts(&instruction.accounts, 3)?; | ||||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( |  | ||||||
|                     ParsableProgram::SplToken, |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             let mut value = json!({ |             let mut value = json!({ | ||||||
|                 "mint": account_keys[instruction.accounts[0] as usize].to_string(), |                 "mint": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|                 "account": account_keys[instruction.accounts[1] as usize].to_string(), |                 "account": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
| @@ -218,11 +191,7 @@ pub fn parse_token( | |||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
|         TokenInstruction::Burn { amount } => { |         TokenInstruction::Burn { amount } => { | ||||||
|             if instruction.accounts.len() < 3 { |             check_num_token_accounts(&instruction.accounts, 3)?; | ||||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( |  | ||||||
|                     ParsableProgram::SplToken, |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             let mut value = json!({ |             let mut value = json!({ | ||||||
|                 "account": account_keys[instruction.accounts[0] as usize].to_string(), |                 "account": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|                 "mint": account_keys[instruction.accounts[1] as usize].to_string(), |                 "mint": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
| @@ -243,11 +212,7 @@ pub fn parse_token( | |||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
|         TokenInstruction::CloseAccount => { |         TokenInstruction::CloseAccount => { | ||||||
|             if instruction.accounts.len() < 3 { |             check_num_token_accounts(&instruction.accounts, 3)?; | ||||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( |  | ||||||
|                     ParsableProgram::SplToken, |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             let mut value = json!({ |             let mut value = json!({ | ||||||
|                 "account": account_keys[instruction.accounts[0] as usize].to_string(), |                 "account": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|                 "destination": account_keys[instruction.accounts[1] as usize].to_string(), |                 "destination": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
| @@ -267,11 +232,7 @@ pub fn parse_token( | |||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
|         TokenInstruction::FreezeAccount => { |         TokenInstruction::FreezeAccount => { | ||||||
|             if instruction.accounts.len() < 3 { |             check_num_token_accounts(&instruction.accounts, 3)?; | ||||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( |  | ||||||
|                     ParsableProgram::SplToken, |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             let mut value = json!({ |             let mut value = json!({ | ||||||
|                 "account": account_keys[instruction.accounts[0] as usize].to_string(), |                 "account": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|                 "mint": account_keys[instruction.accounts[1] as usize].to_string(), |                 "mint": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
| @@ -291,11 +252,7 @@ pub fn parse_token( | |||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
|         TokenInstruction::ThawAccount => { |         TokenInstruction::ThawAccount => { | ||||||
|             if instruction.accounts.len() < 3 { |             check_num_token_accounts(&instruction.accounts, 3)?; | ||||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( |  | ||||||
|                     ParsableProgram::SplToken, |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             let mut value = json!({ |             let mut value = json!({ | ||||||
|                 "account": account_keys[instruction.accounts[0] as usize].to_string(), |                 "account": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|                 "mint": account_keys[instruction.accounts[1] as usize].to_string(), |                 "mint": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
| @@ -315,11 +272,7 @@ pub fn parse_token( | |||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
|         TokenInstruction::TransferChecked { amount, decimals } => { |         TokenInstruction::TransferChecked { amount, decimals } => { | ||||||
|             if instruction.accounts.len() < 4 { |             check_num_token_accounts(&instruction.accounts, 4)?; | ||||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( |  | ||||||
|                     ParsableProgram::SplToken, |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             let mut value = json!({ |             let mut value = json!({ | ||||||
|                 "source": account_keys[instruction.accounts[0] as usize].to_string(), |                 "source": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|                 "mint": account_keys[instruction.accounts[1] as usize].to_string(), |                 "mint": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
| @@ -341,11 +294,7 @@ pub fn parse_token( | |||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
|         TokenInstruction::ApproveChecked { amount, decimals } => { |         TokenInstruction::ApproveChecked { amount, decimals } => { | ||||||
|             if instruction.accounts.len() < 4 { |             check_num_token_accounts(&instruction.accounts, 4)?; | ||||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( |  | ||||||
|                     ParsableProgram::SplToken, |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             let mut value = json!({ |             let mut value = json!({ | ||||||
|                 "source": account_keys[instruction.accounts[0] as usize].to_string(), |                 "source": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|                 "mint": account_keys[instruction.accounts[1] as usize].to_string(), |                 "mint": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
| @@ -367,11 +316,7 @@ pub fn parse_token( | |||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
|         TokenInstruction::MintToChecked { amount, decimals } => { |         TokenInstruction::MintToChecked { amount, decimals } => { | ||||||
|             if instruction.accounts.len() < 3 { |             check_num_token_accounts(&instruction.accounts, 3)?; | ||||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( |  | ||||||
|                     ParsableProgram::SplToken, |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             let mut value = json!({ |             let mut value = json!({ | ||||||
|                 "mint": account_keys[instruction.accounts[0] as usize].to_string(), |                 "mint": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|                 "account": account_keys[instruction.accounts[1] as usize].to_string(), |                 "account": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
| @@ -392,11 +337,7 @@ pub fn parse_token( | |||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
|         TokenInstruction::BurnChecked { amount, decimals } => { |         TokenInstruction::BurnChecked { amount, decimals } => { | ||||||
|             if instruction.accounts.len() < 3 { |             check_num_token_accounts(&instruction.accounts, 3)?; | ||||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( |  | ||||||
|                     ParsableProgram::SplToken, |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             let mut value = json!({ |             let mut value = json!({ | ||||||
|                 "account": account_keys[instruction.accounts[0] as usize].to_string(), |                 "account": account_keys[instruction.accounts[0] as usize].to_string(), | ||||||
|                 "mint": account_keys[instruction.accounts[1] as usize].to_string(), |                 "mint": account_keys[instruction.accounts[1] as usize].to_string(), | ||||||
| @@ -465,6 +406,10 @@ fn parse_signers( | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fn check_num_token_accounts(accounts: &[u8], num: usize) -> Result<(), ParseInstructionError> { | ||||||
|  |     check_num_accounts(accounts, num, ParsableProgram::SplToken) | ||||||
|  | } | ||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod test { | mod test { | ||||||
|     use super::*; |     use super::*; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user