Skip to main content

solana_loader_v3_interface/
instruction.rs

1//! Instructions for the upgradable BPF loader.
2
3#[cfg(feature = "wincode")]
4use {
5    crate::{get_program_data_address, state::UpgradeableLoaderState},
6    core::mem::MaybeUninit,
7    solana_instruction::{error::InstructionError, AccountMeta, Instruction},
8    solana_pubkey::Pubkey,
9    solana_sdk_ids::{bpf_loader_upgradeable::id, sysvar},
10    solana_system_interface::instruction as system_instruction,
11    wincode::{
12        config::ConfigCore,
13        error::invalid_bool_encoding,
14        io::{Reader, Writer},
15        ReadResult, SchemaRead, SchemaWrite, TypeMeta, WriteResult,
16    },
17};
18
19/// Minimum number of bytes for an `ExtendProgram` instruction.
20///
21/// After the SIMD-0431 feature gate is activated, `ExtendProgram` will
22/// reject requests smaller than this value, unless the program data
23/// account is within this many bytes of the max permitted data length of
24/// an account: 10 MiB.
25pub const MINIMUM_EXTEND_PROGRAM_BYTES: u32 = 10_240;
26
27#[repr(u8)]
28#[cfg_attr(
29    feature = "serde",
30    derive(serde_derive::Deserialize, serde_derive::Serialize)
31)]
32#[cfg_attr(feature = "wincode", derive(SchemaRead, SchemaWrite))]
33#[derive(Debug, PartialEq, Eq, Clone)]
34pub enum UpgradeableLoaderInstruction {
35    /// Initialize a Buffer account.
36    ///
37    /// A Buffer account is an intermediary that once fully populated is used
38    /// with the `DeployWithMaxDataLen` instruction to populate the program's
39    /// ProgramData account.
40    ///
41    /// The `InitializeBuffer` instruction requires no signers and MUST be
42    /// included within the same Transaction as the system program's
43    /// `CreateAccount` instruction that creates the account being initialized.
44    /// Otherwise another party may initialize the account.
45    ///
46    /// # Account references
47    ///   0. `[writable]` source account to initialize.
48    ///   1. `[]` Buffer authority, optional, if omitted then the buffer will be
49    ///      immutable.
50    InitializeBuffer,
51
52    /// Write program data into a Buffer account.
53    ///
54    /// # Account references
55    ///   0. `[writable]` Buffer account to write program data to.
56    ///   1. `[signer]` Buffer authority
57    Write {
58        /// Offset at which to write the given bytes.
59        offset: u32,
60        /// Serialized program data
61        #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
62        bytes: Vec<u8>,
63    },
64
65    /// Deploy an executable program.
66    ///
67    /// A program consists of a Program and ProgramData account pair.
68    ///   - The Program account's address will serve as the program id for any
69    ///     instructions that execute this program.
70    ///   - The ProgramData account will remain mutable by the loader only and
71    ///     holds the program data and authority information.  The ProgramData
72    ///     account's address is derived from the Program account's address and
73    ///     created by the DeployWithMaxDataLen instruction.
74    ///
75    /// The ProgramData address is derived from the Program account's address as
76    /// follows:
77    ///
78    /// ```
79    /// # use solana_pubkey::Pubkey;
80    /// # use solana_sdk_ids::bpf_loader_upgradeable;
81    /// # let program_address = &[];
82    /// let (program_data_address, _) = Pubkey::find_program_address(
83    ///      &[program_address],
84    ///      &bpf_loader_upgradeable::id()
85    ///  );
86    /// ```
87    ///
88    /// The `DeployWithMaxDataLen` instruction does not require the ProgramData
89    /// account be a signer and therefore MUST be included within the same
90    /// Transaction as the system program's `CreateAccount` instruction that
91    /// creates the Program account. Otherwise another party may initialize the
92    /// account.
93    ///
94    /// # Account references
95    ///   0. `[writable, signer]` The payer account that will pay to create the
96    ///      ProgramData account.
97    ///   1. `[writable]` The uninitialized ProgramData account.
98    ///   2. `[writable]` The uninitialized Program account.
99    ///   3. `[writable]` The Buffer account where the program data has been
100    ///      written.  The buffer account's authority must match the program's
101    ///      authority
102    ///   4. `[]` Rent sysvar.
103    ///   5. `[]` Clock sysvar.
104    ///   6. `[]` System program (`solana_sdk_ids::system_program::id()`).
105    ///   7. `[signer]` The program's authority
106    DeployWithMaxDataLen {
107        /// Maximum length that the program can be upgraded to.
108        max_data_len: usize,
109        /// SIMD-0430: Whether to close the buffer account after deployment.
110        ///
111        /// Optional on the wire: when the trailing byte is absent, this
112        /// decodes to `true`.
113        #[cfg_attr(feature = "wincode", wincode(with = "OptionalTrailingBool<true>"))]
114        close_buffer: bool,
115    },
116
117    /// Upgrade a program.
118    ///
119    /// A program can be updated as long as the program's authority has not been
120    /// set to `None`.
121    ///
122    /// The Buffer account must contain sufficient lamports to fund the
123    /// ProgramData account to be rent-exempt, any additional lamports left over
124    /// will be transferred to the spill account, leaving the Buffer account
125    /// balance at zero.
126    ///
127    /// # Account references
128    ///   0. `[writable]` The ProgramData account.
129    ///   1. `[writable]` The Program account.
130    ///   2. `[writable]` The Buffer account where the program data has been
131    ///      written.  The buffer account's authority must match the program's
132    ///      authority
133    ///   3. `[writable]` The spill account.
134    ///   4. `[]` Rent sysvar.
135    ///   5. `[]` Clock sysvar.
136    ///   6. `[signer]` The program's authority.
137    Upgrade {
138        /// SIMD-0430: Whether to close the buffer account after upgrade.
139        ///
140        /// Optional on the wire: when the trailing byte is absent, this
141        /// decodes to `true`.
142        #[cfg_attr(feature = "wincode", wincode(with = "OptionalTrailingBool<true>"))]
143        close_buffer: bool,
144    },
145
146    /// Set a new authority that is allowed to write the buffer or upgrade the
147    /// program.  To permanently make the buffer immutable or disable program
148    /// updates omit the new authority.
149    ///
150    /// # Account references
151    ///   0. `[writable]` The Buffer or ProgramData account to change the
152    ///      authority of.
153    ///   1. `[signer]` The current authority.
154    ///   2. `[]` The new authority, optional, if omitted then the program will
155    ///      not be upgradeable.
156    SetAuthority,
157
158    /// Closes an account owned by the upgradeable loader of all lamports and
159    /// withdraws all the lamports
160    ///
161    /// # Account references
162    ///   0. `[writable]` The account to close, if closing a program must be the
163    ///      ProgramData account.
164    ///   1. `[writable]` The account to deposit the closed account's lamports.
165    ///   2. `[signer]` The account's authority, Optional, required for
166    ///      initialized accounts.
167    ///   3. `[writable]` The associated Program account if the account to close
168    ///      is a ProgramData account.
169    Close {
170        /// SIMD-0432: Whether to tombstone the program account instead of
171        /// reclaiming its address.
172        ///
173        /// Optional on the wire: when the trailing byte is absent, this
174        /// decodes to `false`.
175        #[cfg_attr(feature = "wincode", wincode(with = "OptionalTrailingBool<false>"))]
176        tombstone: bool,
177    },
178
179    /// Extend a program's ProgramData account by the specified number of bytes.
180    /// Only upgradeable programs can be extended.
181    ///
182    /// After the SIMD-0431 feature gate is activated, `additional_bytes`
183    /// must be at least [`MINIMUM_EXTEND_PROGRAM_BYTES`] (10 KiB).
184    /// The minimum does not apply when the program data account is
185    /// within [`MINIMUM_EXTEND_PROGRAM_BYTES`] of the max permitted
186    /// data length of an account: 10 MiB.
187    ///
188    /// The payer account must contain sufficient lamports to fund the
189    /// ProgramData account to be rent-exempt. If the ProgramData account
190    /// balance is already sufficient to cover the rent exemption cost
191    /// for the extended bytes, the payer account is not required.
192    ///
193    /// # Account references
194    ///   0. `[writable]` The ProgramData account.
195    ///   1. `[writable]` The ProgramData account's associated Program account.
196    ///   2. `[]` System program (`solana_sdk::system_program::id()`), optional, used to transfer
197    ///      lamports from the payer to the ProgramData account.
198    ///   3. `[writable, signer]` The payer account, optional, that will pay
199    ///      necessary rent exemption costs for the increased storage size.
200    ExtendProgram {
201        /// Number of bytes to extend the program data.
202        additional_bytes: u32,
203    },
204
205    /// Set a new authority that is allowed to write the buffer or upgrade the
206    /// program.
207    ///
208    /// This instruction differs from SetAuthority in that the new authority is a
209    /// required signer.
210    ///
211    /// # Account references
212    ///   0. `[writable]` The Buffer or ProgramData account to change the
213    ///      authority of.
214    ///   1. `[signer]` The current authority.
215    ///   2. `[signer]` The new authority.
216    SetAuthorityChecked,
217}
218
219/// A wincode schema for a `bool` that may be absent from the end of the
220/// wire payload. On write, the byte is always emitted. On read, an
221/// exhausted reader yields `DEFAULT`.
222#[cfg(feature = "wincode")]
223pub struct OptionalTrailingBool<const DEFAULT: bool>;
224
225#[cfg(feature = "wincode")]
226unsafe impl<'de, C: ConfigCore, const DEFAULT: bool> SchemaRead<'de, C>
227    for OptionalTrailingBool<DEFAULT>
228{
229    type Dst = bool;
230
231    fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
232        let value = match reader.take_byte() {
233            Ok(0) => false,
234            Ok(1) => true,
235            Ok(byte) => return Err(invalid_bool_encoding(byte)),
236            Err(_) => DEFAULT,
237        };
238        dst.write(value);
239        Ok(())
240    }
241}
242
243#[cfg(feature = "wincode")]
244unsafe impl<C: ConfigCore, const DEFAULT: bool> SchemaWrite<C> for OptionalTrailingBool<DEFAULT> {
245    type Src = bool;
246
247    const TYPE_META: TypeMeta = TypeMeta::Static {
248        size: 1,
249        zero_copy: false,
250    };
251
252    fn size_of(_src: &Self::Src) -> WriteResult<usize> {
253        Ok(1)
254    }
255
256    fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
257        writer.write(&[u8::from(*src)])?;
258        Ok(())
259    }
260}
261
262#[cfg(feature = "wincode")]
263/// Returns the instructions required to initialize a Buffer account.
264pub fn create_buffer(
265    payer_address: &Pubkey,
266    buffer_address: &Pubkey,
267    authority_address: &Pubkey,
268    lamports: u64,
269    program_len: usize,
270) -> Result<Vec<Instruction>, InstructionError> {
271    Ok(vec![
272        system_instruction::create_account(
273            payer_address,
274            buffer_address,
275            lamports,
276            UpgradeableLoaderState::size_of_buffer(program_len) as u64,
277            &id(),
278        ),
279        Instruction::new_with_wincode(
280            id(),
281            &UpgradeableLoaderInstruction::InitializeBuffer,
282            vec![
283                AccountMeta::new(*buffer_address, false),
284                AccountMeta::new_readonly(*authority_address, false),
285            ],
286        ),
287    ])
288}
289
290#[cfg(feature = "wincode")]
291/// Returns the instructions required to write a chunk of program data to a
292/// buffer account.
293pub fn write(
294    buffer_address: &Pubkey,
295    authority_address: &Pubkey,
296    offset: u32,
297    bytes: Vec<u8>,
298) -> Instruction {
299    Instruction::new_with_wincode(
300        id(),
301        &UpgradeableLoaderInstruction::Write { offset, bytes },
302        vec![
303            AccountMeta::new(*buffer_address, false),
304            AccountMeta::new_readonly(*authority_address, true),
305        ],
306    )
307}
308
309#[cfg(feature = "wincode")]
310/// Returns the instructions required to deploy a program with a specified
311/// maximum program length.  The maximum length must be large enough to
312/// accommodate any future upgrades.
313pub fn deploy_with_max_program_len(
314    payer_address: &Pubkey,
315    program_address: &Pubkey,
316    buffer_address: &Pubkey,
317    upgrade_authority_address: &Pubkey,
318    program_lamports: u64,
319    max_data_len: usize,
320    close_buffer: bool,
321) -> Result<Vec<Instruction>, InstructionError> {
322    let programdata_address = get_program_data_address(program_address);
323    Ok(vec![
324        system_instruction::create_account(
325            payer_address,
326            program_address,
327            program_lamports,
328            UpgradeableLoaderState::size_of_program() as u64,
329            &id(),
330        ),
331        Instruction::new_with_wincode(
332            id(),
333            &UpgradeableLoaderInstruction::DeployWithMaxDataLen {
334                max_data_len,
335                close_buffer,
336            },
337            vec![
338                AccountMeta::new(*payer_address, true),
339                AccountMeta::new(programdata_address, false),
340                AccountMeta::new(*program_address, false),
341                AccountMeta::new(*buffer_address, false),
342                AccountMeta::new_readonly(sysvar::rent::id(), false),
343                AccountMeta::new_readonly(sysvar::clock::id(), false),
344                AccountMeta::new_readonly(solana_sdk_ids::system_program::id(), false),
345                AccountMeta::new_readonly(*upgrade_authority_address, true),
346            ],
347        ),
348    ])
349}
350
351#[cfg(feature = "wincode")]
352/// Returns the instructions required to upgrade a program.
353pub fn upgrade(
354    program_address: &Pubkey,
355    buffer_address: &Pubkey,
356    authority_address: &Pubkey,
357    spill_address: &Pubkey,
358    close_buffer: bool,
359) -> Instruction {
360    let programdata_address = get_program_data_address(program_address);
361    Instruction::new_with_wincode(
362        id(),
363        &UpgradeableLoaderInstruction::Upgrade { close_buffer },
364        vec![
365            AccountMeta::new(programdata_address, false),
366            AccountMeta::new(*program_address, false),
367            AccountMeta::new(*buffer_address, false),
368            AccountMeta::new(*spill_address, false),
369            AccountMeta::new_readonly(sysvar::rent::id(), false),
370            AccountMeta::new_readonly(sysvar::clock::id(), false),
371            AccountMeta::new_readonly(*authority_address, true),
372        ],
373    )
374}
375
376pub fn is_upgrade_instruction(instruction_data: &[u8]) -> bool {
377    !instruction_data.is_empty() && 3 == instruction_data[0]
378}
379
380pub fn is_set_authority_instruction(instruction_data: &[u8]) -> bool {
381    !instruction_data.is_empty() && 4 == instruction_data[0]
382}
383
384pub fn is_close_instruction(instruction_data: &[u8]) -> bool {
385    !instruction_data.is_empty() && 5 == instruction_data[0]
386}
387
388pub fn is_set_authority_checked_instruction(instruction_data: &[u8]) -> bool {
389    !instruction_data.is_empty() && 7 == instruction_data[0]
390}
391
392#[cfg(feature = "wincode")]
393/// Returns the instructions required to set a buffers's authority.
394pub fn set_buffer_authority(
395    buffer_address: &Pubkey,
396    current_authority_address: &Pubkey,
397    new_authority_address: &Pubkey,
398) -> Instruction {
399    Instruction::new_with_wincode(
400        id(),
401        &UpgradeableLoaderInstruction::SetAuthority,
402        vec![
403            AccountMeta::new(*buffer_address, false),
404            AccountMeta::new_readonly(*current_authority_address, true),
405            AccountMeta::new_readonly(*new_authority_address, false),
406        ],
407    )
408}
409
410#[cfg(feature = "wincode")]
411/// Returns the instructions required to set a buffers's authority. If using this instruction, the new authority
412/// must sign.
413pub fn set_buffer_authority_checked(
414    buffer_address: &Pubkey,
415    current_authority_address: &Pubkey,
416    new_authority_address: &Pubkey,
417) -> Instruction {
418    Instruction::new_with_wincode(
419        id(),
420        &UpgradeableLoaderInstruction::SetAuthorityChecked,
421        vec![
422            AccountMeta::new(*buffer_address, false),
423            AccountMeta::new_readonly(*current_authority_address, true),
424            AccountMeta::new_readonly(*new_authority_address, true),
425        ],
426    )
427}
428
429#[cfg(feature = "wincode")]
430/// Returns the instructions required to set a program's authority.
431pub fn set_upgrade_authority(
432    program_address: &Pubkey,
433    current_authority_address: &Pubkey,
434    new_authority_address: Option<&Pubkey>,
435) -> Instruction {
436    let programdata_address = get_program_data_address(program_address);
437
438    let mut metas = vec![
439        AccountMeta::new(programdata_address, false),
440        AccountMeta::new_readonly(*current_authority_address, true),
441    ];
442    if let Some(address) = new_authority_address {
443        metas.push(AccountMeta::new_readonly(*address, false));
444    }
445    Instruction::new_with_wincode(id(), &UpgradeableLoaderInstruction::SetAuthority, metas)
446}
447
448#[cfg(feature = "wincode")]
449/// Returns the instructions required to set a program's authority. If using this instruction, the new authority
450/// must sign.
451pub fn set_upgrade_authority_checked(
452    program_address: &Pubkey,
453    current_authority_address: &Pubkey,
454    new_authority_address: &Pubkey,
455) -> Instruction {
456    let programdata_address = get_program_data_address(program_address);
457
458    let metas = vec![
459        AccountMeta::new(programdata_address, false),
460        AccountMeta::new_readonly(*current_authority_address, true),
461        AccountMeta::new_readonly(*new_authority_address, true),
462    ];
463    Instruction::new_with_wincode(
464        id(),
465        &UpgradeableLoaderInstruction::SetAuthorityChecked,
466        metas,
467    )
468}
469
470#[cfg(feature = "wincode")]
471/// Returns the instructions required to close a buffer account
472pub fn close(
473    close_address: &Pubkey,
474    recipient_address: &Pubkey,
475    authority_address: &Pubkey,
476    tombstone: bool,
477) -> Instruction {
478    close_any(
479        close_address,
480        recipient_address,
481        Some(authority_address),
482        None,
483        tombstone,
484    )
485}
486
487#[cfg(feature = "wincode")]
488/// Returns the instructions required to close program, buffer, or uninitialized account
489pub fn close_any(
490    close_address: &Pubkey,
491    recipient_address: &Pubkey,
492    authority_address: Option<&Pubkey>,
493    program_address: Option<&Pubkey>,
494    tombstone: bool,
495) -> Instruction {
496    let mut metas = vec![
497        AccountMeta::new(*close_address, false),
498        AccountMeta::new(*recipient_address, false),
499    ];
500    if let Some(authority_address) = authority_address {
501        metas.push(AccountMeta::new_readonly(*authority_address, true));
502    }
503    if let Some(program_address) = program_address {
504        metas.push(AccountMeta::new(*program_address, false));
505    }
506    Instruction::new_with_wincode(
507        id(),
508        &UpgradeableLoaderInstruction::Close { tombstone },
509        metas,
510    )
511}
512
513#[cfg(feature = "wincode")]
514/// Returns the instruction required to extend the size of a program's
515/// executable data account.
516///
517/// After SIMD-0431 activation, `additional_bytes` must be at least
518/// [`MINIMUM_EXTEND_PROGRAM_BYTES`] unless the account is near the
519/// max permitted data length of an account: 10 MiB.
520pub fn extend_program(
521    program_address: &Pubkey,
522    payer_address: Option<&Pubkey>,
523    additional_bytes: u32,
524) -> Instruction {
525    let program_data_address = get_program_data_address(program_address);
526    let mut metas = vec![
527        AccountMeta::new(program_data_address, false),
528        AccountMeta::new(*program_address, false),
529    ];
530    if let Some(payer_address) = payer_address {
531        metas.push(AccountMeta::new_readonly(
532            solana_sdk_ids::system_program::id(),
533            false,
534        ));
535        metas.push(AccountMeta::new(*payer_address, true));
536    }
537    Instruction::new_with_wincode(
538        id(),
539        &UpgradeableLoaderInstruction::ExtendProgram { additional_bytes },
540        metas,
541    )
542}
543
544#[cfg(all(test, feature = "wincode"))]
545mod tests {
546    use {super::*, test_case::test_case};
547
548    fn assert_is_instruction<F>(
549        is_instruction_fn: F,
550        expected_instruction: UpgradeableLoaderInstruction,
551    ) where
552        F: Fn(&[u8]) -> bool,
553    {
554        let result = is_instruction_fn(
555            &wincode::serialize(&UpgradeableLoaderInstruction::InitializeBuffer).unwrap(),
556        );
557        let expected_result = matches!(
558            expected_instruction,
559            UpgradeableLoaderInstruction::InitializeBuffer
560        );
561        assert_eq!(expected_result, result);
562
563        let result = is_instruction_fn(
564            &wincode::serialize(&UpgradeableLoaderInstruction::Write {
565                offset: 0,
566                bytes: vec![],
567            })
568            .unwrap(),
569        );
570        let expected_result = matches!(
571            expected_instruction,
572            UpgradeableLoaderInstruction::Write {
573                offset: _,
574                bytes: _,
575            }
576        );
577        assert_eq!(expected_result, result);
578
579        let result = is_instruction_fn(
580            &wincode::serialize(&UpgradeableLoaderInstruction::DeployWithMaxDataLen {
581                max_data_len: 0,
582                close_buffer: true,
583            })
584            .unwrap(),
585        );
586        let expected_result = matches!(
587            expected_instruction,
588            UpgradeableLoaderInstruction::DeployWithMaxDataLen { .. }
589        );
590        assert_eq!(expected_result, result);
591
592        let result = is_instruction_fn(
593            &wincode::serialize(&UpgradeableLoaderInstruction::Upgrade { close_buffer: true })
594                .unwrap(),
595        );
596        let expected_result = matches!(
597            expected_instruction,
598            UpgradeableLoaderInstruction::Upgrade { .. }
599        );
600        assert_eq!(expected_result, result);
601
602        let result = is_instruction_fn(
603            &wincode::serialize(&UpgradeableLoaderInstruction::SetAuthority).unwrap(),
604        );
605        let expected_result = matches!(
606            expected_instruction,
607            UpgradeableLoaderInstruction::SetAuthority
608        );
609        assert_eq!(expected_result, result);
610
611        let result = is_instruction_fn(
612            &wincode::serialize(&UpgradeableLoaderInstruction::Close { tombstone: false }).unwrap(),
613        );
614        let expected_result = matches!(
615            expected_instruction,
616            UpgradeableLoaderInstruction::Close { .. }
617        );
618        assert_eq!(expected_result, result);
619    }
620
621    #[test]
622    fn test_is_set_authority_instruction() {
623        assert!(!is_set_authority_instruction(&[]));
624        assert_is_instruction(
625            is_set_authority_instruction,
626            UpgradeableLoaderInstruction::SetAuthority {},
627        );
628    }
629
630    #[test]
631    fn test_is_set_authority_checked_instruction() {
632        assert!(!is_set_authority_checked_instruction(&[]));
633        assert_is_instruction(
634            is_set_authority_checked_instruction,
635            UpgradeableLoaderInstruction::SetAuthorityChecked {},
636        );
637    }
638
639    #[test]
640    fn test_is_upgrade_instruction() {
641        assert!(!is_upgrade_instruction(&[]));
642        assert_is_instruction(
643            is_upgrade_instruction,
644            UpgradeableLoaderInstruction::Upgrade { close_buffer: true },
645        );
646    }
647
648    /// Verify that wincode produces the exact same bytes as bincode for
649    /// every instruction variant, and that both round-trip correctly.
650    #[test_case(UpgradeableLoaderInstruction::InitializeBuffer)]
651    #[test_case(UpgradeableLoaderInstruction::Write { offset: 42, bytes: vec![1, 2, 3, 4, 5] })]
652    #[test_case(UpgradeableLoaderInstruction::Write { offset: 0, bytes: vec![] })]
653    #[test_case(UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len: 1_000_000, close_buffer: true })]
654    #[test_case(UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len: 0, close_buffer: false })]
655    #[test_case(UpgradeableLoaderInstruction::Upgrade { close_buffer: true })]
656    #[test_case(UpgradeableLoaderInstruction::Upgrade { close_buffer: false })]
657    #[test_case(UpgradeableLoaderInstruction::SetAuthority)]
658    #[test_case(UpgradeableLoaderInstruction::Close { tombstone: false })]
659    #[test_case(UpgradeableLoaderInstruction::Close { tombstone: true })]
660    #[test_case(UpgradeableLoaderInstruction::ExtendProgram { additional_bytes: 10_240 })]
661    #[test_case(UpgradeableLoaderInstruction::ExtendProgram { additional_bytes: 0 })]
662    #[test_case(UpgradeableLoaderInstruction::SetAuthorityChecked)]
663    fn wire_compat_bincode_vs_wincode(instr: UpgradeableLoaderInstruction) {
664        let bincode_bytes = bincode::serialize(&instr).unwrap();
665        let wincode_bytes = wincode::serialize(&instr).unwrap();
666        assert_eq!(bincode_bytes, wincode_bytes);
667
668        let from_bincode: UpgradeableLoaderInstruction =
669            bincode::deserialize(&bincode_bytes).unwrap();
670        let from_wincode: UpgradeableLoaderInstruction =
671            wincode::deserialize(&wincode_bytes).unwrap();
672        assert_eq!(from_bincode, instr);
673        assert_eq!(from_wincode, instr);
674    }
675
676    /// Legacy `DeployWithMaxDataLen` payloads omit the trailing
677    /// `close_buffer` byte; wincode must decode these to `close_buffer: true`.
678    #[test]
679    fn legacy_deploy_decodes_close_buffer_as_true() {
680        let mut data = Vec::new();
681        data.extend_from_slice(&2u32.to_le_bytes()); // Discriminator
682        data.extend_from_slice(&42u64.to_le_bytes()); // max_data_len
683        let decoded: UpgradeableLoaderInstruction = wincode::deserialize(&data).unwrap();
684        assert_eq!(
685            decoded,
686            UpgradeableLoaderInstruction::DeployWithMaxDataLen {
687                max_data_len: 42,
688                close_buffer: true, // <-- Default value
689            }
690        );
691    }
692
693    /// Legacy `Upgrade` payloads omit the trailing `close_buffer` byte;
694    /// wincode must decode these to `close_buffer: true`.
695    #[test]
696    fn legacy_upgrade_decodes_close_buffer_as_true() {
697        let data = 3u32.to_le_bytes(); // Discriminator
698        let decoded: UpgradeableLoaderInstruction = wincode::deserialize(&data).unwrap();
699        assert_eq!(
700            decoded,
701            UpgradeableLoaderInstruction::Upgrade {
702                close_buffer: true, // <-- Default value
703            }
704        );
705    }
706
707    /// Legacy `Close` payloads omit the trailing `tombstone` byte; wincode
708    /// must decode these to `tombstone: false`.
709    #[test]
710    fn legacy_close_decodes_tombstone_as_false() {
711        let data = 5u32.to_le_bytes(); // Discriminator
712        let decoded: UpgradeableLoaderInstruction = wincode::deserialize(&data).unwrap();
713        assert_eq!(
714            decoded,
715            UpgradeableLoaderInstruction::Close {
716                tombstone: false, // <-- Default value
717            }
718        );
719    }
720
721    /// `OptionalTrailingBool` must reject a trailing byte that is not `0` or `1`.
722    #[test]
723    fn invalid_optional_trailing_bool_byte_errors() {
724        let assert_invalid_trailing_bool = |data: &[u8]| {
725            let err = wincode::deserialize::<UpgradeableLoaderInstruction>(data).unwrap_err();
726            assert!(
727                matches!(err, wincode::ReadError::InvalidBoolEncoding(2)),
728                "expected InvalidBoolEncoding(2), got {err:?}",
729            );
730        };
731
732        // `DeployWithMaxDataLen`
733        let mut data = Vec::new();
734        data.extend_from_slice(&2u32.to_le_bytes()); // Discriminator
735        data.extend_from_slice(&42u64.to_le_bytes()); // max_data_len
736        data.push(2);
737        assert_invalid_trailing_bool(&data);
738
739        // `Upgrade`
740        let mut data = Vec::new();
741        data.extend_from_slice(&3u32.to_le_bytes()); // Discriminator
742        data.push(2);
743        assert_invalid_trailing_bool(&data);
744
745        // `Close`
746        let mut data = Vec::new();
747        data.extend_from_slice(&5u32.to_le_bytes()); // Discriminator
748        data.push(2);
749        assert_invalid_trailing_bool(&data);
750    }
751}