1pub use loaded::*;
13#[cfg(feature = "serde")]
14use serde_derive::{Deserialize, Serialize};
15#[cfg(feature = "frozen-abi")]
16use solana_frozen_abi_macro::{frozen_abi, AbiExample, StableAbi, StableAbiSample};
17#[cfg(feature = "std")]
18use std::collections::HashSet;
19use {
20 crate::{
21 compiled_instruction::CompiledInstruction,
22 compiled_keys::{CompileError, CompiledKeys},
23 AccountKeys, AddressLookupTableAccount, AddressSet, MessageHeader,
24 },
25 alloc::vec::Vec,
26 solana_address::Address,
27 solana_hash::Hash,
28 solana_instruction::Instruction,
29 solana_sanitize::SanitizeError,
30 solana_sdk_ids::bpf_loader_upgradeable,
31};
32#[cfg(feature = "wincode")]
33use {
34 solana_short_vec::ShortU16,
35 wincode::{containers, SchemaRead, SchemaWrite},
36};
37
38mod loaded;
39
40#[cfg_attr(
43 feature = "frozen-abi",
44 derive(AbiExample, StableAbi, StableAbiSample),
45 frozen_abi(
46 abi_digest = "BgfDMK6KNGWbvzMmSAcwMsMoL25jmE7x7CCzCeYtMnqa",
47 abi_serializer = ["bincode", "wincode"],
48 test_roundtrip = "eq_and_wire"
49 )
50)]
51#[cfg_attr(
52 feature = "serde",
53 derive(Deserialize, Serialize),
54 serde(rename_all = "camelCase")
55)]
56#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
57#[derive(Default, Debug, PartialEq, Eq, Clone)]
58pub struct MessageAddressTableLookup {
59 pub account_key: Address,
61 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
63 #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
64 pub writable_indexes: Vec<u8>,
65 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
67 #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
68 pub readonly_indexes: Vec<u8>,
69}
70
71#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
79#[cfg_attr(
80 feature = "serde",
81 derive(Deserialize, Serialize),
82 serde(rename_all = "camelCase")
83)]
84#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
85#[derive(Default, Debug, PartialEq, Eq, Clone)]
86pub struct Message {
87 pub header: MessageHeader,
91
92 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
94 #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
95 pub account_keys: Vec<Address>,
96
97 pub recent_blockhash: Hash,
99
100 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
114 #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
115 pub instructions: Vec<CompiledInstruction>,
116
117 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
120 #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
121 pub address_table_lookups: Vec<MessageAddressTableLookup>,
122}
123
124impl Message {
125 pub fn sanitize(&self) -> Result<(), SanitizeError> {
127 let num_static_account_keys = self.account_keys.len();
128 if usize::from(self.header.num_required_signatures)
129 .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts))
130 > num_static_account_keys
131 {
132 return Err(SanitizeError::IndexOutOfBounds);
133 }
134
135 if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
137 return Err(SanitizeError::InvalidValue);
138 }
139
140 let num_dynamic_account_keys = {
141 let mut total_lookup_keys: usize = 0;
142 for lookup in &self.address_table_lookups {
143 let num_lookup_indexes = lookup
144 .writable_indexes
145 .len()
146 .saturating_add(lookup.readonly_indexes.len());
147
148 if num_lookup_indexes == 0 {
150 return Err(SanitizeError::InvalidValue);
151 }
152
153 total_lookup_keys = total_lookup_keys.saturating_add(num_lookup_indexes);
154 }
155 total_lookup_keys
156 };
157
158 if num_static_account_keys == 0 {
162 return Err(SanitizeError::InvalidValue);
163 }
164
165 let total_account_keys = num_static_account_keys.saturating_add(num_dynamic_account_keys);
170 if total_account_keys > 256 {
171 return Err(SanitizeError::IndexOutOfBounds);
172 }
173
174 let max_account_ix = total_account_keys
177 .checked_sub(1)
178 .expect("message doesn't contain any account keys");
179
180 let max_program_id_ix =
184 num_static_account_keys
187 .checked_sub(1)
188 .expect("message doesn't contain any static account keys");
189
190 for ci in &self.instructions {
191 if usize::from(ci.program_id_index) > max_program_id_ix {
192 return Err(SanitizeError::IndexOutOfBounds);
193 }
194 if ci.program_id_index == 0 {
196 return Err(SanitizeError::IndexOutOfBounds);
197 }
198 for ai in &ci.accounts {
199 if usize::from(*ai) > max_account_ix {
200 return Err(SanitizeError::IndexOutOfBounds);
201 }
202 }
203 }
204
205 Ok(())
206 }
207}
208
209impl Message {
210 pub fn try_compile(
308 payer: &Address,
309 instructions: &[Instruction],
310 address_lookup_table_accounts: &[AddressLookupTableAccount],
311 recent_blockhash: Hash,
312 ) -> Result<Self, CompileError> {
313 let mut compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
314
315 let mut address_table_lookups = Vec::with_capacity(address_lookup_table_accounts.len());
316 let mut loaded_addresses_list = Vec::with_capacity(address_lookup_table_accounts.len());
317 for lookup_table_account in address_lookup_table_accounts {
318 if let Some((lookup, loaded_addresses)) =
319 compiled_keys.try_extract_table_lookup(lookup_table_account)?
320 {
321 address_table_lookups.push(lookup);
322 loaded_addresses_list.push(loaded_addresses);
323 }
324 }
325
326 let (header, static_keys) = compiled_keys.try_into_message_components()?;
327 let dynamic_keys = loaded_addresses_list.into_iter().collect();
328 let account_keys = AccountKeys::new(&static_keys, Some(&dynamic_keys));
329 let instructions = account_keys.try_compile_instructions(instructions)?;
330
331 Ok(Self {
332 header,
333 account_keys: static_keys,
334 recent_blockhash,
335 instructions,
336 address_table_lookups,
337 })
338 }
339
340 #[cfg(feature = "wincode")]
341 pub fn serialize(&self) -> Vec<u8> {
343 wincode::serialize(&(crate::MESSAGE_VERSION_PREFIX, self)).unwrap()
344 }
345
346 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
348 if let Ok(key_index) = u8::try_from(key_index) {
349 self.instructions
350 .iter()
351 .any(|ix| ix.program_id_index == key_index)
352 } else {
353 false
354 }
355 }
356
357 fn is_writable_index(&self, key_index: usize) -> bool {
360 let header = &self.header;
361 let num_account_keys = self.account_keys.len();
362 let num_signed_accounts = usize::from(header.num_required_signatures);
363 if key_index >= num_account_keys {
364 let loaded_addresses_index = key_index.saturating_sub(num_account_keys);
365 let num_writable_dynamic_addresses = self
366 .address_table_lookups
367 .iter()
368 .map(|lookup| lookup.writable_indexes.len())
369 .sum();
370 loaded_addresses_index < num_writable_dynamic_addresses
371 } else if key_index >= num_signed_accounts {
372 let num_unsigned_accounts = num_account_keys.saturating_sub(num_signed_accounts);
373 let num_writable_unsigned_accounts = num_unsigned_accounts
374 .saturating_sub(usize::from(header.num_readonly_unsigned_accounts));
375 let unsigned_account_index = key_index.saturating_sub(num_signed_accounts);
376 unsigned_account_index < num_writable_unsigned_accounts
377 } else {
378 let num_writable_signed_accounts = num_signed_accounts
379 .saturating_sub(usize::from(header.num_readonly_signed_accounts));
380 key_index < num_writable_signed_accounts
381 }
382 }
383
384 fn is_upgradeable_loader_in_static_keys(&self) -> bool {
386 self.account_keys
387 .iter()
388 .any(|&key| key == bpf_loader_upgradeable::id())
389 }
390
391 #[cfg(feature = "std")]
401 #[deprecated(
402 since = "4.4.0",
403 note = "Use `is_maybe_writable_with_reserved_addresses` instead"
404 )]
405 pub fn is_maybe_writable(
406 &self,
407 key_index: usize,
408 reserved_account_keys: Option<&HashSet<Address>>,
409 ) -> bool {
410 self.is_maybe_writable_with_reserved_addresses(key_index, reserved_account_keys)
411 }
412
413 pub fn is_maybe_writable_with_reserved_addresses(
423 &self,
424 key_index: usize,
425 reserved_addresses: Option<&impl AddressSet>,
426 ) -> bool {
427 self.is_writable_index(key_index)
428 && !crate::is_account_maybe_reserved(key_index, &self.account_keys, reserved_addresses)
429 && !{
430 self.is_key_called_as_program(key_index)
432 && !self.is_upgradeable_loader_in_static_keys()
433 }
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use {super::*, crate::VersionedMessage, alloc::vec, solana_instruction::AccountMeta};
440
441 #[test]
442 fn test_sanitize() {
443 assert!(Message {
444 header: MessageHeader {
445 num_required_signatures: 1,
446 ..MessageHeader::default()
447 },
448 account_keys: vec![Address::new_unique()],
449 ..Message::default()
450 }
451 .sanitize()
452 .is_ok());
453 }
454
455 #[test]
456 fn test_sanitize_with_instruction() {
457 assert!(Message {
458 header: MessageHeader {
459 num_required_signatures: 1,
460 ..MessageHeader::default()
461 },
462 account_keys: vec![Address::new_unique(), Address::new_unique()],
463 instructions: vec![CompiledInstruction {
464 program_id_index: 1,
465 accounts: vec![0],
466 data: vec![]
467 }],
468 ..Message::default()
469 }
470 .sanitize()
471 .is_ok());
472 }
473
474 #[test]
475 fn test_sanitize_with_table_lookup() {
476 assert!(Message {
477 header: MessageHeader {
478 num_required_signatures: 1,
479 ..MessageHeader::default()
480 },
481 account_keys: vec![Address::new_unique()],
482 address_table_lookups: vec![MessageAddressTableLookup {
483 account_key: Address::new_unique(),
484 writable_indexes: vec![1, 2, 3],
485 readonly_indexes: vec![0],
486 }],
487 ..Message::default()
488 }
489 .sanitize()
490 .is_ok());
491 }
492
493 #[test]
494 fn test_sanitize_with_table_lookup_and_ix_with_dynamic_program_id() {
495 let message = Message {
496 header: MessageHeader {
497 num_required_signatures: 1,
498 ..MessageHeader::default()
499 },
500 account_keys: vec![Address::new_unique()],
501 address_table_lookups: vec![MessageAddressTableLookup {
502 account_key: Address::new_unique(),
503 writable_indexes: vec![1, 2, 3],
504 readonly_indexes: vec![0],
505 }],
506 instructions: vec![CompiledInstruction {
507 program_id_index: 4,
508 accounts: vec![0, 1, 2, 3],
509 data: vec![],
510 }],
511 ..Message::default()
512 };
513
514 assert!(message.sanitize().is_err());
515 }
516
517 #[test]
518 fn test_sanitize_with_table_lookup_and_ix_with_static_program_id() {
519 assert!(Message {
520 header: MessageHeader {
521 num_required_signatures: 1,
522 ..MessageHeader::default()
523 },
524 account_keys: vec![Address::new_unique(), Address::new_unique()],
525 address_table_lookups: vec![MessageAddressTableLookup {
526 account_key: Address::new_unique(),
527 writable_indexes: vec![1, 2, 3],
528 readonly_indexes: vec![0],
529 }],
530 instructions: vec![CompiledInstruction {
531 program_id_index: 1,
532 accounts: vec![2, 3, 4, 5],
533 data: vec![]
534 }],
535 ..Message::default()
536 }
537 .sanitize()
538 .is_ok());
539 }
540
541 #[test]
542 fn test_sanitize_without_signer() {
543 assert!(Message {
544 header: MessageHeader::default(),
545 account_keys: vec![Address::new_unique()],
546 ..Message::default()
547 }
548 .sanitize()
549 .is_err());
550 }
551
552 #[test]
553 fn test_sanitize_without_writable_signer() {
554 assert!(Message {
555 header: MessageHeader {
556 num_required_signatures: 1,
557 num_readonly_signed_accounts: 1,
558 ..MessageHeader::default()
559 },
560 account_keys: vec![Address::new_unique()],
561 ..Message::default()
562 }
563 .sanitize()
564 .is_err());
565 }
566
567 #[test]
568 fn test_sanitize_with_empty_table_lookup() {
569 assert!(Message {
570 header: MessageHeader {
571 num_required_signatures: 1,
572 ..MessageHeader::default()
573 },
574 account_keys: vec![Address::new_unique()],
575 address_table_lookups: vec![MessageAddressTableLookup {
576 account_key: Address::new_unique(),
577 writable_indexes: vec![],
578 readonly_indexes: vec![],
579 }],
580 ..Message::default()
581 }
582 .sanitize()
583 .is_err());
584 }
585
586 #[test]
587 fn test_sanitize_with_max_account_keys() {
588 assert!(Message {
589 header: MessageHeader {
590 num_required_signatures: 1,
591 ..MessageHeader::default()
592 },
593 account_keys: (0..=u8::MAX).map(|_| Address::new_unique()).collect(),
594 ..Message::default()
595 }
596 .sanitize()
597 .is_ok());
598 }
599
600 #[test]
601 fn test_sanitize_with_too_many_account_keys() {
602 assert!(Message {
603 header: MessageHeader {
604 num_required_signatures: 1,
605 ..MessageHeader::default()
606 },
607 account_keys: (0..=256).map(|_| Address::new_unique()).collect(),
608 ..Message::default()
609 }
610 .sanitize()
611 .is_err());
612 }
613
614 #[test]
615 fn test_sanitize_with_max_table_loaded_keys() {
616 assert!(Message {
617 header: MessageHeader {
618 num_required_signatures: 1,
619 ..MessageHeader::default()
620 },
621 account_keys: vec![Address::new_unique()],
622 address_table_lookups: vec![MessageAddressTableLookup {
623 account_key: Address::new_unique(),
624 writable_indexes: (0..=254).step_by(2).collect(),
625 readonly_indexes: (1..=254).step_by(2).collect(),
626 }],
627 ..Message::default()
628 }
629 .sanitize()
630 .is_ok());
631 }
632
633 #[test]
634 fn test_sanitize_with_too_many_table_loaded_keys() {
635 assert!(Message {
636 header: MessageHeader {
637 num_required_signatures: 1,
638 ..MessageHeader::default()
639 },
640 account_keys: vec![Address::new_unique()],
641 address_table_lookups: vec![MessageAddressTableLookup {
642 account_key: Address::new_unique(),
643 writable_indexes: (0..=255).step_by(2).collect(),
644 readonly_indexes: (1..=255).step_by(2).collect(),
645 }],
646 ..Message::default()
647 }
648 .sanitize()
649 .is_err());
650 }
651
652 #[test]
653 fn test_sanitize_with_invalid_ix_program_id() {
654 let message = Message {
655 header: MessageHeader {
656 num_required_signatures: 1,
657 ..MessageHeader::default()
658 },
659 account_keys: vec![Address::new_unique()],
660 address_table_lookups: vec![MessageAddressTableLookup {
661 account_key: Address::new_unique(),
662 writable_indexes: vec![0],
663 readonly_indexes: vec![],
664 }],
665 instructions: vec![CompiledInstruction {
666 program_id_index: 2,
667 accounts: vec![],
668 data: vec![],
669 }],
670 ..Message::default()
671 };
672
673 assert!(message.sanitize().is_err());
674 }
675
676 #[test]
677 fn test_sanitize_with_invalid_ix_account() {
678 assert!(Message {
679 header: MessageHeader {
680 num_required_signatures: 1,
681 ..MessageHeader::default()
682 },
683 account_keys: vec![Address::new_unique(), Address::new_unique()],
684 address_table_lookups: vec![MessageAddressTableLookup {
685 account_key: Address::new_unique(),
686 writable_indexes: vec![],
687 readonly_indexes: vec![0],
688 }],
689 instructions: vec![CompiledInstruction {
690 program_id_index: 1,
691 accounts: vec![3],
692 data: vec![]
693 }],
694 ..Message::default()
695 }
696 .sanitize()
697 .is_err());
698 }
699
700 #[test]
701 fn test_serialize() {
702 let message = Message::default();
703 let versioned_msg = VersionedMessage::V0(message.clone());
704 assert_eq!(message.serialize(), versioned_msg.serialize());
705 }
706
707 #[test]
708 fn test_try_compile() {
709 let mut keys = vec![];
710 keys.resize_with(7, Address::new_unique);
711
712 let payer = keys[0];
713 let program_id = keys[6];
714 let instructions = vec![Instruction {
715 program_id,
716 accounts: vec![
717 AccountMeta::new(keys[1], true),
718 AccountMeta::new_readonly(keys[2], true),
719 AccountMeta::new(keys[3], false),
720 AccountMeta::new(keys[4], false), AccountMeta::new_readonly(keys[5], false), ],
723 data: vec![],
724 }];
725 let address_lookup_table_accounts = vec![
726 AddressLookupTableAccount {
727 key: Address::new_unique(),
728 addresses: vec![keys[4], keys[5], keys[6]],
729 },
730 AddressLookupTableAccount {
731 key: Address::new_unique(),
732 addresses: vec![],
733 },
734 ];
735
736 let recent_blockhash = Hash::new_unique();
737 assert_eq!(
738 Message::try_compile(
739 &payer,
740 &instructions,
741 &address_lookup_table_accounts,
742 recent_blockhash
743 ),
744 Ok(Message {
745 header: MessageHeader {
746 num_required_signatures: 3,
747 num_readonly_signed_accounts: 1,
748 num_readonly_unsigned_accounts: 1
749 },
750 recent_blockhash,
751 account_keys: vec![keys[0], keys[1], keys[2], keys[3], program_id],
752 instructions: vec![CompiledInstruction {
753 program_id_index: 4,
754 accounts: vec![1, 2, 3, 5, 6],
755 data: vec![],
756 },],
757 address_table_lookups: vec![MessageAddressTableLookup {
758 account_key: address_lookup_table_accounts[0].key,
759 writable_indexes: vec![0],
760 readonly_indexes: vec![1],
761 }],
762 })
763 );
764 }
765
766 #[test]
767 #[allow(deprecated)]
768 fn test_is_maybe_writable() {
769 let key0 = Address::new_unique();
770 let key1 = Address::new_unique();
771 let key2 = Address::new_unique();
772 let key3 = Address::new_unique();
773 let key4 = Address::new_unique();
774 let key5 = Address::new_unique();
775
776 let message = Message {
777 header: MessageHeader {
778 num_required_signatures: 3,
779 num_readonly_signed_accounts: 2,
780 num_readonly_unsigned_accounts: 1,
781 },
782 account_keys: vec![key0, key1, key2, key3, key4, key5],
783 address_table_lookups: vec![MessageAddressTableLookup {
784 account_key: Address::new_unique(),
785 writable_indexes: vec![0],
786 readonly_indexes: vec![1],
787 }],
788 ..Message::default()
789 };
790
791 let reserved_account_keys = HashSet::from([key3]);
792
793 assert!(message.is_maybe_writable(0, Some(&reserved_account_keys)));
794 assert!(!message.is_maybe_writable(1, Some(&reserved_account_keys)));
795 assert!(!message.is_maybe_writable(2, Some(&reserved_account_keys)));
796 assert!(!message.is_maybe_writable(3, Some(&reserved_account_keys)));
797 assert!(message.is_maybe_writable(3, None));
798 assert!(message.is_maybe_writable(4, Some(&reserved_account_keys)));
799 assert!(!message.is_maybe_writable(5, Some(&reserved_account_keys)));
800 assert!(message.is_maybe_writable(6, Some(&reserved_account_keys)));
801 assert!(!message.is_maybe_writable(7, Some(&reserved_account_keys)));
802 assert!(!message.is_maybe_writable(8, Some(&reserved_account_keys)));
803 }
804}