Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit 9e9f778

Browse files
mergify[bot]Tyera Eulbergjstarry
authored
Parse address-lookup-table instructions (backport #27316) (#28135)
* Parse address-lookup-table instructions (#27316) * Parse address-lookup-table instructions * Finish extend instruction handling * Rename payer, recipient * Update docs parsing status (cherry picked from commit 62eebe6) # Conflicts: # docs/src/developing/clients/jsonrpc-api.md # transaction-status/Cargo.toml * resolve conflicts Co-authored-by: Tyera Eulberg <[email protected]> Co-authored-by: Justin Starry <[email protected]>
1 parent 02feb73 commit 9e9f778

File tree

7 files changed

+373
-1
lines changed

7 files changed

+373
-1
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/src/developing/clients/jsonrpc-api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ JSON parsing for the following native and SPL programs:
214214

215215
| Program | Account State | Instructions |
216216
| --- | --- | --- |
217-
| Address Lookup | v1.15.0 | v1.15.0 |
217+
| Address Lookup | v1.14.4 | v1.14.4 |
218218
| BPF Loader | n/a | stable |
219219
| BPF Upgradeable Loader | stable | stable |
220220
| Config | stable | |

programs/bpf/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

transaction-status/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ serde = "1.0.138"
2121
serde_derive = "1.0.103"
2222
serde_json = "1.0.81"
2323
solana-account-decoder = { path = "../account-decoder", version = "=1.14.4" }
24+
solana-address-lookup-table-program = { path = "../programs/address-lookup-table", version = "=1.14.4" }
2425
solana-measure = { path = "../measure", version = "=1.14.4" }
2526
solana-metrics = { path = "../metrics", version = "=1.14.4" }
2627
solana-sdk = { path = "../sdk", version = "=1.14.4" }

transaction-status/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ extern crate serde_derive;
3636
pub mod extract_memos;
3737
pub mod option_serializer;
3838
pub mod parse_accounts;
39+
pub mod parse_address_lookup_table;
3940
pub mod parse_associated_token;
4041
pub mod parse_bpf_loader;
4142
pub mod parse_instruction;
Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
use {
2+
crate::parse_instruction::{
3+
check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum,
4+
},
5+
bincode::deserialize,
6+
serde_json::json,
7+
solana_address_lookup_table_program::instruction::ProgramInstruction,
8+
solana_sdk::{instruction::CompiledInstruction, message::AccountKeys},
9+
};
10+
11+
pub fn parse_address_lookup_table(
12+
instruction: &CompiledInstruction,
13+
account_keys: &AccountKeys,
14+
) -> Result<ParsedInstructionEnum, ParseInstructionError> {
15+
let address_lookup_table_instruction: ProgramInstruction = deserialize(&instruction.data)
16+
.map_err(|_| {
17+
ParseInstructionError::InstructionNotParsable(ParsableProgram::AddressLookupTable)
18+
})?;
19+
match instruction.accounts.iter().max() {
20+
Some(index) if (*index as usize) < account_keys.len() => {}
21+
_ => {
22+
// Runtime should prevent this from ever happening
23+
return Err(ParseInstructionError::InstructionKeyMismatch(
24+
ParsableProgram::AddressLookupTable,
25+
));
26+
}
27+
}
28+
match address_lookup_table_instruction {
29+
ProgramInstruction::CreateLookupTable {
30+
recent_slot,
31+
bump_seed,
32+
} => {
33+
check_num_address_lookup_table_accounts(&instruction.accounts, 4)?;
34+
Ok(ParsedInstructionEnum {
35+
instruction_type: "createLookupTable".to_string(),
36+
info: json!({
37+
"lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
38+
"lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
39+
"payerAccount": account_keys[instruction.accounts[2] as usize].to_string(),
40+
"systemProgram": account_keys[instruction.accounts[3] as usize].to_string(),
41+
"recentSlot": recent_slot,
42+
"bumpSeed": bump_seed,
43+
}),
44+
})
45+
}
46+
ProgramInstruction::FreezeLookupTable => {
47+
check_num_address_lookup_table_accounts(&instruction.accounts, 2)?;
48+
Ok(ParsedInstructionEnum {
49+
instruction_type: "freezeLookupTable".to_string(),
50+
info: json!({
51+
"lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
52+
"lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
53+
}),
54+
})
55+
}
56+
ProgramInstruction::ExtendLookupTable { new_addresses } => {
57+
check_num_address_lookup_table_accounts(&instruction.accounts, 2)?;
58+
let new_addresses: Vec<String> = new_addresses
59+
.into_iter()
60+
.map(|address| address.to_string())
61+
.collect();
62+
let mut value = json!({
63+
"lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
64+
"lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
65+
"newAddresses": new_addresses,
66+
});
67+
let map = value.as_object_mut().unwrap();
68+
if instruction.accounts.len() >= 4 {
69+
map.insert(
70+
"payerAccount".to_string(),
71+
json!(account_keys[instruction.accounts[2] as usize].to_string()),
72+
);
73+
map.insert(
74+
"systemProgram".to_string(),
75+
json!(account_keys[instruction.accounts[3] as usize].to_string()),
76+
);
77+
}
78+
Ok(ParsedInstructionEnum {
79+
instruction_type: "extendLookupTable".to_string(),
80+
info: value,
81+
})
82+
}
83+
ProgramInstruction::DeactivateLookupTable => {
84+
check_num_address_lookup_table_accounts(&instruction.accounts, 2)?;
85+
Ok(ParsedInstructionEnum {
86+
instruction_type: "deactivateLookupTable".to_string(),
87+
info: json!({
88+
"lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
89+
"lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
90+
}),
91+
})
92+
}
93+
ProgramInstruction::CloseLookupTable => {
94+
check_num_address_lookup_table_accounts(&instruction.accounts, 3)?;
95+
Ok(ParsedInstructionEnum {
96+
instruction_type: "closeLookupTable".to_string(),
97+
info: json!({
98+
"lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
99+
"lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
100+
"recipient": account_keys[instruction.accounts[2] as usize].to_string(),
101+
}),
102+
})
103+
}
104+
}
105+
}
106+
107+
fn check_num_address_lookup_table_accounts(
108+
accounts: &[u8],
109+
num: usize,
110+
) -> Result<(), ParseInstructionError> {
111+
check_num_accounts(accounts, num, ParsableProgram::AddressLookupTable)
112+
}
113+
114+
#[cfg(test)]
115+
mod test {
116+
use {
117+
super::*,
118+
solana_address_lookup_table_program::instruction,
119+
solana_sdk::{message::Message, pubkey::Pubkey, system_program},
120+
std::str::FromStr,
121+
};
122+
123+
#[test]
124+
fn test_parse_create_address_lookup_table_ix() {
125+
let from_pubkey = Pubkey::new_unique();
126+
// use explicit key to have predicatble bump_seed
127+
let authority = Pubkey::from_str("HkxY6vXdrKzoCQLmdJ3cYo9534FdZQxzBNWTyrJzzqJM").unwrap();
128+
let slot = 42;
129+
130+
let (instruction, lookup_table_pubkey) =
131+
instruction::create_lookup_table(authority, from_pubkey, slot);
132+
let mut message = Message::new(&[instruction], None);
133+
assert_eq!(
134+
parse_address_lookup_table(
135+
&message.instructions[0],
136+
&AccountKeys::new(&message.account_keys, None)
137+
)
138+
.unwrap(),
139+
ParsedInstructionEnum {
140+
instruction_type: "createLookupTable".to_string(),
141+
info: json!({
142+
"lookupTableAccount": lookup_table_pubkey.to_string(),
143+
"lookupTableAuthority": authority.to_string(),
144+
"payerAccount": from_pubkey.to_string(),
145+
"systemProgram": system_program::id().to_string(),
146+
"recentSlot": slot,
147+
"bumpSeed": 254,
148+
}),
149+
}
150+
);
151+
assert!(parse_address_lookup_table(
152+
&message.instructions[0],
153+
&AccountKeys::new(&message.account_keys[0..3], None)
154+
)
155+
.is_err());
156+
let keys = message.account_keys.clone();
157+
message.instructions[0].accounts.pop();
158+
assert!(parse_address_lookup_table(
159+
&message.instructions[0],
160+
&AccountKeys::new(&keys, None)
161+
)
162+
.is_err());
163+
}
164+
165+
#[test]
166+
fn test_parse_freeze_lookup_table_ix() {
167+
let lookup_table_pubkey = Pubkey::new_unique();
168+
let authority = Pubkey::new_unique();
169+
170+
let instruction = instruction::freeze_lookup_table(lookup_table_pubkey, authority);
171+
let mut message = Message::new(&[instruction], None);
172+
assert_eq!(
173+
parse_address_lookup_table(
174+
&message.instructions[0],
175+
&AccountKeys::new(&message.account_keys, None)
176+
)
177+
.unwrap(),
178+
ParsedInstructionEnum {
179+
instruction_type: "freezeLookupTable".to_string(),
180+
info: json!({
181+
"lookupTableAccount": lookup_table_pubkey.to_string(),
182+
"lookupTableAuthority": authority.to_string(),
183+
}),
184+
}
185+
);
186+
assert!(parse_address_lookup_table(
187+
&message.instructions[0],
188+
&AccountKeys::new(&message.account_keys[0..1], None)
189+
)
190+
.is_err());
191+
let keys = message.account_keys.clone();
192+
message.instructions[0].accounts.pop();
193+
assert!(parse_address_lookup_table(
194+
&message.instructions[0],
195+
&AccountKeys::new(&keys, None)
196+
)
197+
.is_err());
198+
}
199+
200+
#[test]
201+
fn test_parse_extend_lookup_table_ix() {
202+
let lookup_table_pubkey = Pubkey::new_unique();
203+
let authority = Pubkey::new_unique();
204+
let from_pubkey = Pubkey::new_unique();
205+
let no_addresses = vec![];
206+
let address0 = Pubkey::new_unique();
207+
let address1 = Pubkey::new_unique();
208+
let some_addresses = vec![address0, address1];
209+
210+
// No payer, no addresses
211+
let instruction =
212+
instruction::extend_lookup_table(lookup_table_pubkey, authority, None, no_addresses);
213+
let mut message = Message::new(&[instruction], None);
214+
assert_eq!(
215+
parse_address_lookup_table(
216+
&message.instructions[0],
217+
&AccountKeys::new(&message.account_keys, None)
218+
)
219+
.unwrap(),
220+
ParsedInstructionEnum {
221+
instruction_type: "extendLookupTable".to_string(),
222+
info: json!({
223+
"lookupTableAccount": lookup_table_pubkey.to_string(),
224+
"lookupTableAuthority": authority.to_string(),
225+
"newAddresses": [],
226+
}),
227+
}
228+
);
229+
assert!(parse_address_lookup_table(
230+
&message.instructions[0],
231+
&AccountKeys::new(&message.account_keys[0..1], None)
232+
)
233+
.is_err());
234+
let keys = message.account_keys.clone();
235+
message.instructions[0].accounts.pop();
236+
assert!(parse_address_lookup_table(
237+
&message.instructions[0],
238+
&AccountKeys::new(&keys, None)
239+
)
240+
.is_err());
241+
242+
// Some payer, some addresses
243+
let instruction = instruction::extend_lookup_table(
244+
lookup_table_pubkey,
245+
authority,
246+
Some(from_pubkey),
247+
some_addresses,
248+
);
249+
let mut message = Message::new(&[instruction], None);
250+
assert_eq!(
251+
parse_address_lookup_table(
252+
&message.instructions[0],
253+
&AccountKeys::new(&message.account_keys, None)
254+
)
255+
.unwrap(),
256+
ParsedInstructionEnum {
257+
instruction_type: "extendLookupTable".to_string(),
258+
info: json!({
259+
"lookupTableAccount": lookup_table_pubkey.to_string(),
260+
"lookupTableAuthority": authority.to_string(),
261+
"payerAccount": from_pubkey.to_string(),
262+
"systemProgram": system_program::id().to_string(),
263+
"newAddresses": [
264+
address0.to_string(),
265+
address1.to_string(),
266+
],
267+
}),
268+
}
269+
);
270+
assert!(parse_address_lookup_table(
271+
&message.instructions[0],
272+
&AccountKeys::new(&message.account_keys[0..1], None)
273+
)
274+
.is_err());
275+
let keys = message.account_keys.clone();
276+
message.instructions[0].accounts.pop();
277+
message.instructions[0].accounts.pop();
278+
message.instructions[0].accounts.pop();
279+
assert!(parse_address_lookup_table(
280+
&message.instructions[0],
281+
&AccountKeys::new(&keys, None)
282+
)
283+
.is_err());
284+
}
285+
286+
#[test]
287+
fn test_parse_deactivate_lookup_table_ix() {
288+
let lookup_table_pubkey = Pubkey::new_unique();
289+
let authority = Pubkey::new_unique();
290+
291+
let instruction = instruction::deactivate_lookup_table(lookup_table_pubkey, authority);
292+
let mut message = Message::new(&[instruction], None);
293+
assert_eq!(
294+
parse_address_lookup_table(
295+
&message.instructions[0],
296+
&AccountKeys::new(&message.account_keys, None)
297+
)
298+
.unwrap(),
299+
ParsedInstructionEnum {
300+
instruction_type: "deactivateLookupTable".to_string(),
301+
info: json!({
302+
"lookupTableAccount": lookup_table_pubkey.to_string(),
303+
"lookupTableAuthority": authority.to_string(),
304+
}),
305+
}
306+
);
307+
assert!(parse_address_lookup_table(
308+
&message.instructions[0],
309+
&AccountKeys::new(&message.account_keys[0..1], None)
310+
)
311+
.is_err());
312+
let keys = message.account_keys.clone();
313+
message.instructions[0].accounts.pop();
314+
assert!(parse_address_lookup_table(
315+
&message.instructions[0],
316+
&AccountKeys::new(&keys, None)
317+
)
318+
.is_err());
319+
}
320+
321+
#[test]
322+
fn test_parse_close_lookup_table_ix() {
323+
let lookup_table_pubkey = Pubkey::new_unique();
324+
let authority = Pubkey::new_unique();
325+
let recipient = Pubkey::new_unique();
326+
327+
let instruction =
328+
instruction::close_lookup_table(lookup_table_pubkey, authority, recipient);
329+
let mut message = Message::new(&[instruction], None);
330+
assert_eq!(
331+
parse_address_lookup_table(
332+
&message.instructions[0],
333+
&AccountKeys::new(&message.account_keys, None)
334+
)
335+
.unwrap(),
336+
ParsedInstructionEnum {
337+
instruction_type: "closeLookupTable".to_string(),
338+
info: json!({
339+
"lookupTableAccount": lookup_table_pubkey.to_string(),
340+
"lookupTableAuthority": authority.to_string(),
341+
"recipient": recipient.to_string(),
342+
}),
343+
}
344+
);
345+
assert!(parse_address_lookup_table(
346+
&message.instructions[0],
347+
&AccountKeys::new(&message.account_keys[0..2], None)
348+
)
349+
.is_err());
350+
let keys = message.account_keys.clone();
351+
message.instructions[0].accounts.pop();
352+
assert!(parse_address_lookup_table(
353+
&message.instructions[0],
354+
&AccountKeys::new(&keys, None)
355+
)
356+
.is_err());
357+
}
358+
}

0 commit comments

Comments
 (0)