Skip to content

Commit 7d0f4d0

Browse files
committed
Adds CLI command.
1 parent 68c8f8f commit 7d0f4d0

File tree

3 files changed

+302
-1
lines changed

3 files changed

+302
-1
lines changed

cli-output/src/cli_output.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2468,6 +2468,25 @@ impl fmt::Display for CliUpgradeableProgramExtended {
24682468
}
24692469
}
24702470

2471+
#[derive(Serialize, Deserialize)]
2472+
#[serde(rename_all = "camelCase")]
2473+
pub struct CliUpgradeableProgramMigrated {
2474+
pub program_id: String,
2475+
}
2476+
impl QuietDisplay for CliUpgradeableProgramMigrated {}
2477+
impl VerboseDisplay for CliUpgradeableProgramMigrated {}
2478+
impl fmt::Display for CliUpgradeableProgramMigrated {
2479+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2480+
writeln!(f)?;
2481+
writeln!(
2482+
f,
2483+
"Migrated Program Id {} from loader-v3 to loader-v4",
2484+
&self.program_id,
2485+
)?;
2486+
Ok(())
2487+
}
2488+
}
2489+
24712490
#[derive(Clone, Serialize, Deserialize)]
24722491
#[serde(rename_all = "camelCase")]
24732492
pub struct CliUpgradeableBuffer {

cli/src/program.rs

Lines changed: 204 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use {
3131
return_signers_with_config, CliProgram, CliProgramAccountType, CliProgramAuthority,
3232
CliProgramBuffer, CliProgramId, CliUpgradeableBuffer, CliUpgradeableBuffers,
3333
CliUpgradeableProgram, CliUpgradeableProgramClosed, CliUpgradeableProgramExtended,
34-
CliUpgradeablePrograms, ReturnSignersConfig,
34+
CliUpgradeableProgramMigrated, CliUpgradeablePrograms, ReturnSignersConfig,
3535
},
3636
solana_client::{
3737
connection_cache::ConnectionCache,
@@ -171,6 +171,11 @@ pub enum ProgramCliCommand {
171171
program_pubkey: Pubkey,
172172
additional_bytes: u32,
173173
},
174+
MigrateProgram {
175+
program_pubkey: Pubkey,
176+
authority_signer_index: SignerIndex,
177+
compute_unit_price: Option<u64>,
178+
},
174179
}
175180

176181
pub trait ProgramSubCommands {
@@ -632,6 +637,33 @@ impl ProgramSubCommands for App<'_, '_> {
632637
data account",
633638
),
634639
),
640+
)
641+
.subcommand(
642+
SubCommand::with_name("migrate")
643+
.about(
644+
"Migrates an upgradeable program to loader-v4",
645+
)
646+
.arg(
647+
Arg::with_name("program_id")
648+
.index(1)
649+
.value_name("PROGRAM_ID")
650+
.takes_value(true)
651+
.required(true)
652+
.validator(is_valid_pubkey)
653+
.help("Address of the program to extend"),
654+
)
655+
.arg(
656+
Arg::with_name("authority")
657+
.long("authority")
658+
.value_name("AUTHORITY_SIGNER")
659+
.takes_value(true)
660+
.validator(is_valid_signer)
661+
.help(
662+
"Upgrade authority [default: the default configured \
663+
keypair]",
664+
),
665+
)
666+
.arg(compute_unit_price_arg()),
635667
),
636668
)
637669
.subcommand(
@@ -991,6 +1023,32 @@ pub fn parse_program_subcommand(
9911023
signers: signer_info.signers,
9921024
}
9931025
}
1026+
("migrate", Some(matches)) => {
1027+
let program_pubkey = pubkey_of(matches, "program_id").unwrap();
1028+
1029+
let (authority_signer, authority_pubkey) =
1030+
signer_of(matches, "authority", wallet_manager)?;
1031+
1032+
let signer_info = default_signer.generate_unique_signers(
1033+
vec![
1034+
Some(default_signer.signer_from_path(matches, wallet_manager)?),
1035+
authority_signer,
1036+
],
1037+
matches,
1038+
wallet_manager,
1039+
)?;
1040+
1041+
let compute_unit_price = value_of(matches, "compute_unit_price");
1042+
1043+
CliCommandInfo {
1044+
command: CliCommand::Program(ProgramCliCommand::MigrateProgram {
1045+
program_pubkey,
1046+
authority_signer_index: signer_info.index_of(authority_pubkey).unwrap(),
1047+
compute_unit_price,
1048+
}),
1049+
signers: signer_info.signers,
1050+
}
1051+
}
9941052
_ => unreachable!(),
9951053
};
9961054
Ok(response)
@@ -1175,6 +1233,17 @@ pub fn process_program_subcommand(
11751233
program_pubkey,
11761234
additional_bytes,
11771235
} => process_extend_program(&rpc_client, config, *program_pubkey, *additional_bytes),
1236+
ProgramCliCommand::MigrateProgram {
1237+
program_pubkey,
1238+
authority_signer_index,
1239+
compute_unit_price,
1240+
} => process_migrate_program(
1241+
&rpc_client,
1242+
config,
1243+
*program_pubkey,
1244+
*authority_signer_index,
1245+
*compute_unit_price,
1246+
),
11781247
}
11791248
}
11801249

