1use {
2 crate::{
3 hash::Hash,
4 instruction::CompiledInstruction,
5 message::{
6 legacy,
7 v0::{self, LoadedAddresses},
8 AccountKeys, AddressLoader, AddressLoaderError, MessageHeader,
9 SanitizedVersionedMessage, VersionedMessage,
10 },
11 nonce::NONCED_TX_MARKER_IX_INDEX,
12 program_utils::limited_deserialize,
13 pubkey::Pubkey,
14 sanitize::{Sanitize, SanitizeError},
15 solana_program::{system_instruction::SystemInstruction, system_program},
16 sysvar::instructions::{BorrowedAccountMeta, BorrowedInstruction},
17 },
18 std::{borrow::Cow, convert::TryFrom},
19 thiserror::Error,
20};
21
22#[derive(Debug, Clone)]
23pub struct LegacyMessage<'a> {
24 pub message: Cow<'a, legacy::Message>,
26 pub is_writable_account_cache: Vec<bool>,
29}
30
31impl<'a> LegacyMessage<'a> {
32 pub fn new(message: legacy::Message) -> Self {
33 let is_writable_account_cache = message
34 .account_keys
35 .iter()
36 .enumerate()
37 .map(|(i, _key)| message.is_writable(i))
38 .collect::<Vec<_>>();
39 Self {
40 message: Cow::Owned(message),
41 is_writable_account_cache,
42 }
43 }
44
45 pub fn has_duplicates(&self) -> bool {
46 self.message.has_duplicates()
47 }
48
49 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
50 self.message.is_key_called_as_program(key_index)
51 }
52
53 pub fn is_upgradeable_loader_present(&self) -> bool {
55 self.message.is_upgradeable_loader_present()
56 }
57
58 pub fn account_keys(&self) -> AccountKeys {
60 AccountKeys::new(&self.message.account_keys, None)
61 }
62
63 pub fn is_writable(&self, index: usize) -> bool {
64 *self.is_writable_account_cache.get(index).unwrap_or(&false)
65 }
66}
67
68#[derive(Debug, Clone)]
70pub enum SanitizedMessage {
71 Legacy(LegacyMessage<'static>),
73 V0(v0::LoadedMessage<'static>),
75}
76
77#[derive(PartialEq, Debug, Error, Eq, Clone)]
78pub enum SanitizeMessageError {
79 #[error("index out of bounds")]
80 IndexOutOfBounds,
81 #[error("value out of bounds")]
82 ValueOutOfBounds,
83 #[error("invalid value")]
84 InvalidValue,
85 #[error("{0}")]
86 AddressLoaderError(#[from] AddressLoaderError),
87}
88
89impl From<SanitizeError> for SanitizeMessageError {
90 fn from(err: SanitizeError) -> Self {
91 match err {
92 SanitizeError::IndexOutOfBounds => Self::IndexOutOfBounds,
93 SanitizeError::ValueOutOfBounds => Self::ValueOutOfBounds,
94 SanitizeError::InvalidValue => Self::InvalidValue,
95 }
96 }
97}
98
99impl TryFrom<legacy::Message> for SanitizedMessage {
100 type Error = SanitizeMessageError;
101 fn try_from(message: legacy::Message) -> Result<Self, Self::Error> {
102 message.sanitize()?;
103 Ok(Self::Legacy(LegacyMessage::new(message)))
104 }
105}
106
107impl SanitizedMessage {
108 pub fn try_new(
112 sanitized_msg: SanitizedVersionedMessage,
113 address_loader: impl AddressLoader,
114 ) -> Result<Self, SanitizeMessageError> {
115 Ok(match sanitized_msg.message {
116 VersionedMessage::Legacy(message) => {
117 SanitizedMessage::Legacy(LegacyMessage::new(message))
118 }
119 VersionedMessage::V0(message) => {
120 let loaded_addresses =
121 address_loader.load_addresses(&message.address_table_lookups)?;
122 SanitizedMessage::V0(v0::LoadedMessage::new(message, loaded_addresses))
123 }
124 })
125 }
126
127 pub fn has_duplicates(&self) -> bool {
129 match self {
130 SanitizedMessage::Legacy(message) => message.has_duplicates(),
131 SanitizedMessage::V0(message) => message.has_duplicates(),
132 }
133 }
134
135 pub fn header(&self) -> &MessageHeader {
138 match self {
139 Self::Legacy(legacy_message) => &legacy_message.message.header,
140 Self::V0(loaded_msg) => &loaded_msg.message.header,
141 }
142 }
143
144 pub fn legacy_message(&self) -> Option<&legacy::Message> {
146 if let Self::Legacy(legacy_message) = &self {
147 Some(&legacy_message.message)
148 } else {
149 None
150 }
151 }
152
153 pub fn fee_payer(&self) -> &Pubkey {
155 self.account_keys()
156 .get(0)
157 .expect("sanitized message always has non-program fee payer at index 0")
158 }
159
160 pub fn recent_blockhash(&self) -> &Hash {
162 match self {
163 Self::Legacy(legacy_message) => &legacy_message.message.recent_blockhash,
164 Self::V0(loaded_msg) => &loaded_msg.message.recent_blockhash,
165 }
166 }
167
168 pub fn instructions(&self) -> &[CompiledInstruction] {
171 match self {
172 Self::Legacy(legacy_message) => &legacy_message.message.instructions,
173 Self::V0(loaded_msg) => &loaded_msg.message.instructions,
174 }
175 }
176
177 pub fn program_instructions_iter(
180 &self,
181 ) -> impl Iterator<Item = (&Pubkey, &CompiledInstruction)> {
182 self.instructions().iter().map(move |ix| {
183 (
184 self.account_keys()
185 .get(usize::from(ix.program_id_index))
186 .expect("program id index is sanitized"),
187 ix,
188 )
189 })
190 }
191
192 pub fn account_keys(&self) -> AccountKeys {
194 match self {
195 Self::Legacy(message) => message.account_keys(),
196 Self::V0(message) => message.account_keys(),
197 }
198 }
199
200 fn is_key_passed_to_program(&self, key_index: usize) -> bool {
203 if let Ok(key_index) = u8::try_from(key_index) {
204 self.instructions()
205 .iter()
206 .any(|ix| ix.accounts.contains(&key_index))
207 } else {
208 false
209 }
210 }
211
212 pub fn is_invoked(&self, key_index: usize) -> bool {
215 match self {
216 Self::Legacy(message) => message.is_key_called_as_program(key_index),
217 Self::V0(message) => message.is_key_called_as_program(key_index),
218 }
219 }
220
221 pub fn is_non_loader_key(&self, key_index: usize) -> bool {
224 !self.is_invoked(key_index) || self.is_key_passed_to_program(key_index)
225 }
226
227 pub fn is_writable(&self, index: usize) -> bool {
230 match self {
231 Self::Legacy(message) => message.is_writable(index),
232 Self::V0(message) => message.is_writable(index),
233 }
234 }
235
236 pub fn is_signer(&self, index: usize) -> bool {
239 index < usize::from(self.header().num_required_signatures)
240 }
241
242 fn loaded_lookup_table_addresses(&self) -> Option<&LoadedAddresses> {
244 match &self {
245 SanitizedMessage::V0(message) => Some(&message.loaded_addresses),
246 _ => None,
247 }
248 }
249
250 pub fn num_readonly_accounts(&self) -> usize {
252 let loaded_readonly_addresses = self
253 .loaded_lookup_table_addresses()
254 .map(|keys| keys.readonly.len())
255 .unwrap_or_default();
256 loaded_readonly_addresses
257 .saturating_add(usize::from(self.header().num_readonly_signed_accounts))
258 .saturating_add(usize::from(self.header().num_readonly_unsigned_accounts))
259 }
260
261 pub fn decompile_instructions(&self) -> Vec<BorrowedInstruction> {
263 let account_keys = self.account_keys();
264 self.program_instructions_iter()
265 .map(|(program_id, instruction)| {
266 let accounts = instruction
267 .accounts
268 .iter()
269 .map(|account_index| {
270 let account_index = *account_index as usize;
271 BorrowedAccountMeta {
272 is_signer: self.is_signer(account_index),
273 is_writable: self.is_writable(account_index),
274 pubkey: account_keys.get(account_index).unwrap(),
275 }
276 })
277 .collect();
278
279 BorrowedInstruction {
280 accounts,
281 data: &instruction.data,
282 program_id,
283 }
284 })
285 .collect()
286 }
287
288 pub fn is_upgradeable_loader_present(&self) -> bool {
290 match self {
291 Self::Legacy(message) => message.is_upgradeable_loader_present(),
292 Self::V0(message) => message.is_upgradeable_loader_present(),
293 }
294 }
295
296 pub fn get_ix_signers(&self, ix_index: usize) -> impl Iterator<Item = &Pubkey> {
298 self.instructions()
299 .get(ix_index)
300 .into_iter()
301 .flat_map(|ix| {
302 ix.accounts
303 .iter()
304 .copied()
305 .map(usize::from)
306 .filter(|index| self.is_signer(*index))
307 .filter_map(|signer_index| self.account_keys().get(signer_index))
308 })
309 }
310
311 pub fn get_durable_nonce(&self) -> Option<&Pubkey> {
313 self.instructions()
314 .get(NONCED_TX_MARKER_IX_INDEX as usize)
315 .filter(
316 |ix| match self.account_keys().get(ix.program_id_index as usize) {
317 Some(program_id) => system_program::check_id(program_id),
318 _ => false,
319 },
320 )
321 .filter(|ix| {
322 matches!(
323 limited_deserialize(&ix.data, 4 ),
324 Ok(SystemInstruction::AdvanceNonceAccount)
325 )
326 })
327 .and_then(|ix| {
328 ix.accounts.first().and_then(|idx| {
329 let idx = *idx as usize;
330 if !self.is_writable(idx) {
331 None
332 } else {
333 self.account_keys().get(idx)
334 }
335 })
336 })
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use {super::*, crate::message::v0, std::collections::HashSet};
343
344 #[test]
345 fn test_try_from_message() {
346 let legacy_message_with_no_signers = legacy::Message {
347 account_keys: vec![Pubkey::new_unique()],
348 ..legacy::Message::default()
349 };
350
351 assert_eq!(
352 SanitizedMessage::try_from(legacy_message_with_no_signers).err(),
353 Some(SanitizeMessageError::IndexOutOfBounds),
354 );
355 }
356
357 #[test]
358 fn test_is_non_loader_key() {
359 let key0 = Pubkey::new_unique();
360 let key1 = Pubkey::new_unique();
361 let loader_key = Pubkey::new_unique();
362 let instructions = vec![
363 CompiledInstruction::new(1, &(), vec![0]),
364 CompiledInstruction::new(2, &(), vec![0, 1]),
365 ];
366
367 let message = SanitizedMessage::try_from(legacy::Message::new_with_compiled_instructions(
368 1,
369 0,
370 2,
371 vec![key0, key1, loader_key],
372 Hash::default(),
373 instructions,
374 ))
375 .unwrap();
376
377 assert!(message.is_non_loader_key(0));
378 assert!(message.is_non_loader_key(1));
379 assert!(!message.is_non_loader_key(2));
380 }
381
382 #[test]
383 fn test_num_readonly_accounts() {
384 let key0 = Pubkey::new_unique();
385 let key1 = Pubkey::new_unique();
386 let key2 = Pubkey::new_unique();
387 let key3 = Pubkey::new_unique();
388 let key4 = Pubkey::new_unique();
389 let key5 = Pubkey::new_unique();
390
391 let legacy_message = SanitizedMessage::try_from(legacy::Message {
392 header: MessageHeader {
393 num_required_signatures: 2,
394 num_readonly_signed_accounts: 1,
395 num_readonly_unsigned_accounts: 1,
396 },
397 account_keys: vec![key0, key1, key2, key3],
398 ..legacy::Message::default()
399 })
400 .unwrap();
401
402 assert_eq!(legacy_message.num_readonly_accounts(), 2);
403
404 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
405 v0::Message {
406 header: MessageHeader {
407 num_required_signatures: 2,
408 num_readonly_signed_accounts: 1,
409 num_readonly_unsigned_accounts: 1,
410 },
411 account_keys: vec![key0, key1, key2, key3],
412 ..v0::Message::default()
413 },
414 LoadedAddresses {
415 writable: vec![key4],
416 readonly: vec![key5],
417 },
418 ));
419
420 assert_eq!(v0_message.num_readonly_accounts(), 3);
421 }
422
423 #[test]
424 fn test_get_ix_signers() {
425 let signer0 = Pubkey::new_unique();
426 let signer1 = Pubkey::new_unique();
427 let non_signer = Pubkey::new_unique();
428 let loader_key = Pubkey::new_unique();
429 let instructions = vec![
430 CompiledInstruction::new(3, &(), vec![2, 0]),
431 CompiledInstruction::new(3, &(), vec![0, 1]),
432 CompiledInstruction::new(3, &(), vec![0, 0]),
433 ];
434
435 let message = SanitizedMessage::try_from(legacy::Message::new_with_compiled_instructions(
436 2,
437 1,
438 2,
439 vec![signer0, signer1, non_signer, loader_key],
440 Hash::default(),
441 instructions,
442 ))
443 .unwrap();
444
445 assert_eq!(
446 message.get_ix_signers(0).collect::<HashSet<_>>(),
447 HashSet::from_iter([&signer0])
448 );
449 assert_eq!(
450 message.get_ix_signers(1).collect::<HashSet<_>>(),
451 HashSet::from_iter([&signer0, &signer1])
452 );
453 assert_eq!(
454 message.get_ix_signers(2).collect::<HashSet<_>>(),
455 HashSet::from_iter([&signer0])
456 );
457 assert_eq!(
458 message.get_ix_signers(3).collect::<HashSet<_>>(),
459 HashSet::default()
460 );
461 }
462
463 #[test]
464 fn test_is_writable_account_cache() {
465 let key0 = Pubkey::new_unique();
466 let key1 = Pubkey::new_unique();
467 let key2 = Pubkey::new_unique();
468 let key3 = Pubkey::new_unique();
469 let key4 = Pubkey::new_unique();
470 let key5 = Pubkey::new_unique();
471
472 let legacy_message = SanitizedMessage::try_from(legacy::Message {
473 header: MessageHeader {
474 num_required_signatures: 2,
475 num_readonly_signed_accounts: 1,
476 num_readonly_unsigned_accounts: 1,
477 },
478 account_keys: vec![key0, key1, key2, key3],
479 ..legacy::Message::default()
480 })
481 .unwrap();
482 match legacy_message {
483 SanitizedMessage::Legacy(message) => {
484 assert_eq!(
485 message.is_writable_account_cache.len(),
486 message.account_keys().len()
487 );
488 assert!(message.is_writable_account_cache.get(0).unwrap());
489 assert!(!message.is_writable_account_cache.get(1).unwrap());
490 assert!(message.is_writable_account_cache.get(2).unwrap());
491 assert!(!message.is_writable_account_cache.get(3).unwrap());
492 }
493 _ => {
494 panic!("Expect to be SanitizedMessage::LegacyMessage")
495 }
496 }
497
498 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
499 v0::Message {
500 header: MessageHeader {
501 num_required_signatures: 2,
502 num_readonly_signed_accounts: 1,
503 num_readonly_unsigned_accounts: 1,
504 },
505 account_keys: vec![key0, key1, key2, key3],
506 ..v0::Message::default()
507 },
508 LoadedAddresses {
509 writable: vec![key4],
510 readonly: vec![key5],
511 },
512 ));
513 match v0_message {
514 SanitizedMessage::V0(message) => {
515 assert_eq!(
516 message.is_writable_account_cache.len(),
517 message.account_keys().len()
518 );
519 assert!(message.is_writable_account_cache.get(0).unwrap());
520 assert!(!message.is_writable_account_cache.get(1).unwrap());
521 assert!(message.is_writable_account_cache.get(2).unwrap());
522 assert!(!message.is_writable_account_cache.get(3).unwrap());
523 assert!(message.is_writable_account_cache.get(4).unwrap());
524 assert!(!message.is_writable_account_cache.get(5).unwrap());
525 }
526 _ => {
527 panic!("Expect to be SanitizedMessage::V0")
528 }
529 }
530 }
531}