Add Close instrruction and tooling to upgradeable loader (#15887)
This commit is contained in:
parent
12399157f5
commit
7f500d610c
|
@ -1392,7 +1392,7 @@ impl fmt::Display for CliAccountBalances {
|
|||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!("{:<44} {}", "Address", "Balance",)).bold()
|
||||
style(format!("{:<44} {}", "Address", "Balance")).bold()
|
||||
)?;
|
||||
for account in &self.accounts {
|
||||
writeln!(
|
||||
|
@ -1684,6 +1684,8 @@ pub struct CliUpgradeableBuffer {
|
|||
pub address: String,
|
||||
pub authority: String,
|
||||
pub data_len: usize,
|
||||
pub lamports: u64,
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
impl QuietDisplay for CliUpgradeableBuffer {}
|
||||
impl VerboseDisplay for CliUpgradeableBuffer {}
|
||||
|
@ -1692,11 +1694,53 @@ impl fmt::Display for CliUpgradeableBuffer {
|
|||
writeln!(f)?;
|
||||
writeln_name_value(f, "Buffer Address:", &self.address)?;
|
||||
writeln_name_value(f, "Authority:", &self.authority)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Balance:",
|
||||
&build_balance_message(self.lamports, self.use_lamports_unit, true),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Data Length:",
|
||||
&format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliUpgradeableBuffers {
|
||||
pub buffers: Vec<CliUpgradeableBuffer>,
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
impl QuietDisplay for CliUpgradeableBuffers {}
|
||||
impl VerboseDisplay for CliUpgradeableBuffers {}
|
||||
impl fmt::Display for CliUpgradeableBuffers {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
"{:<44} | {:<44} | {}",
|
||||
"Buffer Address", "Authority", "Balance"
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
for buffer in self.buffers.iter() {
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
&format!(
|
||||
"{:<44} | {:<44} | {}",
|
||||
buffer.address,
|
||||
buffer.authority,
|
||||
build_balance_message(buffer.lamports, self.use_lamports_unit, true)
|
||||
)
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,15 +10,22 @@ use bincode::serialize;
|
|||
use bip39::{Language, Mnemonic, MnemonicType, Seed};
|
||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
use log::*;
|
||||
use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig};
|
||||
use solana_bpf_loader_program::{bpf_verifier, BpfError, ThisInstructionMeter};
|
||||
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, CliUpgradeableProgram,
|
||||
CliProgramBuffer, CliProgramId, CliUpgradeableBuffer, CliUpgradeableBuffers,
|
||||
CliUpgradeableProgram,
|
||||
};
|
||||
use solana_client::{
|
||||
rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig,
|
||||
rpc_request::MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS, rpc_response::RpcLeaderSchedule,
|
||||
client_error::ClientErrorKind,
|
||||
rpc_client::RpcClient,
|
||||
rpc_config::RpcSendTransactionConfig,
|
||||
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
|
||||
rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType},
|
||||
rpc_request::MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS,
|
||||
rpc_response::RpcLeaderSchedule,
|
||||
};
|
||||
use solana_rbpf::vm::{Config, Executable};
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
|
@ -30,6 +37,7 @@ use solana_sdk::{
|
|||
clock::Slot,
|
||||
commitment_config::CommitmentConfig,
|
||||
instruction::Instruction,
|
||||
instruction::InstructionError,
|
||||
loader_instruction,
|
||||
message::Message,
|
||||
native_token::Sol,
|
||||
|
@ -39,6 +47,7 @@ use solana_sdk::{
|
|||
system_instruction::{self, SystemError},
|
||||
system_program,
|
||||
transaction::Transaction,
|
||||
transaction::TransactionError,
|
||||
};
|
||||
use solana_transaction_status::TransactionConfirmationStatus;
|
||||
use std::{
|
||||
|
@ -89,11 +98,20 @@ pub enum ProgramCliCommand {
|
|||
},
|
||||
Show {
|
||||
account_pubkey: Option<Pubkey>,
|
||||
authority_pubkey: Pubkey,
|
||||
all: bool,
|
||||
use_lamports_unit: bool,
|
||||
},
|
||||
Dump {
|
||||
account_pubkey: Option<Pubkey>,
|
||||
output_location: String,
|
||||
},
|
||||
Close {
|
||||
account_pubkey: Option<Pubkey>,
|
||||
recipient_pubkey: Pubkey,
|
||||
authority_index: SignerIndex,
|
||||
use_lamports_unit: bool,
|
||||
},
|
||||
}
|
||||
|
||||
pub trait ProgramSubCommands {
|
||||
|
@ -200,7 +218,7 @@ impl ProgramSubCommands for App<'_, '_> {
|
|||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("set-buffer-authority")
|
||||
.about("Set a new buffer authority") // TODO deploy with buffer and no file path?
|
||||
.about("Set a new buffer authority")
|
||||
.arg(
|
||||
Arg::with_name("buffer")
|
||||
.index(1)
|
||||
|
@ -266,9 +284,34 @@ impl ProgramSubCommands for App<'_, '_> {
|
|||
.index(1)
|
||||
.value_name("ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("Address of the buffer or program to show")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("buffers")
|
||||
.long("buffers")
|
||||
.conflicts_with("account")
|
||||
.required_unless("account")
|
||||
.help("Show every buffer account that matches the authority")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("all")
|
||||
.long("all")
|
||||
.conflicts_with("account")
|
||||
.help("Show accounts for all authorities")
|
||||
)
|
||||
.arg(
|
||||
pubkey!(Arg::with_name("buffer_authority")
|
||||
.long("buffer-authority")
|
||||
.value_name("AUTHORITY")
|
||||
.conflicts_with("all"),
|
||||
"Authority [default: the default configured keypair]"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("lamports")
|
||||
.long("lamports")
|
||||
.takes_value(false)
|
||||
.help("Display balance in lamports instead of SOL"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("dump")
|
||||
|
@ -290,6 +333,44 @@ impl ProgramSubCommands for App<'_, '_> {
|
|||
.help("/path/to/program.so"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("close")
|
||||
.about("Close an acount and withdraw all lamports")
|
||||
.arg(
|
||||
Arg::with_name("account")
|
||||
.index(1)
|
||||
.value_name("BUFFER_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.help("Address of the 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")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("buffer_authority")
|
||||
.long("buffer-authority")
|
||||
.value_name("AUTHORITY_SIGNER")
|
||||
.takes_value(true)
|
||||
.validator(is_valid_signer)
|
||||
.help("Authority [default: the default configured keypair]")
|
||||
)
|
||||
.arg(
|
||||
pubkey!(Arg::with_name("recipient_account")
|
||||
.long("recipient")
|
||||
.value_name("RECIPIENT_ADDRESS"),
|
||||
"Address of the account to deposit the closed account's lamports [default: the default configured keypair]"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("lamports")
|
||||
.long("lamports")
|
||||
.takes_value(false)
|
||||
.help("Display balance in lamports instead of SOL"),
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -425,15 +506,8 @@ pub fn parse_program_subcommand(
|
|||
|
||||
let (buffer_authority_signer, buffer_authority_pubkey) =
|
||||
signer_of(matches, "buffer_authority", wallet_manager)?;
|
||||
let new_buffer_authority = if let Some(new_buffer_authority) =
|
||||
pubkey_of_signer(matches, "new_buffer_authority", wallet_manager)?
|
||||
{
|
||||
new_buffer_authority
|
||||
} else {
|
||||
let (_, new_buffer_authority) =
|
||||
signer_of(matches, "new_buffer_authority", wallet_manager)?;
|
||||
new_buffer_authority.unwrap()
|
||||
};
|
||||
let new_buffer_authority =
|
||||
pubkey_of_signer(matches, "new_buffer_authority", wallet_manager)?.unwrap();
|
||||
|
||||
let signer_info = default_signer.generate_unique_signers(
|
||||
vec![
|
||||
|
@ -459,14 +533,8 @@ pub fn parse_program_subcommand(
|
|||
let program_pubkey = pubkey_of(matches, "program_id").unwrap();
|
||||
let new_upgrade_authority = if matches.is_present("final") {
|
||||
None
|
||||
} else if let Some(new_upgrade_authority) =
|
||||
pubkey_of_signer(matches, "new_upgrade_authority", wallet_manager)?
|
||||
{
|
||||
Some(new_upgrade_authority)
|
||||
} else {
|
||||
let (_, new_upgrade_authority) =
|
||||
signer_of(matches, "new_upgrade_authority", wallet_manager)?;
|
||||
new_upgrade_authority
|
||||
pubkey_of_signer(matches, "new_upgrade_authority", wallet_manager)?
|
||||
};
|
||||
|
||||
let signer_info = default_signer.generate_unique_signers(
|
||||
|
@ -487,12 +555,33 @@ pub fn parse_program_subcommand(
|
|||
signers: signer_info.signers,
|
||||
}
|
||||
}
|
||||
("show", Some(matches)) => CliCommandInfo {
|
||||
command: CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: pubkey_of(matches, "account"),
|
||||
}),
|
||||
signers: vec![],
|
||||
},
|
||||
("show", Some(matches)) => {
|
||||
let account_pubkey = if matches.is_present("buffers") {
|
||||
None
|
||||
} else {
|
||||
pubkey_of(matches, "account")
|
||||
};
|
||||
|
||||
let authority_pubkey = if let Some(authority_pubkey) =
|
||||
pubkey_of_signer(matches, "buffer_authority", wallet_manager)?
|
||||
{
|
||||
authority_pubkey
|
||||
} else {
|
||||
default_signer
|
||||
.signer_from_path(matches, wallet_manager)?
|
||||
.pubkey()
|
||||
};
|
||||
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey,
|
||||
authority_pubkey,
|
||||
all: matches.is_present("all"),
|
||||
use_lamports_unit: matches.is_present("lamports"),
|
||||
}),
|
||||
signers: vec![],
|
||||
}
|
||||
}
|
||||
("dump", Some(matches)) => CliCommandInfo {
|
||||
command: CliCommand::Program(ProgramCliCommand::Dump {
|
||||
account_pubkey: pubkey_of(matches, "account"),
|
||||
|
@ -500,6 +589,45 @@ pub fn parse_program_subcommand(
|
|||
}),
|
||||
signers: vec![],
|
||||
},
|
||||
("close", Some(matches)) => {
|
||||
let account_pubkey = if matches.is_present("buffers") {
|
||||
None
|
||||
} else {
|
||||
pubkey_of(matches, "account")
|
||||
};
|
||||
|
||||
let recipient_pubkey = if let Some(recipient_pubkey) =
|
||||
pubkey_of_signer(matches, "recipient_account", wallet_manager)?
|
||||
{
|
||||
recipient_pubkey
|
||||
} else {
|
||||
default_signer
|
||||
.signer_from_path(matches, wallet_manager)?
|
||||
.pubkey()
|
||||
};
|
||||
|
||||
let (authority_signer, authority_pubkey) =
|
||||
signer_of(matches, "buffer_authority", wallet_manager)?;
|
||||
|
||||
let signer_info = default_signer.generate_unique_signers(
|
||||
vec![
|
||||
Some(default_signer.signer_from_path(matches, wallet_manager)?),
|
||||
authority_signer,
|
||||
],
|
||||
matches,
|
||||
wallet_manager,
|
||||
)?;
|
||||
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Program(ProgramCliCommand::Close {
|
||||
account_pubkey,
|
||||
recipient_pubkey,
|
||||
authority_index: signer_info.index_of(authority_pubkey).unwrap(),
|
||||
use_lamports_unit: matches.is_present("lamports"),
|
||||
}),
|
||||
signers: signer_info.signers,
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Ok(response)
|
||||
|
@ -573,13 +701,36 @@ pub fn process_program_subcommand(
|
|||
*upgrade_authority_index,
|
||||
*new_upgrade_authority,
|
||||
),
|
||||
ProgramCliCommand::Show { account_pubkey } => {
|
||||
process_show(&rpc_client, config, *account_pubkey)
|
||||
}
|
||||
ProgramCliCommand::Show {
|
||||
account_pubkey,
|
||||
authority_pubkey,
|
||||
all,
|
||||
use_lamports_unit,
|
||||
} => process_show(
|
||||
&rpc_client,
|
||||
config,
|
||||
*account_pubkey,
|
||||
*authority_pubkey,
|
||||
*all,
|
||||
*use_lamports_unit,
|
||||
),
|
||||
ProgramCliCommand::Dump {
|
||||
account_pubkey,
|
||||
output_location,
|
||||
} => process_dump(&rpc_client, config, *account_pubkey, output_location),
|
||||
ProgramCliCommand::Close {
|
||||
account_pubkey,
|
||||
recipient_pubkey,
|
||||
authority_index,
|
||||
use_lamports_unit,
|
||||
} => process_close(
|
||||
&rpc_client,
|
||||
config,
|
||||
*account_pubkey,
|
||||
*recipient_pubkey,
|
||||
*authority_index,
|
||||
*use_lamports_unit,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -945,10 +1096,41 @@ fn process_set_authority(
|
|||
Ok(config.output_format.formatted_string(&authority))
|
||||
}
|
||||
|
||||
fn get_buffers(
|
||||
rpc_client: &RpcClient,
|
||||
authority_pubkey: Option<Pubkey>,
|
||||
) -> Result<Vec<(Pubkey, Account)>, Box<dyn std::error::Error>> {
|
||||
let mut bytes = vec![1, 0, 0, 0, 1];
|
||||
let length = bytes.len() + 32; // Pubkey length
|
||||
if let Some(authority_pubkey) = authority_pubkey {
|
||||
bytes.extend_from_slice(authority_pubkey.as_ref());
|
||||
}
|
||||
|
||||
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()
|
||||
},
|
||||
},
|
||||
)?;
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
fn process_show(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
account_pubkey: Option<Pubkey>,
|
||||
authority_pubkey: Pubkey,
|
||||
all: bool,
|
||||
use_lamports_unit: bool,
|
||||
) -> ProcessResult {
|
||||
if let Some(account_pubkey) = account_pubkey {
|
||||
if let Some(account) = rpc_client
|
||||
|
@ -1013,6 +1195,8 @@ fn process_show(
|
|||
.unwrap_or_else(|| "none".to_string()),
|
||||
data_len: account.data.len()
|
||||
- UpgradeableLoaderState::buffer_data_offset()?,
|
||||
lamports: account.lamports,
|
||||
use_lamports_unit,
|
||||
}))
|
||||
} else {
|
||||
Err(format!(
|
||||
|
@ -1028,7 +1212,30 @@ fn process_show(
|
|||
Err(format!("Unable to find the account {}", account_pubkey).into())
|
||||
}
|
||||
} else {
|
||||
Err("No account specified".into())
|
||||
let authority_pubkey = if all { None } else { Some(authority_pubkey) };
|
||||
let mut buffers = vec![];
|
||||
let results = get_buffers(rpc_client, authority_pubkey)?;
|
||||
for (address, account) in results.iter() {
|
||||
if let Ok(UpgradeableLoaderState::Buffer { authority_address }) = account.state() {
|
||||
buffers.push(CliUpgradeableBuffer {
|
||||
address: address.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,
|
||||
});
|
||||
} else {
|
||||
return Err(format!("Error parsing account {}", address).into());
|
||||
}
|
||||
}
|
||||
Ok(config
|
||||
.output_format
|
||||
.formatted_string(&CliUpgradeableBuffers {
|
||||
buffers,
|
||||
use_lamports_unit,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1103,6 +1310,162 @@ fn process_dump(
|
|||
}
|
||||
}
|
||||
|
||||
fn close(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
account_pubkey: &Pubkey,
|
||||
recipient_pubkey: &Pubkey,
|
||||
authority_signer: &dyn Signer,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash()?;
|
||||
|
||||
let mut tx = Transaction::new_unsigned(Message::new(
|
||||
&[bpf_loader_upgradeable::close(
|
||||
&account_pubkey,
|
||||
&recipient_pubkey,
|
||||
&authority_signer.pubkey(),
|
||||
)],
|
||||
Some(&config.signers[0].pubkey()),
|
||||
));
|
||||
|
||||
tx.try_sign(&[config.signers[0], authority_signer], blockhash)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
RpcSendTransactionConfig {
|
||||
skip_preflight: true,
|
||||
preflight_commitment: Some(config.commitment.commitment),
|
||||
..RpcSendTransactionConfig::default()
|
||||
},
|
||||
);
|
||||
if let Err(err) = result {
|
||||
if let ClientErrorKind::TransactionError(TransactionError::InstructionError(
|
||||
_,
|
||||
InstructionError::InvalidInstructionData,
|
||||
)) = err.kind()
|
||||
{
|
||||
return Err("Closing a buffer account is not supported by the cluster".into());
|
||||
} else {
|
||||
return Err(format!("Close failed: {}", err).into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_close(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
account_pubkey: Option<Pubkey>,
|
||||
recipient_pubkey: Pubkey,
|
||||
authority_index: SignerIndex,
|
||||
use_lamports_unit: bool,
|
||||
) -> ProcessResult {
|
||||
let authority_signer = config.signers[authority_index];
|
||||
let mut buffers = vec![];
|
||||
|
||||
if let Some(account_pubkey) = account_pubkey {
|
||||
if let Some(account) = rpc_client
|
||||
.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,
|
||||
)?;
|
||||
|
||||
if let Ok(UpgradeableLoaderState::Buffer { authority_address }) =
|
||||
account.state()
|
||||
{
|
||||
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,
|
||||
});
|
||||
} else {
|
||||
return Err(format!("Error parsing 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()
|
||||
},
|
||||
},
|
||||
)?;
|
||||
|
||||
for (address, account) in results.iter() {
|
||||
if close(
|
||||
rpc_client,
|
||||
config,
|
||||
&address,
|
||||
&recipient_pubkey,
|
||||
authority_signer,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
if let Ok(UpgradeableLoaderState::Buffer { authority_address }) = account.state() {
|
||||
buffers.push(CliUpgradeableBuffer {
|
||||
address: address.to_string(),
|
||||
authority: authority_address
|
||||
.map(|address| address.to_string())
|
||||
.unwrap_or_else(|| "none".to_string()),
|
||||
data_len: 0,
|
||||
lamports: account.lamports,
|
||||
use_lamports_unit,
|
||||
});
|
||||
} else {
|
||||
return Err(format!("Error parsing account {}", address).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(config
|
||||
.output_format
|
||||
.formatted_string(&CliUpgradeableBuffers {
|
||||
buffers,
|
||||
use_lamports_unit,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Deploy using non-upgradeable loader
|
||||
pub fn process_deploy(
|
||||
rpc_client: &RpcClient,
|
||||
|
@ -1614,16 +1977,21 @@ fn report_ephemeral_mnemonic(words: usize, mnemonic: bip39::Mnemonic) {
|
|||
let phrase: &str = mnemonic.phrase();
|
||||
let divider = String::from_utf8(vec![b'='; phrase.len()]).unwrap();
|
||||
eprintln!(
|
||||
"{}\nTo resume a failed deploy, recover the ephemeral keypair file with",
|
||||
"{}\nRecover the intermediate account's ephemeral keypair file with",
|
||||
divider
|
||||
);
|
||||
eprintln!(
|
||||
"`solana-keygen recover` and the following {}-word seed phrase,",
|
||||
"`solana-keygen recover` and the following {}-word seed phrase:",
|
||||
words
|
||||
);
|
||||
eprintln!("{}\n{}\n{}", divider, phrase, divider);
|
||||
eprintln!("To resume a deploy, pass the recovered keypair as");
|
||||
eprintln!("the [PROGRAM_ADDRESS_SIGNER] argument to `solana deploy` or");
|
||||
eprintln!("as the [BUFFER_SIGNER] to `solana program deploy` or `solana write-buffer'.");
|
||||
eprintln!("Or to recover the account's lamports, pass it as the");
|
||||
eprintln!(
|
||||
"then pass it as the [BUFFER_SIGNER] argument to `solana deploy` or `solana write-buffer`\n{}\n{}\n{}",
|
||||
divider, phrase, divider
|
||||
"[BUFFER_ACCOUNT_ADDRESS] argument to `solana program close`.\n{}",
|
||||
divider
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2267,9 +2635,6 @@ mod tests {
|
|||
let authority = Keypair::new();
|
||||
let authority_keypair_file = make_tmp_path("authority_keypair_file");
|
||||
write_keypair_file(&authority, &authority_keypair_file).unwrap();
|
||||
let new_authority_pubkey = Keypair::new();
|
||||
let new_authority_pubkey_file = make_tmp_path("authority_keypair_file");
|
||||
write_keypair_file(&new_authority_pubkey, &new_authority_pubkey_file).unwrap();
|
||||
let test_deploy = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"program",
|
||||
|
@ -2331,16 +2696,16 @@ mod tests {
|
|||
);
|
||||
|
||||
let buffer_pubkey = Pubkey::new_unique();
|
||||
let new_authority_pubkey = Keypair::new();
|
||||
let new_authority_pubkey_file = make_tmp_path("authority_keypair_file");
|
||||
write_keypair_file(&new_authority_pubkey, &new_authority_pubkey_file).unwrap();
|
||||
let new_authority_keypair = Keypair::new();
|
||||
let new_authority_keypair_file = make_tmp_path("authority_keypair_file");
|
||||
write_keypair_file(&new_authority_keypair, &new_authority_keypair_file).unwrap();
|
||||
let test_deploy = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"program",
|
||||
"set-buffer-authority",
|
||||
&buffer_pubkey.to_string(),
|
||||
"--new-buffer-authority",
|
||||
&new_authority_pubkey_file,
|
||||
&new_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_deploy, &default_signer, &mut None).unwrap(),
|
||||
|
@ -2348,13 +2713,223 @@ mod tests {
|
|||
command: CliCommand::Program(ProgramCliCommand::SetBufferAuthority {
|
||||
buffer_pubkey,
|
||||
buffer_authority_index: Some(0),
|
||||
new_buffer_authority: new_authority_pubkey.pubkey(),
|
||||
new_buffer_authority: new_authority_keypair.pubkey(),
|
||||
}),
|
||||
signers: vec![read_keypair_file(&keypair_file).unwrap().into()],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn test_cli_parse_show() {
|
||||
let test_commands = app("test", "desc", "version");
|
||||
|
||||
let default_keypair = Keypair::new();
|
||||
let keypair_file = make_tmp_path("keypair_file");
|
||||
write_keypair_file(&default_keypair, &keypair_file).unwrap();
|
||||
let default_signer = DefaultSigner {
|
||||
path: keypair_file,
|
||||
arg_name: "".to_string(),
|
||||
};
|
||||
|
||||
// defaults
|
||||
let buffer_pubkey = Pubkey::new_unique();
|
||||
let authority_keypair = Keypair::new();
|
||||
let authority_keypair_file = make_tmp_path("authority_keypair_file");
|
||||
write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
|
||||
|
||||
let test_command = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"program",
|
||||
"show",
|
||||
&buffer_pubkey.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_command, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: Some(buffer_pubkey),
|
||||
authority_pubkey: default_keypair.pubkey(),
|
||||
all: false,
|
||||
use_lamports_unit: false,
|
||||
}),
|
||||
signers: vec![],
|
||||
}
|
||||
);
|
||||
|
||||
let test_command = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"program",
|
||||
"show",
|
||||
"--buffers",
|
||||
"--all",
|
||||
"--lamports",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_command, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: None,
|
||||
authority_pubkey: default_keypair.pubkey(),
|
||||
all: true,
|
||||
use_lamports_unit: true,
|
||||
}),
|
||||
signers: vec![],
|
||||
}
|
||||
);
|
||||
|
||||
let test_command = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"program",
|
||||
"show",
|
||||
"--buffers",
|
||||
"--buffer-authority",
|
||||
&authority_keypair.pubkey().to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_command, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: None,
|
||||
authority_pubkey: authority_keypair.pubkey(),
|
||||
all: false,
|
||||
use_lamports_unit: false,
|
||||
}),
|
||||
signers: vec![],
|
||||
}
|
||||
);
|
||||
|
||||
let test_command = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"program",
|
||||
"show",
|
||||
"--buffers",
|
||||
"--buffer-authority",
|
||||
&authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_command, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: None,
|
||||
authority_pubkey: authority_keypair.pubkey(),
|
||||
all: false,
|
||||
use_lamports_unit: false,
|
||||
}),
|
||||
signers: vec![],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn test_cli_parse_close() {
|
||||
let test_commands = app("test", "desc", "version");
|
||||
|
||||
let default_keypair = Keypair::new();
|
||||
let keypair_file = make_tmp_path("keypair_file");
|
||||
write_keypair_file(&default_keypair, &keypair_file).unwrap();
|
||||
let default_signer = DefaultSigner {
|
||||
path: keypair_file.clone(),
|
||||
arg_name: "".to_string(),
|
||||
};
|
||||
|
||||
// defaults
|
||||
let buffer_pubkey = Pubkey::new_unique();
|
||||
let recipient_pubkey = Pubkey::new_unique();
|
||||
let authority_keypair = Keypair::new();
|
||||
let authority_keypair_file = make_tmp_path("authority_keypair_file");
|
||||
|
||||
let test_command = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"program",
|
||||
"close",
|
||||
&buffer_pubkey.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_command, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Program(ProgramCliCommand::Close {
|
||||
account_pubkey: Some(buffer_pubkey),
|
||||
recipient_pubkey: default_keypair.pubkey(),
|
||||
authority_index: 0,
|
||||
use_lamports_unit: false,
|
||||
}),
|
||||
signers: vec![read_keypair_file(&keypair_file).unwrap().into()],
|
||||
}
|
||||
);
|
||||
|
||||
// with authority
|
||||
write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
|
||||
let test_command = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"program",
|
||||
"close",
|
||||
&buffer_pubkey.to_string(),
|
||||
"--buffer-authority",
|
||||
&authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_command, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Program(ProgramCliCommand::Close {
|
||||
account_pubkey: Some(buffer_pubkey),
|
||||
recipient_pubkey: default_keypair.pubkey(),
|
||||
authority_index: 1,
|
||||
use_lamports_unit: false,
|
||||
}),
|
||||
signers: vec![
|
||||
read_keypair_file(&keypair_file).unwrap().into(),
|
||||
read_keypair_file(&authority_keypair_file).unwrap().into(),
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
// with recipient
|
||||
let test_command = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"program",
|
||||
"close",
|
||||
&buffer_pubkey.to_string(),
|
||||
"--recipient",
|
||||
&recipient_pubkey.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_command, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Program(ProgramCliCommand::Close {
|
||||
account_pubkey: Some(buffer_pubkey),
|
||||
recipient_pubkey,
|
||||
authority_index: 0,
|
||||
use_lamports_unit: false,
|
||||
}),
|
||||
signers: vec![read_keypair_file(&keypair_file).unwrap().into(),],
|
||||
}
|
||||
);
|
||||
|
||||
// --buffers and lamports
|
||||
let test_command = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"program",
|
||||
"close",
|
||||
"--buffers",
|
||||
"--lamports",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_command, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Program(ProgramCliCommand::Close {
|
||||
account_pubkey: None,
|
||||
recipient_pubkey: default_keypair.pubkey(),
|
||||
authority_index: 0,
|
||||
use_lamports_unit: true,
|
||||
}),
|
||||
signers: vec![read_keypair_file(&keypair_file).unwrap().into(),],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cli_keypair_file() {
|
||||
solana_logger::setup();
|
||||
|
|
|
@ -442,6 +442,9 @@ fn test_cli_program_deploy_with_authority() {
|
|||
config.signers = vec![&keypair];
|
||||
config.command = CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: Some(program_pubkey),
|
||||
authority_pubkey: keypair.pubkey(),
|
||||
all: false,
|
||||
use_lamports_unit: false,
|
||||
});
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
|
@ -530,6 +533,9 @@ fn test_cli_program_deploy_with_authority() {
|
|||
config.signers = vec![&keypair];
|
||||
config.command = CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: Some(program_pubkey),
|
||||
authority_pubkey: keypair.pubkey(),
|
||||
all: false,
|
||||
use_lamports_unit: false,
|
||||
});
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
|
@ -657,9 +663,12 @@ fn test_cli_program_write_buffer() {
|
|||
);
|
||||
|
||||
// Get buffer authority
|
||||
config.signers = vec![&keypair];
|
||||
config.signers = vec![];
|
||||
config.command = CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: Some(buffer_keypair.pubkey()),
|
||||
authority_pubkey: keypair.pubkey(),
|
||||
all: false,
|
||||
use_lamports_unit: false,
|
||||
});
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
|
@ -747,9 +756,12 @@ fn test_cli_program_write_buffer() {
|
|||
);
|
||||
|
||||
// Get buffer authority
|
||||
config.signers = vec![&keypair];
|
||||
config.signers = vec![];
|
||||
config.command = CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: Some(buffer_pubkey),
|
||||
authority_pubkey: keypair.pubkey(),
|
||||
all: false,
|
||||
use_lamports_unit: false,
|
||||
});
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
|
@ -764,6 +776,60 @@ fn test_cli_program_write_buffer() {
|
|||
authority_keypair.pubkey(),
|
||||
Pubkey::from_str(&authority_pubkey_str).unwrap()
|
||||
);
|
||||
|
||||
// Close buffer
|
||||
let close_account = rpc_client.get_account(&buffer_pubkey).unwrap();
|
||||
assert_eq!(minimum_balance_for_buffer, close_account.lamports);
|
||||
let recipient_pubkey = Pubkey::new_unique();
|
||||
config.signers = vec![&keypair, &authority_keypair];
|
||||
config.command = CliCommand::Program(ProgramCliCommand::Close {
|
||||
account_pubkey: Some(buffer_pubkey),
|
||||
recipient_pubkey,
|
||||
authority_index: 1,
|
||||
use_lamports_unit: false,
|
||||
});
|
||||
process_command(&config).unwrap();
|
||||
rpc_client.get_account(&buffer_pubkey).unwrap_err();
|
||||
let recipient_account = rpc_client.get_account(&recipient_pubkey).unwrap();
|
||||
assert_eq!(minimum_balance_for_buffer, recipient_account.lamports);
|
||||
|
||||
// Write a buffer with default params
|
||||
config.signers = vec![&keypair];
|
||||
config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
buffer_signer_index: None,
|
||||
buffer_pubkey: None,
|
||||
buffer_authority_signer_index: None,
|
||||
max_len: None,
|
||||
});
|
||||
config.output_format = OutputFormat::JsonCompact;
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
let buffer_pubkey_str = json
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("buffer")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let new_buffer_pubkey = Pubkey::from_str(&buffer_pubkey_str).unwrap();
|
||||
|
||||
// Close buffers and deposit default keypair
|
||||
let pre_lamports = rpc_client.get_account(&keypair.pubkey()).unwrap().lamports;
|
||||
config.signers = vec![&keypair];
|
||||
config.command = CliCommand::Program(ProgramCliCommand::Close {
|
||||
account_pubkey: Some(new_buffer_pubkey),
|
||||
recipient_pubkey: keypair.pubkey(),
|
||||
authority_index: 0,
|
||||
use_lamports_unit: false,
|
||||
});
|
||||
process_command(&config).unwrap();
|
||||
rpc_client.get_account(&new_buffer_pubkey).unwrap_err();
|
||||
let recipient_account = rpc_client.get_account(&keypair.pubkey()).unwrap();
|
||||
assert_eq!(
|
||||
pre_lamports + minimum_balance_for_buffer,
|
||||
recipient_account.lamports
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1029,6 +1095,9 @@ fn test_cli_program_show() {
|
|||
config.signers = vec![&keypair];
|
||||
config.command = CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: Some(buffer_keypair.pubkey()),
|
||||
authority_pubkey: keypair.pubkey(),
|
||||
all: false,
|
||||
use_lamports_unit: false,
|
||||
});
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
|
@ -1086,6 +1155,9 @@ fn test_cli_program_show() {
|
|||
config.signers = vec![&keypair];
|
||||
config.command = CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: Some(program_keypair.pubkey()),
|
||||
authority_pubkey: keypair.pubkey(),
|
||||
all: false,
|
||||
use_lamports_unit: false,
|
||||
});
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
|
|
|
@ -124,20 +124,25 @@ call to `deploy`.
|
|||
Deployment failures will print an error message specifying the seed phrase
|
||||
needed to recover the generated intermediate buffer's keypair:
|
||||
|
||||
```bash
|
||||
=======================================================================
|
||||
To resume a failed deploy, recover the ephemeral keypair file with
|
||||
`solana-keygen recover` and the following 12-word seed phrase,
|
||||
then pass it as the [BUFFER_SIGNER] argument to `solana deploy` or `solana write-buffer`
|
||||
=======================================================================
|
||||
spy axis cream equip bonus daring muffin fish noise churn broken diesel
|
||||
=======================================================================
|
||||
```
|
||||
==================================================================================
|
||||
Recover the intermediate account's ephemeral keypair file with
|
||||
`solana-keygen recover` and the following 12-word seed phrase:
|
||||
==================================================================================
|
||||
valley flat great hockey share token excess clever benefit traffic avocado athlete
|
||||
==================================================================================
|
||||
To resume a deploy, pass the recovered keypair as
|
||||
the [PROGRAM_ADDRESS_SIGNER] argument to `solana deploy` or
|
||||
as the [BUFFER_SIGNER] to `solana program deploy` or `solana write-buffer'.
|
||||
Or to recover the account's lamports, pass it as the
|
||||
[BUFFER_ACCOUNT_ADDRESS] argument to `solana program drain`.
|
||||
==================================================================================
|
||||
```
|
||||
|
||||
To recover the keypair:
|
||||
|
||||
```bash
|
||||
$ solana-keypair recover -o <KEYPAIR_PATH>
|
||||
solana-keypair recover -o <KEYPAIR_PATH>
|
||||
```
|
||||
|
||||
When asked, enter the 12-word seed phrase.
|
||||
|
@ -145,7 +150,57 @@ When asked, enter the 12-word seed phrase.
|
|||
Then issue a new `deploy` command and specify the buffer:
|
||||
|
||||
```bash
|
||||
$ solana program deploy --buffer <KEYPAIR_PATH> <PROGRAM_FILEPATH>
|
||||
solana program deploy --buffer <KEYPAIR_PATH> <PROGRAM_FILEPATH>
|
||||
```
|
||||
|
||||
### Closing buffer accounts and reclaiming their lamports
|
||||
|
||||
If deployment fails there will be a left over buffer account that holds
|
||||
lamports. The buffer account can either be used to [resume a
|
||||
deploy](#resuming-a-failed-deploy) or closed. When closed, the full balance of
|
||||
the buffer account will be transferred to the recipient's account.
|
||||
|
||||
The buffer account's authority must be present to close a buffer account, to
|
||||
list all the open buffer accounts that match the default authority:
|
||||
|
||||
```bash
|
||||
solana program show --buffers
|
||||
```
|
||||
|
||||
To specify a different authority:
|
||||
|
||||
```bash
|
||||
solana program show --buffers --buffer-authority <AURTHORITY_ADRESS>
|
||||
```
|
||||
|
||||
To close a single account:
|
||||
|
||||
```bash
|
||||
solana program close <BUFFER_ADDRESS>
|
||||
```
|
||||
|
||||
To close a single account and specify a different authority than the default:
|
||||
|
||||
```bash
|
||||
solana program close <BUFFER_ADDRESS> --buffer-authority <KEYPAIR_FILEPATH>
|
||||
```
|
||||
|
||||
To close a single account and specify a different recipient than the default:
|
||||
|
||||
```bash
|
||||
solana program close <BUFFER_ADDRESS> --recipient <RECIPIENT_ADDRESS>
|
||||
```
|
||||
|
||||
To close all the buffer accounts associated with the current authority:
|
||||
|
||||
```bash
|
||||
solana program close --buffers
|
||||
```
|
||||
|
||||
To show all buffer accounts regardless of the authority
|
||||
|
||||
```bash
|
||||
solana program show --buffers --all
|
||||
```
|
||||
|
||||
### Set a program's upgrade authority
|
||||
|
|
|
@ -30,7 +30,7 @@ use solana_sdk::{
|
|||
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
||||
clock::Clock,
|
||||
entrypoint::SUCCESS,
|
||||
feature_set::skip_ro_deserialization,
|
||||
feature_set::{skip_ro_deserialization, upgradeable_close_instruction},
|
||||
ic_logger_msg, ic_msg,
|
||||
instruction::InstructionError,
|
||||
keyed_account::{from_keyed_account, next_keyed_account, KeyedAccount},
|
||||
|
@ -566,11 +566,9 @@ fn process_loader_upgradeable_instruction(
|
|||
programdata.try_account_ref_mut()?.data_as_mut_slice()
|
||||
[programdata_data_offset..programdata_data_offset + buffer_data_len]
|
||||
.copy_from_slice(&buffer.try_account_ref()?.data()[buffer_data_offset..]);
|
||||
for i in &mut programdata.try_account_ref_mut()?.data_as_mut_slice()
|
||||
programdata.try_account_ref_mut()?.data_as_mut_slice()
|
||||
[programdata_data_offset + buffer_data_len..]
|
||||
{
|
||||
*i = 0
|
||||
}
|
||||
.fill(0);
|
||||
|
||||
// Fund ProgramData to rent-exemption, spill the rest
|
||||
|
||||
|
@ -634,12 +632,49 @@ fn process_loader_upgradeable_instruction(
|
|||
}
|
||||
_ => {
|
||||
ic_logger_msg!(logger, "Account does not support authorities");
|
||||
return Err(InstructionError::InvalidAccountData);
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
}
|
||||
|
||||
ic_logger_msg!(logger, "New authority {:?}", new_authority);
|
||||
}
|
||||
UpgradeableLoaderInstruction::Close => {
|
||||
if !invoke_context.is_feature_active(&upgradeable_close_instruction::id()) {
|
||||
return Err(InstructionError::InvalidInstructionData);
|
||||
}
|
||||
let close_account = next_keyed_account(account_iter)?;
|
||||
let recipient_account = next_keyed_account(account_iter)?;
|
||||
let authority = next_keyed_account(account_iter)?;
|
||||
|
||||
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()?.lamports += close_account.lamports()?;
|
||||
close_account.try_account_ref_mut()?.lamports = 0;
|
||||
close_account.try_account_ref_mut()?.data.fill(0);
|
||||
} else {
|
||||
ic_logger_msg!(logger, "Account does not support closing");
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
|
||||
ic_logger_msg!(logger, "Closed {}", close_account.unsigned_key());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -3011,7 +3046,7 @@ mod tests {
|
|||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Err(InstructionError::InvalidAccountData),
|
||||
Err(InstructionError::InvalidArgument),
|
||||
process_instruction(
|
||||
&bpf_loader_upgradeable::id(),
|
||||
&[
|
||||
|
@ -3184,7 +3219,7 @@ mod tests {
|
|||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Err(InstructionError::InvalidAccountData),
|
||||
Err(InstructionError::InvalidArgument),
|
||||
process_instruction(
|
||||
&bpf_loader_upgradeable::id(),
|
||||
&[
|
||||
|
@ -3217,6 +3252,87 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bpf_loader_upgradeable_close() {
|
||||
let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Close).unwrap();
|
||||
let authority_address = Pubkey::new_unique();
|
||||
let authority_account = AccountSharedData::new_ref(1, 0, &Pubkey::new_unique());
|
||||
let recipient_address = Pubkey::new_unique();
|
||||
let recipient_account = AccountSharedData::new_ref(1, 0, &Pubkey::new_unique());
|
||||
let buffer_address = Pubkey::new_unique();
|
||||
let buffer_account = AccountSharedData::new_ref(
|
||||
1,
|
||||
UpgradeableLoaderState::buffer_len(0).unwrap(),
|
||||
&bpf_loader_upgradeable::id(),
|
||||
);
|
||||
|
||||
// Case: close a buffer account
|
||||
buffer_account
|
||||
.borrow_mut()
|
||||
.set_state(&UpgradeableLoaderState::Buffer {
|
||||
authority_address: Some(authority_address),
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Ok(()),
|
||||
process_instruction(
|
||||
&bpf_loader_upgradeable::id(),
|
||||
&[
|
||||
KeyedAccount::new(&buffer_address, false, &buffer_account),
|
||||
KeyedAccount::new(&recipient_address, false, &recipient_account),
|
||||
KeyedAccount::new_readonly(&authority_address, true, &authority_account),
|
||||
],
|
||||
&instruction,
|
||||
&mut MockInvokeContext::default()
|
||||
)
|
||||
);
|
||||
assert_eq!(0, buffer_account.borrow().lamports());
|
||||
assert_eq!(2, recipient_account.borrow().lamports());
|
||||
assert!(buffer_account.borrow().data.iter().all(|&value| value == 0));
|
||||
|
||||
// Case: close with wrong authority
|
||||
buffer_account
|
||||
.borrow_mut()
|
||||
.set_state(&UpgradeableLoaderState::Buffer {
|
||||
authority_address: Some(authority_address),
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Err(InstructionError::IncorrectAuthority),
|
||||
process_instruction(
|
||||
&bpf_loader_upgradeable::id(),
|
||||
&[
|
||||
KeyedAccount::new(&buffer_address, false, &buffer_account),
|
||||
KeyedAccount::new(&recipient_address, false, &recipient_account),
|
||||
KeyedAccount::new_readonly(&Pubkey::new_unique(), true, &authority_account),
|
||||
],
|
||||
&instruction,
|
||||
&mut MockInvokeContext::default()
|
||||
)
|
||||
);
|
||||
|
||||
// Case: close but not a buffer account
|
||||
buffer_account
|
||||
.borrow_mut()
|
||||
.set_state(&UpgradeableLoaderState::Program {
|
||||
programdata_address: Pubkey::new_unique(),
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Err(InstructionError::InvalidArgument),
|
||||
process_instruction(
|
||||
&bpf_loader_upgradeable::id(),
|
||||
&[
|
||||
KeyedAccount::new(&buffer_address, false, &buffer_account),
|
||||
KeyedAccount::new(&recipient_address, false, &recipient_account),
|
||||
KeyedAccount::new_readonly(&Pubkey::new_unique(), true, &authority_account),
|
||||
],
|
||||
&instruction,
|
||||
&mut MockInvokeContext::default()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// fuzzing utility function
|
||||
fn fuzz<F>(
|
||||
bytes: &[u8],
|
||||
|
|
|
@ -227,6 +227,20 @@ pub fn set_upgrade_authority(
|
|||
Instruction::new_with_bincode(id(), &UpgradeableLoaderInstruction::SetAuthority, metas)
|
||||
}
|
||||
|
||||
/// Returns the instructions required to close an account
|
||||
pub fn close(
|
||||
close_address: &Pubkey,
|
||||
recipient_address: &Pubkey,
|
||||
authority_address: &Pubkey,
|
||||
) -> Instruction {
|
||||
let metas = vec![
|
||||
AccountMeta::new(*close_address, false),
|
||||
AccountMeta::new(*recipient_address, false),
|
||||
AccountMeta::new_readonly(*authority_address, true),
|
||||
];
|
||||
Instruction::new_with_bincode(id(), &UpgradeableLoaderInstruction::Close, metas)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -107,4 +107,13 @@ pub enum UpgradeableLoaderInstruction {
|
|||
/// 2. `[]` The new authority, optional, if omitted then the program will
|
||||
/// not be upgradeable.
|
||||
SetAuthority,
|
||||
|
||||
/// Closes an account owned by the upgradeable loader of all lamports and
|
||||
/// withdraws all the lamports
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. `[writable]` The account to close.
|
||||
/// 1. `[writable]` The account to deposit the closed account's lamports.
|
||||
/// 2. `[signer]` The account's authority.
|
||||
Close,
|
||||
}
|
||||
|
|
|
@ -119,6 +119,10 @@ pub mod cpi_data_cost {
|
|||
solana_sdk::declare_id!("Hrg5bXePPGiAVWZfDHbvjqytSeyBDPAGAQ7v6N5i4gCX");
|
||||
}
|
||||
|
||||
pub mod upgradeable_close_instruction {
|
||||
solana_sdk::declare_id!("FsPaByos3gA9bUEhp3EimQpQPCoSvCEigHod496NmABQ");
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// Map of feature identifiers to user-visible description
|
||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
||||
|
@ -149,6 +153,7 @@ lazy_static! {
|
|||
(skip_ro_deserialization::id(), "skip deserialization of read-only accounts"),
|
||||
(require_stake_for_gossip::id(), "require stakes for propagating crds values through gossip #15561"),
|
||||
(cpi_data_cost::id(), "charge the compute budget for data passed via CPI"),
|
||||
(upgradeable_close_instruction::id(), "close upgradeable buffer accounts"),
|
||||
/*************** ADD NEW FEATURES HERE ***************/
|
||||
]
|
||||
.iter()
|
||||
|
|
|
@ -131,6 +131,17 @@ pub fn parse_bpf_upgradeable_loader(
|
|||
}),
|
||||
})
|
||||
}
|
||||
UpgradeableLoaderInstruction::Close => {
|
||||
check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 3)?;
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "close".to_string(),
|
||||
info: json!({
|
||||
"account": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
"recipient": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
"authority": account_keys[instruction.accounts[2] as usize].to_string()
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -352,5 +363,20 @@ mod test {
|
|||
}
|
||||
);
|
||||
assert!(parse_bpf_upgradeable_loader(&message.instructions[0], &keys[0..1]).is_err());
|
||||
|
||||
let instruction = solana_sdk::bpf_loader_upgradeable::close(&keys[0], &keys[1], &keys[2]);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&message.instructions[0], &keys[..3]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "close".to_string(),
|
||||
info: json!({
|
||||
"account": keys[1].to_string(),
|
||||
"recipient": keys[2].to_string(),
|
||||
"authority": keys[0].to_string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_bpf_upgradeable_loader(&message.instructions[0], &keys[0..1]).is_err());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue