1use {
2 crate::{
3 fee_calculator::FeeCalculator,
4 hash::Hash,
5 instruction::{CompiledInstruction, Instruction},
6 message::{MappedAddresses, MappedMessage, Message, MessageHeader},
7 pubkey::Pubkey,
8 sanitize::{Sanitize, SanitizeError},
9 secp256k1_program,
10 serialize_utils::{append_slice, append_u16, append_u8},
11 },
12 bitflags::bitflags,
13 std::convert::TryFrom,
14 thiserror::Error,
15};
16
17#[derive(Debug, Clone)]
20pub enum SanitizedMessage {
21 Legacy(Message),
23 V0(MappedMessage),
25}
26
27#[derive(PartialEq, Debug, Error, Eq, Clone)]
28pub enum SanitizeMessageError {
29 #[error("index out of bounds")]
30 IndexOutOfBounds,
31 #[error("value out of bounds")]
32 ValueOutOfBounds,
33 #[error("invalid value")]
34 InvalidValue,
35 #[error("duplicate account key")]
36 DuplicateAccountKey,
37}
38
39impl From<SanitizeError> for SanitizeMessageError {
40 fn from(err: SanitizeError) -> Self {
41 match err {
42 SanitizeError::IndexOutOfBounds => Self::IndexOutOfBounds,
43 SanitizeError::ValueOutOfBounds => Self::ValueOutOfBounds,
44 SanitizeError::InvalidValue => Self::InvalidValue,
45 }
46 }
47}
48
49impl TryFrom<Message> for SanitizedMessage {
50 type Error = SanitizeMessageError;
51 fn try_from(message: Message) -> Result<Self, Self::Error> {
52 message.sanitize()?;
53
54 let sanitized_msg = Self::Legacy(message);
55 if sanitized_msg.has_duplicates() {
56 return Err(SanitizeMessageError::DuplicateAccountKey);
57 }
58
59 Ok(sanitized_msg)
60 }
61}
62
63bitflags! {
64 struct InstructionsSysvarAccountMeta: u8 {
65 const NONE = 0b00000000;
66 const IS_SIGNER = 0b00000001;
67 const IS_WRITABLE = 0b00000010;
68 }
69}
70
71impl SanitizedMessage {
72 pub fn has_duplicates(&self) -> bool {
74 match self {
75 SanitizedMessage::Legacy(message) => message.has_duplicates(),
76 SanitizedMessage::V0(message) => message.has_duplicates(),
77 }
78 }
79
80 pub fn header(&self) -> &MessageHeader {
83 match self {
84 Self::Legacy(message) => &message.header,
85 Self::V0(mapped_msg) => &mapped_msg.message.header,
86 }
87 }
88
89 pub fn legacy_message(&self) -> Option<&Message> {
91 if let Self::Legacy(message) = &self {
92 Some(message)
93 } else {
94 None
95 }
96 }
97
98 pub fn fee_payer(&self) -> &Pubkey {
100 self.get_account_key(0)
101 .expect("sanitized message always has non-program fee payer at index 0")
102 }
103
104 pub fn recent_blockhash(&self) -> &Hash {
106 match self {
107 Self::Legacy(message) => &message.recent_blockhash,
108 Self::V0(mapped_msg) => &mapped_msg.message.recent_blockhash,
109 }
110 }
111
112 pub fn instructions(&self) -> &[CompiledInstruction] {
115 match self {
116 Self::Legacy(message) => &message.instructions,
117 Self::V0(mapped_msg) => &mapped_msg.message.instructions,
118 }
119 }
120
121 pub fn program_instructions_iter(
124 &self,
125 ) -> impl Iterator<Item = (&Pubkey, &CompiledInstruction)> {
126 match self {
127 Self::Legacy(message) => message.instructions.iter(),
128 Self::V0(mapped_msg) => mapped_msg.message.instructions.iter(),
129 }
130 .map(move |ix| {
131 (
132 self.get_account_key(usize::from(ix.program_id_index))
133 .expect("program id index is sanitized"),
134 ix,
135 )
136 })
137 }
138
139 pub fn account_keys_iter(&self) -> Box<dyn Iterator<Item = &Pubkey> + '_> {
141 match self {
142 Self::Legacy(message) => Box::new(message.account_keys.iter()),
143 Self::V0(mapped_msg) => Box::new(mapped_msg.account_keys_iter()),
144 }
145 }
146
147 pub fn account_keys_len(&self) -> usize {
149 match self {
150 Self::Legacy(message) => message.account_keys.len(),
151 Self::V0(mapped_msg) => mapped_msg.account_keys_len(),
152 }
153 }
154
155 pub fn get_account_key(&self, index: usize) -> Option<&Pubkey> {
157 match self {
158 Self::Legacy(message) => message.account_keys.get(index),
159 Self::V0(message) => message.get_account_key(index),
160 }
161 }
162
163 fn is_key_passed_to_program(&self, key_index: usize) -> bool {
166 if let Ok(key_index) = u8::try_from(key_index) {
167 self.instructions()
168 .iter()
169 .any(|ix| ix.accounts.contains(&key_index))
170 } else {
171 false
172 }
173 }
174
175 pub fn is_invoked(&self, key_index: usize) -> bool {
178 match self {
179 Self::Legacy(message) => message.is_key_called_as_program(key_index),
180 Self::V0(message) => message.is_key_called_as_program(key_index),
181 }
182 }
183
184 pub fn is_non_loader_key(&self, key_index: usize) -> bool {
187 !self.is_invoked(key_index) || self.is_key_passed_to_program(key_index)
188 }
189
190 pub fn is_writable(&self, index: usize, demote_program_write_locks: bool) -> bool {
193 match self {
194 Self::Legacy(message) => message.is_writable(index, demote_program_write_locks),
195 Self::V0(message) => message.is_writable(index, demote_program_write_locks),
196 }
197 }
198
199 pub fn is_signer(&self, index: usize) -> bool {
202 index < usize::from(self.header().num_required_signatures)
203 }
204
205 #[allow(clippy::integer_arithmetic)]
219 pub fn serialize_instructions(&self, demote_program_write_locks: bool) -> Vec<u8> {
220 let mut data = Vec::with_capacity(self.instructions().len() * (32 * 2));
222 append_u16(&mut data, self.instructions().len() as u16);
223 for _ in 0..self.instructions().len() {
224 append_u16(&mut data, 0);
225 }
226 for (i, (program_id, instruction)) in self.program_instructions_iter().enumerate() {
227 let start_instruction_offset = data.len() as u16;
228 let start = 2 + (2 * i);
229 data[start..start + 2].copy_from_slice(&start_instruction_offset.to_le_bytes());
230 append_u16(&mut data, instruction.accounts.len() as u16);
231 for account_index in &instruction.accounts {
232 let account_index = *account_index as usize;
233 let is_signer = self.is_signer(account_index);
234 let is_writable = self.is_writable(account_index, demote_program_write_locks);
235 let mut account_meta = InstructionsSysvarAccountMeta::NONE;
236 if is_signer {
237 account_meta |= InstructionsSysvarAccountMeta::IS_SIGNER;
238 }
239 if is_writable {
240 account_meta |= InstructionsSysvarAccountMeta::IS_WRITABLE;
241 }
242 append_u8(&mut data, account_meta.bits());
243 append_slice(
244 &mut data,
245 self.get_account_key(account_index).unwrap().as_ref(),
246 );
247 }
248
249 append_slice(&mut data, program_id.as_ref());
250 append_u16(&mut data, instruction.data.len() as u16);
251 append_slice(&mut data, &instruction.data);
252 }
253 data
254 }
255
256 fn mapped_addresses(&self) -> Option<&MappedAddresses> {
258 match &self {
259 SanitizedMessage::V0(message) => Some(&message.mapped_addresses),
260 _ => None,
261 }
262 }
263
264 pub fn num_readonly_accounts(&self) -> usize {
266 let mapped_readonly_addresses = self
267 .mapped_addresses()
268 .map(|keys| keys.readonly.len())
269 .unwrap_or_default();
270 mapped_readonly_addresses
271 .saturating_add(usize::from(self.header().num_readonly_signed_accounts))
272 .saturating_add(usize::from(self.header().num_readonly_unsigned_accounts))
273 }
274
275 fn try_position(&self, key: &Pubkey) -> Option<u8> {
276 u8::try_from(self.account_keys_iter().position(|k| k == key)?).ok()
277 }
278
279 pub fn try_compile_instruction(&self, ix: &Instruction) -> Option<CompiledInstruction> {
281 let accounts: Vec<_> = ix
282 .accounts
283 .iter()
284 .map(|account_meta| self.try_position(&account_meta.pubkey))
285 .collect::<Option<_>>()?;
286
287 Some(CompiledInstruction {
288 program_id_index: self.try_position(&ix.program_id)?,
289 data: ix.data.clone(),
290 accounts,
291 })
292 }
293
294 pub fn calculate_fee(&self, fee_calculator: &FeeCalculator) -> u64 {
296 let mut num_secp256k1_signatures: u64 = 0;
297 for (program_id, instruction) in self.program_instructions_iter() {
298 if secp256k1_program::check_id(program_id) {
299 if let Some(num_signatures) = instruction.data.get(0) {
300 num_secp256k1_signatures =
301 num_secp256k1_signatures.saturating_add(u64::from(*num_signatures));
302 }
303 }
304 }
305
306 fee_calculator.carats_per_signature.saturating_mul(
307 u64::from(self.header().num_required_signatures)
308 .saturating_add(num_secp256k1_signatures),
309 )
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use crate::{
317 instruction::{AccountMeta, Instruction},
318 message::v0,
319 secp256k1_program, system_instruction,
320 };
321
322 #[test]
323 fn test_try_from_message() {
324 let dupe_key = Pubkey::new_unique();
325 let legacy_message_with_dupes = Message {
326 header: MessageHeader {
327 num_required_signatures: 1,
328 ..MessageHeader::default()
329 },
330 account_keys: vec![dupe_key, dupe_key],
331 ..Message::default()
332 };
333
334 assert_eq!(
335 SanitizedMessage::try_from(legacy_message_with_dupes).err(),
336 Some(SanitizeMessageError::DuplicateAccountKey),
337 );
338
339 let legacy_message_with_no_signers = Message {
340 account_keys: vec![Pubkey::new_unique()],
341 ..Message::default()
342 };
343
344 assert_eq!(
345 SanitizedMessage::try_from(legacy_message_with_no_signers).err(),
346 Some(SanitizeMessageError::IndexOutOfBounds),
347 );
348 }
349
350 #[test]
351 fn test_is_non_loader_key() {
352 let key0 = Pubkey::new_unique();
353 let key1 = Pubkey::new_unique();
354 let loader_key = Pubkey::new_unique();
355 let instructions = vec![
356 CompiledInstruction::new(1, &(), vec![0]),
357 CompiledInstruction::new(2, &(), vec![0, 1]),
358 ];
359
360 let message = SanitizedMessage::try_from(Message::new_with_compiled_instructions(
361 1,
362 0,
363 2,
364 vec![key0, key1, loader_key],
365 Hash::default(),
366 instructions,
367 ))
368 .unwrap();
369
370 assert!(message.is_non_loader_key(0));
371 assert!(message.is_non_loader_key(1));
372 assert!(!message.is_non_loader_key(2));
373 }
374
375 #[test]
376 fn test_num_readonly_accounts() {
377 let key0 = Pubkey::new_unique();
378 let key1 = Pubkey::new_unique();
379 let key2 = Pubkey::new_unique();
380 let key3 = Pubkey::new_unique();
381 let key4 = Pubkey::new_unique();
382 let key5 = Pubkey::new_unique();
383
384 let legacy_message = SanitizedMessage::try_from(Message {
385 header: MessageHeader {
386 num_required_signatures: 2,
387 num_readonly_signed_accounts: 1,
388 num_readonly_unsigned_accounts: 1,
389 },
390 account_keys: vec![key0, key1, key2, key3],
391 ..Message::default()
392 })
393 .unwrap();
394
395 assert_eq!(legacy_message.num_readonly_accounts(), 2);
396
397 let mapped_message = SanitizedMessage::V0(MappedMessage {
398 message: v0::Message {
399 header: MessageHeader {
400 num_required_signatures: 2,
401 num_readonly_signed_accounts: 1,
402 num_readonly_unsigned_accounts: 1,
403 },
404 account_keys: vec![key0, key1, key2, key3],
405 ..v0::Message::default()
406 },
407 mapped_addresses: MappedAddresses {
408 writable: vec![key4],
409 readonly: vec![key5],
410 },
411 });
412
413 assert_eq!(mapped_message.num_readonly_accounts(), 3);
414 }
415
416 #[test]
417 #[allow(deprecated)]
418 fn test_serialize_instructions() {
419 let program_id0 = Pubkey::new_unique();
420 let program_id1 = Pubkey::new_unique();
421 let id0 = Pubkey::new_unique();
422 let id1 = Pubkey::new_unique();
423 let id2 = Pubkey::new_unique();
424 let id3 = Pubkey::new_unique();
425 let instructions = vec![
426 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
427 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]),
428 Instruction::new_with_bincode(
429 program_id1,
430 &0,
431 vec![AccountMeta::new_readonly(id2, false)],
432 ),
433 Instruction::new_with_bincode(
434 program_id1,
435 &0,
436 vec![AccountMeta::new_readonly(id3, true)],
437 ),
438 ];
439
440 let demote_program_write_locks = true;
441 let message = Message::new(&instructions, Some(&id1));
442 let sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap();
443 let serialized = sanitized_message.serialize_instructions(demote_program_write_locks);
444
445 assert_eq!(serialized, message.serialize_instructions());
448
449 for (i, instruction) in instructions.iter().enumerate() {
451 assert_eq!(
452 Message::deserialize_instruction(i, &serialized).unwrap(),
453 *instruction
454 );
455 }
456 }
457
458 #[test]
459 fn test_calculate_fee() {
460 let message =
462 SanitizedMessage::try_from(Message::new(&[], Some(&Pubkey::new_unique()))).unwrap();
463 assert_eq!(message.calculate_fee(&FeeCalculator::default()), 0);
464
465 assert_eq!(message.calculate_fee(&FeeCalculator::new(1)), 1);
467
468 let key0 = Pubkey::new_unique();
470 let key1 = Pubkey::new_unique();
471 let ix0 = system_instruction::transfer(&key0, &key1, 1);
472 let ix1 = system_instruction::transfer(&key1, &key0, 1);
473 let message = SanitizedMessage::try_from(Message::new(&[ix0, ix1], Some(&key0))).unwrap();
474 assert_eq!(message.calculate_fee(&FeeCalculator::new(2)), 4);
475 }
476
477 #[test]
478 fn test_try_compile_instruction() {
479 let key0 = Pubkey::new_unique();
480 let key1 = Pubkey::new_unique();
481 let key2 = Pubkey::new_unique();
482 let program_id = Pubkey::new_unique();
483
484 let valid_instruction = Instruction {
485 program_id,
486 accounts: vec![
487 AccountMeta::new_readonly(key0, false),
488 AccountMeta::new_readonly(key1, false),
489 AccountMeta::new_readonly(key2, false),
490 ],
491 data: vec![],
492 };
493
494 let invalid_program_id_instruction = Instruction {
495 program_id: Pubkey::new_unique(),
496 accounts: vec![
497 AccountMeta::new_readonly(key0, false),
498 AccountMeta::new_readonly(key1, false),
499 AccountMeta::new_readonly(key2, false),
500 ],
501 data: vec![],
502 };
503
504 let invalid_account_key_instruction = Instruction {
505 program_id: Pubkey::new_unique(),
506 accounts: vec![
507 AccountMeta::new_readonly(key0, false),
508 AccountMeta::new_readonly(key1, false),
509 AccountMeta::new_readonly(Pubkey::new_unique(), false),
510 ],
511 data: vec![],
512 };
513
514 let legacy_message = SanitizedMessage::try_from(Message {
515 header: MessageHeader {
516 num_required_signatures: 1,
517 num_readonly_signed_accounts: 0,
518 num_readonly_unsigned_accounts: 0,
519 },
520 account_keys: vec![key0, key1, key2, program_id],
521 ..Message::default()
522 })
523 .unwrap();
524
525 let mapped_message = SanitizedMessage::V0(MappedMessage {
526 message: v0::Message {
527 header: MessageHeader {
528 num_required_signatures: 1,
529 num_readonly_signed_accounts: 0,
530 num_readonly_unsigned_accounts: 0,
531 },
532 account_keys: vec![key0, key1],
533 ..v0::Message::default()
534 },
535 mapped_addresses: MappedAddresses {
536 writable: vec![key2],
537 readonly: vec![program_id],
538 },
539 });
540
541 for message in vec![legacy_message, mapped_message] {
542 assert_eq!(
543 message.try_compile_instruction(&valid_instruction),
544 Some(CompiledInstruction {
545 program_id_index: 3,
546 accounts: vec![0, 1, 2],
547 data: vec![],
548 })
549 );
550
551 assert!(message
552 .try_compile_instruction(&invalid_program_id_instruction)
553 .is_none());
554 assert!(message
555 .try_compile_instruction(&invalid_account_key_instruction)
556 .is_none());
557 }
558 }
559
560 #[test]
561 fn test_calculate_fee_secp256k1() {
562 let key0 = Pubkey::new_unique();
563 let key1 = Pubkey::new_unique();
564 let ix0 = system_instruction::transfer(&key0, &key1, 1);
565
566 let mut secp_instruction1 = Instruction {
567 program_id: secp256k1_program::id(),
568 accounts: vec![],
569 data: vec![],
570 };
571 let mut secp_instruction2 = Instruction {
572 program_id: secp256k1_program::id(),
573 accounts: vec![],
574 data: vec![1],
575 };
576
577 let message = SanitizedMessage::try_from(Message::new(
578 &[
579 ix0.clone(),
580 secp_instruction1.clone(),
581 secp_instruction2.clone(),
582 ],
583 Some(&key0),
584 ))
585 .unwrap();
586 assert_eq!(message.calculate_fee(&FeeCalculator::new(1)), 2);
587
588 secp_instruction1.data = vec![0];
589 secp_instruction2.data = vec![10];
590 let message = SanitizedMessage::try_from(Message::new(
591 &[ix0, secp_instruction1, secp_instruction2],
592 Some(&key0),
593 ))
594 .unwrap();
595 assert_eq!(message.calculate_fee(&FeeCalculator::new(1)), 11);
596 }
597}