1pub use loaded::*;
13use {
14 crate::{
15 address_lookup_table::AddressLookupTableAccount,
16 bpf_loader_upgradeable,
17 hash::Hash,
18 instruction::{CompiledInstruction, Instruction},
19 message::{
20 compiled_keys::{CompileError, CompiledKeys},
21 AccountKeys, MessageHeader, MESSAGE_VERSION_PREFIX,
22 },
23 pubkey::Pubkey,
24 },
25 solana_sanitize::SanitizeError,
26 solana_short_vec as short_vec,
27 std::collections::HashSet,
28};
29
30mod loaded;
31
32#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
35#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
36#[serde(rename_all = "camelCase")]
37pub struct MessageAddressTableLookup {
38 pub account_key: Pubkey,
40 #[serde(with = "short_vec")]
42 pub writable_indexes: Vec<u8>,
43 #[serde(with = "short_vec")]
45 pub readonly_indexes: Vec<u8>,
46}
47
48#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
57#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
58#[serde(rename_all = "camelCase")]
59pub struct Message {
60 pub header: MessageHeader,
64
65 #[serde(with = "short_vec")]
67 pub account_keys: Vec<Pubkey>,
68
69 pub recent_blockhash: Hash,
71
72 #[serde(with = "short_vec")]
86 pub instructions: Vec<CompiledInstruction>,
87
88 #[serde(with = "short_vec")]
91 pub address_table_lookups: Vec<MessageAddressTableLookup>,
92}
93
94impl Message {
95 pub fn sanitize(&self) -> Result<(), SanitizeError> {
97 let num_static_account_keys = self.account_keys.len();
98 if usize::from(self.header.num_required_signatures)
99 .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts))
100 > num_static_account_keys
101 {
102 return Err(SanitizeError::IndexOutOfBounds);
103 }
104
105 if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
107 return Err(SanitizeError::InvalidValue);
108 }
109
110 let num_dynamic_account_keys = {
111 let mut total_lookup_keys: usize = 0;
112 for lookup in &self.address_table_lookups {
113 let num_lookup_indexes = lookup
114 .writable_indexes
115 .len()
116 .saturating_add(lookup.readonly_indexes.len());
117
118 if num_lookup_indexes == 0 {
120 return Err(SanitizeError::InvalidValue);
121 }
122
123 total_lookup_keys = total_lookup_keys.saturating_add(num_lookup_indexes);
124 }
125 total_lookup_keys
126 };
127
128 if num_static_account_keys == 0 {
132 return Err(SanitizeError::InvalidValue);
133 }
134
135 let total_account_keys = num_static_account_keys.saturating_add(num_dynamic_account_keys);
140 if total_account_keys > 256 {
141 return Err(SanitizeError::IndexOutOfBounds);
142 }
143
144 let max_account_ix = total_account_keys
147 .checked_sub(1)
148 .expect("message doesn't contain any account keys");
149
150 let max_program_id_ix =
154 num_static_account_keys
157 .checked_sub(1)
158 .expect("message doesn't contain any static account keys");
159
160 for ci in &self.instructions {
161 if usize::from(ci.program_id_index) > max_program_id_ix {
162 return Err(SanitizeError::IndexOutOfBounds);
163 }
164 if ci.program_id_index == 0 {
166 return Err(SanitizeError::IndexOutOfBounds);
167 }
168 for ai in &ci.accounts {
169 if usize::from(*ai) > max_account_ix {
170 return Err(SanitizeError::IndexOutOfBounds);
171 }
172 }
173 }
174
175 Ok(())
176 }
177}
178
179impl Message {
180 pub fn try_compile(
259 payer: &Pubkey,
260 instructions: &[Instruction],
261 address_lookup_table_accounts: &[AddressLookupTableAccount],
262 recent_blockhash: Hash,
263 ) -> Result<Self, CompileError> {
264 let mut compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
265
266 let mut address_table_lookups = Vec::with_capacity(address_lookup_table_accounts.len());
267 let mut loaded_addresses_list = Vec::with_capacity(address_lookup_table_accounts.len());
268 for lookup_table_account in address_lookup_table_accounts {
269 if let Some((lookup, loaded_addresses)) =
270 compiled_keys.try_extract_table_lookup(lookup_table_account)?
271 {
272 address_table_lookups.push(lookup);
273 loaded_addresses_list.push(loaded_addresses);
274 }
275 }
276
277 let (header, static_keys) = compiled_keys.try_into_message_components()?;
278 let dynamic_keys = loaded_addresses_list.into_iter().collect();
279 let account_keys = AccountKeys::new(&static_keys, Some(&dynamic_keys));
280 let instructions = account_keys.try_compile_instructions(instructions)?;
281
282 Ok(Self {
283 header,
284 account_keys: static_keys,
285 recent_blockhash,
286 instructions,
287 address_table_lookups,
288 })
289 }
290
291 pub fn serialize(&self) -> Vec<u8> {
293 bincode::serialize(&(MESSAGE_VERSION_PREFIX, self)).unwrap()
294 }
295
296 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
298 if let Ok(key_index) = u8::try_from(key_index) {
299 self.instructions
300 .iter()
301 .any(|ix| ix.program_id_index == key_index)
302 } else {
303 false
304 }
305 }
306
307 fn is_writable_index(&self, key_index: usize) -> bool {
310 let header = &self.header;
311 let num_account_keys = self.account_keys.len();
312 let num_signed_accounts = usize::from(header.num_required_signatures);
313 if key_index >= num_account_keys {
314 let loaded_addresses_index = key_index.saturating_sub(num_account_keys);
315 let num_writable_dynamic_addresses = self
316 .address_table_lookups
317 .iter()
318 .map(|lookup| lookup.writable_indexes.len())
319 .sum();
320 loaded_addresses_index < num_writable_dynamic_addresses
321 } else if key_index >= num_signed_accounts {
322 let num_unsigned_accounts = num_account_keys.saturating_sub(num_signed_accounts);
323 let num_writable_unsigned_accounts = num_unsigned_accounts
324 .saturating_sub(usize::from(header.num_readonly_unsigned_accounts));
325 let unsigned_account_index = key_index.saturating_sub(num_signed_accounts);
326 unsigned_account_index < num_writable_unsigned_accounts
327 } else {
328 let num_writable_signed_accounts = num_signed_accounts
329 .saturating_sub(usize::from(header.num_readonly_signed_accounts));
330 key_index < num_writable_signed_accounts
331 }
332 }
333
334 fn is_upgradeable_loader_in_static_keys(&self) -> bool {
336 self.account_keys
337 .iter()
338 .any(|&key| key == bpf_loader_upgradeable::id())
339 }
340
341 pub fn is_maybe_writable(
347 &self,
348 key_index: usize,
349 reserved_account_keys: Option<&HashSet<Pubkey>>,
350 ) -> bool {
351 self.is_writable_index(key_index)
352 && !self.is_account_maybe_reserved(key_index, reserved_account_keys)
353 && !{
354 self.is_key_called_as_program(key_index)
356 && !self.is_upgradeable_loader_in_static_keys()
357 }
358 }
359
360 fn is_account_maybe_reserved(
364 &self,
365 key_index: usize,
366 reserved_account_keys: Option<&HashSet<Pubkey>>,
367 ) -> bool {
368 let mut is_maybe_reserved = false;
369 if let Some(reserved_account_keys) = reserved_account_keys {
370 if let Some(key) = self.account_keys.get(key_index) {
371 is_maybe_reserved = reserved_account_keys.contains(key);
372 }
373 }
374 is_maybe_reserved
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use {
381 super::*,
382 crate::{instruction::AccountMeta, message::VersionedMessage},
383 };
384
385 #[test]
386 fn test_sanitize() {
387 assert!(Message {
388 header: MessageHeader {
389 num_required_signatures: 1,
390 ..MessageHeader::default()
391 },
392 account_keys: vec![Pubkey::new_unique()],
393 ..Message::default()
394 }
395 .sanitize()
396 .is_ok());
397 }
398
399 #[test]
400 fn test_sanitize_with_instruction() {
401 assert!(Message {
402 header: MessageHeader {
403 num_required_signatures: 1,
404 ..MessageHeader::default()
405 },
406 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
407 instructions: vec![CompiledInstruction {
408 program_id_index: 1,
409 accounts: vec![0],
410 data: vec![]
411 }],
412 ..Message::default()
413 }
414 .sanitize()
415 .is_ok());
416 }
417
418 #[test]
419 fn test_sanitize_with_table_lookup() {
420 assert!(Message {
421 header: MessageHeader {
422 num_required_signatures: 1,
423 ..MessageHeader::default()
424 },
425 account_keys: vec![Pubkey::new_unique()],
426 address_table_lookups: vec![MessageAddressTableLookup {
427 account_key: Pubkey::new_unique(),
428 writable_indexes: vec![1, 2, 3],
429 readonly_indexes: vec![0],
430 }],
431 ..Message::default()
432 }
433 .sanitize()
434 .is_ok());
435 }
436
437 #[test]
438 fn test_sanitize_with_table_lookup_and_ix_with_dynamic_program_id() {
439 let message = Message {
440 header: MessageHeader {
441 num_required_signatures: 1,
442 ..MessageHeader::default()
443 },
444 account_keys: vec![Pubkey::new_unique()],
445 address_table_lookups: vec![MessageAddressTableLookup {
446 account_key: Pubkey::new_unique(),
447 writable_indexes: vec![1, 2, 3],
448 readonly_indexes: vec![0],
449 }],
450 instructions: vec![CompiledInstruction {
451 program_id_index: 4,
452 accounts: vec![0, 1, 2, 3],
453 data: vec![],
454 }],
455 ..Message::default()
456 };
457
458 assert!(message.sanitize().is_err());
459 }
460
461 #[test]
462 fn test_sanitize_with_table_lookup_and_ix_with_static_program_id() {
463 assert!(Message {
464 header: MessageHeader {
465 num_required_signatures: 1,
466 ..MessageHeader::default()
467 },
468 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
469 address_table_lookups: vec![MessageAddressTableLookup {
470 account_key: Pubkey::new_unique(),
471 writable_indexes: vec![1, 2, 3],
472 readonly_indexes: vec![0],
473 }],
474 instructions: vec![CompiledInstruction {
475 program_id_index: 1,
476 accounts: vec![2, 3, 4, 5],
477 data: vec![]
478 }],
479 ..Message::default()
480 }
481 .sanitize()
482 .is_ok());
483 }
484
485 #[test]
486 fn test_sanitize_without_signer() {
487 assert!(Message {
488 header: MessageHeader::default(),
489 account_keys: vec![Pubkey::new_unique()],
490 ..Message::default()
491 }
492 .sanitize()
493 .is_err());
494 }
495
496 #[test]
497 fn test_sanitize_without_writable_signer() {
498 assert!(Message {
499 header: MessageHeader {
500 num_required_signatures: 1,
501 num_readonly_signed_accounts: 1,
502 ..MessageHeader::default()
503 },
504 account_keys: vec![Pubkey::new_unique()],
505 ..Message::default()
506 }
507 .sanitize()
508 .is_err());
509 }
510
511 #[test]
512 fn test_sanitize_with_empty_table_lookup() {
513 assert!(Message {
514 header: MessageHeader {
515 num_required_signatures: 1,
516 ..MessageHeader::default()
517 },
518 account_keys: vec![Pubkey::new_unique()],
519 address_table_lookups: vec![MessageAddressTableLookup {
520 account_key: Pubkey::new_unique(),
521 writable_indexes: vec![],
522 readonly_indexes: vec![],
523 }],
524 ..Message::default()
525 }
526 .sanitize()
527 .is_err());
528 }
529
530 #[test]
531 fn test_sanitize_with_max_account_keys() {
532 assert!(Message {
533 header: MessageHeader {
534 num_required_signatures: 1,
535 ..MessageHeader::default()
536 },
537 account_keys: (0..=u8::MAX).map(|_| Pubkey::new_unique()).collect(),
538 ..Message::default()
539 }
540 .sanitize()
541 .is_ok());
542 }
543
544 #[test]
545 fn test_sanitize_with_too_many_account_keys() {
546 assert!(Message {
547 header: MessageHeader {
548 num_required_signatures: 1,
549 ..MessageHeader::default()
550 },
551 account_keys: (0..=256).map(|_| Pubkey::new_unique()).collect(),
552 ..Message::default()
553 }
554 .sanitize()
555 .is_err());
556 }
557
558 #[test]
559 fn test_sanitize_with_max_table_loaded_keys() {
560 assert!(Message {
561 header: MessageHeader {
562 num_required_signatures: 1,
563 ..MessageHeader::default()
564 },
565 account_keys: vec![Pubkey::new_unique()],
566 address_table_lookups: vec![MessageAddressTableLookup {
567 account_key: Pubkey::new_unique(),
568 writable_indexes: (0..=254).step_by(2).collect(),
569 readonly_indexes: (1..=254).step_by(2).collect(),
570 }],
571 ..Message::default()
572 }
573 .sanitize()
574 .is_ok());
575 }
576
577 #[test]
578 fn test_sanitize_with_too_many_table_loaded_keys() {
579 assert!(Message {
580 header: MessageHeader {
581 num_required_signatures: 1,
582 ..MessageHeader::default()
583 },
584 account_keys: vec![Pubkey::new_unique()],
585 address_table_lookups: vec![MessageAddressTableLookup {
586 account_key: Pubkey::new_unique(),
587 writable_indexes: (0..=255).step_by(2).collect(),
588 readonly_indexes: (1..=255).step_by(2).collect(),
589 }],
590 ..Message::default()
591 }
592 .sanitize()
593 .is_err());
594 }
595
596 #[test]
597 fn test_sanitize_with_invalid_ix_program_id() {
598 let message = Message {
599 header: MessageHeader {
600 num_required_signatures: 1,
601 ..MessageHeader::default()
602 },
603 account_keys: vec![Pubkey::new_unique()],
604 address_table_lookups: vec![MessageAddressTableLookup {
605 account_key: Pubkey::new_unique(),
606 writable_indexes: vec![0],
607 readonly_indexes: vec![],
608 }],
609 instructions: vec![CompiledInstruction {
610 program_id_index: 2,
611 accounts: vec![],
612 data: vec![],
613 }],
614 ..Message::default()
615 };
616
617 assert!(message.sanitize().is_err());
618 }
619
620 #[test]
621 fn test_sanitize_with_invalid_ix_account() {
622 assert!(Message {
623 header: MessageHeader {
624 num_required_signatures: 1,
625 ..MessageHeader::default()
626 },
627 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
628 address_table_lookups: vec![MessageAddressTableLookup {
629 account_key: Pubkey::new_unique(),
630 writable_indexes: vec![],
631 readonly_indexes: vec![0],
632 }],
633 instructions: vec![CompiledInstruction {
634 program_id_index: 1,
635 accounts: vec![3],
636 data: vec![]
637 }],
638 ..Message::default()
639 }
640 .sanitize()
641 .is_err());
642 }
643
644 #[test]
645 fn test_serialize() {
646 let message = Message::default();
647 let versioned_msg = VersionedMessage::V0(message.clone());
648 assert_eq!(message.serialize(), versioned_msg.serialize());
649 }
650
651 #[test]
652 fn test_try_compile() {
653 let mut keys = vec![];
654 keys.resize_with(7, Pubkey::new_unique);
655
656 let payer = keys[0];
657 let program_id = keys[6];
658 let instructions = vec![Instruction {
659 program_id,
660 accounts: vec![
661 AccountMeta::new(keys[1], true),
662 AccountMeta::new_readonly(keys[2], true),
663 AccountMeta::new(keys[3], false),
664 AccountMeta::new(keys[4], false), AccountMeta::new_readonly(keys[5], false), ],
667 data: vec![],
668 }];
669 let address_lookup_table_accounts = vec![
670 AddressLookupTableAccount {
671 key: Pubkey::new_unique(),
672 addresses: vec![keys[4], keys[5], keys[6]],
673 },
674 AddressLookupTableAccount {
675 key: Pubkey::new_unique(),
676 addresses: vec![],
677 },
678 ];
679
680 let recent_blockhash = Hash::new_unique();
681 assert_eq!(
682 Message::try_compile(
683 &payer,
684 &instructions,
685 &address_lookup_table_accounts,
686 recent_blockhash
687 ),
688 Ok(Message {
689 header: MessageHeader {
690 num_required_signatures: 3,
691 num_readonly_signed_accounts: 1,
692 num_readonly_unsigned_accounts: 1
693 },
694 recent_blockhash,
695 account_keys: vec![keys[0], keys[1], keys[2], keys[3], program_id],
696 instructions: vec![CompiledInstruction {
697 program_id_index: 4,
698 accounts: vec![1, 2, 3, 5, 6],
699 data: vec![],
700 },],
701 address_table_lookups: vec![MessageAddressTableLookup {
702 account_key: address_lookup_table_accounts[0].key,
703 writable_indexes: vec![0],
704 readonly_indexes: vec![1],
705 }],
706 })
707 );
708 }
709
710 #[test]
711 fn test_is_maybe_writable() {
712 let key0 = Pubkey::new_unique();
713 let key1 = Pubkey::new_unique();
714 let key2 = Pubkey::new_unique();
715 let key3 = Pubkey::new_unique();
716 let key4 = Pubkey::new_unique();
717 let key5 = Pubkey::new_unique();
718
719 let message = Message {
720 header: MessageHeader {
721 num_required_signatures: 3,
722 num_readonly_signed_accounts: 2,
723 num_readonly_unsigned_accounts: 1,
724 },
725 account_keys: vec![key0, key1, key2, key3, key4, key5],
726 address_table_lookups: vec![MessageAddressTableLookup {
727 account_key: Pubkey::new_unique(),
728 writable_indexes: vec![0],
729 readonly_indexes: vec![1],
730 }],
731 ..Message::default()
732 };
733
734 let reserved_account_keys = HashSet::from([key3]);
735
736 assert!(message.is_maybe_writable(0, Some(&reserved_account_keys)));
737 assert!(!message.is_maybe_writable(1, Some(&reserved_account_keys)));
738 assert!(!message.is_maybe_writable(2, Some(&reserved_account_keys)));
739 assert!(!message.is_maybe_writable(3, Some(&reserved_account_keys)));
740 assert!(message.is_maybe_writable(3, None));
741 assert!(message.is_maybe_writable(4, Some(&reserved_account_keys)));
742 assert!(!message.is_maybe_writable(5, Some(&reserved_account_keys)));
743 assert!(message.is_maybe_writable(6, Some(&reserved_account_keys)));
744 assert!(!message.is_maybe_writable(7, Some(&reserved_account_keys)));
745 assert!(!message.is_maybe_writable(8, Some(&reserved_account_keys)));
746 }
747
748 #[test]
749 fn test_is_account_maybe_reserved() {
750 let key0 = Pubkey::new_unique();
751 let key1 = Pubkey::new_unique();
752
753 let message = Message {
754 account_keys: vec![key0, key1],
755 address_table_lookups: vec![MessageAddressTableLookup {
756 account_key: Pubkey::new_unique(),
757 writable_indexes: vec![0],
758 readonly_indexes: vec![1],
759 }],
760 ..Message::default()
761 };
762
763 let reserved_account_keys = HashSet::from([key1]);
764
765 assert!(!message.is_account_maybe_reserved(0, Some(&reserved_account_keys)));
766 assert!(message.is_account_maybe_reserved(1, Some(&reserved_account_keys)));
767 assert!(!message.is_account_maybe_reserved(2, Some(&reserved_account_keys)));
768 assert!(!message.is_account_maybe_reserved(3, Some(&reserved_account_keys)));
769 assert!(!message.is_account_maybe_reserved(4, Some(&reserved_account_keys)));
770 assert!(!message.is_account_maybe_reserved(0, None));
771 assert!(!message.is_account_maybe_reserved(1, None));
772 assert!(!message.is_account_maybe_reserved(2, None));
773 assert!(!message.is_account_maybe_reserved(3, None));
774 assert!(!message.is_account_maybe_reserved(4, None));
775 }
776}