waffles_solana_program/message/versions/v0/
mod.rs

1//! A future Solana message format.
2//!
3//! This crate defines two versions of `Message` in their own modules:
4//! [`legacy`] and [`v0`]. `legacy` is the current version as of Solana 1.10.0.
5//! `v0` is a [future message format] that encodes more account keys into a
6//! transaction than the legacy format.
7//!
8//! [`legacy`]: crate::message::legacy
9//! [`v0`]: crate::message::v0
10//! [future message format]: https://docs.solana.com/proposals/versioned-transactions
11
12use 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/// Address table lookups describe an on-chain address lookup table to use
31/// for loading more readonly and writable accounts in a single tx.
32#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
33#[serde(rename_all = "camelCase")]
34pub struct MessageAddressTableLookup {
35    /// Address lookup table account key
36    pub account_key: Pubkey,
37    /// List of indexes used to load writable account addresses
38    #[serde(with = "short_vec")]
39    pub writable_indexes: Vec<u8>,
40    /// List of indexes used to load readonly account addresses
41    #[serde(with = "short_vec")]
42    pub readonly_indexes: Vec<u8>,
43}
44
45/// A Solana transaction message (v0).
46///
47/// This message format supports succinct account loading with
48/// on-chain address lookup tables.
49///
50/// See the [`message`] module documentation for further description.
51///
52/// [`message`]: crate::message
53#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
54#[serde(rename_all = "camelCase")]
55pub struct Message {
56    /// The message header, identifying signed and read-only `account_keys`.
57    /// Header values only describe static `account_keys`, they do not describe
58    /// any additional account keys loaded via address table lookups.
59    pub header: MessageHeader,
60
61    /// List of accounts loaded by this transaction.
62    #[serde(with = "short_vec")]
63    pub account_keys: Vec<Pubkey>,
64
65    /// The blockhash of a recent block.
66    pub recent_blockhash: Hash,
67
68    /// Instructions that invoke a designated program, are executed in sequence,
69    /// and committed in one atomic transaction if all succeed.
70    ///
71    /// # Notes
72    ///
73    /// Program indexes must index into the list of message `account_keys` because
74    /// program id's cannot be dynamically loaded from a lookup table.
75    ///
76    /// Account indexes must index into the list of addresses
77    /// constructed from the concatenation of three key lists:
78    ///   1) message `account_keys`
79    ///   2) ordered list of keys loaded from `writable` lookup table indexes
80    ///   3) ordered list of keys loaded from `readable` lookup table indexes
81    #[serde(with = "short_vec")]
82    pub instructions: Vec<CompiledInstruction>,
83
84    /// List of address table lookups used to load additional accounts
85    /// for this transaction.
86    #[serde(with = "short_vec")]
87    pub address_table_lookups: Vec<MessageAddressTableLookup>,
88}
89
90impl Message {
91    /// Sanitize message fields and compiled instruction indexes
92    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        // there should be at least 1 RW fee-payer account.
102        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                // each lookup table must be used to load at least one account
115                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        // this is redundant with the above sanitization checks which require that:
125        // 1) the header describes at least 1 RW account
126        // 2) the header doesn't describe more account keys than the number of account keys
127        if num_static_account_keys == 0 {
128            return Err(SanitizeError::InvalidValue);
129        }
130
131        // the combined number of static and dynamic account keys must be <= 256
132        // since account indices are encoded as `u8`
133        // Note that this is different from the per-transaction account load cap
134        // as defined in `Bank::get_transaction_account_lock_limit`
135        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        // `expect` is safe because of earlier check that
141        // `num_static_account_keys` is non-zero
142        let max_account_ix = total_account_keys
143            .checked_sub(1)
144            .expect("message doesn't contain any account keys");
145
146        // switch to rejecting program ids loaded from lookup tables so that
147        // static analysis on program instructions can be performed without
148        // loading on-chain data from a bank
149        let max_program_id_ix = if reject_dynamic_program_ids {
150            // `expect` is safe because of earlier check that
151            // `num_static_account_keys` is non-zero
152            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            // A program cannot be a payer.
164            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    /// Create a signable transaction message from a `payer` public key,
180    /// `recent_blockhash`, list of `instructions`, and a list of
181    /// `address_lookup_table_accounts`.
182    ///
183    /// # Examples
184    ///
185    /// This example uses the [`solana_address_lookup_table_program`], [`solana_rpc_client`], [`solana_sdk`], and [`anyhow`] crates.
186    ///
187    /// [`solana_address_lookup_table_program`]: https://docs.rs/solana-address-lookup-table-program
188    /// [`solana_rpc_client`]: https://docs.rs/solana-rpc-client
189    /// [`solana_sdk`]: https://docs.rs/solana-sdk
190    /// [`anyhow`]: https://docs.rs/anyhow
191    ///
192    /// ```
193    /// # use solana_program::example_mocks::{
194    /// #     solana_address_lookup_table_program,
195    /// #     solana_rpc_client,
196    /// #     solana_sdk,
197    /// # };
198    /// # use std::borrow::Cow;
199    /// # use solana_sdk::account::Account;
200    /// use anyhow::Result;
201    /// use solana_address_lookup_table_program::state::AddressLookupTable;
202    /// use solana_rpc_client::rpc_client::RpcClient;
203    /// use solana_sdk::{
204    ///      address_lookup_table_account::AddressLookupTableAccount,
205    ///      instruction::{AccountMeta, Instruction},
206    ///      message::{VersionedMessage, v0},
207    ///      pubkey::Pubkey,
208    ///      signature::{Keypair, Signer},
209    ///      transaction::VersionedTransaction,
210    /// };
211    ///
212    /// fn create_tx_with_address_table_lookup(
213    ///     client: &RpcClient,
214    ///     instruction: Instruction,
215    ///     address_lookup_table_key: Pubkey,
216    ///     payer: &Keypair,
217    /// ) -> Result<VersionedTransaction> {
218    ///     # client.set_get_account_response(address_lookup_table_key, Account {
219    ///     #   lamports: 1,
220    ///     #   data: AddressLookupTable {
221    ///     #     addresses: Cow::Owned(instruction.accounts.iter().map(|meta| meta.pubkey).collect()),
222    ///     #   }.serialize_for_tests().unwrap(),
223    ///     #   owner: solana_address_lookup_table_program::ID,
224    ///     #   executable: false,
225    ///     #   rent_epoch: 1,
226    ///     # });
227    ///     let raw_account = client.get_account(&address_lookup_table_key)?;
228    ///     let address_lookup_table = AddressLookupTable::deserialize(&raw_account.data)?;
229    ///     let address_lookup_table_account = AddressLookupTableAccount {
230    ///         key: address_lookup_table_key,
231    ///         addresses: address_lookup_table.addresses.to_vec(),
232    ///     };
233    ///
234    ///     let blockhash = client.get_latest_blockhash()?;
235    ///     let tx = VersionedTransaction::try_new(
236    ///         VersionedMessage::V0(v0::Message::try_compile(
237    ///             &payer.pubkey(),
238    ///             &[instruction],
239    ///             &[address_lookup_table_account],
240    ///             blockhash,
241    ///         )?),
242    ///         &[payer],
243    ///     )?;
244    ///
245    ///     # assert!(tx.message.address_table_lookups().unwrap().len() > 0);
246    ///     Ok(tx)
247    /// }
248    /// #
249    /// # let client = RpcClient::new(String::new());
250    /// # let payer = Keypair::new();
251    /// # let address_lookup_table_key = Pubkey::new_unique();
252    /// # let instruction = Instruction::new_with_bincode(Pubkey::new_unique(), &(), vec![
253    /// #   AccountMeta::new(Pubkey::new_unique(), false),
254    /// # ]);
255    /// # create_tx_with_address_table_lookup(&client, instruction, address_lookup_table_key, &payer)?;
256    /// # Ok::<(), anyhow::Error>(())
257    /// ```
258    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    /// Serialize this message with a version #0 prefix using bincode encoding.
292    pub fn serialize(&self) -> Vec<u8> {
293        bincode::serialize(&(MESSAGE_VERSION_PREFIX, self)).unwrap()
294    }
295
296    /// Returns true if the account at the specified index is called as a program by an instruction
297    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    /// Returns true if the account at the specified index was requested to be
308    /// writable.  This method should not be used directly.
309    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    /// Returns true if any static account key is the bpf upgradeable loader
335    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    /// Returns true if the account at the specified index was requested as writable.
342    /// Before loading addresses, we can't demote write locks for dynamically loaded
343    /// addresses so this should not be used by the runtime.
344    pub fn is_maybe_writable(&self, key_index: usize) -> bool {
345        self.is_writable_index(key_index)
346            && !{
347                // demote reserved ids
348                self.account_keys
349                    .get(key_index)
350                    .map(is_builtin_key_or_sysvar)
351                    .unwrap_or_default()
352            }
353            && !{
354                // demote program ids
355                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, // require_static_program_ids
380        )
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, // require_static_program_ids
401        )
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, // require_static_program_ids
422        )
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, // require_static_program_ids
449        ).is_ok());
450
451        assert!(message.sanitize(
452            true, // require_static_program_ids
453        ).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, // require_static_program_ids
478        )
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, // require_static_program_ids
491        )
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, // require_static_program_ids
508        )
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, // require_static_program_ids
529        )
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, // require_static_program_ids
545        )
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, // require_static_program_ids
561        )
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, // require_static_program_ids
582        )
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, // require_static_program_ids
603        )
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 /* require_static_program_ids */)
630            .is_err());
631        assert!(message
632            .sanitize(false /* require_static_program_ids */)
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, // require_static_program_ids
658        )
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), // loaded from lut
683                AccountMeta::new_readonly(keys[5], false), // loaded from lut
684            ],
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}