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
This commit is contained in:
parent
f11c86b2c5
commit
46d0019955
|
@ -190,7 +190,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:
|
||||
|
@ -446,7 +446,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:
|
||||
|
@ -816,7 +816,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:
|
||||
|
||||
|
@ -1539,7 +1539,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:
|
||||
|
@ -1682,7 +1682,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
|
||||
|
||||
|
@ -2226,7 +2226,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:
|
||||
|
@ -2315,7 +2315,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:
|
||||
|
@ -2824,7 +2824,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:
|
||||
|
||||
|
@ -2959,7 +2959,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::*;
|
||||
|
|
|
@ -0,0 +1,452 @@
|
|||
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]
|
||||
#[allow(clippy::same_item_push)]
|
||||
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]
|
||||
#[allow(clippy::same_item_push)]
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,432 @@
|
|||
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]
|
||||
#[allow(clippy::same_item_push)]
|
||||
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]
|
||||
#[allow(clippy::same_item_push)]
|
||||
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,11 +15,14 @@ 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() {
|
||||
// Runtime should prevent this from ever happening
|
||||
return Err(ParseInstructionError::InstructionKeyMismatch(
|
||||
ParsableProgram::SplToken,
|
||||
));
|
||||
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 {
|
||||
|
@ -25,11 +30,7 @@ pub fn parse_token(
|
|||
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::*;
|
||||
|
|
Loading…
Reference in New Issue