@@ -2387,6 +2456,100 @@ fn process_extend_program(
23872456
}))
23882457
}
23892458

2459+
fn process_migrate_program(
2460+
rpc_client: &RpcClient,
2461+
config: &CliConfig,
2462+
program_pubkey: Pubkey,
2463+
authority_signer_index: SignerIndex,
2464+
compute_unit_price: Option<u64>,
2465+
) -> ProcessResult {
2466+
let payer_pubkey = config.signers[0].pubkey();
2467+
let authority_signer = config.signers[authority_signer_index];
2468+
2469+
let program_account = match rpc_client
2470+
.get_account_with_commitment(&program_pubkey, config.commitment)?
2471+
.value
2472+
{
2473+
Some(program_account) => Ok(program_account),
2474+
None => Err(format!("Unable to find program {program_pubkey}")),
2475+
}?;
2476+
2477+
if !bpf_loader_upgradeable::check_id(&program_account.owner) {
2478+
return Err(format!("Account {program_pubkey} is not an upgradeable program").into());
2479+
}
2480+
2481+
let Ok(UpgradeableLoaderState::Program {
2482+
programdata_address: programdata_pubkey,
2483+
}) = program_account.state()
2484+
else {
2485+
return Err(format!("Account {program_pubkey} is not an upgradeable program").into());
2486+
};
2487+
2488+
let Some(programdata_account) = rpc_client
2489+
.get_account_with_commitment(&programdata_pubkey, config.commitment)?
2490+
.value
2491+
else {
2492+
return Err(format!("Program {program_pubkey} is closed").into());
2493+
};
2494+
2495+
let upgrade_authority_address = match programdata_account.state() {
2496+
Ok(UpgradeableLoaderState::ProgramData {
2497+
slot: _slot,
2498+
upgrade_authority_address,
2499+
}) => upgrade_authority_address,
2500+
_ => None,
2501+
};
2502+
2503+
if authority_signer.pubkey() != upgrade_authority_address.unwrap_or(program_pubkey) {
2504+
return Err(format!(
2505+
"Upgrade authority {:?} does not match {:?}",
2506+
upgrade_authority_address,
2507+
Some(authority_signer.pubkey())
2508+
)
2509+
.into());
2510+
}
2511+
2512+
let blockhash = rpc_client.get_latest_blockhash()?;
2513+
let mut message = Message::new(
2514+
&vec![bpf_loader_upgradeable::migrate_program(
2515+
&programdata_pubkey,
2516+
&program_pubkey,
2517+
&authority_signer.pubkey(),
2518+
)]
2519+
.with_compute_unit_config(&ComputeUnitConfig {
2520+
compute_unit_price,
2521+
compute_unit_limit: ComputeUnitLimit::Simulated,
2522+
}),
2523+
Some(&payer_pubkey),
2524+
);
2525+
simulate_and_update_compute_unit_limit(&ComputeUnitLimit::Simulated, rpc_client, &mut message)?;
2526+
2527+
let mut tx = Transaction::new_unsigned(message);
2528+
tx.try_sign(&[config.signers[0], config.signers[1]], blockhash)?;
2529+
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
2530+
&tx,
2531+
config.commitment,
2532+
config.send_transaction_config,
2533+
);
2534+
if let Err(err) = result {
2535+
if let ClientErrorKind::TransactionError(TransactionError::InstructionError(
2536+
_,
2537+
InstructionError::InvalidInstructionData,
2538+
)) = err.kind()
2539+
{
2540+
return Err("Migrating a program is not supported by the cluster".into());
2541+
} else {
2542+
return Err(format!("Migrate program failed: {err}").into());
2543+
}
2544+
}
2545+
2546+
Ok(config
2547+
.output_format
2548+
.formatted_string(&CliUpgradeableProgramMigrated {
2549+
program_id: program_pubkey.to_string(),
2550+
}))
2551+
}
2552+
23902553
pub fn calculate_max_chunk_size<F>(create_msg: &F) -> usize
23912554
where
23922555
F: Fn(u32, Vec<u8>) -> Message,
@@ -4255,6 +4418,46 @@ mod tests {
42554418
);
42564419
}
42574420

