Parse address-lookup-table instructions (#27316)
* Parse address-lookup-table instructions * Finish extend instruction handling * Rename payer, recipient * Update docs parsing status
This commit is contained in:
parent
ed463dd48c
commit
62eebe6e6d
|
@ -6559,6 +6559,7 @@ dependencies = [
|
|||
"serde_derive",
|
||||
"serde_json",
|
||||
"solana-account-decoder",
|
||||
"solana-address-lookup-table-program",
|
||||
"solana-measure",
|
||||
"solana-metrics",
|
||||
"solana-sdk 1.12.0",
|
||||
|
|
|
@ -214,7 +214,7 @@ JSON parsing for the following native and SPL programs:
|
|||
|
||||
| Program | Account State | Instructions |
|
||||
| --- | --- | --- |
|
||||
| Address Lookup | v1.12.0 | |
|
||||
| Address Lookup | v1.12.0 | v1.12.0 |
|
||||
| BPF Loader | n/a | stable |
|
||||
| BPF Upgradeable Loader | stable | stable |
|
||||
| Config | stable | |
|
||||
|
|
|
@ -5819,6 +5819,7 @@ dependencies = [
|
|||
"serde_derive",
|
||||
"serde_json",
|
||||
"solana-account-decoder",
|
||||
"solana-address-lookup-table-program",
|
||||
"solana-measure",
|
||||
"solana-metrics",
|
||||
"solana-sdk 1.12.0",
|
||||
|
|
|
@ -21,6 +21,7 @@ serde = "1.0.143"
|
|||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.83"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.12.0" }
|
||||
solana-address-lookup-table-program = { path = "../programs/address-lookup-table", version = "=1.12.0" }
|
||||
solana-measure = { path = "../measure", version = "=1.12.0" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.12.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.12.0" }
|
||||
|
|
|
@ -34,6 +34,7 @@ extern crate serde_derive;
|
|||
|
||||
pub mod extract_memos;
|
||||
pub mod parse_accounts;
|
||||
pub mod parse_address_lookup_table;
|
||||
pub mod parse_associated_token;
|
||||
pub mod parse_bpf_loader;
|
||||
pub mod parse_instruction;
|
||||
|
|
|
@ -0,0 +1,358 @@
|
|||
use {
|
||||
crate::parse_instruction::{
|
||||
check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum,
|
||||
},
|
||||
bincode::deserialize,
|
||||
serde_json::json,
|
||||
solana_address_lookup_table_program::instruction::ProgramInstruction,
|
||||
solana_sdk::{instruction::CompiledInstruction, message::AccountKeys},
|
||||
};
|
||||
|
||||
pub fn parse_address_lookup_table(
|
||||
instruction: &CompiledInstruction,
|
||||
account_keys: &AccountKeys,
|
||||
) -> Result<ParsedInstructionEnum, ParseInstructionError> {
|
||||
let address_lookup_table_instruction: ProgramInstruction = deserialize(&instruction.data)
|
||||
.map_err(|_| {
|
||||
ParseInstructionError::InstructionNotParsable(ParsableProgram::AddressLookupTable)
|
||||
})?;
|
||||
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::AddressLookupTable,
|
||||
));
|
||||
}
|
||||
}
|
||||
match address_lookup_table_instruction {
|
||||
ProgramInstruction::CreateLookupTable {
|
||||
recent_slot,
|
||||
bump_seed,
|
||||
} => {
|
||||
check_num_address_lookup_table_accounts(&instruction.accounts, 4)?;
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "createLookupTable".to_string(),
|
||||
info: json!({
|
||||
"lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
"lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
"payerAccount": account_keys[instruction.accounts[2] as usize].to_string(),
|
||||
"systemProgram": account_keys[instruction.accounts[3] as usize].to_string(),
|
||||
"recentSlot": recent_slot,
|
||||
"bumpSeed": bump_seed,
|
||||
}),
|
||||
})
|
||||
}
|
||||
ProgramInstruction::FreezeLookupTable => {
|
||||
check_num_address_lookup_table_accounts(&instruction.accounts, 2)?;
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "freezeLookupTable".to_string(),
|
||||
info: json!({
|
||||
"lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
"lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
ProgramInstruction::ExtendLookupTable { new_addresses } => {
|
||||
check_num_address_lookup_table_accounts(&instruction.accounts, 2)?;
|
||||
let new_addresses: Vec<String> = new_addresses
|
||||
.into_iter()
|
||||
.map(|address| address.to_string())
|
||||
.collect();
|
||||
let mut value = json!({
|
||||
"lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
"lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
"newAddresses": new_addresses,
|
||||
});
|
||||
let map = value.as_object_mut().unwrap();
|
||||
if instruction.accounts.len() >= 4 {
|
||||
map.insert(
|
||||
"payerAccount".to_string(),
|
||||
json!(account_keys[instruction.accounts[2] as usize].to_string()),
|
||||
);
|
||||
map.insert(
|
||||
"systemProgram".to_string(),
|
||||
json!(account_keys[instruction.accounts[3] as usize].to_string()),
|
||||
);
|
||||
}
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "extendLookupTable".to_string(),
|
||||
info: value,
|
||||
})
|
||||
}
|
||||
ProgramInstruction::DeactivateLookupTable => {
|
||||
check_num_address_lookup_table_accounts(&instruction.accounts, 2)?;
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "deactivateLookupTable".to_string(),
|
||||
info: json!({
|
||||
"lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
"lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
ProgramInstruction::CloseLookupTable => {
|
||||
check_num_address_lookup_table_accounts(&instruction.accounts, 3)?;
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "closeLookupTable".to_string(),
|
||||
info: json!({
|
||||
"lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
"lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
"recipient": account_keys[instruction.accounts[2] as usize].to_string(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_num_address_lookup_table_accounts(
|
||||
accounts: &[u8],
|
||||
num: usize,
|
||||
) -> Result<(), ParseInstructionError> {
|
||||
check_num_accounts(accounts, num, ParsableProgram::AddressLookupTable)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use {
|
||||
super::*,
|
||||
solana_address_lookup_table_program::instruction,
|
||||
solana_sdk::{message::Message, pubkey::Pubkey, system_program},
|
||||
std::str::FromStr,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_parse_create_address_lookup_table_ix() {
|
||||
let from_pubkey = Pubkey::new_unique();
|
||||
// use explicit key to have predicatble bump_seed
|
||||
let authority = Pubkey::from_str("HkxY6vXdrKzoCQLmdJ3cYo9534FdZQxzBNWTyrJzzqJM").unwrap();
|
||||
let slot = 42;
|
||||
|
||||
let (instruction, lookup_table_pubkey) =
|
||||
instruction::create_lookup_table(authority, from_pubkey, slot);
|
||||
let mut message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&message.account_keys, None)
|
||||
)
|
||||
.unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "createLookupTable".to_string(),
|
||||
info: json!({
|
||||
"lookupTableAccount": lookup_table_pubkey.to_string(),
|
||||
"lookupTableAuthority": authority.to_string(),
|
||||
"payerAccount": from_pubkey.to_string(),
|
||||
"systemProgram": system_program::id().to_string(),
|
||||
"recentSlot": slot,
|
||||
"bumpSeed": 254,
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&message.account_keys[0..3], None)
|
||||
)
|
||||
.is_err());
|
||||
let keys = message.account_keys.clone();
|
||||
message.instructions[0].accounts.pop();
|
||||
assert!(parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&keys, None)
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_freeze_lookup_table_ix() {
|
||||
let lookup_table_pubkey = Pubkey::new_unique();
|
||||
let authority = Pubkey::new_unique();
|
||||
|
||||
let instruction = instruction::freeze_lookup_table(lookup_table_pubkey, authority);
|
||||
let mut message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&message.account_keys, None)
|
||||
)
|
||||
.unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "freezeLookupTable".to_string(),
|
||||
info: json!({
|
||||
"lookupTableAccount": lookup_table_pubkey.to_string(),
|
||||
"lookupTableAuthority": authority.to_string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&message.account_keys[0..1], None)
|
||||
)
|
||||
.is_err());
|
||||
let keys = message.account_keys.clone();
|
||||
message.instructions[0].accounts.pop();
|
||||
assert!(parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&keys, None)
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_extend_lookup_table_ix() {
|
||||
let lookup_table_pubkey = Pubkey::new_unique();
|
||||
let authority = Pubkey::new_unique();
|
||||
let from_pubkey = Pubkey::new_unique();
|
||||
let no_addresses = vec![];
|
||||
let address0 = Pubkey::new_unique();
|
||||
let address1 = Pubkey::new_unique();
|
||||
let some_addresses = vec![address0, address1];
|
||||
|
||||
// No payer, no addresses
|
||||
let instruction =
|
||||
instruction::extend_lookup_table(lookup_table_pubkey, authority, None, no_addresses);
|
||||
let mut message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&message.account_keys, None)
|
||||
)
|
||||
.unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "extendLookupTable".to_string(),
|
||||
info: json!({
|
||||
"lookupTableAccount": lookup_table_pubkey.to_string(),
|
||||
"lookupTableAuthority": authority.to_string(),
|
||||
"newAddresses": [],
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&message.account_keys[0..1], None)
|
||||
)
|
||||
.is_err());
|
||||
let keys = message.account_keys.clone();
|
||||
message.instructions[0].accounts.pop();
|
||||
assert!(parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&keys, None)
|
||||
)
|
||||
.is_err());
|
||||
|
||||
// Some payer, some addresses
|
||||
let instruction = instruction::extend_lookup_table(
|
||||
lookup_table_pubkey,
|
||||
authority,
|
||||
Some(from_pubkey),
|
||||
some_addresses,
|
||||
);
|
||||
let mut message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&message.account_keys, None)
|
||||
)
|
||||
.unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "extendLookupTable".to_string(),
|
||||
info: json!({
|
||||
"lookupTableAccount": lookup_table_pubkey.to_string(),
|
||||
"lookupTableAuthority": authority.to_string(),
|
||||
"payerAccount": from_pubkey.to_string(),
|
||||
"systemProgram": system_program::id().to_string(),
|
||||
"newAddresses": [
|
||||
address0.to_string(),
|
||||
address1.to_string(),
|
||||
],
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&message.account_keys[0..1], None)
|
||||
)
|
||||
.is_err());
|
||||
let keys = message.account_keys.clone();
|
||||
message.instructions[0].accounts.pop();
|
||||
message.instructions[0].accounts.pop();
|
||||
message.instructions[0].accounts.pop();
|
||||
assert!(parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&keys, None)
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_deactivate_lookup_table_ix() {
|
||||
let lookup_table_pubkey = Pubkey::new_unique();
|
||||
let authority = Pubkey::new_unique();
|
||||
|
||||
let instruction = instruction::deactivate_lookup_table(lookup_table_pubkey, authority);
|
||||
let mut message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&message.account_keys, None)
|
||||
)
|
||||
.unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "deactivateLookupTable".to_string(),
|
||||
info: json!({
|
||||
"lookupTableAccount": lookup_table_pubkey.to_string(),
|
||||
"lookupTableAuthority": authority.to_string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&message.account_keys[0..1], None)
|
||||
)
|
||||
.is_err());
|
||||
let keys = message.account_keys.clone();
|
||||
message.instructions[0].accounts.pop();
|
||||
assert!(parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&keys, None)
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_close_lookup_table_ix() {
|
||||
let lookup_table_pubkey = Pubkey::new_unique();
|
||||
let authority = Pubkey::new_unique();
|
||||
let recipient = Pubkey::new_unique();
|
||||
|
||||
let instruction =
|
||||
instruction::close_lookup_table(lookup_table_pubkey, authority, recipient);
|
||||
let mut message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&message.account_keys, None)
|
||||
)
|
||||
.unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "closeLookupTable".to_string(),
|
||||
info: json!({
|
||||
"lookupTableAccount": lookup_table_pubkey.to_string(),
|
||||
"lookupTableAuthority": authority.to_string(),
|
||||
"recipient": recipient.to_string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&message.account_keys[0..2], None)
|
||||
)
|
||||
.is_err());
|
||||
let keys = message.account_keys.clone();
|
||||
message.instructions[0].accounts.pop();
|
||||
assert!(parse_address_lookup_table(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&keys, None)
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
use {
|
||||
crate::{
|
||||
extract_memos::{spl_memo_id_v1, spl_memo_id_v3},
|
||||
parse_address_lookup_table::parse_address_lookup_table,
|
||||
parse_associated_token::{parse_associated_token, spl_associated_token_id},
|
||||
parse_bpf_loader::{parse_bpf_loader, parse_bpf_upgradeable_loader},
|
||||
parse_stake::parse_stake,
|
||||
|
@ -23,6 +24,7 @@ use {
|
|||
};
|
||||
|
||||
lazy_static! {
|
||||
static ref ADDRESS_LOOKUP_PROGRAM_ID: Pubkey = solana_address_lookup_table_program::id();
|
||||
static ref ASSOCIATED_TOKEN_PROGRAM_ID: Pubkey = spl_associated_token_id();
|
||||
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();
|
||||
|
@ -33,6 +35,10 @@ lazy_static! {
|
|||
static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id();
|
||||
static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableProgram> = {
|
||||
let mut m = HashMap::new();
|
||||
m.insert(
|
||||
*ADDRESS_LOOKUP_PROGRAM_ID,
|
||||
ParsableProgram::AddressLookupTable,
|
||||
);
|
||||
m.insert(
|
||||
*ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
ParsableProgram::SplAssociatedTokenAccount,
|
||||
|
@ -89,6 +95,7 @@ pub struct ParsedInstructionEnum {
|
|||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ParsableProgram {
|
||||
AddressLookupTable,
|
||||
SplAssociatedTokenAccount,
|
||||
SplMemo,
|
||||
SplToken,
|
||||
|
@ -108,6 +115,9 @@ pub fn parse(
|
|||
.get(program_id)
|
||||
.ok_or(ParseInstructionError::ProgramNotParsable)?;
|
||||
let parsed_json = match program_name {
|
||||
ParsableProgram::AddressLookupTable => {
|
||||
serde_json::to_value(parse_address_lookup_table(instruction, account_keys)?)?
|
||||
}
|
||||
ParsableProgram::SplAssociatedTokenAccount => {
|
||||
serde_json::to_value(parse_associated_token(instruction, account_keys)?)?
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue