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, legacy::is_builtin_key_or_sysvar, AccountKeys, CompiledKeys,
19 MessageHeader, MESSAGE_VERSION_PREFIX,
20 },
21 pubkey::Pubkey,
22 sanitize::SanitizeError,
23 short_vec,
24};
25pub use loaded::*;
26
27mod loaded;
28
29#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
32#[serde(rename_all = "camelCase")]
33pub struct MessageAddressTableLookup {
34 pub account_key: Pubkey,
36 #[serde(with = "short_vec")]
38 pub writable_indexes: Vec<u8>,
39 #[serde(with = "short_vec")]
41 pub readonly_indexes: Vec<u8>,
42}
43
44#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
53#[serde(rename_all = "camelCase")]
54pub struct Message {
55 pub header: MessageHeader,
59
60 #[serde(with = "short_vec")]
62 pub account_keys: Vec<Pubkey>,
63
64 pub recent_blockhash: Hash,
66
67 #[serde(with = "short_vec")]
81 pub instructions: Vec<CompiledInstruction>,
82
83 #[serde(with = "short_vec")]
86 pub address_table_lookups: Vec<MessageAddressTableLookup>,
87}
88
89impl Message {
90 pub fn sanitize(&self, reject_dynamic_program_ids: bool) -> Result<(), SanitizeError> {
92 let num_static_account_keys = self.account_keys.len();
93 if usize::from(self.header.num_required_signatures)
94 .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts))
95 > num_static_account_keys
96 {
97 return Err(SanitizeError::IndexOutOfBounds);
98 }
99
100 if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
102 return Err(SanitizeError::InvalidValue);
103 }
104
105 let num_dynamic_account_keys = {
106 let mut total_lookup_keys: usize = 0;
107 for lookup in &self.address_table_lookups {
108 let num_lookup_indexes = lookup
109 .writable_indexes
110 .len()
111 .saturating_add(lookup.readonly_indexes.len());
112
113 if num_lookup_indexes == 0 {
115 return Err(SanitizeError::InvalidValue);
116 }
117
118 total_lookup_keys = total_lookup_keys.saturating_add(num_lookup_indexes);
119 }
120 total_lookup_keys
121 };
122
123 if num_static_account_keys == 0 {
127 return Err(SanitizeError::InvalidValue);
128 }
129
130 let total_account_keys = num_static_account_keys.saturating_add(num_dynamic_account_keys);
135 if total_account_keys > 256 {
136 return Err(SanitizeError::IndexOutOfBounds);
137 }
138
139 let max_account_ix = total_account_keys
142 .checked_sub(1)
143 .expect("message doesn't contain any account keys");
144
145 let max_program_id_ix = if reject_dynamic_program_ids {
149 num_static_account_keys
152 .checked_sub(1)
153 .expect("message doesn't contain any static account keys")
154 } else {
155 max_account_ix
156 };
157
158 for ci in &self.instructions {
159 if usize::from(ci.program_id_index) > max_program_id_ix {
160 return Err(SanitizeError::IndexOutOfBounds);
161 }
162 if ci.program_id_index == 0 {
164 return Err(SanitizeError::IndexOutOfBounds);
165 }
166 for ai in &ci.accounts {
167 if usize::from(*ai) > max_account_ix {
168 return Err(SanitizeError::IndexOutOfBounds);
169 }
170 }
171 }
172
173 Ok(())
174 }
175}
176
177impl Message {
178 pub fn try_compile(
258 payer: &Pubkey,
259 instructions: &[Instruction],
260 address_lookup_table_accounts: &[AddressLookupTableAccount],
261 recent_blockhash: Hash,
262 ) -> Result<Self, CompileError> {
263 let mut compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
264
265 let mut address_table_lookups = Vec::with_capacity(address_lookup_table_accounts.len());
266 let mut loaded_addresses_list = Vec::with_capacity(address_lookup_table_accounts.len());
267 for lookup_table_account in address_lookup_table_accounts {
268 if let Some((lookup, loaded_addresses)) =
269 compiled_keys.try_extract_table_lookup(lookup_table_account)?
270 {
271 address_table_lookups.push(lookup);
272 loaded_addresses_list.push(loaded_addresses);
273 }
274 }
275
276 let (header, static_keys) = compiled_keys.try_into_message_components()?;
277 let dynamic_keys = loaded_addresses_list.into_iter().collect();
278 let account_keys = AccountKeys::new(&static_keys, Some(&dynamic_keys));
279 let instructions = account_keys.try_compile_instructions(instructions)?;
280
281 Ok(Self {
282 header,
283 account_keys: static_keys,
284 recent_blockhash,
285 instructions,
286 address_table_lookups,
287 })
288 }
289
290 pub fn serialize(&self) -> Vec<u8> {
292 bincode::serialize(&(MESSAGE_VERSION_PREFIX, self)).unwrap()
293 }
294
295 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
297 if let Ok(key_index) = u8::try_from(key_index) {
298 self.instructions
299 .iter()
300 .any(|ix| ix.program_id_index == key_index)
301 } else {
302 false
303 }
304 }
305
306 fn is_writable_index(&self, key_index: usize) -> bool {
309 let header = &self.header;
310 let num_account_keys = self.account_keys.len();
311 let num_signed_accounts = usize::from(header.num_required_signatures);
312 if key_index >= num_account_keys {
313 let loaded_addresses_index = key_index.saturating_sub(num_account_keys);
314 let num_writable_dynamic_addresses = self
315 .address_table_lookups
316 .iter()
317 .map(|lookup| lookup.writable_indexes.len())
318 .sum();
319 loaded_addresses_index < num_writable_dynamic_addresses
320 } else if key_index >= num_signed_accounts {
321 let num_unsigned_accounts = num_account_keys.saturating_sub(num_signed_accounts);
322 let num_writable_unsigned_accounts = num_unsigned_accounts
323 .saturating_sub(usize::from(header.num_readonly_unsigned_accounts));
324 let unsigned_account_index = key_index.saturating_sub(num_signed_accounts);
325 unsigned_account_index < num_writable_unsigned_accounts
326 } else {
327 let num_writable_signed_accounts = num_signed_accounts
328 .saturating_sub(usize::from(header.num_readonly_signed_accounts));
329 key_index < num_writable_signed_accounts
330 }
331 }
332
333 fn is_upgradeable_loader_in_static_keys(&self) -> bool {
335 self.account_keys
336 .iter()
337 .any(|&key| key == bpf_loader_upgradeable::id())
338 }
339
340 pub fn is_maybe_writable(&self, key_index: usize) -> bool {
344 self.is_writable_index(key_index)
345 && !{
346 self.account_keys
348 .get(key_index)
349 .map(is_builtin_key_or_sysvar)
350 .unwrap_or_default()
351 }
352 && !{
353 self.is_key_called_as_program(key_index)
355 && !self.is_upgradeable_loader_in_static_keys()
356 }
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use {
363 super::*,
364 crate::{instruction::AccountMeta, message::VersionedMessage},
365 };
366
367 #[test]
368 fn test_sanitize() {
369 assert!(Message {
370 header: MessageHeader {
371 num_required_signatures: 1,
372 ..MessageHeader::default()
373 },
374 account_keys: vec![Pubkey::new_unique()],
375 ..Message::default()
376 }
377 .sanitize(
378 true, )
380 .is_ok());
381 }
382
383 #[test]
384 fn test_sanitize_with_instruction() {
385 assert!(Message {
386 header: MessageHeader {
387 num_required_signatures: 1,
388 ..MessageHeader::default()
389 },
390 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
391 instructions: vec![CompiledInstruction {
392 program_id_index: 1,
393 accounts: vec![0],
394 data: vec![]
395 }],
396 ..Message::default()
397 }
398 .sanitize(
399 true, )
401 .is_ok());
402 }
403
404 #[test]
405 fn test_sanitize_with_table_lookup() {
406 assert!(Message {
407 header: MessageHeader {
408 num_required_signatures: 1,
409 ..MessageHeader::default()
410 },
411 account_keys: vec![Pubkey::new_unique()],
412 address_table_lookups: vec![MessageAddressTableLookup {
413 account_key: Pubkey::new_unique(),
414 writable_indexes: vec![1, 2, 3],
415 readonly_indexes: vec![0],
416 }],
417 ..Message::default()
418 }
419 .sanitize(
420 true, )
422 .is_ok());
423 }
424
425 #[test]
426 fn test_sanitize_with_table_lookup_and_ix_with_dynamic_program_id() {
427 let message = Message {
428 header: MessageHeader {
429 num_required_signatures: 1,
430 ..MessageHeader::default()
431 },
432 account_keys: vec![Pubkey::new_unique()],
433 address_table_lookups: vec![MessageAddressTableLookup {
434 account_key: Pubkey::new_unique(),
435 writable_indexes: vec![1, 2, 3],
436 readonly_indexes: vec![0],
437 }],
438 instructions: vec![CompiledInstruction {
439 program_id_index: 4,
440 accounts: vec![0, 1, 2, 3],
441 data: vec![],
442 }],
443 ..Message::default()
444 };
445
446 assert!(message.sanitize(
447 false, ).is_ok());
449
450 assert!(message.sanitize(
451 true, ).is_err());
453 }
454
455 #[test]
456 fn test_sanitize_with_table_lookup_and_ix_with_static_program_id() {
457 assert!(Message {
458 header: MessageHeader {
459 num_required_signatures: 1,
460 ..MessageHeader::default()
461 },
462 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
463 address_table_lookups: vec![MessageAddressTableLookup {
464 account_key: Pubkey::new_unique(),
465 writable_indexes: vec![1, 2, 3],
466 readonly_indexes: vec![0],
467 }],
468 instructions: vec![CompiledInstruction {
469 program_id_index: 1,
470 accounts: vec![2, 3, 4, 5],
471 data: vec![]
472 }],
473 ..Message::default()
474 }
475 .sanitize(
476 true, )
478 .is_ok());
479 }
480
481 #[test]
482 fn test_sanitize_without_signer() {
483 assert!(Message {
484 header: MessageHeader::default(),
485 account_keys: vec![Pubkey::new_unique()],
486 ..Message::default()
487 }
488 .sanitize(
489 true, )
491 .is_err());
492 }
493
494 #[test]
495 fn test_sanitize_without_writable_signer() {
496 assert!(Message {
497 header: MessageHeader {
498 num_required_signatures: 1,
499 num_readonly_signed_accounts: 1,
500 ..MessageHeader::default()
501 },
502 account_keys: vec![Pubkey::new_unique()],
503 ..Message::default()
504 }
505 .sanitize(
506 true, )
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 true, )
529 .is_err());
530 }
531
532 #[test]
533 fn test_sanitize_with_max_account_keys() {
534 assert!(Message {
535 header: MessageHeader {
536 num_required_signatures: 1,
537 ..MessageHeader::default()
538 },
539 account_keys: (0..=u8::MAX).map(|_| Pubkey::new_unique()).collect(),
540 ..Message::default()
541 }
542 .sanitize(
543 true, )
545 .is_ok());
546 }
547
548 #[test]
549 fn test_sanitize_with_too_many_account_keys() {
550 assert!(Message {
551 header: MessageHeader {
552 num_required_signatures: 1,
553 ..MessageHeader::default()
554 },
555 account_keys: (0..=256).map(|_| Pubkey::new_unique()).collect(),
556 ..Message::default()
557 }
558 .sanitize(
559 true, )
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![Pubkey::new_unique()],
572 address_table_lookups: vec![MessageAddressTableLookup {
573 account_key: Pubkey::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 true, )
582 .is_ok());
583 }
584
585 #[test]
586 fn test_sanitize_with_too_many_table_loaded_keys() {
587 assert!(Message {
588 header: MessageHeader {
589 num_required_signatures: 1,
590 ..MessageHeader::default()
591 },
592 account_keys: vec![Pubkey::new_unique()],
593 address_table_lookups: vec![MessageAddressTableLookup {
594 account_key: Pubkey::new_unique(),
595 writable_indexes: (0..=255).step_by(2).collect(),
596 readonly_indexes: (1..=255).step_by(2).collect(),
597 }],
598 ..Message::default()
599 }
600 .sanitize(
601 true, )
603 .is_err());
604 }
605
606 #[test]
607 fn test_sanitize_with_invalid_ix_program_id() {
608 let message = Message {
609 header: MessageHeader {
610 num_required_signatures: 1,
611 ..MessageHeader::default()
612 },
613 account_keys: vec![Pubkey::new_unique()],
614 address_table_lookups: vec![MessageAddressTableLookup {
615 account_key: Pubkey::new_unique(),
616 writable_indexes: vec![0],
617 readonly_indexes: vec![],
618 }],
619 instructions: vec![CompiledInstruction {
620 program_id_index: 2,
621 accounts: vec![],
622 data: vec![],
623 }],
624 ..Message::default()
625 };
626
627 assert!(message
628 .sanitize(true )
629 .is_err());
630 assert!(message
631 .sanitize(false )
632 .is_err());
633 }
634
635 #[test]
636 fn test_sanitize_with_invalid_ix_account() {
637 assert!(Message {
638 header: MessageHeader {
639 num_required_signatures: 1,
640 ..MessageHeader::default()
641 },
642 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
643 address_table_lookups: vec![MessageAddressTableLookup {
644 account_key: Pubkey::new_unique(),
645 writable_indexes: vec![],
646 readonly_indexes: vec![0],
647 }],
648 instructions: vec![CompiledInstruction {
649 program_id_index: 1,
650 accounts: vec![3],
651 data: vec![]
652 }],
653 ..Message::default()
654 }
655 .sanitize(
656 true, )
658 .is_err());
659 }
660
661 #[test]
662 fn test_serialize() {
663 let message = Message::default();
664 let versioned_msg = VersionedMessage::V0(message.clone());
665 assert_eq!(message.serialize(), versioned_msg.serialize());
666 }
667
668 #[test]
669 fn test_try_compile() {
670 let mut keys = vec![];
671 keys.resize_with(7, Pubkey::new_unique);
672
673 let payer = keys[0];
674 let program_id = keys[6];
675 let instructions = vec![Instruction {
676 program_id,
677 accounts: vec![
678 AccountMeta::new(keys[1], true),
679 AccountMeta::new_readonly(keys[2], true),
680 AccountMeta::new(keys[3], false),
681 AccountMeta::new(keys[4], false), AccountMeta::new_readonly(keys[5], false), ],
684 data: vec![],
685 }];
686 let address_lookup_table_accounts = vec![
687 AddressLookupTableAccount {
688 key: Pubkey::new_unique(),
689 addresses: vec![keys[4], keys[5], keys[6]],
690 },
691 AddressLookupTableAccount {
692 key: Pubkey::new_unique(),
693 addresses: vec![],
694 },
695 ];
696
697 let recent_blockhash = Hash::new_unique();
698 assert_eq!(
699 Message::try_compile(
700 &payer,
701 &instructions,
702 &address_lookup_table_accounts,
703 recent_blockhash
704 ),
705 Ok(Message {
706 header: MessageHeader {
707 num_required_signatures: 3,
708 num_readonly_signed_accounts: 1,
709 num_readonly_unsigned_accounts: 1
710 },
711 recent_blockhash,
712 account_keys: vec![keys[0], keys[1], keys[2], keys[3], program_id],
713 instructions: vec![CompiledInstruction {
714 program_id_index: 4,
715 accounts: vec![1, 2, 3, 5, 6],
716 data: vec![],
717 },],
718 address_table_lookups: vec![MessageAddressTableLookup {
719 account_key: address_lookup_table_accounts[0].key,
720 writable_indexes: vec![0],
721 readonly_indexes: vec![1],
722 }],
723 })
724 );
725 }
726}