From 58487c636050cc51a8e8dfe68102063e37e48d51 Mon Sep 17 00:00:00 2001 From: Jack May Date: Fri, 8 Jan 2021 09:37:57 -0800 Subject: [PATCH] Add buffer authority to upgradeable loader (#14482) --- cli/src/cli.rs | 2 +- cli/src/program.rs | 1660 ++++++++++++----- cli/tests/program.rs | 529 +++++- programs/bpf_loader/src/lib.rs | 478 ++++- runtime/src/bank.rs | 2 +- runtime/src/loader_utils.rs | 4 +- sdk/program/src/bpf_loader_upgradeable.rs | 60 +- sdk/program/src/instruction.rs | 6 + .../src/loader_upgradeable_instruction.rs | 13 +- 9 files changed, 2166 insertions(+), 588 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 602aa6d7d6..0793ca1eb4 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -2665,7 +2665,7 @@ mod tests { let program_id = json .as_object() .unwrap() - .get("programId") + .get("ProgramId") .unwrap() .as_str() .unwrap(); diff --git a/cli/src/program.rs b/cli/src/program.rs index 0aadefb33d..52b0f8d5b8 100644 --- a/cli/src/program.rs +++ b/cli/src/program.rs @@ -51,17 +51,32 @@ const DATA_CHUNK_SIZE: usize = 229; // Keep program chunks under PACKET_DATA_SIZ #[derive(Debug, PartialEq)] pub enum ProgramCliCommand { Deploy { - program_location: String, + program_location: Option, program_signer_index: Option, program_pubkey: Option, buffer_signer_index: Option, + buffer_pubkey: Option, upgrade_authority_signer_index: Option, upgrade_authority_pubkey: Option, + is_final: bool, max_len: Option, allow_excessive_balance: bool, }, + WriteBuffer { + program_location: String, + buffer_signer_index: Option, + buffer_pubkey: Option, + buffer_authority_signer_index: Option, + is_final: bool, + max_len: Option, + }, + SetBufferAuthority { + buffer_pubkey: Pubkey, + buffer_authority_index: Option, + new_buffer_authority: Option, + }, SetUpgradeAuthority { - program: Pubkey, + program_pubkey: Pubkey, upgrade_authority_index: Option, new_upgrade_authority: Option, }, @@ -85,15 +100,15 @@ impl ProgramSubCommands for App<'_, '_> { .index(1) .value_name("PROGRAM_FILEPATH") .takes_value(true) - .required(true) .help("/path/to/program.so"), ) .arg( Arg::with_name("buffer") - .long("buffer") + .long("buffer") .value_name("BUFFER_SIGNER") .takes_value(true) .validator(is_valid_signer) + .conflicts_with("program_location") .help("Intermediate buffer account to write data to, which can be used to resume a failed deploy \ [default: random address]") ) @@ -132,6 +147,83 @@ impl ProgramSubCommands for App<'_, '_> { ) .arg(commitment_arg_with_default("singleGossip")), ) + .subcommand( + SubCommand::with_name("write-buffer") + .about("Writes a program into a buffer account") + .arg( + Arg::with_name("program_location") + .index(1) + .value_name("PROGRAM_FILEPATH") + .takes_value(true) + .required(true) + .help("/path/to/program.so"), + ) + .arg( + Arg::with_name("buffer") + .long("buffer") + .value_name("BUFFER_SIGNER") + .takes_value(true) + .validator(is_valid_signer) + .help("Buffer account to write data into [default: random address]") + ) + .arg( + Arg::with_name("buffer_authority") + .long("buffer-authority") + .value_name("BUFFER_AUTHORITY_SIGNER") + .takes_value(true) + .validator(is_valid_signer) + .help("Buffer authority [default: the default configured keypair]") + ) + .arg( + Arg::with_name("final") + .long("final") + .help("The program will not be upgradeable") + ) + .arg( + Arg::with_name("max_len") + .long("max-len") + .value_name("max_len") + .takes_value(true) + .required(false) + .help("Maximum length of the upgradeable program \ + [default: twice the length of the original deployed program]") + ) + .arg(commitment_arg_with_default("singleGossip")), + ) + .subcommand( + SubCommand::with_name("set-buffer-authority") + .about("Set a new buffer authority") // TODO deploy with buffer and no file path? + .arg( + Arg::with_name("buffer") + .index(1) + .value_name("BUFFER_PUBKEY") + .takes_value(true) + .required(true) + .help("Public key of the buffer") + ) + .arg( + Arg::with_name("buffer_authority") + .long("buffer-authority") + .value_name("BUFFER_AUTHORITY_SIGNER") + .takes_value(true) + .validator(is_valid_signer) + .help("Buffer authority [default: the default configured keypair]") + ) + .arg( + pubkey!(Arg::with_name("new_buffer_authority") + .long("new-buffer-authority") + .value_name("NEW_BUFFER_AUTHORITY") + .required_unless("final"), + "Address of the new buffer authority"), + ) + .arg( + Arg::with_name("final") + .long("final") + .conflicts_with("new_buffer_authority") + .help("The buffer will be immutable") + ) + .arg(commitment_arg_with_default("singleGossip")), + ) .subcommand( SubCommand::with_name("set-upgrade-authority") .about("Set a new program authority") @@ -156,7 +248,7 @@ impl ProgramSubCommands for App<'_, '_> { .long("new-upgrade-authority") .required_unless("final") .value_name("NEW_UPGRADE_AUTHORITY"), - "Address of the upgrade authority, if not specified the program will not be upgradeable"), + "Address of the new upgrade authority"), ) .arg( Arg::with_name("final") @@ -181,8 +273,23 @@ pub fn parse_program_subcommand( default_signer.signer_from_path(matches, wallet_manager)?, )]; - let (buffer_signer, buffer_pubkey) = signer_of(matches, "buffer", wallet_manager)?; - bulk_signers.push(buffer_signer); + let program_location = if let Some(location) = matches.value_of("program_location") { + Some(location.to_string()) + } else { + None + }; + + let buffer_pubkey = if let Ok((buffer_signer, Some(buffer_pubkey))) = + signer_of(matches, "buffer", wallet_manager) + { + bulk_signers.push(buffer_signer); + Some(buffer_pubkey) + } else if let Some(buffer_pubkey) = pubkey_of_signer(matches, "buffer", wallet_manager)? + { + Some(buffer_pubkey) + } else { + None + }; let program_pubkey = if let Ok((program_signer, Some(program_pubkey))) = signer_of(matches, "program_id", wallet_manager) @@ -197,24 +304,23 @@ pub fn parse_program_subcommand( None }; - let upgrade_authority_pubkey = if matches.is_present("final") { - None - } else if let Ok((upgrade_authority_signer, Some(upgrade_authority_pubkey))) = - signer_of(matches, "upgrade_authority", wallet_manager) - { - bulk_signers.push(upgrade_authority_signer); - Some(upgrade_authority_pubkey) - } else if let Some(upgrade_authority_pubkey) = - pubkey_of_signer(matches, "upgrade_authority", wallet_manager)? - { - Some(upgrade_authority_pubkey) - } else { - Some( - default_signer - .signer_from_path(matches, wallet_manager)? - .pubkey(), - ) - }; + let upgrade_authority_pubkey = + if let Ok((upgrade_authority_signer, Some(upgrade_authority_pubkey))) = + signer_of(matches, "upgrade_authority", wallet_manager) + { + bulk_signers.push(upgrade_authority_signer); + Some(upgrade_authority_pubkey) + } else if let Some(upgrade_authority_pubkey) = + pubkey_of_signer(matches, "upgrade_authority", wallet_manager)? + { + Some(upgrade_authority_pubkey) + } else { + Some( + default_signer + .signer_from_path(matches, wallet_manager)? + .pubkey(), + ) + }; let max_len = value_of(matches, "max_len"); @@ -223,23 +329,109 @@ pub fn parse_program_subcommand( CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::Deploy { - program_location: matches.value_of("program_location").unwrap().to_string(), + program_location, program_signer_index: signer_info.index_of_or_none(program_pubkey), program_pubkey, buffer_signer_index: signer_info.index_of_or_none(buffer_pubkey), + buffer_pubkey, upgrade_authority_signer_index: signer_info .index_of_or_none(upgrade_authority_pubkey), upgrade_authority_pubkey, + is_final: matches.is_present("final"), max_len, allow_excessive_balance: matches.is_present("allow_excessive_balance"), }), signers: signer_info.signers, } } + ("write-buffer", Some(matches)) => { + let mut bulk_signers = vec![Some( + default_signer.signer_from_path(matches, wallet_manager)?, + )]; + + let buffer_pubkey = if let Ok((buffer_signer, Some(buffer_pubkey))) = + signer_of(matches, "buffer", wallet_manager) + { + bulk_signers.push(buffer_signer); + Some(buffer_pubkey) + } else if let Some(buffer_pubkey) = pubkey_of_signer(matches, "buffer", wallet_manager)? + { + Some(buffer_pubkey) + } else { + None + }; + + let buffer_authority_pubkey = + if let Ok((buffer_authority_signer, Some(buffer_authority_pubkey))) = + signer_of(matches, "buffer_authority", wallet_manager) + { + bulk_signers.push(buffer_authority_signer); + Some(buffer_authority_pubkey) + } else { + Some( + default_signer + .signer_from_path(matches, wallet_manager)? + .pubkey(), + ) + }; + + let max_len = value_of(matches, "max_len"); + + let signer_info = + default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; + + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::WriteBuffer { + program_location: matches.value_of("program_location").unwrap().to_string(), + buffer_signer_index: signer_info.index_of_or_none(buffer_pubkey), + buffer_pubkey, + buffer_authority_signer_index: signer_info + .index_of_or_none(buffer_authority_pubkey), + is_final: matches.is_present("final"), + max_len, + }), + signers: signer_info.signers, + } + } + ("set-buffer-authority", Some(matches)) => { + let buffer_pubkey = pubkey_of(matches, "buffer").unwrap(); + + let (buffer_authority_signer, buffer_authority_pubkey) = + signer_of(matches, "buffer_authority", wallet_manager)?; + let new_buffer_authority = if matches.is_present("final") { + None + } else if let Some(new_buffer_authority) = + pubkey_of_signer(matches, "new_buffer_authority", wallet_manager)? + { + Some(new_buffer_authority) + } else { + let (_, new_buffer_authority) = + signer_of(matches, "new_buffer_authority", wallet_manager)?; + new_buffer_authority + }; + + let signer_info = default_signer.generate_unique_signers( + vec![ + Some(default_signer.signer_from_path(matches, wallet_manager)?), + buffer_authority_signer, + ], + matches, + wallet_manager, + )?; + + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::SetBufferAuthority { + buffer_pubkey, + buffer_authority_index: signer_info.index_of(buffer_authority_pubkey), + new_buffer_authority, + }), + signers: signer_info.signers, + } + } ("set-upgrade-authority", Some(matches)) => { let (upgrade_authority_signer, upgrade_authority_pubkey) = signer_of(matches, "upgrade_authority", wallet_manager)?; - let program = pubkey_of(matches, "program_id").unwrap(); + let program_pubkey = pubkey_of(matches, "program_id").unwrap(); let new_upgrade_authority = if matches.is_present("final") { None } else if let Some(new_upgrade_authority) = @@ -263,7 +455,7 @@ pub fn parse_program_subcommand( CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority { - program, + program_pubkey, upgrade_authority_index: signer_info.index_of(upgrade_authority_pubkey), new_upgrade_authority, }), @@ -286,8 +478,10 @@ pub fn process_program_subcommand( program_signer_index, program_pubkey, buffer_signer_index, + buffer_pubkey, upgrade_authority_signer_index, upgrade_authority_pubkey, + is_final, max_len, allow_excessive_balance, } => process_program_deploy( @@ -297,35 +491,71 @@ pub fn process_program_subcommand( *program_signer_index, *program_pubkey, *buffer_signer_index, + *buffer_pubkey, *upgrade_authority_signer_index, *upgrade_authority_pubkey, + *is_final, *max_len, *allow_excessive_balance, ), - ProgramCliCommand::SetUpgradeAuthority { - program, - upgrade_authority_index, - new_upgrade_authority, - } => process_set_program_upgrade_authority( + ProgramCliCommand::WriteBuffer { + program_location, + buffer_signer_index, + buffer_pubkey, + buffer_authority_signer_index, + is_final, + max_len, + } => process_write_buffer( &rpc_client, config, - *program, + program_location, + *buffer_signer_index, + *buffer_pubkey, + *buffer_authority_signer_index, + *is_final, + *max_len, + ), + ProgramCliCommand::SetBufferAuthority { + buffer_pubkey, + buffer_authority_index, + new_buffer_authority, + } => process_set_authority( + &rpc_client, + config, + None, + Some(*buffer_pubkey), + *buffer_authority_index, + *new_buffer_authority, + ), + ProgramCliCommand::SetUpgradeAuthority { + program_pubkey, + upgrade_authority_index, + new_upgrade_authority, + } => process_set_authority( + &rpc_client, + config, + Some(*program_pubkey), + None, *upgrade_authority_index, *new_upgrade_authority, ), } } -fn get_default_program_keypair(program_location: &str) -> Keypair { +fn get_default_program_keypair(program_location: &Option) -> Keypair { let program_keypair = { - let mut keypair_file = PathBuf::new(); - keypair_file.push(program_location); - let mut filename = keypair_file.file_stem().unwrap().to_os_string(); - filename.push("-keypair"); - keypair_file.set_file_name(filename); - keypair_file.set_extension("json"); - if let Ok(keypair) = read_keypair_file(&keypair_file.to_str().unwrap()) { - keypair + if let Some(program_location) = program_location { + let mut keypair_file = PathBuf::new(); + keypair_file.push(program_location); + let mut filename = keypair_file.file_stem().unwrap().to_os_string(); + filename.push("-keypair"); + keypair_file.set_file_name(filename); + keypair_file.set_extension("json"); + if let Ok(keypair) = read_keypair_file(&keypair_file.to_str().unwrap()) { + keypair + } else { + Keypair::new() + } } else { Keypair::new() } @@ -338,28 +568,34 @@ fn get_default_program_keypair(program_location: &str) -> Keypair { fn process_program_deploy( rpc_client: &RpcClient, config: &CliConfig, - program_location: &str, + program_location: &Option, program_signer_index: Option, program_pubkey: Option, buffer_signer_index: Option, + buffer_pubkey: Option, upgrade_authority_signer_index: Option, - upgrade_authority: Option, + upgrade_authority_pubkey: Option, + is_final: bool, max_len: Option, allow_excessive_balance: bool, ) -> ProcessResult { - // Create ephemeral keypair to use for Buffer account, if not provided let (words, mnemonic, buffer_keypair) = create_ephemeral_keypair()?; - let buffer_signer = if let Some(i) = buffer_signer_index { - config.signers[i] + let (buffer_signer, buffer_pubkey) = if let Some(i) = buffer_signer_index { + (Some(config.signers[i]), config.signers[i].pubkey()) + } else if let Some(pubkey) = buffer_pubkey { + (None, pubkey) } else { - &buffer_keypair + ( + Some(&buffer_keypair as &dyn Signer), + buffer_keypair.pubkey(), + ) }; - let default_program_keypair = get_default_program_keypair(program_location); - let (program_signer, program_id) = if let Some(i) = program_signer_index { + let default_program_keypair = get_default_program_keypair(&program_location); + let (program_signer, program_pubkey) = if let Some(i) = program_signer_index { (Some(config.signers[i]), config.signers[i].pubkey()) - } else if let Some(program_id) = program_pubkey { - (None, program_id) + } else if let Some(program_pubkey) = program_pubkey { + (None, program_pubkey) } else { ( Some(&default_program_keypair as &dyn Signer), @@ -368,7 +604,7 @@ fn process_program_deploy( }; let do_deploy = if let Some(account) = rpc_client - .get_account_with_commitment(&program_id, config.commitment)? + .get_account_with_commitment(&program_pubkey, config.commitment)? .value { if !account.executable { @@ -384,16 +620,16 @@ fn process_program_deploy( { if let UpgradeableLoaderState::ProgramData { slot: _, - upgrade_authority_address: program_authority, + upgrade_authority_address: program_authority_pubkey, } = account.state()? { - if program_authority.is_none() { + if program_authority_pubkey.is_none() { return Err("Program is no longer upgradeable".into()); } - if program_authority != upgrade_authority { + if program_authority_pubkey != upgrade_authority_pubkey { return Err(format!( "Program's authority {:?} does not match authority provided {:?}", - program_authority, upgrade_authority, + program_authority_pubkey, upgrade_authority_pubkey, ) .into()); } @@ -406,13 +642,140 @@ fn process_program_deploy( return Err("Program account is corrupt".into()); } } else { - return Err(format!("Program {:?} is not an upgradeable program", program_id).into()); + return Err( + format!("Program {:?} is not an upgradeable program", program_pubkey).into(), + ); } } else { // do new deploy true }; + let (program_data, buffer_data_len) = if buffer_signer.is_none() { + // Check supplied buffer account + if let Some(account) = rpc_client + .get_account_with_commitment(&buffer_pubkey, config.commitment)? + .value + { + if let UpgradeableLoaderState::Buffer { + authority_address: _, + } = account.state()? + { + } else { + return Err("Buffer account is not initialized".into()); + } + (vec![], account.data.len()) + } else { + return Err("Specified buffer not found, was it already consumed?".into()); + } + } else if let Some(program_location) = program_location { + let program_data = read_and_verify_elf(&program_location)?; + let buffer_data_len = if let Some(len) = max_len { + len + } else { + program_data.len() * 2 + }; + (program_data, buffer_data_len) + } else { + return Err("Program location required if buffer not supplied".into()); + }; + let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption( + UpgradeableLoaderState::programdata_len(buffer_data_len)?, + )?; + + let result = if do_deploy { + do_process_program_write_and_deploy( + rpc_client, + config, + &program_data, + buffer_data_len, + minimum_balance, + &bpf_loader_upgradeable::id(), + Some(program_signer.unwrap()), + buffer_signer, + &buffer_pubkey, + buffer_signer, + upgrade_authority_pubkey, + allow_excessive_balance, + ) + } else if let Some(upgrade_authority_index) = upgrade_authority_signer_index { + do_process_program_upgrade( + rpc_client, + config, + &program_data, + &program_pubkey, + config.signers[upgrade_authority_index], + &buffer_pubkey, + buffer_signer, + ) + } else { + return Err("Program upgrade requires an authority".into()); + }; + if result.is_ok() && is_final { + process_set_authority( + rpc_client, + config, + Some(program_pubkey), + None, + upgrade_authority_signer_index, + None, + )?; + } + if result.is_err() && buffer_signer_index.is_none() { + report_ephemeral_mnemonic(words, mnemonic); + } + result +} + +fn process_write_buffer( + rpc_client: &RpcClient, + config: &CliConfig, + program_location: &str, + buffer_signer_index: Option, + buffer_pubkey: Option, + buffer_authority_signer_index: Option, + is_final: bool, + max_len: Option, +) -> ProcessResult { + // Create ephemeral keypair to use for Buffer account, if not provided + let (words, mnemonic, buffer_keypair) = create_ephemeral_keypair()?; + let (buffer_signer, buffer_pubkey) = if let Some(i) = buffer_signer_index { + (Some(config.signers[i]), config.signers[i].pubkey()) + } else if let Some(pubkey) = buffer_pubkey { + (None, pubkey) + } else { + ( + Some(&buffer_keypair as &dyn Signer), + buffer_keypair.pubkey(), + ) + }; + let buffer_authority = if let Some(i) = buffer_authority_signer_index { + config.signers[i] + } else { + config.signers[0] + }; + + if let Some(account) = rpc_client + .get_account_with_commitment(&buffer_pubkey, config.commitment)? + .value + { + if let UpgradeableLoaderState::Buffer { authority_address } = account.state()? { + if authority_address.is_none() { + return Err("Buffer is immutable".into()); + } + if authority_address != Some(buffer_authority.pubkey()) { + return Err(format!( + "Buffer's authority {:?} does not match authority provided {:?}", + authority_address, + buffer_authority.pubkey() + ) + .into()); + } + } else { + return Err("Buffer account is corrupt".into()); + } + } + let program_data = read_and_verify_elf(program_location)?; let buffer_data_len = if let Some(len) = max_len { len @@ -423,64 +786,80 @@ fn process_program_deploy( UpgradeableLoaderState::programdata_len(buffer_data_len)?, )?; - let result = if do_deploy { - do_process_program_deploy( + let result = do_process_program_write_and_deploy( + rpc_client, + config, + &program_data, + program_data.len(), + minimum_balance, + &bpf_loader_upgradeable::id(), + None, + buffer_signer, + &buffer_pubkey, + Some(buffer_authority), + None, + true, + ); + + if result.is_ok() && is_final { + process_set_authority( rpc_client, config, - &program_data, - buffer_data_len, - minimum_balance, - &bpf_loader_upgradeable::id(), - program_signer.unwrap(), - buffer_signer, - upgrade_authority, - allow_excessive_balance, - ) - } else if let Some(upgrade_authority_index) = upgrade_authority_signer_index { - do_process_program_upgrade( - rpc_client, - config, - &program_data, - program_id, - config.signers[upgrade_authority_index], - buffer_signer, - ) - } else { - return Err("Program upgrade requires an authority".into()); - }; - if result.is_err() && buffer_signer_index.is_none() { + None, + Some(buffer_pubkey), + buffer_authority_signer_index, + None, + )?; + } + + if result.is_err() && buffer_signer_index.is_none() && buffer_signer.is_some() { report_ephemeral_mnemonic(words, mnemonic); } result } -fn process_set_program_upgrade_authority( +fn process_set_authority( rpc_client: &RpcClient, config: &CliConfig, - program_id: Pubkey, - upgrade_authority: Option, - new_upgrade_authority: Option, + program_pubkey: Option, + buffer_pubkey: Option, + authority: Option, + new_authority: Option, ) -> ProcessResult { - let upgrade_authority_signer = if let Some(index) = upgrade_authority { + let authority_signer = if let Some(index) = authority { config.signers[index] } else { return Err("Set authority requires the current authority".into()); }; - trace!("Set a new program upgrade authority"); + trace!("Set a new authority"); let (blockhash, _, _) = rpc_client .get_recent_blockhash_with_commitment(config.commitment)? .value; - let mut tx = Transaction::new_unsigned(Message::new( - &[bpf_loader_upgradeable::set_authority( - &program_id, - &upgrade_authority_signer.pubkey(), - new_upgrade_authority.as_ref(), - )], - Some(&config.signers[0].pubkey()), - )); - tx.try_sign(&[config.signers[0], upgrade_authority_signer], blockhash)?; + let mut tx = if let Some(pubkey) = program_pubkey { + Transaction::new_unsigned(Message::new( + &[bpf_loader_upgradeable::set_upgrade_authority( + &pubkey, + &authority_signer.pubkey(), + new_authority.as_ref(), + )], + Some(&config.signers[0].pubkey()), + )) + } else if let Some(pubkey) = buffer_pubkey { + Transaction::new_unsigned(Message::new( + &[bpf_loader_upgradeable::set_buffer_authority( + &pubkey, + &authority_signer.pubkey(), + new_authority.as_ref(), + )], + Some(&config.signers[0].pubkey()), + )) + } else { + return Err("Program or Buffer not provided".into()); + }; + + tx.try_sign(&[config.signers[0], authority_signer], blockhash)?; rpc_client .send_and_confirm_transaction_with_spinner_and_config( &tx, @@ -491,15 +870,15 @@ fn process_set_program_upgrade_authority( ..RpcSendTransactionConfig::default() }, ) - .map_err(|e| format!("Setting upgrade authority failed: {}", e))?; + .map_err(|e| format!("Setting authority failed: {}", e))?; - match new_upgrade_authority { - Some(address) => Ok(json!({ - "UpgradeAuthority": format!("{:?}", address), + match new_authority { + Some(pubkey) => Ok(json!({ + "Authority": format!("{:?}", pubkey), }) .to_string()), None => Ok(json!({ - "UpgradeAuthority": "None", + "Authority": "None", }) .to_string()), } @@ -530,15 +909,17 @@ pub fn process_deploy( bpf_loader::id() }; - let result = do_process_program_deploy( + let result = do_process_program_write_and_deploy( rpc_client, config, &program_data, program_data.len(), minimum_balance, &loader_id, - buffer_signer, - buffer_signer, + Some(buffer_signer), + Some(buffer_signer), + &buffer_signer.pubkey(), + Some(buffer_signer), None, allow_excessive_balance, ); @@ -549,15 +930,17 @@ pub fn process_deploy( } #[allow(clippy::too_many_arguments)] -fn do_process_program_deploy( +fn do_process_program_write_and_deploy( rpc_client: &RpcClient, config: &CliConfig, program_data: &[u8], buffer_data_len: usize, minimum_balance: u64, loader_id: &Pubkey, - program_signer: &dyn Signer, - buffer_signer: &dyn Signer, + program_signer: Option<&dyn Signer>, + buffer_signer: Option<&dyn Signer>, + buffer_pubkey: &Pubkey, + buffer_authority_signer: Option<&dyn Signer>, upgrade_authority: Option, allow_excessive_balance: bool, ) -> ProcessResult { @@ -565,112 +948,126 @@ fn do_process_program_deploy( let mut messages: Vec<&Message> = Vec::new(); // Initialize buffer account or complete if already partially initialized - - let (initial_instructions, balance_needed) = if let Some(account) = rpc_client - .get_account_with_commitment(&buffer_signer.pubkey(), config.commitment)? - .value - { - complete_partial_program_init( - &loader_id, - &config.signers[0].pubkey(), - &buffer_signer.pubkey(), - &account, - if loader_id == &bpf_loader_upgradeable::id() { - UpgradeableLoaderState::buffer_len(buffer_data_len)? + let (initial_message, write_messages, balance_needed) = + if let Some(buffer_authority_signer) = buffer_authority_signer { + let (initial_instructions, balance_needed) = if let Some(account) = rpc_client + .get_account_with_commitment(buffer_pubkey, config.commitment)? + .value + { + complete_partial_program_init( + &loader_id, + &config.signers[0].pubkey(), + buffer_pubkey, + &account, + if loader_id == &bpf_loader_upgradeable::id() { + UpgradeableLoaderState::buffer_len(buffer_data_len)? + } else { + buffer_data_len + }, + minimum_balance, + allow_excessive_balance, + )? + } else if loader_id == &bpf_loader_upgradeable::id() { + ( + bpf_loader_upgradeable::create_buffer( + &config.signers[0].pubkey(), + buffer_pubkey, + Some(&buffer_authority_signer.pubkey()), + minimum_balance, + buffer_data_len, + )?, + minimum_balance, + ) } else { - buffer_data_len - }, - minimum_balance, - allow_excessive_balance, - )? - } else if loader_id == &bpf_loader_upgradeable::id() { - ( - bpf_loader_upgradeable::create_buffer( - &config.signers[0].pubkey(), - &buffer_signer.pubkey(), - minimum_balance, - buffer_data_len, - )?, - minimum_balance, - ) - } else { - ( - vec![system_instruction::create_account( - &config.signers[0].pubkey(), - &buffer_signer.pubkey(), - minimum_balance, - buffer_data_len as u64, - &loader_id, - )], - minimum_balance, - ) - }; + ( + vec![system_instruction::create_account( + &config.signers[0].pubkey(), + buffer_pubkey, + minimum_balance, + buffer_data_len as u64, + &loader_id, + )], + minimum_balance, + ) + }; + let initial_message = if !initial_instructions.is_empty() { + Some(Message::new( + &initial_instructions, + Some(&config.signers[0].pubkey()), + )) + } else { + None + }; - let initial_message = if !initial_instructions.is_empty() { - Some(Message::new( - &initial_instructions, - Some(&config.signers[0].pubkey()), - )) - } else { - None - }; - if let Some(message) = &initial_message { - messages.push(message); - } + // Create and add write messages - // Create and add write messages + let mut write_messages = vec![]; + for (chunk, i) in program_data.chunks(DATA_CHUNK_SIZE).zip(0..) { + let instruction = if loader_id == &bpf_loader_upgradeable::id() { + bpf_loader_upgradeable::write( + buffer_pubkey, + Some(&buffer_authority_signer.pubkey()), + (i * DATA_CHUNK_SIZE) as u32, + chunk.to_vec(), + ) + } else { + loader_instruction::write( + buffer_pubkey, + &loader_id, + (i * DATA_CHUNK_SIZE) as u32, + chunk.to_vec(), + ) + }; + let message = Message::new(&[instruction], Some(&config.signers[0].pubkey())); + write_messages.push(message); + } - let mut write_messages = vec![]; - for (chunk, i) in program_data.chunks(DATA_CHUNK_SIZE).zip(0..) { - let instruction = if loader_id == &bpf_loader_upgradeable::id() { - bpf_loader_upgradeable::write( - &buffer_signer.pubkey(), - (i * DATA_CHUNK_SIZE) as u32, - chunk.to_vec(), - ) + (initial_message, Some(write_messages), balance_needed) } else { - loader_instruction::write( - &buffer_signer.pubkey(), - &loader_id, - (i * DATA_CHUNK_SIZE) as u32, - chunk.to_vec(), - ) + (None, None, 0) }; - let message = Message::new(&[instruction], Some(&config.signers[0].pubkey())); - write_messages.push(message); + + if let Some(ref initial_message) = initial_message { + messages.push(initial_message); } - let mut write_message_refs = vec![]; - for message in write_messages.iter() { - write_message_refs.push(message); + if let Some(ref write_messages) = write_messages { + let mut write_message_refs = vec![]; + for message in write_messages.iter() { + write_message_refs.push(message); + } + messages.append(&mut write_message_refs); } - messages.append(&mut write_message_refs); // Create and add final message - let final_message = if loader_id == &bpf_loader_upgradeable::id() { - Message::new( - &bpf_loader_upgradeable::deploy_with_max_program_len( - &config.signers[0].pubkey(), - &program_signer.pubkey(), - &buffer_signer.pubkey(), - upgrade_authority.as_ref(), - rpc_client.get_minimum_balance_for_rent_exemption( - UpgradeableLoaderState::program_len()?, + let final_message = if let Some(program_signer) = program_signer { + let message = if loader_id == &bpf_loader_upgradeable::id() { + Message::new( + &bpf_loader_upgradeable::deploy_with_max_program_len( + &config.signers[0].pubkey(), + &program_signer.pubkey(), + buffer_pubkey, + upgrade_authority.as_ref(), + rpc_client.get_minimum_balance_for_rent_exemption( + UpgradeableLoaderState::program_len()?, + )?, + buffer_data_len, )?, - buffer_data_len, - )?, - Some(&config.signers[0].pubkey()), - ) + Some(&config.signers[0].pubkey()), + ) + } else { + Message::new( + &[loader_instruction::finalize(buffer_pubkey, &loader_id)], + Some(&config.signers[0].pubkey()), + ) + }; + Some(message) } else { - Message::new( - &[loader_instruction::finalize( - &buffer_signer.pubkey(), - &loader_id, - )], - Some(&config.signers[0].pubkey()), - ) + None }; - messages.push(&final_message); + if let Some(ref message) = final_message { + messages.push(message); + } check_payer(rpc_client, config, balance_needed, &messages)?; @@ -681,22 +1078,31 @@ fn do_process_program_deploy( &write_messages, &final_message, buffer_signer, + buffer_authority_signer, program_signer, )?; - Ok(json!({ - "programId": format!("{}", program_signer.pubkey()), - }) - .to_string()) + if let Some(program_signer) = program_signer { + Ok(json!({ + "ProgramId": format!("{}", program_signer.pubkey()), + }) + .to_string()) + } else { + Ok(json!({ + "Buffer": format!("{}", buffer_pubkey), + }) + .to_string()) + } } fn do_process_program_upgrade( rpc_client: &RpcClient, config: &CliConfig, program_data: &[u8], - program_id: Pubkey, + program_id: &Pubkey, upgrade_authority: &dyn Signer, - buffer_signer: &dyn Signer, + buffer_pubkey: &Pubkey, + buffer_signer: Option<&dyn Signer>, ) -> ProcessResult { let loader_id = bpf_loader_upgradeable::id(); let data_len = program_data.len(); @@ -707,65 +1113,78 @@ fn do_process_program_upgrade( // Build messages to calculate fees let mut messages: Vec<&Message> = Vec::new(); - // Check Buffer account to see if partial initialization has occurred - let (initial_instructions, balance_needed) = if let Some(account) = rpc_client - .get_account_with_commitment(&buffer_signer.pubkey(), config.commitment)? - .value - { - complete_partial_program_init( - &loader_id, - &config.signers[0].pubkey(), - &buffer_signer.pubkey(), - &account, - UpgradeableLoaderState::buffer_len(data_len)?, - minimum_balance, - true, - )? - } else { - ( - bpf_loader_upgradeable::create_buffer( - &config.signers[0].pubkey(), - &buffer_signer.pubkey(), - minimum_balance, - data_len, - )?, - minimum_balance, - ) - }; - let initial_message = if !initial_instructions.is_empty() { - Some(Message::new( - &initial_instructions, - Some(&config.signers[0].pubkey()), - )) - } else { - None - }; - if let Some(message) = &initial_message { + let (initial_message, write_messages, balance_needed) = + if let Some(buffer_signer) = buffer_signer { + // Check Buffer account to see if partial initialization has occurred + let (initial_instructions, balance_needed) = if let Some(account) = rpc_client + .get_account_with_commitment(&buffer_signer.pubkey(), config.commitment)? + .value + { + complete_partial_program_init( + &loader_id, + &config.signers[0].pubkey(), + &buffer_signer.pubkey(), + &account, + UpgradeableLoaderState::buffer_len(data_len)?, + minimum_balance, + true, + )? + } else { + ( + bpf_loader_upgradeable::create_buffer( + &config.signers[0].pubkey(), + buffer_pubkey, + Some(&buffer_signer.pubkey()), + minimum_balance, + data_len, + )?, + minimum_balance, + ) + }; + + let initial_message = if !initial_instructions.is_empty() { + Some(Message::new( + &initial_instructions, + Some(&config.signers[0].pubkey()), + )) + } else { + None + }; + + // Create and add write messages + let mut write_messages = vec![]; + for (chunk, i) in program_data.chunks(DATA_CHUNK_SIZE).zip(0..) { + let instruction = bpf_loader_upgradeable::write( + &buffer_signer.pubkey(), + None, + (i * DATA_CHUNK_SIZE) as u32, + chunk.to_vec(), + ); + let message = Message::new(&[instruction], Some(&config.signers[0].pubkey())); + write_messages.push(message); + } + + (initial_message, Some(write_messages), balance_needed) + } else { + (None, None, 0) + }; + + if let Some(ref message) = initial_message { messages.push(message); } - - // Create and add write messages - let mut write_messages = vec![]; - for (chunk, i) in program_data.chunks(DATA_CHUNK_SIZE).zip(0..) { - let instruction = bpf_loader_upgradeable::write( - &buffer_signer.pubkey(), - (i * DATA_CHUNK_SIZE) as u32, - chunk.to_vec(), - ); - let message = Message::new(&[instruction], Some(&config.signers[0].pubkey())); - write_messages.push(message); + if let Some(ref write_messages) = write_messages { + let mut write_message_refs = vec![]; + for message in write_messages.iter() { + write_message_refs.push(message); + } + messages.append(&mut write_message_refs); } - let mut write_message_refs = vec![]; - for message in write_messages.iter() { - write_message_refs.push(message); - } - messages.append(&mut write_message_refs); // Create and add final message let final_message = Message::new( &[bpf_loader_upgradeable::upgrade( &program_id, - &buffer_signer.pubkey(), + &buffer_pubkey, &upgrade_authority.pubkey(), &config.signers[0].pubkey(), )], @@ -779,9 +1198,10 @@ fn do_process_program_upgrade( config, &initial_message, &write_messages, - &final_message, + &Some(final_message), buffer_signer, - upgrade_authority, + buffer_signer, + Some(upgrade_authority), )?; Ok(json!({ @@ -882,75 +1302,89 @@ fn send_deploy_messages( rpc_client: &RpcClient, config: &CliConfig, initial_message: &Option, - write_messages: &[Message], - final_message: &Message, - buffer_signer: &dyn Signer, - final_signer: &dyn Signer, + write_messages: &Option>, + final_message: &Option, + initial_signer: Option<&dyn Signer>, + write_signer: Option<&dyn Signer>, + final_signer: Option<&dyn Signer>, ) -> Result<(), Box> { let payer_signer = config.signers[0]; if let Some(message) = initial_message { - trace!("Preparing the required accounts"); + if let Some(initial_signer) = initial_signer { + trace!("Preparing the required accounts"); + let (blockhash, _, _) = rpc_client + .get_recent_blockhash_with_commitment(config.commitment)? + .value; - let (blockhash, _, _) = rpc_client - .get_recent_blockhash_with_commitment(config.commitment)? - .value; - - let mut initial_transaction = Transaction::new_unsigned(message.clone()); - // Most of the initial_transaction combinations require both the fee-payer and new program - // account to sign the transaction. One (transfer) only requires the fee-payer signature. - // This check is to ensure signing does not fail on a KeypairPubkeyMismatch error from an - // extraneous signature. - if message.header.num_required_signatures == 2 { - initial_transaction.try_sign(&[payer_signer, buffer_signer], blockhash)?; + let mut initial_transaction = Transaction::new_unsigned(message.clone()); + // Most of the initial_transaction combinations require both the fee-payer and new program + // account to sign the transaction. One (transfer) only requires the fee-payer signature. + // This check is to ensure signing does not fail on a KeypairPubkeyMismatch error from an + // extraneous signature. + if message.header.num_required_signatures == 2 { + initial_transaction.try_sign(&[payer_signer, initial_signer], blockhash)?; + } else { + initial_transaction.try_sign(&[payer_signer], blockhash)?; + } + let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config( + &initial_transaction, + config.commitment, + config.send_transaction_config, + ); + log_instruction_custom_error::(result, &config) + .map_err(|err| format!("Account allocation failed: {}", err))?; } else { - initial_transaction.try_sign(&[payer_signer], blockhash)?; + return Err("Buffer account not created yet, must provide a key pair".into()); } - let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config( - &initial_transaction, - config.commitment, - config.send_transaction_config, - ); - log_instruction_custom_error::(result, &config) - .map_err(|err| format!("Account allocation failed: {}", err))?; } - trace!("Writing program data"); - let (blockhash, _, last_valid_slot) = rpc_client - .get_recent_blockhash_with_commitment(config.commitment)? - .value; - let mut write_transactions = vec![]; - for message in write_messages.iter() { - let mut tx = Transaction::new_unsigned(message.clone()); - tx.try_sign(&[payer_signer, buffer_signer], blockhash)?; - write_transactions.push(tx); + if let Some(write_messages) = write_messages { + if let Some(write_signer) = write_signer { + trace!("Writing program data"); + let (blockhash, _, last_valid_slot) = rpc_client + .get_recent_blockhash_with_commitment(config.commitment)? + .value; + let mut write_transactions = vec![]; + for message in write_messages.iter() { + let mut tx = Transaction::new_unsigned(message.clone()); + tx.try_sign(&[payer_signer, write_signer], blockhash)?; + write_transactions.push(tx); + } + + send_and_confirm_transactions_with_spinner( + &rpc_client, + write_transactions, + &[payer_signer, write_signer], + config.commitment, + last_valid_slot, + ) + .map_err(|err| format!("Data writes to account failed: {}", err))?; + } } - send_and_confirm_transactions_with_spinner( - &rpc_client, - write_transactions, - &[payer_signer, buffer_signer], - config.commitment, - last_valid_slot, - ) - .map_err(|err| format!("Data writes to account failed: {}", err))?; - trace!("Deploying program"); - let (blockhash, _, _) = rpc_client - .get_recent_blockhash_with_commitment(config.commitment)? - .value; + if let Some(message) = final_message { + if let Some(final_signer) = final_signer { + trace!("Deploying program"); + let (blockhash, _, _) = rpc_client + .get_recent_blockhash_with_commitment(config.commitment)? + .value; + + let mut final_tx = Transaction::new_unsigned(message.clone()); + final_tx.try_sign(&[payer_signer, final_signer], blockhash)?; + rpc_client + .send_and_confirm_transaction_with_spinner_and_config( + &final_tx, + config.commitment, + RpcSendTransactionConfig { + skip_preflight: true, + preflight_commitment: Some(config.commitment.commitment), + ..RpcSendTransactionConfig::default() + }, + ) + .map_err(|e| format!("Deploying program failed: {}", e))?; + } + } - let mut final_tx = Transaction::new_unsigned(final_message.clone()); - final_tx.try_sign(&[payer_signer, final_signer], blockhash)?; - rpc_client - .send_and_confirm_transaction_with_spinner_and_config( - &final_tx, - config.commitment, - RpcSendTransactionConfig { - skip_preflight: true, - preflight_commitment: Some(config.commitment.commitment), - ..RpcSendTransactionConfig::default() - }, - ) - .map_err(|e| format!("Deploying program failed: {}", e))?; Ok(()) } @@ -1153,12 +1587,14 @@ mod tests { parse_command(&test_deploy, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::Deploy { - program_location: "/Users/test/program.so".to_string(), + program_location: Some("/Users/test/program.so".to_string()), buffer_signer_index: None, + buffer_pubkey: None, program_signer_index: None, program_pubkey: None, upgrade_authority_signer_index: Some(0), upgrade_authority_pubkey: Some(default_keypair.pubkey()), + is_final: false, max_len: None, allow_excessive_balance: false, }), @@ -1178,12 +1614,14 @@ mod tests { parse_command(&test_deploy, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::Deploy { - program_location: "/Users/test/program.so".to_string(), + program_location: Some("/Users/test/program.so".to_string()), buffer_signer_index: None, + buffer_pubkey: None, program_signer_index: None, program_pubkey: None, upgrade_authority_signer_index: Some(0), upgrade_authority_pubkey: Some(default_keypair.pubkey()), + is_final: false, max_len: Some(42), allow_excessive_balance: false, }), @@ -1191,124 +1629,59 @@ mod tests { } ); - let buffer_pubkey = Pubkey::new_unique(); + let buffer_keypair = Keypair::new(); + let buffer_keypair_file = make_tmp_path("buffer_keypair_file"); + write_keypair_file(&buffer_keypair, &buffer_keypair_file).unwrap(); let test_deploy = test_commands.clone().get_matches_from(vec![ "test", "program", "deploy", - "/Users/test/program.so", "--buffer", - &buffer_pubkey.to_string(), - ]); - assert!(parse_command(&test_deploy, &default_signer, &mut None).is_err()); - - let buffer_address = Keypair::new(); - let buffer_address_file = make_tmp_path("buffer_address_file"); - write_keypair_file(&buffer_address, &buffer_address_file).unwrap(); - let test_deploy = test_commands.clone().get_matches_from(vec![ - "test", - "program", - "deploy", - "/Users/test/program.so", - "--buffer", - &buffer_address_file, + &buffer_keypair_file, ]); assert_eq!( parse_command(&test_deploy, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::Deploy { - program_location: "/Users/test/program.so".to_string(), + program_location: None, buffer_signer_index: Some(1), + buffer_pubkey: Some(buffer_keypair.pubkey()), program_signer_index: None, program_pubkey: None, upgrade_authority_signer_index: Some(0), upgrade_authority_pubkey: Some(default_keypair.pubkey()), + is_final: false, max_len: None, allow_excessive_balance: false, }), signers: vec![ read_keypair_file(&keypair_file).unwrap().into(), - read_keypair_file(&buffer_address_file).unwrap().into(), + read_keypair_file(&buffer_keypair_file).unwrap().into(), ], } ); - let program_address = Pubkey::new_unique(); + let program_pubkey = Pubkey::new_unique(); let test = test_commands.clone().get_matches_from(vec![ "test", "program", "deploy", "/Users/test/program.so", "--program-id", - &program_address.to_string(), + &program_pubkey.to_string(), ]); assert_eq!( parse_command(&test, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::Deploy { - program_location: "/Users/test/program.so".to_string(), + program_location: Some("/Users/test/program.so".to_string()), buffer_signer_index: None, + buffer_pubkey: None, program_signer_index: None, - program_pubkey: Some(program_address), + program_pubkey: Some(program_pubkey), upgrade_authority_signer_index: Some(0), upgrade_authority_pubkey: Some(default_keypair.pubkey()), - max_len: None, - allow_excessive_balance: false, - }), - signers: vec![read_keypair_file(&keypair_file).unwrap().into(),], - } - ); - - let program_address = Keypair::new(); - let program_address_file = make_tmp_path("program_address_file"); - write_keypair_file(&program_address, &program_address_file).unwrap(); - let test = test_commands.clone().get_matches_from(vec![ - "test", - "program", - "deploy", - "/Users/test/program.so", - "--program-id", - &program_address_file, - ]); - assert_eq!( - parse_command(&test, &default_signer, &mut None).unwrap(), - CliCommandInfo { - command: CliCommand::Program(ProgramCliCommand::Deploy { - program_location: "/Users/test/program.so".to_string(), - buffer_signer_index: None, - program_signer_index: Some(1), - program_pubkey: Some(program_address.pubkey()), - upgrade_authority_signer_index: Some(0), - upgrade_authority_pubkey: Some(default_keypair.pubkey()), - max_len: None, - allow_excessive_balance: false, - }), - signers: vec![ - read_keypair_file(&keypair_file).unwrap().into(), - read_keypair_file(&program_address_file).unwrap().into(), - ], - } - ); - - let authority_address = Pubkey::new_unique(); - let test_deploy = test_commands.clone().get_matches_from(vec![ - "test", - "program", - "deploy", - "/Users/test/program.so", - "--upgrade-authority", - &authority_address.to_string(), - ]); - assert_eq!( - parse_command(&test_deploy, &default_signer, &mut None).unwrap(), - CliCommandInfo { - command: CliCommand::Program(ProgramCliCommand::Deploy { - program_location: "/Users/test/program.so".to_string(), - buffer_signer_index: None, - program_signer_index: None, - program_pubkey: None, - upgrade_authority_signer_index: None, - upgrade_authority_pubkey: Some(authority_address), + is_final: false, max_len: None, allow_excessive_balance: false, }), @@ -1316,33 +1689,96 @@ mod tests { } ); - let authority_address = Keypair::new(); - let authority_address_file = make_tmp_path("authority_address_file"); - write_keypair_file(&authority_address, &authority_address_file).unwrap(); + let program_keypair = Keypair::new(); + let program_keypair_file = make_tmp_path("program_keypair_file"); + write_keypair_file(&program_keypair, &program_keypair_file).unwrap(); + let test = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "deploy", + "/Users/test/program.so", + "--program-id", + &program_keypair_file, + ]); + assert_eq!( + parse_command(&test, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::Deploy { + program_location: Some("/Users/test/program.so".to_string()), + buffer_signer_index: None, + buffer_pubkey: None, + program_signer_index: Some(1), + program_pubkey: Some(program_keypair.pubkey()), + upgrade_authority_signer_index: Some(0), + upgrade_authority_pubkey: Some(default_keypair.pubkey()), + is_final: false, + max_len: None, + allow_excessive_balance: false, + }), + signers: vec![ + read_keypair_file(&keypair_file).unwrap().into(), + read_keypair_file(&program_keypair_file).unwrap().into(), + ], + } + ); + + let authority_pubkey = Pubkey::new_unique(); let test_deploy = test_commands.clone().get_matches_from(vec![ "test", "program", "deploy", "/Users/test/program.so", "--upgrade-authority", - &authority_address_file, + &authority_pubkey.to_string(), ]); assert_eq!( parse_command(&test_deploy, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::Deploy { - program_location: "/Users/test/program.so".to_string(), + program_location: Some("/Users/test/program.so".to_string()), buffer_signer_index: None, + buffer_pubkey: None, + program_signer_index: None, + program_pubkey: None, + upgrade_authority_signer_index: None, + upgrade_authority_pubkey: Some(authority_pubkey), + is_final: false, + max_len: None, + allow_excessive_balance: false, + }), + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], + } + ); + + let authority_keypair = Keypair::new(); + let authority_keypair_file = make_tmp_path("authority_keypair_file"); + write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap(); + let test_deploy = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "deploy", + "/Users/test/program.so", + "--upgrade-authority", + &authority_keypair_file, + ]); + assert_eq!( + parse_command(&test_deploy, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::Deploy { + program_location: Some("/Users/test/program.so".to_string()), + buffer_signer_index: None, + buffer_pubkey: None, program_signer_index: None, program_pubkey: None, upgrade_authority_signer_index: Some(1), - upgrade_authority_pubkey: Some(authority_address.pubkey()), + upgrade_authority_pubkey: Some(authority_keypair.pubkey()), + is_final: false, max_len: None, allow_excessive_balance: false, }), signers: vec![ read_keypair_file(&keypair_file).unwrap().into(), - read_keypair_file(&authority_address_file).unwrap().into(), + read_keypair_file(&authority_keypair_file).unwrap().into(), ], } ); @@ -1358,16 +1794,228 @@ mod tests { parse_command(&test_deploy, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::Deploy { - program_location: "/Users/test/program.so".to_string(), + program_location: Some("/Users/test/program.so".to_string()), buffer_signer_index: None, + buffer_pubkey: None, program_signer_index: None, program_pubkey: None, - upgrade_authority_signer_index: None, - upgrade_authority_pubkey: None, + upgrade_authority_signer_index: Some(0), + upgrade_authority_pubkey: Some(default_keypair.pubkey()), + is_final: true, max_len: None, allow_excessive_balance: false, }), - signers: vec![read_keypair_file(&keypair_file).unwrap().into(),], + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], + } + ); + } + + #[test] + #[allow(clippy::cognitive_complexity)] + fn test_cli_parse_write_buffer() { + let test_commands = app("test", "desc", "version"); + + let default_keypair = Keypair::new(); + let keypair_file = make_tmp_path("keypair_file"); + write_keypair_file(&default_keypair, &keypair_file).unwrap(); + let default_signer = DefaultSigner { + path: keypair_file.clone(), + arg_name: "".to_string(), + }; + + // defaults + let test_deploy = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "write-buffer", + "/Users/test/program.so", + ]); + assert_eq!( + parse_command(&test_deploy, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::WriteBuffer { + program_location: "/Users/test/program.so".to_string(), + buffer_signer_index: None, + buffer_pubkey: None, + buffer_authority_signer_index: Some(0), + is_final: false, + max_len: None, + }), + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], + } + ); + + // specify max len + let test_deploy = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "write-buffer", + "/Users/test/program.so", + "--max-len", + "42", + ]); + assert_eq!( + parse_command(&test_deploy, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::WriteBuffer { + program_location: "/Users/test/program.so".to_string(), + buffer_signer_index: None, + buffer_pubkey: None, + buffer_authority_signer_index: Some(0), + is_final: false, + max_len: Some(42), + }), + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], + } + ); + + // specify buffer + let buffer_keypair = Keypair::new(); + let buffer_keypair_file = make_tmp_path("buffer_keypair_file"); + write_keypair_file(&buffer_keypair, &buffer_keypair_file).unwrap(); + let test_deploy = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "write-buffer", + "/Users/test/program.so", + "--buffer", + &buffer_keypair_file, + ]); + assert_eq!( + parse_command(&test_deploy, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::WriteBuffer { + program_location: "/Users/test/program.so".to_string(), + buffer_signer_index: Some(1), + buffer_pubkey: Some(buffer_keypair.pubkey()), + buffer_authority_signer_index: Some(0), + is_final: false, + max_len: None, + }), + signers: vec![ + read_keypair_file(&keypair_file).unwrap().into(), + read_keypair_file(&buffer_keypair_file).unwrap().into(), + ], + } + ); + + // specify authority + let authority_keypair = Keypair::new(); + let authority_keypair_file = make_tmp_path("authority_keypair_file"); + write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap(); + let test_deploy = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "write-buffer", + "/Users/test/program.so", + "--buffer-authority", + &authority_keypair_file, + ]); + assert_eq!( + parse_command(&test_deploy, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::WriteBuffer { + program_location: "/Users/test/program.so".to_string(), + buffer_signer_index: None, + buffer_pubkey: None, + buffer_authority_signer_index: Some(1), + is_final: false, + max_len: None, + }), + signers: vec![ + read_keypair_file(&keypair_file).unwrap().into(), + read_keypair_file(&authority_keypair_file).unwrap().into(), + ], + } + ); + + // specify both buffer and authority + let buffer_keypair = Keypair::new(); + let buffer_keypair_file = make_tmp_path("buffer_keypair_file"); + write_keypair_file(&buffer_keypair, &buffer_keypair_file).unwrap(); + let authority_keypair = Keypair::new(); + let authority_keypair_file = make_tmp_path("authority_keypair_file"); + write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap(); + let test_deploy = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "write-buffer", + "/Users/test/program.so", + "--buffer", + &buffer_keypair_file, + "--buffer-authority", + &authority_keypair_file, + ]); + assert_eq!( + parse_command(&test_deploy, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::WriteBuffer { + program_location: "/Users/test/program.so".to_string(), + buffer_signer_index: Some(1), + buffer_pubkey: Some(buffer_keypair.pubkey()), + buffer_authority_signer_index: Some(2), + is_final: false, + max_len: None, + }), + signers: vec![ + read_keypair_file(&keypair_file).unwrap().into(), + read_keypair_file(&buffer_keypair_file).unwrap().into(), + read_keypair_file(&authority_keypair_file).unwrap().into(), + ], + } + ); + + // specify authority + let test_deploy = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "write-buffer", + "/Users/test/program.so", + "--final", + ]); + assert_eq!( + parse_command(&test_deploy, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::WriteBuffer { + program_location: "/Users/test/program.so".to_string(), + buffer_signer_index: None, + buffer_pubkey: None, + buffer_authority_signer_index: Some(0), + is_final: true, + max_len: None, + }), + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], + } + ); + + // specify both buffer and authority and final + let authority_keypair = Keypair::new(); + let authority_keypair_file = make_tmp_path("authority_keypair_file"); + write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap(); + let test_deploy = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "write-buffer", + "/Users/test/program.so", + "--buffer-authority", + &authority_keypair_file, + "--final", + ]); + assert_eq!( + parse_command(&test_deploy, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::WriteBuffer { + program_location: "/Users/test/program.so".to_string(), + buffer_signer_index: None, + buffer_pubkey: None, + buffer_authority_signer_index: Some(1), + is_final: true, + max_len: None, + }), + signers: vec![ + read_keypair_file(&keypair_file).unwrap().into(), + read_keypair_file(&authority_keypair_file).unwrap().into(), + ], } ); } @@ -1385,102 +2033,216 @@ mod tests { arg_name: "".to_string(), }; - let program_address = Pubkey::new_unique(); - let new_authority_address = Pubkey::new_unique(); + let program_pubkey = Pubkey::new_unique(); + let new_authority_pubkey = Pubkey::new_unique(); let test_deploy = test_commands.clone().get_matches_from(vec![ "test", "program", "set-upgrade-authority", - &program_address.to_string(), + &program_pubkey.to_string(), "--new-upgrade-authority", - &new_authority_address.to_string(), + &new_authority_pubkey.to_string(), ]); assert_eq!( parse_command(&test_deploy, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority { - program: program_address, + program_pubkey, upgrade_authority_index: Some(0), - new_upgrade_authority: Some(new_authority_address), + new_upgrade_authority: Some(new_authority_pubkey), }), signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } ); - let program_address = Pubkey::new_unique(); - let new_authority_address = Keypair::new(); - let new_authority_address_file = make_tmp_path("authority_address_file"); - write_keypair_file(&new_authority_address, &new_authority_address_file).unwrap(); + let program_pubkey = Pubkey::new_unique(); + let new_authority_pubkey = Keypair::new(); + let new_authority_pubkey_file = make_tmp_path("authority_keypair_file"); + write_keypair_file(&new_authority_pubkey, &new_authority_pubkey_file).unwrap(); let test_deploy = test_commands.clone().get_matches_from(vec![ "test", "program", "set-upgrade-authority", - &program_address.to_string(), + &program_pubkey.to_string(), "--new-upgrade-authority", - &new_authority_address_file, + &new_authority_pubkey_file, ]); assert_eq!( parse_command(&test_deploy, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority { - program: program_address, + program_pubkey, upgrade_authority_index: Some(0), - new_upgrade_authority: Some(new_authority_address.pubkey()), + new_upgrade_authority: Some(new_authority_pubkey.pubkey()), }), - signers: vec![read_keypair_file(&keypair_file).unwrap().into(),], + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } ); - let program_address = Pubkey::new_unique(); - let new_authority_address = Keypair::new(); - let new_authority_address_file = make_tmp_path("authority_address_file"); - write_keypair_file(&new_authority_address, &new_authority_address_file).unwrap(); + let program_pubkey = Pubkey::new_unique(); + let new_authority_pubkey = Keypair::new(); + let new_authority_pubkey_file = make_tmp_path("authority_keypair_file"); + write_keypair_file(&new_authority_pubkey, &new_authority_pubkey_file).unwrap(); let test_deploy = test_commands.clone().get_matches_from(vec![ "test", "program", "set-upgrade-authority", - &program_address.to_string(), + &program_pubkey.to_string(), "--final", ]); assert_eq!( parse_command(&test_deploy, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority { - program: program_address, + program_pubkey, upgrade_authority_index: Some(0), new_upgrade_authority: None, }), - signers: vec![read_keypair_file(&keypair_file).unwrap().into(),], + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } ); - let program_address = Pubkey::new_unique(); - let authority_address = Keypair::new(); - let authority_address_file = make_tmp_path("authority_address_file"); - write_keypair_file(&authority_address, &authority_address_file).unwrap(); - let new_authority_address = Keypair::new(); - let new_authority_address_file = make_tmp_path("authority_address_file"); - write_keypair_file(&new_authority_address, &new_authority_address_file).unwrap(); + let program_pubkey = Pubkey::new_unique(); + let authority = Keypair::new(); + let authority_keypair_file = make_tmp_path("authority_keypair_file"); + write_keypair_file(&authority, &authority_keypair_file).unwrap(); + let new_authority_pubkey = Keypair::new(); + let new_authority_pubkey_file = make_tmp_path("authority_keypair_file"); + write_keypair_file(&new_authority_pubkey, &new_authority_pubkey_file).unwrap(); let test_deploy = test_commands.clone().get_matches_from(vec![ "test", "program", "set-upgrade-authority", - &program_address.to_string(), + &program_pubkey.to_string(), "--upgrade-authority", - &authority_address_file, + &authority_keypair_file, "--final", ]); assert_eq!( parse_command(&test_deploy, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority { - program: program_address, + program_pubkey, upgrade_authority_index: Some(1), new_upgrade_authority: None, }), signers: vec![ read_keypair_file(&keypair_file).unwrap().into(), - read_keypair_file(&authority_address_file).unwrap().into(), + read_keypair_file(&authority_keypair_file).unwrap().into(), + ], + } + ); + } + + #[test] + #[allow(clippy::cognitive_complexity)] + fn test_cli_parse_set_buffer_authority() { + let test_commands = app("test", "desc", "version"); + + let default_keypair = Keypair::new(); + let keypair_file = make_tmp_path("keypair_file"); + write_keypair_file(&default_keypair, &keypair_file).unwrap(); + let default_signer = DefaultSigner { + path: keypair_file.clone(), + arg_name: "".to_string(), + }; + + let buffer_pubkey = Pubkey::new_unique(); + let new_authority_pubkey = Pubkey::new_unique(); + let test_deploy = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "set-buffer-authority", + &buffer_pubkey.to_string(), + "--new-buffer-authority", + &new_authority_pubkey.to_string(), + ]); + assert_eq!( + parse_command(&test_deploy, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::SetBufferAuthority { + buffer_pubkey, + buffer_authority_index: Some(0), + new_buffer_authority: Some(new_authority_pubkey), + }), + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], + } + ); + + let buffer_pubkey = Pubkey::new_unique(); + let new_authority_pubkey = Keypair::new(); + let new_authority_pubkey_file = make_tmp_path("authority_keypair_file"); + write_keypair_file(&new_authority_pubkey, &new_authority_pubkey_file).unwrap(); + let test_deploy = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "set-buffer-authority", + &buffer_pubkey.to_string(), + "--new-buffer-authority", + &new_authority_pubkey_file, + ]); + assert_eq!( + parse_command(&test_deploy, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::SetBufferAuthority { + buffer_pubkey, + buffer_authority_index: Some(0), + new_buffer_authority: Some(new_authority_pubkey.pubkey()), + }), + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], + } + ); + + let buffer_pubkey = Pubkey::new_unique(); + let new_authority_pubkey = Keypair::new(); + let new_authority_pubkey_file = make_tmp_path("authority_keypair_file"); + write_keypair_file(&new_authority_pubkey, &new_authority_pubkey_file).unwrap(); + let test_deploy = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "set-buffer-authority", + &buffer_pubkey.to_string(), + "--final", + ]); + assert_eq!( + parse_command(&test_deploy, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::SetBufferAuthority { + buffer_pubkey, + buffer_authority_index: Some(0), + new_buffer_authority: None, + }), + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], + } + ); + + let buffer_pubkey = Pubkey::new_unique(); + let authority = Keypair::new(); + let authority_keypair_file = make_tmp_path("authority_keypair_file"); + write_keypair_file(&authority, &authority_keypair_file).unwrap(); + let new_authority_pubkey = Keypair::new(); + let new_authority_pubkey_file = make_tmp_path("authority_keypair_file"); + write_keypair_file(&new_authority_pubkey, &new_authority_pubkey_file).unwrap(); + let test_deploy = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "set-buffer-authority", + &buffer_pubkey.to_string(), + "--buffer-authority", + &authority_keypair_file, + "--final", + ]); + assert_eq!( + parse_command(&test_deploy, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::SetBufferAuthority { + buffer_pubkey, + buffer_authority_index: Some(1), + new_buffer_authority: None, + }), + signers: vec![ + read_keypair_file(&keypair_file).unwrap().into(), + read_keypair_file(&authority_keypair_file).unwrap().into(), ], } ); @@ -1491,7 +2253,7 @@ mod tests { solana_logger::setup(); let default_keypair = Keypair::new(); - let program_address = Keypair::new(); + let program_pubkey = Keypair::new(); let deploy_path = make_tmp_path("deploy"); let mut program_location = PathBuf::from(deploy_path.clone()); program_location.push("noop"); @@ -1504,17 +2266,19 @@ mod tests { let program_keypair_location = program_location.with_file_name("noop-keypair.json"); std::fs::create_dir_all(deploy_path).unwrap(); std::fs::copy(pathbuf, program_location.as_os_str()).unwrap(); - write_keypair_file(&program_address, &program_keypair_location).unwrap(); + write_keypair_file(&program_pubkey, &program_keypair_location).unwrap(); let config = CliConfig { rpc_client: Some(RpcClient::new_mock("".to_string())), command: CliCommand::Program(ProgramCliCommand::Deploy { - program_location: program_location.to_str().unwrap().to_string(), + program_location: Some(program_location.to_str().unwrap().to_string()), buffer_signer_index: None, + buffer_pubkey: None, program_signer_index: None, program_pubkey: None, upgrade_authority_signer_index: None, upgrade_authority_pubkey: Some(default_keypair.pubkey()), + is_final: false, max_len: None, allow_excessive_balance: false, }), @@ -1527,14 +2291,14 @@ mod tests { let program_id = json .as_object() .unwrap() - .get("programId") + .get("ProgramId") .unwrap() .as_str() .unwrap(); assert_eq!( program_id.parse::().unwrap(), - program_address.pubkey() + program_pubkey.pubkey() ); } } diff --git a/cli/tests/program.rs b/cli/tests/program.rs index 2d174ce75e..27eeadc371 100644 --- a/cli/tests/program.rs +++ b/cli/tests/program.rs @@ -7,6 +7,7 @@ use solana_client::rpc_client::RpcClient; use solana_core::test_validator::TestValidator; use solana_faucet::faucet::run_local_faucet; use solana_sdk::{ + account_utils::StateMut, bpf_loader, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, commitment_config::CommitmentConfig, @@ -64,7 +65,7 @@ fn test_cli_program_deploy_non_upgradeable() { let program_id_str = json .as_object() .unwrap() - .get("programId") + .get("ProgramId") .unwrap() .as_str() .unwrap(); @@ -191,13 +192,15 @@ fn test_cli_program_deploy_no_authority() { // Deploy a program with no authority config.signers = vec![&keypair]; config.command = CliCommand::Program(ProgramCliCommand::Deploy { - program_location: pathbuf.to_str().unwrap().to_string(), + program_location: Some(pathbuf.to_str().unwrap().to_string()), program_signer_index: None, program_pubkey: None, buffer_signer_index: None, + buffer_pubkey: None, allow_excessive_balance: false, upgrade_authority_signer_index: None, upgrade_authority_pubkey: None, + is_final: false, max_len: None, }); let response = process_command(&config); @@ -205,7 +208,7 @@ fn test_cli_program_deploy_no_authority() { let program_id_str = json .as_object() .unwrap() - .get("programId") + .get("ProgramId") .unwrap() .as_str() .unwrap(); @@ -214,13 +217,15 @@ fn test_cli_program_deploy_no_authority() { // Attempt to upgrade the program config.signers = vec![&keypair, &upgrade_authority]; config.command = CliCommand::Program(ProgramCliCommand::Deploy { - program_location: pathbuf.to_str().unwrap().to_string(), + program_location: Some(pathbuf.to_str().unwrap().to_string()), program_signer_index: None, program_pubkey: Some(program_id), buffer_signer_index: None, + buffer_pubkey: None, allow_excessive_balance: false, upgrade_authority_signer_index: Some(1), upgrade_authority_pubkey: Some(upgrade_authority.pubkey()), + is_final: false, max_len: None, }); process_command(&config).unwrap_err(); @@ -275,27 +280,29 @@ fn test_cli_program_deploy_with_authority() { let program_keypair = Keypair::new(); config.signers = vec![&keypair, &upgrade_authority, &program_keypair]; config.command = CliCommand::Program(ProgramCliCommand::Deploy { - program_location: pathbuf.to_str().unwrap().to_string(), + program_location: Some(pathbuf.to_str().unwrap().to_string()), program_signer_index: Some(2), program_pubkey: Some(program_keypair.pubkey()), buffer_signer_index: None, + buffer_pubkey: None, allow_excessive_balance: false, upgrade_authority_signer_index: Some(1), upgrade_authority_pubkey: Some(upgrade_authority.pubkey()), + is_final: false, max_len: Some(max_len), }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); - let program_id_str = json + let program_pubkey_str = json .as_object() .unwrap() - .get("programId") + .get("ProgramId") .unwrap() .as_str() .unwrap(); assert_eq!( program_keypair.pubkey(), - Pubkey::from_str(&program_id_str).unwrap() + Pubkey::from_str(&program_pubkey_str).unwrap() ); let program_account = rpc_client .get_account_with_commitment(&program_keypair.pubkey(), CommitmentConfig::recent()) @@ -328,27 +335,29 @@ fn test_cli_program_deploy_with_authority() { // Deploy the upgradeable program config.signers = vec![&keypair, &upgrade_authority]; config.command = CliCommand::Program(ProgramCliCommand::Deploy { - program_location: pathbuf.to_str().unwrap().to_string(), + program_location: Some(pathbuf.to_str().unwrap().to_string()), program_signer_index: None, program_pubkey: None, buffer_signer_index: None, + buffer_pubkey: None, allow_excessive_balance: false, upgrade_authority_signer_index: Some(1), upgrade_authority_pubkey: Some(upgrade_authority.pubkey()), + is_final: false, max_len: Some(max_len), }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); - let program_id_str = json + let program_pubkey_str = json .as_object() .unwrap() - .get("programId") + .get("ProgramId") .unwrap() .as_str() .unwrap(); - let program_id = Pubkey::from_str(&program_id_str).unwrap(); + let program_pubkey = Pubkey::from_str(&program_pubkey_str).unwrap(); let program_account = rpc_client - .get_account_with_commitment(&program_id, CommitmentConfig::recent()) + .get_account_with_commitment(&program_pubkey, CommitmentConfig::recent()) .unwrap() .value .unwrap(); @@ -356,7 +365,7 @@ fn test_cli_program_deploy_with_authority() { assert_eq!(program_account.owner, bpf_loader_upgradeable::id()); assert_eq!(program_account.executable, true); let (programdata_pubkey, _) = - Pubkey::find_program_address(&[program_id.as_ref()], &bpf_loader_upgradeable::id()); + Pubkey::find_program_address(&[program_pubkey.as_ref()], &bpf_loader_upgradeable::id()); let programdata_account = rpc_client .get_account_with_commitment(&programdata_pubkey, CommitmentConfig::recent()) .unwrap() @@ -376,27 +385,20 @@ fn test_cli_program_deploy_with_authority() { // Upgrade the program config.signers = vec![&keypair, &upgrade_authority]; config.command = CliCommand::Program(ProgramCliCommand::Deploy { - program_location: pathbuf.to_str().unwrap().to_string(), + program_location: Some(pathbuf.to_str().unwrap().to_string()), program_signer_index: None, - program_pubkey: Some(program_id), + program_pubkey: Some(program_pubkey), buffer_signer_index: None, + buffer_pubkey: None, allow_excessive_balance: false, upgrade_authority_signer_index: Some(1), upgrade_authority_pubkey: Some(upgrade_authority.pubkey()), + is_final: false, max_len: Some(max_len), }); - 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(); + process_command(&config).unwrap(); let program_account = rpc_client - .get_account_with_commitment(&program_id, CommitmentConfig::recent()) + .get_account_with_commitment(&program_pubkey, CommitmentConfig::recent()) .unwrap() .value .unwrap(); @@ -404,7 +406,7 @@ fn test_cli_program_deploy_with_authority() { assert_eq!(program_account.owner, bpf_loader_upgradeable::id()); assert_eq!(program_account.executable, true); let (programdata_pubkey, _) = - Pubkey::find_program_address(&[program_id.as_ref()], &bpf_loader_upgradeable::id()); + Pubkey::find_program_address(&[program_pubkey.as_ref()], &bpf_loader_upgradeable::id()); let programdata_account = rpc_client .get_account_with_commitment(&programdata_pubkey, CommitmentConfig::recent()) .unwrap() @@ -425,7 +427,7 @@ fn test_cli_program_deploy_with_authority() { let new_upgrade_authority = Keypair::new(); config.signers = vec![&keypair, &upgrade_authority]; config.command = CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority { - program: program_id, + program_pubkey, upgrade_authority_index: Some(1), new_upgrade_authority: Some(new_upgrade_authority.pubkey()), }); @@ -434,7 +436,7 @@ fn test_cli_program_deploy_with_authority() { let new_upgrade_authority_str = json .as_object() .unwrap() - .get("UpgradeAuthority") + .get("Authority") .unwrap() .as_str() .unwrap(); @@ -446,27 +448,20 @@ fn test_cli_program_deploy_with_authority() { // Upgrade with new authority config.signers = vec![&keypair, &new_upgrade_authority]; config.command = CliCommand::Program(ProgramCliCommand::Deploy { - program_location: pathbuf.to_str().unwrap().to_string(), + program_location: Some(pathbuf.to_str().unwrap().to_string()), program_signer_index: None, - program_pubkey: Some(program_id), + program_pubkey: Some(program_pubkey), buffer_signer_index: None, + buffer_pubkey: None, allow_excessive_balance: false, upgrade_authority_signer_index: Some(1), upgrade_authority_pubkey: Some(new_upgrade_authority.pubkey()), + is_final: false, max_len: None, }); - 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(); + process_command(&config).unwrap(); let program_account = rpc_client - .get_account_with_commitment(&program_id, CommitmentConfig::recent()) + .get_account_with_commitment(&program_pubkey, CommitmentConfig::recent()) .unwrap() .value .unwrap(); @@ -474,7 +469,7 @@ fn test_cli_program_deploy_with_authority() { assert_eq!(program_account.owner, bpf_loader_upgradeable::id()); assert_eq!(program_account.executable, true); let (programdata_pubkey, _) = - Pubkey::find_program_address(&[program_id.as_ref()], &bpf_loader_upgradeable::id()); + Pubkey::find_program_address(&[program_pubkey.as_ref()], &bpf_loader_upgradeable::id()); let programdata_account = rpc_client .get_account_with_commitment(&programdata_pubkey, CommitmentConfig::recent()) .unwrap() @@ -494,7 +489,7 @@ fn test_cli_program_deploy_with_authority() { // Set no authority config.signers = vec![&keypair, &new_upgrade_authority]; config.command = CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority { - program: program_id, + program_pubkey, upgrade_authority_index: Some(1), new_upgrade_authority: None, }); @@ -503,7 +498,7 @@ fn test_cli_program_deploy_with_authority() { let new_upgrade_authority_str = json .as_object() .unwrap() - .get("UpgradeAuthority") + .get("Authority") .unwrap() .as_str() .unwrap(); @@ -512,14 +507,452 @@ fn test_cli_program_deploy_with_authority() { // Upgrade with no authority config.signers = vec![&keypair, &new_upgrade_authority]; config.command = CliCommand::Program(ProgramCliCommand::Deploy { - program_location: pathbuf.to_str().unwrap().to_string(), + program_location: Some(pathbuf.to_str().unwrap().to_string()), program_signer_index: None, - program_pubkey: Some(program_id), + program_pubkey: Some(program_pubkey), buffer_signer_index: None, + buffer_pubkey: None, allow_excessive_balance: false, upgrade_authority_signer_index: Some(1), upgrade_authority_pubkey: Some(new_upgrade_authority.pubkey()), + is_final: false, max_len: None, }); process_command(&config).unwrap_err(); + + // deploy with finality + config.signers = vec![&keypair, &new_upgrade_authority]; + config.command = CliCommand::Program(ProgramCliCommand::Deploy { + program_location: Some(pathbuf.to_str().unwrap().to_string()), + program_signer_index: None, + program_pubkey: None, + buffer_signer_index: None, + buffer_pubkey: None, + allow_excessive_balance: false, + upgrade_authority_signer_index: Some(1), + upgrade_authority_pubkey: Some(new_upgrade_authority.pubkey()), + is_final: true, + max_len: None, + }); + 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_with_commitment(&programdata_pubkey, CommitmentConfig::recent()) + .unwrap() + .value + .unwrap(); + if let UpgradeableLoaderState::ProgramData { + slot: _, + upgrade_authority_address, + } = programdata_account.state().unwrap() + { + assert_eq!(upgrade_authority_address, None); + } else { + panic!("not a buffer account"); + } +} + +#[test] +fn test_cli_program_write_buffer() { + solana_logger::setup(); + + let mut pathbuf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + pathbuf.push("tests"); + pathbuf.push("fixtures"); + pathbuf.push("noop"); + pathbuf.set_extension("so"); + + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey()); + + let (sender, receiver) = channel(); + run_local_faucet(mint_keypair, sender, None); + let faucet_addr = receiver.recv().unwrap(); + + let rpc_client = RpcClient::new(test_validator.rpc_url()); + + let mut file = File::open(pathbuf.to_str().unwrap()).unwrap(); + let mut program_data = Vec::new(); + file.read_to_end(&mut program_data).unwrap(); + let max_len = program_data.len(); + let minimum_balance_for_buffer = rpc_client + .get_minimum_balance_for_rent_exemption( + UpgradeableLoaderState::programdata_len(max_len).unwrap(), + ) + .unwrap(); + let minimum_balance_for_buffer_default = rpc_client + .get_minimum_balance_for_rent_exemption( + UpgradeableLoaderState::programdata_len(max_len * 2).unwrap(), + ) + .unwrap(); + + let mut config = CliConfig::recent_for_tests(); + let keypair = Keypair::new(); + config.json_rpc_url = test_validator.rpc_url(); + config.signers = vec![&keypair]; + config.command = CliCommand::Airdrop { + faucet_host: None, + faucet_port: faucet_addr.port(), + pubkey: None, + lamports: 100 * minimum_balance_for_buffer, + }; + process_command(&config).unwrap(); + + // Write a buffer with default params + config.signers = vec![&keypair]; + config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer { + program_location: pathbuf.to_str().unwrap().to_string(), + buffer_signer_index: None, + buffer_pubkey: None, + buffer_authority_signer_index: None, + is_final: false, + max_len: None, + }); + 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_with_commitment(&new_buffer_pubkey, CommitmentConfig::recent()) + .unwrap() + .value + .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::buffer_data_offset().unwrap()..], + program_data[..] + ); + + // Specify buffer keypair and max_len + let buffer_keypair = Keypair::new(); + config.signers = vec![&keypair, &buffer_keypair]; + config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer { + program_location: pathbuf.to_str().unwrap().to_string(), + buffer_signer_index: Some(1), + buffer_pubkey: Some(buffer_keypair.pubkey()), + buffer_authority_signer_index: None, + is_final: false, + max_len: Some(max_len), + }); + 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_with_commitment(&buffer_keypair.pubkey(), CommitmentConfig::recent()) + .unwrap() + .value + .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::buffer_data_offset().unwrap()..], + program_data[..] + ); + + // Specify buffer authority + 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: pathbuf.to_str().unwrap().to_string(), + buffer_signer_index: Some(1), + buffer_pubkey: Some(buffer_keypair.pubkey()), + buffer_authority_signer_index: Some(2), + is_final: false, + max_len: None, + }); + 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_with_commitment(&buffer_keypair.pubkey(), CommitmentConfig::recent()) + .unwrap() + .value + .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::buffer_data_offset().unwrap()..], + program_data[..] + ); + + // Specify authority only + 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: pathbuf.to_str().unwrap().to_string(), + buffer_signer_index: None, + buffer_pubkey: None, + buffer_authority_signer_index: Some(2), + is_final: false, + max_len: None, + }); + 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_with_commitment(&buffer_pubkey, CommitmentConfig::recent()) + .unwrap() + .value + .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::buffer_data_offset().unwrap()..], + program_data[..] + ); + + // Specify final + 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: pathbuf.to_str().unwrap().to_string(), + buffer_signer_index: None, + buffer_pubkey: None, + buffer_authority_signer_index: Some(2), + is_final: true, + max_len: None, + }); + 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_with_commitment(&buffer_pubkey, CommitmentConfig::recent()) + .unwrap() + .value + .unwrap(); + if let UpgradeableLoaderState::Buffer { authority_address } = buffer_account.state().unwrap() { + assert_eq!(authority_address, None); + } else { + panic!("not a buffer account"); + } +} + +#[test] +fn test_cli_program_set_buffer_authority() { + solana_logger::setup(); + + let mut pathbuf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + pathbuf.push("tests"); + pathbuf.push("fixtures"); + pathbuf.push("noop"); + pathbuf.set_extension("so"); + + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey()); + + let (sender, receiver) = channel(); + run_local_faucet(mint_keypair, sender, None); + let faucet_addr = receiver.recv().unwrap(); + + let rpc_client = RpcClient::new(test_validator.rpc_url()); + + let mut file = File::open(pathbuf.to_str().unwrap()).unwrap(); + let mut program_data = Vec::new(); + file.read_to_end(&mut program_data).unwrap(); + let max_len = program_data.len(); + let minimum_balance_for_buffer = rpc_client + .get_minimum_balance_for_rent_exemption( + UpgradeableLoaderState::programdata_len(max_len).unwrap(), + ) + .unwrap(); + + let mut config = CliConfig::recent_for_tests(); + let keypair = Keypair::new(); + config.json_rpc_url = test_validator.rpc_url(); + config.signers = vec![&keypair]; + config.command = CliCommand::Airdrop { + faucet_host: None, + faucet_port: faucet_addr.port(), + pubkey: None, + lamports: 100 * minimum_balance_for_buffer, + }; + process_command(&config).unwrap(); + + // Write a buffer + let buffer_keypair = Keypair::new(); + config.signers = vec![&keypair, &buffer_keypair]; + config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer { + program_location: pathbuf.to_str().unwrap().to_string(), + buffer_signer_index: Some(1), + buffer_pubkey: Some(buffer_keypair.pubkey()), + buffer_authority_signer_index: None, + is_final: false, + max_len: None, + }); + process_command(&config).unwrap(); + let buffer_account = rpc_client + .get_account_with_commitment(&buffer_keypair.pubkey(), CommitmentConfig::recent()) + .unwrap() + .value + .unwrap(); + if let UpgradeableLoaderState::Buffer { authority_address } = buffer_account.state().unwrap() { + assert_eq!(authority_address, Some(keypair.pubkey())); + } else { + panic!("not a buffer account"); + } + + // Set new authority + 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: Some(new_buffer_authority.pubkey()), + }); + 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_with_commitment(&buffer_keypair.pubkey(), CommitmentConfig::recent()) + .unwrap() + .value + .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"); + } + + // Set authority to buffer + 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: Some(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_with_commitment(&buffer_keypair.pubkey(), CommitmentConfig::recent()) + .unwrap() + .value + .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"); + } + + // Set authority to None + config.signers = vec![&keypair, &buffer_keypair]; + config.command = CliCommand::Program(ProgramCliCommand::SetBufferAuthority { + buffer_pubkey: buffer_keypair.pubkey(), + buffer_authority_index: Some(1), + new_buffer_authority: None, + }); + 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!(buffer_authority_str, "None"); + let buffer_account = rpc_client + .get_account_with_commitment(&buffer_keypair.pubkey(), CommitmentConfig::recent()) + .unwrap() + .value + .unwrap(); + if let UpgradeableLoaderState::Buffer { authority_address } = buffer_account.state().unwrap() { + assert_eq!(authority_address, None); + } else { + panic!("not a buffer account"); + } } diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index ab995e2b7b..28a57b88d2 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -59,14 +59,14 @@ impl UserDefinedError for BPFError {} /// Point all log messages to the log collector macro_rules! log { ($logger:ident, $message:expr) => { - if let Ok(logger) = $logger.try_borrow_mut() { + if let Ok(logger) = $logger.try_borrow_mut() { if logger.log_enabled() { logger.log($message); } } }; ($logger:ident, $fmt:expr, $($arg:tt)*) => { - if let Ok(logger) = $logger.try_borrow_mut() { + if let Ok(logger) = $logger.try_borrow_mut() { if logger.log_enabled() { logger.log(&format!($fmt, $($arg)*)); } @@ -128,28 +128,19 @@ pub fn create_and_cache_executor( } fn write_program_data( - account: &KeyedAccount, + data: &mut [u8], offset: usize, bytes: &[u8], invoke_context: &mut dyn InvokeContext, ) -> Result<(), InstructionError> { let logger = invoke_context.get_logger(); - if account.signer_key().is_none() { - log!(logger, "Buffer account did not sign"); - return Err(InstructionError::MissingRequiredSignature); - } let len = bytes.len(); - if account.data_len()? < offset + len { - log!( - logger, - "Write overflow: {} < {}", - account.data_len()?, - offset + len - ); + if data.len() < offset + len { + log!(logger, "Write overflow: {} < {}", data.len(), offset + len); return Err(InstructionError::AccountDataTooSmall); } - account.try_account_ref_mut()?.data[offset..offset + len].copy_from_slice(&bytes); + data[offset..offset + len].copy_from_slice(&bytes); Ok(()) } @@ -319,20 +310,41 @@ fn process_loader_upgradeable_instruction( match limited_deserialize(instruction_data)? { UpgradeableLoaderInstruction::InitializeBuffer => { let buffer = next_keyed_account(account_iter)?; + let authority = next_keyed_account(account_iter) + .ok() + .map(|account| account.unsigned_key()); + if UpgradeableLoaderState::Uninitialized != buffer.state()? { log!(logger, "Buffer account already initialized"); return Err(InstructionError::AccountAlreadyInitialized); } - buffer.set_state(&UpgradeableLoaderState::Buffer)?; + buffer.set_state(&UpgradeableLoaderState::Buffer { + authority_address: authority.cloned(), + })?; } UpgradeableLoaderInstruction::Write { offset, bytes } => { let buffer = next_keyed_account(account_iter)?; - if UpgradeableLoaderState::Buffer != buffer.state()? { + let authority = next_keyed_account(account_iter)?; + + if let UpgradeableLoaderState::Buffer { authority_address } = buffer.state()? { + if authority_address == None { + log!(logger, "Buffer is immutable"); + return Err(InstructionError::Immutable); // TODO better error code + } + if authority_address != Some(*authority.unsigned_key()) { + log!(logger, "Incorrect buffer authority provided"); + return Err(InstructionError::IncorrectAuthority); + } + if authority.signer_key().is_none() { + log!(logger, "Buffer authority did not sign"); + return Err(InstructionError::MissingRequiredSignature); + } + } else { log!(logger, "Invalid Buffer account"); return Err(InstructionError::InvalidAccountData); } write_program_data( - buffer, + &mut buffer.try_account_ref_mut()?.data, UpgradeableLoaderState::buffer_data_offset()? + offset as usize, &bytes, invoke_context, @@ -367,7 +379,11 @@ fn process_loader_upgradeable_instruction( // Verify Buffer account - if UpgradeableLoaderState::Buffer != buffer.state()? { + if let UpgradeableLoaderState::Buffer { + authority_address: _, + } = buffer.state()? + { + } else { log!(logger, "Invalid Buffer account"); return Err(InstructionError::InvalidArgument); } @@ -473,9 +489,13 @@ fn process_loader_upgradeable_instruction( // Verify Buffer account - if UpgradeableLoaderState::Buffer != buffer.state()? { + if let UpgradeableLoaderState::Buffer { + authority_address: _, + } = buffer.state()? + { + } else { log!(logger, "Invalid Buffer account"); - return Err(InstructionError::InvalidAccountData); + return Err(InstructionError::InvalidArgument); } let buffer_data_offset = UpgradeableLoaderState::buffer_data_offset()?; @@ -500,11 +520,11 @@ fn process_loader_upgradeable_instruction( { if upgrade_authority_address == None { log!(logger, "Program not upgradeable"); - return Err(InstructionError::InvalidArgument); + return Err(InstructionError::Immutable); } if upgrade_authority_address != Some(*authority.unsigned_key()) { - log!(logger, "Upgrade authority not present"); - return Err(InstructionError::MissingRequiredSignature); + log!(logger, "Incorrect upgrade authority provided"); + return Err(InstructionError::IncorrectAuthority); } if authority.signer_key().is_none() { log!(logger, "Upgrade authority did not sign"); @@ -551,36 +571,55 @@ fn process_loader_upgradeable_instruction( log!(logger, "Upgraded program {:?}", program.unsigned_key()); } UpgradeableLoaderInstruction::SetAuthority => { - let programdata = next_keyed_account(account_iter)?; + let account = next_keyed_account(account_iter)?; let present_authority = next_keyed_account(account_iter)?; let new_authority = next_keyed_account(account_iter) .ok() .map(|account| account.unsigned_key()); - if let UpgradeableLoaderState::ProgramData { - slot, - upgrade_authority_address, - } = programdata.state()? - { - if upgrade_authority_address == None { - log!(logger, "Program not upgradeable"); - return Err(InstructionError::InvalidArgument); + match account.state()? { + UpgradeableLoaderState::Buffer { authority_address } => { + if authority_address == None { + log!(logger, "Buffer is immutable"); + return Err(InstructionError::Immutable); + } + if authority_address != Some(*present_authority.unsigned_key()) { + log!(logger, "Incorrect buffer authority provided"); + return Err(InstructionError::IncorrectAuthority); + } + if present_authority.signer_key().is_none() { + log!(logger, "Buffer authority did not sign"); + return Err(InstructionError::MissingRequiredSignature); + } + account.set_state(&UpgradeableLoaderState::Buffer { + authority_address: new_authority.cloned(), + })?; } - if upgrade_authority_address != Some(*present_authority.unsigned_key()) { - log!(logger, "Upgrade authority not present"); - return Err(InstructionError::MissingRequiredSignature); - } - if present_authority.signer_key().is_none() { - log!(logger, "Upgrade authority did not sign"); - return Err(InstructionError::MissingRequiredSignature); - } - programdata.set_state(&UpgradeableLoaderState::ProgramData { + UpgradeableLoaderState::ProgramData { slot, - upgrade_authority_address: new_authority.cloned(), - })?; - } else { - log!(logger, "Not a ProgramData account"); - return Err(InstructionError::InvalidAccountData); + upgrade_authority_address, + } => { + if upgrade_authority_address == None { + log!(logger, "Program not upgradeable"); + return Err(InstructionError::Immutable); + } + if upgrade_authority_address != Some(*present_authority.unsigned_key()) { + log!(logger, "Incorrect upgrade authority provided"); + return Err(InstructionError::IncorrectAuthority); + } + if present_authority.signer_key().is_none() { + log!(logger, "Upgrade authority did not sign"); + return Err(InstructionError::MissingRequiredSignature); + } + account.set_state(&UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: new_authority.cloned(), + })?; + } + _ => { + log!(logger, "Account does not support authorities"); + return Err(InstructionError::InvalidAccountData); + } } log!(logger, "New authority {:?}", new_authority); @@ -607,7 +646,16 @@ fn process_loader_instruction( } match limited_deserialize(instruction_data)? { LoaderInstruction::Write { offset, bytes } => { - write_program_data(program, offset as usize, &bytes, invoke_context)?; + if program.signer_key().is_none() { + log!(logger, "Program account did not sign"); + return Err(InstructionError::MissingRequiredSignature); + } + write_program_data( + &mut program.try_account_ref_mut()?.data, + offset as usize, + &bytes, + invoke_context, + )?; } LoaderInstruction::Finalize => { if program.signer_key().is_none() { @@ -1150,26 +1198,68 @@ mod tests { Ok(()), process_instruction( &bpf_loader_upgradeable::id(), - &[KeyedAccount::new(&buffer_address, false, &buffer_account),], + &[KeyedAccount::new(&buffer_address, false, &buffer_account)], &instruction, &mut MockInvokeContext::default() ) ); let state: UpgradeableLoaderState = buffer_account.borrow().state().unwrap(); - assert_eq!(state, UpgradeableLoaderState::Buffer); + assert_eq!( + state, + UpgradeableLoaderState::Buffer { + authority_address: None + } + ); // Case: Already initialized assert_eq!( Err(InstructionError::AccountAlreadyInitialized), process_instruction( &bpf_loader_upgradeable::id(), - &[KeyedAccount::new(&buffer_address, false, &buffer_account),], + &[KeyedAccount::new(&buffer_address, false, &buffer_account)], &instruction, &mut MockInvokeContext::default() ) ); let state: UpgradeableLoaderState = buffer_account.borrow().state().unwrap(); - assert_eq!(state, UpgradeableLoaderState::Buffer); + assert_eq!( + state, + UpgradeableLoaderState::Buffer { + authority_address: None + } + ); + + // Case: With authority + let buffer_account = Account::new_ref( + 1, + UpgradeableLoaderState::buffer_len(9).unwrap(), + &bpf_loader_upgradeable::id(), + ); + let authority_address = Pubkey::new_unique(); + let authority_account = Account::new_ref( + 1, + UpgradeableLoaderState::buffer_len(9).unwrap(), + &bpf_loader_upgradeable::id(), + ); + assert_eq!( + Ok(()), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&authority_address, false, &authority_account) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + let state: UpgradeableLoaderState = buffer_account.borrow().state().unwrap(); + assert_eq!( + state, + UpgradeableLoaderState::Buffer { + authority_address: Some(authority_address) + } + ); } #[test] @@ -1191,7 +1281,10 @@ mod tests { Err(InstructionError::InvalidAccountData), process_instruction( &bpf_loader_upgradeable::id(), - &[KeyedAccount::new(&buffer_address, true, &buffer_account),], + &[ + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&buffer_address, true, &buffer_account) + ], &instruction, &mut MockInvokeContext::default() ) @@ -1205,19 +1298,29 @@ mod tests { .unwrap(); buffer_account .borrow_mut() - .set_state(&UpgradeableLoaderState::Buffer) + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(buffer_address), + }) .unwrap(); assert_eq!( Ok(()), process_instruction( &bpf_loader_upgradeable::id(), - &[KeyedAccount::new(&buffer_address, true, &buffer_account),], + &[ + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&buffer_address, true, &buffer_account) + ], &instruction, &mut MockInvokeContext::default() ) ); let state: UpgradeableLoaderState = buffer_account.borrow().state().unwrap(); - assert_eq!(state, UpgradeableLoaderState::Buffer); + assert_eq!( + state, + UpgradeableLoaderState::Buffer { + authority_address: Some(buffer_address) + } + ); assert_eq!( &buffer_account.borrow().data[UpgradeableLoaderState::buffer_data_offset().unwrap()..], &[42; 9] @@ -1236,19 +1339,29 @@ mod tests { ); buffer_account .borrow_mut() - .set_state(&UpgradeableLoaderState::Buffer) + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(buffer_address), + }) .unwrap(); assert_eq!( Ok(()), process_instruction( &bpf_loader_upgradeable::id(), - &[KeyedAccount::new(&buffer_address, true, &buffer_account),], + &[ + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&buffer_address, true, &buffer_account) + ], &instruction, &mut MockInvokeContext::default() ) ); let state: UpgradeableLoaderState = buffer_account.borrow().state().unwrap(); - assert_eq!(state, UpgradeableLoaderState::Buffer); + assert_eq!( + state, + UpgradeableLoaderState::Buffer { + authority_address: Some(buffer_address) + } + ); assert_eq!( &buffer_account.borrow().data[UpgradeableLoaderState::buffer_data_offset().unwrap()..], &[0, 0, 0, 42, 42, 42, 42, 42, 42] @@ -1262,13 +1375,18 @@ mod tests { .unwrap(); buffer_account .borrow_mut() - .set_state(&UpgradeableLoaderState::Buffer) + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(buffer_address), + }) .unwrap(); assert_eq!( Err(InstructionError::MissingRequiredSignature), process_instruction( &bpf_loader_upgradeable::id(), - &[KeyedAccount::new(&buffer_address, false, &buffer_account),], + &[ + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&buffer_address, false, &buffer_account) + ], &instruction, &mut MockInvokeContext::default() ) @@ -1282,13 +1400,18 @@ mod tests { .unwrap(); buffer_account .borrow_mut() - .set_state(&UpgradeableLoaderState::Buffer) + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(buffer_address), + }) .unwrap(); assert_eq!( Err(InstructionError::AccountDataTooSmall), process_instruction( &bpf_loader_upgradeable::id(), - &[KeyedAccount::new(&buffer_address, true, &buffer_account),], + &[ + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&buffer_address, true, &buffer_account) + ], &instruction, &mut MockInvokeContext::default() ) @@ -1302,13 +1425,44 @@ mod tests { .unwrap(); buffer_account .borrow_mut() - .set_state(&UpgradeableLoaderState::Buffer) + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(buffer_address), + }) .unwrap(); assert_eq!( Err(InstructionError::AccountDataTooSmall), process_instruction( &bpf_loader_upgradeable::id(), - &[KeyedAccount::new(&buffer_address, true, &buffer_account),], + &[ + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&buffer_address, true, &buffer_account) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: wrong authority + let authority_address = Pubkey::new_unique(); + let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write { + offset: 1, + bytes: vec![42; 9], + }) + .unwrap(); + buffer_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(buffer_address), + }) + .unwrap(); + assert_eq!( + Err(InstructionError::IncorrectAuthority), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&authority_address, true, &buffer_account) + ], &instruction, &mut MockInvokeContext::default() ) @@ -1349,7 +1503,9 @@ mod tests { &bpf_loader_upgradeable::id(), ); buffer_account - .set_state(&UpgradeableLoaderState::Buffer) + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(buffer_address), + }) .unwrap(); buffer_account.data[UpgradeableLoaderState::buffer_data_offset().unwrap()..] .copy_from_slice(&elf); @@ -1771,6 +1927,7 @@ mod tests { #[allow(clippy::type_complexity)] fn get_accounts( + buffer_authority: &Pubkey, programdata_address: &Pubkey, upgrade_authority_address: &Pubkey, slot: u64, @@ -1791,7 +1948,9 @@ mod tests { ); buffer_account .borrow_mut() - .set_state(&UpgradeableLoaderState::Buffer) + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(*buffer_authority), + }) .unwrap(); buffer_account.borrow_mut().data [UpgradeableLoaderState::buffer_data_offset().unwrap()..] @@ -1832,6 +1991,7 @@ mod tests { // Case: Success let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &buffer_address, &programdata_address, &upgrade_authority_address, slot, @@ -1886,6 +2046,7 @@ mod tests { // Case: not upgradable let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &buffer_address, &programdata_address, &upgrade_authority_address, slot, @@ -1902,7 +2063,7 @@ mod tests { }) .unwrap(); assert_eq!( - Err(InstructionError::InvalidArgument), + Err(InstructionError::Immutable), process_instruction( &bpf_loader_upgradeable::id(), &[ @@ -1925,6 +2086,7 @@ mod tests { // Case: wrong authority let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &buffer_address, &programdata_address, &upgrade_authority_address, slot, @@ -1934,7 +2096,7 @@ mod tests { min_programdata_balance, ); assert_eq!( - Err(InstructionError::MissingRequiredSignature), + Err(InstructionError::IncorrectAuthority), process_instruction( &bpf_loader_upgradeable::id(), &[ @@ -1957,6 +2119,7 @@ mod tests { // Case: authority did not sign let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &buffer_address, &programdata_address, &upgrade_authority_address, slot, @@ -1989,6 +2152,7 @@ mod tests { // Case: Program account not executable let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &buffer_address, &programdata_address, &upgrade_authority_address, slot, @@ -2022,6 +2186,7 @@ mod tests { // Case: Program account now owned by loader let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &buffer_address, &programdata_address, &upgrade_authority_address, slot, @@ -2055,6 +2220,7 @@ mod tests { // Case: Program account not initialized let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &buffer_address, &programdata_address, &upgrade_authority_address, slot, @@ -2091,6 +2257,7 @@ mod tests { // Case: ProgramData account not initialized let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &buffer_address, &programdata_address, &upgrade_authority_address, slot, @@ -2127,6 +2294,7 @@ mod tests { // Case: Program ProgramData account mismatch let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &buffer_address, &programdata_address, &upgrade_authority_address, slot, @@ -2159,6 +2327,7 @@ mod tests { // Case: Buffer account not initialized let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &buffer_address, &programdata_address, &upgrade_authority_address, slot, @@ -2172,7 +2341,7 @@ mod tests { .set_state(&UpgradeableLoaderState::Uninitialized) .unwrap(); assert_eq!( - Err(InstructionError::InvalidAccountData), + Err(InstructionError::InvalidArgument), process_instruction( &bpf_loader_upgradeable::id(), &[ @@ -2195,6 +2364,7 @@ mod tests { // Case: Buffer account too big let (_, program_account, programdata_account, spill_account) = get_accounts( + &buffer_address, &programdata_address, &upgrade_authority_address, slot, @@ -2210,7 +2380,9 @@ mod tests { ); buffer_account .borrow_mut() - .set_state(&UpgradeableLoaderState::Buffer) + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(buffer_address), + }) .unwrap(); assert_eq!( Err(InstructionError::AccountDataTooSmall), @@ -2236,6 +2408,7 @@ mod tests { // Case: bad elf data let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &buffer_address, &programdata_address, &upgrade_authority_address, slot, @@ -2391,7 +2564,7 @@ mod tests { }) .unwrap(); assert_eq!( - Err(InstructionError::MissingRequiredSignature), + Err(InstructionError::IncorrectAuthority), process_instruction( &bpf_loader_upgradeable::id(), &[ @@ -2421,7 +2594,7 @@ mod tests { }) .unwrap(); assert_eq!( - Err(InstructionError::InvalidArgument), + Err(InstructionError::Immutable), process_instruction( &bpf_loader_upgradeable::id(), &[ @@ -2462,6 +2635,165 @@ mod tests { ); } + #[test] + fn test_bpf_loader_upgradeable_set_buffer_authority() { + let instruction = bincode::serialize(&UpgradeableLoaderInstruction::SetAuthority).unwrap(); + let authority_address = Pubkey::new_unique(); + let authority_account = Account::new_ref(1, 0, &Pubkey::new_unique()); + let new_authority_address = Pubkey::new_unique(); + let new_authority_account = Account::new_ref(1, 0, &Pubkey::new_unique()); + let buffer_address = Pubkey::new_unique(); + let buffer_account = Account::new_ref( + 1, + UpgradeableLoaderState::buffer_len(0).unwrap(), + &bpf_loader_upgradeable::id(), + ); + + // Case: Set to new authority + buffer_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(authority_address), + }) + .unwrap(); + assert_eq!( + Ok(()), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new_readonly(&authority_address, true, &authority_account), + KeyedAccount::new_readonly( + &new_authority_address, + false, + &new_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + let state: UpgradeableLoaderState = buffer_account.borrow().state().unwrap(); + assert_eq!( + state, + UpgradeableLoaderState::Buffer { + authority_address: Some(new_authority_address), + } + ); + + // Case: Not upgradeable + buffer_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(authority_address), + }) + .unwrap(); + assert_eq!( + Ok(()), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new_readonly(&authority_address, true, &authority_account) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + let state: UpgradeableLoaderState = buffer_account.borrow().state().unwrap(); + assert_eq!( + state, + UpgradeableLoaderState::Buffer { + authority_address: None, + } + ); + + // Case: Authority did not sign + buffer_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(authority_address), + }) + .unwrap(); + assert_eq!( + Err(InstructionError::MissingRequiredSignature), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new_readonly(&authority_address, false, &authority_account), + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: wrong authority + buffer_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: Some(authority_address), + }) + .unwrap(); + assert_eq!( + Err(InstructionError::IncorrectAuthority), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new_readonly(&Pubkey::new_unique(), true, &authority_account), + KeyedAccount::new_readonly( + &new_authority_address, + false, + &new_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: No authority + buffer_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Buffer { + authority_address: None, + }) + .unwrap(); + assert_eq!( + Err(InstructionError::Immutable), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new_readonly(&Pubkey::new_unique(), true, &authority_account), + ], + &bincode::serialize(&UpgradeableLoaderInstruction::SetAuthority).unwrap(), + &mut MockInvokeContext::default() + ) + ); + + // Case: Not a Buffer account + buffer_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Program { + programdata_address: Pubkey::new_unique(), + }) + .unwrap(); + assert_eq!( + Err(InstructionError::InvalidAccountData), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new_readonly(&Pubkey::new_unique(), true, &authority_account), + ], + &bincode::serialize(&UpgradeableLoaderInstruction::SetAuthority).unwrap(), + &mut MockInvokeContext::default() + ) + ); + } + /// fuzzing utility function fn fuzz( bytes: &[u8], diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index c4389500e4..fbed32cb4b 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -92,7 +92,7 @@ pub const SECONDS_PER_YEAR: f64 = 365.25 * 24.0 * 60.0 * 60.0; pub const MAX_LEADER_SCHEDULE_STAKES: Epoch = 5; type BankStatusCache = StatusCache>; -#[frozen_abi(digest = "GSPuprru1pomsgvopKG7XRWiXdqdXJdLPkgJ2arPbkXM")] +#[frozen_abi(digest = "MUmkgPsCRrWL2HEsMEvpkWMis35kbBnaEZtrph5P6bk")] pub type BankSlotDelta = SlotDelta>; type TransactionAccountRefCells = Vec>>; type TransactionAccountDepRefCells = Vec<(Pubkey, RefCell)>; diff --git a/runtime/src/loader_utils.rs b/runtime/src/loader_utils.rs index a2228da5e3..3df206ca96 100644 --- a/runtime/src/loader_utils.rs +++ b/runtime/src/loader_utils.rs @@ -69,6 +69,7 @@ pub fn load_buffer_account( &bpf_loader_upgradeable::create_buffer( &from_keypair.pubkey(), &buffer_pubkey, + Some(&buffer_pubkey), 1.max( bank_client .get_minimum_balance_for_rent_exemption(program.len()) @@ -88,6 +89,7 @@ pub fn load_buffer_account( let message = Message::new( &[bpf_loader_upgradeable::write( &buffer_pubkey, + None, offset, chunk.to_vec(), )], @@ -168,7 +170,7 @@ pub fn set_upgrade_authority( new_authority_pubkey: Option<&Pubkey>, ) { let message = Message::new( - &[bpf_loader_upgradeable::set_authority( + &[bpf_loader_upgradeable::set_upgrade_authority( program_pubkey, ¤t_authority_keypair.pubkey(), new_authority_pubkey, diff --git a/sdk/program/src/bpf_loader_upgradeable.rs b/sdk/program/src/bpf_loader_upgradeable.rs index d1b2f06cba..a30c74f0a6 100644 --- a/sdk/program/src/bpf_loader_upgradeable.rs +++ b/sdk/program/src/bpf_loader_upgradeable.rs @@ -24,7 +24,12 @@ pub enum UpgradeableLoaderState { /// Account is not initialized. Uninitialized, /// A Buffer account. - Buffer, + Buffer { + /// Authority address + authority_address: Option, + // The raw program data follows this serialized structure in the + // account's data. + }, /// An Program account. Program { /// Address of the ProgramData account. @@ -43,9 +48,11 @@ pub enum UpgradeableLoaderState { impl UpgradeableLoaderState { /// Length of an buffer account's data. pub fn buffer_len(program_len: usize) -> Result { - Ok(serialized_size(&Self::Buffer) - .map(|len| len as usize) - .map_err(|_| InstructionError::InvalidInstructionData)? + Ok(serialized_size(&Self::Buffer { + authority_address: Some(Pubkey::default()), + }) + .map(|len| len as usize) + .map_err(|_| InstructionError::InvalidInstructionData)? + program_len) } /// Offset into the ProgramData account's data of the program bits. @@ -80,9 +87,14 @@ impl UpgradeableLoaderState { pub fn create_buffer( payer_address: &Pubkey, buffer_address: &Pubkey, + authority_address: Option<&Pubkey>, lamports: u64, program_len: usize, ) -> Result, InstructionError> { + let mut metas = vec![AccountMeta::new(*buffer_address, false)]; + if let Some(authority_address) = authority_address { + metas.push(AccountMeta::new(*authority_address, false)); + } Ok(vec![ system_instruction::create_account( payer_address, @@ -91,21 +103,29 @@ pub fn create_buffer( UpgradeableLoaderState::buffer_len(program_len)? as u64, &id(), ), - Instruction::new( - id(), - &UpgradeableLoaderInstruction::InitializeBuffer, - vec![AccountMeta::new(*buffer_address, false)], - ), + Instruction::new(id(), &UpgradeableLoaderInstruction::InitializeBuffer, metas), ]) } /// Returns the instructions required to write a chunk of program data to a /// buffer account. -pub fn write(buffer_address: &Pubkey, offset: u32, bytes: Vec) -> Instruction { +pub fn write( + buffer_address: &Pubkey, + authority_address: Option<&Pubkey>, + offset: u32, + bytes: Vec, +) -> Instruction { + let mut metas = vec![ + AccountMeta::new(*buffer_address, false), + AccountMeta::new(*buffer_address, true), + ]; + if let Some(authority_address) = authority_address { + metas[1] = AccountMeta::new(*authority_address, true); + } Instruction::new( id(), &UpgradeableLoaderInstruction::Write { offset, bytes }, - vec![AccountMeta::new(*buffer_address, true)], + metas, ) } @@ -176,8 +196,24 @@ pub fn is_upgrade_instruction(instruction_data: &[u8]) -> bool { 3 == instruction_data[0] } +/// Returns the instructions required to set a buffers's authority. +pub fn set_buffer_authority( + buffer_address: &Pubkey, + current_authority_address: &Pubkey, + new_authority_address: Option<&Pubkey>, +) -> Instruction { + let mut metas = vec![ + AccountMeta::new(*buffer_address, false), + AccountMeta::new_readonly(*current_authority_address, true), + ]; + if let Some(address) = new_authority_address { + metas.push(AccountMeta::new_readonly(*address, false)); + } + Instruction::new(id(), &UpgradeableLoaderInstruction::SetAuthority, metas) +} + /// Returns the instructions required to set a program's authority. -pub fn set_authority( +pub fn set_upgrade_authority( program_address: &Pubkey, current_authority_address: &Pubkey, new_authority_address: Option<&Pubkey>, diff --git a/sdk/program/src/instruction.rs b/sdk/program/src/instruction.rs index 4eb3974e66..bf5db9df4f 100644 --- a/sdk/program/src/instruction.rs +++ b/sdk/program/src/instruction.rs @@ -180,6 +180,12 @@ pub enum InstructionError { #[error("Program failed to compile")] ProgramFailedToCompile, + + #[error("Account is immutable")] + Immutable, + + #[error("Incorrect authority provided")] + IncorrectAuthority, } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] diff --git a/sdk/program/src/loader_upgradeable_instruction.rs b/sdk/program/src/loader_upgradeable_instruction.rs index 35a8d277c8..17d4442d3e 100644 --- a/sdk/program/src/loader_upgradeable_instruction.rs +++ b/sdk/program/src/loader_upgradeable_instruction.rs @@ -16,12 +16,15 @@ pub enum UpgradeableLoaderInstruction { /// /// # Account references /// 0. [writable] source account to initialize. + /// 1. [] Buffer authority, optional, if omitted then the buffer will be + /// immutable. InitializeBuffer, /// Write program data into a Buffer account. /// /// # Account references - /// 0. [writable, signer] Buffer account to write program data to. + /// 0. [writable] Buffer account to write program data to. + /// 1. [signer] Buffer authority Write { /// Offset at which to write the given bytes. offset: u32, @@ -92,11 +95,13 @@ pub enum UpgradeableLoaderInstruction { /// 6. [signer] The program's authority. Upgrade, - /// Set a new authority that is allowed to upgrade the program. To - /// permanently disable program updates omit the new authority. + /// Set a new authority that is allowed to write the buffer or upgrade the + /// program. To permanently make the buffer immutable or disable program + /// updates omit the new authority. /// /// # Account references - /// 0. `[writable]` The ProgramData account to change the authority of. + /// 0. `[writable]` The Buffer or ProgramData account to change the + /// authority of. /// 1. `[signer]` The current authority. /// 2. `[]` The new authority, optional, if omitted then the program will /// not be upgradeable.