#![allow(clippy::arithmetic_side_effects)]
#![allow(clippy::items_after_test_module)]
use {
agave_feature_set::enable_alt_bn128_syscall,
assert_matches::assert_matches,
serde_json::Value,
solana_account::{state_traits::StateMut, ReadableAccount},
solana_borsh::v1::try_from_slice_unchecked,
solana_cli::{
cli::{process_command, CliCommand, CliConfig},
program::{ProgramCliCommand, CLOSE_PROGRAM_WARNING},
program_v4::{AdditionalCliConfig, ProgramV4CliCommand},
test_utils::wait_n_slots,
},
solana_cli_output::{parse_sign_only_reply_string, OutputFormat},
solana_client::rpc_config::RpcSendTransactionConfig,
solana_commitment_config::CommitmentConfig,
solana_compute_budget_interface::ComputeBudgetInstruction,
solana_faucet::faucet::run_local_faucet_with_unique_port_for_tests,
solana_fee_calculator::FeeRateGovernor,
solana_keypair::Keypair,
solana_loader_v3_interface::state::UpgradeableLoaderState,
solana_pubkey::Pubkey,
solana_rent::Rent,
solana_rpc::rpc::JsonRpcConfig,
solana_rpc_client::rpc_client::{GetConfirmedSignaturesForAddress2Config, RpcClient},
solana_rpc_client_api::config::RpcTransactionConfig,
solana_rpc_client_nonce_utils::blockhash_query::BlockhashQuery,
solana_sdk_ids::{bpf_loader_upgradeable, compute_budget, loader_v4},
solana_signature::Signature,
solana_signer::{null_signer::NullSigner, Signer},
solana_streamer::socket::SocketAddrSpace,
solana_system_interface::program as system_program,
solana_test_validator::TestValidatorGenesis,
solana_transaction::Transaction,
solana_transaction_status::UiTransactionEncoding,
std::{
env,
fs::File,
io::{Read, Seek, SeekFrom},
path::{Path, PathBuf},
str::FromStr,
sync::Arc,
},
test_case::test_case,
};
fn test_validator_genesis(mint_keypair: Keypair) -> TestValidatorGenesis {
let mut genesis = TestValidatorGenesis::default();
genesis
.fee_rate_governor(FeeRateGovernor::new(0, 0))
.rent(Rent {
lamports_per_byte_year: 1,
exemption_threshold: 1.0,
..Rent::default()
})
.faucet_addr(Some(run_local_faucet_with_unique_port_for_tests(
mint_keypair,
)));
genesis
}
fn setup_rpc_client(config: &mut CliConfig) -> Arc<RpcClient> {
let rpc_client = Arc::new(RpcClient::new_with_timeouts_and_commitment(
config.json_rpc_url.to_string(),
config.rpc_timeout,
config.commitment,
config.confirm_transaction_initial_timeout,
));
config.rpc_client = Some(rpc_client.clone());
rpc_client
}
#[track_caller]
fn expect_command_failure(config: &CliConfig, should_fail_because: &str, error_expected: &str) {
let error_actual = process_command(config).expect_err(should_fail_because);
let error_actual = error_actual.to_string();
assert!(
error_expected == error_actual,
"Command failed as expected, but with an unexpected error. Expected: {error_expected}, \
Actual: {error_actual}",
);
}
#[track_caller]
fn expect_account_absent(rpc_client: &RpcClient, pubkey: Pubkey, absent_because: &str) {
let error_actual = rpc_client.get_account(&pubkey).expect_err(absent_because);
let error_actual = error_actual.to_string();
assert!(
format!("AccountNotFound: pubkey={pubkey}") == error_actual,
"Failed to retrieve an account details. Expected account to be absent, but got a \
different error: {error_actual}",
);
}
#[test]
fn test_cli_program_deploy_non_upgradeable() {
agave_logger::setup();
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_path.push("tests");
noop_path.push("fixtures");
noop_path.push("noop");
noop_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let test_validator = test_validator_genesis(mint_keypair)
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let mut file = File::open(noop_path.to_str().unwrap()).unwrap();
let mut program_data = Vec::new();
file.read_to_end(&mut program_data).unwrap();
let minimum_balance_for_programdata = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata(
program_data.len(),
))
.unwrap();
let minimum_balance_for_program = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program())
.unwrap();
let keypair = Keypair::new();
config.signers = vec![&keypair];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 4 * minimum_balance_for_programdata, };
process_command(&config).unwrap();
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: None,
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 0,
is_final: true,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let program_id_str = json
.as_object()
.unwrap()
.get("programId")
.unwrap()
.as_str()
.unwrap();
let program_id = Pubkey::from_str(program_id_str).unwrap();
let account0 = rpc_client.get_account(&program_id).unwrap();
assert_eq!(account0.lamports, minimum_balance_for_program);
assert_eq!(account0.owner, bpf_loader_upgradeable::id());
assert!(account0.executable);
let (programdata_pubkey, _) =
Pubkey::find_program_address(&[program_id.as_ref()], &bpf_loader_upgradeable::id());
let programdata_account = rpc_client.get_account(&programdata_pubkey).unwrap();
assert_eq!(
programdata_account.lamports,
minimum_balance_for_programdata
);
assert_eq!(programdata_account.owner, bpf_loader_upgradeable::id());
assert!(!programdata_account.executable);
assert_eq!(
programdata_account.data[UpgradeableLoaderState::size_of_programdata_metadata()..],
program_data[..]
);
let custom_address_keypair = Keypair::new();
config.signers = vec![&keypair, &custom_address_keypair];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: Some(1),
program_pubkey: None,
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 0,
is_final: true,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
process_command(&config).unwrap();
let account1 = rpc_client
.get_account(&custom_address_keypair.pubkey())
.unwrap();
assert_eq!(account1.lamports, minimum_balance_for_program);
assert_eq!(account1.owner, bpf_loader_upgradeable::id());
assert!(account1.executable);
let (programdata_pubkey, _) = Pubkey::find_program_address(
&[custom_address_keypair.pubkey().as_ref()],
&bpf_loader_upgradeable::id(),
);
let programdata_account = rpc_client.get_account(&programdata_pubkey).unwrap();
assert_eq!(
programdata_account.lamports,
minimum_balance_for_programdata
);
assert_eq!(programdata_account.owner, bpf_loader_upgradeable::id());
assert!(!programdata_account.executable);
assert_eq!(
programdata_account.data[UpgradeableLoaderState::size_of_programdata_metadata()..],
program_data[..]
);
expect_command_failure(
&config,
"Program can not be deployed at the same address twice",
&format!(
"Program {} is no longer upgradeable",
custom_address_keypair.pubkey()
),
);
let custom_address_keypair = Keypair::new();
config.signers = vec![&custom_address_keypair];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 2 * minimum_balance_for_programdata,
};
process_command(&config).unwrap();
config.signers = vec![&keypair, &custom_address_keypair];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: Some(1),
program_pubkey: None,
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 0,
is_final: true,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
expect_command_failure(
&config,
"The CLI blocks deployments into accounts that hold more than the necessary amount of SOL",
&format!(
"Account {} is not an upgradeable program or already in use",
custom_address_keypair.pubkey()
),
);
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: Some(1),
program_pubkey: None,
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 0,
is_final: true,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
expect_command_failure(
&config,
"The program is non-upgradable, so even if we skip the CLI account balance check, the \
upgrade still fails",
&format!(
"Account {} is not an upgradeable program or already in use",
custom_address_keypair.pubkey()
),
);
}
#[test]
fn test_cli_program_deploy_no_authority() {
agave_logger::setup();
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_path.push("tests");
noop_path.push("fixtures");
noop_path.push("noop");
noop_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let test_validator = test_validator_genesis(mint_keypair)
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let mut file = File::open(noop_path.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::size_of_programdata(
max_len,
))
.unwrap();
let minimum_balance_for_program = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program())
.unwrap();
let upgrade_authority = Keypair::new();
let keypair = Keypair::new();
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program,
};
config.signers = vec![&keypair];
process_command(&config).unwrap();
config.signers = vec![&keypair, &upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: None,
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: true,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let program_id_str = json
.as_object()
.unwrap()
.get("programId")
.unwrap()
.as_str()
.unwrap();
let program_id = Pubkey::from_str(program_id_str).unwrap();
config.signers = vec![&keypair, &upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: Some(program_id),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: false,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
expect_command_failure(
&config,
"Can not upgrade a program if it was deployed without the authority signature",
&format!("Program {program_id} is no longer upgradeable"),
);
}
#[test_case(true, true; "Feature enabled, skip preflight")]
#[test_case(true, false; "Feature enabled, don't skip preflight")]
#[test_case(false, true; "Feature disabled, skip preflight")]
#[test_case(false, false; "Feature disabled, don't skip preflight")]
fn test_cli_program_deploy_feature(enable_feature: bool, skip_preflight: bool) {
agave_logger::setup();
let mut program_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
program_path.push("tests");
program_path.push("fixtures");
program_path.push("alt_bn128");
program_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let mut test_validator_builder = test_validator_genesis(mint_keypair);
if !enable_feature {
test_validator_builder.deactivate_features(&[enable_alt_bn128_syscall::id()]);
}
let test_validator = test_validator_builder
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let mut file = File::open(program_path.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::size_of_programdata(
max_len,
))
.unwrap();
let minimum_balance_for_program = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program())
.unwrap();
let upgrade_authority = Keypair::new();
let keypair = Keypair::new();
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program,
};
config.signers = vec![&keypair];
config.send_transaction_config.skip_preflight = skip_preflight;
process_command(&config).unwrap();
config.signers = vec![&keypair, &upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(program_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: None,
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: true,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: false,
});
config.output_format = OutputFormat::JsonCompact;
if enable_feature {
let res = process_command(&config);
assert!(res.is_ok());
} else {
expect_command_failure(
&config,
"Program contains a syscall from a deactivated feature",
"ELF error: ELF error: Unresolved symbol (sol_alt_bn128_group_op) at instruction #49 \
(ELF file offset 0x188)",
);
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(program_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: None,
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: true,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
let response = process_command(&config);
if skip_preflight {
assert!(response
.err()
.unwrap()
.to_string()
.contains("Deploying program failed"));
} else {
assert!(response
.err()
.unwrap()
.to_string()
.contains("Deploying program failed: RPC response error -32002:"));
}
}
}
#[test_case(true; "Feature enabled")]
#[test_case(false; "Feature disabled")]
fn test_cli_program_upgrade_with_feature(enable_feature: bool) {
agave_logger::setup();
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_path.push("tests");
noop_path.push("fixtures");
noop_path.push("noop");
noop_path.set_extension("so");
let mut syscall_program_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
syscall_program_path.push("tests");
syscall_program_path.push("fixtures");
syscall_program_path.push("alt_bn128");
syscall_program_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let mut test_validator_builder = test_validator_genesis(mint_keypair);
if !enable_feature {
test_validator_builder.deactivate_features(&[enable_alt_bn128_syscall::id()]);
}
let test_validator = test_validator_builder
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let blockhash = rpc_client.get_latest_blockhash().unwrap();
let mut file = File::open(syscall_program_path.to_str().unwrap()).unwrap();
let mut large_program_data = Vec::new();
file.read_to_end(&mut large_program_data).unwrap();
let max_program_data_len = large_program_data.len();
let minimum_balance_for_large_buffer = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata(
max_program_data_len,
))
.unwrap();
config.send_transaction_config.skip_preflight = false;
let online_signer = Keypair::new();
let offline_signer = Keypair::new();
let buffer_signer = Keypair::new();
let program_signer = Keypair::new();
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_large_buffer, };
config.signers = vec![&online_signer];
process_command(&config).unwrap();
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_large_buffer, };
config.signers = vec![&offline_signer];
process_command(&config).unwrap();
config.signers = vec![&online_signer, &offline_signer, &program_signer];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: Some(2),
program_pubkey: Some(program_signer.pubkey()),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1, is_final: false,
max_len: Some(max_program_data_len), skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: false,
});
config.output_format = OutputFormat::JsonCompact;
process_command(&config).unwrap();
create_buffer_with_offline_authority(
&rpc_client,
&syscall_program_path,
&mut config,
&online_signer,
&offline_signer,
&buffer_signer,
);
config.signers = vec![&offline_signer];
config.command = CliCommand::Program(ProgramCliCommand::Upgrade {
fee_payer_signer_index: 0,
program_pubkey: program_signer.pubkey(),
buffer_pubkey: buffer_signer.pubkey(),
upgrade_authority_signer_index: 0,
sign_only: true,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::new(Some(blockhash), true, None),
skip_feature_verification: false,
});
config.output_format = OutputFormat::JsonCompact;
let sig_response = process_command(&config).unwrap();
let sign_only = parse_sign_only_reply_string(&sig_response);
let offline_pre_signer = sign_only.presigner_of(&offline_signer.pubkey()).unwrap();
config.signers = vec![&offline_pre_signer, &program_signer];
config.command = CliCommand::Program(ProgramCliCommand::Upgrade {
fee_payer_signer_index: 0,
program_pubkey: program_signer.pubkey(),
buffer_pubkey: buffer_signer.pubkey(),
upgrade_authority_signer_index: 0,
sign_only: false,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::new(Some(blockhash), true, None),
skip_feature_verification: false,
});
config.output_format = OutputFormat::JsonCompact;
if enable_feature {
let res = process_command(&config);
assert!(res.is_ok());
} else {
expect_command_failure(
&config,
"Program contains a syscall to a disabled feature",
format!(
"Buffer account {} has invalid program data: \"ELF error: ELF error: Unresolved \
symbol (sol_alt_bn128_group_op) at instruction #49 (ELF file offset 0x188)\"",
buffer_signer.pubkey()
)
.as_str(),
);
config.command = CliCommand::Program(ProgramCliCommand::Upgrade {
fee_payer_signer_index: 0,
program_pubkey: program_signer.pubkey(),
buffer_pubkey: buffer_signer.pubkey(),
upgrade_authority_signer_index: 0,
sign_only: false,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::new(Some(blockhash), true, None),
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
let response = process_command(&config);
assert!(response
.err()
.unwrap()
.to_string()
.contains("Upgrading program failed: RPC response error -32002"));
}
}
#[test]
fn test_cli_program_deploy_with_authority() {
agave_logger::setup();
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_path.push("tests");
noop_path.push("fixtures");
noop_path.push("noop");
noop_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let test_validator = test_validator_genesis(mint_keypair)
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let mut file = File::open(noop_path.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::size_of_programdata(
max_len,
))
.unwrap();
let minimum_balance_for_program = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program())
.unwrap();
let upgrade_authority = Keypair::new();
let keypair = Keypair::new();
config.signers = vec![&keypair];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program,
};
process_command(&config).unwrap();
let program_keypair = Keypair::new();
config.signers = vec![&keypair, &upgrade_authority, &program_keypair];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: Some(2),
program_pubkey: Some(program_keypair.pubkey()),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: false,
max_len: Some(max_len),
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let program_pubkey_str = json
.as_object()
.unwrap()
.get("programId")
.unwrap()
.as_str()
.unwrap();
assert_eq!(
program_keypair.pubkey(),
Pubkey::from_str(program_pubkey_str).unwrap()
);
let program_account = rpc_client.get_account(&program_keypair.pubkey()).unwrap();
assert_eq!(program_account.lamports, minimum_balance_for_program);
assert_eq!(program_account.owner, bpf_loader_upgradeable::id());
assert!(program_account.executable);
let (programdata_pubkey, _) = Pubkey::find_program_address(
&[program_keypair.pubkey().as_ref()],
&bpf_loader_upgradeable::id(),
);
let programdata_account = rpc_client.get_account(&programdata_pubkey).unwrap();
assert_eq!(
programdata_account.lamports,
minimum_balance_for_programdata
);
assert_eq!(programdata_account.owner, bpf_loader_upgradeable::id());
assert!(!programdata_account.executable);
assert_eq!(
programdata_account.data[UpgradeableLoaderState::size_of_programdata_metadata()..],
program_data[..]
);
config.signers = vec![&keypair, &upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: None,
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: false,
max_len: Some(max_len),
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let program_pubkey_str = json
.as_object()
.unwrap()
.get("programId")
.unwrap()
.as_str()
.unwrap();
let program_pubkey = Pubkey::from_str(program_pubkey_str).unwrap();
let program_account = rpc_client.get_account(&program_pubkey).unwrap();
assert_eq!(program_account.lamports, minimum_balance_for_program);
assert_eq!(program_account.owner, bpf_loader_upgradeable::id());
assert!(program_account.executable);
let (programdata_pubkey, _) =
Pubkey::find_program_address(&[program_pubkey.as_ref()], &bpf_loader_upgradeable::id());
let programdata_account = rpc_client.get_account(&programdata_pubkey).unwrap();
assert_eq!(
programdata_account.lamports,
minimum_balance_for_programdata
);
assert_eq!(programdata_account.owner, bpf_loader_upgradeable::id());
assert!(!programdata_account.executable);
assert_eq!(
programdata_account.data[UpgradeableLoaderState::size_of_programdata_metadata()..],
program_data[..]
);
config.signers = vec![&keypair, &upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: Some(program_pubkey),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: false,
max_len: Some(max_len),
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
process_command(&config).unwrap();
let program_account = rpc_client.get_account(&program_pubkey).unwrap();
assert_eq!(program_account.lamports, minimum_balance_for_program);
assert_eq!(program_account.owner, bpf_loader_upgradeable::id());
assert!(program_account.executable);
let (programdata_pubkey, _) =
Pubkey::find_program_address(&[program_pubkey.as_ref()], &bpf_loader_upgradeable::id());
let programdata_account = rpc_client.get_account(&programdata_pubkey).unwrap();
assert_eq!(
programdata_account.lamports,
minimum_balance_for_programdata
);
assert_eq!(programdata_account.owner, bpf_loader_upgradeable::id());
assert!(program_account.executable);
assert_eq!(
programdata_account.data[UpgradeableLoaderState::size_of_programdata_metadata()..],
program_data[..]
);
let blockhash = rpc_client.get_latest_blockhash().unwrap();
let new_upgrade_authority = Keypair::new();
config.signers = vec![&keypair, &upgrade_authority, &new_upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::SetUpgradeAuthorityChecked {
program_pubkey,
upgrade_authority_index: 1,
new_upgrade_authority_index: 2,
sign_only: true,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::new(Some(blockhash), true, None),
});
let sig_response = process_command(&config).unwrap();
let sign_only = parse_sign_only_reply_string(&sig_response);
let offline_pre_signer = sign_only
.presigner_of(&new_upgrade_authority.pubkey())
.unwrap();
config.signers = vec![&keypair, &upgrade_authority, &offline_pre_signer];
config.command = CliCommand::Program(ProgramCliCommand::SetUpgradeAuthorityChecked {
program_pubkey,
upgrade_authority_index: 1,
new_upgrade_authority_index: 2,
sign_only: false,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::new(Some(blockhash), false, None),
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let new_upgrade_authority_str = json
.as_object()
.unwrap()
.get("authority")
.unwrap()
.as_str()
.unwrap();
assert_eq!(
Pubkey::from_str(new_upgrade_authority_str).unwrap(),
new_upgrade_authority.pubkey()
);
config.signers = vec![&keypair, &new_upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: Some(program_pubkey),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: false,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
process_command(&config).unwrap();
let program_account = rpc_client.get_account(&program_pubkey).unwrap();
assert_eq!(program_account.lamports, minimum_balance_for_program);
assert_eq!(program_account.owner, bpf_loader_upgradeable::id());
assert!(program_account.executable);
let (programdata_pubkey, _) =
Pubkey::find_program_address(&[program_pubkey.as_ref()], &bpf_loader_upgradeable::id());
let programdata_account = rpc_client.get_account(&programdata_pubkey).unwrap();
assert_eq!(
programdata_account.lamports,
minimum_balance_for_programdata
);
assert_eq!(programdata_account.owner, bpf_loader_upgradeable::id());
assert!(program_account.executable);
assert_eq!(
programdata_account.data[UpgradeableLoaderState::size_of_programdata_metadata()..],
program_data[..]
);
config.signers = vec![&keypair];
config.command = CliCommand::Program(ProgramCliCommand::Show {
account_pubkey: Some(program_pubkey),
authority_pubkey: keypair.pubkey(),
get_programs: false,
get_buffers: false,
all: false,
use_lamports_unit: false,
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let authority_pubkey_str = json
.as_object()
.unwrap()
.get("authority")
.unwrap()
.as_str()
.unwrap();
assert_eq!(
new_upgrade_authority.pubkey(),
Pubkey::from_str(authority_pubkey_str).unwrap()
);
config.signers = vec![&keypair, &new_upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority {
program_pubkey,
upgrade_authority_index: Some(1),
new_upgrade_authority: None,
sign_only: false,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::default(),
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let new_upgrade_authority_str = json
.as_object()
.unwrap()
.get("authority")
.unwrap()
.as_str()
.unwrap();
assert_eq!(new_upgrade_authority_str, "none");
config.signers = vec![&keypair, &new_upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: Some(program_pubkey),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: false,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
expect_command_failure(
&config,
"Upgrade without an authority is not allowed",
&format!("Program {program_pubkey} is no longer upgradeable"),
);
config.signers = vec![&keypair, &new_upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: None,
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: true,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let program_pubkey_str = json
.as_object()
.unwrap()
.get("programId")
.unwrap()
.as_str()
.unwrap();
let program_pubkey = Pubkey::from_str(program_pubkey_str).unwrap();
let (programdata_pubkey, _) =
Pubkey::find_program_address(&[program_pubkey.as_ref()], &bpf_loader_upgradeable::id());
let programdata_account = rpc_client.get_account(&programdata_pubkey).unwrap();
if let UpgradeableLoaderState::ProgramData {
slot: _,
upgrade_authority_address,
} = programdata_account.state().unwrap()
{
assert_eq!(upgrade_authority_address, None);
} else {
panic!("not a ProgramData account");
}
config.signers = vec![&keypair];
config.command = CliCommand::Program(ProgramCliCommand::Show {
account_pubkey: Some(program_pubkey),
authority_pubkey: keypair.pubkey(),
get_programs: false,
get_buffers: false,
all: false,
use_lamports_unit: false,
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let authority_pubkey_str = json
.as_object()
.unwrap()
.get("authority")
.unwrap()
.as_str()
.unwrap();
assert_eq!("none", authority_pubkey_str);
}
#[test_case(true; "Skip preflight")]
#[test_case(false; "Dont skip preflight")]
fn test_cli_program_upgrade_auto_extend(skip_preflight: bool) {
agave_logger::setup();
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_path.push("tests");
noop_path.push("fixtures");
noop_path.push("noop");
noop_path.set_extension("so");
let mut noop_large_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_large_path.push("tests");
noop_large_path.push("fixtures");
noop_large_path.push("noop_large");
noop_large_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let test_validator = test_validator_genesis(mint_keypair)
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let mut file = File::open(noop_path.to_str().unwrap()).unwrap();
let mut program_data = Vec::new();
file.read_to_end(&mut program_data).unwrap();
let mut file = File::open(noop_large_path.to_str().unwrap()).unwrap();
let mut program_data_large = Vec::new();
file.read_to_end(&mut program_data_large).unwrap();
let max_len = program_data_large.len();
let minimum_balance_for_programdata = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata(
max_len,
))
.unwrap();
let minimum_balance_for_program = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program())
.unwrap();
let upgrade_authority = Keypair::new();
let keypair = Keypair::new();
config.signers = vec![&keypair];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program,
};
process_command(&config).unwrap();
let program_keypair = Keypair::new();
config.signers = vec![&keypair, &upgrade_authority, &program_keypair];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: Some(2),
program_pubkey: Some(program_keypair.pubkey()),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: false,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
config.send_transaction_config.skip_preflight = skip_preflight;
process_command(&config).unwrap();
config.signers = vec![&keypair, &upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_large_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: Some(program_keypair.pubkey()),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: true,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: false, use_rpc: false,
skip_feature_verification: true,
});
if skip_preflight {
expect_command_failure(
&config,
"Cannot upgrade a program when ELF does not fit into the allocated data account",
"Deploying program failed: Error processing Instruction 0: account data too small for \
instruction",
);
} else {
#[rustfmt::skip]
let expected_error =
"Deploying program failed: \
RPC response error -32002: \
Transaction simulation failed: \
Error processing Instruction 0: \
account data too small for instruction; 3 log messages:\n \
Program BPFLoaderUpgradeab1e11111111111111111111111 invoke [1]\n \
ProgramData account not large enough\n \
Program BPFLoaderUpgradeab1e11111111111111111111111 failed: account data too small \
for instruction\n";
expect_command_failure(
&config,
"Can not upgrade a program when ELF does not fit into the allocated data account",
expected_error,
);
}
config.signers = vec![&keypair, &upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_large_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: Some(program_keypair.pubkey()),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: true,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true, use_rpc: false,
skip_feature_verification: true,
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let program_pubkey_str = json
.as_object()
.unwrap()
.get("programId")
.unwrap()
.as_str()
.unwrap();
let program_pubkey = Pubkey::from_str(program_pubkey_str).unwrap();
let (programdata_pubkey, _) =
Pubkey::find_program_address(&[program_pubkey.as_ref()], &bpf_loader_upgradeable::id());
let programdata_account = rpc_client.get_account(&programdata_pubkey).unwrap();
if let UpgradeableLoaderState::ProgramData {
slot: _,
upgrade_authority_address,
} = programdata_account.state().unwrap()
{
assert_eq!(upgrade_authority_address, None);
} else {
panic!("not a ProgramData account");
}
assert_eq!(
programdata_account.data().len(),
UpgradeableLoaderState::size_of_programdata(program_data_large.len()),
);
}
#[test]
fn test_cli_program_close_program() {
agave_logger::setup();
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_path.push("tests");
noop_path.push("fixtures");
noop_path.push("noop");
noop_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let test_validator = test_validator_genesis(mint_keypair)
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let mut file = File::open(noop_path.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::size_of_programdata(
max_len,
))
.unwrap();
let minimum_balance_for_program = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program())
.unwrap();
let upgrade_authority = Keypair::new();
let keypair = Keypair::new();
config.signers = vec![&keypair];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program,
};
process_command(&config).unwrap();
let program_keypair = Keypair::new();
config.signers = vec![&keypair, &upgrade_authority, &program_keypair];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: Some(2),
program_pubkey: Some(program_keypair.pubkey()),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: false,
max_len: Some(max_len),
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
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(),
);
wait_n_slots(&rpc_client, 1);
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,
bypass_warning: false,
});
expect_command_failure(
&config,
"CLI requires the --bypass-warning flag in order to close a program",
CLOSE_PROGRAM_WARNING,
);
config.command = CliCommand::Program(ProgramCliCommand::Close {
account_pubkey: Some(program_keypair.pubkey()),
recipient_pubkey,
authority_index: 1,
use_lamports_unit: false,
bypass_warning: true,
});
process_command(&config).unwrap();
expect_account_absent(
&rpc_client,
programdata_pubkey,
"Program data account is deleted when the program is closed",
);
let recipient_account = rpc_client.get_account(&recipient_pubkey).unwrap();
assert_eq!(programdata_lamports, recipient_account.lamports);
}
#[test]
fn test_cli_program_extend_program() {
agave_logger::setup();
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_path.push("tests");
noop_path.push("fixtures");
noop_path.push("noop");
noop_path.set_extension("so");
let mut noop_large_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_large_path.push("tests");
noop_large_path.push("fixtures");
noop_large_path.push("noop_large");
noop_large_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let test_validator = test_validator_genesis(mint_keypair)
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let mut file = File::open(noop_path.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::size_of_programdata(
max_len,
))
.unwrap();
let minimum_balance_for_program = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program())
.unwrap();
let upgrade_authority = Keypair::new();
let keypair = Keypair::new();
config.signers = vec![&keypair];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program,
};
config.send_transaction_config = RpcSendTransactionConfig {
skip_preflight: false,
preflight_commitment: Some(CommitmentConfig::processed().commitment),
..RpcSendTransactionConfig::default()
};
process_command(&config).unwrap();
let program_keypair = Keypair::new();
config.signers = vec![&keypair, &upgrade_authority, &program_keypair];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: Some(2),
program_pubkey: Some(program_keypair.pubkey()),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: false,
max_len: None, skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: false,
use_rpc: false,
skip_feature_verification: true,
});
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(),
);
let programdata_account = rpc_client.get_account(&programdata_pubkey).unwrap();
let expected_len = UpgradeableLoaderState::size_of_programdata(max_len);
assert_eq!(expected_len, programdata_account.data.len());
wait_n_slots(&rpc_client, 1);
let mut file = File::open(noop_large_path.to_str().unwrap()).unwrap();
let mut new_program_data = Vec::new();
file.read_to_end(&mut new_program_data).unwrap();
let new_max_len = new_program_data.len();
let additional_bytes = (new_max_len - max_len) as u32;
config.signers = vec![&keypair, &upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::ExtendProgramChecked {
program_pubkey: program_keypair.pubkey(),
authority_signer_index: 1,
additional_bytes: additional_bytes - 1,
});
process_command(&config).unwrap();
let programdata_account = rpc_client.get_account(&programdata_pubkey).unwrap();
let expected_len = UpgradeableLoaderState::size_of_programdata(new_max_len - 1);
assert_eq!(expected_len, programdata_account.data.len());
config.signers = vec![&keypair, &upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_large_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: Some(program_keypair.pubkey()),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: false,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: false,
use_rpc: false,
skip_feature_verification: true,
});
#[rustfmt::skip]
let expected_error =
"Deploying program failed: \
RPC response error -32002: \
Transaction simulation failed: \
Error processing Instruction 0: \
account data too small for instruction; 3 log messages:\n \
Program BPFLoaderUpgradeab1e11111111111111111111111 invoke [1]\n \
ProgramData account not large enough\n \
Program BPFLoaderUpgradeab1e11111111111111111111111 failed: account data too small for \
instruction\n";
expect_command_failure(
&config,
"Program upgrade must fail, as the buffer is 1 byte too short",
expected_error,
);
wait_n_slots(&rpc_client, 1);
config.signers = vec![&keypair, &upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::ExtendProgramChecked {
program_pubkey: program_keypair.pubkey(),
authority_signer_index: 1,
additional_bytes: 1,
});
process_command(&config).unwrap();
let programdata_account = rpc_client.get_account(&programdata_pubkey).unwrap();
let expected_len = UpgradeableLoaderState::size_of_programdata(new_max_len);
assert_eq!(expected_len, programdata_account.data.len());
config.signers = vec![&keypair, &upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_large_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: Some(program_keypair.pubkey()),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: false,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: false,
use_rpc: false,
skip_feature_verification: true,
});
process_command(&config).unwrap();
}
#[test]
fn test_cli_program_migrate_program() {
agave_logger::setup();
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_path.push("tests");
noop_path.push("fixtures");
noop_path.push("noop");
noop_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let test_validator = test_validator_genesis(mint_keypair)
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let mut file = File::open(noop_path.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::size_of_programdata(
max_len,
))
.unwrap();
let minimum_balance_for_program = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program())
.unwrap();
let upgrade_authority = Keypair::new();
let keypair = Keypair::new();
config.signers = vec![&keypair];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program,
};
process_command(&config).unwrap();
let program_keypair = Keypair::new();
config.signers = vec![&keypair, &upgrade_authority, &program_keypair];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: Some(2),
program_pubkey: Some(program_keypair.pubkey()),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: false,
max_len: Some(max_len),
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
process_command(&config).unwrap();
wait_n_slots(&rpc_client, 1);
config.signers = vec![&keypair, &upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::MigrateProgram {
program_pubkey: program_keypair.pubkey(),
authority_signer_index: 1,
compute_unit_price: Some(1),
});
process_command(&config).unwrap();
}
#[test]
fn test_cli_program_write_buffer() {
agave_logger::setup();
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_path.push("tests");
noop_path.push("fixtures");
noop_path.push("noop");
noop_path.set_extension("so");
let mut noop_large_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_large_path.push("tests");
noop_large_path.push("fixtures");
noop_large_path.push("noop_large");
noop_large_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let test_validator = test_validator_genesis(mint_keypair)
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let mut file = File::open(noop_path.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_buffer = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata(
max_len,
))
.unwrap();
let minimum_balance_for_buffer_default = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata(
max_len,
))
.unwrap();
let keypair = Keypair::new();
config.signers = vec![&keypair];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_buffer,
};
process_command(&config).unwrap();
config.signers = vec![&keypair];
config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer {
program_location: noop_path.to_str().unwrap().to_string(),
fee_payer_signer_index: 0,
buffer_signer_index: None,
buffer_pubkey: None,
buffer_authority_signer_index: 0,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
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();
let buffer_account = rpc_client.get_account(&new_buffer_pubkey).unwrap();
assert_eq!(buffer_account.lamports, minimum_balance_for_buffer_default);
assert_eq!(buffer_account.owner, bpf_loader_upgradeable::id());
if let UpgradeableLoaderState::Buffer { authority_address } = buffer_account.state().unwrap() {
assert_eq!(authority_address, Some(keypair.pubkey()));
} else {
panic!("not a buffer account");
}
assert_eq!(
buffer_account.data[UpgradeableLoaderState::size_of_buffer_metadata()..],
program_data[..]
);
let buffer_keypair = Keypair::new();
config.signers = vec![&keypair, &buffer_keypair];
config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer {
program_location: noop_path.to_str().unwrap().to_string(),
fee_payer_signer_index: 0,
buffer_signer_index: Some(1),
buffer_pubkey: Some(buffer_keypair.pubkey()),
buffer_authority_signer_index: 0,
max_len: Some(max_len),
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
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();
assert_eq!(
buffer_keypair.pubkey(),
Pubkey::from_str(buffer_pubkey_str).unwrap()
);
let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap();
assert_eq!(buffer_account.lamports, minimum_balance_for_buffer);
assert_eq!(buffer_account.owner, bpf_loader_upgradeable::id());
if let UpgradeableLoaderState::Buffer { authority_address } = buffer_account.state().unwrap() {
assert_eq!(authority_address, Some(keypair.pubkey()));
} else {
panic!("not a buffer account");
}
assert_eq!(
buffer_account.data[UpgradeableLoaderState::size_of_buffer_metadata()..],
program_data[..]
);
config.signers = vec![];
config.command = CliCommand::Program(ProgramCliCommand::Show {
account_pubkey: Some(buffer_keypair.pubkey()),
authority_pubkey: keypair.pubkey(),
get_programs: false,
get_buffers: false,
all: false,
use_lamports_unit: false,
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let authority_pubkey_str = json
.as_object()
.unwrap()
.get("authority")
.unwrap()
.as_str()
.unwrap();
assert_eq!(
keypair.pubkey(),
Pubkey::from_str(authority_pubkey_str).unwrap()
);
let buffer_keypair = Keypair::new();
let authority_keypair = Keypair::new();
config.signers = vec![&keypair, &buffer_keypair, &authority_keypair];
config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer {
program_location: noop_path.to_str().unwrap().to_string(),
fee_payer_signer_index: 0,
buffer_signer_index: Some(1),
buffer_pubkey: Some(buffer_keypair.pubkey()),
buffer_authority_signer_index: 2,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
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();
assert_eq!(
buffer_keypair.pubkey(),
Pubkey::from_str(buffer_pubkey_str).unwrap()
);
let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap();
assert_eq!(buffer_account.lamports, minimum_balance_for_buffer_default);
assert_eq!(buffer_account.owner, bpf_loader_upgradeable::id());
if let UpgradeableLoaderState::Buffer { authority_address } = buffer_account.state().unwrap() {
assert_eq!(authority_address, Some(authority_keypair.pubkey()));
} else {
panic!("not a buffer account");
}
assert_eq!(
buffer_account.data[UpgradeableLoaderState::size_of_buffer_metadata()..],
program_data[..]
);
let buffer_keypair = Keypair::new();
let authority_keypair = Keypair::new();
config.signers = vec![&keypair, &buffer_keypair, &authority_keypair];
config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer {
program_location: noop_path.to_str().unwrap().to_string(),
fee_payer_signer_index: 0,
buffer_signer_index: None,
buffer_pubkey: None,
buffer_authority_signer_index: 2,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
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 buffer_pubkey = Pubkey::from_str(buffer_pubkey_str).unwrap();
let buffer_account = rpc_client.get_account(&buffer_pubkey).unwrap();
assert_eq!(buffer_account.lamports, minimum_balance_for_buffer_default);
assert_eq!(buffer_account.owner, bpf_loader_upgradeable::id());
if let UpgradeableLoaderState::Buffer { authority_address } = buffer_account.state().unwrap() {
assert_eq!(authority_address, Some(authority_keypair.pubkey()));
} else {
panic!("not a buffer account");
}
assert_eq!(
buffer_account.data[UpgradeableLoaderState::size_of_buffer_metadata()..],
program_data[..]
);
config.signers = vec![];
config.command = CliCommand::Program(ProgramCliCommand::Show {
account_pubkey: Some(buffer_pubkey),
authority_pubkey: keypair.pubkey(),
get_programs: false,
get_buffers: false,
all: false,
use_lamports_unit: false,
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let authority_pubkey_str = json
.as_object()
.unwrap()
.get("authority")
.unwrap()
.as_str()
.unwrap();
assert_eq!(
authority_keypair.pubkey(),
Pubkey::from_str(authority_pubkey_str).unwrap()
);
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,
bypass_warning: false,
});
process_command(&config).unwrap();
expect_account_absent(
&rpc_client,
buffer_pubkey,
"Buffer account is deleted when the buffer is closed",
);
let recipient_account = rpc_client.get_account(&recipient_pubkey).unwrap();
assert_eq!(minimum_balance_for_buffer, recipient_account.lamports);
config.signers = vec![&keypair];
config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer {
program_location: noop_path.to_str().unwrap().to_string(),
fee_payer_signer_index: 0,
buffer_signer_index: None,
buffer_pubkey: None,
buffer_authority_signer_index: 0,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
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();
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,
bypass_warning: false,
});
process_command(&config).unwrap();
expect_account_absent(
&rpc_client,
new_buffer_pubkey,
"Buffer account is deleted when the buffer is closed",
);
let recipient_account = rpc_client.get_account(&keypair.pubkey()).unwrap();
assert_eq!(
pre_lamports + minimum_balance_for_buffer,
recipient_account.lamports
);
let buffer_keypair = Keypair::new();
config.signers = vec![&keypair, &buffer_keypair];
config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer {
program_location: noop_path.to_str().unwrap().to_string(),
fee_payer_signer_index: 0,
buffer_signer_index: Some(1),
buffer_pubkey: Some(buffer_keypair.pubkey()),
buffer_authority_signer_index: 0,
max_len: None, skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
process_command(&config).unwrap();
config.signers = vec![&keypair, &buffer_keypair];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_large_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: None,
buffer_signer_index: Some(1),
buffer_pubkey: Some(buffer_keypair.pubkey()),
upgrade_authority_signer_index: 0,
is_final: true,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
let buffer_account_len = {
let mut file = File::open(noop_path.to_str().unwrap()).unwrap();
let program_data_len = file.seek(SeekFrom::End(0)).unwrap() as usize;
UpgradeableLoaderState::size_of_buffer_metadata() + program_data_len
};
let min_buffer_account_len = {
let mut file = File::open(noop_large_path.to_str().unwrap()).unwrap();
let large_program_data_len = file.seek(SeekFrom::End(0)).unwrap() as usize;
UpgradeableLoaderState::size_of_buffer_metadata() + large_program_data_len
};
expect_command_failure(
&config,
"It should not be possible to deploy a program into an account that is too small",
&format!(
"Buffer account data size ({buffer_account_len}) is smaller than the minimum size \
({min_buffer_account_len})"
),
);
}
#[test_case(true; "Feature enabled")]
#[test_case(false; "Feature disabled")]
fn test_cli_program_write_buffer_feature(enable_feature: bool) {
agave_logger::setup();
let mut program_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
program_path.push("tests");
program_path.push("fixtures");
program_path.push("alt_bn128");
program_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let mut test_validator_builder = test_validator_genesis(mint_keypair);
if !enable_feature {
test_validator_builder.deactivate_features(&[enable_alt_bn128_syscall::id()]);
}
let test_validator = test_validator_builder
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let mut file = File::open(program_path.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_buffer = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata(
max_len,
))
.unwrap();
let keypair = Keypair::new();
config.signers = vec![&keypair];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_buffer,
};
process_command(&config).unwrap();
config.signers = vec![&keypair];
config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer {
program_location: program_path.to_str().unwrap().to_string(),
fee_payer_signer_index: 0,
buffer_signer_index: None,
buffer_pubkey: None,
buffer_authority_signer_index: 0,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: false,
});
config.output_format = OutputFormat::JsonCompact;
if enable_feature {
let response = process_command(&config);
assert!(response.is_ok());
} else {
expect_command_failure(
&config,
"Program contains a syscall from a deactivated feature",
"ELF error: ELF error: Unresolved symbol (sol_alt_bn128_group_op) at instruction #49 \
(ELF file offset 0x188)",
);
config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer {
program_location: program_path.to_str().unwrap().to_string(),
fee_payer_signer_index: 0,
buffer_signer_index: None,
buffer_pubkey: None,
buffer_authority_signer_index: 0,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
let response = process_command(&config);
assert!(response.is_ok());
}
}
#[test]
fn test_cli_program_set_buffer_authority() {
agave_logger::setup();
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_path.push("tests");
noop_path.push("fixtures");
noop_path.push("noop");
noop_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let test_validator = test_validator_genesis(mint_keypair)
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let mut file = File::open(noop_path.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_buffer = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata(
max_len,
))
.unwrap();
let keypair = Keypair::new();
config.signers = vec![&keypair];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_buffer,
};
process_command(&config).unwrap();
let buffer_keypair = Keypair::new();
config.signers = vec![&keypair, &buffer_keypair];
config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer {
program_location: noop_path.to_str().unwrap().to_string(),
fee_payer_signer_index: 0,
buffer_signer_index: Some(1),
buffer_pubkey: Some(buffer_keypair.pubkey()),
buffer_authority_signer_index: 0,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
process_command(&config).unwrap();
let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap();
if let UpgradeableLoaderState::Buffer { authority_address } = buffer_account.state().unwrap() {
assert_eq!(authority_address, Some(keypair.pubkey()));
} else {
panic!("not a buffer account");
}
let new_buffer_authority = Keypair::new();
config.signers = vec![&keypair, &buffer_keypair];
config.command = CliCommand::Program(ProgramCliCommand::SetBufferAuthority {
buffer_pubkey: buffer_keypair.pubkey(),
buffer_authority_index: Some(0),
new_buffer_authority: new_buffer_authority.pubkey(),
});
config.output_format = OutputFormat::JsonCompact;
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let new_buffer_authority_str = json
.as_object()
.unwrap()
.get("authority")
.unwrap()
.as_str()
.unwrap();
assert_eq!(
Pubkey::from_str(new_buffer_authority_str).unwrap(),
new_buffer_authority.pubkey()
);
let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap();
if let UpgradeableLoaderState::Buffer { authority_address } = buffer_account.state().unwrap() {
assert_eq!(authority_address, Some(new_buffer_authority.pubkey()));
} else {
panic!("not a buffer account");
}
config.signers = vec![&keypair, &buffer_keypair];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: None,
buffer_signer_index: None,
buffer_pubkey: Some(buffer_keypair.pubkey()),
upgrade_authority_signer_index: 0,
is_final: false,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
expect_command_failure(
&config,
"Deployment with an old authority should fail",
&format!(
"Buffer's authority Some({}) does not match authority provided {}",
new_buffer_authority.pubkey(),
keypair.pubkey(),
),
);
config.signers = vec![&keypair, &new_buffer_authority];
config.command = CliCommand::Program(ProgramCliCommand::SetBufferAuthority {
buffer_pubkey: buffer_keypair.pubkey(),
buffer_authority_index: Some(1),
new_buffer_authority: buffer_keypair.pubkey(),
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let buffer_authority_str = json
.as_object()
.unwrap()
.get("authority")
.unwrap()
.as_str()
.unwrap();
assert_eq!(
Pubkey::from_str(buffer_authority_str).unwrap(),
buffer_keypair.pubkey()
);
let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap();
if let UpgradeableLoaderState::Buffer { authority_address } = buffer_account.state().unwrap() {
assert_eq!(authority_address, Some(buffer_keypair.pubkey()));
} else {
panic!("not a buffer account");
}
config.signers = vec![&keypair, &buffer_keypair];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: None,
buffer_signer_index: None,
buffer_pubkey: Some(buffer_keypair.pubkey()),
upgrade_authority_signer_index: 1,
is_final: false,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
process_command(&config).unwrap();
}
#[test]
fn test_cli_program_mismatch_buffer_authority() {
agave_logger::setup();
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_path.push("tests");
noop_path.push("fixtures");
noop_path.push("noop");
noop_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let test_validator = test_validator_genesis(mint_keypair)
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let mut file = File::open(noop_path.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_buffer = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata(
max_len,
))
.unwrap();
let keypair = Keypair::new();
config.signers = vec![&keypair];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_buffer,
};
process_command(&config).unwrap();
let buffer_authority = Keypair::new();
let buffer_keypair = Keypair::new();
config.signers = vec![&keypair, &buffer_keypair, &buffer_authority];
config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer {
program_location: noop_path.to_str().unwrap().to_string(),
fee_payer_signer_index: 0,
buffer_signer_index: Some(1),
buffer_pubkey: Some(buffer_keypair.pubkey()),
buffer_authority_signer_index: 2,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
process_command(&config).unwrap();
let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap();
if let UpgradeableLoaderState::Buffer { authority_address } = buffer_account.state().unwrap() {
assert_eq!(authority_address, Some(buffer_authority.pubkey()));
} else {
panic!("not a buffer account");
}
let upgrade_authority = Keypair::new();
config.signers = vec![&keypair, &upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: None,
buffer_signer_index: None,
buffer_pubkey: Some(buffer_keypair.pubkey()),
upgrade_authority_signer_index: 1,
is_final: true,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
expect_command_failure(
&config,
"Deployment with an invalid authority should fail",
&format!(
"Buffer's authority Some({}) does not match authority provided {}",
buffer_authority.pubkey(),
upgrade_authority.pubkey(),
),
);
config.signers = vec![&keypair, &buffer_authority];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: None,
program_pubkey: None,
buffer_signer_index: None,
buffer_pubkey: Some(buffer_keypair.pubkey()),
upgrade_authority_signer_index: 1,
is_final: true,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
process_command(&config).unwrap();
}
#[test_case(true; "offline signer will be fee payer")]
#[test_case(false; "online signer will be fee payer")]
fn test_cli_program_deploy_with_offline_signing(use_offline_signer_as_fee_payer: bool) {
agave_logger::setup();
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_path.push("tests");
noop_path.push("fixtures");
noop_path.push("noop");
noop_path.set_extension("so");
let mut noop_large_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_large_path.push("tests");
noop_large_path.push("fixtures");
noop_large_path.push("noop_large");
noop_large_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let test_validator = test_validator_genesis(mint_keypair)
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let blockhash = rpc_client.get_latest_blockhash().unwrap();
let mut file = File::open(noop_large_path.to_str().unwrap()).unwrap();
let mut large_program_data = Vec::new();
file.read_to_end(&mut large_program_data).unwrap();
let max_program_data_len = large_program_data.len();
let minimum_balance_for_large_buffer = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata(
max_program_data_len,
))
.unwrap();
let online_signer = Keypair::new();
let online_signer_identity = NullSigner::new(&online_signer.pubkey());
let offline_signer = Keypair::new();
let buffer_signer = Keypair::new();
let program_signer = Keypair::new();
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_large_buffer, };
config.signers = vec![&online_signer];
process_command(&config).unwrap();
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_large_buffer, };
config.signers = vec![&offline_signer];
process_command(&config).unwrap();
config.signers = vec![&online_signer, &offline_signer, &program_signer];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: Some(2),
program_pubkey: Some(program_signer.pubkey()),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1, is_final: false,
max_len: Some(max_program_data_len), skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
process_command(&config).unwrap();
create_buffer_with_offline_authority(
&rpc_client,
&noop_large_path,
&mut config,
&online_signer,
&offline_signer,
&buffer_signer,
);
config.signers = vec![&offline_signer];
let fee_payer_signer_index = if use_offline_signer_as_fee_payer {
0 } else {
config.signers.push(&online_signer_identity); 1 };
config.command = CliCommand::Program(ProgramCliCommand::Upgrade {
fee_payer_signer_index,
program_pubkey: program_signer.pubkey(),
buffer_pubkey: program_signer.pubkey(), upgrade_authority_signer_index: 0,
sign_only: true,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::new(Some(blockhash), true, None),
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
let sig_response = process_command(&config).unwrap();
let sign_only = parse_sign_only_reply_string(&sig_response);
let offline_pre_signer = sign_only.presigner_of(&offline_signer.pubkey()).unwrap();
config.signers = vec![&offline_pre_signer, &program_signer];
let fee_payer_signer_index = if use_offline_signer_as_fee_payer {
0 } else {
config.signers.push(&online_signer); 2 };
config.command = CliCommand::Program(ProgramCliCommand::Upgrade {
fee_payer_signer_index,
program_pubkey: program_signer.pubkey(),
buffer_pubkey: buffer_signer.pubkey(),
upgrade_authority_signer_index: 0,
sign_only: false,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::new(Some(blockhash), true, None),
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
expect_command_failure(
&config,
"Signature becomes invalid if the buffer is modified",
"presigner error",
);
config.signers = vec![&offline_signer];
let fee_payer_signer_index = if use_offline_signer_as_fee_payer {
0 } else {
config.signers.push(&online_signer_identity); 1 };
config.command = CliCommand::Program(ProgramCliCommand::Upgrade {
fee_payer_signer_index,
program_pubkey: program_signer.pubkey(),
buffer_pubkey: buffer_signer.pubkey(),
upgrade_authority_signer_index: 0,
sign_only: true,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::new(Some(blockhash), true, None),
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
let sig_response = process_command(&config).unwrap();
let sign_only = parse_sign_only_reply_string(&sig_response);
let offline_pre_signer = sign_only.presigner_of(&offline_signer.pubkey()).unwrap();
config.signers = vec![&offline_pre_signer, &program_signer];
let fee_payer_signer_index = if use_offline_signer_as_fee_payer {
0 } else {
config.signers.push(&online_signer); 2 };
config.command = CliCommand::Program(ProgramCliCommand::Upgrade {
fee_payer_signer_index,
program_pubkey: program_signer.pubkey(),
buffer_pubkey: buffer_signer.pubkey(),
upgrade_authority_signer_index: 0,
sign_only: false,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::new(Some(blockhash), true, None),
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
process_command(&config).unwrap();
let (programdata_pubkey, _) = Pubkey::find_program_address(
&[program_signer.pubkey().as_ref()],
&bpf_loader_upgradeable::id(),
);
let programdata_account = rpc_client.get_account(&programdata_pubkey).unwrap();
assert_eq!(
programdata_account.lamports,
minimum_balance_for_large_buffer
);
assert_eq!(programdata_account.owner, bpf_loader_upgradeable::id());
assert!(!programdata_account.executable);
assert_eq!(
programdata_account.data[UpgradeableLoaderState::size_of_programdata_metadata()..],
large_program_data[..]
);
}
#[test]
fn test_cli_program_show() {
agave_logger::setup();
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_path.push("tests");
noop_path.push("fixtures");
noop_path.push("noop");
noop_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let test_validator = test_validator_genesis(mint_keypair)
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let mut file = File::open(noop_path.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_buffer = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata(
max_len,
))
.unwrap();
let keypair = Keypair::new();
config.output_format = OutputFormat::Json;
config.signers = vec![&keypair];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_buffer,
};
process_command(&config).unwrap();
let buffer_keypair = Keypair::new();
let authority_keypair = Keypair::new();
config.signers = vec![&keypair, &buffer_keypair, &authority_keypair];
config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer {
program_location: noop_path.to_str().unwrap().to_string(),
fee_payer_signer_index: 0,
buffer_signer_index: Some(1),
buffer_pubkey: Some(buffer_keypair.pubkey()),
buffer_authority_signer_index: 2,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
process_command(&config).unwrap();
config.signers = vec![&keypair];
config.command = CliCommand::Program(ProgramCliCommand::Show {
account_pubkey: Some(buffer_keypair.pubkey()),
authority_pubkey: keypair.pubkey(),
get_programs: false,
get_buffers: false,
all: false,
use_lamports_unit: false,
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let address_str = json
.as_object()
.unwrap()
.get("address")
.unwrap()
.as_str()
.unwrap();
assert_eq!(
buffer_keypair.pubkey(),
Pubkey::from_str(address_str).unwrap()
);
let authority_str = json
.as_object()
.unwrap()
.get("authority")
.unwrap()
.as_str()
.unwrap();
assert_eq!(
authority_keypair.pubkey(),
Pubkey::from_str(authority_str).unwrap()
);
let data_len = json
.as_object()
.unwrap()
.get("dataLen")
.unwrap()
.as_u64()
.unwrap();
assert_eq!(max_len, data_len as usize);
let program_keypair = Keypair::new();
config.signers = vec![&keypair, &authority_keypair, &program_keypair];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: Some(2),
program_pubkey: Some(program_keypair.pubkey()),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: false,
max_len: Some(max_len),
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
auto_extend: true,
use_rpc: false,
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
let min_slot = rpc_client.get_slot().unwrap();
process_command(&config).unwrap();
let max_slot = rpc_client.get_slot().unwrap();
config.signers = vec![&keypair];
config.command = CliCommand::Program(ProgramCliCommand::Show {
account_pubkey: Some(program_keypair.pubkey()),
authority_pubkey: keypair.pubkey(),
get_programs: false,
get_buffers: false,
all: false,
use_lamports_unit: false,
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let address_str = json
.as_object()
.unwrap()
.get("programId")
.unwrap()
.as_str()
.unwrap();
assert_eq!(
program_keypair.pubkey(),
Pubkey::from_str(address_str).unwrap()
);
let programdata_address_str = json
.as_object()
.unwrap()
.get("programdataAddress")
.unwrap()
.as_str()
.unwrap();
let (programdata_pubkey, _) = Pubkey::find_program_address(
&[program_keypair.pubkey().as_ref()],
&bpf_loader_upgradeable::id(),
);
assert_eq!(
programdata_pubkey,
Pubkey::from_str(programdata_address_str).unwrap()
);
let authority_str = json
.as_object()
.unwrap()
.get("authority")
.unwrap()
.as_str()
.unwrap();
assert_eq!(
authority_keypair.pubkey(),
Pubkey::from_str(authority_str).unwrap()
);
let deployed_slot = json
.as_object()
.unwrap()
.get("lastDeploySlot")
.unwrap()
.as_u64()
.unwrap();
assert!(deployed_slot >= min_slot);
assert!(deployed_slot <= max_slot);
let data_len = json
.as_object()
.unwrap()
.get("dataLen")
.unwrap()
.as_u64()
.unwrap();
assert_eq!(max_len, data_len as usize);
}
#[test]
fn test_cli_program_dump() {
agave_logger::setup();
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_path.push("tests");
noop_path.push("fixtures");
noop_path.push("noop");
noop_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let test_validator = test_validator_genesis(mint_keypair)
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let mut file = File::open(noop_path.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_buffer = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata(
max_len,
))
.unwrap();
let keypair = Keypair::new();
config.output_format = OutputFormat::Json;
config.signers = vec![&keypair];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_buffer,
};
process_command(&config).unwrap();
let buffer_keypair = Keypair::new();
let authority_keypair = Keypair::new();
config.signers = vec![&keypair, &buffer_keypair, &authority_keypair];
config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer {
program_location: noop_path.to_str().unwrap().to_string(),
fee_payer_signer_index: 0,
buffer_signer_index: Some(1),
buffer_pubkey: Some(buffer_keypair.pubkey()),
buffer_authority_signer_index: 2,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
process_command(&config).unwrap();
let mut out_file = {
let current_exe = env::current_exe().unwrap();
PathBuf::from(current_exe.parent().unwrap().parent().unwrap())
};
out_file.set_file_name("out.txt");
config.signers = vec![&keypair];
config.command = CliCommand::Program(ProgramCliCommand::Dump {
account_pubkey: Some(buffer_keypair.pubkey()),
output_location: out_file.clone().into_os_string().into_string().unwrap(),
});
process_command(&config).unwrap();
let mut file = File::open(out_file).unwrap();
let mut out_data = Vec::new();
file.read_to_end(&mut out_data).unwrap();
assert_eq!(program_data.len(), out_data.len());
for i in 0..program_data.len() {
assert_eq!(program_data[i], out_data[i]);
}
}
fn create_buffer_with_offline_authority<'a>(
rpc_client: &RpcClient,
program_path: &Path,
config: &mut CliConfig<'a>,
online_signer: &'a Keypair,
offline_signer: &'a Keypair,
buffer_signer: &'a Keypair,
) {
config.signers = vec![online_signer, buffer_signer];
config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer {
program_location: program_path.to_str().unwrap().to_string(),
fee_payer_signer_index: 0,
buffer_signer_index: Some(1),
buffer_pubkey: Some(buffer_signer.pubkey()),
buffer_authority_signer_index: 0,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
process_command(config).unwrap();
let buffer_account = rpc_client.get_account(&buffer_signer.pubkey()).unwrap();
if let UpgradeableLoaderState::Buffer { authority_address } = buffer_account.state().unwrap() {
assert_eq!(authority_address, Some(online_signer.pubkey()));
} else {
panic!("not a buffer account");
}
config.signers = vec![online_signer];
config.command = CliCommand::Program(ProgramCliCommand::SetBufferAuthority {
buffer_pubkey: buffer_signer.pubkey(),
buffer_authority_index: Some(0),
new_buffer_authority: offline_signer.pubkey(),
});
config.output_format = OutputFormat::JsonCompact;
process_command(config).unwrap();
let buffer_account = rpc_client.get_account(&buffer_signer.pubkey()).unwrap();
if let UpgradeableLoaderState::Buffer { authority_address } = buffer_account.state().unwrap() {
assert_eq!(authority_address, Some(offline_signer.pubkey()));
} else {
panic!("not a buffer account");
}
}
#[allow(clippy::assertions_on_constants)]
#[test_case(None, false; "default")]
#[test_case(Some(10), false; "with_compute_unit_price")]
#[test_case(None, true; "use_rpc")]
fn test_cli_program_deploy_with_args(compute_unit_price: Option<u64>, use_rpc: bool) {
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_path.push("tests");
noop_path.push("fixtures");
noop_path.push("noop");
noop_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let faucet_addr = run_local_faucet_with_unique_port_for_tests(mint_keypair);
let test_validator = TestValidatorGenesis::default()
.fee_rate_governor(FeeRateGovernor::new(0, 0))
.rent(Rent {
lamports_per_byte_year: 1,
exemption_threshold: 1.0,
..Rent::default()
})
.rpc_config(JsonRpcConfig {
enable_rpc_transaction_history: true,
faucet_addr: Some(faucet_addr),
..JsonRpcConfig::default_for_test()
})
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let mut file = File::open(noop_path.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::size_of_programdata(
max_len,
))
.unwrap();
let minimum_balance_for_program = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program())
.unwrap();
let upgrade_authority = Keypair::new();
let keypair = Keypair::new();
config.signers = vec![&keypair];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program,
};
process_command(&config).unwrap();
let program_keypair = Keypair::new();
config.signers = vec![&keypair, &upgrade_authority, &program_keypair];
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
program_location: Some(noop_path.to_str().unwrap().to_string()),
fee_payer_signer_index: 0,
program_signer_index: Some(2),
program_pubkey: Some(program_keypair.pubkey()),
buffer_signer_index: None,
buffer_pubkey: None,
upgrade_authority_signer_index: 1,
is_final: false,
max_len: Some(max_len),
skip_fee_check: false,
compute_unit_price,
max_sign_attempts: 5,
auto_extend: true,
use_rpc,
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let program_pubkey_str = json
.as_object()
.unwrap()
.get("programId")
.unwrap()
.as_str()
.unwrap();
assert_eq!(
program_keypair.pubkey(),
Pubkey::from_str(program_pubkey_str).unwrap()
);
let program_account = rpc_client.get_account(&program_keypair.pubkey()).unwrap();
assert_eq!(program_account.lamports, minimum_balance_for_program);
assert_eq!(program_account.owner, bpf_loader_upgradeable::id());
assert!(program_account.executable);
let signature_statuses = rpc_client
.get_signatures_for_address_with_config(
&keypair.pubkey(),
GetConfirmedSignaturesForAddress2Config {
commitment: Some(CommitmentConfig::confirmed()),
..GetConfirmedSignaturesForAddress2Config::default()
},
)
.unwrap();
let signatures: Vec<_> = signature_statuses
.into_iter()
.rev()
.map(|status| Signature::from_str(&status.signature).unwrap())
.collect();
fn fetch_and_decode_transaction(rpc_client: &RpcClient, signature: &Signature) -> Transaction {
rpc_client
.get_transaction_with_config(
signature,
RpcTransactionConfig {
encoding: Some(UiTransactionEncoding::Base64),
commitment: Some(CommitmentConfig::confirmed()),
..RpcTransactionConfig::default()
},
)
.unwrap()
.transaction
.transaction
.decode()
.unwrap()
.into_legacy_transaction()
.unwrap()
}
assert!(signatures.len() >= 4);
let initial_tx = fetch_and_decode_transaction(&rpc_client, &signatures[1]);
let write_tx = fetch_and_decode_transaction(&rpc_client, &signatures[2]);
let final_tx = fetch_and_decode_transaction(&rpc_client, signatures.last().unwrap());
if let Some(compute_unit_price) = compute_unit_price {
for tx in [&initial_tx, &write_tx, &final_tx] {
let ix_len = tx.message.instructions.len();
for i in [1, 2] {
assert_eq!(
tx.message.instructions[ix_len - i].program_id(&tx.message.account_keys),
&compute_budget::id()
);
}
assert_matches!(
try_from_slice_unchecked(&tx.message.instructions[ix_len - 2].data),
Ok(ComputeBudgetInstruction::SetComputeUnitPrice(price)) if price == compute_unit_price
);
}
assert_matches!(
try_from_slice_unchecked(&initial_tx.message.instructions.last().unwrap().data),
Ok(ComputeBudgetInstruction::SetComputeUnitLimit(2820))
);
assert_matches!(
try_from_slice_unchecked(&write_tx.message.instructions.last().unwrap().data),
Ok(ComputeBudgetInstruction::SetComputeUnitLimit(2670))
);
assert_matches!(
try_from_slice_unchecked(&final_tx.message.instructions.last().unwrap().data),
Ok(ComputeBudgetInstruction::SetComputeUnitLimit(2970))
);
} else {
assert_eq!(
initial_tx.message.instructions[0].program_id(&initial_tx.message.account_keys),
&system_program::id()
);
assert_eq!(
write_tx.message.instructions[0].program_id(&write_tx.message.account_keys),
&bpf_loader_upgradeable::id()
);
assert_eq!(
final_tx.message.instructions[0].program_id(&final_tx.message.account_keys),
&system_program::id()
);
}
}
#[test]
fn test_cli_program_v4() {
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
noop_path.push("tests");
noop_path.push("fixtures");
noop_path.push("noop");
noop_path.set_extension("so");
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let test_validator = test_validator_genesis(mint_keypair)
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
let rpc_client = setup_rpc_client(&mut config);
let payer_keypair = Keypair::new();
let upgrade_authority = Keypair::new();
let program_keypair = Keypair::new();
let buffer_keypair = Keypair::new();
config.signers = vec![
&payer_keypair,
&upgrade_authority,
&program_keypair,
&buffer_keypair,
];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 10000000,
};
process_command(&config).unwrap();
config.command = CliCommand::Airdrop {
pubkey: Some(program_keypair.pubkey()),
lamports: 1000,
};
process_command(&config).unwrap();
config.output_format = OutputFormat::JsonCompact;
config.command = CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
additional_cli_config: AdditionalCliConfig::default(),
program_address: program_keypair.pubkey(),
buffer_address: None,
upload_signer_index: Some(2),
authority_signer_index: 1,
path_to_elf: Some(noop_path.to_str().unwrap().to_string()),
upload_range: None..None,
});
assert!(process_command(&config).is_ok());
let program_account = rpc_client.get_account(&program_keypair.pubkey()).unwrap();
assert_eq!(program_account.owner, loader_v4::id());
assert!(program_account.executable);
config.command = CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
additional_cli_config: AdditionalCliConfig::default(),
program_address: program_keypair.pubkey(),
buffer_address: None,
upload_signer_index: None,
authority_signer_index: 1,
path_to_elf: Some(noop_path.to_str().unwrap().to_string()),
upload_range: None..None,
});
assert!(process_command(&config).is_ok());
let program_account = rpc_client.get_account(&program_keypair.pubkey()).unwrap();
assert_eq!(program_account.owner, loader_v4::id());
assert!(program_account.executable);
config.command = CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
additional_cli_config: AdditionalCliConfig::default(),
program_address: program_keypair.pubkey(),
buffer_address: Some(buffer_keypair.pubkey()),
upload_signer_index: Some(3),
authority_signer_index: 1,
path_to_elf: Some(noop_path.to_str().unwrap().to_string()),
upload_range: None..None,
});
assert!(process_command(&config).is_ok());
let program_account = rpc_client.get_account(&program_keypair.pubkey()).unwrap();
assert_eq!(program_account.owner, loader_v4::id());
assert!(program_account.executable);
let _error = rpc_client
.get_account(&buffer_keypair.pubkey())
.unwrap_err();
config.command = CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
additional_cli_config: AdditionalCliConfig::default(),
program_address: buffer_keypair.pubkey(),
buffer_address: Some(buffer_keypair.pubkey()),
upload_signer_index: Some(3),
authority_signer_index: 1,
path_to_elf: Some(noop_path.to_str().unwrap().to_string()),
upload_range: None..None,
});
assert!(process_command(&config).is_ok());
let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap();
assert_eq!(buffer_account.owner, loader_v4::id());
assert!(buffer_account.executable);
config.command = CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
additional_cli_config: AdditionalCliConfig::default(),
program_address: program_keypair.pubkey(),
buffer_address: Some(buffer_keypair.pubkey()),
upload_signer_index: None,
authority_signer_index: 1,
path_to_elf: Some(noop_path.to_str().unwrap().to_string()),
upload_range: None..None,
});
assert!(process_command(&config).is_ok());
let program_account = rpc_client.get_account(&program_keypair.pubkey()).unwrap();
assert_eq!(program_account.owner, loader_v4::id());
assert!(program_account.executable);
let _error = rpc_client
.get_account(&buffer_keypair.pubkey())
.unwrap_err();
config.command = CliCommand::ProgramV4(ProgramV4CliCommand::TransferAuthority {
additional_cli_config: AdditionalCliConfig::default(),
program_address: program_keypair.pubkey(),
authority_signer_index: 1,
new_authority_signer_index: 2,
});
assert!(process_command(&config).is_ok());
let program_account = rpc_client.get_account(&program_keypair.pubkey()).unwrap();
assert_eq!(program_account.owner, loader_v4::id());
assert!(program_account.executable);
config.command = CliCommand::ProgramV4(ProgramV4CliCommand::Retract {
additional_cli_config: AdditionalCliConfig::default(),
program_address: program_keypair.pubkey(),
authority_signer_index: 2,
close_program_entirely: true,
});
assert!(process_command(&config).is_ok());
let _error = rpc_client
.get_account(&program_keypair.pubkey())
.unwrap_err();
config.command = CliCommand::ProgramV4(ProgramV4CliCommand::Deploy {
additional_cli_config: AdditionalCliConfig::default(),
program_address: program_keypair.pubkey(),
buffer_address: None,
upload_signer_index: Some(2),
authority_signer_index: 1,
path_to_elf: Some(noop_path.to_str().unwrap().to_string()),
upload_range: None..None,
});
assert!(process_command(&config).is_ok());
let program_account = rpc_client.get_account(&program_keypair.pubkey()).unwrap();
assert_eq!(program_account.owner, loader_v4::id());
assert!(program_account.executable);
config.command = CliCommand::ProgramV4(ProgramV4CliCommand::Finalize {
additional_cli_config: AdditionalCliConfig::default(),
program_address: program_keypair.pubkey(),
authority_signer_index: 1,
next_version_signer_index: 2,
});
assert!(process_command(&config).is_ok());
let program_account = rpc_client.get_account(&program_keypair.pubkey()).unwrap();
assert_eq!(program_account.owner, loader_v4::id());
assert!(program_account.executable);
}