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