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