Parse upgradeable loader instructions and accounts (#15195)
* Parse upgradeable-loader instructions * Parse upgradeable-loader accounts
This commit is contained in:
@ -1,8 +1,11 @@
|
||||
use crate::parse_instruction::{ParsableProgram, ParseInstructionError, ParsedInstructionEnum};
|
||||
use crate::parse_instruction::{
|
||||
check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum,
|
||||
};
|
||||
use bincode::deserialize;
|
||||
use serde_json::json;
|
||||
use solana_sdk::{
|
||||
instruction::CompiledInstruction, loader_instruction::LoaderInstruction, pubkey::Pubkey,
|
||||
instruction::CompiledInstruction, loader_instruction::LoaderInstruction,
|
||||
loader_upgradeable_instruction::UpgradeableLoaderInstruction, pubkey::Pubkey,
|
||||
};
|
||||
|
||||
pub fn parse_bpf_loader(
|
||||
@ -34,9 +37,114 @@ pub fn parse_bpf_loader(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_bpf_upgradeable_loader(
|
||||
instruction: &CompiledInstruction,
|
||||
account_keys: &[Pubkey],
|
||||
) -> Result<ParsedInstructionEnum, ParseInstructionError> {
|
||||
let bpf_upgradeable_loader_instruction: UpgradeableLoaderInstruction =
|
||||
deserialize(&instruction.data).map_err(|_| {
|
||||
ParseInstructionError::InstructionNotParsable(ParsableProgram::BpfUpgradeableLoader)
|
||||
})?;
|
||||
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::BpfUpgradeableLoader,
|
||||
));
|
||||
}
|
||||
}
|
||||
match bpf_upgradeable_loader_instruction {
|
||||
UpgradeableLoaderInstruction::InitializeBuffer => {
|
||||
check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 1)?;
|
||||
let mut value = json!({
|
||||
"account": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
});
|
||||
let map = value.as_object_mut().unwrap();
|
||||
if instruction.accounts.len() > 1 {
|
||||
map.insert(
|
||||
"authority".to_string(),
|
||||
json!(account_keys[instruction.accounts[1] as usize].to_string()),
|
||||
);
|
||||
}
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "initializeBuffer".to_string(),
|
||||
info: value,
|
||||
})
|
||||
}
|
||||
UpgradeableLoaderInstruction::Write { offset, bytes } => {
|
||||
check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 2)?;
|
||||
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(),
|
||||
"authority": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len } => {
|
||||
check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 8)?;
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "deployWithMaxDataLen".to_string(),
|
||||
info: json!({
|
||||
"maxDataLen": max_data_len,
|
||||
"payerAccount": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
"programDataAccount": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
"programAccount": account_keys[instruction.accounts[2] as usize].to_string(),
|
||||
"bufferAccount": account_keys[instruction.accounts[3] as usize].to_string(),
|
||||
"rentSysvar": account_keys[instruction.accounts[4] as usize].to_string(),
|
||||
"clockSysvar": account_keys[instruction.accounts[5] as usize].to_string(),
|
||||
"systemProgram": account_keys[instruction.accounts[6] as usize].to_string(),
|
||||
"authority": account_keys[instruction.accounts[7] as usize].to_string(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
UpgradeableLoaderInstruction::Upgrade => {
|
||||
check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 7)?;
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "upgrade".to_string(),
|
||||
info: json!({
|
||||
"programDataAccount": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
"programAccount": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
"bufferAccount": account_keys[instruction.accounts[2] as usize].to_string(),
|
||||
"spillAccount": account_keys[instruction.accounts[3] as usize].to_string(),
|
||||
"rentSysvar": account_keys[instruction.accounts[4] as usize].to_string(),
|
||||
"clockSysvar": account_keys[instruction.accounts[5] as usize].to_string(),
|
||||
"authority": account_keys[instruction.accounts[6] as usize].to_string(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
UpgradeableLoaderInstruction::SetAuthority => {
|
||||
check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 2)?;
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "setAuthority".to_string(),
|
||||
info: json!({
|
||||
"account": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
"authority": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
"newAuthority": if instruction.accounts.len() > 2 {
|
||||
Some(account_keys[instruction.accounts[2] as usize].to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_num_bpf_upgradeable_loader_accounts(
|
||||
accounts: &[u8],
|
||||
num: usize,
|
||||
) -> Result<(), ParseInstructionError> {
|
||||
check_num_accounts(accounts, num, ParsableProgram::BpfUpgradeableLoader)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use serde_json::Value;
|
||||
use solana_sdk::{message::Message, pubkey};
|
||||
|
||||
#[test]
|
||||
@ -96,4 +204,153 @@ mod test {
|
||||
};
|
||||
assert!(parse_bpf_loader(&bad_compiled_instruction, &account_keys).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_bpf_upgradeable_loader_instructions() {
|
||||
let mut keys: Vec<Pubkey> = vec![];
|
||||
for _ in 0..8 {
|
||||
keys.push(Pubkey::new_unique());
|
||||
}
|
||||
let offset = 4242;
|
||||
let bytes = vec![8; 99];
|
||||
let max_data_len = 54321;
|
||||
|
||||
let instructions = solana_sdk::bpf_loader_upgradeable::create_buffer(
|
||||
&keys[0],
|
||||
&keys[1],
|
||||
&keys[2],
|
||||
55,
|
||||
max_data_len,
|
||||
)
|
||||
.unwrap();
|
||||
let message = Message::new(&instructions, None);
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&message.instructions[1], &keys[0..3]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "initializeBuffer".to_string(),
|
||||
info: json!({
|
||||
"account": keys[1].to_string(),
|
||||
"authority": keys[2].to_string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_bpf_upgradeable_loader(&message.instructions[1], &keys[0..2]).is_err());
|
||||
|
||||
let instruction =
|
||||
solana_sdk::bpf_loader_upgradeable::write(&keys[1], &keys[0], offset, bytes.clone());
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&message.instructions[0], &keys[0..2]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "write".to_string(),
|
||||
info: json!({
|
||||
"offset": offset,
|
||||
"bytes": base64::encode(&bytes),
|
||||
"account": keys[1].to_string(),
|
||||
"authority": keys[0].to_string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_bpf_upgradeable_loader(&message.instructions[0], &keys[0..1]).is_err());
|
||||
|
||||
let instructions = solana_sdk::bpf_loader_upgradeable::deploy_with_max_program_len(
|
||||
&keys[0],
|
||||
&keys[1],
|
||||
&keys[4],
|
||||
&keys[2],
|
||||
55,
|
||||
max_data_len,
|
||||
)
|
||||
.unwrap();
|
||||
let message = Message::new(&instructions, None);
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&message.instructions[1], &keys[0..8]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "deployWithMaxDataLen".to_string(),
|
||||
info: json!({
|
||||
"maxDataLen": max_data_len,
|
||||
"payerAccount": keys[0].to_string(),
|
||||
"programAccount": keys[1].to_string(),
|
||||
"authority": keys[2].to_string(),
|
||||
"programDataAccount": keys[3].to_string(),
|
||||
"bufferAccount": keys[4].to_string(),
|
||||
"rentSysvar": keys[5].to_string(),
|
||||
"clockSysvar": keys[6].to_string(),
|
||||
"systemProgram": keys[7].to_string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_bpf_upgradeable_loader(&message.instructions[1], &keys[0..7]).is_err());
|
||||
|
||||
let instruction =
|
||||
solana_sdk::bpf_loader_upgradeable::upgrade(&keys[2], &keys[3], &keys[0], &keys[4]);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&message.instructions[0], &keys[0..7]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "upgrade".to_string(),
|
||||
info: json!({
|
||||
"authority": keys[0].to_string(),
|
||||
"programDataAccount": keys[1].to_string(),
|
||||
"programAccount": keys[2].to_string(),
|
||||
"bufferAccount": keys[3].to_string(),
|
||||
"spillAccount": keys[4].to_string(),
|
||||
"rentSysvar": keys[5].to_string(),
|
||||
"clockSysvar": keys[6].to_string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_bpf_upgradeable_loader(&message.instructions[0], &keys[0..6]).is_err());
|
||||
|
||||
let instruction =
|
||||
solana_sdk::bpf_loader_upgradeable::set_buffer_authority(&keys[1], &keys[0], &keys[2]);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&message.instructions[0], &keys[0..3]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "setAuthority".to_string(),
|
||||
info: json!({
|
||||
"account": keys[1].to_string(),
|
||||
"authority": keys[0].to_string(),
|
||||
"newAuthority": keys[2].to_string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_bpf_upgradeable_loader(&message.instructions[0], &keys[0..1]).is_err());
|
||||
|
||||
let instruction = solana_sdk::bpf_loader_upgradeable::set_upgrade_authority(
|
||||
&keys[1],
|
||||
&keys[0],
|
||||
Some(&keys[2]),
|
||||
);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&message.instructions[0], &keys[0..3]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "setAuthority".to_string(),
|
||||
info: json!({
|
||||
"account": keys[1].to_string(),
|
||||
"authority": keys[0].to_string(),
|
||||
"newAuthority": keys[2].to_string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_bpf_upgradeable_loader(&message.instructions[0], &keys[0..1]).is_err());
|
||||
|
||||
let instruction =
|
||||
solana_sdk::bpf_loader_upgradeable::set_upgrade_authority(&keys[1], &keys[0], None);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&message.instructions[0], &keys[0..2]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "setAuthority".to_string(),
|
||||
info: json!({
|
||||
"account": keys[1].to_string(),
|
||||
"authority": keys[0].to_string(),
|
||||
"newAuthority": Value::Null,
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_bpf_upgradeable_loader(&message.instructions[0], &keys[0..1]).is_err());
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
use crate::{
|
||||
parse_bpf_loader::parse_bpf_loader, parse_stake::parse_stake, parse_system::parse_system,
|
||||
parse_token::parse_token, parse_vote::parse_vote,
|
||||
parse_bpf_loader::{parse_bpf_loader, parse_bpf_upgradeable_loader},
|
||||
parse_stake::parse_stake,
|
||||
parse_system::parse_system,
|
||||
parse_token::parse_token,
|
||||
parse_vote::parse_vote,
|
||||
};
|
||||
use inflector::Inflector;
|
||||
use serde_json::Value;
|
||||
@ -14,6 +17,7 @@ use thiserror::Error;
|
||||
|
||||
lazy_static! {
|
||||
static ref BPF_LOADER_PROGRAM_ID: Pubkey = solana_sdk::bpf_loader::id();
|
||||
static ref BPF_UPGRADEABLE_LOADER_PROGRAM_ID: Pubkey = solana_sdk::bpf_loader_upgradeable::id();
|
||||
static ref MEMO_V1_PROGRAM_ID: Pubkey =
|
||||
Pubkey::from_str(&spl_memo_v1_0::id().to_string()).unwrap();
|
||||
static ref MEMO_V3_PROGRAM_ID: Pubkey =
|
||||
@ -28,6 +32,10 @@ lazy_static! {
|
||||
m.insert(*MEMO_V3_PROGRAM_ID, ParsableProgram::SplMemo);
|
||||
m.insert(*TOKEN_PROGRAM_ID, ParsableProgram::SplToken);
|
||||
m.insert(*BPF_LOADER_PROGRAM_ID, ParsableProgram::BpfLoader);
|
||||
m.insert(
|
||||
*BPF_UPGRADEABLE_LOADER_PROGRAM_ID,
|
||||
ParsableProgram::BpfUpgradeableLoader,
|
||||
);
|
||||
m.insert(*STAKE_PROGRAM_ID, ParsableProgram::Stake);
|
||||
m.insert(*SYSTEM_PROGRAM_ID, ParsableProgram::System);
|
||||
m.insert(*VOTE_PROGRAM_ID, ParsableProgram::Vote);
|
||||
@ -73,6 +81,7 @@ pub enum ParsableProgram {
|
||||
SplMemo,
|
||||
SplToken,
|
||||
BpfLoader,
|
||||
BpfUpgradeableLoader,
|
||||
Stake,
|
||||
System,
|
||||
Vote,
|
||||
@ -92,6 +101,9 @@ pub fn parse(
|
||||
ParsableProgram::BpfLoader => {
|
||||
serde_json::to_value(parse_bpf_loader(instruction, account_keys)?)?
|
||||
}
|
||||
ParsableProgram::BpfUpgradeableLoader => {
|
||||
serde_json::to_value(parse_bpf_upgradeable_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)?)?,
|
||||
ParsableProgram::Vote => serde_json::to_value(parse_vote(instruction, account_keys)?)?,
|
||||
|
Reference in New Issue
Block a user