Allow closing upgradeable program accounts (#19319)

This commit is contained in:
Jack May 2021-08-24 10:05:54 -07:00 committed by GitHub
parent 27a41c5954
commit a89f180145
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 481 additions and 138 deletions

View File

@ -1954,6 +1954,29 @@ impl fmt::Display for CliUpgradeableProgram {
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CliUpgradeableProgramClosed {
pub program_id: String,
pub lamports: u64,
#[serde(skip_serializing)]
pub use_lamports_unit: bool,
}
impl QuietDisplay for CliUpgradeableProgramClosed {}
impl VerboseDisplay for CliUpgradeableProgramClosed {}
impl fmt::Display for CliUpgradeableProgramClosed {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f)?;
writeln!(
f,
"Closed Program Id {}, {} reclaimed",
&self.program_id,
&build_balance_message(self.lamports, self.use_lamports_unit, true)
)?;
Ok(())
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CliUpgradeableBuffer {

View File

@ -14,7 +14,7 @@ use solana_clap_utils::{self, input_parsers::*, input_validators::*, keypair::*}
use solana_cli_output::{
display::new_spinner_progress_bar, CliProgram, CliProgramAccountType, CliProgramAuthority,
CliProgramBuffer, CliProgramId, CliUpgradeableBuffer, CliUpgradeableBuffers,
CliUpgradeableProgram,
CliUpgradeableProgram, CliUpgradeableProgramClosed,
};
use solana_client::{
client_error::ClientErrorKind,
@ -333,29 +333,31 @@ impl ProgramSubCommands for App<'_, '_> {
)
.subcommand(
SubCommand::with_name("close")
.about("Close an account and withdraw all lamports")
.about("Close a program or buffer account and withdraw all lamports")
.arg(
Arg::with_name("account")
.index(1)
.value_name("BUFFER_ACCOUNT_ADDRESS")
.value_name("ACCOUNT_ADDRESS")
.takes_value(true)
.help("Address of the buffer account to close"),
.help("Address of the program or buffer account to close"),
)
.arg(
Arg::with_name("buffers")
.long("buffers")
.conflicts_with("account")
.required_unless("account")
.help("Close every buffer accounts that match the authority")
.help("Close all buffer accounts that match the authority")
)
.arg(
Arg::with_name("buffer_authority")
.long("buffer-authority")
Arg::with_name("authority")
.long("authority")
.alias("buffer-authority")
.value_name("AUTHORITY_SIGNER")
.takes_value(true)
.validator(is_valid_signer)
.help("Authority [default: the default configured keypair]")
.help("Upgrade or buffer authority [default: the default configured keypair]")
)
.arg(
pubkey!(Arg::with_name("recipient_account")
.long("recipient")
@ -626,7 +628,7 @@ pub fn parse_program_subcommand(
};
let (authority_signer, authority_pubkey) =
signer_of(matches, "buffer_authority", wallet_manager)?;
signer_of(matches, "authority", wallet_manager)?;
let signer_info = default_signer.generate_unique_signers(
vec![
@ -860,15 +862,17 @@ fn process_program_deploy(
false
} else {
return Err(format!(
"{} is not an upgradeable loader ProgramData account",
programdata_address
"Program {} has been closed, use a new Program Id",
program_pubkey
)
.into());
}
} else {
return Err(
format!("ProgramData account {} does not exist", programdata_address).into(),
);
return Err(format!(
"Program {} has been closed, use a new Program Id",
program_pubkey
)
.into());
}
} else {
return Err(format!("{} is not an upgradeable program", program_pubkey).into());
@ -1196,17 +1200,10 @@ fn process_show(
- UpgradeableLoaderState::programdata_data_offset()?,
}))
} else {
Err(format!("Invalid associated ProgramData account {} found for the program {}",
programdata_address, account_pubkey)
.into(),
)
Err(format!("Program {} has been closed", account_pubkey).into())
}
} else {
Err(format!(
"Failed to find associated ProgramData account {} for the program {}",
programdata_address, account_pubkey
)
.into())
Err(format!("Program {} has been closed", account_pubkey).into())
}
} else if let Ok(UpgradeableLoaderState::Buffer { authority_address }) =
account.state()
@ -1298,18 +1295,10 @@ fn process_dump(
f.write_all(program_data)?;
Ok(format!("Wrote program to {}", output_location))
} else {
Err(
format!("Invalid associated ProgramData account {} found for the program {}",
programdata_address, account_pubkey)
.into(),
)
Err(format!("Program {} has been closed", account_pubkey).into())
}
} else {
Err(format!(
"Failed to find associated ProgramData account {} for the program {}",
programdata_address, account_pubkey
)
.into())
Err(format!("Program {} has been closed", account_pubkey).into())
}
} else if let Ok(UpgradeableLoaderState::Buffer { .. }) = account.state() {
let offset = UpgradeableLoaderState::buffer_data_offset().unwrap_or(0);
@ -1341,14 +1330,16 @@ fn close(
account_pubkey: &Pubkey,
recipient_pubkey: &Pubkey,
authority_signer: &dyn Signer,
program_pubkey: Option<&Pubkey>,
) -> Result<(), Box<dyn std::error::Error>> {
let blockhash = rpc_client.get_latest_blockhash()?;
let mut tx = Transaction::new_unsigned(Message::new(
&[bpf_loader_upgradeable::close(
&[bpf_loader_upgradeable::close_any(
account_pubkey,
recipient_pubkey,
&authority_signer.pubkey(),
Some(&authority_signer.pubkey()),
program_pubkey,
)],
Some(&config.signers[0].pubkey()),
));
@ -1393,64 +1384,92 @@ fn process_close(
.get_account_with_commitment(&account_pubkey, config.commitment)?
.value
{
if let Ok(UpgradeableLoaderState::Buffer { authority_address }) = account.state() {
if authority_address != Some(authority_signer.pubkey()) {
return Err(format!(
"Buffer account authority {:?} does not match {:?}",
authority_address,
Some(authority_signer.pubkey())
)
.into());
} else {
close(
rpc_client,
config,
&account_pubkey,
&recipient_pubkey,
authority_signer,
)?;
match account.state() {
Ok(UpgradeableLoaderState::Buffer { authority_address }) => {
if authority_address != Some(authority_signer.pubkey()) {
return Err(format!(
"Buffer account authority {:?} does not match {:?}",
authority_address,
Some(authority_signer.pubkey())
)
.into());
} else {
close(
rpc_client,
config,
&account_pubkey,
&recipient_pubkey,
authority_signer,
None,
)?;
buffers.push(CliUpgradeableBuffer {
address: account_pubkey.to_string(),
authority: authority_address
.map(|pubkey| pubkey.to_string())
.unwrap_or_else(|| "none".to_string()),
data_len: 0,
lamports: account.lamports,
use_lamports_unit,
});
buffers.push(CliUpgradeableBuffer {
address: account_pubkey.to_string(),
authority: authority_address
.map(|pubkey| pubkey.to_string())
.unwrap_or_else(|| "none".to_string()),
data_len: 0,
lamports: account.lamports,
use_lamports_unit,
});
}
}
Ok(UpgradeableLoaderState::Program {
programdata_address: programdata_pubkey,
}) => {
if let Some(account) = rpc_client
.get_account_with_commitment(&programdata_pubkey, config.commitment)?
.value
{
if let Ok(UpgradeableLoaderState::ProgramData {
slot: _,
upgrade_authority_address: authority_pubkey,
}) = account.state()
{
if authority_pubkey != Some(authority_signer.pubkey()) {
return Err(format!(
"Program authority {:?} does not match {:?}",
authority_pubkey,
Some(authority_signer.pubkey())
)
.into());
} else {
close(
rpc_client,
config,
&programdata_pubkey,
&recipient_pubkey,
authority_signer,
Some(&account_pubkey),
)?;
return Ok(config.output_format.formatted_string(
&CliUpgradeableProgramClosed {
program_id: account_pubkey.to_string(),
lamports: account.lamports,
use_lamports_unit,
},
));
}
} else {
return Err(
format!("Program {} has been closed", account_pubkey).into()
);
}
} else {
return Err(format!("Program {} has been closed", account_pubkey).into());
}
}
_ => {
return Err(
format!("{} is not a Program or Buffer account", account_pubkey).into(),
);
}
} else {
return Err(format!(
"{} is not an upgradeble loader buffer account",
account_pubkey
)
.into());
}
} else {
return Err(format!("Unable to find the account {}", account_pubkey).into());
}
} else {
let mut bytes = vec![1, 0, 0, 0, 1];
bytes.extend_from_slice(authority_signer.pubkey().as_ref());
let length = bytes.len();
let results = rpc_client.get_program_accounts_with_config(
&bpf_loader_upgradeable::id(),
RpcProgramAccountsConfig {
filters: Some(vec![RpcFilterType::Memcmp(Memcmp {
offset: 0,
bytes: MemcmpEncodedBytes::Binary(bs58::encode(bytes).into_string()),
encoding: None,
})]),
account_config: RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
data_slice: Some(UiDataSliceConfig { offset: 0, length }),
..RpcAccountInfoConfig::default()
},
..RpcProgramAccountsConfig::default()
},
)?;
let results = get_buffers(rpc_client, Some(authority_signer.pubkey()))?;
for (address, account) in results.iter() {
if close(
@ -1459,6 +1478,7 @@ fn process_close(
address,
&recipient_pubkey,
authority_signer,
None,
)
.is_ok()
{

View File

@ -525,7 +525,7 @@ fn test_cli_program_deploy_with_authority() {
{
assert_eq!(upgrade_authority_address, None);
} else {
panic!("not a buffer account");
panic!("not a ProgramData account");
}
// Get buffer authority
@ -548,6 +548,88 @@ fn test_cli_program_deploy_with_authority() {
assert_eq!("none", authority_pubkey_str);
}
#[test]
fn test_cli_program_close_program() {
solana_logger::setup();
let mut pathbuf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
pathbuf.push("tests");
pathbuf.push("fixtures");
pathbuf.push("noop");
pathbuf.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let faucet_addr = run_local_faucet(mint_keypair, None);
let test_validator =
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
let rpc_client =
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
let mut file = File::open(pathbuf.to_str().unwrap()).unwrap();
let mut program_data = Vec::new();
file.read_to_end(&mut program_data).unwrap();
let max_len = program_data.len();
let minimum_balance_for_programdata = rpc_client
.get_minimum_balance_for_rent_exemption(
UpgradeableLoaderState::programdata_len(max_len).unwrap(),
)
.unwrap();
let minimum_balance_for_program = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::program_len().unwrap())
.unwrap();
let upgrade_authority = Keypair::new();
let mut config = CliConfig::recent_for_tests();
let keypair = Keypair::new();
config.json_rpc_url = test_validator.rpc_url();
config.signers = vec![&keypair];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program,
};
process_command(&config).unwrap();
// Deploy the upgradeable program
let program_keypair = Keypair::new();
config.signers = vec![&keypair, &upgrade_authority, &program_keypair];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(pathbuf.to_str().unwrap().to_string()),
program_signer_index: Some(2),
program_pubkey: Some(program_keypair.pubkey()),
buffer_signer_index: None,
buffer_pubkey: None,
allow_excessive_balance: false,
upgrade_authority_signer_index: 1,
is_final: false,
max_len: Some(max_len),
});
config.output_format = OutputFormat::JsonCompact;
process_command(&config).unwrap();
let (programdata_pubkey, _) = Pubkey::find_program_address(
&[program_keypair.pubkey().as_ref()],
&bpf_loader_upgradeable::id(),
);
// Close program
let close_account = rpc_client.get_account(&programdata_pubkey).unwrap();
let programdata_lamports = close_account.lamports;
let recipient_pubkey = Pubkey::new_unique();
config.signers = vec![&keypair, &upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::Close {
account_pubkey: Some(program_keypair.pubkey()),
recipient_pubkey,
authority_index: 1,
use_lamports_unit: false,
});
process_command(&config).unwrap();
rpc_client.get_account(&programdata_pubkey).unwrap_err();
let recipient_account = rpc_client.get_account(&recipient_pubkey).unwrap();
assert_eq!(programdata_lamports, recipient_account.lamports);
}
#[test]
fn test_cli_program_write_buffer() {
solana_logger::setup();

View File

@ -30,13 +30,16 @@ use solana_sdk::{
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
clock::Clock,
entrypoint::{HEAP_LENGTH, SUCCESS},
feature_set::{add_missing_program_error_mappings, stop_verify_mul64_imm_nonzero},
feature_set::{
add_missing_program_error_mappings, close_upgradeable_program_accounts,
stop_verify_mul64_imm_nonzero,
},
ic_logger_msg, ic_msg,
instruction::InstructionError,
keyed_account::{from_keyed_account, keyed_account_at_index},
keyed_account::{from_keyed_account, keyed_account_at_index, KeyedAccount},
loader_instruction::LoaderInstruction,
loader_upgradeable_instruction::UpgradeableLoaderInstruction,
process_instruction::{stable_log, ComputeMeter, Executor, InvokeContext},
process_instruction::{stable_log, ComputeMeter, Executor, InvokeContext, Logger},
program_error::{ACCOUNT_NOT_RENT_EXEMPT, BORSH_IO_ERROR},
program_utils::limited_deserialize,
pubkey::Pubkey,
@ -198,6 +201,16 @@ fn process_instruction_common(
ic_logger_msg!(logger, "Wrong ProgramData account for this Program account");
return Err(InstructionError::InvalidArgument);
}
if !matches!(
programdata.state()?,
UpgradeableLoaderState::ProgramData {
slot: _,
upgrade_authority_address: _,
}
) {
ic_logger_msg!(logger, "Program has been closed");
return Err(InstructionError::InvalidAccountData);
}
invoke_context.remove_first_keyed_account()?;
UpgradeableLoaderState::programdata_data_offset()?
} else {
@ -620,46 +633,143 @@ fn process_loader_upgradeable_instruction(
UpgradeableLoaderInstruction::Close => {
let close_account = keyed_account_at_index(keyed_accounts, 0)?;
let recipient_account = keyed_account_at_index(keyed_accounts, 1)?;
let authority = keyed_account_at_index(keyed_accounts, 2)?;
if !invoke_context.is_feature_active(&close_upgradeable_program_accounts::id()) {
let _ = keyed_account_at_index(keyed_accounts, 2)?;
}
if close_account.unsigned_key() == recipient_account.unsigned_key() {
ic_logger_msg!(logger, "Recipient is the same as the account being closed");
return Err(InstructionError::InvalidArgument);
}
if let UpgradeableLoaderState::Buffer { authority_address } = close_account.state()? {
if authority_address.is_none() {
ic_logger_msg!(logger, "Buffer is immutable");
return Err(InstructionError::Immutable);
}
if authority_address != Some(*authority.unsigned_key()) {
ic_logger_msg!(logger, "Incorrect buffer authority provided");
return Err(InstructionError::IncorrectAuthority);
}
if authority.signer_key().is_none() {
ic_logger_msg!(logger, "Buffer authority did not sign");
return Err(InstructionError::MissingRequiredSignature);
}
recipient_account
.try_account_ref_mut()?
.checked_add_lamports(close_account.lamports()?)?;
close_account.try_account_ref_mut()?.set_lamports(0);
for elt in close_account.try_account_ref_mut()?.data_as_mut_slice() {
*elt = 0;
}
} else {
if !invoke_context.is_feature_active(&close_upgradeable_program_accounts::id())
&& !matches!(
close_account.state()?,
UpgradeableLoaderState::Buffer {
authority_address: _,
}
)
{
ic_logger_msg!(logger, "Account does not support closing");
return Err(InstructionError::InvalidArgument);
}
ic_logger_msg!(logger, "Closed {}", close_account.unsigned_key());
match close_account.state()? {
UpgradeableLoaderState::Uninitialized => {
recipient_account
.try_account_ref_mut()?
.checked_add_lamports(close_account.lamports()?)?;
close_account.try_account_ref_mut()?.set_lamports(0);
ic_logger_msg!(
logger,
"Closed Uninitialized {}",
close_account.unsigned_key()
);
}
UpgradeableLoaderState::Buffer { authority_address } => {
let authority = keyed_account_at_index(keyed_accounts, 2)?;
common_close_account(
&authority_address,
authority,
close_account,
recipient_account,
logger.clone(),
!invoke_context
.is_feature_active(&close_upgradeable_program_accounts::id()),
)?;
ic_logger_msg!(logger, "Closed Buffer {}", close_account.unsigned_key());
}
UpgradeableLoaderState::ProgramData {
slot: _,
upgrade_authority_address: authority_address,
} => {
let program_account = keyed_account_at_index(keyed_accounts, 3)?;
if !program_account.is_writable() {
ic_logger_msg!(logger, "Program account is not writable");
return Err(InstructionError::InvalidArgument);
}
match program_account.state()? {
UpgradeableLoaderState::Program {
programdata_address,
} => {
if programdata_address != *close_account.unsigned_key() {
ic_logger_msg!(
logger,
"ProgramData account does not match ProgramData account"
);
return Err(InstructionError::InvalidArgument);
}
let authority = keyed_account_at_index(keyed_accounts, 2)?;
common_close_account(
&authority_address,
authority,
close_account,
recipient_account,
logger.clone(),
!invoke_context
.is_feature_active(&close_upgradeable_program_accounts::id()),
)?;
}
_ => {
ic_logger_msg!(logger, "Invalid Program account");
return Err(InstructionError::InvalidArgument);
}
}
ic_logger_msg!(logger, "Closed Program {}", program_account.unsigned_key());
}
_ => {
ic_logger_msg!(logger, "Account does not support closing");
return Err(InstructionError::InvalidArgument);
}
}
}
}
Ok(())
}
fn common_close_account(
authority_address: &Option<Pubkey>,
authority_account: &KeyedAccount,
close_account: &KeyedAccount,
recipient_account: &KeyedAccount,
logger: Rc<RefCell<dyn Logger>>,
do_clear_data: bool,
) -> Result<(), InstructionError> {
if authority_address.is_none() {
ic_logger_msg!(logger, "Account is immutable");
return Err(InstructionError::Immutable);
}
if *authority_address != Some(*authority_account.unsigned_key()) {
ic_logger_msg!(logger, "Incorrect authority provided");
return Err(InstructionError::IncorrectAuthority);
}
if authority_account.signer_key().is_none() {
ic_logger_msg!(logger, "Authority did not sign");
return Err(InstructionError::MissingRequiredSignature);
}
recipient_account
.try_account_ref_mut()?
.checked_add_lamports(close_account.lamports()?)?;
close_account.try_account_ref_mut()?.set_lamports(0);
if do_clear_data {
for elt in close_account.try_account_ref_mut()?.data_as_mut_slice() {
*elt = 0;
}
} else {
close_account.set_state(&UpgradeableLoaderState::Uninitialized)?;
}
Ok(())
}
fn process_loader_instruction(
program_id: &Pubkey,
instruction_data: &[u8],
@ -3234,11 +3344,8 @@ mod tests {
);
assert_eq!(0, buffer_account.borrow().lamports());
assert_eq!(2, recipient_account.borrow().lamports());
assert!(buffer_account
.borrow()
.data()
.iter()
.all(|&value| value == 0));
let state: UpgradeableLoaderState = buffer_account.borrow().state().unwrap();
assert_eq!(state, UpgradeableLoaderState::Uninitialized);
// Case: close with wrong authority
buffer_account
@ -3262,26 +3369,95 @@ mod tests {
)
);
// Case: close but not a buffer account
buffer_account
// Case: close an uninitialized account
let uninitialized_address = Pubkey::new_unique();
let uninitialized_account = AccountSharedData::new_ref(
1,
UpgradeableLoaderState::programdata_len(0).unwrap(),
&bpf_loader_upgradeable::id(),
);
uninitialized_account
.borrow_mut()
.set_state(&UpgradeableLoaderState::Program {
programdata_address: Pubkey::new_unique(),
})
.set_state(&UpgradeableLoaderState::Uninitialized)
.unwrap();
let recipient_account = AccountSharedData::new_ref(1, 0, &Pubkey::new_unique());
let keyed_accounts = vec![
KeyedAccount::new(&buffer_address, false, &buffer_account),
KeyedAccount::new(&uninitialized_address, false, &uninitialized_account),
KeyedAccount::new(&recipient_address, false, &recipient_account),
KeyedAccount::new_readonly(&incorrect_authority_address, true, &authority_account),
];
assert_eq!(
Err(InstructionError::InvalidArgument),
Ok(()),
process_instruction(
&bpf_loader_upgradeable::id(),
&instruction,
&mut MockInvokeContext::new(keyed_accounts),
)
);
assert_eq!(0, uninitialized_account.borrow().lamports());
assert_eq!(2, recipient_account.borrow().lamports());
let state: UpgradeableLoaderState = uninitialized_account.borrow().state().unwrap();
assert_eq!(state, UpgradeableLoaderState::Uninitialized);
// Case: close a program account
let programdata_address = Pubkey::new_unique();
let programdata_account = AccountSharedData::new_ref(
1,
UpgradeableLoaderState::programdata_len(0).unwrap(),
&bpf_loader_upgradeable::id(),
);
programdata_account
.borrow_mut()
.set_state(&UpgradeableLoaderState::ProgramData {
slot: 0,
upgrade_authority_address: Some(authority_address),
})
.unwrap();
let program_address = Pubkey::new_unique();
let program_account = AccountSharedData::new_ref(
1,
UpgradeableLoaderState::program_len().unwrap(),
&bpf_loader_upgradeable::id(),
);
program_account.borrow_mut().set_executable(true);
program_account
.borrow_mut()
.set_state(&UpgradeableLoaderState::Program {
programdata_address,
})
.unwrap();
let recipient_account = AccountSharedData::new_ref(1, 0, &Pubkey::new_unique());
let keyed_accounts = vec![
KeyedAccount::new(&programdata_address, false, &programdata_account),
KeyedAccount::new(&recipient_address, false, &recipient_account),
KeyedAccount::new_readonly(&authority_address, true, &authority_account),
KeyedAccount::new(&program_address, false, &program_account),
];
assert_eq!(
Ok(()),
process_instruction(
&bpf_loader_upgradeable::id(),
&instruction,
&mut MockInvokeContext::new(keyed_accounts),
)
);
assert_eq!(0, programdata_account.borrow().lamports());
assert_eq!(2, recipient_account.borrow().lamports());
let state: UpgradeableLoaderState = programdata_account.borrow().state().unwrap();
assert_eq!(state, UpgradeableLoaderState::Uninitialized);
// Try to invoke closed account
let keyed_accounts = vec![
KeyedAccount::new(&program_address, false, &program_account),
KeyedAccount::new(&programdata_address, false, &programdata_account),
];
assert_eq!(
Err(InstructionError::InvalidAccountData),
process_instruction(
&program_address,
&instruction,
&mut MockInvokeContext::new(keyed_accounts),
)
);
}
/// fuzzing utility function

View File

@ -21,9 +21,9 @@ use solana_sdk::{
entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS},
epoch_schedule::EpochSchedule,
feature_set::{
blake3_syscall_enabled, disable_fees_sysvar, enforce_aligned_host_addrs,
libsecp256k1_0_5_upgrade_enabled, mem_overlap_fix, memory_ops_syscalls,
secp256k1_recover_syscall_enabled,
blake3_syscall_enabled, close_upgradeable_program_accounts, disable_fees_sysvar,
enforce_aligned_host_addrs, libsecp256k1_0_5_upgrade_enabled, mem_overlap_fix,
memory_ops_syscalls, secp256k1_recover_syscall_enabled,
},
hash::{Hasher, HASH_BYTES},
ic_msg,
@ -2236,13 +2236,16 @@ fn check_account_infos(
fn check_authorized_program(
program_id: &Pubkey,
instruction_data: &[u8],
close_upgradeable_program_accounts: bool,
) -> Result<(), EbpfError<BpfError>> {
if native_loader::check_id(program_id)
|| bpf_loader::check_id(program_id)
|| bpf_loader_deprecated::check_id(program_id)
|| (bpf_loader_upgradeable::check_id(program_id)
&& !(bpf_loader_upgradeable::is_upgrade_instruction(instruction_data)
|| bpf_loader_upgradeable::is_set_authority_instruction(instruction_data)))
|| bpf_loader_upgradeable::is_set_authority_instruction(instruction_data)
|| (close_upgradeable_program_accounts
&& bpf_loader_upgradeable::is_close_instruction(instruction_data))))
{
return Err(SyscallError::ProgramNotSupported(*program_id).into());
}
@ -2350,7 +2353,11 @@ fn call<'a>(
}
})
.collect::<Vec<bool>>();
check_authorized_program(&callee_program_id, &instruction.data)?;
check_authorized_program(
&callee_program_id,
&instruction.data,
invoke_context.is_feature_active(&close_upgradeable_program_accounts::id()),
)?;
let (accounts, account_refs) = syscall.translate_accounts(
&message.account_keys,
callee_program_id_index,

View File

@ -589,6 +589,8 @@ fn build_bpf_package(config: &Config, target_directory: &Path, package: &cargo_m
println!();
println!("To deploy this program:");
println!(" $ solana program deploy {}", program_so.display());
println!("The program address will default to this keypair (override with --program-id):");
println!(" {}", program_keypair.display());
} else if config.dump {
println!("Note: --dump is only available for crates with a cdylib target");
}

View File

@ -196,6 +196,10 @@ 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]
}
/// Returns the instructions required to set a buffers's authority.
pub fn set_buffer_authority(
buffer_address: &Pubkey,
@ -231,17 +235,37 @@ pub fn set_upgrade_authority(
Instruction::new_with_bincode(id(), &UpgradeableLoaderInstruction::SetAuthority, metas)
}
/// Returns the instructions required to close an account
/// Returns the instructions required to close a buffer account
pub fn close(
close_address: &Pubkey,
recipient_address: &Pubkey,
authority_address: &Pubkey,
) -> Instruction {
let metas = vec![
close_any(
close_address,
recipient_address,
Some(authority_address),
None,
)
}
/// Returns the instructions required to close program, buffer, or uninitialized account
pub fn close_any(
close_address: &Pubkey,
recipient_address: &Pubkey,
authority_address: Option<&Pubkey>,
program_address: Option<&Pubkey>,
) -> Instruction {
let mut metas = vec![
AccountMeta::new(*close_address, false),
AccountMeta::new(*recipient_address, false),
AccountMeta::new_readonly(*authority_address, true),
];
if let Some(authority_address) = authority_address {
metas.push(AccountMeta::new(*authority_address, true));
}
if let Some(program_address) = program_address {
metas.push(AccountMeta::new(*program_address, false));
}
Instruction::new_with_bincode(id(), &UpgradeableLoaderInstruction::Close, metas)
}

View File

@ -117,8 +117,12 @@ pub enum UpgradeableLoaderInstruction {
/// withdraws all the lamports
///
/// # Account references
/// 0. `[writable]` The account to close.
/// 0. `[writable]` The account to close, if closing a program must be the
/// ProgramData account.
/// 1. `[writable]` The account to deposit the closed account's lamports.
/// 2. `[signer]` The account's authority.
/// 2. `[signer]` The account's authority, Optional, required for
/// initialized accounts.
/// 3. `[writable]` The associated Program account if the account to close
/// is a ProgramData account.
Close,
}

View File

@ -199,6 +199,10 @@ pub mod libsecp256k1_fail_on_bad_count {
solana_sdk::declare_id!("8aXvSuopd1PUj7UhehfXJRg6619RHp8ZvwTyyJHdUYsj");
}
pub mod close_upgradeable_program_accounts {
solana_sdk::declare_id!("EQMtCuSAkMVF9ZdhGuABtgvyXJLtSRF5AQKv1RNsrhj7");
}
lazy_static! {
/// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -241,7 +245,8 @@ lazy_static! {
(gate_large_block::id(), "validator checks block cost against max limit in realtime, reject if exceeds."),
(mem_overlap_fix::id(), "memory overlap fix"),
(versioned_tx_message_enabled::id(), "enable versioned transaction message processing"),
(libsecp256k1_fail_on_bad_count::id(), "Fail libsec256k1_verify if count appears wrong")
(libsecp256k1_fail_on_bad_count::id(), "Fail libsec256k1_verify if count appears wrong"),
(close_upgradeable_program_accounts::id(), "enable closing upgradeable program accounts"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()