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            // A reader that reaches the end without any byte means the trailing
237            // `bool` was simply absent, so fall back to `DEFAULT`. Any other read
238            // error is a genuine failure and must be surfaced.
239            Err(wincode::io::ReadError::ReadSizeLimit(_)) => DEFAULT,
240            Err(err) => return Err(err.into()),
241        };
242        dst.write(value);
243        Ok(())
244    }
245}
246
247#[cfg(feature = "wincode")]
248unsafe impl<C: ConfigCore, const DEFAULT: bool> SchemaWrite<C> for OptionalTrailingBool<DEFAULT> {
249    type Src = bool;
250
251    const TYPE_META: TypeMeta = TypeMeta::Static {
252        size: 1,
253        zero_copy: false,
254    };
255
256    fn size_of(_src: &Self::Src) -> WriteResult<usize> {
257        Ok(1)
258    }
259
260    fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
261        writer.write(&[u8::from(*src)])?;
262        Ok(())
263    }
264}
265
266#[cfg(feature = "wincode")]
267/// Returns the instructions required to initialize a Buffer account.
268pub fn create_buffer(
269    payer_address: &Pubkey,
270    buffer_address: &Pubkey,
271    authority_address: &Pubkey,
272    lamports: u64,
273    program_len: usize,
274) -> Result<Vec<Instruction>, InstructionError> {
275    Ok(vec![
276        system_instruction::create_account(
277            payer_address,
278            buffer_address,
279            lamports,
280            UpgradeableLoaderState::size_of_buffer(program_len) as u64,
281            &id(),
282        ),
283        Instruction::new_with_wincode(
284            id(),
285            &UpgradeableLoaderInstruction::InitializeBuffer,
286            vec![
287                AccountMeta::new(*buffer_address, false),
288                AccountMeta::new_readonly(*authority_address, false),
289            ],
290        ),
291    ])
292}
293
294#[cfg(feature = "wincode")]
295/// Returns the instructions required to write a chunk of program data to a
296/// buffer account.
297pub fn write(
298    buffer_address: &Pubkey,
299    authority_address: &Pubkey,
300    offset: u32,
301    bytes: Vec<u8>,
302) -> Instruction {
303    Instruction::new_with_wincode(
304        id(),
305        &UpgradeableLoaderInstruction::Write { offset, bytes },
306        vec![
307            AccountMeta::new(*buffer_address, false),
308            AccountMeta::new_readonly(*authority_address, true),
309        ],
310    )
311}
312
313#[cfg(feature = "wincode")]
314/// Returns the instructions required to deploy a program with a specified
315/// maximum program length.  The maximum length must be large enough to
316/// accommodate any future upgrades.
317pub fn deploy_with_max_program_len(
318    payer_address: &Pubkey,
319    program_address: &Pubkey,
320    buffer_address: &Pubkey,
321    upgrade_authority_address: &Pubkey,
322    program_lamports: u64,
323    max_data_len: usize,
324    close_buffer: bool,
325) -> Result<Vec<Instruction>, InstructionError> {
326    let programdata_address = get_program_data_address(program_address);
327    Ok(vec![
328        system_instruction::create_account(
329            payer_address,
330            program_address,
331            program_lamports,
332            UpgradeableLoaderState::size_of_program() as u64,
333            &id(),
334        ),
335        Instruction::new_with_wincode(
336            id(),
337            &UpgradeableLoaderInstruction::DeployWithMaxDataLen {
338                max_data_len,
339                close_buffer,
340            },
341            vec![
342                AccountMeta::new(*payer_address, true),
343                AccountMeta::new(programdata_address, false),
344                AccountMeta::new(*program_address, false),
345                AccountMeta::new(*buffer_address, false),
346                AccountMeta::new_readonly(sysvar::rent::id(), false),
347                AccountMeta::new_readonly(sysvar::clock::id(), false),
348                AccountMeta::new_readonly(solana_sdk_ids::system_program::id(), false),
349                AccountMeta::new_readonly(*upgrade_authority_address, true),
350            ],
351        ),
352    ])
353}
354
355#[cfg(feature = "wincode")]
356/// Returns the instructions required to upgrade a program.
357pub fn upgrade(
358    program_address: &Pubkey,
359    buffer_address: &Pubkey,
360    authority_address: &Pubkey,
361    spill_address: &Pubkey,
362    close_buffer: bool,
363) -> Instruction {
364    let programdata_address = get_program_data_address(program_address);
365    Instruction::new_with_wincode(
366        id(),
367        &UpgradeableLoaderInstruction::Upgrade { close_buffer },
368        vec![
369            AccountMeta::new(programdata_address, false),
370            AccountMeta::new(*program_address, false),
371            AccountMeta::new(*buffer_address, false),
372            AccountMeta::new(*spill_address, false),
373            AccountMeta::new_readonly(sysvar::rent::id(), false),
374            AccountMeta::new_readonly(sysvar::clock::id(), false),
375            AccountMeta::new_readonly(*authority_address, true),
376        ],
377    )
378}
379
380pub fn is_upgrade_instruction(instruction_data: &[u8]) -> bool {
381    !instruction_data.is_empty() && 3 == instruction_data[0]
382}
383
384pub fn is_set_authority_instruction(instruction_data: &[u8]) -> bool {
385    !instruction_data.is_empty() && 4 == instruction_data[0]
386}
387
388pub fn is_close_instruction(instruction_data: &[u8]) -> bool {
389    !instruction_data.is_empty() && 5 == instruction_data[0]
390}
391
392pub fn is_set_authority_checked_instruction(instruction_data: &[u8]) -> bool {
393    !instruction_data.is_empty() && 7 == instruction_data[0]
394}
395
396#[cfg(feature = "wincode")]
397/// Returns the instructions required to set a buffers's authority.
398pub fn set_buffer_authority(
399    buffer_address: &Pubkey,
400    current_authority_address: &Pubkey,
401    new_authority_address: &Pubkey,
402) -> Instruction {
403    Instruction::new_with_wincode(
404        id(),
405        &UpgradeableLoaderInstruction::SetAuthority,
406        vec![
407            AccountMeta::new(*buffer_address, false),
408            AccountMeta::new_readonly(*current_authority_address, true),
409            AccountMeta::new_readonly(*new_authority_address, false),
410        ],
411    )
412}
413
414#[cfg(feature = "wincode")]
415/// Returns the instructions required to set a buffers's authority. If using this instruction, the new authority
416/// must sign.
417pub fn set_buffer_authority_checked(
418    buffer_address: &Pubkey,
419    current_authority_address: &Pubkey,
420    new_authority_address: &Pubkey,
421) -> Instruction {
422    Instruction::new_with_wincode(
423        id(),
424        &UpgradeableLoaderInstruction::SetAuthorityChecked,
425        vec![
426            AccountMeta::new(*buffer_address, false),
427            AccountMeta::new_readonly(*current_authority_address, true),
428            AccountMeta::new_readonly(*new_authority_address, true),
429        ],
430    )
431}
432
433#[cfg(feature = "wincode")]
434/// Returns the instructions required to set a program's authority.
435pub fn set_upgrade_authority(
436    program_address: &Pubkey,
437    current_authority_address: &Pubkey,
438    new_authority_address: Option<&Pubkey>,
439) -> Instruction {
440    let programdata_address = get_program_data_address(program_address);
441
442    let mut metas = vec![
443        AccountMeta::new(programdata_address, false),
444        AccountMeta::new_readonly(*current_authority_address, true),
445    ];
446    if let Some(address) = new_authority_address {
447        metas.push(AccountMeta::new_readonly(*address, false));
448    }
449    Instruction::new_with_wincode(id(), &UpgradeableLoaderInstruction::SetAuthority, metas)
450}
451
452#[cfg(feature = "wincode")]
453/// Returns the instructions required to set a program's authority. If using this instruction, the new authority
454/// must sign.
455pub fn set_upgrade_authority_checked(
456    program_address: &Pubkey,
457    current_authority_address: &Pubkey,
458    new_authority_address: &Pubkey,
459) -> Instruction {
460    let programdata_address = get_program_data_address(program_address);
461
462    let metas = vec![
463        AccountMeta::new(programdata_address, false),
464        AccountMeta::new_readonly(*current_authority_address, true),
465        AccountMeta::new_readonly(*new_authority_address, true),
466    ];
467    Instruction::new_with_wincode(
468        id(),
469        &UpgradeableLoaderInstruction::SetAuthorityChecked,
470        metas,
471    )
472}
473
474#[cfg(feature = "wincode")]
475/// Returns the instructions required to close a buffer account
476pub fn close(
477    close_address: &Pubkey,
478    recipient_address: &Pubkey,
479    authority_address: &Pubkey,
480    tombstone: bool,
481) -> Instruction {
482    close_any(
483        close_address,
484        recipient_address,
485        Some(authority_address),
486        None,
487        tombstone,
488    )
489}
490
491#[cfg(feature = "wincode")]
492/// Returns the instructions required to close program, buffer, or uninitialized account
493pub fn close_any(
494    close_address: &Pubkey,
495    recipient_address: &Pubkey,
496    authority_address: Option<&Pubkey>,
497    program_address: Option<&Pubkey>,
498    tombstone: bool,
499) -> Instruction {
500    let mut metas = vec![
501        AccountMeta::new(*close_address, false),
502        AccountMeta::new(*recipient_address, false),
503    ];
504    if let Some(authority_address) = authority_address {
505        metas.push(AccountMeta::new_readonly(*authority_address, true));
506    }
507    if let Some(program_address) = program_address {
508        metas.push(AccountMeta::new(*program_address, false));
509    }
510    Instruction::new_with_wincode(
511        id(),
512        &UpgradeableLoaderInstruction::Close { tombstone },
513        metas,
514    )
515}
516
517#[cfg(feature = "wincode")]
518/// Returns the instruction required to extend the size of a program's
519/// executable data account.
520///
521/// After SIMD-0431 activation, `additional_bytes` must be at least
522/// [`MINIMUM_EXTEND_PROGRAM_BYTES`] unless the account is near the
523/// max permitted data length of an account: 10 MiB.
524pub fn extend_program(
525    program_address: &Pubkey,
526    payer_address: Option<&Pubkey>,
527    additional_bytes: u32,
528) -> Instruction {
529    let program_data_address = get_program_data_address(program_address);
530    let mut metas = vec![
531        AccountMeta::new(program_data_address, false),
532        AccountMeta::new(*program_address, false),
533    ];
534    if let Some(payer_address) = payer_address {
535        metas.push(AccountMeta::new_readonly(
536            solana_sdk_ids::system_program::id(),
537            false,
538        ));
539        metas.push(AccountMeta::new(*payer_address, true));
540    }
541    Instruction::new_with_wincode(
542        id(),
543        &UpgradeableLoaderInstruction::ExtendProgram { additional_bytes },
544        metas,
545    )
546}
547
548#[cfg(all(test, feature = "wincode"))]
549mod tests {
550    use {super::*, test_case::test_case};
551
552    fn assert_is_instruction<F>(
553        is_instruction_fn: F,
554        expected_instruction: UpgradeableLoaderInstruction,
555    ) where
556        F: Fn(&[u8]) -> bool,
557    {
558        let result = is_instruction_fn(
559            &wincode::serialize(&UpgradeableLoaderInstruction::InitializeBuffer).unwrap(),
560        );
561        let expected_result = matches!(
562            expected_instruction,
563            UpgradeableLoaderInstruction::InitializeBuffer
564        );
565        assert_eq!(expected_result, result);
566
567        let result = is_instruction_fn(
568            &wincode::serialize(&UpgradeableLoaderInstruction::Write {
569                offset: 0,
570                bytes: vec![],
571            })
572            .unwrap(),
573        );
574        let expected_result = matches!(
575            expected_instruction,
576            UpgradeableLoaderInstruction::Write {
577                offset: _,
578                bytes: _,
579            }
580        );
581        assert_eq!(expected_result, result);
582
583        let result = is_instruction_fn(
584            &wincode::serialize(&UpgradeableLoaderInstruction::DeployWithMaxDataLen {
585                max_data_len: 0,
586                close_buffer: true,
587            })
588            .unwrap(),
589        );
590        let expected_result = matches!(
591            expected_instruction,
592            UpgradeableLoaderInstruction::DeployWithMaxDataLen { .. }
593        );
594        assert_eq!(expected_result, result);
595
596        let result = is_instruction_fn(
597            &wincode::serialize(&UpgradeableLoaderInstruction::Upgrade { close_buffer: true })
598                .unwrap(),
599        );
600        let expected_result = matches!(
601            expected_instruction,
602            UpgradeableLoaderInstruction::Upgrade { .. }
603        );
604        assert_eq!(expected_result, result);
605
606        let result = is_instruction_fn(
607            &wincode::serialize(&UpgradeableLoaderInstruction::SetAuthority).unwrap(),
608        );
609        let expected_result = matches!(
610            expected_instruction,
611            UpgradeableLoaderInstruction::SetAuthority
612        );
613        assert_eq!(expected_result, result);
614
615        let result = is_instruction_fn(
616            &wincode::serialize(&UpgradeableLoaderInstruction::Close { tombstone: false }).unwrap(),
617        );
618        let expected_result = matches!(
619            expected_instruction,
620            UpgradeableLoaderInstruction::Close { .. }
621        );
622        assert_eq!(expected_result, result);
623    }
624
625    #[test]
626    fn test_is_set_authority_instruction() {
627        assert!(!is_set_authority_instruction(&[]));
628        assert_is_instruction(
629            is_set_authority_instruction,
630            UpgradeableLoaderInstruction::SetAuthority {},
631        );
632    }
633
634    #[test]
635    fn test_is_set_authority_checked_instruction() {
636        assert!(!is_set_authority_checked_instruction(&[]));
637        assert_is_instruction(
638            is_set_authority_checked_instruction,
639            UpgradeableLoaderInstruction::SetAuthorityChecked {},
640        );
641    }
642
643    #[test]
644    fn test_is_upgrade_instruction() {
645        assert!(!is_upgrade_instruction(&[]));
646        assert_is_instruction(
647            is_upgrade_instruction,
648            UpgradeableLoaderInstruction::Upgrade { close_buffer: true },
649        );
650    }
651
652    /// Verify that wincode produces the exact same bytes as bincode for
653    /// every instruction variant, and that both round-trip correctly.
654    #[test_case(UpgradeableLoaderInstruction::InitializeBuffer)]
655    #[test_case(UpgradeableLoaderInstruction::Write { offset: 42, bytes: vec![1, 2, 3, 4, 5] })]
656    #[test_case(UpgradeableLoaderInstruction::Write { offset: 0, bytes: vec![] })]
657    #[test_case(UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len: 1_000_000, close_buffer: true })]
658    #[test_case(UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len: 0, close_buffer: false })]
659    #[test_case(UpgradeableLoaderInstruction::Upgrade { close_buffer: true })]
660    #[test_case(UpgradeableLoaderInstruction::Upgrade { close_buffer: false })]
661    #[test_case(UpgradeableLoaderInstruction::SetAuthority)]
662    #[test_case(UpgradeableLoaderInstruction::Close { tombstone: false })]
663    #[test_case(UpgradeableLoaderInstruction::Close { tombstone: true })]
664    #[test_case(UpgradeableLoaderInstruction::ExtendProgram { additional_bytes: 10_240 })]
665    #[test_case(UpgradeableLoaderInstruction::ExtendProgram { additional_bytes: 0 })]
666    #[test_case(UpgradeableLoaderInstruction::SetAuthorityChecked)]
667    fn wire_compat_bincode_vs_wincode(instr: UpgradeableLoaderInstruction) {
668        let bincode_bytes = bincode::serialize(&instr).unwrap();
669        let wincode_bytes = wincode::serialize(&instr).unwrap();
670        assert_eq!(bincode_bytes, wincode_bytes);
671
672        let from_bincode: UpgradeableLoaderInstruction =
673            bincode::deserialize(&bincode_bytes).unwrap();
674        let from_wincode: UpgradeableLoaderInstruction =
675            wincode::deserialize(&wincode_bytes).unwrap();
676        assert_eq!(from_bincode, instr);
677        assert_eq!(from_wincode, instr);
678    }
679
680    /// Legacy `DeployWithMaxDataLen` payloads omit the trailing
681    /// `close_buffer` byte; wincode must decode these to `close_buffer: true`.
682    #[test]
683    fn legacy_deploy_decodes_close_buffer_as_true() {
684        let mut data = Vec::new();
685        data.extend_from_slice(&2u32.to_le_bytes()); // Discriminator
686        data.extend_from_slice(&42u64.to_le_bytes()); // max_data_len
687        let decoded: UpgradeableLoaderInstruction = wincode::deserialize(&data).unwrap();
688        assert_eq!(
689            decoded,
690            UpgradeableLoaderInstruction::DeployWithMaxDataLen {
691                max_data_len: 42,
692                close_buffer: true, // <-- Default value
693            }
694        );
695    }
696
697    /// Legacy `Upgrade` payloads omit the trailing `close_buffer` byte;
698    /// wincode must decode these to `close_buffer: true`.
699    #[test]
700    fn legacy_upgrade_decodes_close_buffer_as_true() {
701        let data = 3u32.to_le_bytes(); // Discriminator
702        let decoded: UpgradeableLoaderInstruction = wincode::deserialize(&data).unwrap();
703        assert_eq!(
704            decoded,
705            UpgradeableLoaderInstruction::Upgrade {
706                close_buffer: true, // <-- Default value
707            }
708        );
709    }
710
711    /// Legacy `Close` payloads omit the trailing `tombstone` byte; wincode
712    /// must decode these to `tombstone: false`.
713    #[test]
714    fn legacy_close_decodes_tombstone_as_false() {
715        let data = 5u32.to_le_bytes(); // Discriminator
716        let decoded: UpgradeableLoaderInstruction = wincode::deserialize(&data).unwrap();
717        assert_eq!(
718            decoded,
719            UpgradeableLoaderInstruction::Close {
720                tombstone: false, // <-- Default value
721            }
722        );
723    }
724
725    /// `OptionalTrailingBool` must reject a trailing byte that is not `0` or `1`.
726    #[test]
727    fn invalid_optional_trailing_bool_byte_errors() {
728        let assert_invalid_trailing_bool = |data: &[u8]| {
729            let err = wincode::deserialize::<UpgradeableLoaderInstruction>(data).unwrap_err();
730            assert!(
731                matches!(err, wincode::ReadError::InvalidBoolEncoding(2)),
732                "expected InvalidBoolEncoding(2), got {err:?}",
733            );
734        };
735
736        // `DeployWithMaxDataLen`
737        let mut data = Vec::new();
738        data.extend_from_slice(&2u32.to_le_bytes()); // Discriminator
739        data.extend_from_slice(&42u64.to_le_bytes()); // max_data_len
740        data.push(2);
741        assert_invalid_trailing_bool(&data);
742
743        // `Upgrade`
744        let mut data = Vec::new();
745        data.extend_from_slice(&3u32.to_le_bytes()); // Discriminator
746        data.push(2);
747        assert_invalid_trailing_bool(&data);
748
749        // `Close`
750        let mut data = Vec::new();
751        data.extend_from_slice(&5u32.to_le_bytes()); // Discriminator
752        data.push(2);
753        assert_invalid_trailing_bool(&data);
754    }
755
756    /// A read error other than a clean end-of-input (`ReadError::ReadSizeLimit`)
757    /// must be surfaced, not silently treated as a missing trailing `bool`.
758    #[test]
759    fn optional_trailing_bool_surfaces_non_eof_read_error() {
760        use wincode::io::{BorrowKind, ReadError, ReadResult, Reader};
761
762        /// Yields `data`, then fails every further read with an error that is not
763        /// `ReadSizeLimit`, standing in for a genuine reader failure.
764        struct FailAfter<'a> {
765            data: &'a [u8],
766        }
767        unsafe impl<'a> Reader<'a> for FailAfter<'a> {
768            fn copy_into_slice(&mut self, dst: &mut [u8]) -> ReadResult<()> {
769                if dst.len() > self.data.len() {
770                    return Err(ReadError::UnsupportedBorrow(BorrowKind::CallSite));
771                }
772                let (head, rest) = self.data.split_at(dst.len());
773                dst.copy_from_slice(head);
774                self.data = rest;
775                Ok(())
776            }
777        }
778
779        // A `DeployWithMaxDataLen` payload missing its trailing `close_buffer`
780        // byte, where reading that byte fails rather than reaching a clean end.
781        let mut prefix = Vec::new();
782        prefix.extend_from_slice(&2u32.to_le_bytes()); // Discriminator
783        prefix.extend_from_slice(&42u64.to_le_bytes()); // max_data_len
784        let reader = FailAfter { data: &prefix };
785
786        let result = wincode::deserialize_from::<UpgradeableLoaderInstruction>(reader);
787        assert!(
788            result.is_err(),
789            "a non-EOF read error must be surfaced, got {result:?}",
790        );
791    }
792}