diff --git a/Cargo.lock b/Cargo.lock index 14808c6088545d..e378b90098886a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4513,6 +4513,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "solana-address-lookup-table-program", "solana-config-program", "solana-sdk 1.11.5", "solana-vote-program", diff --git a/account-decoder/Cargo.toml b/account-decoder/Cargo.toml index 2492767e281457..a2e9b5ca2ed8f8 100644 --- a/account-decoder/Cargo.toml +++ b/account-decoder/Cargo.toml @@ -19,6 +19,7 @@ lazy_static = "1.4.0" serde = "1.0.138" serde_derive = "1.0.103" serde_json = "1.0.81" +solana-address-lookup-table-program = { path = "../programs/address-lookup-table", version = "=1.11.5" } solana-config-program = { path = "../programs/config", version = "=1.11.5" } solana-sdk = { path = "../sdk", version = "=1.11.5" } solana-vote-program = { path = "../programs/vote", version = "=1.11.5" } diff --git a/account-decoder/src/lib.rs b/account-decoder/src/lib.rs index 303391d1d89b4e..09033d21676c87 100644 --- a/account-decoder/src/lib.rs +++ b/account-decoder/src/lib.rs @@ -5,6 +5,7 @@ extern crate lazy_static; extern crate serde_derive; pub mod parse_account_data; +pub mod parse_address_lookup_table; pub mod parse_bpf_loader; pub mod parse_config; pub mod parse_nonce; diff --git a/account-decoder/src/parse_account_data.rs b/account-decoder/src/parse_account_data.rs index 89d256dce7c28c..1ffdc4c0f0f11f 100644 --- a/account-decoder/src/parse_account_data.rs +++ b/account-decoder/src/parse_account_data.rs @@ -1,5 +1,6 @@ use { crate::{ + parse_address_lookup_table::parse_address_lookup_table, parse_bpf_loader::parse_bpf_upgradeable_loader, parse_config::parse_config, parse_nonce::parse_nonce, @@ -16,6 +17,7 @@ use { }; lazy_static! { + static ref ADDRESS_LOOKUP_PROGRAM_ID: Pubkey = solana_address_lookup_table_program::id(); 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 = stake::program::id(); @@ -24,6 +26,10 @@ lazy_static! { static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id(); pub static ref PARSABLE_PROGRAM_IDS: HashMap = { let mut m = HashMap::new(); + m.insert( + *ADDRESS_LOOKUP_PROGRAM_ID, + ParsableAccount::AddressLookupTable, + ); m.insert( *BPF_UPGRADEABLE_LOADER_PROGRAM_ID, ParsableAccount::BpfUpgradeableLoader, @@ -68,6 +74,7 @@ pub struct ParsedAccount { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum ParsableAccount { + AddressLookupTable, BpfUpgradeableLoader, Config, Nonce, @@ -94,6 +101,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::AddressLookupTable => { + serde_json::to_value(parse_address_lookup_table(data)?)? + } ParsableAccount::BpfUpgradeableLoader => { serde_json::to_value(parse_bpf_upgradeable_loader(data)?)? } diff --git a/account-decoder/src/parse_address_lookup_table.rs b/account-decoder/src/parse_address_lookup_table.rs new file mode 100644 index 00000000000000..26955d74a74242 --- /dev/null +++ b/account-decoder/src/parse_address_lookup_table.rs @@ -0,0 +1,117 @@ +use { + crate::parse_account_data::{ParsableAccount, ParseAccountError}, + solana_address_lookup_table_program::state::AddressLookupTable, + solana_sdk::instruction::InstructionError, +}; + +pub fn parse_address_lookup_table( + data: &[u8], +) -> Result { + AddressLookupTable::deserialize(data) + .map(|address_lookup_table| { + LookupTableAccountType::LookupTable(address_lookup_table.into()) + }) + .or_else(|err| match err { + InstructionError::UninitializedAccount => Ok(LookupTableAccountType::Uninitialized), + _ => Err(ParseAccountError::AccountNotParsable( + ParsableAccount::AddressLookupTable, + )), + }) +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase", tag = "type", content = "info")] +pub enum LookupTableAccountType { + Uninitialized, + LookupTable(UiLookupTable), +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiLookupTable { + pub deactivation_slot: String, + pub last_extended_slot: String, + pub last_extended_slot_start_index: u8, + #[serde(skip_serializing_if = "Option::is_none")] + pub authority: Option, + pub addresses: Vec, +} + +impl<'a> From> for UiLookupTable { + fn from(address_lookup_table: AddressLookupTable) -> Self { + Self { + deactivation_slot: address_lookup_table.meta.deactivation_slot.to_string(), + last_extended_slot: address_lookup_table.meta.last_extended_slot.to_string(), + last_extended_slot_start_index: address_lookup_table + .meta + .last_extended_slot_start_index, + authority: address_lookup_table + .meta + .authority + .map(|authority| authority.to_string()), + addresses: address_lookup_table + .addresses + .iter() + .map(|address| address.to_string()) + .collect(), + } + } +} + +#[cfg(test)] +mod test { + use { + super::*, + solana_address_lookup_table_program::state::{LookupTableMeta, LOOKUP_TABLE_META_SIZE}, + solana_sdk::pubkey::Pubkey, + std::borrow::Cow, + }; + + #[test] + fn test_parse_address_lookup_table() { + let authority = Pubkey::new_unique(); + let deactivation_slot = 1; + let last_extended_slot = 2; + let last_extended_slot_start_index = 3; + let lookup_table_meta = LookupTableMeta { + deactivation_slot, + last_extended_slot, + last_extended_slot_start_index, + authority: Some(authority), + ..LookupTableMeta::default() + }; + let num_addresses = 42; + let mut addresses = Vec::with_capacity(num_addresses); + addresses.resize_with(num_addresses, Pubkey::new_unique); + let lookup_table = AddressLookupTable { + meta: lookup_table_meta, + addresses: Cow::Owned(addresses), + }; + let lookup_table_data = + AddressLookupTable::serialize_for_tests(lookup_table.clone()).unwrap(); + + let parsing_result = parse_address_lookup_table(&lookup_table_data).unwrap(); + if let LookupTableAccountType::LookupTable(ui_lookup_table) = parsing_result { + assert_eq!( + ui_lookup_table.deactivation_slot, + deactivation_slot.to_string() + ); + assert_eq!( + ui_lookup_table.last_extended_slot, + last_extended_slot.to_string() + ); + assert_eq!( + ui_lookup_table.last_extended_slot_start_index, + last_extended_slot_start_index + ); + assert_eq!(ui_lookup_table.authority, Some(authority.to_string())); + assert_eq!(ui_lookup_table.addresses.len(), num_addresses); + } + + assert_eq!( + parse_address_lookup_table(&[0u8; LOOKUP_TABLE_META_SIZE]).unwrap(), + LookupTableAccountType::Uninitialized + ); + assert!(parse_address_lookup_table(&[]).is_err()); + } +} diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 67a00edfd4b2f0..539ef7e503f9de 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -4019,6 +4019,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "solana-address-lookup-table-program", "solana-config-program", "solana-sdk 1.11.5", "solana-vote-program",