cbe_program/message/versions/v0/
mod.rs

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