1use crate::{
13 address_lookup_table_account::AddressLookupTableAccount,
14 bpf_loader_upgradeable,
15 hash::Hash,
16 instruction::{CompiledInstruction, Instruction},
17 message::{
18 compiled_keys::{CompileError, CompiledKeys},
19 legacy::is_builtin_key_or_sysvar,
20 AccountKeys, MessageHeader, MESSAGE_VERSION_PREFIX,
21 },
22 pubkey::Pubkey,
23 sanitize::SanitizeError,
24 short_vec,
25};
26pub use loaded::*;
27
28mod loaded;
29
30#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
33#[serde(rename_all = "camelCase")]
34pub struct MessageAddressTableLookup {
35 pub account_key: Pubkey,
37 #[serde(with = "short_vec")]
39 pub writable_indexes: Vec<u8>,
40 #[serde(with = "short_vec")]
42 pub readonly_indexes: Vec<u8>,
43}
44
45#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
54#[serde(rename_all = "camelCase")]
55pub struct Message {
56 pub header: MessageHeader,
60
61 #[serde(with = "short_vec")]
63 pub account_keys: Vec<Pubkey>,
64
65 pub recent_blockhash: Hash,
67
68 #[serde(with = "short_vec")]
82 pub instructions: Vec<CompiledInstruction>,
83
84 #[serde(with = "short_vec")]
87 pub address_table_lookups: Vec<MessageAddressTableLookup>,
88}
89
90impl Message {
91 pub fn sanitize(&self, reject_dynamic_program_ids: bool) -> Result<(), SanitizeError> {
93 let num_static_account_keys = self.account_keys.len();
94 if usize::from(self.header.num_required_signatures)
95 .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts))
96 > num_static_account_keys
97 {
98 return Err(SanitizeError::IndexOutOfBounds);
99 }
100
101 if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
103 return Err(SanitizeError::InvalidValue);
104 }
105
106 let num_dynamic_account_keys = {
107 let mut total_lookup_keys: usize = 0;
108 for lookup in &self.address_table_lookups {
109 let num_lookup_indexes = lookup
110 .writable_indexes
111 .len()
112 .saturating_add(lookup.readonly_indexes.len());
113
114 if num_lookup_indexes == 0 {
116 return Err(SanitizeError::InvalidValue);
117 }
118
119 total_lookup_keys = total_lookup_keys.saturating_add(num_lookup_indexes);
120 }
121 total_lookup_keys
122 };
123
124 if num_static_account_keys == 0 {
128 return Err(SanitizeError::InvalidValue);
129 }
130
131 let total_account_keys = num_static_account_keys.saturating_add(num_dynamic_account_keys);
136 if total_account_keys > 256 {
137 return Err(SanitizeError::IndexOutOfBounds);
138 }
139
140 let max_account_ix = total_account_keys
143 .checked_sub(1)
144 .expect("message doesn't contain any account keys");
145
146 let max_program_id_ix = if reject_dynamic_program_ids {
150 num_static_account_keys
153 .checked_sub(1)
154 .expect("message doesn't contain any static account keys")
155 } else {
156 max_account_ix
157 };
158
159 for ci in &self.instructions {
160 if usize::from(ci.program_id_index) > max_program_id_ix {
161 return Err(SanitizeError::IndexOutOfBounds);
162 }
163 if ci.program_id_index == 0 {
165 return Err(SanitizeError::IndexOutOfBounds);
166 }
167 for ai in &ci.accounts {
168 if usize::from(*ai) > max_account_ix {
169 return Err(SanitizeError::IndexOutOfBounds);
170 }
171 }
172 }
173
174 Ok(())
175 }
176}
177
178impl Message {
179 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(&self, key_index: usize) -> bool {
345 self.is_writable_index(key_index)
346 && !{
347 self.account_keys
349 .get(key_index)
350 .map(is_builtin_key_or_sysvar)
351 .unwrap_or_default()
352 }
353 && !{
354 self.is_key_called_as_program(key_index)
356 && !self.is_upgradeable_loader_in_static_keys()
357 }
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use {
364 super::*,
365 crate::{instruction::AccountMeta, message::VersionedMessage},
366 };
367
368 #[test]
369 fn test_sanitize() {
370 assert!(Message {
371 header: MessageHeader {
372 num_required_signatures: 1,
373 ..MessageHeader::default()
374 },
375 account_keys: vec![Pubkey::new_unique()],
376 ..Message::default()
377 }
378 .sanitize(
379 true, )
381 .is_ok());
382 }
383
384 #[test]
385 fn test_sanitize_with_instruction() {
386 assert!(Message {
387 header: MessageHeader {
388 num_required_signatures: 1,
389 ..MessageHeader::default()
390 },
391 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
392 instructions: vec![CompiledInstruction {
393 program_id_index: 1,
394 accounts: vec![0],
395 data: vec![]
396 }],
397 ..Message::default()
398 }
399 .sanitize(
400 true, )
402 .is_ok());
403 }
404
405 #[test]
406 fn test_sanitize_with_table_lookup() {
407 assert!(Message {
408 header: MessageHeader {
409 num_required_signatures: 1,
410 ..MessageHeader::default()
411 },
412 account_keys: vec![Pubkey::new_unique()],
413 address_table_lookups: vec![MessageAddressTableLookup {
414 account_key: Pubkey::new_unique(),
415 writable_indexes: vec![1, 2, 3],
416 readonly_indexes: vec![0],
417 }],
418 ..Message::default()
419 }
420 .sanitize(
421 true, )
423 .is_ok());
424 }
425
426 #[test]
427 fn test_sanitize_with_table_lookup_and_ix_with_dynamic_program_id() {
428 let message = Message {
429 header: MessageHeader {
430 num_required_signatures: 1,
431 ..MessageHeader::default()
432 },
433 account_keys: vec![Pubkey::new_unique()],
434 address_table_lookups: vec![MessageAddressTableLookup {
435 account_key: Pubkey::new_unique(),
436 writable_indexes: vec![1, 2, 3],
437 readonly_indexes: vec![0],
438 }],
439 instructions: vec![CompiledInstruction {
440 program_id_index: 4,
441 accounts: vec![0, 1, 2, 3],
442 data: vec![],
443 }],
444 ..Message::default()
445 };
446
447 assert!(message.sanitize(
448 false, ).is_ok());
450
451 assert!(message.sanitize(
452 true, ).is_err());
454 }
455
456 #[test]
457 fn test_sanitize_with_table_lookup_and_ix_with_static_program_id() {
458 assert!(Message {
459 header: MessageHeader {
460 num_required_signatures: 1,
461 ..MessageHeader::default()
462 },
463 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
464 address_table_lookups: vec![MessageAddressTableLookup {
465 account_key: Pubkey::new_unique(),
466 writable_indexes: vec![1, 2, 3],
467 readonly_indexes: vec![0],
468 }],
469 instructions: vec![CompiledInstruction {
470 program_id_index: 1,
471 accounts: vec![2, 3, 4, 5],
472 data: vec![]
473 }],
474 ..Message::default()
475 }
476 .sanitize(
477 true, )
479 .is_ok());
480 }
481
482 #[test]
483 fn test_sanitize_without_signer() {
484 assert!(Message {
485 header: MessageHeader::default(),
486 account_keys: vec![Pubkey::new_unique()],
487 ..Message::default()
488 }
489 .sanitize(
490 true, )
492 .is_err());
493 }
494
495 #[test]
496 fn test_sanitize_without_writable_signer() {
497 assert!(Message {
498 header: MessageHeader {
499 num_required_signatures: 1,
500 num_readonly_signed_accounts: 1,
501 ..MessageHeader::default()
502 },
503 account_keys: vec![Pubkey::new_unique()],
504 ..Message::default()
505 }
506 .sanitize(
507 true, )
509 .is_err());
510 }
511
512 #[test]
513 fn test_sanitize_with_empty_table_lookup() {
514 assert!(Message {
515 header: MessageHeader {
516 num_required_signatures: 1,
517 ..MessageHeader::default()
518 },
519 account_keys: vec![Pubkey::new_unique()],
520 address_table_lookups: vec![MessageAddressTableLookup {
521 account_key: Pubkey::new_unique(),
522 writable_indexes: vec![],
523 readonly_indexes: vec![],
524 }],
525 ..Message::default()
526 }
527 .sanitize(
528 true, )
530 .is_err());
531 }
532
533 #[test]
534 fn test_sanitize_with_max_account_keys() {
535 assert!(Message {
536 header: MessageHeader {
537 num_required_signatures: 1,
538 ..MessageHeader::default()
539 },
540 account_keys: (0..=u8::MAX).map(|_| Pubkey::new_unique()).collect(),
541 ..Message::default()
542 }
543 .sanitize(
544 true, )
546 .is_ok());
547 }
548
549 #[test]
550 fn test_sanitize_with_too_many_account_keys() {
551 assert!(Message {
552 header: MessageHeader {
553 num_required_signatures: 1,
554 ..MessageHeader::default()
555 },
556 account_keys: (0..=256).map(|_| Pubkey::new_unique()).collect(),
557 ..Message::default()
558 }
559 .sanitize(
560 true, )
562 .is_err());
563 }
564
565 #[test]
566 fn test_sanitize_with_max_table_loaded_keys() {
567 assert!(Message {
568 header: MessageHeader {
569 num_required_signatures: 1,
570 ..MessageHeader::default()
571 },
572 account_keys: vec![Pubkey::new_unique()],
573 address_table_lookups: vec![MessageAddressTableLookup {
574 account_key: Pubkey::new_unique(),
575 writable_indexes: (0..=254).step_by(2).collect(),
576 readonly_indexes: (1..=254).step_by(2).collect(),
577 }],
578 ..Message::default()
579 }
580 .sanitize(
581 true, )
583 .is_ok());
584 }
585
586 #[test]
587 fn test_sanitize_with_too_many_table_loaded_keys() {
588 assert!(Message {
589 header: MessageHeader {
590 num_required_signatures: 1,
591 ..MessageHeader::default()
592 },
593 account_keys: vec![Pubkey::new_unique()],
594 address_table_lookups: vec![MessageAddressTableLookup {
595 account_key: Pubkey::new_unique(),
596 writable_indexes: (0..=255).step_by(2).collect(),
597 readonly_indexes: (1..=255).step_by(2).collect(),
598 }],
599 ..Message::default()
600 }
601 .sanitize(
602 true, )
604 .is_err());
605 }
606
607 #[test]
608 fn test_sanitize_with_invalid_ix_program_id() {
609 let message = Message {
610 header: MessageHeader {
611 num_required_signatures: 1,
612 ..MessageHeader::default()
613 },
614 account_keys: vec![Pubkey::new_unique()],
615 address_table_lookups: vec![MessageAddressTableLookup {
616 account_key: Pubkey::new_unique(),
617 writable_indexes: vec![0],
618 readonly_indexes: vec![],
619 }],
620 instructions: vec![CompiledInstruction {
621 program_id_index: 2,
622 accounts: vec![],
623 data: vec![],
624 }],
625 ..Message::default()
626 };
627
628 assert!(message
629 .sanitize(true )
630 .is_err());
631 assert!(message
632 .sanitize(false )
633 .is_err());
634 }
635
636 #[test]
637 fn test_sanitize_with_invalid_ix_account() {
638 assert!(Message {
639 header: MessageHeader {
640 num_required_signatures: 1,
641 ..MessageHeader::default()
642 },
643 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
644 address_table_lookups: vec![MessageAddressTableLookup {
645 account_key: Pubkey::new_unique(),
646 writable_indexes: vec![],
647 readonly_indexes: vec![0],
648 }],
649 instructions: vec![CompiledInstruction {
650 program_id_index: 1,
651 accounts: vec![3],
652 data: vec![]
653 }],
654 ..Message::default()
655 }
656 .sanitize(
657 true, )
659 .is_err());
660 }
661
662 #[test]
663 fn test_serialize() {
664 let message = Message::default();
665 let versioned_msg = VersionedMessage::V0(message.clone());
666 assert_eq!(message.serialize(), versioned_msg.serialize());
667 }
668
669 #[test]
670 fn test_try_compile() {
671 let mut keys = vec![];
672 keys.resize_with(7, Pubkey::new_unique);
673
674 let payer = keys[0];
675 let program_id = keys[6];
676 let instructions = vec![Instruction {
677 program_id,
678 accounts: vec![
679 AccountMeta::new(keys[1], true),
680 AccountMeta::new_readonly(keys[2], true),
681 AccountMeta::new(keys[3], false),
682 AccountMeta::new(keys[4], false), AccountMeta::new_readonly(keys[5], false), ],
685 data: vec![],
686 }];
687 let address_lookup_table_accounts = vec![
688 AddressLookupTableAccount {
689 key: Pubkey::new_unique(),
690 addresses: vec![keys[4], keys[5], keys[6]],
691 },
692 AddressLookupTableAccount {
693 key: Pubkey::new_unique(),
694 addresses: vec![],
695 },
696 ];
697
698 let recent_blockhash = Hash::new_unique();
699 assert_eq!(
700 Message::try_compile(
701 &payer,
702 &instructions,
703 &address_lookup_table_accounts,
704 recent_blockhash
705 ),
706 Ok(Message {
707 header: MessageHeader {
708 num_required_signatures: 3,
709 num_readonly_signed_accounts: 1,
710 num_readonly_unsigned_accounts: 1
711 },
712 recent_blockhash,
713 account_keys: vec![keys[0], keys[1], keys[2], keys[3], program_id],
714 instructions: vec![CompiledInstruction {
715 program_id_index: 4,
716 accounts: vec![1, 2, 3, 5, 6],
717 data: vec![],
718 },],
719 address_table_lookups: vec![MessageAddressTableLookup {
720 account_key: address_lookup_table_accounts[0].key,
721 writable_indexes: vec![0],
722 readonly_indexes: vec![1],
723 }],
724 })
725 );
726 }
727}