1#[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
19pub 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 InitializeBuffer,
51
52 Write {
58 offset: u32,
60 #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
62 bytes: Vec<u8>,
63 },
64
65 DeployWithMaxDataLen {
107 max_data_len: usize,
109 #[cfg_attr(feature = "wincode", wincode(with = "OptionalTrailingBool<true>"))]
114 close_buffer: bool,
115 },
116
117 Upgrade {
138 #[cfg_attr(feature = "wincode", wincode(with = "OptionalTrailingBool<true>"))]
143 close_buffer: bool,
144 },
145
146 SetAuthority,
157
158 Close {
170 #[cfg_attr(feature = "wincode", wincode(with = "OptionalTrailingBool<false>"))]
176 tombstone: bool,
177 },
178
179 ExtendProgram {
201 additional_bytes: u32,
203 },
204
205 SetAuthorityChecked,
217}
218
219#[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(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")]
267pub 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")]
295pub 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")]
314pub 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")]
356pub 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")]
397pub 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")]
415pub 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")]
434pub 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")]
453pub 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")]
475pub 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")]
492pub 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")]
518pub 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 #[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 #[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()); data.extend_from_slice(&42u64.to_le_bytes()); let decoded: UpgradeableLoaderInstruction = wincode::deserialize(&data).unwrap();
688 assert_eq!(
689 decoded,
690 UpgradeableLoaderInstruction::DeployWithMaxDataLen {
691 max_data_len: 42,
692 close_buffer: true, }
694 );
695 }
696
697 #[test]
700 fn legacy_upgrade_decodes_close_buffer_as_true() {
701 let data = 3u32.to_le_bytes(); let decoded: UpgradeableLoaderInstruction = wincode::deserialize(&data).unwrap();
703 assert_eq!(
704 decoded,
705 UpgradeableLoaderInstruction::Upgrade {
706 close_buffer: true, }
708 );
709 }
710
711 #[test]
714 fn legacy_close_decodes_tombstone_as_false() {
715 let data = 5u32.to_le_bytes(); let decoded: UpgradeableLoaderInstruction = wincode::deserialize(&data).unwrap();
717 assert_eq!(
718 decoded,
719 UpgradeableLoaderInstruction::Close {
720 tombstone: false, }
722 );
723 }
724
725 #[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 let mut data = Vec::new();
738 data.extend_from_slice(&2u32.to_le_bytes()); data.extend_from_slice(&42u64.to_le_bytes()); data.push(2);
741 assert_invalid_trailing_bool(&data);
742
743 let mut data = Vec::new();
745 data.extend_from_slice(&3u32.to_le_bytes()); data.push(2);
747 assert_invalid_trailing_bool(&data);
748
749 let mut data = Vec::new();
751 data.extend_from_slice(&5u32.to_le_bytes()); data.push(2);
753 assert_invalid_trailing_bool(&data);
754 }
755
756 #[test]
759 fn optional_trailing_bool_surfaces_non_eof_read_error() {
760 use wincode::io::{BorrowKind, ReadError, ReadResult, Reader};
761
762 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 let mut prefix = Vec::new();
782 prefix.extend_from_slice(&2u32.to_le_bytes()); prefix.extend_from_slice(&42u64.to_le_bytes()); 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}