* 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: | ||||
|   - (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. | ||||
|     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. | ||||
|  | ||||
| #### Results: | ||||
| @@ -314,7 +314,7 @@ Returns identity and transaction information about a confirmed block in the ledg | ||||
| #### Parameters: | ||||
|  | ||||
| - `<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). | ||||
|  | ||||
| #### Results: | ||||
| @@ -512,7 +512,7 @@ Returns transaction details for a confirmed transaction | ||||
|  | ||||
| - `<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). | ||||
| - `<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: | ||||
|  | ||||
| @@ -894,7 +894,7 @@ Returns the account information for a list of Pubkeys | ||||
| - `<object>` - (optional) Configuration object containing the following optional fields: | ||||
|   - (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. | ||||
|     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. | ||||
|  | ||||
| #### Results: | ||||
| @@ -937,7 +937,7 @@ Returns all accounts owned by the provided program Pubkey | ||||
| - `<object>` - (optional) Configuration object containing the following optional fields: | ||||
|   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) | ||||
|   - `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) `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: | ||||
|   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) | ||||
|   - `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. | ||||
|  | ||||
| #### Results: | ||||
| @@ -1229,7 +1229,7 @@ Returns all SPL Token accounts by token owner. **UNSTABLE** | ||||
| - `<object>` - (optional) Configuration object containing the following optional fields: | ||||
|   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) | ||||
|   - `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. | ||||
|  | ||||
| #### 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) [Commitment](jsonrpc-api.md#configuring-state-commitment) | ||||
|   - `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: | ||||
|  | ||||
| @@ -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: | ||||
|   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) | ||||
|   - `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 | ||||
|  | ||||
| #### Results: | ||||
|   | ||||
| @@ -148,7 +148,8 @@ pub enum SystemInstruction { | ||||
|     /// Change the entity authorized to execute nonce instructions on the account | ||||
|     /// | ||||
|     /// # Account references | ||||
|     ///   0. [WRITE, SIGNER] Nonce account | ||||
|     ///   0. [WRITE] Nonce account | ||||
|     ///   1. [SIGNER] Nonce authority | ||||
|     /// | ||||
|     /// The `Pubkey` parameter identifies the entity to authorize | ||||
|     AuthorizeNonceAccount(Pubkey), | ||||
|   | ||||
| @@ -6,6 +6,8 @@ extern crate serde_derive; | ||||
| pub mod parse_accounts; | ||||
| pub mod parse_bpf_loader; | ||||
| pub mod parse_instruction; | ||||
| pub mod parse_stake; | ||||
| pub mod parse_system; | ||||
| pub mod parse_token; | ||||
|  | ||||
| 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 serde_json::Value; | ||||
| 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::{ | ||||
|     collections::HashMap, | ||||
|     str::{from_utf8, FromStr}, | ||||
| @@ -13,12 +16,16 @@ lazy_static! { | ||||
|     static ref BPF_LOADER_PROGRAM_ID: Pubkey = solana_sdk::bpf_loader::id(); | ||||
|     static ref MEMO_PROGRAM_ID: Pubkey = | ||||
|         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 PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableProgram> = { | ||||
|         let mut m = HashMap::new(); | ||||
|         m.insert(*MEMO_PROGRAM_ID, ParsableProgram::SplMemo); | ||||
|         m.insert(*TOKEN_PROGRAM_ID, ParsableProgram::SplToken); | ||||
|         m.insert(*BPF_LOADER_PROGRAM_ID, ParsableProgram::BpfLoader); | ||||
|         m.insert(*STAKE_PROGRAM_ID, ParsableProgram::Stake); | ||||
|         m.insert(*SYSTEM_PROGRAM_ID, ParsableProgram::System); | ||||
|         m | ||||
|     }; | ||||
| } | ||||
| @@ -61,6 +68,8 @@ pub enum ParsableProgram { | ||||
|     SplMemo, | ||||
|     SplToken, | ||||
|     BpfLoader, | ||||
|     Stake, | ||||
|     System, | ||||
| } | ||||
|  | ||||
| pub fn parse( | ||||
| @@ -77,6 +86,8 @@ pub fn parse( | ||||
|         ParsableProgram::BpfLoader => { | ||||
|             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 { | ||||
|         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()) | ||||
| } | ||||
|  | ||||
| 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)] | ||||
| mod test { | ||||
|     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 solana_account_decoder::parse_token::token_amount_to_ui_amount; | ||||
| use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey}; | ||||
| @@ -13,23 +15,22 @@ pub fn parse_token( | ||||
| ) -> Result<ParsedInstructionEnum, ParseInstructionError> { | ||||
|     let token_instruction = TokenInstruction::unpack(&instruction.data) | ||||
|         .map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken))?; | ||||
|     if instruction.accounts.len() > account_keys.len() { | ||||
|     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::SplToken, | ||||
|             )); | ||||
|         } | ||||
|     } | ||||
|     match token_instruction { | ||||
|         TokenInstruction::InitializeMint { | ||||
|             decimals, | ||||
|             mint_authority, | ||||
|             freeze_authority, | ||||
|         } => { | ||||
|             if instruction.accounts.len() < 2 { | ||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( | ||||
|                     ParsableProgram::SplToken, | ||||
|                 )); | ||||
|             } | ||||
|             check_num_token_accounts(&instruction.accounts, 2)?; | ||||
|             let mut value = json!({ | ||||
|                 "mint": account_keys[instruction.accounts[0] as usize].to_string(), | ||||
|                 "decimals": decimals, | ||||
| @@ -49,11 +50,7 @@ pub fn parse_token( | ||||
|             }) | ||||
|         } | ||||
|         TokenInstruction::InitializeAccount => { | ||||
|             if instruction.accounts.len() < 4 { | ||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( | ||||
|                     ParsableProgram::SplToken, | ||||
|                 )); | ||||
|             } | ||||
|             check_num_token_accounts(&instruction.accounts, 4)?; | ||||
|             Ok(ParsedInstructionEnum { | ||||
|                 instruction_type: "initializeAccount".to_string(), | ||||
|                 info: json!({ | ||||
| @@ -65,11 +62,7 @@ pub fn parse_token( | ||||
|             }) | ||||
|         } | ||||
|         TokenInstruction::InitializeMultisig { m } => { | ||||
|             if instruction.accounts.len() < 3 { | ||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( | ||||
|                     ParsableProgram::SplToken, | ||||
|                 )); | ||||
|             } | ||||
|             check_num_token_accounts(&instruction.accounts, 3)?; | ||||
|             let mut signers: Vec<String> = vec![]; | ||||
|             for i in instruction.accounts[2..].iter() { | ||||
|                 signers.push(account_keys[*i as usize].to_string()); | ||||
| @@ -85,11 +78,7 @@ pub fn parse_token( | ||||
|             }) | ||||
|         } | ||||
|         TokenInstruction::Transfer { amount } => { | ||||
|             if instruction.accounts.len() < 3 { | ||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( | ||||
|                     ParsableProgram::SplToken, | ||||
|                 )); | ||||
|             } | ||||
|             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(), | ||||
| @@ -110,11 +99,7 @@ pub fn parse_token( | ||||
|             }) | ||||
|         } | ||||
|         TokenInstruction::Approve { amount } => { | ||||
|             if instruction.accounts.len() < 3 { | ||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( | ||||
|                     ParsableProgram::SplToken, | ||||
|                 )); | ||||
|             } | ||||
|             check_num_token_accounts(&instruction.accounts, 3)?; | ||||
|             let mut value = json!({ | ||||
|                 "source": account_keys[instruction.accounts[0] as usize].to_string(), | ||||
|                 "delegate": account_keys[instruction.accounts[1] as usize].to_string(), | ||||
| @@ -135,11 +120,7 @@ pub fn parse_token( | ||||
|             }) | ||||
|         } | ||||
|         TokenInstruction::Revoke => { | ||||
|             if instruction.accounts.len() < 2 { | ||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( | ||||
|                     ParsableProgram::SplToken, | ||||
|                 )); | ||||
|             } | ||||
|             check_num_token_accounts(&instruction.accounts, 2)?; | ||||
|             let mut value = json!({ | ||||
|                 "source": account_keys[instruction.accounts[0] as usize].to_string(), | ||||
|             }); | ||||
| @@ -161,11 +142,7 @@ pub fn parse_token( | ||||
|             authority_type, | ||||
|             new_authority, | ||||
|         } => { | ||||
|             if instruction.accounts.len() < 2 { | ||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( | ||||
|                     ParsableProgram::SplToken, | ||||
|                 )); | ||||
|             } | ||||
|             check_num_token_accounts(&instruction.accounts, 2)?; | ||||
|             let owned = match authority_type { | ||||
|                 AuthorityType::MintTokens | AuthorityType::FreezeAccount => "mint", | ||||
|                 AuthorityType::AccountOwner | AuthorityType::CloseAccount => "account", | ||||
| @@ -193,11 +170,7 @@ pub fn parse_token( | ||||
|             }) | ||||
|         } | ||||
|         TokenInstruction::MintTo { amount } => { | ||||
|             if instruction.accounts.len() < 3 { | ||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( | ||||
|                     ParsableProgram::SplToken, | ||||
|                 )); | ||||
|             } | ||||
|             check_num_token_accounts(&instruction.accounts, 3)?; | ||||
|             let mut value = json!({ | ||||
|                 "mint": account_keys[instruction.accounts[0] 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 } => { | ||||
|             if instruction.accounts.len() < 3 { | ||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( | ||||
|                     ParsableProgram::SplToken, | ||||
|                 )); | ||||
|             } | ||||
|             check_num_token_accounts(&instruction.accounts, 3)?; | ||||
|             let mut value = json!({ | ||||
|                 "account": account_keys[instruction.accounts[0] as usize].to_string(), | ||||
|                 "mint": account_keys[instruction.accounts[1] as usize].to_string(), | ||||
| @@ -243,11 +212,7 @@ pub fn parse_token( | ||||
|             }) | ||||
|         } | ||||
|         TokenInstruction::CloseAccount => { | ||||
|             if instruction.accounts.len() < 3 { | ||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( | ||||
|                     ParsableProgram::SplToken, | ||||
|                 )); | ||||
|             } | ||||
|             check_num_token_accounts(&instruction.accounts, 3)?; | ||||
|             let mut value = json!({ | ||||
|                 "account": account_keys[instruction.accounts[0] as usize].to_string(), | ||||
|                 "destination": account_keys[instruction.accounts[1] as usize].to_string(), | ||||
| @@ -267,11 +232,7 @@ pub fn parse_token( | ||||
|             }) | ||||
|         } | ||||
|         TokenInstruction::FreezeAccount => { | ||||
|             if instruction.accounts.len() < 3 { | ||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( | ||||
|                     ParsableProgram::SplToken, | ||||
|                 )); | ||||
|             } | ||||
|             check_num_token_accounts(&instruction.accounts, 3)?; | ||||
|             let mut value = json!({ | ||||
|                 "account": account_keys[instruction.accounts[0] as usize].to_string(), | ||||
|                 "mint": account_keys[instruction.accounts[1] as usize].to_string(), | ||||
| @@ -291,11 +252,7 @@ pub fn parse_token( | ||||
|             }) | ||||
|         } | ||||
|         TokenInstruction::ThawAccount => { | ||||
|             if instruction.accounts.len() < 3 { | ||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( | ||||
|                     ParsableProgram::SplToken, | ||||
|                 )); | ||||
|             } | ||||
|             check_num_token_accounts(&instruction.accounts, 3)?; | ||||
|             let mut value = json!({ | ||||
|                 "account": account_keys[instruction.accounts[0] 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 } => { | ||||
|             if instruction.accounts.len() < 4 { | ||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( | ||||
|                     ParsableProgram::SplToken, | ||||
|                 )); | ||||
|             } | ||||
|             check_num_token_accounts(&instruction.accounts, 4)?; | ||||
|             let mut value = json!({ | ||||
|                 "source": account_keys[instruction.accounts[0] 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 } => { | ||||
|             if instruction.accounts.len() < 4 { | ||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( | ||||
|                     ParsableProgram::SplToken, | ||||
|                 )); | ||||
|             } | ||||
|             check_num_token_accounts(&instruction.accounts, 4)?; | ||||
|             let mut value = json!({ | ||||
|                 "source": account_keys[instruction.accounts[0] 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 } => { | ||||
|             if instruction.accounts.len() < 3 { | ||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( | ||||
|                     ParsableProgram::SplToken, | ||||
|                 )); | ||||
|             } | ||||
|             check_num_token_accounts(&instruction.accounts, 3)?; | ||||
|             let mut value = json!({ | ||||
|                 "mint": account_keys[instruction.accounts[0] 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 } => { | ||||
|             if instruction.accounts.len() < 3 { | ||||
|                 return Err(ParseInstructionError::InstructionKeyMismatch( | ||||
|                     ParsableProgram::SplToken, | ||||
|                 )); | ||||
|             } | ||||
|             check_num_token_accounts(&instruction.accounts, 3)?; | ||||
|             let mut value = json!({ | ||||
|                 "account": account_keys[instruction.accounts[0] 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)] | ||||
| mod test { | ||||
|     use super::*; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user