1#[cfg(not(target_os = "solana"))]
2use crate::{
3 address_lookup_table_account::AddressLookupTableAccount,
4 message::v0::{LoadedAddresses, MessageAddressTableLookup},
5};
6use {
7 crate::{instruction::Instruction, message::MessageHeader, pubkey::Pubkey},
8 std::collections::BTreeMap,
9 thiserror::Error,
10};
11
12#[derive(Default, Debug, Clone, PartialEq, Eq)]
14pub(crate) struct CompiledKeys {
15 payer: Option<Pubkey>,
16 key_meta_map: BTreeMap<Pubkey, CompiledKeyMeta>,
17}
18
19#[cfg_attr(target_os = "solana", allow(dead_code))]
20#[derive(PartialEq, Debug, Error, Eq, Clone)]
21pub enum CompileError {
22 #[error("account index overflowed during compilation")]
23 AccountIndexOverflow,
24 #[error("address lookup table index overflowed during compilation")]
25 AddressTableLookupIndexOverflow,
26 #[error("encountered unknown account key `{0}` during instruction compilation")]
27 UnknownInstructionKey(Pubkey),
28}
29
30#[derive(Default, Debug, Clone, PartialEq, Eq)]
31struct CompiledKeyMeta {
32 is_signer: bool,
33 is_writable: bool,
34 is_invoked: bool,
35}
36
37impl CompiledKeys {
38 pub(crate) fn compile(instructions: &[Instruction], payer: Option<Pubkey>) -> Self {
41 let mut key_meta_map = BTreeMap::<Pubkey, CompiledKeyMeta>::new();
42 for ix in instructions {
43 let mut meta = key_meta_map.entry(ix.program_id).or_default();
44 meta.is_invoked = true;
45 for account_meta in &ix.accounts {
46 let meta = key_meta_map.entry(account_meta.pubkey).or_default();
47 meta.is_signer |= account_meta.is_signer;
48 meta.is_writable |= account_meta.is_writable;
49 }
50 }
51 if let Some(payer) = &payer {
52 let mut meta = key_meta_map.entry(*payer).or_default();
53 meta.is_signer = true;
54 meta.is_writable = true;
55 }
56 Self {
57 payer,
58 key_meta_map,
59 }
60 }
61
62 pub(crate) fn try_into_message_components(
63 self,
64 ) -> Result<(MessageHeader, Vec<Pubkey>), CompileError> {
65 let try_into_u8 = |num: usize| -> Result<u8, CompileError> {
66 u8::try_from(num).map_err(|_| CompileError::AccountIndexOverflow)
67 };
68
69 let Self {
70 payer,
71 mut key_meta_map,
72 } = self;
73
74 if let Some(payer) = &payer {
75 key_meta_map.remove_entry(payer);
76 }
77
78 let writable_signer_keys: Vec<Pubkey> = payer
79 .into_iter()
80 .chain(
81 key_meta_map
82 .iter()
83 .filter_map(|(key, meta)| (meta.is_signer && meta.is_writable).then_some(*key)),
84 )
85 .collect();
86 let readonly_signer_keys: Vec<Pubkey> = key_meta_map
87 .iter()
88 .filter_map(|(key, meta)| (meta.is_signer && !meta.is_writable).then_some(*key))
89 .collect();
90 let writable_non_signer_keys: Vec<Pubkey> = key_meta_map
91 .iter()
92 .filter_map(|(key, meta)| (!meta.is_signer && meta.is_writable).then_some(*key))
93 .collect();
94 let readonly_non_signer_keys: Vec<Pubkey> = key_meta_map
95 .iter()
96 .filter_map(|(key, meta)| (!meta.is_signer && !meta.is_writable).then_some(*key))
97 .collect();
98
99 let signers_len = writable_signer_keys
100 .len()
101 .saturating_add(readonly_signer_keys.len());
102
103 let header = MessageHeader {
104 num_required_signatures: try_into_u8(signers_len)?,
105 num_readonly_signed_accounts: try_into_u8(readonly_signer_keys.len())?,
106 num_readonly_unsigned_accounts: try_into_u8(readonly_non_signer_keys.len())?,
107 };
108
109 let static_account_keys = std::iter::empty()
110 .chain(writable_signer_keys)
111 .chain(readonly_signer_keys)
112 .chain(writable_non_signer_keys)
113 .chain(readonly_non_signer_keys)
114 .collect();
115
116 Ok((header, static_account_keys))
117 }
118
119 #[cfg(not(target_os = "solana"))]
120 pub(crate) fn try_extract_table_lookup(
121 &mut self,
122 lookup_table_account: &AddressLookupTableAccount,
123 ) -> Result<Option<(MessageAddressTableLookup, LoadedAddresses)>, CompileError> {
124 let (writable_indexes, drained_writable_keys) = self
125 .try_drain_keys_found_in_lookup_table(&lookup_table_account.addresses, |meta| {
126 !meta.is_signer && !meta.is_invoked && meta.is_writable
127 })?;
128 let (readonly_indexes, drained_readonly_keys) = self
129 .try_drain_keys_found_in_lookup_table(&lookup_table_account.addresses, |meta| {
130 !meta.is_signer && !meta.is_invoked && !meta.is_writable
131 })?;
132
133 if writable_indexes.is_empty() && readonly_indexes.is_empty() {
135 return Ok(None);
136 }
137
138 Ok(Some((
139 MessageAddressTableLookup {
140 account_key: lookup_table_account.key,
141 writable_indexes,
142 readonly_indexes,
143 },
144 LoadedAddresses {
145 writable: drained_writable_keys,
146 readonly: drained_readonly_keys,
147 },
148 )))
149 }
150
151 #[cfg(not(target_os = "solana"))]
152 fn try_drain_keys_found_in_lookup_table(
153 &mut self,
154 lookup_table_addresses: &[Pubkey],
155 key_meta_filter: impl Fn(&CompiledKeyMeta) -> bool,
156 ) -> Result<(Vec<u8>, Vec<Pubkey>), CompileError> {
157 let mut lookup_table_indexes = Vec::new();
158 let mut drained_keys = Vec::new();
159
160 for search_key in self
161 .key_meta_map
162 .iter()
163 .filter_map(|(key, meta)| key_meta_filter(meta).then_some(key))
164 {
165 for (key_index, key) in lookup_table_addresses.iter().enumerate() {
166 if key == search_key {
167 let lookup_table_index = u8::try_from(key_index)
168 .map_err(|_| CompileError::AddressTableLookupIndexOverflow)?;
169
170 lookup_table_indexes.push(lookup_table_index);
171 drained_keys.push(*search_key);
172 break;
173 }
174 }
175 }
176
177 for key in &drained_keys {
178 self.key_meta_map.remove_entry(key);
179 }
180
181 Ok((lookup_table_indexes, drained_keys))
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use {super::*, crate::instruction::AccountMeta, bitflags::bitflags};
188
189 bitflags! {
190 pub struct KeyFlags: u8 {
191 const SIGNER = 0b00000001;
192 const WRITABLE = 0b00000010;
193 const INVOKED = 0b00000100;
194 }
195 }
196
197 impl From<KeyFlags> for CompiledKeyMeta {
198 fn from(flags: KeyFlags) -> Self {
199 Self {
200 is_signer: flags.contains(KeyFlags::SIGNER),
201 is_writable: flags.contains(KeyFlags::WRITABLE),
202 is_invoked: flags.contains(KeyFlags::INVOKED),
203 }
204 }
205 }
206
207 #[test]
208 fn test_compile_with_dups() {
209 let program_id0 = Pubkey::new_unique();
210 let program_id1 = Pubkey::new_unique();
211 let program_id2 = Pubkey::new_unique();
212 let program_id3 = Pubkey::new_unique();
213 let id0 = Pubkey::new_unique();
214 let id1 = Pubkey::new_unique();
215 let id2 = Pubkey::new_unique();
216 let id3 = Pubkey::new_unique();
217 let compiled_keys = CompiledKeys::compile(
218 &[
219 Instruction::new_with_bincode(
220 program_id0,
221 &0,
222 vec![
223 AccountMeta::new_readonly(id0, false),
224 AccountMeta::new_readonly(id1, true),
225 AccountMeta::new(id2, false),
226 AccountMeta::new(id3, true),
227 AccountMeta::new_readonly(id0, false),
229 AccountMeta::new_readonly(id1, true),
230 AccountMeta::new(id2, false),
231 AccountMeta::new(id3, true),
232 AccountMeta::new_readonly(program_id0, false),
234 AccountMeta::new_readonly(program_id1, true),
235 AccountMeta::new(program_id2, false),
236 AccountMeta::new(program_id3, true),
237 ],
238 ),
239 Instruction::new_with_bincode(program_id1, &0, vec![]),
240 Instruction::new_with_bincode(program_id2, &0, vec![]),
241 Instruction::new_with_bincode(program_id3, &0, vec![]),
242 ],
243 None,
244 );
245
246 assert_eq!(
247 compiled_keys,
248 CompiledKeys {
249 payer: None,
250 key_meta_map: BTreeMap::from([
251 (id0, KeyFlags::empty().into()),
252 (id1, KeyFlags::SIGNER.into()),
253 (id2, KeyFlags::WRITABLE.into()),
254 (id3, (KeyFlags::SIGNER | KeyFlags::WRITABLE).into()),
255 (program_id0, KeyFlags::INVOKED.into()),
256 (program_id1, (KeyFlags::INVOKED | KeyFlags::SIGNER).into()),
257 (program_id2, (KeyFlags::INVOKED | KeyFlags::WRITABLE).into()),
258 (program_id3, KeyFlags::all().into()),
259 ]),
260 }
261 );
262 }
263
264 #[test]
265 fn test_compile_with_dup_payer() {
266 let program_id = Pubkey::new_unique();
267 let payer = Pubkey::new_unique();
268 let compiled_keys = CompiledKeys::compile(
269 &[Instruction::new_with_bincode(
270 program_id,
271 &0,
272 vec![AccountMeta::new_readonly(payer, false)],
273 )],
274 Some(payer),
275 );
276 assert_eq!(
277 compiled_keys,
278 CompiledKeys {
279 payer: Some(payer),
280 key_meta_map: BTreeMap::from([
281 (payer, (KeyFlags::SIGNER | KeyFlags::WRITABLE).into()),
282 (program_id, KeyFlags::INVOKED.into()),
283 ]),
284 }
285 );
286 }
287
288 #[test]
289 fn test_compile_with_dup_signer_mismatch() {
290 let program_id = Pubkey::new_unique();
291 let id0 = Pubkey::new_unique();
292 let compiled_keys = CompiledKeys::compile(
293 &[Instruction::new_with_bincode(
294 program_id,
295 &0,
296 vec![AccountMeta::new(id0, false), AccountMeta::new(id0, true)],
297 )],
298 None,
299 );
300
301 assert_eq!(
303 compiled_keys,
304 CompiledKeys {
305 payer: None,
306 key_meta_map: BTreeMap::from([
307 (id0, (KeyFlags::SIGNER | KeyFlags::WRITABLE).into()),
308 (program_id, KeyFlags::INVOKED.into()),
309 ]),
310 }
311 );
312 }
313
314 #[test]
315 fn test_compile_with_dup_signer_writable_mismatch() {
316 let program_id = Pubkey::new_unique();
317 let id0 = Pubkey::new_unique();
318 let compiled_keys = CompiledKeys::compile(
319 &[Instruction::new_with_bincode(
320 program_id,
321 &0,
322 vec![
323 AccountMeta::new_readonly(id0, true),
324 AccountMeta::new(id0, true),
325 ],
326 )],
327 None,
328 );
329
330 assert_eq!(
332 compiled_keys,
333 CompiledKeys {
334 payer: None,
335 key_meta_map: BTreeMap::from([
336 (id0, (KeyFlags::SIGNER | KeyFlags::WRITABLE).into()),
337 (program_id, KeyFlags::INVOKED.into()),
338 ]),
339 }
340 );
341 }
342
343 #[test]
344 fn test_compile_with_dup_nonsigner_writable_mismatch() {
345 let program_id = Pubkey::new_unique();
346 let id0 = Pubkey::new_unique();
347 let compiled_keys = CompiledKeys::compile(
348 &[
349 Instruction::new_with_bincode(
350 program_id,
351 &0,
352 vec![
353 AccountMeta::new_readonly(id0, false),
354 AccountMeta::new(id0, false),
355 ],
356 ),
357 Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, false)]),
358 ],
359 None,
360 );
361
362 assert_eq!(
364 compiled_keys,
365 CompiledKeys {
366 payer: None,
367 key_meta_map: BTreeMap::from([
368 (id0, KeyFlags::WRITABLE.into()),
369 (program_id, KeyFlags::INVOKED.into()),
370 ]),
371 }
372 );
373 }
374
375 #[test]
376 fn test_try_into_message_components() {
377 let keys = vec![
378 Pubkey::new_unique(),
379 Pubkey::new_unique(),
380 Pubkey::new_unique(),
381 Pubkey::new_unique(),
382 ];
383
384 let compiled_keys = CompiledKeys {
385 payer: None,
386 key_meta_map: BTreeMap::from([
387 (keys[0], (KeyFlags::SIGNER | KeyFlags::WRITABLE).into()),
388 (keys[1], KeyFlags::SIGNER.into()),
389 (keys[2], KeyFlags::WRITABLE.into()),
390 (keys[3], KeyFlags::empty().into()),
391 ]),
392 };
393
394 let result = compiled_keys.try_into_message_components();
395 assert_eq!(result.as_ref().err(), None);
396 let (header, static_keys) = result.unwrap();
397
398 assert_eq!(static_keys, keys);
399 assert_eq!(
400 header,
401 MessageHeader {
402 num_required_signatures: 2,
403 num_readonly_signed_accounts: 1,
404 num_readonly_unsigned_accounts: 1,
405 }
406 );
407 }
408
409 #[test]
410 fn test_try_into_message_components_with_too_many_keys() {
411 const TOO_MANY_KEYS: usize = 257;
412
413 for key_flags in [
414 KeyFlags::WRITABLE | KeyFlags::SIGNER,
415 KeyFlags::SIGNER,
416 KeyFlags::empty(),
418 ] {
419 let test_keys = CompiledKeys {
420 payer: None,
421 key_meta_map: BTreeMap::from_iter(
422 (0..TOO_MANY_KEYS).map(|_| (Pubkey::new_unique(), key_flags.into())),
423 ),
424 };
425
426 assert_eq!(
427 test_keys.try_into_message_components(),
428 Err(CompileError::AccountIndexOverflow)
429 );
430 }
431 }
432
433 #[test]
434 fn test_try_extract_table_lookup() {
435 let keys = vec![
436 Pubkey::new_unique(),
437 Pubkey::new_unique(),
438 Pubkey::new_unique(),
439 Pubkey::new_unique(),
440 Pubkey::new_unique(),
441 Pubkey::new_unique(),
442 ];
443
444 let mut compiled_keys = CompiledKeys {
445 payer: None,
446 key_meta_map: BTreeMap::from([
447 (keys[0], (KeyFlags::SIGNER | KeyFlags::WRITABLE).into()),
448 (keys[1], KeyFlags::SIGNER.into()),
449 (keys[2], KeyFlags::WRITABLE.into()),
450 (keys[3], KeyFlags::empty().into()),
451 (keys[4], (KeyFlags::INVOKED | KeyFlags::WRITABLE).into()),
452 (keys[5], (KeyFlags::INVOKED).into()),
453 ]),
454 };
455
456 let addresses = [keys.clone(), keys.clone()].concat();
458 let lookup_table_account = AddressLookupTableAccount {
459 key: Pubkey::new_unique(),
460 addresses,
461 };
462
463 assert_eq!(
464 compiled_keys.try_extract_table_lookup(&lookup_table_account),
465 Ok(Some((
466 MessageAddressTableLookup {
467 account_key: lookup_table_account.key,
468 writable_indexes: vec![2],
469 readonly_indexes: vec![3],
470 },
471 LoadedAddresses {
472 writable: vec![keys[2]],
473 readonly: vec![keys[3]],
474 },
475 )))
476 );
477
478 assert_eq!(compiled_keys.key_meta_map.len(), 4);
479 assert!(!compiled_keys.key_meta_map.contains_key(&keys[2]));
480 assert!(!compiled_keys.key_meta_map.contains_key(&keys[3]));
481 }
482
483 #[test]
484 fn test_try_extract_table_lookup_returns_none() {
485 let mut compiled_keys = CompiledKeys {
486 payer: None,
487 key_meta_map: BTreeMap::from([
488 (Pubkey::new_unique(), KeyFlags::WRITABLE.into()),
489 (Pubkey::new_unique(), KeyFlags::empty().into()),
490 ]),
491 };
492
493 let lookup_table_account = AddressLookupTableAccount {
494 key: Pubkey::new_unique(),
495 addresses: vec![],
496 };
497
498 let expected_compiled_keys = compiled_keys.clone();
499 assert_eq!(
500 compiled_keys.try_extract_table_lookup(&lookup_table_account),
501 Ok(None)
502 );
503 assert_eq!(compiled_keys, expected_compiled_keys);
504 }
505
506 #[test]
507 fn test_try_extract_table_lookup_for_invalid_table() {
508 let writable_key = Pubkey::new_unique();
509 let mut compiled_keys = CompiledKeys {
510 payer: None,
511 key_meta_map: BTreeMap::from([
512 (writable_key, KeyFlags::WRITABLE.into()),
513 (Pubkey::new_unique(), KeyFlags::empty().into()),
514 ]),
515 };
516
517 const MAX_LENGTH_WITHOUT_OVERFLOW: usize = u8::MAX as usize + 1;
518 let mut addresses = vec![Pubkey::default(); MAX_LENGTH_WITHOUT_OVERFLOW];
519 addresses.push(writable_key);
520
521 let lookup_table_account = AddressLookupTableAccount {
522 key: Pubkey::new_unique(),
523 addresses,
524 };
525
526 let expected_compiled_keys = compiled_keys.clone();
527 assert_eq!(
528 compiled_keys.try_extract_table_lookup(&lookup_table_account),
529 Err(CompileError::AddressTableLookupIndexOverflow),
530 );
531 assert_eq!(compiled_keys, expected_compiled_keys);
532 }
533
534 #[test]
535 fn test_try_drain_keys_found_in_lookup_table() {
536 let orig_keys = vec![
537 Pubkey::new_unique(),
538 Pubkey::new_unique(),
539 Pubkey::new_unique(),
540 Pubkey::new_unique(),
541 Pubkey::new_unique(),
542 ];
543
544 let mut compiled_keys = CompiledKeys {
545 payer: None,
546 key_meta_map: BTreeMap::from([
547 (orig_keys[0], KeyFlags::empty().into()),
548 (orig_keys[1], KeyFlags::WRITABLE.into()),
549 (orig_keys[2], KeyFlags::WRITABLE.into()),
550 (orig_keys[3], KeyFlags::empty().into()),
551 (orig_keys[4], KeyFlags::empty().into()),
552 ]),
553 };
554
555 let lookup_table_addresses = vec![
556 Pubkey::new_unique(),
557 orig_keys[0],
558 Pubkey::new_unique(),
559 orig_keys[4],
560 Pubkey::new_unique(),
561 orig_keys[2],
562 Pubkey::new_unique(),
563 ];
564
565 let drain_result = compiled_keys
566 .try_drain_keys_found_in_lookup_table(&lookup_table_addresses, |meta| {
567 !meta.is_writable
568 });
569 assert_eq!(drain_result.as_ref().err(), None);
570 let (lookup_table_indexes, drained_keys) = drain_result.unwrap();
571
572 assert_eq!(
573 compiled_keys.key_meta_map.keys().collect::<Vec<&_>>(),
574 vec![&orig_keys[1], &orig_keys[2], &orig_keys[3]]
575 );
576 assert_eq!(drained_keys, vec![orig_keys[0], orig_keys[4]]);
577 assert_eq!(lookup_table_indexes, vec![1, 3]);
578 }
579
580 #[test]
581 fn test_try_drain_keys_found_in_lookup_table_with_empty_keys() {
582 let mut compiled_keys = CompiledKeys::default();
583
584 let lookup_table_addresses = vec![
585 Pubkey::new_unique(),
586 Pubkey::new_unique(),
587 Pubkey::new_unique(),
588 ];
589
590 let drain_result =
591 compiled_keys.try_drain_keys_found_in_lookup_table(&lookup_table_addresses, |_| true);
592 assert_eq!(drain_result.as_ref().err(), None);
593 let (lookup_table_indexes, drained_keys) = drain_result.unwrap();
594
595 assert!(drained_keys.is_empty());
596 assert!(lookup_table_indexes.is_empty());
597 }
598
599 #[test]
600 fn test_try_drain_keys_found_in_lookup_table_with_empty_table() {
601 let original_keys = vec![
602 Pubkey::new_unique(),
603 Pubkey::new_unique(),
604 Pubkey::new_unique(),
605 ];
606
607 let mut compiled_keys = CompiledKeys {
608 payer: None,
609 key_meta_map: BTreeMap::from_iter(
610 original_keys
611 .iter()
612 .map(|key| (*key, CompiledKeyMeta::default())),
613 ),
614 };
615
616 let lookup_table_addresses = vec![];
617
618 let drain_result =
619 compiled_keys.try_drain_keys_found_in_lookup_table(&lookup_table_addresses, |_| true);
620 assert_eq!(drain_result.as_ref().err(), None);
621 let (lookup_table_indexes, drained_keys) = drain_result.unwrap();
622
623 assert_eq!(compiled_keys.key_meta_map.len(), original_keys.len());
624 assert!(drained_keys.is_empty());
625 assert!(lookup_table_indexes.is_empty());
626 }
627
628 #[test]
629 fn test_try_drain_keys_found_in_lookup_table_with_too_many_addresses() {
630 let key = Pubkey::new_unique();
631 let mut compiled_keys = CompiledKeys {
632 payer: None,
633 key_meta_map: BTreeMap::from([(key, CompiledKeyMeta::default())]),
634 };
635
636 const MAX_LENGTH_WITHOUT_OVERFLOW: usize = u8::MAX as usize + 1;
637 let mut lookup_table_addresses = vec![Pubkey::default(); MAX_LENGTH_WITHOUT_OVERFLOW];
638 lookup_table_addresses.push(key);
639
640 let drain_result =
641 compiled_keys.try_drain_keys_found_in_lookup_table(&lookup_table_addresses, |_| true);
642 assert_eq!(
643 drain_result.err(),
644 Some(CompileError::AddressTableLookupIndexOverflow)
645 );
646 }
647}