Parse bpf loader instructions (#12998)
* Add parsing for BpfLoader2 instructions * Skip info if null * Return account address in info map
This commit is contained in:
parent
c7c6c28455
commit
942e4273ba
|
@ -4,6 +4,7 @@ extern crate lazy_static;
|
|||
extern crate serde_derive;
|
||||
|
||||
pub mod parse_accounts;
|
||||
pub mod parse_bpf_loader;
|
||||
pub mod parse_instruction;
|
||||
pub mod parse_token;
|
||||
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
use crate::parse_instruction::{ParsableProgram, ParseInstructionError, ParsedInstructionEnum};
|
||||
use bincode::deserialize;
|
||||
use serde_json::json;
|
||||
use solana_sdk::{
|
||||
instruction::CompiledInstruction, loader_instruction::LoaderInstruction, pubkey::Pubkey,
|
||||
};
|
||||
|
||||
pub fn parse_bpf_loader(
|
||||
instruction: &CompiledInstruction,
|
||||
account_keys: &[Pubkey],
|
||||
) -> Result<ParsedInstructionEnum, ParseInstructionError> {
|
||||
let bpf_loader_instruction: LoaderInstruction =
|
||||
deserialize(&instruction.data).map_err(|err| {
|
||||
println!("{:?}", err);
|
||||
ParseInstructionError::InstructionNotParsable(ParsableProgram::BpfLoader)
|
||||
})?;
|
||||
if instruction.accounts.is_empty() || instruction.accounts[0] as usize >= account_keys.len() {
|
||||
return Err(ParseInstructionError::InstructionKeyMismatch(
|
||||
ParsableProgram::BpfLoader,
|
||||
));
|
||||
}
|
||||
match bpf_loader_instruction {
|
||||
LoaderInstruction::Write { offset, bytes } => Ok(ParsedInstructionEnum {
|
||||
instruction_type: "write".to_string(),
|
||||
info: json!({
|
||||
"offset": offset,
|
||||
"bytes": base64::encode(bytes),
|
||||
"account": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
}),
|
||||
}),
|
||||
LoaderInstruction::Finalize => Ok(ParsedInstructionEnum {
|
||||
instruction_type: "finalize".to_string(),
|
||||
info: json!({
|
||||
"account": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use solana_sdk::{message::Message, pubkey::Pubkey};
|
||||
|
||||
#[test]
|
||||
fn test_parse_bpf_loader_instructions() {
|
||||
let account_pubkey = Pubkey::new_rand();
|
||||
let program_id = Pubkey::new_rand();
|
||||
let offset = 4242;
|
||||
let bytes = vec![8; 99];
|
||||
let fee_payer = Pubkey::new_rand();
|
||||
let account_keys = vec![fee_payer, account_pubkey];
|
||||
let missing_account_keys = vec![account_pubkey];
|
||||
|
||||
let instruction = solana_sdk::loader_instruction::write(
|
||||
&account_pubkey,
|
||||
&program_id,
|
||||
offset,
|
||||
bytes.clone(),
|
||||
);
|
||||
let message = Message::new(&[instruction], Some(&fee_payer));
|
||||
assert_eq!(
|
||||
parse_bpf_loader(&message.instructions[0], &account_keys).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "write".to_string(),
|
||||
info: json!({
|
||||
"offset": offset,
|
||||
"bytes": base64::encode(&bytes),
|
||||
"account": account_pubkey.to_string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_bpf_loader(&message.instructions[0], &missing_account_keys).is_err());
|
||||
|
||||
let instruction = solana_sdk::loader_instruction::finalize(&account_pubkey, &program_id);
|
||||
let message = Message::new(&[instruction], Some(&fee_payer));
|
||||
assert_eq!(
|
||||
parse_bpf_loader(&message.instructions[0], &account_keys).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "finalize".to_string(),
|
||||
info: json!({
|
||||
"account": account_pubkey.to_string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_bpf_loader(&message.instructions[0], &missing_account_keys).is_err());
|
||||
|
||||
let bad_compiled_instruction = CompiledInstruction {
|
||||
program_id_index: 3,
|
||||
accounts: vec![1, 2],
|
||||
data: vec![2, 0, 0, 0], // LoaderInstruction enum only has 2 variants
|
||||
};
|
||||
assert!(parse_bpf_loader(&bad_compiled_instruction, &account_keys).is_err());
|
||||
|
||||
let bad_compiled_instruction = CompiledInstruction {
|
||||
program_id_index: 3,
|
||||
accounts: vec![],
|
||||
data: vec![1, 0, 0, 0],
|
||||
};
|
||||
assert!(parse_bpf_loader(&bad_compiled_instruction, &account_keys).is_err());
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::parse_token::parse_token;
|
||||
use crate::{parse_bpf_loader::parse_bpf_loader, parse_token::parse_token};
|
||||
use inflector::Inflector;
|
||||
use serde_json::Value;
|
||||
use solana_account_decoder::parse_token::spl_token_id_v2_0;
|
||||
|
@ -10,6 +10,7 @@ use std::{
|
|||
use thiserror::Error;
|
||||
|
||||
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 TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v2_0();
|
||||
|
@ -17,6 +18,7 @@ lazy_static! {
|
|||
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
|
||||
};
|
||||
}
|
||||
|
@ -49,6 +51,7 @@ pub struct ParsedInstruction {
|
|||
pub struct ParsedInstructionEnum {
|
||||
#[serde(rename = "type")]
|
||||
pub instruction_type: String,
|
||||
#[serde(default, skip_serializing_if = "Value::is_null")]
|
||||
pub info: Value,
|
||||
}
|
||||
|
||||
|
@ -57,6 +60,7 @@ pub struct ParsedInstructionEnum {
|
|||
pub enum ParsableProgram {
|
||||
SplMemo,
|
||||
SplToken,
|
||||
BpfLoader,
|
||||
}
|
||||
|
||||
pub fn parse(
|
||||
|
@ -70,6 +74,9 @@ pub fn parse(
|
|||
let parsed_json = match program_name {
|
||||
ParsableProgram::SplMemo => parse_memo(instruction),
|
||||
ParsableProgram::SplToken => serde_json::to_value(parse_token(instruction, account_keys)?)?,
|
||||
ParsableProgram::BpfLoader => {
|
||||
serde_json::to_value(parse_bpf_loader(instruction, account_keys)?)?
|
||||
}
|
||||
};
|
||||
Ok(ParsedInstruction {
|
||||
program: format!("{:?}", program_name).to_kebab_case(),
|
||||
|
|
Loading…
Reference in New Issue