Parse upgradeable loader instructions and accounts (#15195)

* Parse upgradeable-loader instructions

* Parse upgradeable-loader accounts
This commit is contained in:
Tyera Eulberg 2021-02-08 17:18:10 -07:00 committed by GitHub
parent c51e49a746
commit c0a6272afd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 465 additions and 4 deletions

View File

@ -4,6 +4,7 @@ extern crate lazy_static;
extern crate serde_derive;
pub mod parse_account_data;
pub mod parse_bpf_loader;
pub mod parse_config;
pub mod parse_nonce;
pub mod parse_stake;

View File

@ -1,4 +1,5 @@
use crate::{
parse_bpf_loader::parse_bpf_upgradeable_loader,
parse_config::parse_config,
parse_nonce::parse_nonce,
parse_stake::parse_stake,
@ -13,6 +14,7 @@ use std::collections::HashMap;
use thiserror::Error;
lazy_static! {
static ref BPF_UPGRADEABLE_LOADER_PROGRAM_ID: Pubkey = solana_sdk::bpf_loader_upgradeable::id();
static ref CONFIG_PROGRAM_ID: Pubkey = solana_config_program::id();
static ref STAKE_PROGRAM_ID: Pubkey = solana_stake_program::id();
static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id();
@ -21,6 +23,10 @@ lazy_static! {
static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id();
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
let mut m = HashMap::new();
m.insert(
*BPF_UPGRADEABLE_LOADER_PROGRAM_ID,
ParsableAccount::BpfUpgradeableLoader,
);
m.insert(*CONFIG_PROGRAM_ID, ParsableAccount::Config);
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::SplToken);
@ -60,6 +66,7 @@ pub struct ParsedAccount {
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ParsableAccount {
BpfUpgradeableLoader,
Config,
Nonce,
SplToken,
@ -84,6 +91,9 @@ pub fn parse_account_data(
.ok_or(ParseAccountError::ProgramNotParsable)?;
let additional_data = additional_data.unwrap_or_default();
let parsed_json = match program_name {
ParsableAccount::BpfUpgradeableLoader => {
serde_json::to_value(parse_bpf_upgradeable_loader(data)?)?
}
ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
ParsableAccount::SplToken => {

View File

@ -0,0 +1,181 @@
use crate::{
parse_account_data::{ParsableAccount, ParseAccountError},
UiAccountData, UiAccountEncoding,
};
use bincode::{deserialize, serialized_size};
use solana_sdk::{bpf_loader_upgradeable::UpgradeableLoaderState, pubkey::Pubkey};
pub fn parse_bpf_upgradeable_loader(
data: &[u8],
) -> Result<BpfUpgradeableLoaderAccountType, ParseAccountError> {
let account_state: UpgradeableLoaderState = deserialize(data).map_err(|_| {
ParseAccountError::AccountNotParsable(ParsableAccount::BpfUpgradeableLoader)
})?;
let parsed_account = match account_state {
UpgradeableLoaderState::Uninitialized => BpfUpgradeableLoaderAccountType::Uninitialized,
UpgradeableLoaderState::Buffer { authority_address } => {
let offset = if authority_address.is_some() {
UpgradeableLoaderState::buffer_data_offset().unwrap()
} else {
// This case included for code completeness; in practice, a Buffer account will
// always have authority_address.is_some()
UpgradeableLoaderState::buffer_data_offset().unwrap()
- serialized_size(&Pubkey::default()).unwrap() as usize
};
BpfUpgradeableLoaderAccountType::Buffer(UiBuffer {
authority: authority_address.map(|pubkey| pubkey.to_string()),
data: UiAccountData::Binary(
base64::encode(&data[offset as usize..]),
UiAccountEncoding::Base64,
),
})
}
UpgradeableLoaderState::Program {
programdata_address,
} => BpfUpgradeableLoaderAccountType::Program(UiProgram {
program_data: programdata_address.to_string(),
}),
UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address,
} => {
let offset = if upgrade_authority_address.is_some() {
UpgradeableLoaderState::programdata_data_offset().unwrap()
} else {
UpgradeableLoaderState::programdata_data_offset().unwrap()
- serialized_size(&Pubkey::default()).unwrap() as usize
};
BpfUpgradeableLoaderAccountType::ProgramData(UiProgramData {
slot,
authority: upgrade_authority_address.map(|pubkey| pubkey.to_string()),
data: UiAccountData::Binary(
base64::encode(&data[offset as usize..]),
UiAccountEncoding::Base64,
),
})
}
};
Ok(parsed_account)
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
pub enum BpfUpgradeableLoaderAccountType {
Uninitialized,
Buffer(UiBuffer),
Program(UiProgram),
ProgramData(UiProgramData),
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiBuffer {
pub authority: Option<String>,
pub data: UiAccountData,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiProgram {
pub program_data: String,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiProgramData {
pub slot: u64,
pub authority: Option<String>,
pub data: UiAccountData,
}
#[cfg(test)]
mod test {
use super::*;
use bincode::serialize;
use solana_sdk::pubkey::Pubkey;
#[test]
fn test_parse_bpf_upgradeable_loader_accounts() {
let bpf_loader_state = UpgradeableLoaderState::Uninitialized;
let account_data = serialize(&bpf_loader_state).unwrap();
assert_eq!(
parse_bpf_upgradeable_loader(&account_data).unwrap(),
BpfUpgradeableLoaderAccountType::Uninitialized
);
let program = vec![7u8; 64]; // Arbitrary program data
let authority = Pubkey::new_unique();
let bpf_loader_state = UpgradeableLoaderState::Buffer {
authority_address: Some(authority),
};
let mut account_data = serialize(&bpf_loader_state).unwrap();
account_data.extend_from_slice(&program);
assert_eq!(
parse_bpf_upgradeable_loader(&account_data).unwrap(),
BpfUpgradeableLoaderAccountType::Buffer(UiBuffer {
authority: Some(authority.to_string()),
data: UiAccountData::Binary(base64::encode(&program), UiAccountEncoding::Base64),
})
);
// This case included for code completeness; in practice, a Buffer account will always have
// authority_address.is_some()
let bpf_loader_state = UpgradeableLoaderState::Buffer {
authority_address: None,
};
let mut account_data = serialize(&bpf_loader_state).unwrap();
account_data.extend_from_slice(&program);
assert_eq!(
parse_bpf_upgradeable_loader(&account_data).unwrap(),
BpfUpgradeableLoaderAccountType::Buffer(UiBuffer {
authority: None,
data: UiAccountData::Binary(base64::encode(&program), UiAccountEncoding::Base64),
})
);
let programdata_address = Pubkey::new_unique();
let bpf_loader_state = UpgradeableLoaderState::Program {
programdata_address,
};
let account_data = serialize(&bpf_loader_state).unwrap();
assert_eq!(
parse_bpf_upgradeable_loader(&account_data).unwrap(),
BpfUpgradeableLoaderAccountType::Program(UiProgram {
program_data: programdata_address.to_string(),
})
);
let authority = Pubkey::new_unique();
let slot = 42;
let bpf_loader_state = UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address: Some(authority),
};
let mut account_data = serialize(&bpf_loader_state).unwrap();
account_data.extend_from_slice(&program);
assert_eq!(
parse_bpf_upgradeable_loader(&account_data).unwrap(),
BpfUpgradeableLoaderAccountType::ProgramData(UiProgramData {
slot,
authority: Some(authority.to_string()),
data: UiAccountData::Binary(base64::encode(&program), UiAccountEncoding::Base64),
})
);
let bpf_loader_state = UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address: None,
};
let mut account_data = serialize(&bpf_loader_state).unwrap();
account_data.extend_from_slice(&program);
assert_eq!(
parse_bpf_upgradeable_loader(&account_data).unwrap(),
BpfUpgradeableLoaderAccountType::ProgramData(UiProgramData {
slot,
authority: None,
data: UiAccountData::Binary(base64::encode(&program), UiAccountEncoding::Base64),
})
);
}
}

View File

@ -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());
}
}

View File

@ -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)?)?,