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(_) => 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")]
263pub 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")]
291pub 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")]
310pub 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")]
352pub 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")]
393pub 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")]
411pub 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")]
430pub 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")]
449pub 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")]
471pub 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")]
488pub 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")]
514pub 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 #[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 #[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()); data.extend_from_slice(&42u64.to_le_bytes()); let decoded: UpgradeableLoaderInstruction = wincode::deserialize(&data).unwrap();
684 assert_eq!(
685 decoded,
686 UpgradeableLoaderInstruction::DeployWithMaxDataLen {
687 max_data_len: 42,
688 close_buffer: true, }
690 );
691 }
692
693 #[test]
696 fn legacy_upgrade_decodes_close_buffer_as_true() {
697 let data = 3u32.to_le_bytes(); let decoded: UpgradeableLoaderInstruction = wincode::deserialize(&data).unwrap();
699 assert_eq!(
700 decoded,
701 UpgradeableLoaderInstruction::Upgrade {
702 close_buffer: true, }
704 );
705 }
706
707 #[test]
710 fn legacy_close_decodes_tombstone_as_false() {
711 let data = 5u32.to_le_bytes(); let decoded: UpgradeableLoaderInstruction = wincode::deserialize(&data).unwrap();
713 assert_eq!(
714 decoded,
715 UpgradeableLoaderInstruction::Close {
716 tombstone: false, }
718 );
719 }
720
721 #[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 let mut data = Vec::new();
734 data.extend_from_slice(&2u32.to_le_bytes()); data.extend_from_slice(&42u64.to_le_bytes()); data.push(2);
737 assert_invalid_trailing_bool(&data);
738
739 let mut data = Vec::new();
741 data.extend_from_slice(&3u32.to_le_bytes()); data.push(2);
743 assert_invalid_trailing_bool(&data);
744
745 let mut data = Vec::new();
747 data.extend_from_slice(&5u32.to_le_bytes()); data.push(2);
749 assert_invalid_trailing_bool(&data);
750 }
751}