#[cfg(feature = "wincode")]
use {
crate::{get_program_data_address, state::UpgradeableLoaderState},
core::mem::MaybeUninit,
solana_instruction::{error::InstructionError, AccountMeta, Instruction},
solana_pubkey::Pubkey,
solana_sdk_ids::{bpf_loader_upgradeable::id, sysvar},
solana_system_interface::instruction as system_instruction,
wincode::{
config::ConfigCore,
error::invalid_bool_encoding,
io::{Reader, Writer},
ReadResult, SchemaRead, SchemaWrite, TypeMeta, WriteResult,
},
};
pub const MINIMUM_EXTEND_PROGRAM_BYTES: u32 = 10_240;
#[repr(u8)]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Deserialize, serde_derive::Serialize)
)]
#[cfg_attr(feature = "wincode", derive(SchemaRead, SchemaWrite))]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum UpgradeableLoaderInstruction {
InitializeBuffer,
Write {
offset: u32,
#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
bytes: Vec<u8>,
},
DeployWithMaxDataLen {
max_data_len: usize,
#[cfg_attr(feature = "wincode", wincode(with = "OptionalTrailingBool<true>"))]
close_buffer: bool,
},
Upgrade {
#[cfg_attr(feature = "wincode", wincode(with = "OptionalTrailingBool<true>"))]
close_buffer: bool,
},
SetAuthority,
Close {
#[cfg_attr(feature = "wincode", wincode(with = "OptionalTrailingBool<false>"))]
tombstone: bool,
},
ExtendProgram {
additional_bytes: u32,
},
SetAuthorityChecked,
}
#[cfg(feature = "wincode")]
pub struct OptionalTrailingBool<const DEFAULT: bool>;
#[cfg(feature = "wincode")]
unsafe impl<'de, C: ConfigCore, const DEFAULT: bool> SchemaRead<'de, C>
for OptionalTrailingBool<DEFAULT>
{
type Dst = bool;
fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
let value = match reader.take_byte() {
Ok(0) => false,
Ok(1) => true,
Ok(byte) => return Err(invalid_bool_encoding(byte)),
Err(_) => DEFAULT,
};
dst.write(value);
Ok(())
}
}
#[cfg(feature = "wincode")]
unsafe impl<C: ConfigCore, const DEFAULT: bool> SchemaWrite<C> for OptionalTrailingBool<DEFAULT> {
type Src = bool;
const TYPE_META: TypeMeta = TypeMeta::Static {
size: 1,
zero_copy: false,
};
fn size_of(_src: &Self::Src) -> WriteResult<usize> {
Ok(1)
}
fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
writer.write(&[u8::from(*src)])?;
Ok(())
}
}
#[cfg(feature = "wincode")]
pub fn create_buffer(
payer_address: &Pubkey,
buffer_address: &Pubkey,
authority_address: &Pubkey,
lamports: u64,
program_len: usize,
) -> Result<Vec<Instruction>, InstructionError> {
Ok(vec![
system_instruction::create_account(
payer_address,
buffer_address,
lamports,
UpgradeableLoaderState::size_of_buffer(program_len) as u64,
&id(),
),
Instruction::new_with_wincode(
id(),
&UpgradeableLoaderInstruction::InitializeBuffer,
vec![
AccountMeta::new(*buffer_address, false),
AccountMeta::new_readonly(*authority_address, false),
],
),
])
}
#[cfg(feature = "wincode")]
pub fn write(
buffer_address: &Pubkey,
authority_address: &Pubkey,
offset: u32,
bytes: Vec<u8>,
) -> Instruction {
Instruction::new_with_wincode(
id(),
&UpgradeableLoaderInstruction::Write { offset, bytes },
vec![
AccountMeta::new(*buffer_address, false),
AccountMeta::new_readonly(*authority_address, true),
],
)
}
#[cfg(feature = "wincode")]
pub fn deploy_with_max_program_len(
payer_address: &Pubkey,
program_address: &Pubkey,
buffer_address: &Pubkey,
upgrade_authority_address: &Pubkey,
program_lamports: u64,
max_data_len: usize,
close_buffer: bool,
) -> Result<Vec<Instruction>, InstructionError> {
let programdata_address = get_program_data_address(program_address);
Ok(vec![
system_instruction::create_account(
payer_address,
program_address,
program_lamports,
UpgradeableLoaderState::size_of_program() as u64,
&id(),
),
Instruction::new_with_wincode(
id(),
&UpgradeableLoaderInstruction::DeployWithMaxDataLen {
max_data_len,
close_buffer,
},
vec![
AccountMeta::new(*payer_address, true),
AccountMeta::new(programdata_address, false),
AccountMeta::new(*program_address, false),
AccountMeta::new(*buffer_address, false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(solana_sdk_ids::system_program::id(), false),
AccountMeta::new_readonly(*upgrade_authority_address, true),
],
),
])
}
#[cfg(feature = "wincode")]
pub fn upgrade(
program_address: &Pubkey,
buffer_address: &Pubkey,
authority_address: &Pubkey,
spill_address: &Pubkey,
close_buffer: bool,
) -> Instruction {
let programdata_address = get_program_data_address(program_address);
Instruction::new_with_wincode(
id(),
&UpgradeableLoaderInstruction::Upgrade { close_buffer },
vec![
AccountMeta::new(programdata_address, false),
AccountMeta::new(*program_address, false),
AccountMeta::new(*buffer_address, false),
AccountMeta::new(*spill_address, false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(*authority_address, true),
],
)
}
pub fn is_upgrade_instruction(instruction_data: &[u8]) -> bool {
!instruction_data.is_empty() && 3 == instruction_data[0]
}
pub fn is_set_authority_instruction(instruction_data: &[u8]) -> bool {
!instruction_data.is_empty() && 4 == instruction_data[0]
}
pub fn is_close_instruction(instruction_data: &[u8]) -> bool {
!instruction_data.is_empty() && 5 == instruction_data[0]
}
pub fn is_set_authority_checked_instruction(instruction_data: &[u8]) -> bool {
!instruction_data.is_empty() && 7 == instruction_data[0]
}
#[cfg(feature = "wincode")]
pub fn set_buffer_authority(
buffer_address: &Pubkey,
current_authority_address: &Pubkey,
new_authority_address: &Pubkey,
) -> Instruction {
Instruction::new_with_wincode(
id(),
&UpgradeableLoaderInstruction::SetAuthority,
vec![
AccountMeta::new(*buffer_address, false),
AccountMeta::new_readonly(*current_authority_address, true),
AccountMeta::new_readonly(*new_authority_address, false),
],
)
}
#[cfg(feature = "wincode")]
pub fn set_buffer_authority_checked(
buffer_address: &Pubkey,
current_authority_address: &Pubkey,
new_authority_address: &Pubkey,
) -> Instruction {
Instruction::new_with_wincode(
id(),
&UpgradeableLoaderInstruction::SetAuthorityChecked,
vec![
AccountMeta::new(*buffer_address, false),
AccountMeta::new_readonly(*current_authority_address, true),
AccountMeta::new_readonly(*new_authority_address, true),
],
)
}
#[cfg(feature = "wincode")]
pub fn set_upgrade_authority(
program_address: &Pubkey,
current_authority_address: &Pubkey,
new_authority_address: Option<&Pubkey>,
) -> Instruction {
let programdata_address = get_program_data_address(program_address);
let mut metas = vec![
AccountMeta::new(programdata_address, false),
AccountMeta::new_readonly(*current_authority_address, true),
];
if let Some(address) = new_authority_address {
metas.push(AccountMeta::new_readonly(*address, false));
}
Instruction::new_with_wincode(id(), &UpgradeableLoaderInstruction::SetAuthority, metas)
}
#[cfg(feature = "wincode")]
pub fn set_upgrade_authority_checked(
program_address: &Pubkey,
current_authority_address: &Pubkey,
new_authority_address: &Pubkey,
) -> Instruction {
let programdata_address = get_program_data_address(program_address);
let metas = vec![
AccountMeta::new(programdata_address, false),
AccountMeta::new_readonly(*current_authority_address, true),
AccountMeta::new_readonly(*new_authority_address, true),
];
Instruction::new_with_wincode(
id(),
&UpgradeableLoaderInstruction::SetAuthorityChecked,
metas,
)
}
#[cfg(feature = "wincode")]
pub fn close(
close_address: &Pubkey,
recipient_address: &Pubkey,
authority_address: &Pubkey,
tombstone: bool,
) -> Instruction {
close_any(
close_address,
recipient_address,
Some(authority_address),
None,
tombstone,
)
}
#[cfg(feature = "wincode")]
pub fn close_any(
close_address: &Pubkey,
recipient_address: &Pubkey,
authority_address: Option<&Pubkey>,
program_address: Option<&Pubkey>,
tombstone: bool,
) -> Instruction {
let mut metas = vec![
AccountMeta::new(*close_address, false),
AccountMeta::new(*recipient_address, false),
];
if let Some(authority_address) = authority_address {
metas.push(AccountMeta::new_readonly(*authority_address, true));
}
if let Some(program_address) = program_address {
metas.push(AccountMeta::new(*program_address, false));
}
Instruction::new_with_wincode(
id(),
&UpgradeableLoaderInstruction::Close { tombstone },
metas,
)
}
#[cfg(feature = "wincode")]
pub fn extend_program(
program_address: &Pubkey,
payer_address: Option<&Pubkey>,
additional_bytes: u32,
) -> Instruction {
let program_data_address = get_program_data_address(program_address);
let mut metas = vec![
AccountMeta::new(program_data_address, false),
AccountMeta::new(*program_address, false),
];
if let Some(payer_address) = payer_address {
metas.push(AccountMeta::new_readonly(
solana_sdk_ids::system_program::id(),
false,
));
metas.push(AccountMeta::new(*payer_address, true));
}
Instruction::new_with_wincode(
id(),
&UpgradeableLoaderInstruction::ExtendProgram { additional_bytes },
metas,
)
}
#[cfg(all(test, feature = "wincode"))]
mod tests {
use {super::*, test_case::test_case};
fn assert_is_instruction<F>(
is_instruction_fn: F,
expected_instruction: UpgradeableLoaderInstruction,
) where
F: Fn(&[u8]) -> bool,
{
let result = is_instruction_fn(
&wincode::serialize(&UpgradeableLoaderInstruction::InitializeBuffer).unwrap(),
);
let expected_result = matches!(
expected_instruction,
UpgradeableLoaderInstruction::InitializeBuffer
);
assert_eq!(expected_result, result);
let result = is_instruction_fn(
&wincode::serialize(&UpgradeableLoaderInstruction::Write {
offset: 0,
bytes: vec![],
})
.unwrap(),
);
let expected_result = matches!(
expected_instruction,
UpgradeableLoaderInstruction::Write {
offset: _,
bytes: _,
}
);
assert_eq!(expected_result, result);
let result = is_instruction_fn(
&wincode::serialize(&UpgradeableLoaderInstruction::DeployWithMaxDataLen {
max_data_len: 0,
close_buffer: true,
})
.unwrap(),
);
let expected_result = matches!(
expected_instruction,
UpgradeableLoaderInstruction::DeployWithMaxDataLen { .. }
);
assert_eq!(expected_result, result);
let result = is_instruction_fn(
&wincode::serialize(&UpgradeableLoaderInstruction::Upgrade { close_buffer: true })
.unwrap(),
);
let expected_result = matches!(
expected_instruction,
UpgradeableLoaderInstruction::Upgrade { .. }
);
assert_eq!(expected_result, result);
let result = is_instruction_fn(
&wincode::serialize(&UpgradeableLoaderInstruction::SetAuthority).unwrap(),
);
let expected_result = matches!(
expected_instruction,
UpgradeableLoaderInstruction::SetAuthority
);
assert_eq!(expected_result, result);
let result = is_instruction_fn(
&wincode::serialize(&UpgradeableLoaderInstruction::Close { tombstone: false }).unwrap(),
);
let expected_result = matches!(
expected_instruction,
UpgradeableLoaderInstruction::Close { .. }
);
assert_eq!(expected_result, result);
}
#[test]
fn test_is_set_authority_instruction() {
assert!(!is_set_authority_instruction(&[]));
assert_is_instruction(
is_set_authority_instruction,
UpgradeableLoaderInstruction::SetAuthority {},
);
}
#[test]
fn test_is_set_authority_checked_instruction() {
assert!(!is_set_authority_checked_instruction(&[]));
assert_is_instruction(
is_set_authority_checked_instruction,
UpgradeableLoaderInstruction::SetAuthorityChecked {},
);
}
#[test]
fn test_is_upgrade_instruction() {
assert!(!is_upgrade_instruction(&[]));
assert_is_instruction(
is_upgrade_instruction,
UpgradeableLoaderInstruction::Upgrade { close_buffer: true },
);
}
#[test_case(UpgradeableLoaderInstruction::InitializeBuffer)]
#[test_case(UpgradeableLoaderInstruction::Write { offset: 42, bytes: vec![1, 2, 3, 4, 5] })]
#[test_case(UpgradeableLoaderInstruction::Write { offset: 0, bytes: vec![] })]
#[test_case(UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len: 1_000_000, close_buffer: true })]
#[test_case(UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len: 0, close_buffer: false })]
#[test_case(UpgradeableLoaderInstruction::Upgrade { close_buffer: true })]
#[test_case(UpgradeableLoaderInstruction::Upgrade { close_buffer: false })]
#[test_case(UpgradeableLoaderInstruction::SetAuthority)]
#[test_case(UpgradeableLoaderInstruction::Close { tombstone: false })]
#[test_case(UpgradeableLoaderInstruction::Close { tombstone: true })]
#[test_case(UpgradeableLoaderInstruction::ExtendProgram { additional_bytes: 10_240 })]
#[test_case(UpgradeableLoaderInstruction::ExtendProgram { additional_bytes: 0 })]
#[test_case(UpgradeableLoaderInstruction::SetAuthorityChecked)]
fn wire_compat_bincode_vs_wincode(instr: UpgradeableLoaderInstruction) {
let bincode_bytes = bincode::serialize(&instr).unwrap();
let wincode_bytes = wincode::serialize(&instr).unwrap();
assert_eq!(bincode_bytes, wincode_bytes);
let from_bincode: UpgradeableLoaderInstruction =
bincode::deserialize(&bincode_bytes).unwrap();
let from_wincode: UpgradeableLoaderInstruction =
wincode::deserialize(&wincode_bytes).unwrap();
assert_eq!(from_bincode, instr);
assert_eq!(from_wincode, instr);
}
#[test]
fn legacy_deploy_decodes_close_buffer_as_true() {
let mut data = Vec::new();
data.extend_from_slice(&2u32.to_le_bytes()); data.extend_from_slice(&42u64.to_le_bytes()); let decoded: UpgradeableLoaderInstruction = wincode::deserialize(&data).unwrap();
assert_eq!(
decoded,
UpgradeableLoaderInstruction::DeployWithMaxDataLen {
max_data_len: 42,
close_buffer: true, }
);
}
#[test]
fn legacy_upgrade_decodes_close_buffer_as_true() {
let data = 3u32.to_le_bytes(); let decoded: UpgradeableLoaderInstruction = wincode::deserialize(&data).unwrap();
assert_eq!(
decoded,
UpgradeableLoaderInstruction::Upgrade {
close_buffer: true, }
);
}
#[test]
fn legacy_close_decodes_tombstone_as_false() {
let data = 5u32.to_le_bytes(); let decoded: UpgradeableLoaderInstruction = wincode::deserialize(&data).unwrap();
assert_eq!(
decoded,
UpgradeableLoaderInstruction::Close {
tombstone: false, }
);
}
#[test]
fn invalid_optional_trailing_bool_byte_errors() {
let assert_invalid_trailing_bool = |data: &[u8]| {
let err = wincode::deserialize::<UpgradeableLoaderInstruction>(data).unwrap_err();
assert!(
matches!(err, wincode::ReadError::InvalidBoolEncoding(2)),
"expected InvalidBoolEncoding(2), got {err:?}",
);
};
let mut data = Vec::new();
data.extend_from_slice(&2u32.to_le_bytes()); data.extend_from_slice(&42u64.to_le_bytes()); data.push(2);
assert_invalid_trailing_bool(&data);
let mut data = Vec::new();
data.extend_from_slice(&3u32.to_le_bytes()); data.push(2);
assert_invalid_trailing_bool(&data);
let mut data = Vec::new();
data.extend_from_slice(&5u32.to_le_bytes()); data.push(2);
assert_invalid_trailing_bool(&data);
}
}