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};
17use {
18 crate::{
19 compiled_instruction::CompiledInstruction,
20 compiled_keys::{CompileError, CompiledKeys},
21 AccountKeys, AddressLookupTableAccount, AddressSet, MessageHeader,
22 },
23 alloc::vec::Vec,
24 solana_address::Address,
25 solana_hash::Hash,
26 solana_instruction::Instruction,
27 solana_sanitize::SanitizeError,
28 solana_sdk_ids::bpf_loader_upgradeable,
29};
30#[cfg(feature = "wincode")]
31use {
32 solana_short_vec::ShortU16,
33 wincode::{containers, SchemaRead, SchemaWrite},
34};
35
36mod loaded;
37
38#[cfg_attr(
41 feature = "frozen-abi",
42 derive(AbiExample, StableAbi, StableAbiSample),
43 frozen_abi(
44 abi_digest = "BgfDMK6KNGWbvzMmSAcwMsMoL25jmE7x7CCzCeYtMnqa",
45 abi_serializer = ["bincode", "wincode"],
46 test_roundtrip = "eq_and_wire"
47 )
48)]
49#[cfg_attr(
50 feature = "serde",
51 derive(Deserialize, Serialize),
52 serde(rename_all = "camelCase")
53)]
54#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
55#[derive(Default, Debug, PartialEq, Eq, Clone)]
56pub struct MessageAddressTableLookup {
57 pub account_key: Address,
59 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
61 #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
62 pub writable_indexes: Vec<u8>,
63 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
65 #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
66 pub readonly_indexes: Vec<u8>,
67}
68
69#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
77#[cfg_attr(
78 feature = "serde",
79 derive(Deserialize, Serialize),
80 serde(rename_all = "camelCase")
81)]
82#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
83#[derive(Default, Debug, PartialEq, Eq, Clone)]
84pub struct Message {
85 pub header: MessageHeader,
89
90 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
92 #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
93 pub account_keys: Vec<Address>,
94
95 pub recent_blockhash: Hash,
97
98 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
112 #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
113 pub instructions: Vec<CompiledInstruction>,
114
115 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
118 #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16>"))]
119 pub address_table_lookups: Vec<MessageAddressTableLookup>,
120}
121
122impl Message {
123 pub fn sanitize(&self) -> Result<(), SanitizeError> {
125 let num_static_account_keys = self.account_keys.len();
126 if usize::from(self.header.num_required_signatures)
127 .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts))
128 > num_static_account_keys
129 {
130 return Err(SanitizeError::IndexOutOfBounds);
131 }
132
133 if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
135 return Err(SanitizeError::InvalidValue);
136 }
137
138 let num_dynamic_account_keys = {
139 let mut total_lookup_keys: usize = 0;
140 for lookup in &self.address_table_lookups {
141 let num_lookup_indexes = lookup
142 .writable_indexes
143 .len()
144 .saturating_add(lookup.readonly_indexes.len());
145
146 if num_lookup_indexes == 0 {
148 return Err(SanitizeError::InvalidValue);
149 }
150
151 total_lookup_keys = total_lookup_keys.saturating_add(num_lookup_indexes);
152 }
153 total_lookup_keys
154 };
155
156 if num_static_account_keys == 0 {
160 return Err(SanitizeError::InvalidValue);
161 }
162
163 let total_account_keys = num_static_account_keys.saturating_add(num_dynamic_account_keys);
168 if total_account_keys > 256 {
169 return Err(SanitizeError::IndexOutOfBounds);
170 }
171
172 let max_account_ix = total_account_keys
175 .checked_sub(1)
176 .expect("message doesn't contain any account keys");
177
178 let max_program_id_ix =
182 num_static_account_keys
185 .checked_sub(1)
186 .expect("message doesn't contain any static account keys");
187
188 for ci in &self.instructions {
189 if usize::from(ci.program_id_index) > max_program_id_ix {
190 return Err(SanitizeError::IndexOutOfBounds);
191 }
192 if ci.program_id_index == 0 {
194 return Err(SanitizeError::IndexOutOfBounds);
195 }
196 for ai in &ci.accounts {
197 if usize::from(*ai) > max_account_ix {
198 return Err(SanitizeError::IndexOutOfBounds);
199 }
200 }
201 }
202
203 Ok(())
204 }
205}
206
207impl Message {
208 pub fn try_compile(
306 payer: &Address,
307 instructions: &[Instruction],
308 address_lookup_table_accounts: &[AddressLookupTableAccount],
309 recent_blockhash: Hash,
310 ) -> Result<Self, CompileError> {
311 let mut compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
312
313 let mut address_table_lookups = Vec::with_capacity(address_lookup_table_accounts.len());
314 let mut loaded_addresses_list = Vec::with_capacity(address_lookup_table_accounts.len());
315 for lookup_table_account in address_lookup_table_accounts {
316 if let Some((lookup, loaded_addresses)) =
317 compiled_keys.try_extract_table_lookup(lookup_table_account)?
318 {
319 address_table_lookups.push(lookup);
320 loaded_addresses_list.push(loaded_addresses);
321 }
322 }
323
324 let (header, static_keys) = compiled_keys.try_into_message_components()?;
325 let dynamic_keys = loaded_addresses_list.into_iter().collect();
326 let account_keys = AccountKeys::new(&static_keys, Some(&dynamic_keys));
327 let instructions = account_keys.try_compile_instructions(instructions)?;
328
329 Ok(Self {
330 header,
331 account_keys: static_keys,
332 recent_blockhash,
333 instructions,
334 address_table_lookups,
335 })
336 }
337
338 #[cfg(feature = "wincode")]
339 pub fn serialize(&self) -> Vec<u8> {
341 wincode::serialize(&(crate::MESSAGE_VERSION_PREFIX, self)).unwrap()
342 }
343
344 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
346 if let Ok(key_index) = u8::try_from(key_index) {
347 self.instructions
348 .iter()
349 .any(|ix| ix.program_id_index == key_index)
350 } else {
351 false
352 }
353 }
354
355 fn is_writable_index(&self, key_index: usize) -> bool {
358 let header = &self.header;
359 let num_account_keys = self.account_keys.len();
360 let num_signed_accounts = usize::from(header.num_required_signatures);
361 if key_index >= num_account_keys {
362 let loaded_addresses_index = key_index.saturating_sub(num_account_keys);
363 let num_writable_dynamic_addresses = self
364 .address_table_lookups
365 .iter()
366 .map(|lookup| lookup.writable_indexes.len())
367 .sum();
368 loaded_addresses_index < num_writable_dynamic_addresses
369 } else if key_index >= num_signed_accounts {
370 let num_unsigned_accounts = num_account_keys.saturating_sub(num_signed_accounts);
371 let num_writable_unsigned_accounts = num_unsigned_accounts
372 .saturating_sub(usize::from(header.num_readonly_unsigned_accounts));
373 let unsigned_account_index = key_index.saturating_sub(num_signed_accounts);
374 unsigned_account_index < num_writable_unsigned_accounts
375 } else {
376 let num_writable_signed_accounts = num_signed_accounts
377 .saturating_sub(usize::from(header.num_readonly_signed_accounts));
378 key_index < num_writable_signed_accounts
379 }
380 }
381
382 fn is_upgradeable_loader_in_static_keys(&self) -> bool {
384 self.account_keys
385 .iter()
386 .any(|&key| key == bpf_loader_upgradeable::id())
387 }
388
389 pub fn is_maybe_writable_with_reserved_addresses(
399 &self,
400 key_index: usize,
401 reserved_addresses: Option<&impl AddressSet>,
402 ) -> bool {
403 self.is_writable_index(key_index)
404 && !crate::is_account_maybe_reserved(key_index, &self.account_keys, reserved_addresses)
405 && !{
406 self.is_key_called_as_program(key_index)
408 && !self.is_upgradeable_loader_in_static_keys()
409 }
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use {super::*, crate::VersionedMessage, alloc::vec, solana_instruction::AccountMeta};
416
417 #[test]
418 fn test_sanitize() {
419 assert!(Message {
420 header: MessageHeader {
421 num_required_signatures: 1,
422 ..MessageHeader::default()
423 },
424 account_keys: vec![Address::new_unique()],
425 ..Message::default()
426 }
427 .sanitize()
428 .is_ok());
429 }
430
431 #[test]
432 fn test_sanitize_with_instruction() {
433 assert!(Message {
434 header: MessageHeader {
435 num_required_signatures: 1,
436 ..MessageHeader::default()
437 },
438 account_keys: vec![Address::new_unique(), Address::new_unique()],
439 instructions: vec![CompiledInstruction {
440 program_id_index: 1,
441 accounts: vec![0],
442 data: vec![]
443 }],
444 ..Message::default()
445 }
446 .sanitize()
447 .is_ok());
448 }
449
450 #[test]
451 fn test_sanitize_with_table_lookup() {
452 assert!(Message {
453 header: MessageHeader {
454 num_required_signatures: 1,
455 ..MessageHeader::default()
456 },
457 account_keys: vec![Address::new_unique()],
458 address_table_lookups: vec![MessageAddressTableLookup {
459 account_key: Address::new_unique(),
460 writable_indexes: vec![1, 2, 3],
461 readonly_indexes: vec![0],
462 }],
463 ..Message::default()
464 }
465 .sanitize()
466 .is_ok());
467 }
468
469 #[test]
470 fn test_sanitize_with_table_lookup_and_ix_with_dynamic_program_id() {
471 let message = Message {
472 header: MessageHeader {
473 num_required_signatures: 1,
474 ..MessageHeader::default()
475 },
476 account_keys: vec![Address::new_unique()],
477 address_table_lookups: vec![MessageAddressTableLookup {
478 account_key: Address::new_unique(),
479 writable_indexes: vec![1, 2, 3],
480 readonly_indexes: vec![0],
481 }],
482 instructions: vec![CompiledInstruction {
483 program_id_index: 4,
484 accounts: vec![0, 1, 2, 3],
485 data: vec![],
486 }],
487 ..Message::default()
488 };
489
490 assert!(message.sanitize().is_err());
491 }
492
493 #[test]
494 fn test_sanitize_with_table_lookup_and_ix_with_static_program_id() {
495 assert!(Message {
496 header: MessageHeader {
497 num_required_signatures: 1,
498 ..MessageHeader::default()
499 },
500 account_keys: vec![Address::new_unique(), 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: 1,
508 accounts: vec![2, 3, 4, 5],
509 data: vec![]
510 }],
511 ..Message::default()
512 }
513 .sanitize()
514 .is_ok());
515 }
516
517 #[test]
518 fn test_sanitize_without_signer() {
519 assert!(Message {
520 header: MessageHeader::default(),
521 account_keys: vec![Address::new_unique()],
522 ..Message::default()
523 }
524 .sanitize()
525 .is_err());
526 }
527
528 #[test]
529 fn test_sanitize_without_writable_signer() {
530 assert!(Message {
531 header: MessageHeader {
532 num_required_signatures: 1,
533 num_readonly_signed_accounts: 1,
534 ..MessageHeader::default()
535 },
536 account_keys: vec![Address::new_unique()],
537 ..Message::default()
538 }
539 .sanitize()
540 .is_err());
541 }
542
543 #[test]
544 fn test_sanitize_with_empty_table_lookup() {
545 assert!(Message {
546 header: MessageHeader {
547 num_required_signatures: 1,
548 ..MessageHeader::default()
549 },
550 account_keys: vec![Address::new_unique()],
551 address_table_lookups: vec![MessageAddressTableLookup {
552 account_key: Address::new_unique(),
553 writable_indexes: vec![],
554 readonly_indexes: vec![],
555 }],
556 ..Message::default()
557 }
558 .sanitize()
559 .is_err());
560 }
561
562 #[test]
563 fn test_sanitize_with_max_account_keys() {
564 assert!(Message {
565 header: MessageHeader {
566 num_required_signatures: 1,
567 ..MessageHeader::default()
568 },
569 account_keys: (0..=u8::MAX).map(|_| Address::new_unique()).collect(),
570 ..Message::default()
571 }
572 .sanitize()
573 .is_ok());
574 }
575
576 #[test]
577 fn test_sanitize_with_too_many_account_keys() {
578 assert!(Message {
579 header: MessageHeader {
580 num_required_signatures: 1,
581 ..MessageHeader::default()
582 },
583 account_keys: (0..=256).map(|_| Address::new_unique()).collect(),
584 ..Message::default()
585 }
586 .sanitize()
587 .is_err());
588 }
589
590 #[test]
591 fn test_sanitize_with_max_table_loaded_keys() {
592 assert!(Message {
593 header: MessageHeader {
594 num_required_signatures: 1,
595 ..MessageHeader::default()
596 },
597 account_keys: vec![Address::new_unique()],
598 address_table_lookups: vec![MessageAddressTableLookup {
599 account_key: Address::new_unique(),
600 writable_indexes: (0..=254).step_by(2).collect(),
601 readonly_indexes: (1..=254).step_by(2).collect(),
602 }],
603 ..Message::default()
604 }
605 .sanitize()
606 .is_ok());
607 }
608
609 #[test]
610 fn test_sanitize_with_too_many_table_loaded_keys() {
611 assert!(Message {
612 header: MessageHeader {
613 num_required_signatures: 1,
614 ..MessageHeader::default()
615 },
616 account_keys: vec![Address::new_unique()],
617 address_table_lookups: vec![MessageAddressTableLookup {
618 account_key: Address::new_unique(),
619 writable_indexes: (0..=255).step_by(2).collect(),
620 readonly_indexes: (1..=255).step_by(2).collect(),
621 }],
622 ..Message::default()
623 }
624 .sanitize()
625 .is_err());
626 }
627
628 #[test]
629 fn test_sanitize_with_invalid_ix_program_id() {
630 let message = Message {
631 header: MessageHeader {
632 num_required_signatures: 1,
633 ..MessageHeader::default()
634 },
635 account_keys: vec![Address::new_unique()],
636 address_table_lookups: vec![MessageAddressTableLookup {
637 account_key: Address::new_unique(),
638 writable_indexes: vec![0],
639 readonly_indexes: vec![],
640 }],
641 instructions: vec![CompiledInstruction {
642 program_id_index: 2,
643 accounts: vec![],
644 data: vec![],
645 }],
646 ..Message::default()
647 };
648
649 assert!(message.sanitize().is_err());
650 }
651
652 #[test]
653 fn test_sanitize_with_invalid_ix_account() {
654 assert!(Message {
655 header: MessageHeader {
656 num_required_signatures: 1,
657 ..MessageHeader::default()
658 },
659 account_keys: vec![Address::new_unique(), Address::new_unique()],
660 address_table_lookups: vec![MessageAddressTableLookup {
661 account_key: Address::new_unique(),
662 writable_indexes: vec![],
663 readonly_indexes: vec![0],
664 }],
665 instructions: vec![CompiledInstruction {
666 program_id_index: 1,
667 accounts: vec![3],
668 data: vec![]
669 }],
670 ..Message::default()
671 }
672 .sanitize()
673 .is_err());
674 }
675
676 #[test]
677 fn test_serialize() {
678 let message = Message::default();
679 let versioned_msg = VersionedMessage::V0(message.clone());
680 assert_eq!(message.serialize(), versioned_msg.serialize());
681 }
682
683 #[test]
684 fn test_try_compile() {
685 let mut keys = vec![];
686 keys.resize_with(7, Address::new_unique);
687
688 let payer = keys[0];
689 let program_id = keys[6];
690 let instructions = vec![Instruction {
691 program_id,
692 accounts: vec![
693 AccountMeta::new(keys[1], true),
694 AccountMeta::new_readonly(keys[2], true),
695 AccountMeta::new(keys[3], false),
696 AccountMeta::new(keys[4], false), AccountMeta::new_readonly(keys[5], false), ],
699 data: vec![],
700 }];
701 let address_lookup_table_accounts = vec![
702 AddressLookupTableAccount {
703 key: Address::new_unique(),
704 addresses: vec![keys[4], keys[5], keys[6]],
705 },
706 AddressLookupTableAccount {
707 key: Address::new_unique(),
708 addresses: vec![],
709 },
710 ];
711
712 let recent_blockhash = Hash::new_unique();
713 assert_eq!(
714 Message::try_compile(
715 &payer,
716 &instructions,
717 &address_lookup_table_accounts,
718 recent_blockhash
719 ),
720 Ok(Message {
721 header: MessageHeader {
722 num_required_signatures: 3,
723 num_readonly_signed_accounts: 1,
724 num_readonly_unsigned_accounts: 1
725 },
726 recent_blockhash,
727 account_keys: vec![keys[0], keys[1], keys[2], keys[3], program_id],
728 instructions: vec![CompiledInstruction {
729 program_id_index: 4,
730 accounts: vec![1, 2, 3, 5, 6],
731 data: vec![],
732 },],
733 address_table_lookups: vec![MessageAddressTableLookup {
734 account_key: address_lookup_table_accounts[0].key,
735 writable_indexes: vec![0],
736 readonly_indexes: vec![1],
737 }],
738 })
739 );
740 }
741}