Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse address-lookup-table instructions #27316

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/src/developing/clients/jsonrpc-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | |
Expand Down
1 change: 1 addition & 0 deletions programs/bpf/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions transaction-status/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
1 change: 1 addition & 0 deletions transaction-status/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
358 changes: 358 additions & 0 deletions transaction-status/src/parse_address_lookup_table.rs
Original file line number Diff line number Diff line change
@@ -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,
CriesofCarrots marked this conversation as resolved.
Show resolved Hide resolved
});
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());
}
}
Loading