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;
|
extern crate serde_derive;
|
||||||
|
|
||||||
pub mod parse_accounts;
|
pub mod parse_accounts;
|
||||||
|
pub mod parse_bpf_loader;
|
||||||
pub mod parse_instruction;
|
pub mod parse_instruction;
|
||||||
pub mod parse_token;
|
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 inflector::Inflector;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use solana_account_decoder::parse_token::spl_token_id_v2_0;
|
use solana_account_decoder::parse_token::spl_token_id_v2_0;
|
||||||
|
@ -10,6 +10,7 @@ use std::{
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
static ref BPF_LOADER_PROGRAM_ID: Pubkey = solana_sdk::bpf_loader::id();
|
||||||
static ref MEMO_PROGRAM_ID: Pubkey =
|
static ref MEMO_PROGRAM_ID: Pubkey =
|
||||||
Pubkey::from_str(&spl_memo_v1_0::id().to_string()).unwrap();
|
Pubkey::from_str(&spl_memo_v1_0::id().to_string()).unwrap();
|
||||||
static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v2_0();
|
static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v2_0();
|
||||||
|
@ -17,6 +18,7 @@ lazy_static! {
|
||||||
let mut m = HashMap::new();
|
let mut m = HashMap::new();
|
||||||
m.insert(*MEMO_PROGRAM_ID, ParsableProgram::SplMemo);
|
m.insert(*MEMO_PROGRAM_ID, ParsableProgram::SplMemo);
|
||||||
m.insert(*TOKEN_PROGRAM_ID, ParsableProgram::SplToken);
|
m.insert(*TOKEN_PROGRAM_ID, ParsableProgram::SplToken);
|
||||||
|
m.insert(*BPF_LOADER_PROGRAM_ID, ParsableProgram::BpfLoader);
|
||||||
m
|
m
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -49,6 +51,7 @@ pub struct ParsedInstruction {
|
||||||
pub struct ParsedInstructionEnum {
|
pub struct ParsedInstructionEnum {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub instruction_type: String,
|
pub instruction_type: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Value::is_null")]
|
||||||
pub info: Value,
|
pub info: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +60,7 @@ pub struct ParsedInstructionEnum {
|
||||||
pub enum ParsableProgram {
|
pub enum ParsableProgram {
|
||||||
SplMemo,
|
SplMemo,
|
||||||
SplToken,
|
SplToken,
|
||||||
|
BpfLoader,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(
|
pub fn parse(
|
||||||
|
@ -70,6 +74,9 @@ pub fn parse(
|
||||||
let parsed_json = match program_name {
|
let parsed_json = match program_name {
|
||||||
ParsableProgram::SplMemo => parse_memo(instruction),
|
ParsableProgram::SplMemo => parse_memo(instruction),
|
||||||
ParsableProgram::SplToken => serde_json::to_value(parse_token(instruction, account_keys)?)?,
|
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 {
|
Ok(ParsedInstruction {
|
||||||
program: format!("{:?}", program_name).to_kebab_case(),
|
program: format!("{:?}", program_name).to_kebab_case(),
|
||||||
|
|
Loading…
Reference in New Issue