4421+
#[test]
4422+
fn test_cli_parse_migrate_program() {
4423+
let test_commands = get_clap_app("test", "desc", "version");
4424+
4425+
let default_keypair = Keypair::new();
4426+
let keypair_file = make_tmp_path("keypair_file");
4427+
write_keypair_file(&default_keypair, &keypair_file).unwrap();
4428+
let default_signer = DefaultSigner::new("", &keypair_file);
4429+
4430+
let program_pubkey = Pubkey::new_unique();
4431+
let authority_keypair = Keypair::new();
4432+
let authority_keypair_file = make_tmp_path("authority_keypair_file");
4433+
write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
4434+
4435+
let test_command = test_commands.clone().get_matches_from(vec![
4436+
"test",
4437+
"program",
4438+
"migrate",
4439+
&program_pubkey.to_string(),
4440+
"--authority",
4441+
&authority_keypair_file.to_string(),
4442+
"--with-compute-unit-price",
4443+
"1",
4444+
]);
4445+
assert_eq!(
4446+
parse_command(&test_command, &default_signer, &mut None).unwrap(),
4447+
CliCommandInfo {
4448+
command: CliCommand::Program(ProgramCliCommand::MigrateProgram {
4449+
program_pubkey,
4450+
authority_signer_index: 1,
4451+
compute_unit_price: Some(1),
4452+
}),
4453+
signers: vec![
4454+
Box::new(read_keypair_file(&keypair_file).unwrap()),
4455+
Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
4456+
],
4457+
}
4458+
);
4459+
}
4460+
42584461
#[test]
42594462
fn test_cli_keypair_file() {
42604463
solana_logger::setup();

cli/tests/program.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,6 +1524,85 @@ fn test_cli_program_extend_program() {
15241524
process_command(&config).unwrap();
15251525
}
15261526

1527+
#[test]
1528+
fn test_cli_program_migrate_program() {
1529+
solana_logger::setup();
1530+
1531+
let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1532+
noop_path.push("tests");
1533+
noop_path.push("fixtures");
1534+
noop_path.push("noop");
1535+
noop_path.set_extension("so");
1536+
1537+
let mint_keypair = Keypair::new();
1538+
let mint_pubkey = mint_keypair.pubkey();
1539+
let test_validator = test_validator_genesis(mint_keypair)
1540+
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
1541+
.expect("validator start failed");
1542+
1543+
let rpc_client =
1544+
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
1545+
1546+
let mut file = File::open(noop_path.to_str().unwrap()).unwrap();
1547+
let mut program_data = Vec::new();
1548+
file.read_to_end(&mut program_data).unwrap();
1549+
let max_len = program_data.len();
1550+
let minimum_balance_for_programdata = rpc_client
1551+
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata(
1552+
max_len,
1553+
))
1554+
.unwrap();
1555+
let minimum_balance_for_program = rpc_client
1556+
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program())
1557+
.unwrap();
1558+
let upgrade_authority = Keypair::new();
1559+
1560+
let mut config = CliConfig::recent_for_tests();
1561+
let keypair = Keypair::new();
1562+
config.json_rpc_url = test_validator.rpc_url();
1563+
config.signers = vec![&keypair];
1564+
config.command = CliCommand::Airdrop {
1565+
pubkey: None,
1566+
lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program,
1567+
};
1568+
process_command(&config).unwrap();
1569+
1570+
// Deploy the upgradeable program
1571+
let program_keypair = Keypair::new();
1572+
config.signers = vec![&keypair, &upgrade_authority, &program_keypair];
1573+
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
1574+
program_location: Some(noop_path.to_str().unwrap().to_string()),
1575+
fee_payer_signer_index: 0,
1576+
program_signer_index: Some(2),
1577+
program_pubkey: Some(program_keypair.pubkey()),
1578+
buffer_signer_index: None,
1579+
buffer_pubkey: None,
1580+
upgrade_authority_signer_index: 1,
1581+
is_final: false,
1582+
max_len: Some(max_len),
1583+
skip_fee_check: false,
1584+
compute_unit_price: None,
1585+
max_sign_attempts: 5,
1586+
auto_extend: true,
1587+
use_rpc: false,
1588+
skip_feature_verification: true,
1589+
});
1590+
config.output_format = OutputFormat::JsonCompact;
1591+
process_command(&config).unwrap();
1592+
1593+
// Wait one slot to avoid "Program was deployed in this block already" error
1594+
wait_n_slots(&rpc_client, 1);
1595+
1596+
// Migrate program
1597+
config.signers = vec![&keypair, &upgrade_authority];
1598+
config.command = CliCommand::Program(ProgramCliCommand::MigrateProgram {
1599+
program_pubkey: program_keypair.pubkey(),
1600+
authority_signer_index: 1,
1601+
compute_unit_price: Some(1),
1602+
});
1603+
process_command(&config).unwrap();
1604+
}
1605+
15271606
#[test]
15281607
fn test_cli_program_write_buffer() {
15291608
solana_logger::setup();

0 commit comments

Comments
 (